using System.Runtime.InteropServices; namespace BSO { /// /// Speicher des Spiels lesen. Vorher immer mit VirtualQuery pruefen: eine Zugriffsverletzung laesst sich in .NET nicht /// abfangen und beendet das Spiel. Die Bereiche werden pro Bild zwischengespeichert (NewFrame leert den Cache). /// public static unsafe class Mem { [StructLayout(LayoutKind.Sequential)] struct MemoryBasicInformation { public nint BaseAddress, AllocationBase; public uint AllocationProtect; public ushort PartitionId; public nint RegionSize; public uint State, Protect, Type; } [DllImport("kernel32.dll")] static extern nint VirtualQuery(nint address, out MemoryBasicInformation info, nint length); const uint MemCommit = 0x1000, MemImage = 0x1000000; const uint Readable = 0x02 | 0x04 | 0x08 | 0x20 | 0x40 | 0x80; // R, RW, WC, XR, XRW, XWC const uint Guard = 0x100; struct Region { public nint Start, End; public bool Readable, Image; } static readonly Region[] cache = new Region[16]; static int cacheCount, cacheNext; public static void NewFrame() { cacheCount = 0; cacheNext = 0; } static bool Query(nint address, out Region region) { for (int i = 0; i < cacheCount; i++) if (address >= cache[i].Start && address < cache[i].End) { region = cache[i]; return true; } region = default; if (address < 0x10000) return false; if (VirtualQuery(address, out MemoryBasicInformation info, sizeof(MemoryBasicInformation)) == 0) return false; region.Start = info.BaseAddress; region.End = info.BaseAddress + info.RegionSize; region.Readable = info.State == MemCommit && (info.Protect & Readable) != 0 && (info.Protect & Guard) == 0; region.Image = info.Type == MemImage; cache[cacheNext] = region; cacheNext = (cacheNext + 1) % cache.Length; if (cacheCount < cache.Length) cacheCount++; return true; } public static bool IsReadable(nint address, int size) { nint end = address + size; while (address < end) { if (!Query(address, out Region r) || !r.Readable) return false; address = r.End; } return true; } /// Liegt die Adresse in einer geladenen DLL/EXE (z.B. vtables, Typinfos)? public static bool IsImage(nint address) => Query(address, out Region r) && r.Readable && r.Image; public static bool TryReadPtr(nint address, out nint value) { value = 0; if (!IsReadable(address, 8)) return false; value = *(nint*)address; return true; } public static string ReadAscii(nint address, int max) { var chars = new char[max]; int n = 0; for (; n < max; n++) { if (!IsReadable(address + n, 1)) break; byte b = *(byte*)(address + n); if (b == 0) break; chars[n] = (char)b; } return new string(chars, 0, n); } } }