Skarma
May 7th, 2009, 01:19 AM
Edit: I have decided that detouring original functions to mine is illogical and just using these functions as helper functions will be efficient enough. Imitating engine functions can be very powerful. I will leave all this up for reference though.
I'm positive there are only a select few that can help me out here, since this seems to be a little more advanced programming. I think KMan was going to help, but I have not gotten a PM back since I sent him code a few weeks ago. :confused2:
I'm working on reversing Halo engine functions from assembly back into C/C++ to create some useful functions for an SDK and to better understand the engine and how it works. All memory addresses and functions are from Halo PC 1.08, NOT CE.
There are 2 functions I am working on currently, with a billion previous I gave up on after crashing the game so many times! :D The first one is a function that initializes a DataHeader instance by clearing it to all 0's, before it is filled with actual data. A DataHeader looks like this:
struct DataHeader
{
char Name[32];
unsigned short Max; // Max number of <things> possible
unsigned short Size; // Size of each <thing> class instance
bool IsValid;
bool IdentifierZeroInvalid;
short Padding;
unsigned long Signature; // d@t@
short NextIndex;
short LastIndex;
ident Next; // the next <thing> to be initialized
void* First; // Pointer to the first <thing> class instance
};The first function assembly looks like this in OllyDbg, with my commentation:
004D0810 MOVSX ECX,WORD PTR DS:[EDX+22] Move DataHeader->Size into ECX
004D0814 PUSH EBX New Local Variable EBX
004D0815 MOV EBX,ECX Move DataHeader->Size[ECX] into EBX
004D0817 PUSH EDI New Local Variable EDI
004D0818 SHR ECX,2
004D081B XOR EAX,EAX Clear EAX to 0
004D081D MOV EDI,ESI Move Instance into EDI
004D081F REP STOS DWORD PTR ES:[EDI] Fill Instance[EDI] with 0's[EAX]
004D0821 MOV ECX,EBX Move DataHeader->Size[EBX] into ECX
004D0823 AND ECX,3
004D0826 REP STOS BYTE PTR ES:[EDI] .. Um ok..
004D0828 MOV AX,WORD PTR DS:[EDX+32] Move DataHeader->Next.ID into AX
004D082C MOV WORD PTR DS:[ESI],AX Move DataHeader->Next.ID into Instance
004D082F INC WORD PTR DS:[EDX+32] Increase DataHeader->Next.ID
004D0833 CMP WORD PTR DS:[EDX+32],0 Compare DataHeader->Next.ID to 0
004D0838 POP EDI
004D0839 POP EBX
004D083A JNZ SHORT halo.004D0842 If DataHeader->Next.ID is 0...
004D083C MOV WORD PTR DS:[EDX+32],8000 set it to 0x8000
004D0842 RETN Return the ID of the initialized instance[AX]I also am using IDA HexRays disassembler as a crutch. It does a pretty good job, but there is a lot of garbage. Here is what it spit out:
//----- (004D0810) --------------------------------------------------------
__int16 __usercall sub_4D0810<ax>(int a1<edx>, void *a2<esi>)
{
int v2; // ecx@1
int v3; // edi@1
__int16 result; // ax@4
__int16 v5; // bx@1
unsigned int v6; // ecx@1
v5 = *(_WORD *)(a1 + 34);
v6 = (unsigned int)*(_WORD *)(a1 + 34) >> 2;
memset(a2, 0, 4 * v6);
v3 = (int)((char *)a2 + 4 * v6);
v2 = v5 & 3;
while ( v2 )
{
*(_BYTE *)v3++ = 0;
--v2;
}
result = *(_WORD *)(a1 + 50);
*(_WORD *)a2 = result;
++*(_WORD *)(a1 + 50);
if ( !*(_WORD *)(a1 + 50) )
*(_WORD *)(a1 + 50) = -32768;
return result;
}Assembly is really difficult to reverse back into original source code, since when it is compiled and depending on the compiler, it optimizes the code and makes it a pain in the butt for us reverse engineers! :) I have written my own version of this function in C++, which seems to be exactly what the function is accomplishing in every task, but it still crashes when I detour the original function to mine. I think the reason is, my function is using the wrong registers through the parameters. I have debugged my previous attempts and this seems to always be the problem. Here is my version:
short __fastcall InitDataInstance(DataHeader* dHeader, void* Instance)
{
memset(Instance, 0, dHeader->Size);
*(short*)Instance = dHeader->Next.ID;
dHeader->Next.ID+=1;
if(!dHeader->Next.ID)
dHeader->Next.ID = -32768;
return *(short*)Instance;
}I have already made the assumption that the calling convention is not a fastcall because fastcalls always use ECX and EDX as the first 2 parameters, which is not the case here, UNLESS there is another parameter before EDX, a this pointer as the ECX register, which would make this a member function(thiscall). I'm not sure if this is the case though, although it could be. ECX is never pushed on the stack in this function or in the calling function. I guess it might be a cdecl convention, since ESI is pushed on the stack last, but regardless it still crashes. So, really what I need help with is figuring out calling conventions and setting up the arguments so the correct registers are used, since there really is no way to explicitly set which registers go with what unless it's written in assembly.
Now, the second function I'm working on calls the first function. It initializes a player instance and updates the player DataHeader accordingly. Note that the ident type is something I created. It is just a struct with an index and ID of the player. Halo seems to treat this as an int32 and does so bit shifting to merge the index and id together, which is very inconvenient I think.
004D0580 TEST AX,AX
004D0583 JL SHORT halo.004D05C7
004D0585 CMP AX,WORD PTR DS:[EDX+20]
004D0589 JGE SHORT halo.004D05C7
004D058B MOV ECX,DWORD PTR DS:[EDX+34]
004D058E PUSH ESI
004D058F MOVSX ESI,WORD PTR DS:[EDX+22]
004D0593 PUSH EDI
004D0594 MOVSX EDI,AX
004D0597 IMUL ESI,EDI
004D059A ADD ESI,ECX
004D059C CMP WORD PTR DS:[ESI],0
004D05A0 JNZ SHORT halo.004D05C1
004D05A2 INC WORD PTR DS:[EDX+30]
004D05A6 CMP AX,WORD PTR DS:[EDX+2E]
004D05AA JL SHORT halo.004D05B1
004D05AC INC EAX
004D05AD MOV WORD PTR DS:[EDX+2E],AX
004D05B1 CALL halo.004D0810
004D05B6 MOVSX EAX,WORD PTR DS:[ESI]
004D05B9 SHL EAX,10
004D05BC OR EAX,EDI
004D05BE POP EDI
004D05BF POP ESI
004D05C0 RETN
004D05C1 POP EDI
004D05C2 OR EAX,FFFFFFFF
004D05C5 POP ESI
004D05C6 RETN
004D05C7 OR EAX,FFFFFFFF
004D05CA RETN
//----- (004D0580) --------------------------------------------------------
int __usercall sub_4D0580<eax>(__int16 a1<ax>, int a2<edx>)
{
int v2; // edi@3
void *v3; // esi@3
int result; // eax@6
if ( a1 < 0 || a1 >= *(_WORD *)(a2 + 32) )
{
result = -1;
}
else
{
v2 = a1;
v3 = (void *)(*(_DWORD *)(a2 + 52) + a1 * *(_WORD *)(a2 + 34));
if ( *(_WORD *)v3 )
{
result = -1;
}
else
{
++*(_WORD *)(a2 + 48);
if ( a1 >= *(_WORD *)(a2 + 46) )
*(_WORD *)(a2 + 46) = a1 + 1;
sub_4D0810(a2, v3);
result = v2 | (*(_WORD *)v3 << 16);
}
}
return result;
}
ident __cdecl InitPlayerInstance(short PlayerIndex, DataHeader* PlayerHeader)
{
ident iResult = {-1,-1};
if(PlayerIndex < 0 || PlayerIndex >= PlayerHeader->Max)
return iResult;
else
{
APlayer* Player = (APlayer*)((unsigned long)PlayerHeader->First + (PlayerIndex * PlayerHeader->Size));
if(Player->PlayerID)
return iResult;
else
{
PlayerHeader->Next.Index++;
if(PlayerIndex >= PlayerHeader->LastIndex)
PlayerHeader->LastIndex += 1;
InitDataInstance(PlayerHeader, Player);
iResult.Index = PlayerIndex;
iResult.ID = Player->PlayerID;
return iResult;
}
}
}Something I accomplished previously, the only function I ever got working. This function was easy because it has zero parameters, ironic? lol I think so!
typedef void(*SpawnVehicle_t)(TagReference*, int);
SpawnVehicle_t SpawnVehicle = (SpawnVehicle_t)0x0045A960;
int CheatSpawnWarthog(void)
{
int LoopCount = 0;
int VehicleIndex = 0;
Globals* globals = *(Globals**)0x00746EC0;
GlobalsMultiplayerInformation* MPInfo;
GlobalsMultiplayerInformationVehicles* Vehicles;
TagReference* Vehicle;
if(globals->MultiplayerInformation.Count > 0)
{
MPInfo = (GlobalsMultiplayerInformation*)globals->MultiplayerInformation.Address;
VehicleIndex = MPInfo->Vehicles.Count;
if(VehicleIndex > 0)
{
Vehicles = (GlobalsMultiplayerInformationVehicles*)MPInfo->Vehicles.Address;
Vehicle = (TagReference*)&Vehicles->Vehicle;
while(!strstr(Vehicle->Name,"warthog"))
{
VehicleIndex = MPInfo->Vehicles.Count;
LoopCount++;
if(LoopCount > VehicleIndex)
return VehicleIndex;
Vehicle = (TagReference*)(MPInfo->Vehicles.Address + (LoopCount * 16));
}
SpawnVehicle(Vehicle,1);
}
}
return VehicleIndex;
}I was able to modify this code and spawn any vehicle by changing the "warthog" string to a different vehicle name and still call cheat_spawn_warthog. You can also push any TagReference into the SpawnVehicle(); function and it will spawn that object right next to you! :)
Reply with your help, thanks!
Thanks for Kornman00 for his DataHeader member names, they > mine. :dance:hehe
I'm positive there are only a select few that can help me out here, since this seems to be a little more advanced programming. I think KMan was going to help, but I have not gotten a PM back since I sent him code a few weeks ago. :confused2:
I'm working on reversing Halo engine functions from assembly back into C/C++ to create some useful functions for an SDK and to better understand the engine and how it works. All memory addresses and functions are from Halo PC 1.08, NOT CE.
There are 2 functions I am working on currently, with a billion previous I gave up on after crashing the game so many times! :D The first one is a function that initializes a DataHeader instance by clearing it to all 0's, before it is filled with actual data. A DataHeader looks like this:
struct DataHeader
{
char Name[32];
unsigned short Max; // Max number of <things> possible
unsigned short Size; // Size of each <thing> class instance
bool IsValid;
bool IdentifierZeroInvalid;
short Padding;
unsigned long Signature; // d@t@
short NextIndex;
short LastIndex;
ident Next; // the next <thing> to be initialized
void* First; // Pointer to the first <thing> class instance
};The first function assembly looks like this in OllyDbg, with my commentation:
004D0810 MOVSX ECX,WORD PTR DS:[EDX+22] Move DataHeader->Size into ECX
004D0814 PUSH EBX New Local Variable EBX
004D0815 MOV EBX,ECX Move DataHeader->Size[ECX] into EBX
004D0817 PUSH EDI New Local Variable EDI
004D0818 SHR ECX,2
004D081B XOR EAX,EAX Clear EAX to 0
004D081D MOV EDI,ESI Move Instance into EDI
004D081F REP STOS DWORD PTR ES:[EDI] Fill Instance[EDI] with 0's[EAX]
004D0821 MOV ECX,EBX Move DataHeader->Size[EBX] into ECX
004D0823 AND ECX,3
004D0826 REP STOS BYTE PTR ES:[EDI] .. Um ok..
004D0828 MOV AX,WORD PTR DS:[EDX+32] Move DataHeader->Next.ID into AX
004D082C MOV WORD PTR DS:[ESI],AX Move DataHeader->Next.ID into Instance
004D082F INC WORD PTR DS:[EDX+32] Increase DataHeader->Next.ID
004D0833 CMP WORD PTR DS:[EDX+32],0 Compare DataHeader->Next.ID to 0
004D0838 POP EDI
004D0839 POP EBX
004D083A JNZ SHORT halo.004D0842 If DataHeader->Next.ID is 0...
004D083C MOV WORD PTR DS:[EDX+32],8000 set it to 0x8000
004D0842 RETN Return the ID of the initialized instance[AX]I also am using IDA HexRays disassembler as a crutch. It does a pretty good job, but there is a lot of garbage. Here is what it spit out:
//----- (004D0810) --------------------------------------------------------
__int16 __usercall sub_4D0810<ax>(int a1<edx>, void *a2<esi>)
{
int v2; // ecx@1
int v3; // edi@1
__int16 result; // ax@4
__int16 v5; // bx@1
unsigned int v6; // ecx@1
v5 = *(_WORD *)(a1 + 34);
v6 = (unsigned int)*(_WORD *)(a1 + 34) >> 2;
memset(a2, 0, 4 * v6);
v3 = (int)((char *)a2 + 4 * v6);
v2 = v5 & 3;
while ( v2 )
{
*(_BYTE *)v3++ = 0;
--v2;
}
result = *(_WORD *)(a1 + 50);
*(_WORD *)a2 = result;
++*(_WORD *)(a1 + 50);
if ( !*(_WORD *)(a1 + 50) )
*(_WORD *)(a1 + 50) = -32768;
return result;
}Assembly is really difficult to reverse back into original source code, since when it is compiled and depending on the compiler, it optimizes the code and makes it a pain in the butt for us reverse engineers! :) I have written my own version of this function in C++, which seems to be exactly what the function is accomplishing in every task, but it still crashes when I detour the original function to mine. I think the reason is, my function is using the wrong registers through the parameters. I have debugged my previous attempts and this seems to always be the problem. Here is my version:
short __fastcall InitDataInstance(DataHeader* dHeader, void* Instance)
{
memset(Instance, 0, dHeader->Size);
*(short*)Instance = dHeader->Next.ID;
dHeader->Next.ID+=1;
if(!dHeader->Next.ID)
dHeader->Next.ID = -32768;
return *(short*)Instance;
}I have already made the assumption that the calling convention is not a fastcall because fastcalls always use ECX and EDX as the first 2 parameters, which is not the case here, UNLESS there is another parameter before EDX, a this pointer as the ECX register, which would make this a member function(thiscall). I'm not sure if this is the case though, although it could be. ECX is never pushed on the stack in this function or in the calling function. I guess it might be a cdecl convention, since ESI is pushed on the stack last, but regardless it still crashes. So, really what I need help with is figuring out calling conventions and setting up the arguments so the correct registers are used, since there really is no way to explicitly set which registers go with what unless it's written in assembly.
Now, the second function I'm working on calls the first function. It initializes a player instance and updates the player DataHeader accordingly. Note that the ident type is something I created. It is just a struct with an index and ID of the player. Halo seems to treat this as an int32 and does so bit shifting to merge the index and id together, which is very inconvenient I think.
004D0580 TEST AX,AX
004D0583 JL SHORT halo.004D05C7
004D0585 CMP AX,WORD PTR DS:[EDX+20]
004D0589 JGE SHORT halo.004D05C7
004D058B MOV ECX,DWORD PTR DS:[EDX+34]
004D058E PUSH ESI
004D058F MOVSX ESI,WORD PTR DS:[EDX+22]
004D0593 PUSH EDI
004D0594 MOVSX EDI,AX
004D0597 IMUL ESI,EDI
004D059A ADD ESI,ECX
004D059C CMP WORD PTR DS:[ESI],0
004D05A0 JNZ SHORT halo.004D05C1
004D05A2 INC WORD PTR DS:[EDX+30]
004D05A6 CMP AX,WORD PTR DS:[EDX+2E]
004D05AA JL SHORT halo.004D05B1
004D05AC INC EAX
004D05AD MOV WORD PTR DS:[EDX+2E],AX
004D05B1 CALL halo.004D0810
004D05B6 MOVSX EAX,WORD PTR DS:[ESI]
004D05B9 SHL EAX,10
004D05BC OR EAX,EDI
004D05BE POP EDI
004D05BF POP ESI
004D05C0 RETN
004D05C1 POP EDI
004D05C2 OR EAX,FFFFFFFF
004D05C5 POP ESI
004D05C6 RETN
004D05C7 OR EAX,FFFFFFFF
004D05CA RETN
//----- (004D0580) --------------------------------------------------------
int __usercall sub_4D0580<eax>(__int16 a1<ax>, int a2<edx>)
{
int v2; // edi@3
void *v3; // esi@3
int result; // eax@6
if ( a1 < 0 || a1 >= *(_WORD *)(a2 + 32) )
{
result = -1;
}
else
{
v2 = a1;
v3 = (void *)(*(_DWORD *)(a2 + 52) + a1 * *(_WORD *)(a2 + 34));
if ( *(_WORD *)v3 )
{
result = -1;
}
else
{
++*(_WORD *)(a2 + 48);
if ( a1 >= *(_WORD *)(a2 + 46) )
*(_WORD *)(a2 + 46) = a1 + 1;
sub_4D0810(a2, v3);
result = v2 | (*(_WORD *)v3 << 16);
}
}
return result;
}
ident __cdecl InitPlayerInstance(short PlayerIndex, DataHeader* PlayerHeader)
{
ident iResult = {-1,-1};
if(PlayerIndex < 0 || PlayerIndex >= PlayerHeader->Max)
return iResult;
else
{
APlayer* Player = (APlayer*)((unsigned long)PlayerHeader->First + (PlayerIndex * PlayerHeader->Size));
if(Player->PlayerID)
return iResult;
else
{
PlayerHeader->Next.Index++;
if(PlayerIndex >= PlayerHeader->LastIndex)
PlayerHeader->LastIndex += 1;
InitDataInstance(PlayerHeader, Player);
iResult.Index = PlayerIndex;
iResult.ID = Player->PlayerID;
return iResult;
}
}
}Something I accomplished previously, the only function I ever got working. This function was easy because it has zero parameters, ironic? lol I think so!
typedef void(*SpawnVehicle_t)(TagReference*, int);
SpawnVehicle_t SpawnVehicle = (SpawnVehicle_t)0x0045A960;
int CheatSpawnWarthog(void)
{
int LoopCount = 0;
int VehicleIndex = 0;
Globals* globals = *(Globals**)0x00746EC0;
GlobalsMultiplayerInformation* MPInfo;
GlobalsMultiplayerInformationVehicles* Vehicles;
TagReference* Vehicle;
if(globals->MultiplayerInformation.Count > 0)
{
MPInfo = (GlobalsMultiplayerInformation*)globals->MultiplayerInformation.Address;
VehicleIndex = MPInfo->Vehicles.Count;
if(VehicleIndex > 0)
{
Vehicles = (GlobalsMultiplayerInformationVehicles*)MPInfo->Vehicles.Address;
Vehicle = (TagReference*)&Vehicles->Vehicle;
while(!strstr(Vehicle->Name,"warthog"))
{
VehicleIndex = MPInfo->Vehicles.Count;
LoopCount++;
if(LoopCount > VehicleIndex)
return VehicleIndex;
Vehicle = (TagReference*)(MPInfo->Vehicles.Address + (LoopCount * 16));
}
SpawnVehicle(Vehicle,1);
}
}
return VehicleIndex;
}I was able to modify this code and spawn any vehicle by changing the "warthog" string to a different vehicle name and still call cheat_spawn_warthog. You can also push any TagReference into the SpawnVehicle(); function and it will spawn that object right next to you! :)
Reply with your help, thanks!
Thanks for Kornman00 for his DataHeader member names, they > mine. :dance:hehe