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
Expert Cheater
Expert Cheater
Posts: 254
Joined: Sun Jun 17, 2018 2:14 pm
Reputation: 190

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

Post by koderkrazy »

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:
Logger code

Code: Select all

[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)


Following are the logs from the game:
Log data

Code: Select all

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: ]


Let me know any improvements/suggestions.

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

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

Post by SunBeam »

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


Code: Select all


{$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 )




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 debugger_onBreakpoint() 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 true parameter tells readString 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 if..end, whereas if _isInList 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 debug_continueFromBreakpoint( co_run ) + return 1.



If you want to restore CE's default breakpointing behavior, just disable script (it will set debugger_onBreakpoint() to nil; 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: threads/prey-single-player-updated-tabl ... post-50179



BR,

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

User avatar
koderkrazy
Expert Cheater
Expert Cheater
Posts: 254
Joined: Sun Jun 17, 2018 2:14 pm
Reputation: 190

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

Post by koderkrazy »

SunBeam, post: 50214, member: 12587 wrote:
I have one. Use this instead, it's faster and no ASM is required :p


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: 4765
Joined: Sun Feb 04, 2018 7:16 pm
Reputation: 4403

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

Post by SunBeam »

1. Hardware, 4. Software, unlimited.



2. 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 ;) readInteger, readQword, 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.) ;)



3. 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).



4. 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