├── 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 | --------------------------------------------------------------------------------