--[[ Feature: Batch create pointer base + offset sub‑entries (GUI configuration) Filename: pointer_batch_injector.lua Version: 1.0.5 Updates: Added input validation (non‑empty address, step != 0, loop limit), menu duplicate prevention, robustness improvements ]] -- Global flag to prevent duplicate menu creation if not _G.pointerBatchInjectorMenuCreated then _G.pointerBatchInjectorMenuCreated = false end -- Extract parseHex to outer scope to avoid redefinition local function parseHex(str) if type(str) ~= 'string' then return 0 end str = str:gsub('^0x', ''):gsub('%s+', '') if str == '' then return 0 end return tonumber(str, 16) or 0 end local function showConfigDialog() -- Ensure main form exists local mainForm = getMainForm() if not mainForm then showMessage('Error: Cannot get CE main form, script may have loaded too early') return end local frm = createForm() frm.Caption = 'Batch Create Pointer and Offset Entries' frm.Width = 700 frm.Height = 800 frm.Position = poScreenCenter frm.Scaled = false frm.Visible = false local x = 15 local y = 15 local gapV = 10 local labelH = 22 local editH = 26 local fullW = frm.Width - 40 local function addLabelEdit(caption, defaultText, width) local lbl = createLabel(frm) lbl.Parent = frm lbl:setPosition(x, y) lbl:setSize(width, labelH) lbl.Caption = caption local edt = createEdit(frm) edt.Parent = frm edt:setPosition(x, y + labelH + gapV) edt:setSize(width, editH) edt.Text = defaultText or '' y = y + labelH + gapV + editH + gapV return edt end local function addLabelCombo(caption, items, defaultIndex, width) local lbl = createLabel(frm) lbl.Parent = frm lbl:setPosition(x, y) lbl:setSize(width, labelH) lbl.Caption = caption local cb = createComboBox(frm) cb.Parent = frm cb:setPosition(x, y + labelH + gapV) cb:setSize(width, editH) for _, item in ipairs(items) do cb.Items.Add(item) end cb.ItemIndex = defaultIndex or 0 y = y + labelH + gapV + editH + gapV return cb end -- 1. Address description local edtBaseDesc = addLabelEdit('Address description:', 'Deliver all items', fullW) -- 2. Pointer mode local lblMode = createLabel(frm) lblMode.Parent = frm lblMode:setPosition(x, y) lblMode:setSize(200, labelH) lblMode.Caption = 'Pointer mode:' local rgMode = createRadioGroup(frm) rgMode.Parent = frm rgMode:setPosition(x, y + labelH + gapV) rgMode:setSize(450, 100) rgMode.Caption = '' rgMode.Items.Add('Full pointer path (including all offsets)') rgMode.Items.Add('Base address + offset list (comma separated)') rgMode.ItemIndex = 0 y = y + labelH + gapV + 100 + gapV -- 3. Mode 0: address expression local lblExpr = createLabel(frm) lblExpr.Parent = frm lblExpr:setPosition(x, y) lblExpr:setSize(fullW, labelH) lblExpr.Caption = 'Full pointer path (can be left empty, used as child):' local edtExpr = createEdit(frm) edtExpr.Parent = frm edtExpr:setPosition(x, y + labelH + gapV) edtExpr:setSize(fullW, editH) edtExpr.Text = '"[[[[["MesenCore.dll"+046D6D00]+0]+28]+10]+98]+0"' edtExpr.Hint = 'Leave empty to create the entry as a sub‑entry under another pointer path by dragging' edtExpr.ShowHint = true y = y + labelH + gapV + editH + gapV -- 4. Mode 1: base address local lblBaseAddr = createLabel(frm) lblBaseAddr.Parent = frm lblBaseAddr:setPosition(x, y) lblBaseAddr:setSize(fullW, labelH) lblBaseAddr.Caption = 'Base address:' local edtBaseAddr = createEdit(frm) edtBaseAddr.Parent = frm edtBaseAddr:setPosition(x, y + labelH + gapV) edtBaseAddr:setSize(fullW, editH) edtBaseAddr.Text = '"MesenCore.dll"+046D6D00' y = y + labelH + gapV + editH + gapV lblBaseAddr.Visible = false edtBaseAddr.Visible = false -- 5. Offset list local lblOffsets = createLabel(frm) lblOffsets.Parent = frm lblOffsets:setPosition(x, y) lblOffsets:setSize(400, labelH) lblOffsets.Caption = 'Offset list (comma separated, hex supported):' local edtOffsets = createEdit(frm) edtOffsets.Parent = frm edtOffsets:setPosition(x, y + labelH + gapV) edtOffsets:setSize(fullW, editH) edtOffsets.Text = '0, 0x28, 0x10, 0x98, 0' y = y + labelH + gapV + editH + gapV if rgMode.ItemIndex == 0 then lblOffsets.Visible = false edtOffsets.Visible = false end -- 6. Starting offset local edtStartOffset = addLabelEdit('Starting offset (hex):', '0x14DB01B', fullW) -- 7. Loop count local edtCount = addLabelEdit('Loop count (decimal):', '203', fullW) -- 8. Step local edtStep = addLabelEdit('Step (hex):', '0x2', fullW) -- 9. Value type local types = { {name = 'Byte', val = vtByte}, {name = '2 Bytes', val = vtWord}, {name = '4 Bytes', val = vtDword}, {name = '8 Bytes', val = vtQword}, {name = 'Float', val = vtSingle}, {name = 'Double', val = vtDouble}, {name = 'String', val = vtString}, } local typeNames = {} for _, t in ipairs(types) do typeNames[#typeNames + 1] = t.name end local cbType = addLabelCombo('Value type:', typeNames, 0, fullW) local typeMap = types -- Buttons y = y + 15 local btnOK = createButton(frm) btnOK.Parent = frm btnOK:setPosition(frm.Width - 220, y) btnOK:setSize(80, 32) btnOK.Caption = 'Create' btnOK.ModalResult = mrOK local btnCancel = createButton(frm) btnCancel.Parent = frm btnCancel:setPosition(frm.Width - 120, y) btnCancel:setSize(80, 32) btnCancel.Caption = 'Cancel' btnCancel.ModalResult = mrCancel -- Mode switch rgMode.OnClick = function() local mode = rgMode.ItemIndex if mode == 0 then lblExpr.Visible = true edtExpr.Visible = true lblBaseAddr.Visible = false edtBaseAddr.Visible = false lblOffsets.Visible = false edtOffsets.Visible = false else lblExpr.Visible = false edtExpr.Visible = false lblBaseAddr.Visible = true edtBaseAddr.Visible = true lblOffsets.Visible = true edtOffsets.Visible = true end end rgMode.OnClick() -- Create button logic (with validation) btnOK.OnClick = function() local success, err = pcall(function() local desc = edtBaseDesc.Text local mode = rgMode.ItemIndex local startOffsetStr = edtStartOffset.Text local countStr = edtCount.Text local stepStr = edtStep.Text local typeIndex = cbType.ItemIndex local varType = typeMap[typeIndex + 1].val local startOffset = parseHex(startOffsetStr) local count = tonumber(countStr) or 0 local step = parseHex(stepStr) -- Input validation if count <= 0 then showMessage('Loop count must be a positive integer') return end if count > 10000 then showMessage('Loop count exceeds limit (10000), please reduce it') return end if step == 0 then showMessage('Step cannot be 0, otherwise sub‑entries will have identical offsets') return end local al = getAddressList() if not al then showMessage('Cannot get address list, please ensure a process is opened') return end local base = al.createMemoryRecord() base.Description = desc if mode == 0 then local addr = edtExpr.Text if addr == '' then showMessage('Full pointer path cannot be empty (unless you intend to create an empty base, but that won\'t generate valid sub‑entries)') return end base.Address = addr base.OffsetCount = 0 else local baseAddr = edtBaseAddr.Text if baseAddr == '' then showMessage('Base address cannot be empty') return end base.Address = baseAddr local offsetStr = edtOffsets.Text local offsets = {} for token in offsetStr:gmatch('[^,]+') do local val = parseHex(token:gsub(' ', '')) table.insert(offsets, val) end if #offsets == 0 then showMessage('Offset list cannot be empty') return end base.OffsetCount = #offsets for i, off in ipairs(offsets) do base.Offset[i - 1] = off end end -- Batch create sub‑entries local currentOffset = startOffset for i = 1, count do local rec = al.createMemoryRecord() rec.Type = varType rec.Description = string.format('+%X', currentOffset) rec.Address = string.format('+%X', currentOffset) rec.appendToEntry(base) currentOffset = currentOffset + step end showMessage(string.format('Successfully created %d sub‑entries', count)) frm.ModalResult = mrOK end) if not success then showMessage("Execution error, please check the Lua engine output.\nError message: " .. tostring(err)) end end frm.showModal() frm.destroy() end local function createMenu() local mainForm = getMainForm() if not mainForm then print('[PointerBatchInjector] Cannot get main form, menu creation failed') return end -- Duplicate prevention: skip if already created if _G.pointerBatchInjectorMenuCreated then return end local mainMenu = mainForm.Menu if not mainMenu then print('[PointerBatchInjector] Main menu does not exist') return end local myMenu = mainMenu.Items if not myMenu then print('[PointerBatchInjector] Menu item collection does not exist') return end -- Check if "Script Tools" menu already exists local topItem = nil for i = 0, myMenu.Count - 1 do if myMenu[i].Caption == 'Script Tools' then topItem = myMenu[i] break end end if not topItem then topItem = createMenuItem(mainMenu) topItem.Caption = 'Script Tools' myMenu.Add(topItem) end -- Check if sub‑menu already exists local subExists = false if topItem then for j = 0, topItem.Count - 1 do if topItem[j].Caption == 'Batch Create Pointer+Offset Entries' then subExists = true break end end end if not subExists then local subItem = createMenuItem(topItem) subItem.Caption = 'Batch Create Pointer+Offset Entries' subItem.OnClick = function() showConfigDialog() end topItem.Add(subItem) end _G.pointerBatchInjectorMenuCreated = true end -- Execute menu creation createMenu()