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 <mail@csemmler.com>
This commit is contained in:
MS 2024-01-14 16:28:46 -05:00 committed by GitHub
parent 7f7e6e37dd
commit 7e9d3bde65
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
73 changed files with 1357 additions and 427 deletions

View file

@ -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 = ":;";

View file

@ -20,4 +20,10 @@ class IsleActor : public LegoActor {
}
};
// SYNTHETIC: LEGO1 0x1000e940
// IsleActor::~IsleActor
// SYNTHETIC: LEGO1 0x1000e990
// IsleActor::`scalar deleting destructor'
#endif // ISLEACTOR_H

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -7,6 +7,7 @@
#include <stdlib.h>
// 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

View file

@ -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

View file

@ -94,6 +94,9 @@ class LegoInputManager : public MxPresenter {
// TEMPLATE: LEGO1 0x1005bb80
// MxCollection<LegoEventNotificationParam>::Compare
// TEMPLATE: LEGO1 0x1005bbe0
// MxCollection<LegoEventNotificationParam>::~MxCollection<LegoEventNotificationParam>
// TEMPLATE: LEGO1 0x1005bc30
// MxCollection<LegoEventNotificationParam>::Destroy
@ -109,6 +112,9 @@ class LegoInputManager : public MxPresenter {
// SYNTHETIC: LEGO1 0x1005beb0
// LegoEventQueue::`scalar deleting destructor'
// TEMPLATE: LEGO1 0x1005bf20
// MxQueue<LegoEventNotificationParam>::~MxQueue<LegoEventNotificationParam>
// SYNTHETIC: LEGO1 0x1005bf70
// MxQueue<LegoEventNotificationParam>::`scalar deleting destructor'

View file

@ -21,4 +21,10 @@ class LegoLoopingAnimPresenter : public LegoAnimPresenter {
}
};
// SYNTHETIC: LEGO1 0x1006d000
// LegoLoopingAnimPresenter::~LegoLoopingAnimPresenter
// SYNTHETIC: LEGO1 0x1000f440
// LegoLoopingAnimPresenter::`scalar deleting destructor'
#endif // LEGOLOOPINGANIMPRESENTER_H

View file

@ -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

View file

@ -4,6 +4,7 @@
#include "decomp.h"
#include "mxstring.h"
// VTABLE: LEGO1 0x100d7c88
class LegoUnknown100d7c88 {
public:
~LegoUnknown100d7c88();

View file

@ -27,9 +27,15 @@ class LegoUnknown100d9d00 : public MxList<LegoUnknown100d7c88*> {
// TEMPLATE: LEGO1 0x1007b300
// MxCollection<LegoUnknown100d7c88 *>::Compare
// TEMPLATE: LEGO1 0x1007b310
// MxCollection<LegoUnknown100d7c88 *>::~MxCollection<LegoUnknown100d7c88 *>
// TEMPLATE: LEGO1 0x1007b360
// MxCollection<LegoUnknown100d7c88 *>::Destroy
// TEMPLATE: LEGO1 0x1007b370
// MxList<LegoUnknown100d7c88 *>::~MxList<LegoUnknown100d7c88 *>
// SYNTHETIC: LEGO1 0x1007b400
// LegoUnknown100d9d00::`scalar deleting destructor'

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -11,3 +11,9 @@ LegoActor::LegoActor()
m_unk0x10 = 0;
m_unk0x74 = 0;
}
// STUB: LEGO1 0x1002d320
LegoActor::~LegoActor()
{
// TODO
}

View file

@ -46,7 +46,7 @@ MxResult LegoEntity::Create(MxDSAction& p_dsAction)
{
m_mxEntityId = p_dsAction.GetObjectId();
m_atom = p_dsAction.GetAtomId();
Init();
SetWorld();
return SUCCESS;
}

View file

@ -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()
{

View file

@ -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

View file

@ -6,6 +6,12 @@ LegoAnimPresenter::LegoAnimPresenter()
// TODO
}
// STUB: LEGO1 0x10068670
LegoAnimPresenter::~LegoAnimPresenter()
{
// TODO
}
// STUB: LEGO1 0x100686f0
void LegoAnimPresenter::Init()
{

View file

@ -6,6 +6,12 @@ LegoHideAnimPresenter::LegoHideAnimPresenter()
Init();
}
// STUB: LEGO1 0x1006d9f0
LegoHideAnimPresenter::~LegoHideAnimPresenter()
{
// TODO
}
// STUB: LEGO1 0x1006da50
void LegoHideAnimPresenter::Init()
{

View file

@ -13,7 +13,7 @@ class Camera;
/////////////////////////////////////////////////////////////////////////////
// LegoView
// VTABLE: 0x100dc000
// VTABLE: LEGO1 0x100dc000
// SIZE 0x78
class LegoView : public TglSurface {
public:

View file

@ -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

View file

@ -158,11 +158,15 @@ struct MxDriver {
// List<MxDriver>::~List<MxDriver>
// 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<MxDevice,allocator<MxDevice> >::insert
// SYNTHETIC: LEGO1 0x1009c460
// list<MxDisplayMode,allocator<MxDisplayMode> >::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

View file

@ -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 */

View file

@ -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

View file

@ -43,4 +43,50 @@ struct MxAtomIdCounterCompare {
class MxAtomIdCounterSet : public set<MxAtomIdCounter*, MxAtomIdCounterCompare> {};
// SYNTHETIC: LEGO1 0x100ad170
// MxAtomIdCounter::~MxAtomIdCounter
// clang-format off
// TEMPLATE: LEGO1 0x100ad480
// _Tree<MxAtomIdCounter *,MxAtomIdCounter *,set<MxAtomIdCounter *,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Kfn,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::iterator::_Dec
// clang-format on
// clang-format off
// TEMPLATE: LEGO1 0x100ad780
// _Tree<MxAtomIdCounter *,MxAtomIdCounter *,set<MxAtomIdCounter *,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Kfn,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Lbound
// clang-format on
// clang-format off
// TEMPLATE: LEGO1 0x100ad4d0
// _Tree<MxAtomIdCounter *,MxAtomIdCounter *,set<MxAtomIdCounter *,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Kfn,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Insert
// clang-format on
// clang-format off
// TEMPLATE: LEGO1 0x100af6d0
// _Tree<MxAtomIdCounter *,MxAtomIdCounter *,set<MxAtomIdCounter *,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Kfn,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::~_Tree<MxAtomIdCounter *,MxAtomIdCounter *,set<MxAtomIdCounter *,MxAtomIdCou
// clang-format on
// clang-format off
// TEMPLATE: LEGO1 0x100af7a0
// _Tree<MxAtomIdCounter *,MxAtomIdCounter *,set<MxAtomIdCounter *,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Kfn,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::iterator::_Inc
// clang-format on
// clang-format off
// TEMPLATE: LEGO1 0x100af7e0
// _Tree<MxAtomIdCounter *,MxAtomIdCounter *,set<MxAtomIdCounter *,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Kfn,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::erase
// clang-format on
// clang-format off
// TEMPLATE: LEGO1 0x100afc40
// _Tree<MxAtomIdCounter *,MxAtomIdCounter *,set<MxAtomIdCounter *,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Kfn,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::_Erase
// clang-format on
// clang-format off
// TEMPLATE: LEGO1 0x100afc80
// set<MxAtomIdCounter *,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >::~set<MxAtomIdCounter *,MxAtomIdCounterCompare,allocator<MxAtomIdCounter *> >
// clang-format on
// TEMPLATE: LEGO1 0x100afe40
// Set<MxAtomIdCounter *,MxAtomIdCounterCompare>::~Set<MxAtomIdCounter *,MxAtomIdCounterCompare>
#endif // MXATOMIDCOUNTER_H

View file

@ -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

View file

@ -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<MxDSAction *,allocator<MxDSAction *> >::erase
// TEMPLATE: LEGO1 0x100c7330
// list<MxDSAction *,allocator<MxDSAction *> >::_Buynode

View file

@ -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

View file

@ -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;

View file

@ -44,6 +44,9 @@ class MxDSActionListCursor : public MxListCursor<MxDSAction*> {
// TEMPLATE: LEGO1 0x100c9cc0
// MxCollection<MxDSAction *>::Compare
// TEMPLATE: LEGO1 0x100c9cd0
// MxCollection<MxDSAction *>::~MxCollection<MxDSAction *>
// TEMPLATE: LEGO1 0x100c9d20
// MxCollection<MxDSAction *>::Destroy

View file

@ -3,6 +3,7 @@
#include "mxdsmediaaction.h"
// VTABLE: LEGO1 0x100dce18
class MxDSEvent : public MxDSMediaAction {
public:
MxDSEvent();

View file

@ -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

View file

@ -52,4 +52,34 @@ class MxNotificationManager : public MxCore {
void FlushPending(MxCore* p_listener);
};
// TEMPLATE: LEGO1 0x100ac320
// list<unsigned int,allocator<unsigned int> >::~list<unsigned int,allocator<unsigned int> >
// FUNCTION: LEGO1 0x100ac3b0
// MxIdList::~MxIdList
// TEMPLATE: LEGO1 0x100ac400
// List<unsigned int>::~List<unsigned int>
// TEMPLATE: LEGO1 0x100ac540
// List<MxNotification *>::~List<MxNotification *>
// TEMPLATE: LEGO1 0x100ac590
// list<MxNotification *,allocator<MxNotification *> >::~list<MxNotification *,allocator<MxNotification *> >
// TEMPLATE: LEGO1 0x100acbf0
// list<MxNotification *,allocator<MxNotification *> >::begin
// TEMPLATE: LEGO1 0x100acc00
// list<MxNotification *,allocator<MxNotification *> >::insert
// TEMPLATE: LEGO1 0x100acc50
// list<MxNotification *,allocator<MxNotification *> >::erase
// TEMPLATE: LEGO1 0x100acca0
// list<MxNotification *,allocator<MxNotification *> >::_Buynode
// SYNTHETIC: LEGO1 0x100accd0
// MxNotificationPtrList::~MxNotificationPtrList
#endif // MXNOTIFICATIONMANAGER_H

View file

@ -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

View file

@ -8,6 +8,7 @@
#include <windows.h>
// 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

View file

@ -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

View file

@ -53,6 +53,9 @@ class MxPresenterListCursor : public MxPtrListCursor<MxPresenter> {
// TEMPLATE: LEGO1 0x1001ce20
// MxList<MxPresenter *>::~MxList<MxPresenter *>
// TEMPLATE: LEGO1 0x1001cf20
// MxPtrList<MxPresenter>::~MxPtrList<MxPresenter>
// SYNTHETIC: LEGO1 0x1001cf70
// MxCollection<MxPresenter *>::`scalar deleting destructor'

View file

@ -53,6 +53,9 @@ class MxRectListCursor : public MxPtrListCursor<MxRect32> {
// SYNTHETIC: LEGO1 0x100b3d80
// MxRectList::`scalar deleting destructor'
// TEMPLATE: LEGO1 0x100b3df0
// MxPtrList<MxRect32>::~MxPtrList<MxRect32>
// SYNTHETIC: LEGO1 0x100b3e40
// MxCollection<MxRect32 *>::`scalar deleting destructor'

View file

@ -116,12 +116,21 @@ class MxRegionTopBottomListCursor : public MxPtrListCursor<MxRegionTopBottom> {
// TEMPLATE: LEGO1 0x100c32e0
// MxCollection<MxRegionTopBottom *>::Compare
// TEMPLATE: LEGO1 0x100c32f0
// MxCollection<MxRegionTopBottom *>::~MxCollection<MxRegionTopBottom *>
// TEMPLATE: LEGO1 0x100c3340
// MxCollection<MxRegionTopBottom *>::Destroy
// TEMPLATE: LEGO1 0x100c3350
// MxList<MxRegionTopBottom *>::~MxList<MxRegionTopBottom *>
// TEMPLATE: LEGO1 0x100c33e0
// MxPtrList<MxRegionTopBottom>::Destroy
// TEMPLATE: LEGO1 0x100c3480
// MxPtrList<MxRegionTopBottom>::~MxPtrList<MxRegionTopBottom>
// SYNTHETIC: LEGO1 0x100c34d0
// MxCollection<MxRegionTopBottom *>::`scalar deleting destructor'
@ -167,9 +176,18 @@ class MxRegionTopBottomListCursor : public MxPtrListCursor<MxRegionTopBottom> {
// TEMPLATE: LEGO1 0x100c4d80
// MxCollection<MxRegionLeftRight *>::Compare
// TEMPLATE: LEGO1 0x100c4d90
// MxCollection<MxRegionLeftRight *>::~MxCollection<MxRegionLeftRight *>
// TEMPLATE: LEGO1 0x100c4de0
// MxCollection<MxRegionLeftRight *>::Destroy
// TEMPLATE: LEGO1 0x100c4df0
// MxList<MxRegionLeftRight *>::~MxList<MxRegionLeftRight *>
// TEMPLATE: LEGO1 0x100c4f00
// MxPtrList<MxRegionLeftRight>::~MxPtrList<MxRegionLeftRight>
// SYNTHETIC: LEGO1 0x100c4f50
// MxCollection<MxRegionLeftRight *>::`scalar deleting destructor'

View file

@ -5,12 +5,13 @@
#include <windows.h>
// 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);

View file

@ -41,6 +41,9 @@ class MxStreamChunkListCursor : public MxListCursor<MxStreamChunk*> {
// TEMPLATE: LEGO1 0x100b5930
// MxCollection<MxStreamChunk *>::Compare
// TEMPLATE: LEGO1 0x100b5940
// MxCollection<MxStreamChunk *>::~MxCollection<MxStreamChunk *>
// TEMPLATE: LEGO1 0x100b5990
// MxCollection<MxStreamChunk *>::Destroy

View file

@ -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<MxStreamController *,allocator<MxStreamController *> >::~list<MxStreamController *,allocator<MxStreamController *> >
// clang-format on
// SYNTHETIC: LEGO1 0x100b9120
// MxStreamer::`scalar deleting destructor'
// TEMPLATE: LEGO1 0x100b9140
// List<MxStreamController *>::~List<MxStreamController *>
// SYNTHETIC: LEGO1 0x100b97b0
// MxStreamerNotification::`scalar deleting destructor'
// SYNTHETIC: LEGO1 0x100b9820
// MxStreamerNotification::~MxStreamerNotification
#endif // MXSTREAMER_H

View file

@ -41,4 +41,7 @@ class MxStreamProvider : public MxCore {
// SYNTHETIC: LEGO1 0x100d0870
// MxStreamProvider::`scalar deleting destructor'
// SYNTHETIC: LEGO1 0x100d08e0
// MxStreamProvider::~MxStreamProvider
#endif // MXSTREAMPROVIDER_H

View file

@ -15,18 +15,33 @@ class MxStringListCursor : public MxListCursor<MxString> {
MxStringListCursor(MxStringList* p_list) : MxListCursor<MxString>(p_list){};
};
// VTABLE: LEGO1 0x100dd010
// class MxCollection<MxString>
// VTABLE: LEGO1 0x100dd028
// class MxList<MxString>
// VTABLE: LEGO1 0x100dd070
// class MxListCursor<MxString>
// TEMPLATE: LEGO1 0x100cb3c0
// MxCollection<MxString>::Compare
// TEMPLATE: LEGO1 0x100cb420
// MxCollection<MxString>::~MxCollection<MxString>
// TEMPLATE: LEGO1 0x100cb470
// MxCollection<MxString>::Destroy
// TEMPLATE: LEGO1 0x100cb4c0
// MxList<MxString>::~MxList<MxString>
// SYNTHETIC: LEGO1 0x100cb590
// MxCollection<MxString>::`scalar deleting destructor'
// SYNTHETIC: LEGO1 0x100cb600
// MxList<MxString>::`scalar deleting destructor'
// TEMPLATE: LEGO1 0x100cbb40
// MxList<MxString>::Append

View file

@ -50,4 +50,10 @@ class MxTickleManager : public MxCore {
#define TICKLE_MANAGER_NOT_FOUND 0x80000000
// TEMPLATE: LEGO1 0x1005a4a0
// list<MxTickleClient *,allocator<MxTickleClient *> >::~list<MxTickleClient *,allocator<MxTickleClient *> >
// TEMPLATE: LEGO1 0x1005a530
// List<MxTickleClient *>::~List<MxTickleClient *>
#endif // MXTICKLEMANAGER_H

View file

@ -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

View file

@ -26,6 +26,9 @@ class MxVariableTable : public MxHashTable<MxVariable*> {
// VTABLE: LEGO1 0x100dc1e8
// class MxHashTable<MxVariable *>
// VTABLE: LEGO1 0x100dc680
// class MxHashTableCursor<MxVariable *>
// TEMPLATE: LEGO1 0x100afcd0
// MxCollection<MxVariable *>::Compare
@ -50,6 +53,9 @@ class MxVariableTable : public MxHashTable<MxVariable*> {
// SYNTHETIC: LEGO1 0x100b0ca0
// MxHashTable<MxVariable *>::`scalar deleting destructor'
// TEMPLATE: LEGO1 0x100b7680
// MxHashTableCursor<MxVariable *>::~MxHashTableCursor<MxVariable *>
// TEMPLATE: LEGO1 0x100b7ab0
// MxHashTable<MxVariable *>::Resize

View file

@ -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;

View file

@ -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()
{

View file

@ -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);

View file

@ -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 {

View file

@ -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;
}

View file

@ -7,6 +7,7 @@
DECOMP_SIZE_ASSERT(MxDisplaySurface, 0xac);
// GLOBAL: LEGO1 0x1010215c
MxU32 g_unk0x1010215c = 0;
// FUNCTION: LEGO1 0x100ba500

View file

@ -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()));

View file

@ -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

View file

@ -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

View file

@ -108,4 +108,8 @@ class ROI {
LODListBase* m_lods; // 0x8
undefined m_unk0xc; // 0xc
};
// SYNTHETIC: LEGO1 0x100a5d50
// ROI::~ROI
#endif // ROI_H

View file

@ -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) {}

View file

@ -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

View file

@ -93,6 +93,22 @@ class ViewLODListManager {
ViewLODListMap m_map;
};
// FUNCTION: LEGO1 0x1001dde0
// _Lockit::~_Lockit
// clang-format off
// TEMPLATE: LEGO1 0x100a7890
// _Tree<char const *,pair<char const * const,ViewLODList *>,map<char const *,ViewLODList *,ROINameComparator,allocator<ViewLODList *> >::_Kfn,ROINameComparator,allocator<ViewLODList *> >::~_Tree<char const *,pair<char const * const,ViewLODList *>,map<char c
// clang-format on
// clang-format off
// TEMPLATE: LEGO1 0x100a80a0
// map<char const *,ViewLODList *,ROINameComparator,allocator<ViewLODList *> >::~map<char const *,ViewLODList *,ROINameComparator,allocator<ViewLODList *> >
// clang-format on
// TEMPLATE: LEGO1 0x100a70e0
// Map<char const *,ViewLODList *,ROINameComparator>::~Map<char const *,ViewLODList *,ROINameComparator>
//////////////////////////////////////////////////////////////////////////////
//
// ViewLODList implementation

View file

@ -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("<I", self.read(addr, 4))
self._relocated_addrs.add(relocated_addr)
def _populate_imports(self):
"""Parse .idata to find imported DLLs and their functions."""
idata_ofs = self.get_section_offset_by_name(".idata")
def iter_image_import():
ofs = idata_ofs
while True:
# Read 5 dwords until all are zero.
image_import_descriptor = struct.unpack("<5I", self.read(ofs, 20))
ofs += 20
if all(x == 0 for x in image_import_descriptor):
break
(rva_ilt, _, __, dll_name, rva_iat) = image_import_descriptor
# Convert relative virtual addresses into absolute
yield (
self.imagebase + rva_ilt,
self.imagebase + dll_name,
self.imagebase + rva_iat,
)
image_import_descriptors = list(iter_image_import())
def iter_imports():
# ILT = Import Lookup Table
# IAT = Import Address Table
# ILT gives us the symbol name of the import.
# IAT gives the address. The compiler generated a thunk function
# that jumps to the value of this address.
for start_ilt, dll_addr, start_iat in image_import_descriptors:
dll_name = self.read_string(dll_addr).decode("ascii")
ofs_ilt = start_ilt
# Address of "__imp__*" symbols.
ofs_iat = start_iat
while True:
(lookup_addr,) = struct.unpack("<L", self.read(ofs_ilt, 4))
(import_addr,) = struct.unpack("<L", self.read(ofs_iat, 4))
if lookup_addr == 0 or import_addr == 0:
break
# Skip the "Hint" field, 2 bytes
name_ofs = lookup_addr + self.imagebase + 2
symbol_name = self.read_string(name_ofs).decode("ascii")
yield (dll_name, symbol_name, ofs_iat)
ofs_ilt += 4
ofs_iat += 4
self.imports = list(iter_imports())
def _populate_thunks(self):
"""For each imported function, we generate a thunk function. The only
instruction in the function is a jmp to the address in .idata.
Search .text to find these functions."""
text_sect = self._get_section_by_name(".text")
idata_sect = self._get_section_by_name(".idata")
start = text_sect.virtual_address
ofs = start
bs = self.read(ofs, text_sect.size_of_raw_data)
for shift in (0, 2, 4):
window = bs[shift:]
win_end = 6 * (len(window) // 6)
for i, (b0, b1, jmp_ofs) in enumerate(
struct.iter_unpack("<2BL", window[:win_end])
):
if (b0, b1) == (0xFF, 0x25) and idata_sect.contains_vaddr(jmp_ofs):
# Record the address of the jmp instruction and the destination in .idata
thunk_ofs = ofs + shift + i * 6
self.thunks.append((thunk_ofs, jmp_ofs))
def _set_section_for_vaddr(self, vaddr: int):
if self.last_section is not None and self.last_section.contains_vaddr(vaddr):
return
@ -319,6 +395,18 @@ def is_valid_vaddr(self, vaddr: int) -> 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."""

View file

@ -0,0 +1,2 @@
from .parse import ParseAsm
from .swap import can_resolve_register_differences

View file

@ -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"<OFFSET{idx}>" if self.number_placeholders else "<OFFSET>"
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

View file

@ -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

View file

@ -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

View file

@ -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):

View file

@ -36,7 +36,7 @@
# e.g. `S_GDATA32: [0003:000004A4], Type: T_32PRCHAR(0470), g_set`
_gdata32_regex = re.compile(
r"S_GDATA32: \[(?P<section>\w{4}):(?P<offset>\w{8})\], Type:\s*(?P<type>\S+), (?P<name>\S+)"
r"S_GDATA32: \[(?P<section>\w{4}):(?P<offset>\w{8})\], Type:\s*(?P<type>\S+), (?P<name>.+)"
)

View file

@ -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"<OFFSET{self.counter}>"
self.replacements[replace_addr] = replacement
return replacement

View file

@ -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 [<OFFSET1>]"),
("word ptr [0x5555]", "word ptr [<OFFSET1>]"),
("dword ptr [0x5555]", "dword ptr [<OFFSET1>]"),
("qword ptr [0x5555]", "qword ptr [<OFFSET1>]"),
("eax, dword ptr [0x5555]", "eax, dword ptr [<OFFSET1>]"),
("dword ptr [0x5555], eax", "dword ptr [<OFFSET1>], eax"),
("dword ptr [0x5555], 0", "dword ptr [<OFFSET1>], 0"),
("dword ptr [0x5555], 8", "dword ptr [<OFFSET1>], 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 [<OFFSET1>], 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", "<OFFSET1>"),
("dword ptr [0x1234]", "dword ptr [<OFFSET1>]"),
("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 + <OFFSET1>]"
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 [<OFFSET1>]"
(_, op_str) = p.sanitize(inst)
assert op_str == "dword ptr [<OFFSET1>]"
def test_replacement_numbering():
"""If we can use the name lookup for the first address but not the second,
the second replacement should be <OFFSET2> not <OFFSET1>."""
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 [<OFFSET2>]"
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, <OFFSET1>"
(_, 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"

View file

@ -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="<offset>",
type=virtual_address,
help="Print assembly diff for specific function (original file's offset)",
)
parser.add_argument(
@ -258,177 +191,82 @@ 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
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"
)
total_accuracy += match.ratio
total_effective_accuracy += match.effective_ratio
# If html, record the diffs to an HTML file
if html_path:
if args.html is not None:
htmlinsert.append(
{
"address": f"0x{match.orig_addr:x}",
"name": match.name,
"matching": effective_ratio,
"diff": "\n".join(udiff),
"matching": match.effective_ratio,
"diff": "\n".join(match.udiff),
}
)
if html_path:
gen_html(html_path, json.dumps(htmlinsert))
## Generate files and show summary.
if args.html is not None:
gen_html(args.html, 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:
@ -441,10 +279,10 @@ def main():
f"\nTotal effective accuracy {effective_accuracy:.2f}% across {function_count} functions ({actual_accuracy:.2f}% actual accuracy)"
)
if svg:
if args.svg is not None:
gen_svg(
svg,
os.path.basename(original),
args.svg,
os.path.basename(args.original),
args.svg_icon,
implemented_funcs,
function_count,