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;
}
}
}