44using System . Diagnostics ;
55using System . Linq ;
66using System . Runtime . InteropServices ;
7+ using System . Windows . Forms . VisualStyles ;
78
89namespace 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 )
0 commit comments