-
Notifications
You must be signed in to change notification settings - Fork 0
/
EHBSplitter.cs
114 lines (112 loc) · 4.31 KB
/
EHBSplitter.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
using LiveSplit.ComponentUtil;
using LiveSplit.Model;
using LiveSplit.UI.Components.AutoSplit;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading;
namespace EHBSplitter
{
class EHBSplitter : IAutoSplitter
{
private Process game;
private Timer timer;
private readonly static SigScanTarget target =
new SigScanTarget(8, "55 8B EC 83 EC 08 8B 05 ?? ?? ?? ?? 85 C0 75 20");
private bool ready;
private IntPtr worldOwner;
private IntPtr totalWorldTime;
private IntPtr beaten;
private IntPtr storyEventLog;
private readonly EHBSettings settings;
internal EHBSplitter(EHBSettings settings)
{
this.settings = settings;
}
public bool ShouldStart(LiveSplitState state)
{
if (game is null)
{
game = Process.GetProcessesByName("ElseHeartbreak").FirstOrDefault(
// after a reset, the closed process is still visible for a moment
p => !p.HasExited
);
if (!(game is null))
{
// We need to wait until the required code is JITed.
// 2 seconds is the theoretical minimum time from the code becoming available
// to actually needing to start the timer.
// In practice 4 seconds would probably do the job, due to long loading times,
// but let's not take chances.
timer = new Timer(CheckMemory, null, 0, 2000);
}
return false;
}
if (!ready)
{
if (worldOwner == IntPtr.Zero)
{
return false;
}
var status = game.ReadPointer(worldOwner + 0x10);
// yes, the capital I is correct
if (game.ReadString(status + 0x0c, 24) == "World Is set")
{
var worldSettings = game.ReadPointer(game.ReadPointer(worldOwner + 0x08) + 0x20);
totalWorldTime = game.ReadPointer(worldSettings + 0x24);
beaten = game.ReadPointer(worldSettings + 0x50);
storyEventLog = game.ReadPointer(worldSettings + 0x58);
ready = true;
return false;
}
}
return game.ReadValue<float>(totalWorldTime + 0x0c) > 2;
}
private void CheckMemory(object o)
{
foreach(var page in game.MemoryPages())
{
var scanner = new SignatureScanner(game, page.BaseAddress, (int)page.RegionSize);
IntPtr ptr = scanner.Scan(target);
if (ptr != IntPtr.Zero)
{
worldOwner = game.ReadPointer(game.ReadPointer(ptr));
timer.Dispose();
break;
}
}
}
public bool ShouldSplit(LiveSplitState state)
{
if (settings.splitOnArrival && state.CurrentSplitIndex == 0)
{
// We check the length of the log, to see how many story events have occurred.
// If the answer is more than one, we are in Dorisburg - this is true even in
// press H%, where one of the events doesn't trigger.
// Although in that case this split wouldn't be needed anyway.
return game.ReadValue<int>(game.ReadPointer(storyEventLog + 0x08) + 0x0c) > 1;
}
else
{
return game.ReadValue<bool>(beaten + 0x0c);
}
}
public bool ShouldReset(LiveSplitState state)
{
if (game.HasExited)
{
timer.Dispose();
game = null;
ready = false;
worldOwner = IntPtr.Zero;
return true;
}
else
{
return false;
}
}
public TimeSpan? GetGameTime(LiveSplitState state) { return null; }
public bool IsGameTimePaused(LiveSplitState state) { return false; }
}
}