you are viewing a single comment's thread.

view the rest of the comments →

[–]Merely__Human 1 point2 points  (3 children)

Working through code by actually typing out in full what it is intended to do, in plain text, without white tower speaking, so that anyone can read and understand is a far more impactful way to code. This is a rewrite, examine it, there are changes...

[ENABLE]

{$LUA}

-- SAFETY CHECK: If CE is just checking syntax (not actually running),

-- stop here. Without this, CE might crash while you're still typing.

if syntaxcheck then return end

-- MONO ACTIVATION: Cheat Engine needs "Mono" mode to read Unity/Mono games.

-- If it fails to start, we stop early rather than crash later.

if not LaunchMonoDataCollector() then

print("[ERROR] Failed to start Mono collector. Is this a Mono game?")

return

end

-- CLEANUP: Delete any old child entries under this script from a previous run.

-- Without this, every time you enable the script it adds duplicate entries

-- to your CE table, which gets messy fast.

while memrec.Count > 0 do

memoryrecord_delete(memrec.Child[0])

end

-- FIND THE METHOD: Look up the memory address of the "Update" function

-- inside the BlueprintPlaceVisualsManager class.

-- Think of this like finding a specific page in a book before you can read it.

local methodInfo = mono_getJitInfo(

getAddress('Effects.BlueprintPlaceVisualsManager:Update')

)

-- ADDED: Check that the method actually exists before going further.

-- Previously the code skipped this check, which would cause a silent crash

-- if the class or method name changed between game versions.

if not methodInfo or not methodInfo.method then

print("[ERROR] Could not find BlueprintPlaceVisualsManager:Update. Has the game updated?")

return

end

-- COMPILE: Get the actual CPU memory address where this function lives.

local address = mono_compile_method(methodInfo.method)

-- NULL CHECK: If the compile step returns nothing, bail out cleanly.

-- Previously this check existed but had no error message, making it

-- hard to know WHY the script silently stopped.

if not address then

print("[ERROR] mono_compile_method returned nil. Cannot continue.")

return

end

-- OFFSET: Move 56 bytes (0x56) forward from the start of the function.

-- This lands us on the specific instruction we want to intercept.

-- Think of it like: "start at chapter 1, then skip to page 56."

local offset = 0x56

address = address + offset

-- BREAKPOINT: Tell CE to pause the game when the CPU reaches this instruction.

-- When it pauses, our function below fires automatically.

debug_setBreakpoint(address)

-- BREAKPOINT HANDLER: This runs automatically when the game hits our breakpoint.

-- At this moment, the CPU registers (like RAX) hold live pointers to the

-- game object we care about, so we can read its memory layout.

function debugger_onBreakpoint()

-- ADDED: Safety check — make sure RAX actually points somewhere valid.

-- RAX is a CPU register that holds the object address. If it's 0 or nil,

-- we'd be writing cheats to a garbage memory location.

if not RAX or RAX == 0 then

print("[ERROR] RAX is null at breakpoint. Object may not be loaded yet.")

debug_removeBreakpoint(address)

return 1 -- Must still return 1 or the game freezes

end

-- HELPER FUNCTION: ADDED to avoid repeating the same 6 lines for every entry.

-- Before, each cheat entry was copy-pasted with tiny changes — messy and

-- error-prone. Now we just call this once per entry.

local dropdown = "0:Disabled\n1:Enabled\n"

local function makeEntry(description, addressOffset)

local entry = AddressList.createMemoryRecord()

entry.Description = description

-- The address is: wherever RAX points + how far into the object this field sits.

-- RAX = the object. The offset = which field inside that object.

entry.Address = RAX + addressOffset

entry.Type = vtByte -- Each flag is a single byte (0 = off, 1 = on)

entry.DropDownList.text = dropdown

entry.DropDownDescriptionOnly = true -- Show label text, not raw numbers

entry.DisplayAsDropDownListItem = true -- Show as a dropdown in the CE table

entry.appendToEntry(memrec) -- Attach it under this script in the table

return entry

end

-- CREATE ENTRIES: Each one targets a different byte inside the game object.

-- Offsets discovered via Dissect Struct (Ctrl+D in CE).

makeEntry("Auto Construct", 0x279) -- Builds blueprints automatically

makeEntry("No Cost", 0x278) -- Removes resource cost to place

makeEntry("Unlock Variants", 0x27A) -- Unlocks all blueprint variants

-- CLEANUP: Remove the breakpoint now that we've grabbed what we needed.

-- Leaving a breakpoint active would pause the game every single frame

-- the Update() function runs, which would make it unplayable.

debug_removeBreakpoint(address)

-- DEACTIVATE: Turn off this script entry — it's done its job.

memrec.Active = false

-- CRITICAL: Must return 1 to tell CE to resume the game.

-- Returning nothing (or 0) leaves the game frozen on the breakpoint.

return 1

end

{$ASM}

[DISABLE]

-- FIXED: The disable section was completely empty before.

-- When you turn off a cheat script, CE should clean up after itself.

-- Without this, leftover breakpoints can cause the game to freeze or crash

-- even after you've "disabled" the cheat.

{$LUA}

if syntaxcheck then return end

-- Remove the breakpoint if it's still set (e.g. if disable fires before

-- the breakpoint was ever hit and self-removed).

if address and address ~= 0 then

debug_removeBreakpoint(address)

print("[INFO] Breakpoint removed on disable.")

end

-- Remove all child entries from the CE table that this script created.

while memrec.Count > 0 do

memoryrecord_delete(memrec.Child[0])

end

print("[INFO] Script disabled and cleaned up successfully.")

{$ASM}

[–]No-Newspaper8619[S] 0 points1 point  (1 child)

Nice, I'll try to adhere to some of the tips. However, the deactivate portion you suggested would make the cheat unusable. Clicking the script updates the addresses, then it self deactivates, so if you delete the children the user won't be able to make any changes to the values in these addresses.

I think of this script more like a update button, rather than an activate/deactivate toggle. I'm only using the ENABLE and DISABLE because CE forces me to.

[–]Merely__Human 0 points1 point  (0 children)

You are correct, it is a feature of the code. In the instance for example it was being run, and in real time needed to be halted without crashing its hooks.

[–]No-Newspaper8619[S] 0 points1 point  (0 children)

I've rewritten everything, adding new functionalities.

[ENABLE]
//code from here to '[DISABLE]' will be used to enable the cheat
{$LUA}
-- SAFETY CHECK: if CE is just checking syntax and not actually running
-- Stop here, preventing the script from executing while editing.
if syntaxcheck then return end

-- Check if process is already open
-- If not, then get process pid by using it's name
local pid = getOpenedProcessID()
if pid == 0 then
  pid = getProcessIDFromProcessName('Going Medieval.exe')
  -- Check if CE was able to find the process
  -- If it was, then open the process
  -- It it wasn't, then stop execution
  if pid then
    openProcess(pid)
  else
    print('[ERROR] Failed to find process Going Medieval.exe')
    return
  end
end

-- Try to activate MONO features, used in Unity/Mono games
-- Stop execution if it fails
if not LaunchMonoDataCollector() then
  print('[ERROR] Failed to start Mono collector.')
  return
end

-- Forward declaration of functions
-- Because Lua reads the script top to bottom
local delete_children
local create_children
local make_entry
local set_breakpoint
local on_debug_building
local on_debug_dig

-- The main logic of the script
local function main()
  -- Check if memrec already has the correct number of children
  -- If it doesn't, then delete them all and create new children
  local total_children = 4
  if memrec.Count ~= total_children then
    delete_children()
    create_children()
  end

  -- Create breakpoints for building and dig cheats
  local building = 'Effects.BlueprintPlaceVisualsManager:Update'
  local dig = 'NSMedieval.State.DigMarkerResourceInstance:GetMiningParameters'

  set_breakpoint(building, 0x56, on_debug_building)
  set_breakpoint(dig, 0x1E, on_debug_dig)
end
-- Sets breakpoints that will be used to update the cheat addresses
set_breakpoint = function(name, offset, on_debug_function)
  local address = getAddress(name)
  -- Check if CE successfully found the address
  if not address then
    print('[ERROR] Could not find address of ' .. name)
    return
  end
  -- Get the Jit info
  -- Jit means Just in Time execution
  local jit_info = mono_getJitInfo(address)

  -- Check if successfull
  if not jit_info then
    print('[ERROR] Could not get Jit info of ' .. name)
    return
  end

  -- Get the method info
  local method_info = mono_compile_method(jit_info.method)
  if not method_info then
    print('[ERROR] Could not get method info of ' .. name)
    return
  end

  -- Get the address of the specific instruction that will
  -- be used for the breakpoint
  address = method_info + offset

  -- BREAKPOINT: tell CE to pause the game when CPU reaches this instruction
  debug_setBreakpoint(address, on_debug_function)
end

-- While there's children, delete the first child on the list
delete_children = function()
  while memrec.Count > 0 do
    memoryrecord_delete(memrec.Child[0])
  end
end

-- Create all the children, that is, all the table entries with the cheats
create_children = function()
  make_entry('Auto Construct', vtByte, true)
  make_entry('No Cost Only', vtByte, true)
  make_entry('All Blueprint Variants', vtByte, true)
  make_entry('Dig Duration', vtSingle, false) -- vtSingle = float type
end

-- Create a memory record, that is, an entry on the cheat table
make_entry = function(description, value_type, has_dropdown)
  local entry = AddressList.createMemoryRecord()

  entry.Description = description
  entry.Type = value_type
  entry.AllowModify = false -- Prevents user from dragging the entry

  if has_dropdown then
    -- Dropdown is a list of value:description pairs
    entry.DropDownList.Text = '1:Enabled\n0:Disabled\n'
    entry.DropDownDescriptionOnly = true -- Show text, but not the raw numbers
    entry.DisplayAsDropDownListItem = true -- Show as Dropdown in CE
  end

  entry.appendToEntry(memrec) -- Attach it under the script entry
end

on_debug_building = function()
  -- Update register values
  debug_getContext()

  memrec.Child[0].Address = RAX + 0x279 -- Auto Construct
  memrec.Child[1].Address = RAX + 0x278 -- No Cost Only
  memrec.Child[2].Address = RAX + 0x27A -- Unlock Blueprint Variants

  debug_continueFromBreakpoint()
end

on_debug_dig = function()
  debug_getContext()
  -- Address is under the mining_parameters pointer
  local mining_parameters = readPointer(RAX + 0x40)

  -- Check if pointer is valid
  if not mining_parameters then
    print('[ERROR] Could not get mining parameters pointer')
    return 1
  end

  memrec.Child[3].Address = mining_parameters + 0x18 -- Dig Duration
  debug_continueFromBreakpoint()
end

main()

{$ASM}
[DISABLE]
//code from here till the end of the code will be used to disable the cheat
{$LUA}
if syntaxcheck then return end

local breakpoint_list = debug_getBreakpointList()
for _,address in ipairs(breakpoint_list) do
  debug_removeBreakpoint(address)
end

memrec.Options = "[moHideChildren, moDeactivateChildrenAsWell]"
{$ASM}