├── EditableListBox.cs
├── GameMemory.cs
├── GameSpecific
├── ApertureTag.cs
├── BMSRetail.cs
├── GameSupport.cs
├── HL2.cs
├── HL2Ep1.cs
├── HL2Ep2.cs
├── Portal.cs
├── Portal2.cs
├── PortalStoriesMel.cs
└── PortalTFV.cs
├── GameState.cs
├── LiveSplit.SourceSplit.csproj
├── LiveSplit.SourceSplit.sln
├── MapTimesForm.Designer.cs
├── MapTimesForm.cs
├── MapTimesForm.resx
├── Properties
└── AssemblyInfo.cs
├── README.md
├── SourceSplitComponent.cs
├── SourceSplitFactory.cs
├── SourceSplitSettings.Designer.cs
├── SourceSplitSettings.cs
├── SourceSplitSettings.resx
├── Util.cs
└── license.txt
/EditableListBox.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Windows.Forms;
3 | using System.Drawing;
4 |
5 | namespace LiveSplit.SourceSplit
6 | {
7 | ///
8 | /// A DataGridView that emulates the look of a ListBox and can be edited.
9 | ///
10 | class EditableListBox : DataGridView
11 | {
12 | private ContextMenu menuRemove;
13 |
14 | public EditableListBox()
15 | {
16 | this.menuRemove = new ContextMenu();
17 | var delete = new MenuItem("Remove Selected");
18 | delete.Click += delete_Click;
19 | this.menuRemove.MenuItems.Add(delete);
20 |
21 | this.AllowUserToResizeRows = false;
22 | this.RowHeadersVisible = false;
23 | this.ColumnHeadersVisible = false;
24 | this.SelectionMode = DataGridViewSelectionMode.FullRowSelect;
25 | this.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
26 | this.AutoSizeRowsMode = DataGridViewAutoSizeRowsMode.None;
27 | this.CellBorderStyle = DataGridViewCellBorderStyle.None;
28 | this.BorderStyle = BorderStyle.Fixed3D;
29 | this.BackgroundColor = SystemColors.Window;
30 |
31 | this.RowTemplate.Height = base.Font.Height + 1;
32 | }
33 |
34 | void delete_Click(object sender, EventArgs e)
35 | {
36 | foreach (DataGridViewRow row in this.SelectedRows)
37 | {
38 | if (!row.IsNewRow)
39 | this.Rows.Remove(row);
40 | }
41 | }
42 |
43 | protected override void OnEnabledChanged(EventArgs e)
44 | {
45 | base.OnEnabledChanged(e);
46 |
47 | if (!this.Enabled)
48 | this.CurrentCell = null;
49 | this.DefaultCellStyle.BackColor = this.Enabled ? SystemColors.Window : SystemColors.Control;
50 | this.DefaultCellStyle.ForeColor = this.Enabled ? SystemColors.ControlText : SystemColors.GrayText;
51 | this.BackgroundColor = this.Enabled ? SystemColors.Window : SystemColors.Control;
52 | }
53 |
54 | protected override void OnCellMouseUp(DataGridViewCellMouseEventArgs e)
55 | {
56 | base.OnCellMouseUp(e);
57 |
58 | if (e.Button == MouseButtons.Right)
59 | this.menuRemove.Show(this, e.Location);
60 | }
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/GameMemory.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Text;
4 | using System.Threading.Tasks;
5 | using System.Diagnostics;
6 | using System.Threading;
7 | using System.IO;
8 | using System.Runtime.InteropServices;
9 | using System.Windows.Forms;
10 | using LiveSplit.ComponentUtil;
11 | using LiveSplit.SourceSplit.GameSpecific;
12 |
13 | namespace LiveSplit.SourceSplit
14 | {
15 | class GameMemory
16 | {
17 | [DllImport("winmm.dll")]
18 | static extern uint timeBeginPeriod(uint uMilliseconds);
19 | [DllImport("winmm.dll")]
20 | static extern uint timeEndPeriod(uint uMilliseconds);
21 |
22 | public event EventHandler OnSetTimingMethod;
23 | public event EventHandler OnSetTickRate;
24 | public event EventHandler OnSessionTimeUpdate;
25 | public event EventHandler OnPlayerGainedControl;
26 | public event EventHandler OnPlayerLostControl;
27 | public event EventHandler OnMapChanged;
28 | public event EventHandler OnSessionStarted;
29 | public event EventHandler OnGamePaused;
30 | public event EventHandler OnSessionEnded;
31 | public event EventHandler OnNewGameStarted;
32 |
33 | private Task _thread;
34 | private SynchronizationContext _uiThread;
35 | private CancellationTokenSource _cancelSource;
36 |
37 | private SigScanTarget _curTimeTarget;
38 | private SigScanTarget _signOnStateTarget1;
39 | private SigScanTarget _signOnStateTarget2;
40 | private SigScanTarget _curMapTarget;
41 | private SigScanTarget _globalEntityListTarget;
42 | private SigScanTarget _gameDirTarget;
43 | private SigScanTarget _hostStateTarget;
44 | private SigScanTarget _serverStateTarget;
45 |
46 | private bool _gotTickRate;
47 |
48 | private SourceSplitSettings _settings;
49 |
50 | // TODO: match tickrate as closely as possible without going over
51 | // otherwise we will most likely read when the game isn't sleeping
52 | // must also account for variance of windows scheduler
53 | private const int TARGET_UPDATE_RATE = 13;
54 |
55 | public GameMemory(SourceSplitSettings settings)
56 | {
57 | _settings = settings;
58 |
59 | // detect game offsets in a game/version-independent way by scanning for code signatures
60 |
61 | // TODO: refine hl2 2014 signatures once an update after the may 29th one is released
62 |
63 | // CBaseServer::(server_state_t)m_State
64 | _serverStateTarget = new SigScanTarget();
65 | _serverStateTarget.OnFound = (proc, scanner, ptr) => !proc.ReadPointer(ptr, out ptr) ? IntPtr.Zero : ptr;
66 | // works for every engine.dll
67 | // \x83\xf8\x01\x0f\x8c..\x00\x00\x3d\x00\x02\x00\x00\x0f\x8f..\x00\x00\x83\x3d(....)\x02\x7d
68 | _serverStateTarget.AddSignature(22,
69 | "83 F8 01", // cmp eax, 1
70 | "0F 8C ?? ?? 00 00", // jl loc_200087FB
71 | "3D 00 02 00 00", // cmp eax, 200h
72 | "0F 8F ?? ?? 00 00", // jg loc_200087FB
73 | "83 3d ?? ?? ?? ?? 02", // cmp m_State, 2
74 | "7D"); // jge short loc_200085FD
75 |
76 | // TODO: find a generic curTime signature
77 | // frameTime->curtime WIP sig, 76% success
78 | // \xe8...\x00\xd9\x1d....\x8b\x0d....\x8b\x11
79 |
80 | // CGlobalVarsBase::curtime (g_ClientGlobalVariables aka gpGlobals)
81 | _curTimeTarget = new SigScanTarget();
82 | _curTimeTarget.OnFound = (proc, scanner, ptr) => proc.ReadPointer(ptr, out ptr) ? ptr : IntPtr.Zero;
83 | // orange box and older
84 | // \xa3....\xb9....\xa3....\xe8....\xd9\x1d(....)\xb9....\xe8....\xd9\x1d
85 | _curTimeTarget.AddSignature(22,
86 | "A3 ?? ?? ?? ??", // mov dword_2038BA6C, eax
87 | "B9 ?? ?? ?? ??", // mov ecx, offset unk_2038B8E8
88 | "A3 ?? ?? ?? ??", // mov dword_2035DDA4, eax
89 | "E8 ?? ?? ?? ??", // call sub_20048110
90 | "D9 1D ?? ?? ?? ??", // fstp curTime
91 | "B9 ?? ?? ?? ??", // mov ecx, offset unk_2038B8E8
92 | "E8 ?? ?? ?? ??", // call sub_20048130
93 | "D9 1D"); // fstp frametime
94 |
95 | // portal 2
96 | // \x89\x96\xc4\x00\x00\x00\x8b\x86\xc8\x00\x00\x00\x8b\xce\xa3....\xe8....\xd9\x1d(....)\x8b\xce\xe8....\xd9\x1d
97 | _curTimeTarget.AddSignature(26,
98 | "89 96 C4 00 00 00", // mov [esi+0C4h], edx
99 | "8B 86 C8 00 00 00", // mov eax, [esi+0C8h]
100 | "8B CE", // mov ecx, esi
101 | "A3 ?? ?? ?? ??", // mov dword_10414AD0, eax
102 | "E8 ?? ?? ?? ??", // call sub_100A0F30
103 | "D9 1D ?? ?? ?? ??", // fstp curTime
104 | "8B CE", // mov ecx, esi
105 | "E8 ?? ?? ?? ??", // call sub_100A0FB0
106 | "D9 1D"); // fstp frametime
107 |
108 | // source 2009
109 | // \x89\x8f\xc4\x00\x00\x00\x8b\x97\xc8\x00\x00\x00\x8b\xcf\x89\x15....\xe8....\xd9\x1d(....)\x8b\xcf\xe8....\xd9\x1d
110 | _curTimeTarget.AddSignature(27,
111 | "89 8F C4 00 00 00", // mov [edi+0C4h], ecx
112 | "8B 97 C8 00 00 00", // mov edx, [edi+0C8h]
113 | "8B CF", // mov ecx, edi
114 | "89 15 ?? ?? ?? ??", // mov dword_10422624, edx
115 | "E8 ?? ?? ?? ??", // call sub_1008FE40
116 | "D9 1D ?? ?? ?? ??", // fstp curTime
117 | "8B CF", // mov ecx, edi
118 | "E8 ?? ?? ?? ??", // call sub_1008FEB0
119 | "D9 1D"); // fstp flt_1042261C
120 |
121 | // hl2 may 29 2014 update
122 | // \xa3....\x89\x15....\xe8....\xd9\x1d....\x57\xb9....\xe8....\x8b\x0d....\xd9\x1d
123 | _curTimeTarget.AddSignature(18,
124 | "A3 ?? ?? ?? ??", // mov dword_103B4AC8, eax
125 | "89 15 ?? ?? ?? ??", // mov dword_10452F38, edx
126 | "E8 ?? ?? ?? ??", // call sub_100CE610
127 | "D9 1D ?? ?? ?? ??", // fstp curTime
128 | "57", // push edi
129 | "B9 ?? ?? ?? ??", // mov ecx, offset unk_10452D98
130 | "E8 ?? ?? ?? ??", // call sub_100CE390
131 | "8B 0D ?? ?? ?? ??", // mov ecx, dword_1043686C
132 | "D9 1D"); // fstp frametime
133 | // bms retail
134 | // \xa3....\x89\x15....\xe8....\xd9\x1d....\xb9....\xe8....\x8b\x0d....\xd9\x1d
135 | _curTimeTarget.AddSignature(18,
136 | "A3 ?? ?? ?? ??", // mov dword_103B4AC8, eax
137 | "89 15 ?? ?? ?? ??", // mov dword_10452F38, edx
138 | "E8 ?? ?? ?? ??", // call sub_100CE610
139 | "D9 1D ?? ?? ?? ??", // fstp curTime
140 | "B9 ?? ?? ?? ??", // mov ecx, offset unk_10452D98
141 | "E8 ?? ?? ?? ??", // call sub_100CE390
142 | "8B 0D ?? ?? ?? ??", // mov ecx, dword_1043686C
143 | "D9 1D"); // fstp frametime
144 |
145 | // CBaseClientState::m_nSignOnState (older engines)
146 | _signOnStateTarget1 = new SigScanTarget();
147 | _signOnStateTarget1.OnFound = (proc, scanner, ptr) => proc.ReadPointer(ptr, out ptr) ? ptr : IntPtr.Zero;
148 | // orange box and older (and bms retail)
149 | // \x80\x3d....\x00\x74\x06\xb8....\xc3\x83\x3d(....)\x02\xb8
150 | _signOnStateTarget1.AddSignature(17,
151 | "80 3D ?? ?? ?? ?? 00", // cmp byte_698EE114, 0
152 | "74 06", // jz short loc_6936C8FF
153 | "B8 ?? ?? ?? ??", // mov eax, offset aDedicatedServe ; "Dedicated Server"
154 | "C3", // retn
155 | "83 3D ?? ?? ?? ?? 02", // cmp CBaseClientState__m_nSignonState, 2
156 | "B8 ?? ?? ?? ??"); // mov eax, offset MultiByteStr
157 |
158 | // CBaseClientState::m_nSignOnState
159 | _signOnStateTarget2 = new SigScanTarget();
160 | _signOnStateTarget2.OnFound = (proc, scanner, ptr) => {
161 | if (!proc.ReadPointer(ptr, out ptr)) // deref instruction
162 | return IntPtr.Zero;
163 | if (!proc.ReadPointer(ptr, out ptr)) // deref ptr
164 | return IntPtr.Zero;
165 | return IntPtr.Add(ptr, 0x70); // this+0x70 = m_nSignOnState
166 | };
167 | // source 2009 / portal 2
168 | // \x74.\x8b\x74\x87\x04\x83\x7e\x18\x00\x74\x2d\x8b\x0d(....)\x8b\x49\x18
169 | _signOnStateTarget2.AddSignature(14,
170 | "74 ??", // jz short loc_693D4E22
171 | "8B 74 87 04", // mov esi, [edi+eax*4+4]
172 | "83 7E 18 00", // cmp dword ptr [esi+18h], 0
173 | "74 2D", // jz short loc_693D4DFC
174 | "8B 0D ?? ?? ?? ??", // mov ecx, baseclientstate
175 | "8B 49 18"); // mov mov ecx, [ecx+18h]
176 |
177 | // CBaseServer::m_szMapname[64]
178 | _curMapTarget = new SigScanTarget();
179 | _curMapTarget.OnFound = (proc, scanner, ptr) => proc.ReadPointer(ptr, out ptr) ? ptr : IntPtr.Zero;
180 | // \x68(....).\xe8...\x00\x83\xc4\x08\x85\xc0\x0f\x84..\x00\x00\x83\xc7\x01\x83.\x50\x3b\x7e\x18\x7c
181 | // source 2006 and older
182 | _curMapTarget.AddSignature(1,
183 | "68 ?? ?? ?? ??", // push offset map
184 | "??", // push ebp
185 | "E8 ?? ?? ?? 00", // call __stricmp
186 | "83 C4 08", // add esp, 8
187 | "85 C0", // test eax, eax
188 | "0F 84 ?? ?? 00 00", // jz loc_200CDF8D
189 | "83 C7 01", // add edi, 1
190 | "83 ?? 50", // add ebp, 50h
191 | "3B 7E 18", // cmp edi, [esi+18h]
192 | "7C"); // jl short loc_200CDEC0
193 | // orange box and newer
194 | // \xd9.\x2c\xd9\xc9\xdf\xf1\xdd\xd8\x76.\x80.....\x00
195 | _curMapTarget.AddSignature(13,
196 | "D9 ?? 2C", // fld dword ptr [edx+2Ch]
197 | "D9 C9", // fxch st(1)
198 | "DF F1", // fcomip st, st(1)
199 | "DD D8", // fstp st
200 | "76 ??", // jbe short loc_6946F651
201 | "80 ?? ?? ?? ?? ?? 00"); // cmp map, 0
202 | // bms retail
203 | // \xdd.....\xdc.....\xdf\xf1\xdd\xd8\x76.\x80.....\x00
204 | _curMapTarget.AddSignature(20,
205 | "DD ?? ?? ?? ?? ??", // fld [ebp+var_144]
206 | "DC ?? ?? ?? ?? ??", // fsub dbl_103F36D8
207 | "DF F1", // fcomip st, st(1)
208 | "DD D8", // fstp st
209 | "76 ??", // jbe short loc_101B8F6F
210 | "80 ?? ?? ?? ?? ?? 00"); // cmp map, 0
211 |
212 | // CBaseEntityList::(CEntInfo)m_EntPtrArray
213 | _globalEntityListTarget = new SigScanTarget();
214 | // \x6a\x00\x6a\x00\x50\x6a\x00\xb9(....)\xe8
215 | // deref to get vtable ptr, add 4 to get start of entity list
216 | _globalEntityListTarget.OnFound = (proc, scanner, ptr) => proc.ReadPointer(ptr, out ptr) ? ptr + 4 : IntPtr.Zero;
217 | _globalEntityListTarget.AddSignature(8,
218 | "6A 00", // push 0
219 | "6A 00", // push 0
220 | "50", // push eax
221 | "6A 00", // push 0
222 | "B9 ?? ?? ?? ??", // mov ecx, offset CGlobalEntityList_vtable_ptr
223 | "E8"); // call sub_22289800
224 |
225 | // CHostState::m_currentState
226 | _hostStateTarget = new SigScanTarget();
227 | // subtract 4 to get m_currentState
228 | _hostStateTarget.OnFound = (proc, scanner, ptr) => proc.ReadPointer(ptr, out ptr) ? ptr - 4 : IntPtr.Zero;
229 | // \xc7\x05....\x07\x00\x00\x00\xc3
230 | _hostStateTarget.AddSignature(2,
231 | "C7 05 ?? ?? ?? ?? 07 00 00 00", // mov g_HostState_m_nextState, 7
232 | "C3"); // retn
233 |
234 | // TODO: find better way to do this. multiple sigs instead?
235 | _gameDirTarget = new SigScanTarget(0, "25732F736176652F25732E736176"); // "%s/save/%s.sav"
236 | _gameDirTarget.OnFound = (proc, scanner, ptr) => {
237 | byte[] b = BitConverter.GetBytes(ptr.ToInt32());
238 | var target = new SigScanTarget(-4,
239 | // push offset aSMapsS_sav
240 | $"68 {b[0]:X02} {b[1]:X02} {b[2]:X02} {b[3]:X02}");
241 | IntPtr ptrPtr = scanner.Scan(target);
242 | if (ptrPtr == IntPtr.Zero)
243 | return IntPtr.Zero;
244 | IntPtr ret;
245 | proc.ReadPointer(ptrPtr, out ret);
246 | return ret;
247 | };
248 | }
249 |
250 | #if DEBUG
251 | ~GameMemory()
252 | {
253 | Debug.WriteLine("GameMemory finalizer");
254 | }
255 | #endif
256 |
257 | public void StartReading()
258 | {
259 | if (_thread != null && _thread.Status == TaskStatus.Running)
260 | throw new InvalidOperationException();
261 | if (!(SynchronizationContext.Current is WindowsFormsSynchronizationContext))
262 | throw new InvalidOperationException("SynchronizationContext.Current is not a UI thread.");
263 |
264 | _cancelSource = new CancellationTokenSource();
265 | _uiThread = SynchronizationContext.Current;
266 | _thread = Task.Factory.StartNew(() => MemoryReadThread(_cancelSource));
267 | }
268 |
269 | public void Stop()
270 | {
271 | if (_cancelSource == null || _thread == null || _thread.Status != TaskStatus.Running)
272 | return;
273 |
274 | _cancelSource.Cancel();
275 | _thread.Wait();
276 | }
277 |
278 | void MemoryReadThread(CancellationTokenSource cts)
279 | {
280 | // force windows timer resolution to 1ms. it probably already is though, from the game.
281 | timeBeginPeriod(1);
282 | // we do a lot of timing critical stuff so this may help out
283 | Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.AboveNormal;
284 |
285 | while (true)
286 | {
287 | try
288 | {
289 | Debug.WriteLine("Waiting for process");
290 |
291 | Process game;
292 | GameOffsets offsets;
293 | while (!this.TryGetGameProcess(out game, out offsets))
294 | {
295 | Thread.Sleep(750);
296 |
297 | if (cts.IsCancellationRequested)
298 | goto ret;
299 | }
300 |
301 | this.HandleProcess(game, offsets, cts);
302 |
303 | if (cts.IsCancellationRequested)
304 | goto ret;
305 | }
306 | catch (Exception ex) // probably a Win32Exception on access denied to a process
307 | {
308 | Trace.WriteLine(ex.ToString());
309 | Thread.Sleep(1000);
310 | }
311 | }
312 |
313 | ret:
314 |
315 | Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.Normal;
316 | timeEndPeriod(1);
317 | }
318 |
319 | // TODO: log fails
320 | bool TryGetGameProcess(out Process p, out GameOffsets offsets)
321 | {
322 | #if DEBUG
323 | var sw = Stopwatch.StartNew();
324 | #endif
325 |
326 | string[] procs = _settings.GameProcesses.Select(x => x.ToLower().Replace(".exe", String.Empty)).ToArray();
327 | p = Process.GetProcesses().FirstOrDefault(x => procs.Contains(x.ProcessName.ToLower()));
328 | offsets = new GameOffsets();
329 |
330 | if (p == null || p.HasExited || Util.IsVACProtectedProcess(p))
331 | return false;
332 |
333 | // process is up, check if engine and server are both loaded yet
334 | ProcessModuleWow64Safe engine = p.ModulesWow64Safe().FirstOrDefault(x => x.ModuleName.ToLower() == "engine.dll");
335 | ProcessModuleWow64Safe server = p.ModulesWow64Safe().FirstOrDefault(x => x.ModuleName.ToLower() == "server.dll");
336 |
337 | if (engine == null || server == null)
338 | return false;
339 |
340 | // required engine stuff
341 | var scanner = new SignatureScanner(p, engine.BaseAddress, engine.ModuleMemorySize);
342 |
343 | if ((offsets.CurMapPtr = scanner.Scan(_curMapTarget)) == IntPtr.Zero
344 | || (offsets.CurTimePtr = scanner.Scan(_curTimeTarget)) == IntPtr.Zero
345 | || (offsets.GameDirPtr = scanner.Scan(_gameDirTarget)) == IntPtr.Zero
346 | || (offsets.HostStatePtr = scanner.Scan(_hostStateTarget)) == IntPtr.Zero
347 | || (offsets.ServerStatePtr = scanner.Scan(_serverStateTarget)) == IntPtr.Zero)
348 | return false;
349 |
350 | if ((offsets.SignOnStatePtr = scanner.Scan(_signOnStateTarget1)) == IntPtr.Zero
351 | && (offsets.SignOnStatePtr = scanner.Scan(_signOnStateTarget2)) == IntPtr.Zero)
352 | return false;
353 |
354 | // required server stuff
355 | var serverScanner = new SignatureScanner(p, server.BaseAddress, server.ModuleMemorySize);
356 |
357 | if ((offsets.GlobalEntityListPtr = serverScanner.Scan(_globalEntityListTarget)) == IntPtr.Zero)
358 | return false;
359 |
360 | // entity offsets
361 | if ( !GetBaseEntityMemberOffset("m_fFlags", p, serverScanner, out offsets.BaseEntityFlagsOffset)
362 | || !GetBaseEntityMemberOffset("m_vecAbsOrigin", p, serverScanner, out offsets.BaseEntityAbsOriginOffset)
363 | || !GetBaseEntityMemberOffset("m_iName", p, serverScanner, out offsets.BaseEntityTargetNameOffset)
364 | || !GetBaseEntityMemberOffset("m_hViewEntity", p, serverScanner, out offsets.BasePlayerViewEntity))
365 | return false;
366 |
367 | // find m_pParent offset. the string "m_pParent" occurs more than once so we have to do something else
368 | // in old engine it's right before m_iParentAttachment. in new engine it's right before m_nTransmitStateOwnedCounter
369 | // TODO: test on all engines
370 | int tmp;
371 | if (!GetBaseEntityMemberOffset("m_nTransmitStateOwnedCounter", p, serverScanner, out tmp))
372 | {
373 | if (!GetBaseEntityMemberOffset("m_iParentAttachment", p, serverScanner, out tmp))
374 | return false;
375 | tmp -= 4; // sizeof m_iParentAttachment
376 | }
377 | tmp -= 4; // sizeof m_nTransmitStateOwnedCounter (4 aligned byte)
378 | offsets.BaseEntityParentHandleOffset = tmp;
379 |
380 | Debug.WriteLine("CBaseServer::m_szMapname ptr = 0x" + offsets.CurMapPtr.ToString("X"));
381 | Debug.WriteLine("CGlobalVarsBase::curtime ptr = 0x" + offsets.CurTimePtr.ToString("X"));
382 | Debug.WriteLine("CBaseClientState::m_nSignonState ptr = 0x" + offsets.SignOnStatePtr.ToString("X"));
383 | Debug.WriteLine("CBaseEntityList::(CEntInfo)m_EntPtrArray ptr = 0x" + offsets.GlobalEntityListPtr.ToString("X"));
384 | Debug.WriteLine("CBaseEntity::m_fFlags offset = 0x" + offsets.BaseEntityFlagsOffset.ToString("X"));
385 | Debug.WriteLine("CBaseEntity::m_vecAbsOrigin offset = 0x" + offsets.BaseEntityAbsOriginOffset.ToString("X"));
386 | Debug.WriteLine("CBaseEntity::m_iName offset = 0x" + offsets.BaseEntityTargetNameOffset.ToString("X"));
387 | Debug.WriteLine("CBaseEntity::m_pParent offset = 0x" + offsets.BaseEntityParentHandleOffset.ToString("X"));
388 | Debug.WriteLine("CBasePlayer::m_hViewEntity offset = 0x" + offsets.BasePlayerViewEntity.ToString("X"));
389 |
390 | #if DEBUG
391 | Debug.WriteLine("TryGetGameProcess took: " + sw.Elapsed);
392 | #endif
393 |
394 | return true;
395 | }
396 |
397 | // also works for anything derived from CBaseEntity (player etc) (no multiple inheritance)
398 | // var must be included by one of the DEFINE_FIELD macros
399 | public static bool GetBaseEntityMemberOffset(string member, Process game, SignatureScanner scanner, out int offset)
400 | {
401 | offset = -1;
402 |
403 | IntPtr stringPtr = scanner.Scan(new SigScanTarget(0, Encoding.ASCII.GetBytes(member)));
404 | if (stringPtr == IntPtr.Zero)
405 | return false;
406 |
407 | var b = BitConverter.GetBytes(stringPtr.ToInt32());
408 |
409 | var target = new SigScanTarget(10,
410 | $"C7 05 ?? ?? ?? ?? {b[0]:X02} {b[1]:X02} {b[2]:X02} {b[3]:X02}"); // mov dword_15E2BF1C, offset aM_fflags ; "m_fFlags"
411 | target.OnFound = (proc, s, ptr) => {
412 | // this instruction is almost always directly after above one, but there are a few cases where it isn't
413 | // so we have to scan down and find it
414 | var proximityScanner = new SignatureScanner(proc, ptr, 256);
415 | return proximityScanner.Scan(new SigScanTarget(6, "C7 05 ?? ?? ?? ?? ?? ?? 00 00")); // mov dword_15E2BF20, 0CCh
416 | };
417 |
418 | IntPtr addr = scanner.Scan(target);
419 | if (addr == IntPtr.Zero)
420 | {
421 | // seen in Black Mesa Source (legacy version)
422 | var target2 = new SigScanTarget(1,
423 | "68 ?? ?? ?? ??", // push 256
424 | $"68 {b[0]:X02} {b[1]:X02} {b[2]:X02} {b[3]:X02}"); // push offset aM_fflags ; "m_fFlags"
425 | addr = scanner.Scan(target2);
426 |
427 | if (addr == IntPtr.Zero)
428 | return false;
429 | }
430 |
431 | return game.ReadValue(addr, out offset);
432 | }
433 |
434 | void HandleProcess(Process game, GameOffsets offsets, CancellationTokenSource cts)
435 | {
436 | Debug.WriteLine("HandleProcess " + game.ProcessName);
437 |
438 | var state = new GameState(game, offsets);
439 | this.InitGameState(state);
440 | _gotTickRate = false;
441 |
442 | var profiler = Stopwatch.StartNew();
443 | while (!game.HasExited && !cts.IsCancellationRequested)
444 | {
445 | // iteration must never take longer than 1 tick
446 |
447 | this.UpdateGameState(state);
448 | this.CheckGameState(state);
449 |
450 | state.UpdateCount++;
451 | TimedTraceListener.Instance.UpdateCount = state.UpdateCount;
452 |
453 | if (profiler.ElapsedMilliseconds >= TARGET_UPDATE_RATE)
454 | Debug.WriteLine("**** update iteration took too long: " + profiler.ElapsedMilliseconds);
455 | //var sleep = Stopwatch.StartNew();
456 | //MapTimesForm.Instance.Text = profiler.Elapsed.ToString();
457 | Thread.Sleep(Math.Max(TARGET_UPDATE_RATE - (int)profiler.ElapsedMilliseconds, 1));
458 | //MapTimesForm.Instance.Text = sleep.Elapsed.ToString();
459 | profiler.Restart();
460 | }
461 |
462 | // if the game crashed, make sure session ends
463 | if (state.HostState == HostState.Run)
464 | this.SendSessionEndedEvent();
465 | }
466 |
467 | void InitGameState(GameState state)
468 | {
469 | string absoluteGameDir;
470 | state.GameProcess.ReadString(state.GameOffsets.GameDirPtr, ReadStringType.UTF8, 260, out absoluteGameDir);
471 | state.GameDir = new DirectoryInfo(absoluteGameDir).Name.ToLower();
472 | Debug.WriteLine("gameDir = " + state.GameDir);
473 |
474 | state.CurrentMap = String.Empty;
475 |
476 | // inspect memory layout to determine CEntInfo's version
477 | const int SERIAL_MASK = 0x7FFF;
478 | int serial;
479 | state.GameProcess.ReadValue(state.GameOffsets.GlobalEntityListPtr + (4 * 7), out serial);
480 | state.GameOffsets.EntInfoSize = (serial > 0 && serial < SERIAL_MASK) ? CEntInfoSize.Portal2 : CEntInfoSize.HL2;
481 |
482 | state.GameSupport = GameSupport.FromGameDir(state.GameDir);
483 | if (state.GameSupport != null)
484 | {
485 | Debug.WriteLine("running game-specific code for: " + state.GameDir);
486 | state.GameSupport.OnGameAttached(state);
487 | }
488 | this.SendSetTimingMethodEvent(state.GameSupport?.GameTimingMethod ?? GameTimingMethod.EngineTicks);
489 | }
490 |
491 | void UpdateGameState(GameState state)
492 | {
493 | Process game = state.GameProcess;
494 | GameOffsets offsets = state.GameOffsets;
495 |
496 | // update all the stuff that doesn't depend on the signon state
497 | game.ReadValue(offsets.TickCountPtr, out state.RawTickCount);
498 | game.ReadValue(offsets.IntervalPerTickPtr, out state.IntervalPerTick);
499 |
500 | state.PrevSignOnState = state.SignOnState;
501 | game.ReadValue(offsets.SignOnStatePtr, out state.SignOnState);
502 |
503 | state.PrevHostState = state.HostState;
504 | game.ReadValue(offsets.HostStatePtr, out state.HostState);
505 |
506 | state.PrevServerState = state.ServerState;
507 | game.ReadValue(offsets.ServerStatePtr, out state.ServerState);
508 |
509 | bool firstTick = false;
510 |
511 | // update the stuff that's only valid during signon state full
512 | if (state.SignOnState == SignOnState.Full)
513 | {
514 | // if signon state just became full (where demos start timing from)
515 | if (state.SignOnState != state.PrevSignOnState)
516 | {
517 | firstTick = true;
518 |
519 | // start rebasing from this tick
520 | state.TickBase = state.RawTickCount;
521 | Debug.WriteLine("rebasing ticks from " + state.TickBase);
522 |
523 | // player was just spawned, get it's ptr
524 | state.PlayerEntInfo = state.GetEntInfoByIndex(GameState.ENT_INDEX_PLAYER);
525 |
526 | // update map name
527 | state.GameProcess.ReadString(state.GameOffsets.CurMapPtr, ReadStringType.ASCII, 64, out state.CurrentMap);
528 | }
529 |
530 | // update time and rebase it against the first signon state full tick
531 | state.TickCount = state.RawTickCount - state.TickBase;
532 | state.TickTime = state.TickCount * state.IntervalPerTick;
533 | TimedTraceListener.Instance.TickCount = state.TickCount;
534 |
535 | // update player related things
536 | if (state.PlayerEntInfo.EntityPtr != IntPtr.Zero && state.GameSupport != null)
537 | {
538 | // flags
539 | if (state.GameSupport.RequiredProperties.HasFlag(PlayerProperties.Flags))
540 | {
541 | state.PrevPlayerFlags = state.PlayerFlags;
542 | game.ReadValue(state.PlayerEntInfo.EntityPtr + offsets.BaseEntityFlagsOffset, out state.PlayerFlags);
543 | }
544 |
545 | // position
546 | if (state.GameSupport.RequiredProperties.HasFlag(PlayerProperties.Position))
547 | {
548 | state.PrevPlayerPosition = state.PlayerPosition;
549 | game.ReadValue(state.PlayerEntInfo.EntityPtr + offsets.BaseEntityAbsOriginOffset, out state.PlayerPosition);
550 | }
551 |
552 | // view entity
553 | if (state.GameSupport.RequiredProperties.HasFlag(PlayerProperties.ViewEntity))
554 | {
555 | const int ENT_ENTRY_MASK = 0x7FF;
556 |
557 | state.PrevPlayerViewEntityIndex = state.PlayerViewEntityIndex;
558 | int viewEntityHandle; // EHANDLE
559 | game.ReadValue(state.PlayerEntInfo.EntityPtr + offsets.BasePlayerViewEntity, out viewEntityHandle);
560 | state.PlayerViewEntityIndex = viewEntityHandle == -1
561 | ? GameState.ENT_INDEX_PLAYER
562 | : viewEntityHandle & ENT_ENTRY_MASK;
563 | }
564 |
565 | // parent entity
566 | if (state.GameSupport.RequiredProperties.HasFlag(PlayerProperties.ParentEntity))
567 | {
568 | state.PrevPlayerParentEntityHandle = state.PlayerParentEntityHandle; // EHANDLE
569 | game.ReadValue(state.PlayerEntInfo.EntityPtr + offsets.BaseEntityParentHandleOffset, out state.PlayerParentEntityHandle);
570 | }
571 |
572 | // if it's the first tick, don't use stuff from the previous map
573 | if (firstTick)
574 | {
575 | state.PrevPlayerFlags = state.PlayerFlags;
576 | state.PrevPlayerPosition = state.PlayerPosition;
577 | state.PrevPlayerViewEntityIndex = state.PlayerViewEntityIndex;
578 | state.PrevPlayerParentEntityHandle = state.PlayerParentEntityHandle;
579 | }
580 | }
581 | } // if (state.SignOnState == SignOnState.Full)
582 | }
583 |
584 | void CheckGameState(GameState state)
585 | {
586 | if (state.IntervalPerTick > 0 && !_gotTickRate)
587 | {
588 | _gotTickRate = true;
589 | this.SendSetTickRateEvent(state.IntervalPerTick);
590 | }
591 |
592 | if (state.SignOnState != state.PrevSignOnState)
593 | Debug.WriteLine("SignOnState changed to " + state.SignOnState);
594 |
595 | // if player is fully in game
596 | if (state.SignOnState == SignOnState.Full && state.HostState == HostState.Run)
597 | {
598 | // note: seems to be slow sometimes. ~3ms
599 | this.SendSessionTimeUpdateEvent(state.TickCount);
600 |
601 | // first tick when player is fully in game
602 | if (state.SignOnState != state.PrevSignOnState)
603 | {
604 | Debug.WriteLine("session started");
605 | this.SendSessionStartedEvent(state.CurrentMap);
606 |
607 | state.GameSupport?.OnSessionStart(state);
608 | }
609 |
610 | if (state.ServerState == ServerState.Paused && state.PrevServerState == ServerState.Active)
611 | this.SendGamePausedEvent(true);
612 | else if (state.ServerState == ServerState.Active && state.PrevServerState == ServerState.Paused)
613 | this.SendGamePausedEvent(false);
614 |
615 | if (state.GameSupport != null)
616 | this.HandleGameSupportResult(state.GameSupport.OnUpdate(state), state);
617 |
618 | #if DEBUG
619 | if (state.PlayerEntInfo.EntityPtr != IntPtr.Zero)
620 | DebugPlayerState(state);
621 | #endif
622 | }
623 |
624 | if (state.HostState != state.PrevHostState)
625 | {
626 | if (state.PrevHostState == HostState.Run)
627 | {
628 | // the map changed or a quicksave was loaded
629 | Debug.WriteLine("session ended");
630 |
631 | // the map changed or a save was loaded
632 | this.SendSessionEndedEvent();
633 |
634 | state.GameSupport?.OnSessionEnd(state);
635 | }
636 |
637 | Debug.WriteLine("host state changed to " + state.HostState);
638 |
639 | // HostState::m_levelName is changed much earlier than state.CurrentMap (CBaseServer::m_szMapName)
640 | // reading HostStateLevelNamePtr is only valid during these states (not LoadGame!)
641 | if (state.HostState == HostState.ChangeLevelSP || state.HostState == HostState.ChangeLevelMP
642 | || state.HostState == HostState.NewGame)
643 | {
644 | string levelName;
645 | state.GameProcess.ReadString(state.GameOffsets.HostStateLevelNamePtr, ReadStringType.ASCII, 256-1, out levelName);
646 | Debug.WriteLine("host state m_levelName changed to " + levelName);
647 |
648 | if (state.HostState == HostState.NewGame)
649 | {
650 | if (state.GameSupport != null && levelName == state.GameSupport.FirstMap)
651 | this.SendNewGameStartedEvent(levelName);
652 | }
653 | else // changelevel sp/mp
654 | {
655 | // state.CurrentMap should still be the previous map
656 | this.SendMapChangedEvent(levelName, state.CurrentMap);
657 | }
658 | }
659 | }
660 | }
661 |
662 | void HandleGameSupportResult(GameSupportResult result, GameState state)
663 | {
664 | if (result == GameSupportResult.DoNothing)
665 | return;
666 |
667 | switch (result)
668 | {
669 | case GameSupportResult.PlayerGainedControl:
670 | this.SendGainedControlEvent(state.GameSupport.StartOffsetTicks);
671 | break;
672 | case GameSupportResult.PlayerLostControl:
673 | this.SendLostControlEvent(state.GameSupport.EndOffsetTicks);
674 | break;
675 | }
676 | }
677 |
678 | // these functions are ugly but it means we don't have to worry about implicitly captured closures
679 | public void SendMapChangedEvent(string mapName, string prevMapName)
680 | {
681 | _uiThread.Post(d => {
682 | this.OnMapChanged?.Invoke(this, new MapChangedEventArgs(mapName, prevMapName));
683 | }, null);
684 | }
685 |
686 | public void SendSessionTimeUpdateEvent(int sessionTicks)
687 | {
688 | // note: sometimes this takes a few ms
689 | _uiThread.Post(d => {
690 | this.OnSessionTimeUpdate?.Invoke(this, new SessionTicksUpdateEventArgs(sessionTicks));
691 | }, null);
692 | }
693 |
694 | public void SendGainedControlEvent(int ticksOffset)
695 | {
696 | _uiThread.Post(d => {
697 | this.OnPlayerGainedControl?.Invoke(this, new PlayerControlChangedEventArgs(ticksOffset));
698 | }, null);
699 | }
700 |
701 | public void SendLostControlEvent(int ticksOffset)
702 | {
703 | _uiThread.Post(d => {
704 | this.OnPlayerLostControl?.Invoke(this, new PlayerControlChangedEventArgs(ticksOffset));
705 | }, null);
706 | }
707 |
708 | public void SendSessionStartedEvent(string map)
709 | {
710 | _uiThread.Post(d => {
711 | this.OnSessionStarted?.Invoke(this, new SessionStartedEventArgs(map));
712 | }, null);
713 | }
714 |
715 | public void SendSessionEndedEvent()
716 | {
717 | _uiThread.Post(d => {
718 | this.OnSessionEnded?.Invoke(this, EventArgs.Empty);
719 | }, null);
720 | }
721 |
722 | public void SendNewGameStartedEvent(string map)
723 | {
724 | _uiThread.Post(d => {
725 | this.OnNewGameStarted?.Invoke(this, EventArgs.Empty);
726 | }, null);
727 | }
728 |
729 | public void SendGamePausedEvent(bool paused)
730 | {
731 | _uiThread.Post(d => {
732 | this.OnGamePaused?.Invoke(this, new GamePausedEventArgs(paused));
733 | }, null);
734 | }
735 |
736 | public void SendSetTickRateEvent(float intervalPerTick)
737 | {
738 | _uiThread.Post(d => {
739 | this.OnSetTickRate?.Invoke(this, new SetTickRateEventArgs(intervalPerTick));
740 | }, null);
741 | }
742 |
743 | public void SendSetTimingMethodEvent(GameTimingMethod gameTimingMethod)
744 | {
745 | _uiThread.Post(d => {
746 | this.OnSetTimingMethod?.Invoke(this, new SetTimingMethodEventArgs(gameTimingMethod));
747 | }, null);
748 | }
749 |
750 | #if DEBUG
751 | void DebugPlayerState(GameState state)
752 | {
753 | if (state.PlayerFlags != state.PrevPlayerFlags)
754 | {
755 | string addedList = String.Empty;
756 | string removedList = String.Empty;
757 | foreach (FL flag in Enum.GetValues(typeof(FL)))
758 | {
759 | if (state.PlayerFlags.HasFlag(flag) && !state.PrevPlayerFlags.HasFlag(flag))
760 | addedList += Enum.GetName(typeof(FL), flag) + " ";
761 | else if (!state.PlayerFlags.HasFlag(flag) && state.PrevPlayerFlags.HasFlag(flag))
762 | removedList += Enum.GetName(typeof(FL), flag) + " ";
763 | }
764 | if (addedList.Length > 0)
765 | Debug.WriteLine("player flags added: " + addedList);
766 | if (removedList.Length > 0)
767 | Debug.WriteLine("player flags removed: " + removedList);
768 | }
769 |
770 | if (state.PlayerViewEntityIndex != state.PrevPlayerViewEntityIndex)
771 | {
772 | Debug.WriteLine("player view entity changed: " + state.PlayerViewEntityIndex);
773 | }
774 |
775 | if (state.PlayerParentEntityHandle != state.PrevPlayerParentEntityHandle)
776 | {
777 | Debug.WriteLine("player parent entity changed: " + state.PlayerParentEntityHandle.ToString("X"));
778 | }
779 |
780 | #if false
781 | if (!state.PlayerPosition.BitEquals(state.PrevPlayerPosition))
782 | {
783 | Debug.WriteLine("player pos changed: " + state.PlayerParentEntityHandle);
784 | }
785 | #endif
786 | }
787 | #endif
788 | }
789 |
790 | class MapChangedEventArgs : EventArgs
791 | {
792 | public string Map { get; private set; }
793 | public string PrevMap { get; private set; }
794 | public MapChangedEventArgs(string map, string prevMap)
795 | {
796 | this.Map = map;
797 | this.PrevMap = prevMap;
798 | }
799 | }
800 |
801 | class PlayerControlChangedEventArgs : EventArgs
802 | {
803 | public int TicksOffset { get; private set; }
804 | public PlayerControlChangedEventArgs(int ticksOffset)
805 | {
806 | this.TicksOffset = ticksOffset;
807 | }
808 | }
809 |
810 | class SessionTicksUpdateEventArgs : EventArgs
811 | {
812 | public int SessionTicks { get; private set; }
813 | public SessionTicksUpdateEventArgs(int sessionTicks)
814 | {
815 | this.SessionTicks = sessionTicks;
816 | }
817 | }
818 |
819 | class SessionStartedEventArgs : EventArgs
820 | {
821 | public string Map { get; private set; }
822 | public SessionStartedEventArgs(string map)
823 | {
824 | this.Map = map;
825 | }
826 | }
827 |
828 | class GamePausedEventArgs : EventArgs
829 | {
830 | public bool Paused { get; private set; }
831 | public GamePausedEventArgs(bool paused)
832 | {
833 | this.Paused = paused;
834 | }
835 | }
836 |
837 | class SetTickRateEventArgs : EventArgs
838 | {
839 | public float IntervalPerTick { get; private set; }
840 | public SetTickRateEventArgs(float intervalPerTick)
841 | {
842 | this.IntervalPerTick = intervalPerTick;
843 | }
844 | }
845 |
846 | class SetTimingMethodEventArgs : EventArgs
847 | {
848 | public GameTimingMethod GameTimingMethod { get; private set; }
849 | public SetTimingMethodEventArgs(GameTimingMethod gameTimingMethod)
850 | {
851 | this.GameTimingMethod = gameTimingMethod;
852 | }
853 | }
854 |
855 | public enum GameTimingMethod
856 | {
857 | EngineTicks,
858 | EngineTicksWithPauses,
859 | //RealTimeWithoutLoads
860 | }
861 |
862 | enum SignOnState
863 | {
864 | None = 0,
865 | Challenge = 1,
866 | Connected = 2,
867 | New = 3,
868 | PreSpawn = 4,
869 | Spawn = 5,
870 | Full = 6,
871 | ChangeLevel = 7
872 | }
873 |
874 | // HOSTSTATES
875 | enum HostState
876 | {
877 | NewGame = 0,
878 | LoadGame = 1,
879 | ChangeLevelSP = 2,
880 | ChangeLevelMP = 3,
881 | Run = 4,
882 | GameShutdown = 5,
883 | Shutdown = 6,
884 | Restart = 7
885 | }
886 |
887 | // server_state_t
888 | enum ServerState
889 | {
890 | Dead,
891 | Loading,
892 | Active,
893 | Paused
894 | }
895 |
896 | // MapLoadType_t
897 | enum MapLoadType
898 | {
899 | NewGame = 0,
900 | LoadGame = 1,
901 | Transition = 2,
902 | Background = 3
903 | }
904 |
905 | enum CEntInfoSize
906 | {
907 | HL2 = 4 * 4,
908 | Portal2 = 4 * 6
909 | }
910 |
911 | [Flags]
912 | public enum FL
913 | {
914 | //ONGROUND = (1<<0),
915 | //DUCKING = (1<<1),
916 | //WATERJUMP = (1<<2),
917 | ONTRAIN = (1 << 3),
918 | INRAIN = (1 << 4),
919 | FROZEN = (1 << 5),
920 | ATCONTROLS = (1 << 6),
921 | CLIENT = (1 << 7),
922 | FAKECLIENT = (1 << 8),
923 | //INWATER = (1<<9),
924 | FLY = (1 << 10),
925 | SWIM = (1 << 11),
926 | CONVEYOR = (1 << 12),
927 | NPC = (1 << 13),
928 | GODMODE = (1 << 14),
929 | NOTARGET = (1 << 15),
930 | AIMTARGET = (1 << 16),
931 | PARTIALGROUND = (1 << 17),
932 | STATICPROP = (1 << 18),
933 | GRAPHED = (1 << 19),
934 | GRENADE = (1 << 20),
935 | STEPMOVEMENT = (1 << 21),
936 | DONTTOUCH = (1 << 22),
937 | BASEVELOCITY = (1 << 23),
938 | WORLDBRUSH = (1 << 24),
939 | OBJECT = (1 << 25),
940 | KILLME = (1 << 26),
941 | ONFIRE = (1 << 27),
942 | DISSOLVING = (1 << 28),
943 | TRANSRAGDOLL = (1 << 29),
944 | UNBLOCKABLE_BY_PLAYER = (1 << 30)
945 | }
946 | }
947 |
--------------------------------------------------------------------------------
/GameSpecific/ApertureTag.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using LiveSplit.ComponentUtil;
4 |
5 | namespace LiveSplit.SourceSplit.GameSpecific
6 | {
7 | class ApertureTag : GameSupport
8 | {
9 | // how to match with demos:
10 | // start: first tick when your position is at -723 -2481 17 (cl_showpos 1)
11 | // ending: first tick when screen turns black for ending movie
12 |
13 | private bool _onceFlag;
14 | private Vector3f _startPos = new Vector3f(-723f, -2481f, 17f);
15 | private IntPtr _endDetectEntityPtr;
16 |
17 | // update if it breaks in the future
18 | private const int CAmbientGenericVolumeOffset = 0x3B4;
19 |
20 | public ApertureTag()
21 | {
22 | this.FirstMap = "gg_intro_wakeup";
23 | this.LastMap = "gg_stage_theend";
24 | this.RequiredProperties = PlayerProperties.Position;
25 | }
26 |
27 | public override void OnSessionStart(GameState state)
28 | {
29 | base.OnSessionStart(state);
30 |
31 | _onceFlag = false;
32 | _endDetectEntityPtr = IntPtr.Zero;
33 |
34 | if (this.IsLastMap)
35 | _endDetectEntityPtr = state.GetEntityByName("atw_c_note");
36 | }
37 |
38 | // TODO: detect the secret ending
39 |
40 | public override GameSupportResult OnUpdate(GameState state)
41 | {
42 | if (_onceFlag)
43 | return GameSupportResult.DoNothing;
44 |
45 | // "OnTrigger" "tele_out_shower Enable 1.05 -1"
46 | if (this.IsFirstMap)
47 | {
48 | // first tick player out of shower
49 | if (state.PlayerPosition.Distance(_startPos) < 1.0f)
50 | {
51 | Debug.WriteLine("aperture tag start");
52 | _onceFlag = true;
53 | return GameSupportResult.PlayerGainedControl;
54 | }
55 | }
56 | // "OnHitMax" "atw_c_note Volume 0 0 -1"
57 | // "OnHitMax" "credits_video PlayMovie 0 -1
58 | // volume changes from 50 to 0
59 | else if (this.IsLastMap && _endDetectEntityPtr != IntPtr.Zero)
60 | {
61 | int volume;
62 | if (!state.GameProcess.ReadValue(_endDetectEntityPtr + CAmbientGenericVolumeOffset, out volume))
63 | return GameSupportResult.DoNothing;
64 | if (volume == 0)
65 | {
66 | Debug.WriteLine("aperture tag end");
67 | _onceFlag = true;
68 | _endDetectEntityPtr = IntPtr.Zero;
69 | return GameSupportResult.PlayerLostControl;
70 | }
71 | }
72 |
73 | return GameSupportResult.DoNothing;
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/GameSpecific/BMSRetail.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using LiveSplit.ComponentUtil;
5 |
6 | namespace LiveSplit.SourceSplit.GameSpecific
7 | {
8 | class BMSRetail : GameSupport
9 | {
10 | // how to match with demos:
11 | // start: first tick when your position is at 113 -1225 582 (cl_showpos 1)
12 | // ending: first tick nihilanth's health is zero
13 |
14 | private bool _onceFlag;
15 | private Vector3f _startPos = new Vector3f(113f, -1225f, 582f);
16 | private IntPtr _nihiPtr;
17 | private int _baseEntityHealthOffset = -1;
18 |
19 | public BMSRetail()
20 | {
21 | this.GameTimingMethod = GameTimingMethod.EngineTicksWithPauses;
22 | this.FirstMap = "bm_c1a0a";
23 | this.LastMap = "bm_c4a4a";
24 | this.RequiredProperties = PlayerProperties.Position;
25 | }
26 |
27 | public override void OnGameAttached(GameState state)
28 | {
29 | ProcessModuleWow64Safe server = state.GameProcess.ModulesWow64Safe().FirstOrDefault(x => x.ModuleName.ToLower() == "server.dll");
30 | Trace.Assert(server != null);
31 |
32 | var scanner = new SignatureScanner(state.GameProcess, server.BaseAddress, server.ModuleMemorySize);
33 |
34 | if (GameMemory.GetBaseEntityMemberOffset("m_iHealth", state.GameProcess, scanner, out _baseEntityHealthOffset))
35 | Debug.WriteLine("CBaseEntity::m_iHealth offset = 0x" + _baseEntityHealthOffset.ToString("X"));
36 | }
37 |
38 | public override void OnSessionStart(GameState state)
39 | {
40 | base.OnSessionStart(state);
41 |
42 | _onceFlag = false;
43 |
44 | if (this.IsLastMap && state.PlayerEntInfo.EntityPtr != IntPtr.Zero)
45 | {
46 | _nihiPtr = state.GetEntityByName("nihilanth");
47 | Debug.WriteLine("Nihilanth pointer = 0x" + _nihiPtr.ToString("X"));
48 | }
49 | }
50 |
51 | public override GameSupportResult OnUpdate(GameState state)
52 | {
53 | if (_onceFlag)
54 | return GameSupportResult.DoNothing;
55 |
56 | // map starts
57 | if (this.IsFirstMap)
58 | {
59 | if (state.PlayerPosition.DistanceXY(_startPos) < 1.0f)
60 | {
61 | Debug.WriteLine("black mesa start");
62 | _onceFlag = true;
63 | return GameSupportResult.PlayerGainedControl;
64 | }
65 | }
66 | else if (this.IsLastMap && _nihiPtr != IntPtr.Zero)
67 | {
68 | int nihiHealth;
69 | state.GameProcess.ReadValue(_nihiPtr + _baseEntityHealthOffset, out nihiHealth);
70 | if (nihiHealth == 0)
71 | {
72 | int health;
73 | state.GameProcess.ReadValue(state.PlayerEntInfo.EntityPtr + _baseEntityHealthOffset, out health);
74 |
75 | if (health > 0)
76 | {
77 | Debug.WriteLine("black mesa end");
78 | _onceFlag = true;
79 | return GameSupportResult.PlayerLostControl;
80 | }
81 | }
82 | }
83 | return GameSupportResult.DoNothing;
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/GameSpecific/GameSupport.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 |
4 | namespace LiveSplit.SourceSplit.GameSpecific
5 | {
6 | abstract class GameSupport
7 | {
8 | public string FirstMap { get; protected set; }
9 | public string LastMap { get; protected set; }
10 |
11 | // ticks to subtract
12 | public int StartOffsetTicks { get; protected set; }
13 | public int EndOffsetTicks { get; protected set; }
14 |
15 | public GameTimingMethod GameTimingMethod { get; protected set; } = GameTimingMethod.EngineTicks;
16 |
17 | // which player properties should be updated
18 | private PlayerProperties _requiredProperties;
19 | public PlayerProperties RequiredProperties
20 | {
21 | get
22 | {
23 | #if DEBUG
24 | // so DebugPlayerState() works
25 | return PlayerProperties.ALL;
26 | #else
27 | return _requiredProperties;
28 | #endif
29 | }
30 | set { _requiredProperties = value; }
31 | }
32 |
33 | // what kind of generic auto-start detection to use
34 | // must call base.OnUpdate
35 | private AutoStart _autoStartType;
36 | protected AutoStart AutoStartType
37 | {
38 | get { return _autoStartType; }
39 | set {
40 | if (value == AutoStart.Unfrozen)
41 | this.RequiredProperties |= PlayerProperties.Flags;
42 | else if (value == AutoStart.ViewEntityChanged)
43 | this.RequiredProperties |= PlayerProperties.ViewEntity;
44 | else if (value == AutoStart.ParentEntityChanged)
45 | this.RequiredProperties |= PlayerProperties.ParentEntity;
46 | _autoStartType = value;
47 | }
48 | }
49 |
50 | protected bool IsFirstMap { get; private set; }
51 | protected bool IsLastMap { get; private set; }
52 |
53 | private bool _onceFlag;
54 |
55 | // called when attached to a new game process
56 | public virtual void OnGameAttached(GameState state) { }
57 |
58 | // called on the first tick when player is fully in the game (according to demos)
59 | public virtual void OnSessionStart(GameState state)
60 | {
61 | _onceFlag = false;
62 |
63 | this.IsFirstMap = state.CurrentMap == this.FirstMap;
64 | this.IsLastMap = !this.IsFirstMap && state.CurrentMap == this.LastMap;
65 | }
66 |
67 | // called when player no longer fully in the game (map changed, load started)
68 | public virtual void OnSessionEnd(GameState state) { }
69 |
70 | // called once per tick when player is fully in the game
71 | public virtual GameSupportResult OnUpdate(GameState state)
72 | {
73 | if (_onceFlag)
74 | return GameSupportResult.DoNothing;
75 |
76 | if (this.AutoStartType == AutoStart.Unfrozen
77 | && !state.PlayerFlags.HasFlag(FL.FROZEN)
78 | && state.PrevPlayerFlags.HasFlag(FL.FROZEN))
79 | {
80 | Debug.WriteLine("FL_FROZEN removed from player");
81 | _onceFlag = true;
82 | return GameSupportResult.PlayerGainedControl;
83 | }
84 | else if (this.AutoStartType == AutoStart.ViewEntityChanged
85 | && state.PrevPlayerViewEntityIndex != GameState.ENT_INDEX_PLAYER
86 | && state.PlayerViewEntityIndex == GameState.ENT_INDEX_PLAYER)
87 | {
88 | Debug.WriteLine("view entity changed to player");
89 | _onceFlag = true;
90 | return GameSupportResult.PlayerGainedControl;
91 | }
92 | else if (this.AutoStartType == AutoStart.ParentEntityChanged
93 | && state.PrevPlayerParentEntityHandle != -1
94 | && state.PlayerParentEntityHandle == -1)
95 | {
96 | Debug.WriteLine("player no longer parented");
97 | _onceFlag = true;
98 | return GameSupportResult.PlayerGainedControl;
99 | }
100 |
101 | return GameSupportResult.DoNothing;
102 | }
103 |
104 | public static GameSupport FromGameDir(string gameDir)
105 | {
106 | switch (gameDir.ToLower())
107 | {
108 | case "hl2oe":
109 | case "hl2":
110 | case "ghosting":
111 | case "ghostingmod":
112 | return new HL2();
113 | case "episodic":
114 | return new HL2Ep1();
115 | case "ep2":
116 | return new HL2Ep2();
117 | case "portal":
118 | case "portalelevators":
119 | return new Portal();
120 | case "portal_tfv":
121 | return new PortalTFV();
122 | case "portal2":
123 | return new Portal2();
124 | case "aperturetag":
125 | return new ApertureTag();
126 | case "portal_stories":
127 | return new PortalStoriesMel();
128 | case "bms":
129 | return new BMSRetail();
130 | }
131 |
132 | return null;
133 | }
134 |
135 | protected enum AutoStart
136 | {
137 | None,
138 | Unfrozen,
139 | ViewEntityChanged,
140 | ParentEntityChanged
141 | }
142 | }
143 |
144 | enum GameSupportResult
145 | {
146 | DoNothing,
147 | PlayerGainedControl,
148 | PlayerLostControl
149 | }
150 |
151 | [Flags]
152 | enum PlayerProperties
153 | {
154 | Flags = (1 << 0),
155 | Position = (1 << 1),
156 | ViewEntity = (1 << 2),
157 | ParentEntity = (1 << 3),
158 | ALL = Flags | Position | ViewEntity | ParentEntity
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/GameSpecific/HL2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using LiveSplit.ComponentUtil;
5 |
6 | namespace LiveSplit.SourceSplit.GameSpecific
7 | {
8 | class HL2 : GameSupport
9 | {
10 | // how to match with demos:
11 | // start: first tick when your position is at -9419 -2483 22 (cl_showpos 1)
12 | // ending: first tick when screen flashes white
13 |
14 | private bool _onceFlag;
15 |
16 | private Vector3f _startPos = new Vector3f(-9419f, -2483f, 22f);
17 | private int _baseCombatCharacaterActiveWeaponOffset = -1;
18 | private int _baseEntityHealthOffset = -1;
19 | private int _prevActiveWeapon;
20 |
21 | public HL2()
22 | {
23 | this.GameTimingMethod = GameTimingMethod.EngineTicksWithPauses;
24 | this.FirstMap = "d1_trainstation_01";
25 | this.LastMap = "d3_breen_01";
26 | this.RequiredProperties = PlayerProperties.Position;
27 | }
28 |
29 | public override void OnGameAttached(GameState state)
30 | {
31 | ProcessModuleWow64Safe server = state.GameProcess.ModulesWow64Safe().FirstOrDefault(x => x.ModuleName.ToLower() == "server.dll");
32 | Trace.Assert(server != null);
33 |
34 | var scanner = new SignatureScanner(state.GameProcess, server.BaseAddress, server.ModuleMemorySize);
35 |
36 | if (GameMemory.GetBaseEntityMemberOffset("m_hActiveWeapon", state.GameProcess, scanner, out _baseCombatCharacaterActiveWeaponOffset))
37 | Debug.WriteLine("CBaseCombatCharacater::m_hActiveWeapon offset = 0x" + _baseCombatCharacaterActiveWeaponOffset.ToString("X"));
38 | if (GameMemory.GetBaseEntityMemberOffset("m_iHealth", state.GameProcess, scanner, out _baseEntityHealthOffset))
39 | Debug.WriteLine("CBaseEntity::m_iHealth offset = 0x" + _baseEntityHealthOffset.ToString("X"));
40 | }
41 |
42 | public override void OnSessionStart(GameState state)
43 | {
44 | base.OnSessionStart(state);
45 |
46 | _onceFlag = false;
47 |
48 | if (this.IsLastMap && _baseCombatCharacaterActiveWeaponOffset != -1 && state.PlayerEntInfo.EntityPtr != IntPtr.Zero)
49 | state.GameProcess.ReadValue(state.PlayerEntInfo.EntityPtr + _baseCombatCharacaterActiveWeaponOffset, out _prevActiveWeapon);
50 | }
51 |
52 | public override GameSupportResult OnUpdate(GameState state)
53 | {
54 | if (_onceFlag)
55 | return GameSupportResult.DoNothing;
56 |
57 | if (this.IsFirstMap)
58 | {
59 | // "OnTrigger" "point_teleport_destination,Teleport,,0.1,-1"
60 |
61 | // first tick player is moveable and on the train
62 | if (state.PlayerPosition.DistanceXY(_startPos) <= 1.0)
63 | {
64 | Debug.WriteLine("hl2 start");
65 | _onceFlag = true;
66 | return GameSupportResult.PlayerGainedControl;
67 | }
68 | }
69 | else if (this.IsLastMap && _baseCombatCharacaterActiveWeaponOffset != -1 && state.PlayerEntInfo.EntityPtr != IntPtr.Zero
70 | && _baseEntityHealthOffset != -1)
71 | {
72 | // "OnTrigger2" "weaponstrip_end_game,Strip,,0,-1"
73 | // "OnTrigger2" "fade_blast_1,Fade,,0,-1"
74 |
75 | int activeWeapon;
76 | state.GameProcess.ReadValue(state.PlayerEntInfo.EntityPtr + _baseCombatCharacaterActiveWeaponOffset, out activeWeapon);
77 |
78 | if (activeWeapon == -1 && _prevActiveWeapon != -1
79 | && state.PlayerPosition.Distance(new Vector3f(-2449.5f, -1380.2f, -446.0f)) > 256f) // ignore the initial strip that happens at around 2.19 seconds
80 | {
81 | int health;
82 | state.GameProcess.ReadValue(state.PlayerEntInfo.EntityPtr + _baseEntityHealthOffset, out health);
83 |
84 | if (health > 0)
85 | {
86 | Debug.WriteLine("hl2 end");
87 | _onceFlag = true;
88 | return GameSupportResult.PlayerLostControl;
89 | }
90 | }
91 |
92 | _prevActiveWeapon = activeWeapon;
93 | }
94 |
95 | return GameSupportResult.DoNothing;
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/GameSpecific/HL2Ep1.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using LiveSplit.ComponentUtil;
4 |
5 | namespace LiveSplit.SourceSplit.GameSpecific
6 | {
7 | class HL2Ep1 : GameSupport
8 | {
9 | // how to match with demos:
10 | // start: crosshair appear
11 | // ending: the first tick where your position changes on while on train (cl_showpos 1)
12 |
13 | private bool _onceFlag;
14 | private IntPtr _endDetectEntity;
15 | private IntPtr _startDetectEntity;
16 |
17 | // initial position of the train before it starts moving
18 | private Vector3f _trainStartPos = new Vector3f(11957.6f, 8368.25f, -731.75f);
19 |
20 | public HL2Ep1()
21 | {
22 | this.GameTimingMethod = GameTimingMethod.EngineTicksWithPauses;
23 | this.FirstMap = "ep1_citadel_00";
24 | this.LastMap = "ep1_c17_06";
25 | }
26 |
27 | public override void OnSessionStart(GameState state)
28 | {
29 | base.OnSessionStart(state);
30 |
31 | _onceFlag = false;
32 | _startDetectEntity = IntPtr.Zero;
33 | _endDetectEntity = IntPtr.Zero;
34 |
35 | if (this.IsFirstMap)
36 | _startDetectEntity = state.GetEntityByName("ghostanim_DogIntro");
37 | else if (this.IsLastMap)
38 | _endDetectEntity = state.GetEntityByName("outro_train_1");
39 | }
40 |
41 | public override GameSupportResult OnUpdate(GameState state)
42 | {
43 | if (_onceFlag)
44 | return GameSupportResult.DoNothing;
45 |
46 | if (this.IsFirstMap && _startDetectEntity != IntPtr.Zero)
47 | {
48 | // "PlayerOff" "ghostanim_DogIntro,Kill,,0,-1"
49 |
50 | FL flags;
51 | state.GameProcess.ReadValue(_startDetectEntity + state.GameOffsets.BaseEntityFlagsOffset, out flags);
52 | if (flags.HasFlag(FL.KILLME))
53 | {
54 | Debug.WriteLine("ep1 start");
55 | _onceFlag = true;
56 | _startDetectEntity = IntPtr.Zero;
57 | return GameSupportResult.PlayerGainedControl;
58 | }
59 | }
60 | else if (this.IsLastMap && _endDetectEntity != IntPtr.Zero)
61 | {
62 | // "OnTrigger" "razortrain3,StartForward,,0,-1"
63 | // "OnTrigger" "outro_train_1,SetParent,razortrain3,0,-1"
64 |
65 | Vector3f trainPos;
66 | if (!state.GameProcess.ReadValue(_endDetectEntity + state.GameOffsets.BaseEntityAbsOriginOffset, out trainPos))
67 | return GameSupportResult.DoNothing;
68 |
69 | // if the train started moving, stop timing
70 | if (!trainPos.BitEquals(_trainStartPos))
71 | {
72 | Debug.WriteLine("ep1 end");
73 | _onceFlag = true;
74 | _endDetectEntity = IntPtr.Zero;
75 | return GameSupportResult.PlayerLostControl;
76 | }
77 | }
78 |
79 | return GameSupportResult.DoNothing;
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/GameSpecific/HL2Ep2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using LiveSplit.ComponentUtil;
5 |
6 | namespace LiveSplit.SourceSplit.GameSpecific
7 | {
8 | class HL2Ep2 : GameSupport
9 | {
10 | // how to match this timing with demos:
11 | // start:
12 | // ending: the tick where velocity changes from 600.X to 0.0 AFTER the camera effects (cl_showpos 1)
13 |
14 | private bool _onceFlag;
15 | private int _basePlayerLaggedMovementOffset = -1;
16 | private float _prevLaggedMovementValue;
17 |
18 | public HL2Ep2()
19 | {
20 | this.GameTimingMethod = GameTimingMethod.EngineTicksWithPauses;
21 | this.FirstMap = "ep2_outland_01";
22 | this.LastMap = "ep2_outland_12a";
23 | this.RequiredProperties = PlayerProperties.ParentEntity;
24 | }
25 |
26 | public override void OnGameAttached(GameState state)
27 | {
28 | ProcessModuleWow64Safe server = state.GameProcess.ModulesWow64Safe().FirstOrDefault(x => x.ModuleName.ToLower() == "server.dll");
29 | Trace.Assert(server != null);
30 |
31 | var scanner = new SignatureScanner(state.GameProcess, server.BaseAddress, server.ModuleMemorySize);
32 |
33 | if (GameMemory.GetBaseEntityMemberOffset("m_flLaggedMovementValue", state.GameProcess, scanner, out _basePlayerLaggedMovementOffset))
34 | Debug.WriteLine("CBasePlayer::m_flLaggedMovementValue offset = 0x" + _basePlayerLaggedMovementOffset.ToString("X"));
35 | }
36 |
37 | public override void OnSessionStart(GameState state)
38 | {
39 | base.OnSessionStart(state);
40 |
41 | if (state.PlayerEntInfo.EntityPtr != IntPtr.Zero && _basePlayerLaggedMovementOffset != -1)
42 | state.GameProcess.ReadValue(state.PlayerEntInfo.EntityPtr + _basePlayerLaggedMovementOffset, out _prevLaggedMovementValue);
43 |
44 | _onceFlag = false;
45 | }
46 |
47 | public override GameSupportResult OnUpdate(GameState state)
48 | {
49 | if (_onceFlag)
50 | return GameSupportResult.DoNothing;
51 |
52 | if (this.IsFirstMap && state.PlayerEntInfo.EntityPtr != IntPtr.Zero && _basePlayerLaggedMovementOffset != -1)
53 | {
54 | // "OnMapSpawn" "startcar_speedmod,ModifySpeed,0,0,-1"
55 | // "OnMapSpawn" "startcar_speedmod,ModifySpeed,1,12.5,-1"
56 |
57 | float laggedMovementValue;
58 | state.GameProcess.ReadValue(state.PlayerEntInfo.EntityPtr + _basePlayerLaggedMovementOffset, out laggedMovementValue);
59 |
60 | if (laggedMovementValue.BitEquals(1.0f) && !_prevLaggedMovementValue.BitEquals(1.0f))
61 | {
62 | Debug.WriteLine("ep2 start");
63 | _onceFlag = true;
64 | return GameSupportResult.PlayerGainedControl;
65 | }
66 |
67 | _prevLaggedMovementValue = laggedMovementValue;
68 | }
69 | else if (this.IsLastMap)
70 | {
71 | // "OnTrigger4" "cvehicle.hangar,EnterVehicle,,0,1"
72 |
73 | if (state.PlayerParentEntityHandle != -1
74 | && state.PrevPlayerParentEntityHandle == -1)
75 | {
76 | Debug.WriteLine("ep2 end");
77 | _onceFlag = true;
78 | return GameSupportResult.PlayerLostControl;
79 | }
80 | }
81 |
82 | return GameSupportResult.DoNothing;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/GameSpecific/Portal.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using LiveSplit.ComponentUtil;
5 |
6 | namespace LiveSplit.SourceSplit.GameSpecific
7 | {
8 | class Portal : GameSupport
9 | {
10 | // how to match this timing with demos:
11 | // start: crosshair appear
12 | // ending: crosshair disappear
13 |
14 | private int _playerSuppressingCrosshairOffset = -1;
15 | private bool _prevCrosshairSuppressed;
16 | private bool _onceFlag;
17 | private const int VAULT_SAVE_TICK = 4261;
18 |
19 | public Portal()
20 | {
21 | this.GameTimingMethod = GameTimingMethod.EngineTicksWithPauses;
22 | this.AutoStartType = AutoStart.ViewEntityChanged;
23 | this.FirstMap = "testchmb_a_00";
24 | this.LastMap = "escape_02";
25 | // match portal demo timer
26 | this.StartOffsetTicks = 1;
27 | this.EndOffsetTicks = -1;
28 | }
29 |
30 | public override void OnGameAttached(GameState state)
31 | {
32 | ProcessModuleWow64Safe server = state.GameProcess.ModulesWow64Safe().FirstOrDefault(x => x.ModuleName.ToLower() == "server.dll");
33 | Trace.Assert(server != null);
34 |
35 | var scanner = new SignatureScanner(state.GameProcess, server.BaseAddress, server.ModuleMemorySize);
36 |
37 | if (GameMemory.GetBaseEntityMemberOffset("m_bSuppressingCrosshair", state.GameProcess, scanner, out _playerSuppressingCrosshairOffset))
38 | Debug.WriteLine("CPortalPlayer::m_bSuppressingCrosshair offset = 0x" + _playerSuppressingCrosshairOffset.ToString("X"));
39 | }
40 |
41 | public override void OnSessionStart(GameState state)
42 | {
43 | base.OnSessionStart(state);
44 |
45 | if (this.IsLastMap && state.PlayerEntInfo.EntityPtr != IntPtr.Zero && _playerSuppressingCrosshairOffset != -1)
46 | state.GameProcess.ReadValue(state.PlayerEntInfo.EntityPtr + _playerSuppressingCrosshairOffset, out _prevCrosshairSuppressed);
47 |
48 | _onceFlag = false;
49 | }
50 |
51 | public override GameSupportResult OnUpdate(GameState state)
52 | {
53 | if (this.IsFirstMap)
54 | {
55 | // vault save starts at tick 4261, but update interval may miss it so be a little lenient
56 | if ((state.TickBase >= VAULT_SAVE_TICK && state.TickBase <= VAULT_SAVE_TICK+4) && !_onceFlag)
57 | {
58 | _onceFlag = true;
59 | int ticksSinceVaultSaveTick = state.TickBase - VAULT_SAVE_TICK; // account for missing ticks if update interval missed it
60 | this.StartOffsetTicks = -3534 - ticksSinceVaultSaveTick; // 53.01 seconds
61 | return GameSupportResult.PlayerGainedControl;
62 | }
63 |
64 | this.StartOffsetTicks = 1;
65 | return base.OnUpdate(state);
66 | }
67 | else if (!this.IsLastMap || _onceFlag)
68 | return GameSupportResult.DoNothing;
69 |
70 | if (state.PlayerEntInfo.EntityPtr != IntPtr.Zero && _playerSuppressingCrosshairOffset != -1)
71 | {
72 | bool crosshairSuppressed;
73 | state.GameProcess.ReadValue(state.PlayerEntInfo.EntityPtr + _playerSuppressingCrosshairOffset, out crosshairSuppressed);
74 |
75 | if (crosshairSuppressed && !_prevCrosshairSuppressed)
76 | {
77 | _onceFlag = true;
78 | Debug.WriteLine("porto crosshair detected");
79 | return GameSupportResult.PlayerLostControl;
80 | }
81 |
82 | _prevCrosshairSuppressed = crosshairSuppressed;
83 | }
84 |
85 | return GameSupportResult.DoNothing;
86 | }
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/GameSpecific/Portal2.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using LiveSplit.ComponentUtil;
4 |
5 | namespace LiveSplit.SourceSplit.GameSpecific
6 | {
7 | class Portal2 : GameSupport
8 | {
9 | // how to match this timing with demos:
10 | // start: crosshair appear
11 | // ending: crosshair disappear
12 |
13 | private IntPtr _endDetectEntity;
14 | private float _lastEntCheckTime;
15 | private bool _prevCanShoot;
16 | private bool _onceFlag;
17 |
18 | // update if it breaks in the future
19 | private const int CPropVehicleChoreoGenericPlayerCanShootOffset = 0x880;
20 |
21 | public Portal2()
22 | {
23 | this.AutoStartType = AutoStart.ViewEntityChanged;
24 | this.FirstMap = "sp_a1_intro1";
25 | this.LastMap = "sp_a4_finale4";
26 | }
27 |
28 | public override void OnSessionStart(GameState state)
29 | {
30 | base.OnSessionStart(state);
31 |
32 | _endDetectEntity = IntPtr.Zero;
33 | _lastEntCheckTime = 0;
34 | _prevCanShoot = true;
35 | _onceFlag = false;
36 | }
37 |
38 | public override GameSupportResult OnUpdate(GameState state)
39 | {
40 | if (this.IsFirstMap)
41 | return base.OnUpdate(state);
42 | else if (!this.IsLastMap || _onceFlag)
43 | return GameSupportResult.DoNothing;
44 |
45 | // GetEntityByName is rather expensive so don't check every tick
46 | // there's a period of about 10-15 seconds from when it's created until it's used, so a 5 second
47 | // interval should be good enough
48 | if (_endDetectEntity == IntPtr.Zero && state.TickTime - _lastEntCheckTime > 5.0f)
49 | {
50 | Debug.WriteLine("checking for ending_vehicle ent");
51 | _endDetectEntity = state.GetEntityByName("ending_vehicle");
52 | _lastEntCheckTime = state.TickTime;
53 |
54 | if (_endDetectEntity != IntPtr.Zero)
55 | {
56 | Debug.WriteLine("_endDetectEntity = 0x" + _endDetectEntity.ToString("X"));
57 | state.GameProcess.ReadValue(_endDetectEntity + CPropVehicleChoreoGenericPlayerCanShootOffset, out _prevCanShoot);
58 | }
59 | }
60 |
61 | // "OnTrigger" "ending_vehicle SetCanShoot 0 0 -1"
62 | if (_endDetectEntity != IntPtr.Zero)
63 | {
64 | bool canShoot;
65 | state.GameProcess.ReadValue(_endDetectEntity + CPropVehicleChoreoGenericPlayerCanShootOffset, out canShoot);
66 |
67 | if (!canShoot && _prevCanShoot)
68 | {
69 | Debug.WriteLine("portal 2 ending detection");
70 | _onceFlag = true;
71 | _endDetectEntity = IntPtr.Zero;
72 | return GameSupportResult.PlayerLostControl;
73 | }
74 |
75 | _prevCanShoot = canShoot;
76 | }
77 |
78 | return GameSupportResult.DoNothing;
79 | }
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/GameSpecific/PortalStoriesMel.cs:
--------------------------------------------------------------------------------
1 | using LiveSplit.ComponentUtil;
2 |
3 | namespace LiveSplit.SourceSplit.GameSpecific
4 | {
5 | class PortalStoriesMel : GameSupport
6 | {
7 | private Vector3f _endPos = new Vector3f(48f, 2000f, 288f);
8 | private bool _onceFlag;
9 |
10 | public PortalStoriesMel()
11 | {
12 | this.AutoStartType = AutoStart.Unfrozen;
13 | this.RequiredProperties |= PlayerProperties.Position;
14 | this.FirstMap = "sp_a1_tramride";
15 | this.LastMap = "sp_a4_finale";
16 | }
17 |
18 | public override void OnSessionStart(GameState state)
19 | {
20 | base.OnSessionStart(state);
21 | _onceFlag = false;
22 | }
23 |
24 | public override GameSupportResult OnUpdate(GameState state)
25 | {
26 | if (this.IsFirstMap)
27 | return base.OnUpdate(state);
28 | else if (!this.IsLastMap || _onceFlag)
29 | return GameSupportResult.DoNothing;
30 |
31 | // "OnPressed" "end_teleport Teleport 0 -1"
32 | if (state.PlayerPosition.DistanceXY(_endPos) <= 1.0)
33 | {
34 | _onceFlag = true;
35 | return GameSupportResult.PlayerLostControl;
36 | }
37 |
38 | return GameSupportResult.DoNothing;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/GameSpecific/PortalTFV.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Linq;
4 | using LiveSplit.ComponentUtil;
5 |
6 | namespace LiveSplit.SourceSplit.GameSpecific
7 | {
8 | class PortalTFV : GameSupport
9 | {
10 | // how to match with demos:
11 | // start: first tick when your position is at 0 168 129 (cl_showpos 1)
12 | // ending: first tick player is slowed down by the ending trigger
13 |
14 | private bool _onceFlag;
15 | private Vector3f _startPos = new Vector3f(0f, 168f, 129f);
16 | private int _laggedMovementOffset = -1;
17 | private const int VAULT_SAVE_TICK = 3876;
18 |
19 | public PortalTFV()
20 | {
21 | this.GameTimingMethod = GameTimingMethod.EngineTicksWithPauses;
22 | this.FirstMap = "portaltfv1";
23 | this.LastMap = "portaltfv5";
24 | this.RequiredProperties |= PlayerProperties.Position;
25 | }
26 |
27 | public override void OnGameAttached(GameState state)
28 | {
29 | ProcessModuleWow64Safe server = state.GameProcess.ModulesWow64Safe().FirstOrDefault(x => x.ModuleName.ToLower() == "server.dll");
30 | Trace.Assert(server != null);
31 |
32 | var scanner = new SignatureScanner(state.GameProcess, server.BaseAddress, server.ModuleMemorySize);
33 |
34 | if (GameMemory.GetBaseEntityMemberOffset("m_flLaggedMovementValue", state.GameProcess, scanner, out _laggedMovementOffset))
35 | Debug.WriteLine("CBasePlayer::m_flLaggedMovementValue offset = 0x" + _laggedMovementOffset.ToString("X"));
36 | }
37 |
38 | public override void OnSessionStart(GameState state)
39 | {
40 | base.OnSessionStart(state);
41 | _onceFlag = false;
42 | }
43 |
44 | public override GameSupportResult OnUpdate(GameState state)
45 | {
46 | if (_onceFlag)
47 | return GameSupportResult.DoNothing;
48 |
49 | if (this.IsFirstMap)
50 | {
51 | // vault save starts at tick 3876, but update interval may miss it so be a little lenient
52 | if ((state.TickBase >= VAULT_SAVE_TICK && state.TickBase <= VAULT_SAVE_TICK + 4))
53 | {
54 | Debug.WriteLine("tfv start");
55 | _onceFlag = true;
56 | int ticksSinceVaultSaveTick = state.TickBase - VAULT_SAVE_TICK; // account for missing ticks if update interval missed it
57 | this.StartOffsetTicks = -3803 - ticksSinceVaultSaveTick; // 57.045 seconds
58 | return GameSupportResult.PlayerGainedControl;
59 | }
60 |
61 | // map started without vault save
62 | else if (state.PlayerPosition.DistanceXY(_startPos) < 1.0f)
63 | {
64 | Debug.WriteLine("tfv start");
65 | _onceFlag = true;
66 | return GameSupportResult.PlayerGainedControl;
67 | }
68 | }
69 | else if (this.IsLastMap && state.PlayerEntInfo.EntityPtr != IntPtr.Zero)
70 | {
71 | // "OnTrigger" "weapon_disable2:ModifySpeed:0.4:0:-1"
72 | float laggedMovementValue;
73 | state.GameProcess.ReadValue(state.PlayerEntInfo.EntityPtr + _laggedMovementOffset, out laggedMovementValue);
74 | if (laggedMovementValue==0.4f)
75 | {
76 | Debug.WriteLine("tfv end");
77 | _onceFlag = true;
78 | return GameSupportResult.PlayerLostControl;
79 | }
80 | }
81 |
82 | return GameSupportResult.DoNothing;
83 | }
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/GameState.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.Runtime.InteropServices;
4 | using LiveSplit.ComponentUtil;
5 | using LiveSplit.SourceSplit.GameSpecific;
6 |
7 | namespace LiveSplit.SourceSplit
8 | {
9 | // change back to struct if we ever need to give a copy of the state
10 | // to the ui thread
11 | class GameState
12 | {
13 | public const int ENT_INDEX_PLAYER = 1;
14 |
15 | public Process GameProcess;
16 | public GameOffsets GameOffsets;
17 |
18 | public HostState HostState;
19 | public HostState PrevHostState;
20 |
21 | public SignOnState SignOnState;
22 | public SignOnState PrevSignOnState;
23 |
24 | public ServerState ServerState;
25 | public ServerState PrevServerState;
26 |
27 | public string CurrentMap;
28 | public string GameDir;
29 |
30 | public float IntervalPerTick;
31 | public int RawTickCount;
32 | public int TickBase;
33 | public int TickCount;
34 | public float TickTime;
35 |
36 | public FL PlayerFlags;
37 | public FL PrevPlayerFlags;
38 | public Vector3f PlayerPosition;
39 | public Vector3f PrevPlayerPosition;
40 | public int PlayerViewEntityIndex;
41 | public int PrevPlayerViewEntityIndex;
42 | public int PlayerParentEntityHandle;
43 | public int PrevPlayerParentEntityHandle;
44 |
45 | public CEntInfoV2 PlayerEntInfo;
46 | public GameSupport GameSupport;
47 | public int UpdateCount;
48 |
49 | public GameState(Process game, GameOffsets offsets)
50 | {
51 | this.GameProcess = game;
52 | this.GameOffsets = offsets;
53 | }
54 |
55 | public CEntInfoV2 GetEntInfoByIndex(int index)
56 | {
57 | Debug.Assert(this.GameOffsets.EntInfoSize > 0);
58 |
59 | CEntInfoV2 ret;
60 |
61 | IntPtr addr = this.GameOffsets.GlobalEntityListPtr + ((int)this.GameOffsets.EntInfoSize * index);
62 |
63 | if (this.GameOffsets.EntInfoSize == CEntInfoSize.HL2)
64 | {
65 | CEntInfoV1 v1;
66 | this.GameProcess.ReadValue(addr, out v1);
67 | ret = CEntInfoV2.FromV1(v1);
68 | }
69 | else
70 | {
71 | this.GameProcess.ReadValue(addr, out ret);
72 | }
73 |
74 | return ret;
75 | }
76 |
77 | // warning: expensive - 7ms on i5
78 | // do not call frequently!
79 | public IntPtr GetEntityByName(string name)
80 | {
81 | const int MAX_ENTS = 2048; // TODO: is portal2's max higher?
82 |
83 | for (int i = 0; i < MAX_ENTS; i++)
84 | {
85 | CEntInfoV2 info = this.GetEntInfoByIndex(i);
86 | if (info.EntityPtr == IntPtr.Zero)
87 | continue;
88 |
89 | IntPtr namePtr;
90 | this.GameProcess.ReadPointer(info.EntityPtr + this.GameOffsets.BaseEntityTargetNameOffset, false, out namePtr);
91 | if (namePtr == IntPtr.Zero)
92 | continue;
93 |
94 | string n;
95 | this.GameProcess.ReadString(namePtr, ReadStringType.ASCII, 32, out n); // TODO: find real max len
96 | if (n == name)
97 | return info.EntityPtr;
98 | }
99 |
100 | return IntPtr.Zero;
101 | }
102 | }
103 |
104 | struct GameOffsets
105 | {
106 | public IntPtr CurTimePtr;
107 | public IntPtr TickCountPtr => this.CurTimePtr + 12;
108 | public IntPtr IntervalPerTickPtr => this.TickCountPtr + 4;
109 | public IntPtr SignOnStatePtr;
110 | public IntPtr CurMapPtr;
111 | public IntPtr GlobalEntityListPtr;
112 | public IntPtr GameDirPtr;
113 | public IntPtr HostStatePtr;
114 | // note: only valid during host states: NewGame, ChangeLevelSP, ChangeLevelMP
115 | public IntPtr HostStateLevelNamePtr => this.HostStatePtr + (4*8); // note: this may not work pre-ep1 (ancient engine)
116 | public IntPtr ServerStatePtr;
117 |
118 | public CEntInfoSize EntInfoSize;
119 |
120 | public int BaseEntityFlagsOffset;
121 | public int BaseEntityEFlagsOffset => this.BaseEntityFlagsOffset > 0 ? this.BaseEntityFlagsOffset - 4 : -1;
122 | public int BaseEntityAbsOriginOffset;
123 | public int BaseEntityTargetNameOffset;
124 | public int BaseEntityParentHandleOffset;
125 | public int BasePlayerViewEntity;
126 | }
127 |
128 | [StructLayout(LayoutKind.Sequential)]
129 | struct CEntInfoV1
130 | {
131 | public int m_pEntity;
132 | public int m_SerialNumber;
133 | public int m_pPrev;
134 | public int m_pNext;
135 | }
136 |
137 | [StructLayout(LayoutKind.Sequential)]
138 | struct CEntInfoV2
139 | {
140 | public int m_pEntity;
141 | public int m_SerialNumber;
142 | public int m_pPrev;
143 | public int m_pNext;
144 | public int m_targetname;
145 | public int m_classname;
146 |
147 | public IntPtr EntityPtr => (IntPtr)this.m_pEntity;
148 |
149 | public static CEntInfoV2 FromV1(CEntInfoV1 v1)
150 | {
151 | var ret = new CEntInfoV2();
152 | ret.m_pEntity = v1.m_pEntity;
153 | ret.m_SerialNumber = v1.m_SerialNumber;
154 | ret.m_pPrev = v1.m_pPrev;
155 | ret.m_pNext = v1.m_pNext;
156 | return ret;
157 | }
158 | }
159 | }
160 |
--------------------------------------------------------------------------------
/LiveSplit.SourceSplit.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {EE96132E-A56A-4070-BE18-927DE5653906}
8 | Library
9 | Properties
10 | LiveSplit.SourceSplit
11 | LiveSplit.SourceSplit
12 | v4.6.1
13 | 512
14 |
15 |
16 |
17 |
18 | true
19 | full
20 | false
21 | bin\
22 | DEBUG;TRACE
23 | prompt
24 | 4
25 | false
26 | AnyCPU
27 | AllRules.ruleset
28 | true
29 |
30 |
31 | pdbonly
32 | true
33 | bin\
34 | TRACE
35 | prompt
36 | 4
37 | false
38 | true
39 |
40 |
41 |
42 | ..\..\public\LiveSplit\LiveSplit\bin\Debug\LiveSplit.Core.dll
43 | False
44 |
45 |
46 |
47 |
48 |
49 |
50 | ..\..\public\LiveSplit\LiveSplit\bin\Debug\UpdateManager.dll
51 | False
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 | Form
68 |
69 |
70 | MapTimesForm.cs
71 |
72 |
73 |
74 |
75 | Component
76 |
77 |
78 |
79 | UserControl
80 |
81 |
82 | SourceSplitSettings.cs
83 |
84 |
85 |
86 |
87 |
88 |
89 | MapTimesForm.cs
90 |
91 |
92 | SourceSplitSettings.cs
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
107 |
--------------------------------------------------------------------------------
/LiveSplit.SourceSplit.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 2013
4 | VisualStudioVersion = 12.0.21005.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "LiveSplit.SourceSplit", "LiveSplit.SourceSplit.csproj", "{EE96132E-A56A-4070-BE18-927DE5653906}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {EE96132E-A56A-4070-BE18-927DE5653906}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {EE96132E-A56A-4070-BE18-927DE5653906}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {EE96132E-A56A-4070-BE18-927DE5653906}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {EE96132E-A56A-4070-BE18-927DE5653906}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/MapTimesForm.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace LiveSplit.SourceSplit
2 | {
3 | partial class MapTimesForm
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Windows Form Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.lvMapTimes = new System.Windows.Forms.ListView();
32 | this.chMap = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
33 | this.chTime = ((System.Windows.Forms.ColumnHeader)(new System.Windows.Forms.ColumnHeader()));
34 | this.btnCopy = new System.Windows.Forms.Button();
35 | this.tableLayoutPanel = new System.Windows.Forms.TableLayoutPanel();
36 | this.tableLayoutPanel.SuspendLayout();
37 | this.SuspendLayout();
38 | //
39 | // lvMapTimes
40 | //
41 | this.lvMapTimes.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
42 | this.chMap,
43 | this.chTime});
44 | this.lvMapTimes.Dock = System.Windows.Forms.DockStyle.Fill;
45 | this.lvMapTimes.FullRowSelect = true;
46 | this.lvMapTimes.Location = new System.Drawing.Point(3, 3);
47 | this.lvMapTimes.Name = "lvMapTimes";
48 | this.lvMapTimes.Size = new System.Drawing.Size(268, 257);
49 | this.lvMapTimes.TabIndex = 0;
50 | this.lvMapTimes.UseCompatibleStateImageBehavior = false;
51 | this.lvMapTimes.View = System.Windows.Forms.View.Details;
52 | //
53 | // chMap
54 | //
55 | this.chMap.Text = "Map";
56 | this.chMap.Width = 155;
57 | //
58 | // chTime
59 | //
60 | this.chTime.Text = "Time";
61 | this.chTime.Width = 84;
62 | //
63 | // btnCopy
64 | //
65 | this.btnCopy.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right)));
66 | this.btnCopy.Location = new System.Drawing.Point(196, 266);
67 | this.btnCopy.Name = "btnCopy";
68 | this.btnCopy.Size = new System.Drawing.Size(75, 23);
69 | this.btnCopy.TabIndex = 1;
70 | this.btnCopy.Text = "Copy";
71 | this.btnCopy.UseVisualStyleBackColor = true;
72 | this.btnCopy.Click += new System.EventHandler(this.btnCopy_Click);
73 | //
74 | // tableLayoutPanel
75 | //
76 | this.tableLayoutPanel.ColumnCount = 1;
77 | this.tableLayoutPanel.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
78 | this.tableLayoutPanel.Controls.Add(this.btnCopy, 0, 1);
79 | this.tableLayoutPanel.Controls.Add(this.lvMapTimes, 0, 0);
80 | this.tableLayoutPanel.Dock = System.Windows.Forms.DockStyle.Fill;
81 | this.tableLayoutPanel.Location = new System.Drawing.Point(0, 0);
82 | this.tableLayoutPanel.Name = "tableLayoutPanel";
83 | this.tableLayoutPanel.RowCount = 2;
84 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
85 | this.tableLayoutPanel.RowStyles.Add(new System.Windows.Forms.RowStyle());
86 | this.tableLayoutPanel.Size = new System.Drawing.Size(274, 292);
87 | this.tableLayoutPanel.TabIndex = 2;
88 | //
89 | // MapTimesForm
90 | //
91 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
92 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
93 | this.ClientSize = new System.Drawing.Size(274, 292);
94 | this.Controls.Add(this.tableLayoutPanel);
95 | this.Name = "MapTimesForm";
96 | this.ShowIcon = false;
97 | this.Text = "SourceSplit: Map Times";
98 | this.tableLayoutPanel.ResumeLayout(false);
99 | this.ResumeLayout(false);
100 |
101 | }
102 |
103 | #endregion
104 |
105 | private System.Windows.Forms.ListView lvMapTimes;
106 | private System.Windows.Forms.ColumnHeader chMap;
107 | private System.Windows.Forms.ColumnHeader chTime;
108 | private System.Windows.Forms.Button btnCopy;
109 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanel;
110 | }
111 | }
--------------------------------------------------------------------------------
/MapTimesForm.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.ComponentModel;
3 | using System.Diagnostics;
4 | using System.Text;
5 | using System.Windows.Forms;
6 |
7 | namespace LiveSplit.SourceSplit
8 | {
9 | public partial class MapTimesForm : Form
10 | {
11 | private static MapTimesForm _instance;
12 |
13 | public static MapTimesForm Instance => _instance ?? (_instance = new MapTimesForm());
14 |
15 | private MapTimesForm()
16 | {
17 | InitializeComponent();
18 | }
19 |
20 | public void AddMapTime(string map, string time)
21 | {
22 | this.lvMapTimes.Items.Add(new ListViewItem(new[] {map, time}));
23 | }
24 |
25 | public void Reset()
26 | {
27 | this.lvMapTimes.Items.Clear();
28 | }
29 |
30 | private void btnCopy_Click(object sender, EventArgs e)
31 | {
32 | var str = new StringBuilder();
33 |
34 | foreach (ListViewItem lvi in this.lvMapTimes.Items)
35 | {
36 | str.AppendLine(lvi.SubItems[0].Text + " " + lvi.SubItems[1].Text);
37 | }
38 |
39 | try
40 | {
41 | Clipboard.SetText(str.ToString());
42 | }
43 | catch (Exception ex)
44 | {
45 | Trace.WriteLine(ex.Message);
46 | }
47 | }
48 |
49 | protected override void OnClosing(CancelEventArgs e)
50 | {
51 | this.Hide();
52 | e.Cancel = true;
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/MapTimesForm.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
--------------------------------------------------------------------------------
/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | // General Information about an assembly is controlled through the following
5 | // set of attributes. Change these attribute values to modify the information
6 | // associated with an assembly.
7 | [assembly: AssemblyTitle("LiveSplit.SourceSplit")]
8 | [assembly: AssemblyDescription("")]
9 | [assembly: AssemblyConfiguration("")]
10 | [assembly: AssemblyCompany("")]
11 | [assembly: AssemblyProduct("LiveSplit.SourceSplit")]
12 | [assembly: AssemblyCopyright("")]
13 | [assembly: AssemblyTrademark("")]
14 | [assembly: AssemblyCulture("")]
15 |
16 | // Setting ComVisible to false makes the types in this assembly not visible
17 | // to COM components. If you need to access a type in this assembly from
18 | // COM, set the ComVisible attribute to true on that type.
19 | [assembly: ComVisible(false)]
20 |
21 | // The following GUID is for the ID of the typelib if this project is exposed to COM
22 | [assembly: Guid("5a6419bc-7736-445f-bfa1-23ff9c54996e")]
23 |
24 | // Version information for an assembly consists of the following four values:
25 | //
26 | // Major Version
27 | // Minor Version
28 | // Build Number
29 | // Revision
30 | //
31 | // You can specify all the values or you can default the Build and Revision Numbers
32 | // by using the '*' as shown below:
33 | // [assembly: AssemblyVersion("1.0.*")]
34 | [assembly: AssemblyVersion("2.2.1")]
35 | [assembly: AssemblyFileVersion("2.2.1")]
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | SourceSplit
2 | ===========
3 |
4 | SourceSplit is a [LiveSplit] component for Source engine games. It aims to support every game and engine version, but some features are only available on games where support has been added.
5 |
6 | Features
7 | --------
8 | * Keeps track of Game Time to get rid of loading times. Emulates demo timing perfectly.
9 | * Splits when the map changes. Configurable with map whitelists.
10 | * Auto start/stop/reset the timer. (supported games only)
11 |
12 | Install
13 | -------
14 | Starting with LiveSplit 1.4, you can download and install SourceSplit automatically from within the Splits Editor with just one click. Just type in the name of one of the fully supported games (see below) and click Activate. If your game isn't on the list, try entering "Half-Life 2 Mods". If you want to use your game's real name, you'll have to add SourceSplit to your layout via the Layout Editor instead.
15 |
16 | Configure
17 | ---------
18 | Click "Settings" and see below. After configuring everything you'll most likely want to turn on game time as primary timing, so that your splits will run off game time. You can do this by right-clicking LiveSplit and going to Compare Against->Game Time.
19 |
20 | #### Auto Split
21 | The default settings are fine unless you feel it's splitting too often. In that case you can use the whitelist and add maps you think it should split on completion. Use the delete key to delete items from the list. Note to those unfamiliar with LiveSplit: you must add a split in the Splits Editor for every map it's going to auto-split on.
22 |
23 | #### Auto Start/End/Reset
24 | Auto-reset updates your best times without asking, so reset manually if you don't want them updated.
25 |
26 | #### Game Processes
27 | You don't need to mess with this unless your game's process isn't listed. All of the fully supported games (and a few others) are added by default.
28 |
29 | #### Alternate Timing Method
30 | This makes it show Real Time when comparing against Game Time, and vice versa. Doesn't work when SourceSplit is activated via the Splits Editor. Use [Alternate Timing Method by Dalet] as an alternative.
31 |
32 | Fully Supported Games
33 | ---------------------
34 | * Half-Life 2
35 | * Half-Life 2: Episode One
36 | * Half-Life 2: Episode Two
37 | * Portal
38 | * Portal 2
39 | * Aperture Tag
40 | * Portal Stories: Mel
41 | * (more soon)
42 |
43 | Technical Information
44 | ---------------------
45 | #### How It Works
46 | Reading game memory and [signature scanning]. This method is used in order to try to support every game and engine version, including ones I've never tested at all. The code would be a lot simpler if it were targetting just one game. A ton of reverse engineering was required to do this. Tools used: IDA Pro, OllyDbg, Source SDK.
47 |
48 | #### Timing Accuracy
49 | Note: 1 tick is 15 or 16.666... milliseconds. You can think of it as around the same as one frame at 60FPS. The timing emulates what the engine does when recording a demo.
50 |
51 | Many hours were put into making sure the timer is as accurate as possible. For games with auto-start/end support, timing has tick-perfect accuracy >99% of the time. Otherwise timing accuracy depends on the user (start, end), just like RTA.
52 |
53 | There are some situations where the timing can be off by a few ticks:
54 |
55 | * If your frame rate is lower than the tick rate, a few ticks can be lost on auto-start/end.
56 | * Due to the nature of how SourceSplit reads game memory on a time interval, a few ticks can be lost if it misses the interval.
57 |
58 | tl;dr The timing is very accurate and should never be more than half of a second off.
59 |
60 | Change Log
61 | ----------
62 | https://github.com/fatalis/sourcesplit/releases
63 |
64 | Contact
65 | -------
66 | * [@fatalis_](https://twitter.com/fatalis_)
67 | * [fatalis.twitch@gmail.com](mailto:fatalis.twitch@gmail.com)
68 | * [twitch.tv/fatalis_](http://www.twitch.tv/fatalis_)
69 |
70 | [PayPal Donate](http://fatalis.pw/donate)
71 |
72 | [LiveSplit]:http://livesplit.org/
73 | [signature scanning]:https://wiki.alliedmods.net/Signature_scanning
74 | [Alternate Timing Method by Dalet]:http://livesplit.org/components/
75 |
--------------------------------------------------------------------------------
/SourceSplitComponent.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using LiveSplit.Model;
3 | using LiveSplit.Options;
4 | using LiveSplit.TimeFormatters;
5 | using LiveSplit.UI.Components;
6 | using LiveSplit.UI;
7 | using System;
8 | using System.Collections.Generic;
9 | using System.Drawing;
10 | using System.Linq;
11 | using System.Xml;
12 | using System.Windows.Forms;
13 |
14 | namespace LiveSplit.SourceSplit
15 | {
16 | class SourceSplitComponent : IComponent
17 | {
18 | public string ComponentName => "SourceSplit";
19 |
20 | public SourceSplitSettings Settings { get; set; }
21 | public IDictionary ContextMenuControls { get; protected set; }
22 | protected InfoTimeComponent InternalComponent { get; set; }
23 |
24 | public bool Disposed { get; private set; }
25 | public bool IsLayoutComponent { get; private set; }
26 |
27 | private TimerModel _timer;
28 | private GraphicsCache _cache;
29 |
30 | private GameMemory _gameMemory;
31 |
32 | private float _intervalPerTick;
33 | private int _sessionTicks;
34 | private int _totalMapTicks;
35 | private int _totalTicks;
36 | private int _sessionTicksOffset;
37 | private DateTime? _gamePauseTime;
38 | private int _gamePauseTick;
39 | private GameTimingMethod _gameRecommendedTimingMethod;
40 |
41 | private bool _waitingForDelay;
42 |
43 | private string _currentMap = String.Empty;
44 | private int _splitCount;
45 | private List _mapsVisited;
46 |
47 | private GameTimingMethod GameTimingMethod
48 | {
49 | get
50 | {
51 | switch (this.Settings.GameTimingMethod)
52 | {
53 | case GameTimingMethodSetting.EngineTicks:
54 | return GameTimingMethod.EngineTicks;
55 | case GameTimingMethodSetting.EngineTicksWithPauses:
56 | return GameTimingMethod.EngineTicksWithPauses;
57 | default:
58 | return _gameRecommendedTimingMethod;
59 | }
60 | }
61 | }
62 |
63 | private TimeSpan GameTime
64 | {
65 | get
66 | {
67 | if (_gamePauseTime != null && this.GameTimingMethod == GameTimingMethod.EngineTicksWithPauses)
68 | {
69 | return TimeSpan.FromSeconds((_totalTicks + _gamePauseTick + FakeTicks(_gamePauseTime.Value, DateTime.Now) - _sessionTicksOffset) * _intervalPerTick);
70 | }
71 | else
72 | {
73 | return TimeSpan.FromSeconds((_totalTicks + _sessionTicks - _sessionTicksOffset) * _intervalPerTick);
74 | }
75 | }
76 | }
77 |
78 | public SourceSplitComponent(LiveSplitState state, bool isLayoutComponent)
79 | {
80 | #if DEBUG
81 | // make Debug.WriteLine prepend update count and tick count
82 | Debug.Listeners.Clear();
83 | Debug.Listeners.Add(TimedTraceListener.Instance);
84 | Trace.Listeners.Clear();
85 | Trace.Listeners.Add(TimedTraceListener.Instance);
86 | #endif
87 |
88 | this.IsLayoutComponent = isLayoutComponent;
89 |
90 | this.Settings = new SourceSplitSettings();
91 | this.InternalComponent = new InfoTimeComponent("Game Time", null, new RegularTimeFormatter(TimeAccuracy.Hundredths));
92 |
93 | this.ContextMenuControls = new Dictionary();
94 | this.ContextMenuControls.Add("SourceSplit: Map Times", () => MapTimesForm.Instance.Show());
95 |
96 | _cache = new GraphicsCache();
97 |
98 | _timer = new TimerModel { CurrentState = state };
99 |
100 | state.OnSplit += state_OnSplit;
101 | state.OnReset += state_OnReset;
102 | state.OnStart += state_OnStart;
103 |
104 | _mapsVisited = new List();
105 |
106 | _intervalPerTick = 0.015f; // will update these when attached to game
107 | _gameRecommendedTimingMethod = GameTimingMethod.EngineTicks;
108 |
109 | _gameMemory = new GameMemory(this.Settings);
110 | _gameMemory.OnSetTickRate += gameMemory_OnSetTickRate;
111 | _gameMemory.OnSetTimingMethod += gameMemory_OnSetTimingMethod;
112 | _gameMemory.OnSessionTimeUpdate += gameMemory_OnSessionTimeUpdate;
113 | _gameMemory.OnPlayerGainedControl += gameMemory_OnPlayerGainedControl;
114 | _gameMemory.OnPlayerLostControl += gameMemory_OnPlayerLostControl;
115 | _gameMemory.OnMapChanged += gameMemory_OnMapChanged;
116 | _gameMemory.OnSessionStarted += gameMemory_OnSessionStarted;
117 | _gameMemory.OnSessionEnded += gameMemory_OnSessionEnded;
118 | _gameMemory.OnNewGameStarted += gameMemory_OnNewGameStarted;
119 | _gameMemory.OnGamePaused += gameMemory_OnGamePaused;
120 | _gameMemory.StartReading();
121 | }
122 |
123 | #if DEBUG
124 | ~SourceSplitComponent()
125 | {
126 | Debug.WriteLine("SourceSplitComponent finalizer");
127 | }
128 | #endif
129 |
130 | public void Dispose()
131 | {
132 | this.Disposed = true;
133 |
134 | _timer.CurrentState.OnSplit -= state_OnSplit;
135 | _timer.CurrentState.OnReset -= state_OnReset;
136 | _timer.CurrentState.OnStart -= state_OnStart;
137 |
138 | _timer.CurrentState.IsGameTimePaused = false; // hack
139 | _timer.CurrentState.LoadingTimes = TimeSpan.Zero;
140 |
141 | _gameMemory?.Stop();
142 | }
143 |
144 | public void Update(IInvalidator invalidator, LiveSplitState state, float width, float height, LayoutMode mode)
145 | {
146 | // hack to prevent flicker, doesn't actually pause anything
147 | state.IsGameTimePaused = true;
148 |
149 | // Update is called every 25ms, so up to 25ms IGT can be lost if using delay and no auto-start
150 | if (_waitingForDelay)
151 | {
152 | if (state.CurrentTime.RealTime >= TimeSpan.Zero)
153 | {
154 | _sessionTicksOffset = _sessionTicks;
155 | _waitingForDelay = false;
156 | }
157 | else
158 | {
159 | state.SetGameTime(state.CurrentTime.RealTime);
160 | }
161 | }
162 |
163 | if (!_waitingForDelay)
164 | // update game time, don't show negative time due to tick adjusting
165 | state.SetGameTime(this.GameTime >= TimeSpan.Zero ? this.GameTime : TimeSpan.Zero);
166 |
167 | if (!this.Settings.ShowGameTime)
168 | return;
169 |
170 | this.InternalComponent.TimeValue =
171 | state.CurrentTime[state.CurrentTimingMethod == TimingMethod.GameTime
172 | ? TimingMethod.RealTime : TimingMethod.GameTime];
173 | this.InternalComponent.InformationName = state.CurrentTimingMethod == TimingMethod.GameTime
174 | ? "Real Time" : "Game Time";
175 |
176 | _cache.Restart();
177 | _cache["TimeValue"] = this.InternalComponent.ValueLabel.Text;
178 | _cache["TimingMethod"] = state.CurrentTimingMethod;
179 | if (invalidator != null && _cache.HasChanged)
180 | invalidator.Invalidate(0f, 0f, width, height);
181 | }
182 |
183 | public void DrawVertical(Graphics g, LiveSplitState state, float width, Region region)
184 | {
185 | this.PrepareDraw(state);
186 | this.InternalComponent.DrawVertical(g, state, width, region);
187 | }
188 |
189 | public void DrawHorizontal(Graphics g, LiveSplitState state, float height, Region region)
190 | {
191 | this.PrepareDraw(state);
192 | this.InternalComponent.DrawHorizontal(g, state, height, region);
193 | }
194 |
195 | void PrepareDraw(LiveSplitState state)
196 | {
197 | this.InternalComponent.NameLabel.ForeColor = state.LayoutSettings.TextColor;
198 | this.InternalComponent.ValueLabel.ForeColor = state.LayoutSettings.TextColor;
199 | this.InternalComponent.NameLabel.HasShadow = this.InternalComponent.ValueLabel.HasShadow = state.LayoutSettings.DropShadows;
200 | }
201 |
202 | void state_OnStart(object sender, EventArgs e)
203 | {
204 | _timer.InitializeGameTime();
205 | _totalTicks = 0;
206 | _mapsVisited.Clear();
207 | MapTimesForm.Instance.Reset();
208 | _splitCount = 0;
209 | _totalMapTicks = 0;
210 | _gamePauseTime = null;
211 |
212 | // hack to make sure Portal players aren't using manual offset. we handle offset automatically now.
213 | // remove this eventually
214 | if (_timer.CurrentState.TimePausedAt.Seconds == 53 && _timer.CurrentState.TimePausedAt.Milliseconds == 10)
215 | {
216 | _timer.CurrentState.TimePausedAt = TimeSpan.Zero;
217 | _timer.CurrentState.Run.Offset = TimeSpan.Zero;
218 | }
219 |
220 | if (_timer.CurrentState.TimePausedAt >= TimeSpan.Zero)
221 | _sessionTicksOffset = _sessionTicks - (int)(_timer.CurrentState.TimePausedAt.TotalSeconds / _intervalPerTick);
222 | else
223 | _waitingForDelay = true;
224 | }
225 |
226 | void state_OnReset(object sender, TimerPhase t)
227 | {
228 | MapTimesForm.Instance.Reset();
229 | _waitingForDelay = false;
230 | }
231 |
232 | void state_OnSplit(object sender, EventArgs e)
233 | {
234 | Debug.WriteLine("split at time " + this.GameTime);
235 |
236 | if (_timer.CurrentState.CurrentPhase == TimerPhase.Ended)
237 | {
238 | this.AddMapTime(_currentMap, TimeSpan.FromSeconds(_totalMapTicks + _sessionTicks - _sessionTicksOffset));
239 | this.AddMapTime("-Total-", this.GameTime);
240 | }
241 | }
242 |
243 | // first tick when player is fully in game
244 | void gameMemory_OnSessionStarted(object sender, SessionStartedEventArgs e)
245 | {
246 | _currentMap = e.Map;
247 | }
248 |
249 | void gameMemory_OnSetTickRate(object sender, SetTickRateEventArgs e)
250 | {
251 | Debug.WriteLine("tickrate " + e.IntervalPerTick);
252 | _intervalPerTick = e.IntervalPerTick;
253 | }
254 |
255 | void gameMemory_OnSetTimingMethod(object sender, SetTimingMethodEventArgs e)
256 | {
257 | _gameRecommendedTimingMethod = e.GameTimingMethod;
258 | }
259 |
260 | // called when player is fully in game
261 | void gameMemory_OnSessionTimeUpdate(object sender, SessionTicksUpdateEventArgs e)
262 | {
263 | _sessionTicks = e.SessionTicks;
264 | }
265 |
266 | // player is no longer fully in the game
267 | void gameMemory_OnSessionEnded(object sender, EventArgs e)
268 | {
269 | Debug.WriteLine("session ended, total time was " + TimeSpan.FromSeconds((_sessionTicks - _sessionTicksOffset) * _intervalPerTick));
270 |
271 | if (_gamePauseTime != null && this.GameTimingMethod == GameTimingMethod.EngineTicksWithPauses)
272 | {
273 | _sessionTicksOffset -= FakeTicks(_gamePauseTime.Value, DateTime.Now);
274 | _gamePauseTime = null;
275 | }
276 |
277 | // add up total time and reset session time
278 | _totalTicks += _sessionTicks - _sessionTicksOffset;
279 | _totalMapTicks += _sessionTicks - _sessionTicksOffset;
280 | _sessionTicks = 0;
281 | _sessionTicksOffset = 0;
282 | }
283 |
284 | // called immediately after OnSessionEnded if it was a changelevel
285 | void gameMemory_OnMapChanged(object sender, MapChangedEventArgs e)
286 | {
287 | Debug.WriteLine("gameMemory_OnMapChanged " + e.Map + " " + e.PrevMap);
288 |
289 | // this is in case they load a save that was made before current map
290 | // fuck time travel
291 | if (!_mapsVisited.Contains(e.PrevMap))
292 | {
293 | _mapsVisited.Add(e.PrevMap);
294 | this.AutoSplit(e.PrevMap);
295 | }
296 |
297 | // prevent adding map time twice
298 | if (_timer.CurrentState.CurrentPhase != TimerPhase.Ended && _timer.CurrentState.CurrentPhase != TimerPhase.NotRunning)
299 | this.AddMapTime(e.PrevMap, TimeSpan.FromSeconds(_totalMapTicks * _intervalPerTick));
300 | _totalMapTicks = 0;
301 | }
302 |
303 | void gameMemory_OnPlayerGainedControl(object sender, PlayerControlChangedEventArgs e)
304 | {
305 | if (!this.Settings.AutoStartEndResetEnabled)
306 | return;
307 |
308 | _timer.Reset(); // make sure to reset for games that start from a quicksave (Aperture Tag)
309 | _timer.Start();
310 | _sessionTicksOffset += e.TicksOffset;
311 | }
312 |
313 | void gameMemory_OnPlayerLostControl(object sender, PlayerControlChangedEventArgs e)
314 | {
315 | if (!this.Settings.AutoStartEndResetEnabled)
316 | return;
317 |
318 | _sessionTicksOffset += e.TicksOffset;
319 | this.DoSplit();
320 | }
321 |
322 | void gameMemory_OnNewGameStarted(object sender, EventArgs e)
323 | {
324 | if (!this.Settings.AutoStartEndResetEnabled)
325 | return;
326 |
327 | _timer.Reset();
328 | }
329 |
330 | void gameMemory_OnGamePaused(object sender, GamePausedEventArgs e)
331 | {
332 | if (this.GameTimingMethod != GameTimingMethod.EngineTicksWithPauses)
333 | return;
334 |
335 | if (e.Paused)
336 | {
337 | _gamePauseTime = DateTime.Now;
338 | _gamePauseTick = _sessionTicks;
339 | }
340 | else
341 | {
342 | if (_gamePauseTime != null)
343 | {
344 | Debug.WriteLine("pause done, adding " + TimeSpan.FromSeconds((DateTime.Now - _gamePauseTime.Value).TotalSeconds));
345 | _sessionTicksOffset -= FakeTicks(_gamePauseTime.Value, DateTime.Now);
346 | }
347 | _gamePauseTime = null;
348 | }
349 | }
350 |
351 | int FakeTicks(DateTime start, DateTime end)
352 | {
353 | return (int)((end-start).TotalSeconds / _intervalPerTick);
354 | }
355 |
356 | void AutoSplit(string map)
357 | {
358 | if (!this.Settings.AutoSplitEnabled)
359 | return;
360 |
361 | Debug.WriteLine("AutoSplit " + map);
362 |
363 | map = map.ToLower();
364 |
365 | string[] blacklist = this.Settings.MapBlacklist.Select(x => x.ToLower()).ToArray();
366 |
367 | if (this.Settings.AutoSplitType == AutoSplitType.Whitelist)
368 | {
369 | string[] whitelist = this.Settings.MapWhitelist.Select(x => x.ToLower()).ToArray();
370 |
371 | if (whitelist.Length > 0)
372 | {
373 | if (whitelist.Contains(map))
374 | this.DoSplit();
375 | }
376 | else if (!blacklist.Contains(map))
377 | {
378 | this.DoSplit();
379 | }
380 | }
381 | else if (this.Settings.AutoSplitType == AutoSplitType.Interval)
382 | {
383 | if (!blacklist.Contains(map) && ++_splitCount >= this.Settings.SplitInterval)
384 | {
385 | _splitCount = 0;
386 | this.DoSplit();
387 | }
388 | }
389 | }
390 |
391 | void DoSplit()
392 | {
393 | // make split times accurate
394 | _timer.CurrentState.SetGameTime(this.GameTime);
395 |
396 | HotkeyProfile profile = _timer.CurrentState.Settings.HotkeyProfiles[_timer.CurrentState.CurrentHotkeyProfile];
397 | bool before = profile.DoubleTapPrevention;
398 | profile.DoubleTapPrevention = false;
399 | _timer.Split();
400 | profile.DoubleTapPrevention = before;
401 | }
402 |
403 | // TODO: asterisk for manual start and splits
404 | void AddMapTime(string map, TimeSpan time)
405 | {
406 | string timeStr = time.ToString(time >= TimeSpan.FromHours(1) ? @"hh\:mm\:ss\.fff" : @"mm\:ss\.fff");
407 | MapTimesForm.Instance.AddMapTime(map, timeStr);
408 | }
409 |
410 | public XmlNode GetSettings(XmlDocument document)
411 | {
412 | return this.Settings.GetSettings(document);
413 | }
414 |
415 | public Control GetSettingsControl(LayoutMode mode)
416 | {
417 | return this.Settings;
418 | }
419 |
420 | public void SetSettings(XmlNode settings)
421 | {
422 | this.Settings.SetSettings(settings);
423 | }
424 |
425 | public float MinimumWidth => this.InternalComponent.MinimumWidth;
426 | public float MinimumHeight => this.InternalComponent.MinimumHeight;
427 | public float VerticalHeight => this.Settings.ShowGameTime ? this.InternalComponent.VerticalHeight : 0;
428 | public float HorizontalWidth => this.Settings.ShowGameTime ? this.InternalComponent.HorizontalWidth : 0;
429 | public float PaddingLeft => this.Settings.ShowGameTime ? this.InternalComponent.PaddingLeft : 0;
430 | public float PaddingRight => this.Settings.ShowGameTime ? this.InternalComponent.PaddingRight : 0;
431 | public float PaddingTop => this.Settings.ShowGameTime ? this.InternalComponent.PaddingTop : 0;
432 | public float PaddingBottom => this.Settings.ShowGameTime ? this.InternalComponent.PaddingBottom : 0;
433 | }
434 | }
435 |
--------------------------------------------------------------------------------
/SourceSplitFactory.cs:
--------------------------------------------------------------------------------
1 | using System.Diagnostics;
2 | using System.Reflection;
3 | using System.Windows.Forms;
4 | using LiveSplit.SourceSplit;
5 | using LiveSplit.UI.Components;
6 | using System;
7 | using LiveSplit.Model;
8 |
9 | [assembly: ComponentFactory(typeof(SourceSplitFactory))]
10 |
11 | namespace LiveSplit.SourceSplit
12 | {
13 | public class SourceSplitFactory : IComponentFactory
14 | {
15 | private SourceSplitComponent _instance;
16 |
17 | public string ComponentName => "SourceSplit";
18 | public string Description => "Game Time / Auto-splitting for Source engine games.";
19 | public ComponentCategory Category => ComponentCategory.Control;
20 |
21 | public IComponent Create(LiveSplitState state)
22 | {
23 | // hack to prevent double loading
24 | string caller = new StackFrame(1).GetMethod().Name;
25 | bool createAsLayoutComponent = (caller == "LoadLayoutComponent" || caller == "AddComponent");
26 |
27 | // if component is already loaded somewhere else
28 | if (_instance != null && !_instance.Disposed)
29 | {
30 | MessageBox.Show(
31 | "SourceSplit is already loaded in the " +
32 | (_instance.IsLayoutComponent ? "Layout Editor" : "Splits Editor") + "!",
33 | "Error",
34 | MessageBoxButtons.OK,
35 | MessageBoxIcon.Exclamation);
36 |
37 | throw new Exception("Component already loaded.");
38 | }
39 |
40 | return (_instance = new SourceSplitComponent(state, createAsLayoutComponent));
41 | }
42 |
43 | public string UpdateName => this.ComponentName;
44 | public string UpdateURL => "http://fatalis.pw/livesplit/update/";
45 | public Version Version => Assembly.GetExecutingAssembly().GetName().Version;
46 | public string XMLURL => this.UpdateURL + "Components/update.LiveSplit.SourceSplit.xml";
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/SourceSplitSettings.Designer.cs:
--------------------------------------------------------------------------------
1 | namespace LiveSplit.SourceSplit
2 | {
3 | partial class SourceSplitSettings
4 | {
5 | ///
6 | /// Required designer variable.
7 | ///
8 | private System.ComponentModel.IContainer components = null;
9 |
10 | ///
11 | /// Clean up any resources being used.
12 | ///
13 | /// true if managed resources should be disposed; otherwise, false.
14 | protected override void Dispose(bool disposing)
15 | {
16 | if (disposing && (components != null))
17 | {
18 | components.Dispose();
19 | }
20 | base.Dispose(disposing);
21 | }
22 |
23 | #region Component Designer generated code
24 |
25 | ///
26 | /// Required method for Designer support - do not modify
27 | /// the contents of this method with the code editor.
28 | ///
29 | private void InitializeComponent()
30 | {
31 | this.components = new System.ComponentModel.Container();
32 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle();
33 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle5 = new System.Windows.Forms.DataGridViewCellStyle();
34 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle6 = new System.Windows.Forms.DataGridViewCellStyle();
35 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle7 = new System.Windows.Forms.DataGridViewCellStyle();
36 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle8 = new System.Windows.Forms.DataGridViewCellStyle();
37 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle9 = new System.Windows.Forms.DataGridViewCellStyle();
38 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
39 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
40 | System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
41 | this.chkAutoSplitEnabled = new System.Windows.Forms.CheckBox();
42 | this.groupBox1 = new System.Windows.Forms.GroupBox();
43 | this.tableLayoutPanel3 = new System.Windows.Forms.TableLayoutPanel();
44 | this.groupBox4 = new System.Windows.Forms.GroupBox();
45 | this.lbMapBlacklist = new LiveSplit.SourceSplit.EditableListBox();
46 | this.Column1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
47 | this.tableLayoutPanel4 = new System.Windows.Forms.TableLayoutPanel();
48 | this.rdoWhitelist = new System.Windows.Forms.RadioButton();
49 | this.lblMaps = new System.Windows.Forms.Label();
50 | this.rdoInterval = new System.Windows.Forms.RadioButton();
51 | this.dmnSplitInterval = new System.Windows.Forms.NumericUpDown();
52 | this.groupBox3 = new System.Windows.Forms.GroupBox();
53 | this.lbMapWhitelist = new LiveSplit.SourceSplit.EditableListBox();
54 | this.dataGridViewTextBoxColumn1 = new System.Windows.Forms.DataGridViewTextBoxColumn();
55 | this.groupBox2 = new System.Windows.Forms.GroupBox();
56 | this.lbGameProcesses = new LiveSplit.SourceSplit.EditableListBox();
57 | this.dataGridViewTextBoxColumn2 = new System.Windows.Forms.DataGridViewTextBoxColumn();
58 | this.tlpAutoStartEndReset = new System.Windows.Forms.TableLayoutPanel();
59 | this.gbMisc = new System.Windows.Forms.GroupBox();
60 | this.tlpMisc = new System.Windows.Forms.TableLayoutPanel();
61 | this.chkShowGameTime = new System.Windows.Forms.CheckBox();
62 | this.btnShowMapTimes = new System.Windows.Forms.Button();
63 | this.gbAutoStartEndReset = new System.Windows.Forms.GroupBox();
64 | this.tableLayoutPanel2 = new System.Windows.Forms.TableLayoutPanel();
65 | this.chkAutoStartEndReset = new System.Windows.Forms.CheckBox();
66 | this.gbTiming = new System.Windows.Forms.GroupBox();
67 | this.tlpTiming = new System.Windows.Forms.TableLayoutPanel();
68 | this.cmbTimingMethod = new System.Windows.Forms.ComboBox();
69 | this.lblTimingMethod = new System.Windows.Forms.Label();
70 | this.toolTip = new System.Windows.Forms.ToolTip(this.components);
71 | this.groupBox1.SuspendLayout();
72 | this.tableLayoutPanel3.SuspendLayout();
73 | this.groupBox4.SuspendLayout();
74 | ((System.ComponentModel.ISupportInitialize)(this.lbMapBlacklist)).BeginInit();
75 | this.tableLayoutPanel4.SuspendLayout();
76 | ((System.ComponentModel.ISupportInitialize)(this.dmnSplitInterval)).BeginInit();
77 | this.groupBox3.SuspendLayout();
78 | ((System.ComponentModel.ISupportInitialize)(this.lbMapWhitelist)).BeginInit();
79 | this.groupBox2.SuspendLayout();
80 | ((System.ComponentModel.ISupportInitialize)(this.lbGameProcesses)).BeginInit();
81 | this.tlpAutoStartEndReset.SuspendLayout();
82 | this.gbMisc.SuspendLayout();
83 | this.tlpMisc.SuspendLayout();
84 | this.gbAutoStartEndReset.SuspendLayout();
85 | this.tableLayoutPanel2.SuspendLayout();
86 | this.gbTiming.SuspendLayout();
87 | this.tlpTiming.SuspendLayout();
88 | this.SuspendLayout();
89 | //
90 | // chkAutoSplitEnabled
91 | //
92 | this.chkAutoSplitEnabled.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
93 | this.chkAutoSplitEnabled.AutoSize = true;
94 | this.chkAutoSplitEnabled.Location = new System.Drawing.Point(3, 6);
95 | this.chkAutoSplitEnabled.Name = "chkAutoSplitEnabled";
96 | this.chkAutoSplitEnabled.Size = new System.Drawing.Size(216, 17);
97 | this.chkAutoSplitEnabled.TabIndex = 5;
98 | this.chkAutoSplitEnabled.Text = "Enabled";
99 | this.chkAutoSplitEnabled.UseVisualStyleBackColor = true;
100 | //
101 | // groupBox1
102 | //
103 | this.tlpAutoStartEndReset.SetColumnSpan(this.groupBox1, 2);
104 | this.groupBox1.Controls.Add(this.tableLayoutPanel3);
105 | this.groupBox1.Dock = System.Windows.Forms.DockStyle.Fill;
106 | this.groupBox1.Location = new System.Drawing.Point(3, 3);
107 | this.groupBox1.Name = "groupBox1";
108 | this.groupBox1.Size = new System.Drawing.Size(450, 157);
109 | this.groupBox1.TabIndex = 10;
110 | this.groupBox1.TabStop = false;
111 | this.groupBox1.Text = "Auto Split";
112 | //
113 | // tableLayoutPanel3
114 | //
115 | this.tableLayoutPanel3.ColumnCount = 2;
116 | this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
117 | this.tableLayoutPanel3.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
118 | this.tableLayoutPanel3.Controls.Add(this.groupBox4, 1, 1);
119 | this.tableLayoutPanel3.Controls.Add(this.chkAutoSplitEnabled, 0, 0);
120 | this.tableLayoutPanel3.Controls.Add(this.tableLayoutPanel4, 1, 0);
121 | this.tableLayoutPanel3.Controls.Add(this.groupBox3, 0, 1);
122 | this.tableLayoutPanel3.Dock = System.Windows.Forms.DockStyle.Fill;
123 | this.tableLayoutPanel3.Location = new System.Drawing.Point(3, 16);
124 | this.tableLayoutPanel3.Name = "tableLayoutPanel3";
125 | this.tableLayoutPanel3.RowCount = 2;
126 | this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 30F));
127 | this.tableLayoutPanel3.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
128 | this.tableLayoutPanel3.Size = new System.Drawing.Size(444, 138);
129 | this.tableLayoutPanel3.TabIndex = 20;
130 | //
131 | // groupBox4
132 | //
133 | this.groupBox4.Controls.Add(this.lbMapBlacklist);
134 | this.groupBox4.Dock = System.Windows.Forms.DockStyle.Fill;
135 | this.groupBox4.Location = new System.Drawing.Point(225, 33);
136 | this.groupBox4.Name = "groupBox4";
137 | this.groupBox4.Size = new System.Drawing.Size(216, 102);
138 | this.groupBox4.TabIndex = 16;
139 | this.groupBox4.TabStop = false;
140 | this.groupBox4.Text = "Map Blacklist";
141 | //
142 | // lbMapBlacklist
143 | //
144 | this.lbMapBlacklist.AllowUserToResizeRows = false;
145 | this.lbMapBlacklist.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
146 | this.lbMapBlacklist.BackgroundColor = System.Drawing.SystemColors.Window;
147 | this.lbMapBlacklist.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
148 | this.lbMapBlacklist.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.None;
149 | dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
150 | dataGridViewCellStyle4.BackColor = System.Drawing.SystemColors.Control;
151 | dataGridViewCellStyle4.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
152 | dataGridViewCellStyle4.ForeColor = System.Drawing.SystemColors.WindowText;
153 | dataGridViewCellStyle4.SelectionBackColor = System.Drawing.SystemColors.Highlight;
154 | dataGridViewCellStyle4.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
155 | dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
156 | this.lbMapBlacklist.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle4;
157 | this.lbMapBlacklist.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
158 | this.lbMapBlacklist.ColumnHeadersVisible = false;
159 | this.lbMapBlacklist.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
160 | this.Column1});
161 | dataGridViewCellStyle5.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
162 | dataGridViewCellStyle5.BackColor = System.Drawing.SystemColors.Window;
163 | dataGridViewCellStyle5.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
164 | dataGridViewCellStyle5.ForeColor = System.Drawing.SystemColors.ControlText;
165 | dataGridViewCellStyle5.SelectionBackColor = System.Drawing.SystemColors.Highlight;
166 | dataGridViewCellStyle5.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
167 | dataGridViewCellStyle5.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
168 | this.lbMapBlacklist.DefaultCellStyle = dataGridViewCellStyle5;
169 | this.lbMapBlacklist.Dock = System.Windows.Forms.DockStyle.Fill;
170 | this.lbMapBlacklist.Location = new System.Drawing.Point(3, 16);
171 | this.lbMapBlacklist.Name = "lbMapBlacklist";
172 | dataGridViewCellStyle6.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
173 | dataGridViewCellStyle6.BackColor = System.Drawing.SystemColors.Control;
174 | dataGridViewCellStyle6.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
175 | dataGridViewCellStyle6.ForeColor = System.Drawing.SystemColors.WindowText;
176 | dataGridViewCellStyle6.SelectionBackColor = System.Drawing.SystemColors.Highlight;
177 | dataGridViewCellStyle6.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
178 | dataGridViewCellStyle6.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
179 | this.lbMapBlacklist.RowHeadersDefaultCellStyle = dataGridViewCellStyle6;
180 | this.lbMapBlacklist.RowHeadersVisible = false;
181 | this.lbMapBlacklist.RowTemplate.Height = 14;
182 | this.lbMapBlacklist.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
183 | this.lbMapBlacklist.Size = new System.Drawing.Size(210, 83);
184 | this.lbMapBlacklist.TabIndex = 15;
185 | //
186 | // Column1
187 | //
188 | this.Column1.HeaderText = "Column1";
189 | this.Column1.Name = "Column1";
190 | //
191 | // tableLayoutPanel4
192 | //
193 | this.tableLayoutPanel4.ColumnCount = 4;
194 | this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
195 | this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
196 | this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
197 | this.tableLayoutPanel4.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle());
198 | this.tableLayoutPanel4.Controls.Add(this.rdoWhitelist, 0, 0);
199 | this.tableLayoutPanel4.Controls.Add(this.lblMaps, 3, 0);
200 | this.tableLayoutPanel4.Controls.Add(this.rdoInterval, 1, 0);
201 | this.tableLayoutPanel4.Controls.Add(this.dmnSplitInterval, 2, 0);
202 | this.tableLayoutPanel4.Dock = System.Windows.Forms.DockStyle.Fill;
203 | this.tableLayoutPanel4.Location = new System.Drawing.Point(225, 3);
204 | this.tableLayoutPanel4.Name = "tableLayoutPanel4";
205 | this.tableLayoutPanel4.RowCount = 1;
206 | this.tableLayoutPanel4.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
207 | this.tableLayoutPanel4.Size = new System.Drawing.Size(216, 24);
208 | this.tableLayoutPanel4.TabIndex = 20;
209 | //
210 | // rdoWhitelist
211 | //
212 | this.rdoWhitelist.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
213 | this.rdoWhitelist.AutoSize = true;
214 | this.rdoWhitelist.Location = new System.Drawing.Point(3, 3);
215 | this.rdoWhitelist.Name = "rdoWhitelist";
216 | this.rdoWhitelist.Size = new System.Drawing.Size(65, 17);
217 | this.rdoWhitelist.TabIndex = 18;
218 | this.rdoWhitelist.Text = "Whitelist";
219 | this.rdoWhitelist.UseVisualStyleBackColor = true;
220 | //
221 | // lblMaps
222 | //
223 | this.lblMaps.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
224 | this.lblMaps.AutoSize = true;
225 | this.lblMaps.Location = new System.Drawing.Point(173, 5);
226 | this.lblMaps.Name = "lblMaps";
227 | this.lblMaps.Size = new System.Drawing.Size(40, 13);
228 | this.lblMaps.TabIndex = 11;
229 | this.lblMaps.Text = "maps";
230 | //
231 | // rdoInterval
232 | //
233 | this.rdoInterval.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
234 | this.rdoInterval.AutoSize = true;
235 | this.rdoInterval.Checked = true;
236 | this.rdoInterval.Location = new System.Drawing.Point(74, 3);
237 | this.rdoInterval.Name = "rdoInterval";
238 | this.rdoInterval.Size = new System.Drawing.Size(52, 17);
239 | this.rdoInterval.TabIndex = 17;
240 | this.rdoInterval.TabStop = true;
241 | this.rdoInterval.Text = "Every";
242 | this.rdoInterval.UseVisualStyleBackColor = true;
243 | //
244 | // dmnSplitInterval
245 | //
246 | this.dmnSplitInterval.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Left | System.Windows.Forms.AnchorStyles.Right)));
247 | this.dmnSplitInterval.Enabled = false;
248 | this.dmnSplitInterval.Location = new System.Drawing.Point(132, 3);
249 | this.dmnSplitInterval.Maximum = new decimal(new int[] {
250 | 99,
251 | 0,
252 | 0,
253 | 0});
254 | this.dmnSplitInterval.Minimum = new decimal(new int[] {
255 | 1,
256 | 0,
257 | 0,
258 | 0});
259 | this.dmnSplitInterval.Name = "dmnSplitInterval";
260 | this.dmnSplitInterval.Size = new System.Drawing.Size(35, 20);
261 | this.dmnSplitInterval.TabIndex = 10;
262 | this.dmnSplitInterval.Value = new decimal(new int[] {
263 | 1,
264 | 0,
265 | 0,
266 | 0});
267 | //
268 | // groupBox3
269 | //
270 | this.groupBox3.Controls.Add(this.lbMapWhitelist);
271 | this.groupBox3.Dock = System.Windows.Forms.DockStyle.Fill;
272 | this.groupBox3.Location = new System.Drawing.Point(3, 33);
273 | this.groupBox3.Name = "groupBox3";
274 | this.groupBox3.Size = new System.Drawing.Size(216, 102);
275 | this.groupBox3.TabIndex = 15;
276 | this.groupBox3.TabStop = false;
277 | this.groupBox3.Text = "Map Whitelist";
278 | //
279 | // lbMapWhitelist
280 | //
281 | this.lbMapWhitelist.AllowUserToResizeRows = false;
282 | this.lbMapWhitelist.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
283 | this.lbMapWhitelist.BackgroundColor = System.Drawing.SystemColors.Window;
284 | this.lbMapWhitelist.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
285 | this.lbMapWhitelist.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.None;
286 | dataGridViewCellStyle7.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
287 | dataGridViewCellStyle7.BackColor = System.Drawing.SystemColors.Control;
288 | dataGridViewCellStyle7.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
289 | dataGridViewCellStyle7.ForeColor = System.Drawing.SystemColors.WindowText;
290 | dataGridViewCellStyle7.SelectionBackColor = System.Drawing.SystemColors.Highlight;
291 | dataGridViewCellStyle7.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
292 | dataGridViewCellStyle7.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
293 | this.lbMapWhitelist.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle7;
294 | this.lbMapWhitelist.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
295 | this.lbMapWhitelist.ColumnHeadersVisible = false;
296 | this.lbMapWhitelist.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
297 | this.dataGridViewTextBoxColumn1});
298 | dataGridViewCellStyle8.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
299 | dataGridViewCellStyle8.BackColor = System.Drawing.SystemColors.Window;
300 | dataGridViewCellStyle8.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
301 | dataGridViewCellStyle8.ForeColor = System.Drawing.SystemColors.ControlText;
302 | dataGridViewCellStyle8.SelectionBackColor = System.Drawing.SystemColors.Highlight;
303 | dataGridViewCellStyle8.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
304 | dataGridViewCellStyle8.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
305 | this.lbMapWhitelist.DefaultCellStyle = dataGridViewCellStyle8;
306 | this.lbMapWhitelist.Dock = System.Windows.Forms.DockStyle.Fill;
307 | this.lbMapWhitelist.Location = new System.Drawing.Point(3, 16);
308 | this.lbMapWhitelist.Name = "lbMapWhitelist";
309 | dataGridViewCellStyle9.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
310 | dataGridViewCellStyle9.BackColor = System.Drawing.SystemColors.Control;
311 | dataGridViewCellStyle9.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
312 | dataGridViewCellStyle9.ForeColor = System.Drawing.SystemColors.WindowText;
313 | dataGridViewCellStyle9.SelectionBackColor = System.Drawing.SystemColors.Highlight;
314 | dataGridViewCellStyle9.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
315 | dataGridViewCellStyle9.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
316 | this.lbMapWhitelist.RowHeadersDefaultCellStyle = dataGridViewCellStyle9;
317 | this.lbMapWhitelist.RowHeadersVisible = false;
318 | this.lbMapWhitelist.RowTemplate.Height = 14;
319 | this.lbMapWhitelist.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
320 | this.lbMapWhitelist.Size = new System.Drawing.Size(210, 83);
321 | this.lbMapWhitelist.TabIndex = 16;
322 | //
323 | // dataGridViewTextBoxColumn1
324 | //
325 | this.dataGridViewTextBoxColumn1.HeaderText = "Column1";
326 | this.dataGridViewTextBoxColumn1.Name = "dataGridViewTextBoxColumn1";
327 | //
328 | // groupBox2
329 | //
330 | this.groupBox2.Controls.Add(this.lbGameProcesses);
331 | this.groupBox2.Dock = System.Windows.Forms.DockStyle.Fill;
332 | this.groupBox2.Location = new System.Drawing.Point(231, 166);
333 | this.groupBox2.Name = "groupBox2";
334 | this.groupBox2.Size = new System.Drawing.Size(222, 105);
335 | this.groupBox2.TabIndex = 11;
336 | this.groupBox2.TabStop = false;
337 | this.groupBox2.Text = "Game Process List";
338 | //
339 | // lbGameProcesses
340 | //
341 | this.lbGameProcesses.AllowUserToResizeRows = false;
342 | this.lbGameProcesses.AutoSizeColumnsMode = System.Windows.Forms.DataGridViewAutoSizeColumnsMode.Fill;
343 | this.lbGameProcesses.BackgroundColor = System.Drawing.SystemColors.Window;
344 | this.lbGameProcesses.BorderStyle = System.Windows.Forms.BorderStyle.Fixed3D;
345 | this.lbGameProcesses.CellBorderStyle = System.Windows.Forms.DataGridViewCellBorderStyle.None;
346 | dataGridViewCellStyle1.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
347 | dataGridViewCellStyle1.BackColor = System.Drawing.SystemColors.Control;
348 | dataGridViewCellStyle1.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
349 | dataGridViewCellStyle1.ForeColor = System.Drawing.SystemColors.WindowText;
350 | dataGridViewCellStyle1.SelectionBackColor = System.Drawing.SystemColors.Highlight;
351 | dataGridViewCellStyle1.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
352 | dataGridViewCellStyle1.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
353 | this.lbGameProcesses.ColumnHeadersDefaultCellStyle = dataGridViewCellStyle1;
354 | this.lbGameProcesses.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
355 | this.lbGameProcesses.ColumnHeadersVisible = false;
356 | this.lbGameProcesses.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
357 | this.dataGridViewTextBoxColumn2});
358 | dataGridViewCellStyle2.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
359 | dataGridViewCellStyle2.BackColor = System.Drawing.SystemColors.Window;
360 | dataGridViewCellStyle2.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
361 | dataGridViewCellStyle2.ForeColor = System.Drawing.SystemColors.ControlText;
362 | dataGridViewCellStyle2.SelectionBackColor = System.Drawing.SystemColors.Highlight;
363 | dataGridViewCellStyle2.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
364 | dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
365 | this.lbGameProcesses.DefaultCellStyle = dataGridViewCellStyle2;
366 | this.lbGameProcesses.Dock = System.Windows.Forms.DockStyle.Fill;
367 | this.lbGameProcesses.Location = new System.Drawing.Point(3, 16);
368 | this.lbGameProcesses.Name = "lbGameProcesses";
369 | dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
370 | dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Control;
371 | dataGridViewCellStyle3.Font = new System.Drawing.Font("Microsoft Sans Serif", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
372 | dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.WindowText;
373 | dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight;
374 | dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
375 | dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.True;
376 | this.lbGameProcesses.RowHeadersDefaultCellStyle = dataGridViewCellStyle3;
377 | this.lbGameProcesses.RowHeadersVisible = false;
378 | this.lbGameProcesses.RowTemplate.Height = 14;
379 | this.lbGameProcesses.SelectionMode = System.Windows.Forms.DataGridViewSelectionMode.FullRowSelect;
380 | this.lbGameProcesses.Size = new System.Drawing.Size(216, 86);
381 | this.lbGameProcesses.TabIndex = 17;
382 | //
383 | // dataGridViewTextBoxColumn2
384 | //
385 | this.dataGridViewTextBoxColumn2.HeaderText = "Column1";
386 | this.dataGridViewTextBoxColumn2.Name = "dataGridViewTextBoxColumn2";
387 | //
388 | // tlpAutoStartEndReset
389 | //
390 | this.tlpAutoStartEndReset.ColumnCount = 2;
391 | this.tlpAutoStartEndReset.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
392 | this.tlpAutoStartEndReset.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 50F));
393 | this.tlpAutoStartEndReset.Controls.Add(this.groupBox1, 0, 0);
394 | this.tlpAutoStartEndReset.Controls.Add(this.gbMisc, 0, 2);
395 | this.tlpAutoStartEndReset.Controls.Add(this.gbAutoStartEndReset, 0, 1);
396 | this.tlpAutoStartEndReset.Controls.Add(this.groupBox2, 1, 1);
397 | this.tlpAutoStartEndReset.Controls.Add(this.gbTiming, 1, 2);
398 | this.tlpAutoStartEndReset.Dock = System.Windows.Forms.DockStyle.Fill;
399 | this.tlpAutoStartEndReset.Location = new System.Drawing.Point(7, 7);
400 | this.tlpAutoStartEndReset.Name = "tlpAutoStartEndReset";
401 | this.tlpAutoStartEndReset.RowCount = 4;
402 | this.tlpAutoStartEndReset.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 163F));
403 | this.tlpAutoStartEndReset.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 111F));
404 | this.tlpAutoStartEndReset.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 122F));
405 | this.tlpAutoStartEndReset.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 8F));
406 | this.tlpAutoStartEndReset.Size = new System.Drawing.Size(456, 435);
407 | this.tlpAutoStartEndReset.TabIndex = 13;
408 | //
409 | // gbMisc
410 | //
411 | this.gbMisc.Controls.Add(this.tlpMisc);
412 | this.gbMisc.Dock = System.Windows.Forms.DockStyle.Fill;
413 | this.gbMisc.Location = new System.Drawing.Point(3, 277);
414 | this.gbMisc.Name = "gbMisc";
415 | this.gbMisc.Size = new System.Drawing.Size(222, 116);
416 | this.gbMisc.TabIndex = 14;
417 | this.gbMisc.TabStop = false;
418 | this.gbMisc.Text = "Misc.";
419 | //
420 | // tlpMisc
421 | //
422 | this.tlpMisc.ColumnCount = 1;
423 | this.tlpMisc.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
424 | this.tlpMisc.Controls.Add(this.chkShowGameTime, 0, 0);
425 | this.tlpMisc.Controls.Add(this.btnShowMapTimes, 0, 1);
426 | this.tlpMisc.Dock = System.Windows.Forms.DockStyle.Fill;
427 | this.tlpMisc.Location = new System.Drawing.Point(3, 16);
428 | this.tlpMisc.Name = "tlpMisc";
429 | this.tlpMisc.RowCount = 3;
430 | this.tlpMisc.RowStyles.Add(new System.Windows.Forms.RowStyle());
431 | this.tlpMisc.RowStyles.Add(new System.Windows.Forms.RowStyle());
432 | this.tlpMisc.RowStyles.Add(new System.Windows.Forms.RowStyle());
433 | this.tlpMisc.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Absolute, 20F));
434 | this.tlpMisc.Size = new System.Drawing.Size(216, 97);
435 | this.tlpMisc.TabIndex = 0;
436 | //
437 | // chkShowGameTime
438 | //
439 | this.chkShowGameTime.AutoSize = true;
440 | this.chkShowGameTime.Location = new System.Drawing.Point(3, 3);
441 | this.chkShowGameTime.Name = "chkShowGameTime";
442 | this.chkShowGameTime.Size = new System.Drawing.Size(187, 17);
443 | this.chkShowGameTime.TabIndex = 12;
444 | this.chkShowGameTime.Text = "Show alternate timing method time";
445 | this.chkShowGameTime.UseVisualStyleBackColor = true;
446 | //
447 | // btnShowMapTimes
448 | //
449 | this.btnShowMapTimes.Dock = System.Windows.Forms.DockStyle.Fill;
450 | this.btnShowMapTimes.Location = new System.Drawing.Point(3, 26);
451 | this.btnShowMapTimes.Name = "btnShowMapTimes";
452 | this.btnShowMapTimes.Size = new System.Drawing.Size(210, 25);
453 | this.btnShowMapTimes.TabIndex = 20;
454 | this.btnShowMapTimes.Text = "Show Map Times";
455 | this.btnShowMapTimes.UseVisualStyleBackColor = true;
456 | this.btnShowMapTimes.Click += new System.EventHandler(this.btnShowMapTimes_Click);
457 | //
458 | // gbAutoStartEndReset
459 | //
460 | this.gbAutoStartEndReset.Controls.Add(this.tableLayoutPanel2);
461 | this.gbAutoStartEndReset.Dock = System.Windows.Forms.DockStyle.Fill;
462 | this.gbAutoStartEndReset.Location = new System.Drawing.Point(3, 166);
463 | this.gbAutoStartEndReset.Name = "gbAutoStartEndReset";
464 | this.gbAutoStartEndReset.Size = new System.Drawing.Size(222, 105);
465 | this.gbAutoStartEndReset.TabIndex = 13;
466 | this.gbAutoStartEndReset.TabStop = false;
467 | this.gbAutoStartEndReset.Text = "Auto Start / End / Reset";
468 | //
469 | // tableLayoutPanel2
470 | //
471 | this.tableLayoutPanel2.ColumnCount = 1;
472 | this.tableLayoutPanel2.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 100F));
473 | this.tableLayoutPanel2.Controls.Add(this.chkAutoStartEndReset, 0, 0);
474 | this.tableLayoutPanel2.Dock = System.Windows.Forms.DockStyle.Fill;
475 | this.tableLayoutPanel2.Location = new System.Drawing.Point(3, 16);
476 | this.tableLayoutPanel2.Name = "tableLayoutPanel2";
477 | this.tableLayoutPanel2.RowCount = 2;
478 | this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle());
479 | this.tableLayoutPanel2.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
480 | this.tableLayoutPanel2.Size = new System.Drawing.Size(216, 86);
481 | this.tableLayoutPanel2.TabIndex = 0;
482 | //
483 | // chkAutoStartEndReset
484 | //
485 | this.chkAutoStartEndReset.AutoSize = true;
486 | this.chkAutoStartEndReset.Location = new System.Drawing.Point(3, 3);
487 | this.chkAutoStartEndReset.Name = "chkAutoStartEndReset";
488 | this.chkAutoStartEndReset.Size = new System.Drawing.Size(177, 17);
489 | this.chkAutoStartEndReset.TabIndex = 0;
490 | this.chkAutoStartEndReset.Text = "Enabled (supported games only)";
491 | this.chkAutoStartEndReset.UseVisualStyleBackColor = true;
492 | //
493 | // gbTiming
494 | //
495 | this.gbTiming.Controls.Add(this.tlpTiming);
496 | this.gbTiming.Dock = System.Windows.Forms.DockStyle.Fill;
497 | this.gbTiming.Location = new System.Drawing.Point(231, 277);
498 | this.gbTiming.Name = "gbTiming";
499 | this.gbTiming.Size = new System.Drawing.Size(222, 116);
500 | this.gbTiming.TabIndex = 21;
501 | this.gbTiming.TabStop = false;
502 | this.gbTiming.Text = "Game Time";
503 | //
504 | // tlpTiming
505 | //
506 | this.tlpTiming.ColumnCount = 2;
507 | this.tlpTiming.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 26.85185F));
508 | this.tlpTiming.ColumnStyles.Add(new System.Windows.Forms.ColumnStyle(System.Windows.Forms.SizeType.Percent, 73.14815F));
509 | this.tlpTiming.Controls.Add(this.cmbTimingMethod, 1, 0);
510 | this.tlpTiming.Controls.Add(this.lblTimingMethod, 0, 0);
511 | this.tlpTiming.Dock = System.Windows.Forms.DockStyle.Fill;
512 | this.tlpTiming.Location = new System.Drawing.Point(3, 16);
513 | this.tlpTiming.Name = "tlpTiming";
514 | this.tlpTiming.RowCount = 2;
515 | this.tlpTiming.RowStyles.Add(new System.Windows.Forms.RowStyle());
516 | this.tlpTiming.RowStyles.Add(new System.Windows.Forms.RowStyle(System.Windows.Forms.SizeType.Percent, 100F));
517 | this.tlpTiming.Size = new System.Drawing.Size(216, 97);
518 | this.tlpTiming.TabIndex = 1;
519 | //
520 | // cmbTimingMethod
521 | //
522 | this.cmbTimingMethod.Dock = System.Windows.Forms.DockStyle.Fill;
523 | this.cmbTimingMethod.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList;
524 | this.cmbTimingMethod.FormattingEnabled = true;
525 | this.cmbTimingMethod.Items.AddRange(new object[] {
526 | "Automatic",
527 | "Engine Ticks",
528 | "Engine Ticks with Pauses"});
529 | this.cmbTimingMethod.Location = new System.Drawing.Point(60, 3);
530 | this.cmbTimingMethod.Name = "cmbTimingMethod";
531 | this.cmbTimingMethod.Size = new System.Drawing.Size(153, 21);
532 | this.cmbTimingMethod.TabIndex = 0;
533 | this.toolTip.SetToolTip(this.cmbTimingMethod, "Automatic: Choose depending on rules of the game");
534 | //
535 | // lblTimingMethod
536 | //
537 | this.lblTimingMethod.Anchor = System.Windows.Forms.AnchorStyles.Right;
538 | this.lblTimingMethod.AutoSize = true;
539 | this.lblTimingMethod.Location = new System.Drawing.Point(11, 7);
540 | this.lblTimingMethod.Name = "lblTimingMethod";
541 | this.lblTimingMethod.Size = new System.Drawing.Size(43, 13);
542 | this.lblTimingMethod.TabIndex = 1;
543 | this.lblTimingMethod.Text = "Method";
544 | //
545 | // toolTip
546 | //
547 | this.toolTip.AutoPopDelay = 5000;
548 | this.toolTip.InitialDelay = 100;
549 | this.toolTip.ReshowDelay = 100;
550 | //
551 | // SourceSplitSettings
552 | //
553 | this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
554 | this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
555 | this.Controls.Add(this.tlpAutoStartEndReset);
556 | this.Name = "SourceSplitSettings";
557 | this.Padding = new System.Windows.Forms.Padding(7);
558 | this.Size = new System.Drawing.Size(470, 449);
559 | this.groupBox1.ResumeLayout(false);
560 | this.tableLayoutPanel3.ResumeLayout(false);
561 | this.tableLayoutPanel3.PerformLayout();
562 | this.groupBox4.ResumeLayout(false);
563 | ((System.ComponentModel.ISupportInitialize)(this.lbMapBlacklist)).EndInit();
564 | this.tableLayoutPanel4.ResumeLayout(false);
565 | this.tableLayoutPanel4.PerformLayout();
566 | ((System.ComponentModel.ISupportInitialize)(this.dmnSplitInterval)).EndInit();
567 | this.groupBox3.ResumeLayout(false);
568 | ((System.ComponentModel.ISupportInitialize)(this.lbMapWhitelist)).EndInit();
569 | this.groupBox2.ResumeLayout(false);
570 | ((System.ComponentModel.ISupportInitialize)(this.lbGameProcesses)).EndInit();
571 | this.tlpAutoStartEndReset.ResumeLayout(false);
572 | this.gbMisc.ResumeLayout(false);
573 | this.tlpMisc.ResumeLayout(false);
574 | this.tlpMisc.PerformLayout();
575 | this.gbAutoStartEndReset.ResumeLayout(false);
576 | this.tableLayoutPanel2.ResumeLayout(false);
577 | this.tableLayoutPanel2.PerformLayout();
578 | this.gbTiming.ResumeLayout(false);
579 | this.tlpTiming.ResumeLayout(false);
580 | this.tlpTiming.PerformLayout();
581 | this.ResumeLayout(false);
582 |
583 | }
584 |
585 | #endregion
586 |
587 | private System.Windows.Forms.CheckBox chkAutoSplitEnabled;
588 | private System.Windows.Forms.GroupBox groupBox1;
589 | private System.Windows.Forms.Label lblMaps;
590 | private System.Windows.Forms.NumericUpDown dmnSplitInterval;
591 | private System.Windows.Forms.GroupBox groupBox2;
592 | private EditableListBox lbMapWhitelist;
593 | private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn1;
594 | private EditableListBox lbMapBlacklist;
595 | private System.Windows.Forms.DataGridViewTextBoxColumn Column1;
596 | private System.Windows.Forms.RadioButton rdoWhitelist;
597 | private System.Windows.Forms.RadioButton rdoInterval;
598 | private System.Windows.Forms.TableLayoutPanel tlpAutoStartEndReset;
599 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanel3;
600 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanel4;
601 | private System.Windows.Forms.GroupBox groupBox4;
602 | private System.Windows.Forms.GroupBox groupBox3;
603 | private EditableListBox lbGameProcesses;
604 | private System.Windows.Forms.DataGridViewTextBoxColumn dataGridViewTextBoxColumn2;
605 | private System.Windows.Forms.CheckBox chkShowGameTime;
606 | private System.Windows.Forms.GroupBox gbAutoStartEndReset;
607 | private System.Windows.Forms.TableLayoutPanel tableLayoutPanel2;
608 | private System.Windows.Forms.CheckBox chkAutoStartEndReset;
609 | private System.Windows.Forms.GroupBox gbMisc;
610 | private System.Windows.Forms.TableLayoutPanel tlpMisc;
611 | private System.Windows.Forms.ToolTip toolTip;
612 | private System.Windows.Forms.Button btnShowMapTimes;
613 | private System.Windows.Forms.GroupBox gbTiming;
614 | private System.Windows.Forms.ComboBox cmbTimingMethod;
615 | private System.Windows.Forms.TableLayoutPanel tlpTiming;
616 | private System.Windows.Forms.Label lblTimingMethod;
617 | }
618 | }
619 |
--------------------------------------------------------------------------------
/SourceSplitSettings.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 | using System.Reflection;
4 | using System.Windows.Forms;
5 | using System.Xml;
6 |
7 | namespace LiveSplit.SourceSplit
8 | {
9 | public enum AutoSplitType
10 | {
11 | Whitelist,
12 | Interval
13 | }
14 |
15 | public enum GameTimingMethodSetting
16 | {
17 | Automatic,
18 | EngineTicks,
19 | EngineTicksWithPauses,
20 | //RealTimeWithoutLoads
21 | }
22 |
23 | public partial class SourceSplitSettings : UserControl
24 | {
25 | public bool AutoSplitEnabled { get; set; }
26 | public int SplitInterval { get; set; }
27 | public AutoSplitType AutoSplitType { get; private set; }
28 | public bool ShowGameTime { get; set; }
29 | public bool AutoStartEndResetEnabled { get; set; }
30 |
31 | public string[] MapWhitelist => GetListboxValues(this.lbMapWhitelist);
32 | public string[] MapBlacklist => GetListboxValues(this.lbMapBlacklist);
33 |
34 | public string[] GameProcesses
35 | {
36 | get {
37 | lock (_lock)
38 | return GetListboxValues(this.lbGameProcesses);
39 | }
40 | }
41 |
42 | public GameTimingMethodSetting GameTimingMethod
43 | {
44 | get
45 | {
46 | switch ((string)this.cmbTimingMethod.SelectedItem)
47 | {
48 | case "Engine Ticks":
49 | return GameTimingMethodSetting.EngineTicks;
50 | case "Engine Ticks with Pauses":
51 | return GameTimingMethodSetting.EngineTicksWithPauses;
52 | default:
53 | return GameTimingMethodSetting.Automatic;
54 | }
55 | }
56 | set
57 | {
58 | switch (value)
59 | {
60 | case GameTimingMethodSetting.EngineTicks:
61 | this.cmbTimingMethod.SelectedItem = "Engine Ticks";
62 | break;
63 | case GameTimingMethodSetting.EngineTicksWithPauses:
64 | this.cmbTimingMethod.SelectedItem = "Engine Ticks with Pauses";
65 | break;
66 | default:
67 | this.cmbTimingMethod.SelectedItem = "Automatic";
68 | break;
69 | }
70 | }
71 | }
72 |
73 | private readonly object _lock = new object();
74 |
75 | private const int DEFAULT_SPLITINTERVAL = 1;
76 | private const bool DEFAULT_SHOWGAMETIME = true;
77 | private const bool DEFAULT_AUTOSPLIT_ENABLED = true;
78 | private const bool DEFAULT_AUTOSTARTENDRESET_ENABLED = true;
79 | private const AutoSplitType DEFAULT_AUTOSPLITYPE = AutoSplitType.Interval;
80 | private const GameTimingMethodSetting DEFAULT_GAME_TIMING_METHOD = GameTimingMethodSetting.Automatic;
81 |
82 | public SourceSplitSettings()
83 | {
84 | this.InitializeComponent();
85 |
86 | this.chkAutoSplitEnabled.DataBindings.Add("Checked", this, nameof(this.AutoSplitEnabled), false, DataSourceUpdateMode.OnPropertyChanged);
87 | this.dmnSplitInterval.DataBindings.Add("Value", this, nameof(this.SplitInterval), false, DataSourceUpdateMode.OnPropertyChanged);
88 | this.chkShowGameTime.DataBindings.Add("Checked", this, nameof(this.ShowGameTime), false, DataSourceUpdateMode.OnPropertyChanged);
89 | this.chkAutoStartEndReset.DataBindings.Add("Checked", this, nameof(this.AutoStartEndResetEnabled), false, DataSourceUpdateMode.OnPropertyChanged);
90 |
91 | this.rdoWhitelist.CheckedChanged += rdoAutoSplitType_CheckedChanged;
92 | this.rdoInterval.CheckedChanged += rdoAutoSplitType_CheckedChanged;
93 | this.chkAutoSplitEnabled.CheckedChanged += UpdateDisabledControls;
94 |
95 | // defaults
96 | this.lbGameProcesses.Rows.Add("hl2.exe");
97 | this.lbGameProcesses.Rows.Add("portal2.exe");
98 | this.lbGameProcesses.Rows.Add("dearesther.exe");
99 | this.lbGameProcesses.Rows.Add("mm.exe");
100 | this.lbGameProcesses.Rows.Add("EYE.exe");
101 | this.lbGameProcesses.Rows.Add("bms.exe");
102 | this.SplitInterval = DEFAULT_SPLITINTERVAL;
103 | this.AutoSplitType = DEFAULT_AUTOSPLITYPE;
104 | this.ShowGameTime = DEFAULT_SHOWGAMETIME;
105 | this.AutoSplitEnabled = DEFAULT_AUTOSPLIT_ENABLED;
106 | this.AutoStartEndResetEnabled = DEFAULT_AUTOSTARTENDRESET_ENABLED;
107 | this.GameTimingMethod = DEFAULT_GAME_TIMING_METHOD;
108 |
109 | this.UpdateDisabledControls(this, EventArgs.Empty);
110 | }
111 |
112 | protected override void OnParentChanged(EventArgs e)
113 | {
114 | base.OnParentChanged(e);
115 |
116 | Version version = Assembly.GetExecutingAssembly().GetName().Version;
117 |
118 | if (this.Parent?.Parent?.Parent != null && this.Parent.Parent.Parent.GetType().ToString() == "LiveSplit.View.ComponentSettingsDialog")
119 | this.Parent.Parent.Parent.Text = $"SourceSplit v{version.ToString(3)} - Settings";
120 | }
121 |
122 | public XmlNode GetSettings(XmlDocument doc)
123 | {
124 | XmlElement settingsNode = doc.CreateElement("Settings");
125 |
126 | settingsNode.AppendChild(ToElement(doc, "Version", Assembly.GetExecutingAssembly().GetName().Version.ToString(3)));
127 |
128 | settingsNode.AppendChild(ToElement(doc, nameof(this.AutoSplitEnabled), this.AutoSplitEnabled));
129 | settingsNode.AppendChild(ToElement(doc, nameof(this.SplitInterval), this.SplitInterval));
130 |
131 | string whitelist = String.Join("|", this.MapWhitelist);
132 | settingsNode.AppendChild(ToElement(doc, nameof(this.MapWhitelist), whitelist));
133 |
134 | string blacklist = String.Join("|", this.MapBlacklist);
135 | settingsNode.AppendChild(ToElement(doc, nameof(this.MapBlacklist), blacklist));
136 |
137 | string gameProcesses = String.Join("|", this.GameProcesses);
138 | settingsNode.AppendChild(ToElement(doc, nameof(this.GameProcesses), gameProcesses));
139 |
140 | settingsNode.AppendChild(ToElement(doc, nameof(this.AutoSplitType), this.AutoSplitType));
141 |
142 | settingsNode.AppendChild(ToElement(doc, nameof(this.ShowGameTime), this.ShowGameTime));
143 |
144 | settingsNode.AppendChild(ToElement(doc, nameof(this.GameTimingMethod), this.GameTimingMethod));
145 |
146 | settingsNode.AppendChild(ToElement(doc, nameof(this.AutoStartEndResetEnabled), this.AutoStartEndResetEnabled));
147 |
148 | return settingsNode;
149 | }
150 |
151 | public void SetSettings(XmlNode settings)
152 | {
153 | bool bval;
154 | int ival;
155 |
156 | this.AutoSplitEnabled = settings[nameof(this.AutoSplitEnabled)] != null ?
157 | (Boolean.TryParse(settings[nameof(this.AutoSplitEnabled)].InnerText, out bval) ? bval : DEFAULT_AUTOSPLIT_ENABLED)
158 | : DEFAULT_AUTOSPLIT_ENABLED;
159 |
160 | this.AutoStartEndResetEnabled = settings[nameof(this.AutoStartEndResetEnabled)] != null ?
161 | (Boolean.TryParse(settings[nameof(this.AutoStartEndResetEnabled)].InnerText, out bval) ? bval : DEFAULT_AUTOSTARTENDRESET_ENABLED)
162 | : DEFAULT_AUTOSTARTENDRESET_ENABLED;
163 |
164 | this.SplitInterval = settings[nameof(this.SplitInterval)] != null ?
165 | (Int32.TryParse(settings[nameof(this.SplitInterval)].InnerText, out ival) ? ival : DEFAULT_SPLITINTERVAL)
166 | : DEFAULT_SPLITINTERVAL;
167 |
168 | this.ShowGameTime = settings[nameof(this.ShowGameTime)] != null ?
169 | (Boolean.TryParse(settings[nameof(this.ShowGameTime)].InnerText, out bval) ? bval : DEFAULT_SHOWGAMETIME)
170 | : DEFAULT_SHOWGAMETIME;
171 |
172 | GameTimingMethodSetting gtm;
173 | this.GameTimingMethod = settings[nameof(this.GameTimingMethod)] != null ?
174 | Enum.TryParse(settings[nameof(this.GameTimingMethod)].InnerText, out gtm) ? gtm : DEFAULT_GAME_TIMING_METHOD
175 | : DEFAULT_GAME_TIMING_METHOD;
176 |
177 | AutoSplitType splitType;
178 | this.AutoSplitType = settings[nameof(this.AutoSplitType)] != null ?
179 | (Enum.TryParse(settings[nameof(this.AutoSplitType)].InnerText, out splitType) ? splitType : DEFAULT_AUTOSPLITYPE)
180 | : AutoSplitType.Interval;
181 | this.rdoInterval.Checked = this.AutoSplitType == AutoSplitType.Interval;
182 | this.rdoWhitelist.Checked = this.AutoSplitType == AutoSplitType.Whitelist;
183 |
184 | this.lbMapWhitelist.Rows.Clear();
185 | string whitelist = settings[nameof(this.MapWhitelist)]?.InnerText ?? String.Empty;
186 | foreach (string map in whitelist.Split('|'))
187 | this.lbMapWhitelist.Rows.Add(map);
188 |
189 | this.lbMapBlacklist.Rows.Clear();
190 | string blacklist = settings[nameof(this.MapBlacklist)]?.InnerText ?? String.Empty;
191 | foreach (string map in blacklist.Split('|'))
192 | this.lbMapBlacklist.Rows.Add(map);
193 |
194 | lock (_lock)
195 | {
196 | this.lbGameProcesses.Rows.Clear();
197 | string gameProcesses = settings[nameof(this.GameProcesses)]?.InnerText ?? String.Empty;
198 | foreach (string process in gameProcesses.Split('|'))
199 | this.lbGameProcesses.Rows.Add(process);
200 | }
201 | }
202 |
203 | void rdoAutoSplitType_CheckedChanged(object sender, EventArgs e)
204 | {
205 | this.AutoSplitType = this.rdoInterval.Checked ?
206 | AutoSplitType.Interval :
207 | AutoSplitType.Whitelist;
208 |
209 | this.UpdateDisabledControls(sender, e);
210 | }
211 |
212 | void UpdateDisabledControls(object sender, EventArgs e)
213 | {
214 | this.rdoInterval.Enabled = this.rdoWhitelist.Enabled = this.dmnSplitInterval.Enabled =
215 | this.lbMapBlacklist.Enabled = this.lbMapWhitelist.Enabled =
216 | this.lblMaps.Enabled = this.chkAutoSplitEnabled.Checked;
217 |
218 | this.lbMapWhitelist.Enabled =
219 | (this.AutoSplitType == AutoSplitType.Whitelist && chkAutoSplitEnabled.Checked);
220 | this.lbMapBlacklist.Enabled =
221 | (this.AutoSplitType == AutoSplitType.Interval && chkAutoSplitEnabled.Checked);
222 | }
223 |
224 | static XmlElement ToElement(XmlDocument document, string name, T value)
225 | {
226 | XmlElement str = document.CreateElement(name);
227 | str.InnerText = value.ToString();
228 | return str;
229 | }
230 |
231 | static string[] GetListboxValues(EditableListBox lb)
232 | {
233 | var ret = new List();
234 | foreach (DataGridViewRow row in lb.Rows)
235 | {
236 | if (row.IsNewRow || (lb.CurrentRow == row && lb.IsCurrentRowDirty))
237 | continue;
238 |
239 | string value = (string)row.Cells[0].Value;
240 | if (value == null)
241 | continue;
242 |
243 | value = value.Trim().Replace("|", String.Empty);
244 | if (value.Length > 0)
245 | ret.Add(value);
246 | }
247 | return ret.ToArray();
248 | }
249 |
250 | void btnShowMapTimes_Click(object sender, EventArgs e)
251 | {
252 | MapTimesForm.Instance.Show();
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/SourceSplitSettings.resx:
--------------------------------------------------------------------------------
1 |
2 |
3 |
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 | text/microsoft-resx
110 |
111 |
112 | 2.0
113 |
114 |
115 | System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
116 |
117 |
118 | System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
119 |
120 |
121 | True
122 |
123 |
124 | 17, 17
125 |
126 |
127 | True
128 |
129 |
130 | True
131 |
132 |
--------------------------------------------------------------------------------
/Util.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Diagnostics;
3 | using System.IO;
4 | using System.Linq;
5 | using System.Runtime.CompilerServices;
6 |
7 | namespace LiveSplit.SourceSplit
8 | {
9 | public class Util
10 | {
11 | ///
12 | /// Checks if the process is associated with a VAC protected game.
13 | /// We don't want to touch them. Even though reading a VAC process'
14 | /// memory is said to be perfectly fine and only writing is bad.
15 | ///
16 | public static bool IsVACProtectedProcess(Process p)
17 | {
18 | // http://forums.steampowered.com/forums/showthread.php?t=2465755
19 | // http://en.wikipedia.org/wiki/Valve_Anti-Cheat#Games_that_support_VAC
20 | string[] badExes = { "csgo", "dota2", "swarm", "left4dead",
21 | "left4dead2", "dinodday", "insurgency", "nucleardawn", "ship" };
22 | string[] badMods = { "cstrike", "dods", "hl2mp", "insurgency", "tf", "zps" };
23 | string[] badRootDirs = { "Dark Messiah of Might and Magic Multi-Player" };
24 |
25 | if (badExes.Contains(p.ProcessName.ToLower()))
26 | return true;
27 |
28 | if (p.ProcessName.ToLower() == "hl2" || p.ProcessName.ToLower() == "mm")
29 | {
30 | // it's too difficult to get another process' start arguments, so let's scan the dir
31 | // http://stackoverflow.com/questions/440932/reading-command-line-arguments-of-another-process-win32-c-code
32 |
33 | try
34 | {
35 | string dir = Path.GetDirectoryName(p.MainModule.FileName);
36 | if (dir == null)
37 | return true;
38 |
39 | if (new DirectoryInfo(dir).GetDirectories().Any(di => badMods.Contains(di.Name.ToLower())))
40 | return true;
41 |
42 | string root = new DirectoryInfo(dir).Name.ToLower();
43 | if (badRootDirs.Any(badRoot => badRoot.ToLower() == root))
44 | return true;
45 | }
46 | catch (Exception ex)
47 | {
48 | Trace.WriteLine(ex.ToString());
49 | return true;
50 | }
51 | }
52 |
53 | return false;
54 | }
55 | }
56 |
57 | // prepends update count and tick count to every Debug.WriteLine
58 | public class TimedTraceListener : DefaultTraceListener
59 | {
60 | private static TimedTraceListener _instance;
61 | public static TimedTraceListener Instance => _instance ?? (_instance = new TimedTraceListener());
62 |
63 | private TimedTraceListener() { }
64 |
65 | public int TickCount
66 | {
67 | [MethodImpl(MethodImplOptions.Synchronized)]
68 | get;
69 | [MethodImpl(MethodImplOptions.Synchronized)]
70 | set;
71 | }
72 |
73 | public int UpdateCount
74 | {
75 | [MethodImpl(MethodImplOptions.Synchronized)]
76 | get;
77 | [MethodImpl(MethodImplOptions.Synchronized)]
78 | set;
79 | }
80 |
81 | public override void WriteLine(string message)
82 | {
83 | base.WriteLine("SourceSplit: " + this.UpdateCount + " " + this.TickCount + " " + message);
84 | }
85 | }
86 | }
87 |
--------------------------------------------------------------------------------
/license.txt:
--------------------------------------------------------------------------------
1 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
2 | Version 2, December 2004
3 |
4 | Copyright (C) 2004 Sam Hocevar
5 |
6 | Everyone is permitted to copy and distribute verbatim or modified
7 | copies of this license document, and changing it is allowed as long
8 | as the name is changed.
9 |
10 | DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
11 | TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
12 |
13 | 0. You just DO WHAT THE FUCK YOU WANT TO.
14 |
15 |
--------------------------------------------------------------------------------