#include "splashpack.hh" #include #include #include #include #include "bvh.hh" #include "collision.hh" #include "gameobject.hh" #include "lua.h" #include "mesh.hh" #include "worldcollision.hh" #include "navregion.hh" #include "renderer.hh" namespace psxsplash { struct SPLASHPACKFileHeader { char magic[2]; // "SP" uint16_t version; // Format version (8 = movement params) uint16_t luaFileCount; uint16_t gameObjectCount; uint16_t navmeshCount; uint16_t textureAtlasCount; uint16_t clutCount; uint16_t colliderCount; psyqo::GTE::PackedVec3 playerStartPos; psyqo::GTE::PackedVec3 playerStartRot; psyqo::FixedPoint<12, uint16_t> playerHeight; uint16_t sceneLuaFileIndex; // Version 3 additions: uint16_t bvhNodeCount; uint16_t bvhTriangleRefCount; // Version 4 additions (component counts): uint16_t interactableCount; uint16_t healthCount; uint16_t timerCount; uint16_t spawnerCount; // Version 5 additions (navgrid): uint16_t hasNavGrid; // 1 if navgrid present, 0 otherwise uint16_t reserved; // Alignment padding // Version 6 additions (AABB + scene type): uint16_t sceneType; // 0 = exterior, 1 = interior uint16_t reserved2; // Alignment padding // Version 7 additions (world collision + nav regions): uint16_t worldCollisionMeshCount; uint16_t worldCollisionTriCount; uint16_t navRegionCount; uint16_t navPortalCount; // Version 8 additions (movement parameters): uint16_t moveSpeed; // fp12 per-frame speed constant uint16_t sprintSpeed; // fp12 per-frame speed constant uint16_t jumpVelocity; // fp12 per-second initial jump velocity uint16_t gravity; // fp12 per-second² downward acceleration uint16_t playerRadius; // fp12 collision radius uint16_t reserved3; // Alignment padding // Version 9 additions (object names): uint32_t nameTableOffset; // Offset to name string table (0 = no names) // Version 10 additions (audio): uint16_t audioClipCount; // Number of audio clips uint16_t reserved4; // Alignment padding uint32_t audioTableOffset; // Offset to audio clip table (0 = no audio) // Version 11 additions (fog + room/portal): uint8_t fogEnabled; // 0 = off, 1 = on uint8_t fogR, fogG, fogB; // Fog color RGB uint8_t fogDensity; // 1-10 density scale uint8_t reserved5; // Alignment uint16_t roomCount; // 0 = no room system (use BVH path) uint16_t portalCount; uint16_t roomTriRefCount; }; static_assert(sizeof(SPLASHPACKFileHeader) == 96, "SPLASHPACKFileHeader must be 96 bytes"); struct SPLASHPACKTextureAtlas { uint32_t polygonsOffset; uint16_t width, height; uint16_t x, y; }; struct SPLASHPACKClut { uint32_t clutOffset; uint16_t clutPackingX; uint16_t clutPackingY; uint16_t length; uint16_t pad; }; void SplashPackLoader::LoadSplashpack(uint8_t *data, SplashpackSceneSetup &setup) { psyqo::Kernel::assert(data != nullptr, "Splashpack loading data pointer is null"); psxsplash::SPLASHPACKFileHeader *header = reinterpret_cast(data); psyqo::Kernel::assert(__builtin_memcmp(header->magic, "SP", 2) == 0, "Splashpack has incorrect magic"); psyqo::Kernel::assert(header->version >= 8, "Splashpack version mismatch: re-export from SplashEdit"); setup.playerStartPosition = header->playerStartPos; setup.playerStartRotation = header->playerStartRot; setup.playerHeight = header->playerHeight; // Movement parameters (v8+) setup.moveSpeed.value = header->moveSpeed; setup.sprintSpeed.value = header->sprintSpeed; setup.jumpVelocity.value = header->jumpVelocity; setup.gravity.value = header->gravity; setup.playerRadius.value = header->playerRadius; setup.luaFiles.reserve(header->luaFileCount); setup.objects.reserve(header->gameObjectCount); setup.colliders.reserve(header->colliderCount); // Reserve component arrays (version 4+) if (header->version >= 4) { setup.interactables.reserve(header->interactableCount); } // V10 header = 84 bytes, V11+ = 96 bytes. sizeof() always returns 96, // so we must compute the correct offset for older versions. uint32_t headerSize = (header->version >= 11) ? 96 : 84; uint8_t *cursor = data + headerSize; for (uint16_t i = 0; i < header->luaFileCount; i++) { psxsplash::LuaFile *luaHeader = reinterpret_cast(cursor); luaHeader->luaCode = reinterpret_cast(data + luaHeader->luaCodeOffset); setup.luaFiles.push_back(luaHeader); cursor += sizeof(psxsplash::LuaFile); } // sceneLuaFileIndex is stored as uint16_t in header; 0xFFFF means "no scene script" (-1) setup.sceneLuaFileIndex = (header->sceneLuaFileIndex == 0xFFFF) ? -1 : (int)header->sceneLuaFileIndex; for (uint16_t i = 0; i < header->gameObjectCount; i++) { psxsplash::GameObject *go = reinterpret_cast(cursor); go->polygons = reinterpret_cast(data + go->polygonsOffset); setup.objects.push_back(go); cursor += sizeof(psxsplash::GameObject); } // Read collision data (after GameObjects) for (uint16_t i = 0; i < header->colliderCount; i++) { psxsplash::SPLASHPACKCollider *collider = reinterpret_cast(cursor); setup.colliders.push_back(collider); cursor += sizeof(psxsplash::SPLASHPACKCollider); } // Read BVH data (version 3+) if (header->version >= 3 && header->bvhNodeCount > 0) { BVHNode* bvhNodes = reinterpret_cast(cursor); cursor += header->bvhNodeCount * sizeof(BVHNode); TriangleRef* triangleRefs = reinterpret_cast(cursor); cursor += header->bvhTriangleRefCount * sizeof(TriangleRef); setup.bvh.initialize(bvhNodes, header->bvhNodeCount, triangleRefs, header->bvhTriangleRefCount); } // Read component data (version 4+) if (header->version >= 4) { // Interactables for (uint16_t i = 0; i < header->interactableCount; i++) { psxsplash::Interactable *interactable = reinterpret_cast(cursor); setup.interactables.push_back(interactable); cursor += sizeof(psxsplash::Interactable); } // Skip health components (legacy, 24 bytes each) cursor += header->healthCount * 24; // Skip timers (legacy, 16 bytes each) cursor += header->timerCount * 16; // Skip spawners (legacy, 44 bytes each) cursor += header->spawnerCount * 44; } // Read NavGrid (version 5+ — LEGACY, skip if present) if (header->version >= 5 && header->hasNavGrid) { // Skip NavGrid data: header (16 bytes) + cells // NavGridHeader: 4 int32 = 16 bytes, then gridW*gridH*9 bytes int32_t* navGridHeader = reinterpret_cast(cursor); int32_t gridW = navGridHeader[2]; int32_t gridH = navGridHeader[3]; cursor += 16; // header cursor += gridW * gridH * 9; // cells (9 bytes each) // Align to 4 bytes uintptr_t addr = reinterpret_cast(cursor); cursor = reinterpret_cast((addr + 3) & ~3); } // Read world collision soup (version 7+) if (header->version >= 7 && header->worldCollisionMeshCount > 0) { uintptr_t addr = reinterpret_cast(cursor); cursor = reinterpret_cast((addr + 3) & ~3); cursor = const_cast(setup.worldCollision.initializeFromData(cursor)); } // Read nav regions (version 7+) if (header->version >= 7 && header->navRegionCount > 0) { uintptr_t addr = reinterpret_cast(cursor); cursor = reinterpret_cast((addr + 3) & ~3); cursor = const_cast(setup.navRegions.initializeFromData(cursor)); } // Read room/portal data (version 11+, interior scenes) // Must be read here (after nav regions, before navmesh skip / atlas metadata) // to match the sequential cursor position where the writer places it. if (header->version >= 11 && header->roomCount > 0) { uintptr_t addr = reinterpret_cast(cursor); cursor = reinterpret_cast((addr + 3) & ~3); setup.rooms = reinterpret_cast(cursor); setup.roomCount = header->roomCount; cursor += header->roomCount * sizeof(RoomData); setup.portals = reinterpret_cast(cursor); setup.portalCount = header->portalCount; cursor += header->portalCount * sizeof(PortalData); setup.roomTriRefs = reinterpret_cast(cursor); setup.roomTriRefCount = header->roomTriRefCount; cursor += header->roomTriRefCount * sizeof(TriangleRef); } // Skip legacy navmesh metadata (still present in v7 files) cursor += header->navmeshCount * 8; // Navmesh struct: 4+2+2 = 8 bytes for (uint16_t i = 0; i < header->textureAtlasCount; i++) { psxsplash::SPLASHPACKTextureAtlas *atlas = reinterpret_cast(cursor); uint8_t *offsetData = data + atlas->polygonsOffset; uint16_t *castedData = reinterpret_cast(offsetData); psxsplash::Renderer::GetInstance().VramUpload(castedData, atlas->x, atlas->y, atlas->width, atlas->height); cursor += sizeof(psxsplash::SPLASHPACKTextureAtlas); } for (uint16_t i = 0; i < header->clutCount; i++) { psxsplash::SPLASHPACKClut *clut = reinterpret_cast(cursor); uint8_t *clutOffset = data + clut->clutOffset; psxsplash::Renderer::GetInstance().VramUpload((uint16_t *)clutOffset, clut->clutPackingX * 16, clut->clutPackingY, clut->length, 1); cursor += sizeof(psxsplash::SPLASHPACKClut); } // Read object name table (version 9+) if (header->version >= 9 && header->nameTableOffset != 0) { uint8_t* nameData = data + header->nameTableOffset; setup.objectNames.reserve(header->gameObjectCount); for (uint16_t i = 0; i < header->gameObjectCount; i++) { uint8_t nameLen = *nameData++; const char* nameStr = reinterpret_cast(nameData); // Names are stored as length-prefixed, null-terminated strings setup.objectNames.push_back(nameStr); nameData += nameLen + 1; // +1 for null terminator } } // Read audio clip table (version 10+) if (header->version >= 10 && header->audioClipCount > 0 && header->audioTableOffset != 0) { // Audio table: per clip: uint32_t dataOffset, uint32_t sizeBytes, uint16_t sampleRate, uint8_t loop, uint8_t nameLen, uint32_t nameOffset // Total 16 bytes per entry uint8_t* audioTable = data + header->audioTableOffset; setup.audioClips.reserve(header->audioClipCount); setup.audioClipNames.reserve(header->audioClipCount); for (uint16_t i = 0; i < header->audioClipCount; i++) { uint32_t dataOff = *reinterpret_cast(audioTable); audioTable += 4; uint32_t size = *reinterpret_cast(audioTable); audioTable += 4; uint16_t rate = *reinterpret_cast(audioTable); audioTable += 2; uint8_t loop = *audioTable++; uint8_t nameLen = *audioTable++; uint32_t nameOff = *reinterpret_cast(audioTable); audioTable += 4; SplashpackSceneSetup::AudioClipSetup clip; clip.adpcmData = data + dataOff; clip.sizeBytes = size; clip.sampleRate = rate; clip.loop = (loop != 0); clip.name = (nameLen > 0 && nameOff != 0) ? reinterpret_cast(data + nameOff) : nullptr; setup.audioClips.push_back(clip); setup.audioClipNames.push_back(clip.name); } } // Read fog configuration (version 11+) if (header->version >= 11) { setup.fogEnabled = header->fogEnabled != 0; setup.fogR = header->fogR; setup.fogG = header->fogG; setup.fogB = header->fogB; setup.fogDensity = header->fogDensity; } // Read scene type (version 6+ stored it but it was never read until now) setup.sceneType = header->sceneType; } } // namespace psxsplash