#include #include "assets/assets.hpp" #include "irawfile.hpp" #include "utils/compression.hpp" #include #include #include namespace iw4of::interfaces { bool irawfile::write_internal(const native::XAssetHeader& header) const { RETURN_IF_NULL(header.rawfile); RETURN_IF_NULL(header.rawfile->buffer); if (header.rawfile->len > 0) { std::string uncompressed_buffer; if (header.rawfile->compressedLen > 0) { const auto& str = utils::compression::zlib::decompress(std::string(header.rawfile->buffer, header.rawfile->compressedLen)); uncompressed_buffer = std::string(local_allocator.duplicate_string(str), header.rawfile->len); } else { uncompressed_buffer = std::string(header.rawfile->buffer, header.rawfile->len); } write_named_assets(header.rawfile->name, uncompressed_buffer); const auto work_path = get_work_path(header).string(); const auto data = std::string(uncompressed_buffer.data(), header.rawfile->len); return utils::io::write_file(work_path, data); } // Happens return true; } void* irawfile::read_internal(const std::string& name) const { const auto& path = get_work_path(name).string(); if (!utils::io::file_exists(path)) { return nullptr; } const auto& contents = utils::io::read_file(path); auto* asset = local_allocator.allocate(); RETURN_IF_NULL(asset); asset->name = local_allocator.duplicate_string(name); asset->len = static_cast(contents.size()); read_named_assets(asset->name, contents); const auto& compressedData = utils::compression::zlib::compress(contents); // Only save the compressed buffer if we gained space if (compressedData.size() < asset->len) { asset->buffer = local_allocator.allocate_array(compressedData.size()); std::memcpy(const_cast(asset->buffer), compressedData.data(), compressedData.size()); asset->compressedLen = static_cast(compressedData.size()); } else { asset->buffer = local_allocator.allocate_array(contents.size() + 1); std::memcpy(const_cast(asset->buffer), contents.data(), contents.size()); asset->compressedLen = 0; } return asset; } void irawfile::write_named_assets(const std::string& script_name, const std::string& script_data) const { const auto children = get_child_assets(script_name, script_data); for(const auto& child : children) { assets->write(child.type, child.header.data); } } void irawfile::read_named_assets(const std::string& script_name, const std::string& script_data) const { // And this should be enough get_child_assets(script_name, script_data); } std::vector irawfile::get_child_assets(const native::XAssetHeader& header) const { const std::string script_name = header.rawfile->name; std::string script_data; // Decompress if (header.rawfile->compressedLen > 0) { const auto& str = utils::compression::zlib::decompress(std::string(header.rawfile->buffer, header.rawfile->compressedLen)); script_data = std::string(local_allocator.duplicate_string(str), header.rawfile->len); } else { script_data = std::string(header.rawfile->buffer, header.rawfile->len); } return get_child_assets(script_name, script_data); } std::vector irawfile::get_child_assets(const std::string& script_name, const std::string& script_data) const { auto result = std::vector(); if (script_name.ends_with(".gsc")) { if (script_name.ends_with("_fx.gsc")) { if (script_name.starts_with("maps/createfx/")) { // Createfx const auto sounds = get_create_fx_sounds(script_data); for (const auto& sound : sounds) { result.push_back({native::ASSET_TYPE_SOUND, sound}); } } else { // General FX const auto fxs = get_map_fx(script_data); for (const auto& fx : fxs) { result.push_back({native::ASSET_TYPE_FX, fx}); } } } else if (script_name.ends_with("_precache.gsc")) { const auto precache = get_assets_in_precache(script_data); for (const auto& asset : precache) { result.push_back(asset); } } const auto anim_trees = get_map_animtrees(script_data); for (const auto& anim_tree : anim_trees) { result.push_back({native::ASSET_TYPE_RAWFILE, anim_tree}); } const auto ambient = get_ambient_play(script_data); if (ambient) { result.push_back({native::ASSET_TYPE_SOUND, ambient}); } } else if (script_name.ends_with(".atr")) { const auto anims = get_animtree_anims(script_data); for (const auto& anim : anims) { result.push_back({native::ASSET_TYPE_XANIMPARTS, anim}); } } return result; } std::vector irawfile::get_animtree_anims(const std::string& script) const { auto result = std::vector(); std::istringstream iss(script); for (std::string line; std::getline(iss, line);) { line = line.erase(0, line.find_first_not_of(" \n\r\t")); while (line.length() > 0 && (line.ends_with("\n") || line.ends_with("\r") || line.ends_with(" "))) { line.pop_back(); } if (line.length() > 0 && line[0] != '/' && line[1] != '/') { const auto parts = find(iw4of::native::XAssetType::ASSET_TYPE_XANIMPARTS, line); if (parts) { result.push_back(parts); } } } return result; } std::vector irawfile::get_map_animtrees(const std::string& script) const { static std::regex animtree_catcher("#using_animtree\\(\"(.*)\"\\);"); std::smatch m; std::string::const_iterator search_start(script.cbegin()); std::vector trees{}; while (std::regex_search(search_start, script.cend(), m, animtree_catcher)) { if (m.size() > 1) { const auto& match = m[1]; auto animtree_name = std::format("animtrees/{}.atr", match.str()); auto animtree = find(native::ASSET_TYPE_RAWFILE, animtree_name); search_start = m.suffix().first; if (animtree) { auto new_name = std::string(animtree->name); trees.push_back(animtree); } else { print_error("Could not find animtree {}\n", animtree_name); assert(false); } } } return trees; } std::vector irawfile::get_map_animated_model_anims(const std::string& script) const { // We use % because it's used for MP and SP (on destructibles for instance it's for both) // but we also have to use "" for mp_specific anims static std::regex anim_catchers[] = { std::regex("%(.*);"), // SP & MP sometimes std::regex("\"\\] *= \"*(.*)\";") // MP only }; std::vector anim_parts{}; for (size_t i = 0; i < ARRAYSIZE(anim_catchers); i++) { const auto& anim_catcher = anim_catchers[i]; std::smatch m; std::string::const_iterator search_start(script.cbegin()); while (std::regex_search(search_start, script.cend(), m, anim_catcher)) { if (m.size() > 1) { const auto& match = m[1]; auto anim_name = match.str(); auto anim = find(native::ASSET_TYPE_XANIMPARTS, anim_name); search_start = m.suffix().first; if (anim) { anim_parts.push_back(anim); } } } } return anim_parts; } native::snd_alias_list_t* irawfile::get_ambient_play(const std::string& script) const { static std::regex ambientplay("ambientplay\\( \"((.*))\" \\);"); std::smatch matches; if (std::regex_search(script.cbegin(), script.cend(), matches, ambientplay)) { if (matches.size() > 1) { const auto& match = matches[1]; auto sound_name = match.str(); auto sound = find(native::ASSET_TYPE_SOUND, sound_name); if (sound) { return sound; } } } return nullptr; } std::vector irawfile::get_map_fx(const std::string& script) const { static std::regex fx_catcher("\\] = loadfx\\( *\"(.+)\" *\\);"); std::smatch m; std::string::const_iterator search_start(script.cbegin()); std::vector fxs{}; while (std::regex_search(search_start, script.cend(), m, fx_catcher)) { bool skip = true; for (auto match : m) { if (skip) { skip = false; continue; } auto fx_name = match.str(); auto fx = find(native::ASSET_TYPE_FX, fx_name); search_start = m.suffix().first; if (fx) { fxs.push_back(fx); } } } return fxs; } std::vector irawfile::get_assets_in_precache(const std::string& script_contents) const { static std::regex anim_script_catcher(" *(.*)::main\\(\\);"); std::smatch anim_script_matches; std::vector asset_list{}; std::string::const_iterator search_start(script_contents.cbegin()); while (std::regex_search(search_start, script_contents.cend(), anim_script_matches, anim_script_catcher)) { if (anim_script_matches.size() > 1) { const auto& match = anim_script_matches[1]; auto script_name = std::format("{}.gsc", match.str()); std::replace(script_name.begin(), script_name.end(), '\\', '/'); search_start = anim_script_matches.suffix().first; auto script = find(native::ASSET_TYPE_RAWFILE, script_name); if (script) { asset_list.push_back({native::ASSET_TYPE_RAWFILE, script}); std::string contents = script->buffer; if (script->compressedLen) { contents = utils::compression::zlib::decompress(std::string(script->buffer, script->compressedLen)); } const auto& anim_trees = get_map_animtrees(contents); const auto& model_anims = get_map_animated_model_anims(contents); for (const auto& tree : anim_trees) { asset_list.push_back({native::ASSET_TYPE_RAWFILE, tree}); } for (const auto& anims : model_anims) { asset_list.push_back({native::ASSET_TYPE_XANIMPARTS, anims}); } } } } return asset_list; } std::vector irawfile::get_create_fx_sounds(const std::string& script) const { static std::regex sound_catcher("(?:\\.v\\[\"soundalias\"\\] *= *\"(.+)\")"); std::smatch m; std::vector sounds{}; std::string::const_iterator search_start(script.cbegin()); while (std::regex_search(search_start, script.cend(), m, sound_catcher)) { bool skip = true; for (auto match : m) { if (skip) { skip = false; continue; } auto sound_name = match.str(); auto sound = find(native::ASSET_TYPE_SOUND, sound_name); search_start = m.suffix().first; if (sound) { sounds.push_back(sound); } } } return sounds; } std::filesystem::path irawfile::get_file_name(const std::string& basename) const { return basename; } } // namespace iw4of::interfaces