Skip to content

Commit 527639e

Browse files
committed
Support modifying configs in prebuilt hacktice ROM in memory
1 parent 1f30d0a commit 527639e

7 files changed

Lines changed: 150 additions & 89 deletions

File tree

Hacktice/Config.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ public class Config
5353
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 28)]
5454
public byte[] customText;
5555

56+
// since version 1.5
5657
public byte _pad1;
5758
public byte _pad0;
5859
public byte softReset;

Hacktice/Emulator.cs

Lines changed: 122 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,41 @@
44
using System.Diagnostics;
55
using System.Linq;
66
using System.Runtime.InteropServices;
7+
using System.Windows.Forms.VisualStyles;
78

89
namespace Hacktice
910
{
1011
internal class Emulator
1112
{
1213
private Process _process;
1314
private ulong _ramPtrBase = 0;
15+
1416
private IntPtr _ptrRam;
15-
private IntPtr _ptrCanary;
16-
private IntPtr _ptrVersion;
17-
private IntPtr _ptrStatus;
17+
18+
/*
19+
Hacktice header has 20 bytes in size and consists of
20+
+0 - Canary.HackticeMagic
21+
+4 - Encoded version in uint
22+
+8 - Hacktice state as one of Canary.HackticeStatus*
23+
+12 - Pointer in N64 RAM to hacktice config
24+
+16 - Pointer in N64 RAM to hacktice savestate
25+
*/
26+
const int HackticeHeaderSize = 20;
27+
private IntPtr? _ptrHackticeHeader;
28+
private byte[] _hackticeHeader = new byte[HackticeHeaderSize];
29+
private byte[] _ram; // just a cache
30+
31+
const int ConfigHeaderSize = 8;
32+
const int RAMSize = 0x400000;
33+
1834
private IntPtr _ptrOnFrameHook;
19-
private IntPtr _ptrPtrConfig;
35+
36+
public int HackticeCanary { get { return BitConverter.ToInt32(_hackticeHeader, 0); } }
37+
public Version HackticeVersion { get { return new Version(BitConverter.ToInt32(_hackticeHeader, 4)); } }
38+
public int HackticeStatus { get { return BitConverter.ToInt32(_hackticeHeader, 8); } }
39+
40+
uint HackticeConfigVPtr { get { return BitConverter.ToUInt32(_hackticeHeader, 12); } }
41+
uint HackticeSavestateVPtr { get { return BitConverter.ToUInt32(_hackticeHeader, 16); } }
2042

2143
static readonly string[] s_ProcessNames = {
2244
"project64", "project64d",
@@ -151,12 +173,7 @@ public PrepareResult Prepare()
151173
MagicManager mm = new MagicManager(_process, romPtrBaseSuggestions.ToArray(), ramPtrBaseSuggestions.ToArray(), offset);
152174
_ramPtrBase = mm.ramPtrBase;
153175
_ptrRam = new IntPtr((long)_ramPtrBase);
154-
// valid only for the binary case
155-
uint binaryHackticeOffset = 0x4E010;
156-
_ptrCanary = new IntPtr((long)(_ramPtrBase + binaryHackticeOffset));
157-
_ptrVersion = new IntPtr((long)(_ramPtrBase + binaryHackticeOffset + 0x4));
158-
_ptrStatus = new IntPtr((long)(_ramPtrBase + binaryHackticeOffset + 0x8));
159-
_ptrPtrConfig = new IntPtr((long)(_ramPtrBase + binaryHackticeOffset + 0xc));
176+
// only for binary
160177
_ptrOnFrameHook = new IntPtr((long)(_ramPtrBase + 0x3805D4));
161178
return PrepareResult.OK;
162179
}
@@ -225,41 +242,104 @@ public void WriteToEmulator(uint romAddr, byte[] data)
225242
}
226243
}
227244

228-
public int ReadHackticeCanary()
245+
public bool LooksLikeN64VPtr(uint vptr)
229246
{
230-
return _process.ReadValue<int>(_ptrCanary);
247+
if (0 == (0x80000000 & vptr))
248+
return false;
249+
250+
uint physAddr = vptr & 0x7fffffff;
251+
return physAddr < 0x800000;
231252
}
232253

233-
public long ReadOnFrameHook()
254+
bool LooksLikeHackticeHeader()
234255
{
235-
return _process.ReadValue<long>(_ptrOnFrameHook);
256+
if (!HackticeVersion.IsReasonable())
257+
return false;
258+
259+
if (!LooksLikeN64VPtr(HackticeConfigVPtr))
260+
return false;
261+
262+
if (!LooksLikeN64VPtr(HackticeSavestateVPtr))
263+
return false;
264+
265+
// not checking for state but it is fine
266+
return true;
236267
}
237268

238-
public int ReadHackticeStatus()
269+
bool RefreshHackticeHeaderAndCheckIfValid()
239270
{
240-
return _process.ReadValue<int>(_ptrStatus);
271+
if (!Ok())
272+
return false;
273+
274+
_process.FetchBytes(_ptrHackticeHeader.Value, HackticeHeaderSize, _hackticeHeader);
275+
if (HackticeCanary != Canary.HackticeMagic)
276+
return false;
277+
278+
bool ok = LooksLikeHackticeHeader();
279+
if (ok)
280+
{
281+
_ram = null;
282+
}
283+
284+
return ok;
285+
}
286+
287+
public bool RefreshHacktice()
288+
{
289+
if (_ptrHackticeHeader.HasValue)
290+
{
291+
// refresh the pointers and check if it is still reasonable
292+
if (RefreshHackticeHeaderAndCheckIfValid())
293+
return true;
294+
295+
_ptrHackticeHeader = null;
296+
}
297+
298+
if (!(_ram is object))
299+
{
300+
_ram = new byte[RAMSize];
301+
}
302+
_process.FetchBytes(_ptrRam, RAMSize, _ram);
303+
304+
foreach (var location in MemFind.All(_ram, Canary.HackticeMagic))
305+
{
306+
// attempt all locations and find if any is reasonable
307+
_ptrHackticeHeader = new IntPtr((long)(_ramPtrBase + (ulong)location));
308+
if (RefreshHackticeHeaderAndCheckIfValid())
309+
return true;
310+
}
311+
312+
// it is over
313+
return false;
241314
}
242315

243316
public bool Ok()
244317
{
245318
return !_process.HasExited;
246319
}
247320

248-
public void Write(Config cfg)
321+
private static bool LooksLikeConfigHeader(byte[] header)
249322
{
250-
_process.ReadValue(_ptrPtrConfig, out uint configVPtr);
251-
if (0 == (0x80000000 & configVPtr))
252-
throw new ArgumentException("Bad config field");
323+
uint magic = BitConverter.ToUInt32(header, 0);
324+
if (magic != Canary.ConfigMagic)
325+
return false;
253326

254-
uint configOff = configVPtr & 0x7fffffff;
255-
var configMagicPtr = new IntPtr((long)(_ramPtrBase + configOff));
256-
_process.ReadValue(configMagicPtr, out uint magic);
257-
if (magic != 0x48434647)
258-
throw new ArgumentException($"Bad config magic {magic:X}");
327+
int size = BitConverter.ToInt32(header, 4);
328+
if (size <= 8 || size > 0x10000)
329+
return false;
259330

260-
var configSizePtr = new IntPtr((long)(_ramPtrBase + configOff + 4));
261-
_process.ReadValue(configSizePtr, out int hackticeConfigSize);
331+
return true;
332+
}
262333

334+
public void Write(Config cfg)
335+
{
336+
var configOff = HackticeConfigVPtr & 0x7fffffff;
337+
var ptrConfigHeader = new IntPtr((long)(_ramPtrBase + configOff));
338+
var configHeader = _process.ReadBytes(ptrConfigHeader, ConfigHeaderSize);
339+
if (!LooksLikeConfigHeader(configHeader))
340+
throw new ArgumentException($"Bad config");
341+
342+
int hackticeConfigSize = BitConverter.ToInt32(configHeader, 4);
263343
var size = Marshal.SizeOf(typeof(Config));
264344
int writeSize = Math.Min(size, hackticeConfigSize);
265345
if (0 == writeSize)
@@ -277,30 +357,21 @@ public void Write(Config cfg)
277357

278358
public Config ReadConfig()
279359
{
280-
_process.ReadValue(_ptrPtrConfig, out uint configVPtr);
281-
if (0 == (0x80000000 & configVPtr))
282-
throw new ArgumentException("Bad config field");
283-
284-
uint configOff = configVPtr & 0x7fffffff;
285-
var configMagicPtr = new IntPtr((long)(_ramPtrBase + configOff));
286-
_process.ReadValue(configMagicPtr, out uint magic);
287-
if (magic != 0x48434647)
288-
throw new ArgumentException($"Bad config magic {magic:X}");
289-
290-
var configSizePtr = new IntPtr((long)(_ramPtrBase + configOff + 4));
291-
_process.ReadValue(configSizePtr, out int hackticeConfigSize);
292-
if (0 == hackticeConfigSize)
293-
throw new ArgumentException($"Config size cannot be 0");
360+
// We might read more than needed but that is OK as the size is constant
361+
var readSize = 8 + Marshal.SizeOf(typeof(Config));
362+
var configOff = HackticeConfigVPtr & 0x7fffffff;
363+
var ptrConfig = new IntPtr((long)(_ramPtrBase + configOff));
364+
var configBytes = _process.ReadBytes(ptrConfig, readSize);
365+
if (!LooksLikeConfigHeader(configBytes))
366+
throw new ArgumentException($"Bad config");
294367

295-
// We might be reading more than required. It is OK because we will cut unnecessary fields and marshalling won't complain
296368
var size = Marshal.SizeOf(typeof(Config));
297-
var configPtr = new IntPtr((long)(_ramPtrBase + configOff + 8));
298-
var configBytes = _process.ReadBytes(configPtr, size);
299369
var ptr = Marshal.AllocHGlobal(size);
300-
Marshal.Copy(configBytes, 0, ptr, size);
370+
Marshal.Copy(configBytes, ConfigHeaderSize, ptr, size);
301371
var config = (Config) Marshal.PtrToStructure(ptr, typeof(Config));
302372
Marshal.FreeHGlobal(ptr);
303373

374+
int hackticeConfigSize = BitConverter.ToInt32(configBytes, 4);
304375
if (hackticeConfigSize <= (int) Marshal.OffsetOf<Config>("_pad1"))
305376
{
306377
config.SetCustomText("PRACTICE");
@@ -313,21 +384,19 @@ public Config ReadConfig()
313384
return config;
314385
}
315386

316-
public Version ReadVersion()
317-
{
318-
_process.ReadValue(_ptrVersion, out int version);
319-
return new Version(version);
387+
public uint ReadRAMHeader()
388+
{
389+
return _process.ReadValue<uint>(_ptrRam);
320390
}
321391

322-
private uint ReadRAMHeader()
392+
public long OnFrameHook()
323393
{
324-
return _process.ReadValue<uint>(_ptrRam);
394+
return _process.ReadValue<long>(_ptrOnFrameHook);
325395
}
326396

327397
public bool IsDecomp()
328398
{
329-
uint hdr = ReadRAMHeader();
330-
return Canary.BinaryRamMagic != hdr;
399+
return Canary.BinaryRamMagic != ReadRAMHeader();
331400
}
332401

333402
public void WriteHackticeDetours(uint fn, int repeats)

Hacktice/MagicManager.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@ public enum AllocationProtect : uint
5858

5959
public MagicManager(Process process, long[] romPtrBaseSuggestions, long[] ramPtrBaseSuggestions, int offset)
6060
{
61-
GC.Collect();
6261
this.process = process;
6362

6463
bool isRomFound = false;

Hacktice/Patcher.cs

Lines changed: 1 addition & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -69,11 +69,6 @@ public void Apply()
6969
_rom[0x57ec1] = 0x4d;
7070
}
7171

72-
static bool IsReasonable(Version version)
73-
{
74-
return version.major < 10 && version.minor < 100 && version.patch < 1000;
75-
}
76-
7772
public Version FindHackticeVersion()
7873
{
7974
foreach (var location in MemFind.All(_rom, (uint) ((int) Canary.HackticeMagic).ToBigEndian()))
@@ -85,7 +80,7 @@ public Version FindHackticeVersion()
8580
continue;
8681

8782
var version = new Version(BitConverter.ToInt32(_rom, location + 4).ToBigEndian());
88-
if (IsReasonable(version))
83+
if (version.IsReasonable())
8984
return version;
9085
}
9186
catch (Exception) { }

Hacktice/ProcessExtensions/ProcessExtension.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,16 @@ public static bool ReadBytes(this Process process, IntPtr addr, int count, out b
182182
return true;
183183
}
184184

185+
public static bool FetchBytes(this Process process, IntPtr addr, int count, byte[] bytes)
186+
{
187+
SizeT read;
188+
if (!WinAPI.ReadProcessMemory(process.Handle, addr, bytes, (SizeT)bytes.Length, out read)
189+
|| read != (SizeT)bytes.Length)
190+
return false;
191+
192+
return true;
193+
}
194+
185195
public static bool ReadPointer(this Process process, IntPtr addr, out IntPtr val)
186196
{
187197
return ReadPointer(process, addr, process.Is64Bit(), out val);

Hacktice/Tool.cs

Lines changed: 11 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,7 @@ private string GetHackticeName()
150150
{
151151
try
152152
{
153-
var version = _emulator.ReadVersion();
154-
return $"hacktice v{version}";
153+
return $"hacktice v{_emulator.HackticeVersion}";
155154
}
156155
catch (Exception)
157156
{
@@ -231,12 +230,7 @@ private void UpdateEmulatorState()
231230
}
232231
else
233232
{
234-
try
235-
{
236-
version = _emulator.ReadVersion();
237-
}
238-
catch (Exception)
239-
{ }
233+
version = _emulator.HackticeVersion;
240234
}
241235

242236
SafeInvoke(delegate {
@@ -310,40 +304,28 @@ private void PrepareHacktice()
310304
State newState = State.ROM;
311305
try
312306
{
313-
{
314-
int val = _emulator.ReadHackticeCanary();
315-
if (val != Canary.HackticeMagic)
316-
{
317-
return;
318-
}
319-
}
320-
{
321-
long val = _emulator.ReadOnFrameHook();
322-
if (val != Canary.OnFrameHookMagic)
323-
{
324-
return;
325-
}
326-
}
307+
if (!_emulator.RefreshHacktice())
308+
return;
327309

328310
newState = State.HACKTICE_CORRUPTED;
329311
{
330-
int val = _emulator.ReadHackticeStatus();
331-
if (val == Canary.HackticeStatusInit)
312+
int status = _emulator.HackticeStatus;
313+
if (status == Canary.HackticeStatusInit)
332314
{
333315
newState = State.HACKTICE_INJECTED;
334316
return;
335317
}
336-
if (val == Canary.HackticeStatusActive)
318+
if (status == Canary.HackticeStatusActive)
337319
{
338-
newState = _emulator.ReadVersion() == _payloadVersion ? State.HACKTICE_RUNNING : State.HACKTICE_RUNNING_CAN_UPGRADE;
320+
newState = _emulator.HackticeVersion == _payloadVersion ? State.HACKTICE_RUNNING : State.HACKTICE_RUNNING_CAN_UPGRADE;
339321
return;
340322
}
341-
if (val == Canary.HackticeStatusDisabled)
323+
if (status == Canary.HackticeStatusDisabled)
342324
{
343325
newState = State.HACKTICE_UPGRADE_DISABLED;
344326
return;
345327
}
346-
if (val == Canary.HackticeStatusUpgrading)
328+
if (status == Canary.HackticeStatusUpgrading)
347329
{
348330
newState = State.HACKTICE_UPGRADE_DATA_WRITTEN;
349331
return;
@@ -358,7 +340,7 @@ private void PrepareHacktice()
358340

359341
private bool IsEmulatorReady()
360342
{
361-
return EmulatorState >= State.ROM && _emulator.Ok();
343+
return EmulatorState > State.ROM && _emulator.Ok();
362344
}
363345

364346
private void EmulatorStateUpdate(object state)

Hacktice/Version.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,5 +104,10 @@ public string ToString(string format, IFormatProvider formatProvider)
104104
{
105105
return $"{major.ToString(format, formatProvider)}.{minor.ToString(format, formatProvider)}.{patch.ToString(format, formatProvider)}";
106106
}
107+
108+
public bool IsReasonable()
109+
{
110+
return major < 10 && minor < 100 && patch < 1000;
111+
}
107112
}
108113
}

0 commit comments

Comments
 (0)