// Copyright 2023 xensik. All rights reserved. // // Use of this source code is governed by a GNU GPLv3 license // that can be found in the LICENSE file. #include "stdinc.hpp" #include "utils/zlib.hpp" #include "utils/file.hpp" #include "utils/string.hpp" #include "iw5/iw5_pc.hpp" #include "iw5/iw5_ps.hpp" #include "iw5/iw5_xb.hpp" #include "iw6/iw6_pc.hpp" #include "iw6/iw6_ps.hpp" #include "iw6/iw6_xb.hpp" #include "iw7/iw7.hpp" #include "iw8/iw8.hpp" #include "iw9/iw9.hpp" #include "s1/s1_pc.hpp" #include "s1/s1_ps.hpp" #include "s1/s1_xb.hpp" #include "s2/s2.hpp" #include "s4/s4.hpp" #include "h1/h1.hpp" #include "h2/h2.hpp" #include "t6/t6.hpp" namespace fs = std::filesystem; namespace xsk { enum class encd { _, source, assembly, binary }; enum class mode { _, assemble, disassemble, compile, decompile, parse, rename }; enum class game { _, iw5ps, iw5xb, iw6ps, iw6xb, s1ps, s1xb, iw5, iw6, iw7, iw8, iw9, s1, s2, s4, h1, h2, t6 }; std::unordered_map const exts = { { ".gsc", encd::source }, { ".cgsc", encd::binary }, { ".gscbin", encd::binary }, { ".gscasm", encd::assembly }, }; std::unordered_map const modes = { { "asm", mode::assemble }, { "disasm", mode::disassemble }, { "comp", mode::compile }, { "decomp", mode::decompile }, { "parse", mode::parse }, { "rename", mode::rename }, }; std::unordered_map const games = { { "iw5ps", game::iw5ps }, { "iw5xb", game::iw5xb }, { "iw6ps", game::iw6ps }, { "iw6xb", game::iw6xb }, { "s1ps", game::s1ps }, { "s1xb", game::s1xb }, { "iw5", game::iw5 }, { "iw6", game::iw6 }, { "iw7", game::iw7 }, { "iw8", game::iw8 }, { "iw9", game::iw9 }, { "s1", game::s1 }, { "s2", game::s2 }, { "s4", game::s4 }, { "h1", game::h1 }, { "h2", game::h2 }, { "t6", game::t6 }, }; std::map const games_rev = { { game::iw5ps, "iw5ps", }, { game::iw5xb, "iw5xb" }, { game::iw6ps, "iw6ps" }, { game::iw6xb, "iw6xb" }, { game::s1ps, "s1ps" }, { game::s1xb, "s1xb" }, { game::iw5, "iw5" }, { game::iw6, "iw6" }, { game::iw7, "iw7" }, { game::iw8, "iw8" }, { game::iw9, "iw9" }, { game::s1, "s1" }, { game::s2, "s2" }, { game::s4, "s4" }, { game::h1, "h1" }, { game::h2, "h2" }, { game::t6, "t6" }, }; std::map const encds = { { mode::assemble , encd::assembly }, { mode::disassemble, encd::binary }, { mode::compile, encd::source }, { mode::decompile, encd::binary }, }; auto overwrite_prompt(std::string const& file) -> bool { auto overwrite = true; if (utils::file::exists(file)) { do { std::cout << "File \"" << file << "\" already exists, overwrite? [Y/n]: "; auto result = std::getchar(); if (result == '\n' || result == 'Y' || result == 'y') { break; } else if (result == 'N' || result == 'n') { overwrite = false; break; } } while (true); } return overwrite; } namespace gsc { std::map> contexts; std::map> funcs; bool zonetool = false; /*auto choose_resolver_file_name(uint32_t id, game& game) -> std::string { switch (game) { // case game::iw5c: // return iw5c::resolver::token_name(static_cast(id)); // case game::iw6c: // return iw6c::resolver::token_name(static_cast(id)); // case game::s1c: // return s1c::resolver::token_name(static_cast(id)); // case game::iw5: // return iw5::resolver::token_name(static_cast(id)); // case game::iw6: // return iw6::resolver::token_name(static_cast(id)); // case game::iw7: // return iw7::resolver::token_name(id); // case game::iw8: // return iw8::resolver::token_name(id); // case game::s1: // return s1::resolver::token_name(static_cast(id)); // case game::s2: // return s2::resolver::token_name(static_cast(id)); // case game::s4: // return s4::resolver::token_name(id); // case game::h1: // return h1::resolver::token_name(static_cast(id)); // case game::h2: // return h2::resolver::token_name(static_cast(id)); default: return ""; } }*/ auto assemble_file(game game, fs::path file, fs::path rel) -> void { try { if (file.extension() != ".gscasm") throw std::runtime_error("expected .gscasm file"); rel = fs::path{ games_rev.at(game) } / rel / file.filename().replace_extension((zonetool ? ".cgsc" : ".gscbin")); auto data = utils::file::read(file); auto outasm = contexts[game]->source().parse_assembly(data); auto outbin = contexts[game]->assembler().assemble(*outasm); if (true/*overwrite_prompt(file + (zonetool ? ".cgsc" : ".gscbin"))*/) { if (zonetool) { auto path = fs::path{ "compiled" } / rel; utils::file::save(path, outbin.first.data, outbin.first.size); utils::file::save(path.replace_extension(".cgsc.stack"), outbin.second.data, outbin.second.size); fmt::print("compiled {}\n", rel.generic_string()); } else { asset script; script.name = "GSC"s; script.bytecode.resize(outbin.first.size); std::memcpy(script.bytecode.data(), outbin.first.data, script.bytecode.size()); script.buffer.resize(outbin.second.size); std::memcpy(script.buffer.data(), outbin.second.data, script.buffer.size()); script.buffer = utils::zlib::compress(script.buffer); script.len = static_cast(outbin.second.size); script.compressedLen = static_cast(script.buffer.size()); script.bytecodeLen = static_cast(script.bytecode.size()); auto result = script.serialize(); utils::file::save(fs::path{ "compiled" } / rel, result); fmt::print("compiled {}\n", rel.generic_string()); } } } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } auto disassemble_file(game game, fs::path file, fs::path rel) -> void { try { auto script = std::vector{}; auto stack = std::vector{}; if (zonetool) { if (file.extension() != ".cgsc") throw std::runtime_error("expected .cgsc file"); auto fbuf = file; rel = fs::path{ games_rev.at(game) } / rel / file.filename().replace_extension(".gsc"); script = utils::file::read(file); stack = utils::file::read(fbuf.replace_extension(".cgsc.stack")); } else { if (file.extension() != ".gscbin") throw std::runtime_error("expected .gscbin file"); rel = fs::path{ games_rev.at(game) } / rel / file.filename().replace_extension(".gscasm"); auto data = utils::file::read(file); asset asset; asset.deserialize(data); script = std::move(asset.bytecode); stack = utils::zlib::decompress(asset.buffer, asset.len); } auto outasm = contexts[game]->disassembler().disassemble(script, stack); auto outsrc = contexts[game]->source().dump(*outasm); utils::file::save(fs::path{ "disassembled" } / rel, outsrc); fmt::print("disassembled {}\n", rel.generic_string()); } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } auto compile_file(game game, fs::path file, fs::path rel) -> void { try { if (file.extension() != ".gsc") throw std::runtime_error("expected .gsc file"); rel = fs::path{ games_rev.at(game) } / rel / file.filename().replace_extension((zonetool ? ".cgsc" : ".gscbin")); auto data = utils::file::read(file); auto outasm = contexts[game]->compiler().compile(file.string(), data); auto outbin = contexts[game]->assembler().assemble(*outasm); if (true/*overwrite_prompt(file + (zonetool ? ".cgsc" : ".gscbin"))*/) { if (zonetool) { auto path = fs::path{ "compiled" } / rel; utils::file::save(path, outbin.first.data, outbin.first.size); utils::file::save(path.replace_extension(".cgsc.stack"), outbin.second.data, outbin.second.size); fmt::print("compiled {}\n", rel.generic_string()); } else { asset script; script.name = "GSC"s; script.bytecode.resize(outbin.first.size); std::memcpy(script.bytecode.data(), outbin.first.data, script.bytecode.size()); script.buffer.resize(outbin.second.size); std::memcpy(script.buffer.data(), outbin.second.data, script.buffer.size()); script.buffer = utils::zlib::compress(script.buffer); script.len = static_cast(outbin.second.size); script.compressedLen = static_cast(script.buffer.size()); script.bytecodeLen = static_cast(script.bytecode.size()); auto result = script.serialize(); utils::file::save(fs::path{ "compiled" } / rel, result); fmt::print("compiled {}\n", rel.generic_string()); } } } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } auto decompile_file(game game, fs::path file, fs::path rel) -> void { try { auto script = std::vector{}; auto stack = std::vector{}; if (zonetool) { if (file.extension() != ".cgsc") throw std::runtime_error("expected .cgsc file"); auto fbuf = file; rel = fs::path{ games_rev.at(game) } / rel / file.filename().replace_extension(".gsc"); script = utils::file::read(file); stack = utils::file::read(fbuf.replace_extension(".cgsc.stack")); } else { if (file.extension() != ".gscbin") throw std::runtime_error("expected .gscbin file"); rel = fs::path{ games_rev.at(game) } / rel / file.filename().replace_extension(".gsc"); auto data = utils::file::read(file); asset asset; asset.deserialize(data); script = std::move(asset.bytecode); stack = utils::zlib::decompress(asset.buffer, asset.len); } auto outasm = contexts[game]->disassembler().disassemble(script, stack); auto outast = contexts[game]->decompiler().decompile(*outasm); auto outsrc = contexts[game]->source().dump(*outast); utils::file::save(fs::path{ "decompiled" } / rel, outsrc); fmt::print("decompiled {}\n", rel.generic_string()); } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } auto parse_file(game game, fs::path file, fs::path rel) -> void { try { if (file.extension() != ".gsc") throw std::runtime_error("expected .gsc file"); rel = fs::path{ games_rev.at(game) } / rel / file.filename(); auto data = utils::file::read(file); auto prog = contexts[game]->source().parse_program(file.string(), data); utils::file::save(fs::path{ "parsed" } / rel, contexts[game]->source().dump(*prog)); fmt::print("parsed {}\n", rel.generic_string()); } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } auto rename_file(game game, fs::path file, fs::path rel) -> void { try { if (file.extension() != ".cgsc" && file.extension() != ".gsc" && file.extension() != ".csc" && file.extension() != ".gscbin" && file.extension() != ".cscbin") return; auto ext = file.extension(); auto zt = file.extension() == ".cgsc"; if (game == game::iw9) { return; } else { auto name = file.filename().replace_extension().string(); if (utils::string::is_number(name)) { name = contexts[game]->token_name(std::stoul(name)); } else if (utils::string::is_hex_number(name)) { name = contexts[game]->token_name(std::stoul(name, nullptr, 16)); } if (!name.starts_with("_id_")) { rel = fs::path{ games_rev.at(game) } / rel / name; rel.replace_extension(ext); } else { rel = fs::path{ games_rev.at(game) } / rel / file.filename(); } } auto data = utils::file::read(file); utils::file::save(fs::path{ "renamed" } / rel, data); fmt::print("renamed {} -> {}\n", file.filename().generic_string(), rel.generic_string()); if (zt) { auto stack = utils::file::read(file.replace_extension(".cgsc.stack")); utils::file::save(fs::path{ "renamed" } / rel.replace_extension(".cgsc.stack"), stack); fmt::print("renamed {} -> {}\n", file.filename().generic_string(), rel.generic_string()); } } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } std::unordered_map> files; auto read_file_cb(std::string const& name) -> std::pair { auto data = utils::file::read(fs::path{ name }); if(name.ends_with(".gscbin") || (!name.ends_with(".gsh") && !name.ends_with(".gsc"))) { asset s; s.deserialize(data); auto stk = utils::zlib::decompress(s.buffer, s.len); auto res1 = files.insert({ name + "1", std::move(s.bytecode) }); auto res2 = files.insert({ name + "2", std::move(stk) }); if(res1.second && res2.second) { return { {res1.first->second.data(), res1.first->second.size() }, {res2.first->second.data(), res2.first->second.size() } }; } } else { auto res = files.insert({ name, std::move(data) }); if(res.second) { return { {res.first->second.data(), res.first->second.size() }, {} }; } } throw std::runtime_error("file read error"); } auto init_iw5() -> void { if (!contexts.contains(game::iw5)) { contexts[game::iw5] = std::make_unique(); contexts[game::iw5]->init(build::prod, read_file_cb); } } auto init_iw5_ps() -> void { if (!contexts.contains(game::iw5ps)) { contexts[game::iw5ps] = std::make_unique(); contexts[game::iw5ps]->init(build::prod, read_file_cb); } } auto init_iw5_xb() -> void { if (!contexts.contains(game::iw5xb)) { contexts[game::iw5xb] = std::make_unique(); contexts[game::iw5xb]->init(build::prod, read_file_cb); } } auto init_iw6() -> void { if (!contexts.contains(game::iw6)) { contexts[game::iw6] = std::make_unique(); contexts[game::iw6]->init(build::prod, read_file_cb); } } auto init_iw6_ps() -> void { if (!contexts.contains(game::iw6ps)) { contexts[game::iw6ps] = std::make_unique(); contexts[game::iw6ps]->init(build::prod, read_file_cb); } } auto init_iw6_xb() -> void { if (!contexts.contains(game::iw6xb)) { contexts[game::iw6xb] = std::make_unique(); contexts[game::iw6xb]->init(build::prod, read_file_cb); } } auto init_iw7() -> void { if (!contexts.contains(game::iw7)) { contexts[game::iw7] = std::make_unique(); contexts[game::iw7]->init(build::prod, read_file_cb); } } auto init_iw8() -> void { if (!contexts.contains(game::iw8)) { contexts[game::iw8] = std::make_unique(); contexts[game::iw8]->init(build::prod, read_file_cb); } } auto init_iw9() -> void { if (!contexts.contains(game::iw9)) { contexts[game::iw9] = std::make_unique(); contexts[game::iw9]->init(build::prod, read_file_cb); } } auto init_s1() -> void { if (!contexts.contains(game::s1)) { contexts[game::s1] = std::make_unique(); contexts[game::s1]->init(build::prod, read_file_cb); } } auto init_s1_ps() -> void { if (!contexts.contains(game::s1ps)) { contexts[game::s1ps] = std::make_unique(); contexts[game::s1ps]->init(build::prod, read_file_cb); } } auto init_s1_xb() -> void { if (!contexts.contains(game::s1xb)) { contexts[game::s1xb] = std::make_unique(); contexts[game::s1xb]->init(build::prod, read_file_cb); } } auto init_s2() -> void { if (!contexts.contains(game::s2)) { contexts[game::s2] = std::make_unique(); contexts[game::s2]->init(build::prod, read_file_cb); } } auto init_s4() -> void { if (!contexts.contains(game::s4)) { contexts[game::s4] = std::make_unique(); contexts[game::s4]->init(build::prod, read_file_cb); } } auto init_h1() -> void { if (!contexts.contains(game::h1)) { contexts[game::h1] = std::make_unique(); contexts[game::h1]->init(build::prod, read_file_cb); } } auto init_h2() -> void { if (!contexts.contains(game::h2)) { contexts[game::h2] = std::make_unique(); contexts[game::h2]->init(build::prod, read_file_cb); } } auto init(game game) -> void { funcs[mode::assemble] = assemble_file; funcs[mode::disassemble] = disassemble_file; funcs[mode::compile] = compile_file; funcs[mode::decompile] = decompile_file; funcs[mode::parse] = parse_file; funcs[mode::rename] = rename_file; switch (game) { case game::iw5: init_iw5(); break; case game::iw5ps: init_iw5_ps(); break; case game::iw5xb: init_iw5_xb(); break; case game::iw6: init_iw6(); break; case game::iw6ps: init_iw6_ps(); break; case game::iw6xb: init_iw6_xb(); break; case game::iw7: init_iw7(); break; case game::iw8: init_iw8(); break; case game::iw9: init_iw9(); break; case game::s1: init_s1(); break; case game::s1ps: init_s1_ps(); break; case game::s1xb: init_s1_xb(); break; case game::s2: init_s2(); break; case game::s4: init_s4(); break; case game::h1: init_h1(); break; case game::h2: init_h2(); break; default: break; } } } // namespace xsk::gsc namespace arc { std::map contexts; std::map> funcs; void assemble_file(game game, fs::path const& file, fs::path rel) { try { if (file.extension() != ".gscasm" && file.extension() != ".cscasm") throw std::runtime_error("expected .gscasm or .cscasm file"); rel = fs::path{ games_rev.at(game) } / rel / file.filename().replace_extension((file.extension() == ".gscasm" ? ".gsc" : ".csc")); auto& assembler = contexts[game]->assembler(); auto data = utils::file::read(file); assembler.assemble(file.string(), data); utils::file::save(fs::path{ "assembled" } / rel, assembler.output()); fmt::print("assembled {}\n", rel.generic_string()); } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } void disassemble_file(game game, fs::path const& file, fs::path rel) { try { if (file.extension() != ".gsc" && file.extension() != ".csc") throw std::runtime_error("expected .gsc or .csc file"); rel = fs::path{ games_rev.at(game) } / rel / file.filename().replace_extension((file.extension() == ".gsc" ? ".gscasm" : ".cscasm")); auto& disassembler = contexts[game]->disassembler(); auto data = utils::file::read(file.string()); disassembler.disassemble(file.string(), data); utils::file::save(fs::path{ "disassembled" } / rel, disassembler.output_raw()); fmt::print("disassembled {}\n", rel.generic_string()); } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } void compile_file(game game, fs::path const& file, fs::path rel) { try { if (file.extension() != ".gsc" && file.extension() != ".csc") throw std::runtime_error("expected .gsc or .csc file"); rel = fs::path{ games_rev.at(game) } / rel / file.filename(); auto& assembler = contexts[game]->assembler(); auto& compiler = contexts[game]->compiler(); auto data = utils::file::read(file); compiler.compile(file.string(), data); auto assembly = compiler.output(); assembler.assemble(file.string(), assembly); utils::file::save(fs::path{ "compiled" } / rel, assembler.output()); fmt::print("compiled {}\n", rel.generic_string()); } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } void decompile_file(game game, fs::path const& file, fs::path rel) { try { if (file.extension() != ".gsc" && file.extension() != ".csc") throw std::runtime_error("expected .gsc or .csc file"); rel = fs::path{ games_rev.at(game) } / rel / file.filename(); auto& disassembler = contexts[game]->disassembler(); auto& decompiler = contexts[game]->decompiler(); auto data = utils::file::read(file); disassembler.disassemble(file.string(), data); auto output = disassembler.output(); decompiler.decompile(file.string(), output); utils::file::save(fs::path{ "decompiled" } / rel, decompiler.output()); fmt::print("decompiled {}\n", rel.generic_string()); } catch (std::exception const& e) { std::cerr << fmt::format("{} at {}\n", e.what(), file.generic_string()); } } void parse_file(game, fs::path const&, fs::path) { std::cerr << fmt::format("not implemented for t6\n"); } void rename_file(game, fs::path const&, fs::path) { std::cerr << fmt::format("not implemented for t6\n"); } auto init_t6() -> void { if (!contexts.contains(game::t6)) { contexts[game::t6] = std::make_unique(); contexts[game::t6]->init(build::prod, utils::file::read); } } auto init(game game) -> void { funcs[mode::assemble] = assemble_file; funcs[mode::disassemble] = disassemble_file; funcs[mode::compile] = compile_file; funcs[mode::decompile] = decompile_file; funcs[mode::parse] = parse_file; funcs[mode::rename] = rename_file; switch (game) { case game::t6: init_t6(); break; default: break; } } } // namespace xsk::arc auto execute(mode mode, game game, fs::path const& path) -> void { gsc::init(game); arc::init(game); if (fs::is_directory(path)) { for (auto const& entry : fs::recursive_directory_iterator(path)) { if (entry.is_regular_file() && entry.path().extension() != ".stack") { auto rel = fs::relative(entry, path).remove_filename(); if (game < game::t6) gsc::funcs[mode](game, entry.path().generic_string(), rel); else arc::funcs[mode](game, fs::path{ entry.path().generic_string(), fs::path::format::generic_format }, rel); } } } else if (fs::is_regular_file(path)) { if (game < game::t6) gsc::funcs[mode](game, path, fs::path{}); else arc::funcs[mode](game, fs::path(path, fs::path::format::generic_format), fs::path{}); } else { std::cerr << fmt::format("bad path '{}'\n", path.generic_string()); } } auto parse_flags(u32 argc, char** argv, game& game, mode& mode, fs::path& path) -> bool { if (argc != 4) return false; auto arg = utils::string::to_lower(argv[1]); if (arg.at(0) == 'z') { arg.erase(arg.begin()); gsc::zonetool = true; } auto const it = modes.find(arg); if (it != modes.end()) { mode = it->second; } else { std::cout << "[ERROR] unknown mode '" << argv[1] << "'.\n\n"; return false; } arg = utils::string::to_lower(argv[2]); auto const itr = games.find(arg); if (itr != games.end()) { game = itr->second; } else { std::cout << "[ERROR] unknown game '" << argv[2] << "'.\n\n"; return false; } path = fs::path{ utils::string::fordslash(argv[3]), fs::path::format::generic_format }; return true; } auto print_usage() -> void { std::cout << "usage: gsc-tool \n"; std::cout << "\t* modes: asm, disasm, comp, decomp, parse\n"; std::cout << "\t* games: iw5ps, iw5xb, iw6ps, iw6xb, s1ps, s1xb, iw5, iw6, iw7, iw8, iw9, s1, s2, s4, h1, h2, t6\n"; std::cout << "\t* paths: file or directory (recursive)\n"; std::cin.get(); } auto main(u32 argc, char** argv) -> void { auto path = fs::path{}; auto mode = mode::_; auto game = game::_; if (!parse_flags(argc, argv, game, mode, path)) { return print_usage(); } execute(mode, game, path); } } // namespace xsk int main(int argc, char** argv) { xsk::main(argc, argv); return 0; }