Cheat Engine Forum Index Cheat Engine
The Official Site of Cheat Engine
 
 FAQFAQ   SearchSearch   MemberlistMemberlist   UsergroupsUsergroups   RegisterRegister 
 ProfileProfile   Log in to check your private messagesLog in to check your private messages   Log inLog in 


PS1 - Xebra (Digimon World) - Reused ASM Instructions?
Goto page 1, 2  Next
 
Post new topic   Reply to topic    Cheat Engine Forum Index -> General Gamehacking
View previous topic :: View next topic  
Author Message
Majin
Advanced Cheater
Reputation: 0

Joined: 04 May 2016
Posts: 87

PostPosted: Fri Dec 09, 2016 8:41 am    Post subject: PS1 - Xebra (Digimon World) - Reused ASM Instructions? Reply with quote

'Eya lads, been a while.

I was faffing about in the good ol' Digimon World game, and oh lordy does it need some polishing; a couple of tedious mechanics really kills the fun.

The one I started with, was the "battle counter". When the player wins a fight, it is incremented by one through these instructions:

Code:
mov ecx,[XEBRA.EXE+986C0]
mov [eax+edx],cx


In CheatEngine, the codelist shows ECX as the battle counter. No biggie, so I just chuck in inc ECX after the first mov. Doesn't do a thing, and the battle counter is still the same. Tried changing it to inc cx, which instead causes the game to flip out; characters bending over, the UI being offset a bunch, sound corrupting, etc.

The oddest part, is that those exact two mov instructions are called everywhere. Playtime increased by one in-game day? Those two exactly the same instructions are called. Character's digimon ages by one day? Made a care mistake? Virus meter? Yep, they are all incremented by just those two instructions.

I'm thinking that this haberdashery is caused by some JIT (whatever that is) optimization by the emulator, but that puts me in a bind. How the hell do I then make cheats? I've read about this thing called Gameshark, but that's a piece of hardware for the actual PS1, far as I can tell.

If anyone else wants to see what's what, I've uploaded the table to Mediafire, as well as the rom and it's hash. Maybe I'm just messing up someplace?
Back to top
View user's profile Send private message
mgr.inz.Player
I post too much
Reputation: 223

Joined: 07 Nov 2008
Posts: 4438
Location: W kraju nad Wisla. UTC+01:00

PostPosted: Fri Dec 09, 2016 11:01 am    Post subject: Re: PS1 - Xebra (Digimon World) - Reused ASM Instructions? Reply with quote

Majin wrote:
The oddest part, is that those exact two mov instructions are called everywhere.

This is perfectly normal for all emulators. Starting with C64 and ZX Spectrum emulator, ending on PS2, GameCube and Wii emulator .

_________________
Back to top
View user's profile Send private message MSN Messenger
Majin
Advanced Cheater
Reputation: 0

Joined: 04 May 2016
Posts: 87

PostPosted: Fri Dec 09, 2016 1:38 pm    Post subject: Re: PS1 - Xebra (Digimon World) - Reused ASM Instructions? Reply with quote

mgr.inz.Player wrote:
Majin wrote:
The oddest part, is that those exact two mov instructions are called everywhere.

This is perfectly normal for all emulators. Starting with C64 and ZX Spectrum emulator, ending on PS2, GameCube and Wii emulator .


That makes it impossible to get the address of something by hooking an instruction. And since the game is being run through an emulator, I doubt there's going to be any static addresses; tried doing a pointer scan on the battle counter, and I was given a few .ptr files with absolutely nothing in them.

I guess the avenue of approach is to somehow run a PS1 equivalent of CE. Gameshark Pro seems to fit the bill, but that's a piece of hardware for an actual PS1.
Back to top
View user's profile Send private message
++METHOS
I post too much
Reputation: 92

Joined: 29 Oct 2010
Posts: 4196

PostPosted: Fri Dec 09, 2016 2:07 pm    Post subject: Reply with quote

Typically, with emulators, you'll see instructions like this that are handling most things:

Code:
mov [eax+edx],cx


Usually, a reliable identifier is being stored in eax or edx in the above example. Hooking is actually your best bet, usually, in such cases...but you'll need to find proper identifiers to filter out what you want to manipulate while letting all other values remain untouched. Complete the last step of the CE tutorial that covers data structure dissection to see an example of what I am talking about. Other methods used for finding a reliable ID for filtering purposes:

++METHOS wrote:
  • You can use a pointer address for your filter, inside of your script, for the value that you are trying to manipulate.
  • You can use pointer trees inside of the data structure to find something viable.
  • You can shift the data structure (+ or -) and/or expand its size to find something useful.
  • You can use the structure spider to find workable strings and/or for comparative analysis.
  • You can check the register values by attaching the debugger or setting a breakpoint to see if something can be used for your filter.
  • You can check to see if there are any instructions that are exclusive to the address/value that you are trying to manipulate and store the address for your filter by creating a second injection point.
  • You can check to see if there are any instructions that are exclusive to any other address/value inside of the data structure for the address/value that you are trying to manipulate and store the address for your filter by creating a second injection point.
  • You can analyze assembly code to see if an identifier is being checked or assigned somewhere.
  • Et al.
Back to top
View user's profile Send private message
Majin
Advanced Cheater
Reputation: 0

Joined: 04 May 2016
Posts: 87

PostPosted: Fri Dec 09, 2016 6:36 pm    Post subject: Reply with quote

God damn. I spent the whole day trying to find some sort of way to tell what's what, finally found it and you had it right in yer post; EAX is the way to tell what's being increased. At least I managed to pull together a structure for the player's digimon stats.

I had the idea of checking the instructions between two different save files: one end-game, the other a fresh new game. EAX has the same value for each "type" of increase. It's the same even when the emulator has been closed and started up again.

Offense: EAX=0016C830
Defense: EAX=0016C832
In-game time (Minute hand): EAX=0013C088
Battle Counter: EAX=0013FA7C

The EAX values for offense and defense are exactly what you'd expect to find in the structure; the stats in the game are two bytes in size, with defense coming right after. The structure looks as follows:

Code:

0000 - Start of structure = 255
0001 - [[Blank]] = 0
0002 - Offense
0004 - Defense
0006 - Speed
0008 - Brains
0012 - Max HP
0014 - Max MP
0016 - Current HP
0018 - Current MP


There are a few bytes in-between all that, and four bytes at the end, but those have no obvious value, and are different between my two save games. I'm guessing they're to do with the digimon's creature type, voice, animation style, 'n' such and so.

ESP and EIP are the same across the varying calls of the instructions, savegames and instances of Xebra.

Now, on the assumption that EAX is an absolutely unique identifier, I'd wager I should just do something along the lines of:

Code:

[ENABLE]

aobscanmodule(incBTTL,XEBRA.EXE,66 89 0C 10 8B 0D C0 86 49 00) // should be unique
alloc(newmem,$1000)

label(Mod_IncrementBattle)
label(IfNot_Battle_Then)
label(EndOfMod)

newmem:

Mod_IncrementBattle:
  //
  cmp eax,0013FA7C //Battle Counter offset
  jne IfNot_Battle_Then
  inc cx
  //
  IfNot_Battle_Then:
  mov ecx,[XEBRA.EXE+986C0]
  mov [eax+edx],cx
  jmp EndOfMod

incBTTL:
  jmp Mod_IncrementBattle
  nop
  nop
  nop
  nop
  nop
EndOfMod:
registersymbol(incBTTL)

[DISABLE]

incBTTL:
  db 66 89 0C 10 8B 0D C0 86 49 00

unregistersymbol(incBTTL)
dealloc(newmem)


I'll give this a whirl, see if things don't crash instantly.

On a different note, why are we unable to attach CE's .csx structure files? I want to share this stuff.

Edit: Nope, the game didn't like that one bit, no sir. Froze instantly.

Edit2: And for some reason, this worked:

Code:

[ENABLE]

aobscanmodule(INJECT,XEBRA.EXE,66 89 0C 10 8B 0D C0 86 49 00) // should be unique
alloc(newmem,$1000)

label(code)
label(return)

newmem:

code:
  cmp eax,0013FA7C //Check to see if we're modifying the battle counter
  jne cmpSkip //If not, skip the injected memory-modifying stuff
  inc cx //Since it's the battle counter, cx is 1 by default. Inc to make it 2
  mov [eax+edx],cx //Assign it
  jmp incDone //Since we already assigned it, don't assign it again
  cmpSkip: //Since we're not touching the battle counter, run the vanilla code
  mov [eax+edx],cx
  incDone:
  mov ecx,[XEBRA.EXE+986C0]
  jmp return

INJECT:
  jmp code
  nop
  nop
  nop
  nop
  nop
return:
registersymbol(INJECT)

[DISABLE]

INJECT:
  db 66 89 0C 10 8B 0D C0 86 49 00

unregistersymbol(INJECT)
dealloc(newmem)


Dunno why this is the one that works, but eh. It does. Score.

Now I'm going to see if there's a way to calculate the correct offsets for the addresses. First avenue of approach is to hook an instruction that runs on an address every second, get that address and compare it to the same address from a previous Xebra process.

If the new address is "larger", we end up with a positive offset. Smaller? A negative offset. The same? Don't need one.

Trick is to learn how to add and remove entries in the CE address list via an auto assemble script. Last time I looked at that, I couldn't find anything on the subject.
Back to top
View user's profile Send private message
mgr.inz.Player
I post too much
Reputation: 223

Joined: 07 Nov 2008
Posts: 4438
Location: W kraju nad Wisla. UTC+01:00

PostPosted: Sat Dec 10, 2016 6:41 am    Post subject: Reply with quote

ECX and CX are not independent from each other. CX is a part of ECX



Quote:
inc cx
mov ecx,[XEBRA.EXE+986C0]



The change you made with "inc cx" is discarded by "mov ecx,[XEBRA.EXE+986C0]"



Basically:
mov ECX,12345678

will set CX to 5678

_________________
Back to top
View user's profile Send private message MSN Messenger
Majin
Advanced Cheater
Reputation: 0

Joined: 04 May 2016
Posts: 87

PostPosted: Sat Dec 10, 2016 7:05 am    Post subject: Reply with quote

mgr.inz.Player wrote:
ECX and CX are not independent from each other. CX is a part of ECX

Quote:
inc cx
mov ecx,[XEBRA.EXE+986C0]


The change you made with "inc cx" is discarded by "mov ecx,[XEBRA.EXE+986C0]"

Basically:
mov ECX,12345678

will set CX to 5678


Damn, I thought the registers were independent of each other. Guess it makes it easier to split up a wee part of a structure.


My new issue, is that I'm trying to update an address in the cheat table through an auto assemble script. The problem is that I need to chuck the resulting address of eax+edx into the address list entry; I need to push and pop another register, as the following is invalid:

Code:

mov [MinuteHand], [eax+edx]


From what I've been told through google, you cannot chuck a memory address into another memory address. You must have at least one register being used in a mov and lea instructions.

The solution, supposedly, is to temporarily free up another register, do some stuff with it, then revert it back to how it was originally, through the use of the push and pop instructions respectively. However, this doesn't work, for both Xebra and Dark Souls; they instantly crash when I used those instructions.

Current Xebra script (crashes Xebra as soon as it runs):

Code:

[ENABLE]

aobscanmodule(Inject_SetUpAddresses,XEBRA.EXE,66 89 0C 10 8B 0D C0 86 49 00) // should be unique
alloc(newmem,2048)

registersymbol(MinuteHand)
alloc(MinuteHand,2)

label(UpdateTableAddresses)
label(VanillaCode)
label(return)

newmem:

code:

  //Begin injected code
  UpdateTableAddresses:

  cmp eax,0013C088 //Minutehand (minutes, between 0-60)
  jne short VanillaCode

  //begin Assigning
  AssignMinutesAddress:
  push ebx
  mov ebx,[eax+edx]
  mov [MinuteHand], ebx
  pop ebx
  jmp short VanillaCode
  //end

  //End


  VanillaCode:
  //Vanilla code:
  mov [eax+edx],cx
  mov ecx,[XEBRA.EXE+986C0]
  jmp return

Inject_SetUpAddresses:
  jmp UpdateTableAddresses:
  nop
  nop
  nop
  nop
  nop
return:
registersymbol(Inject_SetUpAddresses)

[DISABLE]

Inject_SetUpAddresses:
  db 66 89 0C 10 8B 0D C0 86 49 00

unregistersymbol(Inject_SetUpAddresses)
unregistersymbol(MinuteHand)
dealloc(MinuteHand)
dealloc(newmem)


To clarify, the address I'm wanting contains a word value. Maybe I should push/pop BX, instead of EBX?

But aye, no idea how to go about this, if I can't use any registers.
Back to top
View user's profile Send private message
mgr.inz.Player
I post too much
Reputation: 223

Joined: 07 Nov 2008
Posts: 4438
Location: W kraju nad Wisla. UTC+01:00

PostPosted: Sat Dec 10, 2016 7:27 am    Post subject: Reply with quote

Code:
Offense: EAX=0016C830
Defense: EAX=0016C832
In-game time (Minute hand): EAX=0013C088
Battle Counter: EAX=0013FA7C


EAX changes and EDX is the same? If yes, EDX points at the start of allocated memory for emulated game. You can use that.

You have to get start address of this game allocated memory. Maybe you can find a pointer.

Try this:
- write down EDX value
- add new address to the table, whatever type, address field must be the EDX value
- do a pointer scan for this address, max level 1 (if nothing found, max level 2, ...)

If you find a valid pointer, you can use 13C088 as an offset.

_________________
Back to top
View user's profile Send private message MSN Messenger
Majin
Advanced Cheater
Reputation: 0

Joined: 04 May 2016
Posts: 87

PostPosted: Sat Dec 10, 2016 8:25 am    Post subject: Reply with quote

mgr.inz.Player wrote:
Code:
Offense: EAX=0016C830
Defense: EAX=0016C832
In-game time (Minute hand): EAX=0013C088
Battle Counter: EAX=0013FA7C


So, EAX changes and EDX is the same. If yes, EDX points at the start of allocated memory for emulated game. You can use that.

You have to get start address of this game allocated memory. Maybe you can find a pointer.

Try this:
- write down EDX value
- add new address to the table, whatever type, address field must be the EDX value
- do a pointer scan for this address, max level 1 (if nothing found, max level 2, ...)

If you find a valid pointer, you can use 13C088 as an offset.


EAX is the same for each specific piece of data. The offense stat for the player's digimon is always at 0016C830+EDX, with EDX being what changes between instances of the program.

I've been trying to make a script that writes a new entry to the table's address list, but I've had no luck. Should be super simple, but...

Here's what I've done:

1. Added new address to the table, with the description "BaseAddress". Address is 0.
2. Open Auto Assembler
3. Insert this script:
Code:

[ENABLE]

aobscanmodule(GetBaseAddress,XEBRA.EXE,66 89 0C 10 8B 0D C0 86 49 00) // should be unique
alloc(newmem,$1000)

registersymbol(BaseAddress)
globalalloc(BaseAddress,2)

label(code)
label(return)

newmem:

code:
  mov [BaseAddress],EDX
   //begin vanilla code
  mov [eax+edx],cx
  mov ecx,[XEBRA.EXE+986C0]
  jmp return
   //end

GetBaseAddress:
  jmp code
  nop
  nop
  nop
  nop
  nop
return:
registersymbol(GetBaseAddress)

[DISABLE]

GetBaseAddress:
  db 66 89 0C 10 8B 0D C0 86 49 00

unregistersymbol(GetBaseAddress)
unregistersymbol(BaseAddress)
dealloc(BaseAddress)
dealloc(newmem)


4. File -> Assign to current cheat table

But CE doesn't like that. Throws me the following error:

Quote:

BaseAddress was supposed to be added to the symbollist, but it isn't declared.



I'll go and give the pointer sanner a whirl right the now.


Edit: You're a glorious bastard. Did a pointer scan on the address at EDX @ level 1. The second result in the list (had around 30 entries) was bang on. The pointer is:

"XEBRA.EXE"+0009B8D0

Now, rather than hooking instructions, I want to just add me own code, in order to avoid buggering with registers that are in use; push and pop are too crashy. Is that possible at all?

For example, rather than intercept the instructions that run on the battle counter address, I want to wait for it to be written to, and when it's modified, run some code. That could be a pipe dream, but if it's possible...Oh boy oh boy.
Back to top
View user's profile Send private message
mgr.inz.Player
I post too much
Reputation: 223

Joined: 07 Nov 2008
Posts: 4438
Location: W kraju nad Wisla. UTC+01:00

PostPosted: Sat Dec 10, 2016 8:44 am    Post subject: Reply with quote

if you are still interested in hooking instructions, here:
Code:
[ENABLE]
aobscanmodule(aob_getBaseAddress,xebra.exe,66 89 0C 10 8B 0D C0 86 49 00)
registersymbol(aob_getBaseAddress)
alloc(newmem_getBaseAddress,1024,xebra.exe)
label(return_getBaseAddress)

label(BaseAddress)
registersymbol(BaseAddress)

newmem_getBaseAddress:
  mov [BaseAddress],edx
  mov [eax+edx],cx
  mov ecx,[xebra.exe+986C0]
  jmp return_getBaseAddress

BaseAddress:
dd 0

aob_getBaseAddress:
  jmp newmem_getBaseAddress
  nop
  nop
  nop
  nop
  nop
return_getBaseAddress:
[DISABLE]
aob_getBaseAddress:
  db 66 89 0C 10 8B 0D C0 86 49 00

unregistersymbol(aob_getBaseAddress)
dealloc(newmem_getBaseAddress)



emulator xebra.CT
 Description:

Download
 Filename:  emulator xebra.CT
 Filesize:  1.89 KB
 Downloaded:  806 Time(s)


_________________
Back to top
View user's profile Send private message MSN Messenger
Majin
Advanced Cheater
Reputation: 0

Joined: 04 May 2016
Posts: 87

PostPosted: Sat Dec 10, 2016 9:05 am    Post subject: Reply with quote

Aaah, so you have to call label() first, and then registersymbol(). Gotcha. Now, is there a way to make the script disable itself? So it goes like so:

Enable script -> Output new base address -> Auto-disables, returns code back to vanilla

Not absolutely necessary, but would be pretty nice to have.
Back to top
View user's profile Send private message
mgr.inz.Player
I post too much
Reputation: 223

Joined: 07 Nov 2008
Posts: 4438
Location: W kraju nad Wisla. UTC+01:00

PostPosted: Sat Dec 10, 2016 10:07 am    Post subject: Reply with quote

Quote:
Now, is there a way to make the script disable itself

You will have to use Lua scripting for this.


For
Quote:
I want to wait for it to be written to, and when it's modified, run some code. That could be a pipe dream, but if it's possible...Oh boy oh boy.

I recommend choosing other hack point. Just downloaded recent XEBRA emulator.
I have full picture now:
Code:
XEBRA.EXE+453B0 - 8B 15 D0B84900        - mov edx,[XEBRA.EXE+9B8D0]
XEBRA.EXE+453B6 - 89 D8                 - mov eax,ebx
XEBRA.EXE+453B8 - 25 FEFF1F00           - and eax,001FFFFE
XEBRA.EXE+453BD - 66 89 0C 10           - mov [eax+edx],cx
XEBRA.EXE+453C1 - 8B 0D C0864900        - mov ecx,[XEBRA.EXE+986C0]
XEBRA.EXE+453C7 - C1 E8 02              - shr eax,02
XEBRA.EXE+453CA - 8B 34 81              - mov esi,[ecx+eax*4]
XEBRA.EXE+453CD - 85 F6                 - test esi,esi


What we have here? "XEBRA.EXE+9B8D0" - You didn't have to even launch pointer scanner Very Happy

Instead of injecting at XEBRA.EXE+453BD we will use XEBRA.EXE+453B8
That way we can take off "mov ecx,[XEBRA.EXE+986C0]" from AA script, so it should be more "future XEBRA update" proof.

and eax,001FFFFE - this one ensures that address is even. 200000(hex) = 2097152(dec) = 2M
As we know, PSX has only 2MB of RAM - link

Also I see we can use ESI register, because it is overwritten at XEBRA.EXE+453CA



The script
Code:
[ENABLE]
aobscanmodule(aob_updateGameMemory,XEBRA.EXE,25 FE FF 1F 00)
registersymbol(aob_updateGameMemory)
alloc(newmem_updateGameMemory,1024,XEBRA.EXE)
label(return_updateGameMemory)
label(epilogue_updateGameMemory)

newmem_updateGameMemory:

  and eax,001FFFFE // original instruction

  // at this point
  // EDX points to the beginning of emulated game memory
  // EAX is an address of game memory (used as an offset here, in emulator code)
  // CX keeps value to write into [EAX+EDX]  (emulated game memory)
  // ESI is free to use


  //example one (change current HP to max HP)
  cmp eax,0016C844  // is current HP to be written to?
  jne @f            // if not jump to next label (@@ in this case)
  //get max HP value
  mov esi,0016C840  // max HP address
  mov cx,[edx+esi]  // cx now contains max HP value

@@:
  //example two (change current MP to max MP)
  cmp eax,0016C846  // is current MP to be written to?
  jne @f            // if not jump to next label (epilogue_updateGameMemory in this case)
  //get max MP value
  mov esi,0016C842  // max MP address
  mov cx,[edx+esi]  // cx now contains max MP value

epilogue_updateGameMemory:
  jmp return_updateGameMemory

aob_updateGameMemory:
  jmp newmem_updateGameMemory
return_updateGameMemory:

[DISABLE]
aob_updateGameMemory:
  db 25 FE FF 1F 00

unregistersymbol(aob_updateGameMemory)
dealloc(newmem_updateGameMemory)




Also attached CT file



XEBRA.CT
 Description:

Download
 Filename:  XEBRA.CT
 Filesize:  5.32 KB
 Downloaded:  863 Time(s)


_________________
Back to top
View user's profile Send private message MSN Messenger
Majin
Advanced Cheater
Reputation: 0

Joined: 04 May 2016
Posts: 87

PostPosted: Sat Dec 10, 2016 11:54 am    Post subject: Reply with quote

Lua's a right pain in the arse, I've no idea why CE uses that instead of Pascal, specially since the whole thing was made in Pascal to begin with. Ptooey.

I've actually made a tiny wee bit of progress. I did some reading a day or two ago, and it turns out that you can convert Gameshark codes real easy. For example:

Quote:

Digivolution
8013BFE0 00??


The first set of bytes is the address, the second set is the value. To convert the address, we subtract Xebra's address from Gameshark's. We do this by converting something we already have. For example, the offense address for my current run of Xebra is:

031DC830

Gameshark: 8016C830

Subtract Xebra from GS, and I get 7CF90000. This is the current modifier; subtract that from any GS address for Digimon World EU, and we just converted it.

And to make it into a proper cheat, why, all we have to do is take the resultant address, subtract EDX's address from it, and we get the offset. Add this offset to the pointer, et voila.

I'll share me current table. Chuffed about converting the digivolution code.

Next step from here, is to figure out the structure for enemies. The end goal of all this, is to give major buffs to them. Hell, maybe even add/change the placed enemies entirely, but that's waaaaay too pro.



Xebra WIP.CT
 Description:

Download
 Filename:  Xebra WIP.CT
 Filesize:  8.19 KB
 Downloaded:  808 Time(s)

Back to top
View user's profile Send private message
mgr.inz.Player
I post too much
Reputation: 223

Joined: 07 Nov 2008
Posts: 4438
Location: W kraju nad Wisla. UTC+01:00

PostPosted: Sat Dec 10, 2016 12:08 pm    Post subject: Reply with quote

Yes, converting GameShark code is easy.
Topics about it are here on forum.


EDIT:
Majin wrote:
Lua's a right pain in the arse, I've no idea why CE uses that instead of Pascal, specially since the whole thing was made in Pascal to begin with. Ptooey.

Actually, Lua is pretty useful. We can automate many things. For example you can add custom type:
Code:
typename="XEBRA 16bit" --shown as the typename in ce
bytecount=2  --number of bytes of this type
functionbasename="XEBRA16bit"

function XEBRA16bit_bytestovalue(b1,b2,address)
  return readInteger(address+readInteger('XEBRA.EXE+9B8D0')) & 0xffff
end

function XEBRA16bit_valuetobytes(i,address)
  local a = address+readInteger('XEBRA.EXE+9B8D0')
  writeBytes(a, i & 0xff, (i>>8) & 0xff)
end
return typename,bytecount,functionbasename


To add this type rightclick on "value type combo box" and choose "define new custom type (Lua)". Paste above script.
From now on you can use "XEBRA 16bit" type and use addresses directly in CE address list.

_________________
Back to top
View user's profile Send private message MSN Messenger
Majin
Advanced Cheater
Reputation: 0

Joined: 04 May 2016
Posts: 87

PostPosted: Sat Dec 10, 2016 12:53 pm    Post subject: Reply with quote

I meant in terms of the language itself. It's around ten times slower than Java, and the syntax can be a bit funky. Guess I'm just rallying against something I don't know, but man, Pascal would have been a good fit. Especially since it directly supports assembly; put the code between an "asm begin end" block, and away ya go.

Curiously, does that type have any practical use, or is it just for demonstration purposes?


On another note, I just learned that the stats for loaded NPCs are located right after the player's. Most of them are unloaded after moving to an adjacent area that also has NPCs.

Wonder if I can just hex search the game's files for a set of NPC data (yank it from the structure). If I can do that, hell, we are gettin' some place good.
Back to top
View user's profile Send private message
Display posts from previous:   
Post new topic   Reply to topic    Cheat Engine Forum Index -> General Gamehacking All times are GMT - 6 Hours
Goto page 1, 2  Next
Page 1 of 2

 
Jump to:  
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Powered by phpBB © 2001, 2005 phpBB Group

CE Wiki   IRC (#CEF)   Twitter
Third party websites