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