Some games may utilize custom data types that are not included in the standard definitions. (In this context, standard types include integer, float, double, word, byte, etc. which are built-in types in CE.)
This is a brief article demonstrating how to define a custom variable type. The article showcases the usage of the registerCustomTypeAutoAssembler() function to register Auto Assembler routines.
Definition of custom type labels (Note: this is not an exhaustive list of definitions, these are read by CE):
TypeName: The custom type name displayed in CE ByteSize: Specifies the size of the custom type in bytes (32-bit) UsesFloat: Indicates whether it should be treated as a float in CE (0 = No, 1 = Yes) ConvertRoutine: A subroutine defining how to convert it (from CE to memory) ConvertBackRoutine: A subroutine defining how to convert it back (from memory to CE) registerCustomTypeAutoAssembler: This Lua script contains the main block of AA code for conversion. Section: [64-bit] [/64-bit]: Indicates a 64-bit conversion routine [32-bit] [/32-bit]: Indicates a 32-bit conversion routine
I've only tested this in 64-bit games and haven't used the [64-bit][/64-bit] sections.
For 64-bit:
For ConvertRoutine: At this point, ecx/rcx contains the address where the bytes are stored. The return value is stored in eax/rax.
For ConvertBackRoutine: At this point, edx/rdx contains the address to write the value to (you need to write the value to this address), and ecx/rdx contains the source value.
For 32-bit routines: (Again, I haven't used them)
[32-bit] ConvertRoutine: push ebp mov ebp,esp //[ebp+8]=input . . . pop ebp ret 4 [/32-bit] ConvertBackRoutine: [32-bit] push ebp mov ebp,esp //[ebp+8]=input //[ebp+c]=address of output pop ebp ret 8 [/32-bit]
Example: Civilization VI
In Civ 6, most values are stored as real values * 256 in memory. When the value is needed for display on the screen, the game will divide it by 256 (maybe; I have not traced it):
Values stored in memory: display value * 256
Values displayed on screen: memory value / 256
The following script utilizes XMM registers to perform the conversion:
Code: Select all
[ENABLE]
{$lua}
if _civ6_customFloat == nil then
registerCustomTypeAutoAssembler([[
alloc(TypeName,256)
alloc(ByteSize,8)
alloc(ConvertRoutine,1024)
alloc(ConvertBackRoutine,1024)
alloc(UsesFloat,1)
TypeName:
db 'Civ6 Float',0
ByteSize:
dd 4
UsesFloat:
db 1
ConvertRoutine:
//at this point rcx contains the address where the bytes are stored
xor rax,rax
mov eax, dword ptr [rcx]
cvtsi2ss xmm15, eax
mov eax, (float)256
movd xmm14, eax
vdivss xmm15, xmm15, xmm14
movd eax, xmm15
ret
ConvertBackRoutine:
//at this point rdx contains the address to write the value to
//and rcx contains the value
push rax
mov eax, ecx
movd xmm15, eax
mov eax, (float)256
movd xmm14, eax
vmulss xmm15, xmm15, xmm14
vcvtss2si eax, xmm15
mov dword ptr [rdx], eax
pop rax
ret
]])
_civ6_customFloat = true
end
[DISABLE]
{$asm}
Notice:
In CE 7.6, there is a bug: when registerCustomTypeAutoAssembler executed, process & $process global variable will changed to CE itself.
Workaround example:
Record pid and restore it:
_G.processID = getOpenedProcessID() registerCustomTypeAutoAssembler([[..... . . if getOpenedProcessID() ~= G.processID then openProcess(G.processID) end
of force rewrite
_G.process2 = process registerCustomTypeAutoAssembler([[..... . . process = _G.process2 autoAssemble(string.format([[ unregistersymbol($process) define($process, "%s") registersymbol($process) ]], process))
Spoiler

