diff --git a/.gitignore b/.gitignore index 81e69517..e266e7b2 100644 --- a/.gitignore +++ b/.gitignore @@ -14,8 +14,6 @@ ENV/ VENV/ env.bak/ venv.bak/ -/build/ -/build_debug/ /legobin/ *.swp LEGO1PROGRESS.* @@ -25,5 +23,23 @@ tools/ghidra_scripts/import.log # By convention we put the retail binaries into ./legobin. # These entries are kept for now since that convention has not always been around. -ISLE.EXE -LEGO1.DLL +build/*.EXE +build/B_TMA.DLL +build/X10_TMA.DLL +build/*.pdb +build/*.lib +build/*.exp +setupPaths.bat +build/CMakeFiles/ +build/SAV/ + +build/CMakeCache.txt +build/Makefile +build/cmake_install.cmake +build/tma_sources.txt + +dgVoodooSetup.exe +build/Lego/ +!build/Lego/Scripts +build/Lego/Scripts/* +!build/Lego/Scripts/NOCD.SI diff --git a/CMakeLists.txt b/CMakeLists.txt index 2fc4a4ca..f9ec51df 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,6 +58,7 @@ endfunction() message(STATUS "MSVC for decompilation: ${MSVC_FOR_DECOMP}") option(ISLE_WERROR "Treat warnings as errors" OFF) +<<<<<<< Updated upstream option(ISLE_BUILD_APP "Build ISLE.EXE application" ON) cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIG.EXE application" ON "NOT MINGW" OFF) option(ISLE_USE_SMARTHEAP "Build LEGO1.DLL with SmartHeap" ${MSVC_FOR_DECOMP}) @@ -66,6 +67,16 @@ option(ISLE_DECOMP_ASSERT "Assert struct size" ${MSVC_FOR_DECOMP}) cmake_dependent_option(ISLE_USE_DX5_LIBS "Build with internal DirectX 5 SDK Libraries" ON ISLE_USE_DX5 OFF) option(ISLE_BUILD_LEGO1 "Build LEGO1.DLL library" ON) option(ISLE_BUILD_BETA10 "Build BETA10.DLL library" OFF) +======= +option(ISLE_BUILD_APP "Build TMA_LAUNCHER.EXE application" ON) +cmake_dependent_option(ISLE_BUILD_CONFIG "Build CONFIGURE.EXE application" ON "NOT MINGW" OFF) +option(ISLE_USE_SMARTHEAP "Build B_TMA.DLL with SmartHeap" ${MSVC_FOR_DECOMP}) +option(ISLE_USE_DX5 "Build with internal DirectX 5 SDK" ON) +option(ISLE_DECOMP_ASSERT "Assert struct size" ${MSVC_FOR_DECOMP}) +cmake_dependent_option(ISLE_USE_DX5_LIBS "Build with internal DirectX 5 SDK Libraries" ON ISLE_USE_DX5 OFF) +option(ISLE_BUILD_LEGO1 "Build B_TMA.DLL library" ON) +option(ISLE_BUILD_BETA10 "Build X10_TMA.DLL library" OFF) +>>>>>>> Stashed changes if(NOT (ISLE_BUILD_LEGO1 OR ISLE_BUILD_BETA10)) message(FATAL_ERROR "ISLE_BUILD_LEGO1 AND ISLE_BUILD_BETA10 cannot be both disabled") @@ -321,9 +332,17 @@ function(add_lego_libraries NAME) LEGO1/lego/legoomni/src/paths/legoanimactor.cpp LEGO1/lego/legoomni/src/entity/legoworld.cpp LEGO1/lego/legoomni/src/build/legocarbuild.cpp +<<<<<<< Updated upstream + LEGO1/lego/legoomni/src/actors/ambulance.cpp LEGO1/lego/legoomni/src/common/legoplantmanager.cpp LEGO1/lego/legoomni/src/actors/bumpbouy.cpp LEGO1/modeldb/modeldb.cpp + LEGO1/lego/legoomni/src/actors/pizza.cpp +======= + LEGO1/lego/legoomni/src/common/legoplantmanager.cpp + LEGO1/lego/legoomni/src/actors/bumpbouy.cpp + LEGO1/modeldb/modeldb.cpp +>>>>>>> Stashed changes LEGO1/lego/legoomni/src/actors/racecar.cpp LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp @@ -334,8 +353,11 @@ function(add_lego_libraries NAME) LEGO1/lego/legoomni/src/paths/legopathactor.cpp LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp LEGO1/lego/legoomni/src/worlds/isle.cpp +<<<<<<< Updated upstream +======= LEGO1/lego/legoomni/src/actors/ambulance.cpp LEGO1/lego/legoomni/src/actors/pizza.cpp +>>>>>>> Stashed changes LEGO1/lego/legoomni/src/actors/motorcycle.cpp LEGO1/lego/legoomni/src/actors/act3ammo.cpp LEGO1/lego/legoomni/src/audio/legocachesoundmanager.cpp @@ -468,7 +490,11 @@ endif() if(ISLE_BUILD_LEGO1) add_lego_libraries(lego1 LINK_LIBRARIES ${lego1_link_libraries} +<<<<<<< Updated upstream DLL_OUTPUT_NAME "LEGO1" +======= + DLL_OUTPUT_NAME "B_TMA" +>>>>>>> Stashed changes DLL_PREFIX "" DLL_SUFFIX ".DLL" OUT_TARGETS lego1_targets @@ -479,7 +505,11 @@ endif() if(ISLE_BUILD_BETA10) add_lego_libraries(beta10 SUFFIX "-beta10" +<<<<<<< Updated upstream DLL_OUTPUT_NAME "BETA10" +======= + DLL_OUTPUT_NAME "X10_TMA" +>>>>>>> Stashed changes DLL_PREFIX "" DLL_SUFFIX ".DLL" OUT_TARGETS beta10_targets @@ -513,7 +543,11 @@ if (ISLE_BUILD_APP) endif() # Make sure filenames are ALL CAPS +<<<<<<< Updated upstream set_property(TARGET isle PROPERTY OUTPUT_NAME ISLE) +======= + set_property(TARGET isle PROPERTY OUTPUT_NAME TMA_LAUNCHER) +>>>>>>> Stashed changes set_property(TARGET isle PROPERTY SUFFIX ".EXE") endif() @@ -537,7 +571,11 @@ if (ISLE_BUILD_CONFIG) endif() target_compile_definitions(config PRIVATE DIRECT3D_VERSION=0x500) target_link_libraries(config PRIVATE ddraw dxguid) +<<<<<<< Updated upstream set_property(TARGET config PROPERTY OUTPUT_NAME "CONFIG") +======= + set_property(TARGET config PROPERTY OUTPUT_NAME "CONFIGURE") +>>>>>>> Stashed changes set_property(TARGET config PROPERTY SUFFIX ".EXE") set_property(TARGET config PROPERTY MSVC_RUNTIME_LIBRARY MultiThreaded$<$:Debug>DLL) endif() diff --git a/CONFIG/MainDlg.cpp b/CONFIG/MainDlg.cpp index 7ae5393e..2af9d460 100644 --- a/CONFIG/MainDlg.cpp +++ b/CONFIG/MainDlg.cpp @@ -32,6 +32,7 @@ ON_COMMAND(IDC_RAD_PALETTE_256, OnRadiobuttonPalette256) ON_COMMAND(IDC_CHK_3D_VIDEO_MEMORY, OnCheckbox3DVideoMemory) ON_WM_DESTROY() // FIXME: CONFIG.EXE calls Default ON_COMMAND(IDABORT, OnButtonCancel) +ON_COMMAND(IDAPPLY, OnApply) ON_COMMAND(IDC_CHK_3DSOUND, OnCheckbox3DSound) ON_COMMAND(IDC_RAD_MODEL_QUALITY_LOW, OnRadiobuttonModelLowQuality) ON_COMMAND(IDC_RAD_MODEL_QUALITY_HIGH, OnRadiobuttonModelHighQuality) @@ -41,6 +42,7 @@ ON_COMMAND(IDC_CHK_JOYSTICK, OnCheckboxJoystick) ON_COMMAND(IDC_BTN_ADVANCED, OnButtonAdvanced) ON_COMMAND(IDC_CHK_DRAW_CURSOR, OnCheckboxDrawCursor) ON_COMMAND(IDC_CHK_MUSIC, OnCheckboxMusic) +ON_COMMAND(IDC_CHK_FULLSCREEN, OnCheckboxFullscreen) END_MESSAGE_MAP() // FUNCTION: CONFIG 0x00403e80 @@ -168,6 +170,14 @@ void CMainDialog::OnButtonCancel() OnCancel(); } +void CMainDialog::OnApply() +{ + if (m_modified) { + currentConfigApp->WriteRegisterSettings(); + } +} + + // FUNCTION: CONFIG 0x00404360 void CMainDialog::UpdateInterface() { @@ -218,6 +228,7 @@ void CMainDialog::UpdateInterface() ); CheckDlgButton(IDC_CHK_JOYSTICK, currentConfigApp->m_use_joystick); CheckDlgButton(IDC_CHK_MUSIC, currentConfigApp->m_music); + CheckDlgButton(IDC_CHK_FULLSCREEN, currentConfigApp->m_full_screen); } // FUNCTION: CONFIG 0x004045e0 @@ -344,3 +355,10 @@ void CMainDialog::OnCheckboxMusic() m_modified = TRUE; UpdateInterface(); } + +void CMainDialog::OnCheckboxFullscreen() +{ + currentConfigApp->m_full_screen = IsDlgButtonChecked(IDC_CHK_FULLSCREEN); + m_modified = TRUE; + UpdateInterface(); +} \ No newline at end of file diff --git a/CONFIG/MainDlg.h b/CONFIG/MainDlg.h index eeebc868..346a9185 100644 --- a/CONFIG/MainDlg.h +++ b/CONFIG/MainDlg.h @@ -35,6 +35,7 @@ class CMainDialog : public CDialog { void OnCancel(); void OnDestroy(); void OnButtonCancel(); + void OnApply(); void OnCheckbox3DSound(); void OnCheckbox3DVideoMemory(); void OnRadiobuttonPalette16bit(); @@ -48,6 +49,7 @@ class CMainDialog : public CDialog { void OnButtonAdvanced(); void OnCheckboxDrawCursor(); void OnCheckboxMusic(); + void OnCheckboxFullscreen(); DECLARE_MESSAGE_MAP() }; diff --git a/CONFIG/config.cpp b/CONFIG/config.cpp index d4457b55..c9d2ae0e 100644 --- a/CONFIG/config.cpp +++ b/CONFIG/config.cpp @@ -29,12 +29,17 @@ CConfigApp::CConfigApp() BOOL CConfigApp::InitInstance() { if (!IsLegoNotRunning()) { + AfxMessageBox( + "\"LEGO Island: The Modder's Arrival\" configuration requests that you exit \"LEGO Island: The Modder's Arrival\" " + "before continuing. This ensures that no unwanted behavior occurs by preventing you from changing configurations " + "while the game is running." + ); return FALSE; } if (!DetectDirectX5()) { AfxMessageBox( - "\"LEGO\xae Island\" is not detecting DirectX 5 or later. Please quit all other applications and try " - "again." + "\"LEGO Island: The Modder's Arrival\" configuration failed to detect an installation of DirectX 5. Please ensure any possibly" + "interfering applications have been terminated before re-launching \"LEGO Island: The Modder's Arrival\"." ); return FALSE; } @@ -45,7 +50,7 @@ BOOL CConfigApp::InitInstance() #endif CConfigCommandLineInfo cmdInfo; ParseCommandLine(cmdInfo); - if (_stricmp(afxCurrentAppName, "config") == 0) { + if (_stricmp(afxCurrentAppName, "configure") == 0) { m_run_config_dialog = TRUE; } m_device_enumerator = new LegoDeviceEnumerate; @@ -90,7 +95,7 @@ BOOL CConfigApp::InitInstance() m_device = NULL; char password[256]; ReadReg("password", password, sizeof(password)); - const char* exe = _stricmp("ogel", password) == 0 ? "isled.exe" : "isle.exe"; + const char* exe = _stricmp("ogel", password) == 0 ? "isled.exe" : "isle_tma.exe"; char diskpath[1024]; if (ReadReg("diskpath", diskpath, sizeof(diskpath))) { _chdir(diskpath); @@ -106,8 +111,17 @@ BOOL CConfigApp::InitInstance() // FUNCTION: CONFIG 0x00403100 BOOL CConfigApp::IsLegoNotRunning() { + //hwnd is legacy, hwnd2 is the handle used by TMA HWND hWnd = FindWindowA("Lego Island MainNoM App", "LEGO\xae"); - if (_stricmp(afxCurrentAppName, "config") == 0 || !hWnd) { + HWND hWnd2 = FindWindowA("TMAOMNI PrimaryRuntimeWrapper", "LEGO Island: The Modder's Arrival"); + // This only part of this code that works is checking whether the app filename is configure or not. + // Why does it even check for this. + // And the only part that should work doesn't. Why? Has it ever? + + // Hello me from 30 minutes ago - this was really fucking simple to fix. Are you stupid? + + //if (_stricmp(afxCurrentAppName, "configure") == 0 && hWnd && hWnd2) { + if (!hWnd && !hWnd2) { return TRUE; } if (SetForegroundWindow(hWnd)) { @@ -124,7 +138,7 @@ BOOL CConfigApp::WriteReg(const char* p_key, const char* p_value) const if (RegCreateKeyExA( HKEY_LOCAL_MACHINE, - "SOFTWARE\\Mindscape\\LEGO Island", + "SOFTWARE\\ActionSoft\\LEGO Island TMA", 0, "string", 0, @@ -153,7 +167,7 @@ BOOL CConfigApp::ReadReg(LPCSTR p_key, LPCSTR p_value, DWORD p_size) const BOOL out = FALSE; DWORD size = p_size; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Mindscape\\LEGO Island", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\ActionSoft\\LEGO Island TMA", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { if (RegQueryValueExA(hKey, p_key, NULL, &valueType, (LPBYTE) p_value, &size) == ERROR_SUCCESS) { if (RegCloseKey(hKey) == ERROR_SUCCESS) { out = TRUE; diff --git a/CONFIG/config.h b/CONFIG/config.h index 9172e2d5..265d047d 100644 --- a/CONFIG/config.h +++ b/CONFIG/config.h @@ -55,22 +55,22 @@ class CConfigApp : public CWinApp { public: LegoDeviceEnumerate* m_device_enumerator; // 0x0c4 - MxDriver* m_driver; // 0x0c8 - Direct3DDeviceInfo* m_device; // 0x0cc - int m_display_bit_depth; // 0x0d0 - BOOL m_flip_surfaces; // 0x0d4 - BOOL m_full_screen; // 0x0d8 - BOOL m_3d_video_ram; // 0x0dc - BOOL m_wide_view_angle; // 0x0e0 - BOOL m_3d_sound; // 0x0e4 - BOOL m_draw_cursor; // 0x0e8 - BOOL m_use_joystick; // 0x0ec - int m_joystick_index; // 0x0f0 - BOOL m_run_config_dialog; // 0x0f4 - int m_model_quality; // 0x0f8 - int m_texture_quality; // 0x0fc - undefined m_unk0x100[4]; // 0x100 - BOOL m_music; // 0x104 + MxDriver* m_driver; // 0x0c8 + Direct3DDeviceInfo* m_device; // 0x0cc + int m_display_bit_depth; // 0x0d0 + BOOL m_flip_surfaces; // 0x0d4 + BOOL m_full_screen; // 0x0d8 + BOOL m_3d_video_ram; // 0x0dc + BOOL m_wide_view_angle; // 0x0e0 + BOOL m_3d_sound; // 0x0e4 + BOOL m_draw_cursor; // 0x0e8 + BOOL m_use_joystick; // 0x0ec + int m_joystick_index; // 0x0f0 + BOOL m_run_config_dialog; // 0x0f4 + int m_model_quality; // 0x0f8 + int m_texture_quality; // 0x0fc + undefined m_unk0x100[4]; // 0x100 + BOOL m_music; // 0x104 }; // SYNTHETIC: CONFIG 0x00402cd0 diff --git a/CONFIG/res/CONFIG.ico b/CONFIG/res/CONFIG.ico new file mode 100644 index 00000000..1e67eec2 Binary files /dev/null and b/CONFIG/res/CONFIG.ico differ diff --git a/CONFIG/res/config.aps b/CONFIG/res/config.aps new file mode 100644 index 00000000..739c1477 Binary files /dev/null and b/CONFIG/res/config.aps differ diff --git a/CONFIG/res/config.rc b/CONFIG/res/config.rc index a6164bfa..ef2a2d6f 100644 --- a/CONFIG/res/config.rc +++ b/CONFIG/res/config.rc @@ -1,82 +1,216 @@ -#include "resource.h" -#include "afxres.h" - -IDI_CONFIG ICON "lego.ico" - -IDB_SHARK BITMAP "shark.bmp" - -IDD_ABOUT DIALOG 0, 0, 217, 55 -STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU -CAPTION "About Configure LEGO\xAE Island" -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -FONT 8, "MS Sans Serif" -BEGIN - CONTROL IDI_CONFIG, -1, "STATIC", SS_ICON | WS_CHILD | WS_VISIBLE, 11, 17, 18, 20 - CONTROL "Configure LEGO Island Version 1.0", -1, "STATIC", SS_LEFT | SS_NOPREFIX | WS_CHILD | WS_VISIBLE | WS_GROUP, 40, 10, 119, 8 - CONTROL "Copyright \xA9 1997 mindscape", -1, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 40, 25, 119, 8 - CONTROL "OK", IDOK, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 178, 7, 32, 14 -END - -STRINGTABLE -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -BEGIN - IDS_ABOUT, "&About Config..." -END - -IDD_MAIN DIALOGEX 0, 0, 289, 247 -STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU -EXSTYLE WS_EX_APPWINDOW -CAPTION "Configure LEGO Island" -LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US -FONT 8, "MS Sans Serif" -BEGIN - CONTROL "OK", IDABORT, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 172, 125, 44, 14 - CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 238, 125, 44, 14 - CONTROL "Fast", IDC_RAD_MODEL_QUALITY_LOW, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 124, 18, 42, 10 - CONTROL "High", IDC_RAD_MODEL_QUALITY_HIGH, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 192, 18, 69, 10 - CONTROL "Fast", IDC_RAD_TEXTURE_QUALITY_LOW, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 124, 49, 43, 10 - CONTROL "High", IDC_RAD_TEXTURE_QUALITY_HIGH, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 192, 49, 69, 10 - CONTROL "256 Color", IDC_RAD_PALETTE_256, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 124, 80, 50, 10 - CONTROL "High Color(16 bit)", IDC_RAD_PALETTE_16BIT, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 192, 80, 76, 10 - CONTROL "Use Joystick", IDC_CHK_JOYSTICK, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 192, 104, 60, 10 - CONTROL "", IDC_LIST_3DDEVICES, "LISTBOX", LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL, 115, 168, 161, 36 , WS_EX_TRANSPARENT - CONTROL "Flip Video Memory Pages", IDC_CHK_FLIP_VIDEO_MEM_PAGES, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE, 116, 224, 96, 15 - CONTROL "Draw 3D to Video Memory", IDC_CHK_3D_VIDEO_MEMORY, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE, 116, 210, 99, 10 - CONTROL "Color Palette", -1, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 107, 69, 175, 26 - CONTROL "Direct 3D Devices", -1, "STATIC", SS_CENTER | WS_CHILD | WS_VISIBLE | WS_GROUP, 123, 158, 143, 10 - CONTROL "3D Sound", IDC_CHK_3DSOUND, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE, 220, 210, 45, 10 - CONTROL "Island Model Quality", -1, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 107, 7, 175, 26 - CONTROL "Island Texture Quality", -1, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 107, 38, 175, 26 - CONTROL "Advanced Settings", IDC_GRP_ADVANCED, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 107, 147, 175, 93 - CONTROL "Advanced", IDC_BTN_ADVANCED, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE, 109, 124, 44, 14 - CONTROL IDB_SHARK, IDC_BMP_SHARK, "STATIC", SS_BITMAP | WS_CHILD | WS_VISIBLE, 7, 7, 83, 249 - CONTROL "Draw Cursor", IDC_CHK_DRAW_CURSOR, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE, 219, 227, 54, 10 - CONTROL "Music", IDC_CHK_MUSIC, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 124, 104, 35, 10 -END - -1 VERSIONINFO -FILEVERSION 1,1,0,0 -PRODUCTVERSION 1,1,0,0 -FILEOS 0x4 -FILETYPE 0x1 -BEGIN - BLOCK "StringFileInfo" - BEGIN - BLOCK "040904b0" - BEGIN - VALUE "CompanyName", "Mindscape, Inc." - VALUE "FileDescription", "LEGOIsland & Configuration application" - VALUE "FileVersion", "1, 1, 0, 0" - VALUE "InternalName", "LEGOISLE.EXE" - VALUE "LegalCopyright", "Copyright \xA9 1997" - VALUE "OriginalFilename", "CONFIG.EXE" - VALUE "ProductName", "LEGO Island" - VALUE "ProductVersion", "1, 1, 0, 0" - END - END - - BLOCK "VarFileInfo" - BEGIN - VALUE "Translation", 0x0409, 0x04B0 // U.S. English, Unicode - END -END +//Microsoft Developer Studio generated resource script. +// +#include "resrc1.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "resource.h" +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +///////////////////////////////////////////////////////////////////////////// +// +// Icon +// + +// Icon with lowest ID value placed first to ensure application icon +// remains consistent on all systems. +IDI_CONFIG ICON DISCARDABLE "CONFIG.ico" + +///////////////////////////////////////////////////////////////////////////// +// +// Bitmap +// + +IDB_SHARK BITMAP MOVEABLE PURE "shark.bmp" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_ABOUT DIALOG DISCARDABLE 0, 0, 217, 55 +STYLE DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About Configuration Application" +FONT 8, "MS Sans Serif" +BEGIN + ICON IDI_CONFIG,-1,11,17,18,20 + LTEXT "Configuration settings for ",-1,40,10,119,8,SS_NOPREFIX + LTEXT "LEGO Island: The Modder's Arrival",-1,40,20,119,8, + SS_NOPREFIX + LTEXT "Copyright � 2025 i guess",-1,40,30,119,8 + DEFPUSHBUTTON "OK",IDOK,178,7,32,14,WS_GROUP +END + +IDD_MAIN DIALOGEX 0, 0, 289, 250 +STYLE DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | + WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Configuration for LEGO Island: The Modder's Arrival" +FONT 8, "MS Sans Serif" +BEGIN + PUSHBUTTON "Save && Exit",IDABORT,194,123,44,14 + PUSHBUTTON "Cancel",IDCANCEL,238,123,44,14 + CONTROL "Fast",IDC_RAD_MODEL_QUALITY_LOW,"Button", + BS_AUTORADIOBUTTON,118,18,42,10 + CONTROL "High",IDC_RAD_MODEL_QUALITY_HIGH,"Button", + BS_AUTORADIOBUTTON,192,18,69,10 + CONTROL "Fast",IDC_RAD_TEXTURE_QUALITY_LOW,"Button", + BS_AUTORADIOBUTTON,118,49,43,10 + CONTROL "High",IDC_RAD_TEXTURE_QUALITY_HIGH,"Button", + BS_AUTORADIOBUTTON,192,49,69,10 + CONTROL "8-bit (256) Colour",IDC_RAD_PALETTE_256,"Button", + BS_AUTORADIOBUTTON,118,80,69,10 + CONTROL "16-bit (65536) Colour",IDC_RAD_PALETTE_16BIT,"Button", + BS_AUTORADIOBUTTON,192,80,80,10 + CONTROL "Use Joystick",IDC_CHK_JOYSTICK,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,219,104,54,10 + LISTBOX IDC_LIST_3DDEVICES,115,168,161,36,LBS_NOINTEGRALHEIGHT | + WS_VSCROLL,WS_EX_TRANSPARENT + CONTROL "Flip Video Memory Pages",IDC_CHK_FLIP_VIDEO_MEM_PAGES, + "Button",BS_AUTOCHECKBOX,116,224,96,15 + CONTROL "Draw 3D to Video Memory",IDC_CHK_3D_VIDEO_MEMORY, + "Button",BS_AUTOCHECKBOX,116,210,99,10 + GROUPBOX "Color Palette",-1,107,69,175,26 + CTEXT "Direct 3D Devices",-1,123,158,143,10 + CONTROL "3D Sound",IDC_CHK_3DSOUND,"Button",BS_AUTOCHECKBOX,220, + 210,45,10 + GROUPBOX "Island Model Quality",-1,107,7,175,26 + GROUPBOX "Island Texture Quality",-1,107,38,175,26 + GROUPBOX "Advanced Settings",IDC_GRP_ADVANCED,107,147,175,93 + PUSHBUTTON "Advanced",IDC_BTN_ADVANCED,107,123,43,14,NOT WS_TABSTOP + CONTROL 135,IDC_BMP_SHARK,"Static",SS_BITMAP,7,0,83,249 + CONTROL "Draw Cursor",IDC_CHK_DRAW_CURSOR,"Button", + BS_AUTOCHECKBOX,219,227,54,10 + CONTROL "Music",IDC_CHK_MUSIC,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,114,104,35,10 + CONTROL "Fullscreen",IDC_CHK_FULLSCREEN,"Button",BS_AUTOCHECKBOX | + WS_TABSTOP,166,104,45,10 + PUSHBUTTON "Save",IDAPPLY,150,123,44,14 +END + + +#ifndef _MAC +///////////////////////////////////////////////////////////////////////////// +// +// Version +// + +1 VERSIONINFO + FILEVERSION 1,0,0,0 + PRODUCTVERSION 1,0,0,0 + FILEFLAGSMASK 0x0L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x1L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "FUNNIMAN.EXE" + VALUE "FileDescription", "b_TMAOmni Library Configurator" + VALUE "FileVersion", "1, 0, 0, 0" + VALUE "InternalName", "b_TMAOmni Library Configurator" + VALUE "LegalCopyright", "Copyright \xA9 2025" + VALUE "OriginalFilename", "CONFIGURE.EXE" + VALUE "ProductName", "LEGO Island: The Modder's Arrival" + VALUE "ProductVersion", "1, 0, 0, 0" + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END + +#endif // !_MAC + + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE DISCARDABLE +BEGIN + "resrc1.h\0" +END + +2 TEXTINCLUDE DISCARDABLE +BEGIN + "#include ""resource.h""\r\n" + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE DISCARDABLE +BEGIN + "\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO DISCARDABLE +BEGIN + IDD_MAIN, DIALOG + BEGIN + BOTTOMMARGIN, 249 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE DISCARDABLE +BEGIN + IDS_ABOUT "&About Config..." +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// + + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/CONFIG/res/resource.h b/CONFIG/res/resource.h index 4c72eaa1..1a96ec69 100644 --- a/CONFIG/res/resource.h +++ b/CONFIG/res/resource.h @@ -7,10 +7,12 @@ #define IDC_LIST_3DDEVICES 1000 #define IDC_CHK_FLIP_VIDEO_MEM_PAGES 1001 +#define IDC_CHK_FULLSCREEN 1002 #define IDC_CHK_3D_VIDEO_MEMORY 1003 #define IDC_RAD_PALETTE_256 1004 #define IDC_RAD_PALETTE_16BIT 1005 #define IDC_CHK_3DSOUND 1006 +#define IDAPPLY 1007 #define IDC_CHK_DRAW_CURSOR 1008 #define IDC_RAD_MODEL_QUALITY_LOW 1010 #define IDC_RAD_MODEL_QUALITY_HIGH 1011 diff --git a/CONFIG/res/resrc1.h b/CONFIG/res/resrc1.h new file mode 100644 index 00000000..dcbbc622 --- /dev/null +++ b/CONFIG/res/resrc1.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by config.rc +// +#define IDAPPLY 4 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/CONFIG_orig/AboutDlg.cpp b/CONFIG_orig/AboutDlg.cpp new file mode 100644 index 00000000..9585d1c1 --- /dev/null +++ b/CONFIG_orig/AboutDlg.cpp @@ -0,0 +1,19 @@ +#include "AboutDlg.h" + +#include "decomp.h" + +DECOMP_SIZE_ASSERT(CDialog, 0x60) +DECOMP_SIZE_ASSERT(CAboutDialog, 0x60) + +// FUNCTION: CONFIG 0x00403c20 +CAboutDialog::CAboutDialog() : CDialog(IDD) +{ +} + +// FUNCTION: CONFIG 0x00403d20 +void CAboutDialog::DoDataExchange(CDataExchange* pDX) +{ +} + +BEGIN_MESSAGE_MAP(CAboutDialog, CDialog) +END_MESSAGE_MAP() diff --git a/CONFIG_orig/AboutDlg.h b/CONFIG_orig/AboutDlg.h new file mode 100644 index 00000000..dc648d63 --- /dev/null +++ b/CONFIG_orig/AboutDlg.h @@ -0,0 +1,39 @@ +#if !defined(AFX_ABOUTDLG_H) +#define AFX_ABOUTDLG_H + +#include "StdAfx.h" +#include "compat.h" +#include "res/resource.h" + +// VTABLE: CONFIG 0x00406308 +// SIZE 0x60 +class CAboutDialog : public CDialog { +public: + CAboutDialog(); + enum { + IDD = IDD_ABOUT + }; + +protected: + void DoDataExchange(CDataExchange* pDX) override; + +protected: + DECLARE_MESSAGE_MAP() +}; + +// SYNTHETIC: CONFIG 0x00403cb0 +// CAboutDialog::`scalar deleting destructor' + +// FUNCTION: CONFIG 0x00403d30 +// CAboutDialog::_GetBaseMessageMap + +// FUNCTION: CONFIG 0x00403d40 +// CAboutDialog::GetMessageMap + +// GLOBAL: CONFIG 0x00406100 +// CAboutDialog::messageMap + +// GLOBAL: CONFIG 0x00406108 +// CAboutDialog::_messageEntries + +#endif // !defined(AFX_ABOUTDLG_H) diff --git a/CONFIG_orig/ConfigCommandLineInfo.cpp b/CONFIG_orig/ConfigCommandLineInfo.cpp new file mode 100644 index 00000000..0e376e6d --- /dev/null +++ b/CONFIG_orig/ConfigCommandLineInfo.cpp @@ -0,0 +1,22 @@ +#include "ConfigCommandLineInfo.h" + +#include "decomp.h" + +DECOMP_SIZE_ASSERT(CCommandLineInfo, 0x24) +DECOMP_SIZE_ASSERT(CConfigCommandLineInfo, 0x24) + +// FUNCTION: CONFIG 0x00403b10 +CConfigCommandLineInfo::CConfigCommandLineInfo() +{ + currentConfigApp->m_run_config_dialog = FALSE; +} + +// FUNCTION: CONFIG 0x00403bf0 +void CConfigCommandLineInfo::ParseParam(LPCSTR pszParam, BOOL bFlag, BOOL bLast) +{ + if (bFlag) { + if (lstrcmpiA(pszParam, "config") == 0) { + currentConfigApp->m_run_config_dialog = TRUE; + } + } +} diff --git a/CONFIG_orig/ConfigCommandLineInfo.h b/CONFIG_orig/ConfigCommandLineInfo.h new file mode 100644 index 00000000..82510df1 --- /dev/null +++ b/CONFIG_orig/ConfigCommandLineInfo.h @@ -0,0 +1,21 @@ +#if !defined(AFX_CONFIGCOMMANDLINEINFO_H) +#define AFX_CONFIGCOMMANDLINEINFO_H + +#include "StdAfx.h" +#include "compat.h" +#include "config.h" +#include "decomp.h" + +// VTABLE: CONFIG 0x004060e8 +// SIZE 0x24 +class CConfigCommandLineInfo : public CCommandLineInfo { +public: + CConfigCommandLineInfo(); + + void ParseParam(LPCSTR pszParam, BOOL bFlag, BOOL bLast) override; +}; + +// SYNTHETIC: CONFIG 0x00403b80 +// CConfigCommandLineInfo::`scalar deleting destructor' + +#endif // !defined(AFX_CONFIGCOMMANDLINEINFO_H) diff --git a/CONFIG_orig/MainDlg.cpp b/CONFIG_orig/MainDlg.cpp new file mode 100644 index 00000000..7ae5393e --- /dev/null +++ b/CONFIG_orig/MainDlg.cpp @@ -0,0 +1,346 @@ +#include "MainDlg.h" + +#include "AboutDlg.h" +#include "config.h" +#include "res/resource.h" + +#include + +DECOMP_SIZE_ASSERT(CDialog, 0x60) +DECOMP_SIZE_ASSERT(CMainDialog, 0x70) + +// FUNCTION: CONFIG 0x00403d50 +CMainDialog::CMainDialog(CWnd* pParent) : CDialog(IDD, pParent) +{ + afxCurrentWinApp; + m_icon = LoadIconA(AfxFindResourceHandle(MAKEINTRESOURCE(IDI_CONFIG), RT_GROUP_ICON), MAKEINTRESOURCE(IDI_CONFIG)); +} + +// FUNCTION: CONFIG 0x00403e50 +void CMainDialog::DoDataExchange(CDataExchange* pDX) +{ +} + +BEGIN_MESSAGE_MAP(CMainDialog, CDialog) +ON_WM_SYSCOMMAND() +ON_WM_PAINT() +ON_WM_QUERYDRAGICON() +ON_COMMAND(IDC_CHK_FLIP_VIDEO_MEM_PAGES, OnCheckboxFlipVideoMemPages) +ON_LBN_SELCHANGE(IDC_LIST_3DDEVICES, OnList3DevicesSelectionChanged) +ON_COMMAND(IDC_RAD_PALETTE_16BIT, OnRadiobuttonPalette16bit) +ON_COMMAND(IDC_RAD_PALETTE_256, OnRadiobuttonPalette256) +ON_COMMAND(IDC_CHK_3D_VIDEO_MEMORY, OnCheckbox3DVideoMemory) +ON_WM_DESTROY() // FIXME: CONFIG.EXE calls Default +ON_COMMAND(IDABORT, OnButtonCancel) +ON_COMMAND(IDC_CHK_3DSOUND, OnCheckbox3DSound) +ON_COMMAND(IDC_RAD_MODEL_QUALITY_LOW, OnRadiobuttonModelLowQuality) +ON_COMMAND(IDC_RAD_MODEL_QUALITY_HIGH, OnRadiobuttonModelHighQuality) +ON_COMMAND(IDC_RAD_TEXTURE_QUALITY_LOW, OnRadiobuttonTextureLowQuality) +ON_COMMAND(IDC_RAD_TEXTURE_QUALITY_HIGH, OnRadiobuttonTextureHighQuality) +ON_COMMAND(IDC_CHK_JOYSTICK, OnCheckboxJoystick) +ON_COMMAND(IDC_BTN_ADVANCED, OnButtonAdvanced) +ON_COMMAND(IDC_CHK_DRAW_CURSOR, OnCheckboxDrawCursor) +ON_COMMAND(IDC_CHK_MUSIC, OnCheckboxMusic) +END_MESSAGE_MAP() + +// FUNCTION: CONFIG 0x00403e80 +BOOL CMainDialog::OnInitDialog() +{ + CDialog::OnInitDialog(); + SwitchToAdvanced(FALSE); + CMenu* system_menu = CMenu::FromHandle(::GetSystemMenu(m_hWnd, FALSE)); + CString about_text; + about_text.LoadString(IDS_ABOUT); + if (system_menu) { + AppendMenuA(system_menu->m_hMenu, MF_SEPARATOR, 0, NULL); + AppendMenuA(system_menu->m_hMenu, MF_STRING, 16, (LPCTSTR) about_text); + } + SendMessage(WM_SETICON, ICON_BIG, (LPARAM) m_icon); + SendMessage(WM_SETICON, ICON_SMALL, (LPARAM) m_icon); + LegoDeviceEnumerate* enumerator = currentConfigApp->m_device_enumerator; + enumerator->FUN_1009d210(); + m_modified = currentConfigApp->ReadRegisterSettings(); + CWnd* list_3d_devices = GetDlgItem(IDC_LIST_3DDEVICES); + int driver_i = 0; + int device_i = 0; + int selected = 0; + char device_name[256]; + const list& driver_list = enumerator->GetDriverList(); + for (list::const_iterator it_driver = driver_list.begin(); it_driver != driver_list.end(); it_driver++) { + const MxDriver& driver = *it_driver; + for (list::const_iterator it_device = driver.m_devices.begin(); + it_device != driver.m_devices.end(); + it_device++) { + const Direct3DDeviceInfo& device = *it_device; + if (&device == currentConfigApp->m_device) { + selected = device_i; + } + device_i += 1; + sprintf( + device_name, + driver_i == 0 ? "%s ( Primary Device )" : "%s ( Secondary Device )", + device.m_deviceName + ); + ::SendMessage(list_3d_devices->m_hWnd, LB_ADDSTRING, 0, (LPARAM) device_name); + } + driver_i += 1; + } + ::SendMessage(list_3d_devices->m_hWnd, LB_SETCURSEL, selected, 0); + UpdateInterface(); + return TRUE; +} + +// FUNCTION: CONFIG 0x00404080 +void CMainDialog::OnSysCommand(UINT nID, LPARAM lParam) +{ + if ((nID & 0xfff0) == 0x10) { + CAboutDialog about_dialog; + about_dialog.DoModal(); + } + else { + Default(); + } +} + +// FUNCTION: CONFIG 0x00404150 +void CMainDialog::OnPaint() +{ + if (IsIconic()) { + CPaintDC painter(this); + ::SendMessage(m_hWnd, WM_ICONERASEBKGND, (WPARAM) painter.m_hDC, 0); + RECT dim; + GetClientRect(&dim); + DrawIcon( + painter.m_hDC, + (dim.right - dim.left - GetSystemMetrics(SM_CXICON) + 1) / 2, + (dim.bottom - dim.top - GetSystemMetrics(SM_CYICON) + 1) / 2, + m_icon + ); + } + else { + Default(); + } +} + +// FUNCTION: CONFIG 0x00404230 +HCURSOR CMainDialog::OnQueryDragIcon() +{ + return m_icon; +} + +// FUNCTION: CONFIG 0x00404240 +void CMainDialog::OnList3DevicesSelectionChanged() +{ + LegoDeviceEnumerate* device_enumerator = currentConfigApp->m_device_enumerator; + int selected = ::SendMessage(GetDlgItem(IDC_LIST_3DDEVICES)->m_hWnd, LB_GETCURSEL, 0, 0); + device_enumerator->GetDevice(selected, currentConfigApp->m_driver, currentConfigApp->m_device); + if (currentConfigApp->GetHardwareDeviceColorModel()) { + GetDlgItem(IDC_CHK_DRAW_CURSOR)->EnableWindow(TRUE); + } + else { + currentConfigApp->m_3d_video_ram = FALSE; + currentConfigApp->m_flip_surfaces = FALSE; + CheckDlgButton(IDC_CHK_3D_VIDEO_MEMORY, currentConfigApp->m_3d_video_ram); + CheckDlgButton(IDC_CHK_FLIP_VIDEO_MEM_PAGES, currentConfigApp->m_flip_surfaces); + } + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x00404320 +void CMainDialog::OnCancel() +{ + CDialog::OnCancel(); +} + +// FUNCTION: CONFIG 0x00404330 +void CMainDialog::OnDestroy() +{ + CDialog::Default(); +} + +// FUNCTION: CONFIG 0x00404340 +void CMainDialog::OnButtonCancel() +{ + if (m_modified) { + currentConfigApp->WriteRegisterSettings(); + } + OnCancel(); +} + +// FUNCTION: CONFIG 0x00404360 +void CMainDialog::UpdateInterface() +{ + currentConfigApp->ValidateSettings(); + GetDlgItem(IDC_CHK_3D_VIDEO_MEMORY) + ->EnableWindow(!currentConfigApp->m_flip_surfaces && !currentConfigApp->GetHardwareDeviceColorModel()); + CheckDlgButton(IDC_CHK_FLIP_VIDEO_MEM_PAGES, currentConfigApp->m_flip_surfaces); + CheckDlgButton(IDC_CHK_3D_VIDEO_MEMORY, currentConfigApp->m_3d_video_ram); + BOOL full_screen = currentConfigApp->m_full_screen; + currentConfigApp->AdjustDisplayBitDepthBasedOnRenderStatus(); + if (currentConfigApp->GetHardwareDeviceColorModel()) { + CheckDlgButton(IDC_CHK_DRAW_CURSOR, TRUE); + } + else { + CheckDlgButton(IDC_CHK_DRAW_CURSOR, FALSE); + currentConfigApp->m_draw_cursor = FALSE; + GetDlgItem(IDC_CHK_DRAW_CURSOR)->EnableWindow(FALSE); + } + if (full_screen) { + CheckRadioButton( + IDC_RAD_PALETTE_256, + IDC_RAD_PALETTE_16BIT, + currentConfigApp->m_display_bit_depth == 8 ? IDC_RAD_PALETTE_256 : IDC_RAD_PALETTE_16BIT + ); + } + else { + CheckDlgButton(IDC_RAD_PALETTE_256, 0); + CheckDlgButton(IDC_RAD_PALETTE_16BIT, 0); + currentConfigApp->m_display_bit_depth = 0; + } + GetDlgItem(IDC_RAD_PALETTE_256) + ->EnableWindow(full_screen && currentConfigApp->GetConditionalDeviceRenderBitDepth()); + GetDlgItem(IDC_RAD_PALETTE_16BIT)->EnableWindow(full_screen && currentConfigApp->GetDeviceRenderBitStatus()); + CheckDlgButton(IDC_CHK_3DSOUND, currentConfigApp->m_3d_sound); + CheckDlgButton(IDC_CHK_DRAW_CURSOR, currentConfigApp->m_draw_cursor); + switch (currentConfigApp->m_model_quality) { + case 1: + CheckRadioButton(IDC_RAD_MODEL_QUALITY_LOW, IDC_RAD_MODEL_QUALITY_HIGH, IDC_RAD_MODEL_QUALITY_LOW); + break; + case 2: + CheckRadioButton(IDC_RAD_MODEL_QUALITY_LOW, IDC_RAD_MODEL_QUALITY_HIGH, IDC_RAD_MODEL_QUALITY_HIGH); + break; + } + CheckRadioButton( + IDC_RAD_TEXTURE_QUALITY_LOW, + IDC_RAD_TEXTURE_QUALITY_HIGH, + currentConfigApp->m_texture_quality == 0 ? IDC_RAD_TEXTURE_QUALITY_LOW : IDC_RAD_TEXTURE_QUALITY_HIGH + ); + CheckDlgButton(IDC_CHK_JOYSTICK, currentConfigApp->m_use_joystick); + CheckDlgButton(IDC_CHK_MUSIC, currentConfigApp->m_music); +} + +// FUNCTION: CONFIG 0x004045e0 +void CMainDialog::OnCheckbox3DSound() +{ + currentConfigApp->m_3d_sound = IsDlgButtonChecked(IDC_CHK_3DSOUND); + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x00404610 +void CMainDialog::OnCheckbox3DVideoMemory() +{ + currentConfigApp->m_3d_video_ram = IsDlgButtonChecked(IDC_CHK_3D_VIDEO_MEMORY); + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x00404640 +void CMainDialog::OnRadiobuttonPalette16bit() +{ + currentConfigApp->m_display_bit_depth = 16; + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x00404670 +void CMainDialog::OnRadiobuttonPalette256() +{ + currentConfigApp->m_display_bit_depth = 8; + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x004046a0 +void CMainDialog::OnCheckboxFlipVideoMemPages() +{ + currentConfigApp->m_flip_surfaces = IsDlgButtonChecked(IDC_CHK_FLIP_VIDEO_MEM_PAGES); + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x004046d0 +void CMainDialog::OnRadiobuttonModelLowQuality() +{ + currentConfigApp->m_model_quality = 1; + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x00404700 +void CMainDialog::OnRadiobuttonModelHighQuality() +{ + currentConfigApp->m_model_quality = 2; + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x00404730 +void CMainDialog::OnRadiobuttonTextureLowQuality() +{ + currentConfigApp->m_texture_quality = 0; + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x00404760 +void CMainDialog::OnRadiobuttonTextureHighQuality() +{ + currentConfigApp->m_texture_quality = 1; + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x00404790 +void CMainDialog::OnCheckboxJoystick() +{ + currentConfigApp->m_use_joystick = IsDlgButtonChecked(IDC_CHK_JOYSTICK); + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x004047c0 +void CMainDialog::OnButtonAdvanced() +{ + SwitchToAdvanced(!m_advanced); +} + +// FUNCTION: CONFIG 0x004047d0 +void CMainDialog::SwitchToAdvanced(BOOL p_advanced) +{ + RECT dialog_rect; + RECT grp_advanced_rect; + ::GetWindowRect(m_hWnd, &dialog_rect); + ::GetWindowRect(GetDlgItem(IDC_GRP_ADVANCED)->m_hWnd, &grp_advanced_rect); + CWnd* button_advanced = GetDlgItem(IDC_BTN_ADVANCED); + m_advanced = p_advanced; + int height; + if (p_advanced) { + height = grp_advanced_rect.bottom - dialog_rect.top + 10; + GetDlgItem(IDC_BMP_SHARK)->EnableWindow(TRUE); + button_advanced->SetWindowText("Basic"); + } + else { + height = grp_advanced_rect.top - dialog_rect.top; + GetDlgItem(IDC_BMP_SHARK)->EnableWindow(FALSE); + button_advanced->SetWindowText("Advanced"); + } + SetWindowPos(&wndTop, 0, 0, dialog_rect.right - dialog_rect.left, height, SWP_NOMOVE); +} + +// FUNCTION: CONFIG 0x00404890 +void CMainDialog::OnCheckboxDrawCursor() +{ + currentConfigApp->m_draw_cursor = IsDlgButtonChecked(IDC_CHK_DRAW_CURSOR); + m_modified = TRUE; + UpdateInterface(); +} + +// FUNCTION: CONFIG 0x004048c0 +void CMainDialog::OnCheckboxMusic() +{ + currentConfigApp->m_music = IsDlgButtonChecked(IDC_CHK_MUSIC); + m_modified = TRUE; + UpdateInterface(); +} diff --git a/CONFIG_orig/MainDlg.h b/CONFIG_orig/MainDlg.h new file mode 100644 index 00000000..eeebc868 --- /dev/null +++ b/CONFIG_orig/MainDlg.h @@ -0,0 +1,70 @@ +#if !defined(AFX_MAINDLG_H) +#define AFX_MAINDLG_H + +#include "StdAfx.h" +#include "compat.h" +#include "decomp.h" +#include "res/resource.h" + +// VTABLE: CONFIG 0x004063e0 +// SIZE 0x70 +class CMainDialog : public CDialog { +public: + CMainDialog(CWnd* pParent); + enum { + IDD = IDD_MAIN + }; + +protected: + void DoDataExchange(CDataExchange* pDX) override; + void UpdateInterface(); + void SwitchToAdvanced(BOOL p_advanced); + + undefined m_unk0x60[4]; // 0x60 + HCURSOR m_icon; // 0x64 + BOOL m_modified; // 0x68 + BOOL m_advanced; // 0x6c + // Implementation + +protected: + BOOL OnInitDialog() override; + void OnSysCommand(UINT nID, LPARAM lParam); + void OnPaint(); + HCURSOR OnQueryDragIcon(); + void OnList3DevicesSelectionChanged(); + void OnCancel(); + void OnDestroy(); + void OnButtonCancel(); + void OnCheckbox3DSound(); + void OnCheckbox3DVideoMemory(); + void OnRadiobuttonPalette16bit(); + void OnRadiobuttonPalette256(); + void OnCheckboxFlipVideoMemPages(); + void OnRadiobuttonModelLowQuality(); + void OnRadiobuttonModelHighQuality(); + void OnRadiobuttonTextureLowQuality(); + void OnRadiobuttonTextureHighQuality(); + void OnCheckboxJoystick(); + void OnButtonAdvanced(); + void OnCheckboxDrawCursor(); + void OnCheckboxMusic(); + + DECLARE_MESSAGE_MAP() +}; + +// SYNTHETIC: CONFIG 0x00403de0 +// CMainDialog::`scalar deleting destructor' + +// FUNCTION: CONFIG 0x00403e60 +// CMainDialog::_GetBaseMessageMap + +// FUNCTION: CONFIG 0x00403e70 +// CMainDialog::GetMessageMap + +// GLOBAL: CONFIG 0x00406120 +// CMainDialog::messageMap + +// GLOBAL: CONFIG 0x00406128 +// CMainDialog::_messageEntries + +#endif // !defined(AFX_MAINDLG_H) diff --git a/CONFIG_orig/StdAfx.cpp b/CONFIG_orig/StdAfx.cpp new file mode 100644 index 00000000..3e0b3d04 --- /dev/null +++ b/CONFIG_orig/StdAfx.cpp @@ -0,0 +1,5 @@ +// stdafx.cpp : source file that includes just the standard includes +// simple.pch will be the pre-compiled header +// stdafx.obj will contain the pre-compiled type information + +#include "stdafx.h" diff --git a/CONFIG_orig/StdAfx.h b/CONFIG_orig/StdAfx.h new file mode 100644 index 00000000..39243b3b --- /dev/null +++ b/CONFIG_orig/StdAfx.h @@ -0,0 +1,31 @@ +#if !defined(AFX_STDAFX_H) +#define AFX_STDAFX_H + +#define VC_EXTRALEAN // Exclude rarely-used stuff from Windows headers + +#include // MFC extensions +#include // MFC core and standard components +#ifndef _AFX_NO_AFXCMN_SUPPORT +#include // MFC support for Windows Common Controls +#endif // _AFX_NO_AFXCMN_SUPPORT + +#if 0 + +// FUNCTION: CONFIG 0x402ca0 +// CObject::Serialize + +// FUNCTION: CONFIG 0x402cb0 +// CObject::AssertValid + +// FUNCTION: CONFIG 0x402cc0 +// CObject::Dump + +// FUNCTION: CONFIG 0x00403c90 +// CWnd::BeginModalState + +// FUNCTION: CONFIG 0x00403ca0 +// CWnd::EndModalState + +#endif + +#endif // !defined(AFX_STDAFX_H) diff --git a/CONFIG_orig/config.cpp b/CONFIG_orig/config.cpp new file mode 100644 index 00000000..d4457b55 --- /dev/null +++ b/CONFIG_orig/config.cpp @@ -0,0 +1,435 @@ +#include "config.h" + +#include "ConfigCommandLineInfo.h" +#include "MainDlg.h" +#include "detectdx5.h" + +#include // _chdir +#include +#include +#include // _spawnl + +DECOMP_SIZE_ASSERT(CWinApp, 0xc4) +DECOMP_SIZE_ASSERT(CConfigApp, 0x108) + +DECOMP_STATIC_ASSERT(offsetof(CConfigApp, m_display_bit_depth) == 0xd0) + +BEGIN_MESSAGE_MAP(CConfigApp, CWinApp) +ON_COMMAND(ID_HELP, OnHelp) +END_MESSAGE_MAP() + +// FUNCTION: CONFIG 0x00402c40 +CConfigApp::CConfigApp() +{ +} + +#define MiB (1024 * 1024) + +// FUNCTION: CONFIG 0x00402dc0 +BOOL CConfigApp::InitInstance() +{ + if (!IsLegoNotRunning()) { + return FALSE; + } + if (!DetectDirectX5()) { + AfxMessageBox( + "\"LEGO\xae Island\" is not detecting DirectX 5 or later. Please quit all other applications and try " + "again." + ); + return FALSE; + } +#ifdef _AFXDLL + Enable3dControls(); +#else + Enable3dControlsStatic(); +#endif + CConfigCommandLineInfo cmdInfo; + ParseCommandLine(cmdInfo); + if (_stricmp(afxCurrentAppName, "config") == 0) { + m_run_config_dialog = TRUE; + } + m_device_enumerator = new LegoDeviceEnumerate; + if (m_device_enumerator->DoEnumerate()) { + return FALSE; + } + m_driver = NULL; + m_device = NULL; + m_full_screen = TRUE; + m_wide_view_angle = TRUE; + m_use_joystick = FALSE; + m_music = TRUE; + m_flip_surfaces = FALSE; + m_3d_video_ram = FALSE; + m_joystick_index = -1; + m_display_bit_depth = 16; + MEMORYSTATUS memory_status; + memory_status.dwLength = sizeof(memory_status); + GlobalMemoryStatus(&memory_status); + if (memory_status.dwTotalPhys < 12 * MiB) { + m_3d_sound = FALSE; + m_model_quality = 0; + m_texture_quality = 1; + } + else if (memory_status.dwTotalPhys < 20 * MiB) { + m_3d_sound = FALSE; + m_model_quality = 1; + m_texture_quality = 1; + } + else { + m_model_quality = 2; + m_3d_sound = TRUE; + m_texture_quality = 1; + } + if (!m_run_config_dialog) { + ReadRegisterSettings(); + ValidateSettings(); + WriteRegisterSettings(); + delete m_device_enumerator; + m_device_enumerator = NULL; + m_driver = NULL; + m_device = NULL; + char password[256]; + ReadReg("password", password, sizeof(password)); + const char* exe = _stricmp("ogel", password) == 0 ? "isled.exe" : "isle.exe"; + char diskpath[1024]; + if (ReadReg("diskpath", diskpath, sizeof(diskpath))) { + _chdir(diskpath); + } + _spawnl(_P_NOWAIT, exe, exe, "/diskstream", "/script", "\\lego\\scripts\\isle\\isle.si", NULL); + return FALSE; + } + CMainDialog main_dialog(NULL); + main_dialog.DoModal(); + return FALSE; +} + +// FUNCTION: CONFIG 0x00403100 +BOOL CConfigApp::IsLegoNotRunning() +{ + HWND hWnd = FindWindowA("Lego Island MainNoM App", "LEGO\xae"); + if (_stricmp(afxCurrentAppName, "config") == 0 || !hWnd) { + return TRUE; + } + if (SetForegroundWindow(hWnd)) { + ShowWindow(hWnd, SW_RESTORE); + } + return FALSE; +} + +// FUNCTION: CONFIG 0x004031b0 +BOOL CConfigApp::WriteReg(const char* p_key, const char* p_value) const +{ + HKEY hKey; + DWORD pos; + + if (RegCreateKeyExA( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\Mindscape\\LEGO Island", + 0, + "string", + 0, + KEY_READ | KEY_WRITE, + NULL, + &hKey, + &pos + ) == ERROR_SUCCESS) { + if (RegSetValueExA(hKey, p_key, 0, REG_SZ, (LPBYTE) p_value, strlen(p_value)) == ERROR_SUCCESS) { + if (RegCloseKey(hKey) == ERROR_SUCCESS) { + return TRUE; + } + } + else { + RegCloseKey(hKey); + } + } + return FALSE; +} + +// FUNCTION: CONFIG 0x00403240 +BOOL CConfigApp::ReadReg(LPCSTR p_key, LPCSTR p_value, DWORD p_size) const +{ + HKEY hKey; + DWORD valueType; + + BOOL out = FALSE; + DWORD size = p_size; + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Mindscape\\LEGO Island", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + if (RegQueryValueExA(hKey, p_key, NULL, &valueType, (LPBYTE) p_value, &size) == ERROR_SUCCESS) { + if (RegCloseKey(hKey) == ERROR_SUCCESS) { + out = TRUE; + } + } + } + return out; +} + +// FUNCTION: CONFIG 0x004032b0 +BOOL CConfigApp::ReadRegBool(LPCSTR p_key, BOOL* p_bool) const +{ + char buffer[256]; + + BOOL read = ReadReg(p_key, buffer, sizeof(buffer)); + if (read) { + if (strcmp("YES", buffer) == 0) { + *p_bool = TRUE; + return read; + } + + if (strcmp("NO", buffer) == 0) { + *p_bool = FALSE; + return read; + } + + read = FALSE; + } + return read; +} + +// FUNCTION: CONFIG 0x00403380 +BOOL CConfigApp::ReadRegInt(LPCSTR p_key, int* p_value) const +{ + char buffer[256]; + + BOOL read = ReadReg(p_key, buffer, sizeof(buffer)); + if (read) { + *p_value = atoi(buffer); + } + + return read; +} + +// FUNCTION: CONFIG 0x004033d0 +BOOL CConfigApp::IsDeviceInBasicRGBMode() const +{ + /* + * BUG: should be: + * return !GetHardwareDeviceColorModel() && (m_device->m_HELDesc.dcmColorModel & D3DCOLOR_RGB); + */ + return !GetHardwareDeviceColorModel() && m_device->m_HELDesc.dcmColorModel == D3DCOLOR_RGB; +} + +// FUNCTION: CONFIG 0x00403400 +D3DCOLORMODEL CConfigApp::GetHardwareDeviceColorModel() const +{ + return m_device->m_HWDesc.dcmColorModel; +} + +// FUNCTION: CONFIG 0x00403410 +BOOL CConfigApp::IsPrimaryDriver() const +{ + return m_driver == &m_device_enumerator->GetDriverList().front(); +} + +// FUNCTION: CONFIG 0x00403430 +BOOL CConfigApp::ReadRegisterSettings() +{ + char buffer[256]; + BOOL is_modified = FALSE; + int tmp = -1; + + if (ReadReg("3D Device ID", buffer, sizeof(buffer))) { + tmp = m_device_enumerator->ParseDeviceName(buffer); + if (tmp >= 0) { + tmp = m_device_enumerator->GetDevice(tmp, m_driver, m_device); + } + } + if (tmp != 0) { + is_modified = TRUE; + m_device_enumerator->FUN_1009d210(); + tmp = m_device_enumerator->FUN_1009d0d0(); + m_device_enumerator->GetDevice(tmp, m_driver, m_device); + } + if (!ReadRegInt("Display Bit Depth", &m_display_bit_depth)) { + is_modified = TRUE; + } + if (!ReadRegBool("Flip Surfaces", &m_flip_surfaces)) { + is_modified = TRUE; + } + if (!ReadRegBool("Full Screen", &m_full_screen)) { + is_modified = TRUE; + } + if (!ReadRegBool("Back Buffers in Video RAM", &m_3d_video_ram)) { + is_modified = TRUE; + } + if (!ReadRegBool("Wide View Angle", &m_wide_view_angle)) { + is_modified = TRUE; + } + if (!ReadRegBool("3DSound", &m_3d_sound)) { + is_modified = TRUE; + } + if (!ReadRegBool("Draw Cursor", &m_draw_cursor)) { + is_modified = TRUE; + } + if (!ReadRegInt("Island Quality", &m_model_quality)) { + is_modified = TRUE; + } + if (!ReadRegInt("Island Texture", &m_texture_quality)) { + is_modified = TRUE; + } + if (!ReadRegBool("UseJoystick", &m_use_joystick)) { + is_modified = TRUE; + } + if (!ReadRegBool("Music", &m_music)) { + is_modified = TRUE; + } + if (!ReadRegInt("JoystickIndex", &m_joystick_index)) { + is_modified = TRUE; + } + return is_modified; +} + +// FUNCTION: CONFIG 0x00403630 +BOOL CConfigApp::ValidateSettings() +{ + BOOL is_modified = FALSE; + + if (!IsPrimaryDriver() && !m_full_screen) { + m_full_screen = TRUE; + is_modified = TRUE; + } + if (IsDeviceInBasicRGBMode()) { + if (m_3d_video_ram) { + m_3d_video_ram = FALSE; + is_modified = TRUE; + } + if (m_flip_surfaces) { + m_flip_surfaces = FALSE; + is_modified = TRUE; + } + if (m_display_bit_depth != 16) { + m_display_bit_depth = 16; + is_modified = TRUE; + } + } + if (!GetHardwareDeviceColorModel()) { + m_draw_cursor = FALSE; + is_modified = TRUE; + } + else { + if (!m_3d_video_ram) { + m_3d_video_ram = TRUE; + is_modified = TRUE; + } + if (m_full_screen && !m_flip_surfaces) { + m_flip_surfaces = TRUE; + is_modified = TRUE; + } + } + if (m_flip_surfaces) { + if (!m_3d_video_ram) { + m_3d_video_ram = TRUE; + is_modified = TRUE; + } + if (!m_full_screen) { + m_full_screen = TRUE; + is_modified = TRUE; + } + } + if ((m_display_bit_depth != 8 && m_display_bit_depth != 16) && (m_display_bit_depth != 0 || m_full_screen)) { + m_display_bit_depth = 8; + is_modified = TRUE; + } + if (m_model_quality < 0 || m_model_quality > 2) { + m_model_quality = 1; + is_modified = TRUE; + } + if (m_texture_quality < 0 || m_texture_quality > 1) { + m_texture_quality = 0; + is_modified = TRUE; + } + return is_modified; +} + +// FUNCTION: CONFIG 0x004037a0 +DWORD CConfigApp::GetConditionalDeviceRenderBitDepth() const +{ + if (IsDeviceInBasicRGBMode()) { + return 0; + } + if (GetHardwareDeviceColorModel()) { + return 0; + } + return m_device->m_HELDesc.dwDeviceRenderBitDepth & 0x800; +} + +// FUNCTION: CONFIG 0x004037e0 +DWORD CConfigApp::GetDeviceRenderBitStatus() const +{ + if (GetHardwareDeviceColorModel()) { + return m_device->m_HWDesc.dwDeviceRenderBitDepth & 0x400; + } + else { + return m_device->m_HELDesc.dwDeviceRenderBitDepth & 0x400; + } +} + +// FUNCTION: CONFIG 0x00403810 +BOOL CConfigApp::AdjustDisplayBitDepthBasedOnRenderStatus() +{ + if (m_display_bit_depth == 8) { + if (GetConditionalDeviceRenderBitDepth()) { + return FALSE; + } + } + if (m_display_bit_depth == 16) { + if (GetDeviceRenderBitStatus()) { + return FALSE; + } + } + if (GetConditionalDeviceRenderBitDepth()) { + m_display_bit_depth = 8; + return TRUE; + } + if (GetDeviceRenderBitStatus()) { + m_display_bit_depth = 16; + return TRUE; + } + m_display_bit_depth = 8; + return TRUE; +} + +// FUNCTION: CONFIG 00403890 +void CConfigApp::WriteRegisterSettings() const + +{ + char buffer[128]; + +#define WriteRegBool(NAME, VALUE) WriteReg(NAME, VALUE ? "YES" : "NO") +#define WriteRegInt(NAME, VALUE) \ + do { \ + sprintf(buffer, "%d", VALUE); \ + WriteReg(NAME, buffer); \ + } while (0) + + m_device_enumerator->FormatDeviceName(buffer, m_driver, m_device); + WriteReg("3D Device ID", buffer); + WriteReg("3D Device Name", m_device->m_deviceName); + WriteRegInt("Display Bit Depth", m_display_bit_depth); + WriteRegBool("Flip Surfaces", m_flip_surfaces); + WriteRegBool("Full Screen", m_full_screen); + WriteRegBool("Back Buffers in Video RAM", m_3d_video_ram); + WriteRegBool("Wide View Angle", m_wide_view_angle); + WriteRegBool("3DSound", m_3d_sound); + WriteRegBool("Draw Cursor", m_draw_cursor); + WriteRegInt("Island Quality", m_model_quality); + WriteRegInt("Island Texture", m_texture_quality); + WriteRegBool("UseJoystick", m_use_joystick); + WriteRegBool("Music", m_music); + WriteRegInt("JoystickIndex", m_joystick_index); + +#undef WriteRegBool +#undef WriteRegInt +} + +// FUNCTION: CONFIG 0x00403a90 +int CConfigApp::ExitInstance() +{ + if (m_device_enumerator) { + delete m_device_enumerator; + m_device_enumerator = NULL; + } + return CWinApp::ExitInstance(); +} + +// GLOBAL: CONFIG 0x00408e50 +CConfigApp g_theApp; diff --git a/CONFIG_orig/config.h b/CONFIG_orig/config.h new file mode 100644 index 00000000..9172e2d5 --- /dev/null +++ b/CONFIG_orig/config.h @@ -0,0 +1,91 @@ +#if !defined(AFX_CONFIG_H) +#define AFX_CONFIG_H + +#include "StdAfx.h" +#include "compat.h" +#include "decomp.h" + +#include + +class LegoDeviceEnumerate; +struct Direct3DDeviceInfo; +struct MxDriver; + +#define currentConfigApp ((CConfigApp*) afxCurrentWinApp) + +// VTABLE: CONFIG 0x00406040 +// SIZE 0x108 +class CConfigApp : public CWinApp { +public: + CConfigApp(); + + // Overrides + // ClassWizard generated virtual function overrides + //{{AFX_VIRTUAL(CConfigApp) + +public: + BOOL InitInstance() override; + int ExitInstance() override; + //}}AFX_VIRTUAL + + // Implementation + + BOOL WriteReg(const char* p_key, const char* p_value) const; + BOOL ReadReg(LPCSTR p_key, LPCSTR p_value, DWORD p_size) const; + BOOL ReadRegBool(LPCSTR p_key, BOOL* p_bool) const; + BOOL ReadRegInt(LPCSTR p_key, int* p_value) const; + BOOL IsDeviceInBasicRGBMode() const; + D3DCOLORMODEL GetHardwareDeviceColorModel() const; + BOOL IsPrimaryDriver() const; + BOOL ReadRegisterSettings(); + BOOL ValidateSettings(); + DWORD GetConditionalDeviceRenderBitDepth() const; + DWORD GetDeviceRenderBitStatus() const; + BOOL AdjustDisplayBitDepthBasedOnRenderStatus(); + void CConfigApp::WriteRegisterSettings() const; + + //{{AFX_MSG(CConfigApp) + // NOTE - the ClassWizard will add and remove member functions here. + // DO NOT EDIT what you see in these blocks of generated code ! + //}}AFX_MSG + DECLARE_MESSAGE_MAP() + +private: + BOOL IsLegoNotRunning(); + +public: + LegoDeviceEnumerate* m_device_enumerator; // 0x0c4 + MxDriver* m_driver; // 0x0c8 + Direct3DDeviceInfo* m_device; // 0x0cc + int m_display_bit_depth; // 0x0d0 + BOOL m_flip_surfaces; // 0x0d4 + BOOL m_full_screen; // 0x0d8 + BOOL m_3d_video_ram; // 0x0dc + BOOL m_wide_view_angle; // 0x0e0 + BOOL m_3d_sound; // 0x0e4 + BOOL m_draw_cursor; // 0x0e8 + BOOL m_use_joystick; // 0x0ec + int m_joystick_index; // 0x0f0 + BOOL m_run_config_dialog; // 0x0f4 + int m_model_quality; // 0x0f8 + int m_texture_quality; // 0x0fc + undefined m_unk0x100[4]; // 0x100 + BOOL m_music; // 0x104 +}; + +// SYNTHETIC: CONFIG 0x00402cd0 +// CConfigApp::`scalar deleting destructor' + +// FUNCTION: CONFIG 0x402c20 +// CConfigApp::_GetBaseMessageMap + +// FUNCTION: CONFIG 0x402c30 +// CConfigApp::GetMessageMap + +// GLOBAL: CONFIG 0x406008 +// CConfigApp::messageMap + +// GLOBAL: CONFIG 0x406010 +// CConfigApp::_messageEntries + +#endif // !defined(AFX_CONFIG_H) diff --git a/CONFIG_orig/detectdx5.cpp b/CONFIG_orig/detectdx5.cpp new file mode 100644 index 00000000..546f0dbd --- /dev/null +++ b/CONFIG_orig/detectdx5.cpp @@ -0,0 +1,142 @@ +#include "detectdx5.h" + +#include +#include + +typedef HRESULT WINAPI DirectDrawCreate_fn(GUID FAR* lpGUID, LPDIRECTDRAW FAR* lplpDD, IUnknown FAR* pUnkOuter); +typedef HRESULT WINAPI +DirectInputCreateA_fn(HINSTANCE hinst, DWORD dwVersion, LPDIRECTINPUTA* ppDI, LPUNKNOWN punkOuter); + +// FUNCTION: CONFIG 0x004048f0 +BOOL DetectDirectX5() +{ + unsigned int version; + BOOL found; + DetectDirectX(&version, &found); + return version >= 0x500; +} + +// FUNCTION: CONFIG 0x00404920 +void DetectDirectX(unsigned int* p_version, BOOL* p_found) +{ + OSVERSIONINFOA os_version; + + os_version.dwOSVersionInfoSize = sizeof(os_version); + if (!GetVersionExA(&os_version)) { + *p_version = 0; + *p_found = 0; + return; + } + if (os_version.dwPlatformId == 2) { + *p_found = 2; + if (os_version.dwMajorVersion < 4) { + *p_found = 0; + return; + } + if (os_version.dwMajorVersion != 4) { + *p_version = 0x501; + return; + } + *p_version = 0x200; + HMODULE dinput_module = LoadLibraryA("DINPUT.DLL"); + if (!dinput_module) { + OutputDebugStringA("Couldn't LoadLibrary DInput\r\n"); + return; + } + DirectInputCreateA_fn* func_DirectInputCreateA = + (DirectInputCreateA_fn*) GetProcAddress(dinput_module, "DirectInputCreateA"); + FreeLibrary(dinput_module); + if (!func_DirectInputCreateA) { + OutputDebugStringA("Couldn't GetProcAddress DInputCreate\r\n"); + return; + } + *p_version = 0x300; + return; + } + *p_found = 1; + if (LOWORD(os_version.dwBuildNumber) >= 0x550) { + *p_version = 0x501; + return; + } + HMODULE ddraw_module = LoadLibraryA("DDRAW.DLL"); + if (!ddraw_module) { + *p_version = 0; + *p_found = 0; + FreeLibrary(ddraw_module); + return; + } + DirectDrawCreate_fn* func_DirectDrawCreate = + (DirectDrawCreate_fn*) GetProcAddress(ddraw_module, "DirectDrawCreate"); + if (!func_DirectDrawCreate) { + *p_version = 0; + *p_found = 0; + FreeLibrary(ddraw_module); + OutputDebugStringA("Couldn't LoadLibrary DDraw\r\n"); + return; + } + LPDIRECTDRAW ddraw; + if (FAILED(func_DirectDrawCreate(NULL, &ddraw, NULL))) { + *p_version = 0; + *p_found = 0; + FreeLibrary(ddraw_module); + OutputDebugStringA("Couldn't create DDraw\r\n"); + return; + } + *p_version = 0x100; + LPDIRECTDRAW2 ddraw2; + if (FAILED(ddraw->QueryInterface(IID_IDirectDraw2, (LPVOID*) &ddraw2))) { + ddraw->Release(); + FreeLibrary(ddraw_module); + OutputDebugStringA("Couldn't QI DDraw2\r\n"); + return; + } + ddraw->Release(); + *p_version = 0x200; + HMODULE dinput_module = LoadLibraryA("DINPUT.DLL"); + if (!dinput_module) { + OutputDebugStringA("Couldn't LoadLibrary DInput\r\n"); + ddraw2->Release(); + FreeLibrary(ddraw_module); + return; + } + DirectInputCreateA_fn* func_DirectInputCreateA = + (DirectInputCreateA_fn*) GetProcAddress(dinput_module, "DirectInputCreateA"); + FreeLibrary(dinput_module); + if (!func_DirectInputCreateA) { + FreeLibrary(ddraw_module); + ddraw2->Release(); + OutputDebugStringA("Couldn't GetProcAddress DInputCreate\r\n"); + return; + } + *p_version = 0x300; + DDSURFACEDESC surface_desc; + memset(&surface_desc, 0, sizeof(surface_desc)); + surface_desc.dwSize = sizeof(surface_desc); + surface_desc.dwFlags = DDSD_CAPS; + surface_desc.ddsCaps.dwCaps = DDCAPS2_NONLOCALVIDMEM; + if (FAILED(ddraw2->SetCooperativeLevel(NULL, DISCL_BACKGROUND))) { + ddraw2->Release(); + FreeLibrary(ddraw_module); + *p_version = 0; + OutputDebugStringA("Couldn't Set coop level\r\n"); + return; + } + LPDIRECTDRAWSURFACE surface; + if (FAILED(ddraw2->CreateSurface(&surface_desc, &surface, NULL))) { + ddraw2->Release(); + FreeLibrary(ddraw_module); + *p_version = 0; + OutputDebugStringA("Couldn't CreateSurface\r\n"); + return; + } + LPDIRECTDRAWSURFACE3 surface3; + if (FAILED(surface->QueryInterface(IID_IDirectDrawSurface3, (LPVOID*) &surface3))) { + ddraw2->Release(); + FreeLibrary(ddraw_module); + return; + } + *p_version = 0x500; + surface3->Release(); + ddraw2->Release(); + FreeLibrary(ddraw_module); +} diff --git a/CONFIG_orig/detectdx5.h b/CONFIG_orig/detectdx5.h new file mode 100644 index 00000000..6f45837c --- /dev/null +++ b/CONFIG_orig/detectdx5.h @@ -0,0 +1,10 @@ +#if !defined(AFX_DETECTDX5_H) +#define AFX_DETECTDX5_H + +#include + +extern BOOL DetectDirectX5(); + +extern void DetectDirectX(unsigned int* p_version, BOOL* p_found); + +#endif // !defined(AFX_DETECTDX5_H) diff --git a/CONFIG_orig/res/CONFIG.ico b/CONFIG_orig/res/CONFIG.ico new file mode 100644 index 00000000..1e67eec2 Binary files /dev/null and b/CONFIG_orig/res/CONFIG.ico differ diff --git a/CONFIG_orig/res/config.aps b/CONFIG_orig/res/config.aps new file mode 100644 index 00000000..739c1477 Binary files /dev/null and b/CONFIG_orig/res/config.aps differ diff --git a/CONFIG_orig/res/config.rc b/CONFIG_orig/res/config.rc new file mode 100644 index 00000000..a6164bfa --- /dev/null +++ b/CONFIG_orig/res/config.rc @@ -0,0 +1,82 @@ +#include "resource.h" +#include "afxres.h" + +IDI_CONFIG ICON "lego.ico" + +IDB_SHARK BITMAP "shark.bmp" + +IDD_ABOUT DIALOG 0, 0, 217, 55 +STYLE DS_SETFONT | DS_MODALFRAME | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "About Configure LEGO\xAE Island" +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +FONT 8, "MS Sans Serif" +BEGIN + CONTROL IDI_CONFIG, -1, "STATIC", SS_ICON | WS_CHILD | WS_VISIBLE, 11, 17, 18, 20 + CONTROL "Configure LEGO Island Version 1.0", -1, "STATIC", SS_LEFT | SS_NOPREFIX | WS_CHILD | WS_VISIBLE | WS_GROUP, 40, 10, 119, 8 + CONTROL "Copyright \xA9 1997 mindscape", -1, "STATIC", SS_LEFT | WS_CHILD | WS_VISIBLE | WS_GROUP, 40, 25, 119, 8 + CONTROL "OK", IDOK, "BUTTON", BS_DEFPUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_GROUP | WS_TABSTOP, 178, 7, 32, 14 +END + +STRINGTABLE +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +BEGIN + IDS_ABOUT, "&About Config..." +END + +IDD_MAIN DIALOGEX 0, 0, 289, 247 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU +EXSTYLE WS_EX_APPWINDOW +CAPTION "Configure LEGO Island" +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +FONT 8, "MS Sans Serif" +BEGIN + CONTROL "OK", IDABORT, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 172, 125, 44, 14 + CONTROL "Cancel", IDCANCEL, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 238, 125, 44, 14 + CONTROL "Fast", IDC_RAD_MODEL_QUALITY_LOW, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 124, 18, 42, 10 + CONTROL "High", IDC_RAD_MODEL_QUALITY_HIGH, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 192, 18, 69, 10 + CONTROL "Fast", IDC_RAD_TEXTURE_QUALITY_LOW, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 124, 49, 43, 10 + CONTROL "High", IDC_RAD_TEXTURE_QUALITY_HIGH, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 192, 49, 69, 10 + CONTROL "256 Color", IDC_RAD_PALETTE_256, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 124, 80, 50, 10 + CONTROL "High Color(16 bit)", IDC_RAD_PALETTE_16BIT, "BUTTON", BS_AUTORADIOBUTTON | WS_CHILD | WS_VISIBLE, 192, 80, 76, 10 + CONTROL "Use Joystick", IDC_CHK_JOYSTICK, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 192, 104, 60, 10 + CONTROL "", IDC_LIST_3DDEVICES, "LISTBOX", LBS_NOTIFY | LBS_NOINTEGRALHEIGHT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL, 115, 168, 161, 36 , WS_EX_TRANSPARENT + CONTROL "Flip Video Memory Pages", IDC_CHK_FLIP_VIDEO_MEM_PAGES, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE, 116, 224, 96, 15 + CONTROL "Draw 3D to Video Memory", IDC_CHK_3D_VIDEO_MEMORY, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE, 116, 210, 99, 10 + CONTROL "Color Palette", -1, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 107, 69, 175, 26 + CONTROL "Direct 3D Devices", -1, "STATIC", SS_CENTER | WS_CHILD | WS_VISIBLE | WS_GROUP, 123, 158, 143, 10 + CONTROL "3D Sound", IDC_CHK_3DSOUND, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE, 220, 210, 45, 10 + CONTROL "Island Model Quality", -1, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 107, 7, 175, 26 + CONTROL "Island Texture Quality", -1, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 107, 38, 175, 26 + CONTROL "Advanced Settings", IDC_GRP_ADVANCED, "BUTTON", BS_GROUPBOX | WS_CHILD | WS_VISIBLE, 107, 147, 175, 93 + CONTROL "Advanced", IDC_BTN_ADVANCED, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE, 109, 124, 44, 14 + CONTROL IDB_SHARK, IDC_BMP_SHARK, "STATIC", SS_BITMAP | WS_CHILD | WS_VISIBLE, 7, 7, 83, 249 + CONTROL "Draw Cursor", IDC_CHK_DRAW_CURSOR, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE, 219, 227, 54, 10 + CONTROL "Music", IDC_CHK_MUSIC, "BUTTON", BS_AUTOCHECKBOX | WS_CHILD | WS_VISIBLE | WS_TABSTOP, 124, 104, 35, 10 +END + +1 VERSIONINFO +FILEVERSION 1,1,0,0 +PRODUCTVERSION 1,1,0,0 +FILEOS 0x4 +FILETYPE 0x1 +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Mindscape, Inc." + VALUE "FileDescription", "LEGOIsland & Configuration application" + VALUE "FileVersion", "1, 1, 0, 0" + VALUE "InternalName", "LEGOISLE.EXE" + VALUE "LegalCopyright", "Copyright \xA9 1997" + VALUE "OriginalFilename", "CONFIG.EXE" + VALUE "ProductName", "LEGO Island" + VALUE "ProductVersion", "1, 1, 0, 0" + END + END + + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x0409, 0x04B0 // U.S. English, Unicode + END +END diff --git a/CONFIG_orig/res/lego.ico b/CONFIG_orig/res/lego.ico new file mode 100644 index 00000000..7db1421d Binary files /dev/null and b/CONFIG_orig/res/lego.ico differ diff --git a/CONFIG_orig/res/resource.h b/CONFIG_orig/res/resource.h new file mode 100644 index 00000000..4c72eaa1 --- /dev/null +++ b/CONFIG_orig/res/resource.h @@ -0,0 +1,25 @@ +#define IDI_CONFIG 128 + +#define IDD_ABOUT 100 +#define IDS_ABOUT 101 + +#define IDD_MAIN 102 + +#define IDC_LIST_3DDEVICES 1000 +#define IDC_CHK_FLIP_VIDEO_MEM_PAGES 1001 +#define IDC_CHK_3D_VIDEO_MEMORY 1003 +#define IDC_RAD_PALETTE_256 1004 +#define IDC_RAD_PALETTE_16BIT 1005 +#define IDC_CHK_3DSOUND 1006 +#define IDC_CHK_DRAW_CURSOR 1008 +#define IDC_RAD_MODEL_QUALITY_LOW 1010 +#define IDC_RAD_MODEL_QUALITY_HIGH 1011 +#define IDC_RAD_TEXTURE_QUALITY_LOW 1013 +#define IDC_RAD_TEXTURE_QUALITY_HIGH 1014 +#define IDC_CHK_JOYSTICK 1015 +#define IDC_GRP_ADVANCED 1017 +#define IDC_BTN_ADVANCED 1020 +#define IDC_BMP_SHARK 1023 +#define IDC_CHK_MUSIC 1024 + +#define IDB_SHARK 135 diff --git a/CONFIG_orig/res/resrc1.h b/CONFIG_orig/res/resrc1.h new file mode 100644 index 00000000..dcbbc622 --- /dev/null +++ b/CONFIG_orig/res/resrc1.h @@ -0,0 +1,16 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Developer Studio generated include file. +// Used by config.rc +// +#define IDAPPLY 4 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 101 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1000 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/CONFIG_orig/res/shark.bmp b/CONFIG_orig/res/shark.bmp new file mode 100644 index 00000000..0829a1d1 Binary files /dev/null and b/CONFIG_orig/res/shark.bmp differ diff --git a/ICON_RES_SOURCE/CONFIGraw.png b/ICON_RES_SOURCE/CONFIGraw.png new file mode 100644 index 00000000..15e9e2b5 Binary files /dev/null and b/ICON_RES_SOURCE/CONFIGraw.png differ diff --git a/ICON_RES_SOURCE/TMAraw.png b/ICON_RES_SOURCE/TMAraw.png new file mode 100644 index 00000000..0f5cf5d8 Binary files /dev/null and b/ICON_RES_SOURCE/TMAraw.png differ diff --git a/ISLE/isleapp.cpp b/ISLE/isleapp.cpp index a22f685b..62047412 100644 --- a/ISLE/isleapp.cpp +++ b/ISLE/isleapp.cpp @@ -1,3 +1,17 @@ +//------------------------------------------------------------------------------------------------------------------ +// +// BE WARNED!!! +// +// YOU ARE STEPPING INTO THE TERRITORY OF DECOMPILED CODE VERSES A DUMBASS +// +// THERE WILL BE A LOT OF SHIT THAT IS STUPID - AND A LOT OF SHIT THAT CAN BE FIXED BUT I DONT KNOW HOW +// +// YOU HAVE BEEN WARNED +// +//------------------------------------------------------------------------------------------------------------------ + +// ident - this file is post complete decomp, but has been modified to include the checks and updates from pre-complete decomp + #include "isleapp.h" #include "3dmanager/lego3dmanager.h" @@ -28,6 +42,9 @@ #include "roi/legoroi.h" #include "viewmanager/viewmanager.h" +// i shit you not, this was a thing that could've happened +//#include "discord-rpc/discord.h" + #include DECOMP_SIZE_ASSERT(IsleApp, 0x8c) @@ -66,10 +83,12 @@ int g_targetDepth = 16; BOOL g_reqEnableRMDevice = FALSE; // STRING: ISLE 0x4101c4 -#define WNDCLASS_NAME "Lego Island MainNoM App" +//#define WNDCLASS_NAME "Lego Island MainNoM App" +#define WNDCLASS_NAME "TMAOMNI PrimaryRuntimeWrapper" // STRING: ISLE 0x4101dc -#define WINDOW_TITLE "LEGO\xAE" +//#define WINDOW_TITLE "LEGO\xAE" +#define WINDOW_TITLE "LEGO Island: The Modder's Arrival" // Might be static functions of IsleApp BOOL FindExistingInstance(); @@ -172,14 +191,28 @@ void IsleApp::Close() TickleManager()->Tickle(); } } + //Discord_Shutdown(); } +/*BOOL IsleApp::GetWorkingDir() +{ + string s1 = GetCommandLine(); + s1 = s1.substr(0, s1.find_last_of("\\/")); + //p_assign = s1.c_str(); + const char* ret = s1.c_str(); + if (!WriteReg("temp", ret)) + return FALSE; + + return TRUE; + //return ret; +}*/ + // FUNCTION: ISLE 0x4013b0 BOOL IsleApp::SetupLegoOmni() { BOOL result = FALSE; char mediaPath[256]; - GetProfileStringA("LEGO Island", "MediaPath", "", mediaPath, sizeof(mediaPath)); + GetProfileStringA("LEGO Island TMA", "MediaPath", "", mediaPath, sizeof(mediaPath)); #ifdef COMPAT_MODE BOOL failure; @@ -231,6 +264,31 @@ void IsleApp::SetupVideoFlags( } } +//TODO: FIXME +/*void InitDiscord() +{ + DiscordEventHandlers handlers; + memset(&handlers, 0, sizeof(handlers)); + handlers.ready = handleDiscordReady; + handlers.errored = handleDiscordError; + + Discord_Initialize("1249977928561721357", &handlers, 1, NULL, NULL); +} + +void UpdatePresence() +{ + char buffer[256]; + DiscordRichPresence discordPresence; + memset(&discordPresence, 0, sizeof(discordPresence)); + discordPresence.state = "Lego Island:"; + //sprintf(buffer, "Ranked | Mode: %d", GameEngine.GetMode()); + discordPresence.details = "The Modder's Arrival"; + discordPresence.endTimestamp = time(0) + 5 * 60; + //discordPresence.largeImageKey = "img"; + discordPresence.instance = 1; + Discord_UpdatePresence(&discordPresence); +}*/ + // FUNCTION: ISLE 0x401610 int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) { @@ -253,9 +311,9 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine if (!soundReady) { MessageBoxA( NULL, - "\"LEGO\xAE Island\" is not detecting a DirectSound compatible sound card. Please quit all other " - "applications and try again.", - "Lego Island Error", + "\"LEGO Island: The Modder's Arrival\" failed to detect a DirectSound compatible sound card/driver. Please ensure any " + "possibly interfering applications have been terminated before re-launching \"LEGO Island: The Modder's Arrival\".", + "Sound Card Failure!", MB_OK ); return 0; @@ -264,12 +322,28 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine // Create global app instance g_isle = new IsleApp(); + // Crash if config open. You wana break the game? Too Bad!! + HWND hWnd = FindWindowA(NULL, "Configuration for LEGO Island: The Modder's Arrival"); + if (hWnd) { + MessageBoxA( + NULL, + "\"LEGO Island: The Modder's Arrival\" requests that you exit \"LEGO Island: The Modder's Arrival\" configuration dialogue " + "before continuing. This ensures that no unwanted behavior occurs by preventing you from changing configurations " + "while the game is running.", + "Please close Config!", + MB_OK + ); + return 0; + } + // Create window + // Flip Surfaces don't work in Windowed mode, crash. if (g_isle->SetupWindow(hInstance, lpCmdLine) != SUCCESS) { MessageBoxA( NULL, - "\"LEGO\xAE Island\" failed to start. Please quit all other applications and try again.", - "LEGO\xAE Island Error", + "\"LEGO Island: The Modder's Arrival\" encountered an error while attempting to create the application window. Please ensure any possibly " + "interfering applications have been terminated before re-launching \"LEGO Island: The Modder's Arrival\".", + "Failed to Create Window!", MB_OK ); return 0; @@ -570,6 +644,7 @@ MxResult IsleApp::SetupWindow(HINSTANCE hInstance, LPSTR lpCmdLine) wndclass.hIcon = LoadIconA(hInstance, MAKEINTRESOURCEA(APP_ICON)); wndclass.hCursor = m_cursorArrow = m_cursorCurrent = LoadCursorA(hInstance, MAKEINTRESOURCEA(ISLE_ARROW)); m_cursorBusy = LoadCursorA(hInstance, MAKEINTRESOURCEA(ISLE_BUSY)); + //m_cursorBusy = CreateAniCursor(ISLE_BUSY); m_cursorNo = LoadCursorA(hInstance, MAKEINTRESOURCEA(ISLE_NO)); wndclass.hInstance = hInstance; wndclass.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH); @@ -682,6 +757,34 @@ MxResult IsleApp::SetupWindow(HINSTANCE hInstance, LPSTR lpCmdLine) return SUCCESS; } +BOOL IsleApp::WriteReg(const char* p_key, const char* p_value) const +{ + HKEY hKey; + DWORD pos; + + if (RegCreateKeyExA( + HKEY_LOCAL_MACHINE, + "SOFTWARE\\ActionSoft\\LEGO Island TMA", + 0, + "string", + 0, + KEY_READ | KEY_WRITE, + NULL, + &hKey, + &pos + ) == ERROR_SUCCESS) { + if (RegSetValueExA(hKey, p_key, 0, REG_SZ, (LPBYTE) p_value, strlen(p_value)) == ERROR_SUCCESS) { + if (RegCloseKey(hKey) == ERROR_SUCCESS) { + return TRUE; + } + } + else { + RegCloseKey(hKey); + } + } + return FALSE; +} + // FUNCTION: ISLE 0x402740 BOOL IsleApp::ReadReg(LPCSTR name, LPSTR outValue, DWORD outSize) { @@ -690,7 +793,7 @@ BOOL IsleApp::ReadReg(LPCSTR name, LPSTR outValue, DWORD outSize) BOOL out = FALSE; DWORD size = outSize; - if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\Mindscape\\LEGO Island", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { + if (RegOpenKeyExA(HKEY_LOCAL_MACHINE, "SOFTWARE\\ActionSoft\\LEGO Island TMA", 0, KEY_READ, &hKey) == ERROR_SUCCESS) { if (RegQueryValueExA(hKey, name, NULL, &valueType, (LPBYTE) outValue, &size) == ERROR_SUCCESS) { if (RegCloseKey(hKey) == ERROR_SUCCESS) { out = TRUE; @@ -741,21 +844,86 @@ void IsleApp::LoadConfig() { char buffer[1024]; + //big nasty fuckin' hack comin' up + //a hack which doesn't even work, FUCK + /*GetWorkingDir(); + + if (!ReadReg("temp", buffer, sizeof(buffer))) { + MessageBoxA( + NULL, + "\"LEGO Island: The Modder's Arrival\" encountered an error while attempting to aquire the current directory from temporary registry key. " + "Please restart the computer and/or contact support to resolve the issue.", + "Failed to Aquire Key: \"temp\"", + MB_OK + ); + } + + strcpy(m_curDirBase, buffer); + strcpy(m_curDir1, m_curDirBase); + strcat(m_curDir1, "\""); + + if (!WriteReg("diskpath", m_curDirBase)) + { + MessageBoxA( + NULL, + "\"LEGO Island: The Modder's Arrival\" encountered an error while attempting to assign the diskpath registry key. " + "Please restart the computer and/or contact support to resolve the issue.", + "Failed to Assign Key: \"diskpath\"", + MB_OK + ); + }*/ + + //We're not streaming off CD in this mod, return disk path for cd path + //if (!WriteReg("cdpath", GetWorkingDir())) + //{ + // MessageBoxA( + // NULL, + // "\"LEGO Island: The Modder's Arrival\" encountered an error while attempting to assign the cdpath registry key. " + // "Please restart the computer and/or contact support to resolve the issue.", + // "Failed to Assign Key: \"cdpath\"", + // MB_OK + // ); + //} + if (!ReadReg("diskpath", buffer, sizeof(buffer))) { - strcpy(buffer, MxOmni::GetHD()); + //strcpy(buffer, MxOmni::GetHD()); + MessageBoxA( + NULL, + "\"LEGO Island: The Modder's Arrival\" encountered an error while attempting to aquire the \"diskpath\" registry key. " + "Please ensure you followed the instructions in the README text document properly and relaunch the game.", + "Failed to Aquire Key: \"diskpath\"", + MB_OK + ); + Close(); + MxOmni::DestroyInstance(); } m_hdPath = new char[strlen(buffer) + 1]; strcpy(m_hdPath, buffer); MxOmni::SetHD(m_hdPath); + MxOmni::SetCD(m_hdPath); - if (!ReadReg("cdpath", buffer, sizeof(buffer))) { - strcpy(buffer, MxOmni::GetCD()); - } + //if (!ReadReg("cdpath", buffer, sizeof(buffer))) { + // strcpy(buffer, GetWorkingDir()); + //} + + //if (!ReadReg("savepath", buffer, sizeof(buffer))) { + // if (!WriteReg("savepath", MxOmni::GetHD() + "\\SAV")) + // { + // MessageBoxA( + // NULL, + // "\"LEGO Island: The Modder's Arrival\" encountered an error while attempting to assign the savepath registry key. " + // "Please restart the computer and/or contact support to resolve the issue.", + // "Failed to Assign Key: \"savepath\"", + // MB_OK + // ); + // return 0; + // } + //} - m_cdPath = new char[strlen(buffer) + 1]; - strcpy(m_cdPath, buffer); - MxOmni::SetCD(m_cdPath); + //m_cdPath = new char[strlen(buffer) + 1]; + //strcpy(m_cdPath, buffer); + //MxOmni::SetCD(m_cdPath); ReadRegBool("Flip Surfaces", &m_flipSurfaces); ReadRegBool("Full Screen", &m_fullScreen); @@ -799,6 +967,23 @@ void IsleApp::LoadConfig() if (ReadReg("savepath", buffer, sizeof(buffer))) { m_savePath = new char[strlen(buffer) + 1]; strcpy(m_savePath, buffer); + } else { + m_savePath = new char[strlen(buffer) + 1]; + //strcpy(m_savePath, m_curDirBase); + strcpy(m_savePath, m_hdPath); + strcat(m_savePath, "\\SAV"); + if (!WriteReg("savepath", m_savePath)) + { + MessageBoxA( + NULL, + "\"LEGO Island: The Modder's Arrival\" encountered an error while attempting to assign the savepath registry key. " + "Please restart the computer and/or contact support to resolve the issue.", + "Failed to Assign Key: \"savepath\"", + MB_OK + ); + Close(); + MxOmni::DestroyInstance(); + } } } @@ -853,6 +1038,7 @@ inline void IsleApp::Tick(BOOL sleepIfNotNextFrame) MxDSAction ds; if (!stream) { + // since we have disabled cd drive functionality, this should only trigger if lego\scripts\isle\isle.si is missing stream = Streamer()->Open("\\lego\\scripts\\nocd", MxStreamer::e_diskStream); if (!stream) { return; diff --git a/ISLE/isleapp.h b/ISLE/isleapp.h index 6a8289ce..15fb7e78 100644 --- a/ISLE/isleapp.h +++ b/ISLE/isleapp.h @@ -28,6 +28,7 @@ class IsleApp { ); MxResult SetupWindow(HINSTANCE hInstance, LPSTR lpCmdLine); + BOOL WriteReg(const char* p_key, const char* p_value) const; BOOL ReadReg(LPCSTR name, LPSTR outValue, DWORD outSize); BOOL ReadRegBool(LPCSTR name, BOOL* out); BOOL ReadRegInt(LPCSTR name, int* out); diff --git a/ISLE/res/TMA.ico b/ISLE/res/TMA.ico new file mode 100644 index 00000000..1783b45a Binary files /dev/null and b/ISLE/res/TMA.ico differ diff --git a/ISLE/res/arrow.cur b/ISLE/res/arrow.cur index 548b8dac..8462ef8f 100644 Binary files a/ISLE/res/arrow.cur and b/ISLE/res/arrow.cur differ diff --git a/ISLE/res/busy.ani b/ISLE/res/busy.ani new file mode 100644 index 00000000..5cfcfe7f Binary files /dev/null and b/ISLE/res/busy.ani differ diff --git a/ISLE/res/busy.cur b/ISLE/res/busy.cur index 320d6b9a..25e40496 100644 Binary files a/ISLE/res/busy.cur and b/ISLE/res/busy.cur differ diff --git a/ISLE/res/exclaim.cur b/ISLE/res/exclaim.cur new file mode 100644 index 00000000..0db9ab83 Binary files /dev/null and b/ISLE/res/exclaim.cur differ diff --git a/ISLE/res/isle.rc b/ISLE/res/isle.rc index 95432e06..3f0d2712 100644 --- a/ISLE/res/isle.rc +++ b/ISLE/res/isle.rc @@ -1,13 +1,21 @@ #include "resource.h" +//Why in the fuck does this resource code not accept animated cursor icons (.ani)? +//What the fuck?? + +//3 days later - Hello Dipshit, the syntax for animated cursors is ANICURSOR, not CURSOR +//hope you found this helpful :3 + ISLE_ARROW CURSOR "arrow.cur" -ISLE_NO CURSOR "no.cur" +//ISLE_NO CURSOR "no.cur" +ISLE_NO CURSOR "exclaim.cur" ISLE_BUSY CURSOR "busy.cur" -APP_ICON ICON "isle.ico" +//ISLE_BUSY ANICURSOR "busy.ani" +APP_ICON ICON "TMA.ico" 1 VERSIONINFO -FILEVERSION 1,1,0,0 -PRODUCTVERSION 1,1,0,0 +FILEVERSION 0,0,0,1 +PRODUCTVERSION 0,0,0,1 FILEOS 0x40004 FILETYPE 0x1 { @@ -16,14 +24,14 @@ BLOCK "StringFileInfo" BLOCK "040904b0" { VALUE "Comments", "DG JB AG RC EE" - VALUE "CompanyName", "Mindscape" - VALUE "FileDescription", "isle" - VALUE "FileVersion", "1, 1, 0, 0" - VALUE "InternalName", "isle" - VALUE "LegalCopyright", "Copyright \xA9 1997" - VALUE "OriginalFilename", "isle.exe" - VALUE "ProductName", "Adventures on LEGO Island" - VALUE "ProductVersion", "1, 1, 0, 0" + VALUE "CompanyName", "FUNNIMAN.EXE" + VALUE "FileDescription", "b_TMAOmni Library Launcher" + VALUE "FileVersion", "0, 0, 0, 1" + VALUE "InternalName", "b_TMAOmni Library Launcher" + VALUE "LegalCopyright", "Copyright \xA9 2025" + VALUE "OriginalFilename", "ISLE_LAUNCHER.EXE" + VALUE "ProductName", "LEGO Island: The Modder's Arrival" + VALUE "ProductVersion", "0, 0, 0, 1" VALUE "SpecialBuild", "Very" } } diff --git a/ISLE/res/no.cur b/ISLE/res/no.cur index b3966420..ff9edae0 100644 Binary files a/ISLE/res/no.cur and b/ISLE/res/no.cur differ diff --git a/LEGO1/lego/legoomni/include/act3brickster.h b/LEGO1/lego/legoomni/include/act3brickster.h new file mode 100644 index 00000000..c84b8dd7 --- /dev/null +++ b/LEGO1/lego/legoomni/include/act3brickster.h @@ -0,0 +1,31 @@ +#ifndef ACT3BRICKSTER_H +#define ACT3BRICKSTER_H + +#include "act3actors.h" + +// VTABLE: LEGO1 0x100d7838 LegoPathActor +// VTABLE: LEGO1 0x100d7908 LegoAnimActor +// SIZE 0x1b4 +class Act3Brickster : public Act3Actor { +public: + Act3Brickster(); + ~Act3Brickster() override; + + void ParseAction(char* p_extra) override; // vtable+0x20 + void VTable0x70(float p_und) override; // vtable+0x70 + MxResult VTable0x94(LegoPathActor*, MxBool) override; // vtable+0x94 + void SwitchBoundary( + LegoPathBoundary*& p_boundary, + LegoUnknown100db7f4*& p_edge, + float& p_unk0xe4 + ) override; // vtable+0x98 + MxResult VTable0x9c() override; // vtable+0x9c + + // SYNTHETIC: LEGO1 0x10043250 + // Act3Brickster::`scalar deleting destructor' + +private: + undefined4 m_unk0x20[15]; // 0x20 +}; + +#endif // ACT3BRICKSTER_H diff --git a/LEGO1/lego/legoomni/include/act3cop.h b/LEGO1/lego/legoomni/include/act3cop.h new file mode 100644 index 00000000..a720b84c --- /dev/null +++ b/LEGO1/lego/legoomni/include/act3cop.h @@ -0,0 +1,25 @@ +#ifndef ACT3COP_H +#define ACT3COP_H + +#include "act3actors.h" + +// VTABLE: LEGO1 0x100d7750 LegoPathActor +// VTABLE: LEGO1 0x100d7820 LegoAnimActor +// SIZE 0x188 +class Act3Cop : public Act3Actor { +public: + Act3Cop(); + + void ParseAction(char* p_extra) override; // vtable+0x20 + void VTable0x70(float p_und) override; // vtable+0x70 + MxResult VTable0x94(LegoPathActor*, MxBool) override; // vtable+0x94 + MxResult VTable0x9c() override; // vtable+0x9c + + // SYNTHETIC: LEGO1 0x10043120 + // Act3Cop::`scalar deleting destructor' + +private: + undefined4 m_unk0x20[4]; // 0x20 +}; + +#endif // ACT3COP_H diff --git a/LEGO1/lego/legoomni/include/act3shark.h b/LEGO1/lego/legoomni/include/act3shark.h new file mode 100644 index 00000000..49b467b2 --- /dev/null +++ b/LEGO1/lego/legoomni/include/act3shark.h @@ -0,0 +1,42 @@ +#ifndef ACT3SHARK_H +#define ACT3SHARK_H + +#include "legoanimactor.h" + +// VTABLE: LEGO1 0x100d7920 LegoPathActor +// VTABLE: LEGO1 0x100d79f0 LegoAnimActor +// SIZE 0x1a8 +class Act3Shark : public LegoAnimActor { +public: + Act3Shark(); + + // FUNCTION: LEGO1 0x100430d0 + const char* ClassName() const override // vtable+0x0c + { + // STRING: LEGO1 0x100f03a0 + return "Act3Shark"; + } + + void ParseAction(char*) override; // vtable+0x20 + void VTable0x70(float p_float) override; // vtable+0x70 + + // LegoAnimActor vtable + virtual MxResult FUN_10042ce0(void*); // vtable+0x10 + + // SYNTHETIC: LEGO1 0x10043030 + // Act3Shark::`scalar deleting destructor' + +private: + list m_unk0x1c; // 0x1c + undefined4 m_unk0x28; // 0x28 + undefined4 m_unk0x2c; // 0x2c + undefined m_unk0x30[0x0c]; // 0x30 + Mx3DPointFloat m_unk0x3c; // 0x3c +}; + +// STUB: LEGO1 0x10042c90 +// List::~List +// TODO: Update once type is known. +// STUB to resolve diff in scalar dtor and not create a new one. + +#endif // ACT3SHARK_H diff --git a/LEGO1/lego/legoomni/include/legocarbuild.h b/LEGO1/lego/legoomni/include/legocarbuild.h index 6add3be3..a11c3803 100644 --- a/LEGO1/lego/legoomni/include/legocarbuild.h +++ b/LEGO1/lego/legoomni/include/legocarbuild.h @@ -91,10 +91,13 @@ class LegoCarBuild : public LegoWorld { LegoCarBuild(); ~LegoCarBuild() override; +<<<<<<< Updated upstream +======= // FUNCTION: LEGO1 0x10022930 // FUNCTION: BETA10 0x10070070 MxBool VTable0x5c() override { return TRUE; } // vtable+0x5c +>>>>>>> Stashed changes // FUNCTION: LEGO1 0x10022940 // FUNCTION: BETA10 0x10070090 const char* ClassName() const override // vtable+0x0c @@ -114,6 +117,10 @@ class LegoCarBuild : public LegoWorld { MxResult Create(MxDSAction& p_dsAction) override; // vtable+0x18 void ReadyWorld() override; // vtable+0x50 +<<<<<<< Updated upstream + MxBool VTable0x5c() override; // vtable+0x5c +======= +>>>>>>> Stashed changes MxBool Escape() override; // vtable+0x64 void Enable(MxBool p_enable) override; // vtable+0x68 virtual void VTable0x6c(); // vtable+0x6c diff --git a/LEGO1/lego/legoomni/include/legoextraactor.h b/LEGO1/lego/legoomni/include/legoextraactor.h index 5f930820..f3e1ab6e 100644 --- a/LEGO1/lego/legoomni/include/legoextraactor.h +++ b/LEGO1/lego/legoomni/include/legoextraactor.h @@ -2,7 +2,10 @@ #define LEGOEXTRAACTOR_H #include "legoanimactor.h" +<<<<<<< Updated upstream +======= #include "legopathboundary.h" +>>>>>>> Stashed changes // VTABLE: LEGO1 0x100d6c00 LegoAnimActor // VTABLE: LEGO1 0x100d6c10 LegoPathActor @@ -37,9 +40,15 @@ class LegoExtraActor : public virtual LegoAnimActor { return !strcmp(p_name, LegoExtraActor::ClassName()) || LegoAnimActor::IsA(p_name); } +<<<<<<< Updated upstream + void SetWorldSpeed(MxFloat p_worldSpeed) override; // vtable+0x30 + MxS32 VTable0x68(Vector3&, Vector3&, Vector3&) override; // vtable+0x68 + MxU32 VTable0x6c( +======= void SetWorldSpeed(MxFloat p_worldSpeed) override; // vtable+0x30 MxS32 VTable0x68(Vector3& p_point1, Vector3& p_point2, Vector3& p_point3) override; // vtable+0x68 inline MxU32 VTable0x6c( +>>>>>>> Stashed changes LegoPathBoundary* p_boundary, Vector3& p_v1, Vector3& p_v2, @@ -78,6 +87,8 @@ class LegoExtraActor : public virtual LegoAnimActor { LegoAnimActorStruct* m_disAnim; // 0x64 }; +<<<<<<< Updated upstream +======= // FUNCTION: LEGO1 0x1002b980 inline MxU32 LegoExtraActor::VTable0x6c( LegoPathBoundary* p_boundary, @@ -180,6 +191,7 @@ inline MxU32 LegoExtraActor::VTable0x6c( return 0; } +>>>>>>> Stashed changes // GLOBAL: LEGO1 0x100d6be8 // LegoExtraActor::`vbtable'{for `LegoAnimActor'} diff --git a/LEGO1/lego/legoomni/include/legojetski.h b/LEGO1/lego/legoomni/include/legojetski.h new file mode 100644 index 00000000..56057da2 --- /dev/null +++ b/LEGO1/lego/legoomni/include/legojetski.h @@ -0,0 +1,70 @@ +#ifndef LEGOJETSKI_H +#define LEGOJETSKI_H + +#include "legojetskiraceactor.h" +#include "legoracemap.h" + +// VTABLE: LEGO1 0x100d5a08 LegoCarRaceActor +// VTABLE: LEGO1 0x100d5a28 LegoRaceActor +// VTABLE: LEGO1 0x100d5a30 LegoAnimActor +// VTABLE: LEGO1 0x100d5a40 LegoPathActor +// VTABLE: LEGO1 0x100d5b10 LegoRaceMap +// SIZE 0x1dc +class LegoJetski : public LegoJetskiRaceActor, public LegoRaceMap { +public: + LegoJetski(); + ~LegoJetski() override; + + MxLong Notify(MxParam& p_param) override; // vtable+0x04 + + // FUNCTION: LEGO1 0x10013e90 + const char* ClassName() const override // vtable+0x0c + { + // STRING: LEGO1 0x100f053c + return "LegoJetski"; + } + + // FUNCTION: LEGO1 0x10013eb0 + MxBool IsA(const char* p_name) const override // vtable+0x10 + { + return !strcmp(p_name, LegoJetski::ClassName()) || LegoJetskiRaceActor::IsA(p_name); + } + + void ParseAction(char*) override; // vtable+0x20 + void SetWorldSpeed(MxFloat p_worldSpeed) override; // vtable+0x30 + MxU32 VTable0x6c( + LegoPathBoundary* p_boundary, + Vector3& p_v1, + Vector3& p_v2, + float p_f1, + float p_f2, + Vector3& p_v3 + ) override; // vtable+0x6c + void VTable0x70(float p_float) override; // vtable+0x70 + MxResult VTable0x94(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94 + void SwitchBoundary(LegoPathBoundary*& p_boundary, LegoUnknown100db7f4*& p_edge, float& p_unk0xe4) + override; // vtable+0x98 + MxResult VTable0x9c() override; // vtable+0x9c + + virtual void FUN_100136f0(float p_worldSpeed); + + // SYNTHETIC: LEGO1 0x10013e30 + // LegoJetski::`scalar deleting destructor' +}; + +// GLOBAL: LEGO1 0x100d59b8 +// LegoJetski::`vbtable'{for `LegoCarRaceActor'} + +// GLOBAL: LEGO1 0x100d59c8 +// LegoJetski::`vbtable'{for `LegoRaceActor'} + +// GLOBAL: LEGO1 0x100d59d8 +// LegoJetski::`vbtable'{for `LegoAnimActor'} + +// GLOBAL: LEGO1 0x100d59e0 +// LegoJetski::`vbtable' + +// GLOBAL: LEGO1 0x100d59f0 +// LegoJetski::`vbtable'{for `LegoJetskiRaceActor'} + +#endif // LEGOJETSKI_H diff --git a/LEGO1/lego/legoomni/include/radio.h b/LEGO1/lego/legoomni/include/radio.h index 9ff8cb9c..05d51be4 100644 --- a/LEGO1/lego/legoomni/include/radio.h +++ b/LEGO1/lego/legoomni/include/radio.h @@ -15,9 +15,12 @@ class RadioState : public LegoState { public: RadioState(); +<<<<<<< Updated upstream +======= // FUNCTION: LEGO1 0x1002cf50 MxBool IsSerializable() override { return FALSE; } // vtable+0x14 +>>>>>>> Stashed changes // FUNCTION: LEGO1 0x1002cf60 // FUNCTION: BETA10 0x100f2850 const char* ClassName() const override // vtable+0x0c @@ -32,6 +35,11 @@ class RadioState : public LegoState { return !strcmp(p_name, RadioState::ClassName()) || LegoState::IsA(p_name); } +<<<<<<< Updated upstream + MxBool IsSerializable() override; // vtable+0x14 + +======= +>>>>>>> Stashed changes // SYNTHETIC: LEGO1 0x1002d020 // RadioState::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/src/actors/act3brickster.cpp b/LEGO1/lego/legoomni/src/actors/act3brickster.cpp new file mode 100644 index 00000000..24340578 --- /dev/null +++ b/LEGO1/lego/legoomni/src/actors/act3brickster.cpp @@ -0,0 +1,47 @@ +#include "act3brickster.h" + +DECOMP_SIZE_ASSERT(Act3Brickster, 0x1b4) + +// STUB: LEGO1 0x10040e10 +Act3Brickster::Act3Brickster() +{ + // TODO +} + +// FUNCTION: LEGO1 0x10040f20 +Act3Brickster::~Act3Brickster() +{ + // TODO +} + +// STUB: LEGO1 0x10040ff0 +void Act3Brickster::ParseAction(char* p_extra) +{ + // TODO +} + +// STUB: LEGO1 0x10041050 +void Act3Brickster::VTable0x70(float p_und) +{ + // TODO +} + +// STUB: LEGO1 0x100416b0 +MxResult Act3Brickster::VTable0x94(LegoPathActor*, MxBool) +{ + // TODO + return SUCCESS; +} + +// STUB: LEGO1 0x10042990 +void Act3Brickster::SwitchBoundary(LegoPathBoundary*& p_boundary, LegoUnknown100db7f4*& p_edge, float& p_unk0xe4) +{ + // TODO +} + +// STUB: LEGO1 0x100429d0 +MxResult Act3Brickster::VTable0x9c() +{ + // TODO + return SUCCESS; +} diff --git a/LEGO1/lego/legoomni/src/actors/act3cop.cpp b/LEGO1/lego/legoomni/src/actors/act3cop.cpp new file mode 100644 index 00000000..557386ce --- /dev/null +++ b/LEGO1/lego/legoomni/src/actors/act3cop.cpp @@ -0,0 +1,35 @@ +#include "act3cop.h" + +DECOMP_SIZE_ASSERT(Act3Cop, 0x188) + +// STUB: LEGO1 0x1003fe30 +Act3Cop::Act3Cop() +{ + // TODO +} + +// STUB: LEGO1 0x1003ff70 +MxResult Act3Cop::VTable0x94(LegoPathActor*, MxBool) +{ + // TODO + return SUCCESS; +} + +// STUB: LEGO1 0x10040060 +void Act3Cop::ParseAction(char* p_extra) +{ + // TODO +} + +// STUB: LEGO1 0x100401f0 +void Act3Cop::VTable0x70(float p_und) +{ + // TODO +} + +// STUB: LEGO1 0x10040d20 +MxResult Act3Cop::VTable0x9c() +{ + // TODO + return SUCCESS; +} diff --git a/LEGO1/lego/legoomni/src/actors/act3shark.cpp b/LEGO1/lego/legoomni/src/actors/act3shark.cpp new file mode 100644 index 00000000..8cc9f698 --- /dev/null +++ b/LEGO1/lego/legoomni/src/actors/act3shark.cpp @@ -0,0 +1,28 @@ +#include "act3shark.h" + +DECOMP_SIZE_ASSERT(Act3Shark, 0x1a8) + +// STUB: LEGO1 0x10042ab0 +Act3Shark::Act3Shark() +{ + // TODO +} + +// STUB: LEGO1 0x10042ce0 +MxResult Act3Shark::FUN_10042ce0(void*) +{ + // TODO + return SUCCESS; +} + +// STUB: LEGO1 0x10042d40 +void Act3Shark::VTable0x70(float p_float) +{ + // TODO +} + +// STUB: LEGO1 0x10042f30 +void Act3Shark::ParseAction(char*) +{ + // TODO +} diff --git a/LEGO1/lego/legoomni/src/actors/radio.cpp b/LEGO1/lego/legoomni/src/actors/radio.cpp index 0291c18c..7666c7da 100644 --- a/LEGO1/lego/legoomni/src/actors/radio.cpp +++ b/LEGO1/lego/legoomni/src/actors/radio.cpp @@ -228,6 +228,15 @@ RadioState::RadioState() m_active = FALSE; } +<<<<<<< Updated upstream +// FUNCTION: LEGO1 0x1002cf50 +MxBool RadioState::IsSerializable() +{ + return FALSE; +} + +======= +>>>>>>> Stashed changes // FUNCTION: LEGO1 0x1002d090 MxU32 RadioState::FUN_1002d090() { diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index c65064c1..21e8b151 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -130,6 +130,16 @@ LegoCarBuild::LegoCarBuild() NotificationManager()->Register(this); } +<<<<<<< Updated upstream +// FUNCTION: LEGO1 0x10022930 +// FUNCTION: BETA10 0x10070070 +MxBool LegoCarBuild::VTable0x5c() +{ + return TRUE; +} + +======= +>>>>>>> Stashed changes // FUNCTION: LEGO1 0x10022a80 // FUNCTION: BETA10 0x1006aea3 LegoCarBuild::~LegoCarBuild() diff --git a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp index 2d97b3ef..2dfed092 100644 --- a/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp @@ -661,6 +661,10 @@ MxBool LegoBuildingManager::FUN_10030110(LegoBuildingInfo* p_data) } // FUNCTION: LEGO1 0x10030150 +<<<<<<< Updated upstream +// FUNCTION: BETA10 0x100644ff +======= +>>>>>>> Stashed changes void LegoBuildingManager::ScheduleAnimation(LegoEntity* p_entity, MxLong p_length, MxBool p_haveSound, MxBool p_unk0x28) { m_world = CurrentWorld(); diff --git a/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp b/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp index c7a821ae..b1a6e9e6 100644 --- a/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp +++ b/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp @@ -618,11 +618,15 @@ void LegoPlantManager::ScheduleAnimation(LegoEntity* p_entity, MxLong p_length) entry->m_entity = p_entity; entry->m_roi = p_entity->GetROI(); +<<<<<<< Updated upstream + entry->m_time = Timer()->GetTime() + p_length + 1000; +======= MxLong time = Timer()->GetTime(); time += p_length; entry->m_time = time + 1000; +>>>>>>> Stashed changes FUN_100271b0(p_entity, -1); } diff --git a/LEGO1/lego/legoomni/src/entity/legojetski.cpp b/LEGO1/lego/legoomni/src/entity/legojetski.cpp new file mode 100644 index 00000000..5ad8524d --- /dev/null +++ b/LEGO1/lego/legoomni/src/entity/legojetski.cpp @@ -0,0 +1,90 @@ +#include "legojetski.h" + +#include "mxmisc.h" +#include "mxnotificationmanager.h" + +DECOMP_SIZE_ASSERT(LegoJetski, 0x1dc) + +// STUB: LEGO1 0x100136a0 +void LegoJetski::SetWorldSpeed(MxFloat p_worldSpeed) +{ + // TODO +} + +// FUNCTION: LEGO1 0x100136f0 +void LegoJetski::FUN_100136f0(float p_worldSpeed) +{ + if (p_worldSpeed < 0) { + LegoCarRaceActor::m_unk0x0c = 2; + m_maxLinearVel = 0; + SetWorldSpeed(0); + } + else { + m_maxLinearVel = p_worldSpeed; + } +} + +// STUB: LEGO1 0x10013740 +void LegoJetski::VTable0x70(float p_float) +{ + // TODO +} + +// FUNCTION: LEGO1 0x10013820 +LegoJetski::LegoJetski() +{ + NotificationManager()->Register(this); +} + +// FUNCTION: LEGO1 0x10013aa0 +LegoJetski::~LegoJetski() +{ + NotificationManager()->Unregister(this); +} + +// STUB: LEGO1 0x10013bb0 +void LegoJetski::ParseAction(char*) +{ + // TODO +} + +// STUB: LEGO1 0x10013c30 +MxLong LegoJetski::Notify(MxParam& p_param) +{ + // TODO + return 0; +} + +// STUB: LEGO1 0x10013c40 +MxResult LegoJetski::VTable0x94(LegoPathActor* p_actor, MxBool p_bool) +{ + // TODO + return 0; +} + +// STUB: LEGO1 0x10014150 +MxU32 LegoJetski::VTable0x6c( + LegoPathBoundary* p_boundary, + Vector3& p_v1, + Vector3& p_v2, + float p_f1, + float p_f2, + Vector3& p_v3 +) +{ + // TODO + return 0; +} + +// STUB: LEGO1 0x100141d0 +void LegoJetski::SwitchBoundary(LegoPathBoundary*& p_boundary, LegoUnknown100db7f4*& p_edge, float& p_unk0xe4) +{ + // TODO +} + +// STUB: LEGO1 0x10014210 +MxResult LegoJetski::VTable0x9c() +{ + // TODO + return SUCCESS; +} diff --git a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp index 58b27935..1a908bd9 100644 --- a/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp +++ b/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp @@ -343,7 +343,7 @@ MxBool LegoNavController::CalculateNewPosDir( m_rotationalVel = CalculateNewVel(m_targetRotationalVel, m_rotationalVel, m_rotationalAccel * 40.0f, deltaTime); } else { - m_rotationalVel = m_targetRotationalVel; + m_rotationalVel = ((m_targetRotationalVel * 25.0f) * deltaTime); } m_linearVel = CalculateNewVel(m_targetLinearVel, m_linearVel, m_linearAccel, deltaTime); @@ -1041,4 +1041,4 @@ MxLong LegoNavController::Notify(MxParam& p_param) } return 0; -} +} \ No newline at end of file diff --git a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp index 2c2bd166..d763b6e8 100644 --- a/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp +++ b/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp @@ -3,6 +3,10 @@ #include "anim/legoanim.h" #include "legocachesoundmanager.h" #include "legolocomotionanimpresenter.h" +<<<<<<< Updated upstream +#include "legopathboundary.h" +======= +>>>>>>> Stashed changes #include "legosoundmanager.h" #include "legoworld.h" #include "misc.h" @@ -420,3 +424,108 @@ MxS32 LegoExtraActor::VTable0x68(Vector3& p_point1, Vector3& p_point2, Vector3& { return LegoPathActor::VTable0x68(p_point1, p_point2, p_point3); } +<<<<<<< Updated upstream + +// FUNCTION: LEGO1 0x1002b980 +MxU32 LegoExtraActor::VTable0x6c( + LegoPathBoundary* p_boundary, + Vector3& p_v1, + Vector3& p_v2, + float p_f1, + float p_f2, + Vector3& p_v3 +) +{ + LegoAnimPresenterSet& presenters = p_boundary->GetPresenters(); + + for (LegoAnimPresenterSet::iterator itap = presenters.begin(); itap != presenters.end(); itap++) { + if ((*itap)->VTable0x94(p_v1, p_v2, p_f1, p_f2, p_v3)) { + return 1; + } + } + + LegoPathActorSet& plpas = p_boundary->GetActors(); + LegoPathActorSet lpas(plpas); + + for (LegoPathActorSet::iterator itpa = lpas.begin(); itpa != lpas.end(); itpa++) { + if (plpas.find(*itpa) != plpas.end()) { + LegoPathActor* actor = *itpa; + + if (this != actor && !(actor->GetActorState() & LegoPathActor::c_noCollide)) { + LegoROI* roi = actor->GetROI(); + + if ((roi != NULL && roi->GetVisibility()) || actor->GetCameraFlag()) { + if (actor->GetUserNavFlag()) { + MxMatrix local2world = roi->GetLocal2World(); + Vector3 local60(local2world[3]); + Mx3DPointFloat local54(p_v1); + + local54 -= local60; + float local1c = p_v2.Dot(p_v2, p_v2); + float local24 = p_v2.Dot(p_v2, local54) * 2.0f; + float local20 = local54.Dot(local54, local54); + + if (m_unk0x15 != 0 && local20 < 10.0f) { + return 0; + } + + local20 -= 1.0f; + + if (local1c >= 0.001 || local1c <= -0.001) { + float local40 = (local24 * local24) + (local20 * local1c * -4.0f); + + if (local40 >= -0.001) { + local1c *= 2.0f; + local24 = -local24; + + if (local40 < 0.0f) { + local40 = 0.0f; + } + + local40 = sqrt(local40); + float local20X = (local24 + local40) / local1c; + float local1cX = (local24 - local40) / local1c; + + if (local1cX < local20X) { + local40 = local20X; + local20X = local1cX; + local1cX = local40; + } + + if ((local20X >= 0.0f && local20X <= p_f1) || (local1cX >= 0.0f && local1cX <= p_f1) || + (local20X <= -0.01 && p_f1 + 0.01 <= local1cX)) { + p_v3 = p_v1; + + if (HitActor(actor, TRUE) < 0) { + return 0; + } + + actor->HitActor(this, FALSE); + return 2; + } + } + } + } + else { + if (roi->FUN_100a9410(p_v1, p_v2, p_f1, p_f2, p_v3, m_collideBox && actor->GetCollideBox())) { + if (HitActor(actor, TRUE) < 0) { + return 0; + } + + actor->HitActor(this, FALSE); + return 2; + } + } + } + } + } + } + + if (m_unk0x15 != 0) { + m_unk0x15--; + } + + return 0; +} +======= +>>>>>>> Stashed changes diff --git a/LEGO1/lego/sources/geom/legobox.cpp b/LEGO1/lego/sources/geom/legobox.cpp new file mode 100644 index 00000000..0a5f2bdb --- /dev/null +++ b/LEGO1/lego/sources/geom/legobox.cpp @@ -0,0 +1,19 @@ +#include "legobox.h" + +#include "decomp.h" +#include "misc/legoutil.h" + +DECOMP_SIZE_ASSERT(LegoBox, 0x18) + +// FUNCTION: LEGO1 0x100d3740 +LegoResult LegoBox::Read(LegoStorage* p_storage) +{ + LegoResult result; + if ((result = m_min.Read(p_storage)) != SUCCESS) { + return result; + } + if ((result = m_max.Read(p_storage)) != SUCCESS) { + return result; + } + return SUCCESS; +} diff --git a/LEGO1/lego/sources/geom/legobox.h b/LEGO1/lego/sources/geom/legobox.h new file mode 100644 index 00000000..f0816c98 --- /dev/null +++ b/LEGO1/lego/sources/geom/legobox.h @@ -0,0 +1,32 @@ +#ifndef __LEGOBOX_H +#define __LEGOBOX_H + +#include "legovertex.h" + +// SIZE 0x18 +class LegoBox { +public: + LegoVertex& GetMin() { return m_min; } + void SetMin(LegoVertex& p_min) { m_min = p_min; } + LegoVertex& GetMax() { return m_max; } + void SetMax(LegoVertex& p_max) { m_max = p_max; } + // LegoVertex GetCenter() + // { + // return LegoVertex( + // (m_min.GetX() + m_max.GetX()) / 2, + // (m_min.GetY() + m_max.GetY()) / 2, + // (m_min.GetZ() + m_max.GetZ()) / 2 + // ); + // } + LegoFloat GetDX() { return m_max.GetX() - m_min.GetX(); } + LegoFloat GetDY() { return m_max.GetY() - m_min.GetY(); } + LegoFloat GetDZ() { return m_max.GetZ() - m_min.GetZ(); } + LegoBool IsEmpty() { return m_min.IsOrigin() && m_max.IsOrigin(); } + LegoResult Read(LegoStorage* p_storage); + +protected: + LegoVertex m_min; // 0x00 + LegoVertex m_max; // 0x0c +}; + +#endif // __LEGOBOX_H diff --git a/LEGO1/lego/sources/geom/legomesh.cpp b/LEGO1/lego/sources/geom/legomesh.cpp new file mode 100644 index 00000000..51b61fb0 --- /dev/null +++ b/LEGO1/lego/sources/geom/legomesh.cpp @@ -0,0 +1,91 @@ +#include "legomesh.h" + +#include "misc/legostorage.h" + +DECOMP_SIZE_ASSERT(LegoMeshUnkComponent, 0x1c) +DECOMP_SIZE_ASSERT(LegoMesh, 0x24) + +// FUNCTION: LEGO1 0x100d3810 +LegoMesh::LegoMesh() +{ + m_alpha = 0.0F; + m_shading = e_flat; + m_unk0x14 = 0; + m_textureName = NULL; + m_unk0x0d = 0; + m_unk0x10 = NULL; + m_unk0x20 = 0; + m_unk0x21 = FALSE; + m_materialName = NULL; +} + +// FUNCTION: LEGO1 0x100d3860 +LegoMesh::~LegoMesh() +{ + if (m_textureName != NULL) { + delete[] m_textureName; + } + + if (m_materialName != NULL) { + delete[] m_materialName; + } + + if (m_unk0x10 != NULL) { + delete m_unk0x10; + } +} + +// FUNCTION: LEGO1 0x100d38f0 +LegoResult LegoMesh::Read(LegoStorage* p_storage) +{ + LegoResult result; + LegoU32 textureLength, materialLength; + if ((result = m_color.Read(p_storage)) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_alpha, sizeof(m_alpha))) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_shading, sizeof(m_shading))) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_unk0x0d, sizeof(m_unk0x0d))) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_unk0x20, sizeof(m_unk0x20))) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_unk0x21, sizeof(m_unk0x21))) != SUCCESS) { + return result; + } + + if ((result = p_storage->Read(&textureLength, sizeof(textureLength))) != SUCCESS) { + return result; + } + if (textureLength) { + m_textureName = new LegoChar[textureLength + 1]; + + if ((result = p_storage->Read(m_textureName, textureLength)) != SUCCESS) { + return result; + } + + m_textureName[textureLength] = '\0'; + strlwr(m_textureName); + } + + if ((result = p_storage->Read(&materialLength, sizeof(materialLength))) != SUCCESS) { + return result; + } + if (materialLength) { + m_materialName = new LegoChar[materialLength + 1]; + + if ((result = p_storage->Read(m_materialName, materialLength)) != SUCCESS) { + return result; + } + + m_materialName[materialLength] = '\0'; + strlwr(m_materialName); + } + + return SUCCESS; +} diff --git a/LEGO1/lego/sources/geom/legomesh.h b/LEGO1/lego/sources/geom/legomesh.h new file mode 100644 index 00000000..1832ae7b --- /dev/null +++ b/LEGO1/lego/sources/geom/legomesh.h @@ -0,0 +1,78 @@ +#ifndef __LEGOMESH_H +#define __LEGOMESH_H + +#include "decomp.h" +#include "misc/legocolor.h" +#include "misc/legotypes.h" + +class LegoStorage; + +// SIZE 0x1c +struct LegoMeshUnkComponent { + ~LegoMeshUnkComponent() + { + if (m_unk0x08) { + delete m_unk0x08; + } + if (m_unk0x0c) { + delete m_unk0x0c; + } + if (m_unk0x10) { + delete m_unk0x10; + } + if (m_unk0x14) { + delete m_unk0x14; + } + if (m_unk0x18) { + delete m_unk0x18; + } + } + + undefined m_unk0x00[8]; // 0x00 + undefined* m_unk0x08; // 0x08 + undefined* m_unk0x0c; // 0x0c + undefined* m_unk0x10; // 0x10 + undefined* m_unk0x14; // 0x14 + undefined* m_unk0x18; // 0x18 +}; + +// VTABLE: LEGO1 0x100dd228 +// SIZE 0x24 +class LegoMesh { +public: + enum { + e_flat, + e_gouraud, + e_wireframe + }; + + LegoMesh(); + virtual ~LegoMesh(); + LegoColor GetColor() { return m_color; } + void SetColor(LegoColor p_color) { m_color = p_color; } + LegoFloat GetAlpha() { return m_alpha; } + LegoU8 GetShading() { return m_shading; } + void SetShading(LegoU8 p_shading) { m_shading = p_shading; } + LegoU8 GetUnknown0x0d() { return m_unk0x0d; } + const LegoChar* GetTextureName() { return m_textureName; } + const LegoChar* GetMaterialName() { return m_materialName; } + LegoBool GetUnknown0x21() { return m_unk0x21; } + LegoResult Read(LegoStorage* p_storage); + + // SYNTHETIC: LEGO1 0x100d3840 + // LegoMesh::`scalar deleting destructor' + +protected: + LegoColor m_color; // 0x04 + LegoFloat m_alpha; // 0x08 + LegoU8 m_shading; // 0x0c + LegoU8 m_unk0x0d; // 0x0d + LegoMeshUnkComponent* m_unk0x10; // 0x10 - unused, except in destructor + undefined4 m_unk0x14; // 0x14 - unused + LegoChar* m_textureName; // 0x18 + LegoChar* m_materialName; // 0x1c + undefined m_unk0x20; // 0x20 - unused + LegoBool m_unk0x21; // 0x21 +}; + +#endif // __LEGOMESH_H diff --git a/LEGO1/lego/sources/geom/legosphere.cpp b/LEGO1/lego/sources/geom/legosphere.cpp new file mode 100644 index 00000000..7b916326 --- /dev/null +++ b/LEGO1/lego/sources/geom/legosphere.cpp @@ -0,0 +1,19 @@ +#include "legosphere.h" + +#include "decomp.h" +#include "misc/legostorage.h" + +DECOMP_SIZE_ASSERT(LegoSphere, 0x10) + +// FUNCTION: LEGO1 0x100d3770 +LegoResult LegoSphere::Read(LegoStorage* p_storage) +{ + LegoResult result; + if ((result = m_center.Read(p_storage)) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_radius, sizeof(m_radius))) != SUCCESS) { + return result; + } + return SUCCESS; +} diff --git a/LEGO1/lego/sources/geom/legosphere.h b/LEGO1/lego/sources/geom/legosphere.h new file mode 100644 index 00000000..566713a2 --- /dev/null +++ b/LEGO1/lego/sources/geom/legosphere.h @@ -0,0 +1,21 @@ +#ifndef __LEGOSPHERE_H +#define __LEGOSPHERE_H + +#include "legovertex.h" + +// SIZE 0x10 +class LegoSphere { +public: + LegoSphere() { m_radius = 0.0F; } + LegoVertex& GetCenter() { return m_center; } + void SetCenter(LegoVertex& p_center) { m_center = p_center; } + LegoFloat GetRadius() { return m_radius; } + void SetRadius(LegoFloat p_radius) { m_radius = p_radius; } + LegoResult Read(LegoStorage* p_storage); + +protected: + LegoVertex m_center; // 0x00 + LegoFloat m_radius; // 0x0c +}; + +#endif // __LEGOSPHERE_H diff --git a/LEGO1/lego/sources/geom/legovertex.cpp b/LEGO1/lego/sources/geom/legovertex.cpp new file mode 100644 index 00000000..2977d60a --- /dev/null +++ b/LEGO1/lego/sources/geom/legovertex.cpp @@ -0,0 +1,30 @@ +#include "legovertex.h" + +#include "decomp.h" +#include "misc/legostorage.h" + +DECOMP_SIZE_ASSERT(LegoVertex, 0x0c) + +// FUNCTION: LEGO1 0x100d37b0 +LegoVertex::LegoVertex() +{ + m_coordinates[0] = 0.0F; + m_coordinates[1] = 0.0F; + m_coordinates[2] = 0.0F; +} + +// FUNCTION: LEGO1 0x100d37c0 +LegoResult LegoVertex::Read(LegoStorage* p_storage) +{ + LegoResult result; + if ((result = p_storage->Read(&m_coordinates[0], sizeof(m_coordinates[0]))) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_coordinates[1], sizeof(m_coordinates[1]))) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_coordinates[2], sizeof(m_coordinates[2]))) != SUCCESS) { + return result; + } + return SUCCESS; +} diff --git a/LEGO1/lego/sources/geom/legovertex.h b/LEGO1/lego/sources/geom/legovertex.h new file mode 100644 index 00000000..91aca80a --- /dev/null +++ b/LEGO1/lego/sources/geom/legovertex.h @@ -0,0 +1,30 @@ +#ifndef __LEGOVERTEX_H +#define __LEGOVERTEX_H + +#include "misc/legotypes.h" + +class LegoStorage; + +// SIZE 0x0c +class LegoVertex { +public: + LegoVertex(); + LegoFloat GetCoordinate(LegoU32 p_i) { return m_coordinates[p_i]; } + void SetCoordinate(LegoU32 p_i, LegoFloat p_coordinate) { m_coordinates[p_i] = p_coordinate; } + LegoFloat GetX() { return m_coordinates[0]; } + void SetX(LegoFloat p_x) { m_coordinates[0] = p_x; } + LegoFloat GetY() { return m_coordinates[1]; } + void SetY(LegoFloat p_y) { m_coordinates[1] = p_y; } + LegoFloat GetZ() { return m_coordinates[2]; } + void SetZ(LegoFloat p_z) { m_coordinates[2] = p_z; } + LegoBool IsOrigin() { return m_coordinates[0] == 0.0 && m_coordinates[1] == 0.0 && m_coordinates[2] == 0.0; } + LegoResult Read(LegoStorage* p_storage); + + LegoFloat& operator[](int i) { return m_coordinates[i]; } + LegoFloat operator[](int i) const { return m_coordinates[i]; } + +protected: + LegoFloat m_coordinates[3]; // 0x00 +}; + +#endif // __LEGOVERTEX_H diff --git a/LEGO1/lego/sources/misc/legocolor.cpp b/LEGO1/lego/sources/misc/legocolor.cpp new file mode 100644 index 00000000..1dc1dce4 --- /dev/null +++ b/LEGO1/lego/sources/misc/legocolor.cpp @@ -0,0 +1,22 @@ +#include "legocolor.h" + +#include "decomp.h" +#include "legostorage.h" + +DECOMP_SIZE_ASSERT(LegoColor, 0x03) + +// FUNCTION: LEGO1 0x100d3a20 +LegoResult LegoColor::Read(LegoStorage* p_storage) +{ + LegoResult result; + if ((result = p_storage->Read(&m_red, sizeof(m_red))) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_green, sizeof(m_green))) != SUCCESS) { + return result; + } + if ((result = p_storage->Read(&m_blue, sizeof(m_blue))) != SUCCESS) { + return result; + } + return SUCCESS; +} diff --git a/LEGO1/lego/sources/misc/legocolor.h b/LEGO1/lego/sources/misc/legocolor.h new file mode 100644 index 00000000..027362ce --- /dev/null +++ b/LEGO1/lego/sources/misc/legocolor.h @@ -0,0 +1,26 @@ +#ifndef __LEGOCOLOR_H +#define __LEGOCOLOR_H + +#include "legotypes.h" + +class LegoStorage; + +// SIZE 0x03 +class LegoColor { +public: + LegoColor() { m_red = m_green = m_blue = 0; } + LegoU8 GetRed() { return m_red; } + void SetRed(LegoU8 p_red) { m_red = p_red; } + LegoU8 GetGreen() { return m_green; } + void SetGreen(LegoU8 p_green) { m_green = p_green; } + LegoU8 GetBlue() { return m_blue; } + void SetBlue(LegoU8 p_blue) { m_blue = p_blue; } + LegoResult Read(LegoStorage* p_storage); + +protected: + LegoU8 m_red; // 0x00 + LegoU8 m_green; // 0x01 + LegoU8 m_blue; // 0x02 +}; + +#endif // __LEGOCOLOR_H diff --git a/LEGO1/omni/include/mxsmack.h b/LEGO1/omni/include/mxsmack.h new file mode 100644 index 00000000..e7fdf3ce --- /dev/null +++ b/LEGO1/omni/include/mxsmack.h @@ -0,0 +1,54 @@ +#ifndef MXSMACK_H +#define MXSMACK_H + +#include "decomp.h" +#include "mxrectlist.h" +#include "mxtypes.h" + +#include + +struct MxBITMAPINFO; + +// These functions are not part of the public interface, +// but present in SMACK.LIB and used directly by Mindscape. +extern "C" +{ + u32 SmackGetSizeTables(); + void SmackDoTables( + u8* p_huffmanTrees, + u8* p_huffmanTables, + u32 p_codeSize, + u32 p_abSize, + u32 p_detailSize, + u32 p_typeSize + ); + void SmackDoFrameToBuffer(u8* p_source, u8* p_huffmanTables, u8* p_unk0x6b4); + u32 SmackGetSizeDeltas(u32 p_width, u32 p_height); + u8 SmackGetRect(u8* p_unk0x6b4, u32* p_rect); +} + +// SIZE 0x6b8 +struct MxSmack { + SmackTag m_smackTag; // 0x00 + undefined m_unk0x390[784]; // 0x390 + MxU32* m_frameSizes; // 0x6a0 + MxU8* m_frameTypes; // 0x6a4 + MxU8* m_huffmanTrees; // 0x6a8 + MxU8* m_huffmanTables; // 0x6ac + MxU32 m_maxFrameSize; // 0x6b0 + MxU8* m_unk0x6b4; // 0x6b4 + + static MxResult LoadHeader(MxU8* p_data, MxSmack* p_mxSmack); + static void Destroy(MxSmack* p_mxSmack); + static MxResult LoadFrame( + MxBITMAPINFO* p_bitmapInfo, + MxU8* p_bitmapData, + MxSmack* p_mxSmack, + MxU8* p_chunkData, + MxBool p_paletteChanged, + MxRectList* p_list + ); + static MxBool GetRect(MxU8* p_unk0x6b4, MxU16* p_und, u32* p_smackRect, MxRect32* p_rect); +}; + +#endif // MXSMACK_H diff --git a/LEGO1/omni/include/mxstreamlist.h b/LEGO1/omni/include/mxstreamlist.h new file mode 100644 index 00000000..36a9350a --- /dev/null +++ b/LEGO1/omni/include/mxstreamlist.h @@ -0,0 +1,69 @@ +#ifndef MXSTREAMLIST_H +#define MXSTREAMLIST_H + +#include "mxdsstreamingaction.h" +#include "mxdssubscriber.h" +#include "mxnextactiondatastart.h" +#include "mxstl/stlcompat.h" + +template +class MxStreamList : public list { +public: + MxBool PopFront(T& p_obj) + { + if (this->empty()) { + return FALSE; + } + + p_obj = this->front(); + this->pop_front(); + return TRUE; + } +}; + +// SIZE 0x0c +class MxStreamListMxDSAction : public MxStreamList { +public: + // FUNCTION: BETA10 0x10150e30 + MxDSAction* FindAndErase(MxDSAction* p_action) { return FindInternal(p_action, TRUE); } + + // FUNCTION: BETA10 0x10150fc0 + MxDSAction* Find(MxDSAction* p_action) { return FindInternal(p_action, FALSE); } + + // There chance this list actually holds MxDSStreamingListAction + // instead of MxDSAction. Until then, we use this helper. + MxBool PopFrontStreamingAction(MxDSStreamingAction*& p_obj) + { + if (empty()) { + return FALSE; + } + + p_obj = (MxDSStreamingAction*) front(); + pop_front(); + return TRUE; + } + +private: + MxDSAction* FindInternal(MxDSAction* p_action, MxBool p_delete); +}; + +// SIZE 0x0c +class MxStreamListMxNextActionDataStart : public MxStreamList { +public: + MxNextActionDataStart* Find(MxU32 p_id, MxS16 p_value); + MxNextActionDataStart* FindAndErase(MxU32 p_id, MxS16 p_value); +}; + +// SIZE 0x0c +class MxStreamListMxDSSubscriber : public MxStreamList { +public: + MxDSSubscriber* Find(MxDSObject* p_object); +}; + +// TEMPLATE: BETA10 0x10150950 +// MxStreamList::PopFront + +// TEMPLATE: BETA10 0x10150a70 +// MxStreamList::PopFront + +#endif // MXSTREAMLIST_H diff --git a/LEGO1/omni/src/stream/mxdssource.cpp b/LEGO1/omni/src/stream/mxdssource.cpp new file mode 100644 index 00000000..c2d86abb --- /dev/null +++ b/LEGO1/omni/src/stream/mxdssource.cpp @@ -0,0 +1,23 @@ +#include "mxdssource.h" + +#include "mxdsbuffer.h" + +DECOMP_SIZE_ASSERT(MxDSSource, 0x14) + +// FUNCTION: LEGO1 0x100bffd0 +MxResult MxDSSource::ReadToBuffer(MxDSBuffer* p_buffer) +{ + return Read(p_buffer->GetBuffer(), p_buffer->GetWriteOffset()); +} + +// FUNCTION: LEGO1 0x100bfff0 +MxLong MxDSSource::GetLengthInDWords() +{ + return m_lengthInDWords; +} + +// FUNCTION: LEGO1 0x100c0000 +MxU32* MxDSSource::GetBuffer() +{ + return m_pBuffer; +} diff --git a/LEGO1/omni/src/stream/mxstreamlist.cpp b/LEGO1/omni/src/stream/mxstreamlist.cpp new file mode 100644 index 00000000..37e59463 --- /dev/null +++ b/LEGO1/omni/src/stream/mxstreamlist.cpp @@ -0,0 +1,83 @@ +#include "mxstreamlist.h" + +// Wrappers around STL list that are used by the MxStream* classes. +DECOMP_SIZE_ASSERT(MxStreamListMxDSAction, 0x0c); +DECOMP_SIZE_ASSERT(MxStreamListMxNextActionDataStart, 0x0c); +DECOMP_SIZE_ASSERT(MxStreamListMxDSSubscriber, 0x0c); + +// FUNCTION: LEGO1 0x100b8450 +// FUNCTION: BETA10 0x10134c1d +MxDSSubscriber* MxStreamListMxDSSubscriber::Find(MxDSObject* p_object) +{ + for (iterator it = begin(); it != end(); it++) { + if (p_object->GetObjectId() == -1 || p_object->GetObjectId() == (*it)->GetObjectId()) { + if (p_object->GetUnknown24() == -2 || p_object->GetUnknown24() == (*it)->GetUnknown48()) { + return *it; + } + } + } + + return NULL; +} + +// FUNCTION: LEGO1 0x100bfa80 +// FUNCTION: BETA10 0x10147e02 +MxDSAction* MxStreamListMxDSAction::FindInternal(MxDSAction* p_action, MxBool p_delete) +{ + // DECOMP ALPHA 0x1008b99d ? + + MxDSAction* found = NULL; + +#ifdef COMPAT_MODE + iterator it; + for (it = begin(); it != end(); it++) { +#else + for (iterator it = begin(); it != end(); it++) { +#endif + if (p_action->GetObjectId() == -1 || p_action->GetObjectId() == (*it)->GetObjectId()) { + if (p_action->GetUnknown24() == -2 || p_action->GetUnknown24() == -3 || + p_action->GetUnknown24() == (*it)->GetUnknown24()) { + found = *it; + if (p_action->GetUnknown24() != -3) { + break; + } + } + } + } + + if (p_delete && found != NULL) { + erase(it); + } + + return found; +} + +// FUNCTION: LEGO1 0x100c21e0 +// FUNCTION: BETA10 0x1014f4e6 +MxNextActionDataStart* MxStreamListMxNextActionDataStart::Find(MxU32 p_id, MxS16 p_value) +{ + for (iterator it = begin(); it != end(); it++) { + if (p_id == (*it)->GetObjectId() && p_value == (*it)->GetUnknown24()) { + return *it; + } + } + + return NULL; +} + +// FUNCTION: LEGO1 0x100c2240 +// FUNCTION: BETA10 0x1014f58c +MxNextActionDataStart* MxStreamListMxNextActionDataStart::FindAndErase(MxU32 p_id, MxS16 p_value) +{ + MxNextActionDataStart* match = NULL; + + for (iterator it = begin(); it != end(); it++) { + if (p_id == (*it)->GetObjectId() && (p_value == -2 || p_value == (*it)->GetUnknown24())) { + match = *it; + erase(it); + break; + } + } + + return match; +} diff --git a/LEGO1/omni/src/stream/mxstreamprovider.cpp b/LEGO1/omni/src/stream/mxstreamprovider.cpp new file mode 100644 index 00000000..740b2c65 --- /dev/null +++ b/LEGO1/omni/src/stream/mxstreamprovider.cpp @@ -0,0 +1,17 @@ +#include "mxstreamprovider.h" + +#include "decomp.h" + +DECOMP_SIZE_ASSERT(MxStreamProvider, 0x10); + +// FUNCTION: LEGO1 0x100d07c0 +MxResult MxStreamProvider::SetResourceToGet(MxStreamController* p_resource) +{ + m_pLookup = p_resource; + return SUCCESS; +} + +// FUNCTION: LEGO1 0x100d07d0 +void MxStreamProvider::VTable0x20(MxDSAction* p_action) +{ +} diff --git a/LEGO1/omni/src/video/mxsmack.cpp b/LEGO1/omni/src/video/mxsmack.cpp new file mode 100644 index 00000000..d0a9480c --- /dev/null +++ b/LEGO1/omni/src/video/mxsmack.cpp @@ -0,0 +1,270 @@ +#include "mxsmack.h" + +#include "mxbitmap.h" + +#include + +DECOMP_SIZE_ASSERT(SmackTag, 0x390); +DECOMP_SIZE_ASSERT(MxSmack, 0x6b8); + +// FUNCTION: LEGO1 0x100c5a90 +// FUNCTION: BETA10 0x10151e70 +MxResult MxSmack::LoadHeader(MxU8* p_data, MxSmack* p_mxSmack) +{ +// Macros for readability +// If bit0 of SmackerType is set, there is an extra frame ("ring frame") +// at the end. It is a duplicate of the first frame to simplify looping. +#define FRAME_COUNT(_tag) (_tag->Frames + (_tag->SmackerType & 1)) + + MxResult result = SUCCESS; + MxU32* frameSizes = NULL; + MxU8* frameTypes = NULL; + MxU8* huffmanTrees = NULL; + MxU32 sizetables = 0; + + // Forced to declare here because of the gotos. + MxU32 i; + MxU32 treeSize; + MxU32* data; + MxU32 size; + MxS32 width; + + if (!p_data || !p_mxSmack) { + return FAILURE; + } + + SmackTag* smackTag = &p_mxSmack->m_smackTag; + p_mxSmack->m_frameTypes = NULL; + p_mxSmack->m_frameSizes = NULL; + p_mxSmack->m_huffmanTrees = NULL; + p_mxSmack->m_huffmanTables = NULL; + + memcpy(smackTag, p_data, SmackHeaderSize(smackTag)); + p_data += SmackHeaderSize(smackTag); + + frameSizes = new MxU32[FRAME_COUNT(smackTag)]; + + if (!frameSizes) { + result = FAILURE; + goto done; + } + + memcpy(frameSizes, p_data, FRAME_COUNT(smackTag) * sizeof(MxU32)); + + p_data += FRAME_COUNT(smackTag) * sizeof(MxU32); + p_mxSmack->m_maxFrameSize = 0; + + for (i = 0; i < FRAME_COUNT(smackTag); i++) { + if (p_mxSmack->m_maxFrameSize < frameSizes[i]) { + p_mxSmack->m_maxFrameSize = frameSizes[i]; + } + } + + frameTypes = new MxU8[FRAME_COUNT(smackTag)]; + + if (!frameTypes) { + result = FAILURE; + goto done; + } + + memcpy(frameTypes, p_data, FRAME_COUNT(smackTag)); + p_data += FRAME_COUNT(smackTag); + + treeSize = smackTag->tablesize + 0x1000; + huffmanTrees = new MxU8[treeSize <= 0x2000 ? 0x2000 : treeSize]; + + if (!huffmanTrees) { + result = FAILURE; + goto done; + } + + memcpy(huffmanTrees + 0x1000, p_data, smackTag->tablesize); + p_data += smackTag->tablesize; + + sizetables = SmackGetSizeTables(); + p_mxSmack->m_huffmanTables = + new MxU8[smackTag->codesize + smackTag->detailsize + smackTag->typesize + smackTag->absize + sizetables]; + + if (!p_mxSmack->m_huffmanTables) { + result = FAILURE; + goto done; + } + + SmackDoTables( + huffmanTrees, + p_mxSmack->m_huffmanTables, + smackTag->codesize, + smackTag->absize, + smackTag->detailsize, + smackTag->typesize + ); + + size = SmackGetSizeDeltas(smackTag->Width, smackTag->Height) + 32; + p_mxSmack->m_unk0x6b4 = new MxU8[size]; + memset(p_mxSmack->m_unk0x6b4, 0, size); + + width = p_mxSmack->m_smackTag.Width; + data = (MxU32*) p_mxSmack->m_unk0x6b4; + + *data = 1; + data++; + *data = NULL; // MxU8* bitmapData + data++; + *data = smackTag->Width / 4; + data++; + *data = smackTag->Height / 4; + data++; + *data = width - 4; + data++; + *data = width * 3; + data++; + *data = width; + data++; + *data = width * 3 + (width - smackTag->Width); + data++; + data++; + *data = smackTag->Width; + data++; + *data = smackTag->Height; + +done: + p_mxSmack->m_frameTypes = frameTypes; + p_mxSmack->m_frameSizes = frameSizes; + p_mxSmack->m_huffmanTrees = huffmanTrees; + return result; + +#undef FRAME_COUNT +} + +// FUNCTION: LEGO1 0x100c5d40 +// FUNCTION: BETA10 0x10152298 +void MxSmack::Destroy(MxSmack* p_mxSmack) +{ + if (p_mxSmack->m_frameSizes) { + delete[] p_mxSmack->m_frameSizes; + } + if (p_mxSmack->m_frameTypes) { + delete[] p_mxSmack->m_frameTypes; + } + if (p_mxSmack->m_huffmanTrees) { + delete[] p_mxSmack->m_huffmanTrees; + } + if (p_mxSmack->m_huffmanTables) { + delete[] p_mxSmack->m_huffmanTables; + } + if (p_mxSmack->m_unk0x6b4) { + delete[] p_mxSmack->m_unk0x6b4; + } +} + +// FUNCTION: LEGO1 0x100c5db0 +// FUNCTION: BETA10 0x10152391 +MxResult MxSmack::LoadFrame( + MxBITMAPINFO* p_bitmapInfo, + MxU8* p_bitmapData, + MxSmack* p_mxSmack, + MxU8* p_chunkData, + MxBool p_paletteChanged, + MxRectList* p_list +) +{ + p_bitmapInfo->m_bmiHeader.biHeight = -MxBitmap::HeightAbs(p_bitmapInfo->m_bmiHeader.biHeight); + *(MxU8**) (p_mxSmack->m_unk0x6b4 + 4) = p_bitmapData; + + // Reference: https://wiki.multimedia.cx/index.php/Smacker#Palette_Chunk + if (p_paletteChanged) { + MxU8 palette[772]; + + MxU8* intoChunk = p_chunkData + 1; + MxU8* intoPalette = palette; + MxU16 paletteIndex = 0; + // TODO: struct incorrect, Palette at wrong offset? + MxU8* currentPalette = &p_mxSmack->m_smackTag.Palette[4]; + + do { + if (*intoChunk & 0x80) { + MxU8 length = (*intoChunk & 0x7f) + 1; + memcpy(intoPalette, ¤tPalette[paletteIndex * 3], length * 3); + intoPalette += length * 3; + paletteIndex += length; + intoChunk++; + } + else { + if (*intoChunk & 0x40) { + MxU8 length = (*intoChunk & 0x3f) + 1; + memcpy(intoPalette, ¤tPalette[*(intoChunk + 1) * 3], length * 3); + intoPalette += length * 3; + paletteIndex += length; + intoChunk += 2; + } + else { + *(MxU32*) intoPalette = *(MxU32*) intoChunk; + intoPalette += 3; + paletteIndex++; + intoChunk += 3; + } + } + } while (paletteIndex < 256); + + for (MxU32 i = 0; i < 256; i++) { + memcpy(currentPalette, &palette[i * 3], 3); + currentPalette += 3; + p_bitmapInfo->m_bmiColors[i].rgbBlue = palette[i * 3 + 2] * 4; + p_bitmapInfo->m_bmiColors[i].rgbGreen = palette[i * 3 + 1] * 4; + p_bitmapInfo->m_bmiColors[i].rgbRed = palette[i * 3] * 4; + } + + p_chunkData += *p_chunkData * 4; + } + + SmackDoFrameToBuffer(p_chunkData, p_mxSmack->m_huffmanTables, p_mxSmack->m_unk0x6b4); + + MxU16 und = 1; + u32 smackRect[4]; + MxRect32 rect; + + while (GetRect(p_mxSmack->m_unk0x6b4, &und, smackRect, &rect)) { + MxRect32* newRect = new MxRect32(rect); + p_list->Append(newRect); + } + + return SUCCESS; +} + +// FUNCTION: LEGO1 0x100c6050 +// FUNCTION: BETA10 0x10152739 +MxBool MxSmack::GetRect(MxU8* p_unk0x6b4, MxU16* p_und, u32* p_smackRect, MxRect32* p_rect) +{ + u32 left, bottom, top, right; + + if (!*p_und) { + return FALSE; + } + + if (*p_und == 1) { + if (!SmackGetRect(p_unk0x6b4, p_smackRect)) { + return FALSE; + } + *p_und = 2; + } + + left = p_smackRect[0]; + top = p_smackRect[1]; + right = p_smackRect[2] + p_smackRect[0]; + bottom = p_smackRect[3] + p_smackRect[1]; + + while (SmackGetRect(p_unk0x6b4, p_smackRect)) { + if (left > p_smackRect[0]) { + left = p_smackRect[0]; + } + if (right < p_smackRect[0] + p_smackRect[2]) { + right = p_smackRect[0] + p_smackRect[2]; + } + + bottom = p_smackRect[1] + p_smackRect[3]; + } + + *p_und = 0; + *p_rect = MxRect32(left, top, right, bottom); + return TRUE; +} diff --git a/LEGO1/realtime/matrix.cpp b/LEGO1/realtime/matrix.cpp new file mode 100644 index 00000000..c1de1535 --- /dev/null +++ b/LEGO1/realtime/matrix.cpp @@ -0,0 +1,5 @@ +#include "matrix.h" + +#include "decomp.h" + +DECOMP_SIZE_ASSERT(Matrix4, 0x08); diff --git a/LEGO1/realtime/vector.cpp b/LEGO1/realtime/vector.cpp new file mode 100644 index 00000000..ad09b0b0 --- /dev/null +++ b/LEGO1/realtime/vector.cpp @@ -0,0 +1,7 @@ +#include "vector.h" + +#include "decomp.h" + +DECOMP_SIZE_ASSERT(Vector2, 0x08); +DECOMP_SIZE_ASSERT(Vector3, 0x08); +DECOMP_SIZE_ASSERT(Vector4, 0x08); diff --git a/LEGO1/res/lego1.rc b/LEGO1/res/lego1.rc index 44e07d7c..50a707dc 100644 --- a/LEGO1/res/lego1.rc +++ b/LEGO1/res/lego1.rc @@ -12,8 +12,8 @@ LEGO1_ARROW CURSOR "arrow.cur" LEGO1_BUSY CURSOR "busy.cur" 1 VERSIONINFO -FILEVERSION 1,1,0,0 -PRODUCTVERSION 1,1,0,0 +FILEVERSION 0,0,0,1 +PRODUCTVERSION 0,0,0,1 FILEOS 0x40004 FILETYPE 0x2 { @@ -21,14 +21,14 @@ BLOCK "StringFileInfo" { BLOCK "040904b0" { - VALUE "CompanyName", "Mindscape, Inc." - VALUE "FileDescription", "LegoOmni Library" - VALUE "FileVersion", "1, 1, 0, 0" - VALUE "InternalName", "LegoOmni Library" - VALUE "LegalCopyright", "Copyright \xA9 1997" - VALUE "OriginalFilename", "LegoOmni Library" - VALUE "ProductName", "LegoOmni Library" - VALUE "ProductVersion", "1, 1, 0, 0" + VALUE "CompanyName", "FUNNIMAN.EXE" + VALUE "FileDescription", "b_TMAOmni Library" + VALUE "FileVersion", "0, 0, 0, 1" + VALUE "InternalName", "b_TMAOmni Library" + VALUE "LegalCopyright", "Copyright \xA9 2025" + VALUE "OriginalFilename", "B_TMA.DLL" + VALUE "ProductName", "LEGO Island: The Modder's Arrival" + VALUE "ProductVersion", "0, 0, 0, 1" } } diff --git a/LEGO1/res/lego1_beta.rc b/LEGO1/res/lego1_beta.rc new file mode 100644 index 00000000..9b69f61c --- /dev/null +++ b/LEGO1/res/lego1_beta.rc @@ -0,0 +1,39 @@ +#include "resource.h" + +LEGO1_LEAF4 CURSOR "leaf_4.cur" +LEGO1_LEAF7 CURSOR "leaf_7.cur" +LEGO1_LEAF1 CURSOR "leaf_1.cur" +LEGO1_LEAF2 CURSOR "leaf_2.cur" +LEGO1_LEAF6 CURSOR "leaf_6.cur" +LEGO1_LEAF0 CURSOR "leaf_0.cur" +LEGO1_LEAF5 CURSOR "leaf_5.cur" +LEGO1_LEAF3 CURSOR "leaf_3.cur" +LEGO1_ARROW CURSOR "arrow.cur" +LEGO1_BUSY CURSOR "busy.cur" + +1 VERSIONINFO +FILEVERSION 0,0,0,1 +PRODUCTVERSION 0,0,0,1 +FILEOS 0x40004 +FILETYPE 0x2 +{ +BLOCK "StringFileInfo" +{ + BLOCK "040904b0" + { + VALUE "CompanyName", "FUNNIMAN.EXE" + VALUE "FileDescription", "x10_TMAOmni Library" + VALUE "FileVersion", "0, 0, 0, 1" + VALUE "InternalName", "x10_TMAOmni Library" + VALUE "LegalCopyright", "Copyright \xA9 2025" + VALUE "OriginalFilename", "X10_TMA.DLL" + VALUE "ProductName", "LEGO Island: The Modder's Arrival" + VALUE "ProductVersion", "0, 0, 0, 1" + } +} + +BLOCK "VarFileInfo" +{ + VALUE "Translation", 0x0409, 0x04B0 +} +} diff --git a/README.md b/README.md index c24dc4ef..55914345 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,6 @@ -# LEGO Island Decompilation +# LEGO Island: The Modder's Arrival -[Development Vlog](https://www.youtube.com/playlist?list=PLbpl-gZkNl2COf_bB6cfgTapD5WduAfPz) | [Contributing](/CONTRIBUTING.md) | [Matrix](https://matrix.to/#/#isledecomp:matrix.org) | [Forums](https://forum.mattkc.com/viewforum.php?f=1) | [Patreon](https://www.patreon.com/mattkc) - -This is a functionally complete decompilation of LEGO Island (Version 1.1, English). It aims to be as accurate as possible, matching the recompiled instructions to the original machine code as much as possible. The goal is to provide a workable codebase that can be modified, improved, and ported to other platforms later on. - -## Status - - - -Both `ISLE.EXE` and `LEGO1.DLL` are completely decompiled and, to the best of our knowledge, are functionally identical to the originals. However, work is still ongoing to improve the accuracy, naming, documentation, and structure of the source code. While there may still be unresolved bugs that are not present in retail, the game should be fully playable with the binaries derived from this source code. - -Due to various complexities with regard to the compiler, these binaries are not a byte-for-byte match of the original executables. We remain hopeful that this can be resolved at some point. +## [Original LEGO Island decomp](https://github.com/isledecomp/isle) ## Building @@ -30,18 +20,14 @@ You will need the following software installed: 1. Open a Command Prompt (`cmd`). 1. From Visual C++ 4.2, run `BIN/VCVARS32.BAT x86` to populate the path and other environment variables for compiling with MSVC. -1. Make a folder for compiled objects to go, such as a `build` folder inside the source repository (the folder you cloned/downloaded to). -1. In your Command Prompt, `cd` to the build folder. -1. Configure the project with CMake by running: -``` -cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo -``` +1. Run "reconfigureCMake.bat" to configure CMake for building + - **Visual C++ 4.2 has issues with paths containing spaces**. If you get configure or build errors, make sure neither CMake, the repository, nor Visual C++ 4.2 is in a path that contains spaces. - Replace `` with the source repository. This can be `..` if your build folder is inside the source repository. - `RelWithDebInfo` is recommended because it will produce debug symbols useful for further decompilation work. However, you can change this to `Release` if you don't need them. While `Debug` builds can be compiled and used, they are not recommended as the primary goal is to match the code to the original binary. This is because the retail binaries were compiled as `Release` builds. - `NMake Makefiles` is most recommended because it will be immediately compatible with Visual C++ 4.2. For faster builds, you can use `Ninja` (if you have it installed), however due to limitations in Visual C++ 4.2, you can only build `Release` builds this way (debug symbols cannot be generated with `Ninja`). -1. Build the project by running `nmake` or `cmake --build ` -1. When this is done, there should be a recompiled `ISLE.EXE` and `LEGO1.DLL` in the build folder. +1. Build the project by running `nmake` or `cmake --build ` or by running "compile.bat" +1. When this is done, there should be a compiled `TMA_LAUNCHER.EXE` and `B_TMA.DLL` in the build folder. 1. Note that `nmake` must be run twice under certain conditions, so it is advisable to always (re-)compile using `nmake && nmake`. If you have a CMake-compatible IDE, it should be pretty straightforward to use this repository, as long as you can use `VCVARS32.BAT` and set the generator to `NMake Makefiles`. @@ -67,24 +53,19 @@ You can pass as many CMake flags as you'd like in the `CMAKE_FLAGS` environment ## Usage -The simplest way to use the recompiled binaries is to swap the original executables (`ISLE.EXE`, `LEGO1.DLL`, and `CONFIG.EXE`) in LEGO Island's installation directory for the ones that you've built from this source code. By default, LEGO Island is installed to `C:\Program Files\LEGO Island` on 32-bit operating systems and `C:\Program Files (x86)\LEGO Island` on 64-bit operating systems. +In order to run TMA, you must first configure the `diskpath` environment variable (`usage of cdpath has been disabled entirely`). This is necessary since, currently, nothing creates this environment variable, or populates it. -For advanced users, you can get LEGO Island to run from anywhere as long as `ISLE.EXE` and `LEGO1.DLL` are in the same directory and the `cdpath` and `diskpath` registry keys (usually found in `HKEY_LOCAL_MACHINE\Software\Mindscape\LEGO Island` on 32-bit operating systems and `HKEY_LOCAL_MACHINE\Software\Wow6432Node\Mindscape\LEGO Island` on 64-bit operating systems) point to the correct location for the asset files (the directory that contains the `LEGO` folder). +The registry keys can be created in (usually found in `HKEY_LOCAL_MACHINE\Software\ActionSoft\LEGO Island TMA` on 32-bit operating systems and `HKEY_LOCAL_MACHINE\Software\Wow6432Node\ActionSoft\LEGO Island TMA` on 64-bit operating systems), and should point to the location where the `TMA_LAUNCHER.EXE`, `B_TMA.DLL`, and LEGO files/folders are stored (by default, this is the build folder). As long as you -If you see an error about `d3drm.dll`, you will need to acquire a copy and place it in the same directory as the game executables, as it has not shipped with Windows since Windows XP. We have published a [known good copy here](https://legoisland.org/download/d3drm.zip) that works with LEGO Island. +The build folder contains a copy of `d3drm.dll`, `D3D8.dll`, `D3DImm.dll`, `DDraw.dll`, and `dgVoodoo.conf` files, DO NOT REMOVE ANY OF THEM. -## Contributing +NOTE: Due to Github size limitations, and the fact this is a repo just for me to keep mirrored across devices, this repo does not include anything within the Lego folder. For the time being, access to the Lego folder cannot be requested, but may be able to in the future. -If you're interested in helping or contributing to this project, check out the [CONTRIBUTING](/CONTRIBUTING.md) page. +### What does what? -## Additional Information +`TMA_LAUNCHER.EXE` - The wrapper for `B_TMA.DLL`, launches the game - the equivalant to `ISLE.EXE` -### Which version of LEGO Island do I have? +`B_TMA.DLL` - The primary dll, containing all game logic - the equivalant to `LEGO1.DLL` -Right click on `LEGO1.DLL`, select `Properties`, and switch to the `Details` tab. Under `Version` you should either see `1.0.0.0` (1.0) or `1.1.0.0` (1.1). Additionally, you can look at the game disc files; 1.0's files will all say August 8, 1997, and 1.1's files will all say September 8, 1997. Version 1.1 is by far the most common, especially if you're not using the English or Japanese versions, so that's most likely the version you have. - -Please note that some localized versions of LEGO Island were recompiled with small changes despite maintaining a version number parallel with other versions; this decompilation specifically targets the English release of version 1.1 of LEGO Island. You can verify you have the correct version using the checksums below: - -* ISLE.EXE `md5: f6da12249e03eed1c74810cd23beb9f5` -* LEGO1.DLL `md5: 4e2f6d969ea2ef8655ba3fc221a0c8fe` -* CONFIG.EXE `md5: 92d958a64a273662c591c88b09100f4a` +`CONFIGURE.EXE` - Primary configuration application, contains some options - the equivalant to `CONFIG.EXE` + For more advanced configurations, you will need to acquire `dgVoodooSetup.exe`, and place it in the same folder as `dgVoodoo.conf` \ No newline at end of file diff --git a/build/D3D8.dll b/build/D3D8.dll new file mode 100644 index 00000000..6036139c Binary files /dev/null and b/build/D3D8.dll differ diff --git a/build/D3DImm.dll b/build/D3DImm.dll new file mode 100644 index 00000000..48648fe0 Binary files /dev/null and b/build/D3DImm.dll differ diff --git a/build/DDraw.dll b/build/DDraw.dll new file mode 100644 index 00000000..898fd997 Binary files /dev/null and b/build/DDraw.dll differ diff --git a/build/README.txt b/build/README.txt new file mode 100644 index 00000000..05776063 --- /dev/null +++ b/build/README.txt @@ -0,0 +1,38 @@ +Welcome to LEGO Island: The Modder's Arrival, the first (as far as I am aware) Mod for LEGO Island! + +This mod was made using MattKC's LEGO Island Decompilation. + +--BEFORE PLAYING - INTRO-- + +Before playing, please create the diskpath registry key. + +--BEFORE PLAYING - REGISTRY-- + +The diskpath registry key should be created in "HKEY_LOCAL_MACHINE\SOFTWARE\ActionSoft\LEGO Island TMA" or "HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\ActionSoft\LEGO Island TMA", and should contain the working directory of the TMA_LAUNCHER application. + +--BEFORE PLAYING - CONFIGURATION-- + +Please run the configuration application at least once before running the game for the first time. + +NOTICE: Because this mod uses dgVoodoo, Flip Video Memory Pages must be enabled when running the game in fullscreen. Otherwise, you will only see a black screen. + +--EXTRA - CONFIGURATION-- + +This Mod uses dgVoodoo to fix some framerate issues and upscale the game window among other fixes. To change any settings, simply open the dgVoodooSetup application and click the '.\' button. If you do not press the '.\' button, dgVoodoo will not register your changes. + +--FREQUENTLY ASKED QUESTIONS-- + +none yet :sadface: + +--KNOWN ISSUES-- +"Out of Memory" error when loading save file - Corrupt save file caused by attempting to enter build segment with incomplete code (originaly caused crash on score cube screen). + +Game stuck in full-screen even with full-screen unchecked - caused by running the application as administrator. | OR | caused by full-screen being unchecked in dgVoodoo config, please use the mod's configuration application for this. + +No bitmaps or animated videos in windowed mode - enable "reduced colour mode" in compatibility settings + +Game lagging in windowed mode - no fix currently + +Weird refresh issues in windowed mode - no fix currently + +Thanks, and enjoy! \ No newline at end of file diff --git a/build/compile.bat b/build/compile.bat new file mode 100644 index 00000000..1e567fe4 --- /dev/null +++ b/build/compile.bat @@ -0,0 +1,5 @@ +@echo off +nmake +echo Compolation complete! Check above for errors. +echo Press any key to quit. +pause > NUL \ No newline at end of file diff --git a/build/compile_commands.json b/build/compile_commands.json new file mode 100644 index 00000000..e14c57e9 --- /dev/null +++ b/build/compile_commands.json @@ -0,0 +1,1448 @@ +[ +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\tglrl.dir\\LEGO1\\tgl\\d3drm\\renderer.cpp.obj /FdCMakeFiles\\tglrl.dir\\tglrl.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\tgl\\d3drm\\renderer.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/tgl/d3drm/renderer.cpp", + "output": "CMakeFiles/tglrl.dir/LEGO1/tgl/d3drm/renderer.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\tglrl.dir\\LEGO1\\tgl\\d3drm\\texture.cpp.obj /FdCMakeFiles\\tglrl.dir\\tglrl.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\tgl\\d3drm\\texture.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/tgl/d3drm/texture.cpp", + "output": "CMakeFiles/tglrl.dir/LEGO1/tgl/d3drm/texture.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\tglrl.dir\\LEGO1\\tgl\\d3drm\\view.cpp.obj /FdCMakeFiles\\tglrl.dir\\tglrl.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\tgl\\d3drm\\view.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/tgl/d3drm/view.cpp", + "output": "CMakeFiles/tglrl.dir/LEGO1/tgl/d3drm/view.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\tglrl.dir\\LEGO1\\tgl\\d3drm\\device.cpp.obj /FdCMakeFiles\\tglrl.dir\\tglrl.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\tgl\\d3drm\\device.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/tgl/d3drm/device.cpp", + "output": "CMakeFiles/tglrl.dir/LEGO1/tgl/d3drm/device.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\tglrl.dir\\LEGO1\\tgl\\d3drm\\group.cpp.obj /FdCMakeFiles\\tglrl.dir\\tglrl.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\tgl\\d3drm\\group.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/tgl/d3drm/group.cpp", + "output": "CMakeFiles/tglrl.dir/LEGO1/tgl/d3drm/group.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\tglrl.dir\\LEGO1\\tgl\\d3drm\\camera.cpp.obj /FdCMakeFiles\\tglrl.dir\\tglrl.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\tgl\\d3drm\\camera.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/tgl/d3drm/camera.cpp", + "output": "CMakeFiles/tglrl.dir/LEGO1/tgl/d3drm/camera.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\tglrl.dir\\LEGO1\\tgl\\d3drm\\light.cpp.obj /FdCMakeFiles\\tglrl.dir\\tglrl.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\tgl\\d3drm\\light.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/tgl/d3drm/light.cpp", + "output": "CMakeFiles/tglrl.dir/LEGO1/tgl/d3drm/light.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\tglrl.dir\\LEGO1\\tgl\\d3drm\\meshbuilder.cpp.obj /FdCMakeFiles\\tglrl.dir\\tglrl.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\tgl\\d3drm\\meshbuilder.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/tgl/d3drm/meshbuilder.cpp", + "output": "CMakeFiles/tglrl.dir/LEGO1/tgl/d3drm/meshbuilder.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\tglrl.dir\\LEGO1\\tgl\\d3drm\\mesh.cpp.obj /FdCMakeFiles\\tglrl.dir\\tglrl.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\tgl\\d3drm\\mesh.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/tgl/d3drm/mesh.cpp", + "output": "CMakeFiles/tglrl.dir/LEGO1/tgl/d3drm/mesh.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\realtime.dir\\LEGO1\\realtime\\orientableroi.cpp.obj /FdCMakeFiles\\realtime.dir\\realtime.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\realtime\\orientableroi.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/realtime/orientableroi.cpp", + "output": "CMakeFiles/realtime.dir/LEGO1/realtime/orientableroi.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\realtime.dir\\LEGO1\\realtime\\realtime.cpp.obj /FdCMakeFiles\\realtime.dir\\realtime.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\realtime\\realtime.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/realtime/realtime.cpp", + "output": "CMakeFiles/realtime.dir/LEGO1/realtime/realtime.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\realtime.dir\\LEGO1\\realtime\\realtimeview.cpp.obj /FdCMakeFiles\\realtime.dir\\realtime.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\realtime\\realtimeview.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/realtime/realtimeview.cpp", + "output": "CMakeFiles/realtime.dir/LEGO1/realtime/realtimeview.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\viewmanager.dir\\LEGO1\\viewmanager\\viewlod.cpp.obj /FdCMakeFiles\\viewmanager.dir\\viewmanager.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\viewmanager\\viewlod.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/viewmanager/viewlod.cpp", + "output": "CMakeFiles/viewmanager.dir/LEGO1/viewmanager/viewlod.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\viewmanager.dir\\LEGO1\\viewmanager\\viewmanager.cpp.obj /FdCMakeFiles\\viewmanager.dir\\viewmanager.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\viewmanager\\viewmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/viewmanager/viewmanager.cpp", + "output": "CMakeFiles/viewmanager.dir/LEGO1/viewmanager/viewmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\viewmanager.dir\\LEGO1\\viewmanager\\viewlodlist.cpp.obj /FdCMakeFiles\\viewmanager.dir\\viewmanager.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\viewmanager\\viewlodlist.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/viewmanager/viewlodlist.cpp", + "output": "CMakeFiles/viewmanager.dir/LEGO1/viewmanager/viewlodlist.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\viewmanager.dir\\LEGO1\\viewmanager\\viewroi.cpp.obj /FdCMakeFiles\\viewmanager.dir\\viewmanager.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\viewmanager\\viewroi.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/viewmanager/viewroi.cpp", + "output": "CMakeFiles/viewmanager.dir/LEGO1/viewmanager/viewroi.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\mxdirectx.dir\\LEGO1\\mxdirectx\\mxdirect3d.cpp.obj /FdCMakeFiles\\mxdirectx.dir\\mxdirectx.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\mxdirectx\\mxdirect3d.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/mxdirectx/mxdirect3d.cpp", + "output": "CMakeFiles/mxdirectx.dir/LEGO1/mxdirectx/mxdirect3d.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\mxdirectx.dir\\LEGO1\\mxdirectx\\mxdirectxinfo.cpp.obj /FdCMakeFiles\\mxdirectx.dir\\mxdirectx.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\mxdirectx\\mxdirectxinfo.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/mxdirectx/mxdirectxinfo.cpp", + "output": "CMakeFiles/mxdirectx.dir/LEGO1/mxdirectx/mxdirectxinfo.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\mxdirectx.dir\\LEGO1\\mxdirectx\\legodxinfo.cpp.obj /FdCMakeFiles\\mxdirectx.dir\\mxdirectx.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\mxdirectx\\legodxinfo.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/mxdirectx/legodxinfo.cpp", + "output": "CMakeFiles/mxdirectx.dir/LEGO1/mxdirectx/legodxinfo.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\mxdirectx.dir\\LEGO1\\mxdirectx\\mxdirectdraw.cpp.obj /FdCMakeFiles\\mxdirectx.dir\\mxdirectx.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\mxdirectx\\mxdirectdraw.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/mxdirectx/mxdirectdraw.cpp", + "output": "CMakeFiles/mxdirectx.dir/LEGO1/mxdirectx/mxdirectdraw.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\roi.dir\\LEGO1\\lego\\sources\\roi\\legoroi.cpp.obj /FdCMakeFiles\\roi.dir\\roi.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\roi\\legoroi.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/roi/legoroi.cpp", + "output": "CMakeFiles/roi.dir/LEGO1/lego/sources/roi/legoroi.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\roi.dir\\LEGO1\\lego\\sources\\roi\\legolod.cpp.obj /FdCMakeFiles\\roi.dir\\roi.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\roi\\legolod.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/roi/legolod.cpp", + "output": "CMakeFiles/roi.dir/LEGO1/lego/sources/roi/legolod.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\geom.dir\\LEGO1\\lego\\sources\\geom\\legoedge.cpp.obj /FdCMakeFiles\\geom.dir\\geom.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\geom\\legoedge.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/geom/legoedge.cpp", + "output": "CMakeFiles/geom.dir/LEGO1/lego/sources/geom/legoedge.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\geom.dir\\LEGO1\\lego\\sources\\geom\\legoweedge.cpp.obj /FdCMakeFiles\\geom.dir\\geom.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\geom\\legoweedge.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/geom/legoweedge.cpp", + "output": "CMakeFiles/geom.dir/LEGO1/lego/sources/geom/legoweedge.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\geom.dir\\LEGO1\\lego\\sources\\geom\\legounkown100db7f4.cpp.obj /FdCMakeFiles\\geom.dir\\geom.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\geom\\legounkown100db7f4.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/geom/legounkown100db7f4.cpp", + "output": "CMakeFiles/geom.dir/LEGO1/lego/sources/geom/legounkown100db7f4.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\geom.dir\\LEGO1\\lego\\sources\\geom\\legowegedge.cpp.obj /FdCMakeFiles\\geom.dir\\geom.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\geom\\legowegedge.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/geom/legowegedge.cpp", + "output": "CMakeFiles/geom.dir/LEGO1/lego/sources/geom/legowegedge.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\shape.dir\\LEGO1\\lego\\sources\\shape\\legobox.cpp.obj /FdCMakeFiles\\shape.dir\\shape.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\shape\\legobox.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/shape/legobox.cpp", + "output": "CMakeFiles/shape.dir/LEGO1/lego/sources/shape/legobox.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\shape.dir\\LEGO1\\lego\\sources\\shape\\legosphere.cpp.obj /FdCMakeFiles\\shape.dir\\shape.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\shape\\legosphere.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/shape/legosphere.cpp", + "output": "CMakeFiles/shape.dir/LEGO1/lego/sources/shape/legosphere.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\shape.dir\\LEGO1\\lego\\sources\\shape\\legovertex.cpp.obj /FdCMakeFiles\\shape.dir\\shape.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\shape\\legovertex.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/shape/legovertex.cpp", + "output": "CMakeFiles/shape.dir/LEGO1/lego/sources/shape/legovertex.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\shape.dir\\LEGO1\\lego\\sources\\shape\\legomesh.cpp.obj /FdCMakeFiles\\shape.dir\\shape.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\shape\\legomesh.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/shape/legomesh.cpp", + "output": "CMakeFiles/shape.dir/LEGO1/lego/sources/shape/legomesh.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\shape.dir\\LEGO1\\lego\\sources\\shape\\legocolor.cpp.obj /FdCMakeFiles\\shape.dir\\shape.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\shape\\legocolor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/shape/legocolor.cpp", + "output": "CMakeFiles/shape.dir/LEGO1/lego/sources/shape/legocolor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\anim.dir\\LEGO1\\lego\\sources\\anim\\legoanim.cpp.obj /FdCMakeFiles\\anim.dir\\anim.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\anim\\legoanim.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/anim/legoanim.cpp", + "output": "CMakeFiles/anim.dir/LEGO1/lego/sources/anim/legoanim.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\misc.dir\\LEGO1\\lego\\sources\\misc\\legotexture.cpp.obj /FdCMakeFiles\\misc.dir\\misc.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\misc\\legotexture.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/misc/legotexture.cpp", + "output": "CMakeFiles/misc.dir/LEGO1/lego/sources/misc/legotexture.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\misc.dir\\LEGO1\\lego\\sources\\misc\\legostorage.cpp.obj /FdCMakeFiles\\misc.dir\\misc.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\misc\\legostorage.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/misc/legostorage.cpp", + "output": "CMakeFiles/misc.dir/LEGO1/lego/sources/misc/legostorage.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\misc.dir\\LEGO1\\lego\\sources\\misc\\legoimage.cpp.obj /FdCMakeFiles\\misc.dir\\misc.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\misc\\legoimage.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/misc/legoimage.cpp", + "output": "CMakeFiles/misc.dir/LEGO1/lego/sources/misc/legoimage.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\misc.dir\\LEGO1\\lego\\sources\\misc\\legocontainer.cpp.obj /FdCMakeFiles\\misc.dir\\misc.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\misc\\legocontainer.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/misc/legocontainer.cpp", + "output": "CMakeFiles/misc.dir/LEGO1/lego/sources/misc/legocontainer.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\misc.dir\\LEGO1\\lego\\sources\\misc\\legotree.cpp.obj /FdCMakeFiles\\misc.dir\\misc.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\misc\\legotree.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/misc/legotree.cpp", + "output": "CMakeFiles/misc.dir/LEGO1/lego/sources/misc/legotree.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\misc.dir\\LEGO1\\lego\\sources\\misc\\legounknown.cpp.obj /FdCMakeFiles\\misc.dir\\misc.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\misc\\legounknown.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/misc/legounknown.cpp", + "output": "CMakeFiles/misc.dir/LEGO1/lego/sources/misc/legounknown.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\3dmanager.dir\\LEGO1\\lego\\sources\\3dmanager\\lego3dview.cpp.obj /FdCMakeFiles\\3dmanager.dir\\3dmanager.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\3dmanager\\lego3dview.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/3dmanager/lego3dview.cpp", + "output": "CMakeFiles/3dmanager.dir/LEGO1/lego/sources/3dmanager/lego3dview.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\3dmanager.dir\\LEGO1\\lego\\sources\\3dmanager\\lego3dmanager.cpp.obj /FdCMakeFiles\\3dmanager.dir\\3dmanager.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\3dmanager\\lego3dmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/3dmanager/lego3dmanager.cpp", + "output": "CMakeFiles/3dmanager.dir/LEGO1/lego/sources/3dmanager/lego3dmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\3dmanager.dir\\LEGO1\\lego\\sources\\3dmanager\\legoview1.cpp.obj /FdCMakeFiles\\3dmanager.dir\\3dmanager.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\3dmanager\\legoview1.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/3dmanager/legoview1.cpp", + "output": "CMakeFiles/3dmanager.dir/LEGO1/lego/sources/3dmanager/legoview1.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\3dmanager.dir\\LEGO1\\lego\\sources\\3dmanager\\tglsurface.cpp.obj /FdCMakeFiles\\3dmanager.dir\\3dmanager.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources\\3dmanager\\tglsurface.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/sources/3dmanager/tglsurface.cpp", + "output": "CMakeFiles/3dmanager.dir/LEGO1/lego/sources/3dmanager/tglsurface.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\notify\\mxnotificationmanager.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\notify\\mxnotificationmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/notify/mxnotificationmanager.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/notify/mxnotificationmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxdebug.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxdebug.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxdebug.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxdebug.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxmisc.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxmisc.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxmisc.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxmisc.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxatom.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxatom.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxatom.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxatom.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsaction.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsaction.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsaction.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsaction.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxtimer.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxtimer.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxtimer.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxtimer.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxcore.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxcore.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxcore.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxcore.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxstring.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxstring.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxstring.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxstring.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\audio\\mxsoundmanager.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\audio\\mxsoundmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/audio/mxsoundmanager.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/audio/mxsoundmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\main\\mxomni.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\main\\mxomni.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/main/mxomni.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/main/mxomni.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\notify\\mxactionnotificationparam.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\notify\\mxactionnotificationparam.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/notify/mxactionnotificationparam.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/notify/mxactionnotificationparam.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\main\\mxomnicreateflags.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\main\\mxomnicreateflags.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/main/mxomnicreateflags.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/main/mxomnicreateflags.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\main\\mxomnicreateparam.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\main\\mxomnicreateparam.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/main/mxomnicreateparam.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/main/mxomnicreateparam.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxobjectfactory.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxobjectfactory.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxobjectfactory.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxobjectfactory.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\audio\\mxsoundpresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\audio\\mxsoundpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/audio/mxsoundpresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/audio/mxsoundpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\audio\\mxwavepresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\audio\\mxwavepresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/audio/mxwavepresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/audio/mxwavepresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxvideopresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxvideopresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxvideopresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxvideopresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxflcpresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxflcpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxflcpresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxflcpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxsmkpresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxsmkpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxsmkpresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxsmkpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxloopingflcpresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxloopingflcpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxloopingflcpresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxloopingflcpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxloopingsmkpresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxloopingsmkpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxloopingsmkpresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxloopingsmkpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxpresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxpresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxmediapresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxmediapresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxmediapresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxmediapresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxcompositepresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxcompositepresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxcompositepresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxcompositepresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\system\\mxcriticalsection.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\system\\mxcriticalsection.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/system/mxcriticalsection.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/system/mxcriticalsection.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxutilities.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxutilities.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxutilities.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxutilities.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxvariabletable.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxvariabletable.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxvariabletable.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxvariabletable.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxdssubscriber.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxdssubscriber.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxdssubscriber.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxdssubscriber.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxmediamanager.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxmediamanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxmediamanager.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxmediamanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\system\\mxticklethread.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\system\\mxticklethread.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/system/mxticklethread.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/system/mxticklethread.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\audio\\mxaudiomanager.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\audio\\mxaudiomanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/audio/mxaudiomanager.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/audio/mxaudiomanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\system\\mxautolock.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\system\\mxautolock.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/system/mxautolock.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/system/mxautolock.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxstreamer.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxstreamer.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxstreamer.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxstreamer.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxstillpresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxstillpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxstillpresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxstillpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxdisplaysurface.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxdisplaysurface.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxdisplaysurface.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxdisplaysurface.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxbitmap.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxbitmap.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxbitmap.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxbitmap.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\flic.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\flic.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/flic.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/flic.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\common\\mxticklemanager.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\common\\mxticklemanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/common/mxticklemanager.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/common/mxticklemanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxdschunk.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxdschunk.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxdschunk.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxdschunk.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxvideomanager.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxvideomanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxvideomanager.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxvideomanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxvideoparamflags.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxvideoparamflags.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxvideoparamflags.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxvideoparamflags.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxvideoparam.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxvideoparam.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxvideoparam.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxvideoparam.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxpalette.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxpalette.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxpalette.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxpalette.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\system\\mxscheduler.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\system\\mxscheduler.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/system/mxscheduler.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/system/mxscheduler.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\system\\mxthread.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\system\\mxthread.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/system/mxthread.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/system/mxthread.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsobject.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsobject.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsobject.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsobject.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\event\\mxeventmanager.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\event\\mxeventmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/event/mxeventmanager.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/event/mxeventmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\audio\\mxmusicmanager.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\audio\\mxmusicmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/audio/mxmusicmanager.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/audio/mxmusicmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxstreamcontroller.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxstreamcontroller.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxstreamcontroller.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxstreamcontroller.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\audio\\mxmusicpresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\audio\\mxmusicpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/audio/mxmusicpresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/audio/mxmusicpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\audio\\mxmidipresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\audio\\mxmidipresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/audio/mxmidipresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/audio/mxmidipresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\audio\\mxloopingmidipresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\audio\\mxloopingmidipresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/audio/mxloopingmidipresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/audio/mxloopingmidipresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\event\\mxeventpresenter.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\event\\mxeventpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/event/mxeventpresenter.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/event/mxeventpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxstreamchunk.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxstreamchunk.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxstreamchunk.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxstreamchunk.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxregioncursor.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxregioncursor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxregioncursor.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxregioncursor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxregion.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxregion.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxregion.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxregion.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\video\\mxsmk.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\video\\mxsmk.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/video/mxsmk.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/video/mxsmk.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxramstreamcontroller.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxramstreamcontroller.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxramstreamcontroller.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxramstreamcontroller.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxdsbuffer.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxdsbuffer.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxdsbuffer.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxdsbuffer.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxdiskstreamcontroller.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxdiskstreamcontroller.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxdiskstreamcontroller.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxdiskstreamcontroller.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\system\\mxsemaphore.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\system\\mxsemaphore.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/system/mxsemaphore.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/system/mxsemaphore.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsobjectaction.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsobjectaction.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsobjectaction.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsobjectaction.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsmediaaction.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsmediaaction.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsmediaaction.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsmediaaction.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsanim.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsanim.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsanim.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsanim.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdssound.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdssound.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdssound.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdssound.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsevent.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsevent.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsevent.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsevent.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsstill.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsstill.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsstill.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsstill.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsmultiaction.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsmultiaction.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsmultiaction.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsmultiaction.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsserialaction.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsserialaction.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsserialaction.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsserialaction.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsparallelaction.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsparallelaction.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsparallelaction.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsparallelaction.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsselectaction.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsselectaction.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsselectaction.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsselectaction.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxdsfile.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxdsfile.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxdsfile.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxdsfile.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxio.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxio.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxio.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxio.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\action\\mxdsstreamingaction.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\action\\mxdsstreamingaction.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/action/mxdsstreamingaction.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/action/mxdsstreamingaction.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxramstreamprovider.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxramstreamprovider.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxramstreamprovider.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxramstreamprovider.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smacker -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\omni.dir\\LEGO1\\omni\\src\\stream\\mxdiskstreamprovider.cpp.obj /FdCMakeFiles\\omni.dir\\omni.pdb -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\src\\stream\\mxdiskstreamprovider.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp", + "output": "CMakeFiles/omni.dir/LEGO1/omni/src/stream/mxdiskstreamprovider.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\score.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\score.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/score.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/score.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\helicopter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\helicopter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/helicopter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/helicopter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\gasstation.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\gasstation.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/gasstation.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/gasstation.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\audio\\legocachsound.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\audio\\legocachsound.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/audio/legocachsound.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/audio/legocachsound.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legoobjectfactory.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legoobjectfactory.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legoobjectfactory.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\skateboard.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\skateboard.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/skateboard.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/skateboard.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legoentity.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legoentity.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legoentity.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legoentity.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\audio\\lego3dsound.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\audio\\lego3dsound.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/audio/lego3dsound.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/audio/lego3dsound.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legocameracontroller.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legocameracontroller.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legocameracontroller.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\race\\legoracers.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\race\\legoracers.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/race/legoracers.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/race/legoracers.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\race\\legoraceactor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\race\\legoraceactor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/race/legoraceactor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/race/legoraceactor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legostate.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legostate.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legostate.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legostate.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\buildingentity.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\buildingentity.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/buildingentity.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/buildingentity.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\buildings.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\buildings.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/buildings.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/buildings.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\misc.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\misc.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/misc.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/misc.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\race\\legorace.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\race\\legorace.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/race/legorace.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/race/legorace.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\race\\jetskirace.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\race\\jetskirace.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/race/jetskirace.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/race/jetskirace.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\race\\carrace.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\race\\carrace.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/race/carrace.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/race/carrace.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\pizzeria.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\pizzeria.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/pizzeria.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/pizzeria.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\elevatorbottom.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\elevatorbottom.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/elevatorbottom.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\audio\\legoloadcachesoundpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\audio\\legoloadcachesoundpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/audio/legoloadcachesoundpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/audio/legoloadcachesoundpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\act2genactor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\act2genactor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/act2genactor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/act2genactor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\act2actor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\act2actor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/act2actor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/act2actor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\islepathactor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\islepathactor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/islepathactor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/islepathactor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\paths\\legopathstruct.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\paths\\legopathstruct.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\paths\\legoanimactor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\paths\\legoanimactor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/paths/legoanimactor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legoworld.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legoworld.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legoworld.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legoworld.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\build\\legocarbuild.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\build\\legocarbuild.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/build/legocarbuild.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/build/legocarbuild.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legoplantmanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legoplantmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legoplantmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\bumpbouy.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\bumpbouy.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/bumpbouy.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/bumpbouy.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\modeldb\\modeldb.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\modeldb\\modeldb.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/modeldb/modeldb.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/modeldb/modeldb.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\racecar.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\racecar.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/racecar.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/racecar.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\control\\legocontrolmanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\control\\legocontrolmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/control/legocontrolmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\audio\\legosoundmanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\audio\\legosoundmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/audio/legosoundmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\paths\\legoextraactor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\paths\\legoextraactor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/paths/legoextraactor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\isleactor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\isleactor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/isleactor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/isleactor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\radio.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\radio.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/radio.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/radio.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legoactor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legoactor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legoactor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legoactor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\paths\\legopathactor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\paths\\legopathactor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/paths/legopathactor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/paths/legopathactor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legobuildingmanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legobuildingmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legobuildingmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\isle.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\isle.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/isle.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/isle.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\ambulance.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\ambulance.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/ambulance.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/ambulance.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\pizza.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\pizza.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/pizza.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/pizza.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\motorcycle.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\motorcycle.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/motorcycle.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/motorcycle.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\act3ammo.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\act3ammo.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/act3ammo.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/act3ammo.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\audio\\legocachesoundmanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\audio\\legocachesoundmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/audio/legocachesoundmanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/audio/legocachesoundmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\infocenterdoor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\infocenterdoor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/infocenterdoor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legogamestate.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legogamestate.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legogamestate.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legogamestate.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legobackgroundcolor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legobackgroundcolor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legobackgroundcolor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legobackgroundcolor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legofullscreenmovie.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legofullscreenmovie.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legofullscreenmovie.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legofullscreenmovie.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legoutils.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legoutils.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legoutils.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legoutils.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\act3actors.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\act3actors.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/act3actors.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/act3actors.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\control\\legometerpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\control\\legometerpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/control/legometerpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/control/legometerpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legoactioncontrolpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legoactioncontrolpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legoactioncontrolpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legoactioncontrolpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\mxcontrolpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\mxcontrolpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/mxcontrolpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\paths\\legopathpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\paths\\legopathpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/paths/legopathpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/paths/legopathpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legophoneme.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legophoneme.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legophoneme.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legophoneme.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\paths\\legopathcontroller.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\paths\\legopathcontroller.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/paths/legopathcontroller.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/paths/legopathcontroller.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legojetskiraceactor.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legojetskiraceactor.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legojetskiraceactor.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\audio\\lego3dwavepresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\audio\\lego3dwavepresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/audio/lego3dwavepresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/audio/lego3dwavepresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legoanimmmpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legoanimmmpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legoanimmmpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\mxtransitionmanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\mxtransitionmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/mxtransitionmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\towtrack.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\towtrack.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/towtrack.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/towtrack.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\act2policestation.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\act2policestation.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/act2policestation.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/act2policestation.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legophonemepresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legophonemepresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legophonemepresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legophonemepresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legotexturepresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legotexturepresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legotexturepresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legotexturepresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\legoact2.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\legoact2.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/legoact2.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/legoact2.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legovariables.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legovariables.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legovariables.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legovariables.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\main\\scripts.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\main\\scripts.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/main/scripts.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/main/scripts.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legoentitypresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legoentitypresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legoentitypresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legoentitypresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legonavcontroller.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legonavcontroller.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legonavcontroller.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\paths\\legopathboundary.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\paths\\legopathboundary.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/paths/legopathboundary.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/paths/legopathboundary.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\main\\legomain.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\main\\legomain.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/main/legomain.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/main/legomain.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\input\\legoinputmanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\input\\legoinputmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/input/legoinputmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\race\\legoracemap.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\race\\legoracemap.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/race/legoracemap.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/race/legoracemap.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\jukebox.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\jukebox.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/jukebox.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/jukebox.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legoflctexturepresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legoflctexturepresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legoflctexturepresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legoflctexturepresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\police.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\police.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/police.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/police.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legoanimationmanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legoanimationmanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legoanimationmanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\build\\legocarbuildpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\build\\legocarbuildpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legopovcontroller.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legopovcontroller.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legopovcontroller.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legopovcontroller.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legotextureinfo.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legotextureinfo.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legotextureinfo.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legotextureinfo.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\doors.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\doors.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/doors.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/doors.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legoworldpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legoworldpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legoworldpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\dunebuggy.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\dunebuggy.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/dunebuggy.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legoanimpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legoanimpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legoloopinganimpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legoloopinganimpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legoloopinganimpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legoloopinganimpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legolocomotionanimpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legolocomotionanimpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legolocomotionanimpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legohideanimpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legohideanimpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legohideanimpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\infocenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\infocenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/infocenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/infocenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\race\\raceskel.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\race\\raceskel.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/race/raceskel.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/race/raceskel.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\act3.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\act3.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/act3.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/act3.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legomodelpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legomodelpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\mxcompositemediapresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\mxcompositemediapresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/mxcompositemediapresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\hospital.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\hospital.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/hospital.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/hospital.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\bike.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\bike.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/bike.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/bike.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legoactorpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legoactorpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legoactorpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legoactorpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\registrationbook.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\registrationbook.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/registrationbook.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legopalettepresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legopalettepresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legopalettepresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legopalettepresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\act2brick.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\act2brick.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/act2brick.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/act2brick.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legovideomanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legovideomanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legovideomanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legovideomanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\video\\legopartpresenter.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\video\\legopartpresenter.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/video/legopartpresenter.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\jetski.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\jetski.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/jetski.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/jetski.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\audio\\mxbackgroundaudiomanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\audio\\mxbackgroundaudiomanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\race\\legoracespecial.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\race\\legoracespecial.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/race/legoracespecial.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/race/legoracespecial.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\worlds\\historybook.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\worlds\\historybook.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/worlds/historybook.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/worlds/historybook.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legocharactermanager.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legocharactermanager.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legocharactermanager.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\actors\\jukeboxentity.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\actors\\jukeboxentity.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\main.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\main.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/main.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/main.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\entity\\legolocations.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\entity\\legolocations.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/entity/legolocations.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/entity/legolocations.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\notify\\legoeventnotificationparam.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\notify\\legoeventnotificationparam.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/notify/legoeventnotificationparam.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/notify/legoeventnotificationparam.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legoactors.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legoactors.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legoactors.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legoactors.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\define.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\define.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/define.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/define.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECTX5_SDK -DENABLE_DECOMP_ASSERTS -Dlego1_EXPORTS -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\vec -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\lego1.dir\\LEGO1\\lego\\legoomni\\src\\common\\legoplants.cpp.obj /FdCMakeFiles\\lego1.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\src\\common\\legoplants.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/lego/legoomni/src/common/legoplants.cpp", + "output": "CMakeFiles/lego1.dir/LEGO1/lego/legoomni/src/common/legoplants.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DENABLE_DECOMP_ASSERTS -DISLE_APP -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\omni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\sources -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include -ID:\\ISLE\\Decomp\\isle-master\\LEGO1\\lego\\legoomni\\include\\actions -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\smartheap /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MT /FID:/ISLE/Decomp/isle-master/3rdparty/smartheap/SMRTHEAP.HPP /FoCMakeFiles\\isle.dir\\ISLE\\isleapp.cpp.obj /FdCMakeFiles\\isle.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\ISLE\\isleapp.cpp", + "file": "D:/ISLE/Decomp/isle-master/ISLE/isleapp.cpp", + "output": "CMakeFiles/isle.dir/ISLE/isleapp.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECT3D_VERSION=0x500 -DENABLE_DECOMP_ASSERTS -DMXDIRECTX_FOR_CONFIG -D_AFXDLL -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MD /FoCMakeFiles\\config.dir\\LEGO1\\mxdirectx\\mxdirectxinfo.cpp.obj /FdCMakeFiles\\config.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\mxdirectx\\mxdirectxinfo.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/mxdirectx/mxdirectxinfo.cpp", + "output": "CMakeFiles/config.dir/LEGO1/mxdirectx/mxdirectxinfo.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECT3D_VERSION=0x500 -DENABLE_DECOMP_ASSERTS -DMXDIRECTX_FOR_CONFIG -D_AFXDLL -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MD /FoCMakeFiles\\config.dir\\LEGO1\\mxdirectx\\legodxinfo.cpp.obj /FdCMakeFiles\\config.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\LEGO1\\mxdirectx\\legodxinfo.cpp", + "file": "D:/ISLE/Decomp/isle-master/LEGO1/mxdirectx/legodxinfo.cpp", + "output": "CMakeFiles/config.dir/LEGO1/mxdirectx/legodxinfo.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECT3D_VERSION=0x500 -DENABLE_DECOMP_ASSERTS -DMXDIRECTX_FOR_CONFIG -D_AFXDLL -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MD /FoCMakeFiles\\config.dir\\CONFIG\\config.cpp.obj /FdCMakeFiles\\config.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\CONFIG\\config.cpp", + "file": "D:/ISLE/Decomp/isle-master/CONFIG/config.cpp", + "output": "CMakeFiles/config.dir/CONFIG/config.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECT3D_VERSION=0x500 -DENABLE_DECOMP_ASSERTS -DMXDIRECTX_FOR_CONFIG -D_AFXDLL -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MD /FoCMakeFiles\\config.dir\\CONFIG\\ConfigCommandLineInfo.cpp.obj /FdCMakeFiles\\config.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\CONFIG\\ConfigCommandLineInfo.cpp", + "file": "D:/ISLE/Decomp/isle-master/CONFIG/ConfigCommandLineInfo.cpp", + "output": "CMakeFiles/config.dir/CONFIG/ConfigCommandLineInfo.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECT3D_VERSION=0x500 -DENABLE_DECOMP_ASSERTS -DMXDIRECTX_FOR_CONFIG -D_AFXDLL -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MD /FoCMakeFiles\\config.dir\\CONFIG\\AboutDlg.cpp.obj /FdCMakeFiles\\config.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\CONFIG\\AboutDlg.cpp", + "file": "D:/ISLE/Decomp/isle-master/CONFIG/AboutDlg.cpp", + "output": "CMakeFiles/config.dir/CONFIG/AboutDlg.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECT3D_VERSION=0x500 -DENABLE_DECOMP_ASSERTS -DMXDIRECTX_FOR_CONFIG -D_AFXDLL -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MD /FoCMakeFiles\\config.dir\\CONFIG\\MainDlg.cpp.obj /FdCMakeFiles\\config.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\CONFIG\\MainDlg.cpp", + "file": "D:/ISLE/Decomp/isle-master/CONFIG/MainDlg.cpp", + "output": "CMakeFiles/config.dir/CONFIG/MainDlg.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECT3D_VERSION=0x500 -DENABLE_DECOMP_ASSERTS -DMXDIRECTX_FOR_CONFIG -D_AFXDLL -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MD /FoCMakeFiles\\config.dir\\CONFIG\\detectdx5.cpp.obj /FdCMakeFiles\\config.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\CONFIG\\detectdx5.cpp", + "file": "D:/ISLE/Decomp/isle-master/CONFIG/detectdx5.cpp", + "output": "CMakeFiles/config.dir/CONFIG/detectdx5.cpp.obj" +}, +{ + "directory": "D:/ISLE/Decomp/isle-master/build", + "command": "D:\\tools\\MSVC420\\bin\\CL.EXE /nologo /TP -DDIRECT3D_VERSION=0x500 -DENABLE_DECOMP_ASSERTS -DMXDIRECTX_FOR_CONFIG -D_AFXDLL -ID:\\ISLE\\Decomp\\isle-master\\util -ID:\\ISLE\\Decomp\\isle-master\\LEGO1 -ID:\\ISLE\\Decomp\\isle-master\\3rdparty\\dx5\\inc /W3 /GX /D \"WIN32\" /D \"_WINDOWS\" /Zi /O2 /D \"NDEBUG\" -MD /FoCMakeFiles\\config.dir\\CONFIG\\StdAfx.cpp.obj /FdCMakeFiles\\config.dir\\ -c D:\\ISLE\\Decomp\\isle-master\\CONFIG\\StdAfx.cpp", + "file": "D:/ISLE/Decomp/isle-master/CONFIG/StdAfx.cpp", + "output": "CMakeFiles/config.dir/CONFIG/StdAfx.cpp.obj" +} +] \ No newline at end of file diff --git a/build/d3drm.dll b/build/d3drm.dll new file mode 100644 index 00000000..eecda321 Binary files /dev/null and b/build/d3drm.dll differ diff --git a/build/dgVoodoo.conf b/build/dgVoodoo.conf new file mode 100644 index 00000000..d9e2eaa5 Binary files /dev/null and b/build/dgVoodoo.conf differ diff --git a/cmake/reccmp.cmake b/cmake/reccmp.cmake index 60aeecc1..3829c86b 100644 --- a/cmake/reccmp.cmake +++ b/cmake/reccmp.cmake @@ -1,58 +1,58 @@ -function(reccmp_find_project RESULT) - set(curdir "${CMAKE_CURRENT_SOURCE_DIR}") - while(1) - if(EXISTS "${curdir}/reccmp-project.yml") - break() - endif() - get_filename_component(nextdir "${curdir}" DIRECTORY) - if(nextdir STREQUAL curdir) - set(curdir "${RESULT}-NOTFOUND") - break() - endif() - set(curdir "${nextdir}") - endwhile() - set("${RESULT}" "${curdir}" PARENT_SCOPE) -endfunction() - -function(reccmp_add_target TARGET) - cmake_parse_arguments(ARGS "" "ID" "" ${ARGN}) - if(NOT ARGS_ID) - message(FATAL_ERROR "Missing ID argument") - endif() - set_property(TARGET ${TARGET} PROPERTY INTERFACE_RECCMP_ID "${ARGS_ID}") - set_property(GLOBAL APPEND PROPERTY RECCMP_TARGETS ${TARGET}) -endfunction() - -function(reccmp_configure) - cmake_parse_arguments(ARGS "COPY_TO_SOURCE_FOLDER" "DIR" "" ${ARGN}) - set(binary_dir "${CMAKE_BINARY_DIR}") - if(ARGS_DIR) - set(binary_dir "${ARGS_DIR}") - endif() - - reccmp_find_project(reccmp_project_dir) - if(NOT reccmp_project_dir) - message(FATAL_ERROR "Cannot find reccmp-project.yml") - endif() - - if(CMAKE_CONFIGURATION_TYPES) - set(outputdir "${binary_dir}/$") - else() - set(outputdir "${binary_dir}") - endif() - set(build_yml_txt "project: '${reccmp_project_dir}'\ntargets:\n") - get_property(RECCMP_TARGETS GLOBAL PROPERTY RECCMP_TARGETS) - foreach(target ${RECCMP_TARGETS}) - get_property(id TARGET "${target}" PROPERTY INTERFACE_RECCMP_ID) - string(APPEND build_yml_txt " ${id}:\n") - string(APPEND build_yml_txt " path: '$'\n") - if(WIN32 AND MSVC) - string(APPEND build_yml_txt " pdb: '$'\n") - endif() - endforeach() - file(GENERATE OUTPUT "${outputdir}/reccmp-build.yml" CONTENT "${build_yml_txt}") - - if(ARGS_COPY_TO_SOURCE_FOLDER) - file(GENERATE OUTPUT "${CMAKE_SOURCE_DIR}/reccmp-build.yml" CONTENT "${build_yml_txt}" CONDITION $) - endif() -endfunction() +function(reccmp_find_project RESULT) + set(curdir "${CMAKE_CURRENT_SOURCE_DIR}") + while(1) + if(EXISTS "${curdir}/reccmp-project.yml") + break() + endif() + get_filename_component(nextdir "${curdir}" DIRECTORY) + if(nextdir STREQUAL curdir) + set(curdir "${RESULT}-NOTFOUND") + break() + endif() + set(curdir "${nextdir}") + endwhile() + set("${RESULT}" "${curdir}" PARENT_SCOPE) +endfunction() + +function(reccmp_add_target TARGET) + cmake_parse_arguments(ARGS "" "ID" "" ${ARGN}) + if(NOT ARGS_ID) + message(FATAL_ERROR "Missing ID argument") + endif() + set_property(TARGET ${TARGET} PROPERTY INTERFACE_RECCMP_ID "${ARGS_ID}") + set_property(GLOBAL APPEND PROPERTY RECCMP_TARGETS ${TARGET}) +endfunction() + +function(reccmp_configure) + cmake_parse_arguments(ARGS "COPY_TO_SOURCE_FOLDER" "DIR" "" ${ARGN}) + set(binary_dir "${CMAKE_BINARY_DIR}") + if(ARGS_DIR) + set(binary_dir "${ARGS_DIR}") + endif() + + reccmp_find_project(reccmp_project_dir) + if(NOT reccmp_project_dir) + message(FATAL_ERROR "Cannot find reccmp-project.yml") + endif() + + if(CMAKE_CONFIGURATION_TYPES) + set(outputdir "${binary_dir}/$") + else() + set(outputdir "${binary_dir}") + endif() + set(build_yml_txt "project: '${reccmp_project_dir}'\ntargets:\n") + get_property(RECCMP_TARGETS GLOBAL PROPERTY RECCMP_TARGETS) + foreach(target ${RECCMP_TARGETS}) + get_property(id TARGET "${target}" PROPERTY INTERFACE_RECCMP_ID) + string(APPEND build_yml_txt " ${id}:\n") + string(APPEND build_yml_txt " path: '$'\n") + if(WIN32 AND MSVC) + string(APPEND build_yml_txt " pdb: '$'\n") + endif() + endforeach() + file(GENERATE OUTPUT "${outputdir}/reccmp-build.yml" CONTENT "${build_yml_txt}") + + if(ARGS_COPY_TO_SOURCE_FOLDER) + file(GENERATE OUTPUT "${CMAKE_SOURCE_DIR}/reccmp-build.yml" CONTENT "${build_yml_txt}" CONDITION $) + endif() +endfunction() \ No newline at end of file diff --git a/compile.bat b/compile.bat new file mode 100644 index 00000000..eb454fe7 --- /dev/null +++ b/compile.bat @@ -0,0 +1,9 @@ +@echo off +cd build +echo Pass #1 +nmake +echo Pass #2 +nmake +echo Compolation complete! Check above for errors. +echo Press any key to quit. +pause > NUL \ No newline at end of file diff --git a/reconfigureCMake.bat b/reconfigureCMake.bat new file mode 100644 index 00000000..ef9ea312 --- /dev/null +++ b/reconfigureCMake.bat @@ -0,0 +1,3 @@ +cd build +cmake .. -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo +cd.. \ No newline at end of file diff --git a/tools/datacmp.py b/tools/datacmp.py new file mode 100644 index 00000000..cbd18877 --- /dev/null +++ b/tools/datacmp.py @@ -0,0 +1,371 @@ +# (New) Data comparison. + +import os +import argparse +import logging +from enum import Enum +from typing import Iterable, List, NamedTuple, Optional, Tuple +from struct import unpack +from isledecomp.compare import Compare as IsleCompare +from isledecomp.compare.db import MatchInfo +from isledecomp.cvdump import Cvdump +from isledecomp.cvdump.types import ( + CvdumpKeyError, + CvdumpIntegrityError, +) +from isledecomp.bin import Bin as IsleBin +import colorama + +colorama.just_fix_windows_console() + + +# Ignore all compare-db messages. +logging.getLogger("isledecomp.compare").addHandler(logging.NullHandler()) + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Comparing data values.") + parser.add_argument( + "original", metavar="original-binary", help="The original binary" + ) + parser.add_argument( + "recompiled", metavar="recompiled-binary", help="The recompiled binary" + ) + parser.add_argument( + "pdb", metavar="recompiled-pdb", help="The PDB of the recompiled binary" + ) + parser.add_argument( + "decomp_dir", metavar="decomp-dir", help="The decompiled source tree" + ) + parser.add_argument( + "-v", + "--verbose", + action=argparse.BooleanOptionalAction, + default=False, + help="", + ) + parser.add_argument( + "--no-color", "-n", action="store_true", help="Do not color the output" + ) + parser.add_argument( + "--all", + "-a", + dest="show_all", + action="store_true", + help="Only show variables with a problem", + ) + parser.add_argument( + "--print-rec-addr", + action="store_true", + help="Print addresses of recompiled functions too", + ) + + (args, _) = parser.parse_known_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 + + +class CompareResult(Enum): + MATCH = 1 + DIFF = 2 + ERROR = 3 + WARN = 4 + + +class ComparedOffset(NamedTuple): + offset: int + # name is None for scalar types + name: Optional[str] + match: bool + values: Tuple[str, str] + + +class ComparisonItem(NamedTuple): + """Each variable that was compared""" + + orig_addr: int + recomp_addr: int + name: str + + # The list of items that were compared. + # For a complex type, these are the members. + # For a scalar type, this is a list of size one. + # If we could not retrieve type information, this is + # a list of size one but without any specific type. + compared: List[ComparedOffset] + + # If present, the error message from the types parser. + error: Optional[str] = None + + # If true, there is no type specified for this variable. (i.e. non-public) + # In this case, we can only compare the raw bytes. + # This is different from the situation where a type id _is_ given, but + # we could not retrieve it for some reason. (This is an error.) + raw_only: bool = False + + @property + def result(self) -> CompareResult: + if self.error is not None: + return CompareResult.ERROR + + if all(c.match for c in self.compared): + return CompareResult.MATCH + + # Prefer WARN for a diff without complete type information. + return CompareResult.WARN if self.raw_only else CompareResult.DIFF + + +def create_comparison_item( + var: MatchInfo, + compared: Optional[List[ComparedOffset]] = None, + error: Optional[str] = None, + raw_only: bool = False, +) -> ComparisonItem: + """Helper to create the ComparisonItem from the fields in MatchInfo.""" + if compared is None: + compared = [] + + return ComparisonItem( + orig_addr=var.orig_addr, + recomp_addr=var.recomp_addr, + name=var.name, + compared=compared, + error=error, + raw_only=raw_only, + ) + + +def do_the_comparison(args: argparse.Namespace) -> Iterable[ComparisonItem]: + """Run through each variable in our compare DB, then do the comparison + according to the variable's type. Emit the result.""" + with IsleBin(args.original, find_str=True) as origfile, IsleBin( + args.recompiled + ) as recompfile: + isle_compare = IsleCompare(origfile, recompfile, args.pdb, args.decomp_dir) + + # TODO: We don't currently retain the type information of each variable + # in our compare DB. To get those, we build this mini-lookup table that + # maps recomp addresses to their type. + # We still need to build the full compare DB though, because we may + # need the matched symbols to compare pointers (e.g. on strings) + mini_cvdump = Cvdump(args.pdb).globals().types().run() + + recomp_type_reference = { + recompfile.get_abs_addr(g.section, g.offset): g.type + for g in mini_cvdump.globals + if recompfile.is_valid_section(g.section) + } + + for var in isle_compare.get_variables(): + type_name = recomp_type_reference.get(var.recomp_addr) + + # Start by assuming we can only compare the raw bytes + data_size = var.size + is_type_aware = type_name is not None + + if is_type_aware: + try: + # If we are type-aware, we can get the precise + # data size for the variable. + data_type = mini_cvdump.types.get(type_name) + data_size = data_type.size + except (CvdumpKeyError, CvdumpIntegrityError) as ex: + yield create_comparison_item(var, error=repr(ex)) + continue + + orig_raw = origfile.read(var.orig_addr, data_size) + recomp_raw = recompfile.read(var.recomp_addr, data_size) + + # The IMAGE_SECTION_HEADER defines the SizeOfRawData and VirtualSize for the section. + # If VirtualSize > SizeOfRawData, the section is comprised of the initialized data + # corresponding to bytes in the file, and the rest is padded with zeroes when + # Windows loads the image. + # The linker might place variables initialized to zero on the threshold between + # physical data and the virtual (uninitialized) data. + # If this happens (i.e. we get an incomplete read) we just do the same padding + # to prepare for the comparison. + if orig_raw is not None and len(orig_raw) < data_size: + orig_raw = orig_raw.ljust(data_size, b"\x00") + + if recomp_raw is not None and len(recomp_raw) < data_size: + recomp_raw = recomp_raw.ljust(data_size, b"\x00") + + # If one or both variables are entirely uninitialized + if orig_raw is None or recomp_raw is None: + # If both variables are uninitialized, we consider them equal. + match = orig_raw is None and recomp_raw is None + + # We can match a variable initialized to all zeroes with + # an uninitialized variable, but this may or may not actually + # be correct, so we flag it for the user. + uninit_force_match = not match and ( + (orig_raw is None and all(b == 0 for b in recomp_raw)) + or (recomp_raw is None and all(b == 0 for b in orig_raw)) + ) + + orig_value = "(uninitialized)" if orig_raw is None else "(initialized)" + recomp_value = ( + "(uninitialized)" if recomp_raw is None else "(initialized)" + ) + yield create_comparison_item( + var, + compared=[ + ComparedOffset( + offset=0, + name=None, + match=match, + values=(orig_value, recomp_value), + ) + ], + raw_only=uninit_force_match, + ) + continue + + if not is_type_aware: + # If there is no specific type information available + # (i.e. if this is a static or non-public variable) + # then we can only compare the raw bytes. + yield create_comparison_item( + var, + compared=[ + ComparedOffset( + offset=0, + name="(raw)", + match=orig_raw == recomp_raw, + values=(orig_raw, recomp_raw), + ) + ], + raw_only=True, + ) + continue + + # If we are here, we can do the type-aware comparison. + compared = [] + compare_items = mini_cvdump.types.get_scalars_gapless(type_name) + format_str = mini_cvdump.types.get_format_string(type_name) + + orig_data = unpack(format_str, orig_raw) + recomp_data = unpack(format_str, recomp_raw) + + def pointer_display(addr: int, is_orig: bool) -> str: + """Helper to streamline pointer textual display.""" + if addr == 0: + return "nullptr" + + ptr_match = ( + isle_compare.get_by_orig(addr) + if is_orig + else isle_compare.get_by_recomp(addr) + ) + + if ptr_match is not None: + return f"Pointer to {ptr_match.match_name()}" + + # This variable did not match if we do not have + # the pointer target in our DB. + return f"Unknown pointer 0x{addr:x}" + + # Could zip here + for i, member in enumerate(compare_items): + if member.is_pointer: + match = isle_compare.is_pointer_match(orig_data[i], recomp_data[i]) + + value_a = pointer_display(orig_data[i], True) + value_b = pointer_display(recomp_data[i], False) + + values = (value_a, value_b) + else: + match = orig_data[i] == recomp_data[i] + values = (orig_data[i], recomp_data[i]) + + compared.append( + ComparedOffset( + offset=member.offset, + name=member.name, + match=match, + values=values, + ) + ) + + yield create_comparison_item(var, compared=compared) + + +def value_get(value: Optional[str], default: str): + return value if value is not None else default + + +def main(): + args = parse_args() + + def display_match(result: CompareResult) -> str: + """Helper to return color string or not, depending on user preference""" + if args.no_color: + return result.name + + match_color = ( + colorama.Fore.GREEN + if result == CompareResult.MATCH + else ( + colorama.Fore.YELLOW + if result == CompareResult.WARN + else colorama.Fore.RED + ) + ) + return f"{match_color}{result.name}{colorama.Style.RESET_ALL}" + + var_count = 0 + problems = 0 + + for item in do_the_comparison(args): + var_count += 1 + if item.result in (CompareResult.DIFF, CompareResult.ERROR): + problems += 1 + + if not args.show_all and item.result == CompareResult.MATCH: + continue + + address_display = ( + f"0x{item.orig_addr:x} / 0x{item.recomp_addr:x}" + if args.print_rec_addr + else f"0x{item.orig_addr:x}" + ) + + print(f"{item.name[:80]} ({address_display}) ... {display_match(item.result)} ") + if item.error is not None: + print(f" {item.error}") + + for c in item.compared: + if not args.verbose and c.match: + continue + + (value_a, value_b) = c.values + if c.match: + print(f" {c.offset:5} {value_get(c.name, '(value)'):30} {value_a}") + else: + print( + f" {c.offset:5} {value_get(c.name, '(value)'):30} {value_a} : {value_b}" + ) + + if args.verbose: + print() + + print( + f"{os.path.basename(args.original)} - Variables: {var_count}. Issues: {problems}" + ) + return 0 if problems == 0 else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/decomplint/decomplint.py b/tools/decomplint/decomplint.py new file mode 100644 index 00000000..4ff28168 --- /dev/null +++ b/tools/decomplint/decomplint.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 + +import os +import sys +import argparse +import colorama +from isledecomp.dir import walk_source_dir, is_file_cpp +from isledecomp.parser import DecompLinter + +colorama.just_fix_windows_console() + + +def display_errors(alerts, filename): + sorted_alerts = sorted(alerts, key=lambda a: a.line_number) + + for alert in sorted_alerts: + error_type = ( + f"{colorama.Fore.RED}error: " + if alert.is_error() + else f"{colorama.Fore.YELLOW}warning: " + ) + components = [ + colorama.Fore.LIGHTWHITE_EX, + filename, + ":", + str(alert.line_number), + " : ", + error_type, + colorama.Fore.LIGHTWHITE_EX, + alert.code.name.lower(), + ] + print("".join(components)) + + if alert.line is not None: + print(f"{colorama.Fore.WHITE} {alert.line}") + + +def parse_args() -> argparse.Namespace: + p = argparse.ArgumentParser( + description="Syntax checking and linting for decomp annotation markers." + ) + p.add_argument("target", help="The file or directory to check.") + p.add_argument( + "--module", + required=False, + type=str, + help="If present, run targeted checks for markers from the given module.", + ) + p.add_argument( + "--warnfail", + action=argparse.BooleanOptionalAction, + default=False, + help="Fail if syntax warnings are found.", + ) + + (args, _) = p.parse_known_args() + return args + + +def process_files(files, module=None): + warning_count = 0 + error_count = 0 + + linter = DecompLinter() + for filename in files: + success = linter.check_file(filename, module) + + warnings = [a for a in linter.alerts if a.is_warning()] + errors = [a for a in linter.alerts if a.is_error()] + + error_count += len(errors) + warning_count += len(warnings) + + if not success: + display_errors(linter.alerts, filename) + print() + + return (warning_count, error_count) + + +def main(): + args = parse_args() + + files_to_check = [] + if os.path.isdir(args.target): + files_to_check = list(walk_source_dir(args.target)) + elif os.path.isfile(args.target) and is_file_cpp(args.target): + files_to_check = [args.target] + else: + sys.exit("Invalid target") + + (warning_count, error_count) = process_files(files_to_check, module=args.module) + + print(colorama.Style.RESET_ALL, end="") + + would_fail = error_count > 0 or (warning_count > 0 and args.warnfail) + if would_fail: + return 1 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/ghidra_scripts/README.md b/tools/ghidra_scripts/README.md new file mode 100644 index 00000000..1e5082d7 --- /dev/null +++ b/tools/ghidra_scripts/README.md @@ -0,0 +1,25 @@ +# Ghidra Scripts + +The scripts in this directory provide additional functionality in Ghidra, e.g. imports of symbols and types from the PDB debug symbol file. + +## Setup + +### Ghidrathon +Since these scripts and its dependencies are written in Python 3, [Ghidrathon](https://github.com/mandiant/Ghidrathon) must be installed first. Follow the instructions and install a recent build (these scripts were tested with Python 3.12 and Ghidrathon v4.0.0). + +### Script Directory +- In Ghidra, _Open Window -> Script Manager_. +- Click the _Manage Script Directories_ button on the top right. +- Click the _Add_ (Plus icon) button and select this file's parent directory. +- Close the window and click the _Refresh_ button. +- This script should now be available under the folder _LEGO1_. + +### Virtual environment +As of now, there must be a Python virtual environment set up under `$REPOSITORY_ROOT/.venv`, and the dependencies of `isledecomp` must be installed there, see [here](../README.md#tooling). + +## Development +- Type hints for Ghidra (optional): Download a recent release from https://github.com/VDOO-Connected-Trust/ghidra-pyi-generator, + unpack it somewhere, and `pip install` that directory in this virtual environment. This provides types and headers for Python. + Be aware that some of these files contain errors - in particular, `from typing import overload` seems to be missing everywhere, leading to spurious type errors. +- Note that the imported modules persist across multiple runs of the script (see [here](https://github.com/mandiant/Ghidrathon/issues/103)). + If you indend to modify an imported library, you have to use `import importlib; importlib.reload(${library})` or restart Ghidra for your changes to have any effect. Unfortunately, even that is not perfectly reliable, so you may still have to restart Ghidra for some changes in `isledecomp` to be applied. diff --git a/tools/ghidra_scripts/import_functions_and_types_from_pdb.py b/tools/ghidra_scripts/import_functions_and_types_from_pdb.py new file mode 100644 index 00000000..54bb7e9f --- /dev/null +++ b/tools/ghidra_scripts/import_functions_and_types_from_pdb.py @@ -0,0 +1,322 @@ +# Imports types and function signatures from debug symbols (PDB file) of the recompilation. +# +# This script uses Python 3 and therefore requires Ghidrathon to be installed in Ghidra (see https://github.com/mandiant/Ghidrathon). +# Furthermore, the virtual environment must be set up beforehand under $REPOSITORY_ROOT/.venv, and all required packages must be installed +# (see $REPOSITORY_ROOT/tools/README.md). +# Also, the Python version of the virtual environment must probably match the Python version used for Ghidrathon. + +# @author J. Schulz +# @category LEGO1 +# @keybinding +# @menupath +# @toolbar + + +# In order to make this code run both within and outside of Ghidra, the import order is rather unorthodox in this file. +# That is why some of the lints below are disabled. + +# pylint: disable=wrong-import-position,ungrouped-imports +# pylint: disable=undefined-variable # need to disable this one globally because pylint does not understand e.g. `askYesNo()`` + +# Disable spurious warnings in vscode / pylance +# pyright: reportMissingModuleSource=false + +from enum import Enum +import importlib +from dataclasses import dataclass, field +import logging.handlers +import sys +import logging +from pathlib import Path +import traceback +from typing import TYPE_CHECKING, Optional + + +if TYPE_CHECKING: + import ghidra + from lego_util.headers import * # pylint: disable=wildcard-import # these are just for headers + + +logger = logging.getLogger(__name__) + + +def reload_module(module: str): + """ + Due to a a quirk in Jep (used by Ghidrathon), imported modules persist for the lifetime of the Ghidra process + and are not reloaded when relaunching the script. Therefore, in order to facilitate development + we force reload all our own modules at startup. See also https://github.com/mandiant/Ghidrathon/issues/103. + + Note that as of 2024-05-30, this remedy does not work perfectly (yet): Some changes in isledecomp are + still not detected correctly and require a Ghidra restart to be applied. + """ + importlib.reload(importlib.import_module(module)) + + +reload_module("lego_util.statistics") +from lego_util.statistics import Statistics + + +@dataclass +class Globals: + verbose: bool + loglevel: int + running_from_ghidra: bool = False + # statistics + statistics: Statistics = field(default_factory=Statistics) + + +class SupportedModules(Enum): + LEGO1 = 1 + BETA10 = 2 + + def orig_filename(self): + if self == self.LEGO1: + return "LEGO1.DLL" + return "BETA10.DLL" + + def recomp_filename_without_extension(self): + # in case we want to support more functions + return "LEGO1" + + def build_dir_name(self): + if self == self.BETA10: + return "build_debug" + return "build" + + +# hard-coded settings that we don't want to prompt in Ghidra every time +GLOBALS = Globals( + verbose=False, + # loglevel=logging.INFO, + loglevel=logging.DEBUG, +) + + +def setup_logging(): + logging.root.handlers.clear() + formatter = logging.Formatter("%(levelname)-8s %(message)s") + # formatter = logging.Formatter("%(name)s %(levelname)-8s %(message)s") # use this to identify loggers + stdout_handler = logging.StreamHandler(sys.stdout) + stdout_handler.setFormatter(formatter) + file_handler = logging.FileHandler( + Path(__file__).absolute().parent.joinpath("import.log"), mode="w" + ) + file_handler.setFormatter(formatter) + logging.root.setLevel(GLOBALS.loglevel) + logging.root.addHandler(stdout_handler) + logging.root.addHandler(file_handler) + logger.info("Starting import...") + + +# This script can be run both from Ghidra and as a standalone. +# In the latter case, only the PDB parser will be used. +setup_logging() +try: + from ghidra.program.flatapi import FlatProgramAPI + from ghidra.util.exception import CancelledException + + GLOBALS.running_from_ghidra = True +except ImportError as importError: + logger.error( + "Failed to import Ghidra functions, doing a dry run for the source code parser. " + "Has this script been launched from Ghidra?" + ) + logger.debug("Precise import error:", exc_info=importError) + + GLOBALS.running_from_ghidra = False + CancelledException = None + + +def get_repository_root(): + return Path(__file__).absolute().parent.parent.parent + + +def add_python_path(path: str): + """ + Scripts in Ghidra are executed from the tools/ghidra_scripts directory. We need to add + a few more paths to the Python path so we can import the other libraries. + """ + venv_path = get_repository_root().joinpath(path) + logger.info("Adding %s to Python Path", venv_path) + assert venv_path.exists() + sys.path.insert(1, str(venv_path)) + + +# We need to quote the types here because they might not exist when running without Ghidra +def import_function_into_ghidra( + api: "FlatProgramAPI", + pdb_function: "PdbFunction", + type_importer: "PdbTypeImporter", +): + hex_original_address = f"{pdb_function.match_info.orig_addr:x}" + + # Find the Ghidra function at that address + ghidra_address = getAddressFactory().getAddress(hex_original_address) + # pylint: disable=possibly-used-before-assignment + function_importer = PdbFunctionImporter.build(api, pdb_function, type_importer) + + ghidra_function = getFunctionAt(ghidra_address) + if ghidra_function is None: + ghidra_function = createFunction(ghidra_address, "temp") + assert ( + ghidra_function is not None + ), f"Failed to create function at {ghidra_address}" + logger.info("Created new function at %s", ghidra_address) + + logger.debug("Start handling function '%s'", function_importer.get_full_name()) + + if function_importer.matches_ghidra_function(ghidra_function): + logger.info( + "Skipping function '%s', matches already", + function_importer.get_full_name(), + ) + return + + logger.debug( + "Modifying function %s at 0x%s", + function_importer.get_full_name(), + hex_original_address, + ) + + function_importer.overwrite_ghidra_function(ghidra_function) + + GLOBALS.statistics.functions_changed += 1 + + +def process_functions(extraction: "PdbFunctionExtractor"): + pdb_functions = extraction.get_function_list() + + if not GLOBALS.running_from_ghidra: + logger.info("Completed the dry run outside Ghidra.") + return + + api = FlatProgramAPI(currentProgram()) + # pylint: disable=possibly-used-before-assignment + type_importer = PdbTypeImporter(api, extraction) + + for pdb_func in pdb_functions: + func_name = pdb_func.match_info.name + try: + import_function_into_ghidra(api, pdb_func, type_importer) + GLOBALS.statistics.successes += 1 + except Lego1Exception as e: + log_and_track_failure(func_name, e) + except RuntimeError as e: + cause = e.args[0] + if CancelledException is not None and isinstance(cause, CancelledException): + # let Ghidra's CancelledException pass through + logging.critical("Import aborted by the user.") + return + + log_and_track_failure(func_name, cause, unexpected=True) + logger.error(traceback.format_exc()) + except Exception as e: # pylint: disable=broad-exception-caught + log_and_track_failure(func_name, e, unexpected=True) + logger.error(traceback.format_exc()) + + +def log_and_track_failure( + function_name: Optional[str], error: Exception, unexpected: bool = False +): + if GLOBALS.statistics.track_failure_and_tell_if_new(error): + logger.error( + "%s(): %s%s", + function_name, + "Unexpected error: " if unexpected else "", + error, + ) + + +def main(): + if GLOBALS.running_from_ghidra: + origfile_name = getProgramFile().getName() + + if origfile_name == "LEGO1.DLL": + module = SupportedModules.LEGO1 + elif origfile_name in ["LEGO1D.DLL", "BETA10.DLL"]: + module = SupportedModules.BETA10 + else: + raise Lego1Exception( + f"Unsupported file name in import script: {origfile_name}" + ) + else: + module = SupportedModules.LEGO1 + + logger.info("Importing file: %s", module.orig_filename()) + + repo_root = get_repository_root() + origfile_path = repo_root.joinpath("legobin").joinpath(module.orig_filename()) + build_directory = repo_root.joinpath(module.build_dir_name()) + recompiledfile_name = f"{module.recomp_filename_without_extension()}.DLL" + recompiledfile_path = build_directory.joinpath(recompiledfile_name) + pdbfile_name = f"{module.recomp_filename_without_extension()}.PDB" + pdbfile_path = build_directory.joinpath(pdbfile_name) + + if not GLOBALS.verbose: + logging.getLogger("isledecomp.bin").setLevel(logging.WARNING) + logging.getLogger("isledecomp.compare.core").setLevel(logging.WARNING) + logging.getLogger("isledecomp.compare.db").setLevel(logging.WARNING) + logging.getLogger("isledecomp.compare.lines").setLevel(logging.WARNING) + logging.getLogger("isledecomp.cvdump.symbols").setLevel(logging.WARNING) + + logger.info("Starting comparison") + with Bin(str(origfile_path), find_str=True) as origfile, Bin( + str(recompiledfile_path) + ) as recompfile: + isle_compare = IsleCompare( + origfile, recompfile, str(pdbfile_path), str(repo_root) + ) + + logger.info("Comparison complete.") + + # try to acquire matched functions + migration = PdbFunctionExtractor(isle_compare) + try: + process_functions(migration) + finally: + if GLOBALS.running_from_ghidra: + GLOBALS.statistics.log() + + logger.info("Done") + + +# sys.path is not reset after running the script, so we should restore it +sys_path_backup = sys.path.copy() +try: + # make modules installed in the venv available in Ghidra + add_python_path(".venv/Lib/site-packages") + # This one is needed when isledecomp is installed in editable mode in the venv + add_python_path("tools/isledecomp") + + import setuptools # pylint: disable=unused-import # required to fix a distutils issue in Python 3.12 + + reload_module("isledecomp") + from isledecomp import Bin + + reload_module("isledecomp.compare") + from isledecomp.compare import Compare as IsleCompare + + reload_module("isledecomp.compare.db") + + reload_module("lego_util.exceptions") + from lego_util.exceptions import Lego1Exception + + reload_module("lego_util.pdb_extraction") + from lego_util.pdb_extraction import ( + PdbFunctionExtractor, + PdbFunction, + ) + + if GLOBALS.running_from_ghidra: + reload_module("lego_util.ghidra_helper") + + reload_module("lego_util.function_importer") + from lego_util.function_importer import PdbFunctionImporter + + reload_module("lego_util.type_importer") + from lego_util.type_importer import PdbTypeImporter + + if __name__ == "__main__": + main() +finally: + sys.path = sys_path_backup diff --git a/tools/ghidra_scripts/lego_util/__init__.py b/tools/ghidra_scripts/lego_util/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tools/ghidra_scripts/lego_util/exceptions.py b/tools/ghidra_scripts/lego_util/exceptions.py new file mode 100644 index 00000000..1a92ba2a --- /dev/null +++ b/tools/ghidra_scripts/lego_util/exceptions.py @@ -0,0 +1,47 @@ +class Lego1Exception(Exception): + """ + Our own base class for exceptions. + Makes it easier to distinguish expected and unexpected errors. + """ + + +class TypeNotFoundError(Lego1Exception): + def __str__(self): + return f"Type not found in PDB: {self.args[0]}" + + +class TypeNotFoundInGhidraError(Lego1Exception): + def __str__(self): + return f"Type not found in Ghidra: {self.args[0]}" + + +class TypeNotImplementedError(Lego1Exception): + def __str__(self): + return f"Import not implemented for type: {self.args[0]}" + + +class ClassOrNamespaceNotFoundInGhidraError(Lego1Exception): + def __init__(self, namespaceHierachy: list[str]): + super().__init__(namespaceHierachy) + + def get_namespace_str(self) -> str: + return "::".join(self.args[0]) + + def __str__(self): + return f"Class or namespace not found in Ghidra: {self.get_namespace_str()}" + + +class MultipleTypesFoundInGhidraError(Lego1Exception): + def __str__(self): + return ( + f"Found multiple types matching '{self.args[0]}' in Ghidra: {self.args[1]}" + ) + + +class StackOffsetMismatchError(Lego1Exception): + pass + + +class StructModificationError(Lego1Exception): + def __str__(self): + return f"Failed to modify struct in Ghidra: '{self.args[0]}'\nDetailed error: {self.__cause__}" diff --git a/tools/ghidra_scripts/lego_util/function_importer.py b/tools/ghidra_scripts/lego_util/function_importer.py new file mode 100644 index 00000000..45e93c8d --- /dev/null +++ b/tools/ghidra_scripts/lego_util/function_importer.py @@ -0,0 +1,421 @@ +# This file can only be imported successfully when run from Ghidra using Ghidrathon. + +# Disable spurious warnings in vscode / pylance +# pyright: reportMissingModuleSource=false + +import logging +from typing import Optional +from abc import ABC, abstractmethod + +from ghidra.program.model.listing import Function, Parameter +from ghidra.program.flatapi import FlatProgramAPI +from ghidra.program.model.listing import ParameterImpl +from ghidra.program.model.symbol import SourceType +from ghidra.program.model.data import ( + TypeDef, + TypedefDataType, + Pointer, + ComponentOffsetSettingsDefinition, +) + +from lego_util.pdb_extraction import ( + PdbFunction, + CppRegisterSymbol, + CppStackSymbol, +) +from lego_util.ghidra_helper import ( + add_data_type_or_reuse_existing, + create_ghidra_namespace, + get_or_add_pointer_type, + get_ghidra_namespace, + sanitize_name, +) + +from lego_util.exceptions import StackOffsetMismatchError, Lego1Exception +from lego_util.type_importer import PdbTypeImporter + +logger = logging.getLogger(__name__) + + +class PdbFunctionImporter(ABC): + """A representation of a function from the PDB with each type replaced by a Ghidra type instance.""" + + def __init__( + self, + api: FlatProgramAPI, + func: PdbFunction, + type_importer: "PdbTypeImporter", + ): + self.api = api + self.match_info = func.match_info + self.type_importer = type_importer + + assert self.match_info.name is not None + + colon_split = sanitize_name(self.match_info.name).split("::") + self.name = colon_split.pop() + namespace_hierachy = colon_split + self.namespace = self._do_get_namespace(namespace_hierachy) + + def _do_get_namespace(self, namespace_hierarchy: list[str]): + return get_ghidra_namespace(self.api, namespace_hierarchy) + + def get_full_name(self) -> str: + return f"{self.namespace.getName()}::{self.name}" + + @staticmethod + def build(api: FlatProgramAPI, func: PdbFunction, type_importer: "PdbTypeImporter"): + return ( + ThunkPdbFunctionImport(api, func, type_importer) + if func.signature is None + else FullPdbFunctionImporter(api, func, type_importer) + ) + + @abstractmethod + def matches_ghidra_function(self, ghidra_function: Function) -> bool: + ... + + @abstractmethod + def overwrite_ghidra_function(self, ghidra_function: Function): + ... + + +class ThunkPdbFunctionImport(PdbFunctionImporter): + """For importing thunk functions (like vtordisp or debug build thunks) into Ghidra. + Only the name of the function will be imported.""" + + def _do_get_namespace(self, namespace_hierarchy: list[str]): + """We need to create the namespace because we don't import the return type here""" + return create_ghidra_namespace(self.api, namespace_hierarchy) + + def matches_ghidra_function(self, ghidra_function: Function) -> bool: + name_match = self.name == ghidra_function.getName(False) + namespace_match = self.namespace == ghidra_function.getParentNamespace() + + logger.debug("Matches: namespace=%s name=%s", namespace_match, name_match) + + return name_match and namespace_match + + def overwrite_ghidra_function(self, ghidra_function: Function): + ghidra_function.setName(self.name, SourceType.USER_DEFINED) + ghidra_function.setParentNamespace(self.namespace) + + +# pylint: disable=too-many-instance-attributes +class FullPdbFunctionImporter(PdbFunctionImporter): + """For importing functions into Ghidra where all information are available.""" + + def __init__( + self, + api: FlatProgramAPI, + func: PdbFunction, + type_importer: "PdbTypeImporter", + ): + super().__init__(api, func, type_importer) + + assert func.signature is not None + self.signature = func.signature + + self.is_stub = func.is_stub + + if self.signature.class_type is not None: + # Import the base class so the namespace exists + self.type_importer.import_pdb_type_into_ghidra(self.signature.class_type) + + self.return_type = type_importer.import_pdb_type_into_ghidra( + self.signature.return_type + ) + self.arguments = [ + ParameterImpl( + f"param{index}", + type_importer.import_pdb_type_into_ghidra(type_name), + api.getCurrentProgram(), + ) + for (index, type_name) in enumerate(self.signature.arglist) + ] + + def matches_ghidra_function(self, ghidra_function: Function) -> bool: + """Checks whether this function declaration already matches the description in Ghidra""" + name_match = self.name == ghidra_function.getName(False) + namespace_match = self.namespace == ghidra_function.getParentNamespace() + ghidra_return_type = ghidra_function.getReturnType() + return_type_match = self.return_type == ghidra_return_type + + # Handle edge case: Return type X that is larger than the return register. + # In that case, the function returns `X*` and has another argument `X* __return_storage_ptr`. + if ( + (not return_type_match) + and (self.return_type.getLength() > 4) + and ( + get_or_add_pointer_type(self.api, self.return_type) + == ghidra_return_type + ) + and any( + param + for param in ghidra_function.getParameters() + if param.getName() == "__return_storage_ptr__" + ) + ): + logger.debug( + "%s has a return type larger than 4 bytes", self.get_full_name() + ) + return_type_match = True + + # match arguments: decide if thiscall or not, and whether the `this` type matches + calling_convention_match = ( + self.signature.call_type == ghidra_function.getCallingConventionName() + ) + + ghidra_params_without_this = list(ghidra_function.getParameters()) + + if calling_convention_match and self.signature.call_type == "__thiscall": + this_argument = ghidra_params_without_this.pop(0) + calling_convention_match = self._this_type_match(this_argument) + + if self.is_stub: + # We do not import the argument list for stubs, so it should be excluded in matches + args_match = True + elif calling_convention_match: + args_match = self._parameter_lists_match(ghidra_params_without_this) + else: + args_match = False + + logger.debug( + "Matches: namespace=%s name=%s return_type=%s calling_convention=%s args=%s", + namespace_match, + name_match, + return_type_match, + calling_convention_match, + "ignored" if self.is_stub else args_match, + ) + + return ( + name_match + and namespace_match + and return_type_match + and calling_convention_match + and args_match + ) + + def _this_type_match(self, this_parameter: Parameter) -> bool: + if this_parameter.getName() != "this": + logger.info("Expected first argument to be `this` in __thiscall") + return False + + if self.signature.this_adjust != 0: + # In this case, the `this` argument should be custom defined + if not isinstance(this_parameter.getDataType(), TypeDef): + logger.info( + "`this` argument is not a typedef while `this adjust` = %d", + self.signature.this_adjust, + ) + return False + # We are not checking for the _correct_ `this` type here, which we could do in the future + + return True + + def _parameter_lists_match(self, ghidra_params: "list[Parameter]") -> bool: + # Remove return storage pointer from comparison if present. + # This is relevant to returning values larger than 4 bytes, and is not mentioned in the PDB + ghidra_params = [ + param + for param in ghidra_params + if param.getName() != "__return_storage_ptr__" + ] + + if len(self.arguments) != len(ghidra_params): + logger.info("Mismatching argument count") + return False + + for this_arg, ghidra_arg in zip(self.arguments, ghidra_params): + # compare argument types + if this_arg.getDataType() != ghidra_arg.getDataType(): + logger.debug( + "Mismatching arg type: expected %s, found %s", + this_arg.getDataType(), + ghidra_arg.getDataType(), + ) + return False + # compare argument names + stack_match = self.get_matching_stack_symbol(ghidra_arg.getStackOffset()) + if stack_match is None: + logger.debug("Not found on stack: %s", ghidra_arg) + return False + + if stack_match.name.startswith("__formal"): + # "__formal" is the placeholder for arguments without a name + continue + + if stack_match.name == "__$ReturnUdt": + # These appear in templates and cannot be set automatically, as they are a NOTYPE + continue + + if stack_match.name != ghidra_arg.getName(): + logger.debug( + "Argument name mismatch: expected %s, found %s", + stack_match.name, + ghidra_arg.getName(), + ) + return False + return True + + def overwrite_ghidra_function(self, ghidra_function: Function): + """Replace the function declaration in Ghidra by the one derived from C++.""" + + if ghidra_function.hasCustomVariableStorage(): + # Unfortunately, calling `ghidra_function.setCustomVariableStorage(False)` + # leads to two `this` parameters. Therefore, we first need to remove all `this` parameters + # and then re-generate a new one + ghidra_function.replaceParameters( + Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, # this implicitly sets custom variable storage to False + True, + SourceType.USER_DEFINED, + [ + param + for param in ghidra_function.getParameters() + if param.getName() != "this" + ], + ) + + if ghidra_function.hasCustomVariableStorage(): + raise Lego1Exception("Failed to disable custom variable storage.") + + ghidra_function.setName(self.name, SourceType.USER_DEFINED) + ghidra_function.setParentNamespace(self.namespace) + ghidra_function.setReturnType(self.return_type, SourceType.USER_DEFINED) + ghidra_function.setCallingConvention(self.signature.call_type) + + if self.is_stub: + logger.debug( + "%s is a stub, skipping parameter import", self.get_full_name() + ) + else: + ghidra_function.replaceParameters( + Function.FunctionUpdateType.DYNAMIC_STORAGE_ALL_PARAMS, + True, # force + SourceType.USER_DEFINED, + self.arguments, + ) + self._import_parameter_names(ghidra_function) + + # Special handling for `this adjust` and virtual inheritance + if self.signature.this_adjust != 0: + self._set_this_adjust(ghidra_function) + + def _import_parameter_names(self, ghidra_function: Function): + # When we call `ghidra_function.replaceParameters`, Ghidra will generate the layout. + # Now we read the parameters again and match them against the stack layout in the PDB, + # both to verify the layout and to set the parameter names. + ghidra_parameters: list[Parameter] = ghidra_function.getParameters() + + # Try to add Ghidra function names + for index, param in enumerate(ghidra_parameters): + if param.isStackVariable(): + self._rename_stack_parameter(index, param) + else: + if param.getName() == "this": + # 'this' parameters are auto-generated and cannot be changed + continue + + # Appears to never happen - could in theory be relevant to __fastcall__ functions, + # which we haven't seen yet + logger.warning( + "Unhandled register variable in %s", self.get_full_name() + ) + continue + + def _rename_stack_parameter(self, index: int, param: Parameter): + match = self.get_matching_stack_symbol(param.getStackOffset()) + if match is None: + raise StackOffsetMismatchError( + f"Could not find a matching symbol at offset {param.getStackOffset()} in {self.get_full_name()}" + ) + + if match.data_type == "T_NOTYPE(0000)": + logger.warning("Skipping stack parameter of type NOTYPE") + return + + if param.getDataType() != self.type_importer.import_pdb_type_into_ghidra( + match.data_type + ): + logger.error( + "Type mismatch for parameter: %s in Ghidra, %s in PDB", param, match + ) + return + + name = match.name + if name == "__formal": + # these can cause name collisions if multiple ones are present + name = f"__formal_{index}" + + param.setName(name, SourceType.USER_DEFINED) + + def get_matching_stack_symbol(self, stack_offset: int) -> Optional[CppStackSymbol]: + return next( + ( + symbol + for symbol in self.signature.stack_symbols + if isinstance(symbol, CppStackSymbol) + and symbol.stack_offset == stack_offset + ), + None, + ) + + def get_matching_register_symbol( + self, register: str + ) -> Optional[CppRegisterSymbol]: + return next( + ( + symbol + for symbol in self.signature.stack_symbols + if isinstance(symbol, CppRegisterSymbol) and symbol.register == register + ), + None, + ) + + def _set_this_adjust( + self, + ghidra_function: Function, + ): + """ + When `this adjust` is non-zero, the pointer type of `this` needs to be replaced by an offset version. + The offset can only be set on a typedef on the pointer. We also must enable custom storage so we can modify + the auto-generated `this` parameter. + """ + + # Necessary in order to overwite the auto-generated `this` + ghidra_function.setCustomVariableStorage(True) + + this_parameter = next( + ( + param + for param in ghidra_function.getParameters() + if param.isRegisterVariable() and param.getName() == "this" + ), + None, + ) + + if this_parameter is None: + logger.error( + "Failed to find `this` parameter in a function with `this adjust = %d`", + self.signature.this_adjust, + ) + else: + current_ghidra_type = this_parameter.getDataType() + assert isinstance(current_ghidra_type, Pointer) + class_name = current_ghidra_type.getDataType().getName() + typedef_name = f"{class_name}PtrOffset0x{self.signature.this_adjust:x}" + + typedef_ghidra_type = TypedefDataType( + current_ghidra_type.getCategoryPath(), + typedef_name, + current_ghidra_type, + ) + ComponentOffsetSettingsDefinition.DEF.setValue( + typedef_ghidra_type.getDefaultSettings(), self.signature.this_adjust + ) + typedef_ghidra_type = add_data_type_or_reuse_existing( + self.api, typedef_ghidra_type + ) + + this_parameter.setDataType(typedef_ghidra_type, SourceType.USER_DEFINED) diff --git a/tools/ghidra_scripts/lego_util/ghidra_helper.py b/tools/ghidra_scripts/lego_util/ghidra_helper.py new file mode 100644 index 00000000..24e70c65 --- /dev/null +++ b/tools/ghidra_scripts/lego_util/ghidra_helper.py @@ -0,0 +1,122 @@ +"""A collection of helper functions for the interaction with Ghidra.""" + +import logging +import re + +from lego_util.exceptions import ( + ClassOrNamespaceNotFoundInGhidraError, + TypeNotFoundInGhidraError, + MultipleTypesFoundInGhidraError, +) + +# Disable spurious warnings in vscode / pylance +# pyright: reportMissingModuleSource=false + +from ghidra.program.flatapi import FlatProgramAPI +from ghidra.program.model.data import DataType, DataTypeConflictHandler, PointerDataType +from ghidra.program.model.symbol import Namespace + +logger = logging.getLogger(__name__) + + +def get_ghidra_type(api: FlatProgramAPI, type_name: str): + """ + Searches for the type named `typeName` in Ghidra. + + Raises: + - NotFoundInGhidraError + - MultipleTypesFoundInGhidraError + """ + result = api.getDataTypes(type_name) + if len(result) == 0: + raise TypeNotFoundInGhidraError(type_name) + if len(result) == 1: + return result[0] + + raise MultipleTypesFoundInGhidraError(type_name, result) + + +def get_or_add_pointer_type(api: FlatProgramAPI, pointee: DataType) -> DataType: + new_pointer_data_type = PointerDataType(pointee) + new_pointer_data_type.setCategoryPath(pointee.getCategoryPath()) + return add_data_type_or_reuse_existing(api, new_pointer_data_type) + + +def add_data_type_or_reuse_existing( + api: FlatProgramAPI, new_data_type: DataType +) -> DataType: + result_data_type = ( + api.getCurrentProgram() + .getDataTypeManager() + .addDataType(new_data_type, DataTypeConflictHandler.KEEP_HANDLER) + ) + if result_data_type is not new_data_type: + logger.debug( + "Reusing existing data type instead of new one: %s (class: %s)", + result_data_type, + result_data_type.__class__, + ) + return result_data_type + + +def get_ghidra_namespace( + api: FlatProgramAPI, namespace_hierachy: list[str] +) -> Namespace: + namespace = api.getCurrentProgram().getGlobalNamespace() + for part in namespace_hierachy: + namespace = api.getNamespace(namespace, part) + if namespace is None: + raise ClassOrNamespaceNotFoundInGhidraError(namespace_hierachy) + return namespace + + +def create_ghidra_namespace( + api: FlatProgramAPI, namespace_hierachy: list[str] +) -> Namespace: + namespace = api.getCurrentProgram().getGlobalNamespace() + for part in namespace_hierachy: + namespace = api.getNamespace(namespace, part) + if namespace is None: + namespace = api.createNamespace(namespace, part) + return namespace + + +# These appear in debug builds +THUNK_OF_RE = re.compile(r"^Thunk of '(.*)'$") + + +def sanitize_name(name: str) -> str: + """ + Takes a full class or function name and replaces characters not accepted by Ghidra. + Applies mostly to templates, names like `vbase destructor`, and thunks in debug build. + """ + if (match := THUNK_OF_RE.fullmatch(name)) is not None: + is_thunk = True + name = match.group(1) + else: + is_thunk = False + + # Replace characters forbidden in Ghidra + new_name = ( + name.replace("<", "[") + .replace(">", "]") + .replace("*", "#") + .replace(" ", "_") + .replace("`", "'") + ) + + if "<" in name: + new_name = "_template_" + new_name + + if is_thunk: + split = new_name.split("::") + split[-1] = "_thunk_" + split[-1] + new_name = "::".join(split) + + if new_name != name: + logger.info( + "Changed class or function name from '%s' to '%s' to avoid Ghidra issues", + name, + new_name, + ) + return new_name diff --git a/tools/ghidra_scripts/lego_util/headers.pyi b/tools/ghidra_scripts/lego_util/headers.pyi new file mode 100644 index 00000000..b1655437 --- /dev/null +++ b/tools/ghidra_scripts/lego_util/headers.pyi @@ -0,0 +1,20 @@ +from typing import TypeVar, Any +import ghidra + +# pylint: disable=invalid-name,unused-argument + +T = TypeVar("T") + +# from ghidra.app.script.GhidraScript +def currentProgram() -> "ghidra.program.model.listing.Program": ... +def getAddressFactory() -> " ghidra.program.model.address.AddressFactory": ... +def state() -> "ghidra.app.script.GhidraState": ... +def askChoice(title: str, message: str, choices: list[T], defaultValue: T) -> T: ... +def askYesNo(title: str, question: str) -> bool: ... +def getFunctionAt( + entryPoint: ghidra.program.model.address.Address, +) -> ghidra.program.model.listing.Function: ... +def createFunction( + entryPoint: ghidra.program.model.address.Address, name: str +) -> ghidra.program.model.listing.Function: ... +def getProgramFile() -> Any: ... # actually java.io.File diff --git a/tools/ghidra_scripts/lego_util/pdb_extraction.py b/tools/ghidra_scripts/lego_util/pdb_extraction.py new file mode 100644 index 00000000..6a0db6f8 --- /dev/null +++ b/tools/ghidra_scripts/lego_util/pdb_extraction.py @@ -0,0 +1,182 @@ +from dataclasses import dataclass +import re +from typing import Any, Optional +import logging + +from isledecomp.bin import InvalidVirtualAddressError +from isledecomp.cvdump.symbols import SymbolsEntry +from isledecomp.compare import Compare as IsleCompare +from isledecomp.compare.db import MatchInfo + +logger = logging.getLogger(__file__) + + +@dataclass +class CppStackOrRegisterSymbol: + name: str + data_type: str + + +@dataclass +class CppStackSymbol(CppStackOrRegisterSymbol): + stack_offset: int + """Should have a value iff `symbol_type=='S_BPREL32'.""" + + +@dataclass +class CppRegisterSymbol(CppStackOrRegisterSymbol): + register: str + """Should have a value iff `symbol_type=='S_REGISTER'.` Should always be set/converted to lowercase.""" + + +@dataclass +class FunctionSignature: + original_function_symbol: SymbolsEntry + call_type: str + arglist: list[str] + return_type: str + class_type: Optional[str] + stack_symbols: list[CppStackOrRegisterSymbol] + # if non-zero: an offset to the `this` parameter in a __thiscall + this_adjust: int + + +@dataclass +class PdbFunction: + match_info: MatchInfo + signature: Optional[FunctionSignature] + is_stub: bool + + +class PdbFunctionExtractor: + """ + Extracts all information on a given function from the parsed PDB + and prepares the data for the import in Ghidra. + """ + + def __init__(self, compare: IsleCompare): + self.compare = compare + + scalar_type_regex = re.compile(r"t_(?P\w+)(?:\((?P\d+)\))?") + + _call_type_map = { + "ThisCall": "__thiscall", + "C Near": "default", + "STD Near": "__stdcall", + } + + def _get_cvdump_type(self, type_name: Optional[str]) -> Optional[dict[str, Any]]: + return ( + None + if type_name is None + else self.compare.cv.types.keys.get(type_name.lower()) + ) + + def get_func_signature(self, fn: SymbolsEntry) -> Optional[FunctionSignature]: + function_type_str = fn.func_type + if function_type_str == "T_NOTYPE(0000)": + logger.debug("Treating NOTYPE function as thunk: %s", fn.name) + return None + + # get corresponding function type + + function_type = self.compare.cv.types.keys.get(function_type_str.lower()) + if function_type is None: + logger.error( + "Could not find function type %s for function %s", fn.func_type, fn.name + ) + return None + + class_type = function_type.get("class_type") + + arg_list_type = self._get_cvdump_type(function_type.get("arg_list_type")) + assert arg_list_type is not None + arg_list_pdb_types = arg_list_type.get("args", []) + assert arg_list_type["argcount"] == len(arg_list_pdb_types) + + stack_symbols: list[CppStackOrRegisterSymbol] = [] + + # for some unexplained reason, the reported stack is offset by 4 when this flag is set + stack_offset_delta = -4 if fn.frame_pointer_present else 0 + + for symbol in fn.stack_symbols: + if symbol.symbol_type == "S_REGISTER": + stack_symbols.append( + CppRegisterSymbol( + symbol.name, + symbol.data_type, + symbol.location, + ) + ) + elif symbol.symbol_type == "S_BPREL32": + stack_offset = int(symbol.location[1:-1], 16) + stack_symbols.append( + CppStackSymbol( + symbol.name, + symbol.data_type, + stack_offset + stack_offset_delta, + ) + ) + + call_type = self._call_type_map[function_type["call_type"]] + + # parse as hex number, default to 0 + this_adjust = int(function_type.get("this_adjust", "0"), 16) + + return FunctionSignature( + original_function_symbol=fn, + call_type=call_type, + arglist=arg_list_pdb_types, + return_type=function_type["return_type"], + class_type=class_type, + stack_symbols=stack_symbols, + this_adjust=this_adjust, + ) + + def get_function_list(self) -> list[PdbFunction]: + handled = ( + self.handle_matched_function(match) + for match in self.compare.get_functions() + ) + return [signature for signature in handled if signature is not None] + + def handle_matched_function(self, match_info: MatchInfo) -> Optional[PdbFunction]: + assert match_info.orig_addr is not None + match_options = self.compare.get_match_options(match_info.orig_addr) + assert match_options is not None + + function_data = next( + ( + y + for y in self.compare.cvdump_analysis.nodes + if y.addr == match_info.recomp_addr + ), + None, + ) + if function_data is None: + try: + # this can be either a thunk (which we want) or an external function + # (which we don't want), so we tell them apart based on the validity of their address. + self.compare.orig_bin.get_relative_addr(match_info.orig_addr) + return PdbFunction(match_info, None, False) + except InvalidVirtualAddressError: + logger.debug( + "Skipping external function %s (address 0x%x not in original binary)", + match_info.name, + match_info.orig_addr, + ) + return None + + function_symbol = function_data.symbol_entry + if function_symbol is None: + logger.debug( + "Could not find function symbol (likely a PUBLICS entry): %s", + match_info.name, + ) + return None + + function_signature = self.get_func_signature(function_symbol) + + is_stub = match_options.get("stub", False) + + return PdbFunction(match_info, function_signature, is_stub) diff --git a/tools/ghidra_scripts/lego_util/statistics.py b/tools/ghidra_scripts/lego_util/statistics.py new file mode 100644 index 00000000..02232b01 --- /dev/null +++ b/tools/ghidra_scripts/lego_util/statistics.py @@ -0,0 +1,68 @@ +from dataclasses import dataclass, field +import logging + +from lego_util.exceptions import ( + TypeNotFoundInGhidraError, + ClassOrNamespaceNotFoundInGhidraError, +) + +logger = logging.getLogger(__name__) + + +@dataclass +class Statistics: + functions_changed: int = 0 + successes: int = 0 + failures: dict[str, int] = field(default_factory=dict) + known_missing_types: dict[str, int] = field(default_factory=dict) + known_missing_namespaces: dict[str, int] = field(default_factory=dict) + + def track_failure_and_tell_if_new(self, error: Exception) -> bool: + """ + Adds the error to the statistics. Returns `False` if logging the error would be redundant + (e.g. because it is a `TypeNotFoundInGhidraError` with a type that has been logged before). + """ + error_type_name = error.__class__.__name__ + self.failures[error_type_name] = ( + self.failures.setdefault(error_type_name, 0) + 1 + ) + + if isinstance(error, TypeNotFoundInGhidraError): + return self._add_occurence_and_check_if_new( + self.known_missing_types, error.args[0] + ) + + if isinstance(error, ClassOrNamespaceNotFoundInGhidraError): + return self._add_occurence_and_check_if_new( + self.known_missing_namespaces, error.get_namespace_str() + ) + + # We do not have detailed tracking for other errors, so we want to log them every time + return True + + def _add_occurence_and_check_if_new(self, target: dict[str, int], key: str) -> bool: + old_count = target.setdefault(key, 0) + target[key] = old_count + 1 + return old_count == 0 + + def log(self): + logger.info("Statistics:\n~~~~~") + logger.info( + "Missing types (with number of occurences): %s\n~~~~~", + self.format_statistics(self.known_missing_types), + ) + logger.info( + "Missing classes/namespaces (with number of occurences): %s\n~~~~~", + self.format_statistics(self.known_missing_namespaces), + ) + logger.info("Successes: %d", self.successes) + logger.info("Failures: %s", self.failures) + logger.info("Functions changed: %d", self.functions_changed) + + def format_statistics(self, stats: dict[str, int]) -> str: + if len(stats) == 0: + return "" + return ", ".join( + f"{entry[0]} ({entry[1]})" + for entry in sorted(stats.items(), key=lambda x: x[1], reverse=True) + ) diff --git a/tools/ghidra_scripts/lego_util/type_importer.py b/tools/ghidra_scripts/lego_util/type_importer.py new file mode 100644 index 00000000..1f4a077e --- /dev/null +++ b/tools/ghidra_scripts/lego_util/type_importer.py @@ -0,0 +1,541 @@ +import logging +from typing import Any, Callable, Iterator, Optional, TypeVar + +# Disable spurious warnings in vscode / pylance +# pyright: reportMissingModuleSource=false + +# pylint: disable=too-many-return-statements # a `match` would be better, but for now we are stuck with Python 3.9 +# pylint: disable=no-else-return # Not sure why this rule even is a thing, this is great for checking exhaustiveness + +from isledecomp.cvdump.types import VirtualBasePointer +from lego_util.exceptions import ( + ClassOrNamespaceNotFoundInGhidraError, + TypeNotFoundError, + TypeNotFoundInGhidraError, + TypeNotImplementedError, + StructModificationError, +) +from lego_util.ghidra_helper import ( + add_data_type_or_reuse_existing, + get_or_add_pointer_type, + create_ghidra_namespace, + get_ghidra_namespace, + get_ghidra_type, + sanitize_name, +) +from lego_util.pdb_extraction import PdbFunctionExtractor + +from ghidra.program.flatapi import FlatProgramAPI +from ghidra.program.model.data import ( + ArrayDataType, + CategoryPath, + DataType, + DataTypeConflictHandler, + Enum, + EnumDataType, + StructureDataType, + StructureInternal, + TypedefDataType, + ComponentOffsetSettingsDefinition, +) +from ghidra.util.task import ConsoleTaskMonitor + + +logger = logging.getLogger(__name__) + + +class PdbTypeImporter: + """Allows PDB types to be imported into Ghidra.""" + + def __init__(self, api: FlatProgramAPI, extraction: PdbFunctionExtractor): + self.api = api + self.extraction = extraction + # tracks the structs/classes we have already started to import, otherwise we run into infinite recursion + self.handled_structs: set[str] = set() + + # tracks the enums we have already handled for the sake of efficiency + self.handled_enums: dict[str, Enum] = {} + + @property + def types(self): + return self.extraction.compare.cv.types + + def import_pdb_type_into_ghidra( + self, type_index: str, slim_for_vbase: bool = False + ) -> DataType: + """ + Recursively imports a type from the PDB into Ghidra. + @param type_index Either a scalar type like `T_INT4(...)` or a PDB reference like `0x10ba` + @param slim_for_vbase If true, the current invocation + imports a superclass of some class where virtual inheritance is involved (directly or indirectly). + This case requires special handling: Let's say we have `class C: B` and `class B: virtual A`. Then cvdump + reports a size for B that includes both B's fields as well as the A contained at an offset within B, + which is not the correct structure to be contained in C. Therefore, we need to create a "slim" version of B + that fits inside C. + This value should always be `False` when the referenced type is not (a pointer to) a class. + """ + type_index_lower = type_index.lower() + if type_index_lower.startswith("t_"): + return self._import_scalar_type(type_index_lower) + + try: + type_pdb = self.extraction.compare.cv.types.keys[type_index_lower] + except KeyError as e: + raise TypeNotFoundError( + f"Failed to find referenced type '{type_index_lower}'" + ) from e + + type_category = type_pdb["type"] + + # follow forward reference (class, struct, union) + if type_pdb.get("is_forward_ref", False): + return self._import_forward_ref_type( + type_index_lower, type_pdb, slim_for_vbase + ) + + if type_category == "LF_POINTER": + return get_or_add_pointer_type( + self.api, + self.import_pdb_type_into_ghidra( + type_pdb["element_type"], slim_for_vbase + ), + ) + elif type_category in ["LF_CLASS", "LF_STRUCTURE"]: + return self._import_class_or_struct(type_pdb, slim_for_vbase) + elif type_category == "LF_ARRAY": + return self._import_array(type_pdb) + elif type_category == "LF_ENUM": + return self._import_enum(type_pdb) + elif type_category == "LF_PROCEDURE": + logger.warning( + "Not implemented: Function-valued argument or return type will be replaced by void pointer: %s", + type_pdb, + ) + return get_ghidra_type(self.api, "void") + elif type_category == "LF_UNION": + return self._import_union(type_pdb) + else: + raise TypeNotImplementedError(type_pdb) + + _scalar_type_map = { + "rchar": "char", + "int4": "int", + "uint4": "uint", + "real32": "float", + "real64": "double", + } + + def _scalar_type_to_cpp(self, scalar_type: str) -> str: + if scalar_type.startswith("32p"): + return f"{self._scalar_type_to_cpp(scalar_type[3:])} *" + return self._scalar_type_map.get(scalar_type, scalar_type) + + def _import_scalar_type(self, type_index_lower: str) -> DataType: + if (match := self.extraction.scalar_type_regex.match(type_index_lower)) is None: + raise TypeNotFoundError(f"Type has unexpected format: {type_index_lower}") + + scalar_cpp_type = self._scalar_type_to_cpp(match.group("typename")) + return get_ghidra_type(self.api, scalar_cpp_type) + + def _import_forward_ref_type( + self, + type_index, + type_pdb: dict[str, Any], + slim_for_vbase: bool = False, + ) -> DataType: + referenced_type = type_pdb.get("udt") or type_pdb.get("modifies") + if referenced_type is None: + try: + # Example: HWND__, needs to be created manually + return get_ghidra_type(self.api, type_pdb["name"]) + except TypeNotFoundInGhidraError as e: + raise TypeNotImplementedError( + f"{type_index}: forward ref without target, needs to be created manually: {type_pdb}" + ) from e + logger.debug( + "Following forward reference from %s to %s", + type_index, + referenced_type, + ) + return self.import_pdb_type_into_ghidra(referenced_type, slim_for_vbase) + + def _import_array(self, type_pdb: dict[str, Any]) -> DataType: + inner_type = self.import_pdb_type_into_ghidra(type_pdb["array_type"]) + + array_total_bytes: int = type_pdb["size"] + data_type_size = inner_type.getLength() + array_length, modulus = divmod(array_total_bytes, data_type_size) + assert ( + modulus == 0 + ), f"Data type size {data_type_size} does not divide array size {array_total_bytes}" + + return ArrayDataType(inner_type, array_length, 0) + + def _import_union(self, type_pdb: dict[str, Any]) -> DataType: + try: + logger.debug("Dereferencing union %s", type_pdb) + union_type = get_ghidra_type(self.api, type_pdb["name"]) + assert ( + union_type.getLength() == type_pdb["size"] + ), f"Wrong size of existing union type '{type_pdb['name']}': expected {type_pdb['size']}, got {union_type.getLength()}" + return union_type + except TypeNotFoundInGhidraError as e: + # We have so few instances, it is not worth implementing this + raise TypeNotImplementedError( + f"Writing union types is not supported. Please add by hand: {type_pdb}" + ) from e + + def _import_enum(self, type_pdb: dict[str, Any]) -> DataType: + underlying_type = self.import_pdb_type_into_ghidra(type_pdb["underlying_type"]) + field_list = self.extraction.compare.cv.types.keys.get(type_pdb["field_type"]) + assert field_list is not None, f"Failed to find field list for enum {type_pdb}" + + result = self._get_or_create_enum_data_type( + type_pdb["name"], underlying_type.getLength() + ) + # clear existing variant if there are any + for existing_variant in result.getNames(): + result.remove(existing_variant) + + variants: list[dict[str, Any]] = field_list["variants"] + for variant in variants: + result.add(variant["name"], variant["value"]) + + return result + + def _import_class_or_struct( + self, + type_in_pdb: dict[str, Any], + slim_for_vbase: bool = False, + ) -> DataType: + field_list_type: str = type_in_pdb["field_list_type"] + field_list = self.types.keys[field_list_type.lower()] + + class_size: int = type_in_pdb["size"] + class_name_with_namespace: str = sanitize_name(type_in_pdb["name"]) + if slim_for_vbase: + class_name_with_namespace += "_vbase_slim" + + if class_name_with_namespace in self.handled_structs: + logger.debug( + "Class has been handled or is being handled: %s", + class_name_with_namespace, + ) + return get_ghidra_type(self.api, class_name_with_namespace) + + logger.debug( + "--- Beginning to import class/struct '%s'", class_name_with_namespace + ) + + # Add as soon as we start to avoid infinite recursion + self.handled_structs.add(class_name_with_namespace) + + self._get_or_create_namespace(class_name_with_namespace) + + new_ghidra_struct = self._get_or_create_struct_data_type( + class_name_with_namespace, class_size + ) + + if (old_size := new_ghidra_struct.getLength()) != class_size: + logger.warning( + "Existing class %s had incorrect size %d. Setting to %d...", + class_name_with_namespace, + old_size, + class_size, + ) + + logger.info("Adding class data type %s", class_name_with_namespace) + logger.debug("Class information: %s", type_in_pdb) + + components: list[dict[str, Any]] = [] + components.extend(self._get_components_from_base_classes(field_list)) + # can be missing when no new fields are declared + components.extend(self._get_components_from_members(field_list)) + components.extend( + self._get_components_from_vbase( + field_list, class_name_with_namespace, new_ghidra_struct + ) + ) + + components.sort(key=lambda c: c["offset"]) + + if slim_for_vbase: + # Make a "slim" version: shrink the size to the fields that are actually present. + # This makes a difference when the current class uses virtual inheritance + assert ( + len(components) > 0 + ), f"Error: {class_name_with_namespace} should not be empty. There must be at least one direct or indirect vbase pointer." + last_component = components[-1] + class_size = last_component["offset"] + last_component["type"].getLength() + + self._overwrite_struct( + class_name_with_namespace, + new_ghidra_struct, + class_size, + components, + ) + + logger.info("Finished importing class %s", class_name_with_namespace) + + return new_ghidra_struct + + def _get_components_from_base_classes(self, field_list) -> Iterator[dict[str, Any]]: + non_virtual_base_classes: dict[str, int] = field_list.get("super", {}) + + for super_type, offset in non_virtual_base_classes.items(): + # If we have virtual inheritance _and_ a non-virtual base class here, we play safe and import slim version. + # This is technically not needed if only one of the superclasses uses virtual inheritance, but I am not aware of any instance. + import_slim_vbase_version_of_superclass = "vbase" in field_list + ghidra_type = self.import_pdb_type_into_ghidra( + super_type, slim_for_vbase=import_slim_vbase_version_of_superclass + ) + + yield { + "type": ghidra_type, + "offset": offset, + "name": "base" if offset == 0 else f"base_{ghidra_type.getName()}", + } + + def _get_components_from_members(self, field_list: dict[str, Any]): + members: list[dict[str, Any]] = field_list.get("members") or [] + for member in members: + yield member | {"type": self.import_pdb_type_into_ghidra(member["type"])} + + def _get_components_from_vbase( + self, + field_list: dict[str, Any], + class_name_with_namespace: str, + current_type: StructureInternal, + ) -> Iterator[dict[str, Any]]: + vbasepointer: Optional[VirtualBasePointer] = field_list.get("vbase", None) + + if vbasepointer is not None and any(x.direct for x in vbasepointer.bases): + vbaseptr_type = get_or_add_pointer_type( + self.api, + self._import_vbaseptr( + current_type, class_name_with_namespace, vbasepointer + ), + ) + yield { + "type": vbaseptr_type, + "offset": vbasepointer.vboffset, + "name": "vbase_offset", + } + + def _import_vbaseptr( + self, + current_type: StructureInternal, + class_name_with_namespace: str, + vbasepointer: VirtualBasePointer, + ) -> StructureInternal: + pointer_size = 4 # hard-code to 4 because of 32 bit + + components = [ + { + "offset": 0, + "type": get_or_add_pointer_type(self.api, current_type), + "name": "o_self", + } + ] + for vbase in vbasepointer.bases: + vbase_ghidra_type = self.import_pdb_type_into_ghidra(vbase.type) + + type_name = vbase_ghidra_type.getName() + + vbase_ghidra_pointer = get_or_add_pointer_type(self.api, vbase_ghidra_type) + vbase_ghidra_pointer_typedef = TypedefDataType( + vbase_ghidra_pointer.getCategoryPath(), + f"{type_name}PtrOffset", + vbase_ghidra_pointer, + ) + # Set a default value of -4 for the pointer offset. While this appears to be correct in many cases, + # it does not always lead to the best decompile. It can be fine-tuned by hand; the next function call + # makes sure that we don't overwrite this value on re-running the import. + ComponentOffsetSettingsDefinition.DEF.setValue( + vbase_ghidra_pointer_typedef.getDefaultSettings(), -4 + ) + + vbase_ghidra_pointer_typedef = add_data_type_or_reuse_existing( + self.api, vbase_ghidra_pointer_typedef + ) + + components.append( + { + "offset": vbase.index * pointer_size, + "type": vbase_ghidra_pointer_typedef, + "name": f"o_{type_name}", + } + ) + + size = len(components) * pointer_size + + new_ghidra_struct = self._get_or_create_struct_data_type( + f"{class_name_with_namespace}::VBasePtr", size + ) + + self._overwrite_struct( + f"{class_name_with_namespace}::VBasePtr", + new_ghidra_struct, + size, + components, + ) + + return new_ghidra_struct + + def _overwrite_struct( + self, + class_name_with_namespace: str, + new_ghidra_struct: StructureInternal, + class_size: int, + components: list[dict[str, Any]], + ): + new_ghidra_struct.deleteAll() + new_ghidra_struct.growStructure(class_size) + + # this case happened e.g. for IUnknown, which linked to an (incorrect) existing library, and some other types as well. + # Unfortunately, we don't get proper error handling for read-only types. + # However, we really do NOT want to do this every time because the type might be self-referential and partially imported. + if new_ghidra_struct.getLength() != class_size: + new_ghidra_struct = self._delete_and_recreate_struct_data_type( + class_name_with_namespace, class_size, new_ghidra_struct + ) + + for component in components: + offset: int = component["offset"] + logger.debug( + "Adding component %s to class: %s", component, class_name_with_namespace + ) + + try: + # Make sure there is room for the new structure and that we have no collision. + existing_type = new_ghidra_struct.getComponentAt(offset) + assert ( + existing_type is not None + ), f"Struct collision: Offset {offset} in {class_name_with_namespace} is overlapped by another component" + + if existing_type.getDataType().getName() != "undefined": + # collision of structs beginning in the same place -> likely due to unions + logger.warning( + "Struct collision: Offset %d of %s already has a field (likely an inline union)", + offset, + class_name_with_namespace, + ) + + new_ghidra_struct.replaceAtOffset( + offset, + component["type"], + -1, # set to -1 for fixed-size components + component["name"], # name + None, # comment + ) + except Exception as e: + raise StructModificationError(class_name_with_namespace) from e + + def _get_or_create_namespace(self, class_name_with_namespace: str): + colon_split = class_name_with_namespace.split("::") + class_name = colon_split[-1] + try: + get_ghidra_namespace(self.api, colon_split) + logger.debug("Found existing class/namespace %s", class_name_with_namespace) + except ClassOrNamespaceNotFoundInGhidraError: + logger.info("Creating class/namespace %s", class_name_with_namespace) + class_name = colon_split.pop() + parent_namespace = create_ghidra_namespace(self.api, colon_split) + self.api.createClass(parent_namespace, class_name) + + def _get_or_create_enum_data_type( + self, enum_type_name: str, enum_type_size: int + ) -> Enum: + if (known_enum := self.handled_enums.get(enum_type_name, None)) is not None: + return known_enum + + result = self._get_or_create_data_type( + enum_type_name, + "enum", + Enum, + lambda: EnumDataType( + CategoryPath("/imported"), enum_type_name, enum_type_size + ), + ) + self.handled_enums[enum_type_name] = result + return result + + def _get_or_create_struct_data_type( + self, class_name_with_namespace: str, class_size: int + ) -> StructureInternal: + return self._get_or_create_data_type( + class_name_with_namespace, + "class/struct", + StructureInternal, + lambda: StructureDataType( + CategoryPath("/imported"), class_name_with_namespace, class_size + ), + ) + + T = TypeVar("T", bound=DataType) + + def _get_or_create_data_type( + self, + type_name: str, + readable_name_of_type_category: str, + expected_type: type[T], + new_instance_callback: Callable[[], T], + ) -> T: + """ + Checks if a data type provided under the given name exists in Ghidra. + Creates one using `new_instance_callback` if there is not. + Also verifies the data type. + + Note that the return value of `addDataType()` is not the same instance as the input + even if there is no name collision. + """ + try: + data_type = get_ghidra_type(self.api, type_name) + logger.debug( + "Found existing %s type %s under category path %s", + readable_name_of_type_category, + type_name, + data_type.getCategoryPath(), + ) + except TypeNotFoundInGhidraError: + data_type = ( + self.api.getCurrentProgram() + .getDataTypeManager() + .addDataType( + new_instance_callback(), DataTypeConflictHandler.KEEP_HANDLER + ) + ) + logger.info( + "Created new %s data type %s", readable_name_of_type_category, type_name + ) + assert isinstance( + data_type, expected_type + ), f"Found existing type named {type_name} that is not a {readable_name_of_type_category}" + return data_type + + def _delete_and_recreate_struct_data_type( + self, + class_name_with_namespace: str, + class_size: int, + existing_data_type: DataType, + ) -> StructureInternal: + logger.warning( + "Failed to modify data type %s. Will try to delete the existing one and re-create the imported one.", + class_name_with_namespace, + ) + + assert ( + self.api.getCurrentProgram() + .getDataTypeManager() + .remove(existing_data_type, ConsoleTaskMonitor()) + ), f"Failed to delete and re-create data type {class_name_with_namespace}" + data_type = StructureDataType( + CategoryPath("/imported"), class_name_with_namespace, class_size + ) + data_type = ( + self.api.getCurrentProgram() + .getDataTypeManager() + .addDataType(data_type, DataTypeConflictHandler.KEEP_HANDLER) + ) + assert isinstance(data_type, StructureInternal) # for type checking + return data_type diff --git a/tools/isledecomp/.gitignore b/tools/isledecomp/.gitignore new file mode 100644 index 00000000..a7c50887 --- /dev/null +++ b/tools/isledecomp/.gitignore @@ -0,0 +1,2 @@ +isledecomp.egg-info/ +build \ No newline at end of file diff --git a/tools/isledecomp/isledecomp/__init__.py b/tools/isledecomp/isledecomp/__init__.py new file mode 100644 index 00000000..59c55869 --- /dev/null +++ b/tools/isledecomp/isledecomp/__init__.py @@ -0,0 +1,4 @@ +from .bin import * +from .dir import * +from .parser import * +from .utils import * diff --git a/tools/isledecomp/isledecomp/bin.py b/tools/isledecomp/isledecomp/bin.py new file mode 100644 index 00000000..2f20224b --- /dev/null +++ b/tools/isledecomp/isledecomp/bin.py @@ -0,0 +1,574 @@ +import logging +import struct +import bisect +from functools import cached_property +from typing import Iterator, List, Optional, Tuple +from dataclasses import dataclass +from collections import namedtuple + + +class MZHeaderNotFoundError(Exception): + """MZ magic string not found at the start of the binary.""" + + +class PEHeaderNotFoundError(Exception): + """PE magic string not found at the offset given in 0x3c.""" + + +class SectionNotFoundError(KeyError): + """The specified section was not found in the file.""" + + +class InvalidVirtualAddressError(IndexError): + """The given virtual address is too high or low + to point to something in the binary file.""" + + +PEHeader = namedtuple( + "PEHeader", + [ + "Signature", + "Machine", + "NumberOfSections", + "TimeDateStamp", + "PointerToSymbolTable", # deprecated + "NumberOfSymbols", # deprecated + "SizeOfOptionalHeader", + "Characteristics", + ], +) + +ImageSectionHeader = namedtuple( + "ImageSectionHeader", + [ + "name", + "virtual_size", + "virtual_address", + "size_of_raw_data", + "pointer_to_raw_data", + "pointer_to_relocations", + "pointer_to_line_numbers", + "number_of_relocations", + "number_of_line_numbers", + "characteristics", + ], +) + + +@dataclass +class Section: + name: str + virtual_size: int + virtual_address: int + view: memoryview + + @cached_property + def size_of_raw_data(self) -> int: + return len(self.view) + + @cached_property + def extent(self): + """Get the highest possible offset of this section""" + return max(self.size_of_raw_data, self.virtual_size) + + def match_name(self, name: str) -> bool: + return self.name == name + + def contains_vaddr(self, vaddr: int) -> bool: + return self.virtual_address <= vaddr < self.virtual_address + self.extent + + def read_virtual(self, vaddr: int, size: int) -> memoryview: + ofs = vaddr - self.virtual_address + + # Negative index will read from the end, which we don't want + if ofs < 0: + raise InvalidVirtualAddressError + + try: + return self.view[ofs : ofs + size] + except IndexError as ex: + raise InvalidVirtualAddressError from ex + + def addr_is_uninitialized(self, vaddr: int) -> bool: + """We cannot rely on the IMAGE_SCN_CNT_UNINITIALIZED_DATA flag (0x80) in + the characteristics field so instead we determine it this way.""" + if not self.contains_vaddr(vaddr): + return False + + # Should include the case where size_of_raw_data == 0, + # meaning the entire section is uninitialized + return (self.virtual_size > self.size_of_raw_data) and ( + vaddr - self.virtual_address >= self.size_of_raw_data + ) + + +logger = logging.getLogger(__name__) + + +class Bin: + """Parses a PE format EXE and allows reading data from a virtual address. + Reference: https://learn.microsoft.com/en-us/windows/win32/debug/pe-format""" + + # pylint: disable=too-many-instance-attributes + + def __init__(self, filename: str, find_str: bool = False) -> None: + logger.debug('Parsing headers of "%s"... ', filename) + self.filename = filename + self.view: memoryview = None + self.imagebase = None + self.entry = None + self.sections: List[Section] = [] + self._section_vaddr: List[int] = [] + self.find_str = find_str + self._potential_strings = {} + self._relocations = set() + self._relocated_addrs = set() + self.imports = [] + self.thunks = [] + self.exports: List[Tuple[int, str]] = [] + self.is_debug: bool = False + + def __enter__(self): + logger.debug("Bin %s Enter", self.filename) + with open(self.filename, "rb") as f: + self.view = memoryview(f.read()) + + (mz_str,) = struct.unpack("2s", self.view[0:2]) + if mz_str != b"MZ": + raise MZHeaderNotFoundError + + # Skip to PE header offset in MZ header. + (pe_header_start,) = struct.unpack(" List[int]: + return sorted(self._relocated_addrs) + + def find_string(self, target: str) -> Optional[int]: + # Pad with null terminator to make sure we don't + # match on a subset of the full string + if not target.endswith(b"\x00"): + target += b"\x00" + + c = target[0] + if c not in self._potential_strings: + return None + + for addr in self._potential_strings[c]: + if target == self.read(addr, len(target)): + return addr + + return None + + def is_relocated_addr(self, vaddr) -> bool: + return vaddr in self._relocated_addrs + + def _prepare_string_search(self): + """We are intersted in deduplicated string constants found in the + .rdata and .data sections. For each relocated address in these sections, + read the first byte and save the address if that byte is an ASCII character. + When we search for an arbitrary string later, we can narrow down the list + of potential locations by a lot.""" + + def is_ascii(b): + return b" " <= b < b"\x7f" + + sect_data = self.get_section_by_name(".data") + sect_rdata = self.get_section_by_name(".rdata") + potentials = filter( + lambda a: sect_data.contains_vaddr(a) or sect_rdata.contains_vaddr(a), + self.get_relocated_addresses(), + ) + + for addr in potentials: + c = self.read(addr, 1) + if c is not None and is_ascii(c): + k = ord(c) + if k not in self._potential_strings: + self._potential_strings[k] = set() + + self._potential_strings[k].add(addr) + + def _populate_relocations(self): + """The relocation table in .reloc gives each virtual address where the next four + bytes are, itself, another virtual address. During loading, these values will be + patched according to the virtual address space for the image, as provided by Windows. + We can use this information to get a list of where each significant "thing" + in the file is located. Anything that is referenced absolutely (i.e. excluding + jump destinations given by local offset) will be here. + One use case is to tell whether an immediate value in an operand represents + a virtual address or just a big number.""" + + reloc = self.get_section_by_name(".reloc").view + ofs = 0 + reloc_addrs = [] + + # Parse the structure in .reloc to get the list locations to check. + # The first 8 bytes are 2 dwords that give the base page address + # and the total block size (including this header). + # The page address is used to compact the list; each entry is only + # 2 bytes, and these are added to the base to get the full location. + # If the entry read in is zero, we are at the end of this section and + # these are padding bytes. + while True: + (page_base, block_size) = struct.unpack("<2I", reloc[ofs : ofs + 8]) + if block_size == 0: + break + + # HACK: ignore the relocation type for now (the top 4 bits of the value). + values = list(struct.iter_unpack(" Iterator[Tuple[int, int, float]]: + """Floating point instructions that refer to a memory address can + point to constant values. Search the code sections to find FP + instructions and check whether the pointer address refers to + read-only data.""" + + # TODO: Should check any section that has code, not just .text + text = self.get_section_by_name(".text") + rdata = self.get_section_by_name(".rdata") + + # These are the addresses where a relocation occurs. + # Meaning: it points to an absolute address of something + for addr in self._relocations: + if not text.contains_vaddr(addr): + continue + + # Read the two bytes before the relocated address. + # We will check against possible float opcodes + raw = text.read_virtual(addr - 2, 6) + (opcode, opcode_ext, const_addr) = struct.unpack(" Iterator[Tuple[int, str]]: + """Search for possible strings at each verified address in .data.""" + section = self.get_section_by_name(".data") + for addr in self._relocated_addrs: + if section.contains_vaddr(addr): + raw = self.read_string(addr) + if raw is None: + continue + + try: + string = raw.decode(encoding) + except UnicodeDecodeError: + continue + + yield (addr, string) + + def get_section_by_name(self, name: str) -> Section: + section = next( + filter(lambda section: section.match_name(name), self.sections), + None, + ) + + if section is None: + raise SectionNotFoundError + + return section + + def get_section_by_index(self, index: int) -> Section: + """Convert 1-based index into 0-based.""" + return self.sections[index - 1] + + def get_section_extent_by_index(self, index: int) -> int: + return self.get_section_by_index(index).extent + + def get_section_offset_by_index(self, index: int) -> int: + """The symbols output from cvdump gives addresses in this format: AAAA.BBBBBBBB + where A is the index (1-based) into the section table and B is the local offset. + This will return the virtual address for the start of the section at the given index + so you can get the virtual address for whatever symbol you are looking at. + """ + return self.get_section_by_index(index).virtual_address + + def get_section_offset_by_name(self, name: str) -> int: + """Same as above, but use the section name as the lookup""" + + section = self.get_section_by_name(name) + return section.virtual_address + + def get_abs_addr(self, section: int, offset: int) -> int: + """Convenience function for converting section:offset pairs from cvdump + into an absolute vaddr.""" + return self.get_section_offset_by_index(section) + offset + + def get_relative_addr(self, addr: int) -> Tuple[int, int]: + """Convert an absolute address back into a (section, offset) pair.""" + i = bisect.bisect_right(self._section_vaddr, addr) - 1 + i = max(0, i) + + section = self.sections[i] + if section.contains_vaddr(addr): + return (i + 1, addr - section.virtual_address) + + raise InvalidVirtualAddressError(f"{self.filename} : {hex(addr)}") + + def is_valid_section(self, section_id: int) -> bool: + """The PDB will refer to sections that are not listed in the headers + and so should ignore these references.""" + try: + _ = self.get_section_by_index(section_id) + return True + except IndexError: + return False + + def is_valid_vaddr(self, vaddr: int) -> bool: + """Does this virtual address point to anything in the exe?""" + try: + (_, __) = self.get_relative_addr(vaddr) + except InvalidVirtualAddressError: + return False + + return True + + 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, vaddr: 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.""" + (section_id, offset) = self.get_relative_addr(vaddr) + section = self.sections[section_id - 1] + + if section.addr_is_uninitialized(vaddr): + return None + + # Clamp the read within the extent of the current section. + # Reading off the end will most likely misrepresent the virtual addressing. + _size = min(size, section.size_of_raw_data - offset) + return bytes(section.view[offset : offset + _size]) diff --git a/tools/isledecomp/isledecomp/compare/__init__.py b/tools/isledecomp/isledecomp/compare/__init__.py new file mode 100644 index 00000000..f8d18500 --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/__init__.py @@ -0,0 +1 @@ +from .core import Compare 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/const.py b/tools/isledecomp/isledecomp/compare/asm/const.py new file mode 100644 index 00000000..7c715c0c --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/asm/const.py @@ -0,0 +1,27 @@ +# Duplicates removed, according to the mnemonics capstone uses. +# e.g. je and jz are the same instruction. capstone uses je. +# See: /arch/X86/X86GenAsmWriter.inc in the capstone repo. +JUMP_MNEMONICS = { + "ja", + "jae", + "jb", + "jbe", + "jcxz", # unused? + "je", + "jecxz", + "jg", + "jge", + "jl", + "jle", + "jmp", + "jne", + "jno", + "jnp", + "jns", + "jo", + "jp", + "js", +} + +# Guaranteed to be a single operand. +SINGLE_OPERAND_INSTS = {"push", "call", *JUMP_MNEMONICS} diff --git a/tools/isledecomp/isledecomp/compare/asm/fixes.py b/tools/isledecomp/isledecomp/compare/asm/fixes.py new file mode 100644 index 00000000..d0e326da --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/asm/fixes.py @@ -0,0 +1,314 @@ +import re +from typing import List, Tuple, Set + +DiffOpcode = Tuple[str, int, int, int, int] + +REG_FIND = re.compile(r"(?: |\[)(e?[a-d]x|e?[s,d]i|[a-d][l,h]|e?[b,s]p)") + +ALLOWED_JUMP_SWAPS = ( + ("ja", "jb"), + ("jae", "jbe"), + ("jb", "ja"), + ("jbe", "jae"), + ("jg", "jl"), + ("jge", "jle"), + ("jl", "jg"), + ("jle", "jge"), + ("je", "je"), + ("jne", "jne"), +) + + +def jump_swap_ok(a: str, b: str) -> bool: + """For the instructions a,b, are they both jump instructions + that are compatible with a swapped cmp operand order?""" + # Grab the mnemonic + (jmp_a, _, __) = a.partition(" ") + (jmp_b, _, __) = b.partition(" ") + + return (jmp_a, jmp_b) in ALLOWED_JUMP_SWAPS + + +def is_operand_swap(a: str, b: str) -> bool: + """This is a hack to avoid parsing the operands. It's not as simple as + breaking on the comma because templates or string literals interfere + with this. Instead we check: + 1. Do both strings use the exact same set of characters? + 2. If we do break on ', ', is the first token of each different? + 2 is needed to catch an edge case like: + cmp eax, dword ptr [ecx + 0x1234] + cmp ecx, dword ptr [eax + 0x1234] + """ + return a.partition(", ")[0] != b.partition(", ")[0] and sorted(a) == sorted(b) + + +def can_cmp_swap(orig: List[str], recomp: List[str]) -> bool: + # Make sure we have 1 cmp and 1 jmp for both + if len(orig) != 2 or len(recomp) != 2: + return False + + if not orig[0].startswith("cmp") or not recomp[0].startswith("cmp"): + return False + + if not orig[1].startswith("j") or not recomp[1].startswith("j"): + return False + + # Checking two things: + # Are the cmp operands flipped? + # Is the jump instruction compatible with a flip? + return is_operand_swap(orig[0], recomp[0]) and jump_swap_ok(orig[1], recomp[1]) + + +def patch_jump(a: str, b: str) -> str: + """For jump instructions a, b, return `(mnemonic_a) (operand_b)`. + The reason to do it this way (instead of just returning `a`) is that + the jump instructions might use different displacement offsets + or labels. If we just replace `b` with `a`, this diff would be + incorrectly eliminated.""" + (mnemonic_a, _, __) = a.partition(" ") + (_, __, operand_b) = b.partition(" ") + + return mnemonic_a + " " + operand_b + + +def patch_cmp_swaps( + codes: List[DiffOpcode], orig_asm: List[str], recomp_asm: List[str] +) -> Set[int]: + """Can we resolve the diffs between orig and recomp by patching + swapped cmp instructions? + For example: + cmp eax, ebx cmp ebx, eax + je .label je .label + + cmp eax, ebx cmp ebx, eax + ja .label jb .label + """ + + fixed_lines = set() + + for code, i1, i2, j1, j2 in codes: + # To save us the trouble of finding "compatible" cmp instructions + # use the diff information we already have. + if code != "replace": + continue + + # If the ranges in orig and recomp are not equal, use the shorter one + for i, j in zip(range(i1, i2), range(j1, j2)): + if can_cmp_swap(orig_asm[i : i + 2], recomp_asm[j : j + 2]): + # Patch cmp + fixed_lines.add(j) + + # Patch the jump if necessary + patched = patch_jump(orig_asm[i + 1], recomp_asm[j + 1]) + # We only register a fix if it actually matches + if orig_asm[i + 1] == patched: + fixed_lines.add(j + 1) + + return fixed_lines + + +def effective_match_possible(orig_asm: List[str], recomp_asm: List[str]) -> bool: + # We can only declare an effective match based on the text + # so you need the same amount of "stuff" in each + if len(orig_asm) != len(recomp_asm): + return False + + # mnemonic_orig = [inst.partition(" ")[0] for inst in orig_asm] + # mnemonic_recomp = [inst.partition(" ")[0] for inst in recomp_asm] + + # Cannot change mnemonics. Must be same starting list + # TODO: Fine idea but this will exclude jump swaps for cmp operand order + # if sorted(mnemonic_orig) != sorted(mnemonic_recomp): + # return False + + return True + + +def find_regs_used(inst: str) -> List[str]: + return REG_FIND.findall(inst) + + +def find_regs_changed(a: str, b: str) -> List[Tuple[str, str]]: + """For instructions a, b, return the pairs of registers that were used. + This is not a very precise way to compare the instructions, so it depends + on the input being two instructions that would match *except* for + the register choice.""" + return zip(REG_FIND.findall(a), REG_FIND.findall(b)) + + +def bad_register_swaps( + swaps: Set[int], orig_asm: List[str], recomp_asm: List[str] +) -> Set[int]: + """The list of recomp indices in `swaps` tells which instructions are + a match for orig except for the registers used. From that list, check + whether a register swap should not be allowed. + For now, this means checking for `push` instructions where the register + was not used in any other register swaps on previous instructions.""" + rejects = set() + + # Foreach `push` instruction where we have excused the diff + pushes = [j for j in swaps if recomp_asm[j].startswith("push")] + + for j in pushes: + okay = False + # Get the operands in each + reg = (orig_asm[j].partition(" ")[2], recomp_asm[j].partition(" ")[2]) + # If this isn't a register at all, ignore it + try: + int(reg[0], 16) + continue + except ValueError: + pass + + # For every other excused diff that is *not* a push: + # Assumes same index in orig as in recomp, but so does our naive match + for k in swaps.difference(pushes): + changed_regs = find_regs_changed(orig_asm[k], recomp_asm[k]) + if reg in changed_regs or reg[::-1] in changed_regs: + okay = True + break + + if not okay: + rejects.add(j) + + return rejects + + +# Instructions that result in a change to the first operand +MODIFIER_INSTRUCTIONS = ("adc", "add", "lea", "mov", "neg", "sbb", "sub", "pop", "xor") + + +def instruction_alters_regs(inst: str, regs: Set[str]) -> bool: + (mnemonic, _, op_str) = inst.partition(" ") + (first_operand, _, __) = op_str.partition(", ") + + return (mnemonic in MODIFIER_INSTRUCTIONS and first_operand in regs) or ( + mnemonic == "call" and "eax" in regs + ) + + +def relocate_instructions( + codes: List[DiffOpcode], orig_asm: List[str], recomp_asm: List[str] +) -> Set[int]: + """Collect the list of instructions deleted from orig and inserted + into recomp, according to the diff opcodes. Using this list, match up + any pairs of instructions that we assume to be relocated and return + the indices in recomp where this has occurred. + For now, we are checking only for an exact match on the instruction. + We are not checking whether the given instruction can be moved from + point A to B. (i.e. does this set a register that is used by the + instructions between A and B?)""" + deletes = { + i for code, i1, i2, _, __ in codes for i in range(i1, i2) if code == "delete" + } + inserts = [ + j for code, _, __, j1, j2 in codes for j in range(j1, j2) if code == "insert" + ] + + relocated = set() + + for j in inserts: + line = recomp_asm[j] + recomp_regs_used = set(find_regs_used(line)) + for i in deletes: + # Check for exact match. + # TODO: This will grab the first instruction that matches. + # We should probably use the nearest index instead, if it matters + if orig_asm[i] == line: + # To account for a move in either direction + reloc_start = min(i, j) + reloc_end = max(i, j) + if not any( + instruction_alters_regs(orig_asm[k], recomp_regs_used) + for k in range(reloc_start, reloc_end) + ): + relocated.add(j) + deletes.remove(i) + break + + return relocated + + +DWORD_REGS = ("eax", "ebx", "ecx", "edx", "esi", "edi", "ebp", "esp") +WORD_REGS = ("ax", "bx", "cx", "dx", "si", "di", "bp", "sp") +BYTE_REGS = ("ah", "al", "bh", "bl", "ch", "cl", "dh", "dl") + + +def naive_register_replacement(orig_asm: List[str], recomp_asm: List[str]) -> Set[int]: + """Replace all registers of the same size with a placeholder string. + After doing that, compare orig and recomp again. + Return indices from recomp that are now equal to the same index in orig. + This requires orig and recomp to have the same number of instructions, + but this is already a requirement for effective match.""" + orig_raw = "\n".join(orig_asm) + recomp_raw = "\n".join(recomp_asm) + + # TODO: hardly the most elegant way to do this. + for rdw in DWORD_REGS: + orig_raw = orig_raw.replace(rdw, "~reg4") + recomp_raw = recomp_raw.replace(rdw, "~reg4") + + for rw in WORD_REGS: + orig_raw = orig_raw.replace(rw, "~reg2") + recomp_raw = recomp_raw.replace(rw, "~reg2") + + for rb in BYTE_REGS: + orig_raw = orig_raw.replace(rb, "~reg1") + recomp_raw = recomp_raw.replace(rb, "~reg1") + + orig_scrubbed = orig_raw.split("\n") + recomp_scrubbed = recomp_raw.split("\n") + + return { + j for j in range(len(recomp_scrubbed)) if orig_scrubbed[j] == recomp_scrubbed[j] + } + + +def find_effective_match( + codes: List[DiffOpcode], orig_asm: List[str], recomp_asm: List[str] +) -> bool: + """Check whether the two sequences of instructions are an effective match. + Meaning: do they differ only by instruction order or register selection?""" + if not effective_match_possible(orig_asm, recomp_asm): + return False + + already_equal = { + j for code, _, __, j1, j2 in codes for j in range(j1, j2) if code == "equal" + } + + # We need to come up with some answer for each of these lines + recomp_lines_disputed = { + j + for code, _, __, j1, j2 in codes + for j in range(j1, j2) + if code in ("insert", "replace") + } + + cmp_swaps = patch_cmp_swaps(codes, orig_asm, recomp_asm) + # This naive result includes lines that already match, so remove those + naive_swaps = naive_register_replacement(orig_asm, recomp_asm).difference( + already_equal + ) + relocates = relocate_instructions(codes, orig_asm, recomp_asm) + + bad_swaps = bad_register_swaps(naive_swaps, orig_asm, recomp_asm) + + corrections = set().union( + naive_swaps.difference(bad_swaps), + cmp_swaps, + relocates, + ) + + return corrections.issuperset(recomp_lines_disputed) + + +def assert_fixup(asm: List[Tuple[str, str]]): + """Detect assert calls and replace the code filename and line number + values with macros (from assert.h).""" + for i, (_, line) in enumerate(asm): + if "_assert" in line and line.startswith("call"): + try: + asm[i - 3] = (asm[i - 3][0], "push __LINE__") + asm[i - 2] = (asm[i - 2][0], "push __FILE__") + except IndexError: + continue diff --git a/tools/isledecomp/isledecomp/compare/asm/instgen.py b/tools/isledecomp/isledecomp/compare/asm/instgen.py new file mode 100644 index 00000000..c65aa033 --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/asm/instgen.py @@ -0,0 +1,249 @@ +"""Pre-parser for x86 instructions. Will identify data/jump tables used with +switch statements and local jump/call destinations.""" +import re +import bisect +import struct +from enum import Enum, auto +from collections import namedtuple +from typing import Iterable, List, NamedTuple, Optional, Tuple, Union +from capstone import Cs, CS_ARCH_X86, CS_MODE_32 +from .const import JUMP_MNEMONICS + +disassembler = Cs(CS_ARCH_X86, CS_MODE_32) + +DisasmLiteTuple = Tuple[int, int, str, str] +DisasmLiteInst = namedtuple("DisasmLiteInst", "address, size, mnemonic, op_str") + +displacement_regex = re.compile(r".*\+ (0x[0-9a-f]+)\]") + + +class SectionType(Enum): + CODE = auto() + DATA_TAB = auto() + ADDR_TAB = auto() + + +class FuncSection(NamedTuple): + type: SectionType + contents: List[Union[DisasmLiteInst, Tuple[str, int]]] + + +def stop_at_int3( + disasm_lite_gen: Iterable[DisasmLiteTuple], +) -> Iterable[DisasmLiteTuple]: + """Wrapper for capstone disasm_lite generator. We want to stop reading + instructions if we hit the int3 instruction.""" + for inst in disasm_lite_gen: + # inst[2] is the mnemonic + if inst[2] == "int3": + break + + yield inst + + +class InstructGen: + # pylint: disable=too-many-instance-attributes + def __init__(self, blob: bytes, start: int) -> None: + self.blob = blob + self.start = start + self.end = len(blob) + start + self.section_end: int = self.end + self.code_tracks: List[List[DisasmLiteInst]] = [] + + # Todo: Could be refactored later + self.cur_addr: int = 0 + self.cur_section_type: SectionType = SectionType.CODE + self.section_start = start + + self.sections: List[FuncSection] = [] + + self.confirmed_addrs = {} + self.analysis() + + def _finish_section(self, type_: SectionType, stuff): + sect = FuncSection(type_, stuff) + self.sections.append(sect) + + def _insert_confirmed_addr(self, addr: int, type_: SectionType): + # Ignore address outside the bounds of the function + if not self.start <= addr < self.end: + return + + self.confirmed_addrs[addr] = type_ + + # This newly inserted address might signal the end of this section. + # For example, a jump table at the end of the function means we should + # stop reading instructions once we hit that address. + # However, if there is a jump table in between code sections, we might + # read a jump to an address back to the beginning of the function + # (e.g. a loop that spans the entire function) + # so ignore this address because we have already passed it. + if type_ != self.cur_section_type and addr > self.cur_addr: + self.section_end = min(self.section_end, addr) + + def _next_section(self, addr: int) -> Optional[SectionType]: + """We have reached the start of a new section. Tell what kind of + data we are looking at (code or other) and how much we should read.""" + + # Assume the start of every function is code. + if addr == self.start: + self.section_end = self.end + return SectionType.CODE + + # The start of a new section must be an address that we've seen. + new_type = self.confirmed_addrs.get(addr) + if new_type is None: + return None + + self.cur_section_type = new_type + + # The confirmed addrs dict is sorted by insertion order + # i.e. the order in which we read the addresses + # So we have to sort and then find the next item + # to see where this section should end. + + # If we are in a CODE section, ignore contiguous CODE addresses. + # These are not the start of a new section. + # However: if we are not in CODE, any upcoming address is a new section. + # Do this so we can detect contiguous non-CODE sections. + confirmed = [ + conf_addr + for (conf_addr, conf_type) in sorted(self.confirmed_addrs.items()) + if self.cur_section_type != SectionType.CODE + or conf_type != self.cur_section_type + ] + + index = bisect.bisect_right(confirmed, addr) + if index < len(confirmed): + self.section_end = confirmed[index] + else: + self.section_end = self.end + + return new_type + + def _get_code_for(self, addr: int) -> List[DisasmLiteInst]: + """Start disassembling at the given address.""" + # If we are reading a code block beyond the first, see if we already + # have disassembled instructions beginning at the specified address. + # For a CODE/ADDR/CODE function, we might get lucky and produce the + # correct instruction after the jump table's junk instructions. + for track in self.code_tracks: + for i, inst in enumerate(track): + if inst.address == addr: + return track[i:] + + # If we are here, we don't have the instructions. + # Todo: Could try to be clever here and disassemble only + # as much as we probably need (i.e. if a jump table is between CODE + # blocks, there are probably only a few bad instructions after the + # jump table is finished. We could disassemble up to the next verified + # code address and stitch it together) + + blob_cropped = self.blob[addr - self.start :] + instructions = [ + DisasmLiteInst(*inst) + for inst in stop_at_int3(disassembler.disasm_lite(blob_cropped, addr)) + ] + self.code_tracks.append(instructions) + return instructions + + def _handle_jump(self, inst: DisasmLiteInst): + # If this is a regular jump and its destination is within the + # bounds of the binary data (i.e. presumed function size) + # add it to our list of confirmed addresses. + if inst.op_str[0] == "0": + value = int(inst.op_str, 16) + self._insert_confirmed_addr(value, SectionType.CODE) + + # If this is jumping into a table of addresses, save the destination + elif (match := displacement_regex.match(inst.op_str)) is not None: + value = int(match.group(1), 16) + self._insert_confirmed_addr(value, SectionType.ADDR_TAB) + + def analysis(self): + self.cur_addr = self.start + + while (sect_type := self._next_section(self.cur_addr)) is not None: + self.section_start = self.cur_addr + + if sect_type == SectionType.CODE: + instructions = self._get_code_for(self.cur_addr) + + # If we didn't get any instructions back, something is wrong. + # i.e. We can only read part of the full instruction that is up next. + if len(instructions) == 0: + # Nudge the current addr so we will eventually move on to the + # next section. + # Todo: Maybe we could just call it quits here + self.cur_addr += 1 + break + + for inst in instructions: + # section_end is updated as we read instructions. + # If we are into a jump/data table and would read + # a junk instruction, stop here. + if self.cur_addr >= self.section_end: + break + + # print(f"{inst.address:x} : {inst.mnemonic} {inst.op_str}") + + if inst.mnemonic in JUMP_MNEMONICS: + self._handle_jump(inst) + # Todo: log calls too (unwind section) + elif inst.mnemonic == "mov": + # Todo: maintain pairing of data/jump tables + if (match := displacement_regex.match(inst.op_str)) is not None: + value = int(match.group(1), 16) + self._insert_confirmed_addr(value, SectionType.DATA_TAB) + + # Do this instead of copying instruction address. + # If there is only one instruction, we would get stuck here. + self.cur_addr += inst.size + + # End of for loop on instructions. + # We are at the end of the section or the entire function. + # Cut out only the valid instructions for this section + # and save it for later. + + # Todo: don't need to iter on every instruction here. + # They are already in order. + instruction_slice = [ + inst for inst in instructions if inst.address < self.section_end + ] + self._finish_section(SectionType.CODE, instruction_slice) + + elif sect_type == SectionType.ADDR_TAB: + # Clamp to multiple of 4 (dwords) + read_size = ((self.section_end - self.cur_addr) // 4) * 4 + offsets = range(self.section_start, self.section_start + read_size, 4) + dwords = self.blob[ + self.cur_addr - self.start : self.cur_addr - self.start + read_size + ] + addrs = [addr for addr, in struct.iter_unpack(" {t1:x}") + + self._finish_section(SectionType.ADDR_TAB, jump_table) + self.cur_addr = self.section_end + + else: + # Todo: variable data size? + read_size = self.section_end - self.cur_addr + offsets = range(self.section_start, self.section_start + read_size) + bytes_ = self.blob[ + self.cur_addr - self.start : self.cur_addr - self.start + read_size + ] + data = [b for b, in struct.iter_unpack(" Optional[int]: + try: + return int(string, 16) + except ValueError: + pass + + return None + + +def bytes_to_dword(b: bytes) -> Optional[int]: + if len(b) == 4: + return struct.unpack(" None: + self.relocate_lookup = relocate_lookup + self.name_lookup = name_lookup + self.bin_lookup = bin_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, use_cache: bool = True, exact: bool = False + ) -> Optional[str]: + """Return a replacement name for this address if we find one.""" + if use_cache and (cached := self.replacements.get(addr, None)) is not None: + return cached + + if callable(self.name_lookup): + if (name := self.name_lookup(addr, exact)) is not None: + if use_cache: + 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 hex_replace_always(self, match: re.Match) -> str: + """If a pointer value was matched, always insert a placeholder""" + value = int(match.group(1), 16) + return match.group(0).replace(match.group(1), self.replace(value)) + + def hex_replace_relocated(self, match: re.Match) -> str: + """For replacing immediate value operands. We only want to + use the placeholder if we are certain that this is a valid address. + We can check the relocation table to find out.""" + value = int(match.group(1), 16) + if self.is_relocated(value): + return match.group(0).replace(match.group(1), self.replace(value)) + + return match.group(0) + + def hex_replace_annotated(self, match: re.Match) -> str: + """For replacing immediate value operands. Here we replace the value + only if the name lookup returns something. Do not use a placeholder.""" + value = int(match.group(1), 16) + placeholder = self.lookup(value, use_cache=False) + if placeholder is not None: + return match.group(0).replace(match.group(1), placeholder) + + return match.group(0) + + def hex_replace_indirect(self, match: re.Match) -> str: + """Edge case for hex_replace_always. The context of the instruction + tells us that the pointer value is an absolute indirect. + So we go to that location in the binary to get the address. + If we cannot identify the indirect address, fall back to a lookup + on the original pointer value so we might display something useful.""" + value = int(match.group(1), 16) + indirect_value = None + + if callable(self.bin_lookup): + indirect_value = self.bin_lookup(value, 4) + + if indirect_value is not None: + indirect_addr = bytes_to_dword(indirect_value) + if ( + indirect_addr is not None + and self.lookup(indirect_addr, use_cache=False) is not None + ): + return match.group(0).replace( + match.group(1), "->" + self.replace(indirect_addr) + ) + + return match.group(0).replace(match.group(1), self.replace(value)) + + def sanitize(self, inst: DisasmLiteInst) -> Tuple[str, str]: + # 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. + + if ( + inst.mnemonic in SINGLE_OPERAND_INSTS + and (op_str_address := from_hex(inst.op_str)) is not None + ): + if inst.mnemonic == "call": + return (inst.mnemonic, self.replace(op_str_address)) + + if inst.mnemonic == "push": + if self.is_relocated(op_str_address): + return (inst.mnemonic, self.replace(op_str_address)) + + # To avoid falling into jump handling + return (inst.mnemonic, inst.op_str) + + 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, exact=True) + if potential_name is not None: + return (inst.mnemonic, potential_name) + + # Else: 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)) + + if inst.mnemonic == "call": + # Special handling for absolute indirect CALL. + op_str = ptr_replace_regex.sub(self.hex_replace_indirect, inst.op_str) + else: + op_str = ptr_replace_regex.sub(self.hex_replace_always, inst.op_str) + + # We only want relocated addresses for pointer displacement. + # i.e. ptr [register + something] + # Otherwise we would use a placeholder for every stack variable, + # vtable call, or this->member access. + op_str = displace_replace_regex.sub(self.hex_replace_relocated, op_str) + + # In the event of pointer comparison, only replace the immediate value + # if it is a known address. + if inst.mnemonic == "cmp": + op_str = immediate_replace_regex.sub(self.hex_replace_annotated, op_str) + else: + op_str = immediate_replace_regex.sub(self.hex_replace_relocated, op_str) + + return (inst.mnemonic, op_str) + + def parse_asm(self, data: bytes, start_addr: Optional[int] = 0) -> List[str]: + asm = [] + + ig = InstructGen(data, start_addr) + + for sect_type, sect_contents in ig.sections: + if sect_type == SectionType.CODE: + for inst in sect_contents: + # Use heuristics to disregard some differences that aren't representative + # of the accuracy of a function (e.g. global offsets) + + # If there is no pointer or immediate value in the op_str, + # there is nothing to sanitize. + # This leaves us with cases where a small immediate value or + # small displacement (this.member or vtable calls) appears. + # If we assume that instructions we want to sanitize need to be 5 + # bytes -- 1 for the opcode and 4 for the address -- exclude cases + # where the hex value could not be an address. + # The exception is jumps which are as small as 2 bytes + # but are still useful to sanitize. + if "0x" in inst.op_str and ( + inst.mnemonic in JUMP_MNEMONICS or inst.size > 4 + ): + result = self.sanitize(inst) + else: + result = (inst.mnemonic, inst.op_str) + + # mnemonic + " " + op_str + asm.append((hex(inst.address), " ".join(result))) + elif sect_type == SectionType.ADDR_TAB: + asm.append(("", "Jump table:")) + for i, (ofs, _) in enumerate(sect_contents): + asm.append((hex(ofs), f"Jump_dest_{i}")) + + elif sect_type == SectionType.DATA_TAB: + asm.append(("", "Data table:")) + for ofs, b in sect_contents: + asm.append((hex(ofs), hex(b))) + + 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 new file mode 100644 index 00000000..c44f3987 --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/core.py @@ -0,0 +1,914 @@ +import os +import logging +import difflib +import struct +import uuid +from dataclasses import dataclass +from typing import Any, Callable, Iterable, List, Optional +from isledecomp.bin import Bin as IsleBin, InvalidVirtualAddressError +from isledecomp.cvdump.demangler import demangle_string_const +from isledecomp.cvdump import Cvdump, CvdumpAnalysis +from isledecomp.cvdump.types import scalar_type_pointer +from isledecomp.parser import DecompCodebase +from isledecomp.dir import walk_source_dir +from isledecomp.types import SymbolType +from isledecomp.compare.asm import ParseAsm +from isledecomp.compare.asm.fixes import assert_fixup, find_effective_match +from .db import CompareDb, MatchInfo +from .diff import combined_diff +from .lines import LinesDb + + +logger = logging.getLogger(__name__) + + +@dataclass +class DiffReport: + # pylint: disable=too-many-instance-attributes + match_type: SymbolType + orig_addr: int + recomp_addr: int + name: str + udiff: Optional[List[str]] = None + ratio: float = 0.0 + is_effective_match: bool = False + is_stub: 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 ''}" + + +def create_reloc_lookup(bin_file: IsleBin) -> Callable[[int], bool]: + """Function generator for relocation table lookup""" + + def lookup(addr: int) -> bool: + return addr > bin_file.imagebase and bin_file.is_relocated_addr(addr) + + return lookup + + +def create_bin_lookup(bin_file: IsleBin) -> Callable[[int, int], Optional[str]]: + """Function generator for reading from the bin file""" + + def lookup(addr: int, size: int) -> Optional[bytes]: + try: + return bin_file.read(addr, size) + except InvalidVirtualAddressError: + return None + + return lookup + + +class Compare: + # pylint: disable=too-many-instance-attributes + def __init__( + self, orig_bin: IsleBin, recomp_bin: IsleBin, pdb_file: str, code_dir: str + ): + self.orig_bin = orig_bin + self.recomp_bin = recomp_bin + self.pdb_file = pdb_file + self.code_dir = code_dir + # Controls whether we dump the asm output to a file + self.debug: bool = False + self.runid: str = uuid.uuid4().hex[:8] + + self._lines_db = LinesDb(code_dir) + self._db = CompareDb() + + self._load_cvdump() + self._load_markers() + # Detect floats first to eliminate potential overlap with string data + self._find_float_const() + self._find_original_strings() + self._match_imports() + self._match_exports() + self._match_thunks() + self._find_vtordisp() + + def _load_cvdump(self): + logger.info("Parsing %s ...", self.pdb_file) + self.cv = ( + Cvdump(self.pdb_file) + .lines() + .globals() + .publics() + .symbols() + .section_contributions() + .types() + .run() + ) + self.cvdump_analysis = CvdumpAnalysis(self.cv) + + for sym in self.cvdump_analysis.nodes: + # Skip nodes where we have almost no information. + # These probably came from SECTION CONTRIBUTIONS. + if sym.name() is None and sym.node_type is None: + continue + + # The PDB might contain sections that do not line up with the + # actual binary. The symbol "__except_list" is one example. + # In these cases, just skip this symbol and move on because + # we can't do much with it. + if not self.recomp_bin.is_valid_section(sym.section): + continue + + addr = self.recomp_bin.get_abs_addr(sym.section, sym.offset) + sym.addr = addr + + # If this symbol is the final one in its section, we were not able to + # estimate its size because we didn't have the total size of that section. + # We can get this estimate now and assume that the final symbol occupies + # the remainder of the section. + if sym.estimated_size is None: + sym.estimated_size = ( + self.recomp_bin.get_section_extent_by_index(sym.section) + - sym.offset + ) + + if sym.node_type == SymbolType.STRING: + string_info = demangle_string_const(sym.decorated_name) + if string_info is None: + logger.debug( + "Could not demangle string symbol: %s", sym.decorated_name + ) + continue + + # TODO: skip unicode for now. will need to handle these differently. + if string_info.is_utf16: + continue + + raw = self.recomp_bin.read(addr, sym.size()) + try: + # We use the string length reported in the mangled symbol as the + # data size, but this is not always accurate with respect to the + # null terminator. + # e.g. ??_C@_0BA@EFDM@MxObjectFactory?$AA@ + # reported length: 16 (includes null terminator) + # c.f. ??_C@_03DPKJ@enz?$AA@ + # reported length: 3 (does NOT include terminator) + # This will handle the case where the entire string contains "\x00" + # because those are distinct from the empty string of length 0. + decoded_string = raw.decode("latin1") + rstrip_string = decoded_string.rstrip("\x00") + + if decoded_string != "" and rstrip_string != "": + sym.friendly_name = rstrip_string + else: + sym.friendly_name = decoded_string + + except UnicodeDecodeError: + pass + + self._db.set_recomp_symbol( + addr, sym.node_type, sym.name(), sym.decorated_name, sym.size() + ) + + for (section, offset), ( + filename, + line_no, + ) in self.cvdump_analysis.verified_lines.items(): + addr = self.recomp_bin.get_abs_addr(section, offset) + self._lines_db.add_line(filename, line_no, addr) + + # The _entry symbol is referenced in the PE header so we get this match for free. + self._db.set_function_pair(self.orig_bin.entry, self.recomp_bin.entry) + + def _load_markers(self): + # Assume module name is the base filename of the original binary. + (module, _) = os.path.splitext(os.path.basename(self.orig_bin.filename)) + + codefiles = list(walk_source_dir(self.code_dir)) + codebase = DecompCodebase(codefiles, module.upper()) + + def orig_bin_checker(addr: int) -> bool: + return self.orig_bin.is_valid_vaddr(addr) + + # If the address of any annotation would cause an exception, + # remove it and report an error. + bad_annotations = codebase.prune_invalid_addrs(orig_bin_checker) + + for sym in bad_annotations: + logger.error( + "Invalid address 0x%x on %s annotation in file: %s", + sym.offset, + sym.type.name, + sym.filename, + ) + + # Match lineref functions first because this is a guaranteed match. + # If we have two functions that share the same name, and one is + # a lineref, we can match the nameref correctly because the lineref + # was already removed from consideration. + for fun in codebase.iter_line_functions(): + recomp_addr = self._lines_db.search_line(fun.filename, fun.line_number) + if recomp_addr is not None: + self._db.set_function_pair(fun.offset, recomp_addr) + if fun.should_skip(): + self._db.mark_stub(fun.offset) + + for fun in codebase.iter_name_functions(): + self._db.match_function(fun.offset, fun.name) + if fun.should_skip(): + self._db.mark_stub(fun.offset) + + for var in codebase.iter_variables(): + if var.is_static and var.parent_function is not None: + self._db.match_static_variable( + var.offset, var.name, var.parent_function + ) + else: + if self._db.match_variable(var.offset, var.name): + self._check_if_array_and_match_elements(var.offset, var.name) + + for tbl in codebase.iter_vtables(): + self._db.match_vtable(tbl.offset, tbl.name, tbl.base_class) + + for string in codebase.iter_strings(): + # Not that we don't trust you, but we're checking the string + # annotation to make sure it is accurate. + try: + # TODO: would presumably fail for wchar_t strings + orig = self.orig_bin.read_string(string.offset).decode("latin1") + string_correct = string.name == orig + except UnicodeDecodeError: + string_correct = False + + if not string_correct: + logger.error( + "Data at 0x%x does not match string %s", + string.offset, + repr(string.name), + ) + continue + + self._db.match_string(string.offset, string.name) + + def _check_if_array_and_match_elements(self, orig_addr: int, name: str): + """ + Checks if the global variable at `orig_addr` is an array. + If yes, adds a match for all its elements. If it is an array of structs, all fields in that struct are also matched. + Note that there is no recursion, so an array of arrays would not be handled entirely. + This step is necessary e.g. for `0x100f0a20` (LegoRacers.cpp). + """ + + def _add_match_in_array( + name: str, type_id: str, orig_addr: int, recomp_addr: int + ): + self._db.set_recomp_symbol( + recomp_addr, + SymbolType.POINTER if scalar_type_pointer(type_id) else SymbolType.DATA, + name, + name, + # we only need the matches when they are referenced elsewhere, hence we don't need the size + size=None, + ) + self._db.set_pair(orig_addr, recomp_addr) + + matchinfo = self._db.get_by_orig(orig_addr) + if matchinfo is None or matchinfo.recomp_addr is None: + return + recomp_addr = matchinfo.recomp_addr + + node = next( + (x for x in self.cvdump_analysis.nodes if x.addr == recomp_addr), + None, + ) + if node is None or node.data_type is None: + return + + if not node.data_type.key.startswith("0x"): + # scalar type, so clearly not an array + return + + data_type = self.cv.types.keys[node.data_type.key.lower()] + + if data_type["type"] == "LF_ARRAY": + array_element_type = self.cv.types.get(data_type["array_type"]) + + assert node.data_type.members is not None + + for array_element in node.data_type.members: + orig_element_base_addr = orig_addr + array_element.offset + recomp_element_base_addr = recomp_addr + array_element.offset + if array_element_type.members is None: + _add_match_in_array( + f"{name}{array_element.name}", + array_element_type.key, + orig_element_base_addr, + recomp_element_base_addr, + ) + else: + for member in array_element_type.members: + _add_match_in_array( + f"{name}{array_element.name}.{member.name}", + array_element_type.key, + orig_element_base_addr + member.offset, + recomp_element_base_addr + member.offset, + ) + + def _find_original_strings(self): + """Go to the original binary and look for the specified string constants + to find a match. This is a (relatively) expensive operation so we only + look at strings that we have not already matched via a STRING annotation.""" + # Release builds give each de-duped string a symbol so they are easy to find and match. + for string in self._db.get_unmatched_strings(): + addr = self.orig_bin.find_string(string.encode("latin1")) + if addr is None: + escaped = repr(string) + logger.debug("Failed to find this string in the original: %s", escaped) + continue + + self._db.match_string(addr, string) + + def is_real_string(s: str) -> bool: + """Heuristic to ignore values that only look like strings. + This is mostly about short strings (len <= 4) that could be byte or word values. + """ + # 0x10 is the MSB of the address space for DLLs (LEGO1), so this is a pointer + if len(s) == 0 or "\x10" in s: + return False + + # assert(0) is common + if len(s) == 1 and s[0] != "0": + return False + + # Hack because str.isprintable() will fail on strings with newlines or tabs + if len(s) <= 4 and "\\x" in repr(s): + return False + + return True + + # Debug builds do not de-dupe the strings, so we need to find them via brute force scan. + # We could try to match the string addrs if there is only one in orig and recomp. + # When we sanitize the asm, the result is the same regardless. + if self.orig_bin.is_debug: + for addr, string in self.orig_bin.iter_string("latin1"): + if is_real_string(string): + self._db.set_orig_symbol( + addr, SymbolType.STRING, string, len(string) + ) + + for addr, string in self.recomp_bin.iter_string("latin1"): + if is_real_string(string): + self._db.set_recomp_symbol( + addr, SymbolType.STRING, string, None, len(string) + ) + + def _find_float_const(self): + """Add floating point constants in each binary to the database. + We are not matching anything right now because these values are not + deduped like strings.""" + for addr, size, float_value in self.orig_bin.find_float_consts(): + self._db.set_orig_symbol(addr, SymbolType.FLOAT, str(float_value), size) + + for addr, size, float_value in self.recomp_bin.find_float_consts(): + self._db.set_recomp_symbol( + addr, SymbolType.FLOAT, str(float_value), None, size + ) + + def _match_imports(self): + """We can match imported functions based on the DLL name and + function symbol name.""" + 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. + for orig, recomp in orig_to_recomp.items(): + if orig is None or recomp is None: + continue + + # Match the __imp__ symbol + self._db.set_pair(orig, recomp, SymbolType.POINTER) + + # Read the relative address from .idata + try: + (recomp_rva,) = struct.unpack(" DiffReport: + # Detect when the recomp function size would cause us to read + # enough bytes from the original function that we cross into + # the next annotated function. + next_orig = self._db.get_next_orig_addr(match.orig_addr) + if next_orig is not None: + orig_size = min(next_orig - match.orig_addr, match.size) + else: + orig_size = match.size + + orig_raw = self.orig_bin.read(match.orig_addr, orig_size) + recomp_raw = self.recomp_bin.read(match.recomp_addr, match.size) + + # It's unlikely that a function other than an adjuster thunk would + # start with a SUB instruction, so alert to a possible wrong + # annotation here. + # There's probably a better place to do this, but we're reading + # the function bytes here already. + try: + if orig_raw[0] == 0x2B and recomp_raw[0] != 0x2B: + logger.warning( + "Possible thunk at 0x%x (%s)", match.orig_addr, match.name + ) + except IndexError: + pass + + def orig_lookup(addr: int, exact: bool) -> Optional[str]: + m = self._db.get_by_orig(addr, exact) + if m is None: + return None + + if m.orig_addr == addr: + return m.match_name() + + offset = addr - m.orig_addr + if m.compare_type != SymbolType.DATA or offset >= m.size: + return None + + return m.offset_name(offset) + + def recomp_lookup(addr: int, exact: bool) -> Optional[str]: + m = self._db.get_by_recomp(addr, exact) + if m is None: + return None + + if m.recomp_addr == addr: + return m.match_name() + + offset = addr - m.recomp_addr + if m.compare_type != SymbolType.DATA or offset >= m.size: + return None + + return m.offset_name(offset) + + orig_should_replace = create_reloc_lookup(self.orig_bin) + recomp_should_replace = create_reloc_lookup(self.recomp_bin) + + orig_bin_lookup = create_bin_lookup(self.orig_bin) + recomp_bin_lookup = create_bin_lookup(self.recomp_bin) + + orig_parse = ParseAsm( + relocate_lookup=orig_should_replace, + name_lookup=orig_lookup, + bin_lookup=orig_bin_lookup, + ) + recomp_parse = ParseAsm( + relocate_lookup=recomp_should_replace, + name_lookup=recomp_lookup, + bin_lookup=recomp_bin_lookup, + ) + + orig_combined = orig_parse.parse_asm(orig_raw, match.orig_addr) + recomp_combined = recomp_parse.parse_asm(recomp_raw, match.recomp_addr) + + if self.debug: + self._dump_asm(orig_combined, recomp_combined) + + # Check for assert calls only if we expect to find them + if self.orig_bin.is_debug or self.recomp_bin.is_debug: + assert_fixup(orig_combined) + assert_fixup(recomp_combined) + + # Detach addresses from asm lines for the text diff. + orig_asm = [x[1] for x in orig_combined] + recomp_asm = [x[1] for x in recomp_combined] + + diff = difflib.SequenceMatcher(None, orig_asm, recomp_asm, autojunk=False) + ratio = diff.ratio() + + if ratio != 1.0: + # Check whether we can resolve register swaps which are actually + # perfect matches modulo compiler entropy. + codes = diff.get_opcodes() + is_effective_match = find_effective_match(codes, orig_asm, recomp_asm) + unified_diff = combined_diff( + diff, orig_combined, recomp_combined, context_size=10 + ) + else: + is_effective_match = False + unified_diff = [] + + return DiffReport( + match_type=SymbolType.FUNCTION, + 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_vtable(self, match: MatchInfo) -> DiffReport: + vtable_size = match.size + + # The vtable size should always be a multiple of 4 because that + # is the pointer size. If it is not (for whatever reason) + # it would cause iter_unpack to blow up so let's just fix it. + if vtable_size % 4 != 0: + logger.warning( + "Vtable for class %s has irregular size %d", match.name, vtable_size + ) + vtable_size = 4 * (vtable_size // 4) + + orig_table = self.orig_bin.read(match.orig_addr, vtable_size) + recomp_table = self.recomp_bin.read(match.recomp_addr, vtable_size) + + raw_addrs = zip( + [t for (t,) in struct.iter_unpack(" str: + """Format the function reference at this vtable index as text. + If we have not identified this function, we have the option to + display the raw address. This is only worth doing for the original addr + because we should always be able to identify the recomp function. + If the original function is missing then this probably means that the class + should override the given function from the superclass, but we have not + implemented this yet. + """ + + if m is not None: + orig = hex(m.orig_addr) if m.orig_addr is not None else "no orig" + recomp = ( + hex(m.recomp_addr) if m.recomp_addr is not None else "no recomp" + ) + return f"({orig} / {recomp}) : {m.name}" + + if raw_addr is not None: + return f"0x{raw_addr:x} from orig not annotated." + + return "(no match)" + + orig_text = [] + recomp_text = [] + ratio = 0 + n_entries = 0 + + # Now compare each pointer from the two vtables. + for i, (raw_orig, raw_recomp) in enumerate(raw_addrs): + orig = self._db.get_by_orig(raw_orig) + recomp = self._db.get_by_recomp(raw_recomp) + + if ( + orig is not None + and recomp is not None + and orig.recomp_addr == recomp.recomp_addr + ): + ratio += 1 + + n_entries += 1 + index = f"vtable0x{i*4:02x}" + orig_text.append((index, match_text(orig, raw_orig))) + recomp_text.append((index, match_text(recomp))) + + ratio = ratio / float(n_entries) if n_entries > 0 else 0 + + # n=100: Show the entire table if there is a diff to display. + # Otherwise it would be confusing if the table got cut off. + + sm = difflib.SequenceMatcher( + None, + [x[1] for x in orig_text], + [x[1] for x in recomp_text], + ) + + unified_diff = combined_diff(sm, orig_text, recomp_text, context_size=100) + + return DiffReport( + match_type=SymbolType.VTABLE, + orig_addr=match.orig_addr, + recomp_addr=match.recomp_addr, + name=match.name, + udiff=unified_diff, + ratio=ratio, + ) + + def _compare_match(self, match: MatchInfo) -> Optional[DiffReport]: + """Router for comparison type""" + + if match.size is None or match.size == 0: + return None + + options = self._db.get_match_options(match.orig_addr) + if options.get("skip", False): + return None + + if options.get("stub", False): + return DiffReport( + match_type=match.compare_type, + orig_addr=match.orig_addr, + recomp_addr=match.recomp_addr, + name=match.name, + is_stub=True, + ) + + if match.compare_type == SymbolType.FUNCTION: + return self._compare_function(match) + + if match.compare_type == SymbolType.VTABLE: + return self._compare_vtable(match) + + return None + + ## Public API + + def is_pointer_match(self, orig_addr, recomp_addr) -> bool: + """Check whether these pointers point at the same thing""" + + # Null pointers considered matching + if orig_addr == 0 and recomp_addr == 0: + return True + + match = self._db.get_by_orig(orig_addr) + if match is None: + return False + + return match.recomp_addr == recomp_addr + + def get_by_orig(self, addr: int) -> Optional[MatchInfo]: + return self._db.get_by_orig(addr) + + def get_by_recomp(self, addr: int) -> Optional[MatchInfo]: + return self._db.get_by_recomp(addr) + + def get_all(self) -> List[MatchInfo]: + return self._db.get_all() + + def get_functions(self) -> List[MatchInfo]: + return self._db.get_matches_by_type(SymbolType.FUNCTION) + + def get_vtables(self) -> List[MatchInfo]: + return self._db.get_matches_by_type(SymbolType.VTABLE) + + def get_variables(self) -> List[MatchInfo]: + return self._db.get_matches_by_type(SymbolType.DATA) + + def get_match_options(self, addr: int) -> Optional[dict[str, Any]]: + return self._db.get_match_options(addr) + + def compare_address(self, addr: int) -> Optional[DiffReport]: + match = self._db.get_one_match(addr) + if match is None: + return None + + return self._compare_match(match) + + def compare_all(self) -> Iterable[DiffReport]: + for match in self._db.get_matches(): + diff = self._compare_match(match) + if diff is not None: + yield diff + + def compare_functions(self) -> Iterable[DiffReport]: + for match in self.get_functions(): + diff = self._compare_match(match) + if diff is not None: + yield diff + + def compare_variables(self): + pass + + def compare_pointers(self): + pass + + def compare_strings(self): + pass + + def compare_vtables(self) -> Iterable[DiffReport]: + for match in self.get_vtables(): + diff = self._compare_match(match) + if diff is not None: + yield self._compare_match(match) diff --git a/tools/isledecomp/isledecomp/compare/db.py b/tools/isledecomp/isledecomp/compare/db.py new file mode 100644 index 00000000..ad030548 --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/db.py @@ -0,0 +1,554 @@ +"""Wrapper for database (here an in-memory sqlite database) that collects the +addresses/symbols that we want to compare between the original and recompiled binaries.""" + +import sqlite3 +import logging +from typing import Any, List, Optional +from isledecomp.types import SymbolType +from isledecomp.cvdump.demangler import get_vtordisp_name + +_SETUP_SQL = """ + DROP TABLE IF EXISTS `symbols`; + DROP TABLE IF EXISTS `match_options`; + + CREATE TABLE `symbols` ( + compare_type int, + orig_addr int, + recomp_addr int, + name text, + decorated_name text, + size int + ); + + CREATE TABLE `match_options` ( + addr int not null, + name text not null, + value text, + primary key (addr, name) + ) without rowid; + + CREATE VIEW IF NOT EXISTS `match_info` + (compare_type, orig_addr, recomp_addr, name, size) AS + SELECT compare_type, orig_addr, recomp_addr, name, size + FROM `symbols` + ORDER BY orig_addr NULLS LAST; + + CREATE INDEX `symbols_or` ON `symbols` (orig_addr); + CREATE INDEX `symbols_re` ON `symbols` (recomp_addr); + CREATE INDEX `symbols_na` ON `symbols` (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) -> Optional[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" + name = repr(self.name) if ctype == "STRING" else self.name + return f"{name} ({ctype})" + + def offset_name(self, ofs: int) -> Optional[str]: + if self.name is None: + return None + + return f"{self.name}+{ofs} (OFFSET)" + + +def matchinfo_factory(_, row): + return MatchInfo(*row) + + +logger = logging.getLogger(__name__) + + +class CompareDb: + # pylint: disable=too-many-public-methods + def __init__(self): + self._db = sqlite3.connect(":memory:") + self._db.executescript(_SETUP_SQL) + + def set_orig_symbol( + self, + addr: int, + compare_type: Optional[SymbolType], + name: Optional[str], + size: Optional[int], + ): + # Ignore collisions here. + if self._orig_used(addr): + return + + compare_value = compare_type.value if compare_type is not None else None + self._db.execute( + "INSERT INTO `symbols` (orig_addr, compare_type, name, size) VALUES (?,?,?,?)", + (addr, compare_value, name, size), + ) + + def set_recomp_symbol( + self, + addr: int, + compare_type: Optional[SymbolType], + name: Optional[str], + decorated_name: Optional[str], + size: Optional[int], + ): + # Ignore collisions here. The same recomp address can have + # multiple names (e.g. _strlwr and __strlwr) + if self._recomp_used(addr): + return + + compare_value = compare_type.value if compare_type is not None else None + self._db.execute( + "INSERT INTO `symbols` (recomp_addr, compare_type, name, decorated_name, size) VALUES (?,?,?,?,?)", + (addr, compare_value, name, decorated_name, size), + ) + + def get_unmatched_strings(self) -> List[str]: + """Return any strings not already identified by STRING markers.""" + + cur = self._db.execute( + "SELECT name FROM `symbols` WHERE compare_type = ? AND orig_addr IS NULL", + (SymbolType.STRING.value,), + ) + + return [string for (string,) in cur.fetchall()] + + def get_all(self) -> List[MatchInfo]: + cur = self._db.execute("SELECT * FROM `match_info`") + cur.row_factory = matchinfo_factory + + return cur.fetchall() + + def get_matches(self) -> Optional[MatchInfo]: + cur = self._db.execute( + """SELECT * FROM `match_info` + WHERE orig_addr IS NOT NULL + AND recomp_addr IS NOT NULL + """, + ) + cur.row_factory = matchinfo_factory + + return cur.fetchall() + + def get_one_match(self, addr: int) -> Optional[MatchInfo]: + cur = self._db.execute( + """SELECT * FROM `match_info` + WHERE orig_addr = ? + AND recomp_addr IS NOT NULL + """, + (addr,), + ) + cur.row_factory = matchinfo_factory + return cur.fetchone() + + def _get_closest_orig(self, addr: int) -> Optional[int]: + value = self._db.execute( + """SELECT max(orig_addr) FROM `symbols` + WHERE ? >= orig_addr + LIMIT 1 + """, + (addr,), + ).fetchone() + return value[0] if value is not None else None + + def _get_closest_recomp(self, addr: int) -> Optional[int]: + value = self._db.execute( + """SELECT max(recomp_addr) FROM `symbols` + WHERE ? >= recomp_addr + LIMIT 1 + """, + (addr,), + ).fetchone() + return value[0] if value is not None else None + + def get_by_orig(self, addr: int, exact: bool = True) -> Optional[MatchInfo]: + if not exact and not self._orig_used(addr): + addr = self._get_closest_orig(addr) + if addr is None: + return None + + cur = self._db.execute( + """SELECT * FROM `match_info` + WHERE orig_addr = ? + """, + (addr,), + ) + cur.row_factory = matchinfo_factory + return cur.fetchone() + + def get_by_recomp(self, addr: int, exact: bool = True) -> Optional[MatchInfo]: + if not exact and not self._recomp_used(addr): + addr = self._get_closest_recomp(addr) + if addr is None: + return None + + cur = self._db.execute( + """SELECT * FROM `match_info` + WHERE recomp_addr = ? + """, + (addr,), + ) + cur.row_factory = matchinfo_factory + return cur.fetchone() + + def get_matches_by_type(self, compare_type: SymbolType) -> List[MatchInfo]: + cur = self._db.execute( + """SELECT * FROM `match_info` + WHERE compare_type = ? + AND orig_addr IS NOT NULL + AND recomp_addr IS NOT NULL + """, + (compare_type.value,), + ) + cur.row_factory = matchinfo_factory + + return cur.fetchall() + + def _orig_used(self, addr: int) -> bool: + cur = self._db.execute("SELECT 1 FROM symbols WHERE orig_addr = ?", (addr,)) + return cur.fetchone() is not None + + def _recomp_used(self, addr: int) -> bool: + cur = self._db.execute("SELECT 1 FROM symbols WHERE recomp_addr = ?", (addr,)) + return cur.fetchone() is not None + + def set_pair( + self, orig: int, recomp: int, compare_type: Optional[SymbolType] = None + ) -> bool: + if self._orig_used(orig): + logger.debug("Original address %s not unique!", hex(orig)) + return False + + 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, compare_value, recomp), + ) + + return cur.rowcount > 0 + + def set_pair_tentative( + self, orig: int, recomp: int, compare_type: Optional[SymbolType] = None + ) -> bool: + """Declare a match for the original and recomp addresses given, but only if: + 1. The original address is not used elsewhere (as with set_pair) + 2. The recomp address has not already been matched + If the compare_type is given, update this also, but only if NULL in the db. + + The purpose here is to set matches found via some automated analysis + but to not overwrite a match provided by the human operator.""" + if self._orig_used(orig): + # Probable and expected situation. Just ignore it. + return False + + compare_value = compare_type.value if compare_type is not None else None + + cur = self._db.execute( + """UPDATE `symbols` + SET orig_addr = ?, compare_type = coalesce(compare_type, ?) + WHERE recomp_addr = ? + AND orig_addr IS NULL""", + (orig, compare_value, recomp), + ) + + return cur.rowcount > 0 + + def set_function_pair(self, orig: int, recomp: int) -> bool: + """For lineref match or _entry""" + return self.set_pair(orig, recomp, SymbolType.FUNCTION) + + def create_orig_thunk(self, addr: int, name: str) -> bool: + """Create a thunk function reference using the orig address. + We are here because we have a match on the thunked function, + but it is not thunked in the recomp build.""" + + if self._orig_used(addr): + return False + + thunk_name = f"Thunk of '{name}'" + + # Assuming relative jump instruction for thunks (5 bytes) + cur = self._db.execute( + """INSERT INTO `symbols` + (orig_addr, compare_type, name, size) + VALUES (?,?,?,?)""", + (addr, SymbolType.FUNCTION.value, thunk_name, 5), + ) + + return cur.rowcount > 0 + + def create_recomp_thunk(self, addr: int, name: str) -> bool: + """Create a thunk function reference using the recomp address. + We start from the recomp side for this because we are guaranteed + to have full information from the PDB. We can use a regular function + match later to pull in the orig address.""" + + if self._recomp_used(addr): + return False + + thunk_name = f"Thunk of '{name}'" + + # Assuming relative jump instruction for thunks (5 bytes) + cur = self._db.execute( + """INSERT INTO `symbols` + (recomp_addr, compare_type, name, size) + VALUES (?,?,?,?)""", + (addr, SymbolType.FUNCTION.value, thunk_name, 5), + ) + + return cur.rowcount > 0 + + def _set_opt_bool(self, addr: int, option: str, enabled: bool = True): + if enabled: + self._db.execute( + """INSERT OR IGNORE INTO `match_options` + (addr, name) + VALUES (?, ?)""", + (addr, option), + ) + else: + self._db.execute( + """DELETE FROM `match_options` WHERE addr = ? AND name = ?""", + (addr, option), + ) + + def mark_stub(self, orig: int): + self._set_opt_bool(orig, "stub") + + def skip_compare(self, orig: int): + self._set_opt_bool(orig, "skip") + + def get_match_options(self, addr: int) -> Optional[dict[str, Any]]: + cur = self._db.execute( + """SELECT name, value FROM `match_options` WHERE addr = ?""", (addr,) + ) + + return { + option: value if value is not None else True + for (option, value) in cur.fetchall() + } + + def is_vtordisp(self, recomp_addr: int) -> bool: + """Check whether this function is a vtordisp based on its + decorated name. If its demangled name is missing the vtordisp + indicator, correct that.""" + row = self._db.execute( + """SELECT name, decorated_name + FROM `symbols` + WHERE recomp_addr = ?""", + (recomp_addr,), + ).fetchone() + + if row is None: + return False + + (name, decorated_name) = row + if "`vtordisp" in name: + return True + + if decorated_name is None: + # happens in debug builds, e.g. for "Thunk of 'LegoAnimActor::ClassName'" + return False + + new_name = get_vtordisp_name(decorated_name) + if new_name is None: + return False + + self._db.execute( + """UPDATE `symbols` + SET name = ? + WHERE recomp_addr = ?""", + (new_name, recomp_addr), + ) + + return True + + def _find_potential_match( + self, name: str, compare_type: SymbolType + ) -> Optional[int]: + """Name lookup""" + match_decorate = compare_type != SymbolType.STRING and name.startswith("?") + if match_decorate: + sql = """ + SELECT recomp_addr + FROM `symbols` + WHERE orig_addr IS NULL + AND decorated_name = ? + AND (compare_type IS NULL OR compare_type = ?) + LIMIT 1 + """ + else: + sql = """ + SELECT recomp_addr + FROM `symbols` + WHERE orig_addr IS NULL + AND name = ? + AND (compare_type IS NULL OR compare_type = ?) + LIMIT 1 + """ + + row = self._db.execute(sql, (name, compare_type.value)).fetchone() + return row[0] if row is not None else None + + def _find_static_variable( + self, variable_name: str, function_sym: str + ) -> Optional[int]: + """Get the recomp address of a static function variable. + Matches using a LIKE clause on the combination of: + 1. The variable name read from decomp marker. + 2. The decorated name of the enclosing function. + For example, the variable "g_startupDelay" from function "IsleApp::Tick" + has symbol: `?g_startupDelay@?1??Tick@IsleApp@@QAEXH@Z@4HA` + The function's decorated name is: `?Tick@IsleApp@@QAEXH@Z`""" + + row = self._db.execute( + """SELECT recomp_addr FROM `symbols` + WHERE decorated_name LIKE '%' || ? || '%' || ? || '%' + AND orig_addr IS NULL + AND (compare_type = ? OR compare_type = ? OR compare_type IS NULL)""", + ( + variable_name, + function_sym, + SymbolType.DATA.value, + SymbolType.POINTER.value, + ), + ).fetchone() + return row[0] if row is not None else None + + def _match_on(self, compare_type: SymbolType, addr: int, name: str) -> bool: + # Update the compare_type here too since the marker tells us what we should do + + # Truncate the name to 255 characters. It will not be possible to match a name + # longer than that because MSVC truncates the debug symbols to this length. + # See also: warning C4786. + name = name[:255] + + logger.debug("Looking for %s %s", compare_type.name.lower(), name) + recomp_addr = self._find_potential_match(name, compare_type) + if recomp_addr is None: + return False + + return self.set_pair(addr, recomp_addr, compare_type) + + def get_next_orig_addr(self, addr: int) -> Optional[int]: + """Return the original address (matched or not) that follows + the one given. If our recomp function size would cause us to read + too many bytes for the original function, we can adjust it.""" + result = self._db.execute( + """SELECT orig_addr + FROM `symbols` + WHERE orig_addr > ? + ORDER BY orig_addr + LIMIT 1""", + (addr,), + ).fetchone() + + return result[0] if result is not None else None + + def match_function(self, addr: int, name: str) -> bool: + did_match = self._match_on(SymbolType.FUNCTION, addr, name) + if not did_match: + logger.error("Failed to find function symbol with name: %s", name) + + return did_match + + def match_vtable( + self, addr: int, name: str, base_class: Optional[str] = None + ) -> bool: + # Set up our potential match names + bare_vftable = f"{name}::`vftable'" + for_name = base_class if base_class is not None else name + for_vftable = f"{name}::`vftable'{{for `{for_name}'}}" + + # Only allow a match against "Class:`vftable'" + # if this is the derived class. + if base_class is None or base_class == name: + name_options = (for_vftable, bare_vftable) + else: + name_options = (for_vftable, for_vftable) + + row = self._db.execute( + """ + SELECT recomp_addr + FROM `symbols` + WHERE orig_addr IS NULL + AND (name = ? OR name = ?) + AND (compare_type = ?) + LIMIT 1 + """, + (*name_options, SymbolType.VTABLE.value), + ).fetchone() + + if row is not None and self.set_pair(addr, row[0], SymbolType.VTABLE): + return True + + logger.error("Failed to find vtable for class: %s", name) + return False + + def match_static_variable(self, addr: int, name: str, function_addr: int) -> bool: + """Matching a static function variable by combining the variable name + with the decorated (mangled) name of its parent function.""" + + cur = self._db.execute( + """SELECT name, decorated_name + FROM `symbols` + WHERE orig_addr = ?""", + (function_addr,), + ) + + if (result := cur.fetchone()) is None: + logger.error("No function for static variable: %s", name) + return False + + # Get the friendly name for the "failed to match" error message + (function_name, decorated_name) = result + + recomp_addr = self._find_static_variable(name, decorated_name) + if recomp_addr is not None: + # TODO: This variable could be a pointer, but I don't think we + # have a way to tell that right now. + if self.set_pair(addr, recomp_addr, SymbolType.DATA): + return True + + logger.error( + "Failed to match static variable %s from function %s", + name, + function_name, + ) + + return False + + def match_variable(self, addr: int, name: str) -> bool: + did_match = self._match_on(SymbolType.DATA, addr, name) or self._match_on( + SymbolType.POINTER, addr, name + ) + if not did_match: + logger.error("Failed to find variable: %s", name) + + return did_match + + def match_string(self, addr: int, value: str) -> bool: + did_match = self._match_on(SymbolType.STRING, addr, value) + if not did_match: + escaped = repr(value) + logger.error("Failed to find string: %s", escaped) + + return did_match diff --git a/tools/isledecomp/isledecomp/compare/diff.py b/tools/isledecomp/isledecomp/compare/diff.py new file mode 100644 index 00000000..ad453191 --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/diff.py @@ -0,0 +1,98 @@ +from difflib import SequenceMatcher +from typing import Dict, List, Tuple + +CombinedDiffInput = List[Tuple[str, str]] +CombinedDiffOutput = List[Tuple[str, List[Dict[str, Tuple[str, str]]]]] + + +def combined_diff( + diff: SequenceMatcher, + orig_combined: CombinedDiffInput, + recomp_combined: CombinedDiffInput, + context_size: int = 3, +) -> CombinedDiffOutput: + """We want to diff the original and recomp assembly. The "combined" assembly + input has two components: the address of the instruction and the assembly text. + We have already diffed the text only. This is the SequenceMatcher object. + The SequenceMatcher can generate "opcodes" that describe how to turn "Text A" + into "Text B". These refer to list indices of the original arrays, so we can + use those to create the final diff and include the address for each line of assembly. + This is almost the same procedure as the difflib.unified_diff function, but we + are reusing the already generated SequenceMatcher object. + """ + + unified_diff = [] + + for group in diff.get_grouped_opcodes(context_size): + subgroups = [] + + # Keep track of the addresses we've seen in this diff group. + # This helps create the "@@" line. (Does this have a name?) + # Do it this way because not every line in each list will have an + # address. If our context begins or ends on a line that does not + # have one, we will have an incomplete range string. + orig_addrs = set() + recomp_addrs = set() + + first, last = group[0], group[-1] + orig_range = len(orig_combined[first[1] : last[2]]) + recomp_range = len(recomp_combined[first[3] : last[4]]) + + for code, i1, i2, j1, j2 in group: + if code == "equal": + # The sections are equal, so the list slices are guaranteed + # to have the same length. We only need the diffed value (asm text) + # from one of the lists, but we need the addresses from both. + # Use zip to put the two lists together and then take out what we want. + both = [ + (a, b, c) + for ((a, b), (c, _)) in zip( + orig_combined[i1:i2], recomp_combined[j1:j2] + ) + ] + + for orig_addr, _, recomp_addr in both: + if orig_addr is not None: + orig_addrs.add(orig_addr) + + if recomp_addr is not None: + recomp_addrs.add(recomp_addr) + + subgroups.append({"both": both}) + else: + for orig_addr, _ in orig_combined[i1:i2]: + if orig_addr is not None: + orig_addrs.add(orig_addr) + + for recomp_addr, _ in recomp_combined[j1:j2]: + if recomp_addr is not None: + recomp_addrs.add(recomp_addr) + + subgroups.append( + { + "orig": orig_combined[i1:i2], + "recomp": recomp_combined[j1:j2], + } + ) + + orig_sorted = sorted(orig_addrs) + recomp_sorted = sorted(recomp_addrs) + + # We could get a diff group that has no original addresses. + # This might happen for a stub function where we are not able to + # produce even a single instruction from the original. + # In that case, show the best slug line that we can. + def peek_front(list_, default=""): + try: + return list_[0] + except IndexError: + return default + + orig_first = peek_front(orig_sorted) + recomp_first = peek_front(recomp_sorted) + + diff_slug = f"@@ -{orig_first},{orig_range} +{recomp_first},{recomp_range} @@" + + unified_diff.append((diff_slug, subgroups)) + + return unified_diff diff --git a/tools/isledecomp/isledecomp/compare/lines.py b/tools/isledecomp/isledecomp/compare/lines.py new file mode 100644 index 00000000..0e9c4332 --- /dev/null +++ b/tools/isledecomp/isledecomp/compare/lines.py @@ -0,0 +1,69 @@ +"""Database used to match (filename, line_number) pairs +between FUNCTION markers and PDB analysis.""" +import sqlite3 +import logging +from functools import cache +from typing import Optional +from pathlib import Path +from isledecomp.dir import PathResolver + + +_SETUP_SQL = """ + DROP TABLE IF EXISTS `lineref`; + CREATE TABLE `lineref` ( + path text not null, + filename text not null, + line int not null, + addr int not null + ); + CREATE INDEX `file_line` ON `lineref` (filename, line); +""" + + +logger = logging.getLogger(__name__) + + +@cache +def my_samefile(path: str, source_path: str) -> bool: + return Path(path).samefile(source_path) + + +@cache +def my_basename_lower(path: str) -> str: + return Path(path).name.lower() + + +class LinesDb: + def __init__(self, code_dir) -> None: + self._db = sqlite3.connect(":memory:") + self._db.executescript(_SETUP_SQL) + self._path_resolver = PathResolver(code_dir) + + def add_line(self, path: str, line_no: int, addr: int): + """To be added from the LINES section of cvdump.""" + sourcepath = self._path_resolver.resolve_cvdump(path) + filename = my_basename_lower(sourcepath) + + self._db.execute( + "INSERT INTO `lineref` (path, filename, line, addr) VALUES (?,?,?,?)", + (sourcepath, filename, line_no, addr), + ) + + def search_line(self, path: str, line_no: int) -> Optional[int]: + """Using path and line number from FUNCTION marker, + get the address of this function in the recomp.""" + filename = my_basename_lower(path) + cur = self._db.execute( + "SELECT path, addr FROM `lineref` WHERE filename = ? AND line = ?", + (filename, line_no), + ) + for source_path, addr in cur.fetchall(): + if my_samefile(path, source_path): + return addr + + logger.error( + "Failed to find function symbol with filename and line: %s:%d", + path, + line_no, + ) + return None diff --git a/tools/isledecomp/isledecomp/cvdump/__init__.py b/tools/isledecomp/isledecomp/cvdump/__init__.py new file mode 100644 index 00000000..334788c0 --- /dev/null +++ b/tools/isledecomp/isledecomp/cvdump/__init__.py @@ -0,0 +1,5 @@ +from .symbols import SymbolsEntry +from .analysis import CvdumpAnalysis +from .parser import CvdumpParser +from .runner import Cvdump +from .types import CvdumpTypesParser diff --git a/tools/isledecomp/isledecomp/cvdump/analysis.py b/tools/isledecomp/isledecomp/cvdump/analysis.py new file mode 100644 index 00000000..e030035e --- /dev/null +++ b/tools/isledecomp/isledecomp/cvdump/analysis.py @@ -0,0 +1,187 @@ +"""For collating the results from parsing cvdump.exe into a more directly useful format.""" + +from typing import Dict, List, Tuple, Optional +from isledecomp.cvdump import SymbolsEntry +from isledecomp.types import SymbolType +from .parser import CvdumpParser +from .demangler import demangle_string_const, demangle_vtable +from .types import CvdumpKeyError, CvdumpIntegrityError, TypeInfo + + +class CvdumpNode: + # pylint: disable=too-many-instance-attributes + # These two are required and allow us to identify the symbol + section: int + offset: int + # aka the mangled name from the PUBLICS section + decorated_name: Optional[str] = None + # optional "nicer" name (e.g. of a function from SYMBOLS section) + friendly_name: Optional[str] = None + # To be determined by context after inserting data, unless the decorated + # name makes this obvious. (i.e. string constants or vtables) + # We choose not to assume that section 1 (probably ".text") contains only + # functions. Smacker functions are linked to their own section "_UNSTEXT" + node_type: Optional[SymbolType] = None + # Function size can be read from the LINES section so use this over any + # other value if we have it. + # TYPES section can tell us the size of structs and other complex types. + confirmed_size: Optional[int] = None + # Estimated by reading the distance between this symbol and the one that + # follows in the same section. + # If this is the last symbol in the section, we cannot estimate a size. + estimated_size: Optional[int] = None + # Size as reported by SECTION CONTRIBUTIONS section. Not guaranteed to be + # accurate. + section_contribution: Optional[int] = None + addr: Optional[int] = None + symbol_entry: Optional[SymbolsEntry] = None + # Preliminary - only used for non-static variables at the moment + data_type: Optional[TypeInfo] = None + + def __init__(self, section: int, offset: int) -> None: + self.section = section + self.offset = offset + + def set_decorated(self, name: str): + self.decorated_name = name + + if self.decorated_name.startswith("??_7"): + self.node_type = SymbolType.VTABLE + self.friendly_name = demangle_vtable(self.decorated_name) + + elif self.decorated_name.startswith("??_8"): + # This is the `vbtable' symbol for virtual inheritance. + # Should be okay to reuse demangle_vtable. We still want to + # remove things like "const" from the output. + self.node_type = SymbolType.DATA + self.friendly_name = demangle_vtable(self.decorated_name) + + elif self.decorated_name.startswith("??_C@"): + self.node_type = SymbolType.STRING + (strlen, _) = demangle_string_const(self.decorated_name) + self.confirmed_size = strlen + + elif not self.decorated_name.startswith("?") and "@" in self.decorated_name: + # C mangled symbol. The trailing at-sign with number tells the number of bytes + # in the parameter list for __stdcall, __fastcall, or __vectorcall + # For __cdecl it is more ambiguous and we would have to know which section we are in. + # https://learn.microsoft.com/en-us/cpp/build/reference/decorated-names?view=msvc-170#FormatC + self.node_type = SymbolType.FUNCTION + + def name(self) -> Optional[str]: + """Prefer "friendly" name if we have it. + This is what we have been using to match functions.""" + return ( + self.friendly_name + if self.friendly_name is not None + else self.decorated_name + ) + + def size(self) -> Optional[int]: + if self.confirmed_size is not None: + return self.confirmed_size + + # Better to undershoot the size because we can identify a comparison gap easily + if self.estimated_size is not None and self.section_contribution is not None: + return min(self.estimated_size, self.section_contribution) + + # Return whichever one we have, or neither + return self.estimated_size or self.section_contribution + + +class CvdumpAnalysis: + """Collects the results from CvdumpParser into a list of nodes (i.e. symbols). + These can then be analyzed by a downstream tool.""" + + verified_lines: Dict[Tuple[str, str], Tuple[str, str]] + + def __init__(self, parser: CvdumpParser): + """Read in as much information as we have from the parser. + The more sections we have, the better our information will be.""" + node_dict: Dict[Tuple[int, int], CvdumpNode] = {} + + # PUBLICS is our roadmap for everything that follows. + for pub in parser.publics: + key = (pub.section, pub.offset) + if key not in node_dict: + node_dict[key] = CvdumpNode(*key) + + node_dict[key].set_decorated(pub.name) + + for sizeref in parser.sizerefs: + key = (sizeref.section, sizeref.offset) + if key not in node_dict: + node_dict[key] = CvdumpNode(*key) + + node_dict[key].section_contribution = sizeref.size + + for glo in parser.globals: + key = (glo.section, glo.offset) + if key not in node_dict: + node_dict[key] = CvdumpNode(*key) + + node_dict[key].node_type = SymbolType.DATA + node_dict[key].friendly_name = glo.name + + try: + # Check our types database for type information. + # If we did not parse the TYPES section, we can only + # get information for built-in "T_" types. + g_info = parser.types.get(glo.type) + node_dict[key].confirmed_size = g_info.size + node_dict[key].data_type = g_info + # Previously we set the symbol type to POINTER here if + # the variable was known to be a pointer. We can derive this + # information later when it's time to compare the variable, + # so let's set these to symbol type DATA instead. + # POINTER will be reserved for non-variable pointer data. + # e.g. thunks, unwind section. + except (CvdumpKeyError, CvdumpIntegrityError): + # No big deal if we don't have complete type information. + pass + + for key, _ in parser.lines.items(): + # Here we only set if the section:offset already exists + # because our values include offsets inside of the function. + if key in node_dict: + node_dict[key].node_type = SymbolType.FUNCTION + + # The LINES section contains every code line in the file, naturally. + # There isn't an obvious separation between functions, so we have to + # read everything. However, any function that would be in LINES + # has to be somewhere else in the PDB (probably PUBLICS). + # Isolate the lines that we actually care about for matching. + self.verified_lines = { + key: value for (key, value) in parser.lines.items() if key in node_dict + } + + for sym in parser.symbols: + key = (sym.section, sym.offset) + if key not in node_dict: + node_dict[key] = CvdumpNode(*key) + + if sym.type == "S_GPROC32": + node_dict[key].friendly_name = sym.name + node_dict[key].confirmed_size = sym.size + node_dict[key].node_type = SymbolType.FUNCTION + node_dict[key].symbol_entry = sym + + self.nodes: List[CvdumpNode] = [ + v for _, v in dict(sorted(node_dict.items())).items() + ] + self._estimate_size() + + def _estimate_size(self): + """Get the distance between one section:offset value and the next one + in the same section. This gives a rough estimate of the size of the symbol. + If we have information from SECTION CONTRIBUTIONS, take whichever one is + less to get the best approximate size.""" + for i in range(len(self.nodes) - 1): + this_node = self.nodes[i] + next_node = self.nodes[i + 1] + + # If they are in different sections, we can't compare them + if this_node.section != next_node.section: + continue + + this_node.estimated_size = next_node.offset - this_node.offset diff --git a/tools/isledecomp/isledecomp/cvdump/demangler.py b/tools/isledecomp/isledecomp/cvdump/demangler.py new file mode 100644 index 00000000..9b2445da --- /dev/null +++ b/tools/isledecomp/isledecomp/cvdump/demangler.py @@ -0,0 +1,121 @@ +"""For demangling a subset of MSVC mangled symbols. +Some unofficial information about the mangling scheme is here: +https://en.wikiversity.org/wiki/Visual_C%2B%2B_name_mangling +""" +import re +from collections import namedtuple +from typing import Optional +import pydemangler + + +class InvalidEncodedNumberError(Exception): + pass + + +_encoded_number_translate = str.maketrans("ABCDEFGHIJKLMNOP", "0123456789ABCDEF") + + +def parse_encoded_number(string: str) -> int: + # TODO: assert string ends in "@"? + if string.endswith("@"): + string = string[:-1] + + try: + return int(string.translate(_encoded_number_translate), 16) + except ValueError as e: + raise InvalidEncodedNumberError(string) from e + + +string_const_regex = re.compile( + r"\?\?_C@\_(?P[0-1])(?P\d|[A-P]+@)(?P\w+)@(?P.+)@" +) +StringConstInfo = namedtuple("StringConstInfo", "len is_utf16") + + +def demangle_string_const(symbol: str) -> Optional[StringConstInfo]: + """Don't bother to decode the string text from the symbol. + We can just read it from the binary once we have the length.""" + match = string_const_regex.match(symbol) + if match is None: + return None + + try: + strlen = ( + parse_encoded_number(match.group("len")) + if "@" in match.group("len") + else int(match.group("len")) + ) + except (ValueError, InvalidEncodedNumberError): + return None + + is_utf16 = match.group("is_utf16") == "1" + return StringConstInfo(len=strlen, is_utf16=is_utf16) + + +def get_vtordisp_name(symbol: str) -> Optional[str]: + # pylint: disable=c-extension-no-member + """For adjuster thunk functions, the PDB will sometimes use a name + that contains "vtordisp" but often will just reuse the name of the + function being thunked. We want to use the vtordisp name if possible.""" + name = pydemangler.demangle(symbol) + if name is None: + return None + + if "`vtordisp" not in name: + return None + + # Now we remove the parts of the friendly name that we don't need + try: + # Assuming this is the last of the function prefixes + thiscall_idx = name.index("__thiscall") + # To match the end of the `vtordisp{x,y}' string + end_idx = name.index("}'") + return name[thiscall_idx + 11 : end_idx + 2] + except ValueError: + return name + + +def demangle_vtable(symbol: str) -> str: + # pylint: disable=c-extension-no-member + """Get the class name referenced in the vtable symbol.""" + raw = pydemangler.demangle(symbol) + + if raw is None: + pass # TODO: This shouldn't happen if MSVC behaves + + # Remove storage class and other stuff we don't care about + return ( + raw.replace(" str: + """Parked implementation of MSVC symbol demangling. + We only use this for vtables and it works okay with the simple cases or + templates that refer to other classes/structs. Some namespace support. + Does not support backrefs, primitive types, or vtables with + virtual inheritance.""" + + # Seek ahead 4 chars to strip off "??_7" prefix + t = symbol[4:].split("@") + # "?$" indicates a template class + if t[0].startswith("?$"): + class_name = t[0][2:] + # PA = Pointer/reference + # V or U = class or struct + if t[1].startswith("PA"): + generic = f"{t[1][3:]} *" + else: + generic = t[1][1:] + + return f"{class_name}<{generic}>::`vftable'" + + # If we have two classes listed, it is a namespace hierarchy. + # @@6B@ is a common generic suffix for these vtable symbols. + if t[1] != "" and t[1] != "6B": + return t[1] + "::" + t[0] + "::`vftable'" + + return t[0] + "::`vftable'" diff --git a/tools/isledecomp/isledecomp/cvdump/parser.py b/tools/isledecomp/isledecomp/cvdump/parser.py new file mode 100644 index 00000000..c8f1d67d --- /dev/null +++ b/tools/isledecomp/isledecomp/cvdump/parser.py @@ -0,0 +1,182 @@ +import re +from typing import Iterable, Tuple +from collections import namedtuple +from .types import CvdumpTypesParser +from .symbols import CvdumpSymbolsParser + +# e.g. `*** PUBLICS` +_section_change_regex = re.compile(r"\*\*\* (?P
[A-Z/ ]{2,})") + +# e.g. ` 27 00034EC0 28 00034EE2 29 00034EE7 30 00034EF4` +_line_addr_pairs_findall = re.compile(r"\s+(?P\d+) (?P[A-F0-9]{8})") + +# We assume no spaces in the file name +# e.g. ` Z:\lego-island\isle\LEGO1\viewmanager\viewroi.cpp (None), 0001:00034E90-00034E97, line/addr pairs = 2` +_lines_subsection_header = re.compile( + r"^\s*(?P\S+).*?, (?P
[A-F0-9]{4}):(?P[A-F0-9]{8})-(?P[A-F0-9]{8}), line/addr pairs = (?P\d+)" +) + +# e.g. `S_PUB32: [0001:0003FF60], Flags: 00000000, __read` +_publics_line_regex = re.compile( + r"^(?P\w+): \[(?P
\w{4}):(?P\w{8})], Flags: (?P\w{8}), (?P\S+)" +) + +# e.g. ` Debug start: 00000008, Debug end: 0000016E` +_gproc_debug_regex = re.compile( + r"\s*Debug start: (?P\w{8}), Debug end: (?P\w{8})" +) + +# e.g. ` 00DA 0001:00000000 00000073 60501020` +_section_contrib_regex = re.compile( + r"\s*(?P\w{4}) (?P
\w{4}):(?P\w{8}) (?P\w{8}) (?P\w{8})" +) + +# 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.+)" +) + +# e.g. 0003 "CMakeFiles/isle.dir/ISLE/res/isle.rc.res" +# e.g. 0004 "C:\work\lego-island\isle\3rdparty\smartheap\SHLW32MT.LIB" "check.obj" +_module_regex = re.compile(r"(?P\w{4})(?: \"(?P.+?)\")?(?: \"(?P.+?)\")") + +# User functions only +LinesEntry = namedtuple("LinesEntry", "filename line_no section offset") + +# Strings, vtables, functions +# superset of everything else +# only place you can find the C symbols (library functions, smacker, etc) +PublicsEntry = namedtuple("PublicsEntry", "type section offset flags name") + +# (Estimated) size of any symbol +SizeRefEntry = namedtuple("SizeRefEntry", "module section offset size") + +# global variables +GdataEntry = namedtuple("GdataEntry", "section offset type name") + +ModuleEntry = namedtuple("ModuleEntry", "id lib obj") + + +class CvdumpParser: + # pylint: disable=too-many-instance-attributes + def __init__(self) -> None: + self._section: str = "" + self._lines_function: Tuple[str, int] = ("", 0) + + self.lines = {} + self.publics = [] + self.sizerefs = [] + self.globals = [] + self.modules = [] + + self.types = CvdumpTypesParser() + self.symbols_parser = CvdumpSymbolsParser() + + @property + def symbols(self): + return self.symbols_parser.symbols + + def _lines_section(self, line: str): + """Parsing entries from the LINES section. We only care about the pairs of + line_number and address and the subsection header to indicate which code file + we are in.""" + + # Subheader indicates a new function and possibly a new code filename. + # Save the section here because it is not given on the lines that follow. + if (match := _lines_subsection_header.match(line)) is not None: + self._lines_function = ( + match.group("filename"), + int(match.group("section"), 16), + ) + return + + # Match any pairs as we find them + for line_no, offset in _line_addr_pairs_findall.findall(line): + key = (self._lines_function[1], int(offset, 16)) + self.lines[key] = (self._lines_function[0], int(line_no)) + + def _publics_section(self, line: str): + """Match each line from PUBLICS and pull out the symbol information. + These are MSVC mangled symbol names. String constants and vtable + addresses can only be found here.""" + if (match := _publics_line_regex.match(line)) is not None: + self.publics.append( + PublicsEntry( + type=match.group("type"), + section=int(match.group("section"), 16), + offset=int(match.group("offset"), 16), + flags=int(match.group("flags"), 16), + name=match.group("name"), + ) + ) + + def _globals_section(self, line: str): + """S_PROCREF may be useful later. + Right now we just want S_GDATA32 symbols because it is the simplest + way to access global variables.""" + if (match := _gdata32_regex.match(line)) is not None: + self.globals.append( + GdataEntry( + section=int(match.group("section"), 16), + offset=int(match.group("offset"), 16), + type=match.group("type"), + name=match.group("name"), + ) + ) + + def _section_contributions(self, line: str): + """Gives the size of elements across all sections of the binary. + This is the easiest way to get the data size for .data and .rdata + members that do not have a primitive data type.""" + if (match := _section_contrib_regex.match(line)) is not None: + self.sizerefs.append( + SizeRefEntry( + module=int(match.group("module"), 16), + section=int(match.group("section"), 16), + offset=int(match.group("offset"), 16), + size=int(match.group("size"), 16), + ) + ) + + def _modules_section(self, line: str): + """Record the object file (and lib file, if used) linked into the binary. + The auto-incrementing id is cross-referenced in SECTION CONTRIBUTIONS + (and perhaps other locations)""" + if (match := _module_regex.match(line)) is not None: + self.modules.append( + ModuleEntry( + id=int(match.group("id"), 16), + lib=match.group("lib"), + obj=match.group("obj"), + ) + ) + + def read_line(self, line: str): + if (match := _section_change_regex.match(line)) is not None: + self._section = match.group(1) + return + + if self._section == "TYPES": + self.types.read_line(line) + + elif self._section == "SYMBOLS": + self.symbols_parser.read_line(line) + + elif self._section == "LINES": + self._lines_section(line) + + elif self._section == "PUBLICS": + self._publics_section(line) + + elif self._section == "SECTION CONTRIBUTIONS": + self._section_contributions(line) + + elif self._section == "GLOBALS": + self._globals_section(line) + + elif self._section == "MODULES": + self._modules_section(line) + + def read_lines(self, lines: Iterable[str]): + for line in lines: + self.read_line(line) diff --git a/tools/isledecomp/isledecomp/cvdump/runner.py b/tools/isledecomp/isledecomp/cvdump/runner.py new file mode 100644 index 00000000..9463acfa --- /dev/null +++ b/tools/isledecomp/isledecomp/cvdump/runner.py @@ -0,0 +1,83 @@ +import io +from os import name as os_name +from enum import Enum +from typing import List +import subprocess +from isledecomp.lib import lib_path_join +from isledecomp.dir import winepath_unix_to_win +from .parser import CvdumpParser + + +class DumpOpt(Enum): + LINES = 0 + SYMBOLS = 1 + GLOBALS = 2 + PUBLICS = 3 + SECTION_CONTRIB = 4 + MODULES = 5 + TYPES = 6 + + +cvdump_opt_map = { + DumpOpt.LINES: "-l", + DumpOpt.SYMBOLS: "-s", + DumpOpt.GLOBALS: "-g", + DumpOpt.PUBLICS: "-p", + DumpOpt.SECTION_CONTRIB: "-seccontrib", + DumpOpt.MODULES: "-m", + DumpOpt.TYPES: "-t", +} + + +class Cvdump: + def __init__(self, pdb: str) -> None: + self._pdb: str = pdb + self._options = set() + + def lines(self): + self._options.add(DumpOpt.LINES) + return self + + def symbols(self): + self._options.add(DumpOpt.SYMBOLS) + return self + + def globals(self): + self._options.add(DumpOpt.GLOBALS) + return self + + def publics(self): + self._options.add(DumpOpt.PUBLICS) + return self + + def section_contributions(self): + self._options.add(DumpOpt.SECTION_CONTRIB) + return self + + def modules(self): + self._options.add(DumpOpt.MODULES) + return self + + def types(self): + self._options.add(DumpOpt.TYPES) + return self + + def cmd_line(self) -> List[str]: + cvdump_exe = lib_path_join("cvdump.exe") + flags = [cvdump_opt_map[opt] for opt in self._options] + + if os_name == "nt": + return [cvdump_exe, *flags, self._pdb] + + return ["wine", cvdump_exe, *flags, winepath_unix_to_win(self._pdb)] + + def run(self) -> CvdumpParser: + parser = CvdumpParser() + call = self.cmd_line() + with subprocess.Popen(call, stdout=subprocess.PIPE) as proc: + for line in io.TextIOWrapper(proc.stdout, encoding="utf-8"): + # Blank lines are there to help the reader; they have no context significance + if line != "\n": + parser.read_line(line) + + return parser diff --git a/tools/isledecomp/isledecomp/cvdump/symbols.py b/tools/isledecomp/isledecomp/cvdump/symbols.py new file mode 100644 index 00000000..73cb152e --- /dev/null +++ b/tools/isledecomp/isledecomp/cvdump/symbols.py @@ -0,0 +1,163 @@ +from dataclasses import dataclass, field +import logging +import re +from re import Match +from typing import NamedTuple, Optional + + +logger = logging.getLogger(__name__) + + +class StackOrRegisterSymbol(NamedTuple): + symbol_type: str + location: str + """Should always be set/converted to lowercase.""" + data_type: str + name: str + + +# S_GPROC32 = functions +@dataclass +class SymbolsEntry: + # pylint: disable=too-many-instance-attributes + type: str + section: int + offset: int + size: int + func_type: str + name: str + stack_symbols: list[StackOrRegisterSymbol] = field(default_factory=list) + frame_pointer_present: bool = False + addr: Optional[int] = None # Absolute address. Will be set later, if at all + + +class CvdumpSymbolsParser: + _symbol_line_generic_regex = re.compile( + r"\(\w+\)\s+(?P[^\s:]+)(?::\s+(?P\S.*))?|(?::)$" + ) + """ + Parses the first part, e.g. `(00008C) S_GPROC32`, and splits off the second part after the colon (if it exists). + There are three cases: + - no colon, e.g. `(000350) S_END` + - colon but no data, e.g. `(000370) S_COMPILE:` + - colon and data, e.g. `(000304) S_REGISTER: esi, Type: 0x1E14, this`` + """ + + _symbol_line_function_regex = re.compile( + r"\[(?P
\w{4}):(?P\w{8})\], Cb: (?P\w+), Type:\s+(?P[^\s,]+), (?P.+)" + ) + """ + Parses the second part of a function symbol, e.g. + `[0001:00034E90], Cb: 00000007, Type: 0x1024, ViewROI::IntrinsicImportance` + """ + + # the second part of e.g. + _stack_register_symbol_regex = re.compile( + r"(?P\S+), Type:\s+(?P[\w()]+), (?P.+)$" + ) + """ + Parses the second part of a stack or register symbol, e.g. + `esi, Type: 0x1E14, this` + """ + + _debug_start_end_regex = re.compile( + r"^\s*Debug start: (?P\w+), Debug end: (?P\w+)$" + ) + + _parent_end_next_regex = re.compile( + r"\s*Parent: (?P\w+), End: (?P\w+), Next: (?P\w+)$" + ) + + _flags_frame_pointer_regex = re.compile(r"\s*Flags: Frame Ptr Present$") + + _register_stack_symbols = ["S_BPREL32", "S_REGISTER"] + + # List the unhandled types so we can check exhaustiveness + _unhandled_symbols = [ + "S_COMPILE", + "S_OBJNAME", + "S_THUNK32", + "S_LABEL32", + "S_LDATA32", + "S_LPROC32", + "S_UDT", + ] + + """Parser for cvdump output, SYMBOLS section.""" + + def __init__(self): + self.symbols: list[SymbolsEntry] = [] + self.current_function: Optional[SymbolsEntry] = None + # If we read an S_BLOCK32 node, increment this level. + # This is so we do not end the proc early by reading an S_END + # that indicates the end of the block. + self.block_level: int = 0 + + def read_line(self, line: str): + if (match := self._symbol_line_generic_regex.match(line)) is not None: + self._parse_generic_case(line, match) + elif (match := self._parent_end_next_regex.match(line)) is not None: + # We do not need this info at the moment, might be useful in the future + pass + elif (match := self._debug_start_end_regex.match(line)) is not None: + # We do not need this info at the moment, might be useful in the future + pass + elif (match := self._flags_frame_pointer_regex.match(line)) is not None: + if self.current_function is None: + logger.error( + "Found a `Flags: Frame Ptr Present` but self.current_function is None" + ) + return + self.current_function.frame_pointer_present = True + else: + # Most of these are either `** Module: [...]` or data we do not care about + logger.debug("Unhandled line: %s", line[:-1]) + + def _parse_generic_case(self, line, line_match: Match[str]): + symbol_type: str = line_match.group("symbol_type") + second_part: Optional[str] = line_match.group("second_part") + + if symbol_type == "S_GPROC32": + assert second_part is not None + if (match := self._symbol_line_function_regex.match(second_part)) is None: + logger.error("Invalid function symbol: %s", line[:-1]) + return + self.current_function = SymbolsEntry( + type=symbol_type, + section=int(match.group("section"), 16), + offset=int(match.group("offset"), 16), + size=int(match.group("size"), 16), + func_type=match.group("func_type"), + name=match.group("name"), + ) + self.symbols.append(self.current_function) + + elif symbol_type in self._register_stack_symbols: + assert second_part is not None + if self.current_function is None: + logger.error("Found stack/register outside of function: %s", line[:-1]) + return + if (match := self._stack_register_symbol_regex.match(second_part)) is None: + logger.error("Invalid stack/register symbol: %s", line[:-1]) + return + + new_symbol = StackOrRegisterSymbol( + symbol_type=symbol_type, + location=match.group("location").lower(), + data_type=match.group("data_type"), + name=match.group("name"), + ) + self.current_function.stack_symbols.append(new_symbol) + + elif symbol_type == "S_BLOCK32": + self.block_level += 1 + elif symbol_type == "S_END": + if self.block_level > 0: + self.block_level -= 1 + assert self.block_level >= 0 + else: + self.current_function = None + elif symbol_type in self._unhandled_symbols: + return + else: + logger.error("Unhandled symbol type: %s", line) diff --git a/tools/isledecomp/isledecomp/cvdump/types.py b/tools/isledecomp/isledecomp/cvdump/types.py new file mode 100644 index 00000000..42a9e985 --- /dev/null +++ b/tools/isledecomp/isledecomp/cvdump/types.py @@ -0,0 +1,737 @@ +from dataclasses import dataclass +import re +import logging +from typing import Any, Dict, List, NamedTuple, Optional + + +logger = logging.getLogger(__name__) + + +class CvdumpTypeError(Exception): + pass + + +class CvdumpKeyError(KeyError): + pass + + +class CvdumpIntegrityError(Exception): + pass + + +class FieldListItem(NamedTuple): + """Member of a class or structure""" + + offset: int + name: str + type: str + + +@dataclass +class VirtualBaseClass: + type: str + index: int + direct: bool + + +@dataclass +class VirtualBasePointer: + vboffset: int + bases: list[VirtualBaseClass] + + +class ScalarType(NamedTuple): + offset: int + name: Optional[str] + type: str + + @property + def size(self) -> int: + return scalar_type_size(self.type) + + @property + def format_char(self) -> str: + return scalar_type_format_char(self.type) + + @property + def is_pointer(self) -> bool: + return scalar_type_pointer(self.type) + + +class TypeInfo(NamedTuple): + key: str + size: Optional[int] + name: Optional[str] = None + members: Optional[List[FieldListItem]] = None + + def is_scalar(self) -> bool: + # TODO: distinction between a class with zero members and no vtable? + return self.members is None + + +def normalize_type_id(key: str) -> str: + """Helper for TYPES parsing to ensure a consistent format. + If key begins with "T_" it is a built-in type. + Else it is a hex string. We prefer lower case letters and + no leading zeroes. (UDT identifier pads to 8 characters.)""" + if key[0] == "0": + return f"0x{key[-4:].lower()}" + + # Remove numeric value for "T_" type. We don't use this. + return key.partition("(")[0] + + +def scalar_type_pointer(type_name: str) -> bool: + return type_name.startswith("T_32P") + + +def scalar_type_size(type_name: str) -> int: + if scalar_type_pointer(type_name): + return 4 + + if "CHAR" in type_name: + return 2 if "WCHAR" in type_name else 1 + + if "SHORT" in type_name: + return 2 + + if "QUAD" in type_name or "64" in type_name: + return 8 + + return 4 + + +def scalar_type_signed(type_name: str) -> bool: + if scalar_type_pointer(type_name): + return False + + # According to cvinfo.h, T_WCHAR is unsigned + return not type_name.startswith("T_U") and not type_name.startswith("T_W") + + +def scalar_type_format_char(type_name: str) -> str: + if scalar_type_pointer(type_name): + return "L" + + # "Really a char" + if type_name.startswith("T_RCHAR"): + return "c" + + # floats + if type_name.startswith("T_REAL"): + return "d" if "64" in type_name else "f" + + size = scalar_type_size(type_name) + char = ({1: "b", 2: "h", 4: "l", 8: "q"}).get(size, "l") + + return char if scalar_type_signed(type_name) else char.upper() + + +def member_list_to_struct_string(members: List[ScalarType]) -> str: + """Create a string for use with struct.unpack""" + + format_string = "".join(m.format_char for m in members) + if len(format_string) > 0: + return "<" + format_string + + return "" + + +def join_member_names(parent: str, child: Optional[str]) -> str: + """Helper method to combine parent/child member names. + Child member name is None if the child is a scalar type.""" + + if child is None: + return parent + + # If the child is an array index, join without the dot + if child.startswith("["): + return f"{parent}{child}" + + return f"{parent}.{child}" + + +class CvdumpTypesParser: + """Parser for cvdump output, TYPES section. + Tricky enough that it demands its own parser.""" + + # Marks the start of a new type + INDEX_RE = re.compile(r"(?P0x\w+) : .* (?PLF_\w+)") + + # LF_FIELDLIST class/struct member (1/2) + LIST_RE = re.compile( + r"\s+list\[\d+\] = LF_MEMBER, (?P\w+), type = (?P.*), offset = (?P\d+)" + ) + + # LF_FIELDLIST vtable indicator + VTABLE_RE = re.compile(r"^\s+list\[\d+\] = LF_VFUNCTAB") + + # LF_FIELDLIST superclass indicator + SUPERCLASS_RE = re.compile( + r"^\s+list\[\d+\] = LF_BCLASS, (?P\w+), type = (?P.*), offset = (?P\d+)" + ) + + # LF_FIELDLIST virtual direct/indirect base pointer, line 1/2 + VBCLASS_RE = re.compile( + r"^\s+list\[\d+\] = LF_(?PI?)VBCLASS, .* base type = (?P.*)$" + ) + + # LF_FIELDLIST virtual direct/indirect base pointer, line 2/2 + VBCLASS_LINE_2_RE = re.compile( + r"^\s+virtual base ptr = .+, vbpoff = (?P\d+), vbind = (?P\d+)$" + ) + + # LF_FIELDLIST member name (2/2) + MEMBER_RE = re.compile(r"^\s+member name = '(?P.*)'$") + + LF_FIELDLIST_ENUMERATE = re.compile( + r"^\s+list\[\d+\] = LF_ENUMERATE,.*value = (?P\d+), name = '(?P[^']+)'$" + ) + + # LF_ARRAY element type + ARRAY_ELEMENT_RE = re.compile(r"^\s+Element type = (?P.*)") + + # LF_ARRAY total array size + ARRAY_LENGTH_RE = re.compile(r"^\s+length = (?P\d+)") + + # LF_CLASS/LF_STRUCTURE field list reference + CLASS_FIELD_RE = re.compile( + r"^\s+# members = \d+, field list type (?P0x\w+)," + ) + + # LF_CLASS/LF_STRUCTURE name and other info + CLASS_NAME_RE = re.compile( + r"^\s+Size = (?P\d+), class name = (?P(?:[^,]|,\S)+)(?:, UDT\((?P0x\w+)\))?" + ) + + # LF_MODIFIER, type being modified + MODIFIES_RE = re.compile(r".*modifies type (?P.*)$") + + # LF_ARGLIST number of entries + LF_ARGLIST_ARGCOUNT = re.compile(r".*argument count = (?P\d+)$") + + # LF_ARGLIST list entry + LF_ARGLIST_ENTRY = re.compile( + r"^\s+list\[(?P\d+)\] = (?P[\w()]+)$" + ) + + # LF_POINTER element + LF_POINTER_ELEMENT = re.compile(r"^\s+Element type : (?P.+)$") + + # LF_MFUNCTION attribute key-value pairs + LF_MFUNCTION_ATTRIBUTES = [ + re.compile(r"\s*Return type = (?P[\w()]+)$"), + re.compile(r"\s*Class type = (?P[\w()]+)$"), + re.compile(r"\s*This type = (?P[\w()]+)$"), + # Call type may contain whitespace + re.compile(r"\s*Call type = (?P[\w()\s]+)$"), + re.compile(r"\s*Parms = (?P[\w()]+)$"), # LF_MFUNCTION only + re.compile(r"\s*# Parms = (?P[\w()]+)$"), # LF_PROCEDURE only + re.compile(r"\s*Arg list type = (?P[\w()]+)$"), + re.compile( + r"\s*This adjust = (?P[\w()]+)$" + ), # By how much the incoming pointers are shifted in virtual inheritance; hex value without `0x` prefix + re.compile( + r"\s*Func attr = (?P[\w()]+)$" + ), # Only for completeness, is always `none` + ] + + LF_ENUM_ATTRIBUTES = [ + re.compile(r"^\s*# members = (?P\d+)$"), + re.compile(r"^\s*enum name = (?P.+)$"), + ] + LF_ENUM_TYPES = re.compile( + r"^\s*type = (?P\S+) field list type (?P0x\w{4})$" + ) + LF_ENUM_UDT = re.compile(r"^\s*UDT\((?P0x\w+)\)$") + LF_UNION_LINE = re.compile( + r"^.*field list type (?P0x\w+),.*Size = (?P\d+)\s*,class name = (?P(?:[^,]|,\S)+)(?:,\s.*UDT\((?P0x\w+)\))?$" + ) + + MODES_OF_INTEREST = { + "LF_ARRAY", + "LF_CLASS", + "LF_ENUM", + "LF_FIELDLIST", + "LF_MODIFIER", + "LF_POINTER", + "LF_STRUCTURE", + "LF_ARGLIST", + "LF_MFUNCTION", + "LF_PROCEDURE", + "LF_UNION", + } + + def __init__(self) -> None: + self.mode: Optional[str] = None + self.last_key = "" + self.keys: Dict[str, Dict[str, Any]] = {} + + def _new_type(self): + """Prepare a new dict for the type we just parsed. + The id is self.last_key and the "type" of type is self.mode. + e.g. LF_CLASS""" + self.keys[self.last_key] = {"type": self.mode} + + def _set(self, key: str, value): + self.keys[self.last_key][key] = value + + def _add_member(self, offset: int, type_: str): + obj = self.keys[self.last_key] + if "members" not in obj: + obj["members"] = [] + + obj["members"].append({"offset": offset, "type": type_}) + + def _set_member_name(self, name: str): + """Set name for most recently added member.""" + obj = self.keys[self.last_key] + obj["members"][-1]["name"] = name + + def _add_variant(self, name: str, value: int): + obj = self.keys[self.last_key] + if "variants" not in obj: + obj["variants"] = [] + variants: list[dict[str, Any]] = obj["variants"] + variants.append({"name": name, "value": value}) + + def _get_field_list(self, type_obj: Dict[str, Any]) -> List[FieldListItem]: + """Return the field list for the given LF_CLASS/LF_STRUCTURE reference""" + + if type_obj.get("type") == "LF_FIELDLIST": + field_obj = type_obj + else: + field_list_type = type_obj["field_list_type"] + field_obj = self.keys[field_list_type] + + members: List[FieldListItem] = [] + + super_ids = field_obj.get("super", []) + for super_id in super_ids: + # May need to resolve forward ref. + superclass = self.get(super_id) + if superclass.members is not None: + members += superclass.members + + raw_members = field_obj.get("members", []) + members += [ + FieldListItem( + offset=m["offset"], + type=m["type"], + name=m["name"], + ) + for m in raw_members + ] + + return sorted(members, key=lambda m: m.offset) + + def _mock_array_members(self, type_obj: Dict) -> List[FieldListItem]: + """LF_ARRAY elements provide the element type and the total size. + We want the list of "members" as if this was a struct.""" + + if type_obj.get("type") != "LF_ARRAY": + raise CvdumpTypeError("Type is not an LF_ARRAY") + + array_type = type_obj.get("array_type") + if array_type is None: + raise CvdumpIntegrityError("No array element type") + + array_element_size = self.get(array_type).size + assert ( + array_element_size is not None + ), "Encountered an array whose type has no size" + + n_elements = type_obj["size"] // array_element_size + + return [ + FieldListItem( + offset=i * array_element_size, + type=array_type, + name=f"[{i}]", + ) + for i in range(n_elements) + ] + + def get(self, type_key: str) -> TypeInfo: + """Convert our dictionary values read from the cvdump output + into a consistent format for the given type.""" + + # Scalar type. Handled here because it makes the recursive steps + # much simpler. + if type_key.startswith("T_"): + size = scalar_type_size(type_key) + return TypeInfo( + key=type_key, + size=size, + ) + + # Go to our dictionary to find it. + obj = self.keys.get(type_key.lower()) + if obj is None: + raise CvdumpKeyError(type_key) + + # These type references are just a wrapper around a scalar + if obj.get("type") == "LF_ENUM": + underlying_type = obj.get("underlying_type") + if underlying_type is None: + raise CvdumpKeyError(f"Missing 'underlying_type' in {obj}") + return self.get(underlying_type) + + if obj.get("type") == "LF_POINTER": + return self.get("T_32PVOID") + + if obj.get("is_forward_ref", False): + # Get the forward reference to follow. + # If this is LF_CLASS/LF_STRUCTURE, it is the UDT value. + # For LF_MODIFIER, it is the type being modified. + forward_ref = obj.get("udt", None) or obj.get("modifies", None) + if forward_ref is None: + raise CvdumpIntegrityError(f"Null forward ref for type {type_key}") + + return self.get(forward_ref) + + # Else it is not a forward reference, so build out the object here. + if obj.get("type") == "LF_ARRAY": + members = self._mock_array_members(obj) + else: + members = self._get_field_list(obj) + + return TypeInfo( + key=type_key, + size=obj.get("size"), + name=obj.get("name"), + members=members, + ) + + def get_by_name(self, name: str) -> TypeInfo: + """Find the complex type with the given name.""" + # TODO + raise NotImplementedError + + def get_scalars(self, type_key: str) -> List[ScalarType]: + """Reduce the given type to a list of scalars so we can + compare each component value.""" + + obj = self.get(type_key) + if obj.is_scalar(): + # Use obj.key here for alias types like LF_POINTER + return [ScalarType(offset=0, type=obj.key, name=None)] + + # mypy? + assert obj.members is not None + + # Dedupe repeated offsets if this is a union type + unique_offsets = {m.offset: m for m in obj.members} + unique_members = [m for _, m in unique_offsets.items()] + + return [ + ScalarType( + offset=m.offset + cm.offset, + type=cm.type, + name=join_member_names(m.name, cm.name), + ) + for m in unique_members + for cm in self.get_scalars(m.type) + ] + + def get_scalars_gapless(self, type_key: str) -> List[ScalarType]: + """Reduce the given type to a list of scalars so we can + compare each component value.""" + + obj = self.get(type_key) + total_size = obj.size + assert ( + total_size is not None + ), "Called get_scalar_gapless() on a type without size" + + scalars = self.get_scalars(type_key) + + output = [] + last_extent = total_size + + # Walk the scalar list in reverse; we assume a gap could not + # come at the start of the struct. + for scalar in scalars[::-1]: + this_extent = scalar.offset + scalar_type_size(scalar.type) + size_diff = last_extent - this_extent + # We need to add the gap fillers in reverse here + for i in range(size_diff - 1, -1, -1): + # Push to front + output.insert( + 0, + ScalarType( + offset=this_extent + i, + name="(padding)", + type="T_UCHAR", + ), + ) + + output.insert(0, scalar) + last_extent = scalar.offset + + return output + + def get_format_string(self, type_key: str) -> str: + members = self.get_scalars_gapless(type_key) + return member_list_to_struct_string(members) + + def read_line(self, line: str): + if line.endswith("\n"): + line = line[:-1] + if len(line) == 0: + return + + if (match := self.INDEX_RE.match(line)) is not None: + type_ = match.group(2) + if type_ not in self.MODES_OF_INTEREST: + self.mode = None + return + + # Don't need to normalize, it's already in the format we want + self.last_key = match.group(1) + self.mode = type_ + self._new_type() + + if type_ == "LF_ARGLIST": + submatch = self.LF_ARGLIST_ARGCOUNT.match(line) + assert submatch is not None + self.keys[self.last_key]["argcount"] = int(submatch.group("argcount")) + # TODO: This should be validated in another pass + return + + if self.mode is None: + return + + if self.mode == "LF_MODIFIER": + if (match := self.MODIFIES_RE.match(line)) is not None: + # For convenience, because this is essentially the same thing + # as an LF_CLASS forward ref. + self._set("is_forward_ref", True) + self._set("modifies", normalize_type_id(match.group("type"))) + + elif self.mode == "LF_ARRAY": + if (match := self.ARRAY_ELEMENT_RE.match(line)) is not None: + self._set("array_type", normalize_type_id(match.group("type"))) + + elif (match := self.ARRAY_LENGTH_RE.match(line)) is not None: + self._set("size", int(match.group("length"))) + + elif self.mode == "LF_FIELDLIST": + self.read_fieldlist_line(line) + + elif self.mode == "LF_ARGLIST": + self.read_arglist_line(line) + + elif self.mode in ["LF_MFUNCTION", "LF_PROCEDURE"]: + self.read_mfunction_line(line) + + elif self.mode in ["LF_CLASS", "LF_STRUCTURE"]: + self.read_class_or_struct_line(line) + + elif self.mode == "LF_POINTER": + self.read_pointer_line(line) + + elif self.mode == "LF_ENUM": + self.read_enum_line(line) + + elif self.mode == "LF_UNION": + self.read_union_line(line) + + else: + # Check for exhaustiveness + logger.error("Unhandled data in mode: %s", self.mode) + + def read_fieldlist_line(self, line: str): + # If this class has a vtable, create a mock member at offset 0 + if (match := self.VTABLE_RE.match(line)) is not None: + # For our purposes, any pointer type will do + self._add_member(0, "T_32PVOID") + self._set_member_name("vftable") + + # Superclass is set here in the fieldlist rather than in LF_CLASS + elif (match := self.SUPERCLASS_RE.match(line)) is not None: + superclass_list: dict[str, int] = self.keys[self.last_key].setdefault( + "super", {} + ) + superclass_list[normalize_type_id(match.group("type"))] = int( + match.group("offset") + ) + + # virtual base class (direct or indirect) + elif (match := self.VBCLASS_RE.match(line)) is not None: + virtual_base_pointer = self.keys[self.last_key].setdefault( + "vbase", + VirtualBasePointer( + vboffset=-1, # default to -1 until we parse the correct value + bases=[], + ), + ) + assert isinstance( + virtual_base_pointer, VirtualBasePointer + ) # type checker only + + virtual_base_pointer.bases.append( + VirtualBaseClass( + type=match.group("type"), + index=-1, # default to -1 until we parse the correct value + direct=match.group("indirect") != "I", + ) + ) + + elif (match := self.VBCLASS_LINE_2_RE.match(line)) is not None: + virtual_base_pointer = self.keys[self.last_key].get("vbase", None) + assert isinstance( + virtual_base_pointer, VirtualBasePointer + ), "Parsed the second line of an (I)VBCLASS without the first one" + vboffset = int(match.group("vboffset")) + + if virtual_base_pointer.vboffset == -1: + # default value + virtual_base_pointer.vboffset = vboffset + elif virtual_base_pointer.vboffset != vboffset: + # vboffset is always equal to 4 in our examples. We are not sure if there can be multiple + # virtual base pointers, and if so, how the layout is supposed to look. + # We therefore assume that there is always only one virtual base pointer. + logger.error( + "Unhandled: Found multiple virtual base pointers at offsets %d and %d", + virtual_base_pointer.vboffset, + vboffset, + ) + + virtual_base_pointer.bases[-1].index = int(match.group("vbindex")) + # these come out of order, and the lists are so short that it's fine to sort them every time + virtual_base_pointer.bases.sort(key=lambda x: x.index) + + # Member offset and type given on the first of two lines. + elif (match := self.LIST_RE.match(line)) is not None: + self._add_member( + int(match.group("offset")), normalize_type_id(match.group("type")) + ) + + # Name of the member read on the second of two lines. + elif (match := self.MEMBER_RE.match(line)) is not None: + self._set_member_name(match.group("name")) + + elif (match := self.LF_FIELDLIST_ENUMERATE.match(line)) is not None: + self._add_variant(match.group("name"), int(match.group("value"))) + + def read_class_or_struct_line(self, line: str): + # Match the reference to the associated LF_FIELDLIST + if (match := self.CLASS_FIELD_RE.match(line)) is not None: + if match.group("field_type") == "0x0000": + # Not redundant. UDT might not match the key. + # These cases get reported as UDT mismatch. + self._set("is_forward_ref", True) + else: + field_list_type = normalize_type_id(match.group("field_type")) + self._set("field_list_type", field_list_type) + + elif line.lstrip().startswith("Derivation list type"): + # We do not care about the second line, but we still match it so we see an error + # when another line fails to match + pass + elif (match := self.CLASS_NAME_RE.match(line)) is not None: + # Last line has the vital information. + # If this is a FORWARD REF, we need to follow the UDT pointer + # to get the actual class details. + self._set("name", match.group("name")) + udt = match.group("udt") + if udt is not None: + self._set("udt", normalize_type_id(udt)) + self._set("size", int(match.group("size"))) + else: + logger.error("Unmatched line in class: %s", line[:-1]) + + def read_arglist_line(self, line: str): + if (match := self.LF_ARGLIST_ENTRY.match(line)) is not None: + obj = self.keys[self.last_key] + arglist: list = obj.setdefault("args", []) + assert int(match.group("index")) == len( + arglist + ), "Argument list out of sync" + arglist.append(match.group("arg_type")) + else: + logger.error("Unmatched line in arglist: %s", line[:-1]) + + def read_pointer_line(self, line: str): + if (match := self.LF_POINTER_ELEMENT.match(line)) is not None: + self._set("element_type", match.group("element_type")) + else: + stripped_line = line.strip() + # We don't parse these lines, but we still want to check for exhaustiveness + # in case we missed some relevant data + if not any( + stripped_line.startswith(prefix) + for prefix in ["Pointer", "const Pointer", "L-value", "volatile"] + ): + logger.error("Unrecognized pointer attribute: %s", line[:-1]) + + def read_mfunction_line(self, line: str): + """ + The layout is not consistent, so we want to be as robust as possible here. + - Example 1: + Return type = T_LONG(0012), Call type = C Near + Func attr = none + - Example 2: + Return type = T_CHAR(0010), Class type = 0x101A, This type = 0x101B, + Call type = ThisCall, Func attr = none + """ + + obj = self.keys[self.last_key] + + key_value_pairs = line.split(",") + for pair in key_value_pairs: + if pair.isspace(): + continue + obj |= self.parse_function_attribute(pair) + + def parse_function_attribute(self, pair: str) -> dict[str, str]: + for attribute_regex in self.LF_MFUNCTION_ATTRIBUTES: + if (match := attribute_regex.match(pair)) is not None: + return match.groupdict() + logger.error("Unknown attribute in function: %s", pair) + return {} + + def read_enum_line(self, line: str): + obj = self.keys[self.last_key] + + # We need special comma handling because commas may appear in the name. + # Splitting by "," yields the wrong result. + enum_attributes = line.split(", ") + for pair in enum_attributes: + if pair.endswith(","): + pair = pair[:-1] + if pair.isspace(): + continue + obj |= self.parse_enum_attribute(pair) + + def parse_enum_attribute(self, attribute: str) -> dict[str, Any]: + for attribute_regex in self.LF_ENUM_ATTRIBUTES: + if (match := attribute_regex.match(attribute)) is not None: + return match.groupdict() + if attribute == "NESTED": + return {"is_nested": True} + if attribute == "FORWARD REF": + return {"is_forward_ref": True} + if attribute.startswith("UDT"): + match = self.LF_ENUM_UDT.match(attribute) + assert match is not None + return {"udt": normalize_type_id(match.group("udt"))} + if (match := self.LF_ENUM_TYPES.match(attribute)) is not None: + result = match.groupdict() + result["underlying_type"] = normalize_type_id(result["underlying_type"]) + return result + logger.error("Unknown attribute in enum: %s", attribute) + return {} + + def read_union_line(self, line: str): + """This is a rather barebones handler, only parsing the size""" + if (match := self.LF_UNION_LINE.match(line)) is None: + raise AssertionError(f"Unhandled in union: {line}") + self._set("name", match.group("name")) + if match.group("field_type") == "0x0000": + self._set("is_forward_ref", True) + + self._set("size", int(match.group("size"))) + if match.group("udt") is not None: + self._set("udt", normalize_type_id(match.group("udt"))) diff --git a/tools/isledecomp/isledecomp/dir.py b/tools/isledecomp/isledecomp/dir.py new file mode 100644 index 00000000..ca7e3fd5 --- /dev/null +++ b/tools/isledecomp/isledecomp/dir.py @@ -0,0 +1,103 @@ +import os +import subprocess +import sys +import pathlib +from typing import Iterator + + +def winepath_win_to_unix(path: str) -> str: + return subprocess.check_output(["winepath", path], text=True).strip() + + +def winepath_unix_to_win(path: str) -> str: + return subprocess.check_output(["winepath", "-w", path], text=True).strip() + + +class PathResolver: + """Intended to resolve Windows/Wine paths used in the PDB (cvdump) output + into a "canonical" format to be matched against code file paths from os.walk. + MSVC may include files from the parent dir using `..`. We eliminate those and create + an absolute path so that information about the same file under different names + will be combined into the same record. (i.e. line_no/addr pairs from LINES section.) + """ + + def __init__(self, basedir) -> None: + """basedir is the root path of the code directory in the format for your OS. + We will convert it to a PureWindowsPath to be platform-independent + and match that to the paths from the PDB.""" + + # Memoize the converted paths. We will need to do this for each path + # in the PDB, for each function in that file. (i.e. lots of repeated work) + self._memo = {} + + # Convert basedir to an absolute path if it is not already. + # If it is not absolute, we cannot do the path swap on unix. + self._realdir = pathlib.Path(basedir).resolve() + + self._is_unix = os.name != "nt" + if self._is_unix: + self._basedir = pathlib.PureWindowsPath( + winepath_unix_to_win(str(self._realdir)) + ) + else: + self._basedir = self._realdir + + def _memo_wrapper(self, path_str: str) -> str: + """Wrapper so we can memoize from the public caller method""" + path = pathlib.PureWindowsPath(path_str) + if not path.is_absolute(): + # pathlib syntactic sugar for path concat + path = self._basedir / path + + if self._is_unix: + # If the given path is relative to the basedir, deconstruct the path + # and swap in our unix path to avoid an expensive call to winepath. + try: + # Will raise ValueError if we are not relative to the base. + section = path.relative_to(self._basedir) + # Should combine to pathlib.PosixPath + mockpath = (self._realdir / section).resolve() + if mockpath.is_file(): + return str(mockpath) + except ValueError: + pass + + # We are not relative to the basedir, or our path swap attempt + # did not point at an actual file. Either way, we are forced + # to call winepath using our original path. + return winepath_win_to_unix(str(path)) + + # We must be on Windows. Convert back to WindowsPath. + # The resolve() call will eliminate intermediate backdir references. + return str(pathlib.Path(path).resolve()) + + def resolve_cvdump(self, path_str: str) -> str: + """path_str is in Windows/Wine path format. + We will return a path in the format for the host OS.""" + if path_str not in self._memo: + self._memo[path_str] = self._memo_wrapper(path_str) + + return self._memo[path_str] + + +def is_file_cpp(filename: str) -> bool: + (_, ext) = os.path.splitext(filename) + return ext.lower() in (".h", ".cpp") + + +def walk_source_dir(source: str, recursive: bool = True) -> Iterator[str]: + """Generator to walk the given directory recursively and return + any C++ files found.""" + + source = os.path.abspath(source) + for subdir, _, files in os.walk(source): + for file in files: + if is_file_cpp(file): + yield os.path.join(subdir, file) + + if not recursive: + break + + +def get_file_in_script_dir(fn): + return os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), fn) diff --git a/tools/isledecomp/isledecomp/lib/DUMPBIN.EXE b/tools/isledecomp/isledecomp/lib/DUMPBIN.EXE new file mode 100644 index 00000000..c57ed02c Binary files /dev/null and b/tools/isledecomp/isledecomp/lib/DUMPBIN.EXE differ diff --git a/tools/isledecomp/isledecomp/lib/LINK.EXE b/tools/isledecomp/isledecomp/lib/LINK.EXE new file mode 100644 index 00000000..9d35a899 Binary files /dev/null and b/tools/isledecomp/isledecomp/lib/LINK.EXE differ diff --git a/tools/isledecomp/isledecomp/lib/MSPDB41.DLL b/tools/isledecomp/isledecomp/lib/MSPDB41.DLL new file mode 100644 index 00000000..9d2953e2 Binary files /dev/null and b/tools/isledecomp/isledecomp/lib/MSPDB41.DLL differ diff --git a/tools/isledecomp/isledecomp/lib/__init__.py b/tools/isledecomp/isledecomp/lib/__init__.py new file mode 100644 index 00000000..f1081a81 --- /dev/null +++ b/tools/isledecomp/isledecomp/lib/__init__.py @@ -0,0 +1,13 @@ +"""Provides a reference point for redistributed tools found in this directory. +This allows you to get the path for these tools from a script run anywhere.""" +from os.path import join, dirname + + +def lib_path() -> str: + """Returns the directory for this module.""" + return dirname(__file__) + + +def lib_path_join(name: str) -> str: + """Convenience wrapper for os.path.join.""" + return join(lib_path(), name) diff --git a/tools/isledecomp/isledecomp/lib/cvdump.exe b/tools/isledecomp/isledecomp/lib/cvdump.exe new file mode 100644 index 00000000..8c1eff6e Binary files /dev/null and b/tools/isledecomp/isledecomp/lib/cvdump.exe differ diff --git a/tools/isledecomp/isledecomp/parser/__init__.py b/tools/isledecomp/isledecomp/parser/__init__.py new file mode 100644 index 00000000..14549700 --- /dev/null +++ b/tools/isledecomp/isledecomp/parser/__init__.py @@ -0,0 +1,3 @@ +from .codebase import DecompCodebase +from .parser import DecompParser +from .linter import DecompLinter diff --git a/tools/isledecomp/isledecomp/parser/codebase.py b/tools/isledecomp/isledecomp/parser/codebase.py new file mode 100644 index 00000000..bae0d122 --- /dev/null +++ b/tools/isledecomp/isledecomp/parser/codebase.py @@ -0,0 +1,57 @@ +"""For aggregating decomp markers read from an entire directory and for a single module.""" +from typing import Callable, Iterable, Iterator, List +from .parser import DecompParser +from .node import ( + ParserSymbol, + ParserFunction, + ParserVtable, + ParserVariable, + ParserString, +) + + +class DecompCodebase: + def __init__(self, filenames: Iterable[str], module: str) -> None: + self._symbols: List[ParserSymbol] = [] + + parser = DecompParser() + for filename in filenames: + parser.reset() + with open(filename, "r", encoding="utf-8") as f: + parser.read_lines(f) + + for sym in parser.iter_symbols(module): + sym.filename = filename + self._symbols.append(sym) + + def prune_invalid_addrs(self, is_valid: Callable[int, bool]) -> List[ParserSymbol]: + """Some decomp annotations might have an invalid address. + Return the list of addresses where we fail the is_valid check, + and remove those from our list of symbols.""" + invalid_symbols = [sym for sym in self._symbols if not is_valid(sym.offset)] + self._symbols = [sym for sym in self._symbols if is_valid(sym.offset)] + + return invalid_symbols + + def iter_line_functions(self) -> Iterator[ParserFunction]: + """Return lineref functions separately from nameref. Assuming the PDB matches + the state of the source code, a line reference is a guaranteed match, even if + multiple functions share the same name. (i.e. polymorphism)""" + return filter( + lambda s: isinstance(s, ParserFunction) and not s.is_nameref(), + self._symbols, + ) + + def iter_name_functions(self) -> Iterator[ParserFunction]: + return filter( + lambda s: isinstance(s, ParserFunction) and s.is_nameref(), self._symbols + ) + + def iter_vtables(self) -> Iterator[ParserVtable]: + return filter(lambda s: isinstance(s, ParserVtable), self._symbols) + + def iter_variables(self) -> Iterator[ParserVariable]: + return filter(lambda s: isinstance(s, ParserVariable), self._symbols) + + def iter_strings(self) -> Iterator[ParserString]: + return filter(lambda s: isinstance(s, ParserString), self._symbols) diff --git a/tools/isledecomp/isledecomp/parser/error.py b/tools/isledecomp/isledecomp/parser/error.py new file mode 100644 index 00000000..9ced1498 --- /dev/null +++ b/tools/isledecomp/isledecomp/parser/error.py @@ -0,0 +1,97 @@ +from enum import Enum +from typing import Optional +from dataclasses import dataclass + + +# TODO: poorly chosen name, should be AlertType or AlertCode or something +class ParserError(Enum): + # WARN: Stub function exceeds some line number threshold + UNLIKELY_STUB = 100 + + # WARN: Decomp marker is close enough to be recognized, but does not follow syntax exactly + BAD_DECOMP_MARKER = 101 + + # WARN: Multiple markers in sequence do not have distinct modules + DUPLICATE_MODULE = 102 + + # WARN: Detected a dupcliate module/offset pair in the current file + DUPLICATE_OFFSET = 103 + + # WARN: We read a line that matches the decomp marker pattern, but we are not set up + # to handle it + BOGUS_MARKER = 104 + + # WARN: New function marker appeared while we were inside a function + MISSED_END_OF_FUNCTION = 105 + + # WARN: If we find a curly brace right after the function declaration + # this is wrong but we still have enough to make a match with reccmp + MISSED_START_OF_FUNCTION = 106 + + # WARN: A blank line appeared between the end of FUNCTION markers + # and the start of the function. We can ignore it, but the line shouldn't be there + UNEXPECTED_BLANK_LINE = 107 + + # WARN: We called the finish() method for the parser but had not reached the starting + # state of SEARCH + UNEXPECTED_END_OF_FILE = 108 + + # WARN: We found a marker to be referenced by name outside of a header file. + BYNAME_FUNCTION_IN_CPP = 109 + + # WARN: A GLOBAL marker appeared over a variable without the g_ prefix + GLOBAL_MISSING_PREFIX = 110 + + # WARN: GLOBAL marker points at something other than variable declaration. + # We can't match global variables based on position, but the goal here is + # to ignore things like string literal that are not variables. + GLOBAL_NOT_VARIABLE = 111 + + # WARN: A marked static variable inside a function needs to have its + # function marked too, and in the same module. + ORPHANED_STATIC_VARIABLE = 112 + + # This code or higher is an error, not a warning + DECOMP_ERROR_START = 200 + + # ERROR: We found a marker unexpectedly + UNEXPECTED_MARKER = 200 + + # ERROR: We found a marker where we expected to find one, but it is incompatible + # with the preceding markers. + # For example, a GLOBAL cannot follow FUNCTION/STUB + INCOMPATIBLE_MARKER = 201 + + # ERROR: The line following an explicit by-name marker was not a comment + # We assume a syntax error here rather than try to use the next line + BAD_NAMEREF = 202 + + # ERROR: This function offset comes before the previous offset from the same module + # This hopefully gives some hint about which functions need to be rearranged. + FUNCTION_OUT_OF_ORDER = 203 + + # ERROR: The line following an explicit by-name marker that does _not_ expect + # a comment -- i.e. VTABLE or GLOBAL -- could not extract the name + NO_SUITABLE_NAME = 204 + + # ERROR: Two STRING markers have the same module and offset, but the strings + # they annotate are different. + WRONG_STRING = 205 + + # ERROR: This lineref FUNCTION marker is next to a function declaration or + # forward reference. The correct place for the marker is where the function + # is implemented so we can match with the PDB. + NO_IMPLEMENTATION = 206 + + +@dataclass +class ParserAlert: + code: ParserError + line_number: int + line: Optional[str] = None + + def is_warning(self) -> bool: + return self.code.value < ParserError.DECOMP_ERROR_START.value + + def is_error(self) -> bool: + return self.code.value >= ParserError.DECOMP_ERROR_START.value diff --git a/tools/isledecomp/isledecomp/parser/linter.py b/tools/isledecomp/isledecomp/parser/linter.py new file mode 100644 index 00000000..b44487df --- /dev/null +++ b/tools/isledecomp/isledecomp/parser/linter.py @@ -0,0 +1,144 @@ +from typing import List, Optional +from .parser import DecompParser +from .error import ParserAlert, ParserError +from .node import ParserSymbol, ParserString + + +def get_checkorder_filter(module): + """Return a filter function on implemented functions in the given module""" + return lambda fun: fun.module == module and not fun.lookup_by_name + + +class DecompLinter: + def __init__(self) -> None: + self.alerts: List[ParserAlert] = [] + self._parser = DecompParser() + self._filename: str = "" + self._module: Optional[str] = None + # Set of (str, int) tuples for each module/offset pair seen while scanning. + # This is _not_ reset between files and is intended to report offset reuse + # when scanning the entire directory. + self._offsets_used = set() + # Keep track of strings we have seen. Persists across files. + # Module/offset can be repeated for string markers but the strings must match. + self._strings = {} + + def reset(self, full_reset: bool = False): + self.alerts = [] + self._parser.reset() + self._filename = "" + self._module = None + + if full_reset: + self._offsets_used.clear() + self._strings = {} + + def file_is_header(self): + return self._filename.lower().endswith(".h") + + def _load_offsets_from_list(self, marker_list: List[ParserSymbol]): + """Helper for loading (module, offset) tuples while the DecompParser + has them broken up into three different lists.""" + for marker in marker_list: + is_string = isinstance(marker, ParserString) + + value = (marker.module, marker.offset) + if value in self._offsets_used: + if is_string: + if self._strings[value] != marker.name: + self.alerts.append( + ParserAlert( + code=ParserError.WRONG_STRING, + line_number=marker.line_number, + line=f"0x{marker.offset:08x}, {repr(self._strings[value])} vs. {repr(marker.name)}", + ) + ) + else: + self.alerts.append( + ParserAlert( + code=ParserError.DUPLICATE_OFFSET, + line_number=marker.line_number, + line=f"0x{marker.offset:08x}", + ) + ) + else: + self._offsets_used.add(value) + if is_string: + self._strings[value] = marker.name + + def _check_function_order(self): + """Rules: + 1. Only markers that are implemented in the file are considered. This means we + only look at markers that are cross-referenced with cvdump output by their line + number. Markers with the lookup_by_name flag set are ignored because we cannot + directly influence their order. + + 2. Order should be considered for a single module only. If we have multiple + markers for a single function (i.e. for LEGO1 functions linked statically to + ISLE) then the virtual address space will be very different. If we don't check + for one module only, we would incorrectly report that the file is out of order. + """ + + if self._module is None: + return + + checkorder_filter = get_checkorder_filter(self._module) + last_offset = None + for fun in filter(checkorder_filter, self._parser.functions): + if last_offset is not None: + if fun.offset < last_offset: + self.alerts.append( + ParserAlert( + code=ParserError.FUNCTION_OUT_OF_ORDER, + line_number=fun.line_number, + ) + ) + + last_offset = fun.offset + + def _check_offset_uniqueness(self): + self._load_offsets_from_list(self._parser.functions) + self._load_offsets_from_list(self._parser.vtables) + self._load_offsets_from_list(self._parser.variables) + self._load_offsets_from_list(self._parser.strings) + + def _check_byname_allowed(self): + if self.file_is_header(): + return + + for fun in self._parser.functions: + if fun.lookup_by_name: + self.alerts.append( + ParserAlert( + code=ParserError.BYNAME_FUNCTION_IN_CPP, + line_number=fun.line_number, + ) + ) + + def check_lines(self, lines, filename, module=None): + """`lines` is a generic iterable to allow for testing with a list of strings. + We assume lines has the entire contents of the compilation unit.""" + + self.reset(False) + self._filename = filename + self._module = module + + self._parser.read_lines(lines) + + self._parser.finish() + self.alerts = self._parser.alerts[::] + + self._check_offset_uniqueness() + + if self._module is not None: + self._check_byname_allowed() + + if not self.file_is_header(): + self._check_function_order() + + return len(self.alerts) == 0 + + def check_file(self, filename, module=None): + """Convenience method for decomplint cli tool""" + with open(filename, "r", encoding="utf-8") as f: + return self.check_lines(f, filename, module) diff --git a/tools/isledecomp/isledecomp/parser/marker.py b/tools/isledecomp/isledecomp/parser/marker.py new file mode 100644 index 00000000..8108ac46 --- /dev/null +++ b/tools/isledecomp/isledecomp/parser/marker.py @@ -0,0 +1,146 @@ +import re +from typing import Optional, Tuple +from enum import Enum + + +class MarkerCategory(Enum): + """For the purposes of grouping multiple different DecompMarkers together, + assign a rough "category" for the MarkerType values below. + It's really only the function types that have to get folded down, but + we'll do that in a structured way to permit future expansion.""" + + FUNCTION = 1 + VARIABLE = 2 + STRING = 3 + VTABLE = 4 + ADDRESS = 100 # i.e. no comparison required or possible + + +class MarkerType(Enum): + UNKNOWN = -100 + FUNCTION = 1 + STUB = 2 + SYNTHETIC = 3 + TEMPLATE = 4 + GLOBAL = 5 + VTABLE = 6 + STRING = 7 + LIBRARY = 8 + + +markerRegex = re.compile( + r"\s*//\s*(?P\w+):\s*(?P\w+)\s+(?P0x[a-f0-9]+) *(?P\S.+\S)?", + flags=re.I, +) + + +markerExactRegex = re.compile( + r"\s*// (?P[A-Z]+): (?P[A-Z0-9]+) (?P0x[a-f0-9]+)(?: (?P\S.+\S))?\n?$" +) + + +class DecompMarker: + def __init__( + self, marker_type: str, module: str, offset: int, extra: Optional[str] = None + ) -> None: + try: + self._type = MarkerType[marker_type.upper()] + except KeyError: + self._type = MarkerType.UNKNOWN + + # Convert to upper here. A lot of other analysis depends on this name + # being consistent and predictable. If the name is _not_ capitalized + # we will emit a syntax error. + self._module: str = module.upper() + self._offset: int = offset + self._extra: Optional[str] = extra + + @property + def type(self) -> MarkerType: + return self._type + + @property + def module(self) -> str: + return self._module + + @property + def offset(self) -> int: + return self._offset + + @property + def extra(self) -> Optional[str]: + return self._extra + + @property + def category(self) -> MarkerCategory: + if self.is_vtable(): + return MarkerCategory.VTABLE + + if self.is_variable(): + return MarkerCategory.VARIABLE + + if self.is_string(): + return MarkerCategory.STRING + + # TODO: worth another look if we add more types, but this covers it + if self.is_regular_function() or self.is_explicit_byname(): + return MarkerCategory.FUNCTION + + return MarkerCategory.ADDRESS + + @property + def key(self) -> Tuple[str, str, Optional[str]]: + """For use with the MarkerDict. To detect/avoid marker collision.""" + return (self.category, self.module, self.extra) + + def is_regular_function(self) -> bool: + """Regular function, meaning: not an explicit byname lookup. FUNCTION + markers can be _implicit_ byname. + FUNCTION and STUB markers are (currently) the only heterogenous marker types that + can be lumped together, although the reasons for doing so are a little vague.""" + return self._type in (MarkerType.FUNCTION, MarkerType.STUB) + + def is_explicit_byname(self) -> bool: + return self._type in ( + MarkerType.SYNTHETIC, + MarkerType.TEMPLATE, + MarkerType.LIBRARY, + ) + + def is_variable(self) -> bool: + return self._type == MarkerType.GLOBAL + + def is_synthetic(self) -> bool: + return self._type == MarkerType.SYNTHETIC + + def is_template(self) -> bool: + return self._type == MarkerType.TEMPLATE + + def is_vtable(self) -> bool: + return self._type == MarkerType.VTABLE + + def is_library(self) -> bool: + return self._type == MarkerType.LIBRARY + + def is_string(self) -> bool: + return self._type == MarkerType.STRING + + def allowed_in_func(self) -> bool: + return self._type in (MarkerType.GLOBAL, MarkerType.STRING) + + +def match_marker(line: str) -> Optional[DecompMarker]: + match = markerRegex.match(line) + if match is None: + return None + + return DecompMarker( + marker_type=match.group("type"), + module=match.group("module"), + offset=int(match.group("offset"), 16), + extra=match.group("extra"), + ) + + +def is_marker_exact(line: str) -> bool: + return markerExactRegex.match(line) is not None diff --git a/tools/isledecomp/isledecomp/parser/node.py b/tools/isledecomp/isledecomp/parser/node.py new file mode 100644 index 00000000..21e4c382 --- /dev/null +++ b/tools/isledecomp/isledecomp/parser/node.py @@ -0,0 +1,63 @@ +from typing import Optional +from dataclasses import dataclass +from .marker import MarkerType + + +@dataclass +class ParserSymbol: + """Exported decomp marker with all information (except the code filename) required to + cross-reference with cvdump data.""" + + type: MarkerType + line_number: int + module: str + offset: int + name: str + + # The parser doesn't (currently) know about the code filename, but if you + # wanted to set it here after the fact, here's the spot. + filename: Optional[str] = None + + def should_skip(self) -> bool: + """The default is to compare any symbols we have""" + return False + + def is_nameref(self) -> bool: + """All symbols default to name lookup""" + return True + + +@dataclass +class ParserFunction(ParserSymbol): + # We are able to detect the closing line of a function with some reliability. + # This isn't used for anything right now, but perhaps later it will be. + end_line: Optional[int] = None + + # All marker types are referenced by name except FUNCTION/STUB. These can also be + # referenced by name, but only if this flag is true. + lookup_by_name: bool = False + + def should_skip(self) -> bool: + return self.type == MarkerType.STUB + + def is_nameref(self) -> bool: + return ( + self.type in (MarkerType.SYNTHETIC, MarkerType.TEMPLATE, MarkerType.LIBRARY) + or self.lookup_by_name + ) + + +@dataclass +class ParserVariable(ParserSymbol): + is_static: bool = False + parent_function: Optional[int] = None + + +@dataclass +class ParserVtable(ParserSymbol): + base_class: Optional[str] = None + + +@dataclass +class ParserString(ParserSymbol): + pass diff --git a/tools/isledecomp/isledecomp/parser/parser.py b/tools/isledecomp/isledecomp/parser/parser.py new file mode 100644 index 00000000..92e41dd3 --- /dev/null +++ b/tools/isledecomp/isledecomp/parser/parser.py @@ -0,0 +1,556 @@ +# C++ file parser + +from typing import List, Iterable, Iterator, Optional +from enum import Enum +from .util import ( + get_class_name, + get_variable_name, + get_synthetic_name, + remove_trailing_comment, + get_string_contents, + sanitize_code_line, + scopeDetectRegex, +) +from .marker import ( + DecompMarker, + MarkerCategory, + match_marker, + is_marker_exact, +) +from .node import ( + ParserSymbol, + ParserFunction, + ParserVariable, + ParserVtable, + ParserString, +) +from .error import ParserAlert, ParserError + + +class ReaderState(Enum): + SEARCH = 0 + WANT_SIG = 1 + IN_FUNC = 2 + IN_TEMPLATE = 3 + WANT_CURLY = 4 + IN_GLOBAL = 5 + IN_FUNC_GLOBAL = 6 + IN_VTABLE = 7 + IN_SYNTHETIC = 8 + IN_LIBRARY = 9 + DONE = 100 + + +class MarkerDict: + def __init__(self) -> None: + self.markers: dict = {} + + def insert(self, marker: DecompMarker) -> bool: + """Return True if this insert would overwrite""" + if marker.key in self.markers: + return True + + self.markers[marker.key] = marker + return False + + def query( + self, category: MarkerCategory, module: str, extra: Optional[str] = None + ) -> Optional[DecompMarker]: + return self.markers.get((category, module, extra)) + + def iter(self) -> Iterator[DecompMarker]: + for _, marker in self.markers.items(): + yield marker + + def empty(self): + self.markers = {} + + +class CurlyManager: + """Overly simplified scope manager""" + + def __init__(self): + self._stack = [] + + def reset(self): + self._stack = [] + + def _pop(self): + """Pop stack safely""" + try: + self._stack.pop() + except IndexError: + pass + + def get_prefix(self, name: Optional[str] = None) -> str: + """Return the prefix for where we are.""" + + scopes = [t for t in self._stack if t != "{"] + if len(scopes) == 0: + return name if name is not None else "" + + if name is not None and name not in scopes: + scopes.append(name) + + return "::".join(scopes) + + def read_line(self, raw_line: str): + """Read a line of code and update the stack.""" + line = sanitize_code_line(raw_line) + if (match := scopeDetectRegex.match(line)) is not None: + if not line.endswith(";"): + self._stack.append(match.group("name")) + + change = line.count("{") - line.count("}") + if change > 0: + for _ in range(change): + self._stack.append("{") + elif change < 0: + for _ in range(-change): + self._pop() + + if len(self._stack) == 0: + return + + last = self._stack[-1] + if last != "{": + self._pop() + + +class DecompParser: + # pylint: disable=too-many-instance-attributes + # Could combine output lists into a single list to get under the limit, + # but not right now + def __init__(self) -> None: + # The lists to be populated as we parse + self._symbols: List[ParserSymbol] = [] + self.alerts: List[ParserAlert] = [] + + self.line_number: int = 0 + self.state: ReaderState = ReaderState.SEARCH + + self.last_line: str = "" + + self.curly = CurlyManager() + + # To allow for multiple markers where code is shared across different + # modules, save lists of compatible markers that appear in sequence + self.fun_markers = MarkerDict() + self.var_markers = MarkerDict() + self.tbl_markers = MarkerDict() + + # To handle functions that are entirely indented (i.e. those defined + # in class declarations), remember how many whitespace characters + # came before the opening curly brace and match that up at the end. + # This should give us the same or better accuracy for a well-formed file. + # The alternative is counting the curly braces on each line + # but that's probably too cumbersome. + self.curly_indent_stops: int = 0 + + # For non-synthetic functions, save the line number where the function begins + # (i.e. where we see the curly brace) along with the function signature. + # We will need both when we reach the end of the function. + self.function_start: int = 0 + self.function_sig: str = "" + + def reset(self): + self._symbols = [] + self.alerts = [] + + self.line_number = 0 + self.state = ReaderState.SEARCH + + self.last_line = "" + + self.fun_markers.empty() + self.var_markers.empty() + self.tbl_markers.empty() + + self.curly_indent_stops = 0 + self.function_start = 0 + self.function_sig = "" + + self.curly.reset() + + @property + def functions(self) -> List[ParserFunction]: + return [s for s in self._symbols if isinstance(s, ParserFunction)] + + @property + def vtables(self) -> List[ParserVtable]: + return [s for s in self._symbols if isinstance(s, ParserVtable)] + + @property + def variables(self) -> List[ParserVariable]: + return [s for s in self._symbols if isinstance(s, ParserVariable)] + + @property + def strings(self) -> List[ParserString]: + return [s for s in self._symbols if isinstance(s, ParserString)] + + def iter_symbols(self, module: Optional[str] = None) -> Iterator[ParserSymbol]: + for s in self._symbols: + if module is None or s.module == module: + yield s + + def _recover(self): + """We hit a syntax error and need to reset temp structures""" + self.state = ReaderState.SEARCH + self.fun_markers.empty() + self.var_markers.empty() + self.tbl_markers.empty() + + def _syntax_warning(self, code): + self.alerts.append( + ParserAlert( + line_number=self.line_number, + code=code, + line=self.last_line.strip(), + ) + ) + + def _syntax_error(self, code): + self._syntax_warning(code) + self._recover() + + def _function_starts_here(self): + self.function_start = self.line_number + + def _function_marker(self, marker: DecompMarker): + if self.fun_markers.insert(marker): + self._syntax_warning(ParserError.DUPLICATE_MODULE) + self.state = ReaderState.WANT_SIG + + def _nameref_marker(self, marker: DecompMarker): + """Functions explicitly referenced by name are set here""" + if self.fun_markers.insert(marker): + self._syntax_warning(ParserError.DUPLICATE_MODULE) + + if marker.is_template(): + self.state = ReaderState.IN_TEMPLATE + elif marker.is_synthetic(): + self.state = ReaderState.IN_SYNTHETIC + else: + self.state = ReaderState.IN_LIBRARY + + def _function_done(self, lookup_by_name: bool = False, unexpected: bool = False): + end_line = self.line_number + if unexpected: + # If we missed the end of the previous function, assume it ended + # on the previous line and that whatever we are tracking next + # begins on the current line. + end_line -= 1 + + for marker in self.fun_markers.iter(): + self._symbols.append( + ParserFunction( + type=marker.type, + line_number=self.function_start, + module=marker.module, + offset=marker.offset, + name=self.function_sig, + lookup_by_name=lookup_by_name, + end_line=end_line, + ) + ) + + self.fun_markers.empty() + self.curly_indent_stops = 0 + self.state = ReaderState.SEARCH + + def _vtable_marker(self, marker: DecompMarker): + if self.tbl_markers.insert(marker): + self._syntax_warning(ParserError.DUPLICATE_MODULE) + self.state = ReaderState.IN_VTABLE + + def _vtable_done(self, class_name: str = None): + if class_name is None: + # Best we can do + class_name = self.last_line.strip() + + for marker in self.tbl_markers.iter(): + self._symbols.append( + ParserVtable( + type=marker.type, + line_number=self.line_number, + module=marker.module, + offset=marker.offset, + name=self.curly.get_prefix(class_name), + base_class=marker.extra, + ) + ) + + self.tbl_markers.empty() + self.state = ReaderState.SEARCH + + def _variable_marker(self, marker: DecompMarker): + if self.var_markers.insert(marker): + self._syntax_warning(ParserError.DUPLICATE_MODULE) + + if self.state in (ReaderState.IN_FUNC, ReaderState.IN_FUNC_GLOBAL): + self.state = ReaderState.IN_FUNC_GLOBAL + else: + self.state = ReaderState.IN_GLOBAL + + def _variable_done( + self, variable_name: Optional[str] = None, string_value: Optional[str] = None + ): + if variable_name is None and string_value is None: + self._syntax_error(ParserError.NO_SUITABLE_NAME) + return + + for marker in self.var_markers.iter(): + if marker.is_string(): + self._symbols.append( + ParserString( + type=marker.type, + line_number=self.line_number, + module=marker.module, + offset=marker.offset, + name=string_value, + ) + ) + else: + parent_function = None + is_static = self.state == ReaderState.IN_FUNC_GLOBAL + + # If this is a static variable, we need to get the function + # where it resides so that we can match it up later with the + # mangled names of both variable and function from cvdump. + if is_static: + fun_marker = self.fun_markers.query( + MarkerCategory.FUNCTION, marker.module + ) + + if fun_marker is None: + self._syntax_warning(ParserError.ORPHANED_STATIC_VARIABLE) + continue + + parent_function = fun_marker.offset + + self._symbols.append( + ParserVariable( + type=marker.type, + line_number=self.line_number, + module=marker.module, + offset=marker.offset, + name=self.curly.get_prefix(variable_name), + is_static=is_static, + parent_function=parent_function, + ) + ) + + self.var_markers.empty() + if self.state == ReaderState.IN_FUNC_GLOBAL: + self.state = ReaderState.IN_FUNC + else: + self.state = ReaderState.SEARCH + + def _handle_marker(self, marker: DecompMarker): + # Cannot handle any markers between function sig and opening curly brace + if self.state == ReaderState.WANT_CURLY: + self._syntax_error(ParserError.UNEXPECTED_MARKER) + return + + # If we are inside a function, the only markers we accept are: + # GLOBAL, indicating a static variable + # STRING, indicating a literal string. + # Otherwise we assume that the parser missed the end of the function + # and we have moved on to something else. + # This is unlikely to occur with well-formed code, but + # we can recover easily by just ending the function here. + if self.state == ReaderState.IN_FUNC and not marker.allowed_in_func(): + self._syntax_warning(ParserError.MISSED_END_OF_FUNCTION) + self._function_done(unexpected=True) + + # TODO: How uncertain are we of detecting the end of a function + # in a clang-formatted file? For now we assume we have missed the + # end if we detect a non-GLOBAL marker while state is IN_FUNC. + # Maybe these cases should be syntax errors instead + + if marker.is_regular_function(): + if self.state in ( + ReaderState.SEARCH, + ReaderState.WANT_SIG, + ): + # We will allow multiple offsets if we have just begun + # the code block, but not after we hit the curly brace. + self._function_marker(marker) + else: + self._syntax_error(ParserError.INCOMPATIBLE_MARKER) + + elif marker.is_template(): + if self.state in (ReaderState.SEARCH, ReaderState.IN_TEMPLATE): + self._nameref_marker(marker) + else: + self._syntax_error(ParserError.INCOMPATIBLE_MARKER) + + elif marker.is_synthetic(): + if self.state in (ReaderState.SEARCH, ReaderState.IN_SYNTHETIC): + self._nameref_marker(marker) + else: + self._syntax_error(ParserError.INCOMPATIBLE_MARKER) + + elif marker.is_library(): + if self.state in (ReaderState.SEARCH, ReaderState.IN_LIBRARY): + self._nameref_marker(marker) + else: + self._syntax_error(ParserError.INCOMPATIBLE_MARKER) + + # Strings and variables are almost the same thing + elif marker.is_string() or marker.is_variable(): + if self.state in ( + ReaderState.SEARCH, + ReaderState.IN_GLOBAL, + ReaderState.IN_FUNC, + ReaderState.IN_FUNC_GLOBAL, + ): + self._variable_marker(marker) + else: + self._syntax_error(ParserError.INCOMPATIBLE_MARKER) + + elif marker.is_vtable(): + if self.state in (ReaderState.SEARCH, ReaderState.IN_VTABLE): + self._vtable_marker(marker) + else: + self._syntax_error(ParserError.INCOMPATIBLE_MARKER) + + else: + self._syntax_warning(ParserError.BOGUS_MARKER) + + def read_line(self, line: str): + if self.state == ReaderState.DONE: + return + + self.last_line = line # TODO: Useful or hack for error reporting? + self.line_number += 1 + + marker = match_marker(line) + if marker is not None: + # TODO: what's the best place for this? + # Does it belong with reading or marker handling? + if not is_marker_exact(self.last_line): + self._syntax_warning(ParserError.BAD_DECOMP_MARKER) + self._handle_marker(marker) + return + + self.curly.read_line(line) + + line_strip = line.strip() + if self.state in ( + ReaderState.IN_SYNTHETIC, + ReaderState.IN_TEMPLATE, + ReaderState.IN_LIBRARY, + ): + # Explicit nameref functions provide the function name + # on the next line (in a // comment) + name = get_synthetic_name(line) + if name is None: + self._syntax_error(ParserError.BAD_NAMEREF) + else: + self.function_sig = name + self._function_starts_here() + self._function_done(lookup_by_name=True) + + elif self.state == ReaderState.WANT_SIG: + # Ignore blanks on the way to function start or function name + if len(line_strip) == 0: + self._syntax_warning(ParserError.UNEXPECTED_BLANK_LINE) + + elif line_strip.startswith("//"): + # If we found a comment, assume implicit lookup-by-name + # function and end here. We know this is not a decomp marker + # because it would have been handled already. + self.function_sig = get_synthetic_name(line) + self._function_starts_here() + self._function_done(lookup_by_name=True) + + elif line_strip == "{": + # We missed the function signature but we can recover from this + self.function_sig = "(unknown)" + self._function_starts_here() + self._syntax_warning(ParserError.MISSED_START_OF_FUNCTION) + self.state = ReaderState.IN_FUNC + + else: + # Inline functions may end with a comment. Strip that out + # to help parsing. + self.function_sig = remove_trailing_comment(line_strip) + + # Now check to see if the opening curly bracket is on the + # same line. clang-format should prevent this (BraceWrapping) + # but it is easy to detect. + # If the entire function is on one line, handle that too. + if self.function_sig.endswith("{"): + self._function_starts_here() + self.state = ReaderState.IN_FUNC + elif self.function_sig.endswith("}") or self.function_sig.endswith( + "};" + ): + self._function_starts_here() + self._function_done() + elif self.function_sig.endswith(");"): + # Detect forward reference or declaration + self._syntax_error(ParserError.NO_IMPLEMENTATION) + else: + self.state = ReaderState.WANT_CURLY + + elif self.state == ReaderState.WANT_CURLY: + if line_strip == "{": + self.curly_indent_stops = line.index("{") + self._function_starts_here() + self.state = ReaderState.IN_FUNC + + elif self.state == ReaderState.IN_FUNC: + if line_strip.startswith("}") and line[self.curly_indent_stops] == "}": + self._function_done() + + elif self.state in (ReaderState.IN_GLOBAL, ReaderState.IN_FUNC_GLOBAL): + # TODO: Known problem that an error here will cause us to abandon a + # function we have already parsed if state == IN_FUNC_GLOBAL. + # However, we are not tolerant of _any_ syntax problems in our + # CI actions, so the solution is to just fix the invalid marker. + variable_name = None + + global_markers_queued = any( + m.is_variable() for m in self.var_markers.iter() + ) + + if len(line_strip) == 0: + self._syntax_warning(ParserError.UNEXPECTED_BLANK_LINE) + return + + if global_markers_queued: + # Not the greatest solution, but a consequence of combining GLOBAL and + # STRING markers together. If the marker precedes a return statement, it is + # valid for a STRING marker to be here, but not a GLOBAL. We need to look + # ahead and tell whether this *would* fail. + if line_strip.startswith("return"): + self._syntax_error(ParserError.GLOBAL_NOT_VARIABLE) + return + if line_strip.startswith("//"): + # If we found a comment, assume implicit lookup-by-name + # function and end here. We know this is not a decomp marker + # because it would have been handled already. + variable_name = get_synthetic_name(line) + else: + variable_name = get_variable_name(line) + + string_name = get_string_contents(line) + + self._variable_done(variable_name, string_name) + + elif self.state == ReaderState.IN_VTABLE: + vtable_class = get_class_name(line) + if vtable_class is not None: + self._vtable_done(class_name=vtable_class) + + def read_lines(self, lines: Iterable): + for line in lines: + self.read_line(line) + + def finish(self): + if self.state != ReaderState.SEARCH: + self._syntax_warning(ParserError.UNEXPECTED_END_OF_FILE) + + self.state = ReaderState.DONE diff --git a/tools/isledecomp/isledecomp/parser/util.py b/tools/isledecomp/isledecomp/parser/util.py new file mode 100644 index 00000000..fbc78e10 --- /dev/null +++ b/tools/isledecomp/isledecomp/parser/util.py @@ -0,0 +1,141 @@ +# C++ Parser utility functions and data structures +import re +from typing import Optional +from ast import literal_eval + +# The goal here is to just read whatever is on the next line, so some +# flexibility in the formatting seems OK +templateCommentRegex = re.compile(r"\s*//\s+(.*)") + +# To remove any comment (//) or block comment (/*) and its leading spaces +# from the end of a code line +trailingCommentRegex = re.compile(r"(\s*(?://|/\*).*)$") + +# Get char contents, ignore escape characters +singleQuoteRegex = re.compile(r"('(?:[^\'\\]|\\.)')") + +# Match contents of block comment on one line +blockCommentRegex = re.compile(r"(/\*.*?\*/)") + +# Match contents of single comment on one line +regularCommentRegex = re.compile(r"(//.*)") + +# Get string contents, ignore escape characters that might interfere +doubleQuoteRegex = re.compile(r"(\"(?:[^\"\\]|\\.)*\")") + +# Detect a line that would cause us to enter a new scope +scopeDetectRegex = re.compile(r"(?:class|struct|namespace) (?P\w+).*(?:{)?") + + +def get_synthetic_name(line: str) -> Optional[str]: + """Synthetic names appear on a single line comment on the line after the marker. + If that's not what we have, return None""" + template_match = templateCommentRegex.match(line) + + if template_match is not None: + return template_match.group(1) + + return None + + +def sanitize_code_line(line: str) -> str: + """Helper for scope manager. Removes sections from a code line + that would cause us to incorrectly detect curly brackets. + This is a very naive implementation and fails entirely on multi-line + strings or comments.""" + + line = singleQuoteRegex.sub("''", line) + line = doubleQuoteRegex.sub('""', line) + line = blockCommentRegex.sub("", line) + line = regularCommentRegex.sub("", line) + + return line.strip() + + +def remove_trailing_comment(line: str) -> str: + return trailingCommentRegex.sub("", line) + + +def is_blank_or_comment(line: str) -> bool: + """Helper to read ahead after the offset comment is matched. + There could be blank lines or other comments before the + function signature, and we want to skip those.""" + line_strip = line.strip() + return ( + len(line_strip) == 0 + or line_strip.startswith("//") + or line_strip.startswith("/*") + or line_strip.endswith("*/") + ) + + +template_regex = re.compile(r"<(?P[\w]+)\s*(?P\*+)?\s*>") + + +class_decl_regex = re.compile( + r"\s*(?:\/\/)?\s*(?:class|struct) ((?:\w+(?:<.+>)?(?:::)?)+)" +) + + +def template_replace(match: re.Match) -> str: + (type_name, asterisks) = match.groups() + if asterisks is None: + return f"<{type_name}>" + + return f"<{type_name} {asterisks}>" + + +def fix_template_type(class_name: str) -> str: + """For template classes, we should reformat the class name so it matches + the output from cvdump: one space between the template type and any asterisks + if it is a pointer type.""" + if "<" not in class_name: + return class_name + + return template_regex.sub(template_replace, class_name) + + +def get_class_name(line: str) -> Optional[str]: + """For VTABLE markers, extract the class name from the code line or comment + where it appears.""" + + match = class_decl_regex.match(line) + if match is not None: + return fix_template_type(match.group(1)) + + return None + + +global_regex = re.compile(r"(?P(?:\w+::)*g_\w+)") +less_strict_global_regex = re.compile(r"(?P(?:\w+::)*\w+)(?:\)\(|\[.*|\s*=.*|;)") + + +def get_variable_name(line: str) -> Optional[str]: + """Grab the name of the variable annotated with the GLOBAL marker. + Correct syntax would have the variable start with the prefix "g_" + but we will try to match regardless.""" + + if (match := global_regex.search(line)) is not None: + return match.group("name") + + if (match := less_strict_global_regex.search(line)) is not None: + return match.group("name") + + return None + + +def get_string_contents(line: str) -> Optional[str]: + """Return the first C string seen on this line. + We have to unescape the string, and a simple way to do that is to use + python's ast.literal_eval. I'm sure there are many pitfalls to doing + it this way, but hopefully the regex will ensure reasonably sane input.""" + + try: + if (match := doubleQuoteRegex.search(line)) is not None: + return literal_eval(match.group(1)) + # pylint: disable=broad-exception-caught + # No way to predict what kind of exception could occur. + except Exception: + pass + + return None diff --git a/tools/isledecomp/isledecomp/types.py b/tools/isledecomp/isledecomp/types.py new file mode 100644 index 00000000..31829c65 --- /dev/null +++ b/tools/isledecomp/isledecomp/types.py @@ -0,0 +1,13 @@ +"""Types shared by other modules""" +from enum import Enum + + +class SymbolType(Enum): + """Broadly tells us what kind of comparison is required for this symbol.""" + + FUNCTION = 1 + DATA = 2 + POINTER = 3 + STRING = 4 + VTABLE = 5 + FLOAT = 6 diff --git a/tools/isledecomp/isledecomp/utils.py b/tools/isledecomp/isledecomp/utils.py new file mode 100644 index 00000000..a8c6033b --- /dev/null +++ b/tools/isledecomp/isledecomp/utils.py @@ -0,0 +1,308 @@ +import os +import sys +from datetime import datetime +import logging +import colorama + + +def print_combined_diff(udiff, plain: bool = False, show_both: bool = False): + if udiff is None: + return + + # We don't know how long the address string will be ahead of time. + # Set this value for each address to try to line things up. + padding_size = 0 + + for slug, subgroups in udiff: + if plain: + print("---") + print("+++") + print(slug) + else: + print(f"{colorama.Fore.RED}---") + print(f"{colorama.Fore.GREEN}+++") + print(f"{colorama.Fore.BLUE}{slug}") + print(colorama.Style.RESET_ALL, end="") + + for subgroup in subgroups: + equal = subgroup.get("both") is not None + + if equal: + for orig_addr, line, recomp_addr in subgroup["both"]: + padding_size = max(padding_size, len(orig_addr)) + if show_both: + print(f"{orig_addr} / {recomp_addr} : {line}") + else: + print(f"{orig_addr} : {line}") + else: + for orig_addr, line in subgroup["orig"]: + padding_size = max(padding_size, len(orig_addr)) + addr_prefix = ( + f"{orig_addr} / {'':{padding_size}}" if show_both else orig_addr + ) + + if plain: + print(f"{addr_prefix} : -{line}") + else: + print( + f"{addr_prefix} : {colorama.Fore.RED}-{line}{colorama.Style.RESET_ALL}" + ) + + for recomp_addr, line in subgroup["recomp"]: + padding_size = max(padding_size, len(recomp_addr)) + addr_prefix = ( + f"{'':{padding_size}} / {recomp_addr}" + if show_both + else " " * padding_size + ) + + if plain: + print(f"{addr_prefix} : +{line}") + else: + print( + f"{addr_prefix} : {colorama.Fore.GREEN}+{line}{colorama.Style.RESET_ALL}" + ) + + # Newline between each diff subgroup. + print() + + +def print_diff(udiff, plain): + """Print diff in difflib.unified_diff format.""" + if udiff is None: + return False + + has_diff = False + for line in udiff: + has_diff = True + color = "" + if line.startswith("++") or line.startswith("@@") or line.startswith("--"): + # Skip unneeded parts of the diff for the brief view + continue + # Work out color if we are printing color + if not plain: + if line.startswith("+"): + color = colorama.Fore.GREEN + elif line.startswith("-"): + color = colorama.Fore.RED + print(color + line) + # Reset color if we're printing in color + if not plain: + print(colorama.Style.RESET_ALL, end="") + return has_diff + + +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 diff_json_display(show_both_addrs: bool = False, is_plain: bool = False): + """Generate a function that will display the diff according to + the reccmp display preferences.""" + + def formatter(orig_addr, saved, new) -> str: + old_pct = "new" + new_pct = "gone" + name = "" + recomp_addr = "n/a" + + if new is not None: + new_pct = ( + "stub" + if new.get("stub", False) + else percent_string( + new["matching"], new.get("effective", False), is_plain + ) + ) + + # Prefer the current name of this function if we have it. + # We are using the original address as the key. + # A function being renamed is not of interest here. + name = new.get("name", "") + recomp_addr = new.get("recomp", "n/a") + + if saved is not None: + old_pct = ( + "stub" + if saved.get("stub", False) + else percent_string( + saved["matching"], saved.get("effective", False), is_plain + ) + ) + + if name == "": + name = saved.get("name", "") + + if show_both_addrs: + addr_string = f"{orig_addr} / {recomp_addr:10}" + else: + addr_string = orig_addr + + # The ANSI codes from colorama counted towards string length, + # so displaying this as an ascii-like spreadsheet + # (using f-string formatting) would take some effort. + return f"{addr_string} - {name} ({old_pct} -> {new_pct})" + + return formatter + + +def diff_json( + saved_data, + new_data, + orig_file: str, + show_both_addrs: bool = False, + is_plain: bool = False, +): + """Using a saved copy of the diff summary and the current data, print a + report showing which functions/symbols have changed match percentage.""" + + # Don't try to diff a report generated for a different binary file + base_file = os.path.basename(orig_file).lower() + + if saved_data.get("file") != base_file: + logging.getLogger().error( + "Diff report for '%s' does not match current file '%s'", + saved_data.get("file"), + base_file, + ) + return + + if "timestamp" in saved_data: + now = datetime.now().replace(microsecond=0) + then = datetime.fromtimestamp(saved_data["timestamp"]).replace(microsecond=0) + + print( + " ".join( + [ + "Saved diff report generated", + then.strftime("%B %d %Y, %H:%M:%S"), + f"({str(now - then)} ago)", + ] + ) + ) + + print() + + # Convert to dict, using orig_addr as key + saved_invert = {obj["address"]: obj for obj in saved_data["data"]} + new_invert = {obj["address"]: obj for obj in new_data} + + all_addrs = set(saved_invert.keys()).union(new_invert.keys()) + + # Put all the information in one place so we can decide how each item changed. + combined = { + addr: ( + saved_invert.get(addr), + new_invert.get(addr), + ) + for addr in sorted(all_addrs) + } + + # The criteria for diff judgement is in these dict comprehensions: + # Any function not in the saved file + new_functions = { + key: (saved, new) for key, (saved, new) in combined.items() if saved is None + } + + # Any function now missing from the saved file + # or a non-stub -> stub conversion + dropped_functions = { + key: (saved, new) + for key, (saved, new) in combined.items() + if new is None + or ( + new is not None + and saved is not None + and new.get("stub", False) + and not saved.get("stub", False) + ) + } + + # TODO: move these two into functions if the assessment gets more complex + # Any function with increased match percentage + # or stub -> non-stub conversion + improved_functions = { + key: (saved, new) + for key, (saved, new) in combined.items() + if saved is not None + and new is not None + and ( + new["matching"] > saved["matching"] + or (not new.get("stub", False) and saved.get("stub", False)) + ) + } + + # Any non-stub function with decreased match percentage + degraded_functions = { + key: (saved, new) + for key, (saved, new) in combined.items() + if saved is not None + and new is not None + and new["matching"] < saved["matching"] + and not saved.get("stub") + and not new.get("stub") + } + + # Any function with former or current "effective" match + entropy_functions = { + key: (saved, new) + for key, (saved, new) in combined.items() + if saved is not None + and new is not None + and new["matching"] == 1.0 + and saved["matching"] == 1.0 + and new.get("effective", False) != saved.get("effective", False) + } + + get_diff_str = diff_json_display(show_both_addrs, is_plain) + + for diff_name, diff_dict in [ + ("New", new_functions), + ("Increased", improved_functions), + ("Decreased", degraded_functions), + ("Dropped", dropped_functions), + ("Compiler entropy", entropy_functions), + ]: + if len(diff_dict) == 0: + continue + + print(f"{diff_name} ({len(diff_dict)}):") + + for addr, (saved, new) in diff_dict.items(): + print(get_diff_str(addr, saved, new)) + + print() + + +def get_file_in_script_dir(fn): + return os.path.join(os.path.dirname(os.path.abspath(sys.argv[0])), fn) diff --git a/tools/isledecomp/setup.py b/tools/isledecomp/setup.py new file mode 100644 index 00000000..bf3e05dc --- /dev/null +++ b/tools/isledecomp/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup, find_packages + +setup( + name="isledecomp", + version="0.1.0", + description="Python tools for the isledecomp project", + packages=find_packages(), + tests_require=["pytest"], + include_package_data=True, + package_data={"isledecomp.lib": ["*.exe", "*.dll"]}, +) diff --git a/tools/isledecomp/tests/__init__.py b/tools/isledecomp/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tools/isledecomp/tests/conftest.py b/tools/isledecomp/tests/conftest.py new file mode 100644 index 00000000..9f56b8a7 --- /dev/null +++ b/tools/isledecomp/tests/conftest.py @@ -0,0 +1,3 @@ +def pytest_addoption(parser): + """Allow the option to run tests against the original LEGO1.DLL.""" + parser.addoption("--lego1", action="store", help="Path to LEGO1.DLL") diff --git a/tools/isledecomp/tests/samples/basic_class.cpp b/tools/isledecomp/tests/samples/basic_class.cpp new file mode 100644 index 00000000..4316ad4a --- /dev/null +++ b/tools/isledecomp/tests/samples/basic_class.cpp @@ -0,0 +1,30 @@ +// Sample for python unit tests +// Not part of the decomp + +// A very simple class + +// VTABLE: TEST 0x1001002 +class TestClass { +public: + TestClass(); + virtual ~TestClass() override; + + virtual MxResult Tickle() override; // vtable+08 + + // FUNCTION: TEST 0x12345678 + inline const char* ClassName() const // vtable+0c + { + // 0xabcd1234 + return "TestClass"; + } + + // FUNCTION: TEST 0xdeadbeef + inline MxBool IsA(const char* name) const override // vtable+10 + { + return !strcmp(name, TestClass::ClassName()); + } + +private: + int m_hello; + int m_hiThere; +}; diff --git a/tools/isledecomp/tests/samples/basic_file.cpp b/tools/isledecomp/tests/samples/basic_file.cpp new file mode 100644 index 00000000..99930de8 --- /dev/null +++ b/tools/isledecomp/tests/samples/basic_file.cpp @@ -0,0 +1,22 @@ +// Sample for python unit tests +// Not part of the decomp + +// A very simple well-formed code file + +// FUNCTION: TEST 0x1234 +void function01() +{ + // TODO +} + +// FUNCTION: TEST 0x2345 +void function02() +{ + // TODO +} + +// FUNCTION: TEST 0x3456 +void function03() +{ + // TODO +} diff --git a/tools/isledecomp/tests/samples/global_variables.cpp b/tools/isledecomp/tests/samples/global_variables.cpp new file mode 100644 index 00000000..3be0316a --- /dev/null +++ b/tools/isledecomp/tests/samples/global_variables.cpp @@ -0,0 +1,14 @@ +// Sample for python unit tests +// Not part of the decomp + +// Global variables inside and outside of functions + +// GLOBAL: TEST 0x1000 +const char *g_message = "test"; + +// FUNCTION: TEST 0x1234 +void function01() +{ + // GLOBAL: TEST 0x5555 + static int g_hello = 123; +} diff --git a/tools/isledecomp/tests/samples/inline.cpp b/tools/isledecomp/tests/samples/inline.cpp new file mode 100644 index 00000000..8a36c89a --- /dev/null +++ b/tools/isledecomp/tests/samples/inline.cpp @@ -0,0 +1,8 @@ +// Sample for python unit tests +// Not part of the decomp + +// FUNCTION: TEST 0x10000001 +inline const char* OneLineWithComment() const { return "MxDSObject"; }; // hi there + +// FUNCTION: TEST 0x10000002 +inline const char* OneLine() const { return "MxDSObject"; }; diff --git a/tools/isledecomp/tests/samples/missing_offset.cpp b/tools/isledecomp/tests/samples/missing_offset.cpp new file mode 100644 index 00000000..3f6b3811 --- /dev/null +++ b/tools/isledecomp/tests/samples/missing_offset.cpp @@ -0,0 +1,16 @@ +// Sample for python unit tests +// Not part of the decomp + +#include + +int no_offset_comment() +{ + static int dummy = 123; + return -1; +} + +// FUNCTION: TEST 0xdeadbeef +void regular_ole_function() +{ + printf("hi there"); +} diff --git a/tools/isledecomp/tests/samples/multiple_offsets.cpp b/tools/isledecomp/tests/samples/multiple_offsets.cpp new file mode 100644 index 00000000..dc3c5bfa --- /dev/null +++ b/tools/isledecomp/tests/samples/multiple_offsets.cpp @@ -0,0 +1,25 @@ +// Sample for python unit tests +// Not part of the decomp + +// Handling multiple offset markers + +// FUNCTION: TEST 0x1234 +// FUNCTION: HELLO 0x5555 +void different_modules() +{ + // TODO +} + +// FUNCTION: TEST 0x2345 +// FUNCTION: TEST 0x1234 +void same_module() +{ + // TODO +} + +// FUNCTION: TEST 0x2002 +// FUNCTION: test 0x1001 +void same_case_insensitive() +{ + // TODO +} diff --git a/tools/isledecomp/tests/samples/oneline_function.cpp b/tools/isledecomp/tests/samples/oneline_function.cpp new file mode 100644 index 00000000..feb82314 --- /dev/null +++ b/tools/isledecomp/tests/samples/oneline_function.cpp @@ -0,0 +1,12 @@ +// Sample for python unit tests +// Not part of the decomp + +// FUNCTION: TEST 0x1234 +void short_function() { static char* msg = "oneliner"; } + +// FUNCTION: TEST 0x5555 +void function_after_one_liner() +{ + // This function comes after the previous that is on a single line. + // Do we report the offset for this one correctly? +} diff --git a/tools/isledecomp/tests/samples/out_of_order.cpp b/tools/isledecomp/tests/samples/out_of_order.cpp new file mode 100644 index 00000000..951c99e7 --- /dev/null +++ b/tools/isledecomp/tests/samples/out_of_order.cpp @@ -0,0 +1,20 @@ +// Sample for python unit tests +// Not part of the decomp + +// FUNCTION: TEST 0x1001 +void function_order01() +{ + // TODO +} + +// FUNCTION: TEST 0x1003 +void function_order03() +{ + // TODO +} + +// FUNCTION: TEST 0x1002 +void function_order02() +{ + // TODO +} diff --git a/tools/isledecomp/tests/samples/poorly_formatted.cpp b/tools/isledecomp/tests/samples/poorly_formatted.cpp new file mode 100644 index 00000000..69f365ec --- /dev/null +++ b/tools/isledecomp/tests/samples/poorly_formatted.cpp @@ -0,0 +1,23 @@ +// Sample for python unit tests +// Not part of the decomp + +// While it's reasonable to expect a well-formed file (and clang-format +// will make sure we get one), this will put the parser through its paces. + +// FUNCTION: TEST 0x1234 +void curly_with_spaces() + { + static char* msg = "hello"; + } + +// FUNCTION: TEST 0x5555 +void weird_closing_curly() +{ + int x = 123; } + +// FUNCTION: HELLO 0x5656 +void bad_indenting() { + if (0) +{ + int y = 5; +}} diff --git a/tools/isledecomp/tests/test_compare_db.py b/tools/isledecomp/tests/test_compare_db.py new file mode 100644 index 00000000..55cb75a5 --- /dev/null +++ b/tools/isledecomp/tests/test_compare_db.py @@ -0,0 +1,82 @@ +"""Testing compare database behavior, particularly matching""" +import pytest +from isledecomp.compare.db import CompareDb + + +@pytest.fixture(name="db") +def fixture_db(): + return CompareDb() + + +def test_ignore_recomp_collision(db): + """Duplicate recomp addresses are ignored""" + db.set_recomp_symbol(0x1234, None, "hello", None, 100) + db.set_recomp_symbol(0x1234, None, "alias_for_hello", None, 100) + syms = db.get_all() + assert len(syms) == 1 + + +def test_orig_collision(db): + """Don't match if the original address is not unique""" + db.set_recomp_symbol(0x1234, None, "hello", None, 100) + assert db.match_function(0x5555, "hello") is True + + # Second run on same address fails + assert db.match_function(0x5555, "hello") is False + + # Call set_pair directly without wrapper + assert db.set_pair(0x5555, 0x1234) is False + + +def test_name_match(db): + db.set_recomp_symbol(0x1234, None, "hello", None, 100) + assert db.match_function(0x5555, "hello") is True + + match = db.get_by_orig(0x5555) + assert match.name == "hello" + assert match.recomp_addr == 0x1234 + + +def test_match_decorated(db): + """Should match using decorated name even though regular name is null""" + db.set_recomp_symbol(0x1234, None, None, "?_hello", 100) + assert db.match_function(0x5555, "?_hello") is True + match = db.get_by_orig(0x5555) + assert match is not None + + +def test_duplicate_name(db): + """If recomp name is not unique, match only one row""" + db.set_recomp_symbol(0x100, None, "_Construct", None, 100) + db.set_recomp_symbol(0x200, None, "_Construct", None, 100) + db.set_recomp_symbol(0x300, None, "_Construct", None, 100) + db.match_function(0x5555, "_Construct") + matches = db.get_matches() + # We aren't testing _which_ one would be matched, just that only one _was_ matched + assert len(matches) == 1 + + +def test_static_variable_match(db): + """Set up a situation where we can match a static function variable, then match it.""" + + # We need a matched function to start with. + db.set_recomp_symbol(0x1234, None, "Isle::Tick", "?Tick@IsleApp@@QAEXH@Z", 100) + db.match_function(0x5555, "Isle::Tick") + + # Decorated variable name from PDB. + db.set_recomp_symbol( + 0x2000, None, None, "?g_startupDelay@?1??Tick@IsleApp@@QAEXH@Z@4HA", 4 + ) + + # Provide variable name and orig function address from decomp markers + assert db.match_static_variable(0xBEEF, "g_startupDelay", 0x5555) is True + + +def test_match_options_bool(db): + """Test handling of boolean match options""" + + # You don't actually need an existing orig addr for this. + assert db.get_match_options(0x1234) == {} + + db.mark_stub(0x1234) + assert "stub" in db.get_match_options(0x1234) diff --git a/tools/isledecomp/tests/test_curly.py b/tools/isledecomp/tests/test_curly.py new file mode 100644 index 00000000..8328f5e9 --- /dev/null +++ b/tools/isledecomp/tests/test_curly.py @@ -0,0 +1,73 @@ +# nyuk nyuk nyuk +import pytest +from isledecomp.parser.parser import CurlyManager +from isledecomp.parser.util import sanitize_code_line + + +@pytest.fixture(name="curly") +def fixture_curly(): + return CurlyManager() + + +def test_simple(curly): + curly.read_line("namespace Test {") + assert curly.get_prefix() == "Test" + curly.read_line("}") + assert curly.get_prefix() == "" + + +def test_oneliner(curly): + """Should not go down into a scope for a class forward reference""" + curly.read_line("class LegoEntity;") + assert curly.get_prefix() == "" + # Now make sure that we still would not consider that class name + # even after reading the opening curly brace + curly.read_line("if (true) {") + assert curly.get_prefix() == "" + + +def test_ignore_comments(curly): + curly.read_line("namespace Test {") + curly.read_line("// }") + assert curly.get_prefix() == "Test" + + +@pytest.mark.xfail(reason="todo: need a real lexer") +def test_ignore_multiline_comments(curly): + curly.read_line("namespace Test {") + curly.read_line("/*") + curly.read_line("}") + curly.read_line("*/") + assert curly.get_prefix() == "Test" + curly.read_line("}") + assert curly.get_prefix() == "" + + +def test_nested(curly): + curly.read_line("namespace Test {") + curly.read_line("namespace Foo {") + assert curly.get_prefix() == "Test::Foo" + curly.read_line("}") + assert curly.get_prefix() == "Test" + + +sanitize_cases = [ + ("", ""), + (" ", ""), + ("{", "{"), + ("// comments {", ""), + ("{ // why comment here", "{"), + ("/* comments */ {", "{"), + ('"curly in a string {"', '""'), + ('if (!strcmp("hello { there }", g_test)) {', 'if (!strcmp("", g_test)) {'), + ("'{'", "''"), + ("weird_function('\"', hello, '\"')", "weird_function('', hello, '')"), +] + + +@pytest.mark.parametrize("start, end", sanitize_cases) +def test_sanitize(start: str, end: str): + """Make sure that we can remove curly braces in places where they should + not be considered as part of the semantic structure of the file. + i.e. inside strings or chars, and inside comments""" + assert sanitize_code_line(start) == end diff --git a/tools/isledecomp/tests/test_cvdump.py b/tools/isledecomp/tests/test_cvdump.py new file mode 100644 index 00000000..3670e495 --- /dev/null +++ b/tools/isledecomp/tests/test_cvdump.py @@ -0,0 +1,59 @@ +import pytest +from isledecomp.cvdump.types import ( + scalar_type_size, + scalar_type_pointer, + scalar_type_signed, +) + +# These are all the types seen in the cvdump. +# We have char, short, int, long, long long, float, and double all represented +# in both signed and unsigned. +# We can also identify a 4 byte pointer with the T_32 prefix. +# The type T_VOID is used to designate a function's return type. +# T_NOTYPE is specified as the type of "this" for a static function in a class. + +# For reference: https://github.com/microsoft/microsoft-pdb/blob/master/include/cvinfo.h + +# fmt: off +# Fields are: type_name, size, is_signed, is_pointer +type_check_cases = ( + ("T_32PINT4", 4, False, True), + ("T_32PLONG", 4, False, True), + ("T_32PRCHAR", 4, False, True), + ("T_32PREAL32", 4, False, True), + ("T_32PUCHAR", 4, False, True), + ("T_32PUINT4", 4, False, True), + ("T_32PULONG", 4, False, True), + ("T_32PUSHORT", 4, False, True), + ("T_32PVOID", 4, False, True), + ("T_CHAR", 1, True, False), + ("T_INT4", 4, True, False), + ("T_LONG", 4, True, False), + ("T_QUAD", 8, True, False), + ("T_RCHAR", 1, True, False), + ("T_REAL32", 4, True, False), + ("T_REAL64", 8, True, False), + ("T_SHORT", 2, True, False), + ("T_UCHAR", 1, False, False), + ("T_UINT4", 4, False, False), + ("T_ULONG", 4, False, False), + ("T_UQUAD", 8, False, False), + ("T_USHORT", 2, False, False), + ("T_WCHAR", 2, False, False), +) +# fmt: on + + +@pytest.mark.parametrize("type_name, size, _, __", type_check_cases) +def test_scalar_size(type_name: str, size: int, _, __): + assert scalar_type_size(type_name) == size + + +@pytest.mark.parametrize("type_name, _, is_signed, __", type_check_cases) +def test_scalar_signed(type_name: str, _, is_signed: bool, __): + assert scalar_type_signed(type_name) == is_signed + + +@pytest.mark.parametrize("type_name, _, __, is_pointer", type_check_cases) +def test_scalar_pointer(type_name: str, _, __, is_pointer: bool): + assert scalar_type_pointer(type_name) == is_pointer diff --git a/tools/isledecomp/tests/test_cvdump_symbols.py b/tools/isledecomp/tests/test_cvdump_symbols.py new file mode 100644 index 00000000..f4ca1aff --- /dev/null +++ b/tools/isledecomp/tests/test_cvdump_symbols.py @@ -0,0 +1,38 @@ +"""Test Cvdump SYMBOLS parser, reading function stack/params""" + +from isledecomp.cvdump.symbols import CvdumpSymbolsParser + +PROC_WITH_BLOC = """ +(000638) S_GPROC32: [0001:000C6135], Cb: 00000361, Type: 0x10ED, RegistrationBook::ReadyWorld + Parent: 00000000, End: 00000760, Next: 00000000 + Debug start: 0000000C, Debug end: 0000035C + Flags: Frame Ptr Present +(00067C) S_BPREL32: [FFFFFFD0], Type: 0x10EC, this +(000690) S_BPREL32: [FFFFFFDC], Type: 0x10F5, checkmarkBuffer +(0006AC) S_BPREL32: [FFFFFFE8], Type: 0x10F6, letterBuffer +(0006C8) S_BPREL32: [FFFFFFF4], Type: T_SHORT(0011), i +(0006D8) S_BPREL32: [FFFFFFF8], Type: 0x10F8, players +(0006EC) S_BPREL32: [FFFFFFFC], Type: 0x1044, gameState +(000704) S_BLOCK32: [0001:000C624F], Cb: 000001DA, + Parent: 00000638, End: 0000072C +(00071C) S_BPREL32: [FFFFFFD8], Type: T_SHORT(0011), j +(00072C) S_END +(000730) S_BLOCK32: [0001:000C6448], Cb: 00000032, + Parent: 00000638, End: 0000075C +(000748) S_BPREL32: [FFFFFFD4], Type: 0x10FA, infoman +(00075C) S_END +(000760) S_END +""" + + +def test_sblock32(): + """S_END has double duty as marking the end of a function (S_GPROC32) + and a scope block (S_BLOCK32). Make sure we can distinguish between + the two and not end a function early.""" + parser = CvdumpSymbolsParser() + for line in PROC_WITH_BLOC.split("\n"): + parser.read_line(line) + + # Make sure we can read the proc and all its stack references + assert len(parser.symbols) == 1 + assert len(parser.symbols[0].stack_symbols) == 8 diff --git a/tools/isledecomp/tests/test_cvdump_types.py b/tools/isledecomp/tests/test_cvdump_types.py new file mode 100644 index 00000000..3bd6afa0 --- /dev/null +++ b/tools/isledecomp/tests/test_cvdump_types.py @@ -0,0 +1,705 @@ +"""Specifically testing the Cvdump TYPES parser +and type dependency tree walker.""" + +import pytest +from isledecomp.cvdump.types import ( + CvdumpTypesParser, + CvdumpKeyError, + CvdumpIntegrityError, + FieldListItem, + VirtualBaseClass, + VirtualBasePointer, +) + +TEST_LINES = """ +0x1018 : Length = 18, Leaf = 0x1201 LF_ARGLIST argument count = 3 + list[0] = 0x100D + list[1] = 0x1016 + list[2] = 0x1017 + +0x1019 : Length = 14, Leaf = 0x1008 LF_PROCEDURE + Return type = T_LONG(0012), Call type = C Near + Func attr = none + # Parms = 3, Arg list type = 0x1018 + +0x101e : Length = 26, Leaf = 0x1009 LF_MFUNCTION + Return type = T_CHAR(0010), Class type = 0x101A, This type = 0x101B, + Call type = ThisCall, Func attr = none + Parms = 2, Arg list type = 0x101d, This adjust = 0 + +0x1028 : Length = 10, Leaf = 0x1001 LF_MODIFIER + const, modifies type T_REAL32(0040) + +0x103b : Length = 14, Leaf = 0x1503 LF_ARRAY + Element type = T_REAL32(0040) + Index type = T_SHORT(0011) + length = 16 + Name = + +0x103c : Length = 14, Leaf = 0x1503 LF_ARRAY + Element type = 0x103B + Index type = T_SHORT(0011) + length = 64 + Name = + +0x10e0 : Length = 86, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_MEMBER, public, type = T_REAL32(0040), offset = 0 + member name = 'x' + list[1] = LF_MEMBER, public, type = T_REAL32(0040), offset = 0 + member name = 'dvX' + list[2] = LF_MEMBER, public, type = T_REAL32(0040), offset = 4 + member name = 'y' + list[3] = LF_MEMBER, public, type = T_REAL32(0040), offset = 4 + member name = 'dvY' + list[4] = LF_MEMBER, public, type = T_REAL32(0040), offset = 8 + member name = 'z' + list[5] = LF_MEMBER, public, type = T_REAL32(0040), offset = 8 + member name = 'dvZ' + +0x10e1 : Length = 34, Leaf = 0x1505 LF_STRUCTURE + # members = 6, field list type 0x10e0, + Derivation list type 0x0000, VT shape type 0x0000 + Size = 12, class name = _D3DVECTOR, UDT(0x000010e1) + +0x10e4 : Length = 14, Leaf = 0x1503 LF_ARRAY + Element type = T_UCHAR(0020) + Index type = T_SHORT(0011) + length = 8 + Name = + +0x10ea : Length = 14, Leaf = 0x1503 LF_ARRAY + Element type = 0x1028 + Index type = T_SHORT(0011) + length = 12 + Name = + +0x11f0 : Length = 30, Leaf = 0x1504 LF_CLASS + # members = 0, field list type 0x0000, FORWARD REF, + Derivation list type 0x0000, VT shape type 0x0000 + Size = 0, class name = MxRect32, UDT(0x00001214) + +0x11f2 : Length = 10, Leaf = 0x1001 LF_MODIFIER + const, modifies type 0x11F0 + +0x1213 : Length = 530, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_METHOD, count = 5, list = 0x1203, name = 'MxRect32' + list[1] = LF_ONEMETHOD, public, VANILLA, index = 0x1205, name = 'operator=' + list[2] = LF_ONEMETHOD, public, VANILLA, index = 0x11F5, name = 'Intersect' + list[3] = LF_ONEMETHOD, public, VANILLA, index = 0x1207, name = 'SetPoint' + list[4] = LF_ONEMETHOD, public, VANILLA, index = 0x1207, name = 'AddPoint' + list[5] = LF_ONEMETHOD, public, VANILLA, index = 0x1207, name = 'SubtractPoint' + list[6] = LF_ONEMETHOD, public, VANILLA, index = 0x11F5, name = 'UpdateBounds' + list[7] = LF_ONEMETHOD, public, VANILLA, index = 0x1209, name = 'IsValid' + list[8] = LF_ONEMETHOD, public, VANILLA, index = 0x120A, name = 'IntersectsWith' + list[9] = LF_ONEMETHOD, public, VANILLA, index = 0x120B, name = 'GetWidth' + list[10] = LF_ONEMETHOD, public, VANILLA, index = 0x120B, name = 'GetHeight' + list[11] = LF_ONEMETHOD, public, VANILLA, index = 0x120C, name = 'GetPoint' + list[12] = LF_ONEMETHOD, public, VANILLA, index = 0x120D, name = 'GetSize' + list[13] = LF_ONEMETHOD, public, VANILLA, index = 0x120B, name = 'GetLeft' + list[14] = LF_ONEMETHOD, public, VANILLA, index = 0x120B, name = 'GetTop' + list[15] = LF_ONEMETHOD, public, VANILLA, index = 0x120B, name = 'GetRight' + list[16] = LF_ONEMETHOD, public, VANILLA, index = 0x120B, name = 'GetBottom' + list[17] = LF_ONEMETHOD, public, VANILLA, index = 0x120E, name = 'SetLeft' + list[18] = LF_ONEMETHOD, public, VANILLA, index = 0x120E, name = 'SetTop' + list[19] = LF_ONEMETHOD, public, VANILLA, index = 0x120E, name = 'SetRight' + list[20] = LF_ONEMETHOD, public, VANILLA, index = 0x120E, name = 'SetBottom' + list[21] = LF_METHOD, count = 3, list = 0x1211, name = 'CopyFrom' + list[22] = LF_ONEMETHOD, private, STATIC, index = 0x1212, name = 'Min' + list[23] = LF_ONEMETHOD, private, STATIC, index = 0x1212, name = 'Max' + list[24] = LF_MEMBER, private, type = T_INT4(0074), offset = 0 + member name = 'm_left' + list[25] = LF_MEMBER, private, type = T_INT4(0074), offset = 4 + member name = 'm_top' + list[26] = LF_MEMBER, private, type = T_INT4(0074), offset = 8 + member name = 'm_right' + list[27] = LF_MEMBER, private, type = T_INT4(0074), offset = 12 + member name = 'm_bottom' + +0x1214 : Length = 30, Leaf = 0x1504 LF_CLASS + # members = 34, field list type 0x1213, CONSTRUCTOR, OVERLOAD, + Derivation list type 0x0000, VT shape type 0x0000 + Size = 16, class name = MxRect32, UDT(0x00001214) + +0x1220 : Length = 30, Leaf = 0x1504 LF_CLASS + # members = 0, field list type 0x0000, FORWARD REF, + Derivation list type 0x0000, VT shape type 0x0000 + Size = 0, class name = MxCore, UDT(0x00004060) + +0x14db : Length = 30, Leaf = 0x1504 LF_CLASS + # members = 0, field list type 0x0000, FORWARD REF, + Derivation list type 0x0000, VT shape type 0x0000 + Size = 0, class name = MxString, UDT(0x00004db6) + +0x19b0 : Length = 34, Leaf = 0x1505 LF_STRUCTURE + # members = 0, field list type 0x0000, FORWARD REF, + Derivation list type 0x0000, VT shape type 0x0000 + Size = 0, class name = ROIColorAlias, UDT(0x00002a76) + +0x19b1 : Length = 14, Leaf = 0x1503 LF_ARRAY + Element type = 0x19B0 + Index type = T_SHORT(0011) + length = 440 + Name = + +0x2339 : Length = 26, Leaf = 0x1506 LF_UNION + # members = 0, field list type 0x0000, FORWARD REF, Size = 0 ,class name = FlagBitfield, UDT(0x00002e85) + +0x2e85 : Length = 26, Leaf = 0x1506 LF_UNION + # members = 8, field list type 0x2e84, Size = 1 ,class name = FlagBitfield, UDT(0x00002e85) + +0x2a75 : Length = 98, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_MEMBER, public, type = T_32PRCHAR(0470), offset = 0 + member name = 'm_name' + list[1] = LF_MEMBER, public, type = T_INT4(0074), offset = 4 + member name = 'm_red' + list[2] = LF_MEMBER, public, type = T_INT4(0074), offset = 8 + member name = 'm_green' + list[3] = LF_MEMBER, public, type = T_INT4(0074), offset = 12 + member name = 'm_blue' + list[4] = LF_MEMBER, public, type = T_INT4(0074), offset = 16 + member name = 'm_unk0x10' + +0x2a76 : Length = 34, Leaf = 0x1505 LF_STRUCTURE + # members = 5, field list type 0x2a75, + Derivation list type 0x0000, VT shape type 0x0000 + Size = 20, class name = ROIColorAlias, UDT(0x00002a76) + +0x22d4 : Length = 154, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_VFUNCTAB, type = 0x20FC + list[1] = LF_METHOD, count = 3, list = 0x22D0, name = 'MxVariable' + list[2] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1F0F, + vfptr offset = 0, name = 'GetValue' + list[3] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1F10, + vfptr offset = 4, name = 'SetValue' + list[4] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1F11, + vfptr offset = 8, name = '~MxVariable' + list[5] = LF_ONEMETHOD, public, VANILLA, index = 0x22D3, name = 'GetKey' + list[6] = LF_MEMBER, protected, type = 0x14DB, offset = 4 + member name = 'm_key' + list[7] = LF_MEMBER, protected, type = 0x14DB, offset = 20 + member name = 'm_value' + +0x22d5 : Length = 34, Leaf = 0x1504 LF_CLASS + # members = 10, field list type 0x22d4, CONSTRUCTOR, + Derivation list type 0x0000, VT shape type 0x20fb + Size = 36, class name = MxVariable, UDT(0x00004041) + +0x3c45 : Length = 50, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_ENUMERATE, public, value = 1, name = 'c_read' + list[1] = LF_ENUMERATE, public, value = 2, name = 'c_write' + list[2] = LF_ENUMERATE, public, value = 4, name = 'c_text' + +0x3cc2 : Length = 38, Leaf = 0x1507 LF_ENUM + # members = 64, type = T_INT4(0074) field list type 0x3cc1 +NESTED, enum name = JukeBox::JukeBoxScript, UDT(0x00003cc2) + +0x3fab : Length = 10, Leaf = 0x1002 LF_POINTER + Pointer (NEAR32), Size: 0 + Element type : 0x3FAA + +0x405f : Length = 158, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_VFUNCTAB, type = 0x2090 + list[1] = LF_ONEMETHOD, public, VANILLA, index = 0x176A, name = 'MxCore' + list[2] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x176A, + vfptr offset = 0, name = '~MxCore' + list[3] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x176B, + vfptr offset = 4, name = 'Notify' + list[4] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x2087, + vfptr offset = 8, name = 'Tickle' + list[5] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x202F, + vfptr offset = 12, name = 'ClassName' + list[6] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x2030, + vfptr offset = 16, name = 'IsA' + list[7] = LF_ONEMETHOD, public, VANILLA, index = 0x2091, name = 'GetId' + list[8] = LF_MEMBER, private, type = T_UINT4(0075), offset = 4 + member name = 'm_id' + +0x4060 : Length = 30, Leaf = 0x1504 LF_CLASS + # members = 9, field list type 0x405f, CONSTRUCTOR, + Derivation list type 0x0000, VT shape type 0x1266 + Size = 8, class name = MxCore, UDT(0x00004060) + +0x4262 : Length = 14, Leaf = 0x1503 LF_ARRAY + Element type = 0x3CC2 + Index type = T_SHORT(0011) + length = 24 + Name = + +0x432f : Length = 14, Leaf = 0x1503 LF_ARRAY + Element type = T_INT4(0074) + Index type = T_SHORT(0011) + length = 12 + Name = + +0x4db5 : Length = 246, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_BCLASS, public, type = 0x1220, offset = 0 + list[1] = LF_METHOD, count = 3, list = 0x14E3, name = 'MxString' + list[2] = LF_ONEMETHOD, public, VIRTUAL, index = 0x14DE, name = '~MxString' + list[3] = LF_METHOD, count = 2, list = 0x14E7, name = 'operator=' + list[4] = LF_ONEMETHOD, public, VANILLA, index = 0x14DE, name = 'ToUpperCase' + list[5] = LF_ONEMETHOD, public, VANILLA, index = 0x14DE, name = 'ToLowerCase' + list[6] = LF_ONEMETHOD, public, VANILLA, index = 0x14E8, name = 'operator+' + list[7] = LF_ONEMETHOD, public, VANILLA, index = 0x14E9, name = 'operator+=' + list[8] = LF_ONEMETHOD, public, VANILLA, index = 0x14EB, name = 'Compare' + list[9] = LF_ONEMETHOD, public, VANILLA, index = 0x14EC, name = 'GetData' + list[10] = LF_ONEMETHOD, public, VANILLA, index = 0x4DB4, name = 'GetLength' + list[11] = LF_MEMBER, private, type = T_32PRCHAR(0470), offset = 8 + member name = 'm_data' + list[12] = LF_MEMBER, private, type = T_USHORT(0021), offset = 12 + member name = 'm_length' + + +0x4dee : Length = 406, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_VBCLASS, public, direct base type = 0x15EA + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 3 + list[1] = LF_IVBCLASS, public, indirect base type = 0x1183 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 1 + list[2] = LF_IVBCLASS, public, indirect base type = 0x1468 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 2 + list[3] = LF_VFUNCTAB, type = 0x2B95 + list[4] = LF_ONEMETHOD, public, VANILLA, index = 0x15C2, name = 'LegoRaceMap' + list[5] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15C3, name = '~LegoRaceMap' + list[6] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15C5, name = 'Notify' + list[7] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15C4, name = 'ParseAction' + list[8] = LF_ONEMETHOD, public, VIRTUAL, index = 0x4DED, name = 'VTable0x70' + list[9] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x15C2, + vfptr offset = 0, name = 'FUN_1005d4b0' + list[10] = LF_MEMBER, private, type = T_UCHAR(0020), offset = 8 + member name = 'm_parentClass2Field1' + list[11] = LF_MEMBER, private, type = T_32PVOID(0403), offset = 12 + member name = 'm_parentClass2Field2' + +0x4def : Length = 34, Leaf = 0x1504 LF_CLASS + # members = 21, field list type 0x4dee, CONSTRUCTOR, + Derivation list type 0x0000, VT shape type 0x12a0 + Size = 436, class name = LegoRaceMap, UDT(0x00004def) + +0x4db6 : Length = 30, Leaf = 0x1504 LF_CLASS + # members = 16, field list type 0x4db5, CONSTRUCTOR, OVERLOAD, + Derivation list type 0x0000, VT shape type 0x1266 + Size = 16, class name = MxString, UDT(0x00004db6) + +0x5591 : Length = 570, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_VBCLASS, public, direct base type = 0x15EA + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 3 + list[1] = LF_IVBCLASS, public, indirect base type = 0x1183 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 1 + list[2] = LF_IVBCLASS, public, indirect base type = 0x1468 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 2 + list[3] = LF_VFUNCTAB, type = 0x4E11 + list[4] = LF_ONEMETHOD, public, VANILLA, index = 0x1ABD, name = 'LegoCarRaceActor' + list[5] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1AE0, name = 'ClassName' + list[6] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1AE1, name = 'IsA' + list[7] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1ADD, name = 'VTable0x6c' + list[8] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1ADB, name = 'VTable0x70' + list[9] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1ADA, name = 'SwitchBoundary' + list[10] = LF_ONEMETHOD, public, VIRTUAL, index = 0x1ADC, name = 'VTable0x9c' + list[11] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x558E, + vfptr offset = 0, name = 'FUN_10080590' + list[12] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD8, + vfptr offset = 4, name = 'FUN_10012bb0' + list[13] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD9, + vfptr offset = 8, name = 'FUN_10012bc0' + list[14] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD8, + vfptr offset = 12, name = 'FUN_10012bd0' + list[15] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD9, + vfptr offset = 16, name = 'FUN_10012be0' + list[16] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD8, + vfptr offset = 20, name = 'FUN_10012bf0' + list[17] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1AD9, + vfptr offset = 24, name = 'FUN_10012c00' + list[18] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x1ABD, + vfptr offset = 28, name = 'VTable0x1c' + list[19] = LF_MEMBER, protected, type = T_REAL32(0040), offset = 8 + member name = 'm_parentClass1Field1' + list[25] = LF_ONEMETHOD, public, VIRTUAL, (compgenx), index = 0x15D1, name = '~LegoCarRaceActor' + +0x5592 : Length = 38, Leaf = 0x1504 LF_CLASS + # members = 26, field list type 0x5591, CONSTRUCTOR, + Derivation list type 0x0000, VT shape type 0x34c7 + Size = 416, class name = LegoCarRaceActor, UDT(0x00005592) + +0x5593 : Length = 638, Leaf = 0x1203 LF_FIELDLIST + list[0] = LF_BCLASS, public, type = 0x5592, offset = 0 + list[1] = LF_BCLASS, public, type = 0x4DEF, offset = 32 + list[2] = LF_IVBCLASS, public, indirect base type = 0x1183 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 1 + list[3] = LF_IVBCLASS, public, indirect base type = 0x1468 + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 2 + list[4] = LF_IVBCLASS, public, indirect base type = 0x15EA + virtual base ptr = 0x43E9, vbpoff = 4, vbind = 3 + list[5] = LF_ONEMETHOD, public, VANILLA, index = 0x15CD, name = 'LegoRaceCar' + list[6] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15CE, name = '~LegoRaceCar' + list[7] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15D2, name = 'Notify' + list[8] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15E8, name = 'ClassName' + list[9] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15E9, name = 'IsA' + list[10] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15D5, name = 'ParseAction' + list[11] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15D3, name = 'SetWorldSpeed' + list[12] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15DF, name = 'VTable0x6c' + list[13] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15D3, name = 'VTable0x70' + list[14] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15DC, name = 'VTable0x94' + list[15] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15E5, name = 'SwitchBoundary' + list[16] = LF_ONEMETHOD, public, VIRTUAL, index = 0x15DD, name = 'VTable0x9c' + list[17] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x15D4, + vfptr offset = 32, name = 'SetMaxLinearVelocity' + list[18] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x15D4, + vfptr offset = 36, name = 'FUN_10012ff0' + list[19] = LF_ONEMETHOD, public, INTRODUCING VIRTUAL, index = 0x5588, + vfptr offset = 40, name = 'HandleSkeletonKicks' + list[20] = LF_MEMBER, private, type = T_UCHAR(0020), offset = 84 + member name = 'm_childClassField' + +0x5594 : Length = 34, Leaf = 0x1504 LF_CLASS + # members = 30, field list type 0x5593, CONSTRUCTOR, + Derivation list type 0x0000, VT shape type 0x2d1e + Size = 512, class name = LegoRaceCar, UDT(0x000055bb) +""" + + +@pytest.fixture(name="parser") +def types_parser_fixture(): + parser = CvdumpTypesParser() + for line in TEST_LINES.split("\n"): + parser.read_line(line) + + return parser + + +def test_basic_parsing(parser: CvdumpTypesParser): + obj = parser.keys["0x4db6"] + assert obj["type"] == "LF_CLASS" + assert obj["name"] == "MxString" + assert obj["udt"] == "0x4db6" + + assert len(parser.keys["0x4db5"]["members"]) == 2 + + +def test_scalar_types(parser: CvdumpTypesParser): + """Full tests on the scalar_* methods are in another file. + Here we are just testing the passthrough of the "T_" types.""" + assert parser.get("T_CHAR").name is None + assert parser.get("T_CHAR").size == 1 + + assert parser.get("T_32PVOID").name is None + assert parser.get("T_32PVOID").size == 4 + + +def test_resolve_forward_ref(parser: CvdumpTypesParser): + # Non-forward ref + assert parser.get("0x22d5").name == "MxVariable" + # Forward ref + assert parser.get("0x14db").name == "MxString" + assert parser.get("0x14db").size == 16 + + +def test_members(parser: CvdumpTypesParser): + """Return the list of items to compare for a given complex type. + If the class has a superclass, add those members too.""" + # MxCore field list + mxcore_members = parser.get_scalars("0x405f") + assert mxcore_members == [ + (0, "vftable", "T_32PVOID"), + (4, "m_id", "T_UINT4"), + ] + + # MxCore class id. Should be the same members + assert mxcore_members == parser.get_scalars("0x4060") + + # MxString field list. Should add inherited members from MxCore + assert parser.get_scalars("0x4db5") == [ + (0, "vftable", "T_32PVOID"), + (4, "m_id", "T_UINT4"), + (8, "m_data", "T_32PRCHAR"), + (12, "m_length", "T_USHORT"), + ] + + # LegoRaceCar with multiple superclasses + assert parser.get("0x5594").members == [ + FieldListItem(offset=0, name="vftable", type="T_32PVOID"), + FieldListItem(offset=0, name="vftable", type="T_32PVOID"), + FieldListItem(offset=8, name="m_parentClass1Field1", type="T_REAL32"), + FieldListItem(offset=8, name="m_parentClass2Field1", type="T_UCHAR"), + FieldListItem(offset=12, name="m_parentClass2Field2", type="T_32PVOID"), + FieldListItem(offset=84, name="m_childClassField", type="T_UCHAR"), + ] + + +def test_virtual_base_classes(parser: CvdumpTypesParser): + """Make sure that virtual base classes are parsed correctly.""" + + lego_car_race_actor = parser.keys.get("0x5591") + assert lego_car_race_actor is not None + assert lego_car_race_actor["vbase"] == VirtualBasePointer( + vboffset=4, + bases=[ + VirtualBaseClass(type="0x1183", index=1, direct=False), + VirtualBaseClass(type="0x1468", index=2, direct=False), + VirtualBaseClass(type="0x15EA", index=3, direct=True), + ], + ) + + +def test_members_recursive(parser: CvdumpTypesParser): + """Make sure that we unwrap the dependency tree correctly.""" + # MxVariable field list + assert parser.get_scalars("0x22d4") == [ + (0, "vftable", "T_32PVOID"), + (4, "m_key.vftable", "T_32PVOID"), + (8, "m_key.m_id", "T_UINT4"), + (12, "m_key.m_data", "T_32PRCHAR"), + (16, "m_key.m_length", "T_USHORT"), # with padding + (20, "m_value.vftable", "T_32PVOID"), + (24, "m_value.m_id", "T_UINT4"), + (28, "m_value.m_data", "T_32PRCHAR"), + (32, "m_value.m_length", "T_USHORT"), # with padding + ] + + +def test_struct(parser: CvdumpTypesParser): + """Basic test for converting type into struct.unpack format string.""" + # MxCore: vftable and uint32. The vftable pointer is read as uint32. + assert parser.get_format_string("0x4060") == "::`vftable'"), + ( + "??_7?$MxPtrList@VLegoPathController@@@@6B@", + "MxPtrList::`vftable'", + ), + ("??_7Renderer@Tgl@@6B@", "Tgl::Renderer::`vftable'"), + ("??_7LegoExtraActor@@6B0@@", "LegoExtraActor::`vftable'{for `LegoExtraActor'}"), + ( + "??_7LegoExtraActor@@6BLegoAnimActor@@@", + "LegoExtraActor::`vftable'{for `LegoAnimActor'}", + ), + ( + "??_7LegoAnimActor@@6B?$LegoContainer@PAM@@@", + "LegoAnimActor::`vftable'{for `LegoContainer'}", + ), +] + + +@pytest.mark.parametrize("symbol, class_name", vtable_cases) +def test_vtable(symbol, class_name): + assert demangle_vtable(symbol) == class_name + + +def test_vtordisp(): + """Make sure we can accurately detect an adjuster thunk symbol""" + assert get_vtordisp_name("") is None + assert get_vtordisp_name("?ClassName@LegoExtraActor@@UBEPBDXZ") is None + assert ( + get_vtordisp_name("?ClassName@LegoExtraActor@@$4PPPPPPPM@A@BEPBDXZ") is not None + ) + + # A function called vtordisp + assert get_vtordisp_name("?vtordisp@LegoExtraActor@@UBEPBDXZ") is None diff --git a/tools/isledecomp/tests/test_instgen.py b/tools/isledecomp/tests/test_instgen.py new file mode 100644 index 00000000..060d46d9 --- /dev/null +++ b/tools/isledecomp/tests/test_instgen.py @@ -0,0 +1,212 @@ +from isledecomp.compare.asm.instgen import InstructGen, SectionType + + +def test_ret(): + """Make sure we can handle a function with one instruction.""" + ig = InstructGen(b"\xc3", 0) + assert len(ig.sections) == 1 + + +SCORE_NOTIFY = ( + b"\x53\x56\x57\x8b\xd9\x33\xff\x8b\x74\x24\x10\x56\xe8\xbf\xe1\x01" + b"\x00\x80\xbb\xf6\x00\x00\x00\x00\x0f\x84\x9c\x00\x00\x00\x8b\x4e" + b"\x04\x49\x83\xf9\x17\x0f\x87\x8f\x00\x00\x00\x33\xc0\x8a\x81\xec" + b"\x14\x00\x10\xff\x24\x85\xd4\x14\x00\x10\x8b\xcb\xbf\x01\x00\x00" + b"\x00\xe8\x7a\x05\x00\x00\x8b\xc7\x5f\x5e\x5b\xc2\x04\x00\x56\x8b" + b"\xcb\xe8\xaa\x00\x00\x00\x8b\xf8\x8b\xc7\x5f\x5e\x5b\xc2\x04\x00" + b"\x80\x7e\x18\x20\x75\x07\x8b\xcb\xe8\xc3\xfe\xff\xff\xbf\x01\x00" + b"\x00\x00\x8b\xc7\x5f\x5e\x5b\xc2\x04\x00\x56\x8b\xcb\xe8\x3e\x02" + b"\x00\x00\x8b\xf8\x8b\xc7\x5f\x5e\x5b\xc2\x04\x00\x6a\x09\xa1\x4c" + b"\x45\x0f\x10\x6a\x07\x50\xe8\x35\x45\x01\x00\x83\xc4\x0c\x8b\x83" + b"\xf8\x00\x00\x00\x85\xc0\x74\x0d\x50\xe8\xa2\x42\x01\x00\x8b\xc8" + b"\xe8\x9b\x9b\x03\x00\xbf\x01\x00\x00\x00\x8b\xc7\x5f\x5e\x5b\xc2" + b"\x04\x00\x8b\xff\x4a\x14\x00\x10\x5e\x14\x00\x10\x70\x14\x00\x10" + b"\x8a\x14\x00\x10\x9c\x14\x00\x10\xca\x14\x00\x10\x00\x01\x05\x05" + b"\x05\x05\x02\x05\x05\x05\x05\x05\x05\x05\x05\x05\x03\x05\x05\x05" + b"\x05\x05\x05\x04\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc" +) + + +def test_score_notify(): + """Score::Notify function from 0x10001410 in LEGO1. + Good representative function for jump table (at 0x100014d4) + and switch data (at 0x100014ec).""" + ig = InstructGen(SCORE_NOTIFY, 0x10001410) + + # Did we get everything? + assert len(ig.sections) == 3 + types_only = tuple(s.type for s in ig.sections) + assert types_only == (SectionType.CODE, SectionType.ADDR_TAB, SectionType.DATA_TAB) + + # CODE section stopped at correct place? + instructions = ig.sections[0].contents + assert instructions[-1].address == 0x100014D2 + # n.b. 0x100014d2 is the dummy instruction `mov edi, edi` + # Ghidra does more thorough analysis and ignores this. + # The last real instruction should be at 0x100014cf. Not a big deal + # to include this because it is not junk data. + + # 6 switch addresses + assert len(ig.sections[1].contents) == 6 + + # TODO: The data table at the end includes all of the 0xCC padding bytes. + + +SMACK_CASE = ( + # LEGO1: 0x100cdc43 (modified so jump table points at +0x1016) + b"\x2e\xff\x24\x8d\x16\x10\x00\x00" + # LEGO1: 0x100cdb62 (instructions before and after jump table) + b"\x8b\xf8\xeb\x1a\x87\xdb\x87\xc9\x87\xdb\x87\xc9\x87\xdb\x50\xdc" + b"\x0c\x10\xd0\xe2\x0c\x10\xb0\xe8\x0c\x10\x50\xe9\x0c\x10\xa0\x10" + b"\x27\x10\x10\x3c\x11\x77\x17\x8a\xc8" +) + + +def test_smack_case(): + """Case where we have code / jump table / code. + Need to properly separate code sections, eliminate junk instructions + and continue disassembling at the proper address following the data.""" + ig = InstructGen(SMACK_CASE, 0x1000) + assert len(ig.sections) == 3 + assert ig.sections[0].type == ig.sections[2].type == SectionType.CODE + + # Make sure we captured the instruction immediately after + assert ig.sections[2].contents[0].mnemonic == "mov" + + +# BETA10 0x1004c9cc +BETA_FUNC = ( + b"\x55\x8b\xec\x83\xec\x08\x53\x56\x57\x89\x4d\xfc\x8b\x45\xfc\x33" + b"\xc9\x8a\x88\x19\x02\x00\x00\x89\x4d\xf8\xe9\x1e\x00\x00\x00\xe9" + b"\x41\x00\x00\x00\xe9\x3c\x00\x00\x00\xe9\x37\x00\x00\x00\xe9\x32" + b"\x00\x00\x00\xe9\x2d\x00\x00\x00\xe9\x28\x00\x00\x00\x83\x7d\xf8" + b"\x04\x0f\x87\x1e\x00\x00\x00\x8b\x45\xf8\xff\x24\x85\x1d\xca\x04" + b"\x10\xeb\xc9\x04\x10\xf0\xc9\x04\x10\xf5\xc9\x04\x10\xfa\xc9\x04" + b"\x10\xff\xc9\x04\x10\xb0\x01\xe9\x00\x00\x00\x00\x5f\x5e\x5b\xc9" + b"\xc2\x04\x00" +) + + +def test_beta_case(): + """Complete (and short) function with CODE / ADDR / CODE""" + ig = InstructGen(BETA_FUNC, 0x1004C9CC) + # The JMP into the jump table immediately precedes the jump table. + # We have to detect this and switch sections correctly or we will only + # get 1 section. + assert len(ig.sections) == 3 + assert ig.sections[0].type == ig.sections[2].type == SectionType.CODE + + # Make sure we captured the instruction immediately after + assert ig.sections[2].contents[0].mnemonic == "mov" + + +# LEGO1 0x1000fb50 +# TODO: The test data here is longer than it needs to be. +THUNK_TEST = ( + b"\x2b\x49\xfc\xe9\x08\x00\x00\x00\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc" + b"\x56\x8b\xf1\xe8\xd8\xc5\x00\x00\x8b\xce\xe8\xb1\xdc\x01\x00\xf6" + b"\x44\x24\x08\x01\x74\x0c\x8d\x46\xe0\x50\xe8\xe1\x66\x07\x00\x83" + b"\xc4\x04\x8d\x46\xe0\x5e\xc2\x04\x00\xcc\xcc\xcc\xcc\xcc\xcc\xcc" + b"\x2b\x49\xfc\xe9\x08\x00\x00\x00\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc" + b"\xb8\x7c\x05\x0f\x10\xc3\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc" + b"\x2b\x49\xfc\xe9\x08\x00\x00\x00\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc" + b"\x8b\x54" + # The problem is here: the last two bytes are the start of the next + # function 0x1000fbc0. This is not enough data to read an instruction. +) + + +def test_thunk_case(): + """Adjuster thunk incorrectly annotated. + We are reading way more bytes than we should for this function.""" + ig = InstructGen(THUNK_TEST, 0x1000FB50) + # No switch cases here, so the only section is code. + # This caused an infinite loop during testing so the goal is just to finish. + assert len(ig.sections) == 1 + + # TODO: We might detect the 0xCC padding bytes and cut off the function. + # If we did that, we would correctly read only 2 instructions. + # assert len(ig.sections[0].contents) == 2 + + +# LEGO1 0x1006f080, Infocenter::HandleEndAction +HANDLE_END_ACTION = ( + b"\x53\x56\x57\x8b\xf1\x8b\x5c\x24\x10\x8b\x0d\x84\x45\x0f\x10\x8b" + b"\x7b\x0c\x8b\x47\x20\x39\x01\x75\x29\x81\x7f\x1c\xf3\x01\x00\x00" + b"\x75\x20\xe8\x59\x66\xfa\xff\x6a\x00\x8b\x40\x18\x6a\x00\x6a\x10" + b"\x50\xff\x15\x38\xb5\x10\x10\xb8\x01\x00\x00\x00\x5f\x5e\x5b\xc2" + b"\x04\x00\x39\x46\x0c\x0f\x85\xa2\x00\x00\x00\x8b\x47\x1c\x83\xf8" + b"\x28\x74\x18\x83\xf8\x29\x74\x13\x83\xf8\x2a\x74\x0e\x83\xf8\x2b" + b"\x74\x09\x83\xf8\x2c\x0f\x85\x82\x00\x00\x00\x66\x8b\x86\xd4\x01" + b"\x00\x00\x66\x85\xc0\x74\x09\x66\x48\x66\x89\x86\xd4\x01\x00\x00" + b"\x66\x83\xbe\xd4\x01\x00\x00\x00\x75\x63\x6a\x0b\xe8\xff\x67\xfa" + b"\xff\x66\x8b\x86\xfc\x00\x00\x00\x83\xc4\x04\x50\xe8\x3f\x66\xfa" + b"\xff\x8b\xc8\xe8\x58\xa6\xfc\xff\x0f\xbf\x86\xfc\x00\x00\x00\x48" + b"\x83\xf8\x04\x77\x2f\xff\x24\x85\x78\xf4\x06\x10\x68\x1d\x02\x00" + b"\x00\xeb\x1a\x68\x1e\x02\x00\x00\xeb\x13\x68\x1f\x02\x00\x00\xeb" + b"\x0c\x68\x20\x02\x00\x00\xeb\x05\x68\x21\x02\x00\x00\x8b\xce\xe8" + b"\x9c\x21\x00\x00\x6a\x01\x8b\xce\xe8\x53\x1c\x00\x00\x8d\x8e\x0c" + b"\x01\x00\x00\x53\x8b\x01\xff\x50\x04\x85\xc0\x0f\x85\xef\x02\x00" + b"\x00\x8b\x56\x0c\x8b\x4f\x20\x3b\xd1\x74\x0e\x8b\x1d\x74\x45\x0f" + b"\x10\x39\x0b\x0f\x85\xd7\x02\x00\x00\x81\x7f\x1c\x02\x02\x00\x00" + b"\x75\x1a\x6a\x00\x52\x6a\x10\xe8\xa4\x65\xfa\xff\x8b\xc8\xe8\x0d" + b"\xa2\xfb\xff\x66\xc7\x86\xd6\x01\x00\x00\x00\x00\x8b\x96\x00\x01" + b"\x00\x00\x8d\x42\x74\x8b\x18\x83\xfb\x0c\x0f\x87\x9b\x02\x00\x00" + b"\x33\xc9\x8a\x8b\xac\xf4\x06\x10\xff\x24\x8d\x8c\xf4\x06\x10\x8b" + b"\x86\x08\x01\x00\x00\x83\xf8\x05\x77\x07\xff\x24\x85\xbc\xf4\x06" + b"\x10\x8b\xce\xe8\xb8\x1a\x00\x00\x8b\x86\x00\x01\x00\x00\x68\xf4" + b"\x01\x00\x00\x8b\xce\xc7\x40\x74\x0b\x00\x00\x00\xe8\xef\x20\x00" + b"\x00\x8b\x86\x00\x01\x00\x00\xc7\x86\x08\x01\x00\x00\xff\xff\xff" + b"\xff\x83\x78\x78\x00\x0f\x85\x40\x02\x00\x00\xb8\x01\x00\x00\x00" + b"\x5f\x66\xc7\x86\xd2\x01\x00\x00\x01\x00\x5e\x5b\xc2\x04\x00\x6a" + b"\x00\x8b\xce\x6a\x01\xe8\xd6\x19\x00\x00\xb8\x01\x00\x00\x00\x5f" + b"\x5e\x5b\xc2\x04\x00\x6a\x01\x8b\xce\x6a\x02\xe8\xc0\x19\x00\x00" + b"\xb8\x01\x00\x00\x00\x5f\x5e\x5b\xc2\x04\x00\x8b\xce\xe8\x3e\x1a" + b"\x00\x00\x8b\x86\x00\x01\x00\x00\x68\x1c\x02\x00\x00\x8b\xce\xc7" + b"\x40\x74\x0b\x00\x00\x00\xe8\x75\x20\x00\x00\xb8\x01\x00\x00\x00" + b"\x5f\xc7\x86\x08\x01\x00\x00\xff\xff\xff\xff\x5e\x5b\xc2\x04\x00" + b"\x8b\xce\xe8\x09\x1a\x00\x00\x8b\x86\x00\x01\x00\x00\x68\x1b\x02" + b"\x00\x00\x8b\xce\xc7\x40\x74\x0b\x00\x00\x00\xe8\x40\x20\x00\x00" + b"\xb8\x01\x00\x00\x00\x5f\xc7\x86\x08\x01\x00\x00\xff\xff\xff\xff" + b"\x5e\x5b\xc2\x04\x00\xc7\x00\x0b\x00\x00\x00\x8b\x86\x08\x01\x00" + b"\x00\x83\xf8\x04\x74\x0c\x83\xf8\x05\x74\x0e\x68\xf4\x01\x00\x00" + b"\xeb\x0c\x68\x1c\x02\x00\x00\xeb\x05\x68\x1b\x02\x00\x00\x8b\xce" + b"\xe8\xfb\x1f\x00\x00\xb8\x01\x00\x00\x00\x5f\xc7\x86\x08\x01\x00" + b"\x00\xff\xff\xff\xff\x5e\x5b\xc2\x04\x00\x6a\x00\xa1\xa0\x76\x0f" + b"\x10\x50\xe8\x39\x65\xfa\xff\x83\xc4\x08\xa1\xa4\x76\x0f\x10\x6a" + b"\x00\x50\xe8\x29\x65\xfa\xff\x83\xc4\x08\xe8\xf1\x63\xfa\xff\x8b" + b"\xc8\xe8\x6a\x02\x01\x00\xb8\x01\x00\x00\x00\x5f\x5e\x5b\xc2\x04" + b"\x00\x8b\x47\x1c\x83\xf8\x46\x74\x09\x83\xf8\x47\x0f\x85\x09\x01" + b"\x00\x00\x6a\x00\x6a\x00\x6a\x32\x6a\x03\xe8\x91\x65\xfa\xff\x8b" + b"\xc8\xe8\xfa\xc7\xfd\xff\x8b\x86\x00\x01\x00\x00\x5f\x5e\x5b\xc7" + b"\x40\x74\x0e\x00\x00\x00\xb8\x01\x00\x00\x00\xc2\x04\x00\x8b\x47" + b"\x1c\x39\x86\xf8\x00\x00\x00\x0f\x85\xce\x00\x00\x00\xe8\xbe\x63" + b"\xfa\xff\x83\x78\x10\x02\x74\x19\x66\x8b\x86\xfc\x00\x00\x00\x66" + b"\x85\xc0\x74\x0d\x50\xe8\xa6\x63\xfa\xff\x8b\xc8\xe8\xbf\xa3\xfc" + b"\xff\x6a\x00\x6a\x00\x6a\x32\x6a\x03\xe8\x32\x65\xfa\xff\x8b\xc8" + b"\xe8\x9b\xc7\xfd\xff\x8b\x86\x00\x01\x00\x00\x5f\x5e\x5b\xc7\x40" + b"\x74\x0e\x00\x00\x00\xb8\x01\x00\x00\x00\xc2\x04\x00\x83\x7a\x78" + b"\x00\x75\x32\x8b\x86\xf8\x00\x00\x00\x83\xf8\x28\x74\x27\x83\xf8" + b"\x29\x74\x22\x83\xf8\x2a\x74\x1d\x83\xf8\x2b\x74\x18\x83\xf8\x2c" + b"\x74\x13\x66\xc7\x86\xd0\x01\x00\x00\x01\x00\x6a\x0b\xe8\xee\x64" + b"\xfa\xff\x83\xc4\x04\x8b\x86\x00\x01\x00\x00\x6a\x01\x68\xdc\x44" + b"\x0f\x10\xc7\x40\x74\x02\x00\x00\x00\xe8\x22\x64\xfa\xff\x83\xc4" + b"\x08\xb8\x01\x00\x00\x00\x5f\x5e\x5b\xc2\x04\x00\x8b\x47\x1c\x39" + b"\x86\xf8\x00\x00\x00\x75\x14\x6a\x00\x6a\x00\x6a\x32\x6a\x03\xe8" + b"\x9c\x64\xfa\xff\x8b\xc8\xe8\x05\xc7\xfd\xff\xb8\x01\x00\x00\x00" + b"\x5f\x5e\x5b\xc2\x04\x00\x8b\xff\x3c\xf1\x06\x10\x43\xf1\x06\x10" + b"\x4a\xf1\x06\x10\x51\xf1\x06\x10\x58\xf1\x06\x10\xdf\xf1\x06\x10" + b"\xd5\xf2\x06\x10\x1a\xf3\x06\x10\x51\xf3\x06\x10\x8e\xf3\x06\x10" + b"\xed\xf3\x06\x10\x4c\xf4\x06\x10\x6b\xf4\x06\x10\x00\x01\x02\x07" + b"\x03\x04\x07\x07\x07\x07\x07\x05\x06\x8d\x49\x00\x3f\xf2\x06\x10" + b"\x55\xf2\x06\x10\xf1\xf1\x06\x10\xf1\xf1\x06\x10\x6b\xf2\x06\x10" + b"\xa0\xf2\x06\x10\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc\xcc" +) + + +def test_action_case(): + """3 switches: 3 jump tables, 1 data table""" + ig = InstructGen(HANDLE_END_ACTION, 0x1006F080) + # Two of the jump tables (0x1006f478 with 5, 0x1006f48c with 8) + # are contiguous. + assert len(ig.sections) == 5 diff --git a/tools/isledecomp/tests/test_islebin.py b/tools/isledecomp/tests/test_islebin.py new file mode 100644 index 00000000..14fb85b4 --- /dev/null +++ b/tools/isledecomp/tests/test_islebin.py @@ -0,0 +1,152 @@ +"""Tests for the Bin (or IsleBin) module that: +1. Parses relevant data from the PE header and other structures. +2. Provides an interface to read from the DLL or EXE using a virtual address. +These are some basic smoke tests.""" + +import hashlib +from typing import Tuple +import pytest +from isledecomp.bin import ( + Bin as IsleBin, + SectionNotFoundError, + InvalidVirtualAddressError, +) + + +# LEGO1.DLL: v1.1 English, September +LEGO1_SHA256 = "14645225bbe81212e9bc1919cd8a692b81b8622abb6561280d99b0fc4151ce17" + + +@pytest.fixture(name="binfile", scope="session") +def fixture_binfile(pytestconfig) -> IsleBin: + filename = pytestconfig.getoption("--lego1") + + # Skip this if we have not provided the path to LEGO1.dll. + if filename is None: + pytest.skip(allow_module_level=True, reason="No path to LEGO1") + + with open(filename, "rb") as f: + digest = hashlib.sha256(f.read()).hexdigest() + if digest != LEGO1_SHA256: + pytest.fail(reason="Did not match expected LEGO1.DLL") + + with IsleBin(filename, find_str=True) as islebin: + yield islebin + + +def test_basic(binfile: IsleBin): + assert binfile.entry == 0x1008C860 + assert len(binfile.sections) == 6 + + with pytest.raises(SectionNotFoundError): + binfile.get_section_by_name(".hello") + + +SECTION_INFO = ( + (".text", 0x10001000, 0xD2A66, 0xD2C00), + (".rdata", 0x100D4000, 0x1B5B6, 0x1B600), + (".data", 0x100F0000, 0x1A734, 0x12C00), + (".idata", 0x1010B000, 0x1006, 0x1200), + (".rsrc", 0x1010D000, 0x21D8, 0x2200), + (".reloc", 0x10110000, 0x10C58, 0x10E00), +) + + +@pytest.mark.parametrize("name, v_addr, v_size, raw_size", SECTION_INFO) +def test_sections(name: str, v_addr: int, v_size: int, raw_size: int, binfile: IsleBin): + section = binfile.get_section_by_name(name) + assert section.virtual_address == v_addr + assert section.virtual_size == v_size + assert section.size_of_raw_data == raw_size + + +DOUBLE_PI_BYTES = b"\x18\x2d\x44\x54\xfb\x21\x09\x40" + +# Now that's a lot of pi +PI_ADDRESSES = ( + 0x100D4000, + 0x100D4700, + 0x100D7180, + 0x100DB8F0, + 0x100DC030, +) + + +@pytest.mark.parametrize("addr", PI_ADDRESSES) +def test_read_pi(addr: int, binfile: IsleBin): + assert binfile.read(addr, 8) == DOUBLE_PI_BYTES + + +def test_unusual_reads(binfile: IsleBin): + """Reads that return an error or some specific value based on context""" + # Reading an address earlier than the imagebase + with pytest.raises(InvalidVirtualAddressError): + binfile.read(0, 1) + + # Really big address + with pytest.raises(InvalidVirtualAddressError): + binfile.read(0xFFFFFFFF, 1) + + # Uninitialized part of .data + assert binfile.read(0x1010A600, 4) is None + + # Past the end of virtual size in .text + assert binfile.read(0x100D3A70, 4) == b"\x00\x00\x00\x00" + + +STRING_ADDRESSES = ( + (0x100DB588, b"November"), + (0x100F0130, b"Helicopter"), + (0x100F0144, b"HelicopterState"), + (0x100F0BE4, b"valerie"), + (0x100F4080, b"TARGET"), +) + + +@pytest.mark.parametrize("addr, string", STRING_ADDRESSES) +def test_strings(addr: int, string: bytes, binfile: IsleBin): + """Test string read utility function and the string search feature""" + assert binfile.read_string(addr) == string + assert binfile.find_string(string) == addr + + +def test_relocation(binfile: IsleBin): + # n.b. This is not the number of *relocations* read from .reloc. + # It is the set of unique addresses in the binary that get relocated. + assert len(binfile.get_relocated_addresses()) == 14066 + + # Score::Score is referenced only by CALL instructions. No need to relocate. + assert binfile.is_relocated_addr(0x10001000) is False + + # MxEntity::SetEntityId is in the vtable and must be relocated. + assert binfile.is_relocated_addr(0x10001070) is True + + +# Not sanitizing dll name case. Do we care? +IMPORT_REFS = ( + ("KERNEL32.dll", "CreateMutexA", 0x1010B3D0), + ("WINMM.dll", "midiOutPrepareHeader", 0x1010B550), +) + + +@pytest.mark.parametrize("import_ref", IMPORT_REFS) +def test_imports(import_ref: Tuple[str, str, int], binfile: IsleBin): + assert import_ref in binfile.imports + + +# Location of the JMP instruction and the import address. +THUNKS = ( + (0x100D3728, 0x1010B32C), # DirectDrawCreate + (0x10098F9E, 0x1010B3D4), # RtlUnwind +) + + +@pytest.mark.parametrize("thunk_ref", THUNKS) +def test_thunks(thunk_ref: Tuple[int, int], binfile: IsleBin): + assert thunk_ref in binfile.thunks + + +def test_exports(binfile: IsleBin): + assert len(binfile.exports) == 130 + assert (0x1003BFB0, b"??0LegoBackgroundColor@@QAE@PBD0@Z") in binfile.exports + assert (0x10091EE0, b"_DllMain@12") in binfile.exports diff --git a/tools/isledecomp/tests/test_linter.py b/tools/isledecomp/tests/test_linter.py new file mode 100644 index 00000000..95d19515 --- /dev/null +++ b/tools/isledecomp/tests/test_linter.py @@ -0,0 +1,144 @@ +import pytest +from isledecomp.parser import DecompLinter +from isledecomp.parser.error import ParserError + + +@pytest.fixture(name="linter") +def fixture_linter(): + return DecompLinter() + + +def test_simple_in_order(linter): + lines = [ + "// FUNCTION: TEST 0x1000", + "void function1() {}", + "// FUNCTION: TEST 0x2000", + "void function2() {}", + "// FUNCTION: TEST 0x3000", + "void function3() {}", + ] + assert linter.check_lines(lines, "test.cpp", "TEST") is True + + +def test_simple_not_in_order(linter): + lines = [ + "// FUNCTION: TEST 0x1000", + "void function1() {}", + "// FUNCTION: TEST 0x3000", + "void function3() {}", + "// FUNCTION: TEST 0x2000", + "void function2() {}", + ] + assert linter.check_lines(lines, "test.cpp", "TEST") is False + assert len(linter.alerts) == 1 + + assert linter.alerts[0].code == ParserError.FUNCTION_OUT_OF_ORDER + # N.B. Line number given is the start of the function, not the marker + assert linter.alerts[0].line_number == 6 + + +def test_byname_ignored(linter): + """Should ignore lookup-by-name markers when checking order.""" + lines = [ + "// FUNCTION: TEST 0x1000", + "void function1() {}", + "// FUNCTION: TEST 0x3000", + "// MyClass::MyMethod", + "// FUNCTION: TEST 0x2000", + "void function2() {}", + ] + # This will fail because byname lookup does not belong in the cpp file + assert linter.check_lines(lines, "test.cpp", "TEST") is False + # but it should not fail for function order. + assert all( + alert.code != ParserError.FUNCTION_OUT_OF_ORDER for alert in linter.alerts + ) + + +def test_module_isolation(linter): + """Should check the order of markers from a single module only.""" + lines = [ + "// FUNCTION: ALPHA 0x0001", + "// FUNCTION: TEST 0x1000", + "void function1() {}", + "// FUNCTION: ALPHA 0x0002", + "// FUNCTION: TEST 0x2000", + "void function2() {}", + "// FUNCTION: ALPHA 0x0003", + "// FUNCTION: TEST 0x3000", + "void function3() {}", + ] + + assert linter.check_lines(lines, "test.cpp", "TEST") is True + linter.reset(True) + assert linter.check_lines(lines, "test.cpp", "ALPHA") is True + + +def test_byname_headers_only(linter): + """Markers that ar referenced by name with cvdump belong in header files only.""" + lines = [ + "// FUNCTION: TEST 0x1000", + "// MyClass::~MyClass", + ] + + assert linter.check_lines(lines, "test.h", "TEST") is True + linter.reset(True) + assert linter.check_lines(lines, "test.cpp", "TEST") is False + assert linter.alerts[0].code == ParserError.BYNAME_FUNCTION_IN_CPP + + +def test_duplicate_offsets(linter): + """The linter will retain module/offset pairs found until we do a full reset.""" + lines = [ + "// FUNCTION: TEST 0x1000", + "// FUNCTION: HELLO 0x1000", + "// MyClass::~MyClass", + ] + + # Should not fail for duplicate offset 0x1000 because the modules are unique. + assert linter.check_lines(lines, "test.h", "TEST") is True + + # Simulate a failure by reading the same file twice. + assert linter.check_lines(lines, "test.h", "TEST") is False + + # Two errors because offsets from both modules are duplicated + assert len(linter.alerts) == 2 + assert all(a.code == ParserError.DUPLICATE_OFFSET for a in linter.alerts) + + # Partial reset will retain the list of seen offsets. + linter.reset(False) + assert linter.check_lines(lines, "test.h", "TEST") is False + + # Full reset will forget seen offsets. + linter.reset(True) + assert linter.check_lines(lines, "test.h", "TEST") is True + + +def test_duplicate_strings(linter): + """Duplicate string markers are okay if the string value is the same.""" + string_lines = [ + "// STRING: TEST 0x1000", + 'return "hello world";', + ] + + # No problem to use this marker twice. + assert linter.check_lines(string_lines, "test.h", "TEST") is True + assert linter.check_lines(string_lines, "test.h", "TEST") is True + + different_string = [ + "// STRING: TEST 0x1000", + 'return "hi there";', + ] + + # Same address but the string is different + assert linter.check_lines(different_string, "greeting.h", "TEST") is False + assert len(linter.alerts) == 1 + assert linter.alerts[0].code == ParserError.WRONG_STRING + + same_addr_reused = [ + "// GLOBAL:TEXT 0x1000", + "int g_test = 123;", + ] + + # This will fail like any other offset reuse. + assert linter.check_lines(same_addr_reused, "other.h", "TEST") is False diff --git a/tools/isledecomp/tests/test_parser.py b/tools/isledecomp/tests/test_parser.py new file mode 100644 index 00000000..c7905627 --- /dev/null +++ b/tools/isledecomp/tests/test_parser.py @@ -0,0 +1,773 @@ +import pytest +from isledecomp.parser.parser import ( + ReaderState, + DecompParser, +) +from isledecomp.parser.error import ParserError + + +@pytest.fixture(name="parser") +def fixture_parser(): + return DecompParser() + + +def test_missing_sig(parser): + """In the hopefully rare scenario that the function signature and marker + are swapped, we still have enough to match witch reccmp""" + parser.read_lines( + [ + "void my_function()", + "// FUNCTION: TEST 0x1234", + "{", + "}", + ] + ) + assert parser.state == ReaderState.SEARCH + assert len(parser.functions) == 1 + assert parser.functions[0].line_number == 3 + + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.MISSED_START_OF_FUNCTION + + +def test_not_exact_syntax(parser): + """Alert to inexact syntax right here in the parser instead of kicking it downstream. + Doing this means we don't have to save the actual text.""" + parser.read_line("// function: test 0x1234") + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.BAD_DECOMP_MARKER + + +def test_invalid_marker(parser): + """We matched a decomp marker, but it's not one we care about""" + parser.read_line("// BANANA: TEST 0x1234") + assert parser.state == ReaderState.SEARCH + + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.BOGUS_MARKER + + +def test_incompatible_marker(parser): + """The marker we just read cannot be handled in the current parser state""" + parser.read_lines( + [ + "// FUNCTION: TEST 0x1234", + "// GLOBAL: TEST 0x5000", + ] + ) + assert parser.state == ReaderState.SEARCH + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.INCOMPATIBLE_MARKER + + +def test_variable(parser): + """Should identify a global variable""" + parser.read_lines( + [ + "// GLOBAL: HELLO 0x1234", + "int g_value = 5;", + ] + ) + assert len(parser.variables) == 1 + + +def test_synthetic_plus_marker(parser): + """Marker tracking preempts synthetic name detection. + Should fail with error and not log the synthetic""" + parser.read_lines( + [ + "// SYNTHETIC: HEY 0x555", + "// FUNCTION: HOWDY 0x1234", + ] + ) + assert len(parser.functions) == 0 + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.INCOMPATIBLE_MARKER + + +def test_different_markers_different_module(parser): + """Does it make any sense for a function to be a stub in one module, + but not in another? I don't know. But it's no problem for us.""" + parser.read_lines( + [ + "// FUNCTION: HOWDY 0x1234", + "// STUB: SUP 0x5555", + "void interesting_function() {", + "}", + ] + ) + + assert len(parser.alerts) == 0 + assert len(parser.functions) == 2 + + +def test_different_markers_same_module(parser): + """Now, if something is a regular function but then a stub, + what do we say about that?""" + parser.read_lines( + [ + "// FUNCTION: HOWDY 0x1234", + "// STUB: HOWDY 0x5555", + "void interesting_function() {", + "}", + ] + ) + + # Use first marker declaration, don't replace + assert len(parser.functions) == 1 + assert parser.functions[0].should_skip() is False + + # Should alert to this + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.DUPLICATE_MODULE + + +def test_unexpected_synthetic(parser): + """FUNCTION then SYNTHETIC should fail to report either one""" + parser.read_lines( + [ + "// FUNCTION: HOWDY 0x1234", + "// SYNTHETIC: HOWDY 0x5555", + "void interesting_function() {", + "}", + ] + ) + + assert parser.state == ReaderState.SEARCH + assert len(parser.functions) == 0 + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.INCOMPATIBLE_MARKER + + +@pytest.mark.skip(reason="not implemented yet") +def test_duplicate_offset(parser): + """Repeating the same module/offset in the same file is probably a typo""" + parser.read_lines( + [ + "// GLOBAL: HELLO 0x1234", + "int x = 1;", + "// GLOBAL: HELLO 0x1234", + "int y = 2;", + ] + ) + + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.DUPLICATE_OFFSET + + +def test_multiple_variables(parser): + """Theoretically the same global variable can appear in multiple modules""" + parser.read_lines( + [ + "// GLOBAL: HELLO 0x1234", + "// GLOBAL: WUZZUP 0x555", + "const char *g_greeting;", + ] + ) + assert len(parser.alerts) == 0 + assert len(parser.variables) == 2 + + +def test_multiple_variables_same_module(parser): + """Should not overwrite offset""" + parser.read_lines( + [ + "// GLOBAL: HELLO 0x1234", + "// GLOBAL: HELLO 0x555", + "const char *g_greeting;", + ] + ) + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.DUPLICATE_MODULE + assert len(parser.variables) == 1 + assert parser.variables[0].offset == 0x1234 + + +def test_multiple_vtables(parser): + parser.read_lines( + [ + "// VTABLE: HELLO 0x1234", + "// VTABLE: TEST 0x5432", + "class MxString : public MxCore {", + ] + ) + assert len(parser.alerts) == 0 + assert len(parser.vtables) == 2 + assert parser.vtables[0].name == "MxString" + + +def test_multiple_vtables_same_module(parser): + """Should not overwrite offset""" + parser.read_lines( + [ + "// VTABLE: HELLO 0x1234", + "// VTABLE: HELLO 0x5432", + "class MxString : public MxCore {", + ] + ) + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.DUPLICATE_MODULE + assert len(parser.vtables) == 1 + assert parser.vtables[0].offset == 0x1234 + + +def test_synthetic(parser): + parser.read_lines( + [ + "// SYNTHETIC: TEST 0x1234", + "// TestClass::TestMethod", + ] + ) + assert len(parser.functions) == 1 + assert parser.functions[0].lookup_by_name is True + assert parser.functions[0].name == "TestClass::TestMethod" + + +def test_synthetic_same_module(parser): + parser.read_lines( + [ + "// SYNTHETIC: TEST 0x1234", + "// SYNTHETIC: TEST 0x555", + "// TestClass::TestMethod", + ] + ) + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.DUPLICATE_MODULE + assert len(parser.functions) == 1 + assert parser.functions[0].offset == 0x1234 + + +def test_synthetic_no_comment(parser): + """Synthetic marker followed by a code line (i.e. non-comment)""" + parser.read_lines( + [ + "// SYNTHETIC: TEST 0x1234", + "int x = 123;", + ] + ) + assert len(parser.functions) == 0 + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.BAD_NAMEREF + assert parser.state == ReaderState.SEARCH + + +def test_single_line_function(parser): + parser.read_lines( + [ + "// FUNCTION: TEST 0x1234", + "int hello() { return 1234; }", + ] + ) + assert len(parser.functions) == 1 + assert parser.functions[0].line_number == 2 + assert parser.functions[0].end_line == 2 + + +def test_indented_function(parser): + """Track the number of whitespace characters when we begin the function + and check that against each closing curly brace we read. + Should not report a syntax warning if the function is indented""" + parser.read_lines( + [ + " // FUNCTION: TEST 0x1234", + " void indented()", + " {", + " // TODO", + " }", + " // FUNCTION: NEXT 0x555", + ] + ) + assert len(parser.alerts) == 0 + + +@pytest.mark.xfail(reason="todo") +def test_indented_no_curly_hint(parser): + """Same as above, but opening curly brace is on the same line. + Without the hint of how many whitespace characters to check, can we + still identify the end of the function?""" + parser.read_lines( + [ + " // FUNCTION: TEST 0x1234", + " void indented() {", + " }", + " // FUNCTION: NEXT 0x555", + ] + ) + assert len(parser.alerts) == 0 + + +def test_implicit_lookup_by_name(parser): + """FUNCTION (or STUB) offsets must directly precede the function signature. + If we detect a comment instead, we assume that this is a lookup-by-name + function and end here.""" + parser.read_lines( + [ + "// FUNCTION: TEST 0x1234", + "// TestClass::TestMethod()", + ] + ) + assert parser.state == ReaderState.SEARCH + assert len(parser.functions) == 1 + assert parser.functions[0].lookup_by_name is True + assert parser.functions[0].name == "TestClass::TestMethod()" + + +def test_function_with_spaces(parser): + """There should not be any spaces between the end of FUNCTION markers + and the start or name of the function. If it's a blank line, we can safely + ignore but should alert to this.""" + parser.read_lines( + [ + "// FUNCTION: TEST 0x1234", + " ", + "inline void test_function() { };", + ] + ) + assert len(parser.functions) == 1 + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.UNEXPECTED_BLANK_LINE + + +def test_function_with_spaces_implicit(parser): + """Same as above, but for implicit lookup-by-name""" + parser.read_lines( + [ + "// FUNCTION: TEST 0x1234", + " ", + "// Implicit::Method", + ] + ) + assert len(parser.functions) == 1 + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.UNEXPECTED_BLANK_LINE + + +@pytest.mark.xfail(reason="will assume implicit lookup-by-name function") +def test_function_is_commented(parser): + """In an ideal world, we would recognize that there is no code here. + Some editors (or users) might comment the function on each line like this + but hopefully it is rare.""" + parser.read_lines( + [ + "// FUNCTION: TEST 0x1234", + "// int my_function()", + "// {", + "// return 5;", + "// }", + ] + ) + + assert len(parser.functions) == 0 + + +def test_unexpected_eof(parser): + """If a decomp marker finds its way to the last line of the file, + report that we could not get anything from it.""" + parser.read_lines( + [ + "// FUNCTION: TEST 0x1234", + "// Cls::Method", + "// FUNCTION: TEST 0x5555", + ] + ) + parser.finish() + + assert len(parser.functions) == 1 + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.UNEXPECTED_END_OF_FILE + + +@pytest.mark.xfail(reason="no longer applies") +def test_global_variable_prefix(parser): + """Global and static variables should have the g_ prefix.""" + parser.read_lines( + [ + "// GLOBAL: TEST 0x1234", + 'const char* g_msg = "hello";', + ] + ) + assert len(parser.variables) == 1 + assert len(parser.alerts) == 0 + + parser.read_lines( + [ + "// GLOBAL: TEXT 0x5555", + "int test = 5;", + ] + ) + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.GLOBAL_MISSING_PREFIX + # In spite of that, we should still grab the variable name. + assert parser.variables[1].name == "test" + + +def test_global_nomatch(parser): + """We do our best to grab the variable name, even without the g_ prefix + but this (by design) will not match everything.""" + + parser.read_lines( + [ + "// GLOBAL: TEST 0x1234", + "FunctionCall();", + ] + ) + assert len(parser.variables) == 0 + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.NO_SUITABLE_NAME + + +def test_static_variable(parser): + """We can detect whether a variable is a static function variable + based on the parser's state when we detect it. + Checking for the word `static` alone is not a good test. + Static class variables are filed as S_GDATA32, same as regular globals.""" + + parser.read_lines( + [ + "// GLOBAL: TEST 0x1234", + "int g_test = 1234;", + ] + ) + assert len(parser.variables) == 1 + assert parser.variables[0].is_static is False + + parser.read_lines( + [ + "// FUNCTION: TEST 0x5555", + "void test_function() {", + "// GLOBAL: TEST 0x8888", + "static int g_internal = 0;", + "}", + ] + ) + assert len(parser.variables) == 2 + assert parser.variables[1].is_static is True + + +def test_reject_global_return(parser): + """Previously we had annotated strings with the GLOBAL marker. + For example: if a function returned a string. We now want these to be + annotated with the STRING marker.""" + + parser.read_lines( + [ + "// FUNCTION: TEST 0x5555", + "void test_function() {", + " // GLOBAL: TEST 0x8888", + ' return "test";', + "}", + ] + ) + assert len(parser.variables) == 0 + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.GLOBAL_NOT_VARIABLE + + +def test_global_string(parser): + """We now allow GLOBAL and STRING markers for the same item.""" + + parser.read_lines( + [ + "// GLOBAL: TEST 0x1234", + "// STRING: TEXT 0x5555", + 'char* g_test = "hello";', + ] + ) + assert len(parser.variables) == 1 + assert len(parser.strings) == 1 + assert len(parser.alerts) == 0 + + assert parser.variables[0].name == "g_test" + assert parser.strings[0].name == "hello" + + +def test_comment_variables(parser): + """Match on hidden variables from libraries.""" + + parser.read_lines( + [ + "// GLOBAL: TEST 0x1234", + "// g_test", + ] + ) + assert len(parser.variables) == 1 + assert parser.variables[0].name == "g_test" + + +def test_flexible_variable_prefix(parser): + """Don't alert to library variables that lack the g_ prefix. + This is out of our control.""" + + parser.read_lines( + [ + "// GLOBAL: TEST 0x1234", + "// some_other_variable", + ] + ) + assert len(parser.variables) == 1 + assert len(parser.alerts) == 0 + assert parser.variables[0].name == "some_other_variable" + + +def test_string_ignore_g_prefix(parser): + """String annotations above a regular variable should not alert to + the missing g_ prefix. This is only required for GLOBAL markers.""" + + parser.read_lines( + [ + "// STRING: TEST 0x1234", + 'const char* value = "";', + ] + ) + assert len(parser.strings) == 1 + assert len(parser.alerts) == 0 + + +def test_class_variable(parser): + """We should accurately name static variables that are class members.""" + + parser.read_lines( + [ + "class Test {", + "protected:", + " // GLOBAL: TEST 0x1234", + " static int g_test;", + "};", + ] + ) + + assert len(parser.variables) == 1 + assert parser.variables[0].name == "Test::g_test" + + +def test_namespace_variable(parser): + """We should identify a namespace surrounding any global variables""" + + parser.read_lines( + [ + "namespace Test {", + "// GLOBAL: TEST 0x1234", + "int g_test = 1234;", + "}", + "// GLOBAL: TEST 0x5555", + "int g_second = 2;", + ] + ) + + assert len(parser.variables) == 2 + assert parser.variables[0].name == "Test::g_test" + assert parser.variables[1].name == "g_second" + + +def test_namespace_vtable(parser): + parser.read_lines( + [ + "namespace Tgl {", + "// VTABLE: TEST 0x1234", + "class Renderer {", + "};", + "}", + "// VTABLE: TEST 0x5555", + "class Hello { };", + ] + ) + + assert len(parser.vtables) == 2 + assert parser.vtables[0].name == "Tgl::Renderer" + assert parser.vtables[1].name == "Hello" + + +@pytest.mark.xfail(reason="no longer applies") +def test_global_prefix_namespace(parser): + """Should correctly identify namespaces before checking for the g_ prefix""" + + parser.read_lines( + [ + "class Test {", + " // GLOBAL: TEST 0x1234", + " static int g_count = 0;", + " // GLOBAL: TEST 0x5555", + " static int count = 0;", + "};", + ] + ) + + assert len(parser.variables) == 2 + assert parser.variables[0].name == "Test::g_count" + assert parser.variables[1].name == "Test::count" + + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.GLOBAL_MISSING_PREFIX + + +def test_nested_namespace(parser): + parser.read_lines( + [ + "namespace Tgl {", + "class Renderer {", + " // GLOBAL: TEST 0x1234", + " static int g_count = 0;", + "};", + "};", + ] + ) + + assert len(parser.variables) == 1 + assert parser.variables[0].name == "Tgl::Renderer::g_count" + + +def test_match_qualified_variable(parser): + """If a variable belongs to a scope and we use a fully qualified reference + below a GLOBAL marker, make sure we capture the full name.""" + + parser.read_lines( + [ + "// GLOBAL: TEST 0x1234", + "int MxTest::g_count = 0;", + ] + ) + + assert len(parser.variables) == 1 + assert parser.variables[0].name == "MxTest::g_count" + assert len(parser.alerts) == 0 + + +def test_static_variable_parent(parser): + """Report the address of the parent function that contains a static variable.""" + + parser.read_lines( + [ + "// FUNCTION: TEST 0x1234", + "void test()", + "{", + " // GLOBAL: TEST 0x5555", + " static int g_count = 0;", + "}", + ] + ) + + assert len(parser.variables) == 1 + assert parser.variables[0].is_static is True + assert parser.variables[0].parent_function == 0x1234 + + +@pytest.mark.xfail( + reason="""Without the FUNCTION marker we don't know that we are inside a function, + so we do not identify this variable as static.""" +) +def test_static_variable_no_parent(parser): + """If the function that contains a static variable is not marked, we + cannot match it with cvdump so we should skip it and report an error.""" + + parser.read_lines( + [ + "void test()", + "{", + " // GLOBAL: TEST 0x5555", + " static int g_count = 0;", + "}", + ] + ) + + # No way to match this variable so don't report it + assert len(parser.variables) == 0 + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.ORPHANED_STATIC_VARIABLE + + +def test_static_variable_incomplete_coverage(parser): + """If the function that contains a static variable is marked, but + not for each module used for the variable itself, this is an error.""" + + parser.read_lines( + [ + "// FUNCTION: HELLO 0x1234", + "void test()", + "{", + " // GLOBAL: HELLO 0x5555", + " // GLOBAL: TEST 0x5555", + " static int g_count = 0;", + "}", + ] + ) + + # Match for HELLO module + assert len(parser.variables) == 1 + + # Failed for TEST module + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.ORPHANED_STATIC_VARIABLE + + +def test_header_function_declaration(parser): + """This is either a forward reference or a declaration in a header file. + Meaning: The implementation is not here. This is not the correct place + for the FUNCTION marker and it will probably not match anything.""" + + parser.read_lines( + [ + "// FUNCTION: HELLO 0x1234", + "void sample_function(int);", + ] + ) + + assert len(parser.alerts) == 1 + assert parser.alerts[0].code == ParserError.NO_IMPLEMENTATION + + +def test_extra(parser): + """Allow a fourth field in the decomp annotation. Its use will vary + depending on the marker type. Currently this is only used to identify + a vtable with virtual inheritance.""" + + # Intentionally using non-vtable markers here. + # We might want to emit a parser warning for unnecessary extra info. + parser.read_lines( + [ + "// GLOBAL: TEST 0x5555 Haha", + "int g_variable = 0;", + "// FUNCTION: TEST 0x1234 Something", + "void Test() { g_variable++; }", + "// LIBRARY: TEST 0x8080 Printf", + "// _printf", + ] + ) + + # We don't use this information (yet) but this is all fine. + assert len(parser.alerts) == 0 + + +def test_virtual_inheritance(parser): + """Indicate the base class for a vtable where the class uses + virtual inheritance.""" + parser.read_lines( + [ + "// VTABLE: HELLO 0x1234", + "// VTABLE: HELLO 0x1238 Greetings", + "// VTABLE: HELLO 0x123c Howdy", + "class HiThere : public virtual Greetings {", + "};", + ] + ) + + assert len(parser.alerts) == 0 + assert len(parser.vtables) == 3 + assert parser.vtables[0].base_class is None + assert parser.vtables[1].base_class == "Greetings" + assert parser.vtables[2].base_class == "Howdy" + assert all(v.name == "HiThere" for v in parser.vtables) + + +def test_namespace_in_comment(parser): + parser.read_lines( + [ + "// VTABLE: HELLO 0x1234", + "// class Tgl::Object", + "// VTABLE: HELLO 0x5555", + "// class TglImpl::RendererImpl", + ] + ) + + assert len(parser.vtables) == 2 + assert parser.vtables[0].name == "Tgl::Object" + assert parser.vtables[1].name == "TglImpl::RendererImpl" diff --git a/tools/isledecomp/tests/test_parser_samples.py b/tools/isledecomp/tests/test_parser_samples.py new file mode 100644 index 00000000..e74fda0e --- /dev/null +++ b/tools/isledecomp/tests/test_parser_samples.py @@ -0,0 +1,141 @@ +import os +from typing import List, TextIO +import pytest +from isledecomp.parser import DecompParser +from isledecomp.parser.node import ParserSymbol + +SAMPLE_DIR = os.path.join(os.path.dirname(__file__), "samples") + + +def sample_file(filename: str) -> TextIO: + """Wrapper for opening the samples from the directory that does not + depend on the cwd where we run the test""" + full_path = os.path.join(SAMPLE_DIR, filename) + return open(full_path, "r", encoding="utf-8") + + +def code_blocks_are_sorted(blocks: List[ParserSymbol]) -> bool: + """Helper to make this more idiomatic""" + just_offsets = [block.offset for block in blocks] + return just_offsets == sorted(just_offsets) + + +@pytest.fixture(name="parser") +def fixture_parser(): + return DecompParser() + + +# Tests are below # + + +def test_sanity(parser): + """Read a very basic file""" + with sample_file("basic_file.cpp") as f: + parser.read_lines(f) + + assert len(parser.functions) == 3 + assert code_blocks_are_sorted(parser.functions) is True + # n.b. The parser returns line numbers as 1-based + # Function starts when we see the opening curly brace + assert parser.functions[0].line_number == 8 + assert parser.functions[0].end_line == 10 + + +def test_oneline(parser): + """(Assuming clang-format permits this) This sample has a function + on a single line. This will test the end-of-function detection""" + with sample_file("oneline_function.cpp") as f: + parser.read_lines(f) + + assert len(parser.functions) == 2 + assert parser.functions[0].line_number == 5 + assert parser.functions[0].end_line == 5 + + +def test_missing_offset(parser): + """What if the function doesn't have an offset comment?""" + with sample_file("missing_offset.cpp") as f: + parser.read_lines(f) + + # TODO: For now, the function without the offset will just be ignored. + # Would be the same outcome if the comment was present but mangled and + # we failed to match it. We should detect these cases in the future. + assert len(parser.functions) == 1 + + +def test_jumbled_case(parser): + """The parser just reports what it sees. It is the responsibility of + the downstream tools to do something about a jumbled file. + Just verify that we are reading it correctly.""" + with sample_file("out_of_order.cpp") as f: + parser.read_lines(f) + + assert len(parser.functions) == 3 + assert code_blocks_are_sorted(parser.functions) is False + + +def test_bad_file(parser): + with sample_file("poorly_formatted.cpp") as f: + parser.read_lines(f) + + assert len(parser.functions) == 3 + + +def test_indented(parser): + """Offsets for functions inside of a class will probably be indented.""" + with sample_file("basic_class.cpp") as f: + parser.read_lines(f) + + # TODO: We don't properly detect the end of these functions + # because the closing brace is indented. However... knowing where each + # function ends is less important (for now) than capturing + # all the functions that are there. + + assert len(parser.functions) == 2 + assert parser.functions[0].offset == int("0x12345678", 16) + assert parser.functions[0].line_number == 16 + # assert parser.functions[0].end_line == 19 + + assert parser.functions[1].offset == int("0xdeadbeef", 16) + assert parser.functions[1].line_number == 23 + # assert parser.functions[1].end_line == 25 + + +def test_inline(parser): + with sample_file("inline.cpp") as f: + parser.read_lines(f) + + assert len(parser.functions) == 2 + for fun in parser.functions: + assert fun.line_number is not None + assert fun.line_number == fun.end_line + + +def test_multiple_offsets(parser): + """If multiple offset marks appear before for a code block, take them + all but ensure module name (case-insensitive) is distinct. + Use first module occurrence in case of duplicates.""" + with sample_file("multiple_offsets.cpp") as f: + parser.read_lines(f) + + assert len(parser.functions) == 4 + assert parser.functions[0].module == "TEST" + assert parser.functions[0].line_number == 9 + + assert parser.functions[1].module == "HELLO" + assert parser.functions[1].line_number == 9 + + # Duplicate modules are ignored + assert parser.functions[2].line_number == 16 + assert parser.functions[2].offset == 0x2345 + + assert parser.functions[3].module == "TEST" + assert parser.functions[3].offset == 0x2002 + + +def test_variables(parser): + with sample_file("global_variables.cpp") as f: + parser.read_lines(f) + + assert len(parser.functions) == 1 + assert len(parser.variables) == 2 diff --git a/tools/isledecomp/tests/test_parser_statechange.py b/tools/isledecomp/tests/test_parser_statechange.py new file mode 100644 index 00000000..be37e3c8 --- /dev/null +++ b/tools/isledecomp/tests/test_parser_statechange.py @@ -0,0 +1,141 @@ +from typing import Optional +import pytest +from isledecomp.parser.parser import ( + ReaderState as _rs, + DecompParser, +) +from isledecomp.parser.error import ParserError as _pe + +# fmt: off +state_change_marker_cases = [ + (_rs.SEARCH, "FUNCTION", _rs.WANT_SIG, None), + (_rs.SEARCH, "GLOBAL", _rs.IN_GLOBAL, None), + (_rs.SEARCH, "STUB", _rs.WANT_SIG, None), + (_rs.SEARCH, "SYNTHETIC", _rs.IN_SYNTHETIC, None), + (_rs.SEARCH, "TEMPLATE", _rs.IN_TEMPLATE, None), + (_rs.SEARCH, "VTABLE", _rs.IN_VTABLE, None), + (_rs.SEARCH, "LIBRARY", _rs.IN_LIBRARY, None), + (_rs.SEARCH, "STRING", _rs.IN_GLOBAL, None), + + (_rs.WANT_SIG, "FUNCTION", _rs.WANT_SIG, None), + (_rs.WANT_SIG, "GLOBAL", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.WANT_SIG, "STUB", _rs.WANT_SIG, None), + (_rs.WANT_SIG, "SYNTHETIC", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.WANT_SIG, "TEMPLATE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.WANT_SIG, "VTABLE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.WANT_SIG, "LIBRARY", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.WANT_SIG, "STRING", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + + (_rs.IN_FUNC, "FUNCTION", _rs.WANT_SIG, _pe.MISSED_END_OF_FUNCTION), + (_rs.IN_FUNC, "GLOBAL", _rs.IN_FUNC_GLOBAL, None), + (_rs.IN_FUNC, "STUB", _rs.WANT_SIG, _pe.MISSED_END_OF_FUNCTION), + (_rs.IN_FUNC, "SYNTHETIC", _rs.IN_SYNTHETIC, _pe.MISSED_END_OF_FUNCTION), + (_rs.IN_FUNC, "TEMPLATE", _rs.IN_TEMPLATE, _pe.MISSED_END_OF_FUNCTION), + (_rs.IN_FUNC, "VTABLE", _rs.IN_VTABLE, _pe.MISSED_END_OF_FUNCTION), + (_rs.IN_FUNC, "LIBRARY", _rs.IN_LIBRARY, _pe.MISSED_END_OF_FUNCTION), + (_rs.IN_FUNC, "STRING", _rs.IN_FUNC_GLOBAL, None), + + (_rs.IN_TEMPLATE, "FUNCTION", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_TEMPLATE, "GLOBAL", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_TEMPLATE, "STUB", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_TEMPLATE, "SYNTHETIC", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_TEMPLATE, "TEMPLATE", _rs.IN_TEMPLATE, None), + (_rs.IN_TEMPLATE, "VTABLE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_TEMPLATE, "LIBRARY", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_TEMPLATE, "STRING", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + + (_rs.WANT_CURLY, "FUNCTION", _rs.SEARCH, _pe.UNEXPECTED_MARKER), + (_rs.WANT_CURLY, "GLOBAL", _rs.SEARCH, _pe.UNEXPECTED_MARKER), + (_rs.WANT_CURLY, "STUB", _rs.SEARCH, _pe.UNEXPECTED_MARKER), + (_rs.WANT_CURLY, "SYNTHETIC", _rs.SEARCH, _pe.UNEXPECTED_MARKER), + (_rs.WANT_CURLY, "TEMPLATE", _rs.SEARCH, _pe.UNEXPECTED_MARKER), + (_rs.WANT_CURLY, "VTABLE", _rs.SEARCH, _pe.UNEXPECTED_MARKER), + (_rs.WANT_CURLY, "LIBRARY", _rs.SEARCH, _pe.UNEXPECTED_MARKER), + (_rs.WANT_CURLY, "STRING", _rs.SEARCH, _pe.UNEXPECTED_MARKER), + + (_rs.IN_GLOBAL, "FUNCTION", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_GLOBAL, "GLOBAL", _rs.IN_GLOBAL, None), + (_rs.IN_GLOBAL, "STUB", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_GLOBAL, "SYNTHETIC", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_GLOBAL, "TEMPLATE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_GLOBAL, "VTABLE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_GLOBAL, "LIBRARY", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_GLOBAL, "STRING", _rs.IN_GLOBAL, None), + + (_rs.IN_FUNC_GLOBAL, "FUNCTION", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_FUNC_GLOBAL, "GLOBAL", _rs.IN_FUNC_GLOBAL, None), + (_rs.IN_FUNC_GLOBAL, "STUB", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_FUNC_GLOBAL, "SYNTHETIC", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_FUNC_GLOBAL, "TEMPLATE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_FUNC_GLOBAL, "VTABLE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_FUNC_GLOBAL, "LIBRARY", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_FUNC_GLOBAL, "STRING", _rs.IN_FUNC_GLOBAL, None), + + (_rs.IN_VTABLE, "FUNCTION", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_VTABLE, "GLOBAL", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_VTABLE, "STUB", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_VTABLE, "SYNTHETIC", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_VTABLE, "TEMPLATE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_VTABLE, "VTABLE", _rs.IN_VTABLE, None), + (_rs.IN_VTABLE, "LIBRARY", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_VTABLE, "STRING", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + + (_rs.IN_SYNTHETIC, "FUNCTION", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_SYNTHETIC, "GLOBAL", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_SYNTHETIC, "STUB", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_SYNTHETIC, "SYNTHETIC", _rs.IN_SYNTHETIC, None), + (_rs.IN_SYNTHETIC, "TEMPLATE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_SYNTHETIC, "VTABLE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_SYNTHETIC, "LIBRARY", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_SYNTHETIC, "STRING", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + + (_rs.IN_LIBRARY, "FUNCTION", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_LIBRARY, "GLOBAL", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_LIBRARY, "STUB", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_LIBRARY, "SYNTHETIC", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_LIBRARY, "TEMPLATE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_LIBRARY, "VTABLE", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), + (_rs.IN_LIBRARY, "LIBRARY", _rs.IN_LIBRARY, None), + (_rs.IN_LIBRARY, "STRING", _rs.SEARCH, _pe.INCOMPATIBLE_MARKER), +] +# fmt: on + + +@pytest.mark.parametrize( + "state, marker_type, new_state, expected_error", state_change_marker_cases +) +def test_state_change_by_marker( + state: _rs, marker_type: str, new_state: _rs, expected_error: Optional[_pe] +): + p = DecompParser() + p.state = state + mock_line = f"// {marker_type}: TEST 0x1234" + p.read_line(mock_line) + assert p.state == new_state + + if expected_error is not None: + assert len(p.alerts) > 0 + assert p.alerts[0].code == expected_error + + +# Reading any of these lines should have no effect in ReaderState.SEARCH +search_lines_no_effect = [ + "", + "\t", + " ", + "int x = 0;", + "// Comment", + "/*", + "*/", + "/* Block comment */", + "{", + "}", +] + + +@pytest.mark.parametrize("line", search_lines_no_effect) +def test_state_search_line(line: str): + p = DecompParser() + p.read_line(line) + assert p.state == _rs.SEARCH + assert len(p.alerts) == 0 diff --git a/tools/isledecomp/tests/test_parser_util.py b/tools/isledecomp/tests/test_parser_util.py new file mode 100644 index 00000000..e60f505c --- /dev/null +++ b/tools/isledecomp/tests/test_parser_util.py @@ -0,0 +1,209 @@ +import pytest +from isledecomp.parser.parser import MarkerDict +from isledecomp.parser.marker import ( + DecompMarker, + MarkerType, + match_marker, + is_marker_exact, +) +from isledecomp.parser.util import ( + is_blank_or_comment, + get_class_name, + get_variable_name, + get_string_contents, +) + + +blank_or_comment_param = [ + (True, ""), + (True, "\t"), + (True, " "), + (False, "\tint abc=123;"), + (True, "// OFFSET: LEGO1 0xdeadbeef"), + (True, " /* Block comment beginning"), + (True, "Block comment ending */ "), + # TODO: does clang-format have anything to say about these cases? + (False, "x++; // Comment folows"), + (False, "x++; /* Block comment begins"), +] + + +@pytest.mark.parametrize("expected, line", blank_or_comment_param) +def test_is_blank_or_comment(line: str, expected: bool): + assert is_blank_or_comment(line) is expected + + +marker_samples = [ + # (can_parse: bool, exact_match: bool, line: str) + (True, True, "// FUNCTION: LEGO1 0xdeadbeef"), + (True, True, "// FUNCTION: ISLE 0x12345678"), + # No trailing spaces allowed + (True, False, "// FUNCTION: LEGO1 0xdeadbeef "), + # Must have exactly one space between elements + (True, False, "//FUNCTION: ISLE 0xdeadbeef"), + (True, False, "// FUNCTION:ISLE 0xdeadbeef"), + (True, False, "// FUNCTION: ISLE 0xdeadbeef"), + (True, False, "// FUNCTION: ISLE 0xdeadbeef"), + (True, False, "// FUNCTION: ISLE 0xdeadbeef"), + # Must have 0x prefix for hex number to match at all + (False, False, "// FUNCTION: ISLE deadbeef"), + # Offset, module name, and STUB must be uppercase + (True, False, "// function: ISLE 0xdeadbeef"), + (True, False, "// function: isle 0xdeadbeef"), + # Hex string must be lowercase + (True, False, "// FUNCTION: ISLE 0xDEADBEEF"), + # TODO: How flexible should we be with matching the module name? + (True, True, "// FUNCTION: OMNI 0x12345678"), + (True, True, "// FUNCTION: LEG01 0x12345678"), + (True, False, "// FUNCTION: hello 0x12345678"), + # Not close enough to match + (False, False, "// FUNCTION: ISLE0x12345678"), + (False, False, "// FUNCTION: 0x12345678"), + (False, False, "// LEGO1: 0x12345678"), + # Hex string shorter than 8 characters + (True, True, "// FUNCTION: LEGO1 0x1234"), + # TODO: These match but shouldn't. + # (False, False, '// FUNCTION: LEGO1 0'), + # (False, False, '// FUNCTION: LEGO1 0x'), + # Extra field + (True, True, "// VTABLE: HELLO 0x1234 Extra"), + # Extra with spaces + (True, True, "// VTABLE: HELLO 0x1234 Whatever"), + # Extra, no space (if the first non-hex character is not in [a-f]) + (True, False, "// VTABLE: HELLO 0x1234Hello"), + # Extra, many spaces + (True, False, "// VTABLE: HELLO 0x1234 Hello"), +] + + +@pytest.mark.parametrize("match, _, line", marker_samples) +def test_marker_match(line: str, match: bool, _): + did_match = match_marker(line) is not None + assert did_match is match + + +@pytest.mark.parametrize("_, exact, line", marker_samples) +def test_marker_exact(line: str, exact: bool, _): + assert is_marker_exact(line) is exact + + +def test_marker_dict_simple(): + d = MarkerDict() + d.insert(DecompMarker("FUNCTION", "TEST", 0x1234)) + markers = list(d.iter()) + assert len(markers) == 1 + + +def test_marker_dict_ofs_replace(): + d = MarkerDict() + d.insert(DecompMarker("FUNCTION", "TEST", 0x1234)) + d.insert(DecompMarker("FUNCTION", "TEST", 0x555)) + markers = list(d.iter()) + assert len(markers) == 1 + assert markers[0].offset == 0x1234 + + +def test_marker_dict_type_replace(): + d = MarkerDict() + d.insert(DecompMarker("FUNCTION", "TEST", 0x1234)) + d.insert(DecompMarker("STUB", "TEST", 0x1234)) + markers = list(d.iter()) + assert len(markers) == 1 + assert markers[0].type == MarkerType.FUNCTION + + +class_name_match_cases = [ + ("struct MxString {", "MxString"), + ("class MxString {", "MxString"), + ("// class MxString", "MxString"), + ("class MxString : public MxCore {", "MxString"), + ("class MxPtrList", "MxPtrList"), + # If it is possible to match the symbol MxList::`vftable' + # we should get the correct class name if possible. If the template type is a pointer, + # the asterisk and class name are separated by one space. + ("// class MxList", "MxList"), + ("// class MxList", "MxList"), + ("// class MxList", "MxList"), + # I don't know if this would ever come up, but sure, why not? + ("// class MxList", "MxList"), + ("// class Many::Name::Spaces", "Many::Name::Spaces"), +] + + +@pytest.mark.parametrize("line, class_name", class_name_match_cases) +def test_get_class_name(line: str, class_name: str): + assert get_class_name(line) == class_name + + +class_name_no_match_cases = [ + "MxString { ", + "clas MxString", + "// MxPtrList::`scalar deleting destructor'", +] + + +@pytest.mark.parametrize("line", class_name_no_match_cases) +def test_get_class_name_none(line: str): + assert get_class_name(line) is None + + +variable_name_cases = [ + # with prefix for easy access + ("char* g_test;", "g_test"), + ("g_test;", "g_test"), + ("void (*g_test)(int);", "g_test"), + ("char g_test[50];", "g_test"), + ("char g_test[50] = {1234,", "g_test"), + ("int g_test = 500;", "g_test"), + # no prefix + ("char* hello;", "hello"), + ("hello;", "hello"), + ("void (*hello)(int);", "hello"), + ("char hello[50];", "hello"), + ("char hello[50] = {1234,", "hello"), + ("int hello = 500;", "hello"), +] + + +@pytest.mark.parametrize("line,name", variable_name_cases) +def test_get_variable_name(line: str, name: str): + assert get_variable_name(line) == name + + +string_match_cases = [ + ('return "hello world";', "hello world"), + ('"hello\\\\"', "hello\\"), + ('"hello \\"world\\""', 'hello "world"'), + ('"hello\\nworld"', "hello\nworld"), + # Only match first string if there are multiple options + ('Method("hello", "world");', "hello"), +] + + +@pytest.mark.parametrize("line, string", string_match_cases) +def test_get_string_contents(line: str, string: str): + assert get_string_contents(line) == string + + +def test_marker_extra_spaces(): + """The extra field can contain spaces""" + marker = match_marker("// VTABLE: TEST 0x1234 S p a c e s") + assert marker.extra == "S p a c e s" + + # Trailing spaces removed + marker = match_marker("// VTABLE: TEST 0x8888 spaces ") + assert marker.extra == "spaces" + + # Trailing newline removed if present + marker = match_marker("// VTABLE: TEST 0x5555 newline\n") + assert marker.extra == "newline" + + +def test_marker_trailing_spaces(): + """Should ignore trailing spaces. (Invalid extra field) + Offset field not truncated, extra field set to None.""" + + marker = match_marker("// VTABLE: TEST 0x1234 ") + assert marker is not None + assert marker.offset == 0x1234 + assert marker.extra is None diff --git a/tools/isledecomp/tests/test_path_resolver_nt.py b/tools/isledecomp/tests/test_path_resolver_nt.py new file mode 100644 index 00000000..93ddfd17 --- /dev/null +++ b/tools/isledecomp/tests/test_path_resolver_nt.py @@ -0,0 +1,32 @@ +from os import name as os_name +import pytest +from isledecomp.dir import PathResolver + + +if os_name != "nt": + pytest.skip(reason="Skip Windows-only tests", allow_module_level=True) + + +@pytest.fixture(name="resolver") +def fixture_resolver_win(): + yield PathResolver("C:\\isle") + + +def test_identity(resolver): + assert resolver.resolve_cvdump("C:\\isle\\test.h") == "C:\\isle\\test.h" + + +def test_outside_basedir(resolver): + assert resolver.resolve_cvdump("C:\\lego\\test.h") == "C:\\lego\\test.h" + + +def test_relative(resolver): + assert resolver.resolve_cvdump(".\\test.h") == "C:\\isle\\test.h" + assert resolver.resolve_cvdump("..\\test.h") == "C:\\test.h" + + +def test_intermediate_relative(resolver): + """These paths may not register as `relative` paths, but we want to + produce a single absolute path for each.""" + assert resolver.resolve_cvdump("C:\\isle\\test\\..\\test.h") == "C:\\isle\\test.h" + assert resolver.resolve_cvdump(".\\subdir\\..\\test.h") == "C:\\isle\\test.h" diff --git a/tools/isledecomp/tests/test_path_resolver_posix.py b/tools/isledecomp/tests/test_path_resolver_posix.py new file mode 100644 index 00000000..af0c89e5 --- /dev/null +++ b/tools/isledecomp/tests/test_path_resolver_posix.py @@ -0,0 +1,69 @@ +from os import name as os_name +from unittest.mock import patch +import pytest +from isledecomp.dir import PathResolver + + +if os_name == "nt": + pytest.skip(reason="Skip Posix-only tests", allow_module_level=True) + + +@pytest.fixture(name="resolver") +def fixture_resolver_posix(): + # Skip the call to winepath by using a patch, although this is not strictly necessary. + with patch("isledecomp.dir.winepath_unix_to_win", return_value="Z:\\usr\\isle"): + yield PathResolver("/usr/isle") + + +@patch("isledecomp.dir.winepath_win_to_unix") +def test_identity(winepath_mock, resolver): + """Test with an absolute Wine path where a path swap is possible.""" + # In this and upcoming tests, patch is_file so we always assume there is + # a file at the given unix path. We want to test the conversion logic only. + with patch("pathlib.Path.is_file", return_value=True): + assert resolver.resolve_cvdump("Z:\\usr\\isle\\test.h") == "/usr/isle/test.h" + winepath_mock.assert_not_called() + + # Without the patch, this should call the winepath_mock, but we have + # memoized the value from the previous run. + assert resolver.resolve_cvdump("Z:\\usr\\isle\\test.h") == "/usr/isle/test.h" + winepath_mock.assert_not_called() + + +@patch("isledecomp.dir.winepath_win_to_unix") +def test_file_does_not_exist(winepath_mock, resolver): + """These test files (probably) don't exist, so we always assume + the path swap failed and defer to winepath.""" + resolver.resolve_cvdump("Z:\\usr\\isle\\test.h") + winepath_mock.assert_called_once_with("Z:\\usr\\isle\\test.h") + + +@patch("isledecomp.dir.winepath_win_to_unix") +def test_outside_basedir(winepath_mock, resolver): + """Test an absolute path where we cannot do a path swap.""" + with patch("pathlib.Path.is_file", return_value=True): + resolver.resolve_cvdump("Z:\\lego\\test.h") + winepath_mock.assert_called_once_with("Z:\\lego\\test.h") + + +@patch("isledecomp.dir.winepath_win_to_unix") +def test_relative(winepath_mock, resolver): + """Test relative paths inside and outside of the base dir.""" + with patch("pathlib.Path.is_file", return_value=True): + assert resolver.resolve_cvdump("./test.h") == "/usr/isle/test.h" + + # This works because we will resolve "/usr/isle/test/../test.h" + assert resolver.resolve_cvdump("../test.h") == "/usr/test.h" + winepath_mock.assert_not_called() + + +@patch("isledecomp.dir.winepath_win_to_unix") +def test_intermediate_relative(winepath_mock, resolver): + """We can resolve intermediate backdirs if they are relative to the basedir.""" + with patch("pathlib.Path.is_file", return_value=True): + assert ( + resolver.resolve_cvdump("Z:\\usr\\isle\\test\\..\\test.h") + == "/usr/isle/test.h" + ) + assert resolver.resolve_cvdump(".\\subdir\\..\\test.h") == "/usr/isle/test.h" + winepath_mock.assert_not_called() diff --git a/tools/isledecomp/tests/test_sanitize.py b/tools/isledecomp/tests/test_sanitize.py new file mode 100644 index 00000000..deb3c825 --- /dev/null +++ b/tools/isledecomp/tests/test_sanitize.py @@ -0,0 +1,296 @@ +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" + + +def test_jmp_table(): + """To ignore cases where it would be inappropriate to replace pointer + displacement (i.e. the vast majority of them) we require the address + to be relocated. This excludes any address less than the imagebase.""" + p = ParseAsm() + inst = mock_inst("jmp", "dword ptr [eax*4 + 0x5555]") + (_, op_str) = p.sanitize(inst) + # i.e. no change + assert op_str == "dword ptr [eax*4 + 0x5555]" + + def relocate_lookup(addr: int) -> bool: + return addr == 0x5555 + + # Now add the relocation lookup + p = ParseAsm(relocate_lookup=relocate_lookup) + (_, op_str) = p.sanitize(inst) + # Should replace it now + 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, __: bool) -> 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, _: bool) -> 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, _: bool) -> 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" + + +@pytest.mark.skip(reason="changed implementation") +def test_float_replacement(): + """Floating point constants often appear as pointers to data. + A good example is ViewROI::IntrinsicImportance and the subclass override + LegoROI::IntrinsicImportance. Both return 0.5, but this is done via the + FLD instruction and a dword value at 0x100dbdec. In this case it is more + valuable to just read the constant value rather than use a placeholder. + The float constants don't appear to be deduplicated (like strings are) + because there is another 0.5 at 0x100d40b0.""" + + def bin_lookup(addr: int, _: int) -> Optional[bytes]: + return b"\xdb\x0f\x49\x40" if addr == 0x1234 else None + + p = ParseAsm(bin_lookup=bin_lookup) + inst = DisasmLiteInst(0x1000, 6, "fld", "dword ptr [0x1234]") + (_, op_str) = p.sanitize(inst) + # Single-precision float. struct.unpack(" Optional[str]: + return "g_myFloatVariable" if addr == 0x1234 else None + + p = ParseAsm(name_lookup=name_lookup) + inst = DisasmLiteInst(0x1000, 6, "fld", "dword ptr [0x1234]") + (_, op_str) = p.sanitize(inst) + assert op_str == "dword ptr [g_myFloatVariable]" + + +def test_pointer_compare(): + """A loop on an array could get optimized into comparing on the address + that immediately follows the array. This may or may not be a valid address + and it may or may not be annotated. To avoid a situation where an + erroneous address value would get replaced with a placeholder and silently + pass the comparison check, we will only replace an immediate value on the + CMP instruction if it is a known address.""" + + # 0x1234 and 0x5555 are relocated and so are considered to be addresses. + def relocate_lookup(addr: int) -> bool: + return addr in (0x1234, 0x5555) + + # Only 0x5555 is a "known" address + def name_lookup(addr: int, _: bool) -> Optional[str]: + return "hello" if addr == 0x5555 else None + + p = ParseAsm(relocate_lookup=relocate_lookup, name_lookup=name_lookup) + + # Will always replace on MOV instruction + (_, 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, hello" + + # n.b. We have already cached the replacement for 0x1234, but the + # special handling for CMP should skip the cache and not use it. + + # Do not replace here + (_, op_str) = p.sanitize(mock_inst("cmp", "eax, 0x1234")) + assert op_str == "eax, 0x1234" + # Should replace here + (_, op_str) = p.sanitize(mock_inst("cmp", "eax, 0x5555")) + assert op_str == "eax, hello" + + +def test_absolute_indirect(): + """The instruction `call dword ptr [0x1234]` means we call the function + whose address is at 0x1234. (i.e. absolute indirect addressing mode) + It is probably more useful to show the name of the function itself if + we have it, but there are some circumstances where we want to replace + with the pointer's name (i.e. an import function).""" + + def name_lookup(addr: int, _: bool) -> Optional[str]: + return { + 0x1234: "Hello", + 0x4321: "xyz", + 0x5555: "Test", + }.get(addr) + + def bin_lookup(addr: int, _: int) -> Optional[bytes]: + return ( + { + 0x1234: b"\x55\x55\x00\x00", + 0x4321: b"\x99\x99\x00\x00", + } + ).get(addr) + + p = ParseAsm(name_lookup=name_lookup, bin_lookup=bin_lookup) + + # If we know the indirect address (0x5555) + # Arrow to indicate this is an indirect replacement + (_, op_str) = p.sanitize(mock_inst("call", "dword ptr [0x1234]")) + assert op_str == "dword ptr [->Test]" + + # If we do not know the indirect address (0x9999) + (_, op_str) = p.sanitize(mock_inst("call", "dword ptr [0x4321]")) + assert op_str == "dword ptr [xyz]" + + # If we can't read the indirect address + (_, op_str) = p.sanitize(mock_inst("call", "dword ptr [0x5555]")) + assert op_str == "dword ptr [Test]" diff --git a/tools/reccmp/config.png b/tools/reccmp/config.png new file mode 100644 index 00000000..1f2050ad Binary files /dev/null and b/tools/reccmp/config.png differ diff --git a/tools/reccmp/isle.png b/tools/reccmp/isle.png new file mode 100644 index 00000000..cfa26a23 Binary files /dev/null and b/tools/reccmp/isle.png differ diff --git a/tools/reccmp/lego1.png b/tools/reccmp/lego1.png new file mode 100644 index 00000000..f76a54da Binary files /dev/null and b/tools/reccmp/lego1.png differ diff --git a/tools/reccmp/reccmp.js b/tools/reccmp/reccmp.js new file mode 100644 index 00000000..4a8e6595 --- /dev/null +++ b/tools/reccmp/reccmp.js @@ -0,0 +1,867 @@ +// reccmp.js +/* global data */ + +// Unwrap array of functions into a dictionary with address as the key. +const dataDict = Object.fromEntries(data.map(row => [row.address, row])); + +function getDataByAddr(addr) { + return dataDict[addr]; +} + +// +// Pure functions +// + +function formatAsm(entries, addrOption) { + const output = []; + + const createTh = (text) => { + const th = document.createElement('th'); + th.innerText = text; + return th; + }; + + const createTd = (text, className = '') => { + const td = document.createElement('td'); + td.innerText = text; + td.className = className; + return td; + }; + + entries.forEach(obj => { + // These won't all be present. You get "both" for an equal node + // and orig/recomp for a diff. + const { both = [], orig = [], recomp = [] } = obj; + + output.push(...both.map(([addr, line, recompAddr]) => { + const tr = document.createElement('tr'); + tr.appendChild(createTh(addr)); + tr.appendChild(createTh(recompAddr)); + tr.appendChild(createTd(line)); + return tr; + })); + + output.push(...orig.map(([addr, line]) => { + const tr = document.createElement('tr'); + tr.appendChild(createTh(addr)); + tr.appendChild(createTh('')); + tr.appendChild(createTd(`-${line}`, 'diffneg')); + return tr; + })); + + output.push(...recomp.map(([addr, line]) => { + const tr = document.createElement('tr'); + tr.appendChild(createTh('')); + tr.appendChild(createTh(addr)); + tr.appendChild(createTd(`+${line}`, 'diffpos')); + return tr; + })); + }); + + return output; +} + +// Special internal values to ensure this sort order for matching column: +// 1. Stub +// 2. Any match percentage [0.0, 1.0) +// 3. Effective match +// 4. Actual 100% match +function matchingColAdjustment(row) { + if ('stub' in row) { + return -1; + } + + if ('effective' in row) { + return 1.0; + } + + if (row.matching === 1.0) { + return 1000; + } + + return row.matching; +} + +function getCppClass(str) { + const idx = str.indexOf('::'); + if (idx !== -1) { + return str.slice(0, idx); + } + + return str; +} + +// Clamp string length to specified length and pad with ellipsis +function stringTruncate(str, maxlen = 20) { + str = getCppClass(str); + if (str.length > maxlen) { + return `${str.slice(0, maxlen)}...`; + } + + return str; +} + +function getMatchPercentText(row) { + if ('stub' in row) { + return 'stub'; + } + + if ('effective' in row) { + return '100.00%*'; + } + + return (row.matching * 100).toFixed(2) + '%'; +} + +function countDiffs(row) { + const { diff = '' } = row; + if (diff === '') { + return ''; + } + + const diffs = diff.map(([slug, subgroups]) => subgroups).flat(); + const diffLength = diffs.filter(d => !('both' in d)).length; + const diffWord = diffLength === 1 ? 'diff' : 'diffs'; + return diffLength === 0 ? '' : `${diffLength} ${diffWord}`; +} + +// Helper for this set/remove attribute block +function setBooleanAttribute(element, attribute, value) { + if (value) { + element.setAttribute(attribute, ''); + } else { + element.removeAttribute(attribute); + } +} + +function copyToClipboard(value) { + navigator.clipboard.writeText(value); +} + +const PAGE_SIZE = 200; + +// +// Global state +// + +class ListingState { + constructor() { + this._query = ''; + this._sortCol = 'address'; + this._filterType = 1; + this._sortDesc = false; + this._hidePerfect = false; + this._hideStub = false; + this._showRecomp = false; + this._expanded = {}; + this._page = 0; + + this._listeners = []; + + this._results = []; + this.updateResults(); + } + + addListener(fn) { + this._listeners.push(fn); + } + + callListeners() { + for (const fn of this._listeners) { + fn(); + } + } + + isExpanded(addr) { + return addr in this._expanded; + } + + toggleExpanded(addr) { + this.setExpanded(addr, !this.isExpanded(addr)); + } + + setExpanded(addr, value) { + if (value) { + this._expanded[addr] = true; + } else { + delete this._expanded[addr]; + } + } + + updateResults() { + const filterFn = this.rowFilterFn.bind(this); + const sortFn = this.rowSortFn.bind(this); + + this._results = data.filter(filterFn).sort(sortFn); + + // Set _page directly to avoid double call to listeners. + this._page = this.pageClamp(this.page); + this.callListeners(); + } + + pageSlice() { + return this._results.slice(this.page * PAGE_SIZE, (this.page + 1) * PAGE_SIZE); + } + + resultsCount() { + return this._results.length; + } + + pageCount() { + return Math.ceil(this._results.length / PAGE_SIZE); + } + + maxPage() { + return Math.max(0, this.pageCount() - 1); + } + + // A list showing the range of each page based on the sort column and direction. + pageHeadings() { + if (this._results.length === 0) { + return []; + } + + const headings = []; + + for (let i = 0; i < this.pageCount(); i++) { + const startIdx = i * PAGE_SIZE; + const endIdx = Math.min(this._results.length, ((i + 1) * PAGE_SIZE)) - 1; + + let start = this._results[startIdx][this.sortCol]; + let end = this._results[endIdx][this.sortCol]; + + if (this.sortCol === 'matching') { + start = getMatchPercentText(this._results[startIdx]); + end = getMatchPercentText(this._results[endIdx]); + } + + headings.push([i, stringTruncate(start), stringTruncate(end)]); + } + + return headings; + } + + rowFilterFn(row) { + // Destructuring sets defaults for optional values from this object. + const { + effective = false, + stub = false, + diff = '', + name, + address, + matching + } = row; + + if (this.hidePerfect && (effective || matching >= 1)) { + return false; + } + + if (this.hideStub && stub) { + return false; + } + + if (this.query === '') { + return true; + } + + // Name/addr search + if (this.filterType === 1) { + return ( + address.includes(this.query) || + name.toLowerCase().includes(this.query) + ); + } + + // no diff for review. + if (diff === '') { + return false; + } + + // special matcher for combined diff + const anyLineMatch = ([addr, line]) => line.toLowerCase().trim().includes(this.query); + + // Flatten all diff groups for the search + const diffs = diff.map(([slug, subgroups]) => subgroups).flat(); + for (const subgroup of diffs) { + const { both = [], orig = [], recomp = [] } = subgroup; + + // If search includes context + if (this.filterType === 2 && both.some(anyLineMatch)) { + return true; + } + + if (orig.some(anyLineMatch) || recomp.some(anyLineMatch)) { + return true; + } + } + + return false; + } + + rowSortFn(rowA, rowB) { + const valA = this.sortCol === 'matching' + ? matchingColAdjustment(rowA) + : rowA[this.sortCol]; + + const valB = this.sortCol === 'matching' + ? matchingColAdjustment(rowB) + : rowB[this.sortCol]; + + if (valA > valB) { + return this.sortDesc ? -1 : 1; + } else if (valA < valB) { + return this.sortDesc ? 1 : -1; + } + + return 0; + } + + pageClamp(page) { + return Math.max(0, Math.min(page, this.maxPage())); + } + + get page() { + return this._page; + } + + set page(page) { + this._page = this.pageClamp(page); + this.callListeners(); + } + + get filterType() { + return parseInt(this._filterType); + } + + set filterType(value) { + value = parseInt(value); + if (value >= 1 && value <= 3) { + this._filterType = value; + } + this.updateResults(); + } + + get query() { + return this._query; + } + + set query(value) { + // Normalize search string + this._query = value.toLowerCase().trim(); + this.updateResults(); + } + + get showRecomp() { + return this._showRecomp; + } + + set showRecomp(value) { + // Don't sort by the recomp column we are about to hide + if (!value && this.sortCol === 'recomp') { + this._sortCol = 'address'; + } + + this._showRecomp = value; + this.callListeners(); + } + + get sortCol() { + return this._sortCol; + } + + set sortCol(column) { + if (column === this._sortCol) { + this._sortDesc = !this._sortDesc; + } else { + this._sortCol = column; + } + + this.updateResults(); + } + + get sortDesc() { + return this._sortDesc; + } + + set sortDesc(value) { + this._sortDesc = value; + this.updateResults(); + } + + get hidePerfect() { + return this._hidePerfect; + } + + set hidePerfect(value) { + this._hidePerfect = value; + this.updateResults(); + } + + get hideStub() { + return this._hideStub; + } + + set hideStub(value) { + this._hideStub = value; + this.updateResults(); + } +} + +const appState = new ListingState(); + +// +// Custom elements +// + +// Sets sort indicator arrow based on element attributes. +class SortIndicator extends window.HTMLElement { + static observedAttributes = ['data-sort']; + + attributeChangedCallback(name, oldValue, newValue) { + if (newValue === null) { + // Reserve space for blank indicator so column width stays the same + this.innerHTML = ' '; + } else { + this.innerHTML = newValue === 'asc' ? '▲' : '▼'; + } + } +} + +class FuncRow extends window.HTMLElement { + connectedCallback() { + if (this.shadowRoot !== null) { + return; + } + + const template = document.querySelector('template#funcrow-template').content; + const shadow = this.attachShadow({ mode: 'open' }); + shadow.appendChild(template.cloneNode(true)); + shadow.querySelector(':host > div[data-col="name"]').addEventListener('click', evt => { + this.dispatchEvent(new Event('name-click')); + }); + } + + get address() { + return this.getAttribute('data-address'); + } +} + +class NoDiffMessage extends window.HTMLElement { + connectedCallback() { + if (this.shadowRoot !== null) { + return; + } + + const template = document.querySelector('template#nodiff-template').content; + const shadow = this.attachShadow({ mode: 'open' }); + shadow.appendChild(template.cloneNode(true)); + } +} + +class CanCopy extends window.HTMLElement { + connectedCallback() { + if (this.shadowRoot !== null) { + return; + } + + const template = document.querySelector('template#can-copy-template').content; + const shadow = this.attachShadow({ mode: 'open' }); + shadow.appendChild(template.cloneNode(true)); + + const el = shadow.querySelector('slot').assignedNodes()[0]; + el.addEventListener('mouseout', evt => { this.copied = false; }); + el.addEventListener('click', evt => { + copyToClipboard(evt.target.textContent); + this.copied = true; + }); + } + + get copied() { + return this.getAttribute('copied'); + } + + set copied(value) { + if (value) { + setTimeout(() => { this.copied = false; }, 2000); + } + setBooleanAttribute(this, 'copied', value); + } +} + +// Displays asm diff for the given @data-address value. +class DiffRow extends window.HTMLElement { + connectedCallback() { + if (this.shadowRoot !== null) { + return; + } + + const template = document.querySelector('template#diffrow-template').content; + const shadow = this.attachShadow({ mode: 'open' }); + shadow.appendChild(template.cloneNode(true)); + } + + get address() { + return this.getAttribute('data-address'); + } + + set address(value) { + this.setAttribute('data-address', value); + } +} + +class DiffDisplayOptions extends window.HTMLElement { + static observedAttributes = ['data-option']; + + connectedCallback() { + if (this.shadowRoot !== null) { + return; + } + + const shadow = this.attachShadow({ mode: 'open' }); + shadow.innerHTML = ` + +
+ Address display: + + + + + + +
`; + + shadow.querySelectorAll('input[type=radio]').forEach(radio => { + const checked = this.option === radio.getAttribute('value'); + setBooleanAttribute(radio, 'checked', checked); + + radio.addEventListener('change', evt => (this.option = evt.target.value)); + }); + } + + set option(value) { + this.setAttribute('data-option', parseInt(value)); + } + + get option() { + return this.getAttribute('data-option') ?? 1; + } + + attributeChangedCallback(name, oldValue, newValue) { + if (name !== 'data-option') { + return; + } + + this.dispatchEvent(new Event('change')); + } +} + +class DiffDisplay extends window.HTMLElement { + static observedAttributes = ['data-option']; + + connectedCallback() { + if (this.querySelector('diff-display-options') !== null) { + return; + } + + const optControl = new DiffDisplayOptions(); + optControl.option = this.option; + optControl.addEventListener('change', evt => (this.option = evt.target.option)); + this.appendChild(optControl); + + const div = document.createElement('div'); + const obj = getDataByAddr(this.address); + + const createHeaderLine = (text, className) => { + const div = document.createElement('div'); + div.textContent = text; + div.className = className; + return div; + }; + + const groups = obj.diff; + groups.forEach(([slug, subgroups]) => { + const secondTable = document.createElement('table'); + secondTable.classList.add('diffTable'); + + const hdr = document.createElement('div'); + hdr.appendChild(createHeaderLine('---', 'diffneg')); + hdr.appendChild(createHeaderLine('+++', 'diffpos')); + hdr.appendChild(createHeaderLine(slug, 'diffslug')); + div.appendChild(hdr); + + const tbody = document.createElement('tbody'); + secondTable.appendChild(tbody); + + const diffs = formatAsm(subgroups, this.option); + for (const el of diffs) { + tbody.appendChild(el); + } + + div.appendChild(secondTable); + }); + + this.appendChild(div); + } + + get address() { + return this.getAttribute('data-address'); + } + + set address(value) { + this.setAttribute('data-address', value); + } + + get option() { + return this.getAttribute('data-option') ?? 1; + } + + set option(value) { + this.setAttribute('data-option', value); + } +} + +class ListingOptions extends window.HTMLElement { + constructor() { + super(); + + // Register to receive updates + appState.addListener(() => this.onUpdate()); + + const input = this.querySelector('input[type=search]'); + input.oninput = evt => (appState.query = evt.target.value); + + const hidePerf = this.querySelector('input#cbHidePerfect'); + hidePerf.onchange = evt => (appState.hidePerfect = evt.target.checked); + hidePerf.checked = appState.hidePerfect; + + const hideStub = this.querySelector('input#cbHideStub'); + hideStub.onchange = evt => (appState.hideStub = evt.target.checked); + hideStub.checked = appState.hideStub; + + const showRecomp = this.querySelector('input#cbShowRecomp'); + showRecomp.onchange = evt => (appState.showRecomp = evt.target.checked); + showRecomp.checked = appState.showRecomp; + + this.querySelector('button#pagePrev').addEventListener('click', evt => { + appState.page = appState.page - 1; + }); + + this.querySelector('button#pageNext').addEventListener('click', evt => { + appState.page = appState.page + 1; + }); + + this.querySelector('select#pageSelect').addEventListener('change', evt => { + appState.page = evt.target.value; + }); + + this.querySelectorAll('input[name=filterType]').forEach(radio => { + const checked = appState.filterType === parseInt(radio.getAttribute('value')); + setBooleanAttribute(radio, 'checked', checked); + + radio.onchange = evt => (appState.filterType = radio.getAttribute('value')); + }); + + this.onUpdate(); + } + + onUpdate() { + // Update input placeholder based on search type + this.querySelector('input[type=search]').placeholder = appState.filterType === 1 + ? 'Search for offset or function name...' + : 'Search for instruction...'; + + // Update page number and max page + this.querySelector('fieldset#pageDisplay > legend').textContent = `Page ${appState.page + 1} of ${Math.max(1, appState.pageCount())}`; + + // Disable prev/next buttons on first/last page + setBooleanAttribute(this.querySelector('button#pagePrev'), 'disabled', appState.page === 0); + setBooleanAttribute(this.querySelector('button#pageNext'), 'disabled', appState.page === appState.maxPage()); + + // Update page select dropdown + const pageSelect = this.querySelector('select#pageSelect'); + setBooleanAttribute(pageSelect, 'disabled', appState.resultsCount() === 0); + pageSelect.innerHTML = ''; + + if (appState.resultsCount() === 0) { + const opt = document.createElement('option'); + opt.textContent = '- no results -'; + pageSelect.appendChild(opt); + } else { + for (const row of appState.pageHeadings()) { + const opt = document.createElement('option'); + opt.value = row[0]; + if (appState.page === row[0]) { + opt.setAttribute('selected', ''); + } + + const [start, end] = [row[1], row[2]]; + + opt.textContent = `${appState.sortCol}: ${start} to ${end}`; + pageSelect.appendChild(opt); + } + } + + // Update row count + this.querySelector('#rowcount').textContent = `${appState.resultsCount()}`; + } +} + +// Main application. +class ListingTable extends window.HTMLElement { + constructor() { + super(); + + // Register to receive updates + appState.addListener(() => this.somethingChanged()); + } + + setDiffRow(address, shouldExpand) { + const tbody = this.querySelector('tbody'); + const funcrow = tbody.querySelector(`func-row[data-address="${address}"]`); + if (funcrow === null) { + return; + } + + const existing = tbody.querySelector(`diff-row[data-address="${address}"]`); + if (existing !== null) { + if (!shouldExpand) { + tbody.removeChild(existing); + } + + return; + } + + const diffrow = document.createElement('diff-row'); + diffrow.address = address; + + // Decide what goes inside the diff row. + const obj = getDataByAddr(address); + + if ('stub' in obj) { + const msg = document.createElement('no-diff'); + const p = document.createElement('div'); + p.innerText = 'Stub. No diff.'; + msg.appendChild(p); + diffrow.appendChild(msg); + } else if (obj.diff.length === 0) { + const msg = document.createElement('no-diff'); + const p = document.createElement('div'); + p.innerText = 'Identical function - no diff'; + msg.appendChild(p); + diffrow.appendChild(msg); + } else { + const dd = new DiffDisplay(); + dd.option = '1'; + dd.address = address; + diffrow.appendChild(dd); + } + + // Insert the diff row after the parent func row. + tbody.insertBefore(diffrow, funcrow.nextSibling); + } + + connectedCallback() { + const thead = this.querySelector('thead'); + const headers = thead.querySelectorAll('th:not([data-no-sort])'); // TODO + headers.forEach(th => { + const col = th.getAttribute('data-col'); + if (col) { + const span = th.querySelector('span'); + if (span) { + span.addEventListener('click', evt => { appState.sortCol = col; }); + } + } + }); + + this.somethingChanged(); + } + + somethingChanged() { + // Toggle recomp/diffs column + setBooleanAttribute(this.querySelector('table'), 'show-recomp', appState.showRecomp); + this.querySelectorAll('func-row[data-address]').forEach(row => { + setBooleanAttribute(row, 'show-recomp', appState.showRecomp); + }); + + const thead = this.querySelector('thead'); + const headers = thead.querySelectorAll('th'); + + // Update sort indicator + headers.forEach(th => { + const col = th.getAttribute('data-col'); + const indicator = th.querySelector('sort-indicator'); + if (indicator === null) { + return; + } + + if (appState.sortCol === col) { + indicator.setAttribute('data-sort', appState.sortDesc ? 'desc' : 'asc'); + } else { + indicator.removeAttribute('data-sort'); + } + }); + + // Add the rows + const tbody = this.querySelector('tbody'); + tbody.innerHTML = ''; // ? + + for (const obj of appState.pageSlice()) { + const row = document.createElement('func-row'); + row.setAttribute('data-address', obj.address); // ? + row.addEventListener('name-click', evt => { + appState.toggleExpanded(obj.address); + this.setDiffRow(obj.address, appState.isExpanded(obj.address)); + }); + setBooleanAttribute(row, 'show-recomp', appState.showRecomp); + setBooleanAttribute(row, 'expanded', appState.isExpanded(row)); + + const items = [ + ['address', obj.address], + ['recomp', obj.recomp], + ['name', obj.name], + ['diffs', countDiffs(obj)], + ['matching', getMatchPercentText(obj)] + ]; + + items.forEach(([slotName, content]) => { + const div = document.createElement('span'); + div.setAttribute('slot', slotName); + div.innerText = content; + row.appendChild(div); + }); + + tbody.appendChild(row); + + if (appState.isExpanded(obj.address)) { + this.setDiffRow(obj.address, true); + } + } + } +} + +window.onload = () => { + window.customElements.define('listing-table', ListingTable); + window.customElements.define('listing-options', ListingOptions); + window.customElements.define('diff-display', DiffDisplay); + window.customElements.define('diff-display-options', DiffDisplayOptions); + window.customElements.define('sort-indicator', SortIndicator); + window.customElements.define('func-row', FuncRow); + window.customElements.define('diff-row', DiffRow); + window.customElements.define('no-diff', NoDiffMessage); + window.customElements.define('can-copy', CanCopy); +}; diff --git a/tools/reccmp/reccmp.py b/tools/reccmp/reccmp.py new file mode 100644 index 00000000..bcdb0899 --- /dev/null +++ b/tools/reccmp/reccmp.py @@ -0,0 +1,344 @@ +#!/usr/bin/env python3 + +import argparse +import base64 +import json +import logging +import os +from datetime import datetime + +from isledecomp import ( + Bin, + get_file_in_script_dir, + print_combined_diff, + diff_json, + percent_string, +) +from isledecomp.compare import Compare as IsleCompare +from isledecomp.types import SymbolType +from pystache import Renderer +import colorama + +colorama.just_fix_windows_console() + + +def gen_json(json_file: str, orig_file: str, data): + """Create a JSON file that contains the comparison summary""" + + # If the structure of the JSON file ever changes, we would run into a problem + # reading an older format file in the CI action. Mark which version we are + # generating so we could potentially address this down the road. + json_format_version = 1 + + # Remove the diff field + reduced_data = [ + {key: value for (key, value) in obj.items() if key != "diff"} for obj in data + ] + + with open(json_file, "w", encoding="utf-8") as f: + json.dump( + { + "file": os.path.basename(orig_file).lower(), + "format": json_format_version, + "timestamp": datetime.now().timestamp(), + "data": reduced_data, + }, + f, + ) + + +def gen_html(html_file, data): + js_path = get_file_in_script_dir("reccmp.js") + with open(js_path, "r", encoding="utf-8") as f: + reccmp_js = f.read() + + output_data = Renderer().render_path( + get_file_in_script_dir("template.html"), {"data": data, "reccmp_js": reccmp_js} + ) + + with open(html_file, "w", encoding="utf-8") as htmlfile: + htmlfile.write(output_data) + + +def gen_svg(svg_file, name_svg, icon, svg_implemented_funcs, total_funcs, raw_accuracy): + icon_data = None + if icon: + with open(icon, "rb") as iconfile: + icon_data = base64.b64encode(iconfile.read()).decode("utf-8") + + total_statistic = raw_accuracy / total_funcs + full_percentbar_width = 127.18422 + output_data = Renderer().render_path( + get_file_in_script_dir("template.svg"), + { + "name": name_svg, + "icon": icon_data, + "implemented": f"{(svg_implemented_funcs / total_funcs * 100):.2f}% ({svg_implemented_funcs}/{total_funcs})", + "accuracy": f"{(raw_accuracy / svg_implemented_funcs * 100):.2f}%", + "progbar": total_statistic * full_percentbar_width, + "percent": f"{(total_statistic * 100):.2f}%", + }, + ) + with open(svg_file, "w", encoding="utf-8") as svgfile: + svgfile.write(output_data) + + +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.is_stub: + print(f"{addrs}: {match.name} is a stub. No diff.") + return + + 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_combined_diff(match.udiff, is_plain, show_both_addrs) + + 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) + + if match.is_stub: + print(f" {match.name} ({addrs}) is a stub.") + else: + 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.", + ) + parser.add_argument( + "original", metavar="original-binary", help="The original binary" + ) + parser.add_argument( + "recompiled", metavar="recompiled-binary", help="The recompiled binary" + ) + parser.add_argument( + "pdb", metavar="recompiled-pdb", help="The PDB of the recompiled binary" + ) + parser.add_argument( + "decomp_dir", metavar="decomp-dir", help="The decompiled source tree" + ) + parser.add_argument( + "--total", + "-T", + metavar="", + help="Total number of expected functions (improves total accuracy statistic)", + ) + parser.add_argument( + "--verbose", + "-v", + metavar="", + type=virtual_address, + help="Print assembly diff for specific function (original file's offset)", + ) + parser.add_argument( + "--json", + metavar="", + help="Generate JSON file with match summary", + ) + parser.add_argument( + "--diff", + metavar="", + help="Diff against summary in JSON file", + ) + parser.add_argument( + "--html", + "-H", + metavar="", + help="Generate searchable HTML summary of status and diffs", + ) + parser.add_argument( + "--no-color", "-n", action="store_true", help="Do not color the output" + ) + parser.add_argument( + "--svg", "-S", metavar="", help="Generate SVG graphic of progress" + ) + parser.add_argument("--svg-icon", metavar="icon", help="Icon to use in SVG (PNG)") + parser.add_argument( + "--print-rec-addr", + action="store_true", + help="Print addresses of recompiled functions too", + ) + parser.add_argument( + "--silent", + action="store_true", + help="Don't display text summary of matches", + ) + + parser.set_defaults(loglevel=logging.INFO) + parser.add_argument( + "--debug", + action="store_const", + const=logging.DEBUG, + dest="loglevel", + help="Print script debug information", + ) + + 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") + + 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, args.pdb, args.decomp_dir) + + if args.loglevel == logging.DEBUG: + isle_compare.debug = True + + print() + + ### Compare one or none. + + if args.verbose is not None: + match = isle_compare.compare_address(args.verbose) + if match is None: + print(f"Failed to find a match at 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 = [] + + for match in isle_compare.compare_all(): + if not args.silent and args.diff is None: + print_match_oneline( + match, show_both_addrs=args.print_rec_addr, is_plain=args.no_color + ) + + if match.match_type == SymbolType.FUNCTION and not match.is_stub: + function_count += 1 + total_accuracy += match.ratio + total_effective_accuracy += match.effective_ratio + + # If html, record the diffs to an HTML file + html_obj = { + "address": f"0x{match.orig_addr:x}", + "recomp": f"0x{match.recomp_addr:x}", + "name": match.name, + "matching": match.effective_ratio, + } + + if match.is_effective_match: + html_obj["effective"] = True + + if match.udiff is not None: + html_obj["diff"] = match.udiff + + if match.is_stub: + html_obj["stub"] = True + + htmlinsert.append(html_obj) + + # Compare with saved diff report. + if args.diff is not None: + with open(args.diff, "r", encoding="utf-8") as f: + saved_data = json.load(f) + + diff_json( + saved_data, + htmlinsert, + args.original, + show_both_addrs=args.print_rec_addr, + is_plain=args.no_color, + ) + + ## Generate files and show summary. + + if args.json is not None: + gen_json(args.json, args.original, htmlinsert) + + 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__": + raise SystemExit(main()) diff --git a/tools/reccmp/template.html b/tools/reccmp/template.html new file mode 100644 index 00000000..3ac15b45 --- /dev/null +++ b/tools/reccmp/template.html @@ -0,0 +1,365 @@ + + + + Decompilation Status + + + + + + +
+

Decompilation Status

+ + +
+
+ Options: + + + + + + +
+
+ Search filters on: + + + + + + +
+
+
+

Results:

+
+ Page + + + +
+
+
+ + + + + + + + + + + + + +
+
+ Address + +
+
+
+ Recomp + +
+
+
+ Name + +
+
+
+ + Matching +
+
+
+
+ + + + + + diff --git a/tools/reccmp/template.svg b/tools/reccmp/template.svg new file mode 100644 index 00000000..fad7ba10 --- /dev/null +++ b/tools/reccmp/template.svg @@ -0,0 +1,119 @@ + + + + + + + + {{name}}{{percent}}{{percent}}Implemented: {{implemented}}Accuracy: {{accuracy}} diff --git a/tools/roadmap/roadmap.py b/tools/roadmap/roadmap.py new file mode 100644 index 00000000..b7294a95 --- /dev/null +++ b/tools/roadmap/roadmap.py @@ -0,0 +1,494 @@ +"""For all addresses matched by code annotations or recomp pdb, +report how "far off" the recomp symbol is from its proper place +in the original binary.""" + +import os +import argparse +import logging +import statistics +import bisect +from typing import Iterator, List, Optional, Tuple +from collections import namedtuple +from isledecomp import Bin as IsleBin +from isledecomp.bin import InvalidVirtualAddressError +from isledecomp.cvdump import Cvdump +from isledecomp.compare import Compare as IsleCompare +from isledecomp.types import SymbolType + +# Ignore all compare-db messages. +logging.getLogger("isledecomp.compare").addHandler(logging.NullHandler()) + + +def or_blank(value) -> str: + """Helper for dealing with potential None values in text output.""" + return "" if value is None else str(value) + + +class ModuleMap: + """Load a subset of sections from the pdb to allow you to look up the + module number based on the recomp address.""" + + def __init__(self, pdb, binfile) -> None: + cvdump = Cvdump(pdb).section_contributions().modules().run() + self.module_lookup = {m.id: (m.lib, m.obj) for m in cvdump.modules} + self.library_lookup = {m.obj: m.lib for m in cvdump.modules} + self.section_contrib = [ + ( + binfile.get_abs_addr(sizeref.section, sizeref.offset), + sizeref.size, + sizeref.module, + ) + for sizeref in cvdump.sizerefs + if binfile.is_valid_section(sizeref.section) + ] + + # For bisect performance enhancement + self.contrib_starts = [start for (start, _, __) in self.section_contrib] + + def get_lib_for_module(self, module: str) -> Optional[str]: + return self.library_lookup.get(module) + + def get_all_cmake_modules(self) -> List[str]: + return [ + obj + for (_, (__, obj)) in self.module_lookup.items() + if obj.startswith("CMakeFiles") + ] + + def get_module(self, addr: int) -> Optional[str]: + i = bisect.bisect_left(self.contrib_starts, addr) + # If the addr matches the section contribution start, we are in the + # right spot. Otherwise, we need to subtract one here. + # We don't want the insertion point given by bisect, but the + # section contribution that contains the address. + + (potential_start, _, __) = self.section_contrib[i] + if potential_start != addr: + i -= 1 + + # Safety catch: clamp to range of indices from section_contrib. + i = max(0, min(i, len(self.section_contrib) - 1)) + + (start, size, module_id) = self.section_contrib[i] + if start <= addr < start + size: + if (module := self.module_lookup.get(module_id)) is not None: + return module + + return None + + +def print_sections(sections): + print(" name | start | v.size | raw size") + print("---------|----------|----------|----------") + for sect in sections: + name = sect.name + print( + f"{name:>8} | {sect.virtual_address:8x} | {sect.virtual_size:8x} | {sect.size_of_raw_data:8x}" + ) + print() + + +ALLOWED_TYPE_ABBREVIATIONS = ["fun", "dat", "poi", "str", "vta", "flo"] + + +def match_type_abbreviation(mtype: Optional[SymbolType]) -> str: + """Return abbreviation of the given SymbolType name""" + if mtype is None: + return "" + + return mtype.name.lower()[:3] + + +def get_cmakefiles_prefix(module: str) -> str: + """For the given .obj, get the "CMakeFiles/something.dir/" prefix. + For lack of a better option, this is the library for this module.""" + if module.startswith("CMakeFiles"): + return "/".join(module.split("/", 2)[:2]) + "/" + + return module + + +def truncate_module_name(prefix: str, module: str) -> str: + """Remove the CMakeFiles prefix and the .obj suffix for the given module. + Input: CMakeFiles/lego1.dir/, CMakeFiles/lego1.dir/LEGO1/define.cpp.obj + Output: LEGO1/define.cpp""" + + if module.startswith(prefix): + module = module[len(prefix) :] + + if module.endswith(".obj"): + module = module[:-4] + + return module + + +def avg_remove_outliers(entries: List[int]) -> int: + """Compute the average from this list of entries (addresses) + after removing outlier values.""" + + if len(entries) == 1: + return entries[0] + + avg = statistics.mean(entries) + sd = statistics.pstdev(entries) + + return int(statistics.mean([e for e in entries if abs(e - avg) <= 2 * sd])) + + +RoadmapRow = namedtuple( + "RoadmapRow", + [ + "orig_sect_ofs", + "recomp_sect_ofs", + "orig_addr", + "recomp_addr", + "displacement", + "sym_type", + "size", + "name", + "module", + ], +) + + +class DeltaCollector: + """Reads each row of the results and aggregates information about the + placement of each module.""" + + def __init__(self, match_type: str = "fun") -> None: + # The displacement for each symbol from each module + self.disp_map = {} + + # Each address for each module + self.addresses = {} + + # The earliest address for each module + self.earliest = {} + + # String abbreviation for which symbol type we are checking + self.match_type = "fun" + + match_type = str(match_type).strip().lower()[:3] + if match_type in ALLOWED_TYPE_ABBREVIATIONS: + self.match_type = match_type + + def read_row(self, row: RoadmapRow): + if row.module is None: + return + + if row.sym_type != self.match_type: + return + + if row.orig_addr is not None: + if row.module not in self.addresses: + self.addresses[row.module] = [] + + self.addresses[row.module].append(row.orig_addr) + + if row.orig_addr < self.earliest.get(row.module, 0xFFFFFFFFF): + self.earliest[row.module] = row.orig_addr + + if row.displacement is not None: + if row.module not in self.disp_map: + self.disp_map[row.module] = [] + + self.disp_map[row.module].append(row.displacement) + + def iter_sorted(self) -> Iterator[Tuple[int, int]]: + """Compute the average address for each module, then generate them + in ascending order.""" + avg_address = { + mod: avg_remove_outliers(values) for mod, values in self.addresses.items() + } + for mod, avg in sorted(avg_address.items(), key=lambda x: x[1]): + yield (avg, mod) + + +def suggest_order(results: List[RoadmapRow], module_map: ModuleMap, match_type: str): + """Suggest the order of modules for CMakeLists.txt""" + + dc = DeltaCollector(match_type) + for row in results: + dc.read_row(row) + + # First, show the order of .obj files for the "CMake Modules" + # Meaning: the modules where the .obj file begins with "CMakeFiles". + # These are the libraries where we directly control the order. + # The library name (from cvdump) doesn't make it obvious that these are + # our libraries so we derive the name based on the CMakeFiles prefix. + leftover_modules = set(module_map.get_all_cmake_modules()) + + # A little convoluted, but we want to take the first two tokens + # of the string with '/' as the delimiter. + # i.e. CMakeFiles/isle.dir/ + # The idea is to print exactly what appears in CMakeLists.txt. + cmake_prefixes = sorted(set(get_cmakefiles_prefix(mod) for mod in leftover_modules)) + + # Save this off because we'll use it again later. + computed_order = list(dc.iter_sorted()) + + for prefix in cmake_prefixes: + print(prefix) + + last_earliest = 0 + # Show modules ordered by the computed average of addresses + for _, module in computed_order: + if not module.startswith(prefix): + continue + + leftover_modules.remove(module) + + avg_displacement = None + displacements = dc.disp_map.get(module) + if displacements is not None and len(displacements) > 0: + avg_displacement = int(statistics.mean(displacements)) + + # Call attention to any modules where ordering by earliest + # address is different from the computed order we display. + earliest = dc.earliest.get(module) + ooo_mark = "*" if earliest < last_earliest else " " + last_earliest = earliest + + code_file = truncate_module_name(prefix, module) + print(f"0x{earliest:08x}{ooo_mark} {avg_displacement:10} {code_file}") + + # These modules are included in the final binary (in some form) but + # don't contribute any symbols of the type we are checking. + # n.b. There could still be other modules that are part of + # CMakeLists.txt but are not included in the pdb for whatever reason. + # In other words: don't take the list we provide as the final word on + # what should or should not be included. + # This is merely a suggestion of the order. + for module in leftover_modules: + if not module.startswith(prefix): + continue + + # aligned with previous print + code_file = truncate_module_name(prefix, module) + print(f" no suggestion {code_file}") + + print() + + # Now display the order of all libaries in the final file. + library_order = {} + + for start, module in computed_order: + lib = module_map.get_lib_for_module(module) + if lib is None: + lib = get_cmakefiles_prefix(module) + + if start < library_order.get(lib, 0xFFFFFFFFF): + library_order[lib] = start + + print("Library order (average address shown):") + for lib, start in sorted(library_order.items(), key=lambda x: x[1]): + # Strip off any OS path for brevity + if not lib.startswith("CMakeFiles"): + lib = os.path.basename(lib) + + print(f"{lib:40} {start:08x}") + + +def print_text_report(results: List[RoadmapRow]): + """Print the result with original and recomp addresses.""" + for row in results: + print( + " ".join( + [ + f"{or_blank(row.orig_sect_ofs):14}", + f"{or_blank(row.recomp_sect_ofs):14}", + f"{or_blank(row.displacement):>8}", + f"{row.sym_type:3}", + f"{or_blank(row.size):6}", + or_blank(row.name), + ] + ) + ) + + +def print_diff_report(results: List[RoadmapRow]): + """Print only entries where we have the recomp address. + This is intended for generating a file to diff against. + The recomp addresses are always changing so we hide those.""" + for row in results: + if row.orig_addr is None or row.recomp_addr is None: + continue + + print( + " ".join( + [ + f"{or_blank(row.orig_sect_ofs):14}", + f"{or_blank(row.displacement):>8}", + f"{row.sym_type:3}", + f"{or_blank(row.size):6}", + or_blank(row.name), + ] + ) + ) + + +def export_to_csv(csv_file: str, results: List[RoadmapRow]): + with open(csv_file, "w+", encoding="utf-8") as f: + f.write( + "orig_sect_ofs,recomp_sect_ofs,orig_addr,recomp_addr,displacement,row_type,size,name,module\n" + ) + for row in results: + f.write(",".join(map(or_blank, row))) + f.write("\n") + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser( + description="Show all addresses from original and recomp." + ) + parser.add_argument( + "original", metavar="original-binary", help="The original binary" + ) + parser.add_argument( + "recompiled", metavar="recompiled-binary", help="The recompiled binary" + ) + parser.add_argument( + "pdb", metavar="recompiled-pdb", help="The PDB of the recompiled binary" + ) + parser.add_argument( + "decomp_dir", metavar="decomp-dir", help="The decompiled source tree" + ) + parser.add_argument("--csv", metavar="", help="If set, export to CSV") + parser.add_argument( + "--verbose", "-v", action="store_true", help="Show recomp addresses in output" + ) + parser.add_argument( + "--order", + const="fun", + nargs="?", + type=str, + help="Show suggested order of modules (using the specified symbol type)", + ) + + (args, _) = parser.parse_known_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() + + with IsleBin(args.original, find_str=True) as orig_bin, IsleBin( + args.recompiled + ) as recomp_bin: + engine = IsleCompare(orig_bin, recomp_bin, args.pdb, args.decomp_dir) + + module_map = ModuleMap(args.pdb, recomp_bin) + + def is_same_section(orig: int, recomp: int) -> bool: + """Compare the section name instead of the index. + LEGO1.dll adds extra sections for some reason. (Smacker library?)""" + + try: + orig_name = orig_bin.sections[orig - 1].name + recomp_name = recomp_bin.sections[recomp - 1].name + return orig_name == recomp_name + except IndexError: + return False + + def to_roadmap_row(match): + orig_sect = None + orig_ofs = None + orig_sect_ofs = None + recomp_sect = None + recomp_ofs = None + recomp_sect_ofs = None + orig_addr = None + recomp_addr = None + displacement = None + module_name = None + + if match.recomp_addr is not None and recomp_bin.is_valid_vaddr( + match.recomp_addr + ): + if (module_ref := module_map.get_module(match.recomp_addr)) is not None: + (_, module_name) = module_ref + + row_type = match_type_abbreviation(match.compare_type) + name = ( + repr(match.name) + if match.compare_type == SymbolType.STRING + else match.name + ) + + if match.orig_addr is not None: + orig_addr = match.orig_addr + (orig_sect, orig_ofs) = orig_bin.get_relative_addr(match.orig_addr) + orig_sect_ofs = f"{orig_sect:04}:{orig_ofs:08x}" + + if match.recomp_addr is not None: + recomp_addr = match.recomp_addr + (recomp_sect, recomp_ofs) = recomp_bin.get_relative_addr( + match.recomp_addr + ) + recomp_sect_ofs = f"{recomp_sect:04}:{recomp_ofs:08x}" + + if ( + orig_sect is not None + and recomp_sect is not None + and is_same_section(orig_sect, recomp_sect) + ): + displacement = recomp_ofs - orig_ofs + + return RoadmapRow( + orig_sect_ofs, + recomp_sect_ofs, + orig_addr, + recomp_addr, + displacement, + row_type, + match.size, + name, + module_name, + ) + + def roadmap_row_generator(matches): + for match in matches: + try: + yield to_roadmap_row(match) + except InvalidVirtualAddressError: + # This is here to work around the fact that we have RVA + # values (i.e. not real virtual addrs) in our compare db. + pass + + results = list(roadmap_row_generator(engine.get_all())) + + if args.order is not None: + suggest_order(results, module_map, args.order) + return + + if args.csv is None: + if args.verbose: + print("ORIG sections:") + print_sections(orig_bin.sections) + + print("RECOMP sections:") + print_sections(recomp_bin.sections) + + print_text_report(results) + else: + print_diff_report(results) + + if args.csv is not None: + export_to_csv(args.csv, results) + + +if __name__ == "__main__": + main() diff --git a/tools/verexp/verexp.py b/tools/verexp/verexp.py new file mode 100644 index 00000000..e112c07a --- /dev/null +++ b/tools/verexp/verexp.py @@ -0,0 +1,75 @@ +#!/usr/bin/env python3 + +import argparse +import difflib +import subprocess +import os + +from isledecomp.lib import lib_path_join +from isledecomp.utils import print_diff + + +def main(): + parser = argparse.ArgumentParser( + allow_abbrev=False, + description="Verify Exports: Compare the exports of two DLLs.", + ) + parser.add_argument( + "original", metavar="original-binary", help="The original binary" + ) + parser.add_argument( + "recompiled", metavar="recompiled-binary", help="The recompiled binary" + ) + parser.add_argument( + "--no-color", "-n", action="store_true", help="Do not color the output" + ) + + args = parser.parse_args() + + if not os.path.isfile(args.original): + parser.error(f"Original binary file {args.original} does not exist") + + if not os.path.isfile(args.recompiled): + parser.error(f"Recompiled binary {args.recompiled} does not exist") + + def get_exports(file): + call = [lib_path_join("DUMPBIN.EXE"), "/EXPORTS"] + + if os.name != "nt": + call.insert(0, "wine") + file = ( + subprocess.check_output(["winepath", "-w", file]) + .decode("utf-8") + .strip() + ) + + call.append(file) + + raw = subprocess.check_output(call).decode("utf-8").split("\r\n") + exports = [] + + start = False + + for line in raw: + if not start: + if line == " ordinal hint name": + start = True + else: + if line: + exports.append(line[27 : line.rindex(" (")]) + elif exports: + break + + return exports + + og_exp = get_exports(args.original) + re_exp = get_exports(args.recompiled) + + udiff = difflib.unified_diff(og_exp, re_exp) + has_diff = print_diff(udiff, args.no_color) + + return 1 if has_diff else 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/vtable/vtable.py b/tools/vtable/vtable.py new file mode 100644 index 00000000..aa294659 --- /dev/null +++ b/tools/vtable/vtable.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 + +import os +import argparse +import logging +from typing import List +import colorama +from isledecomp.bin import Bin as IsleBin +from isledecomp.compare import Compare as IsleCompare +from isledecomp.utils import print_combined_diff + +# Ignore all compare-db messages. +logging.getLogger("isledecomp.compare").addHandler(logging.NullHandler()) + +colorama.just_fix_windows_console() + + +def parse_args() -> argparse.Namespace: + parser = argparse.ArgumentParser(description="Comparing vtables.") + parser.add_argument( + "original", metavar="original-binary", help="The original binary" + ) + parser.add_argument( + "recompiled", metavar="recompiled-binary", help="The recompiled binary" + ) + parser.add_argument( + "pdb", metavar="recompiled-pdb", help="The PDB of the recompiled binary" + ) + parser.add_argument( + "decomp_dir", metavar="decomp-dir", help="The decompiled source tree" + ) + parser.add_argument( + "--verbose", "-v", action="store_true", help="Show more detailed information" + ) + parser.add_argument( + "--no-color", "-n", action="store_true", help="Do not color the output" + ) + + (args, _) = parser.parse_known_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 show_vtable_diff(udiff: List, _: bool = False, plain: bool = False): + print_combined_diff(udiff, plain) + + +def print_summary(vtable_count: int, problem_count: int): + if problem_count == 0: + print(f"Vtables found: {vtable_count}.\n100% match.") + return + + print(f"Vtables found: {vtable_count}.\nVtables not matching: {problem_count}.") + + +def main(): + args = parse_args() + vtable_count = 0 + problem_count = 0 + + with IsleBin(args.original) as orig_bin, IsleBin(args.recompiled) as recomp_bin: + engine = IsleCompare(orig_bin, recomp_bin, args.pdb, args.decomp_dir) + + for tbl_match in engine.compare_vtables(): + vtable_count += 1 + if tbl_match.ratio < 1: + problem_count += 1 + + udiff = list(tbl_match.udiff) + + print( + tbl_match.name, + f": orig 0x{tbl_match.orig_addr:x}, recomp 0x{tbl_match.recomp_addr:x}", + ) + show_vtable_diff(udiff, args.verbose, args.no_color) + print() + + print_summary(vtable_count, problem_count) + + # Now compare adjuster thunk functions, if there are any. + # These matches are generated by the compare engine. + # They should always match 100%. If not, there is a problem + # with the inheritance or an overriden function. + for fun_match in engine.get_functions(): + if "`vtordisp" not in fun_match.name: + continue + + diff = engine.compare_address(fun_match.orig_addr) + if diff.ratio < 1.0: + problem_count += 1 + print( + f"Problem with adjuster thunk {fun_match.name} (0x{fun_match.orig_addr:x} / 0x{fun_match.recomp_addr:x})" + ) + + return 1 if problem_count > 0 else 0 + + +if __name__ == "__main__": + raise SystemExit(main())