Example for defining a custom value type in CE
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
