adm244 / Dishonored-MissionStats

A native mod for Dishonored that adds "Statistics" button in pause menu, so it is possible to check statistics mid-mission.
The Unlicense
17 stars 2 forks source link

Question, not an issue. #3

Closed ectrc closed 5 months ago

ectrc commented 6 months ago

What software did you use to reverse the game's classes, pe offset and other usefull unreal related things.

ectrc commented 6 months ago

And how did you manage to find all the offsets?

adm244 commented 5 months ago

IDA for disassembly and static analysis, x64dbg for dynamic analysis/debugging.

I heavily relied on strings references, placing breakpoints and analyzing stack frames. Function that's modifying stats variables (ModifyStatVariable as I called it) was the first thing I've found this way. Then ShowLocationDiscovery and basically mod was done as initially planned.

For scaleform I've found API docs for slightly different version and used that as a reference. For SWF files extraction UModel (UEViewer) was used and ffdec (JPEXS Free Flash Decompiler) for its inspection and ActionScript decompilation.

Back then I didn't use any automation and had to manually search for class names for each class through vftables and RTTI. I would strongly advise NOT to follow this example and automate as much as you can as soon as you can.

For example, this class was manually reversed with no automation and only partially since all the rest isn't required for what I was going for:

struct DisGlobalUIManager {
  void *vtable;
  u8 unk04[0x2DC-0x4];
  DisGFxMoviePlayerHUD *hud; // 0x2DC
  DisGFxMoviePlayerPowerWheel *powerWheel; // 0x2E0
  DisGFxMoviePlayerJournal *journal; // 0x2E4
  DisGFxMoviePlayerNote *note; // 0x2E8
  DisGFxMoviePlayerPauseMenu *pauseMenu; // 0x2EC
  void *unk2F0;
  DisGFxMoviePlayerMissionStats *missionStats; // 0x2F4
  // ...
};
assert_size(DisGlobalUIManager, 0x2F8)

And this is the same class dumped using automation (offsets differ because this dump is from x64 version of executable):

// Class DishonoredGame.DisGlobalUIManager
// 0x03FC (0x0458 - 0x005C)
class UDisGlobalUIManager : public UObject
{
public:
  struct FPointer                           VfTable_IArkSettingsListenerInterface;  // 0x005C (0x0008) [0x0000000000801002] ( CPF_Const | CPF_Native | CPF_NoExport )
  struct FDisUIMovieSet                     m_DefaultUI;                            // 0x0064 (0x00D4) [0x0000000000404000] ( CPF_Config | CPF_NeedCtorLink )
  TArray<struct FDisUIMovieSet>             m_DLCUI;                                // 0x0138 (0x0010) [0x0000000000404000] ( CPF_Config | CPF_NeedCtorLink )
  struct FString                            m_EquipmentIcons[DPWI_MAX];             // 0x0148 (0x0240) [0x0000000000404000] ( CPF_Config | CPF_NeedCtorLink )
  struct FString                            m_ItemSmallIconPackageName;             // 0x0388 (0x0010) [0x0000000000404000] ( CPF_Config | CPF_NeedCtorLink )
  struct FString                            m_ItemLargeIconPackageName;             // 0x0398 (0x0010) [0x0000000000404000] ( CPF_Config | CPF_NeedCtorLink )
  struct FPointer                           m_pSFTweaksPackage;                     // 0x03A8 (0x0008) [0x0000000000001000] ( CPF_Native )
  struct FPointer                           m_pItemSmallIconPackage;                // 0x03B0 (0x0008) [0x0000000000001000] ( CPF_Native )
  struct FString                            m_DLCItemIconPackagesSuffix;            // 0x03B8 (0x0010) [0x0000000000400000] ( CPF_NeedCtorLink )
  class UDisTweaks_PostProcess*             m_pBlurTweaks;                          // 0x03C8 (0x0008) [0x0000000000000000] 
  TArray<struct FDisTexturePackageInfo>     m_TexturePackages;                      // 0x03D0 (0x0010) [0x0000000000001000] ( CPF_Native )
  class UDisGFxMoviePlayerGlobal*           m_pGlobal;                              // 0x03E0 (0x0008) [0x0000000004400008] ( CPF_ExportObject | CPF_NeedCtorLink | CPF_EditInline )
  class UDisGFxMoviePlayerMainMenu*         m_pMainMenu;                            // 0x03E8 (0x0008) [0x0000000000000000] 
  class UDisGFxMoviePlayerHUD*              m_pHUD;                                 // 0x03F0 (0x0008) [0x0000000000000000] 
  class UDisGFxMoviePlayerPowerWheel*       m_pPowerWheel;                          // 0x03F8 (0x0008) [0x0000000000000000] 
  class UDisGFxMoviePlayerJournal*          m_pJournal;                             // 0x0400 (0x0008) [0x0000000000000000] 
  class UDisGFxMoviePlayerNote*             m_pNote;                                // 0x0408 (0x0008) [0x0000000000000000] 
  class UDisGFxMoviePlayerPauseMenu*        m_pPauseMenu;                           // 0x0410 (0x0008) [0x0000000000000000] 
  class UDisGFxMoviePlayerStore*            m_pStore;                               // 0x0418 (0x0008) [0x0000000000000000] 
  class UDisGFxMoviePlayerMissionStats*     m_pMissionStats;                        // 0x0420 (0x0008) [0x0000000000000000] 
  class UDisDLC05MoviePlayerChallengeMenu*  m_pChallengeMenu;                       // 0x0428 (0x0008) [0x0000000000000000] 
  class UDisDLC05MoviePlayerBrief*          m_pBrief;                               // 0x0430 (0x0008) [0x0000000000000000] 
  class UDisDLC05MoviePlayerResultsMenu*    m_pResultsMenu;                         // 0x0438 (0x0008) [0x0000000000000000] 
  unsigned long                             m_bJournalAllowed : 1;                  // 0x0440 (0x0004) [0x0000000000000000] [0x00000001] 
  unsigned long                             m_bFirstVisitToStore : 1;               // 0x0440 (0x0004) [0x0000000000000000] [0x00000002] 
  unsigned long                             m_bEnableAutoSaveInMenus : 1;           // 0x0440 (0x0004) [0x0000000000000000] [0x00000004] 
  unsigned long                             m_bEnableTutorials : 1;                 // 0x0440 (0x0004) [0x0000000000000000] [0x00000008] 
  unsigned long                             m_bEnableBaseTutorials : 1;             // 0x0440 (0x0004) [0x0000000000000000] [0x00000010] 
  TArray<class UDisTweaks_Upgrade*>         m_UpgradesAlreadyDisplayed;             // 0x0444 (0x0010) [0x0000000000402000] ( CPF_Transient | CPF_NeedCtorLink )
  float                                     m_fAutoSavePeriod;                      // 0x0454 (0x0004) [0x0000000000004000] ( CPF_Config )
};

The same applies to enumerables. This is based on trial and error:

enum MissionStatTypes {
  MissionStat_AlarmsRung = 0x03,
  MissionStat_BodiesFound = 0x04,
  MissionStat_DetectedTimes = 0x0A,
  MissionStat_OverallChaos = 0x1B,
  MissionStat_HostilesKilled = 0x1C,
  MissionStat_CiviliansKilled = 0x1E,
  MissionStat_CoinsFound = 0x21,
  MissionStat_RunesFound = 0x22,
  MissionStat_BoneCharmsFound = 0x23,
  MissionStat_OutsiderShrinesFound = 0x24,
  MissionStat_SokolovPaintingsFound = 0x25,
};

And this is dumped using automation:

// Enum DishonoredGame.DisTweaks_PlayerStats.EDisPlayerStat
enum EDisPlayerStat
{
  ePlayerStat_NumKills = 0,
  ePlayerStat_NumAssassinations = 1,
  ePlayerStat_NumFlees = 2,
  ePlayerStat_NumAlarmsTriggered = 3,
  ePlayerStat_NumCorpsesDiscovered = 4,
  ePlayerStat_NumExplosions = 5,
  ePlayerStat_NumRatsSummoned = 6,
  ePlayerStat_LongestDistanceFallen = 7,
  ePlayerStat_AssassinationTargetKilled = 8,
  ePlayerStat_AmountStolen = 9,
  ePlayerStat_NPCsAlerted = 10,
  ePlayerStat_PowersAcquired = 11,
  ePlayerStat_UnawareKill = 12,
  ePlayerStat_SameFactionKill = 13,
  ePlayerStat_DropKill = 14,
  ePlayerStat_MercyKill = 15,
  ePlayerStat_Suicide = 16,
  ePlayerStat_TimePossessing = 17,
  ePlayerStat_DistanceTravelled = 18,
  ePlayerStat_UpgradesAcquired = 19,
  ePlayerStat_ItemsCollected = 20,
  ePlayerStat_MaxPursuersCountEscaped = 21,
  ePlayerStat_MaxSimultaneousEnemyCountKilled = 22,
  ePlayerStat_FishPossessed = 23,
  ePlayerStat_GrenadeThrowbackKill = 24,
  ePlayerStat_RatTunnelUsed = 25,
  ePlayerStat_ChaosScore = 26,
  ePlayerStat_ChaosLevel = 27,
  ePlayerStat_HostileKill = 28,
  ePlayerStat_HostilePutToSleep = 29,
  ePlayerStat_CivilianKill = 30,
  ePlayerStat_CivilianPutToSleep = 31,
  ePlayerStat_NPCsEscapedFrom = 32,
  ePlayerStat_GoldFound = 33,
  ePlayerStat_RuneFound = 34,
  ePlayerStat_BoneCharmFound = 35,
  ePlayerStat_OutsiderShrineFound = 36,
  ePlayerStat_SokolovPaintingFound = 37,
  ePlayerStat_ArcMineBasedOnRatKill = 38,
  ePlayerStat_CrackedCharmFound = 39,
  ePlayerStat_DamageTakenByPullLiftShield = 40,
  ePlayerStat_MAX = 41
};

As you can see not only class layouts and type hierarchy can be extracted from UE games, but also original field names. Although, only for things exposed into so called "unreal engine reflection system".

For more information about this search for "TheFeckless's UE3 SDK Generator".

So, if you're going to analyze unreal engine game, first dump all data types, enums and constants you can, then import those into your favorite software reverse engineering tool (IDA, Ghidra etc.).

ectrc commented 5 months ago

Thank you 🙏 After researching more I came across his hour long tutorial and it answered all my questions. I really appreciate it!

ectrc commented 3 weeks ago

Me again! When this was all over I managed to correctly find all the offsets and the SDK was working beautifully. I was also able to find all of the offsets to GObjects, GNames and ProcessEvent. However there is one big problem, MinHook and every other hooking library fails to redirect the function and always causes a crash when trying to access/modify the parameters or mess with the return value. I assume this is down to how to pop/push the stack. I have tried and remember this project uses raw assembly to place the hooks. I was wondering if you could give some tips/help? I am familiar with the layout of IDA and how to decompile however not too familiar with raw asm and interacting with the stack etc. Would be greatly appreciated as ProcessEvent is the last thing needed to have a fully working SDK.