using System;
using System.Collections.Generic;
namespace BSO
{
///
/// Modelldatei einer Entity finden (z.B. Models\Vehicles\Citaro\Citaro12m\...\.model). Die .pak-Dateien des Spiels sind
/// verschluesselt und VisBaseEntity_cl::GetMesh ist nicht exportiert - also: Entity -> Mesh (Zeiger in der Entity) ->
/// Dateiname (char* im Mesh) durchsuchen. Jeder Zeiger wird vorher geprueft (Mem).
///
public static class Models
{
/// Modell des eigenen Busses / der eigenen Figur - werden fuer die Mitspieler benutzt.
public static string Bus { get; private set; }
public static string Character { get; private set; }
static readonly Dictionary byEntity = new();
/// Solange man noch nicht im eigenen Bus sass: irgendein Fahrzeug aus der Szene.
static string fallbackVehicle;
public static string Vehicle => Bus ?? fallbackVehicle;
public static void RememberBus(nint entity) => Bus ??= Remember(entity, "Bus");
public static void RememberCharacter(nint entity) => Character ??= Remember(entity, "Spielfigur");
public static void RememberFallbackVehicle(nint entity) => fallbackVehicle ??= Remember(entity, "Fahrzeug (Ersatz, bis du im Bus sitzt)");
static string Remember(nint entity, string what)
{
string file = Find(entity);
Bootstrap.Log(file != null ? $"Modell {what}: {file}" : $"Modell {what}: nicht gefunden");
return file;
}
/// Modelldatei der Entity oder null.
public static string Find(nint entity)
{
if (byEntity.TryGetValue(entity, out string known)) return known;
string found = null;
for (int off = 8; off < 0x400 && found == null; off += 8)
{
if (!Mem.TryReadPtr(entity + off, out nint p) || p == 0 || Mem.IsImage(p)) continue;
found = ModelString(p); // Dateiname direkt in der Entity
for (int off2 = 0; off2 < 0x100 && found == null; off2 += 8)
{
// ... oder im Mesh, auf das die Entity zeigt
if (!Mem.TryReadPtr(p + off2, out nint q) || q == 0 || Mem.IsImage(q)) continue;
found = ModelString(q);
}
}
byEntity[entity] = found;
return found;
}
static string ModelString(nint p)
{
if (!Mem.IsReadable(p, 8)) return null;
string s = Mem.ReadAscii(p, 260);
if (s.Length < 8 || !s.EndsWith(".model", StringComparison.OrdinalIgnoreCase)) return null;
foreach (char c in s) if (c < 0x20 || c > 0x7e) return null;
return s;
}
}
}