diff --git a/CMakeLists.txt b/CMakeLists.txt index 943c637b..0295c325 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -108,6 +108,7 @@ set(RESOURCE_HEADERS src/resource/format/lytreader.h src/resource/format/pereader.h src/resource/format/rimreader.h + src/resource/format/rimwriter.h src/resource/format/ssfreader.h src/resource/format/tlkreader.h src/resource/format/visreader.h @@ -135,6 +136,7 @@ set(RESOURCE_SOURCES src/resource/format/lytreader.cpp src/resource/format/pereader.cpp src/resource/format/rimreader.cpp + src/resource/format/rimwriter.cpp src/resource/format/ssfreader.cpp src/resource/format/tlkreader.cpp src/resource/format/visreader.cpp diff --git a/src/common/streamwriter.cpp b/src/common/streamwriter.cpp index e1958e52..0c40626a 100644 --- a/src/common/streamwriter.cpp +++ b/src/common/streamwriter.cpp @@ -68,6 +68,11 @@ void StreamWriter::putBytes(const ByteArray &bytes) { _stream->write(&bytes[0], bytes.size()); } +void StreamWriter::putBytes(int count, uint8_t val) { + ByteArray data(count, val); + _stream->write(&data[0], count); +} + size_t StreamWriter::tell() const { return _stream->tellp(); } diff --git a/src/common/streamwriter.h b/src/common/streamwriter.h index 5196cd1f..31d9e521 100644 --- a/src/common/streamwriter.h +++ b/src/common/streamwriter.h @@ -42,6 +42,7 @@ public: void putString(const std::string &str); void putCString(const std::string &str); void putBytes(const ByteArray &bytes); + void putBytes(int count, uint8_t val = 0); size_t tell() const; diff --git a/src/resource/format/rimwriter.cpp b/src/resource/format/rimwriter.cpp new file mode 100644 index 00000000..954eef09 --- /dev/null +++ b/src/resource/format/rimwriter.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2020-2021 The reone project contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "rimwriter.h" + +#include + +#include + +#include "../../common/streamwriter.h" + +using namespace std; + +namespace fs = boost::filesystem; + +namespace reone { + +namespace resource { + +void RimWriter::add(Resource &&res) { + _resources.push_back(res); +} + +void RimWriter::save(const fs::path &path) { + auto rim = make_shared(path, ios::binary); + StreamWriter writer(rim); + uint32_t numResources = static_cast(_resources.size()); + + writer.putString("RIM V1.0"); + writer.putUint32(0); // reserved + writer.putUint32(numResources); + writer.putUint32(0x78); // offset to resource headers + writer.putBytes(100); // reserved + + uint32_t id = 0; + uint32_t offset = 0x78 + numResources * 32; + + // Write resource headers + for (auto &res : _resources) { + auto size = static_cast(res.data.size()); + + string resRef(res.resRef); + resRef.resize(16); + writer.putString(resRef); + + writer.putUint32(static_cast(res.resType)); + writer.putUint32(id++); + writer.putUint32(offset); + writer.putUint32(size); + + offset += size; + } + + // Write resources data + for (auto &res : _resources) { + writer.putBytes(res.data); + } +} + +} // namespace resource + +} // namespace reone diff --git a/src/resource/format/rimwriter.h b/src/resource/format/rimwriter.h new file mode 100644 index 00000000..f115cebc --- /dev/null +++ b/src/resource/format/rimwriter.h @@ -0,0 +1,50 @@ +/* + * Copyright (c) 2020-2021 The reone project contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include + +#include + +#include "../../common/types.h" + +#include "../types.h" + +namespace reone { + +namespace resource { + +class RimWriter { +public: + struct Resource { + std::string resRef; + ResourceType resType { ResourceType::Invalid }; + ByteArray data; + }; + + void add(Resource &&res); + void save(const boost::filesystem::path &path); + +private: + std::vector _resources; +}; + +} // namespace resource + +} // namespace reone diff --git a/src/resource/typeutil.cpp b/src/resource/typeutil.cpp index 7322ebf9..d56c6183 100644 --- a/src/resource/typeutil.cpp +++ b/src/resource/typeutil.cpp @@ -97,7 +97,7 @@ const string &getExtByResType(ResourceType type) { return g_extByType[type]; } -ResourceType getResTypeByExt(const string &ext) { +ResourceType getResTypeByExt(const string &ext, bool logNotFound) { if (!g_typeByExtInited) { for (auto &entry : g_extByType) { g_typeByExt.insert(make_pair(entry.second, entry.first)); @@ -106,7 +106,9 @@ ResourceType getResTypeByExt(const string &ext) { } auto it = g_typeByExt.find(ext); if (it == g_typeByExt.end()) { - warn("Resource type not found by extension: " + ext); + if (logNotFound) { + warn("Resource type not found by extension: " + ext); + } return ResourceType::Invalid; } diff --git a/src/resource/typeutil.h b/src/resource/typeutil.h index 9491c3ea..3f796672 100644 --- a/src/resource/typeutil.h +++ b/src/resource/typeutil.h @@ -26,7 +26,7 @@ namespace reone { namespace resource { const std::string &getExtByResType(ResourceType type); -ResourceType getResTypeByExt(const std::string &ext); +ResourceType getResTypeByExt(const std::string &ext, bool logNotFound = true); } // namespace resource diff --git a/tools/program.cpp b/tools/program.cpp index af53e21b..c7f86282 100644 --- a/tools/program.cpp +++ b/tools/program.cpp @@ -49,7 +49,8 @@ static const unordered_map g_operations { { "to-json", Operation::ToJSON }, { "to-tga", Operation::ToTGA }, { "to-2da", Operation::To2DA }, - { "to-gff", Operation::ToGFF } + { "to-gff", Operation::ToGFF }, + { "to-rim", Operation::ToRIM }, }; Program::Program(int argc, char **argv) : _argc(argc), _argv(argv) { @@ -93,6 +94,7 @@ void Program::initOptions() { ("to-tga", "convert TPC image to TGA") ("to-2da", "convert JSON to 2DA") ("to-gff", "convert JSON to GFF") + ("to-rim", "create RIM archive from directory") ("target", po::value(), "target name or path to input file"); } diff --git a/tools/rimtool.cpp b/tools/rimtool.cpp index 964c6af5..4d5009a1 100644 --- a/tools/rimtool.cpp +++ b/tools/rimtool.cpp @@ -19,6 +19,7 @@ #include +#include "../src/resource/format/rimwriter.h" #include "../src/resource/typeutil.h" using namespace std; @@ -32,13 +33,23 @@ namespace reone { namespace tools { void RimTool::invoke(Operation operation, const fs::path &target, const fs::path &gamePath, const fs::path &destPath) { - RimReader rim; - rim.load(target); - - if (operation == Operation::List) { - list(rim); - } else if (operation == Operation::Extract) { - extract(rim, destPath); + switch (operation) { + case Operation::List: + case Operation::Extract: { + RimReader rim; + rim.load(target); + if (operation == Operation::List) { + list(rim); + } else if (operation == Operation::Extract) { + extract(rim, destPath); + } + break; + } + case Operation::ToRIM: + toRIM(target); + break; + default: + break; } } @@ -65,11 +76,52 @@ void RimTool::extract(RimReader &rim, const fs::path &destPath) { } } +void RimTool::toRIM(const fs::path &target) { + RimWriter rim; + + for (auto &entry : fs::directory_iterator(target)) { + fs::path path(entry); + if (fs::is_directory(path)) continue; + + string ext(path.extension().string()); + ext.erase(0, 1); + + ResourceType resType = getResTypeByExt(ext, false); + if (resType == ResourceType::Invalid) continue; + + fs::ifstream in(path, ios::binary); + in.seekg(0, ios::end); + size_t size = in.tellg(); + ByteArray data(size); + in.seekg(0); + in.read(&data[0], size); + + fs::path resRef(path.filename()); + resRef.replace_extension(""); + + RimWriter::Resource res; + res.resRef = resRef.string(); + res.resType = resType; + res.data = move(data); + + rim.add(move(res)); + } + + fs::path rimPath(target.parent_path()); + rimPath.append(target.filename().string() + ".rim"); + rim.save(rimPath); +} + bool RimTool::supports(Operation operation, const fs::path &target) const { - return - !fs::is_directory(target) && - target.extension() == ".rim" && - (operation == Operation::List || operation == Operation::Extract); + switch (operation) { + case Operation::List: + case Operation::Extract: + return !fs::is_directory(target) && target.extension() == ".rim"; + case Operation::ToRIM: + return fs::is_directory(target); + default: + return false; + } } } // namespace tools diff --git a/tools/tools.h b/tools/tools.h index 818e6b64..9cffc89c 100644 --- a/tools/tools.h +++ b/tools/tools.h @@ -86,6 +86,7 @@ public: private: void list(const resource::RimReader &rim); void extract(resource::RimReader &rim, const boost::filesystem::path &destPath); + void toRIM(const boost::filesystem::path &target); }; class TwoDaTool : public ITool { diff --git a/tools/types.h b/tools/types.h index 314e0bd4..18acfbe1 100644 --- a/tools/types.h +++ b/tools/types.h @@ -28,7 +28,8 @@ enum class Operation { ToJSON, ToTGA, To2DA, - ToGFF + ToGFF, + ToRIM }; } // namespace tools