@@ -0,0 +1,157 @@
|
||||
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}";
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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).
|
||||
/// </summary>
|
||||
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<VisBaseEntity_cl*>::elementTable (DynArray_cl)
|
||||
|
||||
static delegate* unmanaged<nint, int, int, int, byte*, byte*, void> print;
|
||||
static delegate* unmanaged<nint, nint> getMainCamera;
|
||||
static delegate* unmanaged<nint, float*, float*, float*, void> getPosition;
|
||||
static delegate* unmanaged<nint, float*, float*, float*, void> getOrientation;
|
||||
static delegate* unmanaged<nint> getTimer;
|
||||
static delegate* unmanaged<nint, float> 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<nint, int, int, int, byte*, byte*, void>)Export(module, "?Print@VisMessage_cl@@QEAAXHHHPEBDZZ");
|
||||
getMainCamera = (delegate* unmanaged<nint, nint>)Export(module, "?GetMainCamera@VisCamera_cl@@QEBAPEAVVisContextCamera_cl@@XZ");
|
||||
getPosition = (delegate* unmanaged<nint, float*, float*, float*, void>)Export(module, "?GetPosition@VisObject3D_cl@@QEBAXAEAM00@Z");
|
||||
getOrientation = (delegate* unmanaged<nint, float*, float*, float*, void>)Export(module, "?GetOrientation@VisObject3D_cl@@QEBAXAEAM00@Z");
|
||||
getTimer = (delegate* unmanaged<nint>)Export(module, "?GetTimer@Vision@@SAPEAVIVTimer@@XZ");
|
||||
getTime = (delegate* unmanaged<nint, float>)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;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>Text fuer dieses Bild auf den Bildschirm (Vision::Message.Print).</summary>
|
||||
public static void Print(int x, int y, string text) => Print(x, y, ToAscii(text));
|
||||
|
||||
/// <summary>Wie Print(string), mit vorbereitetem Text (ToAscii) - fuer Ausgaben bei jedem Durchlauf.</summary>
|
||||
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");
|
||||
|
||||
/// <summary>Hauptkamera (VisContextCamera_cl) oder 0, solange keine Szene geladen ist.</summary>
|
||||
public static nint MainCamera => getMainCamera(camera);
|
||||
|
||||
/// <summary>Position eines VisObject3D_cl (Entity, Kamera). obj muss gueltig sein.</summary>
|
||||
public static Vec3 GetPosition(nint obj)
|
||||
{
|
||||
float x, y, z;
|
||||
getPosition(obj, &x, &y, &z);
|
||||
return new Vec3(x, y, z);
|
||||
}
|
||||
|
||||
/// <summary>Drehung eines VisObject3D_cl in Grad (Gier, Nick, Roll), wie SetOrientation sie nimmt.</summary>
|
||||
public static Vec3 GetOrientation(nint obj)
|
||||
{
|
||||
float yaw, pitch, roll;
|
||||
getOrientation(obj, &yaw, &pitch, &roll);
|
||||
return new Vec3(yaw, pitch, roll);
|
||||
}
|
||||
|
||||
static bool tableLogged;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user