Skip to content

Commit

Permalink
Added support for initial release of WW branch of RE4R.
Browse files Browse the repository at this point in the history
  • Loading branch information
Squirrelies committed Apr 1, 2023
1 parent 4febb53 commit 5508988
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 14 deletions.
9 changes: 6 additions & 3 deletions SRTPluginProviderRE4R/GameHashes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,24 @@ namespace SRTPluginProviderRE4R
/// <summary>
/// SHA256 hashes for the RE4 REmake game executables.
///
/// Resident Evil 4 (WW): https://steamdb.info/app/2050650/ / ???
/// Resident Evil 4 (WW): https://steamdb.info/app/2050650/ / https://steamdb.info/depot/2050651/
/// Biohazard 4 (CERO Z): ??? / ???
/// </summary>
public static class GameHashes
{
private static readonly byte[] re4rWW_20230323_1 = new byte[32] { 0x3C, 0x81, 0x07, 0xE9, 0x35, 0x2D, 0x0C, 0x4F, 0x39, 0x3D, 0x37, 0x50, 0xF0, 0xAC, 0x86, 0x62, 0x39, 0x1D, 0x52, 0x55, 0x9E, 0x94, 0xB5, 0x86, 0x73, 0x70, 0x50, 0xE0, 0xC5, 0x5E, 0xC3, 0x18 };
private static readonly byte[] re4rDEMO_20230309_1 = new byte[32] { 0xBA, 0x9D, 0xC7, 0x4A, 0xC8, 0x52, 0x30, 0x62, 0x12, 0xB2, 0xDD, 0x17, 0x93, 0xA8, 0xB6, 0xFD, 0x09, 0x2A, 0x62, 0xD5, 0x03, 0xFC, 0x87, 0x30, 0x59, 0xD2, 0x62, 0x50, 0xAE, 0x73, 0x0E, 0xAC };

public static GameVersion DetectVersion(string filePath)
{
byte[] checksum;
using (SHA256 hashFunc = SHA256.Create())
using (FileStream fs = new FileStream(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete))
checksum = hashFunc.ComputeHash(fs);

if (checksum.SequenceEqual(re4rDEMO_20230309_1))
if (checksum.SequenceEqual(re4rWW_20230323_1))
return GameVersion.RE4R_WW_20230323_1;
else if (checksum.SequenceEqual(re4rDEMO_20230309_1))
return GameVersion.RE4R_Demo_20230309_1;
else
return GameVersion.Unknown;
Expand Down
20 changes: 19 additions & 1 deletion SRTPluginProviderRE4R/GameMemoryRE4R.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,29 @@ public struct GameMemoryRE4R : IGameMemoryRE4R

public string VersionInfo => FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion;

public EntityHealth PlayerHealth { get => playerHealth; }
internal EntityHealth playerHealth;

public int EnemyArraySize { get => enemyArraySize; }
internal int enemyArraySize;

public EntityHealth[] EnemyHealth { get => enemyHealth; }
internal EntityHealth[] enemyHealth;

public int ChapterKillCount { get => chapterKillCount; }
internal int chapterKillCount;

public GameRank Rank { get => rank; }
internal GameRank rank;

public long ChapterTimeStart { get => chapterTimeStart; }
internal long chapterTimeStart;

public GameTimer Timer { get => timer; }
internal GameTimer timer;

// Public Properties - Calculated
public long IGTCalculated => unchecked(Timer.IGTRunningTimer - Timer.IGTCutsceneTimer - Timer.IGTPausedTimer);
public long IGTCalculated => 0L;//unchecked(ChapterTimeStart < 0 ? 0L : (ChapterTimeStart - Timer.IGTRunningTimer - Timer.IGTCutsceneTimer - Timer.IGTPausedTimer));

public long IGTCalculatedTicks => unchecked(IGTCalculated * 10L);

Expand Down
61 changes: 57 additions & 4 deletions SRTPluginProviderRE4R/GameMemoryRE4RScanner.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,18 @@ internal class GameMemoryRE4RScanner : IDisposable
public int ProcessExitCode => (memoryAccess != null) ? memoryAccess.ProcessExitCode : 0;

// Pointer Address Variables
private int pointerAddressEntity;
private int pointerAddressGameStatsManager;
private int pointerAddressGameRankManager;
private int pointerAddressIGT;

// Pointer Classes
private IntPtr BaseAddress { get; set; }
private MultilevelPointer PointerPlayerHealth { get; set; }
private MultilevelPointer PointerEnemyCount { get; set; }
private MultilevelPointer[] PointerEnemyHealth { get; set; }
private MultilevelPointer PointerGameStatsManager { get; set; }
private MultilevelPointer PointerGameRankManager { get; set; }
private MultilevelPointer PointerIGT { get; set; }

internal GameMemoryRE4RScanner(Process process = null)
Expand All @@ -43,18 +51,23 @@ internal unsafe void Initialize(Process process)
BaseAddress = NativeWrappers.GetProcessBaseAddress(pid, PInvoke.ListModules.LIST_MODULES_64BIT); // Bypass .NET's managed solution for getting this and attempt to get this info ourselves via PInvoke since some users are getting 299 PARTIAL COPY when they seemingly shouldn't.

// Setup the pointers.
PointerIGT = new MultilevelPointer(memoryAccess, IntPtr.Add(BaseAddress, pointerAddressIGT), 0x28);
GenerateEntityHealthPointers();
PointerGameStatsManager = new MultilevelPointer(memoryAccess, IntPtr.Add(BaseAddress, pointerAddressGameStatsManager), 0x20, 0x10);
PointerGameRankManager = new MultilevelPointer(memoryAccess, IntPtr.Add(BaseAddress, pointerAddressGameRankManager));
PointerIGT = new MultilevelPointer(memoryAccess, IntPtr.Add(BaseAddress, pointerAddressIGT), 0x20);
}
}

private bool SelectPointerAddresses(GameVersion version)
{
switch (version)
{
case GameVersion.RE4R_Demo_20230309_1:
case GameVersion.RE4R_WW_20230323_1:
{
// pointerAddress
pointerAddressIGT = 0x0DD08088;
pointerAddressEntity = 0x0D23E618;
pointerAddressGameStatsManager = 0x0D20FF80;
pointerAddressGameRankManager = 0x0D23F000;
pointerAddressIGT = 0x0D234048;
return true;
}
}
Expand All @@ -63,17 +76,57 @@ private bool SelectPointerAddresses(GameVersion version)
return false;
}

private unsafe void GenerateEntityHealthPointers()
{
if (PointerPlayerHealth is null)
PointerPlayerHealth = new MultilevelPointer(memoryAccess, IntPtr.Add(BaseAddress, pointerAddressEntity), 0x90, 0x40, 0x148);
else
PointerPlayerHealth.UpdatePointers();

if (PointerEnemyCount is null)
PointerEnemyCount = new MultilevelPointer(memoryAccess, IntPtr.Add(BaseAddress, pointerAddressEntity), 0xA8);
else
PointerEnemyCount.UpdatePointers();

gameMemoryValues.enemyArraySize = PointerEnemyCount.DerefInt(0x3C);
if (PointerEnemyHealth is null || PointerEnemyHealth.Length != gameMemoryValues.enemyArraySize ||
gameMemoryValues.enemyHealth is null || gameMemoryValues.enemyHealth.Length != gameMemoryValues.enemyArraySize)
{
PointerEnemyHealth = new MultilevelPointer[gameMemoryValues.enemyArraySize];
gameMemoryValues.enemyHealth = new EntityHealth[gameMemoryValues.enemyArraySize];
}

for (int i = 0; i < PointerEnemyHealth.Length; ++i)
{
if (PointerEnemyHealth[i] is null)
PointerEnemyHealth[i] = new MultilevelPointer(memoryAccess, IntPtr.Add(BaseAddress, pointerAddressEntity), 0xA8, 0x40 + (i * sizeof(nuint)), 0x148);
else
PointerEnemyHealth[i].UpdatePointers();
}
}

/// <summary>
///
/// </summary>
internal void UpdatePointers()
{
GenerateEntityHealthPointers();
PointerGameStatsManager.UpdatePointers();
PointerGameRankManager.UpdatePointers();
PointerIGT.UpdatePointers();
}

internal unsafe IGameMemoryRE4R Refresh()
{
gameMemoryValues.playerHealth = PointerPlayerHealth.Deref<EntityHealth>(0x10);
for (int i = 0; i < gameMemoryValues.enemyArraySize; ++i)
gameMemoryValues.enemyHealth[i] = PointerEnemyHealth[i].Deref<EntityHealth>(0x10);

gameMemoryValues.chapterKillCount = PointerGameStatsManager.DerefInt(0x34);
gameMemoryValues.rank = PointerGameRankManager.Deref<GameRank>(0x10);

// IGT
gameMemoryValues.chapterTimeStart = PointerGameStatsManager.DerefLong(0x18);
gameMemoryValues.timer = PointerIGT.Deref<GameTimer>(0x18);

HasScanned = true;
Expand Down
1 change: 1 addition & 0 deletions SRTPluginProviderRE4R/GameVersion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@ public enum GameVersion : int
{
Unknown,
RE4R_Demo_20230309_1,
RE4R_WW_20230323_1,
}
}
12 changes: 12 additions & 0 deletions SRTPluginProviderRE4R/IGameMemoryRE4R.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,18 @@ public interface IGameMemoryRE4R

string VersionInfo { get; }

EntityHealth PlayerHealth { get; }

int EnemyArraySize { get; }

EntityHealth[] EnemyHealth { get; }

int ChapterKillCount { get; }

GameRank Rank { get; }

long ChapterTimeStart { get; }

GameTimer Timer { get; }

long IGTCalculated { get; }
Expand Down
2 changes: 1 addition & 1 deletion SRTPluginProviderRE4R/PluginInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ internal class PluginInfo : IPluginInfo

public string Description => "A game memory producer plugin for Resident Evil 4 (2023).";

public string Author => "TheDementedSalad";
public string Author => "TheDementedSalad, Squirrelies";

public Uri MoreInfoURL => new Uri("https://github.com/SpeedrunTooling/SRTPluginProviderRE4R");

Expand Down
2 changes: 1 addition & 1 deletion SRTPluginProviderRE4R/SRTPluginProviderRE4R.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<TargetFramework>net5.0</TargetFramework>
<LangVersion>latest</LangVersion>
<Platforms>x64</Platforms>
<Authors>TheDementedSalad</Authors>
<Authors>TheDementedSalad, Squirrelies</Authors>
<Company>$(Authors)</Company>
<Copyright>Copyright © 2023 $(Authors)</Copyright>
<Product>Resident Evil 4 (2023) Memory Producer Plugin</Product>
Expand Down
38 changes: 38 additions & 0 deletions SRTPluginProviderRE4R/Structs/EntityHealth.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace SRTPluginProviderRE4R.Structs
{
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 0x8)]
[DebuggerDisplay("{_DebuggerDisplay,nq}")]
public struct EntityHealth
{
[FieldOffset(0x0)] private int maximumHealth;
[FieldOffset(0x4)] private int currentHealth;

public int MaximumHealth => maximumHealth;
public int CurrentHealth => currentHealth;

public bool IsTrigger => MaximumHealth == 1 && CurrentHealth == 1;
public bool IsAlive => !IsTrigger && MaximumHealth > 0 && CurrentHealth > 0 && CurrentHealth <= MaximumHealth;
public bool IsDamaged => MaximumHealth > 0 && CurrentHealth > 0 && CurrentHealth < MaximumHealth;
public float Percentage => ((IsAlive) ? (float)CurrentHealth / (float)MaximumHealth : 0f);

/// <summary>
/// Debugger display message.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string _DebuggerDisplay
{
get
{
if (IsTrigger)
return string.Format("TRIGGER", CurrentHealth, MaximumHealth, Percentage);
else if (IsAlive)
return string.Format("{0} / {1} ({2:P1})", CurrentHealth, MaximumHealth, Percentage);
else
return "DEAD / DEAD (0%)";
}
}
}
}
24 changes: 24 additions & 0 deletions SRTPluginProviderRE4R/Structs/GameRank.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Diagnostics;
using System.Runtime.InteropServices;

namespace SRTPluginProviderRE4R.Structs
{
[StructLayout(LayoutKind.Explicit, Pack = 1, Size = 0x0C)]
[DebuggerDisplay("{_DebuggerDisplay,nq}")]
public struct GameRank
{
[FieldOffset(0x0)] private int rank;
[FieldOffset(0x4)] private float actionPoint;
[FieldOffset(0x8)] private float itemPoint;

public int Rank => rank;
public float ActionPoint => actionPoint;
public float ItemPoint => itemPoint;

/// <summary>
/// Debugger display message.
/// </summary>
[DebuggerBrowsable(DebuggerBrowsableState.Never)]
public string _DebuggerDisplay => $"Rank {Rank} (AP {ActionPoint} / IP {ItemPoint})";
}
}
8 changes: 4 additions & 4 deletions SRTPluginProviderRE4R/Structs/GameTimer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,10 @@ namespace SRTPluginProviderRE4R.Structs

public struct GameTimer
{
[FieldOffset(0x0)] private long igtRunningTimer;
[FieldOffset(0x8)] private long igtCutsceneTimer;
[FieldOffset(0x10)] private long igtMenuTimer;
[FieldOffset(0x18)] private long igtPausedTimer;
[FieldOffset(0x0)] private long igtRunningTimer; // GameElapsedTime
[FieldOffset(0x8)] private long igtCutsceneTimer; // DemoSpendingTime
[FieldOffset(0x10)] private long igtMenuTimer; // InventorySpendingTime
[FieldOffset(0x18)] private long igtPausedTimer; // PauseSpendingTime

public long IGTRunningTimer => igtRunningTimer;
public long IGTCutsceneTimer => igtCutsceneTimer;
Expand Down

0 comments on commit 5508988

Please sign in to comment.