Compare commits

..

No commits in common. "bbe9d63294f8f7c98fdd012407c905292b296d16" and "522bf8be7fdb6edb96a47e733a30da32b8334ddb" have entirely different histories.

10 changed files with 263 additions and 292 deletions

View file

@ -40,7 +40,7 @@
}, },
"symbol_mappings": { "symbol_mappings": {
"?finalizeGameData@@YAXXZ": "_$E2", "?finalizeGameData@@YAXXZ": "_$E2",
"?initGameData@@YAXXZ": "_$E1" "?initGameData@@YGXXZ": "_$E1"
} }
} }
] ]

View file

@ -12,18 +12,18 @@ Game and GameObj classes that form the foundation of the JSRF game code.
void readInput(); void readInput();
// Address: 0x00011000 // Address: 0x00011000
// Status: unimplemented // Matching: no
GameObj::~GameObj() { GameObj::~GameObj() {
g_game->gameObjCnt -= 1; g_game->gameObjCnt -= 1;
} }
// Address: 0x00011070 // Address: 0x00011070
// Status: unimplemented // Matching: no
void GameObj::recursiveExecDefault() { void GameObj::recursiveExecDefault() {
} }
// Address: 0x000110A0 // Address: 0x000110A0
// Status: unimplemented // Matching: no
void GameObj::drawListDefault( void GameObj::drawListDefault(
GameObjFlags flagFilterAny1, GameObjFlags flagFilterAny1,
int drawArg1, int drawArg1,
@ -36,22 +36,22 @@ void GameObj::drawListDefault(
} }
// Address: 0x00011220 // Address: 0x00011220
// Status: unimplemented // Matching: no
void GameObj::drawTreeDefault1() { void GameObj::drawTreeDefault1() {
} }
// Address: 0x00011260 // Address: 0x00011260
// Status: unimplemented // Matching: no
void GameObj::drawTreeDefault2() { void GameObj::drawTreeDefault2() {
} }
// Address: 0x000112A0 // Address: 0x000112A0
// Status: unimplemented // Matching: no
void GameObj::recursiveExecEvent() { void GameObj::recursiveExecEvent() {
} }
// Address: 0x000112D0 // Address: 0x000112D0
// Status: unimplemented // Matching: no
void GameObj::drawListEvent( void GameObj::drawListEvent(
GameObjFlags flagFilterAny1, GameObjFlags flagFilterAny1,
int drawArg1, int drawArg1,
@ -64,22 +64,22 @@ void GameObj::drawListEvent(
} }
// Address: 0x00011450 // Address: 0x00011450
// Status: unimplemented // Matching: no
void GameObj::drawTreeEvent1() { void GameObj::drawTreeEvent1() {
} }
// Address: 0x00011490 // Address: 0x00011490
// Status: unimplemented // Matching: no
void GameObj::drawTreeEvent2() { void GameObj::drawTreeEvent2() {
} }
// Address: 0x000114D0 // Address: 0x000114D0
// Status: unimplemented // Matching: no
void GameObj::recursiveExecCoveredPause() { void GameObj::recursiveExecCoveredPause() {
} }
// Address: 0x00011500 // Address: 0x00011500
// Status: unimplemented // Matching: no
void GameObj::drawListCoveredPause( void GameObj::drawListCoveredPause(
GameObjFlags flagFilterAny1, GameObjFlags flagFilterAny1,
int drawArg1, int drawArg1,
@ -92,22 +92,22 @@ void GameObj::drawListCoveredPause(
} }
// Address: 0x00011680 // Address: 0x00011680
// Status: unimplemented // Matching: no
void GameObj::drawTreeCoveredPause1() { void GameObj::drawTreeCoveredPause1() {
} }
// Address: 0x000116C0 // Address: 0x000116C0
// Status: unimplemented // Matching: no
void GameObj::drawTreeCoveredPause2() { void GameObj::drawTreeCoveredPause2() {
} }
// Address: 0x00011700 // Address: 0x00011700
// Status: unimplemented // Matching: no
void GameObj::recursiveExecFreezeCam() { void GameObj::recursiveExecFreezeCam() {
} }
// Address: 0x00011730 // Address: 0x00011730
// Status: unimplemented // Matching: no
void GameObj::drawListFreezeCam( void GameObj::drawListFreezeCam(
GameObjFlags flagFilterAny1, GameObjFlags flagFilterAny1,
int drawArg1, int drawArg1,
@ -120,22 +120,22 @@ void GameObj::drawListFreezeCam(
} }
// Address: 0x000118B0 // Address: 0x000118B0
// Status: unimplemented // Matching: no
void GameObj::drawTreeFreezeCam1() { void GameObj::drawTreeFreezeCam1() {
} }
// Address: 0x000118F0 // Address: 0x000118F0
// Status: unimplemented // Matching: no
void GameObj::drawTreeFreezeCam2() { void GameObj::drawTreeFreezeCam2() {
} }
// Address: 0x00011930 // Address: 0x00011930
// Status: unimplemented // Matching: no
void GameObj::recursiveExecUncoveredPause() { void GameObj::recursiveExecUncoveredPause() {
} }
// Address: 0x00011960 // Address: 0x00011960
// Status: unimplemented // Matching: no
void GameObj::drawListUncoveredPause( void GameObj::drawListUncoveredPause(
GameObjFlags flagFilterAny1, GameObjFlags flagFilterAny1,
int drawArg1, int drawArg1,
@ -148,40 +148,40 @@ void GameObj::drawListUncoveredPause(
} }
// Address: 0x00011AE0 // Address: 0x00011AE0
// Status: unimplemented // Matching: no
void GameObj::drawTreeUncoveredPause1() { void GameObj::drawTreeUncoveredPause1() {
} }
// Address: 0x00011B20 // Address: 0x00011B20
// Status: unimplemented // Matching: no
void GameObj::drawTreeUncoveredPause2() { void GameObj::drawTreeUncoveredPause2() {
} }
// Address: 0x00011B60 // Address: 0x00011B60
// Status: unimplemented // Matching: no
void GameObj::addToSiblings(GameObj * sibling, GameObj * parent) { void GameObj::addToSiblings(GameObj * sibling, GameObj * parent) {
} }
// Address: 0x00011B90 // Address: 0x00011B90
// Status: unimplemented // Matching: no
void GameObj::destructChildren(GameObj * firstChild) { void GameObj::destructChildren(GameObj * firstChild) {
} }
// Address: 0x00011BD0 // Address: 0x00011BD0
// Status: matching // Matching: yes
GameObj * GameObj::getParent() { GameObj * GameObj::getParent() {
return this->parent; return this->parent;
} }
// Address: 0x00011BE0 // Address: 0x00011BE0
// Status: unimplemented // Matching: no
void GameObj::removeFromObjList(GameObj * obj) { void GameObj::removeFromObjList(GameObj * obj) {
if (obj->flags > -1) { if (obj->flags > -1) {
} }
} }
// Address: 0x00011C20 // Address: 0x00011C20
// Status: nonmatching // Matching: no
void GameObj::removeChildrenFromObjList(GameObj * firstChild) { void GameObj::removeChildrenFromObjList(GameObj * firstChild) {
if (firstChild != NULL) do { if (firstChild != NULL) do {
if (firstChild->flags > -1) { if (firstChild->flags > -1) {
@ -196,11 +196,11 @@ void GameObj::removeChildrenFromObjList(GameObj * firstChild) {
} }
// Address: 0x00011C80 // Address: 0x00011C80
// Status: matching // Matching: yes
void GameObj::drawDefault(unsigned unknown) {} void GameObj::drawDefault(unsigned unknown) {}
// Address: 0x00011C90 // Address: 0x00011C90
// Status: matching // Matching: yes
void GameObj::execDefault() {} void GameObj::execDefault() {}
// Eliminated by link time code generation (aliased with 0x00011C90) // Eliminated by link time code generation (aliased with 0x00011C90)
@ -243,42 +243,42 @@ void GameObj::postExecUncoveredPause() {}
void GameObj::drawUncoveredPause(unsigned unknown) {} void GameObj::drawUncoveredPause(unsigned unknown) {}
// Address: 0x00011CA0 // Address: 0x00011CA0
// Status: unimplemented // Matching: no
void DrawTree::copySomeVectors() { void DrawTree::copySomeVectors() {
} }
// Address: 0x00011D00 // Address: 0x00011D00
// Status: unimplemented // Matching: no
void GameObj::recursivePostExecDefault() { void GameObj::recursivePostExecDefault() {
} }
// Address: 0x00011D00 // Address: 0x00011D00
// Status: unimplemented // Matching: no
void GameObj::recursivePostExecEvent() { void GameObj::recursivePostExecEvent() {
} }
// Address: 0x00011E40 // Address: 0x00011E40
// Status: unimplemented // Matching: no
void GameObj::recursivePostExecCoveredPause() { void GameObj::recursivePostExecCoveredPause() {
} }
// Address: 0x00011EE0 // Address: 0x00011EE0
// Status: unimplemented // Matching: no
void GameObj::recursivePostExecFreezeCam() { void GameObj::recursivePostExecFreezeCam() {
} }
// Address: 0x00011F80 // Address: 0x00011F80
// Status: unimplemented // Matching: no
void GameObj::recursivePostExecUncoveredPause() { void GameObj::recursivePostExecUncoveredPause() {
} }
// Address: 0x00012020 // Address: 0x00012020
// Status: unimplemented // Matching: no
void GameObj::setParent(GameObj * parent) { void GameObj::setParent(GameObj * parent) {
} }
// Address: 0x00012100 // Address: 0x00012100
// Status: unimplemented // Matching: no
GameObj::GameObj(GameObj * parent, GameObjIndex index, GameObjFlags flags) { GameObj::GameObj(GameObj * parent, GameObjIndex index, GameObjFlags flags) {
g_game->setObj(index, this); g_game->setObj(index, this);
@ -298,7 +298,7 @@ GameObj::GameObj(GameObj * parent, GameObjIndex index, GameObjFlags flags) {
} }
// Address: 0x00012170 // Address: 0x00012170
// Status: matching // Matching: yes
DrawTree::DrawTree(GameObj * parent, GameObjIndex index, GameObjFlags flags) : DrawTree::DrawTree(GameObj * parent, GameObjIndex index, GameObjFlags flags) :
GameObj(parent, index, flags) { GameObj(parent, index, flags) {
this->otherBitfield = 7; this->otherBitfield = 7;
@ -306,11 +306,11 @@ DrawTree::DrawTree(GameObj * parent, GameObjIndex index, GameObjFlags flags) :
} }
// Address: 0x000121D0 // Address: 0x000121D0
// Status: unimplemented // Matching: no
DrawTree::~DrawTree() {} DrawTree::~DrawTree() {}
// Address: 0x000121E0 // Address: 0x000121E0
// Status: unimplemented // Matching: no
PlayerObj::PlayerObj(GameObj * parent, GameObjIndex index, GameObjFlags flags) : PlayerObj::PlayerObj(GameObj * parent, GameObjIndex index, GameObjFlags flags) :
GameObj(parent, index, flags) { GameObj(parent, index, flags) {
this->unknown0x44 = 0; this->unknown0x44 = 0;
@ -321,27 +321,27 @@ PlayerObj::PlayerObj(GameObj * parent, GameObjIndex index, GameObjFlags flags) :
PlayerObj::~PlayerObj() {} PlayerObj::~PlayerObj() {}
// Address: 0x00012210 // Address: 0x00012210
// Status: unimplemented // Matching: no
Game::Game(unsigned * unknown1, unsigned unknown2) { Game::Game(unsigned * unknown1, unsigned unknown2) {
} }
// Address: 0x00012390 // Address: 0x00012390
// Status: unimplemented // Matching: no
Game::~Game() { Game::~Game() {
} }
// Address: 0x000123E0 // Address: 0x000123E0
// Status: unimplemented // Matching: no
void Game::exec() { void Game::exec() {
} }
// Address: 0x00012580 // Address: 0x00012580
// Status: unimplemented // Matching: no
void Game::drawObj(GameObj * obj, unsigned unknown) { void Game::drawObj(GameObj * obj, unsigned unknown) {
} }
// Address: 0x000125E0 // Address: 0x000125E0
// Status: unimplemented // Matching: no
void Game::drawList_( void Game::drawList_(
GameObjFlags flagFilterAny1, GameObjFlags flagFilterAny1,
int drawArg1, int drawArg1,
@ -354,97 +354,97 @@ void Game::drawList_(
} }
// Address: 0x00012680 // Address: 0x00012680
// Status: unimplemented // Matching: no
void Game::drawTree1(GameObj * obj) { void Game::drawTree1(GameObj * obj) {
} }
// Address: 0x000126D0 // Address: 0x000126D0
// Status: unimplemented // Matching: no
void Game::setCoveredPauseNextFrame(BOOL val) { void Game::setCoveredPauseNextFrame(BOOL val) {
} }
// Address: 0x000126F0 // Address: 0x000126F0
// Status: unimplemented // Matching: no
void Game::setEventNextFrame(BOOL val) { void Game::setEventNextFrame(BOOL val) {
} }
// Address: 0x00012710 // Address: 0x00012710
// Status: unimplemented // Matching: no
void Game::setFreezeCamNextFrame(BOOL val) { void Game::setFreezeCamNextFrame(BOOL val) {
} }
// Address: 0x00012730 // Address: 0x00012730
// Status: unimplemented // Matching: no
void Game::setUncoveredPauseNextFrame(BOOL val) { void Game::setUncoveredPauseNextFrame(BOOL val) {
} }
// Address: 0x00012750 // Address: 0x00012750
// Status: matching // Matching: yes
void Game::enableDrawChildren() { void Game::enableDrawChildren() {
this->drawChildren = TRUE; this->drawChildren = TRUE;
} }
// Address: 0x00012760 // Address: 0x00012760
// Status: matching // Matching: yes
void Game::enableSkipDraw() { void Game::enableSkipDraw() {
this->skipDraw = TRUE; this->skipDraw = TRUE;
} }
// Address: 0x00012770 // Address: 0x00012770
// Status: unimplemented // Matching: no
void Game::fatal() { void Game::fatal() {
} }
// Address: 0x000127B0 // Address: 0x000127B0
// Status: matching // Matching: yes
void Game::setDrawMode(DrawMode mode) { void Game::setDrawMode(DrawMode mode) {
this->drawMode = mode; this->drawMode = mode;
} }
// Address: 0x000127C0 // Address: 0x000127C0
// Status: matching // Matching: yes
void Game::setGlobal(GlobalIndex index, unsigned val) { void Game::setGlobal(GlobalIndex index, unsigned val) {
this->globals[index] = val; this->globals[index] = val;
} }
// Address: 0x000127E0 // Address: 0x000127E0
// Status: matching // Matching: yes
unsigned Game::getGlobal(GlobalIndex index) { unsigned Game::getGlobal(GlobalIndex index) {
return this->globals[index]; return this->globals[index];
} }
// Address: 0x000127F0 // Address: 0x000127F0
// Status: unimplemented // Matching: no
void Game::addToDrawList(GameObj * obj) { void Game::addToDrawList(GameObj * obj) {
} }
// Address: 0x00012840 // Address: 0x00012840
// Status: unimplemented // Matching: no
void Game::removeFromDrawList(GameObj * obj) { void Game::removeFromDrawList(GameObj * obj) {
} }
// Address: 0x00012870 // Address: 0x00012870
// Status: matching // Matching: yes
void Game::setObj(GameObjIndex index, GameObj * obj) { void Game::setObj(GameObjIndex index, GameObj * obj) {
if (0 <= index && index < OBJ_CNT) if (0 <= index && index < OBJ_CNT)
this->objects[index] = obj; this->objects[index] = obj;
} }
// Address: 0x00012890 // Address: 0x00012890
// Status: matching // Matching: yes
void Game::unsetObj(GameObjIndex index) { void Game::unsetObj(GameObjIndex index) {
if (0 <= index && this->objects[index] != NULL) if (0 <= index && this->objects[index] != NULL)
this->objects[index] = NULL; this->objects[index] = NULL;
} }
// Address: 0x000128C0 // Address: 0x000128C0
// Status: matching // Matching: yes
GameObj * Game::getObj(GameObjIndex index) { GameObj * Game::getObj(GameObjIndex index) {
return 0 <= index ? this->objects[index] : NULL; return 0 <= index ? this->objects[index] : NULL;
} }
// Address: 0x000128E0 // Address: 0x000128E0
// Status: matching // Matching: yes
int Game::allocObjIndex(GameObjIndex min, GameObjIndex max) { int Game::allocObjIndex(GameObjIndex min, GameObjIndex max) {
if (min <= max) do if (min <= max) do
if (this->objects[min] == NULL) return min; if (this->objects[min] == NULL) return min;
@ -455,13 +455,13 @@ int Game::allocObjIndex(GameObjIndex min, GameObjIndex max) {
} }
// Address: 0x00012910 // Address: 0x00012910
// Status: matching // Matching: yes
BOOL Game::objIndexAvail(GameObjIndex index) { BOOL Game::objIndexAvail(GameObjIndex index) {
return this->objects[index] == NULL; return this->objects[index] == NULL;
} }
// Address: 0x00012930 // Address: 0x00012930
// Status: matching // Matching: yes
void Game::swapObjs(GameObjIndex index1, GameObjIndex index2) { void Game::swapObjs(GameObjIndex index1, GameObjIndex index2) {
GameObj * tmp = this->objects[index1]; GameObj * tmp = this->objects[index1];
this->objects[index1] = this->objects[index2]; this->objects[index1] = this->objects[index2];
@ -472,37 +472,37 @@ void Game::swapObjs(GameObjIndex index1, GameObjIndex index2) {
} }
// Address: 0x00012980 // Address: 0x00012980
// Status: unimplemented // Matching: no
void Game::clearScreen() { void Game::clearScreen() {
} }
// Address: 0x000129B0 // Address: 0x000129B0
// Status: matching // Matching: yes
void Game::enableSomeExtraDrawListCode() { void Game::enableSomeExtraDrawListCode() {
this->runSomeExtraDrawListCode = TRUE; this->runSomeExtraDrawListCode = TRUE;
} }
// Address: 0x000129C0 // Address: 0x000129C0
// Status: matching // Matching: yes
void Game::setLogosStarted(BOOL val) { void Game::setLogosStarted(BOOL val) {
this->logosStarted = val; this->logosStarted = val;
} }
// Address: 0x000129D0 // Address: 0x000129D0
// Status: matching // Matching: yes
void Game::clearDrawPriorityList() { void Game::clearDrawPriorityList() {
this->drawPriorityListHead = NULL; this->drawPriorityListHead = NULL;
this->drawPriorityListEndNext = &g_game->drawPriorityListHead; this->drawPriorityListEndNext = &g_game->drawPriorityListHead;
} }
// Address: 0x000129F0 // Address: 0x000129F0
// Status: matching // Matching: yes
GameObj * Game::getDrawPriorityListHead() { GameObj * Game::getDrawPriorityListHead() {
return this->drawPriorityListHead; return this->drawPriorityListHead;
} }
// Address: 0x00012A00 // Address: 0x00012A00
// Status: matching // Matching: yes
void Game::appendToDrawPriorityList(GameObj * obj) { void Game::appendToDrawPriorityList(GameObj * obj) {
obj->drawPriorityListNext = NULL; obj->drawPriorityListNext = NULL;
*this->drawPriorityListEndNext = obj; *this->drawPriorityListEndNext = obj;
@ -510,7 +510,7 @@ void Game::appendToDrawPriorityList(GameObj * obj) {
} }
// Address: 0x00012A20 // Address: 0x00012A20
// Status: unimplemented // Matching: no
void Game::sortDrawPriorityListSingleLevel(unsigned char sortKeyBitOffset) { void Game::sortDrawPriorityListSingleLevel(unsigned char sortKeyBitOffset) {
for ( for (
unsigned i = 0; unsigned i = 0;
@ -520,35 +520,35 @@ void Game::sortDrawPriorityListSingleLevel(unsigned char sortKeyBitOffset) {
} }
// Address: 0x00012AC0 // Address: 0x00012AC0
// Status: matching // Matching: yes
void Game::setFallbackBgColour(D3DCOLOR colour, BOOL useFallback) { void Game::setFallbackBgColour(D3DCOLOR colour, BOOL useFallback) {
this->useFallbackBgColour = useFallback; this->useFallbackBgColour = useFallback;
this->bgColourFallback = colour; this->bgColourFallback = colour;
} }
// Address: 0x00012AE0 // Address: 0x00012AE0
// Status: unimplemented // Matching: no
RootExecObj::RootExecObj(GameObj * parent, GameObjIndex index, GameObjFlags flags) : RootExecObj::RootExecObj(GameObj * parent, GameObjIndex index, GameObjFlags flags) :
GameObj(parent, index, flags) { GameObj(parent, index, flags) {
} }
// Address: 0x00012BE0 // Address: 0x00012BE0
// Status: matching // Matching: no
RootExecObj::~RootExecObj() { RootExecObj::~RootExecObj() {
} }
// Address: 0x00012C10 // Address: 0x00012C10
// Status: unimplemented // Matching: no
void Game::initRootExecObj() { void Game::initRootExecObj() {
} }
// Address: 0x00012C80 // Address: 0x00012C80
// Status: unimplemented // Matching: no
void Game::drawList(GameObjFlags flagFilterAll, BOOL unknown) { void Game::drawList(GameObjFlags flagFilterAll, BOOL unknown) {
} }
// Address: 0x000131A0 // Address: 0x000131A0
// Status: nonmatching // Matching: no
void Game::sortDrawPriorityList() { void Game::sortDrawPriorityList() {
this->sortDrawPriorityListSingleLevel(0x00); this->sortDrawPriorityListSingleLevel(0x00);
this->sortDrawPriorityListSingleLevel(0x08); this->sortDrawPriorityListSingleLevel(0x08);
@ -557,23 +557,23 @@ void Game::sortDrawPriorityList() {
} }
// Address: 0x000131F0 // Address: 0x000131F0
// Status: unimplemented // Matching: no
void Game::drawObjs() { void Game::drawObjs() {
} }
// Address: 0x00013930 // Address: 0x00013930
// Status: unimplemented // Matching: no
void Game::draw() { void Game::draw() {
} }
// Address: 0x00013A80 // Address: 0x00013A80
// Status: unimplemented // Matching: no
void Game::frame() { void Game::frame() {
readInput(); readInput();
} }
// Address: 0x00013F80 // Address: 0x00013F80
// Status: matching // Matching: yes
int Game::mainLoop() { int Game::mainLoop() {
if (this->initState >= 0) while (true) { if (this->initState >= 0) while (true) {
if (!this->fatalErr) this->frame(); if (!this->fatalErr) this->frame();
@ -585,7 +585,7 @@ int Game::mainLoop() {
} }
// Address: 0x00013FC0 // Address: 0x00013FC0
// Status: nonmatching // Matching: no
void removeFromObjListByIndex(GameObjIndex index) { void removeFromObjListByIndex(GameObjIndex index) {
GameObj * obj = g_game->getObj(index); GameObj * obj = g_game->getObj(index);
if (obj != NULL) { if (obj != NULL) {

View file

@ -13,347 +13,347 @@ GameData g_gameData = GameData();
// Address: 0x00039B50 // Address: 0x00039B50
// Status: unimplemented // Matching: no
BOOL GameData::checkFlagCondition(unsigned cond) { BOOL GameData::checkFlagCondition(unsigned cond) {
return 0; return 0;
} }
// Address: 0x00039BE0 // Address: 0x00039BE0
// Status: unimplemented // Matching: no
void GameData::writeStateFlag(unsigned flagVal) { void GameData::writeStateFlag(unsigned flagVal) {
} }
// Address: 0x00039C70 // Address: 0x00039C70
// Status: unimplemented // Matching: no
void GameData::incrementChapter() { void GameData::incrementChapter() {
} }
// Address: 0x00039C80 // Address: 0x00039C80
// Status: unimplemented // Matching: no
void GameData::setMissionDigits34(unsigned val) { void GameData::setMissionDigits34(unsigned val) {
} }
// Address: 0x00039C90 // Address: 0x00039C90
// Status: unimplemented // Matching: no
void GameData::setSpawnPosIndex(unsigned val) { void GameData::setSpawnPosIndex(unsigned val) {
} }
// Address: 0x00039CA0 // Address: 0x00039CA0
// Status: unimplemented // Matching: no
unsigned GameData::getSpawnPosIndex() { unsigned GameData::getSpawnPosIndex() {
return 0; return 0;
} }
// Address: 0x00039CB0 // Address: 0x00039CB0
// Status: unimplemented // Matching: no
void GameData::unlockCharacter(PlayerCharId charId) { void GameData::unlockCharacter(unsigned charId) {
} }
// Address: 0x00039CF0 // Address: 0x00039CF0
// Status: unimplemented // Matching: no
void GameData::lockCharacter(PlayerCharId charId) { void GameData::lockCharacter(unsigned charId) {
} }
// Address: 0x00039D10 // Address: 0x00039D10
// Status: unimplemented // Matching: no
BOOL GameData::characterUnlocked(PlayerCharId charId) { BOOL GameData::characterUnlocked(unsigned charId) {
return 0; return 0;
} }
// Address: 0x00039D40 // Address: 0x00039D40
// Status: unimplemented // Matching: no
BOOL GameData::checkFlagConditions(unsigned * conds, unsigned count) { BOOL GameData::checkFlagConditions(unsigned * conds, unsigned count) {
return 0; return 0;
} }
// Address: 0x00039D80 // Address: 0x00039D80
// Status: unimplemented // Matching: no
void GameData::writeStateFlags(unsigned * writes, unsigned count) { void GameData::writeStateFlags(unsigned * writes, unsigned count) {
} }
// Address: 0x00039DB0 // Address: 0x00039DB0
// Status: unimplemented // Matching: no
BOOL GameData::checkFlagConditionUnpacked(FlagList flagList, unsigned index) { BOOL GameData::checkFlagConditionUnpacked(FlagList flagList, unsigned index) {
return 0; return 0;
} }
// Address: 0x00039DE0 // Address: 0x00039DE0
// Status: unimplemented // Matching: no
void GameData::writeStateFlagUnpacked(FlagList flagList, unsigned index, unsigned val) { void GameData::writeStateFlagUnpacked(FlagList flagList, unsigned index, unsigned val) {
} }
// Address: 0x00039E10 // Address: 0x00039E10
// Status: unimplemented // Matching: no
void GameData::setSoulSpawned(unsigned soulId) { void GameData::setSoulSpawned(unsigned soulId) {
} }
// Address: 0x00039E40 // Address: 0x00039E40
// Status: unimplemented // Matching: no
BOOL GameData::getSoulSpawned(unsigned soulId) { BOOL GameData::getSoulSpawned(unsigned soulId) {
return 0; return 0;
} }
// Address: 0x00039E80 // Address: 0x00039E80
// Status: unimplemented // Matching: no
void GameData::setSoulCollected(unsigned soulId) { void GameData::setSoulCollected(unsigned soulId) {
} }
// Address: 0x00039EB0 // Address: 0x00039EB0
// Status: unimplemented // Matching: no
BOOL GameData::getSoulHeld(unsigned soulId) { BOOL GameData::getSoulHeld(unsigned soulId) {
return 0; return 0;
} }
// Address: 0x00039EF0 // Address: 0x00039EF0
// Status: unimplemented // Matching: no
BOOL GameData::soulSpawnedUncollected(unsigned soulId) { BOOL GameData::soulSpawnedUncollected(unsigned soulId) {
return 0; return 0;
} }
// Address: 0x00039F40 // Address: 0x00039F40
// Status: unimplemented // Matching: no
void GameData::clearHeldSouls() { void GameData::clearHeldSouls() {
} }
// Address: 0x00039F60 // Address: 0x00039F60
// Status: unimplemented // Matching: no
void GameData::restoreHeldSouls() { void GameData::restoreHeldSouls() {
} }
// Address: 0x00039FD0 // Address: 0x00039FD0
// Status: unimplemented // Matching: no
unsigned GameData::getSoulCount() { unsigned GameData::getSoulCount() {
return 0; return 0;
} }
// Address: 0x0003A0A0 // Address: 0x0003A0A0
// Status: unimplemented // Matching: no
unsigned GameData::getTotalSoulsInStage(unsigned stageId) { unsigned GameData::getTotalSoulsInStage(unsigned stageId) {
return 0; return 0;
} }
// Address: 0x0003A130 // Address: 0x0003A130
// Status: unimplemented // Matching: no
unsigned GameData::getHeldSoulsInStage(unsigned stageId) { unsigned GameData::getHeldSoulsInStage(unsigned stageId) {
return 0; return 0;
} }
// Address: 0x0003A2B0 // Address: 0x0003A2B0
// Status: unimplemented // Matching: no
BOOL GameData::getSoulCollectedBySize(TagSize size, unsigned index) { BOOL GameData::getSoulCollectedBySize(TagSize size, unsigned index) {
return 0; return 0;
} }
// Address: 0x0003A2F0 // Address: 0x0003A2F0
// Status: unimplemented // Matching: no
void GameData::setUnusedPerStageBitmask(unsigned stageId, unsigned index) { void GameData::setUnusedPerStageBitmask(unsigned stageId, unsigned index) {
} }
// Address: 0x0003A340 // Address: 0x0003A340
// Status: unimplemented // Matching: no
int GameData::getTagState(unsigned stageId, unsigned tagIndex, BOOL rivalTag) { int GameData::getTagState(unsigned stageId, unsigned tagIndex, BOOL rivalTag) {
return 0; return 0;
} }
// Address: 0x0003A3A0 // Address: 0x0003A3A0
// Status: unimplemented // Matching: no
void GameData::setTagState(unsigned stageId, unsigned tagIndex, BOOL rivalTag, unsigned val) { void GameData::setTagState(unsigned stageId, unsigned tagIndex, BOOL rivalTag, unsigned val) {
} }
// Address: 0x0003A400 // Address: 0x0003A400
// Status: unimplemented // Matching: no
void GameData::setTagCovered(unsigned stageId, unsigned tagIndex, BOOL rivalTag, unsigned gangOrPlayer) { void GameData::setTagCovered(unsigned stageId, unsigned tagIndex, BOOL rivalTag, unsigned gangOrPlayer) {
} }
// Address: 0x0003A4A0 // Address: 0x0003A4A0
// Status: unimplemented // Matching: no
void GameData::setVolumeSettings(float volMusic, float volSfx) { void GameData::setVolumeSettings(float volMusic, float volSfx) {
} }
// Address: 0x0003A4C0 // Address: 0x0003A4C0
// Status: unimplemented // Matching: no
void GameData::getVolumeSettings(float * outMusic, float * outSfx) { void GameData::getVolumeSettings(float * outMusic, float * outSfx) {
} }
// Address: 0x0003A4E0 // Address: 0x0003A4E0
// Status: unimplemented // Matching: no
void GameData::setRumbleEnabled(BOOL val) { void GameData::setRumbleEnabled(BOOL val) {
} }
// Address: 0x0003A4F0 // Address: 0x0003A4F0
// Status: unimplemented // Matching: no
BOOL GameData::getRumbleEnabled() { BOOL GameData::getRumbleEnabled() {
return 0; return 0;
} }
// Address: 0x0003A500 // Address: 0x0003A500
// Status: unimplemented // Matching: no
void GameData::setGarageMusic(unsigned songId) { void GameData::setGarageMusic(unsigned songId) {
} }
// Address: 0x0003A510 // Address: 0x0003A510
// Status: unimplemented // Matching: no
unsigned GameData::getGarageMusic() { unsigned GameData::getGarageMusic() {
return 0; return 0;
} }
// Address: 0x0003A520 // Address: 0x0003A520
// Status: unimplemented // Matching: no
void GameData::setUnusedBitfield(unsigned index) { void GameData::setUnusedBitfield(unsigned index) {
} }
// Address: 0x0003A550 // Address: 0x0003A550
// Status: unimplemented // Matching: no
void GameData::setMiscObjective(unsigned index) { void GameData::setMiscObjective(unsigned index) {
} }
// Address: 0x0003A580 // Address: 0x0003A580
// Status: unimplemented // Matching: no
BOOL GameData::getMiscObjective(unsigned index) { BOOL GameData::getMiscObjective(unsigned index) {
return 0; return 0;
} }
// Address: 0x0003A5C0 // Address: 0x0003A5C0
// Status: unimplemented // Matching: no
unsigned GameData::countMiscObjectives() { unsigned GameData::countMiscObjectives() {
return 0; return 0;
} }
// Address: 0x0003A690 // Address: 0x0003A690
// Status: unimplemented // Matching: no
BOOL GameData::getHighScore(unsigned stageId, TestRunType type, unsigned rank, TestRunScore * out) { BOOL GameData::getHighScore(unsigned stageId, TestRunType type, unsigned rank, TestRunScore * out) {
return 0; return 0;
} }
// Address: 0x0003A750 // Address: 0x0003A750
// Status: unimplemented // Matching: no
void GameData::incrementTimer(Timer timer) { void GameData::incrementTimer(Timer timer) {
} }
// Address: 0x0003A780 // Address: 0x0003A780
// Status: unimplemented // Matching: no
unsigned GameData::getTimer(Timer timer) { unsigned GameData::getTimer(Timer timer) {
return 0; return 0;
} }
// Address: 0x0003A7B0 // Address: 0x0003A7B0
// Status: unimplemented // Matching: no
void GameData::setTimer(Timer timer, unsigned frames) { void GameData::setTimer(Timer timer, unsigned frames) {
} }
// Address: 0x0003A7F0 // Address: 0x0003A7F0
// Status: unimplemented // Matching: no
void GameData::setSelectedTag(unsigned gangOrPlayer, TagSize size, unsigned tagId, BOOL multiplayer) { void GameData::setSelectedTag(unsigned gangOrPlayer, TagSize size, unsigned tagId, BOOL multiplayer) {
} }
// Address: 0x0003A820 // Address: 0x0003A820
// Status: unimplemented // Matching: no
unsigned GameData::getSelectedTag(unsigned gangOrPlayer, TagSize size, BOOL multiplayer) { unsigned GameData::getSelectedTag(unsigned gangOrPlayer, TagSize size, BOOL multiplayer) {
return 0; return 0;
} }
// Address: 0x0003A840 // Address: 0x0003A840
// Status: unimplemented // Matching: no
void GameData::setCustomTagSelected(unsigned gangOrPlayer, TagSize size, BOOL active, BOOL multiplayer) { void GameData::setCustomTagSelected(unsigned gangOrPlayer, TagSize size, BOOL active, BOOL multiplayer) {
} }
// Address: 0x0003A870 // Address: 0x0003A870
// Status: unimplemented // Matching: no
BOOL GameData::getCustomTagSelected(unsigned gangOrPlayer, TagSize size, BOOL multiplayer) { BOOL GameData::getCustomTagSelected(unsigned gangOrPlayer, TagSize size, BOOL multiplayer) {
return 0; return 0;
} }
// Address: 0x0003A890 // Address: 0x0003A890
// Status: unimplemented // Matching: no
void GameData::setEventSeen(unsigned eventId) { void GameData::setEventSeen(unsigned eventId) {
} }
// Address: 0x0003A8C0 // Address: 0x0003A8C0
// Status: unimplemented // Matching: no
BOOL GameData::eventSeen(unsigned eventId) { BOOL GameData::eventSeen(unsigned eventId) {
return 0; return 0;
} }
// Address: 0x0003A900 // Address: 0x0003A900
// Status: unimplemented // Matching: no
void GameData::incrementPlaytime() { void GameData::incrementPlaytime() {
} }
// Address: 0x0003A910 // Address: 0x0003A910
// Status: matching // Matching: yes
unsigned GameData::getSaveDataSize() { unsigned GameData::getSaveDataSize() {
// 0x50 byte key + 0x3508 actual save data + 0x8 extra (why extra?) // 0x50 byte key + 0x3508 actual save data + 0x8 extra (why extra?)
return 0x50 + sizeof this->saveActive + 0x8; return 0x50 + sizeof this->saveActive + 0x8;
} }
// Address: 0x0003A920 // Address: 0x0003A920
// Status: unimplemented // Matching: no
BOOL GameData::decrypt(char * saveData) { BOOL GameData::decrypt(char * saveData) {
return 0; return 0;
} }
// Address: 0x0003AB60 // Address: 0x0003AB60
// Status: unimplemented // Matching: no
void GameData::encrypt(char * outSaveData) { void GameData::encrypt(char * outSaveData) {
} }
// Address: 0x0003AE00 // Address: 0x0003AE00
// Status: unimplemented // Matching: no
void GameData::getSaveDescription(SaveDescription * outDesc) { void GameData::getSaveDescription(SaveDescription * outDesc) {
} }
// Address: 0x0003AE20 // Address: 0x0003AE20
// Status: unimplemented // Matching: no
void GameData::clearStateFlags(FlagListOrPtr flagList) { void GameData::clearStateFlags(FlagListOrPtr flagList) {
} }
// Address: 0x0003AEA0 // Address: 0x0003AEA0
// Status: unimplemented // Matching: no
void GameData::resetTimer(Timer timer) { void GameData::resetTimer(Timer timer) {
} }
// Address: 0x0003AED0 // Address: 0x0003AED0
// Status: unimplemented // Matching: no
GameData::GameData() { GameData::GameData() {
} }
// Address: 0x0003B3C0 // Address: 0x0003B3C0
// Status: unimplemented // Matching: no
void GameData::resetSelectedTags() { void GameData::resetSelectedTags() {
} }
// Address: 0x0003B420 // Address: 0x0003B420
// Status: unimplemented // Matching: no
void GameData::resetExceptSettingsAndSouls() { void GameData::resetExceptSettingsAndSouls() {
} }
// Address: 0x0003B5A0 // Address: 0x0003B5A0
// Status: unimplemented // Matching: no
void GameData::resetExceptSettingsAndHighScores() { void GameData::resetExceptSettingsAndHighScores() {
} }
// Address: 0x0003B640 // Address: 0x0003B640
// Status: unimplemented // Matching: no
void GameData::resetExceptSettings() { void GameData::resetExceptSettings() {
} }
// Address: 0x0003B680 // Address: 0x0003B680
// Status: unimplemented // Matching: no
void GameData::stash() { void GameData::stash() {
} }
// Address: 0x0003B6A0 // Address: 0x0003B6A0
// Status: unimplemented // Matching: no
void GameData::stashRestoreExceptSpecialFlags() { void GameData::stashRestoreExceptSpecialFlags() {
} }
// Address: 0x0003B6F0 // Address: 0x0003B6F0
// Status: unimplemented // Matching: no
void GameData::stashRestoreExceptHighScores() { void GameData::stashRestoreExceptHighScores() {
} }
// Address: 0x0003B790 // Address: 0x0003B790
// Status: unimplemented // Matching: no
void GameData::stashRestore() { void GameData::stashRestore() {
} }
@ -361,6 +361,6 @@ void GameData::stashRestore() {
GameData::~GameData() {} GameData::~GameData() {}
// Address: 0x0003B7E0 // Address: 0x0003B7E0
// Status: unimplemented // Matching: no
void GameData::addHighScore(unsigned stageId, TestRunType type, TestRunScore * score) { void GameData::addHighScore(unsigned stageId, TestRunType type, TestRunScore * score) {
} }

View file

@ -8,34 +8,6 @@ Save data and closely-related runtime data.
#include "../XDK/Win32.h" #include "../XDK/Win32.h"
// Enum that should probably go somewhere else
enum PlayerCharId {
PCHARID_BEAT,
PCHARID_CORN,
PCHARID_GUM,
PCHARID_COMBO,
PCHARID_RHYTH,
PCHARID_SODA,
PCHARID_YOYO,
PCHARID_NT3000,
PCHARID_GARAM,
PCHARID_BOOGIE,
PCHARID_CUBE,
PCHARID_LOVESHOCKERS,
PCHARID_POISONJAM,
PCHARID_NOISETANK,
PCHARID_IMMORTALS,
PCHARID_DOOMRIDERS,
PCHARID_RAPID99,
PCHARID_POTTS,
PCHARID_GOUJI,
PCHARID_ROBOY,
PCHARID_CLUTCH,
PCHARID_JAZZ,
PCHARID_AKUMU,
PCHARID_ZEROBEAT
};
// Data structure actually saved to disk // Data structure actually saved to disk
struct SaveData { struct SaveData {
unsigned chapter; unsigned chapter;
@ -122,7 +94,7 @@ enum TestRunType {
// Unpacked version of TestRunScoreSaved // Unpacked version of TestRunScoreSaved
struct TestRunScore { struct TestRunScore {
unsigned score; unsigned score;
PlayerCharId character; unsigned character;
unsigned rank1; // Used by Jet Tech unsigned rank1; // Used by Jet Tech
unsigned rank2; // Used by other test runs unsigned rank2; // Used by other test runs
}; };
@ -168,9 +140,9 @@ struct GameData {
void setMissionDigits34 (unsigned val); void setMissionDigits34 (unsigned val);
void setSpawnPosIndex (unsigned val); void setSpawnPosIndex (unsigned val);
unsigned getSpawnPosIndex (); unsigned getSpawnPosIndex ();
void unlockCharacter (PlayerCharId charId); void unlockCharacter (unsigned charId);
void lockCharacter (PlayerCharId charId); void lockCharacter (unsigned charId);
BOOL characterUnlocked (PlayerCharId charId); BOOL characterUnlocked (unsigned charId);
BOOL checkFlagConditions (unsigned * conds , unsigned count); BOOL checkFlagConditions (unsigned * conds , unsigned count);
void writeStateFlags (unsigned * writes, unsigned count); void writeStateFlags (unsigned * writes, unsigned count);
BOOL checkFlagConditionUnpacked(FlagList flagList, unsigned index); BOOL checkFlagConditionUnpacked(FlagList flagList, unsigned index);

View file

@ -13,7 +13,7 @@ Game * g_game;
// Address: 0x0006F9E0 // Address: 0x0006F9E0
// Status: matching // Matching: yes
void main(void) { void main(void) {
g_game = new Game(NULL, 0); g_game = new Game(NULL, 0);
g_game->initRootExecObj(); g_game->initRootExecObj();

View file

@ -19,7 +19,7 @@ Mat4 * g_matrices[3];
// Address: 0x001BB690 // Address: 0x001BB690
// Status: matching // Matching: yes
HRESULT __stdcall initMatrices(unsigned const count) { HRESULT __stdcall initMatrices(unsigned const count) {
/* Initialize the matrix stack with the given number of matrices. /* Initialize the matrix stack with the given number of matrices.
The requested number of matrices is rounded up to the nearest multiple of two. The requested number of matrices is rounded up to the nearest multiple of two.

View file

@ -17,7 +17,7 @@ DWORD __stdcall mainXapiStartup(LPVOID lpThreadParameter);
// Address: 0x00148023 // Address: 0x00148023
// Status: unimplemented // Matching: no
void __cdecl mainCRTStartup(void) { void __cdecl mainCRTStartup(void) {
/* The true entrypoint of the game, spawning a thread for the rest to run in /* The true entrypoint of the game, spawning a thread for the rest to run in
The linker automatically sets this function to the entrypoint. The linker automatically sets this function to the entrypoint.
@ -25,7 +25,7 @@ The linker automatically sets this function to the entrypoint.
} }
// Address: 0x00147FB4 // Address: 0x00147FB4
// Status: unimplemented // Matching: no
DWORD __stdcall mainXapiStartup(LPVOID const lpThreadParameter) { DWORD __stdcall mainXapiStartup(LPVOID const lpThreadParameter) {
/* Runs some initialization and then calls main() */ /* Runs some initialization and then calls main() */
main(); main();

View file

@ -83,54 +83,38 @@ executable where objdiff doesn't expect them to be, which will mess up our
diffs. To correct this, open the memory map (`Window > Memory Map`) and diffs. To correct this, open the memory map (`Window > Memory Map`) and
uncheck the "X" column for `.rdata`, `.data`, and `DOLBY`. uncheck the "X" column for `.rdata`, `.data`, and `DOLBY`.
Now we'll import data types from the decompilation. Open a Unix-style shell Now we'll import data types from the decompilation. Open a shell in the
(e.g. Git Bash if on Windows) in the `ghidra/` directory of your copy of the `ghidra/` directory of your copy of the repository and run `make_header.sh`,
repository and run `make_header.sh`, which will produce a `jsrf.h` in the same which will produce a `jsrf.h` in the same directory with the combined contents
directory with the combined contents of every header in a format suitable for of every header in a format suitable for Ghidra. Then, in Ghidra, select
Ghidra. Then, in Ghidra, select `File > Parse C Source...` to open a window `File > Parse C Source...` to open a window for importing C headers. Remove
for importing C headers. Remove everything from the "Source files to parse" everything from the "Source files to parse" and "Parse options" boxes, and add
and "Parse options" boxes, and add `jsrf.h` to the former (click the green + `jsrf.h` to the former (click the green + symbol on the right and select the
symbol on the right and select the `jsrf.h` file). Click the "..." on the `jsrf.h` file). Click the "..." on the "Program Architecture:" box and select
"Program Architecture:" box and select the row with the values "x86," the row with the values "x86," "default," "32," "little," and "Visual Studio."
"default," "32," "little," and "Visual Studio." Finally, click the "Parse to Finally, click the "Parse to Program" button, "Continue" to confirm, and
Program" button, "Continue" to confirm, and "Don't Use Open Archives" (the "Don't Use Open Archives" (the header is completely self-contained and doesn't
header is completely self-contained and doesn't need any information from any need any information from any other data type archives). You should then see a
other data type archives). You should then see a window reporting successful window reporting successful import, and you'll be able to find `jsrf.h` with
import, and you'll be able to find `jsrf.h` with all of its definitions under all of its definitions under `default.xbe` in the Data Type Manager window in
`default.xbe` in the Data Type Manager window in the bottom left. the bottom left.
Much of our work with Ghidra will make use of some custom scripts we've Lastly, we'll import symbols from the JSRF decompilation repository. Open the
written, so we'll have to tell it where to find them. Open up the Script script manager (`Window > Script Manager`) and select the "Data" folder in the
Manager (`Window > Script Manager`) and then open the Bundle Manager by left pane. Double click the script titled `ImportSymbolsScript.py`, and a file
clicking the "manage script directories" button (it looks sort of like a picker will open after a moment. Select `symboltable.tsv` from the `ghidra/`
bulleted list). Click the green + in the top right to add a new directory and directory of your cloned JSRF decompilation repository, and you should see a
select the `ghidra/ghidra_scripts` directory in this repository. bunch of `Created function...` and `Created label...` printed to the scripting
console window. Save your changes (save icon in the top left of the
The first script we'll want to run is the symbol importer to get known data and CodeBrowser window), and your Ghidra project should be all ready for creating
functions into your Ghidra project. In the Script Manager window, select the object files for objdiff.
"Import" category in the left pane and double click the `EnhancedImport.java`
script in the right pane to run it. You'll then be asked for an input file;
select `ghidra/symboltable.tsv` from this repository. Afterwards, you'll see a
bunch of "Importing ..." messages in a console in the main CodeBrowser window,
some of which may have "can't find data type X" added on if something's marked
with a type that hasn't made its way into our decompiled code yet, and there'll
be a bunch of new functions and labels defined.
While we imported a bunch of data types earlier, Ghidra's C parser leaves out
some important information that we'll have to fill in with another script. In
the Script Manager, run `ClassFixup.java` from the "Data Types" category, and
you should see some "Converting X to class" and "Fixing calling convention of
X" messages in the console.
Now you've got a Ghidra project containing everything we know about JSRF's
code! Make sure you save your Ghidra project now that everything's set up.
### Producing Object Files ### Producing Object Files
Close all of your Ghidra windows and open a Unix-style shell in the Close all of your Ghidra windows and open a Unix-style shell (e.g. Git Bash if
decompilation repository's `ghidra/` directory. The `delink.sh` script is our on Windows) in the decompilation repository's `ghidra/` directory. The
automated tool for extracting all the object files that have been identified so `delink.sh` script is our automated tool for extracting all the object files
far. The easiest way to run it is to invoke it with three arguments: that have been identified so far. Invoke it with three arguments:
- The path to your Ghidra installation (the directory with files like - The path to your Ghidra installation (the directory with files like
`ghidraRun` and `ghidraRun.bat`, and directories like `docs/` and `ghidraRun` and `ghidraRun.bat`, and directories like `docs/` and
@ -144,30 +128,27 @@ Unix-style paths. Make sure the paths are surrounded by quotes, too (e.g.
`'C:\path\to\whatever'`), else the shell won't understand the backslashes `'C:\path\to\whatever'`), else the shell won't understand the backslashes
correctly. correctly.
If you find typing out these arguments to be too much of a pain, you can also
define the environment variables `$GHIDRA_HOME`, `$JSRFDECOMP_PROJECTPATH`, and
`$JSRFDECOMP_PROJECTNAME` and invoke the script without arguments.
There are a couple errors you might get here: There are a couple errors you might get here:
- `Unable to lock project!`: This means that Ghidra isn't fully closed. Make - `Unable to lock project!`: This means that Ghidra isn't fully closed. Make
sure you've completely closed every Ghidra window before running `delink.sh`. sure you've completely closed every Ghidra window before running `delink.sh`.
- `Script not found` and `Invalid script`: This means that you haven't added - `Script not found: DelinkProgram.java` and
the repository's `ghidra_scripts` directory to the script search path as `Invalid script: DelinkProgram.java`: This means that the either the Ghidra
described in the previous section (particulary if it mentions delinker extension isn't properly installed, or you've somehow invoked the
`MSVC7Mangle.java`), the Ghidra delinker extension isn't properly installed script in a way that can't see the extension (e.g. installing Ghidra on
(particularly if it mentions `DelinkProgram.java`), or you've somehow invoked Windows and then invoking the script from WSL). Ensure it's installed and
the script in a way that can't see the scripts (e.g. installing Ghidra on enabled first, and that you're not running in some kind of environment
Windows and then invoking the script from WSL). different from where you installed Ghidra.
- `java.lang.RuntimeException: Failed to export ...`: This means that the - `java.lang.RuntimeException: Failed to export ...`: This means that the
delinker extension doesn't like something about what it was told to delink. delinker extension doesn't like something about what it was told to delink.
One known cause is duplicate symbol names. If you haven't modified One known cause is duplicate symbol names. If you haven't modified
`objects.csv` or `symboltable.tsv`, let other people on the project know so `objects.csv` or `symboltable.tsv`, let other people on the project know so
that they can look into fixing it. that they can look into fixing it.
If all goes well, the extracted object files will be in the `decompile/target/` If all goes well, you'll see the message `Delinking complete!` at the end of
directory of the repository. Now we're ready to start recompiling and diffing the script's output, and the extracted object files will be in the
code with objdiff. `decompile/target/` directory of the repository. Now we're ready to start
recompiling and diffing code with objdiff.
### Setting Up objdiff ### Setting Up objdiff
@ -186,11 +167,9 @@ correctly set up on your `PATH`.
One important piece of information, to make sure you get the correct match One important piece of information, to make sure you get the correct match
percentages: set `Diff Options > Function relocation diffs` to "None." percentages: set `Diff Options > Function relocation diffs` to "None."
Otherwise, some references to non-local variables will be marked as nonmatching Otherwise, approximately all references to functions and non-local variables
(this is because it's sometimes not possible to make certain things named will be marked as nonmatching (this has to do with the delinking process not
variables in Ghidra, particularly thread-local storage, and other times it's applying name mangling, which isn't expected to be fixed).
not possible to assign a fixed name to certain implicitly generated output in
the recompiled code).
### Using objdiff ### Using objdiff
@ -201,13 +180,14 @@ them. In the best case, corresponding functions in each file will have the
same name and be in the same section, at which point objdiff can link them same name and be in the same section, at which point objdiff can link them
automatically. Otherwise, one has to click on one of the corresponding automatically. Otherwise, one has to click on one of the corresponding
functions in one pane and the other function in the other pane to tell objdiff functions in one pane and the other function in the other pane to tell objdiff
to link them. The most common cases of this are implicitly generated functions to link them. Common cases of this are class methods (the names won't match)
and data, such as exception handling code placed in `.text$x` in the recompiled and implicitly generated functions, such as exception handling code placed in
object file. Be aware that objdiff's matching does not appear fully reliable `.text$x` in the recompiled object file. Keep in mind that objdiff's matching
in some cases, particularly when diffing data with external pointers (which does not appear fully reliable in some cases, particularly when diffing data
appear as `?? ?? ?? ??`) that aren't explicitly marked as non-matching but with external pointers (which appear as `?? ?? ?? ??`) that aren't explicitly
still somehow reduce the match percentage, so you'll have to use a tiny amount marked as non-matching but still somehow reduce the match percentage, so you'll
of judgement to determine when you actually have a match. have to use a tiny amount of judgement to determine when you actually have a
match.
Clicking on a function that's been linked across both object files shows a diff Clicking on a function that's been linked across both object files shows a diff
of the disassembly of both versions of the function, with any differences of the disassembly of both versions of the function, with any differences
@ -217,20 +197,8 @@ reaches 100%. Depending on how you configure objdiff, it will rebuild
automatically whenever you save a change to a source file, or you can manually automatically whenever you save a change to a source file, or you can manually
rebuild with the "Build" button at the top of the right pane. rebuild with the "Build" button at the top of the right pane.
When viewing and editing decompiled source files, be mindful of the There are no concrete instructions to give for writing decompiled code. Try
`// Status:` annotation above each function, which has the following meanings: importing headers from `decompile/src/` into Ghidra
- `unimplemented`: The decompiled function does not yet reproduce the behaviour
of the original
- `nonmatching`: The decompiled function is believed to behave the same as the
original, but it does not fully match in objdiff
- `matching`: The decompiled function perfectly matches the original in objdiff
Be sure to update them as you decompile if appropriate. Some functions may
also have other annotations describing nontrivial effects of link-time code
generation (LTCG), such as a nonstandard calling convention or multiple
functions being merged into one.
Otherwise, there are no concrete instructions to give for writing decompiled
code. Try importing headers from `decompile/src/` into Ghidra
(`File > Parse C Source...`) to get access to JSRF classes, and use Ghidra's (`File > Parse C Source...`) to get access to JSRF classes, and use Ghidra's
decompilation of the function in the CodeBrowser as a starting point for decompilation of the function in the CodeBrowser as a starting point for
writing your matching function, exercising whatever C++ and x86 assembly writing your matching function, exercising whatever C++ and x86 assembly
@ -255,11 +223,46 @@ whole executable in Ghidra.
### Updating `symboltable.tsv` ### Updating `symboltable.tsv`
If you have got a bunch of symbols you'd like to add to `symboltable.tsv`, you If you have got a bunch of symbols you'd like to add to `symboltable.tsv`, a
can generate a new copy from your Ghidra project by running the workflow has been devised to generate it from your Ghidra project. Before
`EnhancedExport.java` script from the "Export" category. If you want to merge regenerating the table, however, make sure that you have all of it symbols
the new table into the repository, make sure to take a look at the diff first already in your project so that you don't end up deleting any. One option is
to ensure you're not inadvertently deleting anything. to import `symboltable.tsv` into your project with the `ImportSymbolsScript.py`
script as mentioned under "Creating a JSRF Ghidra Project," but be aware that
this will overwrite any names you've assigned to the same symbols. You will
also have to ensure that no two symbols share the same name. This can be
avoided by using namespaces if need be (i.e. `X::symbol` and `Y::symbol` may
coexist), but function overloading must be avoided (you may not have one
function with the signature `void X::f(int)` and another with the signature
`void X::f(float)`), else errors can arise when delinking, as the delinker
extension does not mangle symbol names. Thunked functions can also cause
problems because Ghidra does not include them alongside other functions in the
symbol table, so convert them to regular functions (right click on the thunked
function in the symbol tree and unset it as a thunk in the `Function` submenu).
Once you're ready to export your symbols, open the symbol table
(`Window > Symbol Table`). Open the symbol filter window (cog button near the
top right), and uncheck everything but "User Defined" under "Symbol Source,"
"Data Labels" and "Function Labels" under "Symbol Types," "Use Advanced
Filters," and "Non-Externals" under "Non-Externals." This ensures that you
only export symbols that you've defined and that are useful for delinking.
Now we need to configure the columns that we want to export. Right-click on
one of the colum headers, click "Add/Remove Columns..." to open the "Select
Columns" window, and in it check only "Location," "Name," "Namespace," and
"Type." Click "OK" to close the window and ensure that the column order is
"Location," "Namespace," "Name," "Type" (you can drag the column headers to
reorder them if needed).
Now, to actually export the table, right-click on one of the table cells, click
"Select All," and then right-click again on a cell to select "Export > Export
to CSV..." before selecting where to save your exported symbol table.
The final step is converting this CSV file to the format expected by
`ImportSymbolsScript.py`. Open a shell in the repository's `ghidra/` directory
and run `make_symboltable.sh` with the path of your exported CSV as an
argument, and `symboltable.tsv` will be overwritten with a new table containing
your exported symbols.
### Updating `make_header.sh` ### Updating `make_header.sh`
@ -311,16 +314,12 @@ correctly (exception-handling code might be appended onto another function, for
example). Because `symboltable.tsv` should only be populated with symbols that example). Because `symboltable.tsv` should only be populated with symbols that
have been manually defined as per the previous section, this means that you have been manually defined as per the previous section, this means that you
need to define variable names and labels in Ghidra for everything therein (and need to define variable names and labels in Ghidra for everything therein (and
ideally everything referenced externally, as well). Strive to maintain basic ideally everything referenced externally, as well). Do try to maintain basic
consistency with the rest of the codebase: functions and methods begin with consistency with the rest of the codebase: functions and methods begin with
lowercase letters, for instance, while class/struct/enum names begin with lowercase letters, for instance, while class/struct/enum names begin with
capital letters, and special methods like constructors and destructors should capital letters, and special methods like constructors and destructors should
have the names they would have in real C++ code (i.e. `Class::Class` and have the names they would have in real C++ code (i.e. `Class::Class` and
`Class::~Class`, respectively). Special class methods and members like `Class::~Class`, respectively).
constructors and vtables must follow their established naming conventions for
our tooling to work properly. Also note that you can (mostly) disable name
mangling for a symbol by making it a member of the `extern_"C"` namespace,
which applies C-style name mangling as used by some symbols.
Once an object is ready for extracting, its `Delink?` column should be set to Once an object is ready for extracting, its `Delink?` column should be set to
`true` and the `objdiff.json` file in the `decompile/` directory should be `true` and the `objdiff.json` file in the `decompile/` directory should be

View file

@ -141,7 +141,7 @@ public class MSVC7Mangle extends GhidraScript {
// main() and extern "C" symbols get C name mangling // main() and extern "C" symbols get C name mangling
// (some other things, do, too, but just use extern "C" instead // (some other things, do, too, but just use extern "C" instead
// of making me find and list them all...) // of making me find and list them all...)
return nameRaw.equals("main") || return nameRaw == "main" ||
nameRaw.startsWith("extern_\"C\"::") ? mangleCFn (f) nameRaw.startsWith("extern_\"C\"::") ? mangleCFn (f)
: mangleCppFn(f); : mangleCppFn(f);
} }

View file

@ -238,9 +238,9 @@
0x00039c80 func void __thiscall notinline nofixup GameData::setMissionDigits34 uint missionDigits34 0x00039c80 func void __thiscall notinline nofixup GameData::setMissionDigits34 uint missionDigits34
0x00039c90 func void __thiscall notinline nofixup GameData::setSpawnPosIndex uint index 0x00039c90 func void __thiscall notinline nofixup GameData::setSpawnPosIndex uint index
0x00039ca0 func uint __thiscall notinline nofixup GameData::getSpawnPosIndex 0x00039ca0 func uint __thiscall notinline nofixup GameData::getSpawnPosIndex
0x00039cb0 func void __thiscall notinline nofixup GameData::unlockCharacter PlayerCharId charId 0x00039cb0 func void __thiscall notinline nofixup GameData::unlockCharacter PlayerCharId32 charId
0x00039cf0 func void __thiscall notinline nofixup GameData::lockCharacter PlayerCharId charId 0x00039cf0 func void __thiscall notinline nofixup GameData::lockCharacter PlayerCharId32 charId
0x00039d10 func BOOL __thiscall notinline nofixup GameData::characterUnlocked PlayerCharId charId 0x00039d10 func BOOL __thiscall notinline nofixup GameData::characterUnlocked PlayerCharId32 charId
0x00039d40 func BOOL __thiscall notinline nofixup GameData::checkFlagConditions uint * conds uint count 0x00039d40 func BOOL __thiscall notinline nofixup GameData::checkFlagConditions uint * conds uint count
0x00039d80 func void __thiscall notinline nofixup GameData::writeStateFlags uint * writes uint count 0x00039d80 func void __thiscall notinline nofixup GameData::writeStateFlags uint * writes uint count
0x00039db0 func BOOL __thiscall notinline nofixup GameData::checkFlagConditionUnpacked FlagList flagList uint index 0x00039db0 func BOOL __thiscall notinline nofixup GameData::checkFlagConditionUnpacked FlagList flagList uint index
@ -343,7 +343,7 @@
0x0004c070 func void __thiscall notinline nofixup Mission::waitForSomething 0x0004c070 func void __thiscall notinline nofixup Mission::waitForSomething
0x0004c400 func void __thiscall notinline nofixup Mission::showTextForFrame 0x0004c400 func void __thiscall notinline nofixup Mission::showTextForFrame
0x0004ca20 func void __thiscall notinline nofixup Mission::setPauseOptions Call_SetPauseOptions * args 0x0004ca20 func void __thiscall notinline nofixup Mission::setPauseOptions Call_SetPauseOptions * args
0x0004d5f0 func undefined __stdcall notinline nofixup populateSomeCharIdArray PlayerCharId * buf 0x0004d5f0 func undefined __stdcall notinline nofixup populateSomeCharIdArray PlayerCharId32 * buf
0x0004d880 func void __thiscall notinline nofixup Mission::setManyPlayerStateFieldsToOne 0x0004d880 func void __thiscall notinline nofixup Mission::setManyPlayerStateFieldsToOne
0x0004db30 func void default notinline nofixup resolveMissionBinPtrs MissionBin * mssn 0x0004db30 func void default notinline nofixup resolveMissionBinPtrs MissionBin * mssn
0x0004e930 func void __stdcall notinline nofixup newMission uint chapter uint idDigits34 BOOL param_3 0x0004e930 func void __stdcall notinline nofixup newMission uint chapter uint idDigits34 BOOL param_3
@ -446,7 +446,7 @@
0x0006f600 func undefined __cdecl notinline nofixup showReconnectNAndStartMessage1 uint controllerId 0x0006f600 func undefined __cdecl notinline nofixup showReconnectNAndStartMessage1 uint controllerId
0x0006f650 func undefined __cdecl notinline nofixup showReconnectNAndStartMessage2 uint controllerId 0x0006f650 func undefined __cdecl notinline nofixup showReconnectNAndStartMessage2 uint controllerId
0x0006f6a0 func void __cdecl notinline nofixup showReconnectControllerMessage 0x0006f6a0 func void __cdecl notinline nofixup showReconnectControllerMessage
0x0006f6d0 func void default notinline nofixup showCharacterJoinMessage PlayerCharId character undefined4 controller 0x0006f6d0 func void default notinline nofixup showCharacterJoinMessage PlayerCharId32 character undefined4 controller
0x0006f730 func undefined unknown notinline nofixup showProblemWithDisc 0x0006f730 func undefined unknown notinline nofixup showProblemWithDisc
0x0006f760 func undefined unknown notinline nofixup showSaveLoadErr 0x0006f760 func undefined unknown notinline nofixup showSaveLoadErr
0x0006f9c0 func undefined4 * __thiscall notinline nofixup UnknownStatic27::~UnknownStatic27 byte param_1 0x0006f9c0 func undefined4 * __thiscall notinline nofixup UnknownStatic27::~UnknownStatic27 byte param_1
@ -1126,7 +1126,7 @@
0x001f9358 data UnknownStatic13Part g_unknownStatic13PartDefault 0x001f9358 data UnknownStatic13Part g_unknownStatic13PartDefault
0x001f93c8 data UnknownStatic13 g_unknownStatic13 0x001f93c8 data UnknownStatic13 g_unknownStatic13
0x001f9640 data UnknownStatic13Part * g_unchangingUnknownStatic3Ptr 0x001f9640 data UnknownStatic13Part * g_unchangingUnknownStatic3Ptr
0x001f9808 data PlayerCharId[32] charIds 0x001f9808 data PlayerCharId32[32] charIds
0x001f9888 data SwitcherMethod *[119] Mission::exec_1Funcs 0x001f9888 data SwitcherMethod *[119] Mission::exec_1Funcs
0x001f9a68 data SwitcherMethod *[119] Mission::exec_5Funcs 0x001f9a68 data SwitcherMethod *[119] Mission::exec_5Funcs
0x001f9c48 data SwitcherMethod *[119] Mission::exec_3Funcs 0x001f9c48 data SwitcherMethod *[119] Mission::exec_3Funcs
@ -1176,7 +1176,7 @@
0x0021c234 data uint[5] tagIdsPoisonJam 0x0021c234 data uint[5] tagIdsPoisonJam
0x0021c248 data uint[5] tagIdsImmortals 0x0021c248 data uint[5] tagIdsImmortals
0x0021c25c data uint[5] tagIdsZeroBeat 0x0021c25c data uint[5] tagIdsZeroBeat
0x0021c450 data PlayerCharId[24] someCharIdArray 0x0021c450 data PlayerCharId32[24] someCharIdArray
0x0021c510 data EvCharId[24] someCharIdMapping 0x0021c510 data EvCharId[24] someCharIdMapping
0x0021c570 data char *[5] hasJoinedYou 0x0021c570 data char *[5] hasJoinedYou
0x0021c584 data undefined * testRunRankNamesJa 0x0021c584 data undefined * testRunRankNamesJa

Can't render this file because it has a wrong number of fields in line 3.