using System; using System.Runtime.InteropServices; using System.Text; namespace BSO { public struct Vec3 { public float X, Y, Z; public Vec3(float x, float y, float z) { X = x; Y = y; Z = z; } public static Vec3 operator -(Vec3 a, Vec3 b) => new(a.X - b.X, a.Y - b.Y, a.Z - b.Z); public float Length => MathF.Sqrt(X * X + Y * Y + Z * Z); public override string ToString() => $"{X:0.0} / {Y:0.0} / {Z:0.0}"; } /// /// Zugriff auf die Havok Vision Engine (Vision90.dll). Die Engine exportiert ihre C++-Funktionen und globalen Objekte /// mit Namen - die Adressen holen wir ueber die dekorierten Namen, aufgerufen wird ueber Funktionszeiger /// (x64: "this" ist einfach das erste Argument). /// public static unsafe class Vision { [DllImport("kernel32.dll", CharSet = CharSet.Unicode)] static extern nint GetModuleHandleW(string name); static bool tried; public static bool Loaded { get; private set; } static nint message; // Vision::Message (VisMessage_cl) static nint camera; // Vision::Camera (VisCamera_cl) static nint entityTable; // VisElementManager_cl::elementTable (DynArray_cl) static delegate* unmanaged print; static delegate* unmanaged getMainCamera; static delegate* unmanaged getPosition; static delegate* unmanaged getOrientation; static delegate* unmanaged getTimer; static delegate* unmanaged getTime; public static bool Init() { if (tried) return Loaded; tried = true; nint module = GetModuleHandleW("Vision90.dll"); if (module == 0) { Bootstrap.Log("Vision90.dll ist nicht geladen"); return false; } message = Export(module, "?Message@Vision@@2VVisMessage_cl@@A"); camera = Export(module, "?Camera@Vision@@2VVisCamera_cl@@A"); entityTable = Export(module, "?elementTable@?$VisElementManager_cl@PEAVVisBaseEntity_cl@@@@0V?$DynArray_cl@PEAVVisBaseEntity_cl@@@@A"); // void VisMessage_cl::Print(int, int x, int y, const char* format, ...) - variadisch, auf x64 normal aufrufbar print = (delegate* unmanaged)Export(module, "?Print@VisMessage_cl@@QEAAXHHHPEBDZZ"); getMainCamera = (delegate* unmanaged)Export(module, "?GetMainCamera@VisCamera_cl@@QEBAPEAVVisContextCamera_cl@@XZ"); getPosition = (delegate* unmanaged)Export(module, "?GetPosition@VisObject3D_cl@@QEBAXAEAM00@Z"); getOrientation = (delegate* unmanaged)Export(module, "?GetOrientation@VisObject3D_cl@@QEBAXAEAM00@Z"); getTimer = (delegate* unmanaged)Export(module, "?GetTimer@Vision@@SAPEAVIVTimer@@XZ"); getTime = (delegate* unmanaged)Export(module, "?GetTime@IVTimer@@QEBAMXZ"); Loaded = message != 0 && camera != 0 && entityTable != 0 && print != null && getMainCamera != null && getPosition != null && getOrientation != null && getTimer != null && getTime != null; Bootstrap.Log(Loaded ? "Vision Engine gefunden, alle Funktionen da" : "Vision Engine: Funktionen fehlen"); return Loaded; } static nint Export(nint module, string name) { if (NativeLibrary.TryGetExport(module, name, out nint address)) return address; Bootstrap.Log("Export fehlt: " + name); return 0; } static float lastTime = -1; static long lastTick; /// /// Ist die Engine-Uhr (Vision::GetTimer()->GetTime()) weitergelaufen? Sie hat nur ~1 ms Aufloesung, die Spielschleife /// laeuft im Menue aber ~9000-mal pro Sekunde - fuer Berechnungen reicht das, fuer die Textausgabe nicht (siehe /// PositionHud.Draw). Steht die Uhr (Pause), zaehlt hoechstens alle 16 ms ein Aufruf. /// public static bool TimerAdvanced() { nint timer = getTimer(); float now = timer != 0 ? getTime(timer) : 0; long tick = Environment.TickCount64; if (now == lastTime && tick - lastTick < 16) return false; lastTime = now; lastTick = tick; return true; } /// Text fuer dieses Bild auf den Bildschirm (Vision::Message.Print). public static void Print(int x, int y, string text) => Print(x, y, ToAscii(text)); /// Wie Print(string), mit vorbereitetem Text (ToAscii) - fuer Ausgaben bei jedem Durchlauf. public static void Print(int x, int y, byte[] text) { byte* format = stackalloc byte[] { (byte)'%', (byte)'s', 0 }; fixed (byte* p = text) print(message, 1, x, y, format, p); } public static byte[] ToAscii(string text) => Encoding.ASCII.GetBytes(text + "\0"); /// Hauptkamera (VisContextCamera_cl) oder 0, solange keine Szene geladen ist. public static nint MainCamera => getMainCamera(camera); /// Position eines VisObject3D_cl (Entity, Kamera). obj muss gueltig sein. public static Vec3 GetPosition(nint obj) { float x, y, z; getPosition(obj, &x, &y, &z); return new Vec3(x, y, z); } /// Drehung eines VisObject3D_cl in Grad (Gier, Nick, Roll), wie SetOrientation sie nimmt. public static Vec3 GetOrientation(nint obj) { float yaw, pitch, roll; getOrientation(obj, &yaw, &pitch, &roll); return new Vec3(yaw, pitch, roll); } static bool tableLogged; /// /// Liste aller Entities (Zeiger, freie Plaetze sind 0). Der Aufbau von DynArray_cl ist nicht dokumentiert - /// wir probieren die ueblichen Anordnungen und nehmen die erste, deren Datenzeiger lesbar ist. /// public static bool TryGetEntities(out nint* data, out int count) { data = null; count = 0; nint* h = (nint*)entityTable; if (!tableLogged) { tableLogged = true; Bootstrap.Log($"Entity-Tabelle: {h[0]:X} {h[1]:X} {h[2]:X} {h[3]:X}"); } // (Groesse, Daten): [size][data] / [vtable][size][data] / [data][size] if (TryLayout(h[1], h[0], out data, out count)) return true; if (Mem.IsImage(h[0]) && TryLayout(h[2], h[1], out data, out count)) return true; return TryLayout(h[0], h[1], out data, out count); } static bool TryLayout(nint dataPtr, nint sizeField, out nint* data, out int count) { data = null; count = 0; uint size = (uint)sizeField; if (size == 0 || size > 1 << 20 || dataPtr == 0 || Mem.IsImage(dataPtr)) return false; if (!Mem.IsReadable(dataPtr, (int)size * 8)) return false; data = (nint*)dataPtr; count = (int)size; return true; } } }