diff --git a/decompile/objdiff.json b/decompile/objdiff.json index 3cb19c1..193f596 100644 --- a/decompile/objdiff.json +++ b/decompile/objdiff.json @@ -127,8 +127,75 @@ "source_path": "src/JSRF/GameData.cpp" }, "symbol_mappings": { - "GameData::`scalar_deleting_destructor'": "??_GGameData@@UAEPAXI@Z" + "GameData::GameData": "??0GameData@@QAE@XZ", + "GameData::`scalar_deleting_destructor'": "??_GGameData@@UAEPAXI@Z", + "GameData::addHighScore": "?addHighScore@GameData@@QAEXIW4TestRunType@@PAUTestRunScore@@@Z", + "GameData::characterUnlocked": "?characterUnlocked@GameData@@QAEHI@Z", + "GameData::checkFlagCondition": "?checkFlagCondition@GameData@@QAEHI@Z", + "GameData::checkFlagConditionUnpacked": "?checkFlagConditionUnpacked@GameData@@QAEHW4FlagList@@I@Z", + "GameData::checkFlagConditions": "?checkFlagConditions@GameData@@QAEHPAII@Z", + "GameData::clearHeldSouls": "?clearHeldSouls@GameData@@QAEXXZ", + "GameData::clearStateFlags": "?clearStateFlags@GameData@@QAEXTFlagListOrPtr@@@Z", + "GameData::countMiscObjectives": "?countMiscObjectives@GameData@@QAEIXZ", + "GameData::decrypt": "?decrypt@GameData@@QAEHPAD@Z", + "GameData::encrypt": "?encrypt@GameData@@QAEXPAD@Z", + "GameData::eventSeen": "?eventSeen@GameData@@QAEHI@Z", + "GameData::getCustomTagSelected": "?getCustomTagSelected@GameData@@QAEHIW4TagSize@@H@Z", + "GameData::getGarageMusic": "?getGarageMusic@GameData@@QAEIXZ", + "GameData::getHeldSoulsInStage": "?getHeldSoulsInStage@GameData@@QAEII@Z", + "GameData::getHighScore": "?getHighScore@GameData@@QAEHIW4TestRunType@@IPAUTestRunScore@@@Z", + "GameData::getMiscObjective": "?getMiscObjective@GameData@@QAEHI@Z", + "GameData::getRumbleEnabled": "?getRumbleEnabled@GameData@@QAEHXZ", + "GameData::getSaveDataSize": "?getSaveDataSize@GameData@@QAEIXZ", + "GameData::getSaveDescription": "?getSaveDescription@GameData@@QAEXPAUSaveDescription@@@Z", + "GameData::getSelectedTag": "?getSelectedTag@GameData@@QAEIIW4TagSize@@H@Z", + "GameData::getSoulCollectedBySize": "?getSoulCollectedBySize@GameData@@QAEHW4TagSize@@I@Z", + "GameData::getSoulCount": "?getSoulCount@GameData@@QAEIXZ", + "GameData::getSoulHeld": "?getSoulHeld@GameData@@QAEHI@Z", + "GameData::getSoulSpawned": "?getSoulSpawned@GameData@@QAEHI@Z", + "GameData::getSpawnPosIndex": "?getSpawnPosIndex@GameData@@QAEIXZ", + "GameData::getTagState": "?getTagState@GameData@@QAEHIIH@Z", + "GameData::getTimer": "?getTimer@GameData@@QAEIW4Timer@@@Z", + "GameData::getTotalSoulsInStage": "?getTotalSoulsInStage@GameData@@QAEII@Z", + "GameData::getVolumeSettings": "?getVolumeSettings@GameData@@QAEXPAM0@Z", + "GameData::incrementChapter": "?incrementChapter@GameData@@QAEXXZ", + "GameData::incrementPlaytime": "?incrementPlaytime@GameData@@QAEXXZ", + "GameData::incrementTimer": "?incrementTimer@GameData@@QAEXW4Timer@@@Z", + "GameData::lockCharacter": "?lockCharacter@GameData@@QAEXI@Z", + "GameData::resetExceptSettings": "?resetExceptSettings@GameData@@QAEXXZ", + "GameData::resetExceptSettingsAndHighScores": "?resetExceptSettingsAndHighScores@GameData@@QAEXXZ", + "GameData::resetExceptSettingsAndSouls": "?resetExceptSettingsAndSouls@GameData@@QAEXXZ", + "GameData::resetSelectedTags": "?resetSelectedTags@GameData@@QAEXXZ", + "GameData::resetTimer": "?resetTimer@GameData@@QAEXW4Timer@@@Z", + "GameData::restoreHeldSouls": "?restoreHeldSouls@GameData@@QAEXXZ", + "GameData::setCustomTagSelected": "?setCustomTagSelected@GameData@@QAEXIW4TagSize@@HH@Z", + "GameData::setEventSeen": "?setEventSeen@GameData@@QAEXI@Z", + "GameData::setGarageMusic": "?setGarageMusic@GameData@@QAEXI@Z", + "GameData::setMiscObjective": "?setMiscObjective@GameData@@QAEXI@Z", + "GameData::setMissionDigits34": "?setMissionDigits34@GameData@@QAEXI@Z", + "GameData::setRumbleEnabled": "?setRumbleEnabled@GameData@@QAEXH@Z", + "GameData::setSelectedTag": "?setSelectedTag@GameData@@QAEXIW4TagSize@@IH@Z", + "GameData::setSoulCollected": "?setSoulCollected@GameData@@QAEXI@Z", + "GameData::setSoulSpawned": "?setSoulSpawned@GameData@@QAEXI@Z", + "GameData::setSpawnPosIndex": "?setSpawnPosIndex@GameData@@QAEXI@Z", + "GameData::setTagCovered": "?setTagCovered@GameData@@QAEXIIHI@Z", + "GameData::setTagState": "?setTagState@GameData@@QAEXIIHI@Z", + "GameData::setTimer": "?setTimer@GameData@@QAEXW4Timer@@I@Z", + "GameData::setUnusedBitfield": "?setUnusedBitfield@GameData@@QAEXI@Z", + "GameData::setUnusedPerStageBitmask": "?setUnusedPerStageBitmask@GameData@@QAEXII@Z", + "GameData::setVolumeSettings": "?setVolumeSettings@GameData@@QAEXMM@Z", + "GameData::soulSpawnedUncollected": "?soulSpawnedUncollected@GameData@@QAEHI@Z", + "GameData::stash": "?stash@GameData@@QAEXXZ", + "GameData::stashRestore": "?stashRestore@GameData@@QAEXXZ", + "GameData::stashRestoreExceptHighScores": "?stashRestoreExceptHighScores@GameData@@QAEXXZ", + "GameData::stashRestoreExceptSpecialFlags": "?stashRestoreExceptSpecialFlags@GameData@@QAEXXZ", + "GameData::unlockCharacter": "?unlockCharacter@GameData@@QAEXI@Z", + "GameData::writeStateFlag": "?writeStateFlag@GameData@@QAEXI@Z", + "GameData::writeStateFlagUnpacked": "?writeStateFlagUnpacked@GameData@@QAEXW4FlagList@@II@Z", + "GameData::writeStateFlags": "?writeStateFlags@GameData@@QAEXPAII@Z", + "finalizeGameData": "_$E2", + "initGameData": "_$E1" } } ] -} +} \ No newline at end of file diff --git a/decompile/src/JSRF/GameData.cpp b/decompile/src/JSRF/GameData.cpp index 0fda95f..e9bba24 100644 --- a/decompile/src/JSRF/GameData.cpp +++ b/decompile/src/JSRF/GameData.cpp @@ -4,7 +4,363 @@ Save data and closely-related runtime data. #pragma bss_seg(".data") +#include "../XDK/Win32.hpp" #include "GameData.hpp" GameData g_gameData = GameData(); + + + +// Address: 0x00039B50 +// Matching: no +BOOL GameData::checkFlagCondition(unsigned cond) { + return 0; +} + +// Address: 0x00039BE0 +// Matching: no +void GameData::writeStateFlag(unsigned flagVal) { +} + +// Address: 0x00039C70 +// Matching: no +void GameData::incrementChapter() { +} + +// Address: 0x00039C80 +// Matching: no +void GameData::setMissionDigits34(unsigned val) { +} + +// Address: 0x00039C90 +// Matching: no +void GameData::setSpawnPosIndex(unsigned val) { +} + +// Address: 0x00039CA0 +// Matching: no +unsigned GameData::getSpawnPosIndex() { + return 0; +} + +// Address: 0x00039CB0 +// Matching: no +void GameData::unlockCharacter(unsigned charId) { +} + +// Address: 0x00039CF0 +// Matching: no +void GameData::lockCharacter(unsigned charId) { +} + +// Address: 0x00039D10 +// Matching: no +BOOL GameData::characterUnlocked(unsigned charId) { + return 0; +} + +// Address: 0x00039D40 +// Matching: no +BOOL GameData::checkFlagConditions(unsigned * conds, unsigned count) { + return 0; +} + +// Address: 0x00039D80 +// Matching: no +void GameData::writeStateFlags(unsigned * writes, unsigned count) { +} + +// Address: 0x00039DB0 +// Matching: no +BOOL GameData::checkFlagConditionUnpacked(FlagList flagList, unsigned index) { + return 0; +} + +// Address: 0x00039DE0 +// Matching: no +void GameData::writeStateFlagUnpacked(FlagList flagList, unsigned index, unsigned val) { +} + +// Address: 0x00039E10 +// Matching: no +void GameData::setSoulSpawned(unsigned soulId) { +} + +// Address: 0x00039E40 +// Matching: no +BOOL GameData::getSoulSpawned(unsigned soulId) { + return 0; +} + +// Address: 0x00039E80 +// Matching: no +void GameData::setSoulCollected(unsigned soulId) { +} + +// Address: 0x00039EB0 +// Matching: no +BOOL GameData::getSoulHeld(unsigned soulId) { + return 0; +} + +// Address: 0x00039EF0 +// Matching: no +BOOL GameData::soulSpawnedUncollected(unsigned soulId) { + return 0; +} + +// Address: 0x00039F40 +// Matching: no +void GameData::clearHeldSouls() { +} + +// Address: 0x00039F60 +// Matching: no +void GameData::restoreHeldSouls() { +} + +// Address: 0x00039FD0 +// Matching: no +unsigned GameData::getSoulCount() { + return 0; +} + +// Address: 0x0003A0A0 +// Matching: no +unsigned GameData::getTotalSoulsInStage(unsigned stageId) { + return 0; +} + +// Address: 0x0003A130 +// Matching: no +unsigned GameData::getHeldSoulsInStage(unsigned stageId) { + return 0; +} + +// Address: 0x0003A2B0 +// Matching: no +BOOL GameData::getSoulCollectedBySize(TagSize size, unsigned index) { + return 0; +} + +// Address: 0x0003A2F0 +// Matching: no +void GameData::setUnusedPerStageBitmask(unsigned stageId, unsigned index) { +} + +// Address: 0x0003A340 +// Matching: no +int GameData::getTagState(unsigned stageId, unsigned tagIndex, BOOL rivalTag) { + return 0; +} + +// Address: 0x0003A3A0 +// Matching: no +void GameData::setTagState(unsigned stageId, unsigned tagIndex, BOOL rivalTag, unsigned val) { +} + +// Address: 0x0003A400 +// Matching: no +void GameData::setTagCovered(unsigned stageId, unsigned tagIndex, BOOL rivalTag, unsigned gangOrPlayer) { +} + +// Address: 0x0003A4A0 +// Matching: no +void GameData::setVolumeSettings(float volMusic, float volSfx) { +} + +// Address: 0x0003A4C0 +// Matching: no +void GameData::getVolumeSettings(float * outMusic, float * outSfx) { +} + +// Address: 0x0003A4E0 +// Matching: no +void GameData::setRumbleEnabled(BOOL val) { +} + +// Address: 0x0003A4F0 +// Matching: no +BOOL GameData::getRumbleEnabled() { + return 0; +} + +// Address: 0x0003A500 +// Matching: no +void GameData::setGarageMusic(unsigned songId) { +} + +// Address: 0x0003A510 +// Matching: no +unsigned GameData::getGarageMusic() { + return 0; +} + +// Address: 0x0003A520 +// Matching: no +void GameData::setUnusedBitfield(unsigned index) { +} + +// Address: 0x0003A550 +// Matching: no +void GameData::setMiscObjective(unsigned index) { +} + +// Address: 0x0003A580 +// Matching: no +BOOL GameData::getMiscObjective(unsigned index) { + return 0; +} + +// Address: 0x0003A5C0 +// Matching: no +unsigned GameData::countMiscObjectives() { + return 0; +} + +// Address: 0x0003A690 +// Matching: no +BOOL GameData::getHighScore(unsigned stageId, TestRunType type, unsigned rank, TestRunScore * out) { + return 0; +} + +// Address: 0x0003A750 +// Matching: no +void GameData::incrementTimer(Timer timer) { +} + +// Address: 0x0003A780 +// Matching: no +unsigned GameData::getTimer(Timer timer) { + return 0; +} + +// Address: 0x0003A7B0 +// Matching: no +void GameData::setTimer(Timer timer, unsigned frames) { +} + +// Address: 0x0003A7F0 +// Matching: no +void GameData::setSelectedTag(unsigned gangOrPlayer, TagSize size, unsigned tagId, BOOL multiplayer) { +} + +// Address: 0x0003A820 +// Matching: no +unsigned GameData::getSelectedTag(unsigned gangOrPlayer, TagSize size, BOOL multiplayer) { + return 0; +} + +// Address: 0x0003A840 +// Matching: no +void GameData::setCustomTagSelected(unsigned gangOrPlayer, TagSize size, BOOL active, BOOL multiplayer) { +} + +// Address: 0x0003A870 +// Matching: no +BOOL GameData::getCustomTagSelected(unsigned gangOrPlayer, TagSize size, BOOL multiplayer) { + return 0; +} + +// Address: 0x0003A890 +// Matching: no +void GameData::setEventSeen(unsigned eventId) { +} + +// Address: 0x0003A8C0 +// Matching: no +BOOL GameData::eventSeen(unsigned eventId) { + return 0; +} + +// Address: 0x0003A900 +// Matching: no +void GameData::incrementPlaytime() { +} + +// Address: 0x0003A910 +// Matching: yes +unsigned GameData::getSaveDataSize() { + // 0x50 byte key + 0x3508 actual save data + 0x8 extra (why extra?) + return 0x50 + sizeof this->saveActive + 0x8; +} + +// Address: 0x0003A920 +// Matching: no +BOOL GameData::decrypt(char * saveData) { + return 0; +} + +// Address: 0x0003AB60 +// Matching: no +void GameData::encrypt(char * outSaveData) { +} + +// Address: 0x0003AE00 +// Matching: no +void GameData::getSaveDescription(SaveDescription * outDesc) { +} + +// Address: 0x0003AE20 +// Matching: no +void GameData::clearStateFlags(FlagListOrPtr flagList) { +} + +// Address: 0x0003AEA0 +// Matching: no +void GameData::resetTimer(Timer timer) { +} + +// Address: 0x0003AED0 +// Matching: no +GameData::GameData() { +} + +// Address: 0x0003B3C0 +// Matching: no +void GameData::resetSelectedTags() { +} + +// Address: 0x0003B420 +// Matching: no +void GameData::resetExceptSettingsAndSouls() { +} + +// Address: 0x0003B5A0 +// Matching: no +void GameData::resetExceptSettingsAndHighScores() { +} + +// Address: 0x0003B640 +// Matching: no +void GameData::resetExceptSettings() { +} + +// Address: 0x0003B680 +// Matching: no +void GameData::stash() { +} + +// Address: 0x0003B6A0 +// Matching: no +void GameData::stashRestoreExceptSpecialFlags() { +} + +// Address: 0x0003B6F0 +// Matching: no +void GameData::stashRestoreExceptHighScores() { +} + +// Address: 0x0003B790 +// Matching: no +void GameData::stashRestore() { +} + +// Optimized out +GameData::~GameData() {} + +// Address: 0x0003B7E0 +// Matching: no +void GameData::addHighScore(unsigned stageId, TestRunType type, TestRunScore * score) { +} diff --git a/decompile/src/JSRF/GameData.hpp b/decompile/src/JSRF/GameData.hpp index 278f2d9..7896835 100644 --- a/decompile/src/JSRF/GameData.hpp +++ b/decompile/src/JSRF/GameData.hpp @@ -96,6 +96,11 @@ struct TestRunScore { // Numeric IDs for different timers enum Timer { TIMER_DEATHBALLPRACTICE, TIMER_CLUTCH, TIMER_UNUSED }; +// Info showed in save/load menu +struct SaveDescription { unsigned chapter, playtimeSeconds; }; + +union FlagListOrPtr { FlagList list; unsigned * ptr; }; + // Save data-ish data structure used at runtime extern struct GameData { SaveData saveActive; @@ -137,7 +142,7 @@ extern struct GameData { unsigned getSoulCount (); unsigned getTotalSoulsInStage(unsigned stageId); unsigned getHeldSoulsInStage (unsigned stageId); - unsigned setSoulCollectedBySize(TagSize size, unsigned index); + BOOL getSoulCollectedBySize(TagSize size, unsigned index); void setUnusedPerStageBitmask(unsigned stageId, unsigned index); int getTagState (unsigned stageId, unsigned index, BOOL rivalTag); void setTagState (unsigned stageId, unsigned index, BOOL rivalTag, unsigned val); @@ -156,9 +161,33 @@ extern struct GameData { void incrementTimer (Timer timer); unsigned getTimer (Timer timer); void setTimer (Timer timer, unsigned frames); + void setSelectedTag (unsigned gangOrPlayer, TagSize size, unsigned tagId, BOOL multiplayer); + unsigned getSelectedTag (unsigned gangOrPlayer, TagSize size, BOOL multiplayer); + void setCustomTagSelected(unsigned gangOrPlayer, TagSize size, BOOL active, BOOL multiplayer); + BOOL getCustomTagSelected(unsigned gangOrPlayer, TagSize size, BOOL multiplayer); + void setEventSeen (unsigned eventId); + BOOL eventSeen (unsigned eventId); + void incrementPlaytime (); + unsigned getSaveDataSize (); + BOOL decrypt (char * saveData); + void encrypt (char * outSaveData); + void getSaveDescription (SaveDescription * outDesc); + void clearStateFlags (FlagListOrPtr flags); + void resetTimer (Timer timer); + GameData(); + void resetSelectedTags (); + + void resetExceptSettingsAndSouls (); + void resetExceptSettingsAndHighScores(); + void resetExceptSettings (); + + void stash (); + void stashRestoreExceptSpecialFlags(); + void stashRestoreExceptHighScores (); + void stashRestore (); - GameData(); virtual ~GameData(); + void addHighScore(unsigned stageId, TestRunType type, TestRunScore * score); } g_gameData; #endif diff --git a/delink/objects.csv b/delink/objects.csv index 5cc7ec1..c7c3c82 100644 --- a/delink/objects.csv +++ b/delink/objects.csv @@ -1,15 +1,15 @@ -Object,Delink?,.text,.text$x,D3D,DSOUND,MMATRIX,XGRPH,XPP,.rdata,.rdata$x,.data,DOLBY -JSRF/Core.obj,true,0x00011000-0x00013FEB,0x00186BA0-0x00186C14,,,,,,0x001C4390-0x001C44F9,0x001E4D20-0x001E4DAB,0x001EB880-0x001EB933, -JSRF/GameData.obj,true,0x00039B50-0x0003B937,,,,,,,0x001CA16C-0x001CA3DB,,0x001EFC88-0x001F7047, -JSRF/Jet2.obj,true,0x0006F9E0-0x0006FA6F,0x00187710-0x00187724,,,,,,,0x001E620C-0x001E622F,0x0022FCE0-0x0022FCE3, -ADX (need to decompose),false,0x0013A570-0x0014555F,?,,,,,,?,?,?, -XDK Core (need to decompose),false,0x00145560-0x0014B79F,?,,,,,,?,?,?, -Smilebit libs (need to decompose),false,0x0014B7A0-0x0017BF3F,?,,,,,,?,?,?-0x0022ED2B, -C runtime,false,0x0017BF40-0x00182B80,?,,,,,,?,?,0x0022ED2C-?, -Unknown MS math lib,false,0x00182B81-0x0018694F,?,,,,,,?,?,?, -Another (tiny) Smilebit math lib,false,0x00186950-0x00186B7F,?,,,,,,?,?,?, -Direct3D8 (need to decompose),false,,,0x0018CB40-0x0019E334,,,,,?,?,?, -DirectSound8 (need to decompose),false,,,,0x0019E340-0x001BA89B,,,,?,?,?,0x0027E080-0x00284E17 -MMatrix.obj,false,,,,,0x001BA8A0-0x001BBAAF,,,,,0x00264BD8-0x00264C13, -Xgraphics (need to decompose),false,,,,,,0x001BBAC0-0x001BC7BB,,,,, -XDK Peripherals (need to decompose),false,,,,,,,0x001BC7C0-0x001C3F57,,,, +Object,Delink?,.text,.text$XCU1,.text$XCU2,.text$x,D3D,DSOUND,MMATRIX,XGRPH,XPP,.rdata,.rdata$x,.data$CRT,.data,DOLBY +JSRF/Core.obj,true,0x00011000-0x00013FEB,,,0x00186BA0-0x00186C14,,,,,,0x001C4390-0x001C44F9,0x001E4D20-0x001E4DAB,,0x001EB880-0x001EB933, +JSRF/GameData.obj,true,0x00039B50-0x0003B937,0x0018AD60-0x0018AD75,0x0018C9A0-0x0018C9AA,,,,,,,0x001CA16C-0x001CA3DB,,0x001EB790-0x001EB793,0x001EFC88-0x001F7047, +JSRF/Jet2.obj,true,0x0006F9E0-0x0006FA6F,,,0x00187710-0x00187724,,,,,,,0x001E620C-0x001E622F,,0x0022FCE0-0x0022FCE3, +ADX (need to decompose),false,0x0013A570-0x0014555F,?,?,?,?,,,,,,?,?,?, +XDK Core (need to decompose),false,0x00145560-0x0014B79F,?,?,?,?,,,,,,?,?,?, +Smilebit libs (need to decompose),false,0x0014B7A0-0x0017BF3F,?,?,?,?,,,,,,?,?,?-0x0022ED2B, +C runtime,false,0x0017BF40-0x00182B80,?,?,?,,,,,?,?,0x0022ED2C-?,?,?, +Unknown MS math lib,false,0x00182B81-0x0018694F,?,?,?,,,,,?,?,?,?,?, +Another (tiny) Smilebit math lib,false,0x00186950-0x00186B7F,?,?,?,,,,,?,?,?,?,?, +Direct3D8 (need to decompose),false,,?,,0x0018CB40-0x0019E334,,,,,?,?,?,?,?, +DirectSound8 (need to decompose),false,,?,?,,,0x0019E340-0x001BA89B,,,,?,?,?,?,0x0027E080-0x00284E17 +MMatrix.obj,false,,?,,,,0x001BA8A0-0x001BBAAF,,,,,?,?,0x00264BD8-0x00264C13, +Xgraphics (need to decompose),false,,?,,,,,0x001BBAC0-0x001BC7BB,,,,?,?,, +XDK Peripherals (need to decompose),false,,?,,,,,,0x001BC7C0-0x001C3F57,,,?,?,, diff --git a/delink/symboltable.tsv b/delink/symboltable.tsv index 2061e04..94b9f94 100644 --- a/delink/symboltable.tsv +++ b/delink/symboltable.tsv @@ -249,7 +249,7 @@ restoreHeldSouls 00039f60 f getSoulCount 00039fd0 f getTotalSoulsInStage 0003a0a0 f getHeldSoulsInStage 0003a130 f -setSoulCollectedBySize 0003a2b0 f +getSoulCollectedBySize 0003a2b0 f setUnusedPerStageBitmask 0003a2f0 f getTagState 0003a340 f setTagState 0003a3a0 f @@ -904,9 +904,9 @@ initUnknownStatic44 0018c5b0 f initUnknownStatic45 0018c6f0 f initUnknownStatic46 0018c810 f ~UnknownStatic06 0018c980 f -~GameData 0018c9a0 f +finalizeGameData 0018c9a0 f ~UnknownStatic13 0018c9f0 f -~GraphicsSettings 0018caa0 f +finalizeGraphicsSettings 0018caa0 f ~UnknownStatic25 0018caf0 f ~PerformanceCounter 0018cb10 f SetVerticalBlankCallback 0018ce30 f diff --git a/readme.md b/readme.md index dcefa7a..91f5012 100644 --- a/readme.md +++ b/readme.md @@ -2,8 +2,8 @@ A matching decompilation of the Xbox game Jet Set Radio Future. ## Progress -- Delinking progress: 1.02% (26325 out of 2574172 bytes in XBE address space) -- Decompilation progress: 18.3% (30 out of the 164 functions delinked so far) +- Delinking progress: 1.02% (26359 out of 2574172 bytes in XBE address space) +- Decompilation progress: 18.7% (31 out of the 166 functions delinked so far) - **Estimated total progress: 0.19%** (previous two multiplied together) ## Roadmap