Example: Log game data using simple logger. Asm + Lua

Memory scanning, code injection, debugger internals and other gamemodding related discussion
Post Reply
User avatar
koderkrazy
Fearless Donors
Fearless Donors
Posts: 252
Joined: Sun Jun 17, 2018 2:14 pm
Reputation: 165

Example: Log game data using simple logger. Asm + Lua

Post by koderkrazy » Mon Jun 25, 2018 12:39 pm

Hello all I've written primitive Logger to log Combat data in Titan Quest. In production the game doesn't log such data.

Following is the logger code:

[SPOILER="Logger code"]

[CODE][ENABLE]

{$lua}

function getTimeStamp()

return os.date("%d-%m-%Y %X: ")

end



function getLogFile()

-- get relative path or get user input

local ffile = io.open("E:\TQ_combat_logs.txt", "a")

return ffile

end



function myLog(logString)

local str = readString(logString,300)

if not str then

return 1

end

--print(s)

local lb = string.byte(str,-1)

if(lb ~= 10) then

str = str.."
"

end

if (logFile ~= nil) then

logFile:write(getTimeStamp()..str)

logFile:flush()

end

return 0

end



logFile = getLogFile()

if (logFile ~= nil) then

logFile:write(getTimeStamp().."--------------Session start".."
")

end

{$asm}

loadlibrary(luaclient-i386.dll)

luacall(openLuaServer('CELUASERVER'))



CELUA_ServerName:

db 'CELUASERVER',0



//hook to Game::Engine:Log method

aobscanmodule(_loggerInject,Engine.dll,8D 44 24 24 50 8D 4C 24 10 E8 ?? ?? ?? ?? 8b 8F) // should be unique

registersymbol(_loggerInject)

alloc(newmem,$2048)

alloc(_loggerFuncName,256)

alloc(_loggerFuncRef,4)

alloc(_logString, 4)

registersymbol(_logString)



label(code)

label(return)



_loggerFuncName:

db 'myLog',0



newmem:

lea eax,[esp+24]

mov [_logString],eax //retrive pointer to data to log.

push eax

pushad



mov eax,[_loggerFuncRef]

test eax, eax

jne @f

push _loggerFuncName

call CELUA_GetFunctionReferenceFromName

mov [_loggerFuncRef],eax

@@:

push 0 //async

push _logString

push 1 //no of parameters

push [_loggerFuncRef]

call CELUA_ExecuteFunctionByReference //call to myLog method

popad



code:

jmp return



_loggerInject:

jmp newmem

return:





[DISABLE]

{$Lua}

if (logFile ~= nil) then

logFile:write(getTimeStamp().."--------------Session End".."
")

logFile:close()

end

{$Asm}



_loggerInject:

db 8D 44 24 24 50



unregistersymbol(_loggerInject)

dealloc(newmem)



dealloc(_loggerFuncName)

dealloc(_loggerFuncRef)

dealloc(_logString)

unregistersymbol(_logString)[/CODE]

[/SPOILER]



Following are the logs from the game:

[SPOILER="Log data"]

[CODE]1006/25/18 16:38:27: combat

1006/25/18 16:38:27: [

1006/25/18 16:38:27: attackerName = Records/XPack/Creatures/PC/MalePC01.dbr

1006/25/18 16:38:27: attackerID = 25964

1006/25/18 16:38:27: defenderName = RecordsCreatureMonsterSatyrAM_Peltast_04.dbr

1006/25/18 16:38:27: defenderID = 66640

1006/25/18 16:38:27: combatType = Retaliation Attack

1006/25/18 16:38:27: Total Damage: Absolute (97.983086), Over Time (0.000000)

4106/25/18 16:38:27: ^y 97.983086 of Fire(6) Damage to Defender 0x10450

1006/25/18 16:38:27: ^bRacial Bonus Damage: (0.000000)

4106/25/18 16:38:27: criticalStrike = 1.000000

1006/25/18 16:38:27: regionHit = Torso

1006/25/18 16:38:27:

1006/25/18 16:38:27: ][/CODE]

[/SPOILER]



Let me know any improvements/suggestions.

User avatar
SunBeam
Administration
Administration
Posts: 1713
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 349

Example: Log game data using simple logger. Asm + Lua

Post by SunBeam » Mon Jun 25, 2018 12:48 pm

I have one. Use this instead, it's faster and no ASM is required :p



[code]

{$STRICT}



{$lua}



if syntaxcheck then return end



[ENABLE]



function debugger_onBreakpoint()

local _rdx = RDX

local _isInList = 0

if _rdx == 0x1 then

local _string = RAX

local _size = R11

local temp = readString( _string, _size * 2, true )

local filter = { "Continue", "RESUME", "Quick Save", "Quick Load", "Save Game", "Load Game", "Options", "Tutorials", "Exit" }

for _,v in pairs(filter) do

if string.match( v, temp ) then

_isInList = 1

break

end

end

if _isInList == 0 then

print( string.format( "%s", temp ) )

print( "- - - - - - - - - - - - -" )

end

_isInList = 0

end

debug_continueFromBreakpoint( co_run )

return 1

end



addr = getAddress( "PreyDll.dll+1089EF37" )

debugProcess()

debug_setBreakpoint( addr )



[DISABLE]



debugger_onBreakpoint = nil

debug_removeBreakpoint( addr )[/code]





This is a piece of code I used in Prey to dump strings at a certain debug location ("PreyDll.dll+1089EF37").



What script does it to get the raw address from symbols (PreyDll.dll + offset), then set a breakpoint there. CE's default [I]debugger_onBreakpoint()[/I] function is modified in such a way to meet my requirements: run code whenever RDX == 1; when that happens, string size+1 is stored in R11; UNICODE string itself is in RAX; I then just read it up from [RAX], size times 2 (cuz each character in UNICODE is a WORD); the [B]true[/B] parameter tells [I]readString[/I] it's an UNICODE read.



Then I created an array (a list actually) of "hashes" whereas if the read string is equal to any of them, a flag is set to 1. Reason for this is the last [B]if..end[/B], whereas if [I]_isInList[/I] is 0 (thus not in the list), Lua will output the prints :). Then I just force _isInList to be 0 for next iterations/breaks (although not necessary, I think). But just in case :p



Execution is resumed after hit and execution of code via [I]debug_continueFromBreakpoint( co_run )[/I] + [I]return 1[/I].



If you want to restore CE's default breakpointing behavior, just disable script (it will set [I]debugger_onBreakpoint() [/I]to[B] nil[/B]; CE has a check for this and will fallback to the default behavior).



Am sure it could've been done simpler, but works for this quick write-up.



Read/watch this: [URL]https://fearlessrevolution.com/threads/prey-single-player-updated-table.7280/#post-50179[/URL]



BR,

Sun
Last edited by SunBeam on Mon Jun 25, 2018 12:59 pm, edited 4 times in total.

User avatar
koderkrazy
Fearless Donors
Fearless Donors
Posts: 252
Joined: Sun Jun 17, 2018 2:14 pm
Reputation: 165

Example: Log game data using simple logger. Asm + Lua

Post by koderkrazy » Mon Jun 25, 2018 1:34 pm

[QUOTE="SunBeam, post: 50214, member: 12587"]

I have one. Use this instead, it's faster and no ASM is required :p

[/QUOTE]



Thanks! Overriding breakpoint function is a clever trick! ?



A few questions about your approach:

1. How many break points can we set at a time? I mean while setting manually in CE it gives warning, after first 4 breakpoints, like 'Debug registers are used up. want to use software break points..'.

2. You've used RDX==1 condition to determine that this is for logging purpose. If I set such multiple breakpoints for different purposes in my table, is there reliable way to determine(in debugger_onBreakpoint function) for which location this break happen?

3. You said it is faster. how?

4. You said no asm is required. so is it better/preferred to write entire table in Lua only? why?
Last edited by koderkrazy on Thu Jan 01, 1970 12:00 am, edited 2 times in total.

User avatar
SunBeam
Administration
Administration
Posts: 1713
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 349

Example: Log game data using simple logger. Asm + Lua

Post by SunBeam » Mon Jun 25, 2018 2:43 pm

[B]1.[/B] Hardware, 4. Software, unlimited.



[B]2.[/B] That's just my condition; you can use any other logic, depending on your goal and location; make use of registers or the pointers they point to ;) [I]readInteger[/I], [I]readQword[/I], etc. work as well. (e.g.: "cmp [rax+20],0" can be done as "if readInteger(RAX + 0x20) == 0x0 then .. end"). If you plan on using 4 different hardware breakpoints, you can use RIP as the checker in your global function (if RIP == first_break then .. elseif RIP == second_break then .. else if etc.) ;)



[B]3.[/B] You don't have to write a shit ton of ASM and wrappers to call-in Lua functions from ASM :P It's not faster from the perspective of executing Lua vs. ASM (ASM is always the fastest way).



[B]4.[/B] No ASM is required in terms of writing your whole code as ASM. CE's Lua has an interpreter that in the end converts and executes the Lua code as ASM :) So it's still ASM, but you pump it as Lua: Lua -> converted to ASM -> executed (given the conversions, you can guess why Lua is slower; not by much).



BR,

Sun

Post Reply

Who is online

Users browsing this forum: No registered users