diff options
Diffstat (limited to 'src/factories/oot/OoTSceneCommandWriter.cpp')
| -rw-r--r-- | src/factories/oot/OoTSceneCommandWriter.cpp | 696 |
1 files changed, 696 insertions, 0 deletions
diff --git a/src/factories/oot/OoTSceneCommandWriter.cpp b/src/factories/oot/OoTSceneCommandWriter.cpp new file mode 100644 index 0000000..b3553fa --- /dev/null +++ b/src/factories/oot/OoTSceneCommandWriter.cpp @@ -0,0 +1,696 @@ +#include "OoTSceneCommandWriter.h" +#include "AliasManager.h" +#include "spdlog/spdlog.h" +#include "Companion.h" +#include "utils/Decompressor.h" +#include "factories/DisplayListFactory.h" +#include <sstream> +#include <iomanip> + +namespace OoT { + +SceneCommand SceneCommandWriter::Write(uint32_t w0, uint32_t w1, SceneWriteContext& ctx) { + uint8_t cmdID = (w0 >> 24) & 0xFF; + uint8_t cmdArg1 = (w0 >> 16) & 0xFF; + uint32_t cmdArg2 = w1; + + SceneCommand cmd; + cmd.cmdID = cmdID; + + LUS::BinaryWriter cmdWriter; + + switch (cmdID) { + case SetWind: { + WriteSetWind(cmdWriter, w0, w1); + break; + } + case SetTimeSettings: { + WriteSetTimeSettings(cmdWriter, w1); + break; + } + case SetSkyboxModifier: { + WriteSetSkyboxModifier(cmdWriter, w1); + break; + } + case SetEchoSettings: { + WriteSetEchoSettings(cmdWriter, w1); + break; + } + case SetSoundSettings: { + WriteSetSoundSettings(cmdWriter, cmdArg1, w1); + break; + } + case SetSkyboxSettings: { + WriteSetSkyboxSettings(cmdWriter, cmdArg1, w1); + break; + } + case SetRoomBehavior: { + WriteSetRoomBehavior(cmdWriter, cmdArg1, cmdArg2); + break; + } + case SetCameraSettings: { + WriteSetCameraSettings(cmdWriter, cmdArg1, cmdArg2); + break; + } + case SetSpecialObjects: { + WriteSetSpecialObjects(cmdWriter, cmdArg1, w1); + break; + } + case SetStartPositionList: { + WriteSetStartPositionList(cmdWriter, cmdArg1, cmdArg2, ctx); + break; + } + case SetActorList: { + WriteSetActorList(cmdWriter, cmdArg1, cmdArg2, ctx); + break; + } + case SetTransitionActorList: { + WriteSetTransitionActorList(cmdWriter, cmdArg1, cmdArg2, ctx); + break; + } + case SetEntranceList: { + WriteSetEntranceList(cmdWriter, cmdArg2, ctx); + break; + } + case SetObjectList: { + WriteSetObjectList(cmdWriter, cmdArg1, cmdArg2, ctx); + break; + } + case SetLightingSettings: { + WriteSetLightingSettings(cmdWriter, cmdArg1, cmdArg2, ctx); + break; + } + case SetLightList: { + WriteSetLightList(cmdWriter, cmdArg1, cmdArg2, ctx); + break; + } + case SetExitList: { + WriteSetExitList(cmdWriter, cmdArg2, ctx); + break; + } + case SetRoomList: { + WriteSetRoomList(cmdWriter, cmdArg1, cmdArg2, ctx); + break; + } + case SetCollisionHeader: { + WriteSetCollisionHeader(cmdWriter, cmdArg2); + break; + } + case SetMesh: { + WriteSetMesh(cmdWriter, cmdArg2, ctx); + break; + } + case SetCsCamera: { + WriteSetCsCamera(cmdWriter, cmdArg1, cmdArg2, ctx); + break; + } + case SetPathways: { + WriteSetPathways(cmdWriter, cmdArg2, ctx); + break; + } + case SetCutscenes: { + WriteSetCutscenes(cmdWriter, cmdArg2, ctx); + break; + } + case SetAlternateHeaders: { + WriteSetAlternateHeaders(cmdWriter, cmdArg2, ctx); + break; + } + case EndMarker: { + break; + } + default: { + SPDLOG_WARN("Scene: Unhandled command 0x{:02X}", cmdID); + break; + } + } + + std::stringstream ss; + cmdWriter.Finish(ss); + std::string str = ss.str(); + cmd.data = std::vector<uint8_t>(str.begin(), str.end()); + return cmd; +} + +void SceneCommandWriter::WriteSetStartPositionList(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2, + SceneWriteContext& ctx) { + uint32_t count = cmdArg1; + auto sub = ReadSubArray(ctx.buffer, cmdArg2, count * 16); + w.Write(static_cast<uint32_t>(count)); + for (uint32_t i = 0; i < count; i++) { + w.Write(sub.ReadInt16()); // actorNum + w.Write(sub.ReadInt16()); // posX + w.Write(sub.ReadInt16()); // posY + w.Write(sub.ReadInt16()); // posZ + w.Write(sub.ReadInt16()); // rotX + w.Write(sub.ReadInt16()); // rotY + w.Write(sub.ReadInt16()); // rotZ + w.Write(sub.ReadInt16()); // params + } +} + +void SceneCommandWriter::WriteSetActorList(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2, + SceneWriteContext& ctx) { + uint32_t count = cmdArg1; + auto sub = ReadSubArray(ctx.buffer, cmdArg2, count * 16); + w.Write(static_cast<uint32_t>(count)); + for (uint32_t i = 0; i < count; i++) { + w.Write(sub.ReadInt16()); // actorNum + w.Write(sub.ReadInt16()); // posX + w.Write(sub.ReadInt16()); // posY + w.Write(sub.ReadInt16()); // posZ + w.Write(sub.ReadInt16()); // rotX + w.Write(sub.ReadInt16()); // rotY + w.Write(sub.ReadInt16()); // rotZ + w.Write(sub.ReadInt16()); // params + } + + // Create 0-byte ActorEntry companion file (matches OTRExporter behavior). + if (cmdArg2 != 0 && count > 0) { + uint32_t actorOffset = SEGMENT_OFFSET(Companion::Instance->PatchVirtualAddr(cmdArg2)); + std::string actorSymbol = MakeAssetName(ctx.baseName, "ActorEntry", actorOffset); + Companion::Instance->RegisterCompanionFile(actorSymbol, std::vector<char>{}); + } +} + +void SceneCommandWriter::WriteSetWind(LUS::BinaryWriter& w, uint32_t w0, uint32_t w1) { + w.Write(static_cast<uint8_t>((w1 >> 24) & 0xFF)); // windWest + w.Write(static_cast<uint8_t>((w1 >> 16) & 0xFF)); // windVertical + w.Write(static_cast<uint8_t>((w1 >> 8) & 0xFF)); // windSouth + w.Write(static_cast<uint8_t>(w1 & 0xFF)); // clothFlappingStrength +} + +void SceneCommandWriter::WriteSetTimeSettings(LUS::BinaryWriter& w, uint32_t w1) { + w.Write(static_cast<uint8_t>((w1 >> 24) & 0xFF)); // hour + w.Write(static_cast<uint8_t>((w1 >> 16) & 0xFF)); // min + w.Write(static_cast<uint8_t>((w1 >> 8) & 0xFF)); // unk +} + +void SceneCommandWriter::WriteSetSkyboxModifier(LUS::BinaryWriter& w, uint32_t w1) { + w.Write(static_cast<uint8_t>((w1 >> 24) & 0xFF)); // disableSky + w.Write(static_cast<uint8_t>((w1 >> 16) & 0xFF)); // disableSunMoon +} + +void SceneCommandWriter::WriteSetEchoSettings(LUS::BinaryWriter& w, uint32_t w1) { + w.Write(static_cast<uint8_t>(w1 & 0xFF)); // echo +} + +void SceneCommandWriter::WriteSetSoundSettings(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t w1) { + w.Write(static_cast<uint8_t>(cmdArg1)); // reverb + w.Write(static_cast<uint8_t>((w1 >> 8) & 0xFF)); // nightTimeSFX + w.Write(static_cast<uint8_t>(w1 & 0xFF)); // musicSequence +} + +void SceneCommandWriter::WriteSetSkyboxSettings(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t w1) { + w.Write(static_cast<uint8_t>(cmdArg1)); // unk1 + w.Write(static_cast<uint8_t>((w1 >> 24) & 0xFF)); // skyboxNumber + w.Write(static_cast<uint8_t>((w1 >> 16) & 0xFF)); // cloudsType + w.Write(static_cast<uint8_t>((w1 >> 8) & 0xFF)); // isIndoors +} + +void SceneCommandWriter::WriteSetRoomBehavior(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2) { + w.Write(static_cast<uint8_t>(cmdArg1)); // gameplayFlags + w.Write(cmdArg2); // gameplayFlags2 +} + +void SceneCommandWriter::WriteSetCameraSettings(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2) { + w.Write(static_cast<uint8_t>(cmdArg1)); // cameraMovement + w.Write(cmdArg2); // mapHighlight +} + +void SceneCommandWriter::WriteSetSpecialObjects(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t w1) { + w.Write(static_cast<uint8_t>(cmdArg1)); // elfMessage + w.Write(static_cast<uint16_t>(w1 & 0xFFFF)); // globalObject +} + +void SceneCommandWriter::WriteSetTransitionActorList(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2, + SceneWriteContext& ctx) { + uint32_t count = cmdArg1; + auto sub = ReadSubArray(ctx.buffer, cmdArg2, count * 16); + w.Write(static_cast<uint32_t>(count)); + for (uint32_t i = 0; i < count; i++) { + w.Write(sub.ReadInt8()); // frontObjectRoom + w.Write(sub.ReadInt8()); // frontTransitionReaction + w.Write(sub.ReadInt8()); // backObjectRoom + w.Write(sub.ReadInt8()); // backTransitionReaction + w.Write(sub.ReadInt16()); // actorNum + w.Write(sub.ReadInt16()); // posX + w.Write(sub.ReadInt16()); // posY + w.Write(sub.ReadInt16()); // posZ + w.Write(sub.ReadInt16()); // rotY + w.Write(sub.ReadInt16()); // initVar + } +} + +void SceneCommandWriter::WriteSetEntranceList(LUS::BinaryWriter& w, uint32_t cmdArg2, SceneWriteContext& ctx) { + uint32_t count = GetNeighborSize(ctx.knownAddrs, cmdArg2, 2); + if (count == 0) count = 1; + auto sub = ReadSubArray(ctx.buffer, cmdArg2, count * 2); + w.Write(static_cast<uint32_t>(count)); + for (uint32_t i = 0; i < count; i++) { + w.Write(sub.ReadUByte()); // startPositionIndex + w.Write(sub.ReadUByte()); // roomToLoad + } +} + +void SceneCommandWriter::WriteSetObjectList(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2, + SceneWriteContext& ctx) { + uint32_t count = cmdArg1; + auto sub = ReadSubArray(ctx.buffer, cmdArg2, count * 2); + w.Write(static_cast<uint32_t>(count)); + for (uint32_t i = 0; i < count; i++) { + w.Write(sub.ReadUInt16()); + } +} + +void SceneCommandWriter::WriteSetLightingSettings(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2, + SceneWriteContext& ctx) { + uint32_t count = cmdArg1; + auto sub = ReadSubArray(ctx.buffer, cmdArg2, count * 22); + w.Write(static_cast<uint32_t>(count)); + for (uint32_t i = 0; i < count; i++) { + for (int j = 0; j < 18; j++) { + w.Write(sub.ReadUByte()); + } + w.Write(sub.ReadUInt16()); // unk + w.Write(sub.ReadUInt16()); // drawDistance + } +} + +void SceneCommandWriter::WriteSetLightList(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2, + SceneWriteContext& ctx) { + uint32_t count = cmdArg1; + auto sub = ReadSubArray(ctx.buffer, cmdArg2, count * 14); + w.Write(static_cast<uint32_t>(count)); + for (uint32_t i = 0; i < count; i++) { + w.Write(sub.ReadUByte()); // type + w.Write(sub.ReadInt16()); // x + w.Write(sub.ReadInt16()); // y + w.Write(sub.ReadInt16()); // z + w.Write(sub.ReadUByte()); // r + w.Write(sub.ReadUByte()); // g + w.Write(sub.ReadUByte()); // b + w.Write(sub.ReadUByte()); // drawGlow + w.Write(sub.ReadInt16()); // radius + } +} + +void SceneCommandWriter::WriteSetExitList(LUS::BinaryWriter& w, uint32_t cmdArg2, SceneWriteContext& ctx) { + uint32_t count = GetNeighborSize(ctx.knownAddrs, cmdArg2, 2); + if (count == 0) count = 1; + auto sub = ReadSubArray(ctx.buffer, cmdArg2, count * 2); + w.Write(static_cast<uint32_t>(count)); + for (uint32_t i = 0; i < count; i++) { + w.Write(sub.ReadUInt16()); + } +} + +void SceneCommandWriter::WriteSetRoomList(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2, + SceneWriteContext& ctx) { + uint32_t count = cmdArg1; + auto sub = ReadSubArray(ctx.buffer, cmdArg2, count * 8); + w.Write(static_cast<uint32_t>(count)); + + // Derive scene base name from current directory (e.g. "scenes/nonmq/bdan_scene" → "bdan") + std::string sceneBase; + auto lastSlash = ctx.currentDir.rfind('/'); + sceneBase = (lastSlash != std::string::npos) ? ctx.currentDir.substr(lastSlash + 1) : ctx.currentDir; + std::string roomBase = sceneBase; + if (roomBase.size() > 6 && roomBase.substr(roomBase.size() - 6) == "_scene") { + roomBase = roomBase.substr(0, roomBase.size() - 6); + } + + for (uint32_t i = 0; i < count; i++) { + std::string roomName = ctx.currentDir + "/" + roomBase + "_room_" + std::to_string(i); + w.Write(roomName); + w.Write(sub.ReadUInt32()); // virtualAddressStart + w.Write(sub.ReadUInt32()); // virtualAddressEnd + } +} + +void SceneCommandWriter::WriteSetCollisionHeader(LUS::BinaryWriter& w, uint32_t cmdArg2) { + uint32_t colAddr = Companion::Instance->PatchVirtualAddr(cmdArg2); + auto resolved = ResolvePointer(cmdArg2); + if (resolved.empty()) { + SPDLOG_WARN("Undeclared collision at 0x{:08X} — YAML enrichment incomplete", colAddr); + } + w.Write(resolved); +} + +void SceneCommandWriter::WriteSetMesh(LUS::BinaryWriter& w, uint32_t cmdArg2, SceneWriteContext& ctx) { + uint8_t meshData = 0; + auto meshReader = ReadSubArray(ctx.buffer, cmdArg2, 12); + uint8_t meshHeaderType = meshReader.ReadUByte(); + + w.Write(meshData); + w.Write(meshHeaderType); + + DeferredVtx::BeginDefer(); + + if (meshHeaderType == 0 || meshHeaderType == 2) { + uint32_t num = meshReader.ReadUByte(); + meshReader.ReadUInt16(); // padding + uint32_t polyStart = meshReader.ReadUInt32(); + + w.Write(static_cast<uint8_t>(num)); + + uint32_t entrySize = (meshHeaderType == 2) ? 16 : 8; + auto polyReader = ReadSubArray(ctx.buffer, polyStart, num * entrySize); + + for (uint32_t i = 0; i < num; i++) { + uint8_t polyType = 0; + int16_t cx = 0, cy = 0, cz = 0; + int16_t unk_06 = 0; + + if (meshHeaderType == 2) { + polyType = 2; + cx = polyReader.ReadInt16(); + cy = polyReader.ReadInt16(); + cz = polyReader.ReadInt16(); + unk_06 = polyReader.ReadInt16(); + } + + uint32_t opaAddr = polyReader.ReadUInt32(); + uint32_t xluAddr = polyReader.ReadUInt32(); + + w.Write(polyType); + if (polyType == 2) { + w.Write(cx); + w.Write(cy); + w.Write(cz); + w.Write(unk_06); + } + + std::string opaPath; + if (opaAddr != 0) { + uint32_t opaOffset = SEGMENT_OFFSET(Companion::Instance->PatchVirtualAddr(opaAddr)); + std::string opaSymbol = MakeAssetName(ctx.entryName, "DL", opaOffset); + opaPath = ResolveGfxWithAlias(opaAddr, opaSymbol, ctx.currentDir); + } + w.Write(opaPath.empty() ? std::string("") : opaPath); + + std::string xluPath; + if (xluAddr != 0) { + uint32_t xluOffset = SEGMENT_OFFSET(Companion::Instance->PatchVirtualAddr(xluAddr)); + std::string xluSymbol = MakeAssetName(ctx.entryName, "DL", xluOffset); + xluPath = ResolveGfxWithAlias(xluAddr, xluSymbol, ctx.currentDir); + } + w.Write(xluPath.empty() ? std::string("") : xluPath); + } + } else if (meshHeaderType == 1) { + uint8_t format = meshReader.ReadUByte(); + meshReader.ReadUInt16(); // padding + uint32_t polyDListAddr = meshReader.ReadUInt32(); + + w.Write(format); + + auto pdlReader = ReadSubArray(ctx.buffer, polyDListAddr, 8); + uint32_t opaAddr = pdlReader.ReadUInt32(); + uint32_t xluAddr = pdlReader.ReadUInt32(); + + std::string opaPath, xluPath; + if (opaAddr != 0) { + uint32_t opaOffset = SEGMENT_OFFSET(Companion::Instance->PatchVirtualAddr(opaAddr)); + std::string opaSymbol = MakeAssetName(ctx.entryName, "DL", opaOffset); + opaPath = ResolveGfxWithAlias(opaAddr, opaSymbol, ctx.currentDir); + } + if (xluAddr != 0) { + uint32_t xluOffset = SEGMENT_OFFSET(Companion::Instance->PatchVirtualAddr(xluAddr)); + std::string xluSymbol = MakeAssetName(ctx.entryName, "DL", xluOffset); + xluPath = ResolveGfxWithAlias(xluAddr, xluSymbol, ctx.currentDir); + } + w.Write(opaPath.empty() ? std::string("") : opaPath); + w.Write(xluPath.empty() ? std::string("") : xluPath); + + if (format == 2) { + auto mhReader = ReadSubArray(ctx.buffer, cmdArg2 + 0x08, 8); + uint32_t count = mhReader.ReadUByte(); + mhReader.ReadUByte(); mhReader.ReadUByte(); mhReader.ReadUByte(); + uint32_t multiAddr = mhReader.ReadUInt32(); + + w.Write(static_cast<uint32_t>(count)); + + auto bgReader = ReadSubArray(ctx.buffer, multiAddr, count * 28); + for (uint32_t i = 0; i < count; i++) { + uint16_t unk_00 = bgReader.ReadUInt16(); + uint8_t id = bgReader.ReadUByte(); + bgReader.ReadUByte(); + uint32_t source = bgReader.ReadUInt32(); + uint32_t unk_0C = bgReader.ReadUInt32(); + uint32_t tlut = bgReader.ReadUInt32(); + uint16_t width = bgReader.ReadUInt16(); + uint16_t height = bgReader.ReadUInt16(); + uint8_t fmt = bgReader.ReadUByte(); + uint8_t siz = bgReader.ReadUByte(); + uint16_t mode0 = bgReader.ReadUInt16(); + uint16_t tlutCount = bgReader.ReadUInt16(); + bgReader.ReadUInt16(); + + w.Write(unk_00); + w.Write(id); + + uint32_t bgOffset = SEGMENT_OFFSET(Companion::Instance->PatchVirtualAddr(source)); + std::string bgSymbol = MakeAssetName(ctx.baseName, "Background", bgOffset); + std::string bgPath = ResolvePointer(source); + if (bgPath.empty()) { + bgPath = ctx.currentDir + "/" + bgSymbol; + } + w.Write(bgPath); + + w.Write(unk_0C); w.Write(tlut); + w.Write(width); w.Write(height); + w.Write(fmt); w.Write(siz); + w.Write(mode0); w.Write(tlutCount); + + CreateBackgroundCompanion(ctx.buffer, source, bgSymbol); + } + } else { + w.Write(static_cast<uint32_t>(1)); + w.Write(static_cast<uint16_t>(0)); // unk_00 + w.Write(static_cast<uint8_t>(0)); // id + + auto bgReader = ReadSubArray(ctx.buffer, cmdArg2 + 0x08, 24); + uint32_t source = bgReader.ReadUInt32(); + uint32_t unk_0C = bgReader.ReadUInt32(); + uint32_t tlut = bgReader.ReadUInt32(); + uint16_t width = bgReader.ReadUInt16(); + uint16_t height = bgReader.ReadUInt16(); + uint8_t fmt = bgReader.ReadUByte(); + uint8_t siz = bgReader.ReadUByte(); + uint16_t mode0 = bgReader.ReadUInt16(); + uint16_t tlutCount = bgReader.ReadUInt16(); + + uint32_t bgOffset = SEGMENT_OFFSET(Companion::Instance->PatchVirtualAddr(source)); + std::string bgSymbol = MakeAssetName(ctx.baseName, "Background", bgOffset); + std::string bgPath = ResolvePointer(source); + if (bgPath.empty()) { + bgPath = ctx.currentDir + "/" + bgSymbol; + } + w.Write(bgPath); + + w.Write(unk_0C); w.Write(tlut); + w.Write(width); w.Write(height); + w.Write(fmt); w.Write(siz); + w.Write(mode0); w.Write(tlutCount); + + CreateBackgroundCompanion(ctx.buffer, source, bgSymbol); + } + + if (polyDListAddr != 0) { + w.Write(static_cast<uint8_t>(meshHeaderType)); + w.Write(opaPath.empty() ? std::string("") : opaPath); + w.Write(xluPath.empty() ? std::string("") : xluPath); + } + } +} + +void SceneCommandWriter::WriteSetCsCamera(LUS::BinaryWriter& w, uint8_t cmdArg1, uint32_t cmdArg2, + SceneWriteContext& ctx) { + auto camReader = ReadSubArray(ctx.buffer, cmdArg2, 0x1000); + uint32_t numCameras = cmdArg1; + w.Write(numCameras); + + uint32_t firstPointsAddr = 0; + struct CamEntry { int16_t type; int16_t numPoints; uint32_t segOff; }; + std::vector<CamEntry> cameras; + + for (uint32_t i = 0; i < numCameras; i++) { + CamEntry c; + c.type = camReader.ReadInt16(); + c.numPoints = camReader.ReadInt16(); + c.segOff = camReader.ReadUInt32(); + if (i == 0) firstPointsAddr = c.segOff; + cameras.push_back(c); + } + + for (auto& c : cameras) { + w.Write(c.type); + w.Write(c.numPoints); + + auto pointReader = ReadSubArray(ctx.buffer, c.segOff, c.numPoints * 6); + for (int16_t j = 0; j < c.numPoints; j++) { + w.Write(pointReader.ReadInt16()); + w.Write(pointReader.ReadInt16()); + w.Write(pointReader.ReadInt16()); + } + } +} + +void SceneCommandWriter::WriteSetPathways(LUS::BinaryWriter& w, uint32_t cmdArg2, SceneWriteContext& ctx) { + uint8_t pathSeg = (cmdArg2 >> 24) & 0xFF; + uint32_t maxPaths = GetNeighborSize(ctx.knownAddrs, cmdArg2, 8); + if (maxPaths == 0) maxPaths = 256; + auto pathReader = ReadSubArray(ctx.buffer, cmdArg2, maxPaths * 8); + std::vector<std::pair<uint8_t, uint32_t>> pathways; + for (uint32_t i = 0; i < maxPaths; i++) { + uint8_t np = pathReader.ReadUByte(); + pathReader.ReadUByte(); pathReader.ReadUByte(); pathReader.ReadUByte(); + uint32_t ptsAddr = pathReader.ReadUInt32(); + if (ptsAddr == 0 || !IS_SEGMENTED(ptsAddr) || ((ptsAddr >> 24) & 0xFF) != pathSeg) { + break; + } + pathways.push_back({np, ptsAddr}); + } + if (pathways.empty()) pathways.push_back({0, 0}); + + auto existingPath = ResolvePointer(cmdArg2); + bool hasPreExistingResource = !existingPath.empty(); + + if (!hasPreExistingResource && ctx.isAltHeader && pathways.size() > 1) { + pathways.erase(pathways.begin() + 1, pathways.end()); + } + bool doubled = hasPreExistingResource && (pathways.size() > 1); + uint32_t writeCount = doubled ? pathways.size() * 2 : pathways.size(); + + w.Write(static_cast<uint32_t>(writeCount)); + + uint32_t repeats = doubled ? 2 : 1; + for (uint32_t r = 0; r < repeats; r++) { + for (uint32_t i = 0; i < pathways.size(); i++) { + auto [np, ptsAddr] = pathways[i]; + uint32_t pointOffset = SEGMENT_OFFSET(ptsAddr); + std::string pathSymbol = MakeAssetName(ctx.baseName, "PathwayList", pointOffset); + std::string pathPath = ctx.currentDir + "/" + pathSymbol; + w.Write(pathPath); + } + } + + for (uint32_t i = 0; i < pathways.size(); i++) { + auto [np, ptsAddr] = pathways[i]; + uint32_t pointOffset = SEGMENT_OFFSET(ptsAddr); + std::string pathSymbol = MakeAssetName(ctx.baseName, "PathwayList", pointOffset); + auto pathData = SerializePathways(ctx.buffer, pathways, writeCount, repeats); + Companion::Instance->RegisterCompanionFile(pathSymbol, pathData); + } +} + +void SceneCommandWriter::WriteSetCutscenes(LUS::BinaryWriter& w, uint32_t cmdArg2, SceneWriteContext& ctx) { + std::string csSymbol; + uint32_t csAddr = Companion::Instance->PatchVirtualAddr(cmdArg2); + auto resolved = ResolvePointer(cmdArg2); + + if (resolved.empty()) { + uint32_t csOffset = SEGMENT_OFFSET(csAddr); + csSymbol = MakeAssetName(ctx.entryName, "CutsceneData", csOffset); + resolved = ctx.currentDir + "/" + csSymbol; + } else { + csSymbol = resolved.substr(resolved.rfind('/') + 1); + } + w.Write(resolved); + + auto csData = CutsceneSerializer::Serialize(ctx.buffer, cmdArg2); + if (csData.empty()) { + SPDLOG_WARN("Scene: Skipping cutscene {} due to parse failure", csSymbol); + } + Companion::Instance->RegisterCompanionFile(csSymbol, csData); +} + +void SceneCommandWriter::WriteSetAlternateHeaders(LUS::BinaryWriter& w, uint32_t cmdArg2, SceneWriteContext& ctx) { + // An uninitialized alt-header pointer with a bogus segment resolves out of + // bounds. OTRExporter emits no alternate headers in that case; match it + // rather than reading from an invalid address. + auto altSegBase = Companion::Instance->GetFileOffsetFromSegmentedAddr(SEGMENT_NUMBER(cmdArg2)); + uint32_t altFileOffset = altSegBase.has_value() + ? altSegBase.value() + SEGMENT_OFFSET(cmdArg2) + : cmdArg2; + if (altFileOffset >= ctx.buffer.size()) { + SPDLOG_WARN("Scene: skipping alternate headers for {}, unresolvable pointer 0x{:X}", + ctx.entryName, cmdArg2); + w.Write(static_cast<uint32_t>(0)); + return; + } + + uint32_t maxHeaders = GetNeighborSize(ctx.knownAddrs, cmdArg2, 4); + if (maxHeaders == 0) maxHeaders = 16; + auto hdrReader = ReadSubArray(ctx.buffer, cmdArg2, maxHeaders * 4); + std::vector<uint32_t> headers; + for (uint32_t i = 0; i < maxHeaders; i++) { + uint32_t seg = hdrReader.ReadUInt32(); + headers.push_back(seg); + } + + w.Write(static_cast<uint32_t>(headers.size())); + for (auto seg : headers) { + if (seg == 0) { + w.Write(std::string("")); + } else { + uint32_t offset = SEGMENT_OFFSET(seg); + std::string setSymbol = MakeAssetName(ctx.entryName, "Set", offset); + std::string setPath = ctx.currentDir + "/" + setSymbol; + w.Write(setPath); + + ctx.pendingAltHeaders.push_back({seg, setSymbol}); + } + } +} + +void SceneCommandWriter::CreateBackgroundCompanion(std::vector<uint8_t>& buffer, uint32_t source, + const std::string& bgSymbol) { + if (source == 0) return; + uint32_t bgDataSize = 320 * 240 * 2; + auto bgDataReader = ReadSubArray(buffer, source, bgDataSize); + LUS::BinaryWriter bgWriter; + BaseExporter::WriteHeader(bgWriter, Torch::ResourceType::OoTBackground, 0); + bgWriter.Write(static_cast<uint32_t>(bgDataSize)); + for (uint32_t b = 0; b < bgDataSize; b++) { + bgWriter.Write(bgDataReader.ReadUByte()); + } + std::stringstream bgSS; + bgWriter.Finish(bgSS); + std::string bgStr = bgSS.str(); + Companion::Instance->RegisterCompanionFile( + bgSymbol, std::vector<char>(bgStr.begin(), bgStr.end())); +} + +std::string SceneCommandWriter::ResolveGfxPointer(uint32_t ptr, const std::string& symbol) { + if (ptr == 0) return ""; + ptr = Companion::Instance->PatchVirtualAddr(ptr); + auto result = Companion::Instance->GetStringByAddr(ptr); + if (result.has_value()) return result.value(); + + SPDLOG_WARN("Scene: Could not resolve GFX pointer 0x{:08X} ({}) — YAML enrichment incomplete", ptr, symbol); + return ""; +} + +std::string SceneCommandWriter::ResolveGfxWithAlias(uint32_t ptr, const std::string& symbol, + const std::string& currentDir) { + std::string path = ResolveGfxPointer(ptr, symbol); + if (path.empty()) return ""; + std::string expectedPath = currentDir + "/" + symbol; + if (path != expectedPath) { + AliasManager::Instance->Register(path, expectedPath); + } + return path; +} + +uint32_t SceneCommandWriter::GetNeighborSize(const std::set<uint32_t>& knownAddrs, uint32_t segAddr, + uint32_t entrySize) { + uint32_t addr = SEGMENT_OFFSET(segAddr); + auto it = knownAddrs.upper_bound(addr); + if (it != knownAddrs.end()) { + return (*it - addr) / entrySize; + } + return 0; +} + +} // namespace OoT |
