From 7e9d3bde659c1307852e2c5d0d3d8731b43309f1 Mon Sep 17 00:00:00 2001 From: MS Date: Sun, 14 Jan 2024 16:28:46 -0500 Subject: [PATCH] Reccmp: Use symbol names in asm output (#433) * Name substitution for reccmp asm output * Decomp marker corrections * Fix a few annotations * Fix IslePathActor dtor * Fix audio presenter * Fix LegoEntity::Create * Fix Pizza and related * Fix path part * Add missing annotations * Add missing annotations * Add more missing annotations * Fix MxNotificationParam * More fixes * More fixes * Add missing annotations * Fixes * More annotations * More annotations * More annotations * More annotations * Fixes and annotations * Find imports and thunk functions * Fix more bugs * Add some markers for LEGO1 imports, fix SIZE comment * Add more annotations * Rename annotation * Fix bugs and annotations * Fix bug * Order * Update legoanimpresenter.h * Re-enable print-rec-addr option --------- Co-authored-by: Christian Semmler --- LEGO1/define.cpp | 22 +- LEGO1/lego/legoomni/include/isleactor.h | 6 + LEGO1/lego/legoomni/include/islepathactor.h | 3 + LEGO1/lego/legoomni/include/legoactor.h | 4 + .../lego/legoomni/include/legoanimpresenter.h | 4 + .../include/legoeventnotificationparam.h | 8 +- .../legoomni/include/legohideanimpresenter.h | 4 + .../lego/legoomni/include/legoinputmanager.h | 6 + .../include/legoloopinganimpresenter.h | 6 + LEGO1/lego/legoomni/include/legostream.h | 18 +- .../legoomni/include/legounknown100d7c88.h | 1 + .../legoomni/include/legounknown100d9d00.h | 6 + LEGO1/lego/legoomni/src/common/gifmanager.cpp | 1 + .../src/common/legobackgroundcolor.cpp | 5 + .../src/common/legofullscreenmovie.cpp | 4 +- .../legoomni/src/common/legogamestate.cpp | 14 +- LEGO1/lego/legoomni/src/entity/legoactor.cpp | 6 + LEGO1/lego/legoomni/src/entity/legoentity.cpp | 2 +- .../lego/legoomni/src/isle/islepathactor.cpp | 7 + LEGO1/lego/legoomni/src/main/legoomni.cpp | 4 +- .../legoomni/src/video/legoanimpresenter.cpp | 6 + .../src/video/legohideanimpresenter.cpp | 6 + LEGO1/lego/sources/3dmanager/legoview1.h | 2 +- LEGO1/library_msvc.h | 76 +++ LEGO1/mxdirectx/mxdirect3d.h | 11 +- LEGO1/mxdirectx/mxstopwatch.h | 6 + .../omni/include/mxactionnotificationparam.h | 12 + LEGO1/omni/include/mxatomidcounter.h | 46 ++ LEGO1/omni/include/mxaudiopresenter.h | 6 + LEGO1/omni/include/mxdiskstreamcontroller.h | 5 +- LEGO1/omni/include/mxdiskstreamprovider.h | 15 +- LEGO1/omni/include/mxdsaction.h | 1 + LEGO1/omni/include/mxdsactionlist.h | 3 + LEGO1/omni/include/mxdsevent.h | 1 + LEGO1/omni/include/mxdssource.h | 6 + LEGO1/omni/include/mxnotificationmanager.h | 30 ++ LEGO1/omni/include/mxnotificationparam.h | 13 +- LEGO1/omni/include/mxomnicreateparam.h | 4 + LEGO1/omni/include/mxparam.h | 9 + LEGO1/omni/include/mxpresenterlist.h | 3 + LEGO1/omni/include/mxrectlist.h | 3 + LEGO1/omni/include/mxregionlist.h | 18 + LEGO1/omni/include/mxsemaphore.h | 3 +- LEGO1/omni/include/mxstreamchunklist.h | 3 + LEGO1/omni/include/mxstreamer.h | 18 +- LEGO1/omni/include/mxstreamprovider.h | 3 + LEGO1/omni/include/mxstringlist.h | 15 + LEGO1/omni/include/mxticklemanager.h | 6 + LEGO1/omni/include/mxvariable.h | 7 +- LEGO1/omni/include/mxvariabletable.h | 6 + LEGO1/omni/include/mxvideopresenter.h | 1 + LEGO1/omni/src/action/mxdsaction.cpp | 6 + LEGO1/omni/src/event/mxeventpresenter.cpp | 2 +- .../omni/src/stream/mxdiskstreamprovider.cpp | 9 +- LEGO1/omni/src/stream/mxramstreamprovider.cpp | 4 +- LEGO1/omni/src/video/mxdisplaysurface.cpp | 1 + LEGO1/omni/src/video/mxstillpresenter.cpp | 4 +- LEGO1/realtime/orientableroi.h | 9 +- LEGO1/realtime/realtimeview.cpp | 3 +- LEGO1/realtime/roi.h | 4 + LEGO1/tgl/d3drm/impl.h | 18 +- LEGO1/tgl/tgl.h | 20 +- LEGO1/viewmanager/viewlodlist.h | 16 + tools/isledecomp/isledecomp/bin.py | 88 ++++ .../isledecomp/compare/asm/__init__.py | 2 + .../isledecomp/compare/asm/parse.py | 152 ++++++ .../isledecomp/isledecomp/compare/asm/swap.py | 80 +++ tools/isledecomp/isledecomp/compare/core.py | 144 +++++- tools/isledecomp/isledecomp/compare/db.py | 66 ++- tools/isledecomp/isledecomp/cvdump/parser.py | 2 +- tools/isledecomp/isledecomp/utils.py | 14 - tools/isledecomp/tests/test_sanitize.py | 179 +++++++ tools/reccmp/reccmp.py | 486 ++++++------------ 73 files changed, 1357 insertions(+), 427 deletions(-) create mode 100644 tools/isledecomp/isledecomp/compare/asm/__init__.py create mode 100644 tools/isledecomp/isledecomp/compare/asm/parse.py create mode 100644 tools/isledecomp/isledecomp/compare/asm/swap.py create mode 100644 tools/isledecomp/tests/test_sanitize.py diff --git a/LEGO1/define.cpp b/LEGO1/define.cpp index 3f3f70ac..884c8787 100644 --- a/LEGO1/define.cpp +++ b/LEGO1/define.cpp @@ -10,20 +10,20 @@ MxS32 g_mxcoreCount[101] = {0, -6643, -5643, -5058, -4643, -4321, -4058, -38 -358, -340, -321, -304, -286, -268, -251, -234, -217, -200, -184, -168, -152, -136, -120, -104, -89, -74, -58, -43, -29, -14, 0}; -// GLOBAL: LEGO1 0x10101eac -const char* g_parseExtraTokens = ":;"; +// GLOBAL: LEGO1 0x10102048 +const char* g_strACTION = "ACTION"; -// GLOBAL: LEGO1 0x10101edc -const char* g_strWORLD = "WORLD"; - -// GLOBAL: LEGO1 0x10101f20 -const char* g_strSOUND = "SOUND"; - -// GLOBAL: LEGO1 0x10101f58 +// GLOBAL: LEGO1 0x1010209c const char* g_strOBJECT = "OBJECT"; -// GLOBAL: LEGO1 0x10102040 -const char* g_strACTION = "ACTION"; +// GLOBAL: LEGO1 0x101020b0 +const char* g_strSOUND = "SOUND"; // GLOBAL: LEGO1 0x101020cc const char* g_strVISIBILITY = "VISIBILITY"; + +// GLOBAL: LEGO1 0x101020d0 +const char* g_strWORLD = "WORLD"; + +// GLOBAL: LEGO1 0x101020e4 +const char* g_parseExtraTokens = ":;"; diff --git a/LEGO1/lego/legoomni/include/isleactor.h b/LEGO1/lego/legoomni/include/isleactor.h index 8fd7d039..1c2ddc5c 100644 --- a/LEGO1/lego/legoomni/include/isleactor.h +++ b/LEGO1/lego/legoomni/include/isleactor.h @@ -20,4 +20,10 @@ class IsleActor : public LegoActor { } }; +// SYNTHETIC: LEGO1 0x1000e940 +// IsleActor::~IsleActor + +// SYNTHETIC: LEGO1 0x1000e990 +// IsleActor::`scalar deleting destructor' + #endif // ISLEACTOR_H diff --git a/LEGO1/lego/legoomni/include/islepathactor.h b/LEGO1/lego/legoomni/include/islepathactor.h index 559e4bca..96a3dce7 100644 --- a/LEGO1/lego/legoomni/include/islepathactor.h +++ b/LEGO1/lego/legoomni/include/islepathactor.h @@ -13,6 +13,8 @@ class IslePathActor : public LegoPathActor { public: IslePathActor(); + + // FUNCTION: LEGO1 0x10002e10 inline virtual ~IslePathActor() override { IslePathActor::Destroy(TRUE); } // vtable+0x0 // FUNCTION: LEGO1 0x10002ea0 @@ -29,6 +31,7 @@ class IslePathActor : public LegoPathActor { } virtual MxResult Create(MxDSAction& p_dsAction) override; // vtable+0x18 + virtual void Destroy(MxBool p_fromDestructor) override; // vtable+0x1c // FUNCTION: LEGO1 0x10002e70 virtual MxU32 VTable0xcc() { return 0; } // vtable+0xcc // FUNCTION: LEGO1 0x10002df0 diff --git a/LEGO1/lego/legoomni/include/legoactor.h b/LEGO1/lego/legoomni/include/legoactor.h index 95c991df..a183a249 100644 --- a/LEGO1/lego/legoomni/include/legoactor.h +++ b/LEGO1/lego/legoomni/include/legoactor.h @@ -9,6 +9,7 @@ class LegoActor : public LegoEntity { public: LegoActor(); + virtual ~LegoActor() override; // FUNCTION: LEGO1 0x1002d210 inline virtual const char* ClassName() const override // vtable+0x0c @@ -43,4 +44,7 @@ class LegoActor : public LegoEntity { MxU8 m_unk0x74; // 0x74 }; +// SYNTHETIC: LEGO1 0x1002d300 +// LegoActor::`scalar deleting destructor' + #endif // LEGOACTOR_H diff --git a/LEGO1/lego/legoomni/include/legoanimpresenter.h b/LEGO1/lego/legoomni/include/legoanimpresenter.h index de8e91d9..3d43dd9f 100644 --- a/LEGO1/lego/legoomni/include/legoanimpresenter.h +++ b/LEGO1/lego/legoomni/include/legoanimpresenter.h @@ -7,6 +7,7 @@ class LegoAnimPresenter : public MxVideoPresenter { public: LegoAnimPresenter(); + virtual ~LegoAnimPresenter() override; // FUNCTION: LEGO1 0x10068530 inline virtual const char* ClassName() const override // vtable+0x0c @@ -25,4 +26,7 @@ class LegoAnimPresenter : public MxVideoPresenter { void Init(); }; +// SYNTHETIC: LEGO1 0x10068650 +// LegoAnimPresenter::`scalar deleting destructor' + #endif // LEGOANIMPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legoeventnotificationparam.h b/LEGO1/lego/legoomni/include/legoeventnotificationparam.h index c9087909..8f752cce 100644 --- a/LEGO1/lego/legoomni/include/legoeventnotificationparam.h +++ b/LEGO1/lego/legoomni/include/legoeventnotificationparam.h @@ -7,6 +7,7 @@ #include // VTABLE: LEGO1 0x100d6aa0 +// SIZE 0x20 class LegoEventNotificationParam : public MxNotificationParam { public: inline LegoEventNotificationParam() : MxNotificationParam(PARAM_NONE, NULL) {} @@ -22,7 +23,6 @@ class LegoEventNotificationParam : public MxNotificationParam { { } - virtual ~LegoEventNotificationParam() override {} // vtable+0x0 (scalar deleting destructor) inline MxU8 GetKey() const { return m_key; } protected: @@ -33,4 +33,10 @@ class LegoEventNotificationParam : public MxNotificationParam { MxU32 m_unk0x1c; // 0x1c }; +// SYNTHETIC: LEGO1 0x10028770 +// LegoEventNotificationParam::`scalar deleting destructor' + +// SYNTHETIC: LEGO1 0x100287e0 +// LegoEventNotificationParam::~LegoEventNotificationParam + #endif // LEGOEVENTNOTIFICATIONPARAM_H diff --git a/LEGO1/lego/legoomni/include/legohideanimpresenter.h b/LEGO1/lego/legoomni/include/legohideanimpresenter.h index f6e86c2e..fda96c98 100644 --- a/LEGO1/lego/legoomni/include/legohideanimpresenter.h +++ b/LEGO1/lego/legoomni/include/legohideanimpresenter.h @@ -8,6 +8,7 @@ class LegoHideAnimPresenter : public LegoLoopingAnimPresenter { public: LegoHideAnimPresenter(); + virtual ~LegoHideAnimPresenter() override; // FUNCTION: LEGO1 0x1006d880 inline const char* ClassName() const override // vtable+0xc @@ -26,4 +27,7 @@ class LegoHideAnimPresenter : public LegoLoopingAnimPresenter { void Init(); }; +// SYNTHETIC: LEGO1 0x1006d9d0 +// LegoHideAnimPresenter::`scalar deleting destructor' + #endif // LEGOHIDEANIMPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legoinputmanager.h b/LEGO1/lego/legoomni/include/legoinputmanager.h index 961d8ffc..59e0a949 100644 --- a/LEGO1/lego/legoomni/include/legoinputmanager.h +++ b/LEGO1/lego/legoomni/include/legoinputmanager.h @@ -94,6 +94,9 @@ class LegoInputManager : public MxPresenter { // TEMPLATE: LEGO1 0x1005bb80 // MxCollection::Compare +// TEMPLATE: LEGO1 0x1005bbe0 +// MxCollection::~MxCollection + // TEMPLATE: LEGO1 0x1005bc30 // MxCollection::Destroy @@ -109,6 +112,9 @@ class LegoInputManager : public MxPresenter { // SYNTHETIC: LEGO1 0x1005beb0 // LegoEventQueue::`scalar deleting destructor' +// TEMPLATE: LEGO1 0x1005bf20 +// MxQueue::~MxQueue + // SYNTHETIC: LEGO1 0x1005bf70 // MxQueue::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/include/legoloopinganimpresenter.h b/LEGO1/lego/legoomni/include/legoloopinganimpresenter.h index a17c671c..844d1586 100644 --- a/LEGO1/lego/legoomni/include/legoloopinganimpresenter.h +++ b/LEGO1/lego/legoomni/include/legoloopinganimpresenter.h @@ -21,4 +21,10 @@ class LegoLoopingAnimPresenter : public LegoAnimPresenter { } }; +// SYNTHETIC: LEGO1 0x1006d000 +// LegoLoopingAnimPresenter::~LegoLoopingAnimPresenter + +// SYNTHETIC: LEGO1 0x1000f440 +// LegoLoopingAnimPresenter::`scalar deleting destructor' + #endif // LEGOLOOPINGANIMPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legostream.h b/LEGO1/lego/legoomni/include/legostream.h index 8b0a57c1..fe21ddba 100644 --- a/LEGO1/lego/legoomni/include/legostream.h +++ b/LEGO1/lego/legoomni/include/legostream.h @@ -18,7 +18,8 @@ class MxVariableTable; class LegoStream { public: LegoStream() : m_mode(0) {} - inline virtual ~LegoStream(){}; + // FUNCTION: LEGO1 0x10045ad0 + inline virtual ~LegoStream() {} virtual MxResult Read(void* p_buffer, MxU32 p_size) = 0; virtual MxResult Write(const void* p_buffer, MxU32 p_size) = 0; @@ -41,11 +42,14 @@ class LegoStream { MxU8 m_mode; }; +// SYNTHETIC: LEGO1 0x10045b00 +// LegoStream::`scalar deleting destructor' + // VTABLE: LEGO1 0x100db730 class LegoFileStream : public LegoStream { public: LegoFileStream(); - virtual ~LegoFileStream(); + virtual ~LegoFileStream() override; MxResult Read(void* p_buffer, MxU32 p_size) override; MxResult Write(const void* p_buffer, MxU32 p_size) override; @@ -60,11 +64,13 @@ class LegoFileStream : public LegoStream { FILE* m_hFile; }; +// SYNTHETIC: LEGO1 0x10099230 +// LegoFileStream::`scalar deleting destructor' + // VTABLE: LEGO1 0x100db710 class LegoMemoryStream : public LegoStream { public: LegoMemoryStream(char* p_buffer); - ~LegoMemoryStream() {} MxResult Read(void* p_buffer, MxU32 p_size) override; MxResult Write(const void* p_buffer, MxU32 p_size) override; @@ -76,4 +82,10 @@ class LegoMemoryStream : public LegoStream { MxU32 m_offset; }; +// SYNTHETIC: LEGO1 0x10045a80 +// LegoMemoryStream::~LegoMemoryStream + +// SYNTHETIC: LEGO1 0x100990f0 +// LegoMemoryStream::`scalar deleting destructor' + #endif // LEGOSTREAM_H diff --git a/LEGO1/lego/legoomni/include/legounknown100d7c88.h b/LEGO1/lego/legoomni/include/legounknown100d7c88.h index a6004053..2fa960c8 100644 --- a/LEGO1/lego/legoomni/include/legounknown100d7c88.h +++ b/LEGO1/lego/legoomni/include/legounknown100d7c88.h @@ -4,6 +4,7 @@ #include "decomp.h" #include "mxstring.h" +// VTABLE: LEGO1 0x100d7c88 class LegoUnknown100d7c88 { public: ~LegoUnknown100d7c88(); diff --git a/LEGO1/lego/legoomni/include/legounknown100d9d00.h b/LEGO1/lego/legoomni/include/legounknown100d9d00.h index d31634fe..394ffbbd 100644 --- a/LEGO1/lego/legoomni/include/legounknown100d9d00.h +++ b/LEGO1/lego/legoomni/include/legounknown100d9d00.h @@ -27,9 +27,15 @@ class LegoUnknown100d9d00 : public MxList { // TEMPLATE: LEGO1 0x1007b300 // MxCollection::Compare +// TEMPLATE: LEGO1 0x1007b310 +// MxCollection::~MxCollection + // TEMPLATE: LEGO1 0x1007b360 // MxCollection::Destroy +// TEMPLATE: LEGO1 0x1007b370 +// MxList::~MxList + // SYNTHETIC: LEGO1 0x1007b400 // LegoUnknown100d9d00::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/src/common/gifmanager.cpp b/LEGO1/lego/legoomni/src/common/gifmanager.cpp index 0fc25b96..e9b10d49 100644 --- a/LEGO1/lego/legoomni/src/common/gifmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/gifmanager.cpp @@ -6,6 +6,7 @@ DECOMP_SIZE_ASSERT(GifMap, 0x08); DECOMP_SIZE_ASSERT(GifManagerBase, 0x14); DECOMP_SIZE_ASSERT(GifManager, 0x30); +// GLOBAL: LEGO1 0x100f0100 GifMapEntry* g_unk0x100f0100; // FUNCTION: LEGO1 0x10001cc0 diff --git a/LEGO1/lego/legoomni/src/common/legobackgroundcolor.cpp b/LEGO1/lego/legoomni/src/common/legobackgroundcolor.cpp index e682356b..117bc0cc 100644 --- a/LEGO1/lego/legoomni/src/common/legobackgroundcolor.cpp +++ b/LEGO1/lego/legoomni/src/common/legobackgroundcolor.cpp @@ -7,8 +7,13 @@ DECOMP_SIZE_ASSERT(LegoBackgroundColor, 0x30) +// GLOBAL: LEGO1 0x100f3fb0 const char* g_delimiter = "\t"; + +// GLOBAL: LEGO1 0x100f3fb4 const char* g_set = "set"; + +// GLOBAL: LEGO1 0x100f3fb8 const char* g_reset = "reset"; // FUNCTION: LEGO1 0x1003bfb0 diff --git a/LEGO1/lego/legoomni/src/common/legofullscreenmovie.cpp b/LEGO1/lego/legoomni/src/common/legofullscreenmovie.cpp index 110a88d0..7fcdce59 100644 --- a/LEGO1/lego/legoomni/src/common/legofullscreenmovie.cpp +++ b/LEGO1/lego/legoomni/src/common/legofullscreenmovie.cpp @@ -7,10 +7,10 @@ DECOMP_SIZE_ASSERT(LegoFullScreenMovie, 0x24) -// GLOBAL: LEGO1 0x100f3be8 +// GLOBAL: LEGO1 0x100f3fbc const char* g_strEnable = "enable"; -// GLOBAL: LEGO1 0x100f3bf4 +// GLOBAL: LEGO1 0x100f3fc0 const char* g_strDisable = "disable"; // FUNCTION: LEGO1 0x1003c500 diff --git a/LEGO1/lego/legoomni/src/common/legogamestate.cpp b/LEGO1/lego/legoomni/src/common/legogamestate.cpp index 4bf4ab82..2291b72c 100644 --- a/LEGO1/lego/legoomni/src/common/legogamestate.cpp +++ b/LEGO1/lego/legoomni/src/common/legogamestate.cpp @@ -19,15 +19,15 @@ // There may be other members that come after. DECOMP_SIZE_ASSERT(LegoGameState, 0x430) -// GLOBAL: LEGO1 0x100f3e24 -const char* g_historyGSI = "History.gsi"; - -// GLOBAL: LEGO1 0x100f3e30 -const char* g_playersGSI = "Players.gsi"; - // GLOBAL: LEGO1 0x100f3e40 const char* g_fileExtensionGS = ".GS"; +// GLOBAL: LEGO1 0x100f3e44 +const char* g_playersGSI = "Players.gsi"; + +// GLOBAL: LEGO1 0x100f3e48 +const char* g_historyGSI = "History.gsi"; + // GLOBAL: LEGO1 0x100f3e58 ColorStringStruct g_colorSaveData[43] = { {"c_dbbkfny0", "lego red"}, {"c_dbbkxly0", "lego white"}, {"c_chbasey0", "lego black"}, @@ -168,7 +168,7 @@ void LegoGameState::GetFileSavePath(MxString* p_outPath, MxULong p_slotn) strcpy(path, m_savePath); // Slot: "G0", "G1", ... - strcat(path, "G"); + strcat(path, "\\G"); baseForSlot[0] += p_slotn; strcat(path, baseForSlot); diff --git a/LEGO1/lego/legoomni/src/entity/legoactor.cpp b/LEGO1/lego/legoomni/src/entity/legoactor.cpp index 46f59a77..e9010ef4 100644 --- a/LEGO1/lego/legoomni/src/entity/legoactor.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoactor.cpp @@ -11,3 +11,9 @@ LegoActor::LegoActor() m_unk0x10 = 0; m_unk0x74 = 0; } + +// STUB: LEGO1 0x1002d320 +LegoActor::~LegoActor() +{ + // TODO +} diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index e1a8c6f4..7e377671 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -46,7 +46,7 @@ MxResult LegoEntity::Create(MxDSAction& p_dsAction) { m_mxEntityId = p_dsAction.GetObjectId(); m_atom = p_dsAction.GetAtomId(); - Init(); + SetWorld(); return SUCCESS; } diff --git a/LEGO1/lego/legoomni/src/isle/islepathactor.cpp b/LEGO1/lego/legoomni/src/isle/islepathactor.cpp index 036b3298..76ec41bd 100644 --- a/LEGO1/lego/legoomni/src/isle/islepathactor.cpp +++ b/LEGO1/lego/legoomni/src/isle/islepathactor.cpp @@ -17,6 +17,13 @@ MxResult IslePathActor::Create(MxDSAction& p_dsAction) return MxEntity::Create(p_dsAction); } +// FUNCTION: LEGO1 0x1001a2a0 +void IslePathActor::Destroy(MxBool p_fromDestructor) +{ + if (!p_fromDestructor) + LegoPathActor::Destroy(FALSE); +} + // STUB: LEGO1 0x1001a350 void IslePathActor::VTable0xe0() { diff --git a/LEGO1/lego/legoomni/src/main/legoomni.cpp b/LEGO1/lego/legoomni/src/main/legoomni.cpp index 5bb47d0c..9cb67852 100644 --- a/LEGO1/lego/legoomni/src/main/legoomni.cpp +++ b/LEGO1/lego/legoomni/src/main/legoomni.cpp @@ -100,7 +100,7 @@ MxAtomId* g_testScript = NULL; // GLOBAL: LEGO1 0x100f457c MxAtomId* g_jukeboxwScript = NULL; -// GLOBAL: LEGO1 0x100f4580c +// GLOBAL: LEGO1 0x100f4580 MxAtomId* g_sndAnimScript = NULL; // GLOBAL: LEGO1 0x100f4584 @@ -118,7 +118,7 @@ MxBool g_isWorldActive = TRUE; // FUNCTION: LEGO1 0x10015700 LegoOmni* Lego() { - return (LegoOmni*) MxOmni::GetInstance(); + return LegoOmni::GetInstance(); } // FUNCTION: LEGO1 0x10015710 diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 2bdd7475..3c0883d6 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -6,6 +6,12 @@ LegoAnimPresenter::LegoAnimPresenter() // TODO } +// STUB: LEGO1 0x10068670 +LegoAnimPresenter::~LegoAnimPresenter() +{ + // TODO +} + // STUB: LEGO1 0x100686f0 void LegoAnimPresenter::Init() { diff --git a/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp index f13165cc..9aa9f2fe 100644 --- a/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp @@ -6,6 +6,12 @@ LegoHideAnimPresenter::LegoHideAnimPresenter() Init(); } +// STUB: LEGO1 0x1006d9f0 +LegoHideAnimPresenter::~LegoHideAnimPresenter() +{ + // TODO +} + // STUB: LEGO1 0x1006da50 void LegoHideAnimPresenter::Init() { diff --git a/LEGO1/lego/sources/3dmanager/legoview1.h b/LEGO1/lego/sources/3dmanager/legoview1.h index bfee4e3d..4060e54f 100644 --- a/LEGO1/lego/sources/3dmanager/legoview1.h +++ b/LEGO1/lego/sources/3dmanager/legoview1.h @@ -13,7 +13,7 @@ class Camera; ///////////////////////////////////////////////////////////////////////////// // LegoView -// VTABLE: 0x100dc000 +// VTABLE: LEGO1 0x100dc000 // SIZE 0x78 class LegoView : public TglSurface { public: diff --git a/LEGO1/library_msvc.h b/LEGO1/library_msvc.h index 20ae06ef..28e29187 100644 --- a/LEGO1/library_msvc.h +++ b/LEGO1/library_msvc.h @@ -18,6 +18,10 @@ // LIBRARY: LEGO1 0x1008a1c0 // _free +// LIBRARY: ISLE 0x407ec0 +// LIBRARY: LEGO1 0x1008b020 +// ___CxxFrameHandler + // LIBRARY: ISLE 0x408220 // LIBRARY: LEGO1 0x1008b400 // _atol @@ -33,6 +37,60 @@ // LIBRARY: LEGO1 0x1008b5a0 // _sprintf +// LIBRARY: LEGO1 0x1008b608 +// __ftol + +// LIBRARY: LEGO1 0x1008b630 +// _srand + +// LIBRARY: LEGO1 0x1008b680 +// _strncmp + +// LIBRARY: LEGO1 0x1008b730 +// _fprintf + +// LIBRARY: LEGO1 0x1008b780 +// _fwrite + +// LIBRARY: LEGO1 0x1008b950 +// _fread + +// LIBRARY: LEGO1 0x1008bbd0 +// _fclose + +// LIBRARY: LEGO1 0x1008bdd0 +// _ftell + +// LIBRARY: LEGO1 0x1008bff0 +// _fopen + +// LIBRARY: LEGO1 0x1008c010 +// _strncpy + +// LIBRARY: LEGO1 0x1008c110 +// __strcmpi + +// LIBRARY: LEGO1 0x1008c1e0 +// __spawnl + +// LIBRARY: LEGO1 0x1008c200 +// _sscanf + +// LIBRARY: LEGO1 0x1008c410 +// _strlwr + +// LIBRARY: LEGO1 0x1008c5c0 +// _fseek + +// LIBRARY: LEGO1 0x1008ca60 +// _abort + +// LIBRARY: LEGO1 0x100977c0 +// _itoa + +// LIBRARY: LEGO1 0x10097b10 +// _strchr + // LIBRARY: ISLE 0x4081e0 // _srand @@ -46,4 +104,22 @@ // LIBRARY: ISLE 0x409190 // __getptd +// LIBRARY: LEGO1 0x100d1ed0 +// _strnicmp + +// LIBRARY: LEGO1 0x100d1fd0 +// _strupr + +// LIBRARY: LEGO1 0x100d2130 +// _vsprintf + +// LIBRARY: LEGO1 0x100d21c2 +// __CIpow + +// LIBRARY: LEGO1 0x100d21f0 +// _strstr + +// LIBRARY: LEGO1 0x100d2270 +// __beginthreadex + #endif diff --git a/LEGO1/mxdirectx/mxdirect3d.h b/LEGO1/mxdirectx/mxdirect3d.h index f0c87c69..ab572c54 100644 --- a/LEGO1/mxdirectx/mxdirect3d.h +++ b/LEGO1/mxdirectx/mxdirect3d.h @@ -158,11 +158,15 @@ struct MxDriver { // List::~List // Compiler-generated copy ctor -// Part of this function are two more synthetic sub-routines, -// LEGO1 0x1009c400 and LEGO1 0x1009c460 // SYNTHETIC: LEGO1 0x1009c290 // MxDriver::MxDriver +// SYNTHETIC: LEGO1 0x1009c400 +// list >::insert + +// SYNTHETIC: LEGO1 0x1009c460 +// list >::insert + // SYNTHETIC: LEGO1 0x1009d450 // MxDriver::`scalar deleting destructor' @@ -223,4 +227,7 @@ class MxDeviceEnumerate { // SIZE 0x14 class MxDeviceEnumerate100d9cc8 : public MxDeviceEnumerate {}; +// SYNTHETIC: LEGO1 0x1007b590 +// MxDeviceEnumerate100d9cc8::~MxDeviceEnumerate100d9cc8 + #endif // MXDIRECT3D_H diff --git a/LEGO1/mxdirectx/mxstopwatch.h b/LEGO1/mxdirectx/mxstopwatch.h index 7e7fcc38..0630d2f6 100644 --- a/LEGO1/mxdirectx/mxstopwatch.h +++ b/LEGO1/mxdirectx/mxstopwatch.h @@ -97,6 +97,9 @@ inline double MxStopWatch::ElapsedSeconds() const return m_elapsedSeconds; } +// SYNTHETIC: LEGO1 0x100a6fc0 +// MxStopWatch::~MxStopWatch + ////////////////////////////////////////////////////////////////////////////// // // MxFrequencyMeter @@ -182,4 +185,7 @@ inline double MxFrequencyMeter::ElapsedSeconds() const return m_stopWatch.ElapsedSeconds(); } +// SYNTHETIC: LEGO1 0x100abd10 +// MxFrequencyMeter::~MxFrequencyMeter + #endif /* _MxStopWatch_h */ diff --git a/LEGO1/omni/include/mxactionnotificationparam.h b/LEGO1/omni/include/mxactionnotificationparam.h index 750abd6d..d4e02124 100644 --- a/LEGO1/omni/include/mxactionnotificationparam.h +++ b/LEGO1/omni/include/mxactionnotificationparam.h @@ -115,13 +115,25 @@ class MxType4NotificationParam : public MxActionNotificationParam { MxPresenter* m_unk0x14; // 0x14 }; +// SYNTHETIC: LEGO1 0x100511e0 +// MxActionNotificationParam::`scalar deleting destructor' + // SYNTHETIC: LEGO1 0x100513a0 // MxEndActionNotificationParam::`scalar deleting destructor' +// SYNTHETIC: LEGO1 0x10051410 +// MxEndActionNotificationParam::~MxEndActionNotificationParam + // SYNTHETIC: LEGO1 0x100b0430 // MxStartActionNotificationParam::`scalar deleting destructor' +// SYNTHETIC: LEGO1 0x100b04a0 +// MxStartActionNotificationParam::~MxStartActionNotificationParam + // SYNTHETIC: LEGO1 0x100b05c0 // MxType4NotificationParam::`scalar deleting destructor' +// SYNTHETIC: LEGO1 0x100b0630 +// MxType4NotificationParam::~MxType4NotificationParam + #endif diff --git a/LEGO1/omni/include/mxatomidcounter.h b/LEGO1/omni/include/mxatomidcounter.h index f2c4fb3f..79a98f34 100644 --- a/LEGO1/omni/include/mxatomidcounter.h +++ b/LEGO1/omni/include/mxatomidcounter.h @@ -43,4 +43,50 @@ struct MxAtomIdCounterCompare { class MxAtomIdCounterSet : public set {}; +// SYNTHETIC: LEGO1 0x100ad170 +// MxAtomIdCounter::~MxAtomIdCounter + +// clang-format off +// TEMPLATE: LEGO1 0x100ad480 +// _Tree >::_Kfn,MxAtomIdCounterCompare,allocator >::iterator::_Dec +// clang-format on + +// clang-format off +// TEMPLATE: LEGO1 0x100ad780 +// _Tree >::_Kfn,MxAtomIdCounterCompare,allocator >::_Lbound +// clang-format on + +// clang-format off +// TEMPLATE: LEGO1 0x100ad4d0 +// _Tree >::_Kfn,MxAtomIdCounterCompare,allocator >::_Insert +// clang-format on + +// clang-format off +// TEMPLATE: LEGO1 0x100af6d0 +// _Tree >::_Kfn,MxAtomIdCounterCompare,allocator >::~_Tree >::_Kfn,MxAtomIdCounterCompare,allocator >::iterator::_Inc +// clang-format on + +// clang-format off +// TEMPLATE: LEGO1 0x100af7e0 +// _Tree >::_Kfn,MxAtomIdCounterCompare,allocator >::erase +// clang-format on + +// clang-format off +// TEMPLATE: LEGO1 0x100afc40 +// _Tree >::_Kfn,MxAtomIdCounterCompare,allocator >::_Erase +// clang-format on + +// clang-format off +// TEMPLATE: LEGO1 0x100afc80 +// set >::~set > +// clang-format on + +// TEMPLATE: LEGO1 0x100afe40 +// Set::~Set + #endif // MXATOMIDCOUNTER_H diff --git a/LEGO1/omni/include/mxaudiopresenter.h b/LEGO1/omni/include/mxaudiopresenter.h index 4033f84d..82e4b48a 100644 --- a/LEGO1/omni/include/mxaudiopresenter.h +++ b/LEGO1/omni/include/mxaudiopresenter.h @@ -33,4 +33,10 @@ class MxAudioPresenter : public MxMediaPresenter { MxS32 m_volume; }; +// SYNTHETIC: LEGO1 0x1000d370 +// MxAudioPresenter::`scalar deleting destructor' + +// SYNTHETIC: LEGO1 0x1000d3e0 +// MxAudioPresenter::~MxAudioPresenter + #endif // MXAUDIOPRESENTER_H diff --git a/LEGO1/omni/include/mxdiskstreamcontroller.h b/LEGO1/omni/include/mxdiskstreamcontroller.h index 01ddc78a..9e476afc 100644 --- a/LEGO1/omni/include/mxdiskstreamcontroller.h +++ b/LEGO1/omni/include/mxdiskstreamcontroller.h @@ -46,6 +46,7 @@ class MxDiskStreamController : public MxStreamController { void FUN_100c8670(MxDSStreamingAction* p_streamingAction); void InsertToList74(MxDSBuffer* p_buffer); void FUN_100c7cb0(MxDSStreamingAction* p_action); + MxResult FUN_100c7890(MxDSStreamingAction* p_action); private: MxStreamListMxDSAction m_list0x64; // 0x64 @@ -58,7 +59,6 @@ class MxDiskStreamController : public MxStreamController { MxStreamListMxDSAction m_list0xb8; // 0xb8 MxBool m_unk0xc4; // 0xc4 - MxResult FUN_100c7890(MxDSStreamingAction* p_action); void FUN_100c7970(); void FUN_100c7ce0(MxDSBuffer* p_buffer); MxResult FUN_100c7d10(); @@ -69,6 +69,9 @@ class MxDiskStreamController : public MxStreamController { void FUN_100c8720(); }; +// TEMPLATE: LEGO1 0x100c14d0 +// list >::erase + // TEMPLATE: LEGO1 0x100c7330 // list >::_Buynode diff --git a/LEGO1/omni/include/mxdiskstreamprovider.h b/LEGO1/omni/include/mxdiskstreamprovider.h index 6df049da..517fa463 100644 --- a/LEGO1/omni/include/mxdiskstreamprovider.h +++ b/LEGO1/omni/include/mxdiskstreamprovider.h @@ -20,9 +20,6 @@ class MxDiskStreamProviderThread : public MxThread { MxResult Run() override; MxResult StartWithTarget(MxDiskStreamProvider* p_target); - - // SYNTHETIC: LEGO1 0x100d10a0 - // MxDiskStreamProviderThread::`scalar deleting destructor' }; // VTABLE: LEGO1 0x100dd138 @@ -58,9 +55,6 @@ class MxDiskStreamProvider : public MxStreamProvider { virtual MxU32 GetLengthInDWords() override; // vtable+0x24 virtual MxU32* GetBufferForDWords() override; // vtable+0x28 - // SYNTHETIC: LEGO1 0x100d1220 - // MxDiskStreamProvider::`scalar deleting destructor' - private: MxDiskStreamProviderThread m_thread; // 0x10 MxSemaphore m_busySemaphore; // 0x2c @@ -70,4 +64,13 @@ class MxDiskStreamProvider : public MxStreamProvider { MxStreamListMxDSAction m_list; // 0x54 }; +// SYNTHETIC: LEGO1 0x100d10a0 +// MxDiskStreamProviderThread::`scalar deleting destructor' + +// SYNTHETIC: LEGO1 0x100d1110 +// MxDiskStreamProviderThread::~MxDiskStreamProviderThread + +// SYNTHETIC: LEGO1 0x100d1220 +// MxDiskStreamProvider::`scalar deleting destructor' + #endif // MXDISKSTREAMPROVIDER_H diff --git a/LEGO1/omni/include/mxdsaction.h b/LEGO1/omni/include/mxdsaction.h index b4d6192e..b51c75a2 100644 --- a/LEGO1/omni/include/mxdsaction.h +++ b/LEGO1/omni/include/mxdsaction.h @@ -43,6 +43,7 @@ class MxDSAction : public MxDSObject { return !strcmp(p_name, MxDSAction::ClassName()) || MxDSObject::IsA(p_name); } + virtual undefined4 VTable0x14() override; // vtable+14; virtual MxU32 GetSizeOnDisk() override; // vtable+18; virtual void Deserialize(MxU8** p_source, MxS16 p_unk0x24) override; // vtable+1c; virtual MxLong GetDuration(); // vtable+24; diff --git a/LEGO1/omni/include/mxdsactionlist.h b/LEGO1/omni/include/mxdsactionlist.h index 60c3db6d..7ae9ab56 100644 --- a/LEGO1/omni/include/mxdsactionlist.h +++ b/LEGO1/omni/include/mxdsactionlist.h @@ -44,6 +44,9 @@ class MxDSActionListCursor : public MxListCursor { // TEMPLATE: LEGO1 0x100c9cc0 // MxCollection::Compare +// TEMPLATE: LEGO1 0x100c9cd0 +// MxCollection::~MxCollection + // TEMPLATE: LEGO1 0x100c9d20 // MxCollection::Destroy diff --git a/LEGO1/omni/include/mxdsevent.h b/LEGO1/omni/include/mxdsevent.h index 01cf1fa4..5d002a88 100644 --- a/LEGO1/omni/include/mxdsevent.h +++ b/LEGO1/omni/include/mxdsevent.h @@ -3,6 +3,7 @@ #include "mxdsmediaaction.h" +// VTABLE: LEGO1 0x100dce18 class MxDSEvent : public MxDSMediaAction { public: MxDSEvent(); diff --git a/LEGO1/omni/include/mxdssource.h b/LEGO1/omni/include/mxdssource.h index db009fb4..143b9c78 100644 --- a/LEGO1/omni/include/mxdssource.h +++ b/LEGO1/omni/include/mxdssource.h @@ -11,6 +11,9 @@ class MxDSSource : public MxCore { public: MxDSSource() : m_lengthInDWords(0), m_pBuffer(NULL), m_position(-1) {} + // FUNCTION: LEGO1 0x100bff60 + virtual ~MxDSSource() override { delete[] m_pBuffer; } + // FUNCTION: LEGO1 0x100c0010 inline virtual const char* ClassName() const override // vtable+0x0c { @@ -41,4 +44,7 @@ class MxDSSource : public MxCore { MxLong m_position; // 0x10 }; +// SYNTHETIC: LEGO1 0x100c00a0 +// MxDSSource::`scalar deleting destructor' + #endif // MXDSSOURCE_H diff --git a/LEGO1/omni/include/mxnotificationmanager.h b/LEGO1/omni/include/mxnotificationmanager.h index 4a3d8726..b93cc7c8 100644 --- a/LEGO1/omni/include/mxnotificationmanager.h +++ b/LEGO1/omni/include/mxnotificationmanager.h @@ -52,4 +52,34 @@ class MxNotificationManager : public MxCore { void FlushPending(MxCore* p_listener); }; +// TEMPLATE: LEGO1 0x100ac320 +// list >::~list > + +// FUNCTION: LEGO1 0x100ac3b0 +// MxIdList::~MxIdList + +// TEMPLATE: LEGO1 0x100ac400 +// List::~List + +// TEMPLATE: LEGO1 0x100ac540 +// List::~List + +// TEMPLATE: LEGO1 0x100ac590 +// list >::~list > + +// TEMPLATE: LEGO1 0x100acbf0 +// list >::begin + +// TEMPLATE: LEGO1 0x100acc00 +// list >::insert + +// TEMPLATE: LEGO1 0x100acc50 +// list >::erase + +// TEMPLATE: LEGO1 0x100acca0 +// list >::_Buynode + +// SYNTHETIC: LEGO1 0x100accd0 +// MxNotificationPtrList::~MxNotificationPtrList + #endif // MXNOTIFICATIONMANAGER_H diff --git a/LEGO1/omni/include/mxnotificationparam.h b/LEGO1/omni/include/mxnotificationparam.h index 256dd882..0a8b681c 100644 --- a/LEGO1/omni/include/mxnotificationparam.h +++ b/LEGO1/omni/include/mxnotificationparam.h @@ -34,14 +34,13 @@ enum NotificationId { }; // VTABLE: LEGO1 0x100d56e0 +// SIZE 0x0c class MxNotificationParam : public MxParam { public: inline MxNotificationParam(NotificationId p_type, MxCore* p_sender) : MxParam(), m_type(p_type), m_sender(p_sender) { } - virtual ~MxNotificationParam() override {} - // FUNCTION: LEGO1 0x10010390 virtual MxNotificationParam* Clone() { return new MxNotificationParam(m_type, m_sender); }; // vtable+0x4 @@ -50,8 +49,14 @@ class MxNotificationParam : public MxParam { inline NotificationId GetType() const { return m_type; } protected: - NotificationId m_type; // 0x4 - MxCore* m_sender; // 0x8 + NotificationId m_type; // 0x04 + MxCore* m_sender; // 0x08 }; +// SYNTHETIC: LEGO1 0x10010430 +// MxNotificationParam::`scalar deleting destructor' + +// SYNTHETIC: LEGO1 0x100104a0 +// MxNotificationParam::~MxNotificationParam + #endif // MXNOTIFICATIONPARAM_H diff --git a/LEGO1/omni/include/mxomnicreateparam.h b/LEGO1/omni/include/mxomnicreateparam.h index 825b5053..5404f39a 100644 --- a/LEGO1/omni/include/mxomnicreateparam.h +++ b/LEGO1/omni/include/mxomnicreateparam.h @@ -8,6 +8,7 @@ #include +// VTABLE: LEGO1 0x100dc218 class MxOmniCreateParam : public MxParam { public: __declspec(dllexport) MxOmniCreateParam( @@ -30,4 +31,7 @@ class MxOmniCreateParam : public MxParam { MxOmniCreateFlags m_createFlags; }; +// SYNTHETIC: ISLE 0x4014b0 +// MxOmniCreateParam::~MxOmniCreateParam + #endif // MXOMNICREATEPARAM_H diff --git a/LEGO1/omni/include/mxparam.h b/LEGO1/omni/include/mxparam.h index 19e92e0e..a266b12b 100644 --- a/LEGO1/omni/include/mxparam.h +++ b/LEGO1/omni/include/mxparam.h @@ -1,9 +1,18 @@ #ifndef MXPARAM_H #define MXPARAM_H +// VTABLE: ISLE 0x40f018 +// VTABLE: LEGO1 0x100d56e8 +// SIZE 0x04 class MxParam { public: + // FUNCTION: ISLE 0x401530 + // FUNCTION: LEGO1 0x10010360 virtual ~MxParam() {} }; +// SYNTHETIC: ISLE 0x401540 +// SYNTHETIC: LEGO1 0x10010370 +// MxParam::`scalar deleting destructor' + #endif // MXPARAM_H diff --git a/LEGO1/omni/include/mxpresenterlist.h b/LEGO1/omni/include/mxpresenterlist.h index d8b74a3f..f346e645 100644 --- a/LEGO1/omni/include/mxpresenterlist.h +++ b/LEGO1/omni/include/mxpresenterlist.h @@ -53,6 +53,9 @@ class MxPresenterListCursor : public MxPtrListCursor { // TEMPLATE: LEGO1 0x1001ce20 // MxList::~MxList +// TEMPLATE: LEGO1 0x1001cf20 +// MxPtrList::~MxPtrList + // SYNTHETIC: LEGO1 0x1001cf70 // MxCollection::`scalar deleting destructor' diff --git a/LEGO1/omni/include/mxrectlist.h b/LEGO1/omni/include/mxrectlist.h index e19c4994..c25c2483 100644 --- a/LEGO1/omni/include/mxrectlist.h +++ b/LEGO1/omni/include/mxrectlist.h @@ -53,6 +53,9 @@ class MxRectListCursor : public MxPtrListCursor { // SYNTHETIC: LEGO1 0x100b3d80 // MxRectList::`scalar deleting destructor' +// TEMPLATE: LEGO1 0x100b3df0 +// MxPtrList::~MxPtrList + // SYNTHETIC: LEGO1 0x100b3e40 // MxCollection::`scalar deleting destructor' diff --git a/LEGO1/omni/include/mxregionlist.h b/LEGO1/omni/include/mxregionlist.h index 7c6da5c2..679ccebc 100644 --- a/LEGO1/omni/include/mxregionlist.h +++ b/LEGO1/omni/include/mxregionlist.h @@ -116,12 +116,21 @@ class MxRegionTopBottomListCursor : public MxPtrListCursor { // TEMPLATE: LEGO1 0x100c32e0 // MxCollection::Compare +// TEMPLATE: LEGO1 0x100c32f0 +// MxCollection::~MxCollection + // TEMPLATE: LEGO1 0x100c3340 // MxCollection::Destroy +// TEMPLATE: LEGO1 0x100c3350 +// MxList::~MxList + // TEMPLATE: LEGO1 0x100c33e0 // MxPtrList::Destroy +// TEMPLATE: LEGO1 0x100c3480 +// MxPtrList::~MxPtrList + // SYNTHETIC: LEGO1 0x100c34d0 // MxCollection::`scalar deleting destructor' @@ -167,9 +176,18 @@ class MxRegionTopBottomListCursor : public MxPtrListCursor { // TEMPLATE: LEGO1 0x100c4d80 // MxCollection::Compare +// TEMPLATE: LEGO1 0x100c4d90 +// MxCollection::~MxCollection + // TEMPLATE: LEGO1 0x100c4de0 // MxCollection::Destroy +// TEMPLATE: LEGO1 0x100c4df0 +// MxList::~MxList + +// TEMPLATE: LEGO1 0x100c4f00 +// MxPtrList::~MxPtrList + // SYNTHETIC: LEGO1 0x100c4f50 // MxCollection::`scalar deleting destructor' diff --git a/LEGO1/omni/include/mxsemaphore.h b/LEGO1/omni/include/mxsemaphore.h index ecb6509c..4e692395 100644 --- a/LEGO1/omni/include/mxsemaphore.h +++ b/LEGO1/omni/include/mxsemaphore.h @@ -5,12 +5,13 @@ #include +// VTABLE: LEGO1 0x100dccf0 // SIZE 0x08 class MxSemaphore { public: MxSemaphore(); - // Inlined only, no offset + // FUNCTION: LEGO1 0x100c87e0 ~MxSemaphore() { CloseHandle(m_hSemaphore); } virtual MxResult Init(MxU32 p_initialCount, MxU32 p_maxCount); diff --git a/LEGO1/omni/include/mxstreamchunklist.h b/LEGO1/omni/include/mxstreamchunklist.h index 12507961..2d1ce119 100644 --- a/LEGO1/omni/include/mxstreamchunklist.h +++ b/LEGO1/omni/include/mxstreamchunklist.h @@ -41,6 +41,9 @@ class MxStreamChunkListCursor : public MxListCursor { // TEMPLATE: LEGO1 0x100b5930 // MxCollection::Compare +// TEMPLATE: LEGO1 0x100b5940 +// MxCollection::~MxCollection + // TEMPLATE: LEGO1 0x100b5990 // MxCollection::Destroy diff --git a/LEGO1/omni/include/mxstreamer.h b/LEGO1/omni/include/mxstreamer.h index 02f97d12..945f8934 100644 --- a/LEGO1/omni/include/mxstreamer.h +++ b/LEGO1/omni/include/mxstreamer.h @@ -24,6 +24,7 @@ class MxStreamerSubClass1 { } } + // FUNCTION: LEGO1 0x100b9110 ~MxStreamerSubClass1() { delete[] m_buffer; } undefined4 GetSize() const { return m_size; } @@ -48,6 +49,7 @@ class MxStreamerSubClass3 : public MxStreamerSubClass1 { inline MxStreamerSubClass3() : MxStreamerSubClass1(0x80) {} }; +// VTABLE: LEGO1 0x100dc760 class MxStreamerNotification : public MxNotificationParam { public: inline MxStreamerNotification(NotificationId p_type, MxCore* p_sender, MxStreamController* p_ctrlr) @@ -56,8 +58,6 @@ class MxStreamerNotification : public MxNotificationParam { m_controller = p_ctrlr; } - virtual ~MxStreamerNotification() override {} - virtual MxNotificationParam* Clone() override; MxStreamController* GetController() { return m_controller; } @@ -113,7 +113,21 @@ class MxStreamer : public MxCore { MxStreamerSubClass3 m_subclass2; // 0x20 }; +// clang-format off +// TEMPLATE: LEGO1 0x100b9090 +// list >::~list > +// clang-format on + // SYNTHETIC: LEGO1 0x100b9120 // MxStreamer::`scalar deleting destructor' +// TEMPLATE: LEGO1 0x100b9140 +// List::~List + +// SYNTHETIC: LEGO1 0x100b97b0 +// MxStreamerNotification::`scalar deleting destructor' + +// SYNTHETIC: LEGO1 0x100b9820 +// MxStreamerNotification::~MxStreamerNotification + #endif // MXSTREAMER_H diff --git a/LEGO1/omni/include/mxstreamprovider.h b/LEGO1/omni/include/mxstreamprovider.h index 17bba38d..803038e3 100644 --- a/LEGO1/omni/include/mxstreamprovider.h +++ b/LEGO1/omni/include/mxstreamprovider.h @@ -41,4 +41,7 @@ class MxStreamProvider : public MxCore { // SYNTHETIC: LEGO1 0x100d0870 // MxStreamProvider::`scalar deleting destructor' +// SYNTHETIC: LEGO1 0x100d08e0 +// MxStreamProvider::~MxStreamProvider + #endif // MXSTREAMPROVIDER_H diff --git a/LEGO1/omni/include/mxstringlist.h b/LEGO1/omni/include/mxstringlist.h index 8651f4c4..2a61f530 100644 --- a/LEGO1/omni/include/mxstringlist.h +++ b/LEGO1/omni/include/mxstringlist.h @@ -15,18 +15,33 @@ class MxStringListCursor : public MxListCursor { MxStringListCursor(MxStringList* p_list) : MxListCursor(p_list){}; }; +// VTABLE: LEGO1 0x100dd010 +// class MxCollection + +// VTABLE: LEGO1 0x100dd028 +// class MxList + // VTABLE: LEGO1 0x100dd070 // class MxListCursor // TEMPLATE: LEGO1 0x100cb3c0 // MxCollection::Compare +// TEMPLATE: LEGO1 0x100cb420 +// MxCollection::~MxCollection + // TEMPLATE: LEGO1 0x100cb470 // MxCollection::Destroy // TEMPLATE: LEGO1 0x100cb4c0 // MxList::~MxList +// SYNTHETIC: LEGO1 0x100cb590 +// MxCollection::`scalar deleting destructor' + +// SYNTHETIC: LEGO1 0x100cb600 +// MxList::`scalar deleting destructor' + // TEMPLATE: LEGO1 0x100cbb40 // MxList::Append diff --git a/LEGO1/omni/include/mxticklemanager.h b/LEGO1/omni/include/mxticklemanager.h index 44694a61..0fdb648a 100644 --- a/LEGO1/omni/include/mxticklemanager.h +++ b/LEGO1/omni/include/mxticklemanager.h @@ -50,4 +50,10 @@ class MxTickleManager : public MxCore { #define TICKLE_MANAGER_NOT_FOUND 0x80000000 +// TEMPLATE: LEGO1 0x1005a4a0 +// list >::~list > + +// TEMPLATE: LEGO1 0x1005a530 +// List::~List + #endif // MXTICKLEMANAGER_H diff --git a/LEGO1/omni/include/mxvariable.h b/LEGO1/omni/include/mxvariable.h index 52fc48bc..e09d449c 100644 --- a/LEGO1/omni/include/mxvariable.h +++ b/LEGO1/omni/include/mxvariable.h @@ -33,8 +33,11 @@ class MxVariable { inline const MxString* GetKey() const { return &m_key; } protected: - MxString m_key; - MxString m_value; + MxString m_key; // 0x04 + MxString m_value; // 0x14 }; +// SYNTHETIC: LEGO1 0x1003bf40 +// MxVariable::~MxVariable + #endif // MXVARIABLE_H diff --git a/LEGO1/omni/include/mxvariabletable.h b/LEGO1/omni/include/mxvariabletable.h index 7821311e..0d79570c 100644 --- a/LEGO1/omni/include/mxvariabletable.h +++ b/LEGO1/omni/include/mxvariabletable.h @@ -26,6 +26,9 @@ class MxVariableTable : public MxHashTable { // VTABLE: LEGO1 0x100dc1e8 // class MxHashTable +// VTABLE: LEGO1 0x100dc680 +// class MxHashTableCursor + // TEMPLATE: LEGO1 0x100afcd0 // MxCollection::Compare @@ -50,6 +53,9 @@ class MxVariableTable : public MxHashTable { // SYNTHETIC: LEGO1 0x100b0ca0 // MxHashTable::`scalar deleting destructor' +// TEMPLATE: LEGO1 0x100b7680 +// MxHashTableCursor::~MxHashTableCursor + // TEMPLATE: LEGO1 0x100b7ab0 // MxHashTable::Resize diff --git a/LEGO1/omni/include/mxvideopresenter.h b/LEGO1/omni/include/mxvideopresenter.h index 48389490..bcbcbf67 100644 --- a/LEGO1/omni/include/mxvideopresenter.h +++ b/LEGO1/omni/include/mxvideopresenter.h @@ -80,6 +80,7 @@ class MxVideoPresenter : public MxMediaPresenter { // FUNCTION: LEGO1 0x1000c800 virtual MxS32 GetHeight() { return m_alpha ? m_alpha->m_height : m_bitmap->GetBmiHeightAbs(); }; // vtable+0x84 + // VTABLE: LEGO1 0x100dc2bc // SIZE 0xc struct AlphaMask { MxU8* m_bitmask; diff --git a/LEGO1/omni/src/action/mxdsaction.cpp b/LEGO1/omni/src/action/mxdsaction.cpp index b24ff347..d4da16bf 100644 --- a/LEGO1/omni/src/action/mxdsaction.cpp +++ b/LEGO1/omni/src/action/mxdsaction.cpp @@ -88,6 +88,12 @@ void MxDSAction::CopyFrom(MxDSAction& p_dsAction) this->m_unk0x90 = p_dsAction.m_unk0x90; } +// FUNCTION: LEGO1 0x100adbd0 +undefined4 MxDSAction::VTable0x14() +{ + return MxDSObject::VTable0x14(); +} + // FUNCTION: LEGO1 0x100adbe0 MxU32 MxDSAction::GetSizeOnDisk() { diff --git a/LEGO1/omni/src/event/mxeventpresenter.cpp b/LEGO1/omni/src/event/mxeventpresenter.cpp index d934660a..213304ad 100644 --- a/LEGO1/omni/src/event/mxeventpresenter.cpp +++ b/LEGO1/omni/src/event/mxeventpresenter.cpp @@ -78,7 +78,7 @@ void MxEventPresenter::ReadyTickle() // FUNCTION: LEGO1 0x100c2eb0 void MxEventPresenter::StartingTickle() { - MxStreamChunk* chunk = NextChunk(); + MxStreamChunk* chunk = CurrentChunk(); if (chunk && m_action->GetElapsedTime() >= chunk->GetTime()) ProgressTickleState(TickleState_Streaming); diff --git a/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp b/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp index 08046ad4..647794dd 100644 --- a/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp +++ b/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp @@ -215,18 +215,20 @@ void MxDiskStreamProvider::PerformWork() } } + MxDSBuffer* buffer; + { MxAutoLocker lock(&m_criticalSection); if (!m_list.PopFrontStreamingAction(streamingAction)) - return; + goto done; } if (streamingAction->GetUnknowna0()->GetWriteOffset() < 0x20000) { g_unk0x10102878--; } - MxDSBuffer* buffer = streamingAction->GetUnknowna0(); + buffer = streamingAction->GetUnknowna0(); if (m_pFile->GetPosition() == streamingAction->GetBufferOffset() || m_pFile->Seek(streamingAction->GetBufferOffset(), 0) == 0) { @@ -251,6 +253,7 @@ void MxDiskStreamProvider::PerformWork() } } +done: if (streamingAction) { controller->FUN_100c8670(streamingAction); } @@ -349,6 +352,8 @@ MxResult MxDiskStreamProvider::FUN_100d1b20(MxDSStreamingAction* p_action) p_action->SetUnknown94(unk0x14); p_action->SetBufferOffset(p_action->GetUnknowna0()->GetUnknown14()); delete p_action->GetUnknowna0(); + p_action->SetUnknowna0(NULL); + ((MxDiskStreamController*) m_pLookup)->FUN_100c7890(p_action); return SUCCESS; } else { diff --git a/LEGO1/omni/src/stream/mxramstreamprovider.cpp b/LEGO1/omni/src/stream/mxramstreamprovider.cpp index 232e347b..40a12c8a 100644 --- a/LEGO1/omni/src/stream/mxramstreamprovider.cpp +++ b/LEGO1/omni/src/stream/mxramstreamprovider.cpp @@ -47,12 +47,12 @@ MxRAMStreamProvider::~MxRAMStreamProvider() m_bufferSize = 0; m_fileSize = 0; - free(m_pBufferOfFileSize); + delete[] m_pBufferOfFileSize; m_pBufferOfFileSize = NULL; m_lengthInDWords = 0; - free(m_bufferForDWords); + delete[] m_bufferForDWords; m_bufferForDWords = NULL; } diff --git a/LEGO1/omni/src/video/mxdisplaysurface.cpp b/LEGO1/omni/src/video/mxdisplaysurface.cpp index e05b0816..8761cc02 100644 --- a/LEGO1/omni/src/video/mxdisplaysurface.cpp +++ b/LEGO1/omni/src/video/mxdisplaysurface.cpp @@ -7,6 +7,7 @@ DECOMP_SIZE_ASSERT(MxDisplaySurface, 0xac); +// GLOBAL: LEGO1 0x1010215c MxU32 g_unk0x1010215c = 0; // FUNCTION: LEGO1 0x100ba500 diff --git a/LEGO1/omni/src/video/mxstillpresenter.cpp b/LEGO1/omni/src/video/mxstillpresenter.cpp index ea3ba3a6..be0aa232 100644 --- a/LEGO1/omni/src/video/mxstillpresenter.cpp +++ b/LEGO1/omni/src/video/mxstillpresenter.cpp @@ -10,7 +10,7 @@ DECOMP_SIZE_ASSERT(MxStillPresenter, 0x6c); -// GLOBAL: LEGO1 0x10101eb0 +// GLOBAL: LEGO1 0x101020e0 const char* g_strBmpIsmap = "BMP_ISMAP"; // FUNCTION: LEGO1 0x100b9c70 @@ -164,7 +164,7 @@ void MxStillPresenter::VTable0x88(MxS32 p_x, MxS32 p_y) // FUNCTION: LEGO1 0x100ba140 void MxStillPresenter::Enable(MxBool p_enable) { - MxVideoPresenter::Enable(p_enable); + MxPresenter::Enable(p_enable); if (MVideoManager() && (m_alpha || m_bitmap)) { // MxRect32 rect(m_location, MxSize32(GetWidth(), GetHeight())); diff --git a/LEGO1/realtime/orientableroi.h b/LEGO1/realtime/orientableroi.h index 9b754b05..71e0fc71 100644 --- a/LEGO1/realtime/orientableroi.h +++ b/LEGO1/realtime/orientableroi.h @@ -32,9 +32,6 @@ class OrientableROI : public ROI { const float* GetWorldDirection() const { return m_local2world[2]; } const float* GetWorldUp() const { return m_local2world[1]; } - // SYNTHETIC: LEGO1 0x100a4630 - // OrientableROI::`scalar deleting destructor' - protected: MxMatrix m_local2world; // 0x10 BoundingBox m_world_bounding_box; // 0x58 @@ -52,4 +49,10 @@ class OrientableROI : public ROI { undefined4 m_unk0xd8; // 0xd8 }; +// SYNTHETIC: LEGO1 0x100a4630 +// OrientableROI::`scalar deleting destructor' + +// SYNTHETIC: LEGO1 0x100aa2f0 +// OrientableROI::~OrientableROI + #endif // ORIENTABLEROI_H diff --git a/LEGO1/realtime/realtimeview.cpp b/LEGO1/realtime/realtimeview.cpp index 5d1fc1a6..9522d1c1 100644 --- a/LEGO1/realtime/realtimeview.cpp +++ b/LEGO1/realtime/realtimeview.cpp @@ -30,8 +30,7 @@ void RealtimeView::SetPartsThreshold(float p_threshold) // FUNCTION: LEGO1 0x100a5e00 float RealtimeView::GetUserMaxLOD() { - // TODO - return 0; + return g_userMaxLod; } // FUNCTION: LEGO1 0x100a5e10 diff --git a/LEGO1/realtime/roi.h b/LEGO1/realtime/roi.h index 2a96d40b..2cdf943a 100644 --- a/LEGO1/realtime/roi.h +++ b/LEGO1/realtime/roi.h @@ -108,4 +108,8 @@ class ROI { LODListBase* m_lods; // 0x8 undefined m_unk0xc; // 0xc }; + +// SYNTHETIC: LEGO1 0x100a5d50 +// ROI::~ROI + #endif // ROI_H diff --git a/LEGO1/tgl/d3drm/impl.h b/LEGO1/tgl/d3drm/impl.h index 0c1e869d..1e017cf2 100644 --- a/LEGO1/tgl/d3drm/impl.h +++ b/LEGO1/tgl/d3drm/impl.h @@ -36,7 +36,7 @@ class MeshImpl; class TextureImpl; class UnkImpl; -// VTABLE 0x100db910 +// VTABLE: LEGO1 0x100db910 class RendererImpl : public Renderer { public: RendererImpl() : m_data(0) {} @@ -105,7 +105,7 @@ void RendererImpl::Destroy() } } -// VTABLE 0x100db988 +// VTABLE: LEGO1 0x100db988 class DeviceImpl : public Device { public: DeviceImpl() : m_data(0) {} @@ -142,7 +142,7 @@ class DeviceImpl : public Device { IDirect3DRMDevice2* m_data; }; -// VTABLE 0x100db9e8 +// VTABLE: LEGO1 0x100db9e8 class ViewImpl : public View { public: ViewImpl() : m_data(0) {} @@ -194,7 +194,7 @@ class ViewImpl : public View { IDirect3DRMViewport* m_data; }; -// VTABLE 0x100dbad8 +// VTABLE: LEGO1 0x100dbad8 class CameraImpl : public Camera { public: CameraImpl() : m_data(0) {} @@ -219,7 +219,7 @@ class CameraImpl : public Camera { IDirect3DRMFrame2* m_data; }; -// VTABLE 0x100dbaf8 +// VTABLE: LEGO1 0x100dbaf8 class LightImpl : public Light { public: LightImpl() : m_data(0) {} @@ -245,7 +245,7 @@ class LightImpl : public Light { IDirect3DRMFrame2* m_data; }; -// VTABLE 0x100dbb88 +// VTABLE: LEGO1 0x100dbb88 class MeshImpl : public Mesh { public: MeshImpl() : m_data(0) {} @@ -285,7 +285,7 @@ class MeshImpl : public Mesh { MeshData* m_data; }; -// VTABLE 0x100dba68 +// VTABLE: LEGO1 0x100dba68 class GroupImpl : public Group { public: GroupImpl() : m_data(0) {} @@ -324,7 +324,7 @@ class GroupImpl : public Group { IDirect3DRMFrame2* m_data; }; -// VTABLE 0x100dbb18 +// VTABLE: LEGO1 0x100dbb18 class UnkImpl : public Unk { public: UnkImpl() : m_data(0) {} @@ -384,7 +384,7 @@ class TglD3DRMIMAGE { int m_texelsAllocatedByClient; }; -// VTABLE 0x100dbb48 +// VTABLE: LEGO1 0x100dbb48 class TextureImpl : public Texture { public: TextureImpl() : m_data(0) {} diff --git a/LEGO1/tgl/tgl.h b/LEGO1/tgl/tgl.h index 3e09ad5a..137a78ff 100644 --- a/LEGO1/tgl/tgl.h +++ b/LEGO1/tgl/tgl.h @@ -98,7 +98,7 @@ class Mesh; class Texture; class Unk; -// VTABLE 0x100db980 +// VTABLE: LEGO1 0x100db980 class Object { public: virtual ~Object() {} @@ -106,7 +106,7 @@ class Object { virtual void* ImplementationDataPtr() = 0; }; -// VTABLE 0x100db948 +// VTABLE: LEGO1 0x100db948 class Renderer : public Object { public: // vtable+0x08 @@ -146,7 +146,7 @@ class Renderer : public Object { Renderer* CreateRenderer(); -// VTABLE 0x100db9b8 +// VTABLE: LEGO1 0x100db9b8 class Device : public Object { public: // vtable+0x08 @@ -165,7 +165,7 @@ class Device : public Object { virtual void InitFromWindowsDevice(Device*) = 0; }; -// VTABLE 0x100dba28 +// VTABLE: LEGO1 0x100dba28 class View : public Object { public: virtual Result Add(const Light*) = 0; @@ -219,20 +219,20 @@ class View : public Object { ) = 0; }; -// VTABLE 0x100dbae8 +// VTABLE: LEGO1 0x100dbae8 class Camera : public Object { public: virtual Result SetTransformation(FloatMatrix4&) = 0; }; -// VTABLE 0x100dbb08 +// VTABLE: LEGO1 0x100dbb08 class Light : public Object { public: virtual Result SetTransformation(FloatMatrix4&) = 0; virtual Result SetColor(float r, float g, float b) = 0; }; -// VTABLE 0x100dbbb0 +// VTABLE: LEGO1 0x100dbbb0 class Mesh : public Object { public: virtual Result SetColor(float r, float g, float b, float a) = 0; @@ -249,7 +249,7 @@ class Mesh : public Object { virtual Mesh* ShallowClone(Unk*) = 0; }; -// VTABLE 0x100dbaa0 +// VTABLE: LEGO1 0x100dbaa0 class Group : public Object { public: virtual Result SetTransformation(FloatMatrix4&) = 0; @@ -271,7 +271,7 @@ class Group : public Object { // Don't know what this is. Seems like another Tgl object which // was not in the leaked Tgl code. My suspicion is that it's // some kind of builder class for creating meshes. -// VTABLE 0x100dbb30 +// VTABLE: LEGO1 0x100dbb30 class Unk : public Object { public: virtual Result SetMeshData( @@ -287,7 +287,7 @@ class Unk : public Object { virtual Unk* Clone() = 0; }; -// VTABLE 0x100dbb68 +// VTABLE: LEGO1 0x100dbb68 class Texture : public Object { public: // vtable+0x08 diff --git a/LEGO1/viewmanager/viewlodlist.h b/LEGO1/viewmanager/viewlodlist.h index 0fc35d07..1ecec3f8 100644 --- a/LEGO1/viewmanager/viewlodlist.h +++ b/LEGO1/viewmanager/viewlodlist.h @@ -93,6 +93,22 @@ class ViewLODListManager { ViewLODListMap m_map; }; +// FUNCTION: LEGO1 0x1001dde0 +// _Lockit::~_Lockit + +// clang-format off +// TEMPLATE: LEGO1 0x100a7890 +// _Tree,map >::_Kfn,ROINameComparator,allocator >::~_Tree,map >::~map > +// clang-format on + +// TEMPLATE: LEGO1 0x100a70e0 +// Map::~Map + ////////////////////////////////////////////////////////////////////////////// // // ViewLODList implementation diff --git a/tools/isledecomp/isledecomp/bin.py b/tools/isledecomp/isledecomp/bin.py index 26dd00f8..3b600af6 100644 --- a/tools/isledecomp/isledecomp/bin.py +++ b/tools/isledecomp/isledecomp/bin.py @@ -97,6 +97,8 @@ def __init__(self, filename: str, find_str: bool = False) -> None: self.find_str = find_str self._potential_strings = {} self._relocated_addrs = set() + self.imports = [] + self.thunks = [] def __enter__(self): logger.debug("Bin %s Enter", self.filename) @@ -132,6 +134,8 @@ def __enter__(self): sect.virtual_address += self.imagebase self._populate_relocations() + self._populate_imports() + self._populate_thunks() # This is a (semi) expensive lookup that is not necesssary in every case. # We can find strings in the original if we have coverage using STRING markers. @@ -238,6 +242,78 @@ def _populate_relocations(self): (relocated_addr,) = struct.unpack(" bool: return section is not None + def read_string(self, offset: int, chunk_size: int = 1000) -> Optional[bytes]: + """Read until we find a zero byte.""" + b = self.read(offset, chunk_size) + if b is None: + return None + + try: + return b[: b.index(b"\x00")] + except ValueError: + # No terminator found, just return what we have + return b + def read(self, offset: int, size: int) -> Optional[bytes]: """Read (at most) the given number of bytes at the given virtual address. If we return None, the given address points to uninitialized data.""" diff --git a/tools/isledecomp/isledecomp/compare/asm/__init__.py b/tools/isledecomp/isledecomp/compare/asm/__init__.py new file mode 100644 index 00000000..3fd22f6e --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/asm/__init__.py @@ -0,0 +1,2 @@ +from .parse import ParseAsm +from .swap import can_resolve_register_differences diff --git a/tools/isledecomp/isledecomp/compare/asm/parse.py b/tools/isledecomp/isledecomp/compare/asm/parse.py new file mode 100644 index 00000000..ee9c9154 --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/asm/parse.py @@ -0,0 +1,152 @@ +"""Converts x86 machine code into text (i.e. assembly). The end goal is to +compare the code in the original and recomp binaries, using longest common +subsequence (LCS), i.e. difflib.SequenceMatcher. +The capstone library takes the raw bytes and gives us the mnemnonic +and operand(s) for each instruction. We need to "sanitize" the text further +so that virtual addresses are replaced by symbol name or a generic +placeholder string.""" + +import re +from typing import Callable, List, Optional, Tuple +from collections import namedtuple +from capstone import Cs, CS_ARCH_X86, CS_MODE_32 + +disassembler = Cs(CS_ARCH_X86, CS_MODE_32) + +ptr_replace_regex = re.compile(r"ptr \[(0x[0-9a-fA-F]+)\]") + +DisasmLiteInst = namedtuple("DisasmLiteInst", "address, size, mnemonic, op_str") + + +def from_hex(string: str) -> Optional[int]: + try: + return int(string, 16) + except ValueError: + pass + + return None + + +class ParseAsm: + def __init__( + self, + relocate_lookup: Optional[Callable[[int], bool]] = None, + name_lookup: Optional[Callable[[int], str]] = None, + ) -> None: + self.relocate_lookup = relocate_lookup + self.name_lookup = name_lookup + self.replacements = {} + self.number_placeholders = True + + def reset(self): + self.replacements = {} + + def is_relocated(self, addr: int) -> bool: + if callable(self.relocate_lookup): + return self.relocate_lookup(addr) + + return False + + def lookup(self, addr: int) -> Optional[str]: + """Return a replacement name for this address if we find one.""" + if (cached := self.replacements.get(addr, None)) is not None: + return cached + + if callable(self.name_lookup): + if (name := self.name_lookup(addr)) is not None: + self.replacements[addr] = name + return name + + return None + + def replace(self, addr: int) -> str: + """Same function as lookup above, but here we return a placeholder + if there is no better name to use.""" + if (name := self.lookup(addr)) is not None: + return name + + # The placeholder number corresponds to the number of addresses we have + # already replaced. This is so the number will be consistent across the diff + # if we can replace some symbols with actual names in recomp but not orig. + idx = len(self.replacements) + 1 + placeholder = f"" if self.number_placeholders else "" + self.replacements[addr] = placeholder + return placeholder + + def sanitize(self, inst: DisasmLiteInst) -> Tuple[str, str]: + if len(inst.op_str) == 0: + # Nothing to sanitize + return (inst.mnemonic, "") + + # For jumps or calls, if the entire op_str is a hex number, the value + # is a relative offset. + # Otherwise (i.e. it looks like `dword ptr [address]`) it is an + # absolute indirect that we will handle below. + # Providing the starting address of the function to capstone.disasm has + # automatically resolved relative offsets to an absolute address. + # We will have to undo this for some of the jumps or they will not match. + op_str_address = from_hex(inst.op_str) + + if op_str_address is not None: + if inst.mnemonic == "call": + return (inst.mnemonic, self.replace(op_str_address)) + + if inst.mnemonic == "jmp": + # The unwind section contains JMPs to other functions. + # If we have a name for this address, use it. If not, + # do not create a new placeholder. We will instead + # fall through to generic jump handling below. + potential_name = self.lookup(op_str_address) + if potential_name is not None: + return (inst.mnemonic, potential_name) + + if inst.mnemonic.startswith("j"): + # i.e. if this is any jump + # Show the jump offset rather than the absolute address + jump_displacement = op_str_address - (inst.address + inst.size) + return (inst.mnemonic, hex(jump_displacement)) + + def filter_out_ptr(match): + """Helper for re.sub, see below""" + offset = from_hex(match.group(1)) + + if offset is not None: + # We assume this is always an address to replace + placeholder = self.replace(offset) + return f"ptr [{placeholder}]" + + # Strict regex should ensure we can read the hex number. + # But just in case: return the string with no changes + return match.group(0) + + op_str = ptr_replace_regex.sub(filter_out_ptr, inst.op_str) + + # Performance hack: + # Skip this step if there is nothing left to consider replacing. + if "0x" in op_str: + # Replace immediate values with name or placeholder (where appropriate) + words = op_str.split(", ") + for i, word in enumerate(words): + try: + inttest = int(word, 16) + # If this value is a virtual address, it is referenced absolutely, + # which means it must be in the relocation table. + if self.is_relocated(inttest): + words[i] = self.replace(inttest) + except ValueError: + pass + op_str = ", ".join(words) + + return inst.mnemonic, op_str + + def parse_asm(self, data: bytes, start_addr: Optional[int] = 0) -> List[str]: + asm = [] + + for inst in disassembler.disasm_lite(data, start_addr): + # Use heuristics to disregard some differences that aren't representative + # of the accuracy of a function (e.g. global offsets) + result = self.sanitize(DisasmLiteInst(*inst)) + # mnemonic + " " + op_str + asm.append(" ".join(result)) + + return asm diff --git a/tools/isledecomp/isledecomp/compare/asm/swap.py b/tools/isledecomp/isledecomp/compare/asm/swap.py new file mode 100644 index 00000000..599444cf --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/asm/swap.py @@ -0,0 +1,80 @@ +import re + +REGISTER_LIST = set( + [ + "ax", + "bp", + "bx", + "cx", + "di", + "dx", + "eax", + "ebp", + "ebx", + "ecx", + "edi", + "edx", + "esi", + "esp", + "si", + "sp", + ] +) +WORDS = re.compile(r"\w+") + + +def get_registers(line: str): + to_replace = [] + # use words regex to find all matching positions: + for match in WORDS.finditer(line): + reg = match.group(0) + if reg in REGISTER_LIST: + to_replace.append((reg, match.start())) + return to_replace + + +def replace_register( + lines: list[str], start_line: int, reg: str, replacement: str +) -> list[str]: + return [ + line.replace(reg, replacement) if i >= start_line else line + for i, line in enumerate(lines) + ] + + +# Is it possible to make new_asm the same as original_asm by swapping registers? +def can_resolve_register_differences(original_asm, new_asm): + # Split the ASM on spaces to get more granularity, and so + # that we don't modify the original arrays passed in. + original_asm = [part for line in original_asm for part in line.split()] + new_asm = [part for line in new_asm for part in line.split()] + + # Swapping ain't gonna help if the lengths are different + if len(original_asm) != len(new_asm): + return False + + # Look for the mismatching lines + for i, original_line in enumerate(original_asm): + new_line = new_asm[i] + if new_line != original_line: + # Find all the registers to replace + to_replace = get_registers(original_line) + + for replace in to_replace: + (reg, reg_index) = replace + replacing_reg = new_line[reg_index : reg_index + len(reg)] + if replacing_reg in REGISTER_LIST: + if replacing_reg != reg: + # Do a three-way swap replacing in all the subsequent lines + temp_reg = "&" * len(reg) + new_asm = replace_register(new_asm, i, replacing_reg, temp_reg) + new_asm = replace_register(new_asm, i, reg, replacing_reg) + new_asm = replace_register(new_asm, i, temp_reg, reg) + else: + # No replacement to do, different code, bail out + return False + # Check if the lines are now the same + for i, original_line in enumerate(original_asm): + if new_asm[i] != original_line: + return False + return True diff --git a/tools/isledecomp/isledecomp/compare/core.py b/tools/isledecomp/isledecomp/compare/core.py index 07860203..40b93bae 100644 --- a/tools/isledecomp/isledecomp/compare/core.py +++ b/tools/isledecomp/isledecomp/compare/core.py @@ -1,11 +1,14 @@ import os import logging -from typing import List, Optional +import difflib +from dataclasses import dataclass +from typing import Iterable, List, Optional from isledecomp.cvdump.demangler import demangle_string_const from isledecomp.cvdump import Cvdump, CvdumpAnalysis from isledecomp.parser import DecompCodebase from isledecomp.dir import walk_source_dir from isledecomp.types import SymbolType +from isledecomp.compare.asm import ParseAsm, can_resolve_register_differences from .db import CompareDb, MatchInfo from .lines import LinesDb @@ -13,6 +16,24 @@ logger = logging.getLogger(__name__) +@dataclass +class DiffReport: + orig_addr: int + recomp_addr: int + name: str + udiff: Optional[List[str]] = None + ratio: float = 0.0 + is_effective_match: bool = False + + @property + def effective_ratio(self) -> float: + return 1.0 if self.is_effective_match else self.ratio + + def __str__(self) -> str: + """For debug purposes. Proper diff printing (with coloring) is in another module.""" + return f"{self.name} (0x{self.orig_addr:x}) {self.ratio*100:.02f}%{'*' if self.is_effective_match else ''}" + + class Compare: # pylint: disable=too-many-instance-attributes def __init__(self, orig_bin, recomp_bin, pdb_file, code_dir): @@ -27,6 +48,7 @@ def __init__(self, orig_bin, recomp_bin, pdb_file, code_dir): self._load_cvdump() self._load_markers() self._find_original_strings() + self._match_thunks() def _load_cvdump(self): logger.info("Parsing %s ...", self.pdb_file) @@ -126,6 +148,46 @@ def _find_original_strings(self): self._db.match_string(addr, string) + def _match_thunks(self): + orig_byaddr = { + addr: (dll.upper(), name) for (dll, name, addr) in self.orig_bin.imports + } + recomp_byname = { + (dll.upper(), name): addr for (dll, name, addr) in self.recomp_bin.imports + } + # Combine these two dictionaries. We don't care about imports from recomp + # not found in orig because: + # 1. They shouldn't be there + # 2. They are already identified via cvdump + orig_to_recomp = { + addr: recomp_byname.get(pair, None) for addr, pair in orig_byaddr.items() + } + + # Now: we have the IAT offset in each matched up, so we need to make + # the connection between the thunk functions. + # We already have the symbol name we need from the PDB. + orig_thunks = { + iat_ofs: func_ofs for (func_ofs, iat_ofs) in self.orig_bin.thunks + } + recomp_thunks = { + iat_ofs: func_ofs for (func_ofs, iat_ofs) in self.recomp_bin.thunks + } + + for orig, recomp in orig_to_recomp.items(): + self._db.set_pair(orig, recomp, SymbolType.POINTER) + thunk_from_orig = orig_thunks.get(orig, None) + thunk_from_recomp = recomp_thunks.get(recomp, None) + + if thunk_from_orig is not None and thunk_from_recomp is not None: + self._db.set_function_pair(thunk_from_orig, thunk_from_recomp) + # Don't compare thunk functions for now. The comparison isn't + # "useful" in the usual sense. We are only looking at the 6 + # bytes of the jmp instruction and not the larger context of + # where this function is. Also: these will always match 100% + # because we are searching for a match to register this as a + # function in the first place. + self._db.skip_compare(thunk_from_orig) + def get_one_function(self, addr: int) -> Optional[MatchInfo]: """i.e. verbose mode for reccmp""" return self._db.get_one_function(addr) @@ -133,8 +195,84 @@ def get_one_function(self, addr: int) -> Optional[MatchInfo]: def get_functions(self) -> List[MatchInfo]: return self._db.get_matches(SymbolType.FUNCTION) - def compare_functions(self): - pass + def _compare_function(self, match: MatchInfo) -> DiffReport: + if match.size == 0: + # Report a failed match to make the user aware of the empty function. + return DiffReport( + orig_addr=match.orig_addr, + recomp_addr=match.recomp_addr, + name=match.name, + ) + + orig_raw = self.orig_bin.read(match.orig_addr, match.size) + recomp_raw = self.recomp_bin.read(match.recomp_addr, match.size) + + def orig_should_replace(addr: int) -> bool: + return addr > self.orig_bin.imagebase and self.orig_bin.is_relocated_addr( + addr + ) + + def recomp_should_replace(addr: int) -> bool: + return ( + addr > self.recomp_bin.imagebase + and self.recomp_bin.is_relocated_addr(addr) + ) + + def orig_lookup(addr: int) -> Optional[str]: + m = self._db.get_by_orig(addr) + if m is None: + return None + + return m.match_name() + + def recomp_lookup(addr: int) -> Optional[str]: + m = self._db.get_by_recomp(addr) + if m is None: + return None + + return m.match_name() + + orig_parse = ParseAsm( + relocate_lookup=orig_should_replace, name_lookup=orig_lookup + ) + recomp_parse = ParseAsm( + relocate_lookup=recomp_should_replace, name_lookup=recomp_lookup + ) + + orig_asm = orig_parse.parse_asm(orig_raw, match.orig_addr) + recomp_asm = recomp_parse.parse_asm(recomp_raw, match.recomp_addr) + + diff = difflib.SequenceMatcher(None, orig_asm, recomp_asm) + ratio = diff.ratio() + + if ratio != 1.0: + # Check whether we can resolve register swaps which are actually + # perfect matches modulo compiler entropy. + is_effective_match = can_resolve_register_differences(orig_asm, recomp_asm) + unified_diff = difflib.unified_diff(orig_asm, recomp_asm, n=10) + else: + is_effective_match = False + unified_diff = [] + + return DiffReport( + orig_addr=match.orig_addr, + recomp_addr=match.recomp_addr, + name=match.name, + udiff=unified_diff, + ratio=ratio, + is_effective_match=is_effective_match, + ) + + def compare_function(self, addr: int) -> Optional[DiffReport]: + match = self.get_one_function(addr) + if match is None: + return None + + return self._compare_function(match) + + def compare_functions(self) -> Iterable[DiffReport]: + for match in self.get_functions(): + yield self._compare_function(match) def compare_variables(self): pass diff --git a/tools/isledecomp/isledecomp/compare/db.py b/tools/isledecomp/isledecomp/compare/db.py index 850f25fd..8cba0c03 100644 --- a/tools/isledecomp/isledecomp/compare/db.py +++ b/tools/isledecomp/isledecomp/compare/db.py @@ -2,7 +2,6 @@ addresses/symbols that we want to compare between the original and recompiled binaries.""" import sqlite3 import logging -from collections import namedtuple from typing import List, Optional from isledecomp.types import SymbolType @@ -16,12 +15,35 @@ size int, should_skip int default(FALSE) ); + CREATE INDEX `symbols_or` ON `symbols` (orig_addr); CREATE INDEX `symbols_re` ON `symbols` (recomp_addr); - CREATE INDEX `symbols_na` ON `symbols` (compare_type, name); """ -MatchInfo = namedtuple("MatchInfo", "orig_addr, recomp_addr, size, name") +class MatchInfo: + def __init__( + self, + ctype: Optional[int], + orig: Optional[int], + recomp: Optional[int], + name: Optional[str], + size: Optional[int], + ) -> None: + self.compare_type = SymbolType(ctype) if ctype is not None else None + self.orig_addr = orig + self.recomp_addr = recomp + self.name = name + self.size = size + + def match_name(self) -> str: + """Combination of the name and compare type. + Intended for name substitution in the diff. If there is a diff, + it will be more obvious what this symbol indicates.""" + if self.name is None: + return None + + ctype = self.compare_type.name if self.compare_type is not None else "UNK" + return f"{self.name} ({ctype})" def matchinfo_factory(_, row): @@ -61,7 +83,7 @@ def get_unmatched_strings(self) -> List[str]: def get_one_function(self, addr: int) -> Optional[MatchInfo]: cur = self._db.execute( - """SELECT orig_addr, recomp_addr, size, name + """SELECT compare_type, orig_addr, recomp_addr, name, size FROM `symbols` WHERE compare_type = ? AND orig_addr = ? @@ -74,9 +96,31 @@ def get_one_function(self, addr: int) -> Optional[MatchInfo]: cur.row_factory = matchinfo_factory return cur.fetchone() + def get_by_orig(self, addr: int) -> Optional[MatchInfo]: + cur = self._db.execute( + """SELECT compare_type, orig_addr, recomp_addr, name, size + FROM `symbols` + WHERE orig_addr = ? + """, + (addr,), + ) + cur.row_factory = matchinfo_factory + return cur.fetchone() + + def get_by_recomp(self, addr: int) -> Optional[MatchInfo]: + cur = self._db.execute( + """SELECT compare_type, orig_addr, recomp_addr, name, size + FROM `symbols` + WHERE recomp_addr = ? + """, + (addr,), + ) + cur.row_factory = matchinfo_factory + return cur.fetchone() + def get_matches(self, compare_type: SymbolType) -> List[MatchInfo]: cur = self._db.execute( - """SELECT orig_addr, recomp_addr, size, name + """SELECT compare_type, orig_addr, recomp_addr, name, size FROM `symbols` WHERE compare_type = ? AND orig_addr IS NOT NULL @@ -90,14 +134,20 @@ def get_matches(self, compare_type: SymbolType) -> List[MatchInfo]: return cur.fetchall() - def set_function_pair(self, orig: int, recomp: int) -> bool: - """For lineref match or _entry""" + def set_pair( + self, orig: int, recomp: int, compare_type: Optional[SymbolType] = None + ) -> bool: + compare_value = compare_type.value if compare_type is not None else None cur = self._db.execute( "UPDATE `symbols` SET orig_addr = ?, compare_type = ? WHERE recomp_addr = ?", - (orig, SymbolType.FUNCTION.value, recomp), + (orig, compare_value, recomp), ) return cur.rowcount > 0 + + def set_function_pair(self, orig: int, recomp: int) -> bool: + """For lineref match or _entry""" + self.set_pair(orig, recomp, SymbolType.FUNCTION) # TODO: Both ways required? def skip_compare(self, orig: int): diff --git a/tools/isledecomp/isledecomp/cvdump/parser.py b/tools/isledecomp/isledecomp/cvdump/parser.py index 613cd4a4..d14abe88 100644 --- a/tools/isledecomp/isledecomp/cvdump/parser.py +++ b/tools/isledecomp/isledecomp/cvdump/parser.py @@ -36,7 +36,7 @@ # e.g. `S_GDATA32: [0003:000004A4], Type: T_32PRCHAR(0470), g_set` _gdata32_regex = re.compile( - r"S_GDATA32: \[(?P
\w{4}):(?P\w{8})\], Type:\s*(?P\S+), (?P\S+)" + r"S_GDATA32: \[(?P
\w{4}):(?P\w{8})\], Type:\s*(?P\S+), (?P.+)" ) diff --git a/tools/isledecomp/isledecomp/utils.py b/tools/isledecomp/isledecomp/utils.py index ce4896fd..637eee33 100644 --- a/tools/isledecomp/isledecomp/utils.py +++ b/tools/isledecomp/isledecomp/utils.py @@ -26,17 +26,3 @@ def print_diff(udiff, plain): def get_file_in_script_dir(fn): return os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), fn) - - -class OffsetPlaceholderGenerator: - def __init__(self): - self.counter = 0 - self.replacements = {} - - def get(self, replace_addr): - if replace_addr in self.replacements: - return self.replacements[replace_addr] - self.counter += 1 - replacement = f"" - self.replacements[replace_addr] = replacement - return replacement diff --git a/tools/isledecomp/tests/test_sanitize.py b/tools/isledecomp/tests/test_sanitize.py new file mode 100644 index 00000000..67d95b4f --- /dev/null +++ b/tools/isledecomp/tests/test_sanitize.py @@ -0,0 +1,179 @@ +from typing import Optional +import pytest +from isledecomp.compare.asm.parse import DisasmLiteInst, ParseAsm + + +def mock_inst(mnemonic: str, op_str: str) -> DisasmLiteInst: + """Mock up the named tuple DisasmLite from just a mnemonic and op_str. + To be used for tests on sanitize that do not require the instruction address + or size. i.e. any non-jump instruction.""" + return DisasmLiteInst(0, 0, mnemonic, op_str) + + +identity_cases = [ + ("", ""), + ("sti", ""), + ("push", "ebx"), + ("ret", ""), + ("ret", "4"), + ("mov", "eax, 0x1234"), +] + + +@pytest.mark.parametrize("mnemonic, op_str", identity_cases) +def test_identity(mnemonic, op_str): + """Confirm that nothing is substituted.""" + p = ParseAsm() + inst = mock_inst(mnemonic, op_str) + result = p.sanitize(inst) + assert result == (mnemonic, op_str) + + +ptr_replace_cases = [ + ("byte ptr [0x5555]", "byte ptr []"), + ("word ptr [0x5555]", "word ptr []"), + ("dword ptr [0x5555]", "dword ptr []"), + ("qword ptr [0x5555]", "qword ptr []"), + ("eax, dword ptr [0x5555]", "eax, dword ptr []"), + ("dword ptr [0x5555], eax", "dword ptr [], eax"), + ("dword ptr [0x5555], 0", "dword ptr [], 0"), + ("dword ptr [0x5555], 8", "dword ptr [], 8"), + # Same value, assumed to be an addr in the first appearance + # because it is designated as 'ptr', but we have not provided the + # relocation table lookup method so we do not replace the second appearance. + ("dword ptr [0x5555], 0x5555", "dword ptr [], 0x5555"), +] + + +@pytest.mark.parametrize("start, end", ptr_replace_cases) +def test_ptr_replace(start, end): + """Anything in square brackets (with the 'ptr' prefix) will always be replaced.""" + p = ParseAsm() + inst = mock_inst("", start) + (_, op_str) = p.sanitize(inst) + assert op_str == end + + +call_replace_cases = [ + ("ebx", "ebx"), + ("0x1234", ""), + ("dword ptr [0x1234]", "dword ptr []"), + ("dword ptr [ecx + 0x10]", "dword ptr [ecx + 0x10]"), +] + + +@pytest.mark.parametrize("start, end", call_replace_cases) +def test_call_replace(start, end): + """Call with hex operand is always replaced. + Otherwise, ptr replacement rules apply, but skip `this` calls.""" + p = ParseAsm() + inst = mock_inst("call", start) + (_, op_str) = p.sanitize(inst) + assert op_str == end + + +def test_jump_displacement(): + """Display jump displacement (offset from end of jump instruction) + instead of destination address.""" + p = ParseAsm() + inst = DisasmLiteInst(0x1000, 2, "je", "0x1000") + (_, op_str) = p.sanitize(inst) + assert op_str == "-0x2" + + +@pytest.mark.xfail(reason="Not implemented yet") +def test_jmp_table(): + """Should detect the characteristic jump table instruction + (for a switch statement) and use placeholder.""" + p = ParseAsm() + inst = mock_inst("jmp", "dword ptr [eax*4 + 0x5555]") + (_, op_str) = p.sanitize(inst) + assert op_str == "dword ptr [eax*4 + ]" + + +name_replace_cases = [ + ("byte ptr [0x5555]", "byte ptr [_substitute_]"), + ("word ptr [0x5555]", "word ptr [_substitute_]"), + ("dword ptr [0x5555]", "dword ptr [_substitute_]"), + ("qword ptr [0x5555]", "qword ptr [_substitute_]"), +] + + +@pytest.mark.parametrize("start, end", name_replace_cases) +def test_name_replace(start, end): + """Make sure the name lookup function is called if present""" + + def substitute(_: int) -> str: + return "_substitute_" + + p = ParseAsm(name_lookup=substitute) + inst = mock_inst("mov", start) + (_, op_str) = p.sanitize(inst) + assert op_str == end + + +def test_replacement_cache(): + p = ParseAsm() + inst = mock_inst("inc", "dword ptr [0x1234]") + + (_, op_str) = p.sanitize(inst) + assert op_str == "dword ptr []" + + (_, op_str) = p.sanitize(inst) + assert op_str == "dword ptr []" + + +def test_replacement_numbering(): + """If we can use the name lookup for the first address but not the second, + the second replacement should be not .""" + + def substitute_1234(addr: int) -> Optional[str]: + return "_substitute_" if addr == 0x1234 else None + + p = ParseAsm(name_lookup=substitute_1234) + + (_, op_str) = p.sanitize(mock_inst("inc", "dword ptr [0x1234]")) + assert op_str == "dword ptr [_substitute_]" + + (_, op_str) = p.sanitize(mock_inst("inc", "dword ptr [0x5555]")) + assert op_str == "dword ptr []" + + +def test_relocate_lookup(): + """Immediate values would be relocated if they are actually addresses. + So we can use the relocation table to check whether a given value is an + address or just some number.""" + + def relocate_lookup(addr: int) -> bool: + return addr == 0x1234 + + p = ParseAsm(relocate_lookup=relocate_lookup) + (_, op_str) = p.sanitize(mock_inst("mov", "eax, 0x1234")) + assert op_str == "eax, " + + (_, op_str) = p.sanitize(mock_inst("mov", "eax, 0x5555")) + assert op_str == "eax, 0x5555" + + +def test_jump_to_function(): + """A jmp instruction can lead us directly to a function. This can be found + in the unwind section at the end of a function. However: we do not want to + assume this is the case for all jumps. Only replace the jump with a name + if we can find it using our lookup.""" + + def substitute_1234(addr: int) -> Optional[str]: + return "_substitute_" if addr == 0x1234 else None + + p = ParseAsm(name_lookup=substitute_1234) + inst = DisasmLiteInst(0x1000, 2, "jmp", "0x1234") + (_, op_str) = p.sanitize(inst) + assert op_str == "_substitute_" + + # Should not replace this jump. + # 0x1000 (start addr) + # + 2 (size of jump instruction) + # + 0x5555 (displacement, the value we want) + # = 0x6557 + inst = DisasmLiteInst(0x1000, 2, "jmp", "0x6557") + (_, op_str) = p.sanitize(inst) + assert op_str == "0x5555" diff --git a/tools/reccmp/reccmp.py b/tools/reccmp/reccmp.py index b7027435..66bb0f66 100755 --- a/tools/reccmp/reccmp.py +++ b/tools/reccmp/reccmp.py @@ -2,167 +2,20 @@ import argparse import base64 -import difflib import json import logging import os -import re from isledecomp import ( Bin, get_file_in_script_dir, - OffsetPlaceholderGenerator, print_diff, ) from isledecomp.compare import Compare as IsleCompare - -from capstone import Cs, CS_ARCH_X86, CS_MODE_32 -import colorama from pystache import Renderer +import colorama - -REGISTER_LIST = set( - [ - "ax", - "bp", - "bx", - "cx", - "di", - "dx", - "eax", - "ebp", - "ebx", - "ecx", - "edi", - "edx", - "esi", - "esp", - "si", - "sp", - ] -) -WORDS = re.compile(r"\w+") - - -def sanitize(file, placeholder_generator, mnemonic, op_str): - op_str_is_number = False - try: - int(op_str, 16) - op_str_is_number = True - except ValueError: - pass - - if (mnemonic in ["call", "jmp"]) and op_str_is_number: - # Filter out "calls" because the offsets we're not currently trying to - # match offsets. As long as there's a call in the right place, it's - # probably accurate. - op_str = placeholder_generator.get(int(op_str, 16)) - else: - - def filter_out_ptr(ptype, op_str): - try: - ptrstr = ptype + " ptr [" - start = op_str.index(ptrstr) + len(ptrstr) - end = op_str.index("]", start) - - # This will throw ValueError if not hex - inttest = int(op_str[start:end], 16) - - return ( - op_str[0:start] + placeholder_generator.get(inttest) + op_str[end:] - ) - except ValueError: - return op_str - - # Filter out dword ptrs where the pointer is to an offset - op_str = filter_out_ptr("dword", op_str) - op_str = filter_out_ptr("word", op_str) - op_str = filter_out_ptr("byte", op_str) - - # Use heuristics to filter out any args that look like offsets - words = op_str.split(" ") - for i, word in enumerate(words): - try: - inttest = int(word, 16) - if file.is_relocated_addr(inttest): - words[i] = placeholder_generator.get(inttest) - except ValueError: - pass - op_str = " ".join(words) - - return mnemonic, op_str - - -def parse_asm(disassembler, file, asm_addr, size): - asm = [] - data = file.read(asm_addr, size) - placeholder_generator = OffsetPlaceholderGenerator() - for i in disassembler.disasm(data, 0): - # Use heuristics to disregard some differences that aren't representative - # of the accuracy of a function (e.g. global offsets) - mnemonic, op_str = sanitize(file, placeholder_generator, i.mnemonic, i.op_str) - if op_str is None: - asm.append(mnemonic) - else: - asm.append(f"{mnemonic} {op_str}") - return asm - - -def get_registers(line: str): - to_replace = [] - # use words regex to find all matching positions: - for match in WORDS.finditer(line): - reg = match.group(0) - if reg in REGISTER_LIST: - to_replace.append((reg, match.start())) - return to_replace - - -def replace_register( - lines: list[str], start_line: int, reg: str, replacement: str -) -> list[str]: - return [ - line.replace(reg, replacement) if i >= start_line else line - for i, line in enumerate(lines) - ] - - -# Is it possible to make new_asm the same as original_asm by swapping registers? -def can_resolve_register_differences(original_asm, new_asm): - # Split the ASM on spaces to get more granularity, and so - # that we don't modify the original arrays passed in. - original_asm = [part for line in original_asm for part in line.split()] - new_asm = [part for line in new_asm for part in line.split()] - - # Swapping ain't gonna help if the lengths are different - if len(original_asm) != len(new_asm): - return False - - # Look for the mismatching lines - for i, original_line in enumerate(original_asm): - new_line = new_asm[i] - if new_line != original_line: - # Find all the registers to replace - to_replace = get_registers(original_line) - - for replace in to_replace: - (reg, reg_index) = replace - replacing_reg = new_line[reg_index : reg_index + len(reg)] - if replacing_reg in REGISTER_LIST: - if replacing_reg != reg: - # Do a three-way swap replacing in all the subsequent lines - temp_reg = "&" * len(reg) - new_asm = replace_register(new_asm, i, replacing_reg, temp_reg) - new_asm = replace_register(new_asm, i, reg, replacing_reg) - new_asm = replace_register(new_asm, i, temp_reg, reg) - else: - # No replacement to do, different code, bail out - return False - # Check if the lines are now the same - for i, original_line in enumerate(original_asm): - if new_asm[i] != original_line: - return False - return True +colorama.init() def gen_html(html_file, data): @@ -197,9 +50,88 @@ def gen_svg(svg_file, name_svg, icon, svg_implemented_funcs, total_funcs, raw_ac svgfile.write(output_data) -# Do the actual work -def main(): - # pylint: disable=too-many-locals, too-many-nested-blocks, too-many-branches, too-many-statements +def get_percent_color(value: float) -> str: + """Return colorama ANSI escape character for the given decimal value.""" + if value == 1.0: + return colorama.Fore.GREEN + if value > 0.8: + return colorama.Fore.YELLOW + + return colorama.Fore.RED + + +def percent_string( + ratio: float, is_effective: bool = False, is_plain: bool = False +) -> str: + """Helper to construct a percentage string from the given ratio. + If is_effective (i.e. effective match), indicate that with the asterisk. + If is_plain, don't use colorama ANSI codes.""" + + percenttext = f"{(ratio * 100):.2f}%" + effective_star = "*" if is_effective else "" + + if is_plain: + return percenttext + effective_star + + return "".join( + [ + get_percent_color(ratio), + percenttext, + colorama.Fore.RED if is_effective else "", + effective_star, + colorama.Style.RESET_ALL, + ] + ) + + +def print_match_verbose(match, show_both_addrs: bool = False, is_plain: bool = False): + percenttext = percent_string( + match.effective_ratio, match.is_effective_match, is_plain + ) + + if show_both_addrs: + addrs = f"0x{match.orig_addr:x} / 0x{match.recomp_addr:x}" + else: + addrs = hex(match.orig_addr) + + if match.effective_ratio == 1.0: + ok_text = ( + "OK!" + if is_plain + else (colorama.Fore.GREEN + "✨ OK! ✨" + colorama.Style.RESET_ALL) + ) + if match.ratio == 1.0: + print(f"{addrs}: {match.name} 100% match.\n\n{ok_text}\n\n") + else: + print( + f"{addrs}: {match.name} Effective 100%% match. (Differs in register allocation only)\n\n{ok_text} (still differs in register allocation)\n\n" + ) + else: + print_diff(match.udiff, is_plain) + + print( + f"\n{match.name} is only {percenttext} similar to the original, diff above" + ) + + +def print_match_oneline(match, show_both_addrs: bool = False, is_plain: bool = False): + percenttext = percent_string( + match.effective_ratio, match.is_effective_match, is_plain + ) + + if show_both_addrs: + addrs = f"0x{match.orig_addr:x} / 0x{match.recomp_addr:x}" + else: + addrs = hex(match.orig_addr) + + print(f" {match.name} ({addrs}) is {percenttext} similar to the original") + + +def parse_args() -> argparse.Namespace: + def virtual_address(value) -> int: + """Helper method for argparse, verbose parameter""" + return int(value, 16) + parser = argparse.ArgumentParser( allow_abbrev=False, description="Recompilation Compare: compare an original EXE with a recompiled EXE + PDB.", @@ -226,6 +158,7 @@ def main(): "--verbose", "-v", metavar="", + type=virtual_address, help="Print assembly diff for specific function (original file's offset)", ) parser.add_argument( @@ -258,198 +191,103 @@ def main(): args = parser.parse_args() + if not os.path.isfile(args.original): + parser.error(f"Original binary {args.original} does not exist") + + if not os.path.isfile(args.recompiled): + parser.error(f"Recompiled binary {args.recompiled} does not exist") + + if not os.path.isfile(args.pdb): + parser.error(f"Symbols PDB {args.pdb} does not exist") + + if not os.path.isdir(args.decomp_dir): + parser.error(f"Source directory {args.decomp_dir} does not exist") + + return args + + +def main(): + args = parse_args() logging.basicConfig(level=args.loglevel, format="[%(levelname)s] %(message)s") - colorama.init() - - verbose = None - found_verbose_target = False - if args.verbose: - try: - verbose = int(args.verbose, 16) - except ValueError: - parser.error("invalid verbose argument") - html_path = args.html - - plain = args.no_color - - original = args.original - if not os.path.isfile(original): - parser.error(f"Original binary {original} does not exist") - - recomp = args.recompiled - if not os.path.isfile(recomp): - parser.error(f"Recompiled binary {recomp} does not exist") - - syms = args.pdb - if not os.path.isfile(syms): - parser.error(f"Symbols PDB {syms} does not exist") - - source = args.decomp_dir - if not os.path.isdir(source): - parser.error(f"Source directory {source} does not exist") - - svg = args.svg - - with Bin(original, find_str=True) as origfile, Bin(recomp) as recompfile: - if verbose is not None: + with Bin(args.original, find_str=True) as origfile, Bin( + args.recompiled + ) as recompfile: + if args.verbose is not None: # Mute logger events from compare engine logging.getLogger("isledecomp.compare.db").setLevel(logging.CRITICAL) logging.getLogger("isledecomp.compare.lines").setLevel(logging.CRITICAL) - isle_compare = IsleCompare(origfile, recompfile, syms, source) + isle_compare = IsleCompare(origfile, recompfile, args.pdb, args.decomp_dir) print() - capstone_disassembler = Cs(CS_ARCH_X86, CS_MODE_32) + ### Compare one or none. + + if args.verbose is not None: + match = isle_compare.compare_function(args.verbose) + if match is None: + print(f"Failed to find the function with address 0x{args.verbose:x}") + return + + print_match_verbose( + match, show_both_addrs=args.print_rec_addr, is_plain=args.no_color + ) + return + + ### Compare everything. function_count = 0 total_accuracy = 0 total_effective_accuracy = 0 htmlinsert = [] - matches = [] - if verbose is not None: - match = isle_compare.get_one_function(verbose) - if match is not None: - found_verbose_target = True - matches = [match] - else: - matches = isle_compare.get_functions() - - for match in matches: - # The effective_ratio is the ratio when ignoring differing register - # allocation vs the ratio is the true ratio. - ratio = 0.0 - effective_ratio = 0.0 - if match.size: - origasm = parse_asm( - capstone_disassembler, - origfile, - match.orig_addr, - match.size, - ) - recompasm = parse_asm( - capstone_disassembler, - recompfile, - match.recomp_addr, - match.size, - ) - - diff = difflib.SequenceMatcher(None, origasm, recompasm) - ratio = diff.ratio() - effective_ratio = ratio - - if ratio != 1.0: - # Check whether we can resolve register swaps which are actually - # perfect matches modulo compiler entropy. - if can_resolve_register_differences(origasm, recompasm): - effective_ratio = 1.0 - else: - ratio = 0 - - percenttext = f"{(effective_ratio * 100):.2f}%" - if not plain: - if effective_ratio == 1.0: - percenttext = ( - colorama.Fore.GREEN + percenttext + colorama.Style.RESET_ALL - ) - elif effective_ratio > 0.8: - percenttext = ( - colorama.Fore.YELLOW + percenttext + colorama.Style.RESET_ALL - ) - else: - percenttext = ( - colorama.Fore.RED + percenttext + colorama.Style.RESET_ALL - ) - - if effective_ratio == 1.0 and ratio != 1.0: - if plain: - percenttext += "*" - else: - percenttext += colorama.Fore.RED + "*" + colorama.Style.RESET_ALL - - if args.print_rec_addr: - addrs = f"0x{match.orig_addr:x} / 0x{match.recomp_addr:x}" - else: - addrs = hex(match.orig_addr) - - if not verbose: - print( - f" {match.name} ({addrs}) is {percenttext} similar to the original" - ) + for match in isle_compare.compare_functions(): + print_match_oneline( + match, show_both_addrs=args.print_rec_addr, is_plain=args.no_color + ) function_count += 1 - total_accuracy += ratio - total_effective_accuracy += effective_ratio + total_accuracy += match.ratio + total_effective_accuracy += match.effective_ratio - if match.size: - udiff = difflib.unified_diff(origasm, recompasm, n=10) - - # If verbose, print the diff for that function to the output - if verbose: - if effective_ratio == 1.0: - ok_text = ( - "OK!" - if plain - else ( - colorama.Fore.GREEN - + "✨ OK! ✨" - + colorama.Style.RESET_ALL - ) - ) - if ratio == 1.0: - print(f"{addrs}: {match.name} 100% match.\n\n{ok_text}\n\n") - else: - print( - f"{addrs}: {match.name} Effective 100%% match. (Differs in register allocation only)\n\n{ok_text} (still differs in register allocation)\n\n" - ) - else: - print_diff(udiff, plain) - - print( - f"\n{match.name} is only {percenttext} similar to the original, diff above" - ) - - # If html, record the diffs to an HTML file - if html_path: - htmlinsert.append( - { - "address": f"0x{match.orig_addr:x}", - "name": match.name, - "matching": effective_ratio, - "diff": "\n".join(udiff), - } - ) - - if html_path: - gen_html(html_path, json.dumps(htmlinsert)) - - if verbose: - if not found_verbose_target: - print(f"Failed to find the function with address 0x{verbose:x}") - else: - implemented_funcs = function_count - - if args.total: - function_count = int(args.total) - - if function_count > 0: - effective_accuracy = total_effective_accuracy / function_count * 100 - actual_accuracy = total_accuracy / function_count * 100 - print( - f"\nTotal effective accuracy {effective_accuracy:.2f}% across {function_count} functions ({actual_accuracy:.2f}% actual accuracy)" + # If html, record the diffs to an HTML file + if args.html is not None: + htmlinsert.append( + { + "address": f"0x{match.orig_addr:x}", + "name": match.name, + "matching": match.effective_ratio, + "diff": "\n".join(match.udiff), + } ) - if svg: - gen_svg( - svg, - os.path.basename(original), - args.svg_icon, - implemented_funcs, - function_count, - total_effective_accuracy, - ) + ## Generate files and show summary. + + if args.html is not None: + gen_html(args.html, json.dumps(htmlinsert)) + + implemented_funcs = function_count + + if args.total: + function_count = int(args.total) + + if function_count > 0: + effective_accuracy = total_effective_accuracy / function_count * 100 + actual_accuracy = total_accuracy / function_count * 100 + print( + f"\nTotal effective accuracy {effective_accuracy:.2f}% across {function_count} functions ({actual_accuracy:.2f}% actual accuracy)" + ) + + if args.svg is not None: + gen_svg( + args.svg, + os.path.basename(args.original), + args.svg_icon, + implemented_funcs, + function_count, + total_effective_accuracy, + ) if __name__ == "__main__":