From fa6de9cb6bbe71129b4525384362d2988e633fab Mon Sep 17 00:00:00 2001 From: Christian Semmler Date: Thu, 3 Oct 2024 12:43:18 -0700 Subject: [PATCH 01/10] Implement/match Pizza::FUN_10038220 (#1107) --- LEGO1/lego/legoomni/include/pizza.h | 20 +++++++++---------- LEGO1/lego/legoomni/src/actors/pizza.cpp | 25 ++++++++++++++++++++++-- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/LEGO1/lego/legoomni/include/pizza.h b/LEGO1/lego/legoomni/include/pizza.h index 70dd2126..e8bd71c9 100644 --- a/LEGO1/lego/legoomni/include/pizza.h +++ b/LEGO1/lego/legoomni/include/pizza.h @@ -38,8 +38,7 @@ class PizzaMissionState : public LegoState { undefined m_unk0x03[3]; // 0x03 MxS16 m_unk0x06; // 0x06 undefined m_unk0x08[8]; // 0x08 - MxS16 m_unk0x10; // 0x10 - MxS16 m_unk0x12; // 0x12 + undefined4 m_unk0x10; // 0x10 MxS16 m_unk0x14; // 0x14 MxS16 m_unk0x16; // 0x16 MxS16 m_score; // 0x18 @@ -107,6 +106,7 @@ class Pizza : public IsleActor { void FUN_10038220(MxU32 p_objectId); void FUN_100382b0(); void FUN_10038380(); + void FUN_10038fe0(MxU32 p_objectId, MxBool); void SetSkateboard(SkateBoard* p_skateboard) { m_skateboard = p_skateboard; } @@ -114,14 +114,14 @@ class Pizza : public IsleActor { // Pizza::`scalar deleting destructor' private: - PizzaMissionState* m_state; // 0x7c - undefined4 m_unk0x80; // 0x80 - SkateBoard* m_skateboard; // 0x84 - Act1State* m_act1state; // 0x88 - undefined4 m_unk0x8c; // 0x8c - undefined4 m_unk0x90; // 0x90 - undefined4 m_unk0x94; // 0x94 - undefined m_unk0x98; // 0x98 + PizzaMissionState* m_state; // 0x7c + PizzaMissionState::Entry* m_entry; // 0x80 + SkateBoard* m_skateboard; // 0x84 + Act1State* m_act1state; // 0x88 + undefined4 m_unk0x8c; // 0x8c + undefined4 m_unk0x90; // 0x90 + undefined4 m_unk0x94; // 0x94 + undefined m_unk0x98; // 0x98 }; #endif // PIZZA_H diff --git a/LEGO1/lego/legoomni/src/actors/pizza.cpp b/LEGO1/lego/legoomni/src/actors/pizza.cpp index e634891b..593d7f74 100644 --- a/LEGO1/lego/legoomni/src/actors/pizza.cpp +++ b/LEGO1/lego/legoomni/src/actors/pizza.cpp @@ -1,6 +1,8 @@ #include "pizza.h" +#include "isle.h" #include "isle_actions.h" +#include "legoanimationmanager.h" #include "legogamestate.h" #include "legoworld.h" #include "misc.h" @@ -11,11 +13,14 @@ DECOMP_SIZE_ASSERT(Pizza, 0x9c) DECOMP_SIZE_ASSERT(PizzaMissionState, 0xb4) DECOMP_SIZE_ASSERT(PizzaMissionState::Entry, 0x20) +// Flags used in isle.cpp +extern MxU32 g_isleFlags; + // FUNCTION: LEGO1 0x10037ef0 Pizza::Pizza() { m_state = NULL; - m_unk0x80 = 0; + m_entry = NULL; m_skateboard = NULL; m_act1state = NULL; m_unk0x8c = -1; @@ -56,9 +61,19 @@ void Pizza::CreateState() } } -// STUB: LEGO1 0x10038220 +// FUNCTION: LEGO1 0x10038220 void Pizza::FUN_10038220(MxU32 p_objectId) { + AnimationManager()->FUN_10064740(NULL); + m_entry = m_state->GetState(GameState()->GetActorId()); + m_state->m_unk0x0c = 1; + m_act1state->m_unk0x018 = 3; + m_entry->m_unk0x10 = 0x80000000; + g_isleFlags &= ~Isle::c_playMusic; + AnimationManager()->EnableCamAnims(FALSE); + AnimationManager()->FUN_1005f6d0(FALSE); + FUN_10038fe0(p_objectId, FALSE); + m_unk0x8c = -1; } // STUB: LEGO1 0x100382b0 @@ -99,6 +114,12 @@ MxLong Pizza::HandleEndAction(MxEndActionNotificationParam&) return 0; } +// STUB: LEGO1 0x10038fe0 +void Pizza::FUN_10038fe0(MxU32 p_objectId, MxBool) +{ + // TODO +} + // STUB: LEGO1 0x10039030 PizzaMissionState::PizzaMissionState() { From e6474b7fcda541b8d2b08f6e970a4ccb24c47bc7 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Fri, 4 Oct 2024 17:19:42 +0200 Subject: [PATCH 02/10] Implement `LegoCarBuildAnimPresenter::FUN_10079920()` (#1108) * Implement `FUN_10079920()` * Fix formatting * Address review comment --------- Co-authored-by: jonschz --- LEGO1/lego/legoomni/include/legocarbuild.h | 2 +- .../legoomni/include/legocarbuildpresenter.h | 7 ++-- .../lego/legoomni/src/build/legocarbuild.cpp | 4 +- .../src/build/legocarbuildpresenter.cpp | 41 +++++++++++++++++-- LEGO1/lego/sources/anim/legoanim.h | 30 ++++++++++++++ LEGO1/mxgeometry/mxgeometry3d.h | 1 + LEGO1/realtime/vector.h | 2 + 7 files changed, 77 insertions(+), 10 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocarbuild.h b/LEGO1/lego/legoomni/include/legocarbuild.h index 60b5a9c3..c06d4e9e 100644 --- a/LEGO1/lego/legoomni/include/legocarbuild.h +++ b/LEGO1/lego/legoomni/include/legocarbuild.h @@ -202,7 +202,7 @@ class LegoCarBuild : public LegoWorld { static MxS16 g_unk0x100f11cc; static MxFloat g_unk0x100d65a4; - static MxFloat g_unk0x100d65a8; + static MxFloat g_rotationAngleStepYAxis; }; #endif // LEGOCARBUILD_H diff --git a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h index 8d707eff..8084da7c 100644 --- a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h +++ b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h @@ -45,7 +45,8 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { void EndAction() override; // vtable+0x40 void PutFrame() override; // vtable+0x6c - void FUN_10079920(float p_param1); + void FUN_10079160(); + void RotateAroundYAxis(MxFloat p_angle); MxBool FUN_10079c30(const LegoChar* p_name); MxBool FUN_10079ca0(const LegoChar* p_name); MxBool FUN_10079cf0(const LegoChar* p_string); @@ -64,11 +65,11 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { undefined2 m_unk0xbc; // 0xbc MxS16 m_unk0xbe; // 0xbe MxS16 m_unk0xc0; // 0xc0 - undefined4 m_unk0xc4; // 0xc4 + LegoAnimNodeData* m_unk0xc4; // 0xc4 LegoAnim m_unk0xc8; // 0xc8 MxMatrix m_unk0xe0; // 0xe0 UnknownListEntry* m_unk0x128; // 0x128 - undefined4 m_unk0x12c; // 0x12c + MxFloat m_unk0x12c; // 0x12c undefined4 m_unk0x130; // 0x130 undefined4 m_unk0x134; // 0x134 undefined4 m_unk0x138; // 0x138 diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index 2d6501c7..b9865c1b 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -26,7 +26,7 @@ DECOMP_SIZE_ASSERT(LegoVehicleBuildState, 0x50) MxFloat LegoCarBuild::g_unk0x100d65a4 = -0.1f; // GLOBAL: LEGO1 0x100d65a8 -MxFloat LegoCarBuild::g_unk0x100d65a8 = 0.07; +MxFloat LegoCarBuild::g_rotationAngleStepYAxis = 0.07; // GLOBAL: LEGO1 0x100f11cc MxS16 LegoCarBuild::g_unk0x100f11cc = -1; @@ -783,7 +783,7 @@ void LegoCarBuild::FUN_10024ef0() void LegoCarBuild::FUN_10024f50() { m_unk0x2d4 = FALSE; - m_unk0x258->FUN_10079920(g_unk0x100d65a8); + m_unk0x258->RotateAroundYAxis(g_rotationAngleStepYAxis); } // FUNCTION: LEGO1 0x10024f70 diff --git a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp index 2ed86f7b..29b35d21 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp @@ -14,7 +14,7 @@ LegoCarBuildAnimPresenter::LegoCarBuildAnimPresenter() m_unk0xbe = 0; m_unk0xc0 = 0; m_unk0x128 = NULL; - m_unk0xc4 = 0; + m_unk0xc4 = NULL; m_unk0x130 = 0; m_unk0x12c = 0; m_unk0x134 = 0; @@ -83,13 +83,46 @@ void LegoCarBuildAnimPresenter::EndAction() } } -// STUB: LEGO1 0x10079920 -// STUB: BETA10 0x1007225d -void LegoCarBuildAnimPresenter::FUN_10079920(float p_param1) +// STUB: LEGO1 0x10079160 +void LegoCarBuildAnimPresenter::FUN_10079160() { + // called from LegoCarBuildAnimPresenter::StreamingTickle() // TODO } +// FUNCTION: LEGO1 0x10079920 +// FUNCTION: BETA10 0x1007225d +void LegoCarBuildAnimPresenter::RotateAroundYAxis(MxFloat p_angle) +{ + if (m_unk0xc4) { + LegoRotationKey* rotationKey = m_unk0xc4->GetRotationKey(0); + + Mx4DPointFloat + currentRotation(rotationKey->GetX(), rotationKey->GetY(), rotationKey->GetZ(), rotationKey->GetAngle()); + Mx4DPointFloat additionalRotation(0.0f, 1.0f, 0.0f, -p_angle); + Mx4DPointFloat newRotation; + + additionalRotation.NormalizeQuaternion(); + newRotation.EqualsHamiltonProduct(¤tRotation, &additionalRotation); + + if (newRotation[3] < 0.9999) { + rotationKey->FUN_100739a0(TRUE); + } + else { + rotationKey->FUN_100739a0(FALSE); + } + + m_unk0xc4->GetRotationKey(0)->SetX(newRotation[0]); + m_unk0xc4->GetRotationKey(0)->SetY(newRotation[1]); + m_unk0xc4->GetRotationKey(0)->SetZ(newRotation[2]); + m_unk0xc4->GetRotationKey(0)->SetAngle(newRotation[3]); + + if (m_unk0x140->GetROI()) { + FUN_1006b9a0(&m_unk0xc8, m_unk0x12c, NULL); + } + } +} + // FUNCTION: LEGO1 0x10079b80 // FUNCTION: BETA10 0x1007258f MxBool LegoCarBuildAnimPresenter::StringEndsOnYOrN(const LegoChar* p_string) diff --git a/LEGO1/lego/sources/anim/legoanim.h b/LEGO1/lego/sources/anim/legoanim.h index 1a861eb6..0badeae9 100644 --- a/LEGO1/lego/sources/anim/legoanim.h +++ b/LEGO1/lego/sources/anim/legoanim.h @@ -24,6 +24,17 @@ class LegoAnimKey { LegoU32 TestBit2() { return m_flags & c_bit2; } LegoU32 TestBit3() { return m_flags & c_bit3; } + // FUNCTION: BETA10 0x100739a0 + void FUN_100739a0(MxS32 p_param) + { + if (p_param) { + m_flags |= c_bit1; + } + else { + m_flags &= ~c_bit1; + } + } + protected: LegoU8 m_flags; // 0x00 LegoFloat m_time; // 0x04 @@ -52,13 +63,29 @@ class LegoRotationKey : public LegoAnimKey { public: LegoRotationKey(); LegoResult Read(LegoStorage* p_storage); + + // FUNCTION: BETA10 0x10073a00 LegoFloat GetAngle() { return m_angle; } + + // FUNCTION: BETA10 0x10073a30 void SetAngle(LegoFloat p_angle) { m_angle = p_angle; } + + // FUNCTION: BETA10 0x10073a60 LegoFloat GetX() { return m_x; } + + // FUNCTION: BETA10 0x10073a90 void SetX(LegoFloat p_x) { m_x = p_x; } + + // FUNCTION: BETA10 0x10073ac0 LegoFloat GetY() { return m_y; } + + // FUNCTION: BETA10 0x10073af0 void SetY(LegoFloat p_y) { m_y = p_y; } + + // FUNCTION: BETA10 0x10073b20 LegoFloat GetZ() { return m_z; } + + // FUNCTION: BETA10 0x10073b50 void SetZ(LegoFloat p_z) { m_z = p_z; } protected: @@ -129,6 +156,9 @@ class LegoAnimNodeData : public LegoTreeNodeData { LegoU16 GetUnknown0x20() { return m_unk0x20; } LegoU16 GetUnknown0x22() { return m_unk0x22; } + // FUNCTION: BETA10 0x10073b80 + LegoRotationKey* GetRotationKey(MxS32 index) { return &m_rotationKeys[index]; } + void SetTranslationIndex(LegoU32 p_translationIndex) { m_translationIndex = p_translationIndex; } void SetRotationIndex(LegoU32 p_rotationIndex) { m_rotationIndex = p_rotationIndex; } void SetScaleIndex(LegoU32 p_scaleIndex) { m_scaleIndex = p_scaleIndex; } diff --git a/LEGO1/mxgeometry/mxgeometry3d.h b/LEGO1/mxgeometry/mxgeometry3d.h index f5e2c083..f73d5d8a 100644 --- a/LEGO1/mxgeometry/mxgeometry3d.h +++ b/LEGO1/mxgeometry/mxgeometry3d.h @@ -50,6 +50,7 @@ class Mx3DPointFloat : public Vector3 { class Mx4DPointFloat : public Vector4 { public: // FUNCTION: LEGO1 0x10048290 + // FUNCTION: BETA10 0x100484c0 Mx4DPointFloat() : Vector4(m_elements) {} // FUNCTION: BETA10 0x10073bb0 diff --git a/LEGO1/realtime/vector.h b/LEGO1/realtime/vector.h index 4c1e17c3..3faa559e 100644 --- a/LEGO1/realtime/vector.h +++ b/LEGO1/realtime/vector.h @@ -182,6 +182,7 @@ class Vector2 { class Vector3 : public Vector2 { public: // FUNCTION: LEGO1 0x1001d150 + // FUNCTION: BETA10 0x10011660 Vector3(float* p_data) : Vector2(p_data) {} // Hack: Some code initializes a Vector3 from a (most likely) const float* source. @@ -298,6 +299,7 @@ class Vector3 : public Vector2 { // SIZE 0x08 class Vector4 : public Vector3 { public: + // FUNCTION: BETA10 0x10048780 Vector4(float* p_data) : Vector3(p_data) {} // Hack: Some code initializes a Vector4 from a (most likely) const float* source. From 1a15981324c299184d930d4ac3b648caa9fb3ae3 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 6 Oct 2024 01:20:45 +0200 Subject: [PATCH 03/10] Implement `LegoCarBuildAnimPresenter::StreamingTickle()` (#1109) * Implement LegoCarBuildAnimPresenter::StreamingTickle and dependents * Fix naming issue * Address review comment --------- Co-authored-by: jonschz --- .../legoomni/include/legocarbuildpresenter.h | 8 +- LEGO1/lego/legoomni/include/legoutils.h | 1 + .../lego/legoomni/include/legovideomanager.h | 2 + .../src/build/legocarbuildpresenter.cpp | 140 +++++++++++++++++- LEGO1/lego/legoomni/src/common/legoutils.cpp | 2 + LEGO1/lego/legoomni/src/entity/legoentity.cpp | 1 + LEGO1/lego/legoomni/src/entity/legoworld.cpp | 1 + .../lego/sources/3dmanager/lego3dmanager.cpp | 1 + LEGO1/lego/sources/3dmanager/lego3dview.cpp | 1 + LEGO1/lego/sources/anim/legoanim.h | 4 + LEGO1/lego/sources/misc/legotree.h | 7 + LEGO1/lego/sources/roi/legoroi.cpp | 20 +++ LEGO1/lego/sources/roi/legoroi.h | 2 + LEGO1/library_msvc.h | 7 + LEGO1/mxgeometry/mxgeometry3d.h | 10 +- LEGO1/omni/include/mxdsobject.h | 2 + LEGO1/realtime/matrix.h | 2 + LEGO1/realtime/orientableroi.h | 2 + LEGO1/realtime/realtime.cpp | 1 + LEGO1/realtime/vector.h | 17 ++- 20 files changed, 216 insertions(+), 15 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h index 8084da7c..b7b02d52 100644 --- a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h +++ b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h @@ -45,7 +45,11 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { void EndAction() override; // vtable+0x40 void PutFrame() override; // vtable+0x6c + void FUN_10079050(MxS16 p_index); + void FUN_10079090(LegoChar* p_param1, LegoChar* p_param2); void FUN_10079160(); + void FUN_100795d0(LegoChar* p_param); + void FUN_10079680(LegoChar* p_param); void RotateAroundYAxis(MxFloat p_angle); MxBool FUN_10079c30(const LegoChar* p_name); MxBool FUN_10079ca0(const LegoChar* p_name); @@ -77,7 +81,9 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { LegoEntity* m_unk0x140; // 0x140 MxS32 m_unk0x144; // 0x144 MxS32 m_unk0x148; // 0x148 - undefined* m_unk0x14c; // 0x14c + + // name verified by BETA10 0x10070d63 + LegoChar* m_mainSourceId; // 0x14c }; #endif // LEGOCARBUILDPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legoutils.h b/LEGO1/lego/legoomni/include/legoutils.h index 9e8c91fc..b6143c36 100644 --- a/LEGO1/lego/legoomni/include/legoutils.h +++ b/LEGO1/lego/legoomni/include/legoutils.h @@ -43,6 +43,7 @@ void FUN_1003dde0(LegoROI* p_param1, MxFloat p_param2); MxBool FUN_1003ded0(MxFloat p_param1[2], MxFloat p_param2[3], MxFloat p_param3[3]); MxBool TransformWorldToScreen(const MxFloat p_world[3], MxFloat p_screen[4]); MxS16 CountTotalTreeNodes(LegoTreeNode* p_node); +LegoTreeNode* GetTreeNode(LegoTreeNode* p_node, MxU32 p_index); void FUN_1003e050(LegoAnimPresenter* p_presenter); Extra::ActionType MatchActionString(const char*); void InvokeAction(Extra::ActionType p_actionId, const MxAtomId& p_pAtom, MxS32 p_targetEntityId, LegoEntity* p_sender); diff --git a/LEGO1/lego/legoomni/include/legovideomanager.h b/LEGO1/lego/legoomni/include/legovideomanager.h index edfd345e..605d0626 100644 --- a/LEGO1/lego/legoomni/include/legovideomanager.h +++ b/LEGO1/lego/legoomni/include/legovideomanager.h @@ -53,7 +53,9 @@ class LegoVideoManager : public MxVideoManager { // FUNCTION: BETA10 0x100117e0 Lego3DManager* Get3DManager() { return m_3dManager; } + // FUNCTION: BETA10 0x1003a380 LegoROI* GetViewROI() { return m_viewROI; } + MxDirect3D* GetDirect3D() { return m_direct3d; } MxBool GetRender3D() { return m_render3d; } double GetElapsedSeconds() { return m_elapsedSeconds; } diff --git a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp index 29b35d21..5bd69f58 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp @@ -1,7 +1,13 @@ #include "legocarbuildpresenter.h" +#include "3dmanager/lego3dmanager.h" #include "legoentity.h" +#include "legogamestate.h" +#include "legoutils.h" +#include "legovideomanager.h" +#include "misc.h" #include "mxautolock.h" +#include "realtime/realtime.h" DECOMP_SIZE_ASSERT(LegoCarBuildAnimPresenter::UnknownListEntry, 0x0c) DECOMP_SIZE_ASSERT(LegoCarBuildAnimPresenter, 0x150) @@ -23,7 +29,7 @@ LegoCarBuildAnimPresenter::LegoCarBuildAnimPresenter() m_unk0x140 = NULL; m_unk0x144 = -1; m_unk0x148 = -1; - m_unk0x14c = NULL; + m_mainSourceId = NULL; } // FUNCTION: LEGO1 0x10078500 @@ -47,8 +53,8 @@ LegoCarBuildAnimPresenter::~LegoCarBuildAnimPresenter() m_unk0xc8.GetRoot()->SetNumChildren(0); *m_unk0xc8.GetRoot()->GetChildren() = NULL; - if (m_unk0x14c) { - delete m_unk0x14c; + if (m_mainSourceId) { + delete[] m_mainSourceId; } } @@ -65,11 +71,103 @@ void LegoCarBuildAnimPresenter::ReadyTickle() // TODO } -// STUB: LEGO1 0x100789e0 -// STUB: BETA10 0x10070cdd +// FUNCTION: LEGO1 0x100789e0 +// FUNCTION: BETA10 0x10070cdd void LegoCarBuildAnimPresenter::StreamingTickle() { - // TODO + if (!m_unk0x140->GetROI()) { + return; + } + + m_mainSourceId = new LegoChar[strlen(m_action->GetAtomId().GetInternal()) + 1]; + assert(m_mainSourceId); + + strcpy(m_mainSourceId, m_action->GetAtomId().GetInternal()); + m_mainSourceId[strlen(m_mainSourceId) - 1] = 'M'; + + FUN_10079160(); + + if (GameState()->GetCurrentAct() == LegoGameState::e_act2) { + m_unk0xc0 = 10; + } + + MxS16 i; + + for (i = 0; i < m_unk0xbe; i++) { + if (m_unk0xc0 == i) { + FUN_10079680(m_unk0x128[i].m_unk0x04); + } + else { + FUN_100795d0(m_unk0x128[i].m_unk0x04); + } + + if (i < m_unk0xc0) { + FUN_10079050(i); + FUN_10079680(m_unk0x128[i].m_unk0x00); + } + + LegoChar* name = m_unk0x128[i].m_unk0x04; + + if (name) { + for (MxS32 j = 0; j <= m_roiMapSize; j++) { + LegoROI* roi = m_roiMap[j]; + + if (roi && roi->GetName() && (strcmpi(name, roi->GetName()) == 0)) { + roi->FUN_100a9dd0(); + roi->FUN_100a9350("lego red"); + } + } + } + } + + LegoVideoManager* videoManager = VideoManager(); + assert(videoManager); // verifies variable name 'videoManager' + + Lego3DView* lego3dview = videoManager->Get3DManager()->GetLego3DView(); + LegoROI* videoManagerROI = videoManager->GetViewROI(); + LegoROI* local60 = m_unk0x140->GetROI(); + LegoROI* camera = NULL; + MxFloat fov; + + MxS16 totalNodes = CountTotalTreeNodes(m_anim->GetRoot()); + + for (i = 0; i < totalNodes; i++) { + LegoAnimNodeData* animNodeData = (LegoAnimNodeData*) GetTreeNode(m_anim->GetRoot(), i)->GetData(); + + if (strnicmp(animNodeData->GetName(), "CAM", strlen("CAM")) == 0) { + camera = local60->FindChildROI(animNodeData->GetName(), local60); + fov = atof(&animNodeData->GetName()[strlen(animNodeData->GetName()) - 2]); + break; + } + } + + assert(camera); // verifies variable name 'camera' + + LegoROI* targetROI = local60->FindChildROI("TARGET", local60); + + Mx3DPointFloat dirVec; + + Vector3 cameraPosition(camera->GetWorldPosition()); + Vector3 upVec(camera->GetWorldUp()); + Vector3 targetPosition(targetROI->GetWorldPosition()); + + MxMatrix localTransform; + + dirVec[0] = targetPosition[0] - cameraPosition[0]; + dirVec[1] = targetPosition[1] - cameraPosition[1]; + dirVec[2] = targetPosition[2] - cameraPosition[2]; + dirVec.Unitize(); + + CalcLocalTransform(cameraPosition, dirVec, upVec, localTransform); + + videoManagerROI->WrappedSetLocalTransform(localTransform); + lego3dview->Moved(*videoManagerROI); + videoManager->Get3DManager()->SetFrustrum(fov, 0.1, 250.0); + + m_unk0xe0 = local60->FindChildROI("VIEW", local60)->GetLocal2World(); + + m_previousTickleStates |= 1 << m_currentTickleState; + m_currentTickleState = e_repeating; } // FUNCTION: LEGO1 0x10078db0 @@ -83,13 +181,43 @@ void LegoCarBuildAnimPresenter::EndAction() } } +// FUNCTION: LEGO1 0x10079050 +// FUNCTION: BETA10 0x1007151e +void LegoCarBuildAnimPresenter::FUN_10079050(MxS16 p_index) +{ + FUN_10079090(m_unk0x128[p_index].m_unk0x04, m_unk0x128[p_index].m_unk0x00); + FUN_100795d0(m_unk0x128[p_index].m_unk0x04); +} + +// STUB: LEGO1 0x10079090 +// STUB: BETA10 0x10071584 +void LegoCarBuildAnimPresenter::FUN_10079090(LegoChar* p_param1, LegoChar* p_param2) +{ + // TODO +} + // STUB: LEGO1 0x10079160 +// STUB: BETA10 0x1007165d void LegoCarBuildAnimPresenter::FUN_10079160() { // called from LegoCarBuildAnimPresenter::StreamingTickle() // TODO } +// STUB: LEGO1 0x100795d0 +// STUB: BETA10 0x10071d96 +void LegoCarBuildAnimPresenter::FUN_100795d0(LegoChar* p_param) +{ + // TODO +} + +// STUB: LEGO1 0x10079680 +// STUB: BETA10 0x10071ec5 +void LegoCarBuildAnimPresenter::FUN_10079680(LegoChar* p_param) +{ + // TODO +} + // FUNCTION: LEGO1 0x10079920 // FUNCTION: BETA10 0x1007225d void LegoCarBuildAnimPresenter::RotateAroundYAxis(MxFloat p_angle) diff --git a/LEGO1/lego/legoomni/src/common/legoutils.cpp b/LEGO1/lego/legoomni/src/common/legoutils.cpp index 266ac1b1..d90e655e 100644 --- a/LEGO1/lego/legoomni/src/common/legoutils.cpp +++ b/LEGO1/lego/legoomni/src/common/legoutils.cpp @@ -106,6 +106,7 @@ MxBool TransformWorldToScreen(const MxFloat p_world[3], MxFloat p_screen[4]) } // FUNCTION: LEGO1 0x1003df90 +// FUNCTION: BETA10 0x100d39a3 MxS16 CountTotalTreeNodes(LegoTreeNode* p_node) { MxS16 result = 1; @@ -118,6 +119,7 @@ MxS16 CountTotalTreeNodes(LegoTreeNode* p_node) } // FUNCTION: LEGO1 0x1003dfd0 +// FUNCTION: BETA10 0x100d3a09 LegoTreeNode* GetTreeNode(LegoTreeNode* p_node, MxU32 p_index) { LegoTreeNode* result = NULL; diff --git a/LEGO1/lego/legoomni/src/entity/legoentity.cpp b/LEGO1/lego/legoomni/src/entity/legoentity.cpp index f59c83a9..81cb3fe3 100644 --- a/LEGO1/lego/legoomni/src/entity/legoentity.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoentity.cpp @@ -213,6 +213,7 @@ Mx3DPointFloat LegoEntity::GetWorldUp() } // FUNCTION: LEGO1 0x10010d80 +// FUNCTION: BETA10 0x1007ebbe Mx3DPointFloat LegoEntity::GetWorldPosition() { if (m_roi != NULL) { diff --git a/LEGO1/lego/legoomni/src/entity/legoworld.cpp b/LEGO1/lego/legoomni/src/entity/legoworld.cpp index eafc5e1e..822121e6 100644 --- a/LEGO1/lego/legoomni/src/entity/legoworld.cpp +++ b/LEGO1/lego/legoomni/src/entity/legoworld.cpp @@ -380,6 +380,7 @@ void LegoWorld::AddPath(LegoPathController* p_controller) } // FUNCTION: LEGO1 0x10020020 +// FUNCTION: BETA10 0x100da77c LegoPathBoundary* LegoWorld::FindPathBoundary(const char* p_name) { LegoPathControllerListCursor cursor(&m_list0x68); diff --git a/LEGO1/lego/sources/3dmanager/lego3dmanager.cpp b/LEGO1/lego/sources/3dmanager/lego3dmanager.cpp index 1781870a..750f54c6 100644 --- a/LEGO1/lego/sources/3dmanager/lego3dmanager.cpp +++ b/LEGO1/lego/sources/3dmanager/lego3dmanager.cpp @@ -97,6 +97,7 @@ double Lego3DManager::Render(double p_und) } // FUNCTION: LEGO1 0x100ab4d0 +// FUNCTION: BETA10 0x1017baeb int Lego3DManager::SetFrustrum(float p_fov, float p_front, float p_back) { m_pLego3DView->GetView()->SetFrustrum(p_front, p_back, p_fov); diff --git a/LEGO1/lego/sources/3dmanager/lego3dview.cpp b/LEGO1/lego/sources/3dmanager/lego3dview.cpp index e895e378..4f69fa40 100644 --- a/LEGO1/lego/sources/3dmanager/lego3dview.cpp +++ b/LEGO1/lego/sources/3dmanager/lego3dview.cpp @@ -119,6 +119,7 @@ BOOL Lego3DView::SetPointOfView(ViewROI& rROI) } // FUNCTION: LEGO1 0x100ab210 +// FUNCTION: BETA10 0x1017d230 BOOL Lego3DView::Moved(ViewROI& rROI) { assert(m_pViewManager); diff --git a/LEGO1/lego/sources/anim/legoanim.h b/LEGO1/lego/sources/anim/legoanim.h index 0badeae9..62816ba9 100644 --- a/LEGO1/lego/sources/anim/legoanim.h +++ b/LEGO1/lego/sources/anim/legoanim.h @@ -148,7 +148,9 @@ class LegoAnimNodeData : public LegoTreeNodeData { LegoResult CreateLocalTransform(LegoFloat p_time, Matrix4& p_matrix); LegoBool FUN_100a0990(LegoFloat p_time); + // FUNCTION: BETA10 0x100595d0 const LegoChar* GetName() { return m_name; } + LegoU32 GetTranslationIndex() { return m_translationIndex; } LegoU32 GetRotationIndex() { return m_rotationIndex; } LegoU32 GetScaleIndex() { return m_scaleIndex; } @@ -164,6 +166,8 @@ class LegoAnimNodeData : public LegoTreeNodeData { void SetScaleIndex(LegoU32 p_scaleIndex) { m_scaleIndex = p_scaleIndex; } void SetMorphIndex(LegoU32 p_morphIndex) { m_morphIndex = p_morphIndex; } void SetUnknown0x20(LegoU16 p_unk0x20) { m_unk0x20 = p_unk0x20; } + + // FUNCTION: BETA10 0x1005f2e0 void SetUnknown0x22(LegoU16 p_unk0x22) { m_unk0x22 = p_unk0x22; } LegoResult CreateLocalTransform(LegoTime p_time, Matrix4& p_matrix) diff --git a/LEGO1/lego/sources/misc/legotree.h b/LEGO1/lego/sources/misc/legotree.h index fb97a612..fea8d5ec 100644 --- a/LEGO1/lego/sources/misc/legotree.h +++ b/LEGO1/lego/sources/misc/legotree.h @@ -32,14 +32,21 @@ class LegoTreeNode { public: LegoTreeNode(); virtual ~LegoTreeNode(); + + // FUNCTION: BETA10 0x100595a0 LegoTreeNodeData* GetData() { return m_data; } + void SetData(LegoTreeNodeData* p_data) { m_data = p_data; } + + // FUNCTION: BETA10 0x10012150 LegoU32 GetNumChildren() { return m_numChildren; } // FUNCTION: BETA10 0x10073370 void SetNumChildren(LegoU32 p_numChildren) { m_numChildren = p_numChildren; } + // FUNCTION: BETA10 0x10012180 LegoTreeNode* GetChild(LegoU32 p_i) { return m_children[p_i]; } + void SetChild(LegoU32 p_i, LegoTreeNode* p_child) { m_children[p_i] = p_child; } // FUNCTION: BETA10 0x100733a0 diff --git a/LEGO1/lego/sources/roi/legoroi.cpp b/LEGO1/lego/sources/roi/legoroi.cpp index a11436f3..94fed7c6 100644 --- a/LEGO1/lego/sources/roi/legoroi.cpp +++ b/LEGO1/lego/sources/roi/legoroi.cpp @@ -341,6 +341,7 @@ LegoResult LegoROI::FUN_100a8cb0(LegoAnimNodeData* p_data, LegoTime p_time, Matr } // FUNCTION: LEGO1 0x100a8ce0 +// FUNCTION: BETA10 0x1018a815 LegoROI* LegoROI::FindChildROI(const LegoChar* p_name, LegoROI* p_roi) { CompoundObject::iterator it; @@ -551,6 +552,18 @@ LegoResult LegoROI::GetTexture(LegoTextureInfo*& p_textureInfo) return FAILURE; } +// FUNCTION: LEGO1 0x100a9350 +// FUNCTION: BETA10 0x1018b25c +LegoResult LegoROI::FUN_100a9350(const LegoChar* p_color) +{ + MxFloat red, green, blue, alpha; + if (ColorAliasLookup(p_color, red, green, blue, alpha)) { + return FUN_100a9170(red, green, blue, alpha); + } + + return SUCCESS; +} + // FUNCTION: LEGO1 0x100a9410 // FUNCTION: BETA10 0x1018b324 LegoU32 LegoROI::FUN_100a9410( @@ -778,6 +791,13 @@ void LegoROI::SetName(const LegoChar* p_name) } } +// STUB: LEGO1 0x100a9dd0 +// STUB: BETA10 0x1018bfdb +void LegoROI::FUN_100a9dd0() +{ + // TODO +} + // FUNCTION: LEGO1 0x100a9e10 void LegoROI::SetDisplayBB(int p_displayBB) { diff --git a/LEGO1/lego/sources/roi/legoroi.h b/LEGO1/lego/sources/roi/legoroi.h index fb96b2a5..babb566a 100644 --- a/LEGO1/lego/sources/roi/legoroi.h +++ b/LEGO1/lego/sources/roi/legoroi.h @@ -38,12 +38,14 @@ class LegoROI : public ViewROI { LegoResult FUN_100a9170(LegoFloat p_red, LegoFloat p_green, LegoFloat p_blue, LegoFloat p_alpha); LegoResult FUN_100a9210(LegoTextureInfo* p_textureInfo); LegoResult GetTexture(LegoTextureInfo*& p_textureInfo); + LegoResult FUN_100a9350(const LegoChar* p_color); LegoU32 FUN_100a9410(Vector3& p_v1, Vector3& p_v2, float p_f1, float p_f2, Vector3& p_v3, LegoBool p_collideBox); void SetName(const LegoChar* p_name); float IntrinsicImportance() const override; // vtable+0x04 void UpdateWorldBoundingVolumes() override; // vtable+0x18 + void FUN_100a9dd0(); void SetDisplayBB(int p_displayBB); static LegoResult FUN_100a8cb0(LegoAnimNodeData* p_data, LegoTime p_time, Matrix4& p_matrix); diff --git a/LEGO1/library_msvc.h b/LEGO1/library_msvc.h index 5df6786c..b9a1359c 100644 --- a/LEGO1/library_msvc.h +++ b/LEGO1/library_msvc.h @@ -482,6 +482,7 @@ // __setmode_lk // LIBRARY: LEGO1 0x100d1ed0 +// LIBRARY: BETA10 0x1018ec70 // _strnicmp // LIBRARY: LEGO1 0x100d1fd0 @@ -682,6 +683,9 @@ // LIBRARY: BETA10 0x100f9780 // strlen +// LIBRARY: BETA10 0x100fa200 +// strcpy + // LIBRARY: BETA10 0x100f8a88 // operator new @@ -703,6 +707,9 @@ // LIBRARY: BETA10 0x100fe5a0 // abort +// LIBRARY: BETA10 0x100fa0e0 +// atof + // LIBRARY: BETA10 0x100ff82b // __ctrandisp1 diff --git a/LEGO1/mxgeometry/mxgeometry3d.h b/LEGO1/mxgeometry/mxgeometry3d.h index f73d5d8a..f09ea244 100644 --- a/LEGO1/mxgeometry/mxgeometry3d.h +++ b/LEGO1/mxgeometry/mxgeometry3d.h @@ -6,10 +6,15 @@ #include "realtime/vector.h" // VTABLE: LEGO1 0x100d4488 +// VTABLE: BETA10 0x101b84d0 // SIZE 0x14 class Mx3DPointFloat : public Vector3 { public: + // FUNCTION: LEGO1 0x1001d170 + // FUNCTION: BETA10 0x10011990 Mx3DPointFloat() : Vector3(m_elements) {} + + // FUNCTION: BETA10 0x10011870 Mx3DPointFloat(float p_x, float p_y, float p_z) : Vector3(m_elements) { m_elements[0] = p_x; @@ -24,9 +29,6 @@ class Mx3DPointFloat : public Vector3 { // FUNCTION: BETA10 0x100151e0 Mx3DPointFloat(const Vector3& p_other) : Vector3(m_elements) { EqualsImpl(p_other.m_data); } - // SYNTHETIC: LEGO1 0x1001d170 - // Mx3DPointFloat::Mx3DPointFloat - // FUNCTION: LEGO1 0x10003c10 virtual void operator=(const Vector3& p_impl) { EqualsImpl(p_impl.m_data); } // vtable+0x88 @@ -34,7 +36,9 @@ class Mx3DPointFloat : public Vector3 { float GetY() { return m_data[1]; } float GetZ() { return m_data[2]; } + // FUNCTION: BETA10 0x10013460 float& operator[](int idx) { return m_data[idx]; } + const float& operator[](int idx) const { return m_data[idx]; } // SYNTHETIC: LEGO1 0x10010c00 diff --git a/LEGO1/omni/include/mxdsobject.h b/LEGO1/omni/include/mxdsobject.h index 3bb0a3ff..eb80044b 100644 --- a/LEGO1/omni/include/mxdsobject.h +++ b/LEGO1/omni/include/mxdsobject.h @@ -69,7 +69,9 @@ class MxDSObject : public MxCore { // FUNCTION: BETA10 0x10017910 MxU32 GetObjectId() { return m_objectId; } + // FUNCTION: BETA10 0x10017940 const MxAtomId& GetAtomId() { return m_atomId; } + MxS16 GetUnknown24() { return m_unk0x24; } MxPresenter* GetUnknown28() { return m_unk0x28; } diff --git a/LEGO1/realtime/matrix.h b/LEGO1/realtime/matrix.h index 181acedf..8f9f63fe 100644 --- a/LEGO1/realtime/matrix.h +++ b/LEGO1/realtime/matrix.h @@ -183,6 +183,7 @@ class Matrix4 { }; // FUNCTION: LEGO1 0x10002550 +// FUNCTION: BETA10 0x100101c0 inline void Matrix4::ToQuaternion(Vector4& p_outQuat) { float trace = m_data[0][0] + m_data[1][1] + m_data[2][2]; @@ -223,6 +224,7 @@ inline void Matrix4::ToQuaternion(Vector4& p_outQuat) } // FUNCTION: LEGO1 0x10002710 +// FUNCTION: BETA10 0x10010550 inline int Matrix4::FromQuaternion(const Vector4& p_vec) { float len = p_vec.LenSquared(); diff --git a/LEGO1/realtime/orientableroi.h b/LEGO1/realtime/orientableroi.h index 61b61645..5e70aa5e 100644 --- a/LEGO1/realtime/orientableroi.h +++ b/LEGO1/realtime/orientableroi.h @@ -48,7 +48,9 @@ class OrientableROI : public ROI { // FUNCTION: BETA10 0x10011780 const float* GetWorldDirection() const { return m_local2world[2]; } + // FUNCTION: BETA10 0x1004aa70 const float* GetWorldUp() const { return m_local2world[1]; } + OrientableROI* GetParentROI() const { return m_parentROI; } void SetParentROI(OrientableROI* p_parentROI) { m_parentROI = p_parentROI; } diff --git a/LEGO1/realtime/realtime.cpp b/LEGO1/realtime/realtime.cpp index 829b2271..944b093c 100644 --- a/LEGO1/realtime/realtime.cpp +++ b/LEGO1/realtime/realtime.cpp @@ -3,6 +3,7 @@ #include // FUNCTION: LEGO1 0x100a5b40 +// FUNCTION: BETA10 0x10168127 void CalcLocalTransform(const Vector3& p_posVec, const Vector3& p_dirVec, const Vector3& p_upVec, Matrix4& p_outMatrix) { float x_axis[3], y_axis[3], z_axis[3]; diff --git a/LEGO1/realtime/vector.h b/LEGO1/realtime/vector.h index 3faa559e..6d0803b9 100644 --- a/LEGO1/realtime/vector.h +++ b/LEGO1/realtime/vector.h @@ -163,13 +163,18 @@ class Vector2 { return *this; } - // SYNTHETIC: BETA10 0x10013460 - // Vector3::operator[] - - // FUNCTION: BETA10 0x10010890 - float& operator[](int idx) { return m_data[idx]; } + // There is another candidate for `Vector2::operator[]` at BETA10 0x10010890, which is called from only three + // functions in BETA10: + // - `Matrix4::FromQuaternion()` + // - `Matrix4::ToQuaternion()` + // - `UnknownMx4DPointFloat::FUN_100040a0()` + // Maybe there is another subclass of `Vector4` involved that has the same VTABLE but a different `operator[]`. + // It is also interesting that `Matrix4::operator[]` is located right above at BETA10 0x10010860. // FUNCTION: BETA10 0x1001d140 + float& operator[](int idx) { return m_data[idx]; } + + // FUNCTION: BETA10 0x1001d170 const float& operator[](int idx) const { return m_data[idx]; } protected: @@ -189,6 +194,8 @@ class Vector3 : public Vector2 { // Example: LegoCameraController::GetWorldUp // Vector3 however is a class that can mutate its underlying source, making // initialization with a const source fundamentally incompatible. + + // FUNCTION: BETA10 0x100109a0 Vector3(const float* p_data) : Vector2((float*) p_data) {} // Note: virtual function overloads appear in the virtual table From 85ce10ab5024df73e9e70f1a3f0a603d39317af3 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 6 Oct 2024 16:50:34 +0200 Subject: [PATCH 04/10] Refactor various `operator[]` based on BETA10 (#1110) * Tidy up `operator[]` code * Add weird index operator for `FUN_1002ddc0` * Cleanup * Add Matrix4 BETA10 annotations, fix typo --------- Co-authored-by: jonschz --- LEGO1/lego/legoomni/include/legomodelpresenter.h | 1 + .../lego/legoomni/src/video/legoanimpresenter.cpp | 6 +++--- .../legoomni/src/video/legomodelpresenter.cpp | 12 +++++------- LEGO1/lego/sources/geom/legounkown100db7f4.h | 6 +++--- LEGO1/library_msvc.h | 12 ++++++++++++ LEGO1/mxgeometry/mxgeometry3d.h | 11 ++++++----- LEGO1/omni/include/mxdsaction.h | 6 +++--- LEGO1/realtime/matrix.h | 15 +++++++++++++++ LEGO1/realtime/vector.h | 13 +++++-------- 9 files changed, 53 insertions(+), 29 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legomodelpresenter.h b/LEGO1/lego/legoomni/include/legomodelpresenter.h index 29c069b5..7f9886a9 100644 --- a/LEGO1/lego/legoomni/include/legomodelpresenter.h +++ b/LEGO1/lego/legoomni/include/legomodelpresenter.h @@ -9,6 +9,7 @@ class LegoEntity; class MxDSChunk; // VTABLE: LEGO1 0x100d4e50 +// VTABLE: BETA10 0x101bcd88 // SIZE 0x6c class LegoModelPresenter : public MxVideoPresenter { public: diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 4537418a..28b2b2e1 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -789,9 +789,9 @@ void LegoAnimPresenter::StartingTickle() FUN_1006c8a0(TRUE); if (m_unk0x78 == NULL) { - if (fabs(m_action->GetDirection().GetX()) >= 0.00000047683716F || - fabs(m_action->GetDirection().GetY()) >= 0.00000047683716F || - fabs(m_action->GetDirection().GetZ()) >= 0.00000047683716F) { + if (fabs(m_action->GetDirection()[0]) >= 0.00000047683716F || + fabs(m_action->GetDirection()[1]) >= 0.00000047683716F || + fabs(m_action->GetDirection()[2]) >= 0.00000047683716F) { m_unk0x78 = new MxMatrix(); CalcLocalTransform(m_action->GetLocation(), m_action->GetDirection(), m_action->GetUp(), *m_unk0x78); } diff --git a/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp b/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp index d6100be7..a495f528 100644 --- a/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legomodelpresenter.cpp @@ -50,6 +50,7 @@ void LegoModelPresenter::Destroy(MxBool p_fromDestructor) } // FUNCTION: LEGO1 0x1007f6b0 +// FUNCTION: BETA10 0x1009845e MxResult LegoModelPresenter::CreateROI(MxDSChunk* p_chunk) { MxResult result = FAILURE; @@ -171,13 +172,9 @@ MxResult LegoModelPresenter::CreateROI(MxDSChunk* p_chunk) // Get scripted location, direction and up vectors CalcLocalTransform( - Mx3DPointFloat(m_action->GetLocation().GetX(), m_action->GetLocation().GetY(), m_action->GetLocation().GetZ()), - Mx3DPointFloat( - m_action->GetDirection().GetX(), - m_action->GetDirection().GetY(), - m_action->GetDirection().GetZ() - ), - Mx3DPointFloat(m_action->GetUp().GetX(), m_action->GetUp().GetY(), m_action->GetUp().GetZ()), + Mx3DPointFloat(m_action->GetLocation()[0], m_action->GetLocation()[1], m_action->GetLocation()[2]), + Mx3DPointFloat(m_action->GetDirection()[0], m_action->GetDirection()[1], m_action->GetDirection()[2]), + Mx3DPointFloat(m_action->GetUp()[0], m_action->GetUp()[1], m_action->GetUp()[2]), mat ); m_roi->UpdateTransformationRelativeToParent(mat); @@ -234,6 +231,7 @@ MxResult LegoModelPresenter::FUN_1007ff70( } // FUNCTION: LEGO1 0x10080050 +// FUNCTION: BETA10 0x100991c2 void LegoModelPresenter::ReadyTickle() { if (m_compositePresenter != NULL && m_compositePresenter->IsA("LegoEntityPresenter") && diff --git a/LEGO1/lego/sources/geom/legounkown100db7f4.h b/LEGO1/lego/sources/geom/legounkown100db7f4.h index 68f4c9f2..ae877b58 100644 --- a/LEGO1/lego/sources/geom/legounkown100db7f4.h +++ b/LEGO1/lego/sources/geom/legounkown100db7f4.h @@ -25,9 +25,9 @@ struct LegoUnknown100db7f4 : public LegoEdge { LegoResult FUN_1002ddc0(LegoWEEdge& p_f, Vector3& p_point) { if (p_f.IsEqual(*m_faceA)) { - p_point[0] = -m_unk0x28[0]; - p_point[1] = -m_unk0x28[1]; - p_point[2] = -m_unk0x28[2]; + p_point[0] = -m_unk0x28.index_operator(0); + p_point[1] = -m_unk0x28.index_operator(1); + p_point[2] = -m_unk0x28.index_operator(2); } else { // clang-format off diff --git a/LEGO1/library_msvc.h b/LEGO1/library_msvc.h index b9a1359c..b1e8afa6 100644 --- a/LEGO1/library_msvc.h +++ b/LEGO1/library_msvc.h @@ -710,6 +710,18 @@ // LIBRARY: BETA10 0x100fa0e0 // atof +// LIBRARY: BETA10 0x1005a9c0 +// fabs + +// LIBRARY: BETA10 0x1005a9f0 +// fabsf + +// LIBRARY: BETA10 0x100f9bb0 +// _fabs + +// LIBRARY: BETA10 0x100f9570 +// memset + // LIBRARY: BETA10 0x100ff82b // __ctrandisp1 diff --git a/LEGO1/mxgeometry/mxgeometry3d.h b/LEGO1/mxgeometry/mxgeometry3d.h index f09ea244..58d36198 100644 --- a/LEGO1/mxgeometry/mxgeometry3d.h +++ b/LEGO1/mxgeometry/mxgeometry3d.h @@ -32,14 +32,15 @@ class Mx3DPointFloat : public Vector3 { // FUNCTION: LEGO1 0x10003c10 virtual void operator=(const Vector3& p_impl) { EqualsImpl(p_impl.m_data); } // vtable+0x88 - float GetX() { return m_data[0]; } - float GetY() { return m_data[1]; } - float GetZ() { return m_data[2]; } - // FUNCTION: BETA10 0x10013460 float& operator[](int idx) { return m_data[idx]; } - const float& operator[](int idx) const { return m_data[idx]; } + // According to the PDB, BETA10 will not link this one if it is never used + // const float& operator[](int idx) const { return m_data[idx]; } + + // only used by LegoUnknown100db7f4::FUN_1002ddc0() for some unknown reason + // FUNCTION: BETA10 0x100373c0 + float& index_operator(int idx) { return m_data[idx]; } // SYNTHETIC: LEGO1 0x10010c00 // Mx3DPointFloat::operator= diff --git a/LEGO1/omni/include/mxdsaction.h b/LEGO1/omni/include/mxdsaction.h index 1820a68c..a68dbc51 100644 --- a/LEGO1/omni/include/mxdsaction.h +++ b/LEGO1/omni/include/mxdsaction.h @@ -86,13 +86,13 @@ class MxDSAction : public MxDSObject { void SetLoopCount(MxS32 p_loopCount) { m_loopCount = p_loopCount; } // FUNCTION: BETA10 0x1003db50 - Mx3DPointFloat& GetLocation() { return m_location; } + Vector3& GetLocation() { return m_location; } // FUNCTION: BETA10 0x1003db80 - Mx3DPointFloat& GetDirection() { return m_direction; } + Vector3& GetDirection() { return m_direction; } // FUNCTION: BETA10 0x1003dbb0 - Mx3DPointFloat& GetUp() { return m_up; } + Vector3& GetUp() { return m_up; } void SetLocation(const Vector3& p_location) { m_location = p_location; } void SetDirection(const Vector3& p_direction) { m_direction = p_direction; } diff --git a/LEGO1/realtime/matrix.h b/LEGO1/realtime/matrix.h index 8f9f63fe..729239e2 100644 --- a/LEGO1/realtime/matrix.h +++ b/LEGO1/realtime/matrix.h @@ -25,6 +25,7 @@ class Matrix4 { // in reverse order of appearance. // FUNCTION: LEGO1 0x10002320 + // FUNCTION: BETA10 0x1000fcb0 virtual void Equals(float (*p_data)[4]) { memcpy(m_data, p_data, sizeof(float) * 4 * 4); } // vtable+0x04 // FUNCTION: LEGO1 0x10002340 @@ -35,27 +36,35 @@ class Matrix4 { } // vtable+0x00 // FUNCTION: LEGO1 0x10002360 + // FUNCTION: BETA10 0x1000fd30 virtual void SetData(float (*p_data)[4]) { m_data = p_data; } // vtable+0x0c // FUNCTION: LEGO1 0x10002370 + // FUNCTION: BETA10 0x1000fd60 virtual void SetData(UnknownMatrixType& p_matrix) { m_data = p_matrix.m_data; } // vtable+0x08 // FUNCTION: LEGO1 0x10002380 + // FUNCTION: BETA10 0x1000fd90 virtual float (*GetData())[4] { return m_data; } // vtable+0x14 // FUNCTION: LEGO1 0x10002390 + // FUNCTION: BETA10 0x1000fdc0 virtual float (*GetData() const)[4] { return m_data; } // vtable+0x10 // FUNCTION: LEGO1 0x100023a0 + // FUNCTION: BETA10 0x1000fdf0 virtual float* Element(int p_row, int p_col) { return &m_data[p_row][p_col]; } // vtable+0x1c // FUNCTION: LEGO1 0x100023c0 + // FUNCTION: BETA10 0x1000fe30 virtual const float* Element(int p_row, int p_col) const { return &m_data[p_row][p_col]; } // vtable+0x18 // FUNCTION: LEGO1 0x100023e0 + // FUNCTION: BETA10 0x1000fe70 virtual void Clear() { memset(m_data, 0, 16 * sizeof(float)); } // vtable+0x20 // FUNCTION: LEGO1 0x100023f0 + // FUNCTION: BETA10 0x1000feb0 virtual void SetIdentity() { Clear(); @@ -66,9 +75,11 @@ class Matrix4 { } // vtable+0x24 // FUNCTION: LEGO1 0x10002420 + // FUNCTION: BETA10 0x1000ff20 virtual void operator=(const Matrix4& p_matrix) { Equals(p_matrix); } // vtable+0x28 // FUNCTION: LEGO1 0x10002430 + // FUNCTION: BETA10 0x1000ff50 virtual Matrix4& operator+=(float (*p_data)[4]) { for (int i = 0; i < 16; i++) { @@ -78,6 +89,7 @@ class Matrix4 { } // vtable+0x2c // FUNCTION: LEGO1 0x10002460 + // FUNCTION: BETA10 0x1000ffc0 virtual void TranslateBy(const float& p_x, const float& p_y, const float& p_z) { m_data[3][0] += p_x; @@ -86,6 +98,7 @@ class Matrix4 { } // vtable+0x30 // FUNCTION: LEGO1 0x100024a0 + // FUNCTION: BETA10 0x10010040 virtual void SetTranslation(const float& p_x, const float& p_y, const float& p_z) { m_data[3][0] = p_x; @@ -94,6 +107,7 @@ class Matrix4 { } // vtable+0x34 // FUNCTION: LEGO1 0x100024d0 + // FUNCTION: BETA10 0x100100a0 virtual void Product(float (*p_a)[4], float (*p_b)[4]) { float* cur = (float*) m_data; @@ -109,6 +123,7 @@ class Matrix4 { } // vtable+0x3c // FUNCTION: LEGO1 0x10002530 + // FUNCTION: BETA10 0x10010180 virtual void Product(const Matrix4& p_a, const Matrix4& p_b) { Product(p_a.m_data, p_b.m_data); } // vtable+0x38 inline virtual void ToQuaternion(Vector4& p_resultQuat); // vtable+0x40 diff --git a/LEGO1/realtime/vector.h b/LEGO1/realtime/vector.h index 6d0803b9..a09e62c4 100644 --- a/LEGO1/realtime/vector.h +++ b/LEGO1/realtime/vector.h @@ -163,14 +163,6 @@ class Vector2 { return *this; } - // There is another candidate for `Vector2::operator[]` at BETA10 0x10010890, which is called from only three - // functions in BETA10: - // - `Matrix4::FromQuaternion()` - // - `Matrix4::ToQuaternion()` - // - `UnknownMx4DPointFloat::FUN_100040a0()` - // Maybe there is another subclass of `Vector4` involved that has the same VTABLE but a different `operator[]`. - // It is also interesting that `Matrix4::operator[]` is located right above at BETA10 0x10010860. - // FUNCTION: BETA10 0x1001d140 float& operator[](int idx) { return m_data[idx]; } @@ -416,6 +408,11 @@ class Vector4 : public Vector3 { m_data[3] = p_value; } // vtable+0x84 + float& operator[](int idx) { return m_data[idx]; } + + // FUNCTION: BETA10 0x10010890 + const float& operator[](int idx) const { return m_data[idx]; } + friend class Mx4DPointFloat; }; From 974cd7ce7c727d21652c8383f0c58910fce87602 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Fri, 11 Oct 2024 20:21:33 +0200 Subject: [PATCH 05/10] Implement `LegoCarBuildAnimPresenter::FUN_10079160()` and others (#1111) * Implement `LegoCarBuildAnimPresenter::FUN_10079160()` and others * Address review comments --------- Co-authored-by: jonschz --- LEGO1/lego/legoomni/include/legocarbuild.h | 4 + .../legoomni/include/legocarbuildpresenter.h | 64 ++-- .../lego/legoomni/src/build/legocarbuild.cpp | 11 + .../src/build/legocarbuildpresenter.cpp | 283 ++++++++++++++++-- .../legoomni/src/video/legoanimpresenter.cpp | 1 + LEGO1/lego/sources/anim/legoanim.cpp | 9 + LEGO1/lego/sources/anim/legoanim.h | 24 +- LEGO1/lego/sources/misc/legotree.cpp | 1 + LEGO1/lego/sources/misc/legotree.h | 5 + LEGO1/library_msvc.h | 9 + LEGO1/omni/src/common/mxpresenter.cpp | 1 + 11 files changed, 360 insertions(+), 52 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocarbuild.h b/LEGO1/lego/legoomni/include/legocarbuild.h index c06d4e9e..8e9e3922 100644 --- a/LEGO1/lego/legoomni/include/legocarbuild.h +++ b/LEGO1/lego/legoomni/include/legocarbuild.h @@ -103,6 +103,7 @@ class LegoCarBuild : public LegoWorld { MxFloat p_param4[2] ); // vtable+0x80 + MxS16 GetPlacedPartCount(); void InitPresenters(); void FUN_10022f30(); void FUN_10023130(MxLong p_x, MxLong p_y); @@ -127,6 +128,9 @@ class LegoCarBuild : public LegoWorld { void FUN_10025e40(); MxS32 FUN_10025ee0(undefined4 p_param1); + // FUNCTION: BETA10 0x100735b0 + void SetUnknown0x258(LegoCarBuildAnimPresenter* p_unk0x258) { m_unk0x258 = p_unk0x258; } + // SYNTHETIC: LEGO1 0x10022a60 // LegoCarBuild::`scalar deleting destructor' diff --git a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h index b7b02d52..dffaa43b 100644 --- a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h +++ b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h @@ -11,9 +11,22 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { public: // SIZE 0x0c struct UnknownListEntry { - LegoChar* m_unk0x00; // 0x00 - LegoChar* m_unk0x04; // 0x04 - undefined m_unk0x08[4]; // 0x08 + // FUNCTION: LEGO1 0x100795c0 + // FUNCTION: BETA10 0x10073850 + UnknownListEntry() + { + m_name = NULL; + m_wiredName = NULL; + m_unk0x08 = 0; + } + + // variable name verified by BETA10 0x10071b56 + LegoChar* m_name; // 0x00 + + // variable name verified by BETA10 0x100719f0 + LegoChar* m_wiredName; // 0x04 + + undefined2 m_unk0x08; // 0x08 }; LegoCarBuildAnimPresenter(); @@ -46,18 +59,23 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { void PutFrame() override; // vtable+0x6c void FUN_10079050(MxS16 p_index); - void FUN_10079090(LegoChar* p_param1, LegoChar* p_param2); + void SwapNodesByName(LegoChar* p_param1, LegoChar* p_param2); void FUN_10079160(); void FUN_100795d0(LegoChar* p_param); void FUN_10079680(LegoChar* p_param); + LegoAnimNodeData* FindNodeDataByName(LegoTreeNode* p_treeNode, const LegoChar* p_name); + LegoTreeNode* FindNodeByName(LegoTreeNode* p_treeNode, const LegoChar* p_name); void RotateAroundYAxis(MxFloat p_angle); MxBool FUN_10079c30(const LegoChar* p_name); MxBool FUN_10079ca0(const LegoChar* p_name); + MxBool StringEqualsPlatform(const LegoChar* p_string); + MxBool StringEqualsShelf(const LegoChar* p_string); MxBool FUN_10079cf0(const LegoChar* p_string); // FUNCTION: BETA10 0x10070180 void SetUnknown0xbc(undefined2 p_unk0xbc) { m_unk0xbc = p_unk0xbc; } + MxBool StringEndsOnW(LegoChar* p_param); MxBool StringEndsOnYOrN(const LegoChar* p_string); const BoundingSphere& FUN_10079e20(); @@ -66,21 +84,29 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { // LegoCarBuildAnimPresenter::`scalar deleting destructor' private: - undefined2 m_unk0xbc; // 0xbc - MxS16 m_unk0xbe; // 0xbe - MxS16 m_unk0xc0; // 0xc0 - LegoAnimNodeData* m_unk0xc4; // 0xc4 - LegoAnim m_unk0xc8; // 0xc8 - MxMatrix m_unk0xe0; // 0xe0 - UnknownListEntry* m_unk0x128; // 0x128 - MxFloat m_unk0x12c; // 0x12c - undefined4 m_unk0x130; // 0x130 - undefined4 m_unk0x134; // 0x134 - undefined4 m_unk0x138; // 0x138 - undefined4 m_unk0x13c; // 0x13c - LegoEntity* m_unk0x140; // 0x140 - MxS32 m_unk0x144; // 0x144 - MxS32 m_unk0x148; // 0x148 + undefined2 m_unk0xbc; // 0xbc + + // variable name verified by BETA10 0x1007184f + MxS16 m_numberOfParts; // 0xbe + + // name derived from LegoVehicleBuildState, field 0x4f + MxS16 m_placedPartCount; // 0xc0 + + LegoAnimNodeData* m_unk0xc4; // 0xc4 + LegoAnim m_unk0xc8; // 0xc8 + MxMatrix m_unk0xe0; // 0xe0 + + // variable name verified by BETA10 0x100719f0 + UnknownListEntry* m_parts; // 0x128 + + MxFloat m_unk0x12c; // 0x12c + undefined4 m_unk0x130; // 0x130 + MxFloat m_unk0x134; // 0x134 + MxFloat m_unk0x138; // 0x138 + undefined4 m_unk0x13c; // 0x13c + LegoEntity* m_unk0x140; // 0x140 + MxS32 m_unk0x144; // 0x144 + MxS32 m_unk0x148; // 0x148 // name verified by BETA10 0x10070d63 LegoChar* m_mainSourceId; // 0x14c diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index b9865c1b..b8e7f93a 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -153,6 +153,17 @@ MxResult LegoCarBuild::Create(MxDSAction& p_dsAction) return result; } +// FUNCTION: LEGO1 0x10022cd0 +MxS16 LegoCarBuild::GetPlacedPartCount() +{ + if (m_buildState) { + return m_buildState->m_placedPartCount; + } + else { + return 0; + } +} + // FUNCTION: LEGO1 0x10022d10 // FUNCTION: BETA10 0x1006b27a void LegoCarBuild::InitPresenters() diff --git a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp index 5bd69f58..1c41a335 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp @@ -1,12 +1,16 @@ #include "legocarbuildpresenter.h" #include "3dmanager/lego3dmanager.h" +#include "legocarbuild.h" #include "legoentity.h" #include "legogamestate.h" +#include "legomain.h" #include "legoutils.h" #include "legovideomanager.h" +#include "legoworld.h" #include "misc.h" #include "mxautolock.h" +#include "mxcompositepresenter.h" #include "realtime/realtime.h" DECOMP_SIZE_ASSERT(LegoCarBuildAnimPresenter::UnknownListEntry, 0x0c) @@ -17,9 +21,9 @@ DECOMP_SIZE_ASSERT(LegoCarBuildAnimPresenter, 0x150) LegoCarBuildAnimPresenter::LegoCarBuildAnimPresenter() { m_unk0xbc = 0; - m_unk0xbe = 0; - m_unk0xc0 = 0; - m_unk0x128 = NULL; + m_numberOfParts = 0; + m_placedPartCount = 0; + m_parts = NULL; m_unk0xc4 = NULL; m_unk0x130 = 0; m_unk0x12c = 0; @@ -42,12 +46,12 @@ void LegoCarBuildAnimPresenter::RepeatingTickle() // FUNCTION: BETA10 0x1007091e LegoCarBuildAnimPresenter::~LegoCarBuildAnimPresenter() { - if (m_unk0x128) { - for (MxS16 i = 0; i < m_unk0xbe; i++) { - delete m_unk0x128[i].m_unk0x00; - delete m_unk0x128[i].m_unk0x04; + if (m_parts) { + for (MxS16 i = 0; i < m_numberOfParts; i++) { + delete m_parts[i].m_name; + delete m_parts[i].m_wiredName; } - delete[] m_unk0x128; + delete[] m_parts; } m_unk0xc8.GetRoot()->SetNumChildren(0); @@ -64,11 +68,52 @@ void LegoCarBuildAnimPresenter::PutFrame() // TODO } -// STUB: LEGO1 0x100788c0 -// STUB: BETA10 0x10070b56 +// FUNCTION: LEGO1 0x100788c0 +// FUNCTION: BETA10 0x10070b56 void LegoCarBuildAnimPresenter::ReadyTickle() { - // TODO + if (!m_anim) { + LegoAnimPresenter::ReadyTickle(); + + if (!m_currentWorld) { + return; + } + +#ifdef NDEBUG + if (!m_anim) { + return; + } +#else + assert(m_anim); +#endif + } + + m_unk0x140 = (LegoEntity*) m_currentWorld->Find("MxEntity", "Dunebld"); + + if (!m_unk0x140) { + m_unk0x140 = (LegoEntity*) m_currentWorld->Find("MxEntity", "Chptrbld"); + } + + if (!m_unk0x140) { + m_unk0x140 = (LegoEntity*) m_currentWorld->Find("MxEntity", "Jetbld"); + } + + if (!m_unk0x140) { + m_unk0x140 = (LegoEntity*) m_currentWorld->Find("MxEntity", "bldrace"); + } + + if (m_unk0x140) { + ((LegoCarBuild*) m_currentWorld)->SetUnknown0x258(this); + m_placedPartCount = ((LegoCarBuild*) m_currentWorld)->GetPlacedPartCount(); + SetUnknown0xbc(1); + m_previousTickleStates |= 1 << m_currentTickleState; + m_currentTickleState = e_starting; + m_compositePresenter->SendToCompositePresenter(Lego()); + } + else { + m_previousTickleStates |= 1 << m_currentTickleState; + m_currentTickleState = e_ready; + } } // FUNCTION: LEGO1 0x100789e0 @@ -88,25 +133,25 @@ void LegoCarBuildAnimPresenter::StreamingTickle() FUN_10079160(); if (GameState()->GetCurrentAct() == LegoGameState::e_act2) { - m_unk0xc0 = 10; + m_placedPartCount = 10; } MxS16 i; - for (i = 0; i < m_unk0xbe; i++) { - if (m_unk0xc0 == i) { - FUN_10079680(m_unk0x128[i].m_unk0x04); + for (i = 0; i < m_numberOfParts; i++) { + if (m_placedPartCount == i) { + FUN_10079680(m_parts[i].m_wiredName); } else { - FUN_100795d0(m_unk0x128[i].m_unk0x04); + FUN_100795d0(m_parts[i].m_wiredName); } - if (i < m_unk0xc0) { + if (i < m_placedPartCount) { FUN_10079050(i); - FUN_10079680(m_unk0x128[i].m_unk0x00); + FUN_10079680(m_parts[i].m_name); } - LegoChar* name = m_unk0x128[i].m_unk0x04; + LegoChar* name = m_parts[i].m_wiredName; if (name) { for (MxS32 j = 0; j <= m_roiMapSize; j++) { @@ -185,23 +230,125 @@ void LegoCarBuildAnimPresenter::EndAction() // FUNCTION: BETA10 0x1007151e void LegoCarBuildAnimPresenter::FUN_10079050(MxS16 p_index) { - FUN_10079090(m_unk0x128[p_index].m_unk0x04, m_unk0x128[p_index].m_unk0x00); - FUN_100795d0(m_unk0x128[p_index].m_unk0x04); + SwapNodesByName(m_parts[p_index].m_wiredName, m_parts[p_index].m_name); + FUN_100795d0(m_parts[p_index].m_wiredName); } -// STUB: LEGO1 0x10079090 -// STUB: BETA10 0x10071584 -void LegoCarBuildAnimPresenter::FUN_10079090(LegoChar* p_param1, LegoChar* p_param2) +// FUNCTION: LEGO1 0x10079090 +// FUNCTION: BETA10 0x10071584 +void LegoCarBuildAnimPresenter::SwapNodesByName(LegoChar* p_name1, LegoChar* p_name2) { - // TODO + char buffer[40]; + + if (stricmp(p_name1, p_name2) != 0) { + LegoAnimNodeData* node1 = FindNodeDataByName(m_anim->GetRoot(), p_name1); + LegoAnimNodeData* node2 = FindNodeDataByName(m_anim->GetRoot(), p_name2); + + strcpy(buffer, node1->GetName()); + strcpy(node1->GetName(), node2->GetName()); + strcpy(node2->GetName(), buffer); + + LegoU16 val1 = node1->GetUnknown0x20(); + node1->SetUnknown0x20(node2->GetUnknown0x20()); + node2->SetUnknown0x20(val1); + } } -// STUB: LEGO1 0x10079160 -// STUB: BETA10 0x1007165d +// FUNCTION: LEGO1 0x10079160 +// FUNCTION: BETA10 0x1007165d void LegoCarBuildAnimPresenter::FUN_10079160() { - // called from LegoCarBuildAnimPresenter::StreamingTickle() - // TODO + LegoTreeNode* root; + LegoAnimNodeData* data2; + MxS16 i; + MxS16 totalNodes = CountTotalTreeNodes(m_anim->GetRoot()); + LegoChar* name; + LegoTreeNode* destNode; + LegoAnimNodeData* destData; + LegoTreeNode** children; + + for (i = 0; i < totalNodes; i++) { + LegoAnimNodeData* data = (LegoAnimNodeData*) GetTreeNode(m_anim->GetRoot(), i)->GetData(); + name = data->GetName(); + + if (StringEqualsPlatform(name)) { + m_unk0xc4 = data; + if (m_unk0xc4->GetNumRotationKeys() == 0) { + LegoRotationKey* key = new LegoRotationKey(); + m_unk0xc4->SetNumRotationKeys(1); + m_unk0xc4->SetRotationKeys(key); + } + } + else { + if (StringEndsOnYOrN(name)) { + m_numberOfParts++; + } + else { + if (m_unk0x134 == 0.0f && StringEqualsShelf(name)) { + m_unk0x134 = m_anim->GetDuration(); + m_unk0x138 = m_unk0x134 / (data->GetNumTranslationKeys() - 1); + } + } + } + } + + assert(m_numberOfParts); + m_parts = new UnknownListEntry[m_numberOfParts]; + assert(m_parts); + + for (i = 0; i < totalNodes; i++) { + name = ((LegoAnimNodeData*) GetTreeNode(m_anim->GetRoot(), i)->GetData())->GetName(); + + strupr(name); + + if (StringEndsOnW(name)) { + m_parts[name[strlen(name) - 1] - 'A'].m_wiredName = new LegoChar[strlen(name) + 1]; + + // clang-format off + assert(m_parts[name[strlen(name)-1] - 'A'].m_wiredName); + // clang-format on + + strcpy(m_parts[name[strlen(name) - 1] - 'A'].m_wiredName, name); + } + } + + MxS16 counter = 0; + + for (i = 0; i < totalNodes; i++) { + name = ((LegoAnimNodeData*) GetTreeNode(m_anim->GetRoot(), i)->GetData())->GetName(); + if (StringEndsOnYOrN(name)) { + for (MxS16 ii = 0; ii < m_numberOfParts; ii++) { + if (strnicmp(m_parts[ii].m_wiredName, name, strlen(name) - 2) == 0) { + m_parts[ii].m_name = new LegoChar[strlen(name) + 1]; + assert(m_parts[ii].m_name); + strcpy(m_parts[ii].m_name, name); + + counter++; + if (m_numberOfParts == counter) { + break; + } + } + } + } + } + + destNode = new LegoTreeNode(); + assert(destNode); + destData = new LegoAnimNodeData(); + assert(destData); + destNode->SetData(destData); + + root = m_anim->GetRoot(); + data2 = (LegoAnimNodeData*) root->GetData(); + destData->FUN_100a0360(data2->GetName()); + + destNode->SetNumChildren(1); + children = new LegoTreeNode*; + assert(children); + *children = FindNodeByName(m_anim->GetRoot(), "PLATFORM"); + + destNode->SetChildren(children); + m_unk0xc8.SetRoot(destNode); } // STUB: LEGO1 0x100795d0 @@ -218,6 +365,57 @@ void LegoCarBuildAnimPresenter::FUN_10079680(LegoChar* p_param) // TODO } +// FUNCTION: LEGO1 0x100796b0 +// FUNCTION: BETA10 0x10071f3c +LegoAnimNodeData* LegoCarBuildAnimPresenter::FindNodeDataByName(LegoTreeNode* p_treeNode, const LegoChar* p_name) +{ + LegoAnimNodeData* data = NULL; + + if (p_treeNode) { + data = (LegoAnimNodeData*) p_treeNode->GetData(); + + if (stricmp(data->GetName(), p_name) == 0) { + return data; + } + + for (MxS32 i = 0; i < p_treeNode->GetNumChildren(); i++) { + data = FindNodeDataByName(p_treeNode->GetChildren()[i], p_name); + + if (data) { + return data; + } + } + } + + return NULL; +} + +// FUNCTION: LEGO1 0x10079720 +// FUNCTION: BETA10 0x10071fec +LegoTreeNode* LegoCarBuildAnimPresenter::FindNodeByName(LegoTreeNode* p_treeNode, const LegoChar* p_name) +{ + LegoAnimNodeData* data = NULL; + LegoTreeNode* node = NULL; + + if (p_treeNode) { + data = (LegoAnimNodeData*) p_treeNode->GetData(); + + if (stricmp(data->GetName(), p_name) == 0) { + return p_treeNode; + } + + for (MxS32 i = 0; i < p_treeNode->GetNumChildren(); i++) { + node = FindNodeByName(p_treeNode->GetChildren()[i], p_name); + + if (node) { + return node; + } + } + } + + return NULL; +} + // FUNCTION: LEGO1 0x10079920 // FUNCTION: BETA10 0x1007225d void LegoCarBuildAnimPresenter::RotateAroundYAxis(MxFloat p_angle) @@ -251,6 +449,20 @@ void LegoCarBuildAnimPresenter::RotateAroundYAxis(MxFloat p_angle) } } +// FUNCTION: LEGO1 0x10079b20 +// FUNCTION: BETA10 0x100724fa +MxBool LegoCarBuildAnimPresenter::StringEqualsPlatform(const LegoChar* p_string) +{ + return stricmp(p_string, "PLATFORM") == 0; +} + +// FUNCTION: LEGO1 0x10079b40 +// FUNCTION: BETA10 0x10072534 +MxBool LegoCarBuildAnimPresenter::StringEndsOnW(LegoChar* p_param) +{ + return (p_param[strlen(p_param) - 2] == 'W') || (p_param[strlen(p_param) - 2] == 'w'); +} + // FUNCTION: LEGO1 0x10079b80 // FUNCTION: BETA10 0x1007258f MxBool LegoCarBuildAnimPresenter::StringEndsOnYOrN(const LegoChar* p_string) @@ -259,6 +471,13 @@ MxBool LegoCarBuildAnimPresenter::StringEndsOnYOrN(const LegoChar* p_string) (p_string[strlen(p_string) - 2] == 'Y') || (p_string[strlen(p_string) - 2] == 'y'); } +// FUNCTION: LEGO1 0x10079bf0 +// FUNCTION: BETA10 0x10072624 +MxBool LegoCarBuildAnimPresenter::StringEqualsShelf(const LegoChar* p_string) +{ + return strnicmp(p_string, "SHELF", strlen("SHELF")) == 0; +} + // STUB: LEGO1 0x10079c30 // STUB: BETA10 0x100726a6 MxBool LegoCarBuildAnimPresenter::FUN_10079c30(const LegoChar* p_name) @@ -271,8 +490,8 @@ MxBool LegoCarBuildAnimPresenter::FUN_10079c30(const LegoChar* p_name) // FUNCTION: BETA10 0x10072740 MxBool LegoCarBuildAnimPresenter::FUN_10079ca0(const LegoChar* p_name) { - for (MxS16 i = 0; i < m_unk0xc0; i++) { - if (strcmpi(p_name, m_unk0x128[i].m_unk0x00) == 0) { + for (MxS16 i = 0; i < m_placedPartCount; i++) { + if (strcmpi(p_name, m_parts[i].m_name) == 0) { return TRUE; } } @@ -292,5 +511,5 @@ MxBool LegoCarBuildAnimPresenter::FUN_10079cf0(const LegoChar* p_string) const BoundingSphere& LegoCarBuildAnimPresenter::FUN_10079e20() { LegoROI* roi = m_unk0x140->GetROI(); - return roi->FindChildROI(m_unk0x128[m_unk0xc0].m_unk0x04, roi)->GetWorldBoundingSphere(); + return roi->FindChildROI(m_parts[m_placedPartCount].m_wiredName, roi)->GetWorldBoundingSphere(); } diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index 28b2b2e1..fcffcba6 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -750,6 +750,7 @@ MxResult LegoAnimPresenter::FUN_1006b140(LegoROI* p_roi) } // FUNCTION: LEGO1 0x1006b550 +// FUNCTION: BETA10 0x10050a9c void LegoAnimPresenter::ReadyTickle() { m_currentWorld = CurrentWorld(); diff --git a/LEGO1/lego/sources/anim/legoanim.cpp b/LEGO1/lego/sources/anim/legoanim.cpp index 77801f61..0683fa7b 100644 --- a/LEGO1/lego/sources/anim/legoanim.cpp +++ b/LEGO1/lego/sources/anim/legoanim.cpp @@ -279,6 +279,7 @@ LegoResult LegoTranslationKey::Read(LegoStorage* p_storage) } // FUNCTION: LEGO1 0x1009faa0 +// FUNCTION: BETA10 0x1017e2b3 LegoRotationKey::LegoRotationKey() { m_angle = 1.0F; @@ -356,6 +357,7 @@ LegoResult LegoScaleKey::Read(LegoStorage* p_storage) } // FUNCTION: LEGO1 0x1009fcf0 +// FUNCTION: BETA10 0x1017e71a LegoAnimNodeData::LegoAnimNodeData() { m_numTranslationKeys = 0; @@ -494,6 +496,13 @@ LegoResult LegoAnimNodeData::Write(LegoStorage* p_storage) return SUCCESS; } +// STUB: LEGO1 0x100a0360 +// STUB: BETA10 0x1017f1e5 +void LegoAnimNodeData::FUN_100a0360(LegoChar* p_param) +{ + // TODO +} + // FUNCTION: LEGO1 0x100a03c0 LegoResult LegoAnimNodeData::CreateLocalTransform(LegoFloat p_time, Matrix4& p_matrix) { diff --git a/LEGO1/lego/sources/anim/legoanim.h b/LEGO1/lego/sources/anim/legoanim.h index 62816ba9..1782034c 100644 --- a/LEGO1/lego/sources/anim/legoanim.h +++ b/LEGO1/lego/sources/anim/legoanim.h @@ -145,17 +145,37 @@ class LegoAnimNodeData : public LegoTreeNodeData { LegoResult Read(LegoStorage* p_storage) override; // vtable+0x04 LegoResult Write(LegoStorage* p_storage) override; // vtable+0x08 + void FUN_100a0360(LegoChar* p_param); LegoResult CreateLocalTransform(LegoFloat p_time, Matrix4& p_matrix); LegoBool FUN_100a0990(LegoFloat p_time); // FUNCTION: BETA10 0x100595d0 - const LegoChar* GetName() { return m_name; } + LegoChar* GetName() { return m_name; } + + // FUNCTION: BETA10 0x10073780 + LegoU16 GetNumTranslationKeys() { return m_numTranslationKeys; } + + // FUNCTION: BETA10 0x100737b0 + LegoU16 GetNumRotationKeys() { return m_numRotationKeys; } + + // FUNCTION: BETA10 0x100737e0 + void SetNumRotationKeys(LegoU16 p_numRotationKeys) { m_numRotationKeys = p_numRotationKeys; } + + // FUNCTION: BETA10 0x10073810 + void SetRotationKeys(LegoRotationKey* p_keys) + { + m_rotationKeys = p_keys; + m_rotationIndex = 0; + } LegoU32 GetTranslationIndex() { return m_translationIndex; } LegoU32 GetRotationIndex() { return m_rotationIndex; } LegoU32 GetScaleIndex() { return m_scaleIndex; } LegoU32 GetMorphIndex() { return m_morphIndex; } + + // FUNCTION: BETA10 0x1005abc0 LegoU16 GetUnknown0x20() { return m_unk0x20; } + LegoU16 GetUnknown0x22() { return m_unk0x22; } // FUNCTION: BETA10 0x10073b80 @@ -165,6 +185,8 @@ class LegoAnimNodeData : public LegoTreeNodeData { void SetRotationIndex(LegoU32 p_rotationIndex) { m_rotationIndex = p_rotationIndex; } void SetScaleIndex(LegoU32 p_scaleIndex) { m_scaleIndex = p_scaleIndex; } void SetMorphIndex(LegoU32 p_morphIndex) { m_morphIndex = p_morphIndex; } + + // FUNCTION: BETA10 0x10059600 void SetUnknown0x20(LegoU16 p_unk0x20) { m_unk0x20 = p_unk0x20; } // FUNCTION: BETA10 0x1005f2e0 diff --git a/LEGO1/lego/sources/misc/legotree.cpp b/LEGO1/lego/sources/misc/legotree.cpp index e7f24513..e77e8222 100644 --- a/LEGO1/lego/sources/misc/legotree.cpp +++ b/LEGO1/lego/sources/misc/legotree.cpp @@ -8,6 +8,7 @@ DECOMP_SIZE_ASSERT(LegoTreeNode, 0x010) DECOMP_SIZE_ASSERT(LegoTree, 0x08) // FUNCTION: LEGO1 0x10099d60 +// FUNCTION: BETA10 0x10187dd0 LegoTreeNode::LegoTreeNode() { m_data = NULL; diff --git a/LEGO1/lego/sources/misc/legotree.h b/LEGO1/lego/sources/misc/legotree.h index fea8d5ec..c444a31e 100644 --- a/LEGO1/lego/sources/misc/legotree.h +++ b/LEGO1/lego/sources/misc/legotree.h @@ -9,6 +9,7 @@ class LegoStorage; // VTABLE: LEGO1 0x100db778 +// VTABLE: BETA10 0x101c37f4 // SIZE 0x04 class LegoTreeNodeData { public: @@ -36,6 +37,7 @@ class LegoTreeNode { // FUNCTION: BETA10 0x100595a0 LegoTreeNodeData* GetData() { return m_data; } + // FUNCTION: BETA10 0x100736f0 void SetData(LegoTreeNodeData* p_data) { m_data = p_data; } // FUNCTION: BETA10 0x10012150 @@ -52,6 +54,7 @@ class LegoTreeNode { // FUNCTION: BETA10 0x100733a0 LegoTreeNode** GetChildren() { return m_children; } + // FUNCTION: BETA10 0x10073720 void SetChildren(LegoTreeNode** p_children) { m_children = p_children; } // SYNTHETIC: LEGO1 0x10099d80 @@ -73,7 +76,9 @@ class LegoTree { // FUNCTION: BETA10 0x100121b0 LegoTreeNode* GetRoot() { return m_root; } + // FUNCTION: BETA10 0x10073750 void SetRoot(LegoTreeNode* p_root) { m_root = p_root; } + virtual LegoResult Read(LegoStorage* p_storage); // vtable+0x04 virtual LegoResult Write(LegoStorage* p_storage); // vtable+0x08 diff --git a/LEGO1/library_msvc.h b/LEGO1/library_msvc.h index b1e8afa6..49e2efbd 100644 --- a/LEGO1/library_msvc.h +++ b/LEGO1/library_msvc.h @@ -725,4 +725,13 @@ // LIBRARY: BETA10 0x100ff82b // __ctrandisp1 +// LIBRARY: BETA10 0x100f8a92 +// operator delete + +// LIBRARY: BETA10 0x1018ed70 +// _strupr + +// LIBRARY: BETA10 0x1001d1a0 +// `vector constructor iterator' + #endif diff --git a/LEGO1/omni/src/common/mxpresenter.cpp b/LEGO1/omni/src/common/mxpresenter.cpp index 214f35c6..8731baa3 100644 --- a/LEGO1/omni/src/common/mxpresenter.cpp +++ b/LEGO1/omni/src/common/mxpresenter.cpp @@ -112,6 +112,7 @@ void MxPresenter::ParseExtra() } // FUNCTION: LEGO1 0x100b5120 +// FUNCTION: BETA10 0x1012e5d8 void MxPresenter::SendToCompositePresenter(MxOmni* p_omni) { if (m_compositePresenter) { From 8446a7ffa1983e053507876a9c61706a1abbe190 Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 13 Oct 2024 23:31:15 +0200 Subject: [PATCH 06/10] Add new script to compare the stack layout (#1112) * Add new script to debug the stack layout * fix small error in script --------- Co-authored-by: jonschz --- tools/README.md | 2 + .../lego_util/pdb_extraction.py | 3 +- tools/isledecomp/isledecomp/compare/core.py | 4 +- tools/isledecomp/isledecomp/compare/diff.py | 8 +- tools/stackcmp/stackcmp.py | 364 ++++++++++++++++++ 5 files changed, 377 insertions(+), 4 deletions(-) create mode 100644 tools/stackcmp/stackcmp.py diff --git a/tools/README.md b/tools/README.md index 98eb1f8d..d2ff3dc9 100644 --- a/tools/README.md +++ b/tools/README.md @@ -175,6 +175,8 @@ The example usages below assume that the current working directory is this repos * Generate an HTML report: `py -m tools.reccmp.reccmp --html output.html legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` * Create a base file for diffs: `py -m tools.reccmp.reccmp --json base.json --silent legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` * Diff against a base file: `py -m tools.reccmp.reccmp --diff base.json legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` +* [`stackcmp`](/tools/stackcmp): Compares the stack layout for a given function that almost matches. + * e.g. `py -m tools.stackcmp.stackcmp legobin/BETA10.DLL build_debug/LEGO1.DLL build_debug/LEGO1.pdb . 0x1007165d` * [`roadmap`](/tools/roadmap): Compares symbol locations in an original binary with the same symbol locations of a recompiled binary * [`verexp`](/tools/verexp): Verifies exports by comparing the exports of the original DLL and the recompiled DLL * [`vtable`](/tools/vtable): Asserts virtual table correctness by comparing a recompiled binary with the original diff --git a/tools/ghidra_scripts/lego_util/pdb_extraction.py b/tools/ghidra_scripts/lego_util/pdb_extraction.py index 6a0db6f8..3325c159 100644 --- a/tools/ghidra_scripts/lego_util/pdb_extraction.py +++ b/tools/ghidra_scripts/lego_util/pdb_extraction.py @@ -96,7 +96,8 @@ def get_func_signature(self, fn: SymbolsEntry) -> Optional[FunctionSignature]: stack_symbols: list[CppStackOrRegisterSymbol] = [] - # for some unexplained reason, the reported stack is offset by 4 when this flag is set + # for some unexplained reason, the reported stack is offset by 4 when this flag is set. + # Note that this affects the arguments (ebp + ...) but not the function stack (ebp - ...) stack_offset_delta = -4 if fn.frame_pointer_present else 0 for symbol in fn.stack_symbols: diff --git a/tools/isledecomp/isledecomp/compare/core.py b/tools/isledecomp/isledecomp/compare/core.py index c44f3987..f2aebdcf 100644 --- a/tools/isledecomp/isledecomp/compare/core.py +++ b/tools/isledecomp/isledecomp/compare/core.py @@ -15,7 +15,7 @@ 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 .diff import combined_diff, CombinedDiffOutput from .lines import LinesDb @@ -29,7 +29,7 @@ class DiffReport: orig_addr: int recomp_addr: int name: str - udiff: Optional[List[str]] = None + udiff: Optional[CombinedDiffOutput] = None ratio: float = 0.0 is_effective_match: bool = False is_stub: bool = False diff --git a/tools/isledecomp/isledecomp/compare/diff.py b/tools/isledecomp/isledecomp/compare/diff.py index ad453191..a328c997 100644 --- a/tools/isledecomp/isledecomp/compare/diff.py +++ b/tools/isledecomp/isledecomp/compare/diff.py @@ -2,7 +2,13 @@ from typing import Dict, List, Tuple CombinedDiffInput = List[Tuple[str, str]] -CombinedDiffOutput = List[Tuple[str, List[Dict[str, Tuple[str, str]]]]] +# from inner to outer: +# Tuple[str, ...]: either (orig_addr, instruction, recomp_addr) or (addr, instruction) +# List[...]: a contiguous block of instructions, all matching or all mismatching +# Dict[...]: either {"both": List[...]} or {"orig": [...], "recomp": [...]} +# Tuple[str, List[...]]: One contiguous part of the diff (without skipping matching code) +# List[...]: The list of all the contiguous diffs of a given function +CombinedDiffOutput = List[Tuple[str, List[Dict[str, List[Tuple[str, ...]]]]]] def combined_diff( diff --git a/tools/stackcmp/stackcmp.py b/tools/stackcmp/stackcmp.py new file mode 100644 index 00000000..9aa02f2a --- /dev/null +++ b/tools/stackcmp/stackcmp.py @@ -0,0 +1,364 @@ +from dataclasses import dataclass +import re +import logging +import os +import argparse +import struct +from typing import Dict, List, NamedTuple, Optional, Set, Tuple + +from isledecomp import Bin +from isledecomp.compare import Compare as IsleCompare +from isledecomp.compare.diff import CombinedDiffOutput +from isledecomp.cvdump.symbols import SymbolsEntry +import colorama + +# pylint: disable=duplicate-code # misdetects a code duplication with reccmp + +colorama.just_fix_windows_console() + +CHECK_ICON = f"{colorama.Fore.GREEN}✓{colorama.Style.RESET_ALL}" +SWAP_ICON = f"{colorama.Fore.YELLOW}⇄{colorama.Style.RESET_ALL}" +ERROR_ICON = f"{colorama.Fore.RED}✗{colorama.Style.RESET_ALL}" +UNCLEAR_ICON = f"{colorama.Fore.BLUE}?{colorama.Style.RESET_ALL}" + + +STACK_ENTRY_REGEX = re.compile( + r"(?Pe[sb]p)\s(?P[+-])\s(?P(0x)?[0-9a-f]+)(?![0-9a-f])" +) + + +@dataclass +class StackSymbol: + name: str + data_type: str + + +@dataclass +class StackRegisterOffset: + register: str + offset: int + symbol: Optional[StackSymbol] = None + + def __str__(self) -> str: + first_part = ( + f"{self.register} + {self.offset:#04x}" + if self.offset > 0 + else f"{self.register} - {-self.offset:#04x}" + ) + second_part = f" {self.symbol.name}" if self.symbol else "" + return first_part + second_part + + def __hash__(self) -> int: + return hash(self.register) + self.offset + + def copy(self) -> "StackRegisterOffset": + return StackRegisterOffset(self.register, self.offset, self.symbol) + + def __eq__(self, other: "StackRegisterOffset"): + return self.register == other.register and self.offset == other.offset + + +class StackPair(NamedTuple): + orig: StackRegisterOffset + recomp: StackRegisterOffset + + +StackPairs = Set[StackPair] + + +@dataclass +class Warnings: + structural_mismatches_present: bool = False + error_map_not_bijective: bool = False + + +def extract_stack_offset_from_instruction( + instruction: str, +) -> StackRegisterOffset | None: + match = STACK_ENTRY_REGEX.search(instruction) + if not match: + return None + offset = int(match.group("sign") + match.group("offset"), 16) + return StackRegisterOffset(match.group("register"), offset) + + +def analyze_diff( + diff: Dict[str, List[Tuple[str, ...]]], warnings: Warnings +) -> StackPairs: + stack_pairs: StackPairs = set() + if "both" in diff: + # get the matching stack entries + for line in diff["both"]: + # 0 = orig addr, 1 = instruction, 2 = reccmp addr + instruction = line[1] + + if match := extract_stack_offset_from_instruction(instruction): + logging.debug("stack match: %s", match) + # need a copy for recomp because we might add a debug symbol to it + stack_pairs.add(StackPair(match, match.copy())) + elif any(x in instruction for x in ["ebp", "esp"]): + logging.debug("not a stack offset: %s", instruction) + + else: + orig = diff["orig"] + recomp = diff["recomp"] + if len(orig) != len(recomp): + if orig: + mismatch_location = f"orig={orig[0][0]}" + else: + mismatch_location = f"recomp={recomp[0][0]}" + logging.error( + "Structural mismatch at %s:\n%s", + mismatch_location, + print_structural_mismatch(orig, recomp), + ) + warnings.structural_mismatches_present = True + return set() + + for orig_line, recomp_line in zip(orig, recomp): + if orig_match := extract_stack_offset_from_instruction(orig_line[1]): + recomp_match = extract_stack_offset_from_instruction(recomp_line[1]) + + if not recomp_match: + logging.error( + "Mismatching line structure at orig=%s:\n%s", + orig_line[0], + print_structural_mismatch(orig, recomp), + ) + # not recoverable, whole block has a structural mismatch + warnings.structural_mismatches_present = True + return set() + + stack_pair = StackPair(orig_match, recomp_match) + + logging.debug( + "stack match, wrong order: %s vs %s", stack_pair[0], stack_pair[1] + ) + stack_pairs.add(stack_pair) + + elif any(x in orig_line[1] for x in ["ebp", "esp"]): + logging.debug("not a stack offset: %s", orig_line[1]) + + return stack_pairs + + +def print_bijective_match(left: str, right: str, exact: bool): + icon = CHECK_ICON if exact else SWAP_ICON + print(f"{icon}{colorama.Style.RESET_ALL} {left}: {right}") + + +def print_non_bijective_match(left: str, right: str): + print(f"{ERROR_ICON} {left}: {right}") + + +def print_structural_mismatch( + orig: List[Tuple[str, ...]], recomp: List[Tuple[str, ...]] +) -> str: + orig_str = "\n".join(f"-{x[1]}" for x in orig) if orig else "-" + recomp_str = "\n".join(f"+{x[1]}" for x in recomp) if recomp else "+" + return f"{colorama.Fore.RED}{orig_str}\n{colorama.Fore.GREEN}{recomp_str}\n{colorama.Style.RESET_ALL}" + + +def format_list_of_offsets(offsets: List[StackRegisterOffset]) -> str: + return str([str(x) for x in offsets]) + + +def compare_function_stacks(udiff: CombinedDiffOutput, fn_symbol: SymbolsEntry): + warnings = Warnings() + + # consists of pairs (orig, recomp) + # don't use a dict because we can have m:n relations + stack_pairs: StackPairs = set() + + for block in udiff: + # block[0] is e.g. "@@ -0x10071662,60 +0x10031368,60 @@" + for diff in block[1]: + stack_pairs = stack_pairs.union(analyze_diff(diff, warnings)) + + # Note that the 'Frame Ptr Present' property is not relevant to the stack below `ebp`, + # but only to entries above (i.e. the function arguments on the stack). + # See also pdb_extraction.py. + + stack_symbols: Dict[int, StackSymbol] = {} + + for symbol in fn_symbol.stack_symbols: + if symbol.symbol_type == "S_BPREL32": + # convert hex to signed 32 bit integer + hex_bytes = bytes.fromhex(symbol.location[1:-1]) + stack_offset = struct.unpack(">l", hex_bytes)[0] + + stack_symbols[stack_offset] = StackSymbol( + symbol.name, + symbol.data_type, + ) + + for _, recomp in stack_pairs: + if recomp.register == "ebp": + recomp.symbol = stack_symbols.get(recomp.offset) + elif recomp.register == "esp": + logging.debug( + "Matching esp offsets to debug symbols is not implemented right now" + ) + + print("\nOrdered by original stack (left=orig, right=recomp):") + + all_orig_offsets = set(x.orig.offset for x in stack_pairs) + + for orig_offset in sorted(all_orig_offsets): + orig = next(x.orig for x in stack_pairs if x.orig.offset == orig_offset) + recomps = [x.recomp for x in stack_pairs if x.orig == orig] + + if len(recomps) == 1: + recomp = recomps[0] + print_bijective_match(str(orig), str(recomp), exact=orig == recomp) + else: + print_non_bijective_match(str(orig), format_list_of_offsets(recomps)) + warnings.error_map_not_bijective = True + + # Show offsets from the debug symbols that we have not encountered in the diff + all_recomp_offsets = set(x.recomp.offset for x in stack_pairs).union( + stack_symbols.keys() + ) + + print("\nOrdered by recomp stack (left=orig, right=recomp):") + for recomp_offset in sorted(all_recomp_offsets): + recomp = next( + (x.recomp for x in stack_pairs if x.recomp.offset == recomp_offset), None + ) + + if recomp is None: + # The offset only appears in the debug symbols. + # The legend below explains why this can happen. + stack_offset = StackRegisterOffset( + "ebp", recomp_offset, stack_symbols[recomp_offset] + ) + print(f"{UNCLEAR_ICON} not seen: {stack_offset}") + continue + + origs = [x.orig for x in stack_pairs if x.recomp == recomp] + + if len(origs) == 1: + # 1:1 clean match + print_bijective_match(str(origs[0]), str(recomp), origs[0] == recomp) + else: + print_non_bijective_match(format_list_of_offsets(origs), str(recomp)) + warnings.error_map_not_bijective = True + + print( + "\nLegend:\n" + + f"{SWAP_ICON} : This stack variable matches 1:1, but the order of variables is not correct.\n" + + f"{ERROR_ICON} : This stack variable matches multiple variables in the other binary.\n" + + f"{UNCLEAR_ICON} : This stack variable did not appear in the diff. It either matches or only appears in structural mismatches.\n" + ) + + if warnings.error_map_not_bijective: + print( + "ERROR: The stack variables of original and recomp are not in a 1:1 correspondence, " + + "suggesting that the logic in the recomp is incorrect." + ) + elif warnings.structural_mismatches_present: + print( + "WARNING: Original and recomp have at least one structural discrepancy, " + + "so the comparison of stack variables might be incomplete. " + + "The structural mismatches above need to be checked manually." + ) + + +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( + "address", + metavar="", + type=virtual_address, + help="The original file's offset of the function to be analyzed", + ) + + 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.loglevel != logging.DEBUG: + # Mute logger events from compare engine + logging.getLogger("isledecomp.compare.core").setLevel(logging.CRITICAL) + 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() + + match = isle_compare.compare_address(args.address) + if match is None: + print(f"Failed to find a match at address 0x{args.address:x}") + return + + assert match.udiff is not None + + function_data = next( + ( + y + for y in isle_compare.cvdump_analysis.nodes + if y.addr == match.recomp_addr + ), + None, + ) + assert function_data is not None + assert function_data.symbol_entry is not None + + compare_function_stacks(match.udiff, function_data.symbol_entry) + + +if __name__ == "__main__": + raise SystemExit(main()) From 91205be0316e8f98c083c74bc3bfb0d0eae95a51 Mon Sep 17 00:00:00 2001 From: MS Date: Thu, 17 Oct 2024 23:17:00 -0400 Subject: [PATCH 07/10] Handle duplicate thunks in BETA10 (#1113) --- tools/isledecomp/isledecomp/compare/core.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/tools/isledecomp/isledecomp/compare/core.py b/tools/isledecomp/isledecomp/compare/core.py index f2aebdcf..66e923d3 100644 --- a/tools/isledecomp/isledecomp/compare/core.py +++ b/tools/isledecomp/isledecomp/compare/core.py @@ -428,11 +428,6 @@ def _match_thunks(self): points at a function we have already matched, we can find the matching thunk in recomp because it points to the same place.""" - # Turn this one inside out for easy lookup - recomp_thunks = { - func_addr: thunk_addr for (thunk_addr, func_addr) in self.recomp_bin.thunks - } - # Mark all recomp thunks first. This allows us to use their name # when we sanitize the asm. for recomp_thunk, recomp_addr in self.recomp_bin.thunks: @@ -442,17 +437,29 @@ def _match_thunks(self): self._db.create_recomp_thunk(recomp_thunk, recomp_func.name) + # Thunks may be non-unique, so use a list as dict value when + # inverting the list of tuples from self.recomp_bin. + recomp_thunks = {} + for thunk_addr, func_addr in self.recomp_bin.thunks: + recomp_thunks.setdefault(func_addr, []).append(thunk_addr) + + # Now match the thunks from orig where we can. for orig_thunk, orig_addr in self.orig_bin.thunks: orig_func = self._db.get_by_orig(orig_addr) if orig_func is None: continue # Check whether the thunk destination is a matched symbol - recomp_thunk = recomp_thunks.get(orig_func.recomp_addr) - if recomp_thunk is None: + if orig_func.recomp_addr not in recomp_thunks: self._db.create_orig_thunk(orig_thunk, orig_func.name) continue + # If there are multiple thunks, they are already in v.addr order. + # Pop the earliest one and match it. + recomp_thunk = recomp_thunks[orig_func.recomp_addr].pop(0) + if len(recomp_thunks[orig_func.recomp_addr]) == 0: + del recomp_thunks[orig_func.recomp_addr] + self._db.set_function_pair(orig_thunk, recomp_thunk) # Don't compare thunk functions for now. The comparison isn't From b5fee6b240a310dc780f0f0d39abbe28141d1b4c Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Sun, 20 Oct 2024 23:00:44 +0200 Subject: [PATCH 08/10] Complete `LegoCarBuildAnimPresenter` (#1114) * Complete `LegoCarBuildAnimPresenter` * fix CI errors * Drive-by BETA10 fixes * Address review comments --------- Co-authored-by: jonschz --- .../legoomni/include/legocarbuildpresenter.h | 15 +- LEGO1/lego/legoomni/include/legoextraactor.h | 2 +- LEGO1/lego/legoomni/include/legoraceactor.h | 1 + .../lego/legoomni/src/build/legocarbuild.cpp | 8 +- .../src/build/legocarbuildpresenter.cpp | 140 ++++++++++++++++-- .../legoomni/src/video/legoanimpresenter.cpp | 1 + LEGO1/lego/sources/anim/legoanim.cpp | 4 +- LEGO1/lego/sources/anim/legoanim.h | 22 ++- LEGO1/lego/sources/misc/legotree.cpp | 1 + LEGO1/lego/sources/misc/legotree.h | 4 +- LEGO1/omni/include/mxpresenter.h | 1 + LEGO1/omni/include/mxstreamchunk.h | 3 + LEGO1/omni/include/mxvideopresenter.h | 3 + LEGO1/omni/src/common/mxmediapresenter.cpp | 2 + LEGO1/omni/src/common/mxpresenter.cpp | 1 + LEGO1/omni/src/video/mxvideopresenter.cpp | 3 + LEGO1/realtime/roi.h | 2 + 17 files changed, 188 insertions(+), 25 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h index dffaa43b..52135bac 100644 --- a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h +++ b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h @@ -9,6 +9,10 @@ // SIZE 0x150 class LegoCarBuildAnimPresenter : public LegoAnimPresenter { public: + enum { + c_bit1 = 0x01 + }; + // SIZE 0x0c struct UnknownListEntry { // FUNCTION: LEGO1 0x100795c0 @@ -67,7 +71,8 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { LegoTreeNode* FindNodeByName(LegoTreeNode* p_treeNode, const LegoChar* p_name); void RotateAroundYAxis(MxFloat p_angle); MxBool FUN_10079c30(const LegoChar* p_name); - MxBool FUN_10079ca0(const LegoChar* p_name); + MxBool PartIsPlaced(const LegoChar* p_name); + void FUN_10079a90(); MxBool StringEqualsPlatform(const LegoChar* p_string); MxBool StringEqualsShelf(const LegoChar* p_string); MxBool FUN_10079cf0(const LegoChar* p_string); @@ -84,7 +89,9 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { // LegoCarBuildAnimPresenter::`scalar deleting destructor' private: - undefined2 m_unk0xbc; // 0xbc + void Beta10Inline0x100733d0(); + + MxU16 m_unk0xbc; // 0xbc // variable name verified by BETA10 0x1007184f MxS16 m_numberOfParts; // 0xbe @@ -100,10 +107,10 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { UnknownListEntry* m_parts; // 0x128 MxFloat m_unk0x12c; // 0x12c - undefined4 m_unk0x130; // 0x130 + MxFloat m_unk0x130; // 0x130 MxFloat m_unk0x134; // 0x134 MxFloat m_unk0x138; // 0x138 - undefined4 m_unk0x13c; // 0x13c + MxLong m_unk0x13c; // 0x13c LegoEntity* m_unk0x140; // 0x140 MxS32 m_unk0x144; // 0x144 MxS32 m_unk0x148; // 0x148 diff --git a/LEGO1/lego/legoomni/include/legoextraactor.h b/LEGO1/lego/legoomni/include/legoextraactor.h index dfb880f8..f6295efa 100644 --- a/LEGO1/lego/legoomni/include/legoextraactor.h +++ b/LEGO1/lego/legoomni/include/legoextraactor.h @@ -6,7 +6,7 @@ // VTABLE: LEGO1 0x100d6c00 LegoAnimActor // VTABLE: LEGO1 0x100d6c10 LegoPathActor // VTABLE: LEGO1 0x100d6cdc LegoExtraActor -// VTABLE: BETA10 0x101bc2b8 LegoAnimActor +// VTABLE: BETA10 0x101bc2b8 LegoPathActor // SIZE 0x1dc class LegoExtraActor : public virtual LegoAnimActor { public: diff --git a/LEGO1/lego/legoomni/include/legoraceactor.h b/LEGO1/lego/legoomni/include/legoraceactor.h index 1fb1b28c..6fa69eff 100644 --- a/LEGO1/lego/legoomni/include/legoraceactor.h +++ b/LEGO1/lego/legoomni/include/legoraceactor.h @@ -35,6 +35,7 @@ class LegoRaceActor : public virtual LegoAnimActor { MxResult VTable0x94(LegoPathActor* p_actor, MxBool p_bool) override; // vtable+0x94 // FUNCTION: LEGO1 0x10014aa0 + // FUNCTION: BETA10 0x100ca038 virtual MxResult FUN_10014aa0() { return SUCCESS; } // SYNTHETIC: LEGO1 0x10012c10 diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index b8e7f93a..4d5fcbdb 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -218,7 +218,7 @@ void LegoCarBuild::FUN_10022f30() FUN_10024f70(FALSE); FUN_100250e0(FALSE); - if (m_unk0x258->FUN_10079ca0(m_unk0x110->GetName())) { + if (m_unk0x258->PartIsPlaced(m_unk0x110->GetName())) { m_PlaceBrick_Sound->Enable(FALSE); m_PlaceBrick_Sound->Enable(TRUE); } @@ -408,7 +408,7 @@ MxResult LegoCarBuild::Tickle() } if (m_unk0x110) { - if (m_unk0x258->FUN_10079ca0(m_unk0x110->GetName())) { + if (m_unk0x258->PartIsPlaced(m_unk0x110->GetName())) { FUN_10022f30(); } } @@ -697,7 +697,7 @@ undefined4 LegoCarBuild::FUN_100244e0(MxLong p_x, MxLong p_y) FUN_100250e0(TRUE); } - if (m_unk0x100 == 5 && m_unk0x258->FUN_10079ca0(m_unk0x110->GetName())) { + if (m_unk0x100 == 5 && m_unk0x258->PartIsPlaced(m_unk0x110->GetName())) { m_unk0x2d4 = TRUE; } else { @@ -706,7 +706,7 @@ undefined4 LegoCarBuild::FUN_100244e0(MxLong p_x, MxLong p_y) FUN_10025450(); VTable0x70(); - if (m_unk0x258->FUN_10079ca0(m_unk0x110->GetName())) { + if (m_unk0x258->PartIsPlaced(m_unk0x110->GetName())) { if (m_unk0x100 != 5) { m_unk0x250[0] += m_unk0x290[0] - m_unk0x298[0]; m_unk0x250[1] += m_unk0x290[1] - m_unk0x298[1]; diff --git a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp index 1c41a335..71d989dd 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp @@ -11,6 +11,8 @@ #include "misc.h" #include "mxautolock.h" #include "mxcompositepresenter.h" +#include "mxmisc.h" +#include "mxtimer.h" #include "realtime/realtime.h" DECOMP_SIZE_ASSERT(LegoCarBuildAnimPresenter::UnknownListEntry, 0x0c) @@ -62,10 +64,75 @@ LegoCarBuildAnimPresenter::~LegoCarBuildAnimPresenter() } } -// STUB: LEGO1 0x10078790 +// FUNCTION: BETA10 0x100733d0 +inline void LegoCarBuildAnimPresenter::Beta10Inline0x100733d0() +{ + MxLong time = Timer()->GetTime(); + MxLong bvar5; + + if (m_unk0x13c < time) { + bvar5 = FALSE; + + // I have no idea why this conditional is so convoluted + if (m_unk0x13c & c_bit1) { + bvar5 = TRUE; + m_unk0x13c = time + 400; + } + else { + m_unk0x13c = time + 200; + } + + if (bvar5) { + m_unk0x13c &= ~c_bit1; + } + else { + m_unk0x13c |= c_bit1; + } + + if (m_placedPartCount < m_numberOfParts) { + + const LegoChar* wiredName = m_parts[m_placedPartCount].m_wiredName; + + if (wiredName) { + for (MxS32 i = 1; i <= m_roiMapSize; i++) { + LegoROI* roi = m_roiMap[i]; + + if (roi) { + const LegoChar* name = roi->GetName(); + + if (name && stricmp(wiredName, name) == 0) { + if (bvar5) { + roi->SetVisibility(TRUE); + } + else { + roi->SetVisibility(FALSE); + } + } + } + } + } + } + } +} + +// FUNCTION: LEGO1 0x10078790 +// FUNCTION: BETA10 0x10070ab1 void LegoCarBuildAnimPresenter::PutFrame() { - // TODO + switch (m_unk0xbc) { + case 0: + break; + case 2: + FUN_10079a90(); + case 1: + if (m_unk0x140->GetROI()) { + FUN_1006b9a0(m_anim, m_unk0x12c, NULL); + } + default: + break; + } + + Beta10Inline0x100733d0(); } // FUNCTION: LEGO1 0x100788c0 @@ -351,18 +418,42 @@ void LegoCarBuildAnimPresenter::FUN_10079160() m_unk0xc8.SetRoot(destNode); } -// STUB: LEGO1 0x100795d0 -// STUB: BETA10 0x10071d96 +// FUNCTION: LEGO1 0x100795d0 +// FUNCTION: BETA10 0x10071d96 void LegoCarBuildAnimPresenter::FUN_100795d0(LegoChar* p_param) { - // TODO + LegoAnimNodeData* data = FindNodeDataByName(m_anim->GetRoot(), p_param); + + if (data) { + LegoMorphKey* oldMorphKeys = data->GetMorphKeys(); + + LegoMorphKey* newHideKey = new LegoMorphKey(); + assert(newHideKey); + + newHideKey->SetTime(0); + newHideKey->SetUnknown0x08(FALSE); + + data->SetNumMorphKeys(1); + data->SetMorphKeys(newHideKey); + + delete oldMorphKeys; + } } -// STUB: LEGO1 0x10079680 -// STUB: BETA10 0x10071ec5 +// FUNCTION: LEGO1 0x10079680 +// FUNCTION: BETA10 0x10071ec5 void LegoCarBuildAnimPresenter::FUN_10079680(LegoChar* p_param) { - // TODO + LegoAnimNodeData* data = FindNodeDataByName(m_anim->GetRoot(), p_param); + + if (data) { + LegoMorphKey* oldMorphKeys = data->GetMorphKeys(); + + data->SetNumMorphKeys(0); + data->SetMorphKeys(NULL); + + delete oldMorphKeys; + } } // FUNCTION: LEGO1 0x100796b0 @@ -449,6 +540,25 @@ void LegoCarBuildAnimPresenter::RotateAroundYAxis(MxFloat p_angle) } } +// FUNCTION: LEGO1 0x10079a90 +// FUNCTION: BETA10 0x10072412 +void LegoCarBuildAnimPresenter::FUN_10079a90() +{ + if (m_unk0x12c >= m_unk0x134) { + m_unk0x130 = 0.0; + m_unk0x12c = m_unk0x130; + m_unk0xbc = 1; + } + else if (m_unk0x12c >= m_unk0x138 + m_unk0x130) { + m_unk0x130 = m_unk0x138 + m_unk0x130; + m_unk0x12c = m_unk0x130; + m_unk0xbc = 1; + } + else { + m_unk0x12c = m_unk0x138 / 10.0f + m_unk0x12c; + } +} + // FUNCTION: LEGO1 0x10079b20 // FUNCTION: BETA10 0x100724fa MxBool LegoCarBuildAnimPresenter::StringEqualsPlatform(const LegoChar* p_string) @@ -478,17 +588,21 @@ MxBool LegoCarBuildAnimPresenter::StringEqualsShelf(const LegoChar* p_string) return strnicmp(p_string, "SHELF", strlen("SHELF")) == 0; } -// STUB: LEGO1 0x10079c30 -// STUB: BETA10 0x100726a6 +// FUNCTION: LEGO1 0x10079c30 +// FUNCTION: BETA10 0x100726a6 MxBool LegoCarBuildAnimPresenter::FUN_10079c30(const LegoChar* p_name) { - // TODO - return FALSE; + if (PartIsPlaced(p_name)) { + return FALSE; + } + + return m_placedPartCount < m_numberOfParts && + strnicmp(p_name, m_parts[m_placedPartCount].m_name, strlen(p_name) - 3) == 0; } // FUNCTION: LEGO1 0x10079ca0 // FUNCTION: BETA10 0x10072740 -MxBool LegoCarBuildAnimPresenter::FUN_10079ca0(const LegoChar* p_name) +MxBool LegoCarBuildAnimPresenter::PartIsPlaced(const LegoChar* p_name) { for (MxS16 i = 0; i < m_placedPartCount; i++) { if (strcmpi(p_name, m_parts[i].m_name) == 0) { diff --git a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp index fcffcba6..7b08d2f8 100644 --- a/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp +++ b/LEGO1/lego/legoomni/src/video/legoanimpresenter.cpp @@ -904,6 +904,7 @@ void LegoAnimPresenter::FUN_1006b900(LegoAnim* p_anim, MxLong p_time, Matrix4* p } // FUNCTION: LEGO1 0x1006b9a0 +// FUNCTION: BETA10 0x1005118b void LegoAnimPresenter::FUN_1006b9a0(LegoAnim* p_anim, MxLong p_time, Matrix4* p_matrix) { LegoTreeNode* root = p_anim->GetRoot(); diff --git a/LEGO1/lego/sources/anim/legoanim.cpp b/LEGO1/lego/sources/anim/legoanim.cpp index 0683fa7b..9b5e560a 100644 --- a/LEGO1/lego/sources/anim/legoanim.cpp +++ b/LEGO1/lego/sources/anim/legoanim.cpp @@ -221,10 +221,11 @@ LegoResult LegoAnimScene::FUN_1009f490(LegoFloat p_time, Matrix4& p_matrix) } // FUNCTION: LEGO1 0x1009f900 +// FUNCTION: BETA10 0x1017df90 LegoAnimKey::LegoAnimKey() { - m_flags = 0; m_time = 0; + m_flags = 0; } // FUNCTION: LEGO1 0x1009f910 @@ -907,6 +908,7 @@ undefined4 LegoAnim::GetActorUnknown0x04(LegoU32 p_index) } // FUNCTION: LEGO1 0x100a0f60 +// FUNCTION: BETA10 0x1018027c LegoMorphKey::LegoMorphKey() { m_unk0x08 = 0; diff --git a/LEGO1/lego/sources/anim/legoanim.h b/LEGO1/lego/sources/anim/legoanim.h index 1782034c..5da5ffd6 100644 --- a/LEGO1/lego/sources/anim/legoanim.h +++ b/LEGO1/lego/sources/anim/legoanim.h @@ -19,7 +19,11 @@ class LegoAnimKey { LegoAnimKey(); LegoResult Read(LegoStorage* p_storage); LegoFloat GetTime() { return m_time; } - void SetTime(LegoFloat p_time) { m_time = p_time; } + + // The different types (LegoFloat vs. MxS32) are correct according to BETA10 + // FUNCTION: BETA10 0x100738a0 + void SetTime(MxS32 p_time) { m_time = p_time; } + LegoU32 TestBit1() { return m_flags & c_bit1; } LegoU32 TestBit2() { return m_flags & c_bit2; } LegoU32 TestBit3() { return m_flags & c_bit3; } @@ -120,6 +124,9 @@ class LegoMorphKey : public LegoAnimKey { LegoResult Read(LegoStorage* p_storage); LegoBool GetUnknown0x08() { return m_unk0x08; } + // FUNCTION: BETA10 0x100738d0 + void SetUnknown0x08(LegoBool p_unk0x08) { m_unk0x08 = p_unk0x08; } + protected: LegoBool m_unk0x08; // 0x08 }; @@ -186,6 +193,19 @@ class LegoAnimNodeData : public LegoTreeNodeData { void SetScaleIndex(LegoU32 p_scaleIndex) { m_scaleIndex = p_scaleIndex; } void SetMorphIndex(LegoU32 p_morphIndex) { m_morphIndex = p_morphIndex; } + // FUNCTION: BETA10 0x10073930 + LegoMorphKey* GetMorphKeys() { return m_morphKeys; } + + // FUNCTION: BETA10 0x10073960 + void SetMorphKeys(LegoMorphKey* p_morphKeys) + { + m_morphKeys = p_morphKeys; + m_morphIndex = 0; + } + + // FUNCTION: BETA10 0x10073900 + void SetNumMorphKeys(LegoU16 p_numMorphKeys) { m_numMorphKeys = p_numMorphKeys; } + // FUNCTION: BETA10 0x10059600 void SetUnknown0x20(LegoU16 p_unk0x20) { m_unk0x20 = p_unk0x20; } diff --git a/LEGO1/lego/sources/misc/legotree.cpp b/LEGO1/lego/sources/misc/legotree.cpp index e77e8222..f9fb626a 100644 --- a/LEGO1/lego/sources/misc/legotree.cpp +++ b/LEGO1/lego/sources/misc/legotree.cpp @@ -17,6 +17,7 @@ LegoTreeNode::LegoTreeNode() } // FUNCTION: LEGO1 0x10099da0 +// FUNCTION: BETA10 0x10187e10 LegoTreeNode::~LegoTreeNode() { if (m_data) { diff --git a/LEGO1/lego/sources/misc/legotree.h b/LEGO1/lego/sources/misc/legotree.h index c444a31e..9a4eeb3e 100644 --- a/LEGO1/lego/sources/misc/legotree.h +++ b/LEGO1/lego/sources/misc/legotree.h @@ -9,11 +9,11 @@ class LegoStorage; // VTABLE: LEGO1 0x100db778 -// VTABLE: BETA10 0x101c37f4 // SIZE 0x04 class LegoTreeNodeData { public: LegoTreeNodeData() {} + // FUNCTION: LEGO1 0x1009a0e0 virtual ~LegoTreeNodeData() {} @@ -28,6 +28,7 @@ class LegoTreeNodeData { }; // VTABLE: LEGO1 0x100db764 +// VTABLE: BETA10 0x101c37f4 // SIZE 0x10 class LegoTreeNode { public: @@ -58,6 +59,7 @@ class LegoTreeNode { void SetChildren(LegoTreeNode** p_children) { m_children = p_children; } // SYNTHETIC: LEGO1 0x10099d80 + // SYNTHETIC: BETA10 0x10188cb0 // LegoTreeNode::`scalar deleting destructor' protected: diff --git a/LEGO1/omni/include/mxpresenter.h b/LEGO1/omni/include/mxpresenter.h index a30897d1..3358c832 100644 --- a/LEGO1/omni/include/mxpresenter.h +++ b/LEGO1/omni/include/mxpresenter.h @@ -98,6 +98,7 @@ class MxPresenter : public MxCore { virtual void EndAction(); // vtable+0x40 // FUNCTION: LEGO1 0x1000bf90 + // FUNCTION: BETA10 0x10054a50 virtual void SetTickleState(TickleState p_tickleState) { ProgressTickleState(p_tickleState); } // vtable+0x44 // FUNCTION: LEGO1 0x1000bfb0 diff --git a/LEGO1/omni/include/mxstreamchunk.h b/LEGO1/omni/include/mxstreamchunk.h index f71f78e7..fb72ee82 100644 --- a/LEGO1/omni/include/mxstreamchunk.h +++ b/LEGO1/omni/include/mxstreamchunk.h @@ -7,10 +7,13 @@ class MxDSBuffer; class MxStreamListMxDSSubscriber; // VTABLE: LEGO1 0x100dc2a8 +// VTABLE: BETA10 0x101c1d20 // SIZE 0x20 class MxStreamChunk : public MxDSChunk { public: + // FUNCTION: BETA10 0x10134420 MxStreamChunk() : m_buffer(NULL) {} + ~MxStreamChunk() override; // FUNCTION: LEGO1 0x100b1fe0 diff --git a/LEGO1/omni/include/mxvideopresenter.h b/LEGO1/omni/include/mxvideopresenter.h index 98c9948b..30d65354 100644 --- a/LEGO1/omni/include/mxvideopresenter.h +++ b/LEGO1/omni/include/mxvideopresenter.h @@ -52,14 +52,17 @@ class MxVideoPresenter : public MxMediaPresenter { MxBool IsHit(MxS32 p_x, MxS32 p_y) override; // vtable+0x50 // FUNCTION: LEGO1 0x1000c700 + // FUNCTION: BETA10 0x10054a80 virtual void LoadHeader(MxStreamChunk* p_chunk) {} // vtable+0x5c // FUNCTION: LEGO1 0x1000c710 + // FUNCTION: BETA10 0x10054aa0 virtual void CreateBitmap() {} // vtable+0x60 virtual void NextFrame(); // vtable+0x64 // FUNCTION: LEGO1 0x1000c720 + // FUNCTION: BETA10 0x10054ac0 virtual void LoadFrame(MxStreamChunk* p_chunk) {} // vtable+0x68 virtual void PutFrame(); // vtable+0x6c diff --git a/LEGO1/omni/src/common/mxmediapresenter.cpp b/LEGO1/omni/src/common/mxmediapresenter.cpp index 462b8987..0a1b96d9 100644 --- a/LEGO1/omni/src/common/mxmediapresenter.cpp +++ b/LEGO1/omni/src/common/mxmediapresenter.cpp @@ -229,6 +229,7 @@ void MxMediaPresenter::DoneTickle() } // FUNCTION: LEGO1 0x100b5f10 +// FUNCTION: BETA10 0x101366e9 void MxMediaPresenter::LoopChunk(MxStreamChunk* p_chunk) { MxStreamChunk* chunk = new MxStreamChunk; @@ -243,6 +244,7 @@ void MxMediaPresenter::LoopChunk(MxStreamChunk* p_chunk) } // FUNCTION: LEGO1 0x100b6030 +// FUNCTION: BETA10 0x10136814 void MxMediaPresenter::Enable(MxBool p_enable) { if (IsEnabled() != p_enable) { diff --git a/LEGO1/omni/src/common/mxpresenter.cpp b/LEGO1/omni/src/common/mxpresenter.cpp index 8731baa3..ea6e5978 100644 --- a/LEGO1/omni/src/common/mxpresenter.cpp +++ b/LEGO1/omni/src/common/mxpresenter.cpp @@ -263,6 +263,7 @@ MxEntity* MxPresenter::CreateEntity(const char* p_defaultName) } // FUNCTION: LEGO1 0x100b54c0 +// FUNCTION: BETA10 0x1012ebaf MxBool MxPresenter::IsEnabled() { return this->m_action && this->m_action->GetFlags() & MxDSAction::c_enabled; diff --git a/LEGO1/omni/src/video/mxvideopresenter.cpp b/LEGO1/omni/src/video/mxvideopresenter.cpp index b9fc6b0d..c92e5dc4 100644 --- a/LEGO1/omni/src/video/mxvideopresenter.cpp +++ b/LEGO1/omni/src/video/mxvideopresenter.cpp @@ -176,6 +176,7 @@ void MxVideoPresenter::Destroy(MxBool p_fromDestructor) } // FUNCTION: LEGO1 0x100b28b0 +// FUNCTION: BETA10 0x101389c1 void MxVideoPresenter::NextFrame() { MxStreamChunk* chunk = NextChunk(); @@ -191,6 +192,7 @@ void MxVideoPresenter::NextFrame() } // FUNCTION: LEGO1 0x100b2900 +// FUNCTION: BETA10 0x10138a3a MxBool MxVideoPresenter::IsHit(MxS32 p_x, MxS32 p_y) { MxDSAction* action = GetAction(); @@ -563,6 +565,7 @@ void MxVideoPresenter::EndAction() } // FUNCTION: LEGO1 0x100b3280 +// FUNCTION: BETA10 0x101397c0 MxResult MxVideoPresenter::PutData() { AUTOLOCK(m_criticalSection); diff --git a/LEGO1/realtime/roi.h b/LEGO1/realtime/roi.h index 3d638feb..224c9607 100644 --- a/LEGO1/realtime/roi.h +++ b/LEGO1/realtime/roi.h @@ -118,6 +118,8 @@ class ROI { const CompoundObject* GetComp() const { return comp; } unsigned char GetVisibility() { return m_visible; } + + // FUNCTION: BETA10 0x10011720 void SetVisibility(unsigned char p_visible) { m_visible = p_visible; } // SYNTHETIC: LEGO1 0x100a5d60 From c38e157fdb3d0862040b7c03fb24810d50b35fab Mon Sep 17 00:00:00 2001 From: jonschz <17198703+jonschz@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:04:01 +0200 Subject: [PATCH 09/10] Implement LegoCarBuild functions, refactor BackgroundAudioManager (#1117) * Implement LegoCarBuild functions, refactor BackgroundAudioManager * Refactor LegoVehicleBuildState::m_animationState to enum --------- Co-authored-by: jonschz --- LEGO1/lego/legoomni/include/legocarbuild.h | 26 +-- .../legoomni/include/legocarbuildpresenter.h | 5 +- LEGO1/lego/legoomni/include/legomain.h | 3 + .../include/mxbackgroundaudiomanager.h | 17 +- .../legoomni/src/actors/islepathactor.cpp | 2 +- .../legoomni/src/actors/jukeboxentity.cpp | 2 +- LEGO1/lego/legoomni/src/actors/radio.cpp | 4 +- .../src/audio/mxbackgroundaudiomanager.cpp | 51 ++++-- .../lego/legoomni/src/build/legocarbuild.cpp | 162 ++++++++++++++++-- .../src/build/legocarbuildpresenter.cpp | 9 +- LEGO1/lego/legoomni/src/common/misc.cpp | 6 +- LEGO1/lego/legoomni/src/main/legomain.cpp | 1 + LEGO1/lego/legoomni/src/main/scripts.cpp | 1 + .../legoomni/src/paths/legopathstruct.cpp | 2 +- LEGO1/omni/include/mxdssound.h | 1 + LEGO1/omni/include/mxnotificationparam.h | 1 + LEGO1/omni/include/mxsoundmanager.h | 3 +- 17 files changed, 242 insertions(+), 54 deletions(-) diff --git a/LEGO1/lego/legoomni/include/legocarbuild.h b/LEGO1/lego/legoomni/include/legocarbuild.h index 8e9e3922..0afb0824 100644 --- a/LEGO1/lego/legoomni/include/legocarbuild.h +++ b/LEGO1/lego/legoomni/include/legocarbuild.h @@ -16,6 +16,14 @@ class MxActionNotificationParam; // SIZE 0x50 class LegoVehicleBuildState : public LegoState { public: + enum AnimationState { + e_unknown0 = 0, + e_entering = 1, + e_unknown2 = 2, + e_cutscene = 3, + e_exiting = 6 + }; + LegoVehicleBuildState(const char* p_classType); // FUNCTION: LEGO1 0x10025ff0 @@ -46,15 +54,11 @@ class LegoVehicleBuildState : public LegoState { // * LegoJetskiBuildState MxString m_className; // 0x38 - // Known States: - // * 1 == enter(ing) build screen - // * 3 == cutscene/dialogue - // * 6 == exit(ing) build screen - MxU32 m_animationState; // 0x48 - undefined m_unk0x4c; // 0x4c - MxBool m_unk0x4d; // 0x4d - MxBool m_unk0x4e; // 0x4e - MxU8 m_placedPartCount; // 0x4f + AnimationState m_animationState; // 0x48 + undefined m_unk0x4c; // 0x4c + MxBool m_unk0x4d; // 0x4d + MxBool m_unk0x4e; // 0x4e + MxU8 m_placedPartCount; // 0x4f }; typedef LegoVehicleBuildState LegoRaceCarBuildState; @@ -114,7 +118,7 @@ class LegoCarBuild : public LegoWorld { undefined4 FUN_100246e0(MxLong p_x, MxLong p_y); MxS32 FUN_10024850(MxLong p_x, MxLong p_y); undefined4 FUN_10024890(LegoEventNotificationParam* p_param); - void FUN_10024c20(LegoEventNotificationParam* p_param); + undefined4 FUN_10024c20(LegoEventNotificationParam* p_param); void FUN_10024ef0(); void FUN_10024f50(); void FUN_10024f70(MxBool p_enabled); @@ -198,7 +202,7 @@ class LegoCarBuild : public LegoWorld { // variable name verified by BETA10 0x1006cba7 LegoGameState::Area m_destLocation; // 0x334 - undefined4 m_unk0x338; // 0x338 + MxPresenter* m_unk0x338; // 0x338 MxControlPresenter* m_unk0x33c; // 0x33c undefined4 m_unk0x340; // 0x340 undefined4 m_unk0x344; // 0x344 diff --git a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h index 52135bac..b1233ada 100644 --- a/LEGO1/lego/legoomni/include/legocarbuildpresenter.h +++ b/LEGO1/lego/legoomni/include/legocarbuildpresenter.h @@ -75,7 +75,8 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { void FUN_10079a90(); MxBool StringEqualsPlatform(const LegoChar* p_string); MxBool StringEqualsShelf(const LegoChar* p_string); - MxBool FUN_10079cf0(const LegoChar* p_string); + MxBool StringEndsOnY(const LegoChar* p_string); + MxBool StringEndsOnZero(const LegoChar* p_string); // FUNCTION: BETA10 0x10070180 void SetUnknown0xbc(undefined2 p_unk0xbc) { m_unk0xbc = p_unk0xbc; } @@ -117,6 +118,8 @@ class LegoCarBuildAnimPresenter : public LegoAnimPresenter { // name verified by BETA10 0x10070d63 LegoChar* m_mainSourceId; // 0x14c + + friend class LegoCarBuild; }; #endif // LEGOCARBUILDPRESENTER_H diff --git a/LEGO1/lego/legoomni/include/legomain.h b/LEGO1/lego/legoomni/include/legomain.h index 75df917d..182500c1 100644 --- a/LEGO1/lego/legoomni/include/legomain.h +++ b/LEGO1/lego/legoomni/include/legomain.h @@ -134,7 +134,10 @@ class LegoOmni : public MxOmni { LegoPlantManager* GetPlantManager() { return m_plantManager; } LegoAnimationManager* GetAnimationManager() { return m_animationManager; } LegoBuildingManager* GetBuildingManager() { return m_buildingManager; } + + // FUNCTION: BETA10 0x100e52b0 LegoGameState* GetGameState() { return m_gameState; } + MxBackgroundAudioManager* GetBackgroundAudioManager() { return m_bkgAudioManager; } MxTransitionManager* GetTransitionManager() { return m_transitionManager; } MxDSAction& GetCurrentAction() { return m_action; } diff --git a/LEGO1/lego/legoomni/include/mxbackgroundaudiomanager.h b/LEGO1/lego/legoomni/include/mxbackgroundaudiomanager.h index 20aa07bb..88bec8b1 100644 --- a/LEGO1/lego/legoomni/include/mxbackgroundaudiomanager.h +++ b/LEGO1/lego/legoomni/include/mxbackgroundaudiomanager.h @@ -3,6 +3,7 @@ #include "mxcore.h" #include "mxdsaction.h" +#include "mxpresenter.h" #include "mxtypes.h" class MxAudioPresenter; @@ -34,7 +35,7 @@ class MxBackgroundAudioManager : public MxCore { void StartAction(MxParam& p_param); void StopAction(MxParam& p_param); - MxResult PlayMusic(MxDSAction& p_action, undefined4 p_unk0x140, undefined4 p_unk0x13c); + MxResult PlayMusic(MxDSAction& p_action, undefined4 p_unk0x140, MxPresenter::TickleState p_tickleState); void FUN_1007ee70(); void FUN_1007ef40(); @@ -47,6 +48,7 @@ class MxBackgroundAudioManager : public MxCore { void Stop(); void LowerVolume(); void RaiseVolume(); + undefined4 FUN_1007f610(MxPresenter* p_unk0x138, MxS32 p_unk0x140, MxPresenter::TickleState p_tickleState); // SYNTHETIC: LEGO1 0x1007ec00 // MxBackgroundAudioManager::`scalar deleting destructor' @@ -60,11 +62,14 @@ class MxBackgroundAudioManager : public MxCore { MxAudioPresenter* m_unk0xa0; // 0xa0 MxDSAction m_action2; // 0xa4 MxAudioPresenter* m_unk0x138; // 0x138 - MxS32 m_unk0x13c; // 0x13c - MxS32 m_unk0x140; // 0x140 - MxS32 m_targetVolume; // 0x144 - MxS16 m_unk0x148; // 0x148 - MxAtomId m_script; // 0x14c + + // name is inferred from context + MxPresenter::TickleState m_tickleState; // 0x13c + + MxS32 m_unk0x140; // 0x140 + MxS32 m_targetVolume; // 0x144 + MxS16 m_unk0x148; // 0x148 + MxAtomId m_script; // 0x14c }; #endif // MXBACKGROUNDAUDIOMANAGER_H diff --git a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp index 2ab12451..4aafa214 100644 --- a/LEGO1/lego/legoomni/src/actors/islepathactor.cpp +++ b/LEGO1/lego/legoomni/src/actors/islepathactor.cpp @@ -600,7 +600,7 @@ void IslePathActor::SpawnPlayer(LegoGameState::Area p_area, MxBool p_enter, MxU8 MxDSAction action; action.SetAtomId(*g_jukeboxScript); action.SetObjectId(g_spawnLocations[i].m_music); - BackgroundAudioManager()->PlayMusic(action, 5, 4); + BackgroundAudioManager()->PlayMusic(action, 5, MxPresenter::e_repeating); } } } diff --git a/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp b/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp index 6888ebfa..a504fcbc 100644 --- a/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp +++ b/LEGO1/lego/legoomni/src/actors/jukeboxentity.cpp @@ -92,7 +92,7 @@ void JukeBoxEntity::StartAction() BackgroundAudioManager()->Enable(TRUE); } - BackgroundAudioManager()->PlayMusic(action, 5, 4); + BackgroundAudioManager()->PlayMusic(action, 5, MxPresenter::e_repeating); } // FUNCTION: LEGO1 0x100860f0 diff --git a/LEGO1/lego/legoomni/src/actors/radio.cpp b/LEGO1/lego/legoomni/src/actors/radio.cpp index 8d267d5a..4704ca29 100644 --- a/LEGO1/lego/legoomni/src/actors/radio.cpp +++ b/LEGO1/lego/legoomni/src/actors/radio.cpp @@ -115,7 +115,7 @@ void Radio::Play() BackgroundAudioManager()->Enable(TRUE); } - BackgroundAudioManager()->PlayMusic(action, 3, 4); + BackgroundAudioManager()->PlayMusic(action, 3, MxPresenter::e_repeating); m_state->SetActive(TRUE); } } @@ -178,7 +178,7 @@ MxLong Radio::HandleEndAction(MxEndActionNotificationParam& p_param) action.SetObjectId(m_state->FUN_1002d090()); action.SetLoopCount(1); - BackgroundAudioManager()->PlayMusic(action, 3, 4); + BackgroundAudioManager()->PlayMusic(action, 3, MxPresenter::e_repeating); return 1; } diff --git a/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp b/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp index 90d9df9b..d3e08256 100644 --- a/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp +++ b/LEGO1/lego/legoomni/src/audio/mxbackgroundaudiomanager.cpp @@ -20,7 +20,7 @@ MxBackgroundAudioManager::MxBackgroundAudioManager() NotificationManager()->Register(this); m_unk0xa0 = 0; m_unk0x138 = 0; - m_unk0x13c = 0; + m_tickleState = MxPresenter::e_idle; m_unk0x140 = 0; m_targetVolume = 0; m_unk0x148 = 0; @@ -81,7 +81,7 @@ void MxBackgroundAudioManager::DestroyMusic() // FUNCTION: LEGO1 0x1007ee40 MxResult MxBackgroundAudioManager::Tickle() { - switch (m_unk0x13c) { + switch (m_tickleState) { case MxPresenter::e_starting: FadeInOrFadeOut(); break; @@ -108,7 +108,7 @@ void MxBackgroundAudioManager::FUN_1007ee70() m_unk0x138 = NULL; m_action2.SetObjectId(-1); m_action2.SetAtomId(MxAtomId()); - m_unk0x13c = 0; + m_tickleState = MxPresenter::e_idle; } } @@ -141,7 +141,7 @@ void MxBackgroundAudioManager::FUN_1007ef40() m_unk0x138 = NULL; m_action2.SetObjectId(-1); m_action2.SetAtomId(MxAtomId()); - m_unk0x13c = 0; + m_tickleState = MxPresenter::e_idle; } } } @@ -187,11 +187,11 @@ void MxBackgroundAudioManager::FadeInOrFadeOut() } else { m_unk0xa0->SetVolume(volume); - m_unk0x13c = 0; + m_tickleState = MxPresenter::e_idle; } } else { - m_unk0x13c = 0; + m_tickleState = MxPresenter::e_idle; } } @@ -238,7 +238,11 @@ void MxBackgroundAudioManager::StopAction(MxParam& p_param) } // FUNCTION: LEGO1 0x1007f2f0 -MxResult MxBackgroundAudioManager::PlayMusic(MxDSAction& p_action, undefined4 p_unk0x140, undefined4 p_unk0x13c) +MxResult MxBackgroundAudioManager::PlayMusic( + MxDSAction& p_action, + undefined4 p_unk0x140, + MxPresenter::TickleState p_tickleState +) { if (!m_enabled) { return SUCCESS; @@ -262,7 +266,7 @@ MxResult MxBackgroundAudioManager::PlayMusic(MxDSAction& p_action, undefined4 p_ GetCurrentAction().SetUnknown24(action.GetUnknown24()); if (result == SUCCESS) { - m_unk0x13c = p_unk0x13c; + m_tickleState = p_tickleState; m_unk0x140 = p_unk0x140; } @@ -292,15 +296,15 @@ void MxBackgroundAudioManager::Stop() m_action1.SetAtomId(MxAtomId()); m_unk0x148 = 0; m_action1.SetObjectId(-1); - m_unk0x13c = 0; + m_tickleState = MxPresenter::e_idle; } // FUNCTION: LEGO1 0x1007f570 void MxBackgroundAudioManager::LowerVolume() { if (m_unk0x148 == 0) { - if (m_unk0x13c == 0) { - m_unk0x13c = 2; + if (m_tickleState == 0) { + m_tickleState = MxPresenter::e_starting; } m_unk0x140 = 20; } @@ -313,8 +317,8 @@ void MxBackgroundAudioManager::RaiseVolume() if (m_unk0x148 != 0) { m_unk0x148--; if (m_unk0x148 == 0) { - if (m_unk0x13c == 0) { - m_unk0x13c = 2; + if (m_tickleState == 0) { + m_tickleState = MxPresenter::e_starting; } m_unk0x140 = 10; } @@ -333,9 +337,28 @@ void MxBackgroundAudioManager::Enable(MxBool p_enable) } } +// FUNCTION: LEGO1 0x1007f610 +// FUNCTION: BETA10 0x100e95ee +undefined4 MxBackgroundAudioManager::FUN_1007f610( + MxPresenter* p_unk0x138, + MxS32 p_unk0x140, + MxPresenter::TickleState p_tickleState +) + +{ + m_unk0x138 = (MxAudioPresenter*) p_unk0x138; + m_targetVolume = ((MxDSSound*) m_unk0x138->GetAction())->GetVolume(); + + ((MxCompositePresenter*) m_unk0x138)->VTable0x60(NULL); + + m_unk0x140 = p_unk0x140; + m_tickleState = p_tickleState; + return 0; +} + // FUNCTION: LEGO1 0x1007f650 void MxBackgroundAudioManager::Init() { this->m_unk0xa0 = 0; - this->m_unk0x13c = 0; + this->m_tickleState = MxPresenter::e_idle; } diff --git a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp index 4d5fcbdb..48e6bb03 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuild.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuild.cpp @@ -1,9 +1,14 @@ #include "legocarbuild.h" +#include "dunebuggy.h" +#include "helicopter.h" +#include "jetski.h" +#include "jukebox_actions.h" #include "legocarbuildpresenter.h" #include "legocontrolmanager.h" #include "legogamestate.h" #include "legoinputmanager.h" +#include "legosoundmanager.h" #include "legoutils.h" #include "misc.h" #include "mxactionnotificationparam.h" @@ -15,8 +20,10 @@ #include "mxstillpresenter.h" #include "mxticklemanager.h" #include "mxtransitionmanager.h" +#include "racecar.h" #include "scripts.h" +#include #include DECOMP_SIZE_ASSERT(LegoCarBuild, 0x34c) @@ -141,7 +148,7 @@ MxResult LegoCarBuild::Create(MxDSAction& p_dsAction) GameState()->StopArea(LegoGameState::e_previousArea); - m_buildState->m_animationState = 1; + m_buildState->m_animationState = LegoVehicleBuildState::e_entering; m_unk0x100 = 0; BackgroundAudioManager()->Stop(); @@ -548,7 +555,7 @@ MxLong LegoCarBuild::Notify(MxParam& p_param) assert(m_buildState); if (((m_buildState->m_animationState != 4) && (m_buildState->m_animationState != 6)) && (m_buildState->m_animationState != 2)) { - m_buildState->m_animationState = 0; + m_buildState->m_animationState = LegoVehicleBuildState::e_unknown0; result = FUN_100244e0( ((LegoEventNotificationParam&) p_param).GetX(), ((LegoEventNotificationParam&) p_param).GetY() @@ -618,7 +625,7 @@ void LegoCarBuild::ReadyWorld() if (BackgroundAudioManager()->GetEnabled()) { InvokeAction(Extra::ActionType::e_start, *g_jukeboxScript, FUN_10025ee0(m_unk0x330), NULL); - m_buildState->m_animationState = 2; + m_buildState->m_animationState = LegoVehicleBuildState::e_unknown2; NotificationManager()->Send(this, MxNotificationParam()); } else { @@ -663,7 +670,7 @@ undefined4 LegoCarBuild::FUN_10024480(MxActionNotificationParam* p_param) switch (m_buildState->m_animationState) { case 3: BackgroundAudioManager()->RaiseVolume(); - m_buildState->m_animationState = 0; + m_buildState->m_animationState = LegoVehicleBuildState::e_unknown0; result = 1; break; case 6: @@ -772,18 +779,115 @@ undefined4 LegoCarBuild::FUN_10024890(LegoEventNotificationParam* p_param) return 0; } -// STUB: LEGO1 0x10024c20 -// STUB: BETA10 0x1006db21 -void LegoCarBuild::FUN_10024c20(LegoEventNotificationParam* p_param) +// FUNCTION: LEGO1 0x10024c20 +// FUNCTION: BETA10 0x1006db21 +undefined4 LegoCarBuild::FUN_10024c20(LegoEventNotificationParam* p_param) { - // TODO + LegoEntity* entity; + assert(m_buildState); + + switch (m_buildState->m_animationState) { + case 4: + entity = (LegoEntity*) Find(m_atomId, m_unk0x330); + + if (entity && entity->GetROI()) { + + // This function was changed between BETA10 and LEGO1. + // These lines looks like a relic from older code. + LegoWorld* destWorld = NULL; + destWorld = FindWorld(*g_isleScript, 0); + + Act1State* gameState = (Act1State*) GameState()->GetState("Act1State"); + + switch (GameState()->GetCurrentArea()) { + case LegoGameState::e_copterbuild: + if (gameState->m_helicopter) { + delete gameState->m_helicopter; + } + + gameState->m_helicopter = (Helicopter*) entity; + gameState->m_unk0x108.SetName(""); + break; + case LegoGameState::e_dunecarbuild: + if (gameState->m_dunebuggy) { + delete gameState->m_dunebuggy; + } + + gameState->m_dunebuggy = (DuneBuggy*) entity; + gameState->m_unk0x1bc.SetName(""); + break; + case LegoGameState::e_jetskibuild: + if (gameState->m_jetski) { + delete gameState->m_jetski; + } + + gameState->m_jetski = (Jetski*) entity; + gameState->m_unk0x164.SetName(""); + break; + case LegoGameState::e_racecarbuild: + if (gameState->m_racecar) { + delete gameState->m_racecar; + } + + gameState->m_racecar = (RaceCar*) entity; + gameState->m_unk0x210.SetName(""); + break; + } + + assert(destWorld); + m_buildState->m_animationState = LegoVehicleBuildState::e_exiting; + + if (m_unk0x258->m_numberOfParts != m_unk0x258->m_placedPartCount) { + FUN_100243a0(); + } + else { + FUN_10025720(5); + } + } + else { + MxNotificationParam param; + NotificationManager()->Send(this, param); + } + break; + case 2: + MxU32 jukeboxScript; + + switch (m_unk0x330) { + case 1: + jukeboxScript = JukeboxScript::c_HelicopterBuild_Music; + break; + case 2: + jukeboxScript = JukeboxScript::c_DuneCarBuild_Music; + break; + case 3: + jukeboxScript = JukeboxScript::c_JetskiBuild_Music; + break; + case 4: + jukeboxScript = JukeboxScript::c_RaceCarBuild_Music; + } + + m_unk0x338 = SoundManager()->FUN_100aebd0(*g_jukeboxScript, jukeboxScript); + + if (m_unk0x338) { + BackgroundAudioManager()->FUN_1007f610(m_unk0x338, 5, MxPresenter::e_repeating); + FUN_10024ef0(); + } + else { + MxNotificationParam p; + // In BETA10, NotificationManager->Send() also takes __FILE__ and __LINE__ arguments + NotificationManager()->Send(this, p); + } + break; + } + + return 1; } // FUNCTION: LEGO1 0x10024ef0 void LegoCarBuild::FUN_10024ef0() { FUN_1003eda0(); - m_buildState->m_animationState = 3; + m_buildState->m_animationState = LegoVehicleBuildState::e_cutscene; FUN_10025720(FUN_10025d70()); m_buildState->m_unk0x4c += 1; FUN_10015820(FALSE, 7); @@ -801,7 +905,7 @@ void LegoCarBuild::FUN_10024f50() // FUNCTION: BETA10 0x1006e002 void LegoCarBuild::FUN_10024f70(MxBool p_enabled) { - if (m_unk0x258->FUN_10079cf0(m_unk0x110->GetName())) { + if (m_unk0x258->StringEndsOnY(m_unk0x110->GetName())) { SetPresentersEnabled(p_enabled); } } @@ -832,11 +936,41 @@ void LegoCarBuild::TogglePresentersEnabled() m_Black_Ctl->Enable(!m_Black_Ctl->IsEnabled()); } -// STUB: LEGO1 0x100250e0 -// STUB: BETA10 0x1006e124 +// FUNCTION: LEGO1 0x100250e0 +// FUNCTION: BETA10 0x1006e124 void LegoCarBuild::FUN_100250e0(MxBool p_enabled) { - // TODO + if (m_unk0x258->StringEndsOnZero(m_unk0x110->GetName()) && m_Decals_Ctl) { + if (strnicmp(m_unk0x110->GetName(), "JSFRNT", strlen("JSFRNT")) == 0) { + m_Decal_Bitmap->Enable(p_enabled); + m_Decals_Ctl->Enable(p_enabled); + m_Decals_Ctl1->Enable(p_enabled); + m_Decals_Ctl2->Enable(p_enabled); + m_Decals_Ctl3->Enable(p_enabled); + } + else if (strnicmp(m_unk0x110->GetName(), "JSWNSH", strlen("JSWNSH")) == 0) { + m_Decal_Bitmap->Enable(p_enabled); + m_Decals_Ctl4->Enable(p_enabled); + m_Decals_Ctl5->Enable(p_enabled); + m_Decals_Ctl6->Enable(p_enabled); + m_Decals_Ctl7->Enable(p_enabled); + } + else if (strnicmp(m_unk0x110->GetName(), "RCBACK", strlen("RCBACK")) == 0) { + m_Decals_Ctl1->Enable(p_enabled); + } + else if (strnicmp(m_unk0x110->GetName(), "RCTAIL", strlen("RCTAIL")) == 0) { + m_Decals_Ctl2->Enable(p_enabled); + } + else if (m_Decals_Ctl1 && strnicmp(m_unk0x110->GetName(), "chljety", strlen("chljety")) == 0) { + m_Decals_Ctl1->Enable(p_enabled); + } + else if (m_Decals_Ctl2 && strnicmp(m_unk0x110->GetName(), "chrjety", strlen("chrjety")) == 0) { + m_Decals_Ctl2->Enable(p_enabled); + } + else if (m_Decals_Ctl) { + m_Decals_Ctl->Enable(p_enabled); + } + } } // STUB: LEGO1 0x10025450 @@ -927,7 +1061,7 @@ MxBool LegoCarBuild::Escape() InvokeAction(Extra::ActionType::e_stop, *g_jukeboxScript, targetEntityId, NULL); DeleteObjects(&m_atomId, 500, 999); - m_buildState->m_animationState = 0; + m_buildState->m_animationState = LegoVehicleBuildState::e_unknown0; m_destLocation = LegoGameState::e_infomain; return TRUE; } diff --git a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp index 71d989dd..28cfbc53 100644 --- a/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp +++ b/LEGO1/lego/legoomni/src/build/legocarbuildpresenter.cpp @@ -615,11 +615,18 @@ MxBool LegoCarBuildAnimPresenter::PartIsPlaced(const LegoChar* p_name) // FUNCTION: LEGO1 0x10079cf0 // FUNCTION: BETA10 0x100727b3 -MxBool LegoCarBuildAnimPresenter::FUN_10079cf0(const LegoChar* p_string) +MxBool LegoCarBuildAnimPresenter::StringEndsOnY(const LegoChar* p_string) { return (p_string[strlen(p_string) - 2] == 'Y') || (p_string[strlen(p_string) - 2] == 'y'); } +// FUNCTION: LEGO1 0x10079d30 +// FUNCTION: BETA10 0x1007280e +MxBool LegoCarBuildAnimPresenter::StringEndsOnZero(const LegoChar* p_string) +{ + return (p_string[strlen(p_string) - 2] != '0'); +} + // FUNCTION: LEGO1 0x10079e20 // FUNCTION: BETA10 0x10072959 const BoundingSphere& LegoCarBuildAnimPresenter::FUN_10079e20() diff --git a/LEGO1/lego/legoomni/src/common/misc.cpp b/LEGO1/lego/legoomni/src/common/misc.cpp index dee4ac0e..f9c78e5d 100644 --- a/LEGO1/lego/legoomni/src/common/misc.cpp +++ b/LEGO1/lego/legoomni/src/common/misc.cpp @@ -53,8 +53,10 @@ LegoControlManager* ControlManager() } // FUNCTION: LEGO1 0x10015760 +// FUNCTION: BETA10 0x100e492a LegoGameState* GameState() { + assert(LegoOmni::GetInstance()); return LegoOmni::GetInstance()->GetGameState(); } @@ -166,8 +168,10 @@ void DeleteAction() } // FUNCTION: LEGO1 0x100158c0 +// FUNCTION: BETA10 0x100e4e18 LegoWorld* FindWorld(const MxAtomId& p_atom, MxS32 p_entityid) { + assert(LegoOmni::GetInstance()); return LegoOmni::GetInstance()->FindWorld(p_atom, p_entityid); } @@ -196,7 +200,7 @@ void PlayMusic(JukeboxScript::Script p_script) action.SetAtomId(*g_jukeboxScript); action.SetObjectId(p_script); - LegoOmni::GetInstance()->GetBackgroundAudioManager()->PlayMusic(action, 5, 4); + LegoOmni::GetInstance()->GetBackgroundAudioManager()->PlayMusic(action, 5, MxPresenter::e_repeating); } // FUNCTION: LEGO1 0x100159c0 diff --git a/LEGO1/lego/legoomni/src/main/legomain.cpp b/LEGO1/lego/legoomni/src/main/legomain.cpp index 4244c5bb..2d39165d 100644 --- a/LEGO1/lego/legoomni/src/main/legomain.cpp +++ b/LEGO1/lego/legoomni/src/main/legomain.cpp @@ -357,6 +357,7 @@ void LegoOmni::RemoveWorld(const MxAtomId& p_atom, MxLong p_objectId) } // FUNCTION: LEGO1 0x1005b0c0 +// FUNCTION: BETA10 0x1008e827 LegoWorld* LegoOmni::FindWorld(const MxAtomId& p_atom, MxS32 p_entityid) { if (m_worldList) { diff --git a/LEGO1/lego/legoomni/src/main/scripts.cpp b/LEGO1/lego/legoomni/src/main/scripts.cpp index 32c3f86e..b5d7fe54 100644 --- a/LEGO1/lego/legoomni/src/main/scripts.cpp +++ b/LEGO1/lego/legoomni/src/main/scripts.cpp @@ -27,6 +27,7 @@ MxAtomId* g_jetraceScript = NULL; MxAtomId* g_jetracerScript = NULL; // GLOBAL: LEGO1 0x100f453c +// GLOBAL: BETA10 0x10211534 MxAtomId* g_isleScript = NULL; // GLOBAL: LEGO1 0x100f4540 diff --git a/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp b/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp index 1a39478e..970402c8 100644 --- a/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp +++ b/LEGO1/lego/legoomni/src/paths/legopathstruct.cpp @@ -161,6 +161,6 @@ void LegoPathStruct::PlayMusic(MxBool p_direction, MxU32 p_data) } if (action.GetObjectId() != -1) { - BackgroundAudioManager()->PlayMusic(action, 5, 4); + BackgroundAudioManager()->PlayMusic(action, 5, MxPresenter::e_repeating); } } diff --git a/LEGO1/omni/include/mxdssound.h b/LEGO1/omni/include/mxdssound.h index a0b81b4b..ae69e2d9 100644 --- a/LEGO1/omni/include/mxdssound.h +++ b/LEGO1/omni/include/mxdssound.h @@ -30,6 +30,7 @@ class MxDSSound : public MxDSMediaAction { void Deserialize(MxU8*& p_source, MxS16 p_unk0x24) override; // vtable+1c; MxDSAction* Clone() override; // vtable+2c; + // FUNCTION: BETA10 0x1008d060 MxS32 GetVolume() const { return m_volume; } // SYNTHETIC: LEGO1 0x100c9450 diff --git a/LEGO1/omni/include/mxnotificationparam.h b/LEGO1/omni/include/mxnotificationparam.h index bf1d589b..a5b30b24 100644 --- a/LEGO1/omni/include/mxnotificationparam.h +++ b/LEGO1/omni/include/mxnotificationparam.h @@ -39,6 +39,7 @@ enum NotificationId { // SIZE 0x0c class MxNotificationParam : public MxParam { public: + // FUNCTION: BETA10 0x100702d0 MxNotificationParam() : m_type(c_notificationType0), m_sender(NULL) {} // FUNCTION: BETA10 0x10013490 diff --git a/LEGO1/omni/include/mxsoundmanager.h b/LEGO1/omni/include/mxsoundmanager.h index 14bd3491..3d7c35c7 100644 --- a/LEGO1/omni/include/mxsoundmanager.h +++ b/LEGO1/omni/include/mxsoundmanager.h @@ -24,10 +24,11 @@ class MxSoundManager : public MxAudioManager { MxS32 GetAttenuation(MxU32 p_volume); + MxPresenter* FUN_100aebd0(const MxAtomId& p_atomId, MxU32 p_objectId); + protected: void Init(); void Destroy(MxBool p_fromDestructor); - MxPresenter* FUN_100aebd0(const MxAtomId& p_atomId, MxU32 p_objectId); LPDIRECTSOUND m_directSound; // 0x30 LPDIRECTSOUNDBUFFER m_dsBuffer; // 0x34 From 0cb753e5231969c0a56ff6d51ec827dcf9467255 Mon Sep 17 00:00:00 2001 From: Anonymous Maarten Date: Sat, 26 Oct 2024 14:57:47 +0200 Subject: [PATCH 10/10] Use reccmp as a python requirement (#1116) * Use reccmp as a python requirement * Add BETA10 to reccmp-project.yml --- .github/workflows/analyze.yml | 8 +- .github/workflows/build.yml | 29 +- .github/workflows/format.yml | 37 - .github/workflows/naming.yml | 4 + .github/workflows/unittest.yml | 60 -- .gitignore | 2 + CMakeLists.txt | 6 + README.md | 1 + {tools/reccmp => assets}/config.png | Bin {tools/reccmp => assets}/isle.png | Bin {tools/reccmp => assets}/lego1.png | Bin cmake/reccmp.cmake | 58 ++ reccmp-project.yml | 21 + tools/README.md | 66 +- tools/datacmp.py | 371 ------- tools/decomplint/decomplint.py | 103 -- tools/ghidra_scripts/README.md | 25 - .../import_functions_and_types_from_pdb.py | 285 ------ tools/ghidra_scripts/lego_util/__init__.py | 0 tools/ghidra_scripts/lego_util/exceptions.py | 47 - .../lego_util/function_importer.py | 421 -------- .../ghidra_scripts/lego_util/ghidra_helper.py | 129 --- tools/ghidra_scripts/lego_util/globals.py | 42 - tools/ghidra_scripts/lego_util/headers.pyi | 20 - .../lego_util/pdb_extraction.py | 183 ---- tools/ghidra_scripts/lego_util/statistics.py | 68 -- .../ghidra_scripts/lego_util/type_importer.py | 541 ---------- tools/isledecomp/.gitignore | 2 - tools/isledecomp/isledecomp/__init__.py | 4 - tools/isledecomp/isledecomp/bin.py | 574 ----------- .../isledecomp/isledecomp/compare/__init__.py | 1 - .../isledecomp/compare/asm/__init__.py | 2 - .../isledecomp/compare/asm/const.py | 27 - .../isledecomp/compare/asm/fixes.py | 314 ------ .../isledecomp/compare/asm/instgen.py | 249 ----- .../isledecomp/compare/asm/parse.py | 243 ----- .../isledecomp/isledecomp/compare/asm/swap.py | 80 -- tools/isledecomp/isledecomp/compare/core.py | 921 ------------------ tools/isledecomp/isledecomp/compare/db.py | 554 ----------- tools/isledecomp/isledecomp/compare/diff.py | 104 -- tools/isledecomp/isledecomp/compare/lines.py | 69 -- .../isledecomp/isledecomp/cvdump/__init__.py | 5 - .../isledecomp/isledecomp/cvdump/analysis.py | 187 ---- .../isledecomp/isledecomp/cvdump/demangler.py | 121 --- tools/isledecomp/isledecomp/cvdump/parser.py | 182 ---- tools/isledecomp/isledecomp/cvdump/runner.py | 83 -- tools/isledecomp/isledecomp/cvdump/symbols.py | 162 --- tools/isledecomp/isledecomp/cvdump/types.py | 737 -------------- tools/isledecomp/isledecomp/dir.py | 103 -- tools/isledecomp/isledecomp/lib/DUMPBIN.EXE | Bin 5632 -> 0 bytes tools/isledecomp/isledecomp/lib/LINK.EXE | Bin 514048 -> 0 bytes tools/isledecomp/isledecomp/lib/MSPDB41.DLL | Bin 271872 -> 0 bytes tools/isledecomp/isledecomp/lib/__init__.py | 13 - tools/isledecomp/isledecomp/lib/cvdump.exe | Bin 979968 -> 0 bytes .../isledecomp/isledecomp/parser/__init__.py | 3 - .../isledecomp/isledecomp/parser/codebase.py | 57 -- tools/isledecomp/isledecomp/parser/error.py | 97 -- tools/isledecomp/isledecomp/parser/linter.py | 144 --- tools/isledecomp/isledecomp/parser/marker.py | 146 --- tools/isledecomp/isledecomp/parser/node.py | 63 -- tools/isledecomp/isledecomp/parser/parser.py | 556 ----------- tools/isledecomp/isledecomp/parser/util.py | 141 --- tools/isledecomp/isledecomp/types.py | 13 - tools/isledecomp/isledecomp/utils.py | 308 ------ tools/isledecomp/setup.py | 11 - tools/isledecomp/tests/__init__.py | 0 tools/isledecomp/tests/conftest.py | 3 - .../isledecomp/tests/samples/basic_class.cpp | 30 - tools/isledecomp/tests/samples/basic_file.cpp | 22 - .../tests/samples/global_variables.cpp | 14 - tools/isledecomp/tests/samples/inline.cpp | 8 - .../tests/samples/missing_offset.cpp | 16 - .../tests/samples/multiple_offsets.cpp | 25 - .../tests/samples/oneline_function.cpp | 12 - .../isledecomp/tests/samples/out_of_order.cpp | 20 - .../tests/samples/poorly_formatted.cpp | 23 - tools/isledecomp/tests/test_compare_db.py | 82 -- tools/isledecomp/tests/test_curly.py | 73 -- tools/isledecomp/tests/test_cvdump.py | 59 -- tools/isledecomp/tests/test_cvdump_symbols.py | 38 - tools/isledecomp/tests/test_cvdump_types.py | 705 -------------- tools/isledecomp/tests/test_demangler.py | 83 -- tools/isledecomp/tests/test_instgen.py | 212 ---- tools/isledecomp/tests/test_islebin.py | 152 --- tools/isledecomp/tests/test_linter.py | 144 --- tools/isledecomp/tests/test_parser.py | 773 --------------- tools/isledecomp/tests/test_parser_samples.py | 141 --- .../tests/test_parser_statechange.py | 141 --- tools/isledecomp/tests/test_parser_util.py | 209 ---- .../isledecomp/tests/test_path_resolver_nt.py | 32 - .../tests/test_path_resolver_posix.py | 69 -- tools/isledecomp/tests/test_sanitize.py | 296 ------ tools/reccmp/reccmp.js | 867 ----------------- tools/reccmp/reccmp.py | 344 ------- tools/reccmp/template.html | 365 ------- tools/reccmp/template.svg | 119 --- tools/requirements.txt | 10 +- tools/roadmap/roadmap.py | 494 ---------- tools/stackcmp/stackcmp.py | 364 ------- tools/verexp/verexp.py | 75 -- tools/vtable/vtable.py | 111 --- 101 files changed, 143 insertions(+), 14791 deletions(-) delete mode 100644 .github/workflows/format.yml delete mode 100644 .github/workflows/unittest.yml rename {tools/reccmp => assets}/config.png (100%) rename {tools/reccmp => assets}/isle.png (100%) rename {tools/reccmp => assets}/lego1.png (100%) create mode 100644 cmake/reccmp.cmake create mode 100644 reccmp-project.yml delete mode 100644 tools/datacmp.py delete mode 100755 tools/decomplint/decomplint.py delete mode 100644 tools/ghidra_scripts/README.md delete mode 100644 tools/ghidra_scripts/import_functions_and_types_from_pdb.py delete mode 100644 tools/ghidra_scripts/lego_util/__init__.py delete mode 100644 tools/ghidra_scripts/lego_util/exceptions.py delete mode 100644 tools/ghidra_scripts/lego_util/function_importer.py delete mode 100644 tools/ghidra_scripts/lego_util/ghidra_helper.py delete mode 100644 tools/ghidra_scripts/lego_util/globals.py delete mode 100644 tools/ghidra_scripts/lego_util/headers.pyi delete mode 100644 tools/ghidra_scripts/lego_util/pdb_extraction.py delete mode 100644 tools/ghidra_scripts/lego_util/statistics.py delete mode 100644 tools/ghidra_scripts/lego_util/type_importer.py delete mode 100644 tools/isledecomp/.gitignore delete mode 100644 tools/isledecomp/isledecomp/__init__.py delete mode 100644 tools/isledecomp/isledecomp/bin.py delete mode 100644 tools/isledecomp/isledecomp/compare/__init__.py delete mode 100644 tools/isledecomp/isledecomp/compare/asm/__init__.py delete mode 100644 tools/isledecomp/isledecomp/compare/asm/const.py delete mode 100644 tools/isledecomp/isledecomp/compare/asm/fixes.py delete mode 100644 tools/isledecomp/isledecomp/compare/asm/instgen.py delete mode 100644 tools/isledecomp/isledecomp/compare/asm/parse.py delete mode 100644 tools/isledecomp/isledecomp/compare/asm/swap.py delete mode 100644 tools/isledecomp/isledecomp/compare/core.py delete mode 100644 tools/isledecomp/isledecomp/compare/db.py delete mode 100644 tools/isledecomp/isledecomp/compare/diff.py delete mode 100644 tools/isledecomp/isledecomp/compare/lines.py delete mode 100644 tools/isledecomp/isledecomp/cvdump/__init__.py delete mode 100644 tools/isledecomp/isledecomp/cvdump/analysis.py delete mode 100644 tools/isledecomp/isledecomp/cvdump/demangler.py delete mode 100644 tools/isledecomp/isledecomp/cvdump/parser.py delete mode 100644 tools/isledecomp/isledecomp/cvdump/runner.py delete mode 100644 tools/isledecomp/isledecomp/cvdump/symbols.py delete mode 100644 tools/isledecomp/isledecomp/cvdump/types.py delete mode 100644 tools/isledecomp/isledecomp/dir.py delete mode 100755 tools/isledecomp/isledecomp/lib/DUMPBIN.EXE delete mode 100755 tools/isledecomp/isledecomp/lib/LINK.EXE delete mode 100644 tools/isledecomp/isledecomp/lib/MSPDB41.DLL delete mode 100644 tools/isledecomp/isledecomp/lib/__init__.py delete mode 100644 tools/isledecomp/isledecomp/lib/cvdump.exe delete mode 100644 tools/isledecomp/isledecomp/parser/__init__.py delete mode 100644 tools/isledecomp/isledecomp/parser/codebase.py delete mode 100644 tools/isledecomp/isledecomp/parser/error.py delete mode 100644 tools/isledecomp/isledecomp/parser/linter.py delete mode 100644 tools/isledecomp/isledecomp/parser/marker.py delete mode 100644 tools/isledecomp/isledecomp/parser/node.py delete mode 100644 tools/isledecomp/isledecomp/parser/parser.py delete mode 100644 tools/isledecomp/isledecomp/parser/util.py delete mode 100644 tools/isledecomp/isledecomp/types.py delete mode 100644 tools/isledecomp/isledecomp/utils.py delete mode 100644 tools/isledecomp/setup.py delete mode 100644 tools/isledecomp/tests/__init__.py delete mode 100644 tools/isledecomp/tests/conftest.py delete mode 100644 tools/isledecomp/tests/samples/basic_class.cpp delete mode 100644 tools/isledecomp/tests/samples/basic_file.cpp delete mode 100644 tools/isledecomp/tests/samples/global_variables.cpp delete mode 100644 tools/isledecomp/tests/samples/inline.cpp delete mode 100644 tools/isledecomp/tests/samples/missing_offset.cpp delete mode 100644 tools/isledecomp/tests/samples/multiple_offsets.cpp delete mode 100644 tools/isledecomp/tests/samples/oneline_function.cpp delete mode 100644 tools/isledecomp/tests/samples/out_of_order.cpp delete mode 100644 tools/isledecomp/tests/samples/poorly_formatted.cpp delete mode 100644 tools/isledecomp/tests/test_compare_db.py delete mode 100644 tools/isledecomp/tests/test_curly.py delete mode 100644 tools/isledecomp/tests/test_cvdump.py delete mode 100644 tools/isledecomp/tests/test_cvdump_symbols.py delete mode 100644 tools/isledecomp/tests/test_cvdump_types.py delete mode 100644 tools/isledecomp/tests/test_demangler.py delete mode 100644 tools/isledecomp/tests/test_instgen.py delete mode 100644 tools/isledecomp/tests/test_islebin.py delete mode 100644 tools/isledecomp/tests/test_linter.py delete mode 100644 tools/isledecomp/tests/test_parser.py delete mode 100644 tools/isledecomp/tests/test_parser_samples.py delete mode 100644 tools/isledecomp/tests/test_parser_statechange.py delete mode 100644 tools/isledecomp/tests/test_parser_util.py delete mode 100644 tools/isledecomp/tests/test_path_resolver_nt.py delete mode 100644 tools/isledecomp/tests/test_path_resolver_posix.py delete mode 100644 tools/isledecomp/tests/test_sanitize.py delete mode 100644 tools/reccmp/reccmp.js delete mode 100755 tools/reccmp/reccmp.py delete mode 100644 tools/reccmp/template.html delete mode 100644 tools/reccmp/template.svg delete mode 100644 tools/roadmap/roadmap.py delete mode 100644 tools/stackcmp/stackcmp.py delete mode 100755 tools/verexp/verexp.py delete mode 100755 tools/vtable/vtable.py diff --git a/.github/workflows/analyze.yml b/.github/workflows/analyze.yml index 8e903cc1..a8e33042 100644 --- a/.github/workflows/analyze.yml +++ b/.github/workflows/analyze.yml @@ -17,10 +17,14 @@ jobs: steps: - uses: actions/checkout@v4 + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install python libraries run: | - python -m pip install -r tools/requirements.txt + pip install -r tools/requirements.txt - name: Run decomplint.py run: | - tools/decomplint/decomplint.py ${{ matrix.who }} --module ${{ matrix.who }} --warnfail + reccmp-decomplint ${{ matrix.who }} --module ${{ matrix.who }} --warnfail diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index efbe1c2e..67516cc4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -107,6 +107,10 @@ jobs: steps: - uses: actions/checkout@master + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - uses: actions/download-artifact@master with: name: Win32 @@ -125,12 +129,17 @@ jobs: run: | pip install -r tools/requirements.txt + - name: Detect binaries + run: | + reccmp-project detect --what original --search-path legobin + reccmp-project detect --what recompiled --search-path build + - name: Summarize Accuracy shell: bash run: | - python3 tools/reccmp/reccmp.py -S CONFIGPROGRESS.SVG --svg-icon tools/reccmp/config.png -H CONFIGPROGRESS.HTML legobin/CONFIG.EXE build/CONFIG.EXE build/CONFIG.PDB . | tee CONFIGPROGRESS.TXT - python3 tools/reccmp/reccmp.py -S ISLEPROGRESS.SVG --svg-icon tools/reccmp/isle.png -H ISLEPROGRESS.HTML legobin/ISLE.EXE build/ISLE.EXE build/ISLE.PDB . | tee ISLEPROGRESS.TXT - python3 tools/reccmp/reccmp.py -S LEGO1PROGRESS.SVG -T 4252 --svg-icon tools/reccmp/lego1.png -H LEGO1PROGRESS.HTML legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB . | tee LEGO1PROGRESS.TXT + reccmp-reccmp -S CONFIGPROGRESS.SVG --svg-icon assets/config.png --target CONFIG | tee CONFIGPROGRESS.TXT + reccmp-reccmp -S ISLEPROGRESS.SVG --svg-icon assets/isle.png --target ISLE | tee ISLEPROGRESS.TXT + reccmp-reccmp -S LEGO1PROGRESS.SVG -T 4252 --svg-icon assets/lego1.png --target LEGO1 | tee LEGO1PROGRESS.TXT - name: Compare Accuracy With Current Master shell: bash @@ -147,21 +156,21 @@ jobs: - name: Test Exports shell: bash run: | - tools/verexp/verexp.py legobin/LEGO1.DLL build/LEGO1.DLL + reccmp-verexp --target LEGO1 - name: Check Vtables shell: bash run: | - python3 tools/vtable/vtable.py legobin/CONFIG.EXE build/CONFIG.EXE build/CONFIG.PDB . - python3 tools/vtable/vtable.py legobin/ISLE.EXE build/ISLE.EXE build/ISLE.PDB . - python3 tools/vtable/vtable.py legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB . + reccmp-vtable --target CONFIG + reccmp-vtable --target ISLE + reccmp-vtable --target LEGO1 - name: Check Variables shell: bash run: | - python3 tools/datacmp.py legobin/CONFIG.EXE build/CONFIG.EXE build/CONFIG.PDB . - python3 tools/datacmp.py legobin/ISLE.EXE build/ISLE.EXE build/ISLE.PDB . - python3 tools/datacmp.py legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB . + reccmp-datacmp --target CONFIG + reccmp-datacmp --target ISLE + reccmp-datacmp --target LEGO1 - name: Upload Artifact uses: actions/upload-artifact@master diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml deleted file mode 100644 index da6ec354..00000000 --- a/.github/workflows/format.yml +++ /dev/null @@ -1,37 +0,0 @@ -name: Format - -on: [push, pull_request] - -jobs: - clang-format: - name: 'C++' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Run clang-format - run: | - find CONFIG LEGO1 ISLE -iname '*.h' -o -iname '*.cpp' | xargs \ - pipx run "clang-format>=17,<18" \ - --style=file \ - -i - git diff --exit-code - - python-format: - name: 'Python' - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Install python libraries - shell: bash - run: | - pip install black==23.* pylint==3.2.7 pytest==7.* -r tools/requirements.txt - - - name: Run pylint and black - shell: bash - run: | - pylint tools --ignore=build,ncc - black --check tools --exclude=ncc diff --git a/.github/workflows/naming.yml b/.github/workflows/naming.yml index 3afed885..b31fe07b 100644 --- a/.github/workflows/naming.yml +++ b/.github/workflows/naming.yml @@ -15,6 +15,10 @@ jobs: with: version: "16" + - uses: actions/setup-python@v5 + with: + python-version: '3.12' + - name: Install python libraries run: | pip install -r tools/requirements.txt diff --git a/.github/workflows/unittest.yml b/.github/workflows/unittest.yml deleted file mode 100644 index b1dee943..00000000 --- a/.github/workflows/unittest.yml +++ /dev/null @@ -1,60 +0,0 @@ -name: Test - -on: [push, pull_request] - -jobs: - fetch-deps: - name: Download original binaries - uses: ./.github/workflows/legobin.yml - - pytest-win: - name: 'Python Windows' - runs-on: windows-latest - needs: fetch-deps - - steps: - - uses: actions/checkout@v4 - - - name: Restore cached original binaries - id: cache-original-binaries - uses: actions/cache/restore@v3 - with: - enableCrossOsArchive: true - path: legobin - key: legobin - - - name: Install python libraries - shell: bash - run: | - pip install pytest -r tools/requirements.txt - - - name: Run python unit tests (Windows) - shell: bash - run: | - pytest tools/isledecomp --lego1=legobin/LEGO1.DLL - - pytest-ubuntu: - name: 'Python Linux' - runs-on: ubuntu-latest - needs: fetch-deps - - steps: - - uses: actions/checkout@v4 - - - name: Restore cached original binaries - id: cache-original-binaries - uses: actions/cache/restore@v3 - with: - enableCrossOsArchive: true - path: legobin - key: legobin - - - name: Install python libraries - shell: bash - run: | - pip install pytest -r tools/requirements.txt - - - name: Run python unit tests (Ubuntu) - shell: bash - run: | - pytest tools/isledecomp --lego1=legobin/LEGO1.DLL diff --git a/.gitignore b/.gitignore index b5e87a00..81e69517 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +reccmp-user.yml +reccmp-build.yml Debug/ Release/ *.ncb diff --git a/CMakeLists.txt b/CMakeLists.txt index 54d4b76b..c22b0c98 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,6 +8,7 @@ project(isle CXX) include(CheckCXXSourceCompiles) include(CMakeDependentOption) include(CMakePushCheckState) +include("${CMAKE_CURRENT_LIST_DIR}/cmake/reccmp.cmake") set(CMAKE_EXPORT_COMPILE_COMMANDS TRUE) option(ENABLE_CLANG_TIDY "Enable clang-tidy") @@ -405,6 +406,7 @@ add_library(lego1 SHARED LEGO1/main.cpp LEGO1/modeldb/modeldb.cpp ) +reccmp_add_target(lego1 ID LEGO1) register_lego1_target(lego1) if (MINGW) @@ -447,6 +449,7 @@ if (ISLE_BUILD_APP) ISLE/res/isle.rc ISLE/isleapp.cpp ) + reccmp_add_target(isle ID ISLE) target_compile_definitions(isle PRIVATE ISLE_APP) @@ -477,6 +480,7 @@ if (ISLE_BUILD_CONFIG) CONFIG/StdAfx.cpp CONFIG/res/config.rc ) + reccmp_add_target(config ID CONFIG) target_compile_definitions(config PRIVATE _AFXDLL MXDIRECTX_FOR_CONFIG) target_include_directories(config PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/util" "${CMAKE_CURRENT_SOURCE_DIR}/LEGO1") if(CMAKE_CXX_COMPILER_VERSION VERSION_LESS 14) @@ -603,3 +607,5 @@ if(EXISTS "${CLANGFORMAT_BIN}") endif() endif() endif() + +reccmp_configure() diff --git a/README.md b/README.md index 519f6422..55e74dd1 100644 --- a/README.md +++ b/README.md @@ -62,3 +62,4 @@ Right click on `LEGO1.DLL`, select `Properties`, and switch to the `Details` tab * ISLE.EXE `md5: f6da12249e03eed1c74810cd23beb9f5` * LEGO1.DLL `md5: 4e2f6d969ea2ef8655ba3fc221a0c8fe` +* CONFIG.DLL `md5: 92d958a64a273662c591c88b09100f4a` diff --git a/tools/reccmp/config.png b/assets/config.png similarity index 100% rename from tools/reccmp/config.png rename to assets/config.png diff --git a/tools/reccmp/isle.png b/assets/isle.png similarity index 100% rename from tools/reccmp/isle.png rename to assets/isle.png diff --git a/tools/reccmp/lego1.png b/assets/lego1.png similarity index 100% rename from tools/reccmp/lego1.png rename to assets/lego1.png diff --git a/cmake/reccmp.cmake b/cmake/reccmp.cmake new file mode 100644 index 00000000..60aeecc1 --- /dev/null +++ b/cmake/reccmp.cmake @@ -0,0 +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() diff --git a/reccmp-project.yml b/reccmp-project.yml new file mode 100644 index 00000000..c4213582 --- /dev/null +++ b/reccmp-project.yml @@ -0,0 +1,21 @@ +targets: + ISLE: + filename: ISLE.EXE + source-root: ISLE + hash: + sha256: 5cf57c284973fce9d14f5677a2e4435fd989c5e938970764d00c8932ed5128ca + LEGO1: + filename: LEGO1.DLL + source-root: LEGO1 + hash: + sha256: 14645225bbe81212e9bc1919cd8a692b81b8622abb6561280d99b0fc4151ce17 + CONFIG: + filename: CONFIG.EXE + source-root: CONFIG + hash: + sha256: 864766d024d78330fed5e1f6efb2faf815f1b1c3405713a9718059dc9a54e52c + BETA10: + filename: BETA10.DLL + source-root: LEGO1 + hash: + sha256: d91435a40fa31f405fba33b03bd3bd40dcd4ca36ccf8ef6162c6c5ca0d7190e7 diff --git a/tools/README.md b/tools/README.md index d2ff3dc9..84c2a96a 100644 --- a/tools/README.md +++ b/tools/README.md @@ -160,58 +160,42 @@ inline virtual const char* ClassName() const override // vtable+0x0c Use `pip` to install the required packages to be able to use the Python tools found in this folder: -``` +```sh pip install -r tools/requirements.txt ``` +Run the following command to allow reccmp to detect the original LEGO binaries: + +```sh +reccmp-project detect --what original --search-path +``` + +After building recompiled binaries, run the following command in this repository's root: + +```sh +reccmp-project detect --what recompiled --search-path +``` + The example usages below assume that the current working directory is this repository's root and that the retail binaries have been copied to `./legobin`. -* [`decomplint`](/tools/decomplint): Checks the decompilation annotations (see above) - * e.g. `py -m tools.decomplint.decomplint --module LEGO1 LEGO1` -* [`isledecomp`](/tools/isledecomp): A library that implements a parser to identify the decompilation annotations (see above) +* `reccmp-decomplint`: Checks the decompilation annotations (see above) + * e.g. `reccmp-decomplint --module LEGO1 LEGO1` * [`ncc`](/tools/ncc): Checks naming conventions based on a set of rules -* [`reccmp`](/tools/reccmp): Compares an original binary with a recompiled binary, provided a PDB file. For example: +* `reccmp-reccmp`: Compares an original binary with a recompiled binary, provided a PDB file. For example: * Display the diff for a single function: `py -m tools.reccmp.reccmp --verbose 0x100ae1a0 legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` * Generate an HTML report: `py -m tools.reccmp.reccmp --html output.html legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` * Create a base file for diffs: `py -m tools.reccmp.reccmp --json base.json --silent legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` * Diff against a base file: `py -m tools.reccmp.reccmp --diff base.json legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` -* [`stackcmp`](/tools/stackcmp): Compares the stack layout for a given function that almost matches. - * e.g. `py -m tools.stackcmp.stackcmp legobin/BETA10.DLL build_debug/LEGO1.DLL build_debug/LEGO1.pdb . 0x1007165d` -* [`roadmap`](/tools/roadmap): Compares symbol locations in an original binary with the same symbol locations of a recompiled binary -* [`verexp`](/tools/verexp): Verifies exports by comparing the exports of the original DLL and the recompiled DLL -* [`vtable`](/tools/vtable): Asserts virtual table correctness by comparing a recompiled binary with the original - * e.g. `py -m tools.vtable.vtable legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` -* [`datacmp.py`](/tools/datacmp.py): Compares global data found in the original with the recompiled version - * e.g. `py -m tools.datacmp legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` +* `reccmp-stackcmp`: Compares the stack layout for a given function that almost matches. + * e.g. `reccmp-stackcmp legobin/BETA10.DLL build_debug/LEGO1.DLL build_debug/LEGO1.pdb . 0x1007165d` +* `reccmp-roadmap`: Compares symbol locations in an original binary with the same symbol locations of a recompiled binary +* `reccmp-verexp`: Verifies exports by comparing the exports of the original DLL and the recompiled DLL +* `reccmp-vtable`: Asserts virtual table correctness by comparing a recompiled binary with the original + * e.g. `reccmp-vtable legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` +* `reccmp-datacmp`: Compares global data found in the original with the recompiled version + * e.g. `reccmp-datacmp legobin/LEGO1.DLL build/LEGO1.DLL build/LEGO1.PDB .` * [`patch_c2.py`](/tools/patch_c2.py): Patches `C2.EXE` (part of MSVC 4.20) to get rid of a bugged warning -## Testing - -`isledecomp` comes with a suite of tests. Install `pytest` and run it, passing in the directory: - -``` -pip install pytest -pytest tools/isledecomp/tests/ -``` - -## Tool Development - -In order to keep the Python code clean and consistent, we use `pylint` and `black`: - -`pip install black pylint` - -### Run pylint (ignores build and virtualenv) - -`pylint tools/ --ignore=build,ncc` - -### Check Python code formatting without rewriting files - -`black --check tools/` - -### Apply Python code formatting - -`black tools/` - # Modules The following is a list of all the modules found in the annotations (e.g. `// FUNCTION: [module] [address]`) and which binaries they refer to. See [this list of all known versions of the game](https://www.legoisland.org/wiki/LEGO_Island#Download). @@ -243,7 +227,7 @@ cmake -G "NMake Makefiles" -DCMAKE_BUILD_TYPE=RelWithDebInfo -D ``` **TODO**: If you can figure out how to make a debug build with SmartHeap enabled, please add it here. -If you want to run scripts to compare your debug build to `BETA10` (e.g. `reccmp`), it is advisable to add a copy of `LEGO1D.DLL` to `/legobin` and rename it to `BETA10.DLL`. +If you want to run scripts to compare your debug build to `BETA10` (e.g. `reccmp-reccmp`), it is advisable to add a copy of `LEGO1D.DLL` to `/legobin` and rename it to `BETA10.DLL`. ### Finding matching functions diff --git a/tools/datacmp.py b/tools/datacmp.py deleted file mode 100644 index cbd18877..00000000 --- a/tools/datacmp.py +++ /dev/null @@ -1,371 +0,0 @@ -# (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 deleted file mode 100755 index 4ff28168..00000000 --- a/tools/decomplint/decomplint.py +++ /dev/null @@ -1,103 +0,0 @@ -#!/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 deleted file mode 100644 index 1e5082d7..00000000 --- a/tools/ghidra_scripts/README.md +++ /dev/null @@ -1,25 +0,0 @@ -# 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 deleted file mode 100644 index 2ac843b1..00000000 --- a/tools/ghidra_scripts/import_functions_and_types_from_pdb.py +++ /dev/null @@ -1,285 +0,0 @@ -# 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 - -import importlib -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") -reload_module("lego_util.globals") -from lego_util.globals import GLOBALS, SupportedModules - - -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": - GLOBALS.module = SupportedModules.LEGO1 - elif origfile_name in ["LEGO1D.DLL", "BETA10.DLL"]: - GLOBALS.module = SupportedModules.BETA10 - else: - raise Lego1Exception( - f"Unsupported file name in import script: {origfile_name}" - ) - - logger.info("Importing file: %s", GLOBALS.module.orig_filename()) - - repo_root = get_repository_root() - origfile_path = repo_root.joinpath("legobin").joinpath( - GLOBALS.module.orig_filename() - ) - build_directory = repo_root.joinpath(GLOBALS.module.build_dir_name()) - recompiledfile_name = f"{GLOBALS.module.recomp_filename_without_extension()}.DLL" - recompiledfile_path = build_directory.joinpath(recompiledfile_name) - pdbfile_name = f"{GLOBALS.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 deleted file mode 100644 index e69de29b..00000000 diff --git a/tools/ghidra_scripts/lego_util/exceptions.py b/tools/ghidra_scripts/lego_util/exceptions.py deleted file mode 100644 index 1a92ba2a..00000000 --- a/tools/ghidra_scripts/lego_util/exceptions.py +++ /dev/null @@ -1,47 +0,0 @@ -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 deleted file mode 100644 index 45e93c8d..00000000 --- a/tools/ghidra_scripts/lego_util/function_importer.py +++ /dev/null @@ -1,421 +0,0 @@ -# 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 deleted file mode 100644 index 91717efd..00000000 --- a/tools/ghidra_scripts/lego_util/ghidra_helper.py +++ /dev/null @@ -1,129 +0,0 @@ -"""A collection of helper functions for the interaction with Ghidra.""" - -import logging -import re - -from lego_util.exceptions import ( - ClassOrNamespaceNotFoundInGhidraError, - TypeNotFoundInGhidraError, - MultipleTypesFoundInGhidraError, -) -from lego_util.globals import GLOBALS, SupportedModules - -# 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("`", "'") - ) - - # Importing function names like `FUN_10001234` into BETA10 can be confusing - # because Ghidra's auto-generated functions look exactly the same. - # Therefore, such function names are replaced by `LEGO_10001234` in the BETA10 import. - if GLOBALS.module == SupportedModules.BETA10: - new_name = re.sub(r"FUN_([0-9a-f]{8})", r"LEGO1_\1", new_name) - - 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/globals.py b/tools/ghidra_scripts/lego_util/globals.py deleted file mode 100644 index 541b1106..00000000 --- a/tools/ghidra_scripts/lego_util/globals.py +++ /dev/null @@ -1,42 +0,0 @@ -import logging -from enum import Enum -from dataclasses import dataclass, field -from lego_util.statistics import 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" - - -@dataclass -class Globals: - verbose: bool - loglevel: int - module: SupportedModules - running_from_ghidra: bool = False - # statistics - statistics: Statistics = field(default_factory=Statistics) - - -# hard-coded settings that we don't want to prompt in Ghidra every time -GLOBALS = Globals( - verbose=False, - # loglevel=logging.INFO, - loglevel=logging.DEBUG, - module=SupportedModules.LEGO1, # this default value will be used when run outside of Ghidra -) diff --git a/tools/ghidra_scripts/lego_util/headers.pyi b/tools/ghidra_scripts/lego_util/headers.pyi deleted file mode 100644 index b1655437..00000000 --- a/tools/ghidra_scripts/lego_util/headers.pyi +++ /dev/null @@ -1,20 +0,0 @@ -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 deleted file mode 100644 index 3325c159..00000000 --- a/tools/ghidra_scripts/lego_util/pdb_extraction.py +++ /dev/null @@ -1,183 +0,0 @@ -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. - # Note that this affects the arguments (ebp + ...) but not the function stack (ebp - ...) - 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 deleted file mode 100644 index 02232b01..00000000 --- a/tools/ghidra_scripts/lego_util/statistics.py +++ /dev/null @@ -1,68 +0,0 @@ -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 deleted file mode 100644 index 1f4a077e..00000000 --- a/tools/ghidra_scripts/lego_util/type_importer.py +++ /dev/null @@ -1,541 +0,0 @@ -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 deleted file mode 100644 index a7c50887..00000000 --- a/tools/isledecomp/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -isledecomp.egg-info/ -build \ No newline at end of file diff --git a/tools/isledecomp/isledecomp/__init__.py b/tools/isledecomp/isledecomp/__init__.py deleted file mode 100644 index 59c55869..00000000 --- a/tools/isledecomp/isledecomp/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 2f20224b..00000000 --- a/tools/isledecomp/isledecomp/bin.py +++ /dev/null @@ -1,574 +0,0 @@ -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 deleted file mode 100644 index f8d18500..00000000 --- a/tools/isledecomp/isledecomp/compare/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .core import Compare diff --git a/tools/isledecomp/isledecomp/compare/asm/__init__.py b/tools/isledecomp/isledecomp/compare/asm/__init__.py deleted file mode 100644 index 3fd22f6e..00000000 --- a/tools/isledecomp/isledecomp/compare/asm/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -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 deleted file mode 100644 index 7c715c0c..00000000 --- a/tools/isledecomp/isledecomp/compare/asm/const.py +++ /dev/null @@ -1,27 +0,0 @@ -# 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 deleted file mode 100644 index d0e326da..00000000 --- a/tools/isledecomp/isledecomp/compare/asm/fixes.py +++ /dev/null @@ -1,314 +0,0 @@ -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 deleted file mode 100644 index c65aa033..00000000 --- a/tools/isledecomp/isledecomp/compare/asm/instgen.py +++ /dev/null @@ -1,249 +0,0 @@ -"""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 deleted file mode 100644 index 599444cf..00000000 --- a/tools/isledecomp/isledecomp/compare/asm/swap.py +++ /dev/null @@ -1,80 +0,0 @@ -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 deleted file mode 100644 index 66e923d3..00000000 --- a/tools/isledecomp/isledecomp/compare/core.py +++ /dev/null @@ -1,921 +0,0 @@ -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, CombinedDiffOutput -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[CombinedDiffOutput] = 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 deleted file mode 100644 index ad030548..00000000 --- a/tools/isledecomp/isledecomp/compare/db.py +++ /dev/null @@ -1,554 +0,0 @@ -"""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 deleted file mode 100644 index a328c997..00000000 --- a/tools/isledecomp/isledecomp/compare/diff.py +++ /dev/null @@ -1,104 +0,0 @@ -from difflib import SequenceMatcher -from typing import Dict, List, Tuple - -CombinedDiffInput = List[Tuple[str, str]] -# from inner to outer: -# Tuple[str, ...]: either (orig_addr, instruction, recomp_addr) or (addr, instruction) -# List[...]: a contiguous block of instructions, all matching or all mismatching -# Dict[...]: either {"both": List[...]} or {"orig": [...], "recomp": [...]} -# Tuple[str, List[...]]: One contiguous part of the diff (without skipping matching code) -# List[...]: The list of all the contiguous diffs of a given function -CombinedDiffOutput = List[Tuple[str, List[Dict[str, List[Tuple[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 deleted file mode 100644 index 0e9c4332..00000000 --- a/tools/isledecomp/isledecomp/compare/lines.py +++ /dev/null @@ -1,69 +0,0 @@ -"""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 deleted file mode 100644 index 334788c0..00000000 --- a/tools/isledecomp/isledecomp/cvdump/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -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 deleted file mode 100644 index e030035e..00000000 --- a/tools/isledecomp/isledecomp/cvdump/analysis.py +++ /dev/null @@ -1,187 +0,0 @@ -"""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 deleted file mode 100644 index 9b2445da..00000000 --- a/tools/isledecomp/isledecomp/cvdump/demangler.py +++ /dev/null @@ -1,121 +0,0 @@ -"""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 deleted file mode 100644 index c8f1d67d..00000000 --- a/tools/isledecomp/isledecomp/cvdump/parser.py +++ /dev/null @@ -1,182 +0,0 @@ -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 deleted file mode 100644 index 9463acfa..00000000 --- a/tools/isledecomp/isledecomp/cvdump/runner.py +++ /dev/null @@ -1,83 +0,0 @@ -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 deleted file mode 100644 index cfb9f474..00000000 --- a/tools/isledecomp/isledecomp/cvdump/symbols.py +++ /dev/null @@ -1,162 +0,0 @@ -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_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 in ["S_GPROC32", "S_LPROC32"]: - 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 deleted file mode 100644 index 42a9e985..00000000 --- a/tools/isledecomp/isledecomp/cvdump/types.py +++ /dev/null @@ -1,737 +0,0 @@ -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 deleted file mode 100644 index ca7e3fd5..00000000 --- a/tools/isledecomp/isledecomp/dir.py +++ /dev/null @@ -1,103 +0,0 @@ -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 deleted file mode 100755 index c57ed02cf5ada945542683c25c83e4b935f79a57..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5632 zcmeHLeQXh&%6nwUf@Tyt46v zD}H6;9z9!@N=9+qNadw;s!%AJ(jiSUDg`N9kfOVXrF=1?1=p-u6==Fn#E9aq6*T#y z*Fvpm^VI2D?ecbuq&gk$=slRbfD+RYXYF?KW)9~spoFnWa9u)??buk%ixV@2t8F|d zh|W0%OfA7E(bt!#kG*;(LiFSvw1cL0)P(#)j>&m(&WlB04UmXNgGMH0rifTq)Q+0T z$HWpuP({#DqX(HS16gks6H5>wnh9nd#RpgphKSjLxeX$$XOxXJc%mohjnLyf+aSVm z4J}uM;vbnm3S__Cz&3~wZEqu&m!F^xSkZt_Cn-W!Jm5+yhhWZ&KinxMp>?SCl(J0BM3u&AF3grQU46A09!0l7NYt&hU5z8}NjP{Q^< z4<&G`(}B%yL|J=cJ=CoefdHs_$4NfaaCH`1wP;{Nb@eVY^^+dl(& zbMxH?aKP5 zIHAi@1lwWz=N84&q8qMn^jfE#=A9Re>a1_T+Hx9dmZv)FP1wIexdW9O@2qHYomM-1 zj?vzHAEMl@`TG{P;Xv}>6O(g%S}t}_>-b#GOkqoCueC5?>pFbsH$EMqM5flm?CQei z;gNkct#K)KlWE@6H<2Imx^&lO84&?fyf`B><-ZD{wMN3a=Cl282x^0%rY&;A zzJ;^9AgsG}FGMx7?g z3@&Qi-}JZGdtS6Z%S_K+wtLx)zTGHlj1uE z9uLO$$N1+Ymn|F)LV)G1HpPl5700B!mM%3EH2bxZ^_6O%rQjvj2Rl{9;Rs%d zyIX6z&zn!>#4Y!C?qoi7SSzJWorYPS%bJ3q_3mV{gun58$uwx!@JRpAp0FIuy2vbjjS2&6%1(s~wAEwvaWo!V&tRTPz{UlN+*e&CI8=g_JQ~rq6>eYi6Yk zA!6e_P4tL6naWI5%4TvblRZLox8pBe%=0%Ek>8=m9ID6hkW7_IN%L3OMHs1BRbACw)0(} z<=gOA;;#A_WTOdu$9vf7R)~ATV2sc(B{9Zm2&Nm=rBAPka2|-IwVRDFUT*;)AEYYrciH4 z4ZRS$7Z{oL8omi^|WHHIL;Z^#c{#jvdC`KrZoPxD5a04*UZTumq<7 diff --git a/tools/isledecomp/isledecomp/lib/LINK.EXE b/tools/isledecomp/isledecomp/lib/LINK.EXE deleted file mode 100755 index 9d35a8993d959611a7e1c15cb41bd8c3c676a784..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 514048 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%0Dw%fIS}4V*0PQ23-r0^ie}44 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%0D=lYG)_*cDJR?I* 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%0DSs~wI- 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 V`|k<-_XPfX0{=aM|Bol|{{Z)(1AYJi diff --git a/tools/isledecomp/isledecomp/lib/MSPDB41.DLL b/tools/isledecomp/isledecomp/lib/MSPDB41.DLL deleted file mode 100644 index 9d2953e2b3308944c7fd531780fe63020a74bc71..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 271872 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%0D64W!-TzeE*> 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 diff --git a/tools/isledecomp/isledecomp/lib/__init__.py b/tools/isledecomp/isledecomp/lib/__init__.py deleted file mode 100644 index f1081a81..00000000 --- a/tools/isledecomp/isledecomp/lib/__init__.py +++ /dev/null @@ -1,13 +0,0 @@ -"""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 deleted file mode 100644 index 8c1eff6e058dbf5226ae1b5876cec4f55240d84f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 979968 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%0AhV?W@?7ZOhAoV6Lh{Sm@NIl7p&Ljo?Jl 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

- 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 deleted file mode 100755 index bcdb0899..00000000 --- a/tools/reccmp/reccmp.py +++ /dev/null @@ -1,344 +0,0 @@ -#!/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 deleted file mode 100644 index 3ac15b45..00000000 --- a/tools/reccmp/template.html +++ /dev/null @@ -1,365 +0,0 @@ - - - - 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 deleted file mode 100644 index fad7ba10..00000000 --- a/tools/reccmp/template.svg +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - {{name}}{{percent}}{{percent}}Implemented: {{implemented}}Accuracy: {{accuracy}} diff --git a/tools/requirements.txt b/tools/requirements.txt index 3f5e4e84..72c72178 100644 --- a/tools/requirements.txt +++ b/tools/requirements.txt @@ -1,11 +1,3 @@ -tools/isledecomp -capstone +reccmp @ git+https://github.com/isledecomp/reccmp clang==16.* -colorama>=0.4.6 -isledecomp -pystache pyyaml -git+https://github.com/wbenny/pydemangler.git -# requirement of capstone due to python dropping distutils. -# see: https://github.com/capstone-engine/capstone/issues/2223 -setuptools ; python_version >= "3.12" \ No newline at end of file diff --git a/tools/roadmap/roadmap.py b/tools/roadmap/roadmap.py deleted file mode 100644 index b7294a95..00000000 --- a/tools/roadmap/roadmap.py +++ /dev/null @@ -1,494 +0,0 @@ -"""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/stackcmp/stackcmp.py b/tools/stackcmp/stackcmp.py deleted file mode 100644 index 9aa02f2a..00000000 --- a/tools/stackcmp/stackcmp.py +++ /dev/null @@ -1,364 +0,0 @@ -from dataclasses import dataclass -import re -import logging -import os -import argparse -import struct -from typing import Dict, List, NamedTuple, Optional, Set, Tuple - -from isledecomp import Bin -from isledecomp.compare import Compare as IsleCompare -from isledecomp.compare.diff import CombinedDiffOutput -from isledecomp.cvdump.symbols import SymbolsEntry -import colorama - -# pylint: disable=duplicate-code # misdetects a code duplication with reccmp - -colorama.just_fix_windows_console() - -CHECK_ICON = f"{colorama.Fore.GREEN}✓{colorama.Style.RESET_ALL}" -SWAP_ICON = f"{colorama.Fore.YELLOW}⇄{colorama.Style.RESET_ALL}" -ERROR_ICON = f"{colorama.Fore.RED}✗{colorama.Style.RESET_ALL}" -UNCLEAR_ICON = f"{colorama.Fore.BLUE}?{colorama.Style.RESET_ALL}" - - -STACK_ENTRY_REGEX = re.compile( - r"(?Pe[sb]p)\s(?P[+-])\s(?P(0x)?[0-9a-f]+)(?![0-9a-f])" -) - - -@dataclass -class StackSymbol: - name: str - data_type: str - - -@dataclass -class StackRegisterOffset: - register: str - offset: int - symbol: Optional[StackSymbol] = None - - def __str__(self) -> str: - first_part = ( - f"{self.register} + {self.offset:#04x}" - if self.offset > 0 - else f"{self.register} - {-self.offset:#04x}" - ) - second_part = f" {self.symbol.name}" if self.symbol else "" - return first_part + second_part - - def __hash__(self) -> int: - return hash(self.register) + self.offset - - def copy(self) -> "StackRegisterOffset": - return StackRegisterOffset(self.register, self.offset, self.symbol) - - def __eq__(self, other: "StackRegisterOffset"): - return self.register == other.register and self.offset == other.offset - - -class StackPair(NamedTuple): - orig: StackRegisterOffset - recomp: StackRegisterOffset - - -StackPairs = Set[StackPair] - - -@dataclass -class Warnings: - structural_mismatches_present: bool = False - error_map_not_bijective: bool = False - - -def extract_stack_offset_from_instruction( - instruction: str, -) -> StackRegisterOffset | None: - match = STACK_ENTRY_REGEX.search(instruction) - if not match: - return None - offset = int(match.group("sign") + match.group("offset"), 16) - return StackRegisterOffset(match.group("register"), offset) - - -def analyze_diff( - diff: Dict[str, List[Tuple[str, ...]]], warnings: Warnings -) -> StackPairs: - stack_pairs: StackPairs = set() - if "both" in diff: - # get the matching stack entries - for line in diff["both"]: - # 0 = orig addr, 1 = instruction, 2 = reccmp addr - instruction = line[1] - - if match := extract_stack_offset_from_instruction(instruction): - logging.debug("stack match: %s", match) - # need a copy for recomp because we might add a debug symbol to it - stack_pairs.add(StackPair(match, match.copy())) - elif any(x in instruction for x in ["ebp", "esp"]): - logging.debug("not a stack offset: %s", instruction) - - else: - orig = diff["orig"] - recomp = diff["recomp"] - if len(orig) != len(recomp): - if orig: - mismatch_location = f"orig={orig[0][0]}" - else: - mismatch_location = f"recomp={recomp[0][0]}" - logging.error( - "Structural mismatch at %s:\n%s", - mismatch_location, - print_structural_mismatch(orig, recomp), - ) - warnings.structural_mismatches_present = True - return set() - - for orig_line, recomp_line in zip(orig, recomp): - if orig_match := extract_stack_offset_from_instruction(orig_line[1]): - recomp_match = extract_stack_offset_from_instruction(recomp_line[1]) - - if not recomp_match: - logging.error( - "Mismatching line structure at orig=%s:\n%s", - orig_line[0], - print_structural_mismatch(orig, recomp), - ) - # not recoverable, whole block has a structural mismatch - warnings.structural_mismatches_present = True - return set() - - stack_pair = StackPair(orig_match, recomp_match) - - logging.debug( - "stack match, wrong order: %s vs %s", stack_pair[0], stack_pair[1] - ) - stack_pairs.add(stack_pair) - - elif any(x in orig_line[1] for x in ["ebp", "esp"]): - logging.debug("not a stack offset: %s", orig_line[1]) - - return stack_pairs - - -def print_bijective_match(left: str, right: str, exact: bool): - icon = CHECK_ICON if exact else SWAP_ICON - print(f"{icon}{colorama.Style.RESET_ALL} {left}: {right}") - - -def print_non_bijective_match(left: str, right: str): - print(f"{ERROR_ICON} {left}: {right}") - - -def print_structural_mismatch( - orig: List[Tuple[str, ...]], recomp: List[Tuple[str, ...]] -) -> str: - orig_str = "\n".join(f"-{x[1]}" for x in orig) if orig else "-" - recomp_str = "\n".join(f"+{x[1]}" for x in recomp) if recomp else "+" - return f"{colorama.Fore.RED}{orig_str}\n{colorama.Fore.GREEN}{recomp_str}\n{colorama.Style.RESET_ALL}" - - -def format_list_of_offsets(offsets: List[StackRegisterOffset]) -> str: - return str([str(x) for x in offsets]) - - -def compare_function_stacks(udiff: CombinedDiffOutput, fn_symbol: SymbolsEntry): - warnings = Warnings() - - # consists of pairs (orig, recomp) - # don't use a dict because we can have m:n relations - stack_pairs: StackPairs = set() - - for block in udiff: - # block[0] is e.g. "@@ -0x10071662,60 +0x10031368,60 @@" - for diff in block[1]: - stack_pairs = stack_pairs.union(analyze_diff(diff, warnings)) - - # Note that the 'Frame Ptr Present' property is not relevant to the stack below `ebp`, - # but only to entries above (i.e. the function arguments on the stack). - # See also pdb_extraction.py. - - stack_symbols: Dict[int, StackSymbol] = {} - - for symbol in fn_symbol.stack_symbols: - if symbol.symbol_type == "S_BPREL32": - # convert hex to signed 32 bit integer - hex_bytes = bytes.fromhex(symbol.location[1:-1]) - stack_offset = struct.unpack(">l", hex_bytes)[0] - - stack_symbols[stack_offset] = StackSymbol( - symbol.name, - symbol.data_type, - ) - - for _, recomp in stack_pairs: - if recomp.register == "ebp": - recomp.symbol = stack_symbols.get(recomp.offset) - elif recomp.register == "esp": - logging.debug( - "Matching esp offsets to debug symbols is not implemented right now" - ) - - print("\nOrdered by original stack (left=orig, right=recomp):") - - all_orig_offsets = set(x.orig.offset for x in stack_pairs) - - for orig_offset in sorted(all_orig_offsets): - orig = next(x.orig for x in stack_pairs if x.orig.offset == orig_offset) - recomps = [x.recomp for x in stack_pairs if x.orig == orig] - - if len(recomps) == 1: - recomp = recomps[0] - print_bijective_match(str(orig), str(recomp), exact=orig == recomp) - else: - print_non_bijective_match(str(orig), format_list_of_offsets(recomps)) - warnings.error_map_not_bijective = True - - # Show offsets from the debug symbols that we have not encountered in the diff - all_recomp_offsets = set(x.recomp.offset for x in stack_pairs).union( - stack_symbols.keys() - ) - - print("\nOrdered by recomp stack (left=orig, right=recomp):") - for recomp_offset in sorted(all_recomp_offsets): - recomp = next( - (x.recomp for x in stack_pairs if x.recomp.offset == recomp_offset), None - ) - - if recomp is None: - # The offset only appears in the debug symbols. - # The legend below explains why this can happen. - stack_offset = StackRegisterOffset( - "ebp", recomp_offset, stack_symbols[recomp_offset] - ) - print(f"{UNCLEAR_ICON} not seen: {stack_offset}") - continue - - origs = [x.orig for x in stack_pairs if x.recomp == recomp] - - if len(origs) == 1: - # 1:1 clean match - print_bijective_match(str(origs[0]), str(recomp), origs[0] == recomp) - else: - print_non_bijective_match(format_list_of_offsets(origs), str(recomp)) - warnings.error_map_not_bijective = True - - print( - "\nLegend:\n" - + f"{SWAP_ICON} : This stack variable matches 1:1, but the order of variables is not correct.\n" - + f"{ERROR_ICON} : This stack variable matches multiple variables in the other binary.\n" - + f"{UNCLEAR_ICON} : This stack variable did not appear in the diff. It either matches or only appears in structural mismatches.\n" - ) - - if warnings.error_map_not_bijective: - print( - "ERROR: The stack variables of original and recomp are not in a 1:1 correspondence, " - + "suggesting that the logic in the recomp is incorrect." - ) - elif warnings.structural_mismatches_present: - print( - "WARNING: Original and recomp have at least one structural discrepancy, " - + "so the comparison of stack variables might be incomplete. " - + "The structural mismatches above need to be checked manually." - ) - - -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( - "address", - metavar="", - type=virtual_address, - help="The original file's offset of the function to be analyzed", - ) - - 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.loglevel != logging.DEBUG: - # Mute logger events from compare engine - logging.getLogger("isledecomp.compare.core").setLevel(logging.CRITICAL) - 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() - - match = isle_compare.compare_address(args.address) - if match is None: - print(f"Failed to find a match at address 0x{args.address:x}") - return - - assert match.udiff is not None - - function_data = next( - ( - y - for y in isle_compare.cvdump_analysis.nodes - if y.addr == match.recomp_addr - ), - None, - ) - assert function_data is not None - assert function_data.symbol_entry is not None - - compare_function_stacks(match.udiff, function_data.symbol_entry) - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/tools/verexp/verexp.py b/tools/verexp/verexp.py deleted file mode 100755 index e112c07a..00000000 --- a/tools/verexp/verexp.py +++ /dev/null @@ -1,75 +0,0 @@ -#!/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 deleted file mode 100755 index aa294659..00000000 --- a/tools/vtable/vtable.py +++ /dev/null @@ -1,111 +0,0 @@ -#!/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())

{}ZRk?AmwqlUJ4!#EveWbvQAQI|%ErO% zvvcr`YQA%F@Ce~cyFh%Wqnm~ zVSECfaB2uXGR)1xK?vRSPB74@IuunU9cLaAF-L{sRVUx01QI5lF>Y;`3kMfC;PVP9 z?T^qZyxZwdbdI{0=%&I+^$S5@Nq&#uFGw0w+N}LGTw2SRzdT4!Zfyv*?!ur-5t5Fh28gHpYS`Y1&hG=B&O^ToxRg%s z0iYBvL)8)Nw+Z*v5d@)s`tfDp;91iE&Thcqix!4$y)fOmVkOXLYc~^B*Ya!A<703} zLX1+F2r1e62PPY_G~uv@xMCccqIJm53wq8*rbr zgYFLFE-=WY?ZH62*OR!#xjki8F8%NcITdyFJ^uR zGol4`(hjZq1Co07J7q=1+5K&|=jcZ?Dp^QSaVq&q+Xs<}cz;>oO59eQg~O`vgi$Rd zlT4QW1jJFy=!d@1oWb)v2zW84Xw$ z-AKF_i-J?b-8BM`Dnk; z-ekUEug(+5>(}3Jxp?pIR0lG6Za#s<{xX>^47v6*@};9Xl=x0Cfvdg@iAA)$z^sf( z6|8(L0(KA>kLD2ls;R`3e&P%Ski>KdKzfN>gPgIIbEnM#ZU(Crj2Qu z2nUuLs!_L2;04-e1Sbk*szw^GWXwr7!O3L z+mg5+uOr8!znK{C-XBScOCX}kE68UtrFPq~yQiT0hUvx=-HVdWX5lx@=#~)OEJ=qS zBZJB|5n?AGrRdumP^IK*gu)p0AR$wk%Z)?Mx@+eH)8aWKRM#4ydI(ftc9{P}nD58H z5PAGqWmycNNnfeWsIoo;ozx793T_~(HLNkC@J3H1H`r+z_QEJCpL;#J9s||k#8uLX z` zOIbM~{6I_(`L`0G2ZkiuK4Wb5%t)fCQ33|YNnaV z6~5a}gMRg=&!jtk#?^Ltx+4MC5L}aSEx@$~R|T%`aAD&!$JMx!aZSWkh-*ErEx4{l zF>md1KAY~i64%YRmf$*ti)tFBBrKI`6=l(%;ZIR3P)d*N#!6*J54gq~rSca07ZO`olobq1PDokmnzsp|i6m zf=sUvG}uuPOBUsUh=mv_M?=C#&VK+$8%MV*i`L`Getdgv$hq*tZG4MD_`D_`4he3` z$FyjETjWN0amLX=#%b$h`6!!&v=9k>O$p+lcxZ04_&)bo7P~|EenSf+EQaPr(u_61 zI5)HHCzySO1?a-7DmkJQV{X*1^IvrNdK(cz-Lj`R&qcRv&99=s0Nj5#sYD-9n{+%Y z<-D@!4NScTlDp>WZ;dw3`8`TA*bl=5^K{VdAuD@W*#@<3y$K9x{yq|&&K*<2y4J(g|knKY|g0Td_EWwDLrf= z#)pWw(-eVx#!3L8{eqR%KVrbw2+pfQSQV2M+Ao+{P5Rp+%MV%^ewzGKjkT(o;ylS8 zv~9@Bieupc&uHh4#0uYyIQFrZ94e+5ctYj~9iH&gDKiF=VJ(GBrHjUOUqS%jWM*yt z(KKXA3NmHUg8h(m42;YZCpBb7ei^{yWtpwff8LMLp6Jh(Pko z2s=lvzGD>IRh&^pZ=-La(3uC-rC&xUJ^*qp;3CE`(BePu@v2%zZ6F!^#RmUg;`iJ6 zT^7I3p0Sv#4^2nokjINY2%-hX#4x_HHTzsiea= zly;3^qjGq&sUxX;Kt`?d%or3**y@YWk{4rz@QA%iN%EkyVqZ-zmeG(;GPdi*?Z_uM z1`@*(&nRFBsX+%DMq!D@D?nPMYXzRn(uL81#*lImW#h{a;Tv!ASvBZ3r371=+N#e_ zFurY+jq5|$uQb_3^$E4M>hn1p--(bib=Y2}jOw%PE)=D@jl-9# z5F6wPY?ITk5xY9*{cWQ06|c<`Pn)7~_`6lU+t?WH4Pn50uykD@qt0U37ilHBZwN!Y zjp3lh@Vbp5EQA3iOKKQWM*V%Q@`bbKiaz)C>fecvnWd`W(Yr~VXbzq|K}qMMLwjWs z7{|ueD9cV_O^TMWJtd=dep^~jJb!onziF9@uS)NT8Utsag9-1|IBPSUYY-Ps5*N?E zaEW#qH32ESc$cLx&Qe&K!3qt^%R#wUP>LR)Sp9qAXVb#Pg2BX!TH=~djas5J-SNra z?M#@{ygOH8U{BB;A8q(O?yUMGS@cIl9rD|(kiN)X{VsHN;UE1l5IA%`7}_5R`f$)W z_eX9KbRnhbw2P7|`Xhd{dRA%j;mKZ_Li;0|Y<$B)_)<+it2BxJ2qf4F)Dt!dmxV|; zk4nSTd|KHU+8-HgCt51Nevg*vfCePF*(?7klNi*cqtYA5|9i9i53j$ znM4X}xc9j#q!L?fuf)(Z39bBg@qo98l1U+aqf9>dTy{&UxmU6~r1o8Alh8FpLOWA} z=#@aTtxGc2#&;y7Liy6HfuPyylI(8#S+mZ_AKHj|(`?opEHj3^uslTOLQ^KHS*uq2 z3{{y?NYHn%g^5grU2Q!B){eY-7Mckxc_C8OP8}F8OAMDx^1cDx^2n zTgDRgrbZq!Blw3jj$riE@REioq9Fc`s?zy2+KAdg2&xn`#f#no$f)%?pw>GyR+NV> zU~Hf*2+r4=BA*i?4RY#Oy(P%=^u_7SC*POBS84H05PW(1pF)U4_4sqOQ2`#s9aVtb zpGuegDe*q0H$6f%U{jvTy{P%MdR_#X<<`FiBGBz4xXdv|_j9v>)b4f$^K>7cv=O_h zm}cxP+MyKAMRBC=(G2{K$&pR!_dGW2NvTwd(y6%evA!tvdGZu)y-E^PD@8XF0Ux%W zv}3>s^lhj&vH~NrWj*=R2%=-Z(0UKP?*C)!wxLs*|QSwl+XZx_iPQKqQ}WYwd0nhfT9!uF7U#AX}o zRUxd@TN71xNL~Mojp2Amc{pU22hn*#x@tno-WVI}+aav4nyeumt{ygqf)EC*RA(Ad!d8qb=#{ zunR1TmqAH2LzSSkO8DXLO=BJx!k*G zw)WXG$l;AjOQl!hnJ4hp60hDsE^J;rS|$4ux8VDIA!JL%)!mE@X?y8gq$@=2I+H=|_g zk2c|};^~eYUD<+{=ozkHAKihW0#J)@K1^cVp#GKlPCFlaI|MR3+OBLJ3*sOh`!>-i z-uFj1?*k(|+P<6|ZM$Aw!3RPETVpbx^tR8{vDAwK46#3%7}tX_J69iy5{B4ksExjD zuKqcX1i6@afgrw~L7vGnQ!z_Y9Z@@Q6ugT1{ETx-BR--+uZ}3kJw5ADzsd6L z^o?6fQ0(jx_Uc}6rd~iK%(^pM{leta51vDbY)4oahQoAw_$!>%5eKE7V#ercXm%3& z5s|NWa%I_)Z79mUk@T`Ey6b_vni?&PPUloows_m46OTD`Jo-*1l~-zmT$BVlPv|82 z>}S09aDQ&3em4?=3LanVRzu1>~HEKOr5?tj2+8I1m=Dx zilX?Y<*XP;DLUhOjZPn0J2iEbBPC)tjK*AgJ4#Usr)E|j-oyD>Ye(*Rl?bsKub7|A zfm}shL1qn*`lufI;V=#2GI%K~^Q>jD5#*Vf<`T7NwVXJE;1Ay zSEIEfJG4$@aBkqS@RVQX+|1%JFPp*|=5&_^lhWdTM2_Q1!u%EC()5&_Gi`fZvz<=} zBe*O{!8(1BL2AT2LHb$Kb@B-`KJ?q z!K?^qzN=E%xIM7&DXLtXE>jjSfvYiF!J1 zt;X-C1SuhipEVfX?y6?P27OT$R}*D4d8sdyVo-#yJtV6Prm7MHE`n-;q!R8CzR3~3 zsXK?Xy=JCu4<6wgvP=uEUXS@F*iF~RxZjTJGhFL&ZL~dOn6x^Af(hmZtGQl6=f|Os zg<3s`KdWD*;95&neA=t@H-b70+3H?O&t#|!_9Ftdj-E%d3DNo$U|d>T7WXx+aXajHjvtde*;o* z-skJDI}K4fQT7y++z=M@_xmR3Nrs88oxn#3d_#W0`PY2=_1R!vja52?I(Akh`%eOv zK(KuhM)rbkD8aS}Si`mY5_r|jjW6_i$j0dmDiOWRL>~P81E7T@TWRgwpNwb4{sEKV3>)pWx^6(m49@l%~;7L@9ONq%#IfoBoO-fn_7W>o*G! zrKz{Rh~X;;-dU0^hZQh?J;9s;R{teNEA=Zxv63kL411vd145;+m>Oc)4LExgIY~~9 z)ej*KIW&$FeuLm^h|?p9N!!fqaCGyJ5 zx>$W~h?YA|Eo(@Mp=B0pNh6U7QcD4AnL(gVf=PdvL8}3(SD~crJwmEL)_;o5jy{sn zdkJ{tR}lvPg4}`gOB(@0FuFe_|MPK9TO`3!5O55~0jIAQ(E6uH+-8bNDmlXm^{ixg z08o4_L7=B3bvCGts!UN|B2>v>tH(2KzpTC(6Tuj_evcu&mgtkDs0dPg1}VY>L9XiB z1yrm+`G0nJd;5P5^D1SV2>ckp_51MtuICAh2zdOo$or28emlbx2)cIzIsXX8zwa(ZHBy8~!xvM;`B%yL5OMy*@Y4ieB;jKT{w3gAqrQvKxoXU3!2gNy zp890M(|NhT-z4zk2|rlkV+haZ?>6es2>fco_mKG0sBiUOsZ6T11dkGW_3s#Tfk0I=2NJwm!e1cxAK(lWs;NM5{jI+Vtj1jig!IB2x$}+} z;6&~w)K3ME8QWd;mmrlxWII(`k4V8Zfvw+y{82WFLNr=`4rXVouaWwpOy$w18ocEM z?F(r0g0u`7sGsAKag=DDMoK?0@UWk3fKL!y)w^!>FSFL@$9nQ1(sJ(6iJ zY(ouE<~TSA3y<}FY-#~f9BT{)7j9cXeKbrVTTfC>Nw z-9Q44meRVw)IzNF#)mI&ArKlmFWs&`jr?tBFA81Y4y?u2L!Vndxgzpm^HaF~f@>qLeYnoxx(4YO zfa`CtzZh30t|_=8fOELQLO1LVcRF1zr_=sEIA-a7Wn-nkZUJgU%C(n%=HMti@VL1! zR#E2>W?zbE;I2Y;_&=A~!I*f)xOu!lPeisatzG&_!QqRv2f|1rTNSm6q^yuqXeD;4 zA%!uAc+|yxrZUVMp>IWia5W;ktn2hIP9VnCGEBrxO2wROmR2tP1ba->`P%4}h~|cc z`Z9!=jVN)ueghaSBe+A`Rv5vNRl3I>S$7E|tYb3ZAKi^4!AV%QN-22aCsE(bM9iWC zMxgrvGFuSQZs_mAk6VpXTiRr$78BB~BE}8n< zm}KePA((J7A(c+A!N%V0B-jXt@uV@idMsQ)@9J|qu@`5M)?|?cB3fF>TZr7CHzKik z;Vd|E8|Y|wI-f?bE712Mj_?qUMrz8m#o!30XEXv@skfq8XXd=9~yR$B~)uR5r6$%fDI^#lzwie%6KOuCqH zS2K>)*a{fND{~M-xp{csU-BPn)}mN_xl+=Yt86@^*tmYH18d$3lIp;1xa145rF zG;)@5s&D z&5gE+6VF>$2b?xWLzj{VegYR{RxIBws*&^ zoJh;w1d=t(M&IT63tAZNS|!c$TG|GyS>+{7#qY;kd~T;o1`c=d-DCME5PRTcAf4DWbDN@qj=>pIC1P6 zS>hp-xeWLB!wgd$Y4Gmd2_yyc!i_W^{59%~{Iw(xyM|)N$p~*G{`yB0bl z$Z<|pd*stTQ;Hhw0_WD$!j2HBK_W+ZPUy_-v_WurU*@jar>3bB+ot&L_&3$yj;Cv8 z;SMp$@#rZY(L|eJAxjXHv|8WTb2$DKx0 zTV8wx@6l)rOX0a-VkIXq3`>KHuE4Y5F`m{M_YcEQ!*Cbc-Yz^Iji9oQNX3^+{rbZT z?}pKLrR{*zGW+lf7Ajw~5OA#nXspVM!j1+mrKk=dv>LbcJ<$IuS#iyyEZo~iq=jnl zh1DW0Dr0&u23yO-h!QZ}EbX6M!`U4uYN9k@TQ~>wXY0|I+rW5hEyUR9H8`^owL;Np zP*(HOR(rMa^*5vttAt#>NgC6@;LgbD*%1V5r;Y66uXpJ+7$s}y(TX91VF=OSWAX7& zq}>W<6rF-9WWeO4PbcV&C{HxsTDFeTdipit)-kzB2ee?8rv*n7}+oUTP z)0r_;L3=0Rn-XPXn}yh}5@{CmhXZkVBUCECO1Ac9*D#v}_X&StnNGwltj;|Li4ZDYLKU}VgBEWU#I#E91v zJ7YIu^x7D&F&G)MMKHq7G&@%^asAQ8)lenvWXv*?tH7INRC*oNY~peYF5c4u;rx!l z$jC8*k&(8tMYr(x!s7`T&o|3gla>)p-_~P3-cwvu$73_jylm~a;y@!mRA-k`02IG` z6ptY*9A=OuH`Gxz*%q;lzJ77wIEYfoJ<9UMSRkqtcEaDG{-#dK;->3)`f2J3?5#QR zn6Z=Fv2Th0$?!bY3z&WYk4fd&ld>whxQK*d$$oyyXNtcD+(~EFg)Mb@UFEuK2o3Mo zDTtXgghQ^nj+kEovnIHzJZYIiKz>T~%6^eMKO{{m*a}dP9yYXB{&nNJ#Y#bJ@RaDfYcikP%tA+D|$$|A$ z?qZIDW&$hlfGrGc0|``0Y$d~7y7CRZGEXE}GGI%Gk^7cbl;uP3S%K&S_CCtep8;xRA=MqmRroW=L| z2QeRUn&@hQC~Km-U3AChti)aYT@YFp`#sGIm5rw#PuUv!i73x0Kv)mw5jFc>DJ7sEVZTnam^^V8{s&Bp^bN zfG8kH0Lc;{kPH_wFc3(HfZ(oV$A~T~Gl0u2coJc998ec;yXdOmF8boiDq`>g31O3< ztbig0K@5W0i7Q{X`_ zq(`@W#Ldr%PH}Mz>dDo9UMAChGXyn7KH}<;dU_m#M-wRICl#-?B!Q@%| z4&MOq&&RjxOqi29T5~dzA1C8R+(Z#PMh((Lq*D!!@RAuaM4ja$DVtL~I~rBXB!kB< zK)LjVimx-Z3F+zVXK z4|DxnE+V<^&wLyrXs1kOu9G-9K*{Nh#I~#ZzwxjM4?dWkjdY##P^8FSuqaM+lwiK3 z9%yE>Nc%06+(vXuehxL%k3bq+jN93>=&e9jr&k*WGNOmQj59#GSOg7P% z8^;Vk0%HDr;P*|qOygH}i@~-rehp!y#Pnkr*+lP9X{&$0CE_9r^;+IyspyZ(ZBbPH z(Z)BodOtWpqhAx)h^qH8K!|#o26ad(B-fs9hoLo_a>}eiCR)CRM|WhHaQahul)@Rl z@mJs)`j)rNlva@2C0RdI+gp?Es^A@ zGMVMpiIe9LjuDa--)fusZ!29w{3nflBaN#D3paX~i{Od@r+vUa3?}*+%lW+gJBq0N zjM!O3AU^;rc=4@{p%tgE4GcK-u^kmt-dP-n8;0{;%K+*dx>)Ry&}z`OWV~JWoghIb$ttS{db<= zO22vbyM{l?In6h+`^)%7z$S@72KUC1|B#ESU|xQn0|!?Bbv@{`gy?7>Le6WL-T9ku zfZlEjcffBU5!nH@Q>XeIkFcC!e6D6n?Ht9}-dzS`obwyWm@ogb4_-*#b?QPn3oMS!$nmC?Uc2@1}pF|j*wUSK6M7ZUw8(yV1sv-Mq}99 zV62&V}hS$zH}HTXs(`&+t0r2nDG=2X?*BQ|C@l8(yIeX z@!Zweo|M>(^F4}~DDl-2zJ=Go_iMnrQo=@M5?c9-wNm0s{auoOdPOZ@#45|o_( z0isYN2)x!MrVukfgw3q(_V|o*$L~TDn)J@W#a34a;d=$p0Oc)an1)!+vO8m`69tBw0eO#_k4aBdRB{>N6Wj<;N^JIzHmy3(zp~wsGLCLWwiOw10 z+7~XJ%B4veGN41EvqXm0PZY-vQ>A$5XaQbga|XfOW5DdeB-EsyQq-!rPrrlp7@O~7 zPHXs+gqG_R`#!W`>S?!p6?~g&As0H{kmzWSiG3Hpt)V?|>U(xIeS>&}8NdBccOzDp z$GFafS$!C#q`EMxMf@lonp-2R=0>x+(}39>#p<`P9Zx@==VOB$G0WgJ+2HkL@NF`F zl2?m?Z@_O*^+G&ctK`Z3vX-q%?gvv48g3O)N^V5M$(r^C3VjW?f;BWUi5CDGRkaQL zD65uxDBKFmBtqE=$LXOejvT;$!r8)1R`g@gP1Hs?64W=F?6Xp`84!~E7G&t3kcD=o zhROFtBFMwsvSbtSsF6Y?iq8&)sb2>6uny4sBh@M!QJ( ztBfSjLQ*p&-H#+~avTn!)c2%W_$gY``o%2#GqA5^!4tAqiuQOr0p4mt^V8i(23N!j zdLmHr{5;v9VzN=vodg)mRAR*3f04v^#{LMQWM3Q8e_C9NcaK*qTIg`t+Hq$oudTS1 z!FkS}3vlRi0=UaQiQvF|nu?BQ`iXL`_~tv?SbuIzt?+SiR_)+(DwXC8PWH6LubGQvtQH3F2x zKut86Nk=NCCp>GJJ2*U#x{;H!;?X`%v4GQg9@Uo8-^rq@#%OYL1&D%++sawgzwtn` zsKzAF-HvS8RX<0v!Q7^gNI*=tl2e)NqnyffsQ=+qyu8LH7I7+1p`Jo2c*L88Y+~1- z@DyqlCufoS>lBro&QqvDN`EH{*V*zEDj$!*#U14Y>JEOmPEMdk;ejSlW5BR6FIX0d zYfziFGnDj(OnaIZXxs!}wS zX90)t5H$8+=J~Pz#{63uyQB z#WBjG`;h5d9%n4-X^ME53L4Xc=K(sN)B{t4#W4?^@;?n|lIS|Br#)l_Hp_fLa||LpJY#-PgnzD@f3Zvz?>ciWL%s4kD8fzL&B?4V<2x0AID z>$jK$)Bsvzw&dc#*a(l0%z?)F7b$2?_T|D8z+9Apy$Shp*@Qy+a&4Gt_UCVvY)DV? z2?-K$=WXQ9Df9@u|L6l?MB zL!*A%Br;h)g?OrJOG3YN4pq80)ugo(1q{vZ^ePD*@|I~+uB84MGW1W0`j#;DX-ErG z&&Gp6y-kGrtrDNn{`4TJFDL3e-v#|+h(_amP{1o%P!v_)#X`G>QCh{WNd8Hg@0%?Z zU}%`XVSv@&avcgYw*!y}9TIOmdBALigk_^OH!C0|cBV5G^OmRfxmf8!KVq zwoHfGKaB1($_x)q*>JcG8MM3bInV+)HPB62I}zD9Ia3RR2D?^5b}i+*kWosLtV##@ zX*^)jj-nTFipt~ca=D+B?vlkzSnazqQBphzI6V`K-bip#!dyxigiDK&a4#j?NeMj$ zlB=x-l%;8KKZ~(M6` zf}o~HpdJR)yR>@Z8)q&Y`5_iilCj93caV!ITj2j;F=YUfr2U-;B%m3!m@*tGkiJ|> zxmjk@T;D0SAvPk%x9JSiRxYH(@Sduphon>%ire6%8PgjXtk%vlt4yhJ_1GL>OE&L=;dYIauRA`X_nPJ zGK@EY@dBaSI^?(>^Oo6_8Co_G(-O`R<4AKU5@C@)=p{M0{(XqsGf`4AuA^l=k;U4{~h{*SI~>ToqxFwqTV2Esr=F-UEa6Vqlii z>O*C}#~|lA6E1rgz@&DDwD#gLWdHV%!t=V*Hl)&Yf>)jXB1=e3yfGEnBCAd+6=zo( zvnmLwJRZU_%mK~|PKm1H(9^Px1Mm%CRPyOwAx2jKjejgLT0sk1+^qeWhZt8Kt$bb2 z4{@$$dPv}hwyps*Z!+^kf@>i?G@%NvXX)4X_@(X};LGjE@5X5dPPEAhTBBOoF)e8M zXyjQ%Jsgj&x&`&27qXXAp|`n^jjmUPG5b6RWf~`FSqa>CU>EBA&!dd%@$gix1O<3> zw24W(NqW36@*t=3lq!u=c~qYzk;?_1_wYap_|{*+aMS`%TX7o|mJ&_sK@ugyX-L#8 zvcQu+nV8rS@|Z*rGz=#8eLzfbgwao_M#~T@ zpVy4GASt|N6d&O%H=(?ZL|0^n#(!24n0p9KXOkPtgLl^3OsBQHq;5 z#4w5_mfNCV`~PHm7%UGxx9 zZbnVX_VeQ5JmmN$Cy4wlt#vCwe8yllEFLlo7w!NHA7QrPY6!FNCO`}pmNKzs!oLZr zG(JL)eCtdtAaJ3Uw~zn3&zTaYF0mE|yfweBYSy;boju0xb_ z2ZLGlo6~|@l_kAh`~oL>KP5nY_8#I-n}b11;{*`JHZEmk;Gr*f)l<1g07ChHK)yVO zya*}&SgJ(;0FsFYux`}iAMb_*c@(KMGT1Kw(SAIHMK*x2qC2JnYwPa#GJ}OhRy7I4 zp2dDdS0<21Vyh#>vLnRqGl5k24JPU255 zx8|giNakW;PSS~!1mfgGn3JDC-q8JbcTu&Q2>Dl>N`6M^es`*Pk#p=9k@HXFP?P4i zWqtT|B2d1E;oQ}aa{tD;)=+LOL z4g7MtYNP4u*DgMQQioJ1m!GyyyXwN|D>>N+$)*B5EsMwD?E!TYScf39M6r;OH=&UK zk5qhU0KBX<5PB(P22y187a>^=u*~s2c%)UhM0zt;R?Ol)zQxwZwFJ>M!tZFK1YYe< zm#9G5zmlr`6f*p`5c)-7^c|39sP@r!sLGEMFIRvKMPU%8K>@`3$U$8Z1EtK9oN4#h zqX)V~7*mZbqmEjHbK53QKNo%DEiP|!ByB&YznDkjeJsi@w~mD$8!_`dp{S#Qv$T7_ z7`=Bti)`NqO>m8hOy^&5iYaLb!?q`EK%kjvKyh}*cDTMW(Z{~fbl+1{qK2JF6T0to z12Pqrz;;*U(e4Gj`3JQX`c9)li4>u0&zvXe9bJv#Rgz0wWn1ew4DIFK6DQXq=n|My z&=!&lk{>?DUY(W`@J%q!92pDZCd$V zw&}Uk@;~MqoN4?v+w|tKY||}YWSfrT--q9qh2-T9eBXFH+q4V{jqh~)UjRRc@cr6X*`_<|aaa2((8hm=c8D&~AsU3A$~S?Z-}-T9 zIsT`9pKbaW_3x}_n;yacG4M(9F`3OV7He!=e4_DZYty!!-JIAyL3#YihHTRlKj2+X z_|C+4BmRH&Z+QngHgBKH_dVQF)?+-*n(#Trq#zWd>22-?IHwr3wltV>Zna1)pk3O> zpOaeP;+z#R9MLOO$C#Ep3>ym_2$m(_E!8bJOg26g(K8Mze#gLaVQXUu4Ifk-Ced8L zx>h^wsCH1$HRO7i@rCA|dMCX4Svy*Msa`wiQA7|OT5BYFOP|r>MI+#}#t@xEsAhXB z^Qu+TW@hl)-ynpnsvo7+a#my$F73xI)XwH>m%|&fv#Hi2qS*cBS)E_v@$V*ul+# z-QL*w*AoddkWf|yLzSvb$cT@=ABx9+ZxqGhA&Q*yIaa1nEUTPWsK(=Z?{A_T}e_eiSwd9GAb z7UKVJQFZK=mgvTsFGZ}?7>%{cz<|cO?%%SpI4jav_%)667ccIiJv4lxb0kogk$bf@ z(bH4zK}IO*Q*hS{IWTvO3Ca|&j|@>)TzrUg1~Vy}$sA&zb6%FESR4O9trXTW#l=TC zyG9kQe%cTb=lnLR=ol0=Xljk_PA=Yaf06;Lq@#S;b8VB zgO}5Lgz$6Do}XDUE?ncdXGT;Z|3-r;J|0*m3xz78xcE?~JqmtT2%dB9lJMFU6{iq~ zM2hjT&d)g0st-Undn8nhb9&2SdPi1sKJGbVI~#=Y*F5j38JNgKj|gF6hL1IKEhCiq zROjp{cv}dbb6%D3;q#t_*i`Z#PJ8Zs13Kuy88;hk4?o=#FHe|5B7Y6(<43yi*lk*cy1%e?FJ-OZEgsXb6T!&)iexZDb(Uo;}%zaNmR++ zp^}`lPnL`bg^zNMkAgq{q`?#CY?ScVI^@|Qs?hFGAwJ|eFH{JJJd4q(SVd1;Do zoigedpRenV9(oqXG*NAnw%6+sX~*%pl6);dD*;`w2({akgdyI7{dBpmBh<{E*U9x) zvb=MfpitT|?NhC46agt4e#T56@9aZ}YoH0vt{Si_YqK$CV*0eDjRW>;YK(~6M*e(a zY?jGCh=x->A4@L<8HyyakZT|%*fmr)&UyC|OAxPw$8IvG2x1A8@$LK{!Z+WH!pDIc zEJaYdDYa`9%Ie1radFOXB}(7aIFY!Kf_QY|&E#7Y=ca9#3#HioPbcuYre+jHPVeuq z{=Ndq5lSvIzNt2`@Q+bE+90u%7y$e#is*M6jOuaDI!V-LA1!V}QA=oGR7wV7vWa?>Ny+$k zw2!XJGOEfME-S0@LIgbGc3mlwnWE`v^yFl7i z4{S0V_Zl8CX~kqXA4(-Y8iBg(TxwOTWBo5;0Vh-k#nnL6h%fNZ1Jue8HO}b~q9%3o zH?@XLx-sSM78D-Cq->;XTb=+D6OqVFK;6WRMrQZX+NXT5VJtWaQBQX9tF@<-RCZd% zg9A&W-wgI3xC=g`(hTy6T+dm>~UZsA*u%Ng^F^{92Aub%Dw9)EShqw z&e|I8T`N%;*9f4IvU?DZkex4IZh7BuJIfB6uH*gpMp5qNnnRQ~5^JIMXPn%o0|80x zAL4~z{a1dDR9VJR~Go#^o~GKVfa!ivU_6>Y|PC!yNG zsMv@e16$fu-bnNUr?L&LLMnvgn`#&R)+3%n*3q}fF0KbMvW}f7ks`O2M)5gd{hKj@ z?HU0n5x`@J-eU&6Y{pEMPG7%k9YKAaL|W2?7d6*qFS_Yw${u_L!NGjgsKV<&Bo92|xCORs^Lb5=>j zC|Obw9W>w*ua`JeHuzsd#W-hzEEeIvg81Jb1-~Q&&pBNryboBE>8fXV)-nsgBJYju)a9al{r5#^|BR`JZ@2U;&eld2MC&*ZH(cMSGM}zB_~sn9EJNL9C4%pUw3jsrS&r#gzNK2V z%Gn`9Fky{KT6KVYN2qR9O6_E-0nzVNO%_1d+B!b965wc{>xol(1w5QRhS zDoTyoappb$M(zHNoKPXfwTNnW3h5ztXMLyy=M3T;@@Cl%yZ6Z24R|r!X46nn*G6EG zZ8i>%Mw=ys+ss63s*N_@DTK*Z22AfK2Hve&JSj*vev)J}B4cR+>7oyHN z@^wYFa>7B?N=CJ4KUG*AsXm!BM9QiS!_SE9hmwt55QV&B2s!7x)ex>4c?|(tHic$w ze}O8+PQz!YA6Xl0DWYp1;5xu*fJ*ThAn7+G2bbEU->?7roPTR|C0c7lA(1VA{-prNweo^9b+Ez{sPE?=Mpj(Qic9uRY%cpjZNy zLo&;$&26H)iq7zKeyc?(xwSD;zmBfNc;No=dzPoa6S80?o?ByxmotBbOdvsB{O5lR zhakSWgnJ$@XPX|lg7qGg!_=uc+jJOdYw(@*bGE4&|Gy1pn?Atz2Y}hxg8L`|w+jEY z7KiB=@}9IhOp9V2rVeoqQ=H9Vszlxv{5Rk^0q)`>NF&&*;V7T-9*mK2Cb`UFlYj9C z)Jt6#+weY;dMw*l@paU#UI#$;d6YS9cn^A;a(A076TKaLYYZN?gdVJTI23val#ghe zW%9(9JCBcJSSp{Fb^#ZYn4{x`>i{KQ@w4do>YwSwbcTSFF9=Gu^CnW1i*bkF$bg%a zsoVom<+^b>PTnocA);urMwcn4GU-uezFkeUIJqoR#ui=X5S59ID)T0n;p9A{Oz3)- zU(;)1qb|QYLuL0fki0f(376&MZnA9Jg-EAqAn-p$flpv?PSz1afhed-iSBFCxhJY% zLWItnvS2iwW}-7B3jC{8BnD1aBXm-(P3ICr$ol-13vzw#_$8f`cA)cN6!=mG=j7`n zbTY3^=ZUC-qa$>_{+0z1T^}dW`E?Yy!r+|zmINoc6oCw2wEcV;!Pmiwv4~q09|jhLbCeGFi0M zH>yknmB}#5^hFu}a;hgM`;0P5AzkgY%Vr1Yz-O^xgk)Sn6^DGd;!&?-;Tq~(Qday~ zM##w-5+UC2N0EpG_!4GM`0lEXu7yNkyDWM)8B(xw7nkOomeX8q&OyS%8mF6;G}_5P}L(KCM_A#l#RsG=11ruC>+?|6IU31*Axa((EXYY%k@ zzweUP+4xp4ehui!S*x#kMn18T2l0LZpI*$5M{UJT2my;pS1>*|ikmi)@V#-O!762r>xC8`|625D zgTT*ju^E@;O7Ym3RW&T-XavX7(lk*aM9oa z7y05$JIG=$0@GSRyiH$?;w3~cox$4yGwG=o?6yV3<1Rb3PLim0lL$R=dEy%sFFEds z6|3^6?m$NU4&2K8y5+3E~F4!Z2T&+&bq(CR8w4n zXHo+Btg$S-cg6T>!|*+Zm~w){N=H%19Sa5Uj(9Ozi902z0ze zN0tJZ^nBNgl(Y%f^I}~=w~CeuJ)5ug^Ld)`u~5E;N))9O^siNGgP+!>#ZrgCeWG4R z0MXf3UU_8?m6r)9g0$^sq|UpWRHZ0ZjD(^bD6+Y>lCcXyQ^=^5=iG&vUOFn3Y4Xf} zDFa_?%IU%B!1lzcyV}8M6{fFCrtGn3f?br?ztTBP+=qr@D|gGq%v1=o*wf4C-P_H2 zPfVLN7|AswfVl$GO0AMig8?w4Li`i-Nx0%Q06+jrF-f^iTE#AElC@HR#iK~(CU;!% zO1sI^5oSPq4e8ML?TC*T6c~bLDQS=hNeD;S0q|)py-hD(To03{x0AwNjo$?j?e!HG z5A}A~xQz%G?RAyObrX$-OHvm!K|TR_w_;xYo+;mEzPB5sgClF)?MtiOZ7v?#Or26M z#Oq99i{`)<;TqsHYL4b$=UI5Xjeg9KKYR|nW4N=EfO7B__~x{nhP0gKwAzseJ@LDy z0=J%6`Y~<}tZd(5g2}|2XuNC$E*|!B=a;RRR*_j%js`GQxwfOK!Oru6F*J+ZZmx16 zFk-@o85UdYM|Cl!fi|0I6wE*y9VjnHu_|$!6Lx>1Rkc~Y6RqMq)SsVX@KlvnksfPB z&2ZiZw6cJ|ycutsjM&-SJXqS`iYOC^ih{3T0A|q%%o8%v48C9zJ63M`brvgBYm|WWy*Zc?2b#TASYEU zflF{e?L&x7Rd~JLxzja2}BmO$yF zHE8>*L4$ov7(sZ~g)1|~VY)Xs3?L>gJ2la}Fg4MK|Niy3*_gW&G&t>_PVWK{^x$gI zW~29#kQ|NoZl-eQs-VyJdWnpwV4mEzGwocJ|HTj4cJ1z;K`}&DGPTx z<>h;CpfbLdM{<>ywez#R@!q9Y?^K%?mlHaUXgT$;iEGZ@_uzs_cv8!#bZV0S>3P}SzKu@_yq!Y7EttQ91~PVRbWrEyZ5O1aX6yfebgfQ>O9IB zXTep7NW@n>5-lcA2W?a>*ijFfz5GLuGZI!d#cK{Qa|Ud%(H1L)6vfX<+b^m5<{t4{ zyo;^g$u@5;IInxv1POUzNF4!p8}(p}L^tp5S53Hn+WmkOfVC!;Di25#(oHt^O!`W6 zPf6Ram=EFRaxX4+b~I`^O=^7{?qbC^4U zao<>LjhUdFqLI?CF6ZPFl3UQ7yD3# zCT>P%E@dV+8JT~hOuRA>svcXu!(f;+{0CU30@iM#M;n~t>>V<3(4FYk0Y@}lxD1K; z+Ddy~K8glAFCw#Z0dF?~(zN}BUS(vpM+tUbD6LU!p*MSJ%RG2EU}{anolpbrxoQ7| z{eoP4z9yJK7cG6mM}GmNUF+K1zWn&68##efT$|ZwK zrx<_n=O7LTAi;^lS@b3D=cFCU+{L2lcIThfO&g^QG|GV?7ZYHt-|TVO=b{)c;_dgkys_4ZUdUA zw>9&8o!mKU8!b)^w77B6x7Bhm%|1emA!Ori+7vrKqdoleHYYZ-;SW}u?zo>BO_-J1pVG;zmRDyR7!V1OJPCq(sjIP zKwED2W;Lt97Nw*D>3pmr#%G{6^zGG0ljN&jL z0r4^(39F;Y*!XF{eU2AKuaqPJ%wFO|ppr$UYz*#0W2Fo*)k58O}KQ z_`)N9$BTBz)_)KkP!>)S+Cg;1Pxd%Fibnt?{P=*&^b}CAQ%Q}c-I;1vc4nvAa5#8H zs+I2g&p;F1S7FAVdMQO&{}rmX=29^}mB%#te76r-e)Hls=U>B!YSk`RA&E??SyA5v zK%w$rMWM2&!elw9bck1L64mb-`yZ4jR^4gr>o>k0FH}~?<9|XTeWcLGVEV|U4<~)h zr;kOxJ;cBoKYd=TgohgR{8R4~qPuu>m?l_$sQY;!#s7pvwI)UNryy-{!^O&;DC_>U zZ$)~2IN#v^VkHd??u2#m6I8!hK6%bvti-h4E7!%*h`tz@#bHmH(ejz^5-9>AMs!cH(?7?P&;8A|bNHd`97vgR1cv5jVaXbycn>jpZ5cUXmcEdhPW!`I}PzE|2#`(N_ zU-!}zP?59>Of=hRovA;l2ZP#&+BwWy(i%Vgc;rR*eW)__w(GyaNHEfq?v9;TBeQXo=XEF+lH9XY=yCd>UzjUSj+7*6 z8-**vg-fCeZ=-Ts=!1SC9+eQqFo~OL&TUpK%i}ja20Jg4SlD67XT{t@eFNH=RuPJC z6tyWh|3CA{X0y4zDQ`Dvr#jl?PE#8lno1Rj# z)i-pBZ_ZnZT08GVySm(J@*HsCAQKJcV((mR(xV}q9**Ea-+u1~j3=0{>`O*Bs{&f< z#j#ezfK25C=S~AXn>NvwRx4V*4+dvyuH@h%dvH-=a8Z&NjfR4uo69h$hBv01EOFJ$ zpgYwjii_c1@3R7>O}jbx`|iQtCkKDuPqZ-#!T+6^m!GR4_?31%?US?%X~E#(+2kf_ z-)m@$PWg;g@hrnBGYu&If!VQNPWOY28@iu!c^PzX&{>Ei zuADH+G!S!!c&B*Y4;w|kw=3ah1oj6)4>r$-fh(X;42{Sxf^-Us1aO!?UXXnBZ!jpo zM1X*ECt&L-XFuGRWj{3z|-g2 z-2HQlY`YeL8&NTY<9XD>;k_lq0KTsDIOdBjwW*eM!(=&tH;bh0*JHF= zRA${2E3K(@aEjmM!!3!Uag^$9iPQw)25Am>-ZT?|xU~Ive-tdpN}JDfY28HYn)%#< zUfa5yOY3q@k376OafNl{rNwP8epT%_ZSHjV;upEO(~8EF$9dOuP=n70(%8PN4t6Y55S`eFiz4 zu%Z_Yt!+iF4Z>=6xb}Iz-w#j|f}3ZdJvYz5N9iZ-M7&yN^L+gFcAkSzJwfe5-zK#W zC1MtkyqT0yb0m3(-H9KY7t&LuxBWzXSI4^WiQ#5%lHm*0nEUm}G&U23>r%M)X6D&&J5{O8`3A; zf;NdLl{V9^UT(8AMNJ1=BRj0_q|JE33T4&WdTrVr6nfP=6vgNz$!S1QN-H85hEKDUkGGdJ9cV(I8j7-lEHI^DH#)o&l3D=FV z#ZJWO@S44D@NIMI#j}V)pi4eVw26Q(z?4x-EWQ-u?pQjE zn$y$!B|-_a;_k{EusU#ocp_lHTRgqeD*W$*os2P_Qw&(VEGE-)bj_(4R6yMyqklnI zyBS#9hOl~UsE{|OSx*QKqY6S%#T<+=FOhDcm)=SVnYmzi7#WvL)<8@ED&p1pfKv?8 z$(z|s!06NN(ZOM)j40z2FOESZgfg^Ry-V@s6i-6tx(;J?b7t{~6!&G_iigay#F9@u zr_1)PGczQwlfV#7n+B%^NXSR=*oC;vV#5@fCgMgo<0_a@*qjH^_Wy4%xe^8w%_uxt z2b!gTjQ2Wik`-EvKKcMbkd2iShigEr|6d`fE&e+WELufDz=$02YyiM>nFuH>@DLfd2P>doog?F|Mr9!q`u@!Lx{U%r^ zV}SU}03IOP)tGGP)$3+iF-NFy0^I_jn;pTIs~sy|4wj~n4MxXy6pt~vGqJ-hJqHG`cdgh#VsAvuLD2gR@1CbsMGZJy=Ye1pE$i+{ zLq|6nIyys{x;VwyY1D_n*+(RVIHx#IxS}hpj&9=~!NWYr-WsXnd(Z^!5X`1sQ@m}l z1=M>7Rr3?1*n)9doOi~fRx(J2p(}paYC$x~VJpkDW|&u5)`g59j;pIQNxoH^VDnBO zhbJ{pn_$ID*lhPIP;v%mZjb>NGKwwg(6t);ba&OH{jB1X=Q ziD~UD#7bNzN*Tt2Ymi9N64Z(IV2x=a+6P9;yUa#Sgwfa-HsMFgAzN*@cg=!BWTVn5 za`SlQ@qm~;9(9Md)X}TIGSd^>_KTQv!w*2%%46~7oLK`YVr{V_R1Gt%3`7pVp?|*} zL5wSOtlXpI;T@N_>=-Q!FHb+U1}Ct%04kCI(>aAqr~g?L$k(o{`OZ{K8;IV6U#(T8 zebUF2Om~E1g6ehDX;sR`z22FPkb8U|6Ji#6EY?4`mL0XKKBnsl6wgY@3NC&PxEsQ7 z9SCj);N%VDC6n;;VwEWqp8Q-EwpLr|w;UE14`X?;_ClrEeXGy0!zmsB0W!tgoJA?V zocF~uh$28STS%E4+q{kgnq#}yaY*k(AYzP!&N(1X(||CuF>SwA^-I2#61TH(c)(+A`>2$$ivO`@Cnf zJS-{^FgkScFw00OIg4f~J*t%Y@f{}3f_hMr{)`N;BSN|;h3J+sL)&~gJFtw$5-j&Q z4q!~6bR_09d2xW8 zE7W@e-%VkBm<)Nk8W2t0sTmpLJ}z2oWE@8ZlA#y{a8QwjuR5T~45L9;a}QyKRO{zw z%qMri-AS0n0ikBoGOehd*HNJ>;)k(Ff;PM_mXXlfa}!lUfnhYKbpLVbIS1TBjJXQ*%+oiIkWpU<2n`_t zAUg*mz!ac24+1e<_ja(7=o;e@!#)&gDQw0#1GQ_O=7!s{#VLkhwoyQxs~*?tv*K+M z+a=By7-u~*(+xNy94Ms=plfLt=zY0Wa2xNGrs0mz2=z160$ybf#m=!Q1lD-XJ<|Ed z+!Dn;#>oV9woOj))@?*y8%$E=9T)ol81_XeNkW&T+1?jWn{eBZ%KU*k7jxXt;J5{s z!$JyljW`wvLn?iyXaE#Ryu=0)>R*qIR_J*g{fD6#&*^}cJ(8ifi^ZIJRkzrO7kMCE zzZDgl66S@}Ez{@N2VQpI<-7(j4@q9;GcQq{4!mp^oq>h>DB7tS5PEx8w$a`1V8SOt z?9_z~#BEV!oMllj8yn6W$& z>W8H8cCvB=FbDvw{FR~;DKe3BcMx3$g%mRZ08sw8aKdshXq2!;m1qMGQmXO^aD@_q z(V>J%TqU%BDFO`=W_Sci4ELWKuu1`OU#B?^crgSYqKS>8+3Pvr-voXsffne{b(;L^ zk&s8cF9xAqrWMc|ysf*G^`>{_DB95BR&d+;60M3J7opt}s87-$xhfljnj4t~GIKsM zr(i#Z<9%#=cmrbtXcWLiKqFv+4izmYflgsDU!^1%B+kR&vi$~TXrcHEp7pLI=VrXR zy3%u9i0YeJs5h*W&EEl9q*d*vQ3%dNo8Hv}Y3QY47MxtHdzVI*>nk!#E3~T65FFEc zFPiU0tx7w{BXIw{6kpD~cbq~XURE4 zKFeu~v;{1it4N1ElV59}rd6!@nw%5u;F`-+AMC}E;_-CG#FY}kRyff;SX$_95gWMe z-`t=6M5Ykqbq*X1Y)m?Usg^hAke(vpp}>bh1Pv*N1sXA-m1J=LbBi+9O35ndtkG$( z6>2N)|DFeX_g`e}z7xXgWb|3S6q?1VTd=1I>Ht@X1{qYLj^(>a_fx{T{q3-TGxSIb32v*+R=h{FSVN4i;Fux7ey56Yu`3A1_DYA#ZsI?*R zcqwq6!J%%b6e>>a><#xjZYB31q_>Atyg#)fBNLnOFsOdi`{Vu}k}i#faZov+ z4zqkUqA5{`iV2ZIh}QV2cZZPO5=Is<>T;8;OJ}MJtk2Btq+c@+{TDj@L8o1j=|WY- zENHjhhWb{h4I<21yMx&mw3*F_Vf+`3i&&gc6T62-oB&V1Fu#ew=gZk7mLi;hVR!+K zn?>^rAD=?3H-_Q68}Jh({ID=Qdb`)=bz0@ZABBN2DbD8|+=>yNq%e_pwCp=7!2zu{S2U0?177R_(|cf%rNGE73(cbu zOp4KJc#=kuU5kwDQp`UTOiV^R&11u+s@8!+3I;2sC!zZsmB}x&6<^B4iv*BReCaNw z1hqGTQ)}9BJ)vj-X2Xf7FN0EaSt%h)t?b^f`Uk6*5bVk{LPTPk#<4%AI0w#Y7X1{C z>FIaVrb;ca23t?zdXq;Ss!_>M%!>6kMP^V|P?qC=M)ZcE)P_W5Hx{R7DJ5^93X`zBt(27D z7rVJ1((>KR<^n{B3d5Ck&xUB+PKd>=$l2>EKdxs`QAk$X=%5{`gO;y@nqVW)P$T(AQlk&j^+&W>N#5V6lmv6DR9IGC;J?AMOfS!36sDcd84yvwlC+9 z*oy6?=v4}tb4Q~uM|d3qJ3Wf#Vsnbvhg9kK2ZRT|i&r+AS8c&9jqZm+)G;FpXa~XS zA*c9|I)Tlb6M)05=j8J|0{-J6ktK<|M)_PPY<$BOffVpowI3 zl2l>w;7|ueW&TR=4x2uS``)pB(#;Ks!=R{lWwj%;Gr@=67HdQJO|p0OgLcwG1~Ev! z4-^{98}NM$)s#%A4c*JkSZ*w-P)h!2!CZJjlW7l6&Q}J;Dmxq1kAg*0v}(!WUDenu zHFEFjDN4yScpa+1$+Nb~+D`b5D>H7K#8+mt#g}V4Q*RPo(Hs1<-Q87=6urS!L0{jv z_R-f#*8%$aTn$WBO3st-u)Y?bY9LoxPwTR302bjD{xMG}eH}l_x`L-F$;`;T%7&L9 zZKY%*KGnd4)%UA`@hj({FF4dds=J3;@jmRAvi4(SyUu_TZ6aI;_4E@e7;|jhf=z^Z z!K($K9OT6R53C4I-}q@qERGXVGocg~X&<3-9+Y!65YIc*z@$~lYGCpzyBf$@{h-SV z1o{iEX3BiV)kI&9yH3!T7u9pGRs+-Bi%>K7J!)X4dpdS!xUJ+QJ-%P^H1~vacm{r$M`qi6$^`l>1=~s99rO>ZL`W1^` zMJ=>aZ8&vJX4_&s&Kdy`&YW{__yKT z+P~6sJM9CaCU)IP&Covb2%-3L8brs_V6|4@1O__D85jZBEAISkQf}n36dP#}zx$dY zk3t6sA+bB%F!N|6?pql2izr+_;zE_#-ODwDX+Zx`s|!^zNSR}#Y(qi4Bc-)>%{Quh z{tW8gk1RW3r&A12#q&L&)%z0lL};qq_csLe5r#!?HryI5_>#k6dJF&W;=dCAwfO(* z1P2s`?iP-*SYoZbjuIX?qr8N%a^xzCQ7i!6HIBCCGmbiHShS--w0R_~(v?cw~cMR9su5rWs) z(V{~D(iApR1GF+tdV6>YIX3Z+Q!+;mqH4no%~6}SpGQP&oJ1pWePFiAu*Fa*SrQ8c z^(12rZ2%%#2&+00@sAMXr-)2r)<+wc5I_R0@cgd=MHmT!x-&(YDPyd%yRMuJpR%d4 zm!p1CT2MXJ-@EdRT6taXL;a6=XNt7_Ln;uHT<2(6=lB^nRtl?9@;RhN7TZG~^XOv| zK9o|j6j8EMN)#l(rA{Si; zsvSR@{RfSNZ#bdF?B9)qRuo<*i2d$iw6Q&>0cuOp*+$U-4J>LWJO&ZOyP(icBpOpq z?0>J<-GIoI5IHBry-;`k9!5@q^bXO1^gB?AAykP2-jy_k5RVc5HlnkEAE6A$y%7qn zji6ZEN^~rtCjsI-YD?{YMp&sktvtixmpWKHBGr=;)pQglYe!ADkDE>@U5laV7tMv1 znuJC|3-v#SC49dNwugael{7BMYq08-p32(o5Dl*+Hi#R(BFzdw)arsG@CWMH!j%xe z6v{O>tsKoT(;o`U!1jy)2EpNtKdMxuM~F2;MBs9CY0!HRLUI|liA;&DiCPAe`2Vn)VG0%TD!ku;>VgRSj0 zaqE}BQ7gJ%RWCBq5#R~~*erp6WaASNLYbolj@pgzOwppQS^#^cR-9t25v?h6{2T>% zAOcu^72qDsVySzVK8eQ03LQu1=a#Ordd6YfM!L;C5|TK=f%$FX+;Of#0dobVLR%yH zQb($FNF)F=`VIB!17bKp^x?F|hq2tVig7<{EflCTzxI$I^lG-XSh&_Kj7W1Y*#bRvGgkl0;k3Rrht*lf$S5 zd{PZ~q1&vAmLhB4@KZFpY^$BtpPXke_T zz-SAakUKp^AI2!9U*cKz>=8Ck?-3KtxTeW*!YQ5r3Wh7t<3dKZ#UmT{+1N$dv1;oor zaE1-O5vX42LJD{Si|b0s+tfs(ct4Z-A>t zGO=mgy^@jr9mr~|sbo=&!H|Yc8Z_fsq>DOZDYa%)#6Mn7}j)qeOd1rOld;cs+h@;9XNOI|#Auz5s(X z^oy2Ya1Vv`O25J-RCtiB0=c*oGBVv(>{q%&j&Y@xlo5bBS&hnj$7+uSan@wym{z>|^(= zfV|{vg=VmF_kdZne9ndaIALhQ_Q*Rk(pOK!+*TEX#y%&Zr#msMQg;)tfe`p=%y)`k zosfMTy{E=T5Ig| zg)xgRqYYS};sk>v3Q2V;iXoqJu4GWphlBbf9b?YTgUFa8Ayi}<9BqYhA*%x)Cm`-e zx_%v?+7f@~=m{Z!fDnYNHKz#hwVGM;^gcafo@ZFuSeohIil4Wrl`)bpWw#mJhi3i( z%Mv4u_0CZiBena56>LeCX8Hqj_Yyy8V|Fi zox_e|6eA}UyV$lNop!N(@)=mI6%}NKsEbh8gnIY&u60z>?kc9QSl2R!4~X_?T{42b z<~Bj~F^z%K)JQ9FY_FZ-XLLtwmw+XNr9Q`4VU!xlGBv?6>0OKoIa%BLfKyB(1{^kd z@|Q<}WbPwWaU-lL=LgS$dD6v-Luw#iS^Fp+akpoE9s(5uV4(F*uA%g0cV*C5tT87I zh}mdm#;EKz8SbRG&E1h>Gq}q`{jIrm?Oxn4J z3LqCV9xZqWIHUh*#{WWm&%*ya<6qWQ>rA5vg_4`oj<5NanjEHM9FBl`IVZoYt+M-) zRs%g#N^XK-1*=NQIc|UaIEfz=H6|NdHVzu9l+@s7`rIU?l+t`t52<@&Dog{grkFQ8 zBC-LWAB$ifn;bm!UWj)9yy$MYdg6AH8v}9~xiyje;1D$3%nec%1cyDlGogF7HTzAO z2o5HauB=8U?}%VNwoY|Mlcu6pxVoL~z)WxrXnhV`Yv3pvU5&dWe4k)A;|YU446vS? z=?s&(ctl^1y~a!^q_8RJ7D{r3lg3H003$Zgg|;TsDT9cI!K(>*v?omrpqVP23`%Sr z`p34e3*-X{*G00>h{N~A5026XV2r|@t0+ZW5AkyFN=kbySt6S^fr8i=MWTOqY! z1Y2pGb}^y|4X`jWir_zv2}Z_n-pZbcZSQs{A|j4MAq%}e7xQ6NA09FtS8nzUVvE*= z(?^BVtt(R0EAe{QF#g9Re*OB=@dsUKbND8jp(WD55ERI!I zE7Eo$LJXr`D_%UrrQ*3%eK_{te#E8+^EN2n8EJFL z^u-%uAxSAETfj2Bm(r*3&4zm{wgDN#(tMwz*6DsgoX6@>Xw?@svG(3wN8$& z6X}3>8d%bfr}uD=3`-UZ2eqQDtj_P*5Jp;s77iLj->WJUKICv5EOLiRh#Y3{_P_$% zd8~q9W$6^V-YZ>mI6B*o({8(ycu7D*uC5TB=+@s=Oya6 z`bJOaU+tZc2#mPSz%s#ZEZ)kF&h`|WC92@&c7&UY9op>X%ASpluX+mHy|3V_4uG%P zukvdv0qoslITW@Ia8XZ0xu_nCwzmogGo`HhVMKeEM)O`Xn|k92t~QLuc`RPjo{^OS zWp~_o@5)9mr>a<_*{~daKq>hfh8CI@&A^iy7^JMnbR0i!TQx-uq$?Xn;7Qp%K8Rc6 z=|m+d?h4YBl>vHL{WB;myJMZxm5jlS9K5q1aoc=F7GY%_Mc5Vt3|whnVN@S6*Y3s! zx_YM&*cU@>TIVdvpf=5v<^G0plt>OjZ`sXiA|cCsNr-5aY?LIug+; z77WL-#v^**#3HG<*x$*~UehX+znNyNluoG9R(*(|fsu>Ffy1#k28 z30s*}zMML-R(zk(2pC9TUFgn4#s9*M z_5@=XzN&{B7@=%XximK#?HVUXaSke=ml!QciT>L#siS8!85)<8#O+kenP(8dDTd5) z>it8Y{c2#0J6#QAD(eR@k#Wj;Cv%Xcly*Z3y2}}Ad1})0ScU=uNESE}O(y-_NF_+g zegsW5Fjy%)1#QcB&Bv5buT}#ylnpwoU#{Y7A|k@6l&qx`ay^F;HHwbYoj;P}063<@ zs-h7B1@}fH&lX_lB$MqEOXkI~aXorq94pNh_(3e9^jDGaU z73meqftx%L^q_=SP0(YGMaL6yyet&LAvg7-17PS1_LZxFJY{`XZj<~~SjjC=*3)h% zH89V!KcX@)Q!CyjonKE28(5}Yb+;P0Ls?(JPm?__MD#bY6SQGz4U{|ybN3X(WP*FA z=!I%Tnc#X@(U8N%cA}n!!ZUt~pi6uL`oZ{a0zShj9796)^b&`$E>4595NlBvBQw*} z#h8vL;zMdP1}Fr8YZ+9Wpd0YO)C5s$&|O5zpyQtni5SnM)&9v>JwJLGzE5z1xCCBuYsb14VOQKGYsV#L7Vb z8l{9@r>BZk_d{^1RdIuRIJ%uXktY~2^`vS3GC&yXj{&h54B*;Iw#otVZn!78GH99; zH+mS26sP_Pb?HKn;3ql+ z5}3C74@${tv>3KsJ%E|2JC|6!9Uq>3y=Kxzko-`Hv=NmXi#yG@lZ!K0$zWBy?JLsU z?*4KhDKb+X4V@9RnaKYFKB5!9K1Z61Zl9;GP{nj!uXB$eqBp1_Rw9(oFG>VE}E z#h2!JI#Jv<0Ve_Pf-hBJ;qY_uhyuJO$YcLM?7e$@RMoloJ(C&#|0O2A;Kn(^o z5Uhy_k{}7FKocSf(I!-@5l34u%m(x&A#@U($#!Zlwx>PEUaa;U+j^?CRj?{1#FGSV z5!9n7R?$*jb2dk#QW!2}-tV*ao=HIM<$Qjh_xsilw*0Y{V8M*Zf zwG7EueZ93_j$!~F?)g9efVMt-L>Wt=K7c59K2QZMu{wtn?AZHWs1szcUP z$({?*KuOTa{Dz2u_M~>yM!lt4V^c7=iqiIh)PX>CwT40RcGqMO&MDP{42?<8mYtzS z+OEL=2;O;$3qs?IFT@ofdQxDSF`RW9SHBdr<}D(MxVn!A>5{&>|!we>UAiX+=_91CC9IR+6m>6`6q!5i26ABNbM}&Jlx25_lhuw%c}Y$_G#UPhOuwHB`-NasfPLSqRq zEv2lzujs1gOI0oo7eH4iQ@`4$3EiOGZE~>wcH!n@ODGU+pUwYOYU2UDz__I5gd3g7 zJ9Ykcd;FSWJ(Rrud?a{|T;X0$_vpeDDaTJW@d!@Np!*wJXV)OMv^ z+frTI0|K`Pftxu}jQvGR7m+>o1b?Dht0ZBou(kUrSj(i`V|k+$cS))Zc_h%9GZxI! zLyXHJ?jIbY)<$mo?TI0Lc}Q5Ys#B-MDr4Okc%|S(-oOgL5}3-JB~)>j7MtCp_A?N` zxL3EKqVgj$OB6&ZwfXH3X{n1i<*X?=uJ(Gb3v&cu_Q5r#!osjaZO!fIb`cgA(vxOE*`^*SY+UXAaLai?_y z&2G1~uz0F7^lP4RhMts8vQ64M#h;$iL;nNxj7GmDKeo6QS?y3&XovDk-_bKi5}T%e zPEo4>f0#!X8q;j*FT+t+gJM;|IYJy?0pwg~+m!NB-rE_bDNw>uK<$&A#Z8 zg!z1WZ&j`OHn`Obr<%#P^xh1Y9#m4-9r=F|~%Uy3X$l+mCMuY-BPkJoU>=tBU-Ktu*5j+lP=b9+2e%>+bNWT-MH zn77HdZ#my!pPi}`w|Wz|Qe5}v=d57*lkD-q0DBmp)JMOiO1*xDifXztOA%OiI1h`fca7WF%!2N7j|S+hArG~Fy^Qy%f34l zsv=%0&cXG3BR`jg=Ip@XgPR7Oq0p%J z(X@_LR`?uOFWlFb>$QDm$2i$o#jTvby^-C?5n|PJX2`+dGTz8uyold<``TU%4eMyI zEcdU|!q@G0IVYL}^EgTMJGKn7Fkz^oLP%jHPK{+C>MXskM3;5H)@3~qdlbaNtPeH7 z=|z=k#u*&5+;R%42y<&!Al!{U2hBvgIyAd8qja^g&=Dp%YSjDH4Nt-~mr>7FLXoH2 zEm<#w8A9GjLGR4Gl713LrtKHX`Q5t|};sba?w@x5QdS3{|2k>PN+KJG(@CRf4?OXX? zqqe*O3}WJlBU;f~rSdt;N85!j#5WD;*59PO~O_?LXEXg2^^nS6UnJ#U^vXA`j36|ozU-c+;^DSKR*Tq*jl5$ zbds1hO3uQA6vep37YkX z`CnA+(Q+EA|fFF^DR4-ERbbD?^Q+7qG3C|HQA7zrC2cG$z={ zw$>#ke>O(Ur3|Pr?FnQPLJ4Y|xn0SNSk`If4v@j>9a>Yry!j2+H+s!%IrxyEt{h^{ z6SUfxHq+lela#ZvyWHXB4<*9OTNB~v71W)m;e0UAFDoV>XeHAhn%@(Lfxwb*iGmJ%L9 zRKn6YlCVOcG?#(FAeA^e;C5oK`pKgjD#N!4+!WY$NVQ9vU(snz-cB4+kMdS-Cl09x zNr}c#D4;atx3}A)t)#rCE-fS?qw=ziFmU8T4M}fgkOenvPXBixdii;avvg!qop9sV$Ru%Orp?Kq!X2)sd8qv#)sp}Hr;_u zPRl4UEBd-%XgFSNT<=s6TMcUZw@=z8HzQ%Id_=$VK~#c8g(MeFHe<`%iMLp9$8-GA z_vrEV1a^+kh*m9T@fkCAjd($4`4OOaDf~_j48}2UXh!&0Ae05Bu>JprLS*%Bd3zzv zSw+=8xel{nJqhM+-04~>mw}wo%)7ltbE+^_GSu%ca~WmX(s%~jIhNRuc5xn z;pal<>HjCl|HeHOKcR~wq>n8m3ok%+>SNHc^Xxv1=H)T{5@stl(a!ZF2*4!9DQArRJy2 zzJ`6MTtj@S{O@m{O7S7{`emw+dA;t`d9CI3;Jhw9bzX}#`Vca7dv<5|imA?f#E0Gwn%es!#A9Y{82t4X|dPYCx@^mRrmVR2xQwdKW>!(|In#0p^ z{j`y%X{E)+s6|iw1yr=bWQ{D`$$8{E-CMe49?Wj%g2hZ+*j(Y-z~5H>6n`$j_YuFN z_?^Pvx%?$}Mt!`S+IqAcDzDXYCzP!nIHSyEubhV-VkBAidH!|M5Kny?tH$f34ob8( zPF_cd0pYCls0gH4pYumDE`0(x01XZ{bCjJbR9l}(QHmP8{nBwBM0-Gt{Ll`?qSH1o zM>>QihDa+fdLA7B@Fcl|`P(vh(!0ZxdV>)!M%cx0>3v9U6Hll|X_W0lw#c}8{Sg`- zA|1M@$A5Q>Id!TlvrV_{vFuWP6xCmf8a@8IAp$N9q@bLE*_9E(%=<5UM?y;SMK1T0 zPvjB(YMnHL+BBE)wizxWvJ)zg>HvsXoJ&8FZ5qxdOe34mhtCatbFB+8L+t+niRIm7 zHfsFd0k-cY;rHgy;__bEXI% zEFC;hr1Ud52zO5PZ^tgIARHgQVWKUNtE{SX&a1}1Lv;1+_;m+RdQ-CRw;o?;EW-*% zgpJ>3`6IlsFJMzTWol5|0F32qc_|LyGzJu*kahs&ow6!*0K|#~`M7QVB-qEXRk(oL zue2_K4m&g~+&k8qwr#0=9BF?HLC0a8*l*j9FhAj53?yGpo?*Yt`G1g+t@pRxDOsEo zYqP`rE`QlE8t241c{0mq@Wh(?eWu`*diuwqbwrgKBmI+u!`jt!L8bF31}$X)uL3?4 zj9Adscy7fXz(${|d47$#+Jls5qX@mE+;5$7=2$as&f~?;jHKIE*@*dKKFWxUXw-vFRBI7zp z-rvT5k#ToP@*DEsz54k9Nj}JbeZwB&e;5feJwi%2(0KZWtQ)(0!=z|K&o;@XNuO5q zF>rqfBPSI`yU^k-;LMn;I(U46W2w(`dtRsoGsdH zC}`bC+yUc2t@`OtSv8{ayx&3ej^vO>i;+D|2Of!e{02*D!JaXg?`cHcd2hb z2mL)baBNvS{xKSDuSf1`d{`v##w6}N?1!~0Q zvb2a3b9*{UU3$pTifnEcQq3zQ8zQz9=ji;SHpF+ULZ^J1FNcnC)WSF(9GHE=iqhS^!++QJPvWYzUkL^ubyE0U0?ib zufl;Lp4&;RAz^J5H|tJBD#!79Vb`^$z|^(nc8~Od3%_cgxzbl$?ZrO_^ow9y)lc4n zzj14Xlb{k`Mnt->T}W)%6YaSK;(Bu^VqXzg^7K>Ie>5cBDo-h6@24Uh40u zRr`Rl7FiExaolx}&?J==mC!)Act411r)IB&27?a+&3tP&3Yt9v+==#p1bmEIM2}GA z8!q#42kCmWqklp9da++>UC>42$T!8h0M|)RY~%53X`qgbLr1SLO3gDQu7sQtw14vF zw14s*bL3OxE$I)9D|}5yy)mDA>3J6JIEe_Bcxi1;0RYHC)7}`db|z`{=MJY)uz&R0 zZ;)$kEB~oGzRLUhwcnE$_sEMI^b0!AF<81M-GOIKsB`EOuV>3p&XxX2R6uZ5+#le+ z-i{W^DUG2s$Wb!L9`)Y=uM2YoV5R;OMifxwUVC4GlrCF~;wprM^AyGi3{?C)6 zUVnH15AAGjiE()(L{6=p&pVM0(~OVJ^3wF~Ek!!Q;k!g zgo$vaJHZW!KvPFllB2Ue&wddSB`rKi3v4zsYS(V$4KZqlgyFz*k6e4$;C-c0Ecawm z$)Y1)=m<#}Cb%M$Qicolx?iRUv`7oOgD&+ZYn$Ct9wi>e3(lj?%p121`?vN>rwZNG zPr9Wq9wznr+s8%NXDCz(V_NUDm8?yFkVt#9NDl&Qf2`@>H)T(Nt;0C%J6(8y%~oNo z6#HD`fsuOZTY`kvI&w(d6rCgVri2w}DxPLP?QY_>YNT3`DrD`_wqaGgs4s3fz_)q& zTMiXkkK(yJ(r`jYQ3=!$_4a#0RtRAcG4>~s3KMPI4y!Od>oJY};1^Impc6;nvhzwe6qiMXid+ z8fvsMYt+BVi#3{?tC4@|Zjk$T_dUzR)CSWV0d3|s*$%CYlHTS_v-f`a67=AWhIWIl z`}=uR7f~Ja6J8)sE+aqasf}D98I{GCWUlN}a}8rZXzW3icY7;{47Hai3en2kDm9!H zcb`;czS?~(BQk#*c02nQ-q>GYdcIRNkJpR6xEsN3o`k!SxMrDsm2xOggnNsYY&n^i zhKulr*AQXV2KS8>-lAdBhG1i@5(Pc=+kXZ{4(mn|^E1@tz)L^Wo1TntS1v&rBJ4d2wgMNOWk;_>cZVv z#)DmJ&po@gnkBZ_J824)3~hWj;KOX9d63#=X(o&m@RR+G`K+^Dl; z1pl&n;7p=4>IfQwO>)o?#H&>cz{Yy(yjs-)SVXug(6*+UmA-B@g0f?3RR(QTsRbCb z>8KZP>4>ykvS4{O9%@ts#h1)HAtCedkI>6R#H*?kXZfLH}y% z)OUHQ4&s6UUkpch1^XCB5-3X{wo`Mu%&C&O`1^C+ksn*fKf z*k5QPcu{nPyD2htbrX2n?KT^-u^QMHDsL)I50z5XQ}W!p-#3}*;U03O5e1VG7pEJ4 z@^8I_>dp_14}RgQrjnk}C@1fIO_7P@5tHZ~>N!*DnUepS^EKRLdPdkS6?Qchdqx=V znhoGbsM^0Zy@_5=XFATKTTLYuCvc07jYY|G{;=Q^y#8Rp^iZy@Q-2kF`b53&B+HKE zD{W!^Fj^H0JjXXi3e>v003bmrGhi z+qtKkxRjM1II5C}7 z8h5zQ?cbV7CSxD-Gb`LeRF-LGPyUWQ@#0FK|IXWiYZWc|66?X)p9ppb%$F=5VF`sO zXE$Tqwtfmgt5KAM&`5`0BC z!BdccMQD1t!x~ojVpC)^p^}EA#kr32zHG2|XKmt&0DT3vXnmlEUnH>~hYs4XK1XZa zPeFIxtU%^8U{=gTwb-o46HyfQ{=y-JH6s)l%9}mt33(*oG*okPct25Qws1LvP3iGol`5xA2EDXWx&3%btMAE#?GGE1*O9` zctxtzwcDD(^VOvjo#&rfV|gx5J?|vuNxzslfzZZ+VAdL|s57Io1Iui~SGqAYxpcKe zWf4oMH3P6v;)O6%XWYt0K*=8Vm`R-OcO45(#F$Nym51voy z2hY-vor&<#M5v(Tg>|zLCx)N%Vz*P$bDPf?RvlPkv7GG%y1^ z4Z7C=)ALLEz?8UP$|&8YX1bTKz5X>!IiNbZX>c$FiRK}VDgLbm>3u;jB08VC=0~#f zz}oi;_Z2mOB0WvvlXDEuh_|c^Qi2`LKL6GWp*fLe!44vxF3)Bwn8*Nw1*?OBD_ML( z?A!ObWpfzte{~g!;6z zS>!|P5MbZdG}5WX;^~w@^C7lJvQ)t|>4y4G%OW3QzmzPylUaUxS{C^bgA}{A<#IaE zwS09jOP0)$e6ViZRu<6TZ)+{?8Pq4pTHuRgkvAD1EG+B|jaRtG0kg9isH+ZEW*ZSen${z_gvLV|^P|Jq&u*wa0rD)Me zj{FsU71>OZ%PM8tv=KQR--v(4`0CI^xv}Rvl*olx)~7s3Btdd$60(BGs_Yc*Ly-z! zJVNNne$ZY6<$v+~Gz|S;Jcov9y#4fvxO07z^BGBh0Z2-D7DGO+2lqaNo{gJ*z%8Vas#r-U56?FXTszr zS1XTlV8())gIezrpLh$*;^L8OigJ%N{yra)Ms}hx_vNJ{L0{szZ?^vRT8nE{14rg) zohOpHGP=QQE?p*0lb2RSMyYTAP|T|?RNMG9{le*PkxjMnLANndxE%dNV`*mJ8i`e; zyO0>(2|GEQ9X6X_ZvpjWYl`_gwAtlK>Fu{m!}G^f2*h5@~IsNfq(-~OJ`IdX0vlH7hZNe$JdRXw3bnX zAJZDblaYN{zT^|v2DW5Q7pX=hn8ju8vs!(&N-h1d=J^m3{){Pfly*Kf=hRfrI_~<1 z=EV9-hD`vl{R;_QSJQnoqqgqGII^Iq$bwL;GM5$Q;N{WO{q?n8lx1YLE-T9Oz*1gY zly@AJU-NajLH(#?w?8bKR&-HX=ZMy$D9-fp)OvJeD63;scQc3r^X{|+B zhM(hXZ$=g;tjZW-JXdeVNXZ4yT*7ITv2qcbskC?S;j~PSn`pPL8F!UQ%?&ff*4kL z)$f-G90QUk)T@cP^xq^x0`BN$!&M=AY! zJpn_(DyP^$9e@R_n1BbfoSt1}UIAo<@%15H^feVvIvujN31kOs$kwrZql-8WjzIY= zd=xdTxW8>WFM}hmY%0c3ewB|PUVIW6!KiSr8@R@f0Lzk!fpvw!EHVYiV`K-j>cc%A z$$Xdtma&q$RK_H!+2SN;<7&;IBwob{#T=?5sNztLy(DaO~4qeoQ zDPB{_Zev(eB*VHsI1&LzhBNwLmds3Yl0rrKPV!1#&$ddMiaBhLp~FlV!io$!#*|pI z^sJqfS({L)XDyhuTJoaOt82IONx262(_OIJR_>=y-0&uoiUm^nABEpNdFuS(!k_rt z-M2BURT9p%-gMLR-tcsZXX%iZ>gpOq0A_=aAFui)`cc|QM8f}^!@S6{qU`>K2w7!C zVfya~SmkK8sc4qV$V6Ij9MIK9*Co`FrQC}8AMaX3z|CWx4P#0^zHQWs zMgGS#TDOVt)fI|$j`Ba=?cd%ikI>P8I`IDpSv!}Ku{-&%tYnAbN&m>I!Qk9p9T!w~ zEGs%Ue9V2@Xl{x*tvNsehx5Zk;lyh0gz0OSH7`fG#l^GpKeDoqyiV}&wC}Ifr{jNJ ztO3qtWd6t4Bi0gpbghW565|Tw%7w^x#uz&wc$Jz;f9h?SXvK+=SDczTmb$y|XVk6BM(4S&%0J@FJ2xNM zYrTI)Zuy6e%p!;8lP8TFwdoE9oa<6wWmC2hXh zeO&NE%h4lvd+_`~K{&5}QjZ0+YMKU1`QRHN~m_99_R0^Q=v^ z>gweJx)-poKA?RU+BM->;V^YcvH0!D?x@#E(nHG$a9*pRWFiJQMW`waG2+fyj7tkt zw@L#&>W6k&^|Qh1-q}d${)9RMD5S(0qQPE=^KJlgqf=yksz~gs?7&3&?YM^Dk~3_8 z)RH!l86RX7xMW{B-klKO<4VNzJgv7C{2(uL=iy)(_ci{uv!uZCz!vz1#Ac*S=}tSN zbUOl=*f?p2m35f&;o*ve+uweWl(=aL?XERL?wroW?pYlb32y2O4~s&f5`m3MW6a$; zG}A=R#!ybyUhG~02y~YZZ9A|&x`$z(S)hK*k0Y0PZ39j25aQ}ty(b0*Ea|^(80rzr zam@JcS9r~xkh)(d)V=%wa+oge1}3X3buAp=T>8R|fS1z!_0+{4CO3LDs-c|reHswL z#WbtPVt+1<@2~LBG+z}p--ZFK9Z^b|qLGN(vBQfe2h!%4IGU`I|FUoI78MA@7X%}}pLukYT)?sK#3s299Hr|xe>ZSJqRS6dOoF&{H)H3#6=Ycjf+pIWwu?*ENVH+sU`id)G|A(MrGr*N-j}u zm5T5&VR!TJ;}!m@BH%+PxI7RoU1H7ZtvJYxI5nl}arlfY$yL03|Av+6&HJTis|I`K zre{Bsj)W%ci3zjD#bCTf{RspX<|=O8wFkeC2Z=9kyipU%Qd5p=KF<1+dJ|5~I%M}S zIBX5~78BGt^wXvaa?3Piv&*eb%w|f?%^r&o$v9#k=5~?d-(<6nZ50j!pbf;+AbB@ zJ&Crrl4d${qPk}Qs~R((2Y9I@JgSqNqmc$_@#P=_Al#oeqB|saiXP_iX0u%yE9o)L zZ9lwmR>{Zf&$6yE?M`)vmNTSr5`(70k9C5RGqk+Jml=&7kY+a1OzYDfBwYlq(nAj4 zr^ThhlQ9_*wEV>zY&@e$Eqm=r)?_>$JlWW>Y+Ay4M04JM59}X@uX8B<2e;bzDIP;U z4fgXt{Qn2`7oaKb!2W0w_SL{1$cOt~DbTM^f#EzrUjXQ5Ti4j2muxi*X)>_S)R#_H zAelQ^Hbz}CMx16Alw$V&g@4E&lIkOmg7Icfd7ef$C8;Z#-tTnElyyG6s9 ziw&*ugsxX}wrl--ds*B1ZOJ8VhXB?(ZJ7AV;>%SYUDI?30*mhmbve}**wr>BZ(rn!oUoA|IUx}wa~aOJz(JgW4)aD#VgdOXrH-&~Q`zH_q&%lnC68jC03Rgflbz$tCFZDG07<~C7Bh%sop*%b8Jv? z}xZDz8TInoTH;_@R+k@6{#^4v)I%t(1&Q>45gQeMWLtLtUfEIuHJ}+7qevMyh?0>OiDAJ5rq!sh$$4&W%*h zj8x}!R2M|5i#n=HBj41ueN)$#tcyueUzE+B^OP%c=Td12jvAK`0aX6YikwKLc>a%6 z2F!{nk;-fx=$viOR-E}z-S|H(%Rb0q+{l4v7*uLyH%Ns4wTe!-l!Cd7RQgV7@KX!j zGNjP2Pb(w`{>L>^{_YxP{9h%B4wt=%w;bHadas#EWAmgvwMD?S_Qqfyz# z;GIrla$siI^c!gDoz<*_tBUGfu?j_@L;;udT8Y=*c&pSRiaI3!UAP*XHgnEc!~52! zWmDJtqcq{{k9CR1WKUP)wU&`+fcLH`{C%VP=`}W~*1%_$K;;@llC2_IGhLlI*075R z%v!F!Oa2+Hf4ShahL>I!8c|x2eE*@j=h9Z39UUv0Xrx`*=`LT?fu~vHFly4e+D;gf z^8KY(Zt(lJcN3DUe`=X4yx%VlFK_H0PNV+E+tL&-yOl@$9oT)*h0t-3Vq5?2VnROg z@P?;ilevjsXZb3Th7c_(!lvC-Z+xgf4(B5ZPa6q%FTx^U=cplLcHyOJY7(Ek1J^-UXN{;g*+HDZm9gD2AWJ^R2`c)^vgZBGIS}+?ENd zLz><5y21+~{tzQ~WSXtaqRFmQ`*m3+sxO9dc^7<(XyifaMHi+0IYHey!PHLHbOS@{ zPsI9pA89O#G){@NlT1_wiW_tMcikoF^;7(hSI!mNl-r@4kxJNJZwP01nH&BCyQ{Ka zSGZ6<4slYpD#9!B5|PH49gPK%#@tBb?2g7#tB9{}fLYc#B>o`xdQ&5fdDfXcHPBDk zgI#KqBr-|7%(+z@D2T=+@aS}PRL&K5N3xFT6gj@h4WHzwls9+ES`2TO|2dnN@W!mn zxvH_};YTqBIQ>X{3Y@waxEcmh<9^7_Qf24*0janj)3+xJK}|%PZ;FY0#-iLB~w&Wzyc}?=XJ}{=D>U zB!A=io5tTz_-w{f?Gt$Pg23lb*Qek!m89U&@BcpdT!+~5bohMZF%6#^1OlH4pWW22 z;q%PMq43FG?7$}n238}|gb{xeK7e!aI6GH4usID}pG$!&dZ{Z8Ow#Dzc>bpGm&e~6 z{yxXwLjL^Q%ja@09GWAZH>Qgts|lL(86j~Fr-U!f^O#YY9PY&j8%28U=Fr^cbJ5`ETL(X{Nq(L<_<3dOGiZR! z=|gpeoy*+DiY{>Umqbh5oEm!}YgM{d9}{ zNuycaGp<(1U8T2k#oXS?QlyuAprEd%_R^|Qk+fIs5VWIX$+jZUnFdXWmZ!D0%w>%Q z&=PN;!_LURd#z?|0qdGZ=wa*abI&ERMR^|a(=iC`372Pc7np69oq&;n?@y=$-ra6mXxkA&NkD6-YaM9(BDP90xm|qLwioslXcTL zoSyA6-V<41Jm|k$=!I=LNIb>R=ds-mU5po55GB6W-)`~MEGPceS|d=_S1#7BE_g0| zIl?29!WHY|KhmJPOFKX$4UKfg9_Bx@I)I&Qp^<|P`8;C`7d3np!K+mAb$&}fXN=?9 ze8X>lV^PRA%P74TS64TL0;M;He5D4g_{NY2cQFl=hp@P;D@Y8J#O2}mO`&tc@lXhA zwjspvIid~4~;L5om z#O9lbNvqLj_2!GYYr?yL^L&YriN!5v5cSpn^;VswhA>>a)LY=DFhoD%-Mp^Y@4@1l zV89}j_mz0-QJJAy*?5ct)#QB))U;kCF$oN@c824(Y+fE(7>-}xLgP2cg8qsu=$nnl z$@5Yfd45XbL-5MFH@qS@P|eNiF7;2Kuyq%Sf0kOpBrc&3u?_5xVYLo#J|y&Jx0}t* z=02)pa#Ct-DZsUIXHP7TPMWp3gBn=p0F8kjUt*I5Ec>-}aJD z<)cizV~=v)=bFda8(NRDSj0!{a49pat;cZB+t+&Z>dyzDhKQGLb?Vz6@800VqBfSR*> zz>D;W!_AEJi+d1qD^=a=QX{&|T_TKnBCU=?t?n z*EaMR%t~9ic2?Q725%#9yu=+n6c^)IrcP}Q- z10;SUDEBXdJT)45u9R9#K5T_-SzjfQ0XoIjCy}S=ROFGz8jUnm)U25AF%_tq(QsbcZsU@Z4#>=HI$vUKr%@ z23Mn3*N;Mm;19Fr`HdW;4uruWU$A^lQ^^ZSGzu21X*-}XsXz)vHZ>fgq~wwslDSxi zj<{!I)AtZWHXKHE!EBkC-%^x+JcUwqgDB;Fe<(^}4|XVtQdff?f>Mp2L@5FgrBJHx zzjXx~rE*xO_%a-dQmL$<)V+4rHIJlmLT?md_OO~E=kx#L$n|LHKY(1n5Nr_S!q4j8 zM6P0L*2q=IUOW`J{vx#qa$U)8r;+PtHtPIDO7sBZA)Z6YFQoaR);b51Kh=BT`{Yh&0Y;>2J*pjo70il|>v{(yR$2 ztWkZH^A}GmbM@ge5gIF}nKg}%q!BdcZ!`f#cbZ85pEn_vr`2o|bwYm4N;%)0(OEgS z^y<*a((=&ozREo5lywFPs152H#u)j0J)Hj%v9^?LnsZoQw56=(Nl`dAUr6t%Qopdz z|33T^N^JT3o=9b$`8APLq66f{iW5U+ky|0z283R7MYdV4otu=OFkfK5S1T!H>RSd3 zr1w$eE$oJb1JXcEtk0{ertW&gFjf4{B)2&7!c2DKozkD&@UwZ)>#HExW5X?ZE@LFc z6YJ-zIuO3TesBmA^xg_#Pvy7`G{xgc<^0wnu}N8xTUJId&D!~#nr`6Wb14U(uQ2=- zhNri3?z-q5X`NY%@J@R)|5di)NNtgJS#R@h)o2NQ7`(ndxWobPmB2Sw_Tq5-YGI>> zfH*e^;t9q$$x#C?dWf3S(C;5*TLn z*@=!cmaIm92eCtLl$l!QJ7R=E?!8VHyHig3w{`Ui0gbbGD~I&am}fVI*%Q&!?FJEY zRN<817LQz|RjVI}4h50hyKWz+JLey|tePS@$;ZLYIK9+NV)B+vh!uYwDF4-7D*qRTW1i3iRdhw_1b1tV=KtKX#93Y}S~#l*ku+zUOXFtM32U|e+LL^Z zl~~ycYoUohju)+x(?4eTpW*A7G6~k?Pk!9aYmfi&vJ>Ao`|He>T(eJCi>=m+o5ya# z1M<3&M_voR=sNOx_=U1aORlW(HO)eGOs0Fh)?$j^bC3ZmaUBu zKJGO}tds-K_&F5tN@^B3Kv%j|CS+q$xCMuXUX7RqAs;t#=8E~fX$6VOB4k(EWwI@a zawe87K;0bH^etPt0G1qrOlwpVzgS-}icheuP5>*MgW(nUu z#f5a^_9>a6v1(U!3SyZghw7=P;jO1z`Ir;0RzY`#rY7c!j?7OH#Mak8A#V4Cws5EC z3wsk5p1pku)a8>GIeW}39Q~A(bMuEzaqI2q;o-a#10HX}ba$5dtnmaea3{*VrrY$Q zg5QzG6LP12poWPtGS0o898^vMQJ%dP~|F*&FMeRd1YKTrq&1ynVnFlSRp5U*5b4?q5>USY$kDO%1Qe zNmx0_Z0n2cEHaDQE_wM0!6sqwXD-n^SduWHDJ*1E`exMHefzxGIKNO#D7EGUXYnBf z33BN}-vLxY-->aZ5nYjkl9_YrRVAQidP96kNlTHj%0k`EL=r7PxZW_k({$C&BZLb$NPT+SV`FD=b#RBQ|)VP3) zhvswhT*bti&sV8DR5y4G7ZLh1eDS%)xT@en-0hBbUiZ06nok&nEs0A2fecg=-H}nw zRb9ZonS@`{H~E-}#xXA84iMXi6T~7czQv1F7pGVluq69+nY6{CsK~#Ci`51n3fe@0 zM1Mt;?OxxV$(5mmQZ>`k3i!ime4121p^zsn#~M-cF|tOAyVDA;;z(#;A4;G6I#S7w zRF_0y%Hv=Z1% zYuOSTui_vI;p5b0%TqKcybMgjPsVvnzH*2OBSkkoQ_h;-tj;xUs+`z$hmj@goo`IeFWEiSLo*eLFK$3-2;k9i^ykI$vm4 zQfk|ezgyUat%^LREfId^bI?JTiJdJ@~l zyE9uYtdw#hb0fD)Y%)%ozO_q5(>4E6(TSuv?Rf}^nO3TfAX<9W;Gy6NOtrnTpHaz zd|p>0zE8-Aa*8=Bzx(?os{)(Zv~5^E_kFq+~pfM6NfR`5*4DsS6inIV%}kIpvxj6|0+-c z_J&{J_Wov7lC$Sg|S{!#k%#*ClnTe>+*UO*m}}nQ)l}<}29wBrQN3LmzFi5gJje z-T?sh=72Ryl`It)$_*191U{*ReLrmsRl*)Ig@O)}9sEos>?>K9sYQw4!8TwSr=DCw zGr2C+%dg$2RfC%7g|_9&y7SFm^A$nDV;O_qpX@#pWddqWOuQ3BU1_Fz;&x0{H#+sL zPt~U$uA~X{T2jr+HtMZYIcErX1oVZ6trho)(T6yh8-7?TA>nNfhZx3`!)2?}@LuCq zF~&(;p8>UB23%j#8|tr9FH0I&m?Dc#@Fhd$^w~g?pu(C;Fg<^p+z+u%AfysI^uV)BRkK&uj&y4j z>jmi+`){CR*ZMJP%3xEgXv$iFg+qATgH+)%{CG1{zuP3O_`s#Y{Nw(|J=1U+Uri9z z>rfcO!ehYycsh>hxUPk-3Bu$}rBj#)oUzq(*dPsg;s_ zy7dCn{}O)XI%EZHzC>uXY2={jul>r6Rr#q}uhX?!*M~<9q35AD#iT&?g6y+4Lr{+F z)y7kyK9m}X;f4EC(d@^y%-}ACcisI}>Py1}0#i;ggyLj_3$Rc+E#PUhh|WM8bFhgD zHxn1+EIv&%M{$u9&j`Q0@W!3kz-oJ$)@!|aCy_aB&H<(DwJ4g{wsD_0n9D2NXD^|S zX+su%tAWNUI&OFlVD$8_9h+y$NSEYV8m&!y(=zVq@G|JA>pD)?6%T*@8)sG7# z9jaI$&d@`%dE)0Rp4_fbK2QA2I-ON0sl3?_?2)4)Qpdcg-zKI1Zm z$sn&hOU|bY#FIA5_`OA`W&8$7)1@QVEJ&Q>>*Sgm+hw9+3s3dwRe{Mkr?4|MfqCnZ%#z;aGVBL9Gnxl2v-0 zLIM44O;ne@x8&o^`(jU$Q~Utds5{G(&Oz}&&I_tQp+XZ; zWtgZQqj87uyPf%^RJ z(8c^+z~5~C2%uC(Gs6a(x!7rD5=>ul5j!E^~;wc296Q7!J#nzE6B`|w)WN=t3~ zZclF=70|j(?Qa=V>6fg;v9hk|Ub_SZt=?z}$$$hvd7nf1G0LQDt zJ%QwxK+4`Y>4nZ%m_!J@gKJhoGZlk-b0-#{4=&aI4GY`4Zjaa0;V~2=_F9z%M}cSN z*Vz-F4a|5Rmq^BV^%OO3DMEbaGFHI+5WI+!l$<#9#!l>yS~a0pm^55-8xzpkKQYM& z)Zm(KBj%616=xIUboXkN+~*~ikMXDqQMKu^exY$Cd~mVe;HD6$$v&D9`OfC4qC{~V z2xioO{8QMSVq?ECQJqcEaIUaToP%cTdSb7FylRnfxedNZdPy&fI>ALY*&>C?OP)R# z#MfoLz%qy$FN+|jaA!FdrVttzw)Vm`W+P%`zPf3RD`*qmh{ zeKpY&0Ul&&uh~6g2gL4z!xhBt}pllXF&GVqC7x!z@YiFtzG9)VBVVO6pG8aBQ~cu)qCPXogCs zvObsG7RCB_hAtbKofR+p%1~j9r;{?=K5t#35EEFn2+kE}2;8@fXniit?Cw~VNOiPh z)yqyR9anhsPH7$}xSemki@md4AvR(qF&*%Kij`MhwBlrM#YrJk*)j(Qb2NAog#hQh zxS$a?h3wP=l==uS6ZK|qEJ1&f@bo(;o!teUD4f`C6gn1cnwW&FRLiG~>T_4L4>OVM=6yFfJ!keSd94Y2X z$G96Ryio5P7Qp4Ch`cXc<~@n`g76()S2HT|72cIt9AgGuUr(MaM+_Qvi$`;@tT;sg zD#vhKeA;5&030XMTp_OYvG;%nOH|zo<$AKnO|Umv+vnA(FYgd>+X{o z>Qa)-*^ONV_4VnV$Ao)_UiWmC%_?>K+wP*aQp~p7_^nfC*i|G#v+LC8WOcc9YS>_P z$5`1?-Cn6~imvYPC794w(nX{`!C|&Mc3C6Y@Sw1so?@Hz#MOq)dQMP} zT$I`bW5t^Lr?LxlosdX8kKX<>s+1$1$k5bXx`8G$jxI~eb{4YlYJ!=xiZnCnYtk0> zS4cs7f*N9XFa@|5=$Br!kj*)r<)|}g9}1`PbU)-s7<-)m=wOFPZjI!XTx_X43C zYSp>WhQI1Ipqnu|Cd2oWq~$@k0n6Cj62!`at$)nZ9uCu(4(%m%c|Q3tNTR} zrEweZF4oH8*;dI=zs&;&op3@N2h4K{El3rL{Z59O@OL~ULtS7y4*0P&`2wCABFhih zPKj|EunK1i+VKj`6asnJKZZ8dsZt1ky*@#|Y`z(3*lQoe4>;s&h_i&}>6cP`Oo%%T~f^s73c^PLb4SSQL7fOpK&GSU^+_2X)UanJ{rOh7IWLKkZ9jy817gEcv zg!u|0qPJ03x2Ilsr&{I5d_W(B18QNa1kke1w~vlh>H%p=_+Mdp|HQukuHIC34LyJS z#oUoRSc+x=!BcD1WAqU}5%t)Rk5+%A3-&n8KJ!&;xjt^j9hnek=%?)c$Dbr^&E&7Q zUcy2j7cK09eW5-{?~=yxlyNwDq;9=-Zr)$)0NP1{gM=36*j0sxYqf0o6ID;wRnr3z zu@GLR+7`3mAc5cs$)1Gv&|I-`VpHDc%mqS|*H4q`4srRyVzYCpQF>70WsZi57#d;^ z$!09{Z|K2KZLxU{hcuq@Nh}SS=Z2oxf6oTmHtEb3`DFlKzM(Ua;JHE*m~6}qC#vs$ zE=3t$xInDalgfb0bL70flhEbbMciRTo9kzWk0(NYoT1dIXRv!gNGS}}&d?Ksc{coe z6cavN2)Jyipx4BZ4>dX5(H#=bQQUT!FX8mnJlTFYwomY(+z}vN#x0wO(z`-s{*FKM zr6j&?nAsO;K8Tv(*SdL8m6*E{L9M#0gJRM+U_Y48w8qmaPRuIzrlsZY)}}-y&4+HQ zgS_E`B))zam2#;k6@{A*VOS=zY}}z9B-ZPyc*%|pH?Ys2s8$OFeXG<5PYV1bgiG7k zXmjIN$doS);`%5wca}w4T4&# zD+df@+nV}&4?~PH3aM7&ugudEjq@}_5XKxNJxm?)8%^NToVLF=KQ;S%3T5`yNQ(E^ z?yIl6e{fG2lsTxMYt+N@!zT%7w=H9+RToRq~feOW-C2Bj}J6}y-lZEiQQII=}B2So%k$}aXhG9!iQLkjb}gY|iXH>PZ& zA`Qz{KP8)U9(;2iLq2SMPDIrnQ41(8ST|C&r7~Dk*sS-$;BREU#hoaAAb9LIWcqR6 zfeoUVAWlD*rI5V184^rEZd6!9C9kc&2@ynXT{Jt*{K!07^6`c|JRV;P24~mRI^h#m zWfNS4`%gD_Efs=K)tdA@k?(k^&)iY?Vt)6LBhl4q zs996)UUnDkgFUC3wG zK`;yb_SDSdoEDrCkDpqi=G4rvqPjaZ>g#qsvt9Q(Zt9xN?+8pH?FaezjgmLlUzq+; zY$xA=&MW zE&I%;r7^n|Q%{bSVb8anIsaJ6@%0mt?WH1l=+-vBZGSd(_aeFy?&%sM-w}{So<1O* zJ`yT4-^7p4WAZNlEeAixf}i{)@l#h|>RE8A)F3H&%MNM?PouxZ{+l||kCnW&ZeH|( znSubiXGy4VuGC>%PKQe9wUW1PJKr7{1)i2!J#{!kU=sR+Jsi_Ur9SfqlHGhRe^-7_ zN%y8=8qYB`0!Hp_7kAf@P3>)E5&g#0=v?yAx@G1ryEz-_V?pR4uagCTV;8()u%KQW zb`e}Rj{{!1s#rb+lQUw!vfD=7WH-5Sym^q(`rE zIbPs^CX}rnLH6S;zStb0^VTiqs%+NfpTfA22)$JkU6b%X{CaKrUTeAe0&bv|$I&eL zF#KMcksiHOHhTZVAEgi2Opu<3RXIWt@q4r?`*uXuKJ(4|*W2NODpVz4e@?H|Uep1q*^@ z$^s|MINm&){OpUI`F!<$Y z<^I0Py)IW)R&V7SS*=Zfh<7x;2|xKe`r25dx@QPc@3Td|x@BQKp-5-dM1Lra)R`wy zW*7w?>UbV~1trIgkBEOF<+Q5=4nAvT18&bJG$MD)xh~h_t|NPq@5}?_!QpZjev@!y zz3M@-5c!Hbv0$cpgn>z{E%W4kax{oE@8`y5)3oCG+H00ER|$QH*v`ac2MhWdhg_zO zW1(0N>5}2DviqfmCDxPN+fwrnrDl5`f789JlYxY!6+iN^DBQL%}6=svCZeb2yR6jp^a{T^O0YpT^BIyVBUY;33Of7yPr8 zUKc!^7}o_$?)ZrV43kdp>Y0MDi9yb2F!4; zm#MJ5$$=&~es6er%kfs=)4bei8~qC)SfhTz%8S^}=D^|e!@~q@W%zD;FN13*z+JE7 z#hex_21!v~+7PDaPO9cSypN5xC)y!6BHr)@aHJeD-YEGn^u@zy6B?7C=K^JU2#w}J z`p&71Z0Q5mf94x|&ED?^3+j&??fzp%bbh*hxwGpEe(uj3^I>w6AD0Oycx3^r%;4O(RBln_s8BN%Bg|^aE@2H2<5INX*;E z&5su$cTet-3ZJV?1lFz3xAL-85&=zg95Lm%UIqE@!p4DD($=4b`Y?|B0%!mc6mxzcoX+?i|z& zzM@s&Q3=s%lPq|=QT_oheuMH9-RVBnP2+^j@qwW=Dqkw=3FfSQQ2O;nyI&1nr&0VK z9hJ^7xt_uPEwK9+3j>7v%J7>k9+I-uyf3F76QQ@vkBKN#NJJKKoN7nM`Ebw4;BZwp zjR}REa@cG?+%kX5$;4)S4G~M4AB0{U340-=bwtIiuBu5~AoVB4J!=aOCTSR?LUN%1 zAxnLL{T5x21JNm$L>j#LAj4j4gqru$*gR=0MQfol<5UT~cQ)P7*&5Is*ULDVBdF^a zAY4Ut`i=rObfQl8uTupBlX@0-SEy2)G+p&g=0MXBl8-kF1aZCU8j7Pp(Ufvy_e&{n z_W?^Q>ikqUb}hnIrj9Fv$`1^pn@@<+Qy+)Gd^=)E=Me{MT5@Pj#-HHw6S!)QF85Zm znb@!ky(1FK!Afj}(9@k9J^C2ESmhH>k3;+%Wtcr9c7!g{x(3nm#7b_>^3=;e&UCeQ z7eLYRd303!Jjy=B=TSdO)*W8)dGrLS{2@M%=tWYI&v9b(39m{a;uI6Jp$;Il5020FOHj{2`!oF#yh%@-PED{o^}n|9|FCWR|8aZN={8EX{}|l) zo5s$Qe+PbxJ!(xqiN9g&{4{~&JO{=zr!AN_fcJeHgE<*IVO;Slb|=2M_KA zGfp+O5wY|1ID34Od1HgYSC{DL4>5T8$4xjcT?n8)!-N9`g1^s%1J$HY*iC3Q#wQ0F zZ5Sao0aG2@hz2z1+ecLuL_(w%<<*ak}lr4!~Ba zBS%tB?S2iAQ%>zVVF6m77VmZ1shw2l=p@oHTZH`9SO;Mim!X`E0DO=!*GYlMJwXyU3p+%5b%f~s!`oE#s>L)y9p1QV%+bSC+Aj8V?ubp z4l&&iOZ!s49S6IiM}IMSe3{%~35zY-UPt!Y)qo67$OpCZL87kM?}o_cYRwCD^y*!O z7bJ`C;)X^dxsIafpd7Z|>FJKqLy$w3a3h#kf4>xVYG;{X&ET%ZyGpYcEo0RM^I~72 z9Gl2I_r^W|QZ;pJJ)pz(y%a1=AUGd)i-;o5@DQOUa)DRk)a=7zxVQu})i=dLr5V?& z=x{b4s;a|1=&z_k^b5JSJfY5IqcaCkBBAF#RV~f=)F%$?gQ z#|@SAOmrE7NlRB!D8&MQzy*U2QIUdzZXu&}}ZEoMf2j!Lo>k*+P zJ{AzJxg*p1L<3H=sJ%o4>GcrOudgDW%PTG`hx0=$RKnvJS#A6L_w*2E!J3rFL}*Gl zjWjN*!aE32qC>KBFrJ1JnBBzfiu1JuJ&)52HT&t~>u^$v8eHS&PD$b6#NJIhcOE4+*&2%Os)v^^O&k9q{#gP{HK;Z zfWSRLK?52&~rc)rpO!eSgCgpmZ{zKa^1mG@;%+SW?*g=lpu`*W=e z7vTMlB^SC-W<-YKgbFV}C5dJC^WDOvh<0T;Yn)Iz1wzqhT3uDjpFk3UacRfQHVv=l#E|y$yU+)tUG|lbIv~Ntg%$gP=q~MI(v^Dq*As zAt5TUiIE8r19q#~?5MjIWU@sx7XzwN(+{GlV7qSq$J}1XNV2 zC!MHKDG3rYzwdMIoynls?f>`t=kv+jbI(2RFVA_-^NRR4{XQ2NO0(Y!=yzyl!D0H1 z6H1i1L1?7o5Q}OH3j)86jzyV+Re~^ys^lAlmSL~DR6?>EG|@ZWbCFLodv0+drWk23 zAflP&Dwho5bxtE)Rz|;9xugJdKU5idL2>kp{nZuiqR$tt5D{!&EOsIC>f^fw;P(Js z7yIm$>OQhsy_v1ub$BIR!M=D(2KeMknE7mtO7-Aqz4wBh&cZ2y$B~X-wxZpuT8Y)V zC;bLYU%gR|t^V?n%C+kpsl3#eQmH15RDOPCQpJ+Ws<`{#rQ$P_EB;fw;uDf87Mv*w zZ3DFq-{!=-IiAwvyd_sP@ZB>2yWC=(-85Pss&QRmp;*~Gsj^}H>E%k`lt>{8+_FB& zzLQV2=BHkLv=&+q%8?Zv?FcKhhzPbUC$L`5nPF2d==;7b^^8PTDuHduoq=vXRCDWxz6w zu6Sn$>FXlcQwOE9T1k7|s3a8Pn6W~ zBbi121bI$`noR}=WkrLH2LQrR?l zqDC@Vo+yx9lG)Hg0Twf)V`27L_UO4^i{r$fwocMT$6DV^Up8o8orOo9A^cixl4_!- zaQqXb=IFSBwuYmknOb@}+JB;uVCP<=w|8v(>#XjFZJ=7Q@jVh0+zYEjn*^b0-z2yU zQtQu&&%mPfb{rnpDIpYJfLr;OZ$_52aQ0P3=Ng%@yrc2hU;WFG0M-XIpCgNE}ccj-;SqP$Y7B1sv($3VvbtPZ5Qb z@oE*gRh<3>as_CKo)~=hOK_U4DIuLXnjXw*bI~b|<%hCqd}8OYFi(Q5L@6^yN~twV zscC;s(yqt){-zws1YO)UKzA360j4Ij5ARO5ItsF&q8uH^XsdD%34g>(mSknZhb5B~ zI9&?-EGxOdi<1ldf#g4qi01Ux@{C zyxF6V0=>0YS~y;=4D13A5=6_K!4IYsd=U!t89NI0?tX3es|c5xR!Gt1X?lkA;OwA# zzP#%E9R+RNTQ&!yPUIx6Ep+(*ZWI`0eFdNG-hph4!vYz5@CI$xVc;NciWH5`1CAYw zoCVtu1}NBU?ATp4xVsB&e^1$=Kn6wQY|sB7@8WIgh)DePM1RC(-=9^b{LXljt3_TX zeuvt<4VOm+x+{hqqXXIcx7=wyTq}U>`pUCRy~efum~j+a1zyoT1?AGCataH6C49d;@%_H!@46{mjj`77C*w`Bhi5%!wyJ(8 z-fNiv<2Cc@DB2nw`JKRVfVgL1(yoDBQ@in&=f_{+Ayj%oCxkC`;{2D3JeW?A6Q`0xY%SDa9l;nCvWFLevi*m|WO#X>?^OSyUudn))%5U*Owa`38*@5}F+enin!D@Aba6g$x%m@1p{+8??WAcY%sZ@)X@3QW?k zFBhd)Ym~^l!VoX#+Tip<~oPpTXh|Ixb2Zs8G{L{Ek zWeJ9+hBcHArKz{Uf6Nwq@ZNt>=hLQcRv-Ft-kOb)0;BVl6~%$9n0??~c+f;tM&|1KF}iXSB7 zQrvaW)Ryvis}7~b*R2eAaQMI)0Wv3+ zM03^k&#N+JJrW#`ZSr`j2Te@%&1jY?r=U)e070~h($o!1GpSv^3F-xz@x!Up zS@S9WQ++3@M{iG;Z>hfY>i@>aXC0TEBA-L8=d|V`LFh|lJB~sAGkpgNb}bz^>?zJ% zIST=5X3lKi-E0oCeK!hHX|~UGoc2A*;h)q#3{O?PGKWzuIE-V<2RV+b)Z4%{3doJ6 z-h4irq$4-jncFxl_Q_W6!}r5z2Ki(G9~U4YZVvPf#je~`DJ4mc2;!DxXBp(((2-je z-jv68OJ*vTrAz)KSIezJHa|`|reIIq^wZfedGaG6pIvdF$ev2z6<~zn$<19u zoWrc@bv(|ivRcw@)$1Lwm36Y{o7Ne*Spy;gL$4KlvEHMF=0|Yds_;e4w@Ay}&|)*V zLTXjeNVKZbnA|L&z*{Gt5^;|h8-KS|Ti{G{O$G<60$E;f;tO2h!_4+55P|j3bGg)X ztcP6VPv>THr{qPlPV8Q2y0He51%CnNdew6*2;Ge{V}a?=6yt8?l2x+UBk}V;nx}Pf zk?XP~y@dtuQctCb(EWHNHJN17*Cb}+cZ+}-lOIqSa!#f zuW5C+NWp&jk&w@=KIb_7=H^~AQfiY`>hXs5K`HP)`FTQ`o1-3}R3YoigpC)i78Z`Q zJF!f7G2d?)so^xM2FtzFG40+Uh3jf01UU30)$sqcG)3}?rP(*qj$eH7%}cXQ+ObJ~ zB;?CgPsvT&>giZIB|RPK<|tlN@67-UYuSI>D%)iuomCnDydv-iD4w?)*6Q>@#b+@%!IWS&U&{J&NF0dr97-78fs5LnOu? zrwg+OkzkvPv2gxnKk2PO@haJ|6jTv%sox1Y1wmh;LNf(p+A~>(1jm9iL6q8qZ8`0# zJsx$A8I>&X2P*|9iQYoU))3h1MYBK%T>IeCzq3bty~OrcUNKDt7sVx~#5>z5q;o2!2(%ZIki z_0m^;09P6(Z-KeSJ$+sm&tPX}u>H&M^WUj8?9F_^ zmdD6o3M}K*e)pe7!*Hz`BGKV)l1=iePj4a_bXO>PFG!7fadMJ0VrKL+-y9I6?Wm!1 zflXI~hgjO_nk4Ptshgc{;dsHeJebs=w8n+cNsZx>Ow`Pj9cRD_8S@PutAGItQYFRS;Cwt3HF_TE| zZhDJ^!NJOPH~X*2?{qgkE3Ya8w<^jm-WYTJX9m@1aXR62HwP%J^`=o zV@3nX|77oN(@j1Hc87UoykE_H8My09;N>xW|%xr3u=g)Rp(7G*pRZv~!FAAzL zn69NN+$m*#bJuk2_{^m=qE7e)9@mX0gJ=HO~k>&2RMdw3Iyh?wb zm;5rtuA7t@rUiXr^28K#RDT;?oP1tg^`4tVOfCF#CvyG*0mrc}$ zX~IJ+RAShevKwC!O`D>|lOHNBz& zr_0AE7_$g$^UPeCu6``xnW{-1&L@GkEQJjMNB44S0r~eLQfA(|m$;z)&S;ox^G`Rn zg>Oyp|6Z%Ol61vmSeGev8~{Ajg}iaLtE&*Xf=<=MZ?(~#Y+UStI<0s!5kl~ZjN9cfH@#iHUhkmMni#qc=ws;@&e#9MCJ*W^BguL zXLGLV0=il2(oRswr<3_6f;?rftIAs#o-!qtnSRdNGnVue{&@o}2+R@}+`lL4`53RY zg(te#*UCOlZ*iWFxk`kGU^cp&4B|>KfckcCBcb3Aoc6%?U}sH3BB7?Q76+1}aDx=Q zsm~txtT52$&m=04ZuI28v-9WzG|3T$K$*!KTM3@28F5fc+)ZCnW3ay8=5GF+5Ommv zwKHS934!y9-dujItiD)s-a$AvCtx_j-TWfi!fRr^AMw-?j}wUBCV${={uQs%Kj9{$ z^h~d1l^sdF&EZR}4%hTodMm`Rn%Kx0XJf7pd;`j}Qnu z=X%=R>wiZOS^DCGShYHZ0d8HGdZ@)&h&jZjJLOaH!Jb_mr zWzn0|IH(wy6zH^_P2wnic6$n+)YEbra$UmZonTH2n%WmI6(weD;1l?vGqE#Q;q}5k zWNdD>#+4kSX>%Yod`k>7gio{WAP$(tH`E`{R^eWcOJH?^V3|C!WgjP@aAJ5MQ~1q8 zqRPs_eJlL2S+_b*Euu2tgsAY9cd5DhMfAS> z*gk<2aRZlZq{ni+Y64)9YZhicv<|KVds8Zd0qMH}v5tTq0d{AEoQJ;It=gCD8}spa2S*tQ2)8EdfBOcRxx%1EI^o(#fiWBz_Ip~# z9o}sB&pNyroA4EJ14#Z;F;DSY5UwRgCg56PFD*wK_W(P+B)8Ska>;b!79lRMfLcAn zf!`;jWuR_>-j3w%pXr#Tvj%TtMP%|j>`!aBVtPaLhel^d$@E6CRfZvWyFcA`hzAzY z{*Iln!%OLP7ri!m#PC2CYhx6YB~g45+hNS)DW?m-3oXwTeJ3fgD0-GCJWV6$ik>17 zoh7*pZA?#@>@35%sk10sq80^XxZYYqh=vPpu4EUa7c6Aq-+gocsq=0v&0M;*H-?&4p@=*Dha`jybLY;zl zmYdvLL0)Vt-_m4$*oT9(z)2xmxsro}gK0>r7X3po{s_M6ZYs(T%;rsA14$dWK1V{sw7KOK&9A%}G9JGvi94l_&~& zEn_rVnZLd}RQ9^DFc-rXJ>Xf6POMnR#vhW!1zoRff2bHTh-^Gz1$mCRgY!6UTF(>7 zFtL>Vddp3JHFC7otJcLQ%{Ux!`X{z7Jn7tqcaOHY1cZa(^h; zimkJX3QRAdsZ^{MmBT6NGLPU;@pgd$!od?=rUMlSJc=o`p4j;9dI|WykLBR};%@pY zp1-#j79Uty+fe`4(SdWg_xfyNJxPpkO34S0$v6i~oA#A+V|czUaZ2-V=X{BAye)aX zHQmV6>V7Q?C!QPE{9Bwm!%500huDo&jkj?T4mOXbwhxn!CM^ttUpqmAtN|CB5zGj- z;#Xbles)Twx@a4dU+ZZPOb?bgQ-ViF2gdudmQ|{0@%U`&qHS6(dT8aVBh@C1^=U*> zFGsJc0d14*3(^~1iXf7h&OwZZv(wW$}nxgI$`KY4BaWk%Ctix`Iu=|sI*?B9JJPcjwWNoOk1p~IqI=Kx zm2ZceB^@>~nh)`WlvQQMuxjFKVv3=^*_qeQl_!}Q4PBY1XXVdFhB9dehq0UamF~^E z;s0~@9PMVG@O7_S8=4UEIzwJpG?3_BKC*E*gbNOi4rB#OTq(Y+t7}6i3I>bil;SUB zzT;ip`L(VFKO8pWs{YyDu0L*XvybU*u1w~4eE*=gKpG7iFh9>pFUr2+f8YpIIi^6M zO(cXKn9%fiN5I?D`XVdjC28U+cfr2iD@cEw&HeuS=F;%x)J>P;t2oNL zFe1g8*g=q+V4h8V!qYr>{$C8IefkPe0bCeWUCsD4Rp66Mh_xiDQ9iTg5yNSV7;qr& z@$CMbC1*fAX5<#N$$_WNwOU;`Qk;0j;^a}k|C=hn;rI;$fP65~!s8*=vU_#;l_0SU4dQJ9O z4M$UO_P-(2CvKQIu?}N`Ezj@9>?mi0SKae8skdY! zJH>ss8fBcqrhWd?hF=v~uk{}@Hi(0AcyKg%wGI+?t9#|@Wkq|-6=Y>8A^HJmZo&ej z(w6Z3CGMBo1&KRXkhqEbxSM}*Oy+jTEYZkFVGlupiT-KieG8RFA2JqVSz&8MsH`tk z){kJ0T#rao1xjO{XMs`H3M}pm2l~SGJrHG#;{Aa3?{V*9wNLYbs>6Wy;#OfSl0k|ym;Qbjs5^jm1P1Q})KKI+IXK9I0o z(sv*UB358V1X>E_xC2?td{$vuCWaB)q6IKaP62VFVR&wm6@k+S22d_dfl2yJz1}NV zF1clmAp}EI;B007r$!I1g~Z0?gkudyofC}O1AWockBuEoEH|kUN zLvs5o+#B1<)!j=$T|_iu9P7NL7s< z9E-F0zD4p08Lvgk8YlyFQ1Qc9wN z@5L+FAQk)>Yfol_9q|g_l1UY`rR?i%?IL}msN8r_rBc+tCW_khR=n?4TZ>BjV+q++ z-RFkna=Imb{OI^b>jq+pDrxn6W4fFF$#gJM|EcmO%a)JhuN5M+jMZk}f>0@FsjJg= z?UW)0%oS>pB}1UPv#3<2f?#c^^hW(?S*Z^mSMb5bB!cW0cT0hELOsRnh-9HUr$XOU zj+8{jTJ{Ncwo8GqbM(N(-pi;}k4RQfli_MSBJJ^yu29?fEX|4Ce35$D499Mssh)}l zXQ>VG;2iZpJUCtbBpy6Rt&azb)Y^D(q58fReDYfQqQ1x5G5wj%uqH(~m7j>{R`MGtjAEv;^pjgVn$1h z(9K27P3}`h|3f#DN88mcSADD{0}^WB(z@$tAN;dBrM;$QsZr(XsW%z5=$xw9Y{{g? zy)Bqvz)=W=}gw+O72EOSWsYeuhtgt%7XC%2G=foRPurIk8*TdEKWq&{ zz=4lEwSomik#t!|@zv3&$Xdn+3X#a0$Pvtv2$90nM+7sIiA1P56DM+NKO~~x&5g3M z1L^AOg%FgIMe1~7k*V~5J@L#7k`q&w*a^x~cDxIkxJPsj_JO8Z_JJmU-98Y8a$Ok8 zErNr*9kEFZVM!&J&^~~rDa@#{epPv`EV@JGKBw%A?|xU$-BrxHeZ-h@N;MvZZ)RI{xsW6()v1K51#6tv0Igr7 zts8#IfV($l=E_-e&^k+uNbhGvOB$lf4#v-sc5NxuJUhjaeX+uQoc*?Q<+M}!v^!*; zcK=x+^_ZvKp_bwy^R)YtJk{7~dh7t>vel%7%h5M9A1|SD>V=mKwiJIsvHI-uIj)2y zFeCz{F-?irWR(Xh&y~^rwf=1$G5JNM+IF~#FplgkQ?u3@W3X^4T-BE2f(vz2Ym7OW%@ zXYPuMlc(_fSAv5R*EoWaJR@SI7dXfr6VsL1@DVvAQ$iOPy6dc+L-sNX=(Q{YKR=N^ zU~*Hnk2OS1^{~Nf)&6$;H3G{MK6J*@bNZF$-iYR=y&$%zw~3`C>z^3>9Dm1JDMd#8 zq2M}J)0v(X&b6n<9gIQDa>dp-y}nVgHPEC)EsKQAeg?@_p4)I6N;T7sbVP%RTr!v}cZ&ks*9LPRm zzLfC@j0Nb9F^9v!{Q@lP2k|#o%-cwDFl0_p8-bbT?Vuk}7G;O#hRga?=`}J|{aAiA zSxq%yu{qCa?w=>J`df^*oANWN&|m(a_h5}|$y7H=NoRA<5GJHTDh+83BHGB`_lZ)1A8Z- zguvWLHFhqQIbVH%dpE}?3THtX04$n4xGq8G$LC22#FT^h1l>EWJ9ID#t zI1+XL=&NTcrW+{6Q)o>z;0}b^9Z=?s)y#)c?2R)E_-b6rQ%pm(AEsh<8ugwrzzGiOYaj zJS5}9*3m=dzEilXha+~e@SJJv3_IsTdupl&1YvQkaIU*aa5glbJ>7x+Uf~&IH1h*J zt{nq!PdYrcpKLon_H;0^{o$PNVQ83(p>R^O+P8 z&Z#iE@^^69G0I&s%Hhn*WS9dfiDBNH=h-R43=qo1?Qd+u{wiDIY?ww!AaPKvT z+kNL#$Lquu!e!k^VYi&3c2 z&R&zFCV{s3PQz+~TGGQd-oQOdmS_s>c^rO0>{dCaOP}V+PvA<=zQ7fLuW6OQdyAWk zzeGcF;qYY|fG54>l-_^g-FLlLU0jRkx<&-%)a1E;8X4Z&@a}H)82*kCefhjF%CB`LA zV>Bk{7}2gk>ZxUiqFLk_CQp>o&Mz@`l!OqrKpD9M0U}5zuN&GSo?&aX;Uf7Gr_PJw z*qTwnxi)SalCHG&(uZSGgLs>~(n zq^quVMBLA|1qTaPr#QgQ`WZuM9i)UOx)@0KeuI%-Cjw*N5tWNWF0Z?l)PL#uE?55eFPFb|Zl zkUuCa%a%(4B8M2}pTU5LEHy$gzab!qiqL{93bvXbBi1L!YmP{{y82QI5eF)MyrgMI z7t_f>*G)|aZ1-eWDUm6_MoAK93A{=+XEt->#N&r}&}tinf7Uf8syqKDeyVo74srnN zO`wbHIJrBVcvURcx+D+8zG)X$kIN?0nivf&r-VXjx^{+^S*e^5%}6M>a{LB=9-K2H zkk;Xt5ng4d&hf1!cD*uz8(`mZadz904Ua4(jIH%PUm)nsf#W8kR=Riu)7ftzP))T&K$APd&douAIay z7qp3>Uv9V_n=m<#KmIr~0N_iS>Z-EjsRkEMo@xP%Ofvh^(J8~o5~z@2=z!X`Xk>QN zY|#n$IW}jP>L9_J^O}3?yfRC2(GE;jH(bg5K~NSKtuBDtr43@_ItRo+s-LT8D^lC( z%$;fD&Gz&4tfp-v4l0yH=0S|ESs@r-5G)$J^Mtnjpfg>ZHIM(zvK!&)W z4H3%tAom|T$e&%V8-uyXnoKqo!esO?QVQr}HHBwQZX)!yYvv~hCJVM1U$~lE7LffWt{l3M0Z#xnQmv36CQyr|+v9AZ;N7SrGqy~ks z>+-&QmHyo4=OJ2JG!)0Q+rv&Yp1YBCLb#A4tbwnFzxl;*2?kF#S%Z)t*yQKyq?7&8MjN9{CxzP)r3rx{5%h_OxK^s7e;7ls$)ubl5`UtM$H1 z$Saxd*`X=n)^3oJ%-hQags|fzQh~zz=30QsCmgw?i2?UD6V#4GN|%}PbHU>RlY_(a z{o{he#r`qD;S&ESadjOWUK9|Y*LI_LTJQiW%r@MBaoc(du`&iqkcQK+@g#VLON~Y- zty&1_u#uElvEcUGiwY>qpUl~021zoV5(bCAi>F5Hf6s@J^070DO2Rp0L%KK5#8}J{ z_fSoAN$}(A`HY`w{szEm`hOoQwHf_FJ=X z8sk#hsgn?JQo!yp7n_2UM&7bSZwIij)n1sj6hSW_P=d~daQxXDF!-iwL>P^M(T=b0 zruP`7w<|S|SYOIAPk$i&710-tvNv{1Br*T?if9L$GR)yCGLhVE<75b2x#@ObNB=0h zAy$|2>qHP)-S!uzS%mjJ1hDKPHD!md$br=zy22S6gMe0aya=GAhte>6CV3Fck_Y-f zDQILai(g+v*T;X3km%w9bEA-xo9I<-(yUGEzD!8T<%+0Inyfk`x4MKhza7ix@w+?? z7_WW+6~$t{F=H}(U7)unF#Eu}NDMn7Gc z@2Yp81uv6S4!C>iXN>Az0oGz>ta;zMG44D2sGbA-_Jcg-3R0B00o(dIgp+ZMj7{2W z?Cwpc_#`h?qP}lRDu7D1Vz*mX5+=3m`S5No&Ai{}KOAxR*EZD0?8vMK&M@lxEg7oA z3)5$$;Ay}=*4zYQyOzVY_qS5LX^kJ$)#L>)qZPsHp%S|Cxn$hpESxhbHt@#OBN%m% z5-ygjM}2=o;phMsukFEIw)uPr1^owp|KF+V2<`w;OaQ$)Fn4P7NHg9+g4wl40q_ zavFWI;T<@rJqpWJ_aIt1mVA={;j;dQwXU-eQY0+{tH-_61+yBKFFd?V%(L}Qk`b4V zR~ivUxtuOR1t2ht%6Ym7SlBc66W!ukQg5os_XE zcRKdq+oRO!DR*j13p@Q27&{rbR0eL`JM&}3hYFV*;vQX;8BwVl?9m(K1k=Q)Lux(6 znr=Y1#jt|NbuYTk^Uk{b=S1QVH}aq2a6&H}Qx+BWqO78b5{o**`25(8T)11k#^KlZl!nci|0UBvXhiYU=14B)#L zFBaI`;HyiiL6ImU8Jy#Xxxtrf* zay$zQrbFkuXHpj z7Pd0F!9Y!v<&-uPWzqMan%?=MT`Ej;TwZ+vGNAu>g`NlwoUzqkCKRyx%xC96b@j$P zd_lFIG5TO@y8q14ZT_j!icb674_BtmeSb|#$_H~h3AGdIn0o+?9z2ilEZ92tfPd6; z;2g1)Tn3HEvgIqbY7txkHd2<%?W z>*fELkARf^soO$J1j7~wznSV|L}~GMeHE+Nmm=vLIvwAqZWBT5w}mvE-vgREX)~Cw zyQPN~u5)?bcHgms5E>}@qdia{3%@sVux3(7RDBJ|!znmSv97(%$?8Tz0R#v#t( zwI{3-Rt|e@CH9?oL~#yUPW^NC$tu;3gD<^bDjw1mbAjkt%c={&JQ%D}g%svh*S)0E zV)@N<`&Xrk?~Vf(cKSM%t$P=}MI93H<~i7FRbLV^z2K_P2zre<>ST(+R$R2g)3NRu zAkf>Z{z!qcX{ywryG9E1|FKF1WhM~&88RvGcc|Zwly?`w5uxU2V?kRo)?_+eG)0iF zC#7#xFb`RgTMARfMD+>;4FgS#6wqK#Kimp|ZcmwS zfs87Z{yrbTv{0(fR9(WkTTX~OYo!vyE5a`foFJ7X^(gA#(v&dypa*3A178`CQ&roR z;|<6rk<#h?pRWzcPjqTMB;8bJq6XhEB!52ckf1;O&yC6X=8*j1g5)82(0r32IRxi6 zF(l8FGYB#w;!fownyp8KgLY&@be9;DXOrw;2oWGteP1?!ER$}%Ot_-`L^_Va(U?fu zoS)y1PN4L*H1|@p3h@wf25)d?oL8#v@6r2}6(--Et;MbuJNnPV+zq1FiHWpH!7qkE zCN>+}Ed7pzPo-$;CUH7^xCq(7h+rsJs&C&pvNZ$0Pi{TfJJbfYMERBKOUO^alTzV# z79}o&W?vo^5PnQw1YRZgMU<+n-e76g?2pW#*`J5Zvg7@M*M-zcqDQs=e2-o+dvqf= z--L(s$G_5}6GcQ{dXz7dqXU!M8 zFeYIFGQy3wK3!EI@S89!gdrOBSL?xorbbSO za@}xiYH6@*s3$?ta-2DT?%b}cT<=1c{iU79f?q(A2A2>3w_oQ8zakZ|8S{DLe zSMH|!WR5v=+;{wlkdPBIPpS>2xo=;`tETw6oBRX|7v;F`yg}j?K|OZ2TuU%GnBl&w zN&@R#Hus%nX6QusU1D+_-Z*iEX2|2d>pTfvOw<`VaFP42lL;6rFxcmA87px|)7*E7 zqI+x&28CesluP86OworF>5dzAW6Zg6v+a9Dw(w;RBa*+_vu)DWft^#k{MohcmyuW( zPezw4@Vtrkecn=D3-;Yml`SgRjhl1-xm?RcHRY6WNs6>V3FPx(vN{(q0XHA}rV=rC zxx`_ompIJH-DD-w5f?)9;N9qYS;pRXU2SNhu_(vUvF!eVqg<-P^D%~dkxrnabu1*Y zvxo|ADfNx6M(kdM6iT76M^8{QLAy*kmJb|8XzRYkZh%KA~)JhgII{D6*ig-ZvL|KlFc#q&0 zNZz=ydyiSu`s|a}v8*)NB;JN9RfXIY>+;O-Ewf;x<%z9^z!XHkPg1L%2Kj(8uW*HPUE10Sg;Q3J^`$Q@T;yDPG6?C#vHvK#XZ1MMLqc7y z(P5!*YWYwb9AJ*%z*-@RX$G=1fh_Hu*wVs29Xc(EMOQqzBc z;TIMUx|`0XIIo&Sn`Hd5(ootamZ18$jXWuth5cYIlhY~?X9d*PZy>mkp|ZnC5nuZKD+XfrD>6ggT>BO9lzK%llAG_} zxkGGS^$CfBZRvcEC&hdSNx5X~47N?_EXfwVNjyr08*gIN>?M<9SL8&W6N}H4D*O33 z6}qPP2G*!hr+ql_bJUTisP3mwxntuuaXolgJfF44oqIu79vnu5GB*BczL|G9&CtfB z1TxfR+9$ZCK$`;i>8g^7%*7pk4qZ?&Nyf%{qu_9D;RRa4L9VQy)93E|y-3ZC!;XCT z$|N7oP=B_GXj{4(B_*8}ecD0@GCjqv)pF6UX&3Jb)^0skZ$wN?zOMIK8}#xn8eldh zg%I@eA2KWjTPv%;;*VgUvkZzCbmiOVOTA`~7o_nm&BOjt07+7msTJ;2oCt4Bms|d^?n~vhMQOh13GMZRe5v2q@e+uL*49oD3|4M{cmX0KI zuJ9s}38ew)AbczM=7#Rwz|QJyeuUri*j6i|6SY$Z9pOKr7Q3SOQs_i(r8mX%YT$kM zv**{sj6W&7AjQ+=A9rOzOgw7{f0c&!uBQawd3c10U0v(h?9cICqo0|+x%w##YkUwC z<-ZEeYUh#p13h)+G*9d;g>;4C<|k+e8c3(TD7?i?>%FR~9Dmp2GeNyrC-CwLjAm)J z_Uf_PtQ!%MVFXAJA9hKy5vvHrzqbfmVaeha2L~d3B_fTaht2~ zHm6^lQ>+v}Lis)?1_{fbU}QLA)LKfw4W)L2pciZzhe)suJwR76u{@d`N(*Oh3Onx;#t%kyG0>?_*A0^s-UP=ry`O(Ujjp=sN1*nN ze~K9@7ESkB+7Z_#QKc-VWAr-;NS9E`2ujlbl~8xT45?OXpz}Tixl#NnDrG(%eh~a8 z@#=c`k)+oKvAFW^uSR0Hc$C9q+CYrjg_yaK_MF%vsnCUSQkGZL&2jQ=qxntbd(CgD z6AALQD3!1G@zu27yJaTQBie%RI2|$1)M-IK=`oo=vyl&r#JLqltt4*8tk^Z<5w08l z>Bs}&Ek|XUW)qlDESEpYk;vl-^RdBvJfa`^M1oWa2b~ES4Z$LK`qOks?wsP3bZlp; zywY9{)m+wc^qk<}m&Zo$=RA#$hrnAG90 z{B|@;be_n3SJ(kn(JW8rs;mw(bzYHY^XgAH;#%!cx8ACx_;B+OxwM`&`OpZ11puMd z`UD67evwALRmC<+Z$WNgSOvEf5j_M_p?o9QVqnuF%65y zOw4X=${M2VX!Ztd+)GTJ#86sG(-;}9XnM=9bN~_9m62$*?>FI}NxbnYBCA*sq%6ly z2cd(6Fmxgk=A(7V;rV&dalw})e&U)jVdt2J(usLE`D{svrnI0gE?Ld8r0D12AedVI zdw-Y2Lx6m)z73lbTDBTZwF02l&|b(uLvoT|TI!ka)arJXU}trC^jTf_JGSd!@Kvte z7^^V?L(jFyM6E9U{83|&L-J7YOq*IEeSiY)`5YD4`<{%`T8z)D)Jwmme2u@!rf=Xf z9Y|F-{f0u3qC-2&9m%~sFrJsjTeR{LW)N>KRqG{Jr*KMbxN1V>AQmbu<$1V4G(u7W zzdM{N3L^Hvuf>^^Hug}CMpMI4yrNM>b+x})Z{vs00S(oD3mfKblC2dO>$Cg5R39<% z)zuxT5{IcR;c~bH?w$Mx%gl$GuwD9XXcq*{cS)_7Mq0BN%dl%oxqj8D8)+g{mM}ir z63CiTSQ#E|=-WS2MC`#d=v^8La>FJt@v0{uC9!T;Xi=%5(~_w_mKF!9sdPB{pqQUd zRkNhzb{HZ%I43)PlkqwYs9A{$p}}>_4@&)Q^C#`sRSPBQe|q zRMKu^3+D-t^k;b06vmyx@dAcFvUV24qB5Gy+_iU`J> zD>qfrb%ZEny{Zq7BsCe8P6Rc4Y2mvUQ`CxDLtj%cwts0#@DqFB6Id1WkO^DC9@K7C zrazLjW4VOnu-XJSxQSB%^k$rz1A=MZM^U+N?Mfr1}d8~AMnT(&Er^SEB^)1j? z1Q^wIf7bnnCNN43kPWs;CdpTgQ@!$bns7#@n#DQrx00Y0BU8#M5<&1&y;yJie!?A6 zC+>`PfaK>~hJZNV1Me;mUy=f~4z{E|*l;L}hFYGWVEv|nBd_8tja64wRiQ?m|Mt$K zq#%iyEdwHP=blxry8ctPoxmC{uH26TLsgYto;npt(u{Ws@GOb5HT6A`R>L`ahs`EP z<}|8;cWSzOGL`hq+Vud&q8EbBPp1L-9XkiZlY#fwm~}~I88k&*d#)Ze&SIAoZ56qU zJBM}B13%^LWTR!PKZ8FdAb{UT@`RUnUcHIDXIJ}i~0d89Jy7liPze~Vde;B=kvZ+xwMSUVQV zAxILd+6T-$WVlBzX|Hi}t59##ViD$|bGMWhp5k8^n{|iU&sC8FWKev(jb)7*M?=mZ zMb{Y%f8-=Ezi{%80-yVw6<&1>{M||}NRN{N+}h}*@TJjm^0}MOWnKmlsv8WK3QB(a zB;Fg@N3o0DK{pRwz$B{xpILwqXg>#zQ#S(f;MUF`g|4AeJ(vuky9sGX*uKdw$6$;= zWuzX}uWz`*wDm6nj>1^=s=3$cAz-kJ5VhfcTkM+e=t9Gcwm~fDZmGtDf?O=7Yp{q4 z(i5y`&PG%F&8xb9!&vlwmugLN$-4-8nY$r*>8v5s$kM5gFC9(lv6jwvSUQ&*vt?om z_SF_H#g9&;rHsWR4xVK3+zU9YZY|@w8q%p`>Bv=2d##i8lg|P?Q~mK?y>@DqW28=5 zJ7-EmN7l~PKp&kxOD~Bx_++geJ3+l9WF`4K%=WOGPSpwjpcCq~)AMVcFrL8wOBue- z{H~oxD$21Kp;gbnws0OM(?7Lvrb3XCg|kxEa_qtp6|dxla|-YAg)^40UO4ZKHVZi4 zEI<~{;o;*f9O;W*IQ!%)3nu~XpdSeF04|iZeEvjHmYcI;)*gV0^|N|aeErO00Op4? zuc!^pA0Amh=HfXgzIgtF13qc--1C4eo@=SvTs)6}U|66ON_5=$YBQ4z>8G>M?H?uH zD}x-J172t?;&5-$NUN@go!~F8G~UjC2MSG+*FD(+cDBHf`iKg-Vmdu4mk<*jyvT!~ zU+h8=ADZP+`QMFq=VbBFmLAv$2SD}xN~R6^hTwCmmYi`K$LC--R)Z^&TKr>L4AGhU z|Eo}ry@gXOx0|`xHZhxh9feqfc_W{f#v7d&ew^1FijTjveNdfG7Ftf#Y8efRH-3gR zeoz|!x9`Ure~Q`oKj4E-oo6+Enc4acyvAEUJJEX10@Lf9F(89@HbNj=ss&lTtSEl3 z&N9+koeq2;o&?|(Kc8}Qe=~moe?S9oTjWap7X3`0hPq5yUuKyzM#?-gFJ5N0ktX&g zf`jw?leCFStJpo?Tx?D6_kfbRhML}c1P?T=Y%GlsOtP}^^Ub_#D;q_wgtV8*NidN@ zXuzJ-wW3gsqWh>{$`qGmdr}*$n1NsDm>f@PlNIwl)r=|fq&{iI{HqzW(3ASK6|=;Q zDfOgovSJpPF^fH^FIq9@OH7=VV)ow8-QJUWvy~v*%;NN<`mLB@$Q2BQ%aeMG71L+N z%=Dx_XvOR`V`h0$AF*P(Bqm-Fd?u5l$YtAkhiKsiBaR5PyAJJc?@fO;6q}!sV^Mgu zvl@fk{Uf*N_!M=shkWT?1qKicmrV9CNt|B<=lEd70ptnRNw8IdcL*n6TuZLyEw=GN z-qdU=h;4jCf@kaC1_@3f7{23af>LCe&TQ6It}f;+xMgAZjz0NJ*>XsS@0bjdD{p!F zZLz#jlfc^8MvNBYQTYkIN&qi(cyCp0q04^`HzD`4-!^vCg3ggW z*Lb7Wv)g|n8QssGP>VsFel8lY4+~v^y+VK!eu8Y4U|*7_Gzw#%qyMgcNdM5tF{McR zAg74@7&vJGJg7X85zWE89D-6|=U?a(6eUwJ8Q3J3BV16~z%j#@yF71BYHN=8FSCME zBLwEzK%~}8luuJ5>MRH)#vwX;ZD-o{?cD#Y59R6wFhAfTpNc!v-g*l-(cuE&9Ux*B zMt{{^ztu>+oISLM#)y*nne~7wu0KTwuXbCfGJenP=81it<+QEYkh{9I4I?g<6Uz zP|JDH7}RpsF>n)%MRJeIlr+rLeEBHf+MU79Z1xoe2A=e)vm{<7T$5qh129aOvI&hx z)CHJu83R86%0=UoGyx+Ro2Y1=HQyG{%xfIhDRYfGBoW&e%ERRhmRJ2Z63*3$Df&vm zCP_pWTzYK5=0aQ5A1fVMOrw$)6E6)?m0o7Nj4Y;6-?W%Ue`PU^4j#LhuBKzsQiwmL zLj1}57263_1a;E$%-^xJpLAo*VldwYQ0A|a(bHNA+Hq!h(c*G7lcq5Z$=V8;(%Qp; z6Vw7mL?}A=)03P+(HZ(>oT9^kW+H0hzsMYb*~T1<_KJt88D4JW_YIK&c=kz^+5=t~ zTnA|@@Lpp~TdLVxrT#A8qdPS}Ax_4pTGNe^6ivyK5{|yISGmj#=#u7R}pWv{URaWtnK-hN$&VjBqCbjGSQgQM9o3&6m97MHJ^!c zJ6vH|G(UO)vo~C`?zi4iOCs#M(r#Masn zTjNw8tNOuxESbQw2u6{9a9yyKR-oP&yuRB8wK9XQUyn>UH|_U>*YCG=6u+m{$uVol z*Br}p+cuf+?A_Z)#Oa)EAS=FKyqDmRX>({vSId(3P{nO-6?g#sj*Kpf^-8fkucKjN z8ACdj8XR^8rWy;-Fz*h2&uN3J>rqILHmzhvSBaX3o?y3^CiX zi)^ta`>Uh*ObSzD)V$tW{2nTi4fXHabSHj{#CdhQ_n6ejbOyqHH;!E>J40XshdBV5Cjq-}A z+mYC-+RG%V$Z|wzB5Q(1Yd`^ zkng0~G-JcX{ z?msL!#LGwl)w+PKW&vBx0=Ak3Y&8pbEn}MzSrMIAD=?2Syx4z%m=&S&r7~`gV3$jV zk*8Azm}ko2h%}(fZjB}_PHbv8k-vPievtkQ7Hl>9@V_-7t#PY#y3*@@w*LijRcY)o zXOAg311dVx#>jp_5}QAhsTfOoCMY^OrrK*;%+ zZiRToW*~UjACs!hAz|*iU$9-P7yQ|@B2Tyb3tJIS8SM$yXv?^o?bajiD>_})^s@YR z|D+jh>m+f0yG~5|=_He2bC{E0OEEg9*1HC}r$#b5JrUCnbh%mx+5r{jkoq6MM|Cy* zVC5eAC;Rc3OzZKw@`~=t8%)7hyYj|2bwzjNpX)~?V@E;9V+^}Bc(w*30<;#X(Zmh_ zTQxzEy=Y|L#P?B(?3--8zk*$P7SNF`3~d<34rzp0Agzh0AfW6 z0Ahg|I~(5uWY0TDvsaD6ms`IUgcw0Lw8gb5&gf=2 z<}3+oxk!PaaD_ind47^OwEdi*`fd_kZAuWnSLyZ5U*@}ddHzl;V?`Z==>KT?DE;rS z`rl9g`?Ww-`%2`fiiZ*d5D}J~^#8E*zu*4~mS#l>BtNn&ihUDPD@r=TG>XWcrXgB1 zgdB_n9j+=2WV@S~SDb1Km*^yQCvCKFjK6SJ>gM*g>#iZ6>qk)&D{Fm-CU7(s_ZOOW z!$h4g{~h1_tLZ>eM?A=H9j7Dd5`An(FjwAd_ZLe?igZV!g<^FUEAH%FM9P5H<~w)9 zS-jI1v37G)wmrNoCA=;rX$(5`)E~~r#LQO>lv$H(>LJE|xsorQmT6oyE!((eTFyW_ zQy%k|=WiR>%nrd?qkE%s^xTv{DYw+QmtbaiH4aW3f!?qat3Phc5)JXQX0PEK?MVsj z4o01;bHY!?hKGldR|E=mJl4RUp9>GpL81;Soz%u*#>=MZONwmmo3g9<)l*{LR8cq| z#kgBjaLHZfGOd8xS%p-i>G`G5s|N7e=QRe^d*FdyWz+%Q)F+Dsr-U?hYpMu9jtky4 z8FNmt)sxlVn)y;Y*;egUrw9Th3_@;^*>xKPsofjq;(@96zyWPr=H51tzVRwZ)mAHB z{E^uT53N9f%*#3DQvbvE>ZsD!&-t`QUogGqmO18HDZ;tjBA%xfQwjcnI$hGoZx9fD zv|Y*TuN`t5weo{cm_b; z(@`D1*)INTjC#yMi3Tv`8u&u2pC#YtlDcEwhoMG^}G62m!YC%8O2 zHA(DBD+o#KGK(a3vV4LhHX#d=*nV*^CG^EkCK|wLeJodxG|@GYcW^h&(4RGFtR2!A zEL0+MiG(IrDb2xuN1Pt!ZW2>`D70G?f8-ano}>Cji1M`GthY%pP@Jt#-#JEco{;jW z8qYoMyEgO040;7Jy4WC!=UpDp+x!UMdmrE@ob#=bPG0u)PENEsd449H90Cel*O_k7 z{dDikRXfs$CKLS9&UD?mPU##*p!{=WoEd2dkc06y>}}C{1}96uAWjD6VxIsRE4(u& zT!ID8MG3Z#C01~_Qk^CNqa%EMpKM{dE~}@I)RbdT+s2q2ZL3mWMrA>o8Z}^sfIY<2 z)b$zcXR*(Mah_>rVpFNy9LjR_$1i2qV3711)|n7^0pt_Z$~$2`X$$L581%NI_h$^9 z=-RBt27Go$3X~qKDRD$YjKX@+no+7qa-0_3u0}b_jQ@@=CRPoM}M?JyuPqMp)6SVrR2}(zsGpUo-^+KUmF(B6sqgd^QL#cDQ09 z{*e5qVNqkB7BJ&bFdRUG^nzWcjZML5@#h^Fy#l(j8Rre(91G9KfDBeK-OaMRjqf-^ zWj(m}4V-7Ja)vK=mYY6jLk^_r813@9?WC$u+uzdg3Q6Bxanq591D8q`oea87_=nC; z?XH%Qw&`3|odT6@>TdF4ey(F6&;!UT*y`C9I&sq0{=1Z(2Xs?)9Iij2J4VV zs9wZ={R@FHB}ItAgl9b!zHS;zv!b9aBMljpm8nQVENKg`9>TK-i0%aGL`w2EOY<&TFJk8Dp0q&LrNK{}n3M5rtN#S| z=V->EFl8*f!|7RQI9J&(VTEQaADI-Rz6(l;F>8~ta1+vI2iz}pVBrT{`6&!UepfX0 zxjX`!vD(#2HF**T^Za>oERd*RJ6Pw76zizpPQ}3`J@mB=<*Qh1^(&&Eg9Zb{ zZ*rGxM8vYN26`WWc5^_q9QQS6geUHJ+zQ_}?%R$3oyf*GL zW4PcjZU#~@moV-ybBX60B)9P}=nUR;2nu>%pbb~LQh1xJww0-c_1&wAsAaay>Jei} zKWtm0C;Z4XnGRGiRwKO?zS8NqVrX2Osf=d)@d$5!nl@Zv4 z*29v6Vs!9nNYD@m_Sk|q^*b+Pn*@8ErJ?#wVgjY?X*pGQxkDebZRBV$mK?go*z9qv zn#|%4XBuGVzm)nrfeoysOPxg?$I8z_==0|r4b5({MnJ~j3jofgEfpw)Gw zj*;9oYI4Kj8wWv+px@?%UTPp1dZ3A4?YD$()8-wG?@|$VFQ1$dNO6AdewnCHxzE$jc>)!i_FkbqV z{Js`yZs8f}%|TgIY$J-vs9sO?*0f#tmzudiS=`eliq= z|Fj*Jhoe9A%+sV&?;<{2QSsK~Y3jo>KiEN8}oLUrm=1Ib#@ZJ2XZgzO^ zQ5hXusrw}cXam84?ojicJpGROth22?toug7-jqUJNa)@t_~ib1TkwljEzK|JaD;Hc zCE{qNLZo;p{)CRBsoMM@Pv6RF&(4)DV`nHVnT>6s@Pj>(jhgkh^8F~()cNw~Oh8`lWQklk%@Jo8e+CG20;U(#bYfbolED3bed3D z=L%mVl$Jy`@sLcm_x?v`>%GRwXjwB{gp9UD=?yi@z%jp{FuyO!0$qJrTye(n6*=^G_i? zRorI(RZnx_$TV}_nG>w1)|_S|EZvlemBkyMXwEjXo?hlvWZFaY*M#Ryck=+_2fEfY-rV@(&5aMJ1%Yq|zhL7hrnl3d8ZJjM84p8Zm)rQ%6A3rZ zp*C|kj6wHL^uA@pM;j-G^qz~R2OpKO#hJV8u~ef<27i~K*8+%BJP=nMvunCX^D}fD>gs1_pt+{D@sfAszN+5BwmgVVM+FN>itG3$O*0$Dq zTPgU0CIm@%kr(A^A_Rj{ed0z9N=eYj{=aAD*-e5kt@r=?eg1#=WS@DTGiPSboH=vm zjL{vA$ZTzRGfAB+ZVRywQ6aGX9IL$49AwM8`)JM1%GJE%q#WKg9l$U0-Z)(}lL zK8PdI&r1gIhi^^DG4}T%aW0if;fgecLo)d2%uf)QT2=a3L#1c*$%S`W zTc4pS-MEflvP5!4-&6OPIJQZHZJxS^@EEG~JpQg$Ik?)U?uR!Ef#qGmI_1C!UcRy0 zyIIC(pj}-k2>&1?Nn$E*$F+$vd94kO!DeieSd45Za2k$NaEo+*>454tARR4`>ZPF{ zk?;72Onw3QV{ya*?vj!J1p$^9$+sr*1$$O{?iM!W)2r+4z?!)<9r8$o!RJJ0uM?0lCcndkM=v1vAPl ziby-?fI8(6QE|qo%LMvk++TDY)ub5vMnID%zlEx2Nt_ zMQ_9-^hR)I(HmK~6}=HhoZd)0n4)VhOXO3S+JP4eBxz%68SO|<)M8(~#!zg_KGChOaEI@ZCdhx{o?O04vUbS|83hj`ZG|XdJ*DV}no|t2mn4-_(T~3Q z{4)ZDm#W(=v8r=rK>iM&$W&M=r9z&Eu2T*}4Qe%jKlGkp@W%Y>3^ha`E`u~qlq4y9P}HNc>k%Up@a*h6niUF+ZFSOn-5Pkk zMMp@p8Y&et>eN)J8hmZ{>q4lM59kXZRHp;S!kg8|I;B}P{~TQe1byXj=(gTJT5aJy z(a&wm4)G!)jkMaN!W z(bVqOs|E}1`v~lZ*LwE_j%k2F{`I;PLB0p%3qjUAOLR^@fCZui_N>L{o+gF8=x7$@ zL7D-aA3fJO70A}7YLIlOmdk{(dKXy_qB+(btnS^cTb<-UrZ#pyPa`K84UUj>gO;^$ zilRrG8W}>*pYPu?>Heo|yMOMbQ{vlnIwjZmkhZ;+Xj@L-z>>B}u79yJ{;*TE?X0f2 z)|9T$tw7HyYhU(@-J6Bmt=T@6tJIG5>6b4;#uzYzrw75^4k~k;(0K4}mWCsw2Y{U^ zw&<5Ddv46JMbEA5l8?L6luvSHJ0DJrur5ME?x}#-FV$@GlU0t%{X-+6TX6f#dSq8} z5u4MPNmcqZhQ3noBm8_t{9=tZ{jKz#pB6oeV)H-0tuFE8JI!KT!L?3yZEo&A52?y2 z!zP=u^8KWZ6%|h;KOGa_tQj`3`;8R|dC)v1p$Q+MGY@1P3~qODmfi4nlD9!Fy-nPX z5N?SpKRg^MA~wiRDbp2|HMe6R;D{*q)zkhe`;+bBM#U>u*>xfod-Aw=xl$8r;wJM# zn{SX9L~qcdN=EX-MHDb2OcN;iI&vd>$J0R_n@<^|)5=c2Jp z+N{2`-%6yF;y)#Aj09G>GZJY9i2iBCqQ6*$D~-&LC1>HlY8ckzy0F!YRi))w;t0O( zaES4cVEucJex|wH)|vBp<|b9V+^OAvJ49$_$Awbj+19y+o3|#?{f>0JfxJk}vFDxS z*=qKq>jteke|AnO=6KVpQm^&yUNaiA=KC+o{STfX%Z}%y@Evq$F^ZZy^I~k4Fg0Y# zVS-W4EmW>|<)?40DYDBOCOySw;kzk>jD1on_?hf*|U_R?ZBAzEASdk&)@aFniSuHTB;|JJ_70u+WHQC4P z+>W$yh-G-iKJL9_drCqh^YbIQPjOJt{dN)C3h$Wr!z-QU70`m`RMXxoyVzkkMio1a ztFd{tZW@-a>Q{0_q*vv}!h(Z?sD&lrtDR=^pPB!#As5=Q@$C*4J8e5rgGwiDiTS7% zmx6-HPP=`%n4wB&3zc^`sZWW_i$H&G_F;^p^_rI=kwGDSj;9qS9#kSz*P+z9Q^*T}EBRe1lZM9aALZd@EzV^_?eQs+h$$Fo6U) zI8+sLtQ1$uH?}IhG|D5`$^ApAgmCPVq46f=M;BE&mR2QQn|-)y$lT?@(>GVU5pnWx z3^Ik}Ib0@G|J7UQi+#L$$n8Se&MC6Tr6M0@ml``5hh-M#Kz@BXze4*KK{{oilScg=FBG3QA6 zc9%SF#3{~ho*%sI2I-3j>Z9Aem)#=H`|G1y^gYb6x5lY&QBm-w9yz3DRHB;{_#RCS z|A{eBp5E|A5|_JW9J!3Y-u_J}e~R(fS$s?L&0otcl5g+772G>^R;AW9eC>W=`~Rz~ z`?OIw^5%wAj5RJ9yUr1;KkdjH>p;~ehI*|Ll-Wr!L79-(1y3gjzU=KulbKwOyeZ$VmzFy6rm9yD&JSNESp2oL zI6Pa%qJHz{*V1mR{Q1I{34ToxN;8lH^S;e}OY$2-C4r+ktiInHR@-2^{-WgAP)Qse zzEwQl<~75znq$clq?3hb%OHG!)ED`)>^9^qBCUrYIBj4QCZDsGBnrd2GG zob~96+`Z*^^von{b@zl!25XgLIS*Fma6{)bs{2Zh$P?O61TSv-F`>ih-#==uykt3# zq5-o#zr+ki(c<}bv!a%Ty&F&=97MEH6>mV7)PSZrL0d)L1oF^*sMcect#4kIxIFJMp2Ldyefyld%M$|9|zd) zie_M!2@MK}p;-u*5}s=CHp1n~r72;YMd^n5h~IQaSJ9kA8aUR&VxFa3^(clT&07go zT`23J3^mgc4@pxYtX<>cA!oo=>@w92ciceTbp)8R4WKPyx1`cs%1sq zg?{>xZ|1N~D^{=CBAMtRRwKk`exJozjYjl&wn?^Vez51lKyGZaq%yBsF5M=U>|Ld1 z5}xQq#X2eyI!OYa7Rrm4;CGZnF~f0W8ClMhX&{X5tSB7YW-Rc3?u2`}p zir->LVGNkRBWojBdL@fo>S~q(uO?Kq>WH{6UY6)^&XZyGX_|F6NHKC>E5-baDyhFZ z&t5Lel~`jQ)||(H$(U}=NAvYyr8&U{!^Xh`<*Urq9C*(5Aj*Mly z8cqKekpi(j|4D<;TJr3|YKX1>M}^lXfS zW$&3)>E?xfiLWCuH^^=+{=cCO`w3;(K|sui8u)G+?3=u&n~EVKk^ic7XSBH%z$^xQ z%T4hHoghD}shF!BZC>A(VpAf8a1$lErAJqdFlY3o`7UXcU>;3BQBT`>oY909Ma{!x zVd=UaEyb*+s@!m%OH_epR9%bF!=0i;yWP?TZblmzC0~?i2Lvl6=^%QvQ9E~#ksF|D z*yh-tn{Xvheh&86(&g%=YPk(?oSaIo)h~LmDdgrc<*|bwo(0Hc!0qx5TaV zq|Z&jU@pL_iNapAQX_Has&mW_2(-Fl3eISuD0=@=q3tkFngmXk_%OY^0*HWs7dtMA ztxog7q^ahz93xFE*^Y&5pg(xE?qC+q~RT-ZxwfhE9yWgJBI^PMPG5-}6-X4E*G#ef5Na4vMpLxHmhlM9STO<=|*#ybW5qblS z?8qvW2`%*={|lZiuMm`VV7Ux5_3B@rdhhncZbePO5#~n#Z?$7OPL%dV=@SjSxH0t^ ztg9f&Rq6T=>Nyl!HrCCRHS&biBRT$W1ZUq4OK0=v3e(1|C0Fz-=tmj0+8JQ9-an*v z#yI(6RkRUiywSMH3{Fxxf;YLIlk^tVjt&mCKgZ0JAoFePo>fhueIxt zw(^tr;gKs*q}fUu36UF9gIeEhHIJTXYr&V{Pic)xL?0OU0os7dwj#u_VL+f_Ce5gO zj4`0JuX)6+UFK(}Q6mF`8FB$&@nSqLz>Zoyv?AD$;n{+gAa|`8o8pL0P?i+2oj!Ak zYvoi~px(cO1xonuLGrW$J&dAaUZ-aZ`rI|L2%t$Xp^|wlZ-KLYiWItvc!4PuYd4w{ zxviX%#wnMgtegLZ=Gp4sGAUfjI#yZYYQJ2DnPKN9k-Qz6Q!|TqpH5n|2yWFaT%im5 zq|e}D8;?r96>05~7#y?+)4<4$o&xy(_X~q&16b3sPeAj0>jd_{QNj2-Zeo!-kY3ym8m04jpGb@7a>*%p{=(V~6OFDiJ z_#3VK{%Kz%Z*s>Hp_SYoOwTlLOovy&OCPx#BiCd)O_=dJY@W~*az=(8U<5xkh6EiKQrK-f=FUF*@nw%fvtul`YpN?eAMBnzjq$f`NQLL+1)0t^%4)3ed?j{Q=2M+)St07- zYq*YbD7ZZxhl@XFm_C>Z>`+pbJPyGJyQ#UQ!n3_0Wwtz0y=KzvX^2hNJuWb`1Kj(b zr2vl8uvOBwFAEFN@-dHxbcr!pRZp^o^I+tz|K zS(&&j9i^=L?lP>NH^fDC)ja5lo&U+g zC5T{qu&U}x_SWn-77AEVRd;Lp>3lW}J_eL3CpB zdN+xeFApA9pvlkT%QJlc$U1TG%*^xsIl6XCrbC%6lj&l4s<7wa zX@D465uCLm0Y&b~I26Nyf;%v&f%-l$BF@{9KSaA20+VG3>?lhNf%M|U5D=R!13Dj8 z3xlBXZ4HB5?)vN_uhsb(lGo~d5s{X_WS?tZ1des*-*Bz!{HuZ!q0VWVt@h{AE4kp@ z!K2nN%I0lnNOS;s1~0~a0@=CYAtawpb1$g65Ic}c-bu)FW<6!=T0SgOwYgFU{4UN&NpB(XD$o->KdXy3Ka!S4v15Y1!GCcdDNm?f ze0>UbPU+i1v$U$~PD&wM%>Gj$*Z09(dbk>Og|!#b#?Bsq5m=6t8RZ$F!)r&u{=OoC zWkySMIO^w~IN` zoQ_jK7(8QNEAQSOj})5RGExSTY3Xl(;;UqsnP;vzrp&1pzUyzxx%B_dG!fj%G1VdOZ3RFkWV5c_^OcXGY+N&UNpG@$p=1`*L;oSvkdcJP2*C;vpmM z59#|`Eh0A?L+pV2TV{zrZcV1(=9uS2+O=z<-?-ZO`qiC|cpX01EEPpn;_B zUm^8Jr9ui;4Wy!|R7gWrg<$DZE3A?Vlb@`yF|@T64B7}rq{#v(<(%3sHVbwMqNRRu zg6L0za06A2ri-M#;B`RtIX2r_I6k#~f`4Jjvy_DS66vUbJ@;9va9KXV^Rv{>gS9Fa zzw23AmlSWc;6r#y`@{p@0|9&)0~?sAAk9LQt^gATi2|hptUM(AQbAGztyTqg+b1h1 z;>y_tEZHy(Y!l<#Ri{|j@NCKJMP@0t5;_0GhUjEmWkYtBO_C1oztupukf)_!mkN8> z<(*Z?l3t_X)b`n!*8t_oThl417yTf^BYCL6u6l2%KrLf2d8nNZXOkj1zov3N zDG0VYQ?M&p=|MrP^%kqwrr@k}@?P_%L~`Pn zAgJv-^9*+Wv-*Ly*p!3OM1jZ6e$}Octe#i`jfZ%D&%`jSB2;LofV$4lYHN2pKu2E4&uqR zbSJlkJS^@V_S(}QlU=I{*LIwtryRTA_k8&Tz@2J1HrUZ1!$X;-;bg<+nRig&?9%YC zF_}EV#-@yt*@cuHrexW$j7THwedV8g$?Ym_**^&XLyd<2@1z5orM;4mbd;m>W$3cl zRZZ@$@QgS`{2o>uyfj&ojHss}4gBrouc^XwF8GvP4f`ewyQCfZIMuxG+1?CX&o>Cz ztLKX*{j%EAXK3r{TXa82%Bi{`BQ&BFVz-m?faKgRIsK_^$t@&me5wP~L>G}&C4S$t z3@FbTdVsj6m^HR2y?D;hdR22-!&7@1YaQn^;(`r!eKQM1Br0EdMTMty7rA#mrDW8d zV`;>K%*2jR9GI4rkJ=Pwrr2yWFfF)atTd2NerYiKQ0ia#Vg`#YH=~L-MT|rV~>pP@5 zeqY-*&dOr<=?ZcKFSed&v{J;k^^Jpf1&SM6JEKXG19|g+l>#@U^ETol3ThDIUCL(+LYTK|$FxtAS4x zg!zXmK}e%8leTD&l6XW4Nd7|urWdh=#)wJ%?^|b!os-A#9 zsfz!8;Nt_&Z+M=HH?;SI;=u6(#|Q8DBrHnXqjgtXOQX3%uy{&3we{Ub{i#!T~@oTF=jiG{1~`tY~A%~i3eGUqp6nTXv3!#n(?n?x079u zK@p18Y?H}rTW6JRAH2(oe3(c*dIxh9)Rz1={6}{&R%OQabv|p746P%_n2GJJT{6*I zKW=Mn#2ktfb3aVm;O=!5(3_OjTQXA`k<^JbA~p6Jk0c+UQ0+G6I1$%%t(&c6e~z}K zMnZy}gOM7>)`jO~rrwP1GOq?OYQ{$U=ECd#bZatb>f#vx|NY{ zTiJAEsGHR``dnM`$+Hq3k0%7fwpG74GSn#ve=013@CQ5E*%%smBh@Q(?CwuS2PJ~r zIPWh!&QThVxP`ewUdS7gSh{HU6)iUFs24d?8?0q*wTt#RT5omj(mRu2XJ(%eAvpU{ zk;j&lVsiH~7yLYqDKHj*hGFW1PS$ufvHKce)_zS2|qW9i6155`HVDmyUx8yun_*kKIAxgFtgeYyc$)*4n}ZSZ%`fAL=cLucFuLm2>3O^Un{s14IR~(d;mcYY z+OE%(<*3SC<%)jY{6Lo6B|W5JO12&2G;7DaJI^U;Yb|bTF`JG{rM%M-9e=7ZBhVoQ zK%f%^q?u>P4xiJHvoG&BI@=P&0bQO;-(=s-SyEZqBO3$XNCbUPY6djwEGuhkm3p&| z488rf9J%}sMBINbQAv7U?T{prK#Ai9$+?ZQ4PDQ~c7}4E6dKoh48qqHn^~A1@XGtU zfk3O-a!?wTe5a?a6$+hfyPf{)0QV9u)#fszJ)u{nH&|^t4&s=Z7teoLU;ZJIp9FvU zL|%~-C5jd${I-^vh3>#tL{jVt-AzF2yKQ-C8jxI1-AWdRmR1E7b>DR(#hTIUF-{XG zQ>UxNB-Ut?ai;Q?m9-o+nk7qa;M~53h&7iCn5VH;ad&zoWS9VxMmq&dG9^Z~jnoRy z#l{g1!;WMA#JR+{*%`itGpo+p&h>!UM}>^JX(h%=cd2KKD;!w^tjM>SMiTT;Hu%#lqzR53GMeH9&_@Qntx(`Yl(h=akVRWIt@wt>GS+U zf~Uv%Q-U|upNaC)OMP2@TCn|%V6&sG{BcZiNY+Tc3+>ZLNUBE_xyEPCkyVdF4FwUJ zOk%3$ORvMMU+F?iJ>5*cSo9d>`KHOZ?85s-v?iIeI=OH|3D9yb;@v&Z?wn$|LOBQAI6p-8-n?Sh6%SD0(A2Ix;5$BG~%^-zt^LH8l~8YZcmb;L&+$(?;@fp1l!QmCO7MNg&T;9&d$0Osx_m6uKwVJ!*Yh+uKG3-#pVH(%@5#NuIhr z8boaEHQ++u?^Gjlx&q2*v$wu0WCx<-G1Qc{i$bZMmG?Vk(deZElWl01BJ62f($)Iz z?0p}_V%4eHv7@^RqGd<1d8Fs7eU6cL>Pd$jSG0RD{fKnv6xqEE)md5GaOpag*Tt~n z^vzyy&^(nVwqd&bE;-q)!FBM~lWh3|ueRlNanJ&rC@c0z-dc?ngksCFo||SacujT6 z#{&IuP@ci1jSTZ^I`lm`yjaGYQGLP}Iiw#T`;{|$MhK&2Y+PWfUPQ<~JU2WaqcyJ5 z=K!Z3i@UhPSXM8@er1|pY_~o4Lm~z(#V%|Tmv{012mauECkA3U@|OUxCjq2z>HruO z2~Z|wRSL$31i(7EkTLU;XG1$i`m%Ff!S=LEb6s*mpW@&Ki3)MWLk^l&h%+A2U^=Xz zBnd*<-qm0xtDuREgqUlOG6D~X1TB@g;8kuVLrVq9;C#0dqFHVwMYG&mj7FM4O93rD zcrVj~_d-2*#n?4wnp{DbBPuEy9Qw7N!K7!{4uR9&kB2lyIr(t~GkT>Is2NirW403Q zO7;s?+#Au+R{FOvwi_K{d7@b04ckcpciC_(IonmsM6}y3mHrsCG$UA@!FxMWzzrG4 zAos1p)3h#4Q?WPjYm|=--f#m^u2x6uQZDmKxw#pN>L!JAGh~U7JZ*_Q#FvjXa&L$y zky6?mZA*RTFOP73gJ~U6vqz8Bk7YheJC*Aa>G?%xaELhlqK|NhnERq`93tnw=sg@r z-h&stX4s#KeIV1p5j1W7%VZ_FP=1E;6PR2(Oqn^&Fj6MPm{CK8JcLuCylOKk)-8Uf zU-aDz&NJ*hRvm0ll`=+2c46rPqc1owdY+Loar;m~ZFG=!f^J?`2TjZTsja!b)-loK zwp?G^nB~{?4S1vxENJy-_^uvlj=nO^q+xJ;sPbIE6p1Ha;zoqYMY@KW>Cd~qlY@9Z zC)hJ?%?V7Gx}SFwz-|C_KP*P2u^oVM$KVrK)>{YBXJPw*VL-Kz#R8K#Ddw;bxhllO z^c@xqxBJXx7lI^pAkLF59M{LnSg4jo$Ha4)7m-pX?@T^zc`Ov}#4;vJ+q=SCl?vlD zsI4_S*70W=P*Pz8hM~mvd*L7hYoK);j&wID9SMq-C*NaLu9$eDA{-coNW;C=R)c>?m-qn(lWxkY z(WQ#gxT6yZXlqeH;|cO?IRpF+?>RA(l!|HN_e8L{Oz<~@*8r`Pcqk8|cqwg?hr(I9 zBVkF`aLlRHxX$!n$!oC!2vf+&4e;VcU<3=f4&w1b{K8C@vwJNZ_EZnJsF=7L#ms;l zoSIJwa>O^hJI@^hoPd)C@tkt>g~ZJdrz6EBSK{-->6tkpR z3D}4vF0WuzU&be##Fkwcw-Q7F(s)%J+BS?;w)CFv)-k(+$2LZ4i6^b#}?(g^Vbdxjaazew+n#)<^#g~K+v3Hf*Y-QPp5u{-wnrsiR z$gwr$FtcD3a%A6bYwQz_v;Xz(Ug_pc*q_W(n=4Lvcgvgi{RI|s$d>1NN(v}03DrLh z3^wBfPhOWidA*a9zt=q*8@gMEG#8lc6-?Hj>H0HO zJ{&^Gi`lZD2VQwLoAytR+##l9u+-+CK;=zmSN{Gq1##5Lo#py)ih{d|2e46BQ-YRN zX&a4UP0m`WAjg~UTya7aRaHGAJCJ|WM`cP#n7ym?Il)u+TM3l>hzhvUpAxE9DR@~R zywJrX+u*G)(JeYrJ_<5@snWv>v9s7ORlb#Kq#_)Ygqq62jPOEUAo7onx-1#P1HBtlZH?JHQgiD6|_s;nN?w8$W#7> z@5#Uwz9&=tPFBBSwb9(eSy!bdjZ?A&?J>yqMA+WR@<_uyCcK$zaYi)V!w z&KS_ef3#BN^iMTEyl~!tR3R(Xg8l>Hn(#s&17TD&)f&J?{(`>o;8WwFDSxS;s44#j z{jr9I9tC=Ygj1x0xwFiwk`MEg9?o~ms+^xG>N@!&S&_NbDt#Xx_vyMUHBhWs(UhMd zfjgNAU}>x#6(GmR&xDo?!n)8`6uDrEY5!w!KQmY2e_{DkMDUO4?0fN-^(Ur`GAR#o z;2-BRzoU~k!i2q&mHnH@~_uGjIw~p_i6v2 zxSwe%K%TzDY6O#8{2g(>)yUSk-)iKpxZi5zla@c;$lv4d*GNvESs0dcxx+ufD9Fg> z^=7y)!~Iu!IoYtsd^v>X=XkqTM2+R?q?(vWHA?KmetOCRZZZh4v#iPs(zSm|+&@M8 z$7+9YU(6_&k?-wVnPn{ZHN26GnXa)$!2)9+Cc~bRCG4fwjeQLr$)WW1fp6%fD-Ih4 zOLZwPV}pSOlnmOxGwx@A3lx8g`=_(&5JXr1njrM<3UZ9GkJG4HvEwUA4V|>gU2NuE zCZ)PMf~ zwS&8$F%pqNOat8@j1Ua>ZeNKx$4dJMX-g`5kO#y9o#r!7sFrqQOTMcmAJ*FP*?ODW z-f3KwG+;6yZ;xQlR?0dxQ{nE067<^w$IaDNA@81{kYZJcJZ&xG)6)Bdr_;|{PcIaz z3V)r+W=WWP9 za9_{gk8#^l|8M`ZQ$dod#G$Kl@<~ojO;P6~owpPlt=xJH-t{fs1?&U7G;-H;(cZ*X zYPD(Jt;Szq=<=qyvfSCUT;gh1WJ%XzCjQ2gf~Wex@L0C7#|+VV%Pm}F?@~pUi2jhR z`^^ONbj`(T@yYv1sJ_ZBtW)9r;liJpv#sC<`>i&GkAw>!X0_?de7=?H(SEB9MsW)t z7q%m@!Yp|l>xFrbM&FGT{=_n|+>}=j7>NI$(Vu4h*&!eEu`-4)k6u8>c=ym#mn`ER z3(QN7FODoZLNg;vI&rn|*TY}$;+QWJy}pL1`_xM{Zy;e6eIx1;t9E};`0~{GD1(7x z?B}*Uv&#H%jv9+Yj72PA?Z))l=xmsJCuH2B51zVlWNA|Cg|XjSF|1X@(r41+*;6Mt zH!zdFub|ZYaUoU>56ipHp1LCvJ}lzg#)dHFD+;f5V%x)K#{Pz~<1jGBMG{0D5Lr*% zf2*XVkg$1Aa?3_H->5c;4)L@ASD{YoP;N-=ZOYqggHsAuV{jVNmfRWbxKlM~2{*s4 zV)7p{mK=ztUC7k_gOZ=cu7*eyx8bG1d^xK4p&W9|KYB_y{z` zi6dXuUvnhAPQ4?Hg-+n0@2BERRfCp?-|rPFEpqKDhoaS7bm_O|W3`eJ?g2vbtf@+& z8Q;^M?^LDY5tf#i8>?J+mTS+FszG=bYtQ_u!Fckt$6IB`Gedj6LWTRxEbZaRZ9KBy zrnK~`WIV&QCl08FOVRtO%xB9?yNE0ps7-lYq_!cTW#*Djxmro7`e zJEQ5|3FP4M0`q0agXhm!G%q~BlISycW=p$Zm^EqX_y@Uw!3Z2cE44=ehCv9V#)}?&h&7hX<%l!6Jy|)3h`&^$G#@82&XiT2fl%=Gd^5~8il69M{s9G zbT=lZ0td(gF>;cE1^Z;&iS&Ogvnk##wA)$36^@G(2-0koql?kTDa!#WgVDG!Q}Zl5 zH6k;BPSAsx{;utu4`uB;2VzKLNFNED;G(oCbi{H-Hc8-OV<8e90b6*k3k(&=`sR)0 zXGFG13KEnGMOQd~EvUk5{xP(DpZTZhimvNN7R~Al)B!}Jk)QFT5O-er&gnkp+1S{9 zct}gJxpE@WJ;L)~ShpCdqj{7)f{>s53{r;Irm0;0a^!v@$D=w&a4m+f*QR^w?nG!5 z;@1BPzS)OQ6rqCe*)q!}Z#d1OEBa*a;c!+$Op?r}|CSeLUaMzwXxmFO5kunxXHg(1m ztdRtYZ?dvV(Ihy@{!p!#H6TpVh}b7yNV86}Ll+X+B=*J0b3i^ZAGMWyXB63Aib?SrQogVwcOPnrFoC2 zBj#fd6u3|o=ARuRcjn|0ema^0;{~D-@+sl^`71<xdgSbz)7$Fh$qB(_b6d&34|jd{~l&%kK{d9t@X15-xv~h{x^Vh0Sk(>$AN^Gw-#U5Q~mV2$meE zbwvuFF!PZ^S}77tMThL$jZ^_87I=aeVxd!$0!fsUX*6?)HOUU9 zdDxxJNbNCk8+mY~?cs7)*QA$#kD@aCR9k z@8tdxccvz3eyovan>M&|>UMFJZs(jFM+*iFym?2~yh%cG_P2gxycAhs-;gv%UAucZ zQkLX+wb*=2I#_R9J$0{AWu(CE!@$e1*UrQ$pIL!+N{M;plPR(}!!;e3oLwjJT251* z07Zw_>Cbxo*{DB4r-U>3ncZ$~S)*`av|w}AJ*Fs9SZ6)~-y@A&Lya72*HQMtuc%dB zn(QA`Vx|EY2&6}fW4DiuEjh^mA}8HA*j)84$S*%0{fc>uc15^~^?{EUj0^8CF@>j+ zvS(4YJfzfOR{U1&Dpl7QA4zZAeXTIQfd`H9`{~834?Ll-ky^2$9hcBi^g_~;Y4jxb z`Te&%Z!E69#Fzk^4Nn0L8S<~>cc{=D}fgoIcf4KU=aP=eMYHMclrqBr@ zq&J@BtIj8#pu(J|fb`V;hEO`dkQ37|&4CS9@tIb<>@$dhZR;PKqMLZLWSYtfb)#e~ zk#ajPR*f%`>sv1Kwue(}-A5yN_Xpo}lvF;>X!e=A88IlVfT1aJ$d>n@QGGvewj0As zV~gSZ0v=69FqHHl{S!@BVr1azIVCW;8dR}Ao2|sX>4~)D;?gOJ0i<=HC!k;Uu{&%$Dp|*|2 z+>xA4L^{bpmBWi-D=j)3k|3kG1R5JUfD9%ebqoy^USe#P zic8GrCIPpqGx7_`AUr(X#1qjfuu^V1MIx$V1**9ZjZ+yrE{eiHD21prvc*_QS&@4r zhcKM|pheCUdO^3hUns`E$<2bWS+H#8jp-F(9xrX-lNyFG=Z&td!_?%;hbXsDWpJC| zkSSTAp}6T6}xc?XGZ8GKo}8e zl{GkJ;G}0C?zmcNQ84OK7_w=;(%5EciTQ9qAx`&OsFxs-X^aO3W3%8SqQVrm$+ODo z*^=+}>B?fOT(R3Tg(hzj^k4^25GZ22ir_XW$>#U#>^`&F4-A`R5E;$9FQJko3l zt!1b6V}VZ!f0S>DH445yilbQ8K37<^Sw_x>AICAQajKB1PE8SWuqxeoo}Te)`roQO zvZZU(=B}|u1Pt@^mv4luk z@n;!G1Q6pdW*@jn(Gg*f(qfkz`^~)<2_;yrs7D@B=75$5s(@lBXirm~35IM%zDQxa zwZE6`J^z~WI`yYZJ}m7gS=u`%3R(k0IMihZ!7)YWQ783d^-K#J5TNxsj`C&}Li~vv zW2@2-jKx;bJt@#!Iw{2$Tg)z*cg=UEdp}rlRMA@`XPSr(9-+Aa409?Nu}VrSQlu59 zqOI!u7J)e;a5=ZQZ|VF+Ia+H(~u8W?Vh z1y*8sh&wSuRYi$w>8g1%sL4C-F z=+9biuX`)L+Ru{g-wgBw_8WD-a3BTf4ybQU>1NE2))oA&a%p@G+|Ha~-_;caK8or5QxM(+aFhqkJePsnei zggb6m^_j-k#7~UnaXI4~MobYcuV!RVd*TK-OGT6!JBa!Os%uKNiJ0-Ma(%EFjkbWQU*FJ1b!BZcKJM%zADEiR`uzF zDc}z~wu6`#+a!;Ok}?phS=nKm60Sp*OWqy)kXmcDNgvz%Ut{wakz_@c_aiZv9b|@p z^B8F)O7itZL4rMX9PO(psiugwkVP2LbqH?=iA3h}8`~^nQufuGB^fIizojMSIY`oV z3iIES718w?AwO{xaDJ6EvKK8p)oh>n$R#rC(k$WjCW%=b+k88z)mXr8r>e^wil%mS zjzHV{o|U;spju$%d_aPWA=DgVMn@)!qa2D8q*}!ps*mt&tdk}xAN-LdAnS}c%0?Rs zE0QV0P`I1eqC9*e(ppAxsszlK*aEw9jz|PzzRASyH&SE1DTLAq52#L1V>D2uhfA7q z9uE)p#1^L;k!PgVuFo2`#_({c-^h(Ez9zPLD>?LF)r~6gm9kJ1i1o{vYP?QtQ4E(r zc-Ws&v+W67Hvjp$-*k_anm4eZ2p`hYk0H3i9iOrZd2UH*RY3|z?x zaFsorYTPRU<_DNyCG=cF#fSAN0`FL}4Y5+xF0`X}64ynMmGCTG8i82x6Vs8}aLj87 z|K;pqFI`{*299#DsG2w&Xpvy97s&?(q1|p5o8@LH`%@YI*8>=e)f)<9>sb&vSnem& zf8*a2@*rEo)P`pwXCcbsifA5}r#X&_807;`u!Evm$}Tyn`}?rIx8MF zajzLvDg3wT#Xp($%0_!{zm4|4y%t~juR=YVlMbVLZ~u+<6wDGMuY<3>obeI8lx7lQ zZDOLcb?j7gv_wu(%mBM^vQ+QwzY9OV)O>TWTKJ>!<0T4;^8|ZzRALXF*8gl!#<$YX zK}swe_Fw44RqhgV>A6y*YgwaGhbeHw(;8D5EgK&dgdmXHQtylX^l|a*HDz_3@5_<&|X8`e39*TPpG0R*Q z>)Tjdc~XZP5Lf@KN*BC{%5*C5lm#Cg5NGF zsl0}0UpRoxtKQfWfbfL&fHJs4Q&?CiK;^ecA#CwW5-BTZBC^w0xxRnokBE$6o%&-J z%RL8kJ?00v=p-5*^J5IuQzvVW=7UH8$8Ei6&D|cUpY{Z%i>eJG2TmPhOK4GGA^|56 zl;NdCJXou4igKI^hHc>)=(>o0e-Djy`eI93^d>N%Ds~!yE+{=qRtkIA*pfqW&XeXx z-?R)9dj6(5xWP=-9gbfV`hx9t?U(T<`eY2h3A)tJ*C%5 zIpzJ7)9k*(L`~1jJO2HN#2Go|8C)1h*aqTY#^Ri1b}0H^Tw1kSb|t6H<*@kOEmbS= zjXB(X*j$8Ptz~9qs$R*`)k^k5Ia2Spl4WlA;pePm7}@`ABgp=~wUAANF={rm$6{OM zhgMJ;-Lm|;uuIL3a`<0!M*)kM%0WLVRTK%f)+?;evuePFkE~jOm|8YgwkCAfSr+fV zKZT{J>W&i6ro5PWulB7J--l+6_Em^)jJZPlZV_Lv`7Q1H4sZCDtS>)fUZedBJe!(I z)>nUQ&cn~T7TiIHV0L|d-bZ!2{5eS4T^Oi$*2VleO=4S~tsn}&JozIGKwgF{JvBJ1 z*7MYL!qHcn^p3f5h$5N-!G9B2QVh$O}4O9mH}s**agNwoo6_IYCzb5eiIIk?&Lw|k_o zGmj+g5>H+%(_ZCgzzl9)uh}Bn-*6UDFqxg z4fYrg*(6$9rq^U<2$F?-Cs~1tbBPCX_^W2i=Kd+2R^WpKs_8LA_Z0NZx1b-AzP@3} z$Ka{vb^G9k4TT@Ma~Q4~Wl6g%q-FLFZXUdQ{k?)fy``88Dnc(H4wSx@Jt=qdd<(*< z(W@e=!9H{9sQ9Kglpzh2Wkrn|dvq+fhL(q?Dte`-a$l?ijs)P5C7a~_)^_9cR#vflMVFllIEHmE zvq-f*5926;N~@+06O3yqpmG6&%zVjakvKGRwIqbW72jPAt#X!#&O)>K z``7J^X9az|zmR?+Y_l87WV%K59Ll4d?$Y!1k(_|boudcOb-H_XBlXTzM)dVfsxZK? zT_-2(>R4Q_rV3wMb6}m#mBF**QA#@I;w)4|nbngyoe3SlSb!+QhYl&n*zl%$9O1}b z`3*J8?*lvJcXNyUUM?$(+)b`Ri6M049fpt#3hgt0K2G-bpoC5CX#^p0H<|^$l9Cdg z7Bgxu-*qO&QnXphCn;lJ-E{DoWfTzHF;9nK8GcTZ6h(a1^*-~0|4gw(BI1+n&H)r{ z&r-5Q`)IgMo^>F&J?Y?w*x8>^d)fg&Ta)KJ&+8Rl7jJ(^7#%T4|&dT}D5& zh3}B@vjFsfk5VmJiG&2ztS}=r)|eferFT%ck#JK890%kWt@VVI=vQ@3@JzBN^bA!N zS3UtxOTApP)X#UAcmARmq-!|mS|C|bua5!45=H8PF%Lnv375{~^LdTfv0552{|hyu zluof4QRk&?hHiwkX#54`23Kq2}q2 zK8~yC3%@MHNiN?oMcF_mh?7V=G@GgqM4Il#X`#u8%E5H-%oZP3(XDpe_6&DT&)WnBt4KHbFlEG<?0y_;0gW|YD%TaKd7JzX4k+=7 zCL01zTlCSYZ(_pLrWfJ*<(M zzg8pZ=RNU})Gph5*+Ckp^yD2;a!W%rZ7`PHVYGqN|;JU=Xj;7w{)-w`V-m**OsCa~Zp=$$G zTAy$nS%YgQI|I)ds=i1>TFQa3XeaV2Audt4~TY(5VCz`^=a3iGT*l9>flzW~7qB%!VDZTI|IcZ(d~KbQaUAMX(lFi&Fnr&rk+i zD<^|JPbY0+!DVGJ_B73PiK}TYeaA!;`i?yk%+{A5m87iicQ}-pl_tVWiLo$ zuac!|o1}t~mu*EPBr1tMtwU-7@Pi8lFi%J%P6K0Z9HL_Cu98+M@D(Ca=&x}T_p5~m2R_@ZoEn-p@zaAhn@R+p3!c@Q76VAT@y7OSBDAu6_6lE*en^4MlcX11+EF09J3&Pi3vt)tSZ zyM;cE6nKA1b|WP7nG+NFO*544a#n+{$cRXIOq7-UrB<~!HN|2DUdF=REPXK+XvwqB zP;{YEPQ?+j9BkKJ@k>%vicF-q6ph`dtAy)H^@5{a1;@I>q^XC7QGNjmZ^1Uj> zierivDQTHXx7?W5<+QG!Yj^^#U;_uWh*w2xyrfFb>fmdvx8aPP0msf1xnpYGSC=Q5 z{O7>6yqfX zy{uOlSNujEst5AGTAdt>WbZo5QUFGu&F9~TcJ_!gtNJAE8HUVPt}JB5g!glk{Zn#v znLi|#Ve@&&kB3I8aZ-R@Xwo8oSdBz?eFSZs-Tk;8Jtr2-U!Eu(PH#4T*@&|Mzu&@{m-G-;6%y47no=I^$Ul#;A~S zdHT*%*NziebSI=T7BOUgbI71POF9uzi|biowK=m8cQi%ccQx!@lo1Ugg!SI;48H4J z8C4hSv?h=or7(Msn=i4U(o50}NfwpkLWdHQwx+w{0 z{^MOe_Y0;N1yjJNY)n=DA`7B98Y0@M?n*2~K5;h91wJ{8kfS--2l5tMnw;O5nG1xp zy0xj5_~mfj9-g&f{u-xQ1YPE%G=Qo{8Kpv^7|F|*h23{6^jnNzpqKYa{I?hdlN;KT zxiWvgQIM%F$4AoF!vZ96W#04G=5P{JVbMtQ7WgA+icv7~>_mSgktXHt$JOOkci8#g z9Avh_n6fTj02NA{6mwk z_H}VM4j9`TOc$Ts4IOEY-JZYga-0Z8?SUth*1%Z{EbzkYWY-5z4_^JXSY~8KR)LN8 zBE+)ORw3Vfn6dJitUt0J!3Q5HpA7ZkUZRtHL#)4Z_{!fX{xbOclJ)l$D@@uM|4037 z{9PCUotxy@;`Q-{sW~z%w8S}k(r{E_#?HP-J|kv-Nj}47PnJ*W?2GvXCYE@%tV}8~ z-&(IS@LY>Ojb{cYu#pb7_d1%C)iw1SuWqmEoc9qVY2qTho*3b1csJRy!x2h<-2Z8c z4OR&jyb$|_X!6`r?=mMGx^<(yo`J;J8*ij=J-un()?ScfBgN{Yaq|kQ&xH+1FtU_a z+c^Ui_tASqdYB(WDh?4B+E0=BRY^tq##yMDP(r3hLOr;&BrT#=2V@tf1jQ~By+D}# zsVj3hZ+YsNED#xRC&NX0ucSo&Hx*@XEH)mHQV=@uTLdS?vD9?oW3ICjUTq~bR0_49 zQup=%%2TKA&!~vVrY;KLreUXiWU+J$W4KvDl}{Zs?gt&Q&5}g!akTIi1aUC%z#-h> z%?IT7fg|d-onN-c$N8{5&M#0MjIuob$SiKu-jTF6gUzC3G4EiY3OpvI8LBKpl?9hp zY>q*m!on1BdReoEg*W$A6&-^vBKbLv;1?Zwfy}%vPc3A41^=;4f^W6q3S6s|CFUdD zVsvWEX}m}i1tTFb(82w+L}(gWR>~P3N`G`(rFKqP9!GEIaorEPFbiK-s}m*E_!Rc` z@CZyd?38Zn#4ht2Tj8c2u$n#_&vJDtqx$v!czLQy?gI(Ka-G)e7e8y~mJN#NEgPffLZ)uvXNo&#T4j;F}% z7X@#pQIrbqiFLDMTr?Isdn_44itNU?bN@A?S+2+ zXQtN%W~=q>I%~Di{0=gj#l~fiF?TF99mAL2o|WHbw{7h2XIyE&Hm<}2f)2yI)w|D+ zSfa-&kHF->Zuo*3aKrVf!R=w~{ z%cKFKfWlrLj|n(Z->l_oC*yF7}iBVg`pm|*#3n4O! z;`44%n1wjrk$vlp$deYjj1uOAah2O}ZZy(=1TbNCm1)b2t8RBR3wL3OX4H6{xAl*I z_`@I6cr{9$v4W|N7rS3S`1YKcrBgk3FK6ktOx&Dp;bz6_AB%awyl48Gve^+Xba1c`SdZcUaA6CZ*OBr4V3YG6Sp;&7z#(s5 z3-;9(O;^B&G3&C(>DXmmQt3Ws-Qzj<_Q2aak+DnQ%xvEBde7Yt<0y_4K4V_4m|#y` zjSgrIm)DyUHDi1`Z&ZiNu?lliTZKWVPHfJ^%$KIC#rsmVE9r?$50MwM6GnOpdq)b5 zO;1^DeuaZe=vEemn7>jP0{bI*&tNOZe2HUSsr$OWh*>3TKPg9g@tZ$s$Mm`($dH>? zHp`y}9%Xae)vn_O`{Hlc{<8!l{rXq2GGC;7|AP|$dH3FGrTYBtEwEA@qJLTW26Qgw zLk=aZRQ|6zb-M1<0G;{=x&Z~O_2xnqCg*x5!+}rf)HBmmr+WL@ zqm)hOXLWDvgzDZeOZPUatcmVDi7J#pvF35QSM{#aOilFewAZZOLl;$g@4~p>>fLkV zeyewrwBOo!_A%wEf9HSR{6)+Cv*zz#j_MBHny^sCjC;ns&kEkrf5Pq!7d8)=umP3I zny?4-gng0O8ZJDDBm^*O>Xa!L;dG#r!`-XNW9F~$?=ixQt3_k(QlU#k6QO;_0u20Q<^^!W87|H_1Y#!B`16ZWSn z)&Dp2_;(ChVbE)IhbwygA1Vx!cb`Fz=W-GaW?djuwt1>ChVR^ z&+g!M-C;F{2h!u)Eq|OI-w^j(GuEg5XLT_BEBja2@Yp)p_b+l~HFz6WiZ;61 zSJ@a?C$UQmPk6Npi9*(ilA>a>>RTMSc_Pm7va)hcF#&_d-aY8S6+npnLyL=ItJ5$g zgx&caoH_lRf;JS4V8gP$AYIcfcEyMuln^zs4NdHiX%E~4EC)B$Z5gXlm9htCG7DYB zrQU{Bw?<}Nb?`lJlc%7kR(5Y0tCF9S&!<^4&g^SLAeAkTR2IC_=FiODHFKeJ)w$Z< zM~dNGA9fnK-i6K;@AHyIN2}_V@c8gr*K$iEtsj{G1itdf*sBgky^WrNmRhmx2z*b= zr`Y@{hGs~#4+-mM3pY zv(KE7(EU;7OEZkPo?_g4KqOVW{KK>fUS=6w3>LkV!eG1*7e*6iKC8|bFV;$%vFSbL zZ7rZksc^>pJx+5u-U7D1UX^ggD{K*$Q@|M6nu(CWy5Z&HUZ-5AW27`%y8iw3Ku1vu zqtDU<-S&IVYE2+4F`rlbjaGq=-yYe!x2d*8_&NS5j5O;0_STB>kqQY6Yf>IbBIBZ| zRx};sKB2d4wzBI*o5a|IahsCJ^bNsdsln5!tEV8}%DJ+@mOaOnJ;#}y6iMIUeR<`1 z*>lD|i2#P+k=b+7v*)B`&v8c&^KgfsedOTm22}i(@zTh3{$$VJ@D9!3AE%Se4IM+W z8|0Ig-OwH29a^y--#NR$_BMZS@pqg*C-vF+JA?Zp{(vvoIacoKTG!7RD|7~z%X(~F zG35lqdL~ugNEIpaU9F2hgSxS*v^pgYu=v4}d{ku2|N6Kmb zt2otpLy^y{Jc-h6^>L0Hu!V9Xg~<@D?&ZXsO4X%JPaPK&{vYPv1wN|kTKu2MOp*ac zW{?4cMnyrz23r(V=zu0bCXq*+B$ChyL0g+rF1;6%89*zTIC*e5JwWTD_N8rad#`Q1 zl~x6`78BrR04)#kr&z6})}K8XT7z;SXv+M)Ywt6Y31I8J|7Sj*%-Lt3{eG;yUVAMR zqYPz68U~qGi3T(oO3T@Tt(L^$xDR4Sn$+?asYxZC_HOwE9I?p3KkeeB1n=7~!8a-= zx&!T95)}RP<<}%WDh#x3l5EtXE-2xUfmi1b269HlTBZLJjMXQWTPFZziL zGon;1s+|A7)bh_<*b1p#odLI6-XP+Uqe?t7O=B$QBQS&zsWbh}0Ehv~$HK$Q(!fQE z#(uobmoAa@)qG^mY5YVVYDTcJw3x@zA|K0oN}>iZ3!}F6+cdLKRFW~cHR~mZLp3)@ zOR#(*1=TJo=1$A%^b$d%S1l11ubx)KZ+co0zv*d3{HEpwYwg5V1hh^3&%qkeT?W`qPQhFmK0ZV58SzL{6(>?-MWM5wWWn}sdT5= zdQ9?pc3t*2DybKr4VqD@GMo95-K7gCWD+mc>UHA?p66)VEpj-ZH|=)@KwaZ@SZcyi zo8Wv%kGy^^302zN3RCbYq@MZUe1Kd}ob)zwnsWB~T$OAwtXTd_uY=xr)fWzwXzIGuMElDNI6bgwj2nwXIqtN)SrzRt#`NJqlcatT z*qN^-PWe_S?E-0mZ~zm3^}+pi88ap%^1Xz7(wclv7alj*=Hb~W-#kWpU<=#t$mj!I z9#+fVZ_V~cjTv5huM^#zjxMOFHD(0ue5RA3{Hk2wnLXC*D$3`kJo`c-=UcN2?2&&? zrKXYS;uSX)UEg`ggZb@ZG+r9L%dawDkp%N>m3S1*8Y1W8FLe`VtHV-WBH>1F!+|uR zEnPaj=XyTqKhoeLcYHqAVG|#E8?Zv5t71Z%$6n%-wz9PYXI>ek~cuRqy`L8aP0jsrUk~?ZKy^6k5W%7IM}3JSa;nRy`@jsMHRcW0>}4L@ zHu~*v5gMz|<-0{&NRzmsGzUX?Mmgrx!L1!D+gXTarPsXL++DOM+!?SMX^`d5 z3-1mjaQ2ciQ{sCgsC;i^wNXBcS{&ENU$B426?<6=lO-#J@^{8a&2b|=7unEBt-nf9 z-*DjDf4r;ab*>pH8!6ma(%Q5mhvY39&^G5r;Do_S9a0 zp|>NFDDh`JmG-Dm(yuK6$?Za6mCEqcqLFQXLA58)GKz_&Q^N6}FWwCo^7b5g)Vpk<3wgjkw8WIyYa@grj8dU?R`ITw~TOO6%4XlC3W$A)%*JG$wJCOiv{6^^IsLtq?@5WA3A>u=t;c^JiBqC>X zEC@RbTPsU)cAq0;h*#)?<|BT_ykCCG+;drZt`Dq1bg4nk3NeqX4B0CEM=aAu9lN$m zMs$lr>Kxq)RiZ+B>g=jr>ey#J58P1HN70Io0x{z%wOAzv2Mncgzyff<4{e15iabfR zZ=gPp!lRCo?Q*4dbiM#gqK1=o3;I!tu&Fxu!mrlrdYqOoP zpcvJpw>9?aYPI$;fOKoWzX|47HK*UdQBr#;(I)5}8dUiZ5)B5|iPw^mpaj4IlM!6- zIF4145$q@d8Z_W_+!8su^&}jRv5&}95>L>nVE2OzB_qZ9wOl`D%EO*Q>*1_N=45eL zE5w%!9M*ovO2eI0%d%6gG%8{PQvHQ_m9p;1O0}+qT8wo~{0>OlsnfQt;e%3^(|(xN z30!F1Wvu%;&)B8igPq9``beeCI2?FLQZ&5Fk~TY&Z4dAwf62D*=>Q=?`Q$Iz_G29& z^aCd(F2{!P6Casudrac0%3R%pb-1SzZ$;}i63N+h`K6pq%wiTJ$JtgjhNWj=YF ziGQ$JO|`C=Te##ybWXhrbyV+vYB(QXY~f;8lb})<6q|$nuYp?_T$3cYFS=J}%4*o9TCHFyHs2-&v9J zzSVgbdm9&|`IpOlOSm(%n2X+`>E6X09XSoY!ylv7i4S`lYZKc%TjYjGEqGbtt-hip zl;?2pO3QnmXD{2$?9?z{@hoqlI&-JY`!38d;g!yf^Ei;pMP{ek*b{eULSlL;ga0+% zHP#ioaEVtL--Mn5vZqFrGcTendg7o^Pio&NGD^;$#l@gl_K&hogd1=hmp7jkIC-v* zy;xFdRru_q+!nRg?O;aiO&=*Cdh2lq_;SEMXTFx@7kYA(LbNuSo76NzeaPS$WH&%vTQHCNtb^h*#0!kus?t{`*S@f)U zt6l;ZDTD8*W^RvN1b{gor7XU4d)v*S0X#E7Qc;>X#VNqWU1?2!>uya$q$fKw5DD}M zoKZM5>z-E_Qo8GE1Q_3HrLg=Y_PB&C1BQ9f99B*1de|#+Z)UqrAomBT-qC1Ik0AMl zenLS%X+=dpMVl&@!7tiYJYW=rjnZd@T;|nPsgbI5ZW2JHqBGsc9h?E(UnCwX)kkNZ z(vM7gXRyeGdKaOPd9@X~AYEuylOLr|r!|@U(p(N%*5vI@9s&d%q^pCq311$0A9!md&RWF9Aj8Q_(Z*}diaBtzz27h^qN&Jy%X<@wx#7tC z#_a@YzFi!(jgTCSnk}NO_M}NFcNPLftykfE zy&_D8SsC;E`>oHrCzstGzm_tu78`JTxj-F&+e|YqQM|GmdqWxYbSYcXUvz!Hu@Qla z#4@3JMV-NK6M3anw#<`wr;iH*$`Y8IKPV(rbKj(A#c-6Wi@LZ>=ACGiaJe;p7rlL5 z3t|1GfTmQrXU~a!V|F4>4ex2xcE6+qtAw+3!EgG{J*2c?mTYuspD9K&HoS9=jS-r)%uL%2N&1m*vteXN5`Y$cN+Llug|A{eYW+v z{+Y9{PdNWvhF282XfbqLnVq?;73UOt>3(R34Co==D&OAT;f(g{rR+}e3$?<7V z2Gi7Mq~4uCbbVMx^(hqlls+SCs_nmFT7|LDhfwTXeKwlsv3pWg49MTxv_E+EY=d(5 zmVtx9{KBth`y}q2@5(lwZGVAvhMg{C7RpdH3%e-4x<(1d(6t%=2UK}X4ngD{%o@4`2}h`FFU zRrdRs;~~|>oQ~Gb%jCvj#|zk0j~{FJZk@)IR6FXxlvE%yABiJlo9?GLAass>8yjBo0V_vU$(^6|{PFLv=H%>@ay?x?1GTnn# zBn75+yxfC-hSDCKp@a_x+ApRY;Jr1Sv*?&vtQfur8!r}4=?hsprbwG}&EjJ5;klCJ zqLtJYcmiz?am+PqX%WvO;iHWnBYvdmZito39h4Y!#Lh;D11$#`B;8IaXVx89{|h|v zf!Ka3KUo;UX=$+G+qV$Pdrad_dO#w)`gHHOxT`^~po zFoY5QsNwx?FC&Ica2ws#8BME(FwC#X`LVFinq6$)APwndAZIKs_jH{&9`40v+nO0) zSX2kjpc(Y)5Y|DNLACyb9%)*AyDRoj!evY?Uuu7ed)O?Fe9#KgZZ|9L5EJZ?rCdj5 zV#GGm^2M;iSTDb48p4sK@&6^9^J4fmw+n^l5VVTUb-Xs)muQ|doc5WFh-;(!WALp3 z`qTfvR-R6epVB8V>!O&HqliErejb;BJ;xQ|IX7L;|w1G@jM$o*;Ym1d& znq{~ZS*?MkUN4&EHiP5AvDGXF!SHhKOXieA@fowc$>}~pid2m+NreiVk~k zgvP6JvA3cStuqET_1HbINwV^OCX?4GdmrCLIMKmchCT>nzGqgSBM%upU_F?fLhtq3Wke?^?J z8Wum183MI3L9Q1XSuLx=9y{<=+y%d9A$C|CdU1BY{}c_??IbP4HiNNs|KPyFVTgN zN1RZ=bHCM)qwNvYqiwLwY5FUsnxg$5 znt2HHFR7%bx7??h40`jthgQmAOxnVSlDV`hb2em~2HZXI_5CMVGY@QsA*LtC$g`i7 zOzL8wF63vtXHD1P$MtkNTnRN_XW<7ZmeWz(EC1b<*niUZ^8>Q@OlRW<)Y^w-+rrny zsDwjthQ~n1k3@3lE!rRkvEPAP~Dk z2r++}5W5C?Rna-fRr9tmk1jyC;)yz}Q3XXPPZa|MQ7l^C#oh}HIiVwtBC4-gW|!^E zLN)q0(bWlll{&%EBN1FB+zU&pE@K)5#N~__!D0rc()$TTqAkz=aM>>fOo>L(m9^%F zo$op833t`+dObT6UEZ&Bx@n~7hvuO}`kp)f_9D#coFCXU)LQQL>>T*{U3dJQuXLPK zKMGcHUGAa}{&}jSQ7Zk*(x3S%W{cQ|pIkpsZq{+F&WD0>u`$CJF7q~y=jx)qYdGrk zyN1q{#dyF;+wihNOK=9I&(H0`pMdpm)=*l+7oHw%JlC8xCo#3z6+g#`Y51#X0;}{0 z9%#Rs9Rv=~3nRl7D{0!=!P6DLL=0yjxBws==Ux>jn!~yAxw_gUiQ8-wmdB#HRFh;? zMG8)#3~ubL793-%Bv{kd!LL<^5?l4+!sW$S%N2>r^{9@a`HXAHkr>5QNh+ZEC)%R- z%=hzzv!Fb)#79UYjskfBVQKCUTqjlit&5C}!gJAYr?(~3zs-^c4~DGk3lS8UdunYL zkb-(v0dOeW*#{4 zyIN4y{RUR=cg2QSC8_YXKtl9mSkpdpHz@E&AN(BrX>RY7ifEwie7U72>KZvn zPski`*<)2-rP`!F5_5ElrWoST6#iz={JsBxuGkq!*iw?BD{Acpl)@Mfb*Kt&Gt?oZ zKFnOGi$9>iAk@WXG61H8xOtr5#}XKw+ApaPCLu(9*nXYmhA3J)c)D^&IQ{rV^&S2rx zCsy_O0!IuYvTVEk%jmoIv}#ERt~BM&cWXkjDMI6xBUkW^Nv! zQ@?uUOQu56cNEZ1h3V>kOes|74YYoT5QG!d#~Sf}fuo?uX`S!9WF$)3Qqn2_Jn=c)4Isl z=@+T7A5Av&UqpN)CK=Klm5VHVnNr#pXnle(37yfX^x&EP7&a%-&vYhX9SQWPv=G84 zgjF$z(Xb1ZLRzc=8lLwIcJ)7Z8(F3Fqb0 zQht0~)9Tx>%aK|x)Ak5h5ntev_bxIb3-lr2+-S}axLMc%_dQB{LTYAKw48SWoodaY z0GjKBm(Yn{ol3jv`W~Vl*1Q1)SRatYnW0>z3xQFfmIvM;Er13TDh-;!T$xXo(AQ zj!x+ez4jJ$&A83wv`Yg}#z#upXEY1kNxbWh4d*9bkZgX8cTr#xUWa}Y6()JhhQH)@ z@R?e>39H4lcz(4U>FP||^BWbTd0ak+mGn7GhTaN@nE>fAUY$LK3KJcocu88pLh+NF zAUq`<5;5Uf>KB8G+iI+3Il_O>mX~%RErpRjNIHhEA0_jb{?7GtA4;%QtFQv6l`Ctz z2RYm-6A$krA>FLCu5qbU^T`y#(iLM!QytiRgSFl@HWPVe^R_`G>3nOj=T$N^f7nDV zjh^D}%YdrD?S}Jty4frW8M(B^L?Bgt>2Z; zsBc(hDP)P46VVWa;N8Xto#UCB0hBOPV4i3 z>p1BRYz3Ff;bymuk4BrtDJHa#F5!&jzRQO}%%8C(=C~9|D&# zqW=XdD}(vxydk}^r#@x1%fLmZ;b_>kqA=1uibU>6roU@3P1SqgJ2q=}0JeL>-9^B|_8K0LR#;f`lgoYM7EIHKqJSJaU3hI%?RT^zZc zBdHe#>&@NuornERyAG}K7l;Y2yr#EqZ2IGkO}m|*i!X;V*er>p zC(-4hlY!0W9)5%+O^Jb)Khd3MTyJD53*0Am4}P8cgTyT4Vq9p&*Qoxh*6hD=k0W7` zk43==PZJ|OMY znf}9a>7ckQLWq4X*3hS0vrkFY=StO?e$$&XyPht#zw6TPE)RktHN1(F_pUgfBDr&h zbGYEzLDI^FA*wsPmdY6VV=IdJes%bxK;lQlNGFpyGPb)qBrPj)P=AkrH{@ndcHc~p zgc6)d{_BBk_0rb<37YGxHwW>a*Pnf0X{xBqEHPuHz8qFi>j6~DZ(3B>`ueT007hV9 zgVWm&Qb<|y_m^}ZQtMA3FEm_9y6s<>1sZwa4E>w1$ zv^BdtLA|W4%V20KpAE^p$Ku8Kgu|f@QSyh+n@p%66Y@%^Nxu3raig73S6fg*$+k(E zPg66Y$(hht38^B+W#WpRP*>Xo2_@TZ$$VOn3C+)hW=cpEQJ#sr%?Wk2MJ1GMyCd^y zaXQ4t=ZjWhbrf3dCofP`Q@*^j!k6U2d49A%E?1THV@eS86;;}{$DGm@CEGSSLHHj? z)|2^a4BI6E(}Ut>?sqk=*`CgyR3D`k=6cBtLY8N`E5XFz>Pv&|v7`9bzKkH~mXy)? z9Ze~=%aG`(U!>pwcCmN-1fv_?_mY+dDc>~vj_yg5f)D{ndf8X zQ7I|sp)#gMJjLmPt82UEbFxUieB80E*mb^DKBxPAh5Xq>2Mu<=xK!E6F%~J&9j;@zN75(_s^;p#@ zQ0E#Y?4IPqWUSX*-$Zutnpt{8Vd4i4aaS^Kd-geRElHgna(bm#qeLLPmo-Quu4?(g7)2)q7|j;Ru&2&pdo?|yjAYm z#+gBN`(VM%4Hj*)rhWr1?#|Q)KQ~t)JPgt_S}OVt=C@jC`IuLb^2Tn?suO(cz#-)H zs#B^)b zb;isg&Mg#YlF}5%0Chg$PXcR)+bzfl7e7UBxU%*GwJKH9vQ?qgil5j4Loo{gvc4Ss z&ZD(h8R|3JmQsjZ7S6HO$xck|gOxB?P;*W0sGDF@z6Eir&2wKah zOxD=grmc@tj4S>bA$;}_ZEq=519TC&jH4tj~(L!8+ri&(FO zb+RDYhX>wr$~0Z59Y-dZZ30)y9S)FZ+W}iqO`8M&S&_+6`-;)(IOwi6B9kY+PQt0w z>5_zO^c$?z#aZ@}RDIUHIl9VXZ@uY#ct*WZcFI)Mn~}-o2i}sb;e!n?2^+p`EY(D< zbzUC!SCMrwUV@F&H3kODu$iIbA|yP6B_P#QJH|rYd`~pBNtU3f)7szoj2advuMA68 zUQ+>CF@@|)RgUIr>z^oUFN-GI@+_pAz@NoUP0Sm|;O`rdTd{pOj#po|BxK*H;V`w2 zOBO3w=G-_jmT?J1!M|b!|C0J3I?EZX;F0)VkB2a7sW0PJ8y8Jk=#R}?5?p+ru2~j? zeRvVIgQM*TfiDDFzd?tlEDQ!(-X*ZOiLsIN&8np;{WCf}wpn!!*xh;Eb&DyHo9pmD z0WN;R(IvTyc{I^MVU#eD2&;s=*+-jt25>Y2aKCh z?w8#v8qfC4)vD06ef$3N#t}v)!P#)eiFTOkV&c$8Nttu{n&`-tiB5`i=rUr8lAK(fBS@%YVA|NMq({~wBa!al)k}i*gm0@oCyQ}lzZS6` z>XI&XfE9b?0K0YJb~4NHrKqib9E$ZIhm$&j8b?vo2%2tRt}V9@QZPtJdQoCkj0J%= ziB(t(FEtH0`Q?V+R5>26h4U&5nH<3oRX?V>2FZ<F+(yR`0ghIiLG-q(>V_%}9H!+#9O4vT3;6AYY4|*9V zMH*v@snKtKWL|{DN@OR245;5O=SQFAZzfryOJ)3>96<|FEyF@lFzkyJXj>VME2WGI zSP*9lA&ct=wKAFFF42CmKmbPU4avP%RL_Zf_&f&?90!Qx$)dhw}O1>1LkJ7wov7_v4C)P;DM?YZbcw-E=qh+U*8<`Jes?_hyV+GzirL=o5_-6keu z&?l!Hj(Q_cvrN5crK$5R?btcJJ~-FnUp3ds5>Ws z?!1HKhktUpgORHBin>2*RUs#!M91vi>odnKGD9_9URTre>Re@-rL)*aEgRnr9b9%D z=CncOXxg>hf2>YzmJXPE@jhl>e6dVzY$WXQ>eM{MU5X@&i0_J5yYrm|BcsO069=m= zI6elg)N(2Z0|UuT*PMkuytoA@n>9QyZgCg8nzj{WPz?IhKVBL>xRL*%tVKi44j*b1 z9%Fy=fi!fQfkxgf??ZKPVvD z83|TJg=|O}EES^>?IU(>rmfPiEth>`!fF0(*wtd894u(dAeW~JTtCR^8U$nYrIz{a zAD%C>37h^dJOqd_WOX+>hr7~37Npm8ddwJ)vy~^n&yNfwU21B?6?y@j7Qce%kzoUXR;Su}c{`XW(MnaAUH~9;yc~Jr6+zrw24y+UvIF$&INX z_%XIkgX*?RDk{c+0#o8aM~Rxg`qjXmao?nW{x2j8?n-3pqV}%_)-9P&yQ`w+L#?NN zo;2-|1;Ehj6~F`>SRi;tf!qhiO9;30D!r{Q-5aGxaJXR3bpEhZ90AaVK)Td>zrJ z7DephbPCKq_NKFCaCE;*>{GB%<%k?u`-?B)zg)nEMlE_=IOgN6AG}F_wu~mqzJF-8 ze^Z%55mPy&<`j0lP@h-xbfPXN7;e+l=lO)qNA$Ye4pKH)-sfSw$78h0C3_PV)`9{r zCW?4L1CrnH{=oV_F##gX6hc;G+s>I=Yu%mdYW;|0E>Eu$ci=r!WxolJ017h)Bkv6) ze#abER#}!!Vx7f}(=lGuk=S}f%HYp_0C~6c;2P?}RGq{{t5I3wyWKk+3RIP;>M|dc zW(pB{XqH0!dmATo=Q|RG)TCbnbh?m1oROm}lfL0K&PJr&e=`(FGuO$`n7@&s_+bk! zy3OxPy^N6I4g7Eo5ys4b7u`N0jn+f*tH!81Pv)JY%I}dUj$c$6*gX{w^}6))oL*7?;T2i>i3zrG*iQ%n6B!!QAv>7#SL_lBRSo)F^UKrl2@PK)md6k&jlZxGB<}2|Z zVvG=j)oSCZCdta(;Cgw>crhoI_~M{ZW3`n$jOL z(ZsKjxP#x8U9pkL#9~6S$dZYr68atnCqhmv?>e^Q5o|oZL{9|9O{obj4`3-BwLh+D zP7l|F8jd=Bwai<{V@5(Cm3}+t=Sp^qcpg_#!c$gE6Z~~ODXHuafd5MWz(@>izubgR zNW<4g{A&`@cBv{Mox;fGuYbT3k^UaL0SQ%ESiNI0;-zF}=%(C1S<+mn^`*>7QiA%Z z+N(Y`*r0a(!!BAOTE~rRkqWvflVqm3k-Yu9<3)AJgv7|7V|h;C3F-mQ3?{0kc54sI z#G2vlO2}ZlqsbZL<;=H!Ez9PEpQN5tsIu~6Z+}4Z0e|AjhE4d{q)RBF?rSgz_BldC zov%nZyGWV2QJW&XHaQp>!V%Ysb)g=R6FjUG4~YJX+e8-InpL1$7VxvE;y6yia`spw0( zA{0r=k{(r=_IevW)$qTZQBSu})h7m%?+n#iD=+3y>3lDDqdRn~de&~sFnwQ?%?g8*&HDTgWV2R# z@kf;x&X12yba@f?BF1TaFV#4~d_iwwmwh+KB&W&tXXFgndOms09K2nROWh)9qIW+P+CO**2_Is@&)00ilz8*2niO zvsAa;o8dIX>djZp7hp18l5I~Z=_YH6HSer zey=g}fR_`<+wd-iu3Z$(YCglzwPOmzfr3J0)Ta0SNV822?W{Dh3QIsC3wV&FAO z3~RQHR`IfOWUZ!mlbXLRQo23HolS2sr>sARv?}G0N(=U;2)*K007YYvh9$H^VyX{1 zOIb7y385DAdC_84QZ#2>mKzTWz2VtrJT2smh&}=>vN{w;?RL}qNSb7D7)C~GFX@CM zXv21w)Q?FI)mKe9)X2x5E1Jgd&=Ooug4B3ga@w6pCjew|Q&pIn^aygb?<1zlY8H1~ zNI4@-C>;#kr!0ml2L$`>(ZtfXmN7i46Wl@}wRw(^Oosm&^BhGg&AA4PAd57S@%6jY zM25aLttgKbnMg_0G4Z#H>^m}10?%Z_V-HcmZ$=#m38Pe|Q^XRWN4PNsV&|ZLnFz97aPQk}x$=xJhY<57jGydE#4^m8V5OeysPCD7I|u#w`Z5(Y(q6;2(# zXBUR5yV~W{b=%+7c*s!QDml$xa$AZ3~@1jp=#GtNz1i) zU%4lT+&!Lyq!ksiDgG_7cZ78`w@*Zq@t{3S*E7Pskn--`lt$o${s^&4ommOF`a>Gt za$j0jjBL;}0SNcL_4SosaA?0!7VQT=zs5d~RiD}{BVx@(qZ=#77zA<_oIRMipu3mB z{KqIuuW)Lk_6Wrsi*svM7`fmA+wM}kA=50sOINmhI7QlB+$GBTlolkTlvtgPqSg@Q zVeg&w!Vuw*`dF<<(||?Ymz#AWA-LF#fSw!#J<;=*!B245_yO4v?ThuER~_@D2Ke0| zEpOZK56f78Eb-H1miRE^$xI=(8FAvuRmyfF! z+p#5D5Q1z*?g4kG>TV80!#ZEv9b>Mq_(=n-XlNUnyD4#%K z!OFN3`!)q{V0cV_uQ{{V%N+`1?%Sv|7^SudP+NY@cSeZOK<+1lut6fUCxrutps9rw zHWb#6_r7pxPoRAktBkosRsL=JCve3Dgp2l$lf|5ec~bkpA+#hETT7jE;zj19ixZA3 z@7QxF_8b0Q1Lp|c7o~H>dk-9{5FHAg{z#R*OVZCn&dWufWBti0L|=p$8Wg&Zfhj|o z?Y;}CA0eJQekIERYYU?={H|DFhWDhy`8w$(Wy7Bt^gwI7+#e|o7R?i{Z$ZbVLN>ZA z_z(6ls!jAweWJipn>d+XZ}Wo)ht-l3j94wXn_Q=@w?2}X^VE_%;b^Rw=Ozxb-oD4+ zCJwt}A$|g_zv2xO!q%iW5C6M-;2K-80A`IdB&-FBH; zXn*F-9IVgac|D3xNNG&$q{!hH27Gh4kL*MXljWty0_{^}6;@eu->@%H-lhUAe_>K_ zLW1p$i_KMV2F!(r0-N(n4DbE)Ah3CV_|=B@%vDEBnHjEzyuocLR)<-7$e4N3+g;b+ z5QR24W{*{S*w!~nksa9UNNar0eR~Ef9L>09q;zv|VDokwm&S=a$piKjZdR4{G`ym# z#P-J#S*R}yA}$LRhf96t=Vh&4sQ6v8K5o6nj|Ez9cGhd$&jq-&X!FeV3UK7`Fx%I# zVAeh==e+qXIlKBPgJk87K$7)PM@2f`i|8x7D zJ87F%*4yDtOi9IZ?f())SE)bJEUc5myDj(9rr#q5?Z;2EQ5J`OQt(i%uh%qxtvX~j zdSSy3H)jMb@5aPr%$G#aa)uTAfgxYw zjUX{8-k=*p8`Ta1p0sX@`%3oXPI+|0vCemLv48Y6KhSe6c@w8R0nzWlb%U>bOqZY+ zxgZ+xJFObG9F@H+yeTt6jpNK2A*wEhE7@l+Rh7qP=!&`BHn8rrCu&PEdY;4)wYi%n zB``yeHGLh48T!MyG<8uOJRg~*?W9l{7oFx(9SN=ap22=KJ&j~sp?F8b4{cxp2NE50 zV(lZc_%Te8#Rc0JNc@CP+|LL?Cr>y{3IlAhW~Dqk!|SA!#>RnpcQ$^(d_*C`O0z5Q z#P6(my;UN7PnHT%?($$I&{rKzRU&9zdEJzSz45DsK8P1-RWNIwuL>J|A(JbNNL+y9Q$XCLT$* zL{sw$*`_u9`2K8Qu0r|4zmlByaB0_X?ylBpN0atbDuFH*6hGXv>i3}e+(nUOP@tJo zvs_OUG8$^ts*PKi8IZy(?wh8^ebi~+e>8W1%}~)H61#h1T2hn7n&i!B@Nv-^`2J{P*KlIT->)(P%SnlH4%LD zi+@237*-t+*rO=h@Bfn~chM^n%9h#2D8ZUcq%3RTf$(=7j!K~QH_T#c(;C|0Xe<_C zAVuyB>kE_|cr|J6ORiZlPBcm?CSlQ-41p(9`%{~QUxCCLn+f)B0SW?m8F@MmPAyz= zc+3KY9W%F1_`g$XJ7Bc?u}qnQr)SEnHtWVEK61wj6CdTo&da0`!dUr*)}qJ=B2G2f z9r@xBxWgliN;{|4^0o>T^K^du#7R*mGkfE=n|lLK@b!kv_3}@Vm6BTRjZe!PO-r0FT zf)zSLCf(CMtySBdMto5U&T7Ep(tw*X_1~gzo?diD-EZP{SNv`3aY)x8(~?z3Kfpt? zC0SA^V>0E=(DG3m%*voy0%!JDk?@@&Inv^}y6wiD+H5}SNj3pyLs_4U6SMwz?d3~) z^Qcl8(Qqqj22)iKU@J8~36mUo$sT(WDgu;saazxnGh0En0wfw8b%Aepno5;Qi7&DR zCh7A|x%{bB+oQt#wSXdXF9b*r643*HwagzAfoqT28qD_D4@1?mXqka5ouCY;6r-+C z;Yv?D(RRgUGt?xdA-3N&CEgcLWWy|>+v%@D9v$s3Q*jmg@h4!E`2x0tpT<7I zfPJYJnUpR2()5^nlm;QH%oC4`(4V0{FOe*UKAr7F`8lw;_LLEVJKxjr*K`ZC*htv3 z=IWsNs#2m9TK)_rN~K2=-oG51+1&T?L2<=qln@B-ZulVaVX844UhQqX&RXrq#iO}5 z0=>e8Q5rBt{62a$H8}HVuR{(V?ZXn|qVl@!Vr+Z6660jCPt)*CYYK`o>EuMOCtjo9 zg-e&{4BE+`ixZL96t z?Ve+5$teMVw@FDTtw~8$MOq*UpqI~?$}~Jr9MSsM=9>VuS{&D|>y|23F&6^exv~+m zFT*Yd*3LeJZLnQF!dL-+gm)GauUDKFBr5v0K*-kD@7>R5m}tdbT!mYqEx0Ly_h3FR zUpJ&m{fM+m%w+gf7DLTDWpM0&Gnyc$fhSHTOR@M1i+YX1b85^c88>TQkHTh5yx?dY zX4;fhJ@LtjRmYTBgg$Ft2)wDPS|_hnitX&jl_RGt48@0eJ~BY6j;|h0zYZ?|JZ1K$ zD5{e^uF{jftPNyeNi64tkOswj4 z#ji@I=@VT#tYB)~P5OMU?DcH>%Uab#fcU`fNqcCFm=U~p#b9%orvr*6T5 zmI}7josiLbkdR=|#%~T0g1P?rz~)F!qIXd2qGyJ)km|#oG45JzzZ-%aznUvOi9h!p zej0dIV?RM0=+h2w=bv+Q_C%_8#ksZEqM|6fN2qa(d!gUGH+A?vmE|5?Ot=?+a72Dp zMShvcDAs3Ff5~-8<@oDk*Mj-va20_&pDn^FOq?7PXxUHWn@>u!Vxy|<_i&3S1UBVZvqx4IS7H87T2}=IVLm8dOx*WL6tV@w zdYeGezGq~V4D~heK&N57A<5LY#E0%!apE@;;fh~xCgC^QBcoQyNY8eRUaMj{Mpk*c zqPDcv$+IJ2L`%PfugQTu4ZU200G*#61C{Ee&i9~b3I*5QN7_w{6^;16(15- zQ@h;50AbRYxc;R9F{@l`y!8b|Sf6HK>$%$$f|~{KS^uZoS7g0&k_$`TFLy#86jNoN z#4a&)4TAvroI7K~w*+kkROLWztNM+Kn|Rh$+ikApy?`MW%Xr$@St^(|#1^}{nrkM? zNSacgPCGS1MI2oC^XE=K7jX z<{&Uwu_ShT^GQF8bH%jSq~?g-}PnWsA$o~@lEBL>Y|CRjT%YPgH5AbjC ze;K3jU7kPU|EK(K=6@sq53^HJ_xL}~|KIrc z@@)wJ=kh z*UATcibU;)*s35&i5-PVi|cN+CoM?tJ+&VFO8z(Tb?(C=rw{i85)ZKkG{0@GXrT`Q z)iPa0+dVII=Wh`?)WluU${Kqwl0QHsIB1~d+vLFLi07a>?UI~1!{8opnoN_07%QA6 z%Plrynk>H9&}rl3kw5K99`Q@`8TCh?l0Kun9FhLOChzs&cDR1<^mvD@Nr2zyj1cXt24?J|1=bFa1kAYn`p><9)n-Ext)XyV|VbG z9?SXjAQ%XpIXr^u0-I(JwyFn1Uqf{q5AO`D{~F^^b@(W&LRT}m)^5qFX61lt2CHfY zi$eMg^69GPk;MAiU{7yg)AbP8VidHhTj;$|m-hyjGGozvN?@=Sk_TG$(Z%M!dg*TB zlt0ieJ||LRpZ5NmU7~t=ZmsRkYQld#n%&cLRTIvYCUlTdH{m6cBs$KO-u}Cuy2Dpw z_vlwrgfnpeHlVny9&yppDK4Rk8!W}mcZxeGOLEifp;k41Ph{}km%+P%ELDe}(%Z=DLAaQC)p#2;o>FZ?rN@+f;TEy?<=#?BjEb^-EujoPW)!Kbn z07i(%c|`w2$~02g+=>3^VkSJ9J&V9 zw~{Mre@!KWjpd0t`_Bt%k|t`wu(xk$c&5xH|DF5?pDlCUv1Q=@0p~saZ9Hk}oOCOk zu)L?^B>#5jdpbS~J}G^Z0?hgdAL)CaQlzQJz?vg$B)MzuL5;hQu}Vy3dNpeq0wiSl z!vazU0F>2efG`HA^D3B9LIxBbfG&^9bb-kNVWJoWv7c6u=aa*hR?>eAY?-pkzj{WE zJrUqy?vXKv=hf+zM$^gyaM_jKvRZow#?VFJ{0kZ)(KrWM>BYnEoZen*wwFg=cJoI5 zH=8H^nLnm^0{Z#__w8U2W`VwIHRu};`4_bx21sbghoZs&$S2Do1Ff>5pL|HeKL!2* zn;Ua#>^j<^fM1~Hmvpq+{{4BP)+rQ0%TJ}}%1MgPnluVancX9ir^}c1k~FoCbQ~XM zPqlr0gDM7|5?XJ;JYV7=wRj$Qh~&Y@We6^Hbznya-O8R;J#r z;XOSBz6*~VCtGrc8Iy&ZhyZ1-9HF5a(+%m03}?9r#YUPQ$fz7MDM#B(-6&a%;qIJ< zxggj1MjSd2hbEqcO;7z?c*p44JbzWxD(iz{@kZ@msCk#0WXuH2A}{vHm9&jB0h4cv zBBy$#%%vxQ_95?oR!$n32kDVD8|}_*@fZ$CM9h3XitEB;h*g-Y4uw}8X^g<)nJ!ND{#l*|wmI>Gf5~x>c5f zigqHC!4BB#u$8Sz_+C|@At?9;NQJ9b_|dY9rU}{@STA_!G(jC#T{$gR&cXGvCZ~A` zt~!s(^un7aH^En*J5A8@dxr?vd9&!X=~R@xAKMgM`Q+fDhV&n^A6Yt{6NqcV@nGP- z3Qh)8&|5LGuemqdMZ;?rwk~|;f+60+V$N=wVDW*ke^r8lp$FF051^JT8V1VOmyoX`;2e;ND9v?#E-BnNRo;!wWK#0Z%QybZ{u zxWpl^oG%b4YE}kD>HU%6e;*pFVD~@de+$1{@P?b+HI*0>|C8bq3*I1hq@8(JldsD^ z-M_CepOhnC9EMrq;kWWM-?{N&by^-|RVp-9aa;bSk&k%(5EjSBx%R1b0bnmGM! zK5_^J*=%~lZ&?#ixH4MD@yqFFw2oJSLIGJu>uB{dR=cfR5ls{wP&r=aiyF?Fyg}{cdBaaz?(37 z2MU{4`7|S4nNBx8u=!?==}O$I*$e9cV0bRfaHORCB_W*X{b?RulrtT{$kl9FYuy9@ zZ!*V!XR%8zqVO`*Y=z%WOE~8||{r@kk3MteVIzDJ*JzV-SL#CzZi*s)kqgq-%@rv^zT09jnTPcD2cHOt0z9?Hc8=Xqw?Sf#CUnz9N$#BVOJ zm)@buBT>QWiGIG*$T8)<8uw~UT!L_~j{yD-+3eX?g7hsgUAy3WiXJ(O1H*i(lY=kj_#EQNE69^DPWwh&+TvySGWprB*K&DlklnncdYU&lLEhuO= zON+3`!>-zEx3Gk>Fe(`c^j+G8ONq&fuS?-gS`KmXa!ex}xOjW>lWqnA#+wg27eQ)(sz?yCb9 zPTZv?zfc!+=P z4$;1xdk|0>H0QpI!ZMY+aFKMdp~tM4q*`puCFu2__jX7=p7^7q?708kWa--ylPoImE?WJj$v_c{C0JM~1#kP@-eSk++lzwLv<@c&gLZ(5lu$CT{gsDol9^9$QmtUz5WAbKkNX+I0TAov>tXriPBjD_hlRzelCzEoIxuQPE+h#jgc=5`^<4^3n10m$V*M%bI*9UVRa$JF{wHy?_2drs;Wrj~^Sp;6PI^s&_xoiPm|r_#q5w{)edx?20#m$0F-_bgQv z?JnuBrNF5r_7z?488xsLgrPhuQ@W2)ueBdKv-G0fR_j6O#7^r$dDc|H-7wYS_9PJs z6D6z73pPHe#@-Rp;6g1%iI;`iif|C2=uVOXL}mX6^^{_zHuElvt^ZBtBJ-wH=srJF zx6NUQ_ca463;9V2Mr1D%tXq8eyHq$dPq2f0x#-}}2L@eS{;Ee%*(?_lbaZ5>3&ANo6OJ9UuQ9huO+Oz1@?w5H7_B>FvV zZ)85coCzJwgbrmwZ#$u`w!?(n$+lyePe(GLo=m7W6Y6t9^~p9D&2^J?G*9_UwiRST zqJ^X5V9~jj$)epfju6?z;s;BzO@u4SHgRdG6Hdy6$}=J5qJ+=MwmDARl4A7p+iuCE zSday0*9e95-w_|;9`nF)1dLOU{{7c-%KPKe&WOi%{xVCLhYOy~_K zw1n?=CblONI^u-v#jt6YxUF@MaR%DA^0KxxxTft6FwwQ8p*3w^B9Lrb%&%@{EE8Iq z2`$TnR%Sx?I3b$ZL{OU9lKJ@cOlVCeWM@K0GNB$Pw8YP%%Eb0%LcLDNUVcORY#(8j zWBpeCxMPzn_DM)=Rv2v|ugpgQzgU6iSN?#$oWOj2WC2Ab+k%<#38h03RYWr4m-I&4 z2GSXA52)XV)$jMz?+?`PkJayE>i2Q=yHWi z+q{{OFB9@-LgLF&{@9pKNR(JJT8wJ#Ya*d&8{Oy%cDM7G8Xt!GEQEk8I&h1rMbSv8wHsOvYqo* zt|;y|2c=qLm(FF*;0Sf;uMRUv<5SuBM=u$4*8I7jFu$M8cDDNebIw1iAG+4P#CO)r zYd$V>cKgXcZb|N0^Ot|({DaS$-}edgdzWxy?kt19bN0t{{7%T>+-oAJ1WiAm ze3uG39T{s*AC0AyWMsTk**HG+d&rwCv5pb+wC>|xYw9qo>}qSi+wo9Tv^|l$){{h? z2=hnwgs3&0|44Dv{@peuzY^B|8QST3zcM}|!WLTN83keTrNVmSX8L?{DC2iRal(^j zdXUO=cgu8lFDV;6v4g~yn!CHoF5%o9tD0PZ&O1}n7^e?J`!`B|g=0p;PKCw`KoMehq&+bS#Zpga3tow}3CGu3JQ zM5X*i->eEZ{;GBUqN7&bxrsmZHGdQYfHdY>W!`QKJ`S6#4jCgK-!b)u?-^0^GxO_> za>b}suq*)O0Tuu}gLMmB^NKYfGauG!qGM>(UP?pc+!uXZZV#FH#Cp2KM8q{vu1Nev zhL{Cl1zye*Din~JBM=kj1rU>UMh&d9629?^^rp}#LGi1=C=n$Hqhz#PuqjaRG0i`> z7qxS#H?Kp1M>X^Ue8f3Hyie#v> z(P$GS$Y>L!$m}AaV0CVHmJ~qAk5>4S`5RXjyV9b>-0L*1f%jlgK*~-#Us*Ijb|DmA zCcltl|k$U_KYB|BXVIjIFV6S4fS|Tp{j;`?I$Govi=l zWW8Kx#XipQ>iTe5uyIm-ctx=B3bWl$9|N2Bn6SEDkDX`En55e?K^K~wF-bZUk<>Uu z*eu8dm7gz{Mul@bNGDt3Lxq2u^uTAX7c>K6WT>!glI>}v!+cbbGwPU#g`2c}RkK~N zxYVQx_NBQH+WIXv!DkQWmBOs$p#-Kw4q|7Ej@(L}A#<5;3HF8OIkk16ym3Tl)Gb;u zNe|?D!5?Xc;v6O0)7YfhE?B2dI>$-czEnqD5w)l2sAT&xqU4YJ?U_)_30dnEZu}lf z4Ay#CBlg<_&30K&y5#pK>PEB+Ca$CYkdErmQB?M#j!L%gAWByzA9ZM_6EfRx(HR)+ z@21Nr&qS@!QBBVa#p8;NH`^!ZZVdSs4+>l%Py3^r242svL)ruZ13P# zkJx|c3}*XNnW*oiqXZ!6RPE^~0U+g z-Y<&Um+1Fo*&K>wf0ye=R32D2(24r4);%m!dnQ>nK_{B56BX!0uci|f>aPX*FG|2hn`0{-PJK~}*ls?_NthvET%P%Jd?j|rCgWlyK zoOZl%qj)8Ubu^|lC`=<&K8z+fb|r{>r^{226pnHZ5jj>0)Ui@1=u63Hb2>-KSV7HI z56>zO5&Ss~)Ve3=ZE!ZAwjUhI@wEhKYnvh|2l!eNc&YLE#Mawn0piI^o{0PNYL^T` zmxTeHp4hrTc4cge-1m)f6$=LfS0%R2mki5?z@Po;r^TF6Y?K+`(3P_f&{}xnApX)B z2oceWw}7+~3-wha5cZj`+3umtY>)7VkH_;gE+n@n=lN=@7C#R37azfkmo6{HJ@;Z7 zG6JF_bsZ{nB11vdvsaku1vR$)89fC098dG1rEl^_eaL6IREmh&epH+%RCuY zlCQc31@C%LrTxLDb*Du|Wa4k@>;vP|W$JEcerccRC+P}gem2O)?$;7f?HADBFJF`U zv9QoFj0d-$n@0Q=Hx?r@>Lo;Gxqaq@?C+a2b)YJdd9XTlu440lhLY@mT9>UsgeFwT zR0Zo6iT1-crA2>Vm&mKT%R+(l>=Tqtv|`>YIKRUlEx ztg`7N0qy{d%UF|S9BSs_t0S7$(;;Z!jsxDPLP6#J7L9u%%*6oxf=ZP<2dMqm{-E#fxhm=_DNmVKMY+s@FAcjx& z*b7DzwN99Xv@lnIe@KJI{?|QrKI*fJCji81>_-(EksdII9$+i~AL8CUKC1dm{GZ7T z$p9l0Btq0ERI$YdEgGuefDMEspav#H5-S3_)hx|+y9hI)t{^Z8Fdh$JSFOAC+S;w( zZTDNdEe5Gd0+tDg+_WuLwxY7_!B9n|5H)4K@8|iPGc!T#Wq*JC@`5?H&;9azp8He6 zfVPC2tRv16lS1fk1~Y&B;^Sn{YKoierN!|PSl`Rk(YK1dF;v>hi;llvAIn_sTzEoY zZ?QE9Pj^9Q_AIc9WX-5dqLRs)9!$5cUgu)53_2x%+f-ku&^lcst$=e-Y%YqJw2W?a z-cgJt*XX<Gy7W$ngKz7EPsE`i4>?hN=42e zh+$_DUS3lzLHd|Y1=KMKkD_^mcL(T&3(yq&oK5IGxa>6D*;j5BU%q}(80Gu}J%7qH zIltkTeu7IlyHG0jRJmQfvq<}Lfl8_zEOB5IUWHQ2h5ad%^K#*Ny0A^w{inVQC$#T( zrbrGXOIfH4XgHdVyCPt;o~pyRdP%(?<)@Ujn%SLNDV5-4mfseCle~~p-4{4{L$F$! zS}B}Xtw~^lw+BDfo@^?U#_iu8CwV}_+>jpoZF-zLKN(?lF@cH$I#-}7oTS`vlJYBL zZE}+`c{xydp-Z9dsCyUxYQ2+Msb?y0G(?~sKD!=0T^`+IbtnkbdMVL)qc!hrjb++( zkXK3Jp7Pg#O*GAS=ov_13e`(bS%1+t)q-|sUkg1ETA$Hq-9%G{a_flH#rAx%a%FL`3Rgpu$G#z8uZR&#CfQkl4MFSP#6gT7`XQL@@=Gy4q)>I5 zWVeLuwSuuYW9HM0Bj%x{B(+|K+c{fa;XtcJEmXVt|QmRu9g^qHg)59=>>DwBqyKifxeeML7W!B60ScCJk6}nWc zeaBxv`}Ol5ku=szc0I_5p8+@UWBW!%%C|?DdHBlLe&$?TyJP-bGa1onmhKR(4^@X& zA3Y-UixgS!G#p2%#H`L4BLNiJZ-JKV{VIY%t{TZ0>!QSdM$}hRV!Cx`D;1j}I5k6V zTaO(7Eg`hD2i8v7D)iUi+TJkjO;rgbzkyvNZHzfTK9+6c56`EKx^PSzEnVBWmP#W{ zSV6J$(B*Fh{W$E^TkS_`tiOAJV2hpCfoF<3JeA}&JDWre^e0gh%jYB%_UzA5zbX>D zFdujVE)hiSSdSu3pX2YVstUIDgRE&m=jT$3(R86xf1JsYgaQk=#pq9@0+7-8fy`|< zHRIJk{iqMRA@mAGAi3!81Sv*viR80K1S&MzKap$V{N%HW*Yp7k(_FB?#z+)^U=-$I zA<1VIs*qp7%Ad_IVCCfe3O_|Qn1i2&{zyIlS>%T`Jb-Tuj|9QxxQwCDk*W0#bct;J zI7^Zv8;lQ;f*i9}Qz-CjLb^O4T4!+lT{d@%uN-&5%ID5GEWj_3v5SbIf+y5Z27PVg zec;`>tbQD3VBZyKl~{mQBmRQQw>QBGxU}B+&08`BVGLF7DXJB=II%v;KdM9_3-N?n zSXj4*12{*5k?#7}%ryi}OKC{cpKnN$(~{qY71Foqgx75f>};razWV6~grxyJruNj* z5-WxwA~O7JQ!0Ra6C=`qMa74AE5MUiKaxReB+aD^Ft(wdjDsMQ zll*qLfLE#MVfty8)Y=m+k-5D?vTP|AE9#lfQ(jT=Tk@xtk+qxrv^21pah%f>mT@ z$5^!`gmSKpIk#rBU2IL4fvgE*@IFXKHs<_GyX;M29+lUG%eR7)t3*h&d>fsT;bu5- zNtd;JGXR3|iM0Ehp|QkfZ@7hoVpcf1dr1)r5Q3qU@2=>G)`udGC|P24McOx)Bbe~e z8^O1dhf8vs4ZfA=(*lU2{dx!-pQvEXM*H>3?I>Ct?dQ9U+j9i-#&hNPLM+elC1yAo zbIQ#0Nrbz@7@b_p`l+z%4sVph*7Yx5NZ^R!5_^JJ67LXu!jKjw|2bZM@xCW)jIjG6 zhX5;m@R`i11<4oY3;0SbvLZ%hf<1|0BaIE>cVw$@am7lwakgT#Y9$=WT&4+%VRk3R z#`rqUd@XWAged2IXv|rAj!uGcW$%h6g_X4hNfQ;6O6bcijHk8SaQI;w1=krxh#cQe zzPtVt;7gdJB-t|{vdX4uxcqqaBYZ-Ho&Pyc>yp*__k0j-$!{INYEQxy@Of+Qvvf&5 ze<+{NGM|5t`;6JJelB^7&z~?D57g8xtO|M&baWS-9B|CRi2L^#-3iKqE0JOWOBF$RTr$zyubo=jb6&Y|EBquG}_tVe0wp2-IoiPH7GQm zgmU<(oa*4C*NTTHJb8NfC~v`(aAtS)smhWQ3FAalD6@GksGPiNbi!^qF0Lr0qkD8r zFzrs3TJm}68)4)HD|lL7C}+?g4-3ZjPnf}0WV&ZJHrTbLv6f2bU1C(2WAJW9rpx)J zw~7|ns;?fy0DHz5XVoadu;a5qgDE{V22;0UtE*CU(LlI)CG)vr9!JQj+!QG~crEJbus}Yw-sbo4!FiB#)oiL6nP&u{v$|yI zIdj=v*@Y)ak~H+$JM$Kvl>3(!-oCor!ehO0o(PHV$q7!EwRwg#ozqKj+fs4DVAtFX zDIRmK`GP{?x9wU2EPu>|px00hh%8N2zbi8J>JNk=T7Ar}E{$2$Qns~2w3m*k_R>++ zUg|-V0uvP%S(A$GH6oRAesO~qdoPOFVm|Y`>GH@io}s_ur83g`O}U*Fs@OJejetP( zU~OVJkr(HsrGYN=tkJ7RBcg!wl!tzttf_*W=?%TmX)yg&)fKP9L!`qa858NS3I_1z zJEz!wL0X2*X_M3Rd2QVrb`?efr|vJ-psDJQ^LqU%Euq&i*NFB!8J(@3)MU5UlZ%~S znj_10`+E#^Dd{Ae!Rs&XCH@!v?c?EBI&3Zf2BVcj(CEMFZ&Y-EqlGK>F7JGj?>`ql zhWhVm9)D(Bgy%)kq9tdiN>)}*7JB~UfeU|^DssV@g5E5&&0pM79|%ols84;04}Mhf zdd0Siy^;3C;7K5CK%YeyV6)y#AjKw6#=hMOu4Z4>M%pXKOb+a8j?U^`_*-k$5$3`A zl}&82@apolhj~hN4otq;yBrq3*LlUw(N`7?&Tk61mFPj5)^w4D6bp9?u#m1u4@O~U zwP;X&o@@s3w=Xud6uW!je`7)m4;6iDbX}r*7EQ;g-=e`lVv`ls0y;oe9i!&x)ngV7 z_x$%)jg7Q#pUl8$fz8o|F@zE)dJY9vYH6Jd+szP_t9TjT&^ln|38s>aSEDoXQIvr6 z15n$13mma)0th{^xg1o!Rn4bzWW6+^Dmg!;zi27fhiA^!U5-IYJR7oKu@n=MwSiKpPr z@BaVa=Xc7(f6e@!&~xQ4%r5{}U_;th$;tuADMN5vxd_=8ax@I4?VsEX#CTSC;2(b9 zz#IKwmWjPwzV_MwnZyX=LP5lsUI1>m8=TPmAOvYVx8$@9|SDc}L zLRP0Oorqf=%(YtPvO#an5@W08G5AAPzL~zd4D-JmkR?E2W+>dx1=yN zjzb?4_#RrPwQ+1UQRCo-na(5srW_rU`ZVgqU8>f}sjik@zrmy&uOFPjI`iib>iUQbzSAhT{5ePhUC&LHoA1U>ZG!rlN6H7e53B4ybVPv)mBTN%K^Cp?^ zNc$`|U`TDyS^yVM$Th2Mono<(_NxInT?8EQeE9(F)qoV?f3l}|;qMXV5m7)WT5G7d zmlwtJPOQind4M~M9Rv~`q z@s<)S-xBS)t^aI8$1w7U)aygm=E#rJksrSj3)C0SWWb$&|8|a%NvzAhNJn8=n`u`BVf6S?m>VCc&fTJ$WNBj{zqdihXdklWdqT$U%0wFw^r_!gT1;>}h3n}Y^g};-Z|07G`42CF+;&9x87#hximfW}f z5PXsLX~lU|W8RLC{e!*QwBV^f%la1jbLBqPPWehvWk?L6zPTsWh7?L*G86;B2y$u1 z7cI$2((>LDltGHBcP@HR2R=Vf7LU_j&@y<{lS;}b6(7G9yp8sVzYUp37f^c6zj3L9 z4+Ifs!>5bC15m;>Nsow;NeF8>{%iAUgsT3SS1(4|8)fjfHOc6gS}&M^@3exej#Z8* zNDP|Pj92t`W%aP&wn!{XR_i=ubDo!RkKbJ~N$~R=AIuwK+PnO)NX&BdvEsMZYfX)R zff#;5Bu#y(UaO}mb2?o)y9v_t)_-t0kPw?!8?O7BO0ujMJ(^tEo`7J)K(mhxMJTj{ z=(v0wG8Cc5z?1QnIOV;r%s#=P-?D4@V%@O2#}4ndCiMmg z-xQ8+jb9)A?c%d=9aKmgF6|>C*Vb7UrR*Q+uUOot><#>8E$vhGy*zdoy8Kfka|-)Y z_QSjudE1Zs<@*E;Ps^L zNw;;8Omw@Na^4cs>U==*IzC%OEymTB4aHOTa8jv+eSe9o?7{ND4n|kC$zNa3Mq3ABO^vP zd|*>!y;FOifpS`QcVTM1d^|-RC9A1puYFY|wiA8jH|0C+g_8pByVc{sNa|L7QU4?n z1iAH3lKKr!7>|H1Ixnx&nvt~s*3w!D)xPzf;wuKbGVz%Iih7ZWcN9QIpCl7cOvPV? zh-1|~g)(^U@MXCvg271Sg<~edT$^;M5eCc;Z`OIT48!dBq5qUY)z*GT#@CaLY`0!9 z`mY6(wf(=cZw&@MXmsu_qz6dAf+8jPXW=Yq$uU;I{`8+R^EUv!xetH0AK<9T338qP zoO##tL_cQ#hp0m4T0FK-i^bP`5asnDjWA6L`ZV;zCMVNR0iSKl}bCY_np!ZCPH4h0Y&` znA#5su(##g69=_F%}+R7;gkife+JDtS>jKR#`c$~e431Q+g)Ug;r>i}^n_dH1AGFr zTrVG;GI~UN*u8d)(Cv&5A}sU=wxNHt*<{?)V~?e*+qEobwp)rQMKqEVQL21#Ua`%d zJ3FOa2X7l}=|rRyRva3FM;%K+g8?IW>fho`b&#nJx~=@7@?s;c@5wsOPybSKOVB_V z_Q4>WKq@pcZ=Seu#U!vr-S3(80>n~&vnV_z8Ug6IR9U~9Z*(>XD!t?S)$l zO9j(b|17v}yZ&Q3qA(p1m;vs~KkCgye32Ey`J}$&=so1xxu!@V*9feJ=WM6f$8Hn9 zKW5F_9kd3F#%UY}#6*$Sd#>TxO`|RTbaOWsWSh$dTkD}eg0C$ za3)T_+p)F6&mMsRRIvekE78xsy|lZ0qd3FoF@7%o@#AN16!-S>IAEjrzL#_%`}Sg( zg14jC+%_Dh+lxU!$w)Tf5JLs)d3vVXMcML^8+EX|Cwg9@ZR2a)`FBefi|1mT;d-7h zU+7tw1Py&0Ix>E$eO~eM`FK8cHe`Br472Aw`g9!e&UfF|I8y!Q$pAN4k6ptcXv}6r z2WN6m9P$@ze<`z#J3&V3EK|LEUJ313Ta)Y<5&dxC zhiDwE)%2HLsW{z$^eQPt{^XPP?jI(2pJX`XZ|>};{W@s$?Rjgh2xqOW_+BgjZ9qGU zDpi9z(@8ugq|nAMqeMOX(hkic>rJ9}OE?piTk&l4p_L8MN0g-##`k>u4>|^=-YyRV z3=*j>f(58S(4Y(RBw^Y4hM}K)?!MkToe2v07v0>cm%Ei`$ZWFrfJ<$sm`9v98o zV_z?7jcJ>x^$oVl3Y>q%(3ZyjRP0v5&e^LJ6uB$T6%W``5xWSH?^5ZeDp`SM{i1rx zV#2&eF43B65@RYl?CDd-sdE54$aD}_uZ%H?Iis~QSZm0X7F)=Jhe@25R=9DO zJJ!m6u)H{;PX6xqcLKu5~v2&z;U~*&H0(LIRP@~oM(G+oJnXfEE*Er;SYpC%)wHs7ty6|EK#Ab@}o@m_DIsrG*x=QBl*fBM)RfJ%9tu9+$q$#RVo&t{3qORMG@lV!V?Lgp#+aofgWe zJkKy%Bdy}2GP&enIMPCTHMR z1d|C#;lhR29L3t0_?O7>v73WV;%(rgVEinT?Nv@#H`{8mnby}Gp2V0oN2eD@R{V_l zKpW(CKFSD{<@ch|%h8;AP!hOWBcDolab7x6D$3b^A%8Y(kF5{qnw4P3QrVV$1zatES8~ zb(tFk6{{&_Ke6quOx57ap|pFtz|yf7d8dG4o!|STK?>#5nSDR4c0ZBZ*v5zpHMNZy zr90J{K86>Vii~wDJp|g0Ul>y<{wT(#YQ~6q;#iY?f|tG0%|6v+zsAk}ADVq?Haj-| zQziQ~Ci}Hs_Ulacnrpys=V9s~^Y$`cvbf5hf@ORsxp)hR@1~>qh_6A1^gL=2d!9wW zU(cS0_`sob!8afj84oe(QG1@h%EPjpCtBY5pDJ(4|GK*iGW%B=WUN@FA0&%6AQ$Tfv)?EKRET_%$B(n8A#$RteT=- zOA67d#P?fy7fO{FIEhx}mh!DRZ*Y3PpCW!=NCH}>+awm9%#9T#hIsjLykPPIMJxRk zD^C=f_=WW+@%H?FA;kZY`Ls3ApAh1b!6^;OaS&-0a4UqkM1XVtWhnw%2^fd6x}^nM zJy9(>k}Q)l48Kf@EXfuLXhqAZD`3hKFvK8b z4aj$=eF=T53YG_s?-QpT^PgT=i3np!IAN_+Aoth93$+ILn5 zdWfWFqUS|cybpvmBc(KVcgQ?IB{x#zaR1nvD0JmxoDRKduO!Rw7QrCx4*MnQ(NhF-Q zzBqOLh}4}d=CawsXiTY#%D!PO1z=qV%fl6U4tDEErwfCA}pfI{`DXNCp=M6IpE-K%|Xg2Ut4lOE8eTK~}$1g~K zEOS|;eU+Sg5a-rl2mp1L9%+4@)jNJpZ=`Ln>@f9-Vb5~Vx5`{*`B6sZK#8=jly7qm z+iUkxK!fwm&4TboTK|jJsmJ#7&VH>@Y#RD4zAyXdk-kLG3UmjoD&)_6 zjyZEVDp)PI_4$@pHz+yCkfly3GM5Q$FD()DwF)$CtwUAL_Mut^wDw^h<&P2iBbF&1 zJ!ytqNSC`m?;Tn?9rEgizOMv+!OvjzsPJbha}sIahYEITPFS2w`#A!xtKZ?67L~sm zD*PbcKV5x{)IOvDHPnry+}b*Btzg9H>6LGR=WcZMj=wKxVOA;duqp+j8i@WDs)3>& z*Q11MUC3E!zirW}vP3LJ^HYXjS`?#`IyZq)X0x$kLwWu53VzDBIZu9;%Z zDIGtzEO8kdcYMu=g%_jgVmAgiDcU+98cLj#+)VV7VDdfm+@TfW893edshbr>J)+iY zn_Nx-RK4r7m2p5QuI)fGt`9No?Kd;I%&oohpbTo+Dm%D}DS}l|>WUm9trE;Eul+0* zozq^SgJUfNozq9=EvB%~Ua9+LmzpXx!=5AEP%=r<-=E(nY0GG*8eBp^M@RfQ(fjD4 zcX0O~`w3$#dImoLhD6%$U93nXjxj7rGLxC?u@|9zxpV>Bmen)kPbXM^iAl1M|Ajh~ zPCF;&T<~r$-oBKE68f$Wxi-4NdFhx`&Y9|RM9_}KbYoV}Oxfwiw?PcMr-WKzT}`>& z|IS7fnf{4C$bvT?K7i!wwq>D|qL;cwDfh7GZ!bUmI7635@(AA(jsxSJZo82~<8cs~ zblZIHWQ|7J3)5{kyALLi@b!TX+m@6ur?*45*wje-pn6WfN8X{A*a&q68nKuan(ty( zc*9KB@jP3uq9dmC(@`0$>cjXWUJRe!X~jehp9tEe`R5^0L~blC$hC+CIoQfbywl$u zHj~p=m$>5{J-tp6d24^gk9e-X26(&=-(Bjg3vIZc{-*35JaLlXtfYUcZ8s0;wtf7{ z5Tx5)b?=0ejSA(NcCdfUwk4I*!U*6pL$ioVqMnrITc%i`#P29Ufbeg*B2OewJpijd zh7Fm?b(g91(i)@{x$RLf#?&08NORdPv*FVZG}8?V}0 zObpCMxvm`+pA}1E~iOSuM|};RU-L5@KaV0 zLPi*llu=}qur81jc`MX+OR&lLoNg1mSJ4a~2nL^i$S0XR89St`{L1h$c_qppmiha9 z_o?R3cgW0N*7c(v!WA8c3>#lEDbc?Y8tks46&)tll4`l>c`#0>_NJDAp~bD2U@u!j zbXh&AB}fG$YaU~ZaVW@+OJ;&gRD*f%xp|u&gM$EJ37D9J^+KNsu`XLoQ#!-@q=Y8V z#Bal)sk-=T@yDE^|56^Am;R}~>4~Lays)zCd`|g)GA`9eg$tC-lt%8o6`<_Z-IZT= zyw$Gmsvz*_nw!b!d=Ij|`=7{JsW~en_g*eJhuGCeWA;i(BFL_gmJ(eh(pn{7UzpGP z<(Uuf8{?0x5XZ8N(DDr>YzOm7%&>DFVwYzw-Z;j;XaG)$rK$W`58eQM1v2-=dsg1e z-OoQ^Ka9kOc~`Tgswd6pX*Hj}MI8*T5nYt-!T0Eg4yRxwUUMarrNsqS-pl8}%GLVY zs=v$h_b&Yvx(W%2ip`d-sP-hk&$W>o{BKZkb)Qf0ImTov6hyWrvr~l zA5tsj*Ex%(buW^}cARY*8|vFw`SeTTa!5b^u%S;s#0*Xb)w$)E8H+!O6tnJ#{h$=h zfXvJr5i~jYYU*ZY{FLkIObxY{mNq$$zb~EO(2caNmvX*BlQ}UMmD`ss!KkGif{d~G z=)YuaBCVH88pdawjL%_#SMG;%`CvejJ$(!fW=20}97L-7N1V`O*W&!wA5D|5l44-o z?MjVvc47$DoWV4aL8IPjjaY&b=}4nr9JFqDUkx(SPl-c3DN7pz=Ql0A+5e&R#(1F`ah7SrINFfE(TZ%XkUk!^c6mKC zS^jjU$D5j$Iqa$r9k|~L&TN8gr(TM|BA7Nat(*x zRRs1kRC+c{GTQ348;U||Hn95Jq!rKx&b-rUVdP{~ZR(l)gnwRpsi+qY|NKB>*UtS%FYy*g&>%7E%bDt!GU{&d$U+d@hVR zKc{#?Qx@{IVlM~D1Do&JOtP4>d6|9?4>S26a;yTqqdyzOhmVuIrC%SMGh{z-klhI< zp%i2rivia-7Hyn2WY!4l_mtes2%<~Jjd4M?fjd?X80gR3JIk+J7s%|G-WWqY0!vtp zg3SpdxC*W4%z7!-`O{`CQaSO7(;8d)IiEm|A9_YkO8Ckp!{I`6S zc(;VO+z@jEG5BDMBi%@OqIL#*Eu-(rYF_dhPrG{jTm? ztbz6e*%hOHb-W?)I=6GVOK>Dkvj)nd8Ep*2ZChs8dAd{(BEuPg;zjxN@P0!Rz&(J3 zj!})9w2h>a1QJ=ahF{F4Q+iK)uH@5uC~k`T3?ckXWF~l$)ndeHjDQjUiyDHu=|lC} ze&H}9mLiiwCb-)C${tMFJ=~3tB)+KLLdLHV+}Lv^G(Fhlxe_w02}@I3MJaWuAq#gH zb3XpjBV@8)kl(~eQ;r4(IJq2_3#x&TU)EP-%B5yGch%Y!7BY|UC2%vujU%deQo++S za{MEbG%=-RfwTN=LuMfwhB7?DYu74v_1}v*Hk6&)Ioc(5OWy#QI&Goa#(o{#th;jl zGCnw0WQ)1FZ!!0M-L>kjfJHB*?{Z#Ptl7Wlc3vQirNjDjsRP{$9n#b|LnNIWBspd=ueG())0uP6Fb zQa|xyIa(Op5~Q1D^2l$z<&=fmte1L^yPMopDb0+uKEkEJ>4bJhflHMtzF*u2S9hJ` z!gN_%N?O=>YsYZ4;M|pWTDy*a`L5`6pkHGu(d(Q=%Q?R~|9qa)BuC}(_oG`AYjY{T z?Uw#+DLv9&fxi0YioKPAEyv%tul5Iaq6i`u&BL^%kyhNuWl)}}i6%me%Z$9Dk*CO9{pqKvqt*fbf+9~SIZH{$=CWIA98K!(^Cn4tzFgd*QUSl zb$XrAG*s5&F0jd5UiJLTD5=X}OGl@Hwmat{qp)70Bx1t97@QX9Jbp~t&!^~-#b=2= zv9w=^GAZ+)DbuvyEjfOH8TaUG#AOupb=5ek4|GQNEj&LD;fpHIJF+55lIUY)GL*?L zac?@k{f@j&$7eM-(^iPvsaHUYIW{4}CKJ0oY?#^BubkKMi`CYFX@^~tsgJaGfXGGe zrz^=3rjaPevHy^`^=1}@oO4h&#-&mgswrPPyZE?K{6~VNix1W4qf=u2~x73ugs z!ysuN)Mt0NC8HRDLElR0(o`anNB3c7htbu_+j)Ud1A}6iZ^w^YAEU2YX!3XsS<*{G zG(HcDMuYBHgVma*u7?J=Wz>`1HA)*ko*ZXcYh(x-oN?F&^c`^_VbAN^NOuz4@n`Ve zx&i+(=14+{pj&DYzeU=l3$CsLTo_R>=!&#{iIq zkvh9J+rT5lSfZ&5SP?_XhbPIL*<&Yxy$U=XSH3GT9$+MIHyL2GD}I?(P%m~*C?ds& zG^bvWkGjlmJ5aTN3+kI1BqhTg@1xP1+mD#cTAk5YaH0D8Zxsc) zBG33EKis|AuZ4@qT;XTfS}vg&F5N3?^L0I|XTkYUT5T7zgaSZgUVPx693^GoLnIKH z%Y@>8wN}nq$X4-0-cy_0QW9Nqbm6{xsxxzEt27?f}zUBYg}8yqYx@d+MJ`5oM*;R%>xG% z8+U`@8cJSXVNEBdTHJUPeMgkRGv?AC=d4Bfe$tEigezt;!K84Ot9f)I7en zEODvb6np_ZTSNht2*1yEh2QTY{BFoBE^&@5bgA3;5X(e6{tDgPVgSQQO-{*%Z-F7GC8d0P*2ovO(6$GCr3Z9PDgsx_(hj}Jt#1KlOF}qg&!{i6;W#eSq!>;3BT80-*ega`mz2K<5z2*948oW+rXs zkm39s`6-hLg^FbjZaVkdxNTa##sU?|W6lska7Ys=}e zRARsL-Lw6^ioMBQnd8S;iK!(At<9WY<%1J1Kp{I`Hu3XvIb-4%ixAe3tksi^D z4ZS($5dmZG@7}hO3oJR@+kQ{I?6lJK|r)sv)(Y1RvA(8acMo zK2ZUNtYd3th%ySl0teK^DaspPG!L^F1!WSVqt6SeUv`+FK-T+ig+diBdm$uq9Is?A zPEcXfWq6UCldb;@>~qpS(TO%uO{H2@{wN~DK-bHOLij!|7*APWQ?9;5-}?Tr)1z2T zeWEk{x7L?DJW7s~k0Afq`h{ufmM?ktRJ3`g5)UBKCfILslTc{B$eM3+5w*=(z1F-N zXWFxla2TMD)GOpN?u%Lisk);^V6Tk6UR1U`dX?i0oxzDh+eBJFA^pUI^_Ot_M{YN~ z%nI46WRdUq52UUZU*|iVTW|vG;4ewxE8mPsh%&`$f30MiR$!f6L63DA%_$*+fEn=@ z`m7w4uD)+`s%{@`pUKs+XYI4uW!Ji3Qw@4pX>o7Ps{YrWnX+?y@VRBSjEedO%fHl(ku(0TAuSqFAZDtSM*qk^v_>cc!Uo}~c!A#AVz~19;?RjVN_{K4j6)R{_#Wp>;G`e=qgS)e3 zYi|ZCp6cgL4hUZNVyX@&E60DI4YmD{o3tx0aCY79RNam~sTR7a#2lQS5)iy2RkxoR zVjT{3{a4hznhq|PF|3SM?^}2Ti$86As*c-WU>lPqACJG8u6E>R&pYnCxm3SBVHTVW zUvx=FWW{D~n(Vsgoa5Zc$Ub3SbfuPbIE%W?XxqF)b1AiC2f9H<|FwtpBrC!hex{sV z@fo6x02h36I5T!NPBX&W&?p7Nuyzpg(Qiq_N1~d9H11`-&5nrSV zXE#C@mc>u(>gR+3=|A01&CfZh2TyovrNZE1fl((^?pJ={(!H;m=P!BB46=M5?maU< z@*FYGuBLa698;qi&Kgt|*bY${yn>@A zHRTGFOrhwm2#L<{lq=A{6{X=RR}?2VpB|kXT(}$1o4L%HLh-1Gay-w*1a4`_1)rx_ z;BvkwzB-W!8-AO2TF|)$?VT$4WqXHgT2l4fBp2;hh~DQd`Q2Nn*mbzkZ2j7L3G(H0 z-2+@;0zPS=r9CZ_p43ZLpLo6LmVSSsV(<9I;B7% zUv=($UH(_imzZ?wY?a7t?KQLYAcg;Rv!(c~^sm8sO(v{J`lpB$>+x#bp2>o2l@5-b zw&&gG)<`YfyAV=ONg8@VEEylr?z z$2zMb&=WZFtW5id->1uy#k9R4xbXKN$(ai0rA{+W(bS?-3muprHb1AQcwiJa7GdRR zyF87C#^~DB@Mj_``wO6NUnLJ5H)u^nlA^J`E|T@ZiHA+p>K9ZtI$a}#_{IUbm@lZ; zoVDK|pVNU5NWj)4%3^%2u5x`3C}hJAY(N`e&~PQ&F>i6>;tEIR{ba|az!CgOnPR0D zjv3`2m~!X4tLkEn;ze?88`e1NaHlEnAt&d#42;VRBR zXxH~G&f7|w#e0Uwlwy9xzkuLJ+WuscK@zic;|@;E(ib%}qw?Jxojc~XDVX&c=zHHL zT{+^jZ_7qOj`DWjo#q=Ren88?(vn{w9m_hgBZpu*s1M&Z7QdonoHW!SN2J0C*L|Hp zb-}I$IreC|&kAofDuvd<7~0V2bfl%Eyhu?3UnqkBj?Abp@eT1jfd69@AlM85yX&KB zbG~S%^f6?i=F~iXUU6d7$=2c*szD(-ok137lk?@Ns=?cR`pM}6J*L2g&&)n05ZC<# zzZ51LO7t;|N_v|_7<2{4YB|xc57>(j1ejaoUndsLL4KR=?CJNf<(v2PK!FC*k#=lF z_9QMMXY@CBjgiT^wq0C2mhZVEXdUT(vTg6u zJ@)M*y1#>>zGyd{4vs`w6@+ux9IXl6)xUdGqk4Uj>_DU)_Dg4?C^|1lF(b5?JI)}lnkJ+thu9k#(rdDYIC#~6&+r3n9MtI@L5J`DR3Kr;<3*1zl@yrt@2&R4+ZvgejKzmcRIrZo0$|DR;vqsa`0u%h|BC%5`BWm6BDg^8PzLv zR40X&jHl72m;u>huF`T$zx&Qu`EKUw{q7>>DsA6UMoU)Q#?Ucik@h8_=4d*x6znki z+FkuAzf)!{E`V)nuso!_JpN|+_C1F{L>S0J^vi9YHNY-et_@5l8lztg-qqC5WM6gA zNk6PpFqbl93Qkc>55-Iad&PsCSyH=LAoqeKnm%1}{N2E>4t+spmk)RRANus^DQ{>( zvUvklx$~!4=gBNyI_>zoGKs%pbZPWvvdhqP&Sg}xE~X8Julrdnti;mkukapn&0)ny z&w_(5BNSdP9y?t4AYCcNh)=OO*RTkzErYp~@*{L0qI*Pc?Xj13$VdL=G2Y2h?uYvx z-Q6dlX|BtU3O8*M8ZFX3v^n}~vDgl&^3Q0%BTgmeY-vti=~2wtnzH=`G$+3z!u{$H zj(|ZNnnqM?6DAjO%X1p6d1YwFcJ6UdV;H*yjl13O4X65X`{>L@HaUK57v?=vx`J~} z-mT{9w$A7~3jMD~hxLAQw8wD4o1;r{VDRs<{MA1}q zRm{fWxQ980e4f}D{%Y77*!P=vUfA_xBrvh(ujE(B&6foBSevnXb?tFOm95+GH@6kx zY+y6qx$|N*$)MQvl_%w&^f%>aeKJq#-DO_RyGw8#hvtl9xa5pa%Pg3%yz?lhh~qm$ z*XHFSYQaO3DjGIA+r(W`U9qSoZHo`K8$DE~`~!HgDI-baXcR9no)ehtZ0^`$?Ez$7 z1)~Fw5BY=B*s}RcswG_Se5XSXNqyZ?TJKnS&$M6p|Ipbr!DUM)_|TcD8I!IVD*(>* zSwdgFJm0rWSQsdGT!V5)H;KgX7^>`!c3lvzJCjelFR4t|e8ZGA-&}5%%hsBk`2RJ( zvx7i*6ofmJDe@jro%|%U2=o>>pl8{V(AJt85m+xDcjCPh@3BP_7tzlP@j$WoH&Nw; zEeeyupDYR`yF=Y2@Ee>aloH(MpnOBzy|W>(r3sInPWk_6g^yAb95ujc5l$=~d|O|p ztfKOL$xng{m*JPH=DK7NBXXlJJ~(rmSnK&!RY6;olP{ctAGrMRNEA$RA_Ey;AOO2A zjL$G6frRpsU;8M(c3A|AqsWS}Q1#;6wR!yDNZS|mVG=$LgUy8E9>LkyHiUOxwZLJN zAK&{aB!Pj>n=!a6A?_0Kyrkqdhu`S-Nbg8p6leYHmsz$Xw0gkT{r0qi z!0zOG{=|otHVh>^X;^rjn6}#oxF4@?BFVKBQ~puld1-k3oF;qCVW!MU{gW(fR5nrP zc=??ZnL`UmRbpf<=PO1@YY z>n^jTyaCI``QpWu(Jc#)S#4VJ)L{4QCesKUA<9TN|Fd2}!8A+zTI2QY^2myEIaGDH zaEwdRNsNkL`{oUzct`mKtvK`tRLNw}M`0{2O9G5YWG}z|iMK9*6T%b|k2qQXL$hD= zHe8v^jeHyF)MQ_d(wBag`=!};f&vpbRQv>;lYJ@j78YbatP!yEsrtJ2oua;9Wnbpi zhrGZ*g1q|vGn?=;>PvHe^Ov5a3~=LbSpk~;=#OY1wz#(_x4Spif(5Me-<{eTRKt3$ zKoiWZDrIi%bnaJ8nfSOSfPyp3Jd`K5UKjs7oO1L{#QwqLJp*`e4FdTzCzVTv=kbK| zptCFA!m}FFV3z+1S|taC@d8w~jutKyPt=+CKUh^07=SCW=#%3@PTQS>bWF;vUHANT zaY4nsP6!{IT7olAEjHq()8bUs1P^)$Sk1y$;J?ZPXrEL0p4K)fwbb=`VE8xr>>DGm z`>Xpi?xg;lK&y;b|J(koMIDsvra$98t3QA7JRaws#|unrPCAdr@B0689vghXj%EFE zxcJBBhDQ53e}gmQX*Q=T-#@JZzCfe%+IXccg-V~J@zZ93=N4};E+=1Wv=$;!9zPl9 z-qvIUV{&mrpw!|6 zl08G?{gT_nNB@o+>Zv@Vunh_t3 zs8+F;rbcJMHQIWh>QHiHWa~W3?#atHPdX;&l*-r`ALTzN#xy90F^ye^FzHxwYo#mw zk6*4{SJq|ytw6bamGO)G*yV%KjDd)GobRB`NsF!YyxF>3sZEe(Zxed;x%+RCR%!NL ziBzfHi?i`B%Xis#P6~YFsXKgGBU9#@pe?ZfZ*B$$HZTHbnijxfF;&x)TobH4jrQa* zf{wcWV{-WDC4tSv&Xezzr~E!izRUgu^%t@5#D2`;-LZ*5E=z7gr7hVLh^&HRC2{k@ z^O0|fT2l>9agV8(pe?R|!#Q(SW|1tl4l)jy7Ay=P2 z7%T|(`3rb+Cs&1>n+!3v=61j$#7J{=S7Hg6pd9DL&DOr=)T8pHIr;|7jp?E@5_ZNS z>()i+DKE2q9#rZ3gQcWJzvV%ZzI*yCAxpJ-dHsUQv1@`e(StKpfPN*K<(&0p%f#m! zdi7Qj8oh1_);=m&qZMRzvinxhS6z8n`AhYIEutKrRd^oR{h$ZZa;GZu&tVltSSB(} zzepyTaFOESV}#B4dc20^A?_K8!XyOuCu~w+RrF|;*owD-l|7_*d2j75br>}{prj2w}#Tb>` zWEqaQckB#Cz6F^+uoEYM|KU=kkyeXTHaxa**7ip0xQ)loj3>{iOjq{s)n`|7_cOUm zS3YMhFPck-xpbL}QO>R0VV(rxB*UxbVl;Lu516Nea%oOiI(~n;QV?Qa@?C#o)m#BC zsUg;u-lHm^E~HruRn~in&SnLTa5`)kR@bC zO9GdkP1lSwPZQ+QoUW;4>T0er@1~dwkV(?V%;j3SknlQ5c!PO2-&}4qmz&My8|Jb= zE~Jl>e&sTG;4fWsySdz@7nK82-{+~7VOe+@qm?)|_n%IM%h8nVNEnO^x~_O2^? zw>x|Hs(VM)ecbx}_34%SvtJKp?+&_Tg=My+NC$PiCr2NY zFALvED~3RscydmwiL~6O)oR+@38Xi7a-7H?r_cy)xyT8UUO6(GVib2uq^uO{C;3aO zzNEPumwl&7R{FIv`|g_T-F4Z!SoZGP?A;C7yBpm*CgNspWe(?OKQ74LiT1u}Kso>bE_-*sdq)c%;?|GObe3}kA+$k0V@IjmJa2}_eqX1o z24JK~WP0&xI30OtUaCQED%>dLQsFbLq)#g6NmEu?-rP?gnVLR=Sx51+EIZ+iBNiS> z*Nl>pV5)1z=#bQmHkWbcGFC40(uK?Jtn{5s@QNVKT8xLAoB8Oe-rhp(0AEqPP1gps zklK(Ia&mT|mo4?@dv46R*kp5?ldc)5IZb1-{I>ok5z6hrZ1`^|zUh0(Xfmxssm$6b2T+kR!@hB))v9(gBpVGb%bN<;a zO&vdIbM>RS^VW;CQkIx$?p1DcbxoRAmwT^dU7KP@dd21zcy7^OC+mklLqeM}KK!m3 z1>Mc&)dBC7ffzDYFMF>H%#l|cy;lOOOvWF0uLR(k_S|n?rB`-I#r)+%Lk~h)D+R`y z3}5pyJgvzkgM5^~bQUP%6@q7z;ZiTdLz+mE$w&Fi9Rb!F0aMIFq2;g7#E4RUJGnm> zbB;ct-A~utr5W6VCVTQ&H8+xu6}H1mce6=%qq)qNi&X<_EIuTOMxpTXZZOqgL(6`d zVzS#__NgY{6mz-8WJkIobI5JT0WY;mm9 z59cD(`DP*5D?SCj@C6}ULo~-_AvtP-T^Evs16wK^oVTBnhG3iFCXoKpA05JwY3nDC zxor)bwua25ST1zipDr8+>O^s1lJ~pC^&ybG@=8p*e`w0Xk1WsGwIGrSmYl8yb=2*x zzv-%B=`P0U!Q9G}x~X+!qjPTGTB#5HOfWUEItgqM@G08z70DC+`1#2b1w_v{5saUi zyt~Yon1{oC=jvDTxCvqOILmyg_9=jX#BiZ&B>?qkr||y@(GzL?8BB5z#I$g4eZ)IS z*XlW)niG;zBQOBN>f$}p`YlPhO3HE5Bzp>wD)a>7BV99ZdzGBRi8EU0cg&g6?6Nr` zt>Q+?CBxFyX56a}$bh@ZCO63-1jU{AJ{-bbLx)&%V)wmGa?aV;;5_tGAs68ciOI}Qd281l zsVA<&XlKoj%(zR)OsJS6$ld8gh?U*o{GH}1eku^JXmDonpd@IszmVXG-V-jWMyog5 zm{WDDRKhZhvW?jdlacZ#2nt_Su<YpIlI#rC zJL3@{^MxLxC$wX`ax9w|U-8!&zd#x@F-XC8&zV@jWyv>_J%#b569Y7QY0*ScqFy>1 z0Uyh|!I_K4seq{4u>xi8SDXgl9xP#rtn6ENpU4o7N{Ci@{MMd63;m)9g(rKaFa9ZP z8RrgV%k1X@I6+cZzIZg|2~<;)N`$-r#i|`$egrXlDqbAop7+yvZ(UmuOwH;&bSoW{ z8pcoq!EVmUE{8#KoTqi2%NkWTz*hA>0}3Ahj)Imf`!4LeiRy3Teg&pTY;VVPRpiAAI_$h6qh&<*BP0}035M8m8diy{ z{0b|=^m=DNk<`Jqg&Qe8T0cS>ho6Rls)#6z$Pqp(BGy96KN1TgySgM@T_U+qQX)rM zbGRnlnp6|Z&Kb#xk?esrF_4K?&E4a-j3ijO^>OEW1*s(?o0GQ(ec(w1`mtsaD7Qq| zFe%hV{4?F9hyNKW;tMUr&2E`-+1YDVj*^9GeDSI~jOZszXIM){Je#?jg^(U##*Jepgx2?*tx(Tl0 z8NZ=oJj|b}Coh29K`|`u$ zKPq3IDh#3~YF=MfjCByzclW zmzJVQzqB-7fmz|Yqs)Y$KV;Jw&(30O&4s6rYTfiOlXtfB%a;JwDi{kfm$)X=3cdsk zQAl(pc%gBlv@Cv+s@gP@&uHy2SwR89;Klnrm@E5bZgPGgH&Q%G;FuJcK3#Vb`X>7BP+?+a1 zO8cr|MzfYswPlD9g6>OI@6RM~VpqLC<_uyY*sNQuTO2p{r5&frott6CujR!2)t~B5 z$!GT`RY&XUt4+68ZJ2m&($|{?kAJ$0^E_f411^(5k#-qgaz(p3 zR?sJ^A@K}Ls`)M<39K%ZSuusRzgl3D ztiYX*c&_@{Ooa(klXsW;5{1WVM=nF&xyw1{N_~9^x05I8?dQ4uf(e~{Hn-sleOqjf z$$?oZOT{ttxlvY{QS5AsYYi*Ve^52MCg+`hGlbt(S2mMd&H}HJhszLf6@JScIYgef zr-sFXj}e|4#1c^Mgt0FU6Nx8;9}p~-D>_^QB1W6&L`S(WZ@tb0b(a!9PrQX3;fSF7 zLphY>@sh9TqwCihZ(vs4K|smSM(fs)sFh7|Te*{C@92K&hx zRNi^}`B)Sb5#S&-h>$L5w4(85Naz9e^%DANH#^0PmcV%WeM`=mFBg!qz4*~Y64wtG zj&ru?OHn8fI(I$-n42!%#I3^RosyVTZiuPRj-@b2q+*?T7w249jm{0(E4B$@Cx(+A z^MF3c;XA!lpmm9$Oo`9APo?^V`)9soV(4uoip*&#vd_Hy^xx{Vrj|C^3!p#}LtFe3 zHBB7Ga~@&It;~z!Z}}x26HMm;Oe_z z^e-#>Q5!^=OqTdoA;5<4?u+GG~c(z;?Nae<4?rlro4T z&}B_F>+pQb)^b^VrPkFTC7H2p_x7LebD~=W2|x69e;T0K><4y`nK0H~wOQ07@tK7j z+pLAG(Ly9+#W9U|+9l0kFR)Req0hlyOf1fPzD1{7wj`^~eoXw1K)}m*<$Uoi^601` zYgEsd#GY+?m`|zo_iA~m_4muuBkT0{A^m-vU-%R<7JimzmS3_QaLh54JS=CSmTEun zBht3`X4(%t#SMg({lGqMxa>BU4!MZ)tu{$u+foCOZ26rU*qU0upBtS0%S&xuWz00w zdHM!38504imbwD1vL2Ak&G_JuOt$o*0sFqIPT02e#W`n=$udB*X!o>aw{!8=dQwwf z>$TdXN!TMMk3g$hI!>Jj3Nuqe6Rh>}NqCd*n5DR(fr2s1#!{Mm8m1`6{t=SJt!S}!XMbx5<& z%mQc=)xJo3IyV1d@ElxRlEf*Qr(~O~k4M?9D1i3){72mV0n+93-F&um^LGBeKvX(# z{Zak_G0mloT2Wi(_Q6(Vh|t-aqdiN9n#AHJecq2vrzT{Jkk|_gf==PLfkaxS96M8k zfn5Mp4B3K70E_8^rW}w=wM`U3pw~R_XB0I*)7`TN!*vW(!V z6wAtLlMx`aeK2WhcT?eF*oBw_?rWI;cy{gL$eCo$%p0UK`0SC^)l$I&vaW3qnKygw z;OvUmZERO`YnBfP;10tUcs2ey=e7o`@?ib^Cn;{G6@17JR@xJ)g+3s%S27)RKAD}J zolB%xEuzZO?4}MVMSY51=Pdjw3doe(oP1u| zl-$lby&t1)dHpi42~;9!&9-+23^qu3SEhWcrJGe^mO&)Xe~LCYI@fChmC?J7%x0v} z!@K%mN&$At){3io4HO<zDOosnUpPcY`$vTe=_) zlx4UZop-O#j`2w9()iD#4V1TnM*@Hp8AlhSbZTOS8ON!whKSIw;-l^L_yYHRw#m}GUiyRRLEwbtsqIpvItolPgzZ1uw-2*V+>h7o1KaYLDQ)8|w z5t7!TyM>#j@0sC}%~|Sh&PJy^mlRUkJ%=nEsmW%*?C@*ua?>-)((#KjY%8#X75wb# zeNJ1W0CmhPs`ktNu&RW~O9a)Qw<)5PuG%NB(^UuLB3TYPm;O*@Sh6_s(yBUWvaI&9 z9F*7Ts>5=TEJvKXXL+><3xzC4OqN+*mLu{yUDcysukMAK__7rM4JRgn^I!J?Q>(Cv zX9j?yXeIS_4@(6Uj9x8rCW$$@C~TrWu9uyj+xkG&GtBzX_&=>V>`~E@CfI^`b%FOvfTz5|J5`r+2_CgAMdNr?XyMzmK4dA5nE(77 zS)l$Th@Y26k5+BoU^t&>zHe1Kfgdk7=yXn577SSMX}>6>HG1f5KV=r7U$C`ugkL<= zot$1Ic~BrYRStR1y`1W)0;l8eAyiIRgA3`p=$GsW31Mo5Y#8E|u;;;t1q}8c zeSzMr@xE>x;aXA^2IWQ59i zw|A!FsKqSMLYE&1k&&|nIGMTVjwkj4(?+@G0w)chq36XM8;l! z8i_({*r|QSa2tidE=Cp*O#WjynCKe_aryL-%jf}Ot%3&pqhcFx%@G+EWZ6Vf|4xjR zll@wj#en3J-hxQ$UQJz7B6OSc%e|zpQ>v~3PXF)j$GqexBv37M{M!{I&J`9cZsPkU zd|nc+CZQ9UM+wu5#cE;0T#kIgRls71{}bf>{9Y2Is*9bOtT^FEc0d6`ls!(j;7P$} z?NNeQ7y#fG;}NZ-ICJ7iM&0IZ-2>1?sxc65PPoZQpP@wl5NjaQTBL&MSebDZFJZ3| z#bUOI!JsQID)!~uX6JL`4S!u!cyL+2SQw?A3<-=609IGrn4^|-%89jxWu{2^1DVR8 zRhus-pOG&oFSKWr!1!oL)fVGM6=DAO6`Wp(Qs}%=^`v3+EekEu1=kVoT|H^MH(O#; z2p?s(1grQILxuIK`l8b2rl38K`3+(PfN5yNDuBqr3s}gEgyjcQP&S3(g;{gMoMjgx z2&9{Xjm+Mj5jG04w1wdI5`@~S9I3?JO-2XcFZ8mem~LtpOB)=Y=^~y&w2P&;m2h4% zgLxe+UHI;2x&rj*Z|H4d8MPop14uNMZ;s7$ZaJjzAuOIqigbX)PbR~Y&kK+O7$2C` zjqnrG)q2{%swVQI9$_pVHkr!@6K^>Dxw0?B^SwCUeN?T7aXonqUxsy#gbgYz-!l^k zm5JSqtG;T7i3eu4F4cwX%C_>`An+4@55j8|LMKW*vGhP^FyWcRh7-fqB`l+!pk;a8W_KgZgLy1VbR>VK} zuuZL+Dma7w3P_0vNQvN(#mmwYObkm#13Cdtw5HvFX-#OpCGZVnJZ$UG6`}XC0XFud;2zwol zxa_|=`?YsuHgMP^g-hr`zXRkU@Ej>;h(kK(&Cq*!}j$c)fKcuM(G)5RaPF(lh2EJZr z|KT2i*}lXj-cY1FxJvy6t2V$j>Bfey%BTull9=oVe!eINj(|^eC^l6C7^@5Hir)#4 z>`8sIi211%*6lBTrss3Bxa@v=rXF(NwBDkgrd6Fd17K_xGrmq-kRG#@;M!<@63}^XJ(-cR<|;=ix@o7 zsF+zGVP}4-LxfHOTr~V~&(Kl&jEqvC>FyIdI}e&WyO|Sj%HpOP{Nz*i*RL2R*noU+L0UFgi)>l>|s_u9x-#R9ksJ(*)={TUgDRh z<#2tE3xO}f5*G2E(M}o0KrTo!!Y+f6y(y-?i$38E?1IG}d+tSwqWI-JlC7(Ak3NE7 z7|R=zxHg5+lYIfH7t&?}+^BDnpv!4Nu|0tAe0EL-`7Z9iGT-05@VM_(hsgcY@sC9U z2s8PAc6@&Ej~P$|FI*5s2PQH=&^&=cML@D5PdG2yTzP+D#@qoK1}5NG`@soxWejffu5s51*8NLPZ#mW@?2K)~|k*XDi$hivD{Ii)EUPXFM+~i5jW;~yg@+FrjXVw&3 z1Wq81rT8Jw3niO!tt9LH&@6jpjxJ`1qultFIN()~Qi7IpIx`dLyv!PpmV*UwMKb*F z;HHU%EFcH%XbuRU1wzRhk(J`<#Dw$76V61_H-CRN@-J6#uL2{k_M;6GE08O2f%yT0sYuDEr&e4Bf}_ z!3%>~Er&xx+C0D8AO?RTNO4Vpkh z{ToI^$~O4Iz778HIU7)^bd?G6!YmjZG)~N857gTKHQJ~)uRY6#GF`GXuA1rn~?msC{9D`-`|0Ly=@4`(h z?gp|;G9^fR$?Ug>eQJB72VMFJb=v_>h&O<$-Hxs&5a>VuE)iOnAwoTWGLQ&yvq;B< zy(M_m2@pv&2qU9eDzaCLR42q0zdZd@r8u(>barg#A85TIM6T@QjWzh6xUtlrPba~5 zP!Xi;Kz;mW$^p$3l?fgd_>2rI#5=PIdkTaON*gp{mHOp*5C8z z9k-r0k1l|I{qVnhSuoEWc^dN?-wfErTMe*sA!IrlLz4=Y1g$#)tPIPyT3!lIBf5N) zC)k>hX`Wi0ST?0BF9PlOlKm*Bn~Yh9Bu_|50FG;{5WUB@8itvnWOF~XdCIPANL@C| z{@usg$8n+CE){@Xl8VRA&^ZpBGNEYK6G8g*J0Qgza)0?(w>d80b*{4hh5eGX{?9pX{r_*z`}5oB=zsgXFHhq?|Ks!i z7;_-(q`q<9_kAsUDCqyE(c>)-n}b$qaiJHKT*#spa8mCEGRcM~EeDGbdu8w=MT`>a zB1aH^>ve=yD3uC(8viYV=wReMxzL&hpY3<*_a)f+Hd7)7&T=lWW=UtzDc5BVdxCd- z6l|@?(a?}&26M6_95e7Ernwt0T*PwvI#JrSX2`BrQBaiibO!J1)9>**rCP_QjINz2 z@_;LjN3{|OsYDSj+}Ctl??~=|WJ) zpO0QFCwN_h{qqrUZwL=EomGybtgJ7SOZhT7yS@{Sm77Z4^aMR?uz$$G%&{nzNCk(h zN-H7*Qsvg}274SVc@*J7jAqUypu<3uXi; z8E9M>eE#r6{|M126|@~AJJ9wk&;3VeyEUsI`lRZAhDT_@%5?~zieu?QYfS;e-dG{h zMSIDAdOX{O0{Phwq&^n~XMDsTD$HCr;6NQ?n-i(As;Uz+rj+GGCXcK1hc6U2skF>~ zdbzTxKf^sO6ykdPhl-LIW`*HBicI1!-&R#Ty1QJ6nwX`tN zynj(Gk5+PVajkucCNFBplr=X~)@k~&a3SE*R3xgV|Mnm|zNS}6#2+`NH`OEjpl+szREo=`AzT zev`z0^A7!dnOF(lDNF=)St)pXZoH3&W(FDj zd@H8?>F?^ao6SfilrQo#g2&-L`F0pFv>Zpqek&MX=3CfkVGlp$o5$QTi2tL+ab{bw zzJ(rlu<8unX;q9ulTv=&o|r53NqV9AToQgz76}i!`m&P93mV5w8WF)CGb?txHEEQ0 z(n#;5(#1Z9mkonM*xEg{b{P;%pdUgpAAAvP5y=qUnUV`tetP>xYWqeW*S=3rU#jU# zk4vxPx46_gYJVUnB+i$>XTsMe^IBzX?{>_zlT|81O;#yeJXr<70^U$nXfDO(QY4p# z9aSYB`}f3?gz_E4L@bPtR(T0SuL6_I(CdIwKk|mm=2TdbhF`{s#sWEaerMnQCEX?4 zQRPIFukwR<1^+v^ z=}HdB+-MSe%;tdoJFnp`2Tl4=5=2n#F)w%QF$wx{x!Pmy2Wm(SI}nQ- zJHnC1j_^#eBOI%Cgu%5UwozNc;GGk=o!luJFc;R1wFgsD@@BA(W@H+Mrt`bBX|7B+in-Osa>QvGOO3kUmz4pYy zIvBJC=6r%7*>xj*&XK$qD)XVY&ZOe}ZmbYiF6S8Ot+SiMtGukn3bM%|n5Y_DOKGMA`)YLPshMtOK6A{`#wG&kFwL|GL`q1^zz6-`V`t^KLnR_j2#a@%#O` zUcav&zj=9nzx?(6=e{}N1Ic{-^7!Y^%g@(5>7-_J@T>b>)HN~~fw8Y#^6m6_P6uWb zo4~xvsM!_CwenRO*vZVW(NCXME8d<)KP$*LUQ~A58{zCK6urt(Sy;E8zB~9qKus*O zMTmmd-2*|3;)ZBT3--vIo>Y!_CLff|rmUzz6O|Q39&8RD5vLLy!sURp`Q#6 z&rG>q{erB*Xip%x>U(5lH~F5>-PFCYnu3+ZCg}>mlGDtrYYya$V7!~L0)ACyTd-8;L4nVGvCk++M zYw7j2mk+wD!qv)lbH#&w`WzcXAB>$)AGu$)Bt&QpLEB_j;ZHL!U5He(#&pY7I;MSU zK>+@98bv6`?0(U-275f@zTxpvVrs|Z)t*U=*hv1);O{d2JUO{JUR=86$iMW;%Yx79 zKN4jTLyAl+SRzMwi*nS8#VOw#Iv&P~8z&c|%c@3oAwIgFaV%4}7;(baRaKW&O^Ht) ziJ2;hG0emG5yZ;|^EIx}vT7g3L|+X#0lnaN9i+J#CK^=PCOJXTM>KHUICwwq;W%mP zN5ROvZyi;pmXuogXc4?*;;Jr8Eg2~hcvDNKq~$u(U{dpD$tdeI9J|5qtTUl%%dPxG zO%Jhc5uO?e(Al#5B=-~2n>zC{kOhD^poxEXZwB^cqa5bE2V*;4f50b3)qX7dk;S}I z*w4eA{T_@CcJ&toD;nK$Oiq@jYNWv!vB=ga;aeTj@!ThKP8H+)=feY@%r$qB$(Ar= zd+LdRHd9SxE@239`tXwd5t=|*s zrAE|6Y7-NE$s9ouMb8Q!1w<{Gj6vmHxl*psrvvcnMUniEnob`r6xt{BmLU1qmFt4D z7iq<~fmcr;E{Q37lD;xm_tRGgRA%)wydq1gPjb%{G=ec5@Y zhSSj)7kj3^W+*l#oXwDqp&C7ovdLQ7n<@X7-^`SMd3O0fS*%pbIlu_u4+8*kQdd9A z9)5gHh9pVem9EK^1Gr$o86S{C&XECzE)Y~ZjrXB`hy%bCqa1^)@cD@a@WJa+OR*oK ztAY)z;@W6=iL~PAzswT=CwQq_N-(yXG=hk#0#tZda%vzKryyD%jzTqg6$h6!_ohUZ zuXb@Ram!OneD)u;ittFxagqOFYY71Hcq*_9#zt{L9>OBT&lmt~)O3;lu1^8lFYOte zU4aqR@zRZkB(>6ikaD3Q@3*Ci?^~M>N?C7&XTbZoMqmr`iaL#O|LnJx=(raHV#Mg) zpXHHqh^(Bi-5Uod2`f{m8VW0$HZA+m>oQk$e>f2Xr?P z{+g|05g0o_j5@!4-*sv7BtoFNfc^H`tQ{I)pZUccN-`l6W1kgEqTi9I-kw8V)-7v% zo>K!BmeLm@I<)VswT2|B#=?E*)#$Az<2ub)d_qP-2S`K2U%sg8NpE}UZ8iW&^J=Bj zGTwWmPa6JI-u%u-L7{PuivSpXZ4m$u;(Ry$V127q*a27Sc?BD?l?Fj#9lyWPFU!zN zY@YWUh{Fy#C1!CBlO@=?l}T+3RTnI|&047kn{PGS<{MjN)ElwWl%r2IFQ+|Vkc4X1 z+27-qMVFU)R(y`*=sWLY50RKL${sRQauPO(g)k@Kf_o*^UY%oTBGHkBTFR$vQAs!e zz;Y_a#=`FYwpJb6L0f?nAO%o)4>(!1@dMM1m!S!6svTh#b!ZPdaxZsbL>Z9axr$G5 zgcCwSy0udSw+5Y%LdD3)szgTihBdF0@KAj|2HcN?6(UyNL5WGEQ)0pq1!PKgguj1%pg}@kY`0d7v?4avQ{R$-Eqkr(mC&qF!EZ6Joc#sX%BOi7Z<85k#LtaxtKiO) zXp>9iM&r$A>2RYN(H^Pm+zP9nnQ)^HPs%#|FOzUiVlL~_x=^3B(wkcQ@hUTIcT@J} zg)FMZag`s0&uom&@Rzt-&kShwwPr5X)Eqk8;SZNd5+BR92Pc)j!yUQdK|FjUZ^dn8 z-r){kc$+-=yEYx}C)Q= z(ONrzRW$j~iw19VbSZfzk^!62t8fxvo%D9UPABX~4u|4(} z8JQzMSyXCztrzfjK2Vp)WI%{FDQtiqc;eFv2ui6WsJ-Xc`u!Yu1rT~o2ed`x@84iY zDV(BR=Q!J?Yd>7gF@Ct~t?cd?daG3Ftx07i9gN<<>$+zOSjP7ML)eUBATEn4-;{=~ zTF*D|+uW9;;!Y`e=O5We!PdL@>pD9A+^>8_ zY4pz}V`h|gJ=Jnlh`8XLo0##Iqj_?l_zAGB2xK4HU?!%@BkMPb=fdS{u zdMT2D-GdW|ROKNcyeZf1C!yp!*8yo-j-JlW3dY_PsAHPY>@V1}#foHorrVknjtC8qhUo>}(H zA4(=UO40d$`*w<{A#qEm__c~2{Zeq%Ws>)a7T+$Vj33)s&DnKpZr7;Ir;wuS?9Hdk z<&4d9z}#O}b)ae{$d z#5*(a_Z~7+Q7lFNgMmL24vn=<<{OQQZL%Ti?DA4m%bTna#sG~P8Kl__>A@fBG(&tb znJP=1Du4QES!O}*HJYEE2FN2v$gYbwU z3~JM!I{U+ue75!>%EskqIM1@u;kI3ccrJ!*6J*kl5Pe5p_sz0R5!t??muKK zWVzrC6?a4psoK%9Q;~zDgtOB)$RG^$Qm(Y>&MUZE*_`r3&TUC49rzhZcTCfThiIs; zy9aBg*x4l24jkyyfs3^R7=%|65F z?8PK6@u@<972>VSy$n-NddGU0vCjR}F2&Vp(UCWm$`j06US4VH>vE^!*au9-1afT7 zV?boJx?c|Pg_d=Q34nr7)>x|MP+3QO$`HSC+$rwW+_9GJy%EVdE>VDEc0YwH zX}&t(Sb2NLpM46+3iyp(XU57DBGs|rXB1%CQt0&N%VACm}EbyTHwt%VhRFQP$&6kVU zT2fv?l3M#(4FA*LWr-kc2HUwX`!yzDyY0}~LX*RVb7(^Bi747(MQ&f5W1m5S^yk!> zzpeI+;_oj0F4$4+iShTqvw%CfK3DCzBU$a)&fmw|t3CVq%jMmFaP80aKe>K~bnX0Y z;_nd8e)96KurMdLu&~hUE9}>g-+uk_l2yG8vgyS-Hg=;R6t;)EKuKgwl z*di5G_I8eyY~t3=wFjvw#BwmL7Y_Qi8?sY+vX309_S3qXyHFAvfF26NZO$Pg2sFd$ zyy{hDq_li28mz(CwIbw9x?AI+7P4=psCEi?!v?Sq+37(8Qq)<=OzdH8HrwZ#R zH5#_TJrDCyD*hH%5wdKS)Zww^sd2XkmX;=#6d-@HV8BUK^weD@b@+=I2m3;|2Ackg ze@$z$Xo^#hVCQ8$-hEUYYCgE+0SIDxH4Zh--r#-P5wOXi(2L66t_vJBPYiL;<$MwC zTL#l`mfi8J>_xjCZTW>+V_ezOSAQPOPG?!UOHZ%5{=`3uNb?)4I#8Hf3#}yHCsm{? z)U~T`C?q7-WgyyC@>4#J1keA}kE6W8KkLDRS-rQzdV#|wAa)(Lpp!Iw*tkd_f-RcW9;e^LqcakV3G^HdpFIL|CFE!&hA zeMXpysGDq(YcTdY57B3Y>STKy?c4jX~tG5`Ha&kd5%~Z z&KAC_=&V}7(9YZXVR=|Kcvd`^x@@+6%3gSkdB&ar`?woTr4BKvqh^d~9fA_&T{DW5 z!SI$1Uf>3Pbl#`en9kQ|oiEYAU07&sKk3>rjFoZKe`mgGo%xb0GDv={?T%$&dd9Nn zDDBR7kD0hv%|%Q?n9coiVHywQ*=AEX8kG;?6~+w0FF3$hi)Z3$M!N z;A^5DDdOi+L>}2qIa&xz3~QC-#PgRauvC)Xp-GdTT&{^#vUaiAs?})L6{=M9)^e~g z`f%!L?i!F-WuLMJhP>2g4cq{W5lrQuSOcur^`a%C>-Bt-Eo;60-8I7c5Z=G&pwu}` zMR0}yise9_$^usH1Z#BYeT-t>kB?5wFCa}kQXpC2Tg^uAOpX{iTl$TQ6krQRTTZ7f z4xc>W$td9%gDa^|fX7T0qXhl#BfRn{=m>6_>$1uA!73G&dEvr&Y5ObAW!+8x0lyYV zTyKpDb>Eh2b;rkW5Nh)ZXOlvjC(`g7Bs|i-A>$&2k-!3DGzR;GVrc8X-V75~wzj;G zO{4pT?kkIVJaH4>h}Rs#Vmuz{fyEuV#TSfzmU&M5Qxbde2|~>U@BENeFhPj7=JQoC zA$TVgu*WDTV2hp?Y<*Ck)zR@Os+@59U?BZ60zyw_DO0=@7Tk1JZFGCCeec^UOZdm0 ztUnXnvmhX${EF$;#L9Mc$D;zyik20N6#m)CHOhT0f zTa|@S)2qQ4Z)<%SC!4c!#@FUVZbD}c$Cj-x<79Ie8vMH!Lfbhf-2ct21!JFQtvWV* ze$ScI65W-PU6EtTcbvdY;eqf|QQ=*C7d>KOHA%nGxDq=>BMv<^lG#Sv6DtrJG}td2 zlY84_BLJC$ttgT{mUUDn5y*l#iiV3A-oi;{yhPtJL(ZY_1`$~G*u6Uyh0fYY+0tke z-|Z=bYial#yn2@apHO<3QP5!To+vnp#LS+z{qG4!8pqA`MNT=au7XSO;c#S2K3z>L z_kH+dw`QkW9gkvx;NnPCgnKK4v4NmFu2w7#peAwe?r9BcoQ)G`;7Hii2yK;9;~FZ%(+(}heNmJENMcG&DoSz zZpq`7u@|#pRXKVrF1-YO6Ks_fN-y;rz)Fes^b4-~H6zL1-t&63x(f)l{!otinx4d* zZU-IJ67Yo;J?0PI5tXlVx~&0LNAy`=N9S4M>gaa((DB^h6CKF$Yvs(FU2Cs`bdvr( zYBEIs=8rz>kI#`(mUMJp@hO!MS(!?P%U38F(bG9FTSy(eG^)XDNWN4az~BqZC!DtW zVtyvEY@fFq+E$`MztH(-UWnpCu=O?WPS%r}ZaOngSS#gkKq;)QOhx~OgN$xC?CZBD ziXRk`EA(OT4x#1N?7=@p4w2_K?t`VX>AqF8+5h7is_26Hs z@?aIdafEREu<0$xZT62#o3U=vs~~m^_MS>1<{4KMZlcOVkbIy6!*4R40#35D&Ox@{ zr({GGTl%;OB2>J9ELTcy?!UON+LO=U{jXJfCbN#@?+DNC8DQ`sa6;nsg)ii^uD6!> ztZVN>QsuiC%2oW6`R*V)jk z1d+`m7uxa-!O_HIUMn4&snpQQC#_Qm<=C4PbpknTWTMzT(sk?Vk@XfnUo@p5iq-Qg z9#iTZj`?+LBD%irWeMXgZ40(efMCvQ&?A?d5wa$iZpg(kG#R2hbFoSyF?}YIf~Z@) zfGkO037Ptnhh-oUCjn+*-|?lJ#276(Sx7^jE1gtNkjnBV5(oG?OTHvyaw}!0YDdaO zTPG>>C4+WW^h>3mSXx%rKG3>5e~0B&tvz6zBe9Hc+($v;$ZvLb9re}=T5x1A+NlgT zr+4QVNsy2}nTp~uVZq~#)oF>GiHkiuV(mQV#5amPf++v3mkQ)e$1NRqjiKCNbQH_6 zE6?HYgxAn}NmT3oAmfC0Rw5w<#W#u0+V~e5 zi+PKckcmn7<|ce?-jA#TJP`8C@nwxEd$uCJ^>Lm6N%^%R&o{vM2m>wXyh4cUV0pP7 zT1-)etAd-RH2H{(qgQLa7C5ff@|F=!c%!t}n(73!c$A_nt)mJ|K?)6ZH*K=C>853@ z31FP!Bf-{YNp5OcFOy^N4NEsUCR#dS0#0MnYU{Mb3Yd;7rQhlbnb_CY`0c}$h87l* z7uS)JGPQ9JzxLPKAyT!hBX7a3i2jFn=_&5swLj-%8$&irr*1M%Po)|iVX0MH%?y|! z4!s_{<2&%b@wrDTLMcx)%Otcjc*on4Pl1$~;69zapDpOldsZh;>(fD5JKDW3k~+J4 zr#bDshWB(&(mCBr5LEBTTR?^r`c^l^49ThF(cL)|%U82XWhW2fwd_0lg}Z6?NjG2C zX3WR7kxFF;^LAL`CVy7}HmuU)s{@_FJt{wp$4#E?dFxnPY)E^=6AF9>;(?R*otsEx zZIs(YVn4shnEXnK)|K{ol1a$<-DWkm%Y?~lQ~FSB{Wl)d^eiqEwMbd;8F)mI zuiPs|?oKrj-$Oo1K}i>7njlQMU`(S*I7?3^6sM&8N}C)^YpXotIh36^oxF*d41H~V zLOng(D<_*F9>b@4XI|`&x3b_i>i$yz$i!s8AMFZByB7H|;;&Dg1}t3txGuf}6pVo- zzZpPG>OtCz%)p$h10%`O0}`Iqhrh06;0l=}0^np7DpVST}igAk^izxP%L@NTJ#%Y#*f>CN7W2C@YX4+b;x@2d`(zhNQ!s@)zszYc z?RwA%`_TE^9zU#3<-yi-jw_+-Yu0i7I{;I|ajXOO&tO7)zKq!Y6x_*fW5tnaVOB7<} zLLtO6#GDbnS`JK*5+0zTW<%2QLO3Hh+_9RAt@%Z$3Js1nmv~SZiugO%GcA~_wB9S% zRCobQd8&WF8VK4N>VT!#A#Si|H`u2=$sV5pL4zs<(oOakJqCG@dZ6xjk`9!>@GGk+ zK!zIei2d5&wJVmG2ri!?*Ml$A4j?<*ROt`=(6{&G0pK zr!KRUB`yz?rtM_(=wg;!~6ZgKP5cj^RvuNM; zCxskL2LYdD`=ou2f>(C?MwP!T{uR$O?Hg?Q_Qrj$SyT4<;!|GpsguQbX=TSnrj=L5 z7o6D2%$lUVgt}AakU#NFw14djt`;cIEMXB{*}K5lYsURsfW(*#GjTW_V^aKtxRu8R z>1Vh9M!!wWvJ<#uB%nqbP=*I)q*tgj`x;~!JWaXj6b^fThVM9jj97F?;@^;X<%$wA zgi*r{7}qg@t}n^GdXKqI*69jXS}%cHs2nDvMiPam0D??Sxee-3W;V{5hFMp!90SI$ zq~lZ)!vow(Y77E))xdPa2#nFk=Zyq|492D~cdi8#0U3IY1|7_719{TwbLEHAx0#`L z$4m6-quW->_(m>8vQg73M8s}~9R+^un4c%siUcD(bS17F=sldfUp4{4_OP{Jr>|Yk zM|9MdpKd*&XtHm&(4*rDmJW&9em>lM$a+>KoanGm+3hRy?e@CQS#47IB)+iTZ;+r- z!PZA;7Ih`CW_au`K95{CzvEN(!M9{Q=k!?Pt)yWxd>#Cni86dAfB4*$HxuP@oONB1 zh(4^!Lo2WFdaz5cDu{0$1wKYnvTGt7a0yG;B+BuieM>AKBaK9h2i``z*d3_`Q>|^o zw`YY8m$r;DZHa|-CNzhN*wDUo>{-~=ef!sPa03k&70g7ZDo^}JAo~s zuW#f9^T0bSQ#enTL(>^qrv21aLHy9Be&L@ZU*+vPpQGI)i(MA!l9V=Pe96m$CSQ{S}a58KCikqBnQ5=ANgR zL}D&wJMv5W$F0<(-bvG=ZPR&LYs+ZIBrOBpExY0Ya!YyZqy;tIeISYJy>mDp6j`G2 zoMldIkw4Lk&RWfuaV8aPJ)eUkO%IDc2l2BGNbL^REMbF!tsjs|rWDmJcA^v>aM11z z@*E*hk;^&*(Jftl99k?IbSQu)YVf}&*t(DjsZ}J(A`|9N1$%!<0iWYPwq{VVgk7|Hy8sl)!`k4>WYrS?80J1ofN{k%@hdDY<7Y5PKu zi#?zfh0$i)6P}3yc5Ae0aWsn4}`FW?EbwZGMw6 zYoq~gF)Y-7`Lr@{6Z+=yWf*s6aN{$b|CjSkzr?J#&jnw?E>G&D;NpY2EA*3mTELELg>`x&4eYUtWBJaf9)thE$c4w6_`Np!z zH?aBFUMy2J`9@LNoqU6VJU0mB5vjSyo(SbC>-q;ep>SfeBo?kVZ0r+Kf?u7uw!MU7 z=xmUzY}em^9zN=Dx$7lbf!Ir$-JqmH94G)%eUdRMGmx(99Laf)l)-eIWFLK-Li-L= zpR==aC7nl$V)%KAqF4&N&8va#D;BR{A*AX#*GYAK`qf{ZR|*g^L)y7oXFGfAa#AoH zeJyVV)y9dm*#Tys{ujxDu~N+rn!ApTot|^G`L5*cL0r-uB%x=~NT^vE3S80!R~t?} zRDcLOgzLXw!9oEB#Y-9Df@pg$3X|BW?}xuaMEOIyquw zS~A%zba8Gc=TLeeo;Cyb4EnL)saEA<=J}WI^TX!(2k!Iz=J`(Zyigp-8|Ju7^BdR6 zT*?k9aAw~xuh-3$*J3J{uJ+T~$GISKX>$lGeleHBSA3&zfFo7M`?PyF%P=Lq1_I%>m$oR2NL29D?I<=sapQZ>WR4kAAMK7z9AW#im5NxS@*>iH|xp zpmamg^^_cK-cRi8UOb@7iPDajgb!yZ)Q8M#R}j5#cD;;liS&IFjg)hsrhq7hiK~e( zbcw0|KGXV4e`2F$sn3Uo%9atycmR*uS#85>?RQYXW?d&MpCdPTMZ3A|lnZ1m)+@g? zDMWtT2ha@?5qAUFm5t%FSSD9OCI5kjv%i+JGHq=(>&KHE=1gU;y<53gT2UUa#iP27}2e9{Wsk{m7D zG0D4!kC6il(gg~(enNqL>VF9;X3=t|{x6;^`TwMTw?Jrwlh$v>kA*$Ox|x{@CBq?W zPogPD`3q|XOw#`RzOI&6-fsJer(`TyUDgg_($wa-9e%dLyCam0{8kL})cP$A?_~*a z-hx#CIfZTxpVesPG@?&}4j!`NS@xH|3Txrz=q>?1N8XR0ik+J0K~~q(65UYfhY^j%5%t~h-+HhnibeJ3&vlUMXI z%w2_Z*O+XZ!fj3tv@Uvz(5@JiVuh-DG}aCbJT-mQD3fiqW|szOYk5klHxBdlTj{U& zr|-U(zN=1G{!seee(uKdmux$berKoe1lyE!oRs?$n2P6?{Z(6#UO`Av-`>{ zCwaBlc{Ozrxj2kJZ=RD=zowN0#}_E$BgO>gcRb8~c7L;nS(;x0rnQ_x)I~P!s|4iT`zaZ|b^i5Dq*MC5{Fw zH=dK6=qsQF!A(uR_%~4*yd^JwbD->)HOcQy#V2BnIw{Z?pIEqAL}c8Y8o#+Te)GuW z#3C?9PjX_h*oh@4mhiwu$ja0YZDuJxar6%la$P8a&`l_EnMw2SxcUppYiae?Eq(r5)u;EG+daB}i*Kh!fD*BJKP3mBvs#b}f- z_M<+Nm~jQ_xb_IiOw24$YZ=5-p%-RX_w#h40ekGdRB+i4YwhEFKs_MdCO2IeRQX_I z`0bwQ$5LBB@s)X*izo%qFw+=L6;-m+~2 zDF!}2CT9tzCt{_LSSvdb9dxnSSe$FmJx@94b14@+-fmpKcYQ`bVXZ7F?aNJq%T;%p z@ZW$#kRx#>Xhx=Nr+3E4Sp?*Q9se;Vh)4Dm~SrZYwD-x@|RiJ>kjrTZ3hsVDZlIm8a~-rZGd3&`mHRU9P&!DU#_!Dm24q z$K0e&(2(?&r9UE1u;v$IAMfyBK~;v6Pr_up0xE*;ElWcbYaA1hBIr61eo30z3IM@7 zFl9)!rJp$O8CRVf`Mp)^GX_0~&tO&>NmzI|S==(kB%d-zqz9Q&p|j@q)+rS!#U9XN z16zDm|6@;$yHT|AN zkf`>r$$@otA;Pn>R_9f9kyipSyt9xJ7dXzEbrBSKQu%8p=1fG}>lIf6R0x;j#f^@< zEY$KC6Vx;$)FL}!Nq~_Rzg|Kx=4n;%QLyJfk@o|Z@vT4Jo$#_Q!?8E1>n$x1R zLWsoz`)#xhvfr5%Y+dbJRC{ZMXYpXW1Cf4t`*BAm?Vr_m_9J;RFht9-BE3K%zhUtmuC6Z?9^+$?|~y;@&p8lG24M8ynmHm8^20~% z6+VeyvYS=?M;3?R?ktdg2nymk>e$fn9yFQ5BR{cCW=I@ioY9baLN=)cl>Z7caD%_FFY*~TlXGFGe*(S~UZ=yT#-p*AP=?6%?Z$u7W=YM`RlJ~LCQE;ReguqRw&*mN;s zE1;P>5uB14J<`AqUp`DUp;~j}V^10cPsg9JcLu(8Q9c7LK<9mX$Os+j{{L{G-x6yZ zjX{Sy9`J&At~Ez2Z~9p{W#hik+y~;hy07|}5oWE|Aq)=dH49j?q-Y=TO}E_rGPPlh zuLY~@IwjdAFPJ}%{aHx2xV~mjx$X6KAG}s>lap4vLC=^Ed4#5Xl;{;ha;rt1*pCZ1;BmtEPRQI%|g0Z>O)QT(ATDgKbJ<0n9`}-_0YK&JN z09zy8=-+r2U!27)bn;S>SHm?;RPK>AeKFh9s(F=xTZ$}UT;-m~;0Aju6dQW-%a!R5Zmt-AR<9VA@Jyg|M> z$K1b9>NDf%`4WCbgT3kV4wii6eM_k~<}NDvIt`%GOt1GQf-bgB-N7}wd@=s9Pj>@O5A+M^2CK{Rx1Mgp$AV_ zC}F9-z`5WY3#lihzh%$Q0$*ppI2b&7{=rSVhg z$XP#l6QR0jb5BmNEy^9AKI1Med>o2@<_25z&!lB9Pz+6=&1beB*@u+p0H?T^9NWJ( zj%;Gc$^jm(=0)Xxwi_N875}l1jprkh5X_NYQHLC$0^omTE1L&;!soIWjT0z8GgOJj z$3ZK;k42gqU1x{?q8#+~?F;8Q&K}IcV$Kto2GM8S^F;l>a-OV8pC_-O8XMQw(uFp< zAALr5ewav)^Zh!*jsV4(==#*A^h|HL7_@#SlNqosX#rCmDe3~8Lj)^cL976C4h<9R zz+QX0j{gwrGU)kzX@8d-Eu0!Nd<$HwQO9c@yu;&95c)&>3}3c=1bL;E^$m4I5slek z11Q4s`>6rj>_!^AUXmm;{z>kr_SP_3glYbo%@cXgS^jn^b88^Y%YVAi08!@ z_-I&}Ql9?A%tdxPR?e(Gl`Z5EKD>K$&azPW{0Y|&8jMQPgloP!Iww4E!qBge&I!g=ks{eL63D;uvsW9Rl-skO zS;3y|6O!x55t^{9|CpTcB@+aTj?9j3Y3G|Sa<$bWt4&>fH(i0IpU<=8Qd7c1QlilA z+fCx!NO8*+Ni58mNI_OLnkW+}!WneNQ^wNx^M9#GmM5xi1RL~=KmSA_5~BE|9&4g6 zKB?DWb3!HwGHy81y1$Br>cuA+(x!ga?S2%nR+Ml*LHt`x^&!(JOGa%rIeN@PP+C%U zBde!WcH1v#Dv2~jPTOXAx}8{IksbK;r7d|$=?mydX>ilpr@5<3d|xi{s!@gy71|kI z5#9C>uRM{6)%GG+?1g6dy&9boUqM8w8c9K8tu|o>mbD4T!CC-&ON$eb-zLn;^RYvZ z^m@(b5RochNHx%aNFDpqJ{-y*;G(E!|CJXGMIz62c4>*OrI)R3Qj2tXn^Y9sGz>~Z ztIToC`hSV)jCR3V@ij@eVo`9D7p!LyV`F>(C!*Vw$7}3Q6P2jfC?4TJ$$#u;!afX~ zx0(;oe8Qsp?9b9#+)K;=Ikn3w56~!U9AFu9Jnf$gS`>uYvYgze9 z@uAK~dBXBsWi zi)H=H=fudK+IexEPF<^};o0EjMLd^mfhmK&{W+L+_tO=$GWyhAlt}PZ1CD@{+pVZe3T(I@) zKoBb`8w@l92aH3c#P411jEyufxOR<9U=7x$lFT!!Hc|6jS}wg)UL`B{n9HkjL0Mp* zC&#{gs2<*}>t*Pt`3b(tJV>SRD7vj7^{L-TUZw>3Y_QcY?EulyrUYY0fuGT*+DVXW zMTMPXJtb=n3_lpVjhj$UF!~p+#(vB6TGr{3GaHPyoV^c}T>_z_jq(Y&ueB5B|a1(SaU=G-QOAz_s5ruTg>*U@!U{zchkDA zZ{T6_t0$|mF;k;uY8>d+Xa(Bb3Xe%=`c^pM?^U>-(YF8J(NA#U@tj@v$|or+`lgNq zjJx34U($TGjn0V9&Ob3R-!G7fA+|R&{q|*;3Q+JzFMpEewBiSo*@EZm&en4}Tl=Wh zovqKys8#lcv?;;ZGdWe$J`iY>qbM{-7Fn%0G!o(FZ5{NRUHOBH0 zg7+Zx*IK!$OY&3o=y*d7h1>8s7jCJgrDe=d;u4c%aXQfQi{Y~`rS zR+jGGyZ4h+*3ub}R7fQY*Tqm2BrrhNIRo?sa=6^tG=k!I0+7nAy#^*9`NNqnt6ZSv zZ9x*edkiEOT*cA@I9?T40v9by>G*7=k>UJXdedJ8g0t-(kX?pjgOvV#*?S~u>5@|G z>^q!~7OxS(E+`DWzV!6F(5XoJWH)<~Q*af)KkNt!n{3ktfksUOSp*yVD<<;!%pucs z0k79WLlD~~;*z$JQ$X5`I&ev(|H{p83oaFw0!q1I9#7|1{N|-426}p*25qJzwcYTW z3Iqj8ZQ4wp#LUt5K1dY7gMpbKfbW)P9rddujWtsk?83kdZdzrI3+RFUd~p~3dyqJX}<-JOVD;T|(-$dZIo|Tw&;vpb~mn!e;@g;((o}1>(3YA@N z&zj~?*@BRxLU1TP40Z8zqJ7?1C^|H!ciES$RWc)rQZ}8TDCLr2^>)r94ub@{ZK^_1 zy|NqAqYCQvewb;}9XGQ)vv`Nw83Y)Y1MR)gIW8AcUfK_hIqt>2g8ooiioNNeRj)oR8q9;?88k0MbMZ zPkR1#Q(Cbp?G#hmU{hMYmKG`6yb>7D7@L?E88pjoeTY*-c|F^tmNbr#Rlc_G@0B=G z+Sb~>yyRRDN74lOi4@MV&v%PXH!iCN%5OPH0`%>1Jm(jkzgGK^-}vA;1`>%I7<&5w zp5$!~esyJcHL<9l@nXM*R$!1m!>Syjm!5!L4(0L9g?COFzujc=UPfLkb{oZ7qpiw^ zEPrS>4jbhqs=R(eRG>}kr!|I!9&CM8z%%~j$1TS)JSh*}L`?vT4AKR%(ml#2nxCk7 z(SG;WrtzrJ{fS#%;e{w=*J<#1my|O1ffZs!)4D>|G_@$N+d0JZ}R^{*TK``JyaSd+D+v2wGw8l_Jc&HYH zpF0Z5lY$~wVM9#=gRS`tFiC=~$G}Fnn7;7TEl;LDJmK&twZ-&_r&oEBetnlVSUbyh zM>~B@!`T0Mq5Mc81`YUd;?~hmTtuQrkca{!|DuaaR@|K%b=AZ+ z!NkcrWyqAhKg2%AVV8?-j__+#Ikkeah$DzthLvFtqYPe{hz$<6$B`^#6=&eD(2tZj zmQe>MBSZlxq*U>{mY7=cXWtf)Y zu@6lw&JnN{{EYkTepzkIsOLCC899qncHCoc0yZN3&1qW^9_UD>1q%^ZyTd*N#z<7) zxo`?|Bg76HjjBM2Z6UAECf<^YV$c#g_9(wH>1N>c&P2SkhBPqUB~l(RlJ-SLaU^ed zDN$bB$cy8S8eOAGKNV`kM!Zk{U?rtnGZ!V6q~ei7vBN7kG5w*!j;6fOwH^MZewbq3 z)*ae@8-Cz?_TCF9GGio_DC_{IuknX=(1bf%DG0s>);gcZy-bgF=YC!!E;&6O=@m#Y zz*-h4gMILRS3j#MQ1)TH;1PF?rP0y_E&fMkeToN(kZ@`a27&J+5aC0aRsJ1m_?$zb z-v?tVl$6+|0G=hw5|%6C zF9Kdv!Q#5$w)SZI528J%F1wh>XqTLGwfK=L;bJuYBw^R6WZP;dQ&)*8Uf&mH(wM5W zSfh;bNM`FBz$pmLPD)X{T_d33R*L7etlpe?+y=fkC>hNRgq;B zAn^iON4eQW`0Niqn<;U(U5O@?mhPT5_t1)m{!@>Ow1a)_4Z^X2NV`{Q-j2l`EPw@$Y(~hS>_@7gSr%}8*G4l%h zj}SWyPk(D5+>D!)n-TY#y2bN4%3PL5Qw)FugEAMfc>F}VmCBmdEtr>F^$>S5|2hWA zRWgA}vr6M!dB<_<6_AYnbG^W>M(g6n=#Jb*VD6mYro*`MY8rwDc5b8qlYe}a@dAwK zSa0~kH96yRBV(e+J_(ON?`Qm^oJeW(*s<^_YbNH7pOhOp4MA1dzh9Qj=#YL zpl(`2+A(1I4SFbkdoi3yRKY}UQ?Wo3IvtK~Q6@v8MJ|zHwW(jpGE05aXMYja60m=} z&Rlwm^gQ+Jdc2>6qK~oadtCd-&%XU07?{4Md$+r?v!yCKdkHDhX~MCaS!~2(S7yXx z3+26tj|AZ2Ac2GM ztfj8Dh`LC?Lqf3aNAxD+B_ZDIZy_Nm{m;mSXZ4lfCsmd&g5`hvSSFg?LyvE=UbA*0 z?txF}B<>q;O&Q{g zPZ>{a7GKj}(9*zN!B8q-XsBZzk}Di57@)#7a>Os@?nQQvY@FHRzIT>=(WyFzJh&6+ z?kwvA*SKInNZAPR5DqV}o^{h@?FX2yGT~xKtu!_ccxdWB-WSCVfnAjs8B%YbbGNfE zf~`@q*V{h=O*a(xmG$;1SAmu6;SnQ>v`GafA_(5UnHHuS5$kUJVAP+Y{gTjoKHwG& z)0OfW)wV$G`P6t`8^X*{l*?TP##e#*?dUO zF{1_QU`C0+C&Hp4VVy)-F80Ug2dwM;zVHoo_W6YQVJ4B&*BPx@G>y2DjHZ%;bkYI=yCl$cp$zjvmbeYn-+LtSEKsr{Do;x1kg7k-&<>8*){Md-H>=L1JB zi!86TJNoCdo)n<)%G=iNj?R9u!qwqg>#T%tBkw%kec=Z9S$ekm?9XY%;Y!X{Ie?us z*1`bEMr$%@z3n&2hD*B#QlA{b%Y03%`{L+Xq>Q_{&G@LiS0Z>f9QkdPKR(rO$Ivr0`}bTk=85V6VULm(vO-br&%>O=wOBJpt$By7 zNqRNv{BS;{PPu>9Ot4r6^MMYlBWwSlp`{SC5m9`+#Yfrk7C*nMTLSu9sK3SfTdKcT z=hJaXdn3Q`mQngUT7SQ$zc=wqWUa71gacB+a(?42x9RUn{cY9X)%yD_ z{k@Cd)h+Av{vrLnUw_;6_c{ID$*+3wA`M+c{}Rn>YNGR_M0N z$~&M>-mxa{81~z`&xqbz_`K9csn&X7M^uJ8$9@y74L20w;FFc;>r5 zWu>sTO7ZciOf|_aT3oWI=hg}|?H)!?Tg)H>rn@Zjp$!TM%03G+v)trcoRf%!MZx8T z+Qk*3tXpp%M3PRsLycJF^98p27q(0Rg@@Z0X{fi~nU9n$b{k)qRqKU#R2EKyeIMd2 zG%U}Fh6YB4n=UUN!YFJzk7gYX_pi5qK}ND%EY^Xb(F9^FIN*-wmYUC-3Vth2OrJ!D zh!*U$Vvk7!+E@INW+uKyH)kyp8F{UG+nZwAd2h>B`BGa6a)-;tSSt_ZFC%_gKUcl7A~ z!q!vmP86kaZoF(3uUMP=zkhL6*w5k7TIO#r>6^*Rz; zZzOl;4UB{t<8qjPiIu#Iwim%l**cb6#y>{`)S;9uQzgr|t47SQi9F0jSBdzuOrHe6{qR^gYrB;Hakn-yIdY`v+ zf5LtUSX)K=D!yLYL;Il*CWN#H%PuupteEu2`@AxGT@n zCk|;pMV-TTIm?PkCoJt;MO+_3rbl;_%i7RYWU^Ta8HsTT$q;NUg4j2qO&Wc!G^yQ@15cLNSII2Hl&nBFsL`hcI>}A4*bG#rgdF=;040tJX^66}x06yD8*Kd*ca|#l37nB* z$BM~dp>3k~GFA?Zj{inZoOo1DDEkjiQy!F(o_d&96eb8u^r@%KgW%^@RE`$l|IL}< zNc1wR_}()Aad^$|O)*BitaXOU4zvmw6Q0RL>NxP?p&0VL-YD;`}3WR$^;0 z55`sdMj11-ZgioTm1^nOvg~AahII7@?|6{iYw47WX2r$3OxL1YWokXE6JO;LzQ_#6 zuW~H2Dx3k4wP8}Mw)Di-@#q?$!x?Mjs};M0-wwNfY0hdrkHQP1TV=U;u;$n(>j|hY zzb~XV>bg?!p4up@%ZkaWs}~o-(ukaRRImm6dZ6_hZ5Dn*Ivmh`>09o2K*6I#dA*jo zc$oc*5;ygl4o(43Hasilr&Bv8$FR;0*i8nB1HXk_03(hb^(`3&wv<1N*&`IS93{eX zP@>yAVaB8}qa&lEM-h`OW|c;7g>H%oh}*GBm%zm@Ta*3qRhwxuwldM}Fj%^~u9+~q zZc1G+Hdb;REehW8xpan{U*~|<##e374CH*&z!szy*XooF`#VT1} zXbT6{_=Z;jmIsC2j~rSs&uM7%SWfr@T&&N9L9hd>JHa;8fyy7zaN;=1Cb#>upg1k# zl?W`tUj%`yCDRZzGd1jcV>lLBG2vCx^ z20mAA^x51-w#|Q)?PW6eew4U5SI-OROkzuzFE1q~ zhImR+f&q9IHiCk~5LS!00?)+tssWcgvU;EzVywN?zNSn0GrW2MU7anx27hQTqz&qA9+R7I~d!| zLo5CScW{N=KNjt|K8ypQ&xHqWzMfa6?H2A!b|8H$OYZF{+c~QqJqG*LUs5nJ!=4MQ z#Kf*4oNvDo`Ka}&=L0B|JR$E$`FJ;ZrZVsohmYQ)`YBmAcDsFL^ZMH7GME{&-uhj+gv0Ugu^$rN?Us`_JI> z75fzEZ#SoB8pSvy$LC_N$o?EE@CKr;F7mK}5^X|`a<*z3Ns)*hfVr~B&%o8%9}(I+ z`rH-fomd2o7M{KwxC`u8UNe;m|D)yL=sxtGoCxs;-{c6Pr}2#kxIrdasLpfsxW3P1 zhPAdRAkBy!B#BdL^idXL+iK>SJ=GA8y(+oe!y^O(kW`}df?Io1@m;X9{k-iI4zbl1 zCKqY1>zj5AX%}5TTgVrI7OVy&uOPl=3a@O&2UP zQ(t=EvI<;Ifo4>*tN9{t#p(u|ofF~h^NID+fW1c(w`A3yfz+=7rYGfQ;BjmZsk1=x zmt@BGvF;e&paIFUDm(9C?OI!90szUeF0SZ}SRX1>p3k>v@;I2BZ(#C=!PZOooQBDD z0+Sz~?!x3V9hh8aKV;tR(6R+A=bKv>GS}HF++4^FR&JFh08*Y|mINS*JE~8IvI*&> zwWDmiKv}akY5}&1eZ01B{29w5Axnzsud~0;o;E#{a$6|ZOfn$?Bze7kN#FEeCp};= zAq@gGsk|>*%J3VtDpgm82JWc>w%edcJlYY zOdT4g1^_a~(mfE}Dr?z;_NK0;^7hW*Y)#`+TDQgR&~1jx&L#ttC~~$q{nEe`1s=k1 z$^stpXn;pv20{Ql=JMXsUwT-S`IH78a(Hjw@=*U79{XBOk<_IdxCMrO7AgXe2|VNT zqw@Ju{rtH3?AOmC4_5s+?1TF3f0`(!pGYbNS_fO7+OXhSL+a~8m=1i3*GtD`fxn!D4beiRt zX)d6}Looqy&s;pQq$9%&PdBR$HbLcMi$~YmH9rwZ3a*nAzDbU@fpkNF$p5(Vf!USU z*_TZd@FB#4%XhkeuRF|}m9aIh^0Dv*w0fY7^MkCjIy>itOkYZ8vMP7d_`pi5^2LO2 zCrTwx->qi>Q0U1zd(;s4W6dJyiC1ngMEE;pAMJh5`%%YxJ~$6m+VlN07Z=ysvw!S% zJ#y{JZGv@pA|s;B2Qg_vQ#IbEByVL;vQ6k+`4?{!0vB7MC#}lOp*C5c7=x{q^#+>e zLuC(l@Q>1!!cKLg*ZgQ_sc1Vb_5%vZ+{NvM9LL=5ud`k<*j6#7 zjcIHPqRe1h8&AZxMz1i~7M6W}TY@?pZ0lV6-5>U*su(IlRqaS^5>m%{2_eLX>_CT> z^Lgywl87__D4~L~JWMM-QcR}ImP7tveEl22qP>Ma3MdkRh|GZe=kQ~)kfjEDY>m`W z+6a#bK#gTgQW2$#$mS-8r%>zO16(q!oF}B+_TlNmz!0VhAnuSP=!{>X+4(?*YTjdQ#xIazWad-)04U5r zznpaODQ!HPv4A$XAnonnxwU6-Te;KmE@aX(<1P3fYfNyyOjN4>Push|M^#;m|4C*D zBaF;|14M~R6)Ww~UMf_9!I}U`Kp`YVGFXJzmP_+PdrOlUz?MgF5@0wz%I&SKy{)Z% z-Fo}9^#RsbLM#)|3V5x>(%Wil^MUTZMcRB-oj_W-tYJemSJoX%yU0-rDzj6kKhIvA0=?2N4uHlzw~d z;5#sJj=K|dlz>&niJ@>rSxh!!`3 zj5Z*2X2{w*Wp|wN%ciBye?B>A;Qs&Gza_#|ZvXxybjaZSJE@=D{Tsh6eEy&I@3u#; z9}H#kXrOFp9+ZHx8x+dq_snEQsN-LO*0x8L50nU?WyM4bcg?m3(*XpvlCgvXn0-K4 zIl8}R9}u1qXBSyfkO4P>Ex|Zv+Rnp(VIH6-Kj97h>D&2BXBDO_&80LGEbA{qlRemf zX`fe;3-5#J9VEtL^@1rhjgRO1d8^ycz4n(Lr|pZHi#VFv%l!?FrhBGm_02Q=vpCXx z0&^qbuS^gb!jGkPmADBf_O^c zMwVK)O7-@m@+*S!NpWpSgXyekwyli~J+MV4$!*_>L#Bn8Vt2VDn%mZ$SpLvb6}Xp5 z7>Zuy`;1Vt?e4mI(*iKaMga|o+s=+`lP-wmOPxFR=0{VET7{#VDfrvp@AHpNJ9ndTE6RPFhU+%TaU< zC;f1S@M4AgqsXFsrf^{J1o#X}G(c1el`v^f=X zzsUajdYaT=Z)xXJXKfMTu6=8pG&7HWh)r?xs62%`)2+n_H1ZAtG94<}p?LOix1xBe zzkluWI$K>5IPHFDa8V$cA7@?2j_uWKE1~Dkr)h?Ws;PP1B;&ULpKMA*5KoqakazC6 zy}NgW3>d_c8L-2rx-Dn|>Oy2+yY=L^dXE{_!TYW5AO8UJD5+w=2~~?8_sBvXyONEKhBnxlVCQh}wP*_Gy1?+nDZW0N_)irV)I0>xAh!)C%&DVO6XUW9R;)IuSvq>K=xTs#d&fwIzXD`T1R&D*SHr} zEe7=cQ}!Nolz)VkN5zRh#61gcXQBX?uzWcyjLQ|#bXplwD<(313`o9O(p!C|Qb6{4 ztLoU4y(`nf8nN%g3(V;B=TMhAwKZZ!`5{>+@$Bg9QN~fnALCCKjNJMQSM-9Fb8=kV zx!F9QUSK1bP)ibR*9KD24zq~H)j6}@Emu=|cHp$O<-z~Scym~0>ZChUoBTz|8a*I% zGU$71{KeVPI~d?p|M-@FVsa*N-+YKqb*L<5pKW$zYSyB^6y%|3gZmK9KARr8TuV0d z)09(#1U_`h1%bMPB_WSyNr0NY9gQUs=DnrA%>79AOHNJs1VQ$zLSrXGpKJM%!R5=N zDgUf|cFHf)^5vKEvFk4=+-OzNUg&#Q!7rRvNA(g>wA$Z0Z582itNlt`)Hz>zVL)t} ziYV&AqC-O2V-nZ1_M8W^hrx%juC-{!;t5#3w>>JXje_3GqiNmW;&ey=@PhWi>A+{o zsSbQ;@9F+)L_ODdph3ctwnfXK?a>L^hWC3C40$wdC>beJIM&y?ci0=zv3hZ_n%_dG-efrnhnOw1<`z%1zT#} zDLWKY7QZ;aq7^_wgV#*6%`o2irZg>Ot2syfd&ze5-ra@UjWDeIox0bEj`LXm{ys{!6vgUutpT7M? zd6+XfknT97>5s~@CSeN9w_v(MY#5aF6W z=&D{2J&DG>49_2=TRK{nj@*$06RiUlB^}7rmZ6>mJ(6=Wpj00ZOIYDBw zLQ?jAK=eT56}n?qRm4q&hPvLt#FjyEzK&)sb+;~b%T9fTvK3GnFv-2A{Ytr*4bOb`rgd|P* zM(0x)N#3*IT=yCL!9yYZ6X4i^gTS~$yCwnJetjwfr*N|}wXyX5Ruy5D`}Flhm?7zo zW85c)*vkXGA0|QY{lw;j5k1_l9?@LJK2hgfGesFp{h^LNfr$Oi z4IJxIn?yI~_wwR?t+gi1e2fy%-Dl>ucvT>Kpcm8C1Th;REJ|>Pr|q)(Tuz8;)Ohzy zMUL75qtHs{su`#1-84`8#H=Ej@d3U!X93eX=N76{{)fEF-Y|82Hn9R&=&j`|7HUpr%$7R( zCcZSJOS7ZzqqitBmNp5e$aojh^SXFr#ll5`8-uQPPPVb6d8eoi^RHp)%d~DfzIl)6 z@2B1wxAxE33HML_=)9uPqs2IA=II2t{@G) zN}uIf4xw_r$Xqe<$AuFw@}3c+`4+}@^;8(j|@WC%@be@5rmwf?# zCsa{-Z4mo`6TcgG{Ngv#Ya{8kVdosQ8!|N$D6PoKWNIeKO{PXl=Q7n?Cd)<1s2oR~ z{%k%P8FXG}{-<;boC#mu{^%&|+tJvs4yK7KSsh7Nm!+%A-EbeJQa;c-?hp8SnMEQA zK1kBg1GB>0s>`{I+*Wl0kK0&TC*n_gOIV-> zDJ_{aSO{V%Qjq2cq`{U1r+mSC?BD}bDRt*+MXgu>X>jE^`;S2<5c-okI%n4pzW@F2 zs~t$(pRJkfd-}v54!$yLdVF%|mLF0O0=)$fJ6$p}3#V#YT#R99>f3wbg@doyQzh|h#b6d|IPcnfq8CA?oOg;r|n4n?yc$71YsWOza0Sn7DG~%9W*JKd5?F zApUD`xhV#O7=YplVBW!3VTIQ8rIUfp?`YD%qjKRHys_t0ym19p>VZGKVSvNO$AoBL zW)1p?JGbkRAc*EyVhZ7n8~kf7fWI6cf zC%~OveBGFBVF4Li=nvhpmqDn9COY~XfrC){gFK6#+`=FyaldoFPG+e6Ti)xkhQ@SF z*g2c1W0HKkyk9neO_kLhEydZ4q3Z0dclpIlVXQjmSIfU z`Z|@**n_cyva@_X2?;5>VV zWzX)`(U}hmN({@yqHNhf;z~_$4ATVXev=@&Y9fdDW6tGQ0^iH9J`GlBcfK{$&%YDNo}Fdf|AB;8Qi@)3xI*=DT`Ax@JPUc7nBV zqE$UHT{AIVJJDJ=$*P`|u9=jsoy2@%ik2P${xSAwL~6l#hOtUZzQ%)ac+v#u zUKqwi5x`30s%&9|fNtrUNV+y+Eu3ssPX>aU(zTP7Z1dJQ?d6arrv_Hwg|dT&9thc+ zibD?^m~ybo2~6?d9)75zDD`S_@+Bc4Nss>E;Qd$g2Mpf=*TooGraQ!fBY)n94muOd z8~WDuCydM}x{o7dWpNz>7U)maST;_24QIp-7J2-LQHD^Qa=aIV1KCy^R-uTV&(&uL zjium~h^-a_IB0I|ljBSP=Y}Q7RE{T*Yb7N1Sh;n(ur0FpON;A4#QPevI|QY@t7~6n zk+=NRY`Zz1zQVRx@DsCB7chNvyvj})FV>SR#pl?B)hs2RAE`4AQ_jEk|8A6otMef< zLh(T(qy%0j=g#;X&g|JQ$eR1xecp%xmY%m&EFcF8ISnoOaRGTE^a7|w=}jmyl(s>c z7ZUHZfMlFWF@_N?!QYZ+flwvB8{~qU7O$u95Y%}85|P1Raoq8+R2_5^dcbS;Cn5e# zNVsW_RS0!Q7A~BTQIV0e3lv@?Q;ppbNBzf`BI(1s$A&Z1Vy1e6q-Qm4Z+n65egZ3k zlg+r!OR|6Q6%7#=f6$Gmw}+NwN;}t9z!iNSTAO+Z$5mx2#j%i(-~8e5CwJ$#cgR=y zVlqU$3{Pru$six)FSF%gKC+vVO_x>cJDKl3u&;eIqu zOiTt*UzINb5Z!@FMYGF#f+7gqHOk`e&yIyY%`vnHrsVJ~jl+M2*&mQhP3pcfC3UP3q%``UdMd zKSsez{Z`jO=aR+zMSHBS%`Z&hugfX9M6SLr>%b);{<>cAZ~lYaV0gb9WGD9b*pd78 zg&x@JJ1IEtzNNXyMh55vvDZX0ZDDWLBo>?7+Oo!6{IRk@?Oio7?v$E0N|NN+?N0(P zVChU-#4XQx2;H9Mex4eg|59Scb%5hiWvnle-&oHzt_oPK!D{CRKl1>31jab{!~d+1 zt$lLvDp*h0j1q@jrU1LYPKXx5+KOj@qSVX>vx!S$A89?G&Bfa9lHL0aY5s#AF+DF~ z&#oX#P3&{QHTy;MMfivH)mmys*m$)&h5$iB(jk>S)fpnMC8(vf)0y~CN zUgvioW?$7^nZ?W+ACRT)-2HP+bs>ZSKC3nat;p8YTe#9rkHqUqMQnC(&7Y|ehoWI; z8D!CAhpD-VVxeq%f1tfz6=-z6d!O6}kXE`>j82%2*CaDugwxd_Tj%0#BnoE8YC!#` z4H+E`NtLfWFI66y$COwN$m5A@^jjG@0sza!`m99eC617|^3~VRo6C!GSxFFH!VNAM zz&Jw`DemT-1bFIi9bxTrng|e1(f`d$`cCAeMZ$F9aE6Kjp{j&u_AiLQ)wpm&n6bhH zhOFAGi%s5dl9v;Lcp84jX?jow(lmRDY4&C*o474~LilxFSdZsRu(36k!hBW1dO6aG zLxcV5T#lOQ@L8I(_v$H!*`H$TK~dEy*#<*^`Q4aHX< z6epBClT;PS`0tP-j#YJZc5F#-%_9{$Vb>(QGl-Js!8psl5!);Z3{78+#}p1G5a!n8-c_P!w?EH=$+iXwO%Znj zsJ4W5B8m-i)Rd(DZDby;_D7VEcwD{Nz6p_h=KMXVn+(c7b({FSc*|ahzl)(YXak+p zQHq|l=VlXQC_55*)IajdHX1*!v+~5D*#4GRSdh1~T8<~K6F(i-M4Vfm)Dz*=W^87Q zYr#6_Qy)`5HWYE0wRiyUAVjaL>i42;B#%WG^vCf)7+B=AVhjNbft2TLDpgl8-^(3& z*jfo3a!mxks)!q@&lznh`)^rEr>>XN0CZ>i@$d1L6&T zo1Af)hoIZ=Zt)j{XnDU&v@|#$2&gj>mtYy?ikOJ)hZD2l-=727s-|#>JZTi-%}28=hn`bQ)FN3C&VAc!bo&aSAR*x!T#*jEq3`9 zXVpG|3!H#%eIEFk?hADcmAt|Y3GJ-I9p^qs$pyja+(2(2^w{puV|$}L9eZyM*t3ao zH_(fr#YQ3;Qhv*~r4g4?gEMAunWdqQdnwy04ehL;!0u$yUOo_On%}b6zD+8%eCQIN zO~yLsw|f;zXIjH0b$u|j6E7GG0xM$&t>Ls|UO?J$79`mnp~v<~Ly{v?y?#>+-m56I z^{1RswY?lXIE$5{GHb@G^(E}{_V{CK>k^lmcK(ajmK^5xy%f~uwUGnafA!v9;0-A^ zpM$4kC_zMoA1xpZkA9g$4V0mHWjK{2zHU&8Hdk6h*>9w& zcOkq`zbwf19v#=;u^r67K&o!xkOlp$q;#58`x^lH)C;pu4>3iAk^1N$S?V%>6 zKW=xW{UEM5IIC@!8M1)YR>s(&dDf&Hk1{~nL2SHp3wdk+-YD4kN(ME-1*)t?VTK} zSrDv_mQg#1L0F8pH=N(%@Xhn~cKP<9Gm4X!inq7u_L|}umBq>P#M|2i+iOZ@RF)*q z7B1_^?KQp`mA=GT<2spiEEK!7t%JCEM!#`Ym}qyccY_GUFaf0ZK6;xeDTn69@n8CC ziCB`x(`RikO(eH4+LBL;$VG<%Kb%i5H}Bk6-bk3M!V7x^p*Dh67y%R*q6M6?CwUgp z@uIWP+CbRy)E!X*;Y!oI^m0tICWm&;B0Zr`@h$>uJ`k-A^p=9rNDc~|gmBWndc^jT zGs4MnSd)(0USB+;t~eP&#s$;8{b{_z!TjzX*J=7MEu#F?QQ_@*9(yJ9-!Xw;0e9A) zOSw9}XJr9~+TY|83qWpP;Z|uY=cVi1&m{-?CUGwiHCNM!TLG%iq7|lSb+J)-*Km(H+c7ZYans;_7O7z%=cS= zO%3lz8Q0U2|~$AtR-PviMcxCv^j?rfm>g8 zZXmTTP_$_zHWALB{RpZIzV&ls0NgBgH`r2s>F9)-^^@8-k)R)%+6evLbvdaFrgnX9 zJzf8kcVACCes#w6q|A|cBUT}y2ZU$ihuw2LafKpc`!-p-PN^yNVgVWgF-o{cbe`*- zjqf`JUqJaRB>sz3B&5n2N-9FOjC2o8g)}2MPswBOha6dhc5S)s z?*N>TXMK+o1Fke{%h>LT^&55`KuF{wN=fRbL(9c^7=qub6%%>qUw@&U{k4FM>xZ;I z>A6)_WK)eJ(76(TxGtFL&n8|(tS&}b)5=BGBPrgcJ!4O8&wJmsJ(Ke7DR$e#zJ?7q zx?my-a)w_pNUSaEK?sa^P>F;cc5ny4Sqh7`xHq^8et)_16;5WVP595iW`ii$TW5jq z(<3r+dJ)bPAS{HQ6XLATR8196*l1eTP37TIG9hy=O@D|zg`$UNr?76{L~9{k>P00G z;!-0KD3!S|>{N52F*{QM>(N`k%Z}7sT;y$tZu?W4SkUL$YgaTnZ@mXzgW$sb&hJTQ z2G%)-JWsIM&!nj}k%s*4d55e(cF#L>_l(bn&#pD_0Jzj?bz=4aq)`Sgx zRD7Pj$X^Zdd7M(-wU8B3NOQiBX19>xJM#JMn)xF8y5{_%qNR*exm~kDo?|@oc(%P$ znYR0nyq~jYewU`d5NtYSmoZ5t?!@_bz;%Ri4tJiz+kR^(3ou(Z*@G$_I&b`TT^j}t zx-ak#(zy|wfzsX&as3HH<^FrPV$7fE=;21D*{Yq8exy?zgLWL!-^ca$N&P**@5)Ta zGhB*`VM1S_LdFv>SiedAvA@AN@2e~lqYWEs7w*6rO1e`jVsTA5T!C@BlzQhbels1< z^Sk1+6-DVg9ew*EzjzACUKQFp3PvUef7_Ncb#G^?bb#=TJ?M;gqUqh|LTaKzUDs_%WptYjyI_eX$kZbUJEfU zmc*B*x`U>fprVH?t-#WXdx<ZXqFBsP~UaP zOqn~GEx98?5?X~k*qXI2H-pZ~qoUX5Vsl%QEIw*!VbR}5HT5bOM5JPds`}15>z`gD zL}j`|MxOJ$MQk)-_)Gwh7om#B+Xi>wo`_qh61m?l&)zRNIQ!v{M_Qlzf)qm_5n67z z_!a34`>KwmOLP;0C&K_wMYulYu%%6*meF<2J&ZA%tNHw^C-w9F0y23J0{y74qr)U+ z>M)FuBxYO$vgD2vJDx3XFo{H(-EAZTANi6ui-Xn^JlcaIaH^H93H6IASU8>QT3q?w z)n#^hhm>mh&lRLU*9Xa&P5aNaW)UnjV3qr9{~FuB*^ayxd#c4+Nv6f&m|+ceX< z+FzTV8?c6@UIF&3ew;HBbuZT4`g^vx-k}-)xmqlqPaK#i!JEHmJT~c3-B=~-ZN9B? zr}R$}-}cbG;Qbwf_5C{-!7{hKb47?W#P<}n=^5|US+RI<%^|@gqIe+q2l)i}jqeAo zqUeFtZeQ0cC9$4ydrus^c;A|{Ia?Kn?Fx|OYc0p!T=_PTKXnvh8WHt%95d$nH+Me_ zNXM;dR#!BWUdCMeT+$Zyc5MH+<0qcIScLFv*eT&BAVIgTNL+3;pxO>}TwpCNw{}P2 zjr+P{Q@hvttpl*V<`&2HwKzhw&Mu!mwI}Jf5SEs=QiQ_O~w6rra@@PmE9L(G<(!Y8Ehn9dGXfCy`j#!m5VJuXH zezJ$iT8+NSIrxm-gRw2`A>_TGRRJ4j+d}PNo8%Z{Z+@QgV+V*JJ})0Ysd#5v?@GbFA3I+xK zt~$C67CCp0*4_P8f?%UXqC&7Zb24@%G^6nsHGQ2wF{(1v832S|mwjJ`1KwK^>}@<= zC61;xgo_e4HmsWLw0#A9bV!c8he5Mn67Gb(;YENXwP&(=Wqaeh>Dhk!a|HE0YA0XR zR~g@GM*WTN+WuS84S_U@M^)j>YzP9TrA!Y*CMbtgSrtZ6q0A%FOgprHYL6M`JWU+Y zXmP0hBeE$~`mO1(|5#Pl?^(z2^;Zc0uQn3Rc>KSuK(k%G)%oNDvZmn9S4W0CPtDVP z$s5@O7Q+CX%USs##9NL5*yZxw3K)QI8wOy1%eQ2S*G95e$U+@;YN10>@f=!E=s9ko z=M*(At6zQvgXKJHAYYh$UToSY@@M_kdSeVL~ zcpX%awwt$Hr|ELthIR}X^$y7){drFM)9*4gsqYEpBL=gz{?uRmQgre`DF)S-`Z=-R zte-9z<0qBVe7j?ZHcVnVSH>zUk{8>HgNqvKH2#V#q$_Ba4DNMvh>&~Y=9#p3kcN<} zC8qNgMjgR=&e?%!FT*}Wjz-^ke3`B8i-G~ZA*2d6ZkTLjbPD^<7OO0_XB)-~%- zOYrtTn*sW>-x|aei6G@GNczD3w@!B0gBD8+xDryhQ3hsP8^qEZ5!T! zwb%aVUYDeX_Eg_tpd%vM19E#$7_$1??x&2drWf!gLKk_d!au5{- z1ssXXJ7h(_{Dnuixq4$Td6>!}s327aQa1;RlIOEM__LMFRWQ~VY&~13NvLm4=79aC zR|a4t<+xOW2^s^!>^Ki-pOtP5#OkJPFj3Bk99oS$fImc)jWBgK`q>4d!)*T}^q?r# z)mnxA29OciWzXD|1G13{WEbD7Ye>Qvm7r4NuB2E`5||B0cGz5d#X5Ak^3A*ku`37& z(5+q{V@og6mElGLvy7!%vR*60(Ui4aY#Kck} z)P4fwvg-ymO#7O5@QaA_7KI@H_NoaJidUvX8cqPtbLfGd?N#Gf`u0vJw(gWBtQ^jZ za?+$;@S*C!7kyL48{b}eA>ShfPj+>2gM>6*nO-;y=YtCY^PX&UPwIIeW)NrlGUCGQ zDcvZ_^47GrYs+fgskTB-IO|`PBXFo)u0?oVZ%nUBuWVyMtltTXt|(aw=k*OOJ{Bkkln ziB;QYmCUFXM%u}2V%hdtz8TfN#Ny*M!y<`~8E2&M{9mieG5thA%?y*(jgbt)rA)~4 z3Yd^13Yn0-LzD*z7tI7^GWkQY~ITW=)>rvz2+r@3f}#6gMlfq zg3bZDU42gK`2lRALtABadv?(`!GJSx{ypB>wWD^O%VxY8<37ZnM@j_3r_U}S^58^P zK=1n+oEvo(#o}q`uJ_8j(GAX4^G>9SspsDraw)HKD@BOjicF0#w&Z0HFgK$NtdFR3 zPT)dCw|x)NLk7}c{4#^5(}F3sQ6`H!O_afW70k<>ooAXCrll}m{21$$*-^=5-!?Nn zgFPO)^o6orv5mE!i=(aRTt3^IgaTbwa5%8^7Z}!_mqGZ}PdIN8J4lw;ccrme(M$6d zU*C_+;`^g~F^f-B9K_n>*YMj}e2y%>VX+ap#pehIyPt@C67_WZFv7S*Q~gBj6K$dG zS8FVn^HTlqBtE_U>XI3?vH&^aecWAuAB{VI)`mk$BtI~&Z*P7Lhav{`B>K9`(^3XfzZx?pdRpGMD`;=h2g=dF!9Me z8oa&`4K6H1gR|UOm|>k`m85-!JBA38bC6+_MGT{?MHppFKupdR`&Sh$YH(iG*-5;w zfLh@oq*9awZZH}|Jkm`X_ekQ0$(U0k&kwcUx%8I^)THN3B(D0G9m|yZ=(MxEB8QSC zQV3L{cp$l#TPDYj7jwQb`WJ{Z-BWR0z4b)&Ehpj_C_i7aB?oda9{hoU@fGsk!1#6C z3Tf2f{N<`#b#pXdpN1}S&rs2Qv6G<=IYo#P|9+VP zt!T762RF)A6fkp;TrM)#H!@;($1o0>WV6|wI>VpK&+zKP8U94!3^%$nTx%^O#-hl3 zWof-I$$Rn)RTG}xGE}@{*wb@jgt;`Vu1*Yh z#J`Gl0Vgy}#C{qx)Fo7u4;NT+y!PuTerBXvo)9=R6=q zJRa^Hx-v88+!60%8!pc8+c^0_%sw<_6XWunq~to^AO{G+?crgSL!6_SE`v%dKrstY<@LWh`EiJWHoY z*#<0tp^f#6kjK=8tr7KN(r@iIc99Ud$ZP0~B!ttTS9p4}0ZzfQJGgJ#5GV?@ZB5+;1fqC}-9 z$81A9)FA`7d4sb5@f9HB1|qI4?}C#xyV&+us5b0Jv6c}EH+i;{coQWaxrvbl;iWPV zH^)+)eX*|f3!`1cCtW|+THuJIWLA7!=1g3A2i!oxA0$_H4NP+y3BEj*>I$RzjOHA~ z(Vw1CfrUCRJ#w}bPhAJ@qb?8;(}{LY*cICnEI4*({qLiPXlZ&`cyMEX#5u!|;|ql{ zqcJG4(oQI_0xee8?n#>ZBDk!KzQCQ6V6VBxZkEx=YZZ*Jww2S6=(aL`IXr9=N5F!Z ziU0Ysk+obc73OoCqSk{88`6G+UB3V}hw|!kppqt|_~6d$mp9~QmO&WKq|1zV62O#0 z(PwiX4WUxF{z5x%mF0mO5nTCHPo#w=?4ugd0Z|R{dSZK7PoRTQhq7XdPz@WQN|0U? zQ>J|VH0vBu@+43&_80C_6O(KS^PB|thn-DKNG^_Q6lL4$$+7+o`e_1sQn^oFZX7@1 zNAz4HRemA0aZ*w8Y#j%TaX~JLr*a+7S@{V*Mmj3{opl9PPOW3myh9uH*f*7Ql}OxT z`}B%O+6k^u>dj?!>v{@{d^Po$U}b{W=htbsi>9_JnhLn=S$#jh&Y&t5ox7aVMe5*(gu?6UdViNq$^ zo6D@GI+Q=rkBq(1T4&@*M(=UWT{`+7lcnA0-2EF@aqMR0;=R%Gn*ttlcl397oq#3O z0D1Gya3Q;KI~2?-3n=pv9f8ZxY*VEH@5RW%7akeDDqQa@?U3y~pe(Relk1&p3Z8Yo zIsYG=(%Fc-8xfuaW;iND*#<%`_fMY{T=NMFQ62WI<)T7)waA68jyT``hIVNPsx6#_ zI^jbF2x}Qgme<$|j>et)zG>!Om^K#WQP}mn2CQ+S!!iZIUk%LmPQbx^A2%tEAUxlNaEBi3?BwpGq75mfZ5=DR0~d`&8_=iK?^ww&}TiIz1ul0cjEH8kf{tX z!bO=%6(gxX2tC|dt$yoS%NIS6h{vs)BW;g)%94+{82B+|!96wQ>1dBp^@#PUtnsxq z!-}UNzMKV}bsVkW+5YB=)_G8j9*6v`T3rgXL{Nso7uG>RIv?ZnUN@s3kx|`*E8yAJeMJyjAsSg>F<0|Xk-)efm?LF344+(zQ$`Y#*p@#@p`R6 zF!Trh&878g!*#2I&X0c#xv!L=P*er_E`wA~p%U4962bh z@B5KbFRC0?9chSrgjm2Dtn-7ueAb%fw|cBmp2|*ax2v%;?0ur}Qxoe47~XXN{*nGK z2-?dhuPSd)5n8%>s!(j}uvn7g@z~H#**C`v)^?8%)>94g(wX^NJ>4ol3L?oGi)Lzt zKiUQM4`8otGK${Cr%Xt8YkySD>Tm!-itGcjr{xpPwYEGd2#Q3yakz=(KA#A!M9vJ> zSQ^Vq-O`Pf!lOml z?5m8zErElW@$!6 ztoquPzD)J?@?h=BRIiYmO!WA4ZKis)RHoyZsZPj!L4SUjG-I=_px3vyktSWc zeo)6#CgGNLz7?kZyrjK#8^42+U+X5{!H3*XJ)OQ)j!x-YMd-$Wjj^`K_l#eDi)O{X zB=1{Zq4`_BCU5vNUDzUr2l->Rx7?+7WYhDRJjkSPx!Zkx;yIb~XA;^k*Ge=u?YHhC zRr=QZ_#M>T@4Cq!=0joc9@pfZrzT(HCKuQ*?A-xPE~|vzjoo2n_WAC~hn80|Tb|)6 ze*ngo-6g3AmSgUVQvpQa;}r1t3I>vy(moc9 zK;>%9Z-63Gz0N$Xmx~r9l&!TkkQv$sFnQvWWUCSkst;u(1(}+FWN9iiO;rfhvt`7r zPmloA#x#=|#BZJ_de7IJ=Q8iP&O8UaXI7=;?{Cq{t?FrR=+l>ZA)!xm_31bn@Hbh0 z;blpf=lk8~TV)uTCQ-4)l0@djB>1A6K(U;8wb^@R;6qySFW#$rav7I;ufCRhHP?H! zBlqgV-Yc_`q^yhFSJkXSc^>6HKehY_$~<{5Y%-r%FMOAG$EyOxiQ%hOH#*<{tP-q> z9tscpjEG6k_%nUZ@4Z|j)P+`~^O@5!y5)<%4lRE~^8n3mbZSmZX~x^6x2jhgh?2&` z)vI&nT#6k{?K%c`i0Chmw`$iH+$X&IX;$rO?|u@yIOaoMy=_|W{+kDPjr!obGX$x( z`dj1ZlI;?gLLv%J{b3-QiS2GV$F4==daMsGNEksIjaGXWO?rGa=Uj0>ONHl%`-oz^ zM_^{sWMPW|X*OkV?oM0?nSnI$YvyDl1&}=lx&PumziT(8Yr+Poo8+#u<=vO%Sy?)O z+c(SjdeHmiliN4Tg3dn|u)Z`s+kgI}nO`XNWk;qq86c7IsS3m@%UjP@{UB@JY93np z8|}}mL-2*f>nUe0oG*HGu_)wJ5mP&|Stx_611O5YcE{w!*zX9N4`z;B%v{T_ls}KD zOqPnaSFmW=k)JOw08jZY)_(J3^Aaa;0cY6t*=)WH(>_KuR$bm2k)*Y&Nm}Rp5q5^w zMQCuEY1giE?t+IUB#m;4Zk4q{a~rJk&9HzKXl4hY|7$t>hMVtscwQ04%xK7Gs=}l! z%C-ia{Y#W497ne*nE!ua{#_rt@DTh?_&e^2P?|?MULhh_CilOZ-2YAPJ^rE8&2EpO z#We5mDY@iGpYPlm#>Z^qW+aN17LJ4&QyouWWLfrxsqXj+f0O13+<**{Lp_ZyT`9Oo z_vnIyj2JB{;C2SF@>?EAJ>-b6C~oT&L1Ku(QWO_ArW$x~Z}mGZu Df>Y06_`aQI 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 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 deleted file mode 100644 index 9ced1498..00000000 --- a/tools/isledecomp/isledecomp/parser/error.py +++ /dev/null @@ -1,97 +0,0 @@ -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 deleted file mode 100644 index b44487df..00000000 --- a/tools/isledecomp/isledecomp/parser/linter.py +++ /dev/null @@ -1,144 +0,0 @@ -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 deleted file mode 100644 index 8108ac46..00000000 --- a/tools/isledecomp/isledecomp/parser/marker.py +++ /dev/null @@ -1,146 +0,0 @@ -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 deleted file mode 100644 index 21e4c382..00000000 --- a/tools/isledecomp/isledecomp/parser/node.py +++ /dev/null @@ -1,63 +0,0 @@ -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 deleted file mode 100644 index 92e41dd3..00000000 --- a/tools/isledecomp/isledecomp/parser/parser.py +++ /dev/null @@ -1,556 +0,0 @@ -# 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 deleted file mode 100644 index fbc78e10..00000000 --- a/tools/isledecomp/isledecomp/parser/util.py +++ /dev/null @@ -1,141 +0,0 @@ -# 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 deleted file mode 100644 index 31829c65..00000000 --- a/tools/isledecomp/isledecomp/types.py +++ /dev/null @@ -1,13 +0,0 @@ -"""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 deleted file mode 100644 index a8c6033b..00000000 --- a/tools/isledecomp/isledecomp/utils.py +++ /dev/null @@ -1,308 +0,0 @@ -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 deleted file mode 100644 index bf3e05dc..00000000 --- a/tools/isledecomp/setup.py +++ /dev/null @@ -1,11 +0,0 @@ -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 deleted file mode 100644 index e69de29b..00000000 diff --git a/tools/isledecomp/tests/conftest.py b/tools/isledecomp/tests/conftest.py deleted file mode 100644 index 9f56b8a7..00000000 --- a/tools/isledecomp/tests/conftest.py +++ /dev/null @@ -1,3 +0,0 @@ -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 deleted file mode 100644 index 4316ad4a..00000000 --- a/tools/isledecomp/tests/samples/basic_class.cpp +++ /dev/null @@ -1,30 +0,0 @@ -// 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 deleted file mode 100644 index 99930de8..00000000 --- a/tools/isledecomp/tests/samples/basic_file.cpp +++ /dev/null @@ -1,22 +0,0 @@ -// 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 deleted file mode 100644 index 3be0316a..00000000 --- a/tools/isledecomp/tests/samples/global_variables.cpp +++ /dev/null @@ -1,14 +0,0 @@ -// 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 deleted file mode 100644 index 8a36c89a..00000000 --- a/tools/isledecomp/tests/samples/inline.cpp +++ /dev/null @@ -1,8 +0,0 @@ -// 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 deleted file mode 100644 index 3f6b3811..00000000 --- a/tools/isledecomp/tests/samples/missing_offset.cpp +++ /dev/null @@ -1,16 +0,0 @@ -// 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 deleted file mode 100644 index dc3c5bfa..00000000 --- a/tools/isledecomp/tests/samples/multiple_offsets.cpp +++ /dev/null @@ -1,25 +0,0 @@ -// 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 deleted file mode 100644 index feb82314..00000000 --- a/tools/isledecomp/tests/samples/oneline_function.cpp +++ /dev/null @@ -1,12 +0,0 @@ -// 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 deleted file mode 100644 index 951c99e7..00000000 --- a/tools/isledecomp/tests/samples/out_of_order.cpp +++ /dev/null @@ -1,20 +0,0 @@ -// 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 deleted file mode 100644 index 69f365ec..00000000 --- a/tools/isledecomp/tests/samples/poorly_formatted.cpp +++ /dev/null @@ -1,23 +0,0 @@ -// 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 deleted file mode 100644 index 55cb75a5..00000000 --- a/tools/isledecomp/tests/test_compare_db.py +++ /dev/null @@ -1,82 +0,0 @@ -"""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 deleted file mode 100644 index 8328f5e9..00000000 --- a/tools/isledecomp/tests/test_curly.py +++ /dev/null @@ -1,73 +0,0 @@ -# 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 deleted file mode 100644 index 3670e495..00000000 --- a/tools/isledecomp/tests/test_cvdump.py +++ /dev/null @@ -1,59 +0,0 @@ -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 deleted file mode 100644 index f4ca1aff..00000000 --- a/tools/isledecomp/tests/test_cvdump_symbols.py +++ /dev/null @@ -1,38 +0,0 @@ -"""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 deleted file mode 100644 index 3bd6afa0..00000000 --- a/tools/isledecomp/tests/test_cvdump_types.py +++ /dev/null @@ -1,705 +0,0 @@ -"""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 deleted file mode 100644 index 060d46d9..00000000 --- a/tools/isledecomp/tests/test_instgen.py +++ /dev/null @@ -1,212 +0,0 @@ -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 deleted file mode 100644 index 14fb85b4..00000000 --- a/tools/isledecomp/tests/test_islebin.py +++ /dev/null @@ -1,152 +0,0 @@ -"""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 deleted file mode 100644 index 95d19515..00000000 --- a/tools/isledecomp/tests/test_linter.py +++ /dev/null @@ -1,144 +0,0 @@ -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 deleted file mode 100644 index c7905627..00000000 --- a/tools/isledecomp/tests/test_parser.py +++ /dev/null @@ -1,773 +0,0 @@ -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 deleted file mode 100644 index e74fda0e..00000000 --- a/tools/isledecomp/tests/test_parser_samples.py +++ /dev/null @@ -1,141 +0,0 @@ -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 deleted file mode 100644 index be37e3c8..00000000 --- a/tools/isledecomp/tests/test_parser_statechange.py +++ /dev/null @@ -1,141 +0,0 @@ -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 deleted file mode 100644 index e60f505c..00000000 --- a/tools/isledecomp/tests/test_parser_util.py +++ /dev/null @@ -1,209 +0,0 @@ -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 deleted file mode 100644 index 93ddfd17..00000000 --- a/tools/isledecomp/tests/test_path_resolver_nt.py +++ /dev/null @@ -1,32 +0,0 @@ -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 deleted file mode 100644 index af0c89e5..00000000 --- a/tools/isledecomp/tests/test_path_resolver_posix.py +++ /dev/null @@ -1,69 +0,0 @@ -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 deleted file mode 100644 index deb3c825..00000000 --- a/tools/isledecomp/tests/test_sanitize.py +++ /dev/null @@ -1,296 +0,0 @@ -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/reccmp.js b/tools/reccmp/reccmp.js deleted file mode 100644 index 4a8e6595..00000000 --- a/tools/reccmp/reccmp.js +++ /dev/null @@ -1,867 +0,0 @@ -// 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 = ` - -