namespace MemoryViewer { using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Numerics; using System.Text; using GameHelper; using GameHelper.Plugin; using GameHelper.Utils; using GameOffsets; using ImGuiNET; using Newtonsoft.Json; public sealed class MemoryViewer : PCore { private bool isWindowVisible = false; private DateTime lastRefreshTime = DateTime.MinValue; private MemoryValidationData validationData = new MemoryValidationData(); private struct MemoryValidationData { public bool GameStatesValid; public IntPtr GameStatesAddress; public IntPtr InGameStateAddress; public IntPtr AreaInstanceAddress; public IntPtr IngameDataAddress; public IntPtr EntityListAddress; public IntPtr LocalPlayerAddress; public Dictionary Addresses; } private struct AddressInfo { public string Name; public IntPtr Address; public bool IsValid; public bool IsReadable; public string ValueHex; public string Description; } public override void OnEnable(bool isGameOpened) { LoadSettings(); } public override void OnDisable() { SaveSettings(); } public override void SaveSettings() { try { var settingsPath = Path.Combine(this.DllDirectory, "settings.json"); var json = JsonConvert.SerializeObject(this.Settings, Formatting.Indented); File.WriteAllText(settingsPath, json); } catch (Exception ex) { CategorizedLogger.LogError($"[MemoryViewer] Failed to save settings: {ex.Message}"); } } private void LoadSettings() { try { var settingsPath = Path.Combine(this.DllDirectory, "settings.json"); if (File.Exists(settingsPath)) { var json = File.ReadAllText(settingsPath); this.Settings = JsonConvert.DeserializeObject(json) ?? new MemoryViewerSettings(); } } catch (Exception ex) { CategorizedLogger.LogError($"[MemoryViewer] Failed to load settings: {ex.Message}"); this.Settings = new MemoryViewerSettings(); } } public override void DrawSettings() { if (ImGui.CollapsingHeader("Memory Viewer Settings")) { var hexBytes = this.Settings.HexViewBytes; if (ImGui.InputInt("Hex View Bytes", ref hexBytes, 64, 256)) { this.Settings.HexViewBytes = Math.Max(64, Math.Min(4096, hexBytes)); } ImGui.Checkbox("Auto Refresh", ref this.Settings.AutoRefresh); if (this.Settings.AutoRefresh) { var interval = this.Settings.RefreshIntervalMs; if (ImGui.InputInt("Refresh Interval (ms)", ref interval, 100, 500)) { this.Settings.RefreshIntervalMs = Math.Max(100, Math.Min(10000, interval)); } } } } public override void DrawUI() { if (Core.Process.Handle == null || Core.Process.Handle.IsInvalid || Core.Process.Handle.IsClosed) { return; } if (this.Settings.AutoRefresh && (DateTime.Now - lastRefreshTime).TotalMilliseconds >= this.Settings.RefreshIntervalMs) { RefreshMemoryData(); lastRefreshTime = DateTime.Now; } ImGui.SetNextWindowSize(new Vector2(900, 700), ImGuiCond.FirstUseEver); if (ImGui.Begin("Memory Viewer", ref this.isWindowVisible)) { DrawMemoryViewer(); ImGui.End(); } } private void DrawMemoryViewer() { ImGui.TextWrapped("Memory Address Validator - View and validate game memory addresses"); ImGui.Separator(); if (ImGui.Button("Refresh Data")) { RefreshMemoryData(); } ImGui.SameLine(); if (ImGui.Button("Dump to Log")) { DumpToLog(); } ImGui.Separator(); if (ImGui.CollapsingHeader("GameStates Structure")) { DrawAddressInfo("GameStates", validationData.GameStatesAddress, validationData.GameStatesValid, "Main GameStates pointer - Base offset for all game state access"); if (validationData.GameStatesValid && validationData.GameStatesAddress != IntPtr.Zero) { DrawOffsetInfo("InGameState Index", 4, "Index in GameStates array"); } } if (ImGui.CollapsingHeader("InGameState Structure")) { DrawAddressInfo("InGameState", validationData.InGameStateAddress, validationData.InGameStateAddress != IntPtr.Zero, "Current InGameState pointer"); if (validationData.InGameStateAddress != IntPtr.Zero) { DrawOffsetInfo("AreaInstanceData", 0x948, "AreaInstanceData offset"); DrawOffsetInfo("IngameData Offset", 0x370, "IngameData structure offset"); } } if (ImGui.CollapsingHeader("IngameData Structure")) { DrawAddressInfo("IngameData", validationData.IngameDataAddress, validationData.IngameDataAddress != IntPtr.Zero, "IngameData structure pointer"); if (validationData.IngameDataAddress != IntPtr.Zero) { DrawOffsetInfo("Entity List", 0x490, "EntityList pointer offset"); DrawOffsetInfo("Entities Count", 0x498, "Entity count offset"); DrawOffsetInfo("Local Player", 0x408, "Local player pointer offset"); } } if (ImGui.CollapsingHeader("AreaInstance Structure")) { DrawAddressInfo("AreaInstance", validationData.AreaInstanceAddress, validationData.AreaInstanceAddress != IntPtr.Zero, "AreaInstance structure pointer"); if (validationData.AreaInstanceAddress != IntPtr.Zero) { DrawOffsetInfo("Local Player Ptr", 0xA10, "Local player pointer"); DrawOffsetInfo("Entity List Ptr", 0xB50, "Entity list pointer (was 0x13F8)"); DrawOffsetInfo("Terrain List Ptr", 0x12C8, "Terrain/Exits list pointer"); DrawOffsetInfo("Terrain Grid Ptr", 0x08, "Terrain grid pointer (was 0x30)"); DrawOffsetInfo("Terrain Dimensions", 0x28, "Terrain dimensions pointer"); } } if (ImGui.CollapsingHeader("Entity Structure")) { DrawOffsetInfo("Component List Ptr", 0x10, "ComponentList pointer (inside ItemBase)"); } if (ImGui.CollapsingHeader("Component Offsets")) { DrawOffsetInfo("Component Index Debuffs", 3, "Debuff component index"); DrawOffsetInfo("Component Owner Entity", 0x08, "Entity pointer in ComponentHeader"); } if (ImGui.CollapsingHeader("Life Component Offsets")) { DrawOffsetInfo("Health", 0x1A8, "Health VitalStruct offset"); DrawOffsetInfo("Mana", 0x1F8, "Mana VitalStruct offset"); DrawOffsetInfo("Energy Shield", 0x230, "Energy Shield VitalStruct offset"); DrawOffsetInfo("Buffs", 0x58, "Buffs offset"); } if (ImGui.CollapsingHeader("VitalStruct Offsets")) { DrawOffsetInfo("Reserved Flat", 0x10, "Reserved flat value"); DrawOffsetInfo("Reserved Percent", 0x14, "Reserved percentage value"); DrawOffsetInfo("Total (Max)", 0x2C, "Maximum value"); DrawOffsetInfo("Current", 0x30, "Current value"); } if (ImGui.CollapsingHeader("Render/Position Component")) { DrawOffsetInfo("Position X", 0x138, "X coordinate"); DrawOffsetInfo("Position Y", 0x13C, "Y coordinate"); DrawOffsetInfo("Position Z", 0x140, "Z coordinate"); } if (ImGui.CollapsingHeader("Memory Hex View")) { if (validationData.Addresses != null && validationData.Addresses.Count > 0) { var selectedAddr = validationData.Addresses.First().Key; if (ImGui.BeginCombo("Select Address", selectedAddr)) { foreach (var addr in validationData.Addresses.Keys) { bool isSelected = addr == selectedAddr; if (ImGui.Selectable(addr, isSelected)) { selectedAddr = addr; } if (isSelected) { ImGui.SetItemDefaultFocus(); } } ImGui.EndCombo(); } if (validationData.Addresses.ContainsKey(selectedAddr)) { var addrInfo = validationData.Addresses[selectedAddr]; DrawHexView(addrInfo.Address, this.Settings.HexViewBytes); } } } } private void DrawAddressInfo(string name, IntPtr address, bool isValid, string description) { var color = isValid && address != IntPtr.Zero ? new Vector4(0, 1, 0, 1) : new Vector4(1, 0, 0, 1); var status = isValid && address != IntPtr.Zero ? "✓ VALID" : "✗ INVALID"; ImGui.TextColored(color, $"{status} {name}"); ImGui.SameLine(); ImGui.Text($"0x{address.ToInt64():X}"); if (!string.IsNullOrEmpty(description)) { ImGui.TextWrapped($" {description}"); } } private void DrawOffsetInfo(string name, long offset, string description) { ImGui.Text($"{name}: 0x{offset:X}"); if (!string.IsNullOrEmpty(description)) { ImGui.SameLine(); ImGui.TextDisabled($"({description})"); } } private void DrawHexView(IntPtr address, int byteCount) { if (address == IntPtr.Zero) { ImGui.Text("Invalid address"); return; } try { var reader = Core.Process.Handle; var bytes = reader.ReadMemoryArray(address, byteCount); if (bytes != null && bytes.Length > 0) { ImGui.Text($"Address: 0x{address.ToInt64():X} ({bytes.Length} bytes)"); ImGui.Separator(); ImGui.PushFont(ImGui.GetIO().Fonts.Fonts[1]); var sb = new StringBuilder(); for (int i = 0; i < bytes.Length; i++) { if (i > 0 && i % 16 == 0) { ImGui.Text(sb.ToString()); sb.Clear(); } sb.Append($"{bytes[i]:X2} "); } if (sb.Length > 0) { ImGui.Text(sb.ToString()); } ImGui.PopFont(); } } catch (Exception ex) { ImGui.TextColored(new Vector4(1, 0, 0, 1), $"Error reading memory: {ex.Message}"); } } private void RefreshMemoryData() { validationData = new MemoryValidationData { Addresses = new Dictionary() }; if (Core.Process.Handle == null || Core.Process.Handle.IsInvalid || Core.Process.Handle.IsClosed) { return; } try { var reader = Core.Process.Handle; if (Core.Process.StaticAddresses.ContainsKey("Game States")) { validationData.GameStatesAddress = Core.Process.StaticAddresses["Game States"]; validationData.GameStatesValid = ValidateAddress(validationData.GameStatesAddress); if (validationData.GameStatesValid) { try { var gameStatesPtr = reader.ReadMemory(validationData.GameStatesAddress); var inGameStatePtr = reader.ReadMemory(new IntPtr(gameStatesPtr.ToInt64() + (4 * 8))); if (ValidateAddress(inGameStatePtr)) { validationData.InGameStateAddress = inGameStatePtr; try { var ingameDataPtr = reader.ReadMemory(new IntPtr(inGameStatePtr.ToInt64() + 0x370)); if (ValidateAddress(ingameDataPtr)) { validationData.IngameDataAddress = ingameDataPtr; try { var localPlayerPtr = reader.ReadMemory(new IntPtr(ingameDataPtr.ToInt64() + 0x408)); if (ValidateAddress(localPlayerPtr)) { validationData.LocalPlayerAddress = localPlayerPtr; validationData.Addresses["Local Player"] = new AddressInfo { Name = "Local Player", Address = localPlayerPtr, IsValid = true, IsReadable = true, ValueHex = $"0x{localPlayerPtr.ToInt64():X}", Description = "Local player entity pointer" }; } } catch { } } } catch { } } } catch { } } } if (validationData.GameStatesAddress != IntPtr.Zero) { validationData.Addresses["GameStates"] = new AddressInfo { Name = "GameStates", Address = validationData.GameStatesAddress, IsValid = validationData.GameStatesValid, IsReadable = validationData.GameStatesValid, ValueHex = $"0x{validationData.GameStatesAddress.ToInt64():X}", Description = "GameStates pointer" }; } if (validationData.InGameStateAddress != IntPtr.Zero) { validationData.Addresses["InGameState"] = new AddressInfo { Name = "InGameState", Address = validationData.InGameStateAddress, IsValid = true, IsReadable = true, ValueHex = $"0x{validationData.InGameStateAddress.ToInt64():X}", Description = "InGameState pointer" }; } if (validationData.IngameDataAddress != IntPtr.Zero) { validationData.Addresses["IngameData"] = new AddressInfo { Name = "IngameData", Address = validationData.IngameDataAddress, IsValid = true, IsReadable = true, ValueHex = $"0x{validationData.IngameDataAddress.ToInt64():X}", Description = "IngameData pointer" }; } } catch (Exception ex) { CategorizedLogger.LogError($"[MemoryViewer] Error refreshing memory data: {ex.Message}"); } } private bool ValidateAddress(IntPtr address) { if (address == IntPtr.Zero || address.ToInt64() < 0x10000 || address.ToInt64() > 0x7FFFFFFFFFFF) { return false; } try { var reader = Core.Process.Handle; reader.ReadMemory(address); return true; } catch { return false; } } private void DumpToLog() { var sb = new StringBuilder(); sb.AppendLine("=== Memory Viewer Dump ==="); sb.AppendLine($"GameStates: 0x{validationData.GameStatesAddress.ToInt64():X} (Valid: {validationData.GameStatesValid})"); sb.AppendLine($"InGameState: 0x{validationData.InGameStateAddress.ToInt64():X}"); sb.AppendLine($"IngameData: 0x{validationData.IngameDataAddress.ToInt64():X}"); sb.AppendLine($"Local Player: 0x{validationData.LocalPlayerAddress.ToInt64():X}"); sb.AppendLine(); sb.AppendLine("All Addresses:"); foreach (var addr in validationData.Addresses.Values) { sb.AppendLine($" {addr.Name}: 0x{addr.Address.ToInt64():X} (Valid: {addr.IsValid}, Readable: {addr.IsReadable})"); } CategorizedLogger.Log(CategorizedLogger.LogCategory.AddressOffsets, sb.ToString()); } } }