void DebugPlayerState(GameState state) { if (state.PlayerFlags != state.PrevPlayerFlags) { string addedList = String.Empty; string removedList = String.Empty; foreach (FL flag in Enum.GetValues(typeof(FL))) { if (state.PlayerFlags.HasFlag(flag) && !state.PrevPlayerFlags.HasFlag(flag)) addedList += Enum.GetName(typeof(FL), flag) + " "; else if (!state.PlayerFlags.HasFlag(flag) && state.PrevPlayerFlags.HasFlag(flag)) removedList += Enum.GetName(typeof(FL), flag) + " "; } if (addedList.Length > 0) Debug.WriteLine("player flags added: " + addedList); if (removedList.Length > 0) Debug.WriteLine("player flags removed: " + removedList); } if (state.PlayerViewEntityIndex != state.PrevPlayerViewEntityIndex) { Debug.WriteLine("player view entity changed: " + state.PlayerViewEntityIndex); } if (state.PlayerParentEntityHandle != state.PrevPlayerParentEntityHandle) { Debug.WriteLine("player parent entity changed: " + state.PlayerParentEntityHandle.ToString("X")); } #if false if (!state.PlayerPosition.BitEquals(state.PrevPlayerPosition)) { Debug.WriteLine("player pos changed: " + state.PlayerParentEntityHandle); } #endif }
void UpdateGameState(GameState state) { Process game = state.GameProcess; GameOffsets offsets = state.GameOffsets; // update all the stuff that doesn't depend on the signon state game.ReadInt32(offsets.TickCountPtr, out state.RawTickCount); game.ReadFloat(offsets.IntervalPerTickPtr, out state.IntervalPerTick); state.PrevSignOnState = state.SignOnState; game.ReadEnum32(offsets.SignOnStatePtr, out state.SignOnState); state.PrevHostState = state.HostState; game.ReadEnum32(offsets.HostStatePtr, out state.HostState); bool firstTick = false; // update the stuff that's only valid during signon state full if (state.SignOnState == SignOnState.Full) { // if signon state just became full (where demos start timing from) if (state.SignOnState != state.PrevSignOnState) { firstTick = true; // start rebasing from this tick state.TickBase = state.RawTickCount; Debug.WriteLine("rebasing ticks from " + state.TickBase); // player was just spawned, get it's ptr state.PlayerEntInfo = state.GetEntInfoByIndex(GameState.ENT_INDEX_PLAYER); // update map name state.GameProcess.ReadASCIIString(state.GameOffsets.CurMapPtr, out state.CurrentMap, 64); } // update time and rebase it against the first signon state full tick state.TickCount = state.RawTickCount - state.TickBase; state.TickTime = state.TickCount * state.IntervalPerTick; TimedTraceListener.Instance.TickCount = state.TickCount; // update player related things if (state.PlayerEntInfo.EntityPtr != IntPtr.Zero && state.GameSupport != null) { // flags if (state.GameSupport.RequiredProperties.HasFlag(PlayerProperties.Flags)) { state.PrevPlayerFlags = state.PlayerFlags; game.ReadEnum32(state.PlayerEntInfo.EntityPtr + offsets.BaseEntityFlagsOffset, out state.PlayerFlags); } // position if (state.GameSupport.RequiredProperties.HasFlag(PlayerProperties.Position)) { state.PrevPlayerPosition = state.PlayerPosition; game.ReadVector3f(state.PlayerEntInfo.EntityPtr + offsets.BaseEntityAbsOriginOffset, out state.PlayerPosition); } // view entity if (state.GameSupport.RequiredProperties.HasFlag(PlayerProperties.ViewEntity)) { const int ENT_ENTRY_MASK = 0x7FF; state.PrevPlayerViewEntityIndex = state.PlayerViewEntityIndex; int viewEntityHandle; // EHANDLE game.ReadInt32(state.PlayerEntInfo.EntityPtr + offsets.BasePlayerViewEntity, out viewEntityHandle); state.PlayerViewEntityIndex = viewEntityHandle == -1 ? GameState.ENT_INDEX_PLAYER : viewEntityHandle & ENT_ENTRY_MASK; } // parent entity if (state.GameSupport.RequiredProperties.HasFlag(PlayerProperties.ParentEntity)) { state.PrevPlayerParentEntityHandle = state.PlayerParentEntityHandle; // EHANDLE game.ReadInt32(state.PlayerEntInfo.EntityPtr + offsets.BaseEntityParentHandleOffset, out state.PlayerParentEntityHandle); } // if it's the first tick, don't use stuff from the previous map if (firstTick) { state.PrevPlayerFlags = state.PlayerFlags; state.PrevPlayerPosition = state.PlayerPosition; state.PrevPlayerViewEntityIndex = state.PlayerViewEntityIndex; state.PrevPlayerParentEntityHandle = state.PlayerParentEntityHandle; } } } // if (state.SignOnState == SignOnState.Full) }
void InitGameState(GameState state) { string absoluteGameDir; state.GameProcess.ReadASCIIString(state.GameOffsets.GameDirPtr, out absoluteGameDir, 260); state.GameDir = new DirectoryInfo(absoluteGameDir).Name.ToLower(); Debug.WriteLine("gameDir = " + state.GameDir); state.CurrentMap = String.Empty; // inspect memory layout to determine CEntInfo's version const int SERIAL_MASK = 0x7FFF; int serial; state.GameProcess.ReadInt32(state.GameOffsets.GlobalEntityListPtr + (4 * 7), out serial); state.GameOffsets.EntInfoSize = (serial > 0 && serial < SERIAL_MASK) ? CEntInfoSize.Portal2 : CEntInfoSize.HL2; state.GameSupport = GameSupport.FromGameDir(state.GameDir); if (state.GameSupport != null) { Debug.WriteLine("running game-specific code for: " + state.GameDir); state.GameSupport.OnGameAttached(state); } }
void HandleProcess(Process game, GameOffsets offsets, CancellationTokenSource cts) { Debug.WriteLine("HandleProcess " + game.ProcessName); var state = new GameState(game, offsets); this.InitGameState(state); var profiler = Stopwatch.StartNew(); while (!game.HasExited && !cts.IsCancellationRequested) { // iteration must never take longer than 1 tick this.UpdateGameState(state); this.CheckGameState(state); state.UpdateCount++; TimedTraceListener.Instance.UpdateCount = state.UpdateCount; if (profiler.ElapsedMilliseconds >= TARGET_UPDATE_RATE) Debug.WriteLine("**** update iteration took too long: " + profiler.ElapsedMilliseconds); //var sleep = Stopwatch.StartNew(); //MapTimesForm.Instance.Text = profiler.Elapsed.ToString(); Thread.Sleep(Math.Max(TARGET_UPDATE_RATE - (int)profiler.ElapsedMilliseconds, 1)); //MapTimesForm.Instance.Text = sleep.Elapsed.ToString(); profiler.Restart(); } }
void HandleGameSupportResult(GameSupportResult result, GameState state) { if (result == GameSupportResult.DoNothing) return; switch (result) { case GameSupportResult.PlayerGainedControl: this.SendGainedControlEvent(state.GameSupport.StartOffsetTicks * state.IntervalPerTick); break; case GameSupportResult.PlayerLostControl: this.SendLostControlEvent(state.GameSupport.EndOffsetTicks * state.IntervalPerTick); break; } }
void CheckGameState(GameState state) { if (state.SignOnState != state.PrevSignOnState) Debug.WriteLine("SignOnState changed to " + state.SignOnState); // if player is fully in game if (state.SignOnState == SignOnState.Full && state.HostState == HostState.Run) { // note: seems to be slow sometimes. ~3ms this.SendSessionTimeUpdateEvent(state.TickTime); // first tick when player is fully in game if (state.SignOnState != state.PrevSignOnState) { Debug.WriteLine("session started"); this.SendSessionStartedEvent(state.CurrentMap); if (state.GameSupport != null) state.GameSupport.OnSessionStart(state); } if (state.GameSupport != null) this.HandleGameSupportResult(state.GameSupport.OnUpdate(state), state); #if DEBUG if (state.PlayerEntInfo.EntityPtr != IntPtr.Zero) DebugPlayerState(state); #endif } if (state.HostState != state.PrevHostState) { if (state.PrevHostState == HostState.Run) { // the map changed or a quicksave was loaded Debug.WriteLine("session ended"); // the map changed or a save was loaded this.SendSessionEndedEvent(); if (state.GameSupport != null) state.GameSupport.OnSessionEnd(state); } Debug.WriteLine("host state changed to " + state.HostState); // HostState::m_levelName is changed much earlier than state.CurrentMap (CBaseServer::m_szMapName) // reading HostStateLevelNamePtr is only valid during these states (not LoadGame!) if (state.HostState == HostState.ChangeLevelSP || state.HostState == HostState.ChangeLevelMP || state.HostState == HostState.NewGame) { string levelName; state.GameProcess.ReadASCIIString(state.GameOffsets.HostStateLevelNamePtr, out levelName, 256-1); Debug.WriteLine("host state m_levelName changed to " + levelName); if (state.HostState == HostState.NewGame) { if (state.GameSupport != null && levelName == state.GameSupport.FirstMap) this.SendNewGameStartedEvent(levelName); } else // changelevel sp/mp { // state.CurrentMap should still be the previous map this.SendMapChangedEvent(levelName, state.CurrentMap); } } } }