Broken RUntime
This commit is contained in:
@@ -50,7 +50,7 @@ namespace SplashEdit.RuntimeCode
|
||||
public float gravity;
|
||||
|
||||
// Scene configuration (v11)
|
||||
public int sceneType; // 0=exterior, 1=interior
|
||||
public PSXSceneType sceneType;
|
||||
public bool fogEnabled;
|
||||
public Color fogColor;
|
||||
public int fogDensity; // 1-10
|
||||
@@ -111,7 +111,12 @@ namespace SplashEdit.RuntimeCode
|
||||
int colliderCount = 0;
|
||||
foreach (var e in scene.exporters)
|
||||
{
|
||||
if (e.CollisionType != PSXCollisionType.None)
|
||||
if (e.CollisionType == PSXCollisionType.None || e.StaticCollider)
|
||||
continue;
|
||||
Mesh cm = e.CustomCollisionMesh != null
|
||||
? e.CustomCollisionMesh
|
||||
: e.GetComponent<MeshFilter>()?.sharedMesh;
|
||||
if (cm != null)
|
||||
colliderCount++;
|
||||
}
|
||||
|
||||
@@ -121,17 +126,17 @@ namespace SplashEdit.RuntimeCode
|
||||
exporterIndex[scene.exporters[i]] = i;
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
// Header (72 bytes total — splashpack v8)
|
||||
// Header (104 bytes — splashpack v15)
|
||||
// ──────────────────────────────────────────────────────
|
||||
writer.Write('S');
|
||||
writer.Write('P');
|
||||
writer.Write((ushort)13); // version
|
||||
writer.Write((ushort)15);
|
||||
writer.Write((ushort)luaFiles.Count);
|
||||
writer.Write((ushort)scene.exporters.Length);
|
||||
writer.Write((ushort)0); // navmeshCount (legacy)
|
||||
writer.Write((ushort)scene.atlases.Length);
|
||||
writer.Write((ushort)clutCount);
|
||||
writer.Write((ushort)colliderCount);
|
||||
writer.Write((ushort)scene.interactables.Length);
|
||||
writer.Write((ushort)PSXTrig.ConvertCoordinateToPSX(scene.playerPos.x, gte));
|
||||
writer.Write((ushort)PSXTrig.ConvertCoordinateToPSX(-scene.playerPos.y, gte));
|
||||
writer.Write((ushort)PSXTrig.ConvertCoordinateToPSX(scene.playerPos.z, gte));
|
||||
@@ -142,37 +147,23 @@ namespace SplashEdit.RuntimeCode
|
||||
|
||||
writer.Write((ushort)PSXTrig.ConvertCoordinateToPSX(scene.playerHeight, gte));
|
||||
|
||||
// Scene Lua index
|
||||
if (scene.sceneLuaFile != null)
|
||||
writer.Write((short)luaFiles.IndexOf(scene.sceneLuaFile));
|
||||
else
|
||||
writer.Write((short)-1);
|
||||
|
||||
// BVH info
|
||||
writer.Write((ushort)scene.bvh.NodeCount);
|
||||
writer.Write((ushort)scene.bvh.TriangleRefCount);
|
||||
|
||||
// Component counts (version 4)
|
||||
writer.Write((ushort)scene.interactables.Length);
|
||||
writer.Write((ushort)0); // healthCount (removed)
|
||||
writer.Write((ushort)0); // timerCount (removed)
|
||||
writer.Write((ushort)0); // spawnerCount (removed)
|
||||
writer.Write((ushort)scene.sceneType);
|
||||
writer.Write((ushort)0); // pad0
|
||||
|
||||
// NavGrid (version 5, legacy)
|
||||
writer.Write((ushort)0);
|
||||
writer.Write((ushort)0);
|
||||
|
||||
// Scene type (version 6)
|
||||
writer.Write((ushort)scene.sceneType); // 0=exterior, 1=interior
|
||||
writer.Write((ushort)0);
|
||||
|
||||
// World collision + nav regions (version 7)
|
||||
writer.Write((ushort)scene.collisionExporter.MeshCount);
|
||||
writer.Write((ushort)scene.collisionExporter.TriangleCount);
|
||||
writer.Write((ushort)scene.navRegionBuilder.RegionCount);
|
||||
writer.Write((ushort)scene.navRegionBuilder.PortalCount);
|
||||
|
||||
// Movement parameters (version 8, 12 bytes)
|
||||
// Movement parameters (12 bytes)
|
||||
{
|
||||
const float fps = 30f;
|
||||
float movePerFrame = scene.moveSpeed / fps / gte;
|
||||
@@ -187,52 +178,49 @@ namespace SplashEdit.RuntimeCode
|
||||
writer.Write((ushort)Mathf.Clamp(Mathf.RoundToInt(gravPsx * 4096f), 0, 65535));
|
||||
|
||||
writer.Write((ushort)PSXTrig.ConvertCoordinateToPSX(scene.playerRadius, gte));
|
||||
writer.Write((ushort)0); // padding
|
||||
writer.Write((ushort)0); // pad1
|
||||
}
|
||||
|
||||
// Name table offset placeholder (version 9, 4 bytes)
|
||||
long nameTableOffsetPos = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // placeholder for name table offset
|
||||
writer.Write((uint)0);
|
||||
|
||||
// Audio clip info (version 10, 8 bytes)
|
||||
int audioClipCount = scene.audioClips?.Length ?? 0;
|
||||
writer.Write((ushort)audioClipCount);
|
||||
writer.Write((ushort)0); // padding
|
||||
writer.Write((ushort)0); // pad2
|
||||
long audioTableOffsetPos = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // placeholder for audio table offset
|
||||
writer.Write((uint)0);
|
||||
|
||||
// Fog + room/portal header (version 11, 12 bytes)
|
||||
{
|
||||
writer.Write((byte)(scene.fogEnabled ? 1 : 0));
|
||||
writer.Write((byte)Mathf.Clamp(Mathf.RoundToInt(scene.fogColor.r * 255f), 0, 255));
|
||||
writer.Write((byte)Mathf.Clamp(Mathf.RoundToInt(scene.fogColor.g * 255f), 0, 255));
|
||||
writer.Write((byte)Mathf.Clamp(Mathf.RoundToInt(scene.fogColor.b * 255f), 0, 255));
|
||||
writer.Write((byte)Mathf.Clamp(scene.fogDensity, 1, 10));
|
||||
writer.Write((byte)0); // reserved
|
||||
writer.Write((byte)0); // pad3
|
||||
int roomCount = scene.roomBuilder?.RoomCount ?? 0;
|
||||
int portalCount = scene.roomBuilder?.PortalCount ?? 0;
|
||||
int roomTriRefCount = scene.roomBuilder?.TotalTriRefCount ?? 0;
|
||||
// roomCount is the room count NOT including catch-all; the binary adds +1 for it
|
||||
writer.Write((ushort)(roomCount > 0 ? roomCount + 1 : 0));
|
||||
writer.Write((ushort)portalCount);
|
||||
writer.Write((ushort)roomTriRefCount);
|
||||
}
|
||||
|
||||
// Cutscene header (version 12, 8 bytes)
|
||||
int cutsceneCount = scene.cutscenes?.Length ?? 0;
|
||||
writer.Write((ushort)cutsceneCount);
|
||||
writer.Write((ushort)0); // reserved_cs
|
||||
writer.Write((ushort)0); // pad4
|
||||
long cutsceneTableOffsetPos = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // cutsceneTableOffset placeholder
|
||||
|
||||
// UI canvas header (version 13, 8 bytes)
|
||||
int uiCanvasCount = scene.canvases?.Length ?? 0;
|
||||
int uiFontCount = scene.fonts?.Length ?? 0;
|
||||
writer.Write((ushort)uiCanvasCount);
|
||||
writer.Write((byte)uiFontCount); // was uiReserved low byte
|
||||
writer.Write((byte)0); // was uiReserved high byte
|
||||
writer.Write((byte)uiFontCount);
|
||||
writer.Write((byte)0); // uiPad5
|
||||
long uiTableOffsetPos = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // uiTableOffset placeholder
|
||||
writer.Write((uint)0);
|
||||
|
||||
long pixelDataOffsetPos = writer.BaseStream.Position;
|
||||
writer.Write((uint)0); // pixelDataOffset placeholder
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
// Lua file metadata
|
||||
@@ -295,7 +283,7 @@ namespace SplashEdit.RuntimeCode
|
||||
for (int exporterIdx = 0; exporterIdx < scene.exporters.Length; exporterIdx++)
|
||||
{
|
||||
PSXObjectExporter exporter = scene.exporters[exporterIdx];
|
||||
if (exporter.CollisionType == PSXCollisionType.None)
|
||||
if (exporter.CollisionType == PSXCollisionType.None || exporter.StaticCollider)
|
||||
continue;
|
||||
|
||||
MeshFilter meshFilter = exporter.GetComponent<MeshFilter>();
|
||||
@@ -483,33 +471,6 @@ namespace SplashEdit.RuntimeCode
|
||||
}
|
||||
}
|
||||
|
||||
// Atlas pixel data
|
||||
foreach (TextureAtlas atlas in scene.atlases)
|
||||
{
|
||||
AlignToFourBytes(writer);
|
||||
atlasOffset.DataOffsets.Add(writer.BaseStream.Position);
|
||||
|
||||
for (int y = 0; y < atlas.vramPixels.GetLength(1); y++)
|
||||
for (int x = 0; x < atlas.vramPixels.GetLength(0); x++)
|
||||
writer.Write(atlas.vramPixels[x, y].Pack());
|
||||
}
|
||||
|
||||
// CLUT data
|
||||
foreach (TextureAtlas atlas in scene.atlases)
|
||||
{
|
||||
foreach (var texture in atlas.ContainedTextures)
|
||||
{
|
||||
if (texture.ColorPalette != null)
|
||||
{
|
||||
AlignToFourBytes(writer);
|
||||
clutOffset.DataOffsets.Add(writer.BaseStream.Position);
|
||||
|
||||
foreach (VRAMPixel color in texture.ColorPalette)
|
||||
writer.Write((ushort)color.Pack());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
// Object name table (version 9)
|
||||
// ──────────────────────────────────────────────────────
|
||||
@@ -535,46 +496,50 @@ namespace SplashEdit.RuntimeCode
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
// Audio clip data (version 10)
|
||||
// Metadata entries are 16 bytes each, written contiguously.
|
||||
// Name strings follow the metadata block with backfilled offsets.
|
||||
// ADPCM blobs deferred to dead zone.
|
||||
// ──────────────────────────────────────────────────────
|
||||
List<long> audioDataOffsetPositions = new List<long>();
|
||||
if (audioClipCount > 0 && scene.audioClips != null)
|
||||
{
|
||||
// Write audio table: per clip metadata (12 bytes each)
|
||||
AlignToFourBytes(writer);
|
||||
long audioTableStart = writer.BaseStream.Position;
|
||||
|
||||
// First pass: write metadata placeholders (16 bytes each)
|
||||
List<long> audioDataOffsetPositions = new List<long>();
|
||||
List<long> audioNameOffsetPositions = new List<long>();
|
||||
List<string> audioClipNames = new List<string>();
|
||||
|
||||
// Phase 1: Write all 16-byte metadata entries contiguously
|
||||
for (int i = 0; i < audioClipCount; i++)
|
||||
{
|
||||
var clip = scene.audioClips[i];
|
||||
audioDataOffsetPositions.Add(writer.BaseStream.Position);
|
||||
writer.Write((uint)0); // dataOffset placeholder
|
||||
writer.Write((uint)(clip.adpcmData?.Length ?? 0)); // sizeBytes
|
||||
writer.Write((ushort)clip.sampleRate);
|
||||
string name = clip.clipName ?? "";
|
||||
if (name.Length > 255) name = name.Substring(0, 255);
|
||||
|
||||
audioDataOffsetPositions.Add(writer.BaseStream.Position);
|
||||
writer.Write((uint)0); // dataOffset placeholder (backfilled in dead zone)
|
||||
writer.Write((uint)(clip.adpcmData?.Length ?? 0));
|
||||
writer.Write((ushort)clip.sampleRate);
|
||||
writer.Write((byte)(clip.loop ? 1 : 0));
|
||||
writer.Write((byte)System.Math.Min(name.Length, 255));
|
||||
writer.Write((byte)name.Length);
|
||||
audioNameOffsetPositions.Add(writer.BaseStream.Position);
|
||||
writer.Write((uint)0); // nameOffset placeholder
|
||||
audioClipNames.Add(name);
|
||||
}
|
||||
|
||||
// Second pass: write ADPCM data and backfill offsets
|
||||
// Phase 2: Write name strings (after all metadata entries)
|
||||
for (int i = 0; i < audioClipCount; i++)
|
||||
{
|
||||
byte[] data = scene.audioClips[i].adpcmData;
|
||||
if (data != null && data.Length > 0)
|
||||
{
|
||||
AlignToFourBytes(writer);
|
||||
long dataPos = writer.BaseStream.Position;
|
||||
writer.Write(data);
|
||||
string name = audioClipNames[i];
|
||||
long namePos = writer.BaseStream.Position;
|
||||
byte[] nameBytes = System.Text.Encoding.ASCII.GetBytes(name);
|
||||
writer.Write(nameBytes);
|
||||
writer.Write((byte)0);
|
||||
|
||||
// Backfill data offset
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)audioDataOffsetPositions[i], SeekOrigin.Begin);
|
||||
writer.Write((uint)dataPos);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)audioNameOffsetPositions[i], SeekOrigin.Begin);
|
||||
writer.Write((uint)namePos);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
// Backfill audio table offset in header
|
||||
@@ -584,28 +549,6 @@ namespace SplashEdit.RuntimeCode
|
||||
writer.Write((uint)audioTableStart);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
int totalAudioBytes = 0;
|
||||
foreach (var clip in scene.audioClips)
|
||||
if (clip.adpcmData != null) totalAudioBytes += clip.adpcmData.Length;
|
||||
|
||||
// Third pass: write audio clip names and backfill name offsets
|
||||
for (int i = 0; i < audioClipCount; i++)
|
||||
{
|
||||
string name = scene.audioClips[i].clipName ?? "";
|
||||
if (name.Length > 255) name = name.Substring(0, 255);
|
||||
long namePos = writer.BaseStream.Position;
|
||||
byte[] nameBytes = System.Text.Encoding.ASCII.GetBytes(name);
|
||||
writer.Write(nameBytes);
|
||||
writer.Write((byte)0); // null terminator
|
||||
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)audioNameOffsetPositions[i], SeekOrigin.Begin);
|
||||
writer.Write((uint)namePos);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
log?.Invoke($"{audioClipCount} audio clips ({totalAudioBytes / 1024}KB ADPCM) written.", LogType.Log);
|
||||
}
|
||||
|
||||
// ──────────────────────────────────────────────────────
|
||||
@@ -635,11 +578,12 @@ namespace SplashEdit.RuntimeCode
|
||||
// ──────────────────────────────────────────────────────
|
||||
// UI canvas + font data (version 13)
|
||||
// Font descriptors: 112 bytes each (before canvas data)
|
||||
// Font pixel data: raw 4bpp (after font descriptors)
|
||||
// Canvas descriptor table: 12 bytes per canvas
|
||||
// Element records: 48 bytes each
|
||||
// Name and text strings follow with offset backfill
|
||||
// Font pixel data is deferred to the dead zone.
|
||||
// ──────────────────────────────────────────────────────
|
||||
List<long> fontDataOffsetPositions = new List<long>();
|
||||
if ((uiCanvasCount > 0 && scene.canvases != null) || uiFontCount > 0)
|
||||
{
|
||||
AlignToFourBytes(writer);
|
||||
@@ -648,7 +592,6 @@ namespace SplashEdit.RuntimeCode
|
||||
// ── Font descriptors (112 bytes each) ──
|
||||
// Layout: glyphW(1) glyphH(1) vramX(2) vramY(2) textureH(2)
|
||||
// dataOffset(4) dataSize(4)
|
||||
List<long> fontDataOffsetPositions = new List<long>();
|
||||
if (scene.fonts != null)
|
||||
{
|
||||
foreach (var font in scene.fonts)
|
||||
@@ -669,32 +612,9 @@ namespace SplashEdit.RuntimeCode
|
||||
}
|
||||
}
|
||||
|
||||
// ── Font pixel data (raw 4bpp) ──
|
||||
if (scene.fonts != null)
|
||||
{
|
||||
for (int fi = 0; fi < scene.fonts.Length; fi++)
|
||||
{
|
||||
var font = scene.fonts[fi];
|
||||
if (font.PixelData == null || font.PixelData.Length == 0) continue;
|
||||
|
||||
AlignToFourBytes(writer);
|
||||
long dataPos = writer.BaseStream.Position;
|
||||
writer.Write(font.PixelData);
|
||||
|
||||
// Backfill data offset
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)fontDataOffsetPositions[fi], SeekOrigin.Begin);
|
||||
writer.Write((uint)dataPos);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
if (scene.fonts.Length > 0)
|
||||
{
|
||||
int totalFontBytes = 0;
|
||||
foreach (var f in scene.fonts) totalFontBytes += f.PixelData?.Length ?? 0;
|
||||
log?.Invoke($"{scene.fonts.Length} custom font(s) written ({totalFontBytes} bytes 4bpp data).", LogType.Log);
|
||||
}
|
||||
}
|
||||
// Font pixel data is deferred to the dead zone (after pixelDataOffset).
|
||||
// The C++ loader reads font pixel data via the dataOffset, uploads to VRAM,
|
||||
// then never accesses it again.
|
||||
|
||||
// ── Canvas descriptor table (12 bytes each) ──
|
||||
// Layout per descriptor:
|
||||
@@ -873,6 +793,96 @@ namespace SplashEdit.RuntimeCode
|
||||
log?.Invoke($"{uiCanvasCount} UI canvases ({totalElements} elements) written.", LogType.Log);
|
||||
}
|
||||
|
||||
// ══════════════════════════════════════════════════════
|
||||
// DEAD ZONE — pixel/audio bulk data (freed after VRAM/SPU upload)
|
||||
// Everything written after this point is not needed at runtime.
|
||||
// ══════════════════════════════════════════════════════
|
||||
AlignToFourBytes(writer);
|
||||
long pixelDataStart = writer.BaseStream.Position;
|
||||
|
||||
// Atlas pixel data
|
||||
foreach (TextureAtlas atlas in scene.atlases)
|
||||
{
|
||||
AlignToFourBytes(writer);
|
||||
atlasOffset.DataOffsets.Add(writer.BaseStream.Position);
|
||||
|
||||
for (int y = 0; y < atlas.vramPixels.GetLength(1); y++)
|
||||
for (int x = 0; x < atlas.vramPixels.GetLength(0); x++)
|
||||
writer.Write(atlas.vramPixels[x, y].Pack());
|
||||
}
|
||||
|
||||
// CLUT data
|
||||
foreach (TextureAtlas atlas in scene.atlases)
|
||||
{
|
||||
foreach (var texture in atlas.ContainedTextures)
|
||||
{
|
||||
if (texture.ColorPalette != null)
|
||||
{
|
||||
AlignToFourBytes(writer);
|
||||
clutOffset.DataOffsets.Add(writer.BaseStream.Position);
|
||||
|
||||
foreach (VRAMPixel color in texture.ColorPalette)
|
||||
writer.Write((ushort)color.Pack());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Audio ADPCM data
|
||||
if (audioClipCount > 0 && scene.audioClips != null)
|
||||
{
|
||||
for (int i = 0; i < audioClipCount; i++)
|
||||
{
|
||||
byte[] data = scene.audioClips[i].adpcmData;
|
||||
if (data != null && data.Length > 0)
|
||||
{
|
||||
AlignToFourBytes(writer);
|
||||
long dataPos = writer.BaseStream.Position;
|
||||
writer.Write(data);
|
||||
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)audioDataOffsetPositions[i], SeekOrigin.Begin);
|
||||
writer.Write((uint)dataPos);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
int totalAudioBytes = 0;
|
||||
foreach (var clip in scene.audioClips)
|
||||
if (clip.adpcmData != null) totalAudioBytes += clip.adpcmData.Length;
|
||||
log?.Invoke($"{audioClipCount} audio clips ({totalAudioBytes / 1024}KB ADPCM) written.", LogType.Log);
|
||||
}
|
||||
|
||||
// Font pixel data
|
||||
if (scene.fonts != null)
|
||||
{
|
||||
for (int fi = 0; fi < scene.fonts.Length; fi++)
|
||||
{
|
||||
var font = scene.fonts[fi];
|
||||
if (font.PixelData == null || font.PixelData.Length == 0) continue;
|
||||
|
||||
AlignToFourBytes(writer);
|
||||
long dataPos = writer.BaseStream.Position;
|
||||
writer.Write(font.PixelData);
|
||||
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)fontDataOffsetPositions[fi], SeekOrigin.Begin);
|
||||
writer.Write((uint)dataPos);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
}
|
||||
|
||||
// Backfill pixelDataOffset in header
|
||||
{
|
||||
long curPos = writer.BaseStream.Position;
|
||||
writer.Seek((int)pixelDataOffsetPos, SeekOrigin.Begin);
|
||||
writer.Write((uint)pixelDataStart);
|
||||
writer.Seek((int)curPos, SeekOrigin.Begin);
|
||||
}
|
||||
|
||||
long totalSize = writer.BaseStream.Position;
|
||||
long deadBytes = totalSize - pixelDataStart;
|
||||
log?.Invoke($"Pixel/audio dead zone: {deadBytes / 1024}KB (freed after VRAM/SPU upload).", LogType.Log);
|
||||
|
||||
// Backfill offsets
|
||||
BackfillOffsets(writer, luaOffset, "lua", log);
|
||||
BackfillOffsets(writer, meshOffset, "mesh", log);
|
||||
|
||||
Reference in New Issue
Block a user