#include "ScriptFactory.h" #include "spdlog/spdlog.h" #include "Companion.h" #include "utils/Decompressor.h" #include "utils/TorchUtils.h" #include "regex.h" #include "storm/SWrapper.h" #define CLAMP_MAX(val, max) (((val) < (max)) ? (val) : (max)) #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #define VALUE_TO_ENUM(val, enumname, fallback) (Companion::Instance->GetEnumFromValue(enumname, val).value_or("/*" + std::string(fallback) + " */ " + std::to_string(val))); SF64::ScriptData::ScriptData(std::vector ptrs, std::vector cmds, std::map sizeMap, uint32_t ptrsStart, uint32_t cmdsStart): mPtrs(ptrs), mCmds(cmds), mSizeMap(sizeMap), mPtrsStart(ptrsStart), mCmdsStart(cmdsStart) { } ExportResult SF64::ScriptHeaderExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement) { const auto symbol = GetSafeNode(node, "symbol", entryName); if(Companion::Instance->IsOTRMode()){ write << "static const char " << symbol << "[] = \"__OTR__" << (*replacement) << "\";\n\n"; return std::nullopt; } write << "extern u16* " << symbol << "[];\n"; return std::nullopt; } std::string MakeScriptCmd(uint16_t s1, uint16_t s2) { auto opcode = (s1 & 0xFE00) >> 9; auto arg1 = s1 & 0x1FF; std::ostringstream cmd; std::ostringstream comment; switch (opcode) { case 0: case 1: { auto f3 = arg1 & 0x7F; // auto zm = (arg1 >> 7) & 3; // auto zmode = Companion::Instance->GetEnumFromValue("EventModeZ", zm).value_or("/* EVOP_UNK */ " + std::to_string(zm)); auto zmode = VALUE_TO_ENUM((arg1 >> 7) & 3, "EventModeZ", "EVOP_UNK"); cmd << "EVENT_SET_" << (opcode ? "ACCEL" : "SPEED") << "(" << std::dec << f3 << ", " << zmode << ", " << s2; comment << " // wait " << s2 << " frames"; } break; case 2: cmd << "EVENT_SET_BASE_ZVEL(" << std::dec << s2; break; case 9: case 10: case 11: case 12: case 16: case 17: case 18: case 19: case 20: case 21: { auto rotcmd = VALUE_TO_ENUM(opcode, "EventOpcode", "EVOP_UNK"); cmd << rotcmd.replace(0, 4, "EVENT") << "(" << std::dec << s2 << ", " << std::fixed << std::setprecision(1) << arg1 / 10.0f; if(opcode < 16 && arg1 != 0) { comment << " // wait " << 10 * s2 / arg1 << " frames"; } } break; case 24: cmd << "EVENT_SET_ROTATE("; break; case 25: cmd << "EVENT_STOP_ROTATE("; break; case 44: cmd << "EVENT_CHASE_TARGET(" << std::dec << arg1 << ", " << s2; break; case 45: { auto teamId = VALUE_TO_ENUM(arg1, "TeamId", "TEAMID_UNK"); cmd << "EVENT_SET_TARGET(" << teamId << ", " << std::dec << s2; } break; case 56: cmd << "EVENT_SET_CALL(" << std::dec << s2 << ", " << arg1; break; case 57: { auto teamId = VALUE_TO_ENUM(s2, "TeamId", "TEAMID_UNK"); cmd << "EVENT_RESTORE_TEAM(" << teamId; } break; case 58: case 59: { // auto sfxIndex = Companion::Instance->GetEnumFromValue("EventSfx", s2).value_or("/* EVSFX_UNK */ " + std::to_string(s2)); auto sfxIndex = VALUE_TO_ENUM(s2, "EventSfx", "EVSFX_UNK"); cmd << "EVENT_" << ((opcode == 58) ? "PLAY" : "STOP") << "_SFX(" << sfxIndex; } break; // case 59: { // auto sfxIndex = Companion::Instance->GetEnumFromValue("EventSfx", s2).value_or("/* EVSFX_UNK */ " + std::to_string(s2)); // cmd << "EVENT_STOP_SFX(" << sfxIndex; // } break; case 96: cmd << ((s2 == 0) ? "EVENT_CLEAR_TRIGGER(" : "EVENT_SET_TRIGGER("); if(s2 != 0) { auto condition = VALUE_TO_ENUM(MIN(s2, 100), "EventCondition", "EVC_UNK"); cmd << condition << ((s2 < 100) ? "" : " + " + std::to_string(s2 - 100)) << ", "; } cmd << ((arg1 < 200) ? "" : "EVENT_AI_CHANGE + ") << std::dec << ((arg1 < 200) ? arg1 : arg1 - 200); break; case 104: { // auto actorinfo = Companion::Instance->GetEnumFromValue("EventActorInfo", s2).value_or("/* EINFO_UNK */ " + std::to_string(s2)); auto actorInfo = VALUE_TO_ENUM(s2, "EventActorInfo", "EINFO_UNK"); cmd << "EVENT_INIT_ACTOR(" << actorInfo << ", " << std::dec << arg1; } break; case 105: { auto teamId = VALUE_TO_ENUM(s2, "TeamId", "TEAMID_UNK"); cmd << "EVENT_SET_TEAM_ID(" << teamId; } break; case 112:{ // auto actiontype = Companion::Instance->GetEnumFromValue("EventAction", s2).value_or("/* EVACT_UNK */ " + std::to_string(s2)); auto actiontype = VALUE_TO_ENUM(s2, "EventAction", "EVACT_UNK"); cmd << "EVENT_SET_ACTION(" << actiontype; if((s2 == 14 || s2 == 15)) { comment << " // wait 1 frame"; } } break; case 113: cmd << "EVENT_ADD_TO_GROUP(" << std::dec << s2 << ", " << arg1; break; case 116: { // auto itemtype = Companion::Instance->GetEnumFromValue("ItemDrop", s2).value_or("/* DROP_UNK */ " + std::to_string(s2)); auto itemType = VALUE_TO_ENUM(s2, "ItemDrop", "DROP_UNK"); cmd << "EVENT_DROP_ITEM(" << itemType; } break; case 118: cmd << "EVENT_SET_REVERB(" << std::dec << s2; break; case 119: { // auto groundtype = Companion::Instance->GetEnumFromValue("GroundType", s2).value_or("/* GROUNDTYPE_UNK */ " + std::to_string(s2)); auto groundtype = VALUE_TO_ENUM(s2, "GroundType", "GROUNDTYPE_UNK"); cmd << "EVENT_SET_GROUND(" << groundtype; } break; case 120: { // auto rcidName = Companion::Instance->GetEnumFromValue("RadioCharacterId", arg1).value_or("/* RCID_UNK */ " + std::to_string(arg1)); auto rcidName = VALUE_TO_ENUM(arg1, "RadioCharacterId", "RCID_UNK"); cmd << "EVENT_PLAY_MSG(" << rcidName << ", " << std::dec << s2; } break; case 121: { auto teamId = VALUE_TO_ENUM(s2, "TeamId", "TEAMID_UNK"); cmd << "EVENT_DAMAGE_TEAM(" << teamId << ", " << std::dec << arg1; } break; case 122: cmd << "EVENT_STOP_BGM("; break; case 124: { // auto color = Companion::Instance->GetEnumFromValue("TexLineColor", s2).value_or("/* TXLC_UNK */ " + std::to_string(s2)); auto color = VALUE_TO_ENUM(s2, "TexLineColor", "TXLC_UNK"); cmd << "EVENT_MAKE_TEXLINE(" << color; } break; case 125: cmd << "EVENT_STOP_TEXLINE("; break; case 126: cmd << ((s2 == 0) ? "EVENT_GOTO(" : "EVENT_LOOP("); if(s2 != 0) { cmd << std::dec << s2 << ", "; } cmd << ((arg1 < 200) ? "" : "EVENT_AI_CHANGE + ") << std::dec << ((arg1 < 200) ? arg1 : arg1 - 200); break; case 127: cmd << "EVENT_STOP_SCRIPT("; comment << "// wait"; break; default: { // auto opcodeName = Companion::Instance->GetEnumFromValue("EventOpcode", opcode).value_or("/* EVOP_UNK */ " + std::to_string(opcode)); auto opcodeName = VALUE_TO_ENUM(opcode, "EventOpcode", "EVOP_UNK"); cmd << "EVENT_CMD(" << opcodeName << ", " << std::dec << arg1 << ", " << s2; if(opcode >= 40 && opcode <= 48) { comment << " // wait"; } } break; } cmd << "), " << comment.str(); return cmd.str(); } ExportResult SF64::ScriptCodeExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { const auto symbol = GetSafeNode(node, "symbol", entryName); auto offset = GetSafeNode(node, "offset"); auto script = std::static_pointer_cast(raw); auto sortedPtrs = script->mPtrs; std::sort(sortedPtrs.begin(), sortedPtrs.end()); auto cmdOff = ASSET_PTR(script->mCmdsStart); auto cmdIndex = 0; std::map scriptNames; for(int i = 0; i < sortedPtrs.size(); i++) { std::ostringstream scriptDefaultName; auto scriptIndex = std::find(script->mPtrs.begin(), script->mPtrs.end(), sortedPtrs[i]) - script->mPtrs.begin(); scriptDefaultName << symbol << "_script_" << std::dec << scriptIndex << "_" << std::uppercase << std::hex << cmdOff; auto scriptName = GetSafeNode(node, "script_symbol", scriptDefaultName.str()); scriptNames[sortedPtrs[i]] = scriptName; if (Companion::Instance->IsDebug()) { write << "// 0x" << std::hex << std::uppercase << cmdOff << "\n"; } write << "u16 " << scriptName << "[] = {"; auto cmdCount = script->mSizeMap[sortedPtrs[i]] / 2; for(int j = 0; j < cmdCount; j++, cmdIndex+=2) { // if((j % 3) == 0) { write << "\n" << fourSpaceTab << "/* " << std::setfill(' ') << std::setw(2) << std::dec << j << " */ "; // } write << MakeScriptCmd(script->mCmds[cmdIndex], script->mCmds[cmdIndex + 1]); } write << "\n};\n"; cmdOff += 4 * cmdCount; if (Companion::Instance->IsDebug()) { write << "// count: " << cmdCount << " commands\n"; write << "// 0x" << std::hex << std::uppercase << cmdOff << "\n"; } write << "\n"; } write << "// 0x" << std::hex << std::uppercase << ASSET_PTR(offset) << "\n"; write << "u16* " << symbol << "[] = {"; for(int i = 0; i < script->mPtrs.size(); i++) { if((i % 4) == 0) { write << "\n" << fourSpaceTab; } write << scriptNames[script->mPtrs[i]] << ", "; } write << "\n};\n"; if (Companion::Instance->IsDebug()) { write << "// count: " << std::dec << script->mPtrs.size() << " events\n"; } return OffsetEntry { script->mCmdsStart, static_cast(offset + script->mPtrs.size() * sizeof(uint32_t)) }; } ExportResult SF64::ScriptBinaryExporter::Export(std::ostream &write, std::shared_ptr raw, std::string& entryName, YAML::Node &node, std::string* replacement ) { const auto symbol = GetSafeNode(node, "symbol", entryName); auto scriptWriter = LUS::BinaryWriter(); auto script = std::static_pointer_cast(raw); auto sortedPtrs = script->mPtrs; std::sort(sortedPtrs.begin(), sortedPtrs.end()); uint32_t ptrCount = 0; uint32_t cmdIndex = 0; std::unordered_map ptrMap; // Export Commands and Populate Hashes Map for (auto ptr : sortedPtrs) { auto wrapper = Companion::Instance->GetCurrentWrapper(); std::ostringstream stream; stream.str(""); stream.clear(); auto cmdWriter = LUS::BinaryWriter(); WriteHeader(cmdWriter, LUS::ResourceType::ScriptCmd, 0); auto cmdCount = script->mSizeMap.at(ptr) / 2; cmdWriter.Write((uint32_t) cmdCount); // Writing in pairs for readability for (uint32_t i = 0; i < cmdCount; ++i) { cmdWriter.Write(script->mCmds.at(cmdIndex++)); cmdWriter.Write(script->mCmds.at(cmdIndex++)); } cmdWriter.Finish(stream); auto data = stream.str(); wrapper->CreateFile(entryName + "_cmd_" + std::to_string(ptrCount), std::vector(data.begin(), data.end())); std::ostringstream cmdName; cmdName << entryName << "_cmd_" << std::dec << ptrCount; ptrMap[ptr] = CRC64(cmdName.str().c_str()); ptrCount++; } // Export Script WriteHeader(scriptWriter, LUS::ResourceType::Script, 0); scriptWriter.Write((uint32_t) script->mPtrs.size()); for (auto &ptr : script->mPtrs) { scriptWriter.Write(ptrMap.at(ptr)); } scriptWriter.Finish(write); return std::nullopt; } std::optional> SF64::ScriptFactory::parse(std::vector& buffer, YAML::Node& node) { const auto offset = GetSafeNode(node, "offset"); auto ptrsStart = offset; YAML::Node scriptNode; std::vector scriptPtrs; std::vector scriptCmds; std::map sizeMap; auto [_, segment] = Decompressor::AutoDecode(node, buffer, 0x1000); LUS::BinaryReader reader(segment.data, segment.size); reader.SetEndianness(LUS::Endianness::Big); auto ptr = reader.ReadUInt32(); while(SEGMENT_NUMBER(ptr) == SEGMENT_NUMBER(offset)) { scriptPtrs.push_back(ptr); ptr = reader.ReadUInt32(); } auto sortedPtrs = scriptPtrs; std::sort(sortedPtrs.begin(), sortedPtrs.end()); auto cmdsStart = sortedPtrs[0]; for(int i = 0; i < sortedPtrs.size() - 1; i++) { sizeMap[sortedPtrs[i]] = (sortedPtrs[i+1] - sortedPtrs[i]) / sizeof(uint16_t); } sizeMap[sortedPtrs[sortedPtrs.size() - 1]] = (ptrsStart - sortedPtrs[sortedPtrs.size() - 1]) / sizeof(uint16_t); auto scriptLen = (ptrsStart - cmdsStart) / sizeof(uint16_t); scriptNode["offset"] = cmdsStart; auto [__, scriptSegment] = Decompressor::AutoDecode(scriptNode, buffer, scriptLen * sizeof(uint16_t)); LUS::BinaryReader scriptReader(scriptSegment.data, scriptSegment.size); scriptReader.SetEndianness(LUS::Endianness::Big); for(int i = 0; i < scriptLen; i++) { scriptCmds.push_back(scriptReader.ReadUInt16()); } return std::make_shared(scriptPtrs, scriptCmds, sizeMap, ptrsStart, cmdsStart); }