Final Fantasy® VII Searches
Posted: Thu Aug 03, 2006 10:45 pm
Game: Final Fantasy® VII
Purpose: Find Accesory and Armor data blocks and to find your player information during a battle.
Use FF7AccSearchSetup() to find accessories.
Use FF7ArmorSearchSetup() to find armors.
Use FF7BPlayerSearchSetup() to find your player battle data (you must fight a battle to find this).
Use FF7SearchSetup() to find all of the above.
The range to search should be from 0x00400000 to 0x00FFFFFF.
Searching the full user-mode range may take about one minute for the FF7Search() search.
This showcases how to use structures and then perform sanity checks on their members to detemine areas in RAM that hold instances of those structures.
First you have to actually study the game to figure out its structure layouts.
Once you have done that, you can recreate the structures in your own search functions as shown above and then use that to easily verify the data.
You don’t have to understand every member in the structures, as you can see from above.
L. Spiro
Purpose: Find Accesory and Armor data blocks and to find your player information during a battle.
Use FF7AccSearchSetup() to find accessories.
Use FF7ArmorSearchSetup() to find armors.
Use FF7BPlayerSearchSetup() to find your player battle data (you must fight a battle to find this).
Use FF7SearchSetup() to find all of the above.
The range to search should be from 0x00400000 to 0x00FFFFFF.
Searching the full user-mode range may take about one minute for the FF7Search() search.
- Code: Select all
// Search for accessory data.
// I happen to know the structure format
// for accessory data already, so just assume
// I am right.
INT FF7AccSearch( LPVOID lpvAddress, LPVOID lpvBuffer, INT iSize ) {
struct FF7_Acc {
BYTE bIncType[2]; // Stat increase type.
// 00 = Str, 01 = Vit, 02 = Mag,
// 03 = Spir, 04 = Dex, 05 = Luck
// FF = None. Two bytes.
BYTE bIncBy[2]; // Amount by which the stats
// above increase.
BYTE bElemStr; // Elemental strength. 0, 1, 0r FF.
BYTE bSpecEff; // Special effects. 0-6 or FF.
WORD wElementType; // Element.
DWORD dwStatus; // Status protection.
WORD wEquipMask; // Always FF 01.
BYTE bRestMask; // F6 to FF (but always FE for accessories).
BYTE bPad; // Pad, always FF.
} * pff7aAcc = lpvBuffer; // Cast the buffer to this type.
// There are several fast checks we should do first.
// Firstly, we should check that the buffer is large enough for
// this structure.
if ( iSize < sizeof( FF7_Acc ) ) { return 0; }
// The last byte is always FF. Easy to check.
if ( pff7aAcc->bPad != 0xFF ) { return 0; }
// The restriction mask is always FE for accessories.
if ( pff7aAcc->bRestMask != 0xFE ) { return 0; }
// The equipmask is always FF 01 (0x01FF).
if ( pff7aAcc->wEquipMask != 0x01FF ) { return 0; }
// These were the easy things to check.
// The next items have valid ranges.
// The Elemental Strength is 0, 1, or FF.
BYTE bStr = pff7aAcc->bElemStr;
if ( bStr > 0x01 && bStr != 0xFF ) { return 0; }
// By now we have eliminated all fakers.
// We can test by simply performing a search.
// Return the size of the structure.
// In a fixed-size search this won’t matter, but in a variable--
// sized search this will cause the correct buffer size to be used.
return sizeof( FF7_Acc );
}
VOID FF7AccSearchSetup( INT * piDataSize, INT * piAlign, CHAR ** ppcCallback, CHAR ** ppcDecoder ) {
(*piDataSize) = sizeof( FF7_Acc );
(*piAlign) = 4;
(*ppcCallback) = "FF7AccSearch";
}
// Search for armor data.
INT FF7ArmorSearch( LPVOID lpvAddress, LPVOID lpvBuffer, INT iSize ) {
struct FF7_Armor {
BYTE bUnknown; // Some byte at the start which is only 0 or FF.
BYTE bDamageType; // Damage type. FF = Normal. 0 = Absorb. 1 = None. 2 = Half.
BYTE bDef; // Defense.
BYTE bMagicDef; // Magic defense.
BYTE bDefPerc; // Defense %.
BYTE bMagicDefPerc; // Magic Defense %.
WORD wFill; // Always FFFF.
BYTE bFill; // Always 0 or FF.
BYTE bSlots[8]; // Slot links.
BYTE bGrowth; // Growth rate. 0, 1, or 2.
WORD wEquipMask; // Who can equip it?
BYTE bElemType[3]; // Element type.
BYTE bPad; // Always FF.
BYTE bIncType[2]; // Stat increase type.
WORD wPad2; // Always FFFF.
BYTE bIncAmount[2]; // Amount by which to increase stats in bIncType.
WORD wPad3; // Always FFFF.
BYTE bRestMask; // F6 to FF (but always FE for armors).
BYTE bPad4; // Always FF.
WORD wPad5; // Always FFFF.
} * pff7aAcc = lpvBuffer; // Cast the buffer to this type.
// Start with the fastest checks.
if ( iSize < sizeof( FF7_Armor ) ) { return 0; }
// The pad at the end is a good check to make.
if ( pff7aAcc->wPad5 != 0xFFFF ) { return 0; }
if ( pff7aAcc->bPad4 != 0xFF ) { return 0; }
// The restriction mask is always FE.
if ( pff7aAcc->bRestMask != 0xFE ) { return 0; }
// wPad3 is always FFFF.
if ( pff7aAcc->wPad3 != 0xFFFF ) { return 0; }
// The rest of the pads.
if ( pff7aAcc->wPad2 != 0xFFFF ) { return 0; }
if ( pff7aAcc->bPad != 0xFF ) { return 0; }
if ( pff7aAcc->wFill != 0xFFFF ) { return 0; }
// Now there are a few members that can only have a few valid values.
// Check those.
if ( pff7aAcc->bUnknown != 0xFF && pff7aAcc->bUnknown != 0x00 ) { return 0; }
if ( pff7aAcc->bFill != 0xFF && pff7aAcc->bFill != 0x00 ) { return 0; }
// Lastly, all stats can not be FF.
if ( *(DWORD *)&pff7aAcc->bDef == 0xFFFFFFFF ) { return 0; }
// Getting here means the chances are pretty good that this is a valid item.
// Return the size of the item to make this search compatible with both variable
// and fixed searches.
return sizeof( FF7_Armor );
}
VOID FF7ArmorSearchSetup( INT * piDataSize, INT * piAlign, CHAR ** ppcCallback, CHAR ** ppcDecoder ) {
(*piDataSize) = sizeof( FF7_Armor );
(*piAlign) = 4;
(*ppcCallback) = "FF7ArmorSearch";
}
// Search for player data.
INT FF7BPlayerSearch( LPVOID lpvAddress, LPVOID lpvBuffer, INT iSize ) {
struct FF7_BPlayer {
DWORD dwStatus; // Status inflictions.
BYTE bOrder; // Fron/back/against enemy/defending.
BYTE bPad1; // Always 0.
WORD wPad2; // Always 0.
BYTE bCharID; // Character ID.
BYTE bLevel; // From 6 to 99.
WORD wPad3; // Always 0.
BYTE bUnknown1; // Something.
BYTE bPhysAtt; // Physical attack.
BYTE MagAttack; // Magic attack.
BYTE bPad4[5]; // 5 pad bytes.
BYTE bDex; // Dexterity.
BYTE bLuck; // Luck.
DWORD dwPad5; // Always 0.
DWORD dwPad6; // Always 0.
WORD wPad7; // Always 0.
WORD wPhysDef; // Physical defense.
WORD wMagicDefense; // Magic Defense.
DWORD dwPad8; // Always 0.
WORD wCurMP; // Current MP.
WORD wMaxMP; // Max MP.
DWORD dwCurHP; // Current HP.
DWORD dwMaxHP; // Max HP.
BYTE bPad9[16]; // 0-filled area.
DWORD dwStatusImmune; // Status immunities.
BYTE bPad10[10]; // 0-filled area.
WORD wPad11; // Always FFFF.
BYTE bPad12[20]; // Final padding.
} * pff7abPlayer = lpvBuffer; // Cast the buffer to this type.
// Check the fastest things first.
if ( iSize < sizeof( FF7_BPlayer ) ) { return 0; }
// Put the non-zero check first because empty areas filled with
// zeros are common.
if ( pff7abPlayer->wPad11 != 0xFFFF ) { return 0; }
if ( pff7abPlayer->bCharID > 13 ) { return 0; }
if ( pff7abPlayer->bLevel > 99 ) { return 0; }
if ( pff7abPlayer->bPad1 != 0 ) { return 0; }
if ( pff7abPlayer->wPad2 != 0 ) { return 0; }
if ( pff7abPlayer->wPad3 != 0 ) { return 0; }
if ( pff7abPlayer->dwPad5 != 0 ) { return 0; }
if ( pff7abPlayer->dwPad6 != 0 ) { return 0; }
if ( pff7abPlayer->wPad7 != 0 ) { return 0; }
if ( pff7abPlayer->dwPad8 != 0 ) { return 0; }
// Secondary checks.
if ( pff7abPlayer->dwCurHP > pff7abPlayer->dwMaxHP ) { return 0; }
if ( pff7abPlayer->wCurMP > pff7abPlayer->wMaxMP ) { return 0; }
if ( (pff7abPlayer->bOrder & 0x0F) != 0x08 ) { return 0; }
// Last detail.
// bPad12[2] should be 0x08.
if ( pff7abPlayer->bPad12[2] != 0x08 ) { return 0; }
return sizeof( FF7_BPlayer );
}
VOID FF7BPlayerSearchSetup( INT * piDataSize, INT * piAlign, CHAR ** ppcCallback, CHAR ** ppcDecoder ) {
(*piDataSize) = sizeof( FF7_BPlayer );
(*piAlign) = 4;
(*ppcCallback) = "FF7BPlayerSearch";
}
// =====
// The ultimate Final Fantasy VII search! Search for everything we know how to find (well, everything I have time to write)!
INT FF7Search( LPVOID lpvAddress, LPVOID lpvBuffer, INT iSize ) {
INT iNewSize;
// Is this an accessory?
if ( iNewSize = FF7AccSearch( lpvAddress, lpvBuffer, iSize ) ) { return iNewSize; }
// Is this an armor?
if ( iNewSize = FF7ArmorSearch( lpvAddress, lpvBuffer, iSize ) ) { return iNewSize; }
// Is this a player loaded into battle?
if ( iNewSize = FF7BPlayerSearch( lpvAddress, lpvBuffer, iSize ) ) { return iNewSize; }
return 0;
}
VOID FF7SearchSetup( INT * piDataSize, INT * piAlign, CHAR ** ppcCallback, CHAR ** ppcDecoder ) {
(*piDataSize) = 0; // Items are of variable size.
(*piAlign) = 4;
(*ppcCallback) = "FF7Search";
}
This showcases how to use structures and then perform sanity checks on their members to detemine areas in RAM that hold instances of those structures.
First you have to actually study the game to figure out its structure layouts.
Once you have done that, you can recreate the structures in your own search functions as shown above and then use that to easily verify the data.
You don’t have to understand every member in the structures, as you can see from above.
L. Spiro