--[[ Launcher_Generator.lua (v4.5) Features: Generate PowerShell launcher script (UTF‑8 with BOM) New: Added process wait timeout mechanism (60 seconds), auto‑exit with prompt on timeout. Other features same as v4.4. ]] local DEBUG = false local MENU_CAPTION = "Generate Launcher Script" local TOP_MENU_CAPTION = "Script Tools" local function logDebug(...) if DEBUG then print("[LauncherGen-DBG] ", ...) end end -- Find Cheat Engine executable local function findCheatEngineExe() local ceDir = getCheatEngineDir():gsub("\\$", "") local candidates = { ceDir .. "\\cheatengine.exe", ceDir .. "\\CheatEngine.exe", ceDir .. "\\cheatengine-x86_64.exe", ceDir .. "\\CheatEngine-x86_64.exe", ceDir .. "\\cheatengine-i386.exe", ceDir .. "\\CheatEngine-i386.exe", ceDir .. "\\Cheat Engine.exe", ceDir .. "\\cheat engine.exe", } for _, path in ipairs(candidates) do if fileExists(path) then return path end end return nil end -- Get available desktop path (by actual write test) local function getDesktopPath() local userProfile = os.getenv("USERPROFILE") if userProfile then local desktop = userProfile .. "\\Desktop" local f, err = io.open(desktop .. "\\_test.tmp", "w") if f then f:close() os.remove(desktop .. "\\_test.tmp") return desktop end desktop = userProfile .. "\\桌面" f, err = io.open(desktop .. "\\_test.tmp", "w") if f then f:close() os.remove(desktop .. "\\_test.tmp") return desktop end end local temp = os.getenv("TEMP") if temp then print("[LauncherGen] Warning: Desktop not found, saving script to temp directory: " .. temp) return temp end return getCheatEngineDir() end -- Get raw process name (only remove extension, keep all characters) local function getRawProcessName(path) local name = path:match("([^\\/]+)$") or "Game" name = name:gsub("%.[^.]*$", "") -- remove extension if name == "" then name = "Game" end return name end -- Get safe filename (only letters, numbers, underscores for .ps1 file) local function getSafeFileName(path) local name = getRawProcessName(path) name = name:gsub("[^%w_]", "_") if #name > 40 then name = name:sub(1, 40) end if name == "" then name = "Game" end return name end -- PowerShell single‑quote string escaping (replace single quote with two single quotes) local function escapePowerShellString(str) if not str then return "" end return str:gsub("'", "''") end -- GUI window local function createGUI() local frm = createForm() frm.Caption = "Generate Launcher Script v4.5" frm.Width = 1390 frm.Height = 460 frm.Position = poScreenCenter frm.BorderStyle = bsDialog frm.OnClose = function() return caFree end local y = 10 local labelWidth = 350 local paramLabelWidth = 450 local editWidth = 750 local btnWidth = 160 local btnHeight = 30 -- Game path local lblGame = createLabel(frm) lblGame.Caption = "Game Executable:" lblGame.Left = 10 lblGame.Top = y lblGame.Width = labelWidth local edtGame = createEdit(frm) edtGame.Left = 10 edtGame.Top = y + 25 edtGame.Width = editWidth edtGame.ReadOnly = true local btnGame = createButton(frm) btnGame.Caption = "Browse..." btnGame.Left = editWidth + 20 btnGame.Top = y + 23 btnGame.Width = btnWidth btnGame.Height = btnHeight btnGame.OnClick = function() local dlg = createOpenDialog(frm) dlg.Filter = "Executable (*.exe)|*.exe|All Files (*.*)|*.*" dlg.Options = "ofFileMustExist, ofPathMustExist" if dlg.Execute() then edtGame.Text = dlg.FileName end dlg.destroy() end y = y + 65 -- Game command line arguments local lblParams = createLabel(frm) lblParams.Caption = "Command Line Arguments (optional):" lblParams.Left = 10 lblParams.Top = y lblParams.Width = paramLabelWidth local edtParams = createEdit(frm) edtParams.Left = 10 edtParams.Top = y + 25 edtParams.Width = editWidth + 100 edtParams.Text = "" y = y + 60 -- CT table path local lblCT = createLabel(frm) lblCT.Caption = "Target CT Table File:" lblCT.Left = 10 lblCT.Top = y lblCT.Width = labelWidth local edtCT = createEdit(frm) edtCT.Left = 10 edtCT.Top = y + 25 edtCT.Width = editWidth edtCT.ReadOnly = true local btnCT = createButton(frm) btnCT.Caption = "Browse..." btnCT.Left = editWidth + 20 btnCT.Top = y + 23 btnCT.Width = btnWidth btnCT.Height = btnHeight btnCT.OnClick = function() local dlg = createOpenDialog(frm) dlg.Filter = "Cheat Engine Table (*.ct)|*.ct|All Files (*.*)|*.*" dlg.Options = "ofFileMustExist, ofPathMustExist" if dlg.Execute() then edtCT.Text = dlg.FileName end dlg.destroy() end y = y + 65 -- Wait option local chkWait = createCheckBox(frm) chkWait.Caption = "Wait for game process before launching CE" chkWait.Left = 10 chkWait.Top = y chkWait.Width = 600 chkWait.Checked = true y = y + 40 -- Pause option local chkPause = createCheckBox(frm) chkPause.Caption = "Pause after execution (to view error messages)" chkPause.Left = 10 chkPause.Top = y chkPause.Width = 600 chkPause.Checked = false y = y + 40 -- Generate button local btnGenerate = createButton(frm) btnGenerate.Caption = "Generate Launcher Script (Desktop)" btnGenerate.Left = 350 btnGenerate.Top = y btnGenerate.Width = 480 btnGenerate.Height = 40 btnGenerate.OnClick = function() local game = edtGame.Text local ct = edtCT.Text if game == "" or ct == "" then showMessage("Please fill in both game and CT table paths") return end if not fileExists(game) then showMessage("Game file does not exist") return end if not fileExists(ct) then showMessage("CT table file does not exist") return end local ceExe = findCheatEngineExe() if not ceExe then showMessage("Could not find Cheat Engine executable, please check installation") return end local procName = getRawProcessName(game) local safeProcName = getSafeFileName(game) local args = edtParams.Text local wait = chkWait.Checked local pause = chkPause.Checked local escGame = escapePowerShellString(game) local escCt = escapePowerShellString(ct) local escCe = escapePowerShellString(ceExe) local escArgs = escapePowerShellString(args) local escProc = escapePowerShellString(procName) local waitStr = wait and "$true" or "$false" -- Generate PowerShell script (with timeout logic) local psScript = [[ # Launcher script (generated by Cheat Engine Launcher Generator) $ErrorActionPreference = "Stop" try { $game = ']] .. escGame .. [[' # Game path $ct = ']] .. escCt .. [[' # CT table path $ce = ']] .. escCe .. [[' # Cheat Engine path $proc = ']] .. escProc .. [[' # Process name (original, for waiting) $gameArgs = ']] .. escArgs .. [[' # Game arguments (escaped) $wait = ]] .. waitStr .. [[ # Whether to wait for the process $timeoutSeconds = 60 # Wait timeout (seconds) # Launch the game if ($gameArgs -ne '') { Start-Process -FilePath $game -ArgumentList $gameArgs } else { Start-Process -FilePath $game } # Whether to wait for the process (with timeout) if ($wait) { Write-Host "Waiting for process $proc to appear (timeout $timeoutSeconds seconds)..." $sw = [System.Diagnostics.Stopwatch]::StartNew() while (-not (Get-Process -Name $proc -ErrorAction SilentlyContinue)) { if ($sw.Elapsed.TotalSeconds -gt $timeoutSeconds) { Write-Host "Timeout waiting for process $proc. Please check if the game has started." -ForegroundColor Red exit 1 } Start-Sleep -Milliseconds 200 } $sw.Stop() } # Launch CE and automatically load the CT table (wrap path in double quotes to support spaces) Start-Process -FilePath $ce -ArgumentList "`"$ct`"" Write-Host "`n✅ All operations completed." -ForegroundColor Green } catch { Write-Host "`n❌ An error occurred:" -ForegroundColor Red Write-Host $_.Exception.Message -ForegroundColor Red Write-Host "`nPlease check that the following paths exist and are correct:" -ForegroundColor Yellow Write-Host " Game: $game" Write-Host " CT: $ct" Write-Host " CE: $ce" } ]] .. (pause and [[ Write-Host "`nPress Enter to exit..." Read-Host ]] or "") local desktop = getDesktopPath() local fileName = "Launch_" .. safeProcName .. ".ps1" local filePath = desktop .. "\\" .. fileName local f, err = io.open(filePath, "wb") if not f then showMessage("❌ Unable to create file:\n" .. filePath .. "\n\nError: " .. (err or "unknown reason")) return end f:write(string.char(0xEF, 0xBB, 0xBF)) f:write(psScript) f:close() local msg = "✅ Launcher script generated:\n" .. filePath .. "\n\n📌 Usage:\n" .. " Right‑click on the file → “Run with PowerShell”\n\n" .. "(Double‑clicking will open it in Notepad by default due to Windows security policy)\n\n" .. "Process wait timeout is set to 60 seconds, auto‑exit on timeout." local choice = messageDialog(msg, mtConfirmation, mbYes, mbNo) if choice == mrYes then os.execute('explorer /select,"' .. filePath .. '"') end end y = y + 60 -- Status information: using read‑only Edit controls (no border, background matches form) local edtStatus1 = createEdit(frm) edtStatus1.Left = 10 edtStatus1.Top = y edtStatus1.Width = frm.Width - 30 edtStatus1.Height = 30 edtStatus1.ReadOnly = true edtStatus1.BorderStyle = bsNone edtStatus1.Text = "Generated file will be saved to the desktop (or to the temp directory if desktop is unavailable)." local edtStatus2 = createEdit(frm) edtStatus2.Left = 10 edtStatus2.Top = y + 30 edtStatus2.Width = frm.Width - 30 edtStatus2.Height = 30 edtStatus2.ReadOnly = true edtStatus2.BorderStyle = bsNone edtStatus2.Text = "Recommended: Right‑click → “Run with PowerShell”." return frm end -- Menu registration local function addMenuEntry() local mainMenu = getMainForm().Menu if not mainMenu then print("[LauncherGen] Error: Failed to get main menu") return end local items = mainMenu.Items if not items then return end local topMenu = nil for i = 0, items.Count - 1 do local item = items.Item[i] if item and item.Caption and item.Caption == TOP_MENU_CAPTION then topMenu = item break end end if not topMenu then topMenu = createMenuItem(items) topMenu.Caption = TOP_MENU_CAPTION items.add(topMenu) end for i = 0, topMenu.Count - 1 do local sub = topMenu.Item[i] if sub and sub.Caption and sub.Caption == MENU_CAPTION then return end end local menuItem = createMenuItem(topMenu) menuItem.Caption = MENU_CAPTION menuItem.OnClick = function() local frm = createGUI() frm.show() end topMenu.add(menuItem) end addMenuEntry()