Files
MrMahoandClaude Opus 5.5 51c1ebc938 0.4.0: Mitspieler in der Welt, BSO-Konto (Spiel-Ticket)
- Mitspieler als Bus/Spielfigur mit Namensschild, weich bewegt (RemotePlayers, Models)
- Ohne gueltiges Spiel-Ticket vom Launcher kein Multiplayer; Name kommt aus dem Konto
- Protokoll 2 (Hello mit Ticket), IPv6-Adressen
- Nach Szenenwechsel erst nach 3 s neu anlegen (Absturz im Test 0.3.0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 18:10:48 +01:00

198 lines
10 KiB
C#

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;
// Mitspieler in der Welt (RemotePlayers) - optional: fehlt etwas, laeuft der Rest trotzdem
static nint game; // Vision::Game (VisGame_cl)
static delegate* unmanaged<nint, byte*, float*, byte*, byte*, nint> createEntity;
static delegate* unmanaged<nint, float, float, float, void> setPosition;
static delegate* unmanaged<nint, float, float, float, void> setOrientation;
static delegate* unmanaged<nint, void> removeEntity;
static delegate* unmanaged<nint, byte*, float*, short, int> drawMessage3D;
public static bool CanSpawn { get; private set; }
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;
game = Export(module, "?Game@Vision@@2VVisGame_cl@@A");
// VisBaseEntity_cl* VisGame_cl::CreateEntity(const char* klasse, const VisVector_cl& pos, const char* modell, const char* parameter)
createEntity = (delegate* unmanaged<nint, byte*, float*, byte*, byte*, nint>)Export(module, "?CreateEntity@VisGame_cl@@QEAAPEAVVisBaseEntity_cl@@PEBDAEBVVisVector_cl@@00@Z");
setPosition = (delegate* unmanaged<nint, float, float, float, void>)Export(module, "?SetPosition@VisObject3D_cl@@QEAAXMMM@Z");
setOrientation = (delegate* unmanaged<nint, float, float, float, void>)Export(module, "?SetOrientation@VisObject3D_cl@@QEAAXMMM@Z");
removeEntity = (delegate* unmanaged<nint, void>)Export(module, "?Remove@VisBaseEntity_cl@@QEAAXXZ");
// int VisGame_cl::DrawMessage3D(const char* text, const VisVector_cl& pos, short) - wie Print nur fuer dieses Bild
drawMessage3D = (delegate* unmanaged<nint, byte*, float*, short, int>)Export(module, "?DrawMessage3D@VisGame_cl@@QEAAHPEBDAEBVVisVector_cl@@F@Z");
CanSpawn = game != 0 && createEntity != null && setPosition != null && setOrientation != null && removeEntity != null && drawMessage3D != null;
Bootstrap.Log(Loaded ? "Vision Engine gefunden, alle Funktionen da" : "Vision Engine: Funktionen fehlen");
return Loaded;
}
/// <summary>Neue Entity mit Modell (nur Grafik, keine Physik). 0 = fehlgeschlagen.</summary>
public static nint CreateEntity(string className, Vec3 pos, string modelFile)
{
float* v = stackalloc float[4] { pos.X, pos.Y, pos.Z, 0 }; // VisVector_cl (3 float, Platz fuer Ausrichtung)
byte[] cls = ToAscii(className), model = ToAscii(modelFile);
byte* none = stackalloc byte[1] { 0 };
fixed (byte* c = cls, m = model) return createEntity(game, c, v, m, none);
}
public static void SetPosition(nint obj, Vec3 p) => setPosition(obj, p.X, p.Y, p.Z);
public static void SetOrientation(nint obj, Vec3 yawPitchRoll) => setOrientation(obj, yawPitchRoll.X, yawPitchRoll.Y, yawPitchRoll.Z);
/// <summary>Entity zum Entfernen vormerken (die Engine raeumt sie am Ende des Bildes weg).</summary>
public static void RemoveEntity(nint entity) => removeEntity(entity);
/// <summary>Text an einer Stelle in der Welt, fuer dieses Bild (Namensschild). text mit ToAscii vorbereitet.</summary>
public static void DrawText3D(byte[] text, Vec3 pos)
{
float* v = stackalloc float[4] { pos.X, pos.Y, pos.Z, 0 };
fixed (byte* t = text) drawMessage3D(game, t, v, 0);
}
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;
}
}
}