using System.Runtime.CompilerServices; using System.Runtime.InteropServices; namespace XFEEliteDangerous.OfflineRuntime; /// Bounded PE32+ import walker. Changes only this image's existing IAT pointers, never system DLL code. internal sealed unsafe class ImportPointerHooks { private readonly List<(nint Slot, nint Previous, nint Replacement)> installed = []; internal IReadOnlyDictionary Install(nint image, IReadOnlyDictionary replacements) { var found = Find(image, replacements.Keys.ToHashSet(StringComparer.Ordinal)); if (replacements.Keys.Any(key => !found.ContainsKey(key))) throw new InvalidDataException("A required main-image import is absent."); var previous = new Dictionary(StringComparer.Ordinal); try { foreach (var pair in replacements) { var slot = found[pair.Key]; var original = *(nint*)slot; if (original == 0) throw new InvalidDataException("An imported native pointer is null."); Change(slot, pair.Value); installed.Add((slot, original, pair.Value)); previous.Add(pair.Key, original); } return previous; } catch { Rollback(); throw; } } internal void Rollback() { foreach (var entry in installed.AsEnumerable().Reverse()) if (*(nint*)entry.Slot == entry.Replacement) Change(entry.Slot, entry.Previous); installed.Clear(); } private static void Change(nint slot, nint value) { if (!Native.VirtualProtect(slot, 8, 0x04, out var previous)) throw new InvalidOperationException("Cannot protect owned import slot."); try { Interlocked.Exchange(ref Unsafe.AsRef((void*)slot), value); } finally { Native.VirtualProtect(slot, 8, previous, out _); } } internal static Dictionary Find(nint image, HashSet names) { if (image == 0 || *(ushort*)image != 0x5a4d) throw new InvalidDataException("Expected a mapped PE image."); var pe = *(int*)(image + 0x3c); if (pe is < 0x40 or > 0x100000 || *(uint*)(image + pe) != 0x4550 || *(ushort*)(image + pe + 24) != 0x20b) throw new InvalidDataException("Expected a mapped PE32+ image."); var optional = image + pe + 24; var size = *(uint*)(optional + 56); if (size < 4096 || size > 0x70000000) throw new InvalidDataException("Invalid image bounds."); var imports = *(uint*)(optional + 120); var importSize = *(uint*)(optional + 124); Bound(imports, importSize, size); var result = new Dictionary(StringComparer.Ordinal); for (uint descriptor = 0; descriptor + 20 <= importSize; descriptor += 20) { var entry = image + (int)(imports + descriptor); var lookup = *(uint*)entry; var dll = *(uint*)(entry + 12); var iat = *(uint*)(entry + 16); if (lookup == 0 && dll == 0 && iat == 0) break; if (lookup == 0) throw new InvalidDataException("Original import names are required."); Bound(dll, 1, size); for (uint index = 0; index < 32768; index++) { Bound(lookup + index * 8, 8, size); Bound(iat + index * 8, 8, size); var thunk = *(ulong*)(image + (int)(lookup + index * 8)); if (thunk == 0) break; if ((thunk & 0x8000000000000000) != 0) continue; if (thunk > uint.MaxValue) throw new InvalidDataException("Invalid named import."); var nameRva = checked((uint)thunk + 2); Bound(nameRva, 1, size); var length = 0; while (length < 128 && nameRva + length < size && *((byte*)image + nameRva + length) != 0) length++; if (length == 128 || nameRva + length >= size) throw new InvalidDataException("Unterminated import name."); var name = Marshal.PtrToStringAnsi(image + (int)nameRva, length)!; if (names.Contains(name) && !result.TryAdd(name, image + (int)(iat + index * 8))) throw new InvalidDataException("Duplicate required import."); } } return result; } private static void Bound(uint rva, uint length, uint size) { if (rva >= size || (ulong)rva + length > size) throw new InvalidDataException("Import entry exceeds mapped image."); } }