public IntPtr _object; //Assume this always has largest offset public PlayerInfo(IntPtr baseAddr, ProcessMemory MemInstance, GameOffsets CurrentOffsets) { unsafe { var baseAddrCopy = baseAddr; int last = MemInstance.OffsetAddress(ref baseAddrCopy, 0, 0); var intPtrSize = MemInstance.is64Bit ? 8 : 4; int size = ((int)Math.Ceiling((decimal)((intPtrSize + CurrentOffsets.PlayerInfoStructOffsets.ObjectOffset) / 8))) * 8; //Find the nearest multiple of 8 byte[] buffer = MemInstance.Read(baseAddrCopy + last, size); PlayerInfoStructOffsets pOf = CurrentOffsets.PlayerInfoStructOffsets; fixed(byte *ptr = buffer) { var buffptr = (IntPtr)ptr; PlayerId = Marshal.ReadByte(buffptr, pOf.PlayerIDOffset); var NamePTR = MemInstance.is64Bit ? (IntPtr)Marshal.ReadInt64(buffptr, pOf.PlayerNameOffset) : (IntPtr)Marshal.ReadInt32(buffptr, pOf.PlayerNameOffset); PlayerName = NamePTR == IntPtr.Zero ? "" : MemInstance.ReadString(NamePTR, CurrentOffsets.StringOffsets[0], CurrentOffsets.StringOffsets[1]); ColorId = (PlayerColor)(uint)Marshal.ReadInt32(buffptr, pOf.ColorIDOffset); HatId = (uint)Marshal.ReadInt32(buffptr, pOf.HatIDOffset); PetId = (uint)Marshal.ReadInt32(buffptr, pOf.PetIDOffset); SkinId = (uint)Marshal.ReadInt32(buffptr, pOf.SkinIDOffset); Disconnected = Marshal.ReadByte(buffptr, pOf.DisconnectedOffset) > 0; Tasks = Marshal.ReadIntPtr(buffptr, pOf.TasksOffset); IsImpostor = Marshal.ReadByte(buffptr, pOf.ImposterOffset) == 1; IsDead = Marshal.ReadByte(buffptr, pOf.DeadOffset) > 0; _object = Marshal.ReadIntPtr(buffptr, pOf.ObjectOffset); } } }
public IntPtr _object; //Assume this always has largest offset public PlayerInfo(IntPtr baseAddr, ProcessMemory MemInstance, GameOffsets CurrentOffsets) { unsafe { var baseAddrCopy = baseAddr; int last = MemInstance.OffsetAddress(ref baseAddrCopy, 0, 0); var intPtrSize = MemInstance.is64Bit ? 8 : 4; int size = ((int)Math.Ceiling((decimal)((intPtrSize + CurrentOffsets.PlayerInfoStructOffsets.ObjectOffset) / 8))) * 8; //Find the nearest multiple of 8 byte[] buffer = MemInstance.Read(baseAddrCopy + last, size); PlayerInfoStructOffsets pOf = CurrentOffsets.PlayerInfoStructOffsets; PlayerOutfitStructOffsets oOf = CurrentOffsets.PlayerOutfitStructOffsets; var outfit = MemInstance.Read <IntPtr>(baseAddrCopy, pOf.OutfitsOffset); fixed(byte *ptr = buffer) { var buffptr = (IntPtr)ptr; PlayerId = Marshal.ReadByte(buffptr, pOf.PlayerIDOffset); Disconnected = Marshal.ReadByte(buffptr, pOf.DisconnectedOffset) > 0; Tasks = Marshal.ReadIntPtr(buffptr, pOf.TasksOffset); IsDead = Marshal.ReadByte(buffptr, pOf.IsDeadOffset) > 0; _object = Marshal.ReadIntPtr(buffptr, pOf.ObjectOffset); // Read from Role RoleType = (uint)MemInstance.Read <int>(baseAddrCopy, pOf.RoleTypeOffset); RoleTeamType = (uint)MemInstance.Read <int>(baseAddrCopy, pOf.RoleTeamTypeOffset); IsImpostor = RoleTeamType == 1; // Read from PlayerOutfit PlayerName = MemInstance.ReadString(MemInstance.Read <IntPtr>(outfit, oOf.PlayerNameOffset), CurrentOffsets.StringOffsets[0], CurrentOffsets.StringOffsets[1]); ColorId = (PlayerColor)(uint)MemInstance.Read <int>(outfit, oOf.ColorIDOffset); // TODO: Since IDs are changed from enum to string like "hat_police", renaming or mapping existing svgs to string is required // TODO: As a workaround just fill with 0 as IDs //HatId = MemInstance.ReadString(MemInstance.Read<IntPtr>(outfit, oOf.HatIDOffset), CurrentOffsets.StringOffsets[0], CurrentOffsets.StringOffsets[1]); //PetId = MemInstance.ReadString(MemInstance.Read<IntPtr>(outfit, oOf.PetIDOffset), CurrentOffsets.StringOffsets[0], CurrentOffsets.StringOffsets[1]); //SkinId = MemInstance.ReadString(MemInstance.Read<IntPtr>(outfit, oOf.SkinIDOffset), CurrentOffsets.StringOffsets[0], CurrentOffsets.StringOffsets[1]); HatId = 0; PetId = 0; SkinId = 0; } } }
public void RunLoop() { while (true) { if (!ProcessMemory.IsHooked) { if (!ProcessMemory.HookProcess("Among Us")) { Thread.Sleep(1000); continue; } else { Console.WriteLine("Connected to Among Us process ({0})", ProcessMemory.process.Id); bool foundModule = false; while (true) { foreach (ProcessMemory.Module module in ProcessMemory.modules) { if (module.Name.Equals("GameAssembly.dll", StringComparison.OrdinalIgnoreCase)) { GameAssemblyPtr = module.BaseAddress; foundModule = true; break; } } if (!foundModule) { Console.WriteLine("Still looking for modules..."); // TODO: This still isn't functional, we need to re-hook to reload module addresses Thread.Sleep(500); // delay and try again } else { break; // we have found all modules } } Console.WriteLine($"({GameAssemblyPtr})"); } } GameState state; int meetingHudState = ProcessMemory.ReadWithDefault <int>(GameAssemblyPtr, 4, 0xDA58D0, 0x5C, 0, 0x84); // 0 = Discussion, 1 = NotVoted, 2 = Voted, 3 = Results, 4 = Proceeding int gameState = ProcessMemory.Read <int>(GameAssemblyPtr, 0xDA5ACC, 0x5C, 0, 0x64); // 0 = NotJoined, 1 = Joined, 2 = Started, 3 = Ended (during "defeat" or "victory" screen only) if (gameState == 0) { state = GameState.MENU; exileCausesEnd = false; } else if (gameState == 1 || gameState == 3) { state = GameState.LOBBY; exileCausesEnd = false; } else if (exileCausesEnd) { state = GameState.LOBBY; } else if (meetingHudState < 4) { state = GameState.DISCUSSION; } else { state = GameState.TASKS; } IntPtr allPlayersPtr = ProcessMemory.Read <IntPtr>(GameAssemblyPtr, 0xDA5A60, 0x5C, 0, 0x24); IntPtr allPlayers = ProcessMemory.Read <IntPtr>(allPlayersPtr, 0x08); int playerCount = ProcessMemory.Read <int>(allPlayersPtr, 0x0C); IntPtr playerAddrPtr = allPlayers + 0x10; // check if exile causes end if (oldState == GameState.DISCUSSION && state == GameState.TASKS) { byte exiledPlayerId = ProcessMemory.ReadWithDefault <byte>(GameAssemblyPtr, 255, 0xDA58D0, 0x5C, 0, 0x94, 0x08); Console.WriteLine($"Player with id {exiledPlayerId} was exiled."); int impostorCount = 0, innocentCount = 0; for (int i = 0; i < playerCount; i++) { PlayerInfo pi = ProcessMemory.Read <PlayerInfo>(playerAddrPtr, 0, 0); playerAddrPtr += 4; // skip invalid, dead and exiled players if (pi.PlayerName == 0 || pi.PlayerId == exiledPlayerId || pi.IsDead == 1) { continue; } if (pi.IsImpostor == 1) { impostorCount++; } else { innocentCount++; } } if (impostorCount == 0 || impostorCount >= innocentCount) { exileCausesEnd = true; state = GameState.LOBBY; } } if (this.shouldTransmitState) { shouldTransmitState = false; GameStateChanged.Invoke(this, new GameStateChangedEventArgs() { NewState = state }); } else if (state != oldState) { if (oldState == GameState.DISCUSSION && state == GameState.TASKS) // send delayed { Task.Delay(7000).ContinueWith((task) => { this.ForceTransmitState(); }); } else { GameStateChanged.Invoke(this, new GameStateChangedEventArgs() { NewState = state }); } } oldState = state; newPlayerInfos.Clear(); playerAddrPtr = allPlayers + 0x10; for (int i = 0; i < playerCount; i++) { PlayerInfo pi = ProcessMemory.Read <PlayerInfo>(playerAddrPtr, 0, 0); playerAddrPtr += 4; if (pi.PlayerName == 0) { continue; } string playerName = pi.GetPlayerName(); newPlayerInfos[playerName] = pi; // add to new playerinfos for comparison later if (!oldPlayerInfos.ContainsKey(playerName)) // player wasn't here before, they just joined { PlayerChanged.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.Joined, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } else // player was here before, we have an old playerInfo to compare against { PlayerInfo oldPlayerInfo = oldPlayerInfos[playerName]; if (!oldPlayerInfo.GetIsDead() && pi.GetIsDead()) // player just died { PlayerChanged.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.Died, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } if (oldPlayerInfo.ColorId != pi.ColorId) { PlayerChanged.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.ChangedColor, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } } } foreach (KeyValuePair <string, PlayerInfo> kvp in oldPlayerInfos) { PlayerInfo pi = kvp.Value; string playerName = kvp.Key; if (!newPlayerInfos.ContainsKey(playerName)) // player was here before, isn't now, so they left { PlayerChanged.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.Left, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } } oldPlayerInfos.Clear(); bool emitAll = false; if (shouldForceUpdate) { shouldForceUpdate = false; emitAll = true; } foreach (KeyValuePair <string, PlayerInfo> kvp in newPlayerInfos) // do this instead of assignment so they don't point to the same object { PlayerInfo pi = kvp.Value; oldPlayerInfos[kvp.Key] = pi; if (emitAll) { PlayerChanged.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.ForceUpdated, Name = kvp.Key, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } } //foreach (KeyValuePair<string, PlayerInfo> kvp in oldPlayerInfos) //{ // PlayerInfo pi = kvp.Value; // Console.WriteLine($"Player ID {pi.PlayerId}; Name: {ProcessMemory.ReadString((IntPtr)pi.PlayerName)}; Color: {pi.ColorId}; Dead: " + ((pi.IsDead > 0) ? "yes" : "no")); //} Thread.Sleep(250); } }
public void RunLoop() { while (true) { if (!ProcessMemory.IsHooked) { if (!ProcessMemory.HookProcess("Among Us")) { Thread.Sleep(1000); continue; } else { Program.conInterface.WriteLine($"Connected to Among Us process ({ProcessMemory.process.Id})"); bool foundModule = false; while (true) { foreach (ProcessMemory.Module module in ProcessMemory.modules) { if (module.Name.Equals("GameAssembly.dll", StringComparison.OrdinalIgnoreCase)) { GameAssemblyPtr = module.BaseAddress; foundModule = true; break; } } if (!foundModule) { Program.conInterface.WriteLine("Still looking for modules..."); // TODO: This still isn't functional, we need to re-hook to reload module addresses Thread.Sleep(500); // delay and try again } else { break; // we have found all modules } } Console.WriteLine($"({GameAssemblyPtr})"); prevChatBubsVersion = ProcessMemory.Read <int>(GameAssemblyPtr, HudManagerOffset, 0x5C, 0, 0x28, 0xC, 0x14, 0x10); } } GameState state; //int meetingHudState = /*meetingHud_cachePtr == 0 ? 4 : */ProcessMemory.ReadWithDefault<int>(GameAssemblyPtr, 4, 0xDA58D0, 0x5C, 0, 0x84); // 0 = Discussion, 1 = NotVoted, 2 = Voted, 3 = Results, 4 = Proceeding IntPtr meetingHud = ProcessMemory.Read <IntPtr>(GameAssemblyPtr, MeetingHudOffset, 0x5C, 0); uint meetingHud_cachePtr = meetingHud == IntPtr.Zero ? 0 : ProcessMemory.Read <uint>(meetingHud, 0x8); int meetingHudState = meetingHud_cachePtr == 0 ? 4 : ProcessMemory.ReadWithDefault <int>(meetingHud, 4, 0x84); // 0 = Discussion, 1 = NotVoted, 2 = Voted, 3 = Results, 4 = Proceeding int gameState = ProcessMemory.Read <int>(GameAssemblyPtr, AmongUsClientOffset, 0x5C, 0, 0x64); // 0 = NotJoined, 1 = Joined, 2 = Started, 3 = Ended (during "defeat" or "victory" screen only) if (gameState == 0) { state = GameState.MENU; exileCausesEnd = false; } else if (gameState == 1 || gameState == 3) { state = GameState.LOBBY; exileCausesEnd = false; } else if (exileCausesEnd) { state = GameState.LOBBY; } else if (meetingHudState < 4) { state = GameState.DISCUSSION; } else { state = GameState.TASKS; } IntPtr allPlayersPtr = ProcessMemory.Read <IntPtr>(GameAssemblyPtr, GameDataOffset, 0x5C, 0, 0x24); IntPtr allPlayers = ProcessMemory.Read <IntPtr>(allPlayersPtr, 0x08); int playerCount = ProcessMemory.Read <int>(allPlayersPtr, 0x0C); IntPtr playerAddrPtr = allPlayers + 0x10; // check if exile causes end if (oldState == GameState.DISCUSSION && state == GameState.TASKS) { byte exiledPlayerId = ProcessMemory.ReadWithDefault <byte>(GameAssemblyPtr, 255, MeetingHudOffset, 0x5C, 0, 0x94, 0x08); int impostorCount = 0, innocentCount = 0; for (int i = 0; i < playerCount; i++) { PlayerInfo pi = ProcessMemory.Read <PlayerInfo>(playerAddrPtr, 0, 0); playerAddrPtr += 4; if (pi.PlayerId == exiledPlayerId) { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.Exiled, Name = pi.GetPlayerName(), IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } // skip invalid, dead and exiled players if (pi.PlayerName == 0 || pi.PlayerId == exiledPlayerId || pi.IsDead == 1 || pi.Disconnected == 1) { continue; } if (pi.IsImpostor == 1) { impostorCount++; } else { innocentCount++; } } if (impostorCount == 0 || impostorCount >= innocentCount) { exileCausesEnd = true; state = GameState.LOBBY; } } if (this.shouldTransmitState) { shouldTransmitState = false; GameStateChanged?.Invoke(this, new GameStateChangedEventArgs() { NewState = state }); } else if (state != oldState) { GameStateChanged?.Invoke(this, new GameStateChangedEventArgs() { NewState = state }); } oldState = state; newPlayerInfos.Clear(); playerAddrPtr = allPlayers + 0x10; for (int i = 0; i < playerCount; i++) { PlayerInfo pi = ProcessMemory.Read <PlayerInfo>(playerAddrPtr, 0, 0); playerAddrPtr += 4; if (pi.PlayerName == 0) { continue; } string playerName = pi.GetPlayerName(); newPlayerInfos[playerName] = pi; // add to new playerinfos for comparison later if (!oldPlayerInfos.ContainsKey(playerName)) // player wasn't here before, they just joined { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.Joined, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } else // player was here before, we have an old playerInfo to compare against { PlayerInfo oldPlayerInfo = oldPlayerInfos[playerName]; if (!oldPlayerInfo.GetIsDead() && pi.GetIsDead()) // player just died { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.Died, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } if (oldPlayerInfo.ColorId != pi.ColorId) { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.ChangedColor, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } if (!oldPlayerInfo.GetIsDisconnected() && pi.GetIsDisconnected()) { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.Disconnected, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } } } foreach (KeyValuePair <string, PlayerInfo> kvp in oldPlayerInfos) { PlayerInfo pi = kvp.Value; string playerName = kvp.Key; if (!newPlayerInfos.ContainsKey(playerName)) // player was here before, isn't now, so they left { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.Left, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } } oldPlayerInfos.Clear(); bool emitAll = false; if (shouldForceUpdate) { shouldForceUpdate = false; emitAll = true; } foreach (KeyValuePair <string, PlayerInfo> kvp in newPlayerInfos) // do this instead of assignment so they don't point to the same object { PlayerInfo pi = kvp.Value; oldPlayerInfos[kvp.Key] = pi; if (emitAll) { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs() { Action = PlayerAction.ForceUpdated, Name = kvp.Key, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } } IntPtr chatBubblesPtr = ProcessMemory.Read <IntPtr>(GameAssemblyPtr, HudManagerOffset, 0x5C, 0, 0x28, 0xC, 0x14); int poolSize = 20; // = ProcessMemory.Read<int>(GameAssemblyPtr, 0xD0B25C, 0x5C, 0, 0x28, 0xC, 0xC) int numChatBubbles = ProcessMemory.Read <int>(chatBubblesPtr, 0xC); int chatBubsVersion = ProcessMemory.Read <int>(chatBubblesPtr, 0x10); IntPtr chatBubblesAddr = ProcessMemory.Read <IntPtr>(chatBubblesPtr, 0x8) + 0x10; IntPtr[] chatBubblePtrs = ProcessMemory.ReadArray(chatBubblesAddr, numChatBubbles); int newMsgs = 0; if (chatBubsVersion > prevChatBubsVersion) // new message has been sent { if (chatBubsVersion > poolSize) // increments are twofold (push to and pop from pool) { if (prevChatBubsVersion > poolSize) { newMsgs = (chatBubsVersion - prevChatBubsVersion) >> 1; } else { newMsgs = (poolSize - prevChatBubsVersion) + ((chatBubsVersion - poolSize) >> 1); } } else // single increments { newMsgs = chatBubsVersion - prevChatBubsVersion; } } else if (chatBubsVersion < prevChatBubsVersion) // reset { if (chatBubsVersion > poolSize) // increments are twofold (push to and pop from pool) { newMsgs = poolSize + ((chatBubsVersion - poolSize) >> 1); } else // single increments { newMsgs = chatBubsVersion; } } prevChatBubsVersion = chatBubsVersion; for (int i = numChatBubbles - newMsgs; i < numChatBubbles; i++) { string msgText = ProcessMemory.ReadString(ProcessMemory.Read <IntPtr>(chatBubblePtrs[i], 0x20, 0x28)); if (msgText.Length == 0) { continue; } string msgSender = ProcessMemory.ReadString(ProcessMemory.Read <IntPtr>(chatBubblePtrs[i], 0x1C, 0x28)); ChatMessageAdded?.Invoke(this, new ChatMessageEventArgs() { Sender = msgSender, Message = msgText }); } //string gameCode = ProcessMemory.ReadString(ProcessMemory.Read<IntPtr>(GameAssemblyPtr, GameStartManagerOffset, 0x5c, 0, 0x20, 0x28)); //Console.WriteLine(gameCode); Thread.Sleep(250); } }
public void RunLoop() { while (true) { if (!ProcessMemory.IsHooked) { if (!ProcessMemory.HookProcess("Among Us")) { Thread.Sleep(1000); continue; } Settings.conInterface.WriteModuleTextColored("GameMemReader", Color.Lime, $"Connected to Among Us process ({Color.Red.ToTextColor()}{ProcessMemory.process.Id}{MainWindow.NormalTextColor.ToTextColor()})"); var foundModule = false; while (true) { foreach (var module in ProcessMemory.modules) { if (module.Name.Equals("GameAssembly.dll", StringComparison.OrdinalIgnoreCase)) { GameAssemblyPtr = module.BaseAddress; if (!GameVerifier.VerifySteamHash(module.FileName)) { cracked = true; Settings.conInterface.WriteModuleTextColored("GameVerifier", Color.Red, $"Client verification: {Color.Red.ToTextColor()}FAIL{MainWindow.NormalTextColor.ToTextColor()}."); } else { cracked = false; Settings.conInterface.WriteModuleTextColored("GameVerifier", Color.Red, $"Client verification: {Color.Lime.ToTextColor()}PASS{MainWindow.NormalTextColor.ToTextColor()}."); } foundModule = true; break; } } if (!foundModule) { Settings.conInterface.WriteModuleTextColored("GameMemReader", Color.Lime, "Still looking for modules..."); //Program.conInterface.WriteModuleTextColored("GameMemReader", Color.Green, "Still looking for modules..."); // TODO: This still isn't functional, we need to re-hook to reload module addresses Thread.Sleep(500); // delay and try again ProcessMemory.LoadModules(); } else { break; // we have found all modules } } prevChatBubsVersion = ProcessMemory.Read <int>(GameAssemblyPtr, _gameOffsets.HudManagerOffset, 0x5C, 0, 0x28, 0xC, 0x14, 0x10); } if (cracked && ProcessMemory.IsHooked) { Settings.form.PlayGotEm(); var result = Settings.form.context.DialogCoordinator.ShowMessageAsync(Settings.form.context, "Uh oh.", "We have detected that you are running an unsupported version of the game. This may or may not work.", MessageDialogStyle.AffirmativeAndNegative, new MetroDialogSettings { AffirmativeButtonText = "I understand", NegativeButtonText = "Exit", ColorScheme = MetroDialogColorScheme.Theme, DefaultButtonFocus = MessageDialogResult.Negative }) .GetAwaiter().GetResult(); if (result == MessageDialogResult.Negative) { Environment.Exit(0); } else { cracked = false; } continue; } GameState state; //int meetingHudState = /*meetingHud_cachePtr == 0 ? 4 : */ProcessMemory.ReadWithDefault<int>(GameAssemblyPtr, 4, 0xDA58D0, 0x5C, 0, 0x84); // 0 = Discussion, 1 = NotVoted, 2 = Voted, 3 = Results, 4 = Proceeding var meetingHud = ProcessMemory.Read <IntPtr>(GameAssemblyPtr, _gameOffsets.MeetingHudOffset, 0x5C, 0); var meetingHud_cachePtr = meetingHud == IntPtr.Zero ? 0 : ProcessMemory.Read <uint>(meetingHud, 0x8); var meetingHudState = meetingHud_cachePtr == 0 ? 4 : ProcessMemory.ReadWithDefault(meetingHud, 4, 0x84); // 0 = Discussion, 1 = NotVoted, 2 = Voted, 3 = Results, 4 = Proceeding var gameState = ProcessMemory.Read <int>(GameAssemblyPtr, _gameOffsets.AmongUsClientOffset, 0x5C, 0, 0x64); // 0 = NotJoined, 1 = Joined, 2 = Started, 3 = Ended (during "defeat" or "victory" screen only) switch (gameState) { case 0: state = GameState.MENU; exileCausesEnd = false; break; case 1: case 3: state = GameState.LOBBY; exileCausesEnd = false; break; default: { if (exileCausesEnd) { state = GameState.LOBBY; } else if (meetingHudState < 4) { state = GameState.DISCUSSION; } else { state = GameState.TASKS; } break; } } var allPlayersPtr = ProcessMemory.Read <IntPtr>(GameAssemblyPtr, _gameOffsets.GameDataOffset, 0x5C, 0, 0x24); var allPlayers = ProcessMemory.Read <IntPtr>(allPlayersPtr, 0x08); var playerCount = ProcessMemory.Read <int>(allPlayersPtr, 0x0C); var playerAddrPtr = allPlayers + 0x10; // check if exile causes end if (oldState == GameState.DISCUSSION && state == GameState.TASKS) { var exiledPlayerId = ProcessMemory.ReadWithDefault <byte>(GameAssemblyPtr, 255, _gameOffsets.MeetingHudOffset, 0x5C, 0, 0x94, 0x08); int impostorCount = 0, innocentCount = 0; for (var i = 0; i < playerCount; i++) { var pi = ProcessMemory.Read <PlayerInfo>(playerAddrPtr, 0, 0); playerAddrPtr += 4; if (pi.PlayerId == exiledPlayerId) { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs { Action = PlayerAction.Exiled, Name = pi.GetPlayerName(), IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } // skip invalid, dead and exiled players if (pi.PlayerName == 0 || pi.PlayerId == exiledPlayerId || pi.IsDead == 1 || pi.Disconnected == 1) { continue; } if (pi.IsImpostor == 1) { impostorCount++; } else { innocentCount++; } } if (impostorCount == 0 || impostorCount >= innocentCount) { exileCausesEnd = true; state = GameState.LOBBY; } } if (state != oldState || shouldForceTransmitState) { GameStateChanged?.Invoke(this, new GameStateChangedEventArgs { NewState = state }); shouldForceTransmitState = false; } if (state != oldState && state == GameState.LOBBY) { shouldReadLobby = true; // will eventually transmit } oldState = state; newPlayerInfos.Clear(); playerAddrPtr = allPlayers + 0x10; for (var i = 0; i < playerCount; i++) { var pi = ProcessMemory.Read <PlayerInfo>(playerAddrPtr, 0, 0); playerAddrPtr += 4; if (pi.PlayerName == 0) { continue; } var playerName = pi.GetPlayerName(); if (playerName.Length == 0) { continue; } newPlayerInfos[playerName] = pi; // add to new playerinfos for comparison later if (!oldPlayerInfos.ContainsKey(playerName)) // player wasn't here before, they just joined { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs { Action = PlayerAction.Joined, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } else { // player was here before, we have an old playerInfo to compare against var oldPlayerInfo = oldPlayerInfos[playerName]; if (!oldPlayerInfo.GetIsDead() && pi.GetIsDead()) // player just died { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs { Action = PlayerAction.Died, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } if (oldPlayerInfo.ColorId != pi.ColorId) { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs { Action = PlayerAction.ChangedColor, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } if (!oldPlayerInfo.GetIsDisconnected() && pi.GetIsDisconnected()) { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs { Action = PlayerAction.Disconnected, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } } } foreach (var kvp in oldPlayerInfos) { var pi = kvp.Value; var playerName = kvp.Key; if (!newPlayerInfos.ContainsKey(playerName)) // player was here before, isn't now, so they left { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs { Action = PlayerAction.Left, Name = playerName, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } } oldPlayerInfos.Clear(); var emitAll = false; if (shouldForceUpdatePlayers) { shouldForceUpdatePlayers = false; emitAll = true; } foreach (var kvp in newPlayerInfos ) // do this instead of assignment so they don't point to the same object { var pi = kvp.Value; oldPlayerInfos[kvp.Key] = pi; if (emitAll) { PlayerChanged?.Invoke(this, new PlayerChangedEventArgs { Action = PlayerAction.ForceUpdated, Name = kvp.Key, IsDead = pi.GetIsDead(), Disconnected = pi.GetIsDisconnected(), Color = pi.GetPlayerColor() }); } } var chatBubblesPtr = ProcessMemory.Read <IntPtr>(GameAssemblyPtr, _gameOffsets.HudManagerOffset, 0x5C, 0, 0x28, 0xC, 0x14); var poolSize = 20; // = ProcessMemory.Read<int>(GameAssemblyPtr, 0xD0B25C, 0x5C, 0, 0x28, 0xC, 0xC) var numChatBubbles = ProcessMemory.Read <int>(chatBubblesPtr, 0xC); var chatBubsVersion = ProcessMemory.Read <int>(chatBubblesPtr, 0x10); var chatBubblesAddr = ProcessMemory.Read <IntPtr>(chatBubblesPtr, 0x8) + 0x10; var chatBubblePtrs = ProcessMemory.ReadArray(chatBubblesAddr, numChatBubbles); var newMsgs = 0; if (chatBubsVersion > prevChatBubsVersion) // new message has been sent { if (chatBubsVersion > poolSize) // increments are twofold (push to and pop from pool) { if (prevChatBubsVersion > poolSize) { newMsgs = (chatBubsVersion - prevChatBubsVersion) >> 1; } else { newMsgs = poolSize - prevChatBubsVersion + ((chatBubsVersion - poolSize) >> 1); } } else // single increments { newMsgs = chatBubsVersion - prevChatBubsVersion; } } else if (chatBubsVersion < prevChatBubsVersion) // reset { if (chatBubsVersion > poolSize) // increments are twofold (push to and pop from pool) { newMsgs = poolSize + ((chatBubsVersion - poolSize) >> 1); } else // single increments { newMsgs = chatBubsVersion; } } prevChatBubsVersion = chatBubsVersion; for (var i = numChatBubbles - newMsgs; i < numChatBubbles; i++) { var msgText = ProcessMemory.ReadString(ProcessMemory.Read <IntPtr>(chatBubblePtrs[i], 0x20, 0x28)); if (msgText.Length == 0) { continue; } var msgSender = ProcessMemory.ReadString(ProcessMemory.Read <IntPtr>(chatBubblePtrs[i], 0x1C, 0x28)); var oldPlayerInfo = oldPlayerInfos[msgSender]; ChatMessageAdded?.Invoke(this, new ChatMessageEventArgs { Sender = msgSender, Message = msgText, Color = oldPlayerInfo.GetPlayerColor() }); } if (shouldReadLobby) { var gameCode = ProcessMemory.ReadString(ProcessMemory.Read <IntPtr>(GameAssemblyPtr, _gameOffsets.GameStartManagerOffset, 0x5c, 0, 0x20, 0x28)); string[] split; if (gameCode != null && gameCode.Length > 0 && (split = gameCode.Split('\n')).Length == 2) { PlayRegion region = (PlayRegion)((4 - (ProcessMemory.Read <int>(GameAssemblyPtr, _gameOffsets.ServerManagerOffset, 0x5c, 0, 0x10, 0x8, 0x8) & 0b11)) % 3); // do NOT ask this.latestLobbyEventArgs = new LobbyEventArgs() { LobbyCode = split[1], Region = region }; shouldReadLobby = false; shouldTransmitLobby = true; // since this is probably new info } } if (shouldTransmitLobby) { if (this.latestLobbyEventArgs != null) { JoinedLobby?.Invoke(this, this.latestLobbyEventArgs); } shouldTransmitLobby = false; } Thread.Sleep(250); } }