refactor: Streamline and cleanup tools
This commit is contained in:
parent
41dca204fa
commit
2f4d568c15
19 changed files with 472 additions and 1227 deletions
|
@ -688,23 +688,19 @@ endif()
|
|||
|
||||
if(BUILD_TOOLS)
|
||||
set(TOOLS_HEADERS
|
||||
tools/moduleprobe.h
|
||||
tools/program.h
|
||||
tools/tools.h)
|
||||
tools/tools.h
|
||||
tools/types.h)
|
||||
|
||||
set(TOOLS_SOURCES
|
||||
tools/main.cpp
|
||||
tools/2datool.cpp
|
||||
tools/biftool.cpp
|
||||
tools/erftool.cpp
|
||||
tools/gfftool.cpp
|
||||
tools/jsontool.cpp
|
||||
tools/keytool.cpp
|
||||
tools/moduleprobe.cpp
|
||||
tools/keybiftool.cpp
|
||||
tools/main.cpp
|
||||
tools/program.cpp
|
||||
tools/rimtool.cpp
|
||||
tools/tlktool.cpp
|
||||
tools/tools.cpp
|
||||
tools/tpctool.cpp)
|
||||
|
||||
add_executable(reone-tools ${TOOLS_HEADERS} ${TOOLS_SOURCES})
|
||||
|
|
|
@ -60,7 +60,7 @@ RimFile::Resource RimFile::readResource() {
|
|||
|
||||
Resource res;
|
||||
res.resRef = boost::to_lower_copy(resRef);
|
||||
res.type = static_cast<ResourceType>(type);
|
||||
res.resType = static_cast<ResourceType>(type);
|
||||
res.offset = offset;
|
||||
res.size = size;
|
||||
|
||||
|
@ -77,7 +77,7 @@ shared_ptr<ByteArray> RimFile::find(const string &resRef, ResourceType type) {
|
|||
auto it = find_if(
|
||||
_resources.begin(),
|
||||
_resources.end(),
|
||||
[&](const Resource &res) { return res.resRef == lcResRef && res.type == type; });
|
||||
[&](const Resource &res) { return res.resRef == lcResRef && res.resType == type; });
|
||||
|
||||
if (it == _resources.end()) return nullptr;
|
||||
|
||||
|
|
|
@ -30,7 +30,7 @@ class RimFile : public BinaryFile, public IResourceProvider {
|
|||
public:
|
||||
struct Resource {
|
||||
std::string resRef;
|
||||
ResourceType type { ResourceType::Invalid };
|
||||
ResourceType resType { ResourceType::Invalid };
|
||||
uint32_t offset { 0 };
|
||||
uint32_t size { 0 };
|
||||
};
|
||||
|
@ -38,7 +38,7 @@ public:
|
|||
RimFile();
|
||||
|
||||
bool supports(ResourceType type) const override;
|
||||
std::shared_ptr<ByteArray> find(const std::string &resRef, ResourceType type) override;
|
||||
std::shared_ptr<ByteArray> find(const std::string &resRef, ResourceType resType) override;
|
||||
ByteArray getResourceData(int idx);
|
||||
|
||||
const std::vector<Resource> &resources() const { return _resources; }
|
||||
|
|
|
@ -17,10 +17,9 @@
|
|||
|
||||
#include "tools.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "../src/resource/format/2dafile.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::resource;
|
||||
|
@ -32,7 +31,15 @@ namespace reone {
|
|||
|
||||
namespace tools {
|
||||
|
||||
void TwoDaTool::toJson(const fs::path &path, const fs::path &destPath) const {
|
||||
void TwoDaTool::invoke(Operation operation, const fs::path &target, const fs::path &gamePath, const fs::path &destPath) {
|
||||
if (operation == Operation::ToJSON) {
|
||||
toJSON(target, destPath);
|
||||
} else {
|
||||
to2DA(target, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
void TwoDaTool::toJSON(const fs::path &path, const fs::path &destPath) {
|
||||
TwoDaFile twoDa;
|
||||
twoDa.load(path);
|
||||
|
||||
|
@ -42,7 +49,7 @@ void TwoDaTool::toJson(const fs::path &path, const fs::path &destPath) const {
|
|||
auto &headers = table->headers();
|
||||
auto &rows = table->rows();
|
||||
|
||||
for (int i = 0; i < static_cast<int>(rows.size()); ++i) {
|
||||
for (size_t i = 0; i < rows.size(); ++i) {
|
||||
pt::ptree child;
|
||||
child.put("_id", i);
|
||||
|
||||
|
@ -60,6 +67,40 @@ void TwoDaTool::toJson(const fs::path &path, const fs::path &destPath) const {
|
|||
pt::write_json(json, tree);
|
||||
}
|
||||
|
||||
void TwoDaTool::to2DA(const fs::path &path, const fs::path &destPath) {
|
||||
pt::ptree tree;
|
||||
pt::read_json(path.string(), tree);
|
||||
|
||||
auto table = make_unique<TwoDaTable>();
|
||||
|
||||
for (auto &jsonRow : tree.get_child("rows")) {
|
||||
TwoDaRow row;
|
||||
for (auto &column : jsonRow.second) {
|
||||
// Columns starting with an underscore we consider meta and skip
|
||||
if (boost::starts_with(column.first, "_")) continue;
|
||||
|
||||
row.add(column.first, column.second.data());
|
||||
}
|
||||
table->add(move(row));
|
||||
}
|
||||
|
||||
vector<string> tokens;
|
||||
boost::split(tokens, path.filename().string(), boost::is_any_of("."), boost::token_compress_on);
|
||||
|
||||
fs::path twoDaPath(destPath);
|
||||
twoDaPath.append(tokens[0] + ".2da");
|
||||
|
||||
TwoDaWriter writer(move(table));
|
||||
writer.save(twoDaPath);
|
||||
}
|
||||
|
||||
bool TwoDaTool::supports(Operation operation, const fs::path &target) const {
|
||||
return
|
||||
!fs::is_directory(target) &&
|
||||
(target.extension() == ".2da" || target.extension() == ".json") &&
|
||||
(operation == Operation::ToJSON || operation == Operation::To2DA);
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -1,98 +0,0 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "../src/common/log.h"
|
||||
#include "../src/resource/format/biffile.h"
|
||||
#include "../src/resource/typeutil.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::resource;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tools {
|
||||
|
||||
void BifTool::list(const fs::path &path, const fs::path &keyPath) const {
|
||||
if (!fs::exists(keyPath)) {
|
||||
throw runtime_error("BIF: key file does not exist: " + keyPath.string());
|
||||
}
|
||||
KeyFile key;
|
||||
key.load(keyPath);
|
||||
|
||||
auto &files = key.files();
|
||||
string filename(path.filename().string());
|
||||
int bifIdx = getFileIndexByFilename(files, filename);
|
||||
if (bifIdx == -1) {
|
||||
throw runtime_error("BIF: filename not found in Key file: " + filename);
|
||||
}
|
||||
|
||||
for (auto &key : key.keys()) {
|
||||
if (key.bifIdx == bifIdx) {
|
||||
info(boost::format("%16s\t%4s") % key.resRef % getExtByResType(key.resType));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int BifTool::getFileIndexByFilename(const vector<KeyFile::FileEntry> &files, const string &filename) const {
|
||||
for (int i = 0; i < files.size(); ++i) {
|
||||
if (boost::contains(files[i].filename, filename)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
void BifTool::extract(const fs::path &path, const fs::path &keyPath, const fs::path &destPath) const {
|
||||
if (!fs::exists(keyPath)) {
|
||||
throw runtime_error("BIF: key file does not exist: " + keyPath.string());
|
||||
}
|
||||
KeyFile key;
|
||||
key.load(keyPath);
|
||||
|
||||
auto &files = key.files();
|
||||
string filename(path.filename().string());
|
||||
int bifIdx = getFileIndexByFilename(files, filename);
|
||||
if (bifIdx == -1) {
|
||||
throw runtime_error("BIF: filename not found in Key file: " + filename);
|
||||
}
|
||||
BifFile bif;
|
||||
bif.load(path);
|
||||
|
||||
for (auto &key : key.keys()) {
|
||||
if (key.bifIdx != bifIdx) continue;
|
||||
info(boost::format("Extracting %16s\t%4s") % key.resRef % getExtByResType(key.resType));
|
||||
|
||||
fs::path resPath(destPath);
|
||||
resPath.append(key.resRef + "." + getExtByResType(key.resType));
|
||||
|
||||
auto data = bif.getResourceData(key.resIdx);
|
||||
fs::ofstream res(resPath, ios::binary);
|
||||
res.write(data->data(), data->size());
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
|
@ -17,10 +17,8 @@
|
|||
|
||||
#include "tools.h"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include "../src/common/log.h"
|
||||
#include "../src/resource/format/erffile.h"
|
||||
#include "../src/resource/typeutil.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -33,33 +31,49 @@ namespace reone {
|
|||
|
||||
namespace tools {
|
||||
|
||||
void ErfTool::list(const fs::path &path, const fs::path &keyPath) const {
|
||||
void ErfTool::invoke(Operation operation, const fs::path &target, const fs::path &gamePath, const fs::path &destPath) {
|
||||
ErfFile erf;
|
||||
erf.load(path);
|
||||
erf.load(target);
|
||||
|
||||
for (auto &key : erf.keys()) {
|
||||
info(boost::format("%16s\t%4s") % key.resRef % getExtByResType(key.resType));
|
||||
if (operation == Operation::List) {
|
||||
list(erf);
|
||||
} else if (operation == Operation::Extract) {
|
||||
extract(erf, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
void ErfTool::extract(const fs::path &path, const fs::path &keyPath, const fs::path &destPath) const {
|
||||
ErfFile erf;
|
||||
erf.load(path);
|
||||
void ErfTool::list(const ErfFile &erf) {
|
||||
for (auto &key : erf.keys()) {
|
||||
cout << key.resRef << " " << getExtByResType(key.resType) << endl;
|
||||
}
|
||||
}
|
||||
|
||||
auto &keys = erf.keys();
|
||||
for (int i = 0; i < keys.size(); ++i) {
|
||||
const ErfFile::Key &key = keys[i];
|
||||
info(boost::format("Extracting %16s\t%4s") % key.resRef % getExtByResType(key.resType));
|
||||
void ErfTool::extract(ErfFile &erf, const fs::path &destPath) {
|
||||
if (!fs::is_directory(destPath) || !fs::exists(destPath)) return;
|
||||
|
||||
for (size_t i = 0; i < erf.keys().size(); ++i) {
|
||||
const ErfFile::Key &key = erf.keys()[i];
|
||||
string ext(getExtByResType(key.resType));
|
||||
cout << "Extracting " << key.resRef << " " << ext << endl;
|
||||
ByteArray data(erf.getResourceData(i));
|
||||
|
||||
fs::path resPath(destPath);
|
||||
resPath.append(key.resRef + "." + getExtByResType(key.resType));
|
||||
resPath.append(key.resRef + "." + ext);
|
||||
|
||||
ByteArray data(erf.getResourceData(i));
|
||||
fs::ofstream res(resPath, ios::binary);
|
||||
res.write(data.data(), data.size());
|
||||
res.write(&data[0], data.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool ErfTool::supports(Operation operation, const fs::path &target) const {
|
||||
if (fs::is_directory(target)) return false;
|
||||
|
||||
string ext(target.extension().string());
|
||||
if (ext != ".erf" && ext != ".mod" && ext != ".sav") return false;
|
||||
|
||||
return operation == Operation::List || operation == Operation::Extract;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -17,9 +17,6 @@
|
|||
|
||||
#include "tools.h"
|
||||
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "../src/resource/format/gfffile.h"
|
||||
|
@ -35,21 +32,13 @@ namespace reone {
|
|||
|
||||
namespace tools {
|
||||
|
||||
void GffTool::toJson(const fs::path &path, const fs::path &destPath) const {
|
||||
GffFile gff;
|
||||
gff.load(path);
|
||||
|
||||
shared_ptr<GffStruct> gffs(gff.top());
|
||||
pt::ptree tree(getPropertyTree(*gffs));
|
||||
|
||||
fs::path jsonPath(destPath);
|
||||
jsonPath.append(path.filename().string() + ".json");
|
||||
|
||||
fs::ofstream json(jsonPath);
|
||||
pt::write_json(json, tree);
|
||||
void GffTool::invoke(Operation operation, const fs::path &target, const fs::path &gamePath, const fs::path &destPath) {
|
||||
if (operation == Operation::ToJSON) {
|
||||
toJSON(target, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
pt::ptree GffTool::getPropertyTree(const GffStruct &gffs) const {
|
||||
static pt::ptree getPropertyTree(const GffStruct &gffs) {
|
||||
pt::ptree tree;
|
||||
pt::ptree child;
|
||||
pt::ptree children;
|
||||
|
@ -101,6 +90,24 @@ pt::ptree GffTool::getPropertyTree(const GffStruct &gffs) const {
|
|||
return tree;
|
||||
}
|
||||
|
||||
void GffTool::toJSON(const fs::path &path, const fs::path &destPath) {
|
||||
GffFile gff;
|
||||
gff.load(path);
|
||||
|
||||
shared_ptr<GffStruct> gffs(gff.top());
|
||||
pt::ptree tree(getPropertyTree(*gffs));
|
||||
|
||||
fs::path jsonPath(destPath);
|
||||
jsonPath.append(path.filename().string() + ".json");
|
||||
|
||||
fs::ofstream json(jsonPath);
|
||||
pt::write_json(json, tree);
|
||||
}
|
||||
|
||||
bool GffTool::supports(Operation operation, const fs::path &target) const {
|
||||
return !fs::is_directory(target) && operation == Operation::ToJSON;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -1,77 +0,0 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "../src/common/log.h"
|
||||
#include "../src/resource/format/2dafile.h"
|
||||
#include "../src/resource/format/gfffile.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::resource;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tools {
|
||||
|
||||
void JsonTool::to2DA(const fs::path &path, const fs::path &destPath) const {
|
||||
pt::ptree tree;
|
||||
pt::read_json(path.string(), tree);
|
||||
|
||||
if (!tree.count("rows")) {
|
||||
warn("JSON: property not found: rows");
|
||||
return;
|
||||
}
|
||||
|
||||
auto table = make_unique<TwoDaTable>();
|
||||
|
||||
for (auto &jsonRow : tree.get_child("rows")) {
|
||||
TwoDaRow row;
|
||||
for (auto &column : jsonRow.second) {
|
||||
// Columns starting with an underscore we consider meta and skip
|
||||
if (boost::starts_with(column.first, "_")) continue;
|
||||
|
||||
row.add(column.first, column.second.data());
|
||||
}
|
||||
table->add(move(row));
|
||||
}
|
||||
|
||||
vector<string> tokens;
|
||||
boost::split(tokens, path.filename().string(), boost::is_any_of("."), boost::token_compress_on);
|
||||
|
||||
fs::path twoDaPath(destPath);
|
||||
twoDaPath.append(tokens[0] + ".2da");
|
||||
|
||||
TwoDaWriter writer(move(table));
|
||||
writer.save(twoDaPath);
|
||||
}
|
||||
|
||||
void JsonTool::toGFF(const fs::path &path, const fs::path &destPath) const {
|
||||
pt::ptree tree;
|
||||
pt::read_json(path.string(), tree);
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
122
tools/keybiftool.cpp
Normal file
122
tools/keybiftool.cpp
Normal file
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "../src/common/pathutil.h"
|
||||
#include "../src/resource/typeutil.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::resource;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tools {
|
||||
|
||||
void KeyBifTool::invoke(Operation operation, const fs::path &target, const fs::path &gamePath, const fs::path &destPath) {
|
||||
bool isKey = target.extension() == ".key";
|
||||
if (isKey) {
|
||||
KeyFile key;
|
||||
key.load(target);
|
||||
|
||||
listKEY(key);
|
||||
|
||||
} else {
|
||||
fs::path keyPath(getPathIgnoreCase(gamePath, "chitin.key"));
|
||||
|
||||
KeyFile key;
|
||||
key.load(keyPath);
|
||||
|
||||
int bifIdx = -1;
|
||||
for (size_t i = 0; i < key.files().size(); ++i) {
|
||||
if (boost::ends_with(key.getFilename(i), target.filename().string())) {
|
||||
bifIdx = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (bifIdx == -1) return;
|
||||
|
||||
BifFile bif;
|
||||
bif.load(target);
|
||||
|
||||
if (operation == Operation::List) {
|
||||
listBIF(key, bif, bifIdx);
|
||||
} else if (operation == Operation::Extract) {
|
||||
extractBIF(key, bif, bifIdx, destPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBifTool::listKEY(const KeyFile &key) {
|
||||
for (auto &file : key.files()) {
|
||||
cout << file.filename << " " << file.fileSize << endl;
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBifTool::listBIF(const KeyFile &key, const BifFile &bif, int bifIdx) {
|
||||
for (auto &keyEntry : key.keys()) {
|
||||
if (keyEntry.bifIdx == bifIdx) {
|
||||
cout << keyEntry.resRef << " " << getExtByResType(keyEntry.resType) << endl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void KeyBifTool::extractBIF(const KeyFile &key, BifFile &bif, int bifIdx, const fs::path &destPath) {
|
||||
if (!fs::is_directory(destPath) || !fs::exists(destPath)) return;
|
||||
|
||||
for (auto &keyEntry : key.keys()) {
|
||||
if (keyEntry.bifIdx != bifIdx) continue;
|
||||
|
||||
string ext(getExtByResType(keyEntry.resType));
|
||||
cout << "Extracting " + keyEntry.resRef << " " << ext << endl;
|
||||
unique_ptr<ByteArray> data(bif.getResourceData(keyEntry.resIdx));
|
||||
|
||||
fs::path resPath(destPath);
|
||||
resPath.append(keyEntry.resRef + "." + ext);
|
||||
|
||||
fs::ofstream out(resPath, ios::binary);
|
||||
out.write(&(*data)[0], data->size());
|
||||
}
|
||||
}
|
||||
|
||||
bool KeyBifTool::supports(Operation operation, const fs::path &target) const {
|
||||
if (fs::is_directory(target)) return false;
|
||||
|
||||
bool key = target.extension() == ".key";
|
||||
bool bif = target.extension() == ".bif";
|
||||
|
||||
if (key) {
|
||||
return operation == Operation::List;
|
||||
} else if (bif) {
|
||||
return operation == Operation::List || operation == Operation::Extract;
|
||||
} else {
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
|
@ -1,620 +0,0 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "moduleprobe.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "../src/common/pathutil.h"
|
||||
#include "../src/common/streamutil.h"
|
||||
#include "../src/resource/format/lytfile.h"
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
namespace pt = boost::property_tree;
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::resource;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tools {
|
||||
|
||||
void ModuleProbe::probe(const string &name, const fs::path &gamePath, const fs::path &destPath) {
|
||||
_gamePath = gamePath;
|
||||
_keyBifProvider.init(_gamePath);
|
||||
loadResources(name);
|
||||
pt::ptree description(describeModule());
|
||||
writeDescription(name, description, destPath);
|
||||
}
|
||||
|
||||
void ModuleProbe::loadResources(const string &moduleName) {
|
||||
fs::path tlkPath(getPathIgnoreCase(_gamePath, "dialog.tlk", false));
|
||||
if (!fs::exists(tlkPath)) {
|
||||
throw runtime_error("TLK file not found: " + tlkPath.string());
|
||||
}
|
||||
TlkFile tlk;
|
||||
tlk.load(tlkPath);
|
||||
_talkTable = tlk.table();
|
||||
|
||||
fs::path modulesPath(getPathIgnoreCase(_gamePath, "modules", false));
|
||||
if (!fs::exists(modulesPath)) {
|
||||
throw runtime_error("Modules directory not found: " + modulesPath.string());
|
||||
}
|
||||
|
||||
fs::path mainPath(getPathIgnoreCase(modulesPath, moduleName + ".rim", false));
|
||||
if (!fs::exists(mainPath)) {
|
||||
throw runtime_error("Main module RIM not found: " + mainPath.string());
|
||||
}
|
||||
_rimMain.load(mainPath);
|
||||
|
||||
fs::path blueprintsPath(getPathIgnoreCase(modulesPath, moduleName + "_s.rim", false));
|
||||
if (!fs::exists(blueprintsPath)) {
|
||||
throw runtime_error("Module blueprints RIM not found: " + blueprintsPath.string());
|
||||
}
|
||||
_rimBlueprints.load(blueprintsPath);
|
||||
}
|
||||
|
||||
static void putIfNotEmpty(const std::string &name, std::string value, pt::ptree &tree) {
|
||||
if (!value.empty()) {
|
||||
tree.put(name, move(value));
|
||||
}
|
||||
}
|
||||
|
||||
pt::ptree ModuleProbe::describeModule() {
|
||||
pt::ptree description;
|
||||
|
||||
GffFile ifo;
|
||||
ifo.load(wrap(_rimMain.find("module", ResourceType::Ifo)));
|
||||
auto ifoGffs = ifo.top();
|
||||
|
||||
// Entry
|
||||
|
||||
pt::ptree entry;
|
||||
entry.put("x", ifoGffs->getFloat("Mod_Entry_X"));
|
||||
entry.put("y", ifoGffs->getFloat("Mod_Entry_Y"));
|
||||
entry.put("z", ifoGffs->getFloat("Mod_Entry_Z"));
|
||||
entry.put("dir_x", ifoGffs->getFloat("Mod_Entry_Dir_X"));
|
||||
entry.put("dir_y", ifoGffs->getFloat("Mod_Entry_Dir_Y"));
|
||||
description.add_child("entry", entry);
|
||||
|
||||
// END Entry
|
||||
|
||||
// Scripts
|
||||
|
||||
pt::ptree scripts;
|
||||
putIfNotEmpty("on_heartbeat", ifoGffs->getString("Mod_OnHeartbeat"), scripts);
|
||||
putIfNotEmpty("on_mod_load", ifoGffs->getString("Mod_OnModLoad"), scripts);
|
||||
putIfNotEmpty("on_client_entr", ifoGffs->getString("Mod_OnClientEntr"), scripts);
|
||||
putIfNotEmpty("on_client_leav", ifoGffs->getString("Mod_OnClientLeav"), scripts);
|
||||
putIfNotEmpty("on_actvt_item", ifoGffs->getString("Mod_OnActvtItem"), scripts);
|
||||
putIfNotEmpty("on_acquir_item", ifoGffs->getString("Mod_OnAcquirItem"), scripts);
|
||||
putIfNotEmpty("on_usr_defined", ifoGffs->getString("Mod_OnUsrDefined"), scripts);
|
||||
putIfNotEmpty("on_un_aqre_item", ifoGffs->getString("Mod_OnUnAqreItem"), scripts);
|
||||
putIfNotEmpty("on_plr_death", ifoGffs->getString("Mod_OnPlrDeath"), scripts);
|
||||
putIfNotEmpty("on_plr_dying", ifoGffs->getString("Mod_OnPlrDying"), scripts);
|
||||
putIfNotEmpty("on_plr_lvl_up", ifoGffs->getString("Mod_OnPlrLvlUp"), scripts);
|
||||
putIfNotEmpty("on_spawn_btn_dn", ifoGffs->getString("Mod_OnSpawnBtnDn"), scripts);
|
||||
putIfNotEmpty("on_plr_death", ifoGffs->getString("Mod_OnPlrDeath"), scripts);
|
||||
putIfNotEmpty("on_plr_rest", ifoGffs->getString("Mod_OnPlrRest"), scripts);
|
||||
if (!scripts.empty()) {
|
||||
description.add_child("scripts", scripts);
|
||||
}
|
||||
|
||||
// END Scripts
|
||||
|
||||
TwoDaFile appearance;
|
||||
appearance.load(wrap(getResource("appearance", ResourceType::TwoDa)));
|
||||
auto appearanceTable = appearance.table();
|
||||
|
||||
TwoDaFile placeables;
|
||||
placeables.load(wrap(getResource("placeables", ResourceType::TwoDa)));
|
||||
auto placeablesTable = placeables.table();
|
||||
|
||||
// Areas
|
||||
|
||||
pt::ptree areas;
|
||||
for (auto &areaGffs : ifoGffs->getList("Mod_Area_list")) {
|
||||
string name(areaGffs->getString("Area_Name"));
|
||||
areas.push_back(make_pair("", describeArea(name, *appearanceTable, *placeablesTable)));
|
||||
}
|
||||
description.add_child("areas", areas);
|
||||
|
||||
// END Areas
|
||||
|
||||
return move(description);
|
||||
}
|
||||
|
||||
pt::ptree ModuleProbe::describeArea(const string &name, const TwoDaTable &appearance, const resource::TwoDaTable &placeables) {
|
||||
GffFile are;
|
||||
are.load(wrap(_rimMain.find(name, ResourceType::Are)));
|
||||
auto areGffs = are.top();
|
||||
|
||||
GffFile git;
|
||||
git.load(wrap(_rimMain.find(name, ResourceType::Git)));
|
||||
auto gitGffs = git.top();
|
||||
|
||||
LytFile lyt;
|
||||
lyt.load(wrap(getResource(name, ResourceType::Lyt)));
|
||||
|
||||
// Rooms
|
||||
|
||||
pt::ptree rooms;
|
||||
for (auto &lytRoom : lyt.rooms()) {
|
||||
pt::ptree position;
|
||||
position.put("x", lytRoom.position.x);
|
||||
position.put("y", lytRoom.position.y);
|
||||
position.put("z", lytRoom.position.z);
|
||||
|
||||
pt::ptree room;
|
||||
room.put("name", lytRoom.name);
|
||||
room.add_child("position", position);
|
||||
|
||||
rooms.push_back(make_pair("", room));
|
||||
}
|
||||
|
||||
// END Rooms
|
||||
|
||||
// Creatures
|
||||
|
||||
pt::ptree creatures;
|
||||
for (auto &gitCreature : gitGffs->getList("Creature List")) {
|
||||
creatures.push_back(make_pair("", describeCreature(*gitCreature, appearance)));
|
||||
}
|
||||
|
||||
// END Creatures
|
||||
|
||||
// Doors
|
||||
|
||||
pt::ptree doors;
|
||||
for (auto &gitDoor : gitGffs->getList("Door List")) {
|
||||
doors.push_back(make_pair("", describeDoor(*gitDoor)));
|
||||
}
|
||||
|
||||
// END Doors
|
||||
|
||||
// Placeables
|
||||
|
||||
pt::ptree placeablesTree;
|
||||
for (auto &gitPlaceable : gitGffs->getList("Placeable List")) {
|
||||
placeablesTree.push_back(make_pair("", describePlaceable(*gitPlaceable, placeables)));
|
||||
}
|
||||
|
||||
// END Placeables
|
||||
|
||||
// Triggers
|
||||
|
||||
pt::ptree triggers;
|
||||
for (auto &gitTrigger : gitGffs->getList("TriggerList")) {
|
||||
triggers.push_back(make_pair("", describeTrigger(*gitTrigger)));
|
||||
}
|
||||
|
||||
// END Triggers
|
||||
|
||||
// Waypoints
|
||||
|
||||
pt::ptree waypoints;
|
||||
for (auto &gitWaypoint : gitGffs->getList("WaypointList")) {
|
||||
waypoints.push_back(make_pair("", describeWaypoint(*gitWaypoint)));
|
||||
}
|
||||
|
||||
// END Waypoints
|
||||
|
||||
// Sounds
|
||||
|
||||
pt::ptree sounds;
|
||||
for (auto &gitSound : gitGffs->getList("SoundList")) {
|
||||
sounds.push_back(make_pair("", describeSound(*gitSound)));
|
||||
}
|
||||
|
||||
// END Sounds
|
||||
|
||||
// Scripts
|
||||
|
||||
pt::ptree scripts;
|
||||
putIfNotEmpty("on_enter", areGffs->getString("OnEnter"), scripts);
|
||||
putIfNotEmpty("on_exit", areGffs->getString("OnExit"), scripts);
|
||||
putIfNotEmpty("on_heartbeat", areGffs->getString("OnHeartbeat"), scripts);
|
||||
putIfNotEmpty("on_user_defined", areGffs->getString("OnUserDefined"), scripts);
|
||||
|
||||
// END Scripts
|
||||
|
||||
pt::ptree area;
|
||||
area.put("name", name);
|
||||
area.put("title", getString(areGffs->getInt("Name")));
|
||||
if (!scripts.empty()) {
|
||||
area.add_child("scripts", scripts);
|
||||
}
|
||||
if (!rooms.empty()) {
|
||||
area.add_child("rooms", rooms);
|
||||
}
|
||||
if (!creatures.empty()) {
|
||||
area.add_child("creatures", creatures);
|
||||
}
|
||||
if (!doors.empty()) {
|
||||
area.add_child("doors", doors);
|
||||
}
|
||||
if (!placeablesTree.empty()) {
|
||||
area.add_child("placeables", placeablesTree);
|
||||
}
|
||||
if (!triggers.empty()) {
|
||||
area.add_child("triggers", triggers);
|
||||
}
|
||||
if (!waypoints.empty()) {
|
||||
area.add_child("waypoints", waypoints);
|
||||
}
|
||||
if (!sounds.empty()) {
|
||||
area.add_child("sounds", sounds);
|
||||
}
|
||||
|
||||
return move(area);
|
||||
}
|
||||
|
||||
static string describeFaction(int id) {
|
||||
static unordered_map<int, string> descriptions {
|
||||
{ 1, "Hostile1" },
|
||||
{ 2, "Friendly1" },
|
||||
{ 3, "Hostile2" },
|
||||
{ 4, "Friendly2" },
|
||||
{ 5, "Neutral" },
|
||||
{ 6, "Insane" },
|
||||
{ 7, "Tuskan" },
|
||||
{ 8, "GlobalXor" },
|
||||
{ 9, "Surrender1" },
|
||||
{ 10, "Surrender2" },
|
||||
{ 11, "Predator" },
|
||||
{ 12, "Prey" },
|
||||
{ 13, "Trap" },
|
||||
{ 14, "EndarSpire" },
|
||||
{ 15, "Rancor" },
|
||||
{ 16, "Gizka1" },
|
||||
{ 17, "Gizka2" },
|
||||
{ 21, "SelfLoathing" },
|
||||
{ 22, "OneOnOne" },
|
||||
{ 23, "PartyPuppet" }
|
||||
};
|
||||
|
||||
auto maybeDescription = descriptions.find(id);
|
||||
|
||||
return maybeDescription != descriptions.end() ? maybeDescription->second : to_string(id);
|
||||
}
|
||||
|
||||
pt::ptree ModuleProbe::describeCreature(const GffStruct &gitCreature, const TwoDaTable &appearance) {
|
||||
pt::ptree position;
|
||||
position.put("x", gitCreature.getFloat("XPosition"));
|
||||
position.put("y", gitCreature.getFloat("YPosition"));
|
||||
position.put("z", gitCreature.getFloat("ZPosition"));
|
||||
|
||||
pt::ptree orientation;
|
||||
orientation.put("x", gitCreature.getFloat("XOrientation"));
|
||||
orientation.put("y", gitCreature.getFloat("YOrientation"));
|
||||
|
||||
GffFile utc;
|
||||
utc.load(wrap(getResource(gitCreature.getString("TemplateResRef"), ResourceType::Utc)));
|
||||
auto utcGffs = utc.top();
|
||||
|
||||
pt::ptree equipment;
|
||||
for (auto &utcItem : utcGffs->getList("Equip_ItemList")) {
|
||||
pt::ptree item;
|
||||
item.put("resref", utcItem->getString("EquippedRes"));
|
||||
equipment.push_back(make_pair("", item));
|
||||
}
|
||||
|
||||
pt::ptree items;
|
||||
for (auto &utcItem : utcGffs->getList("ItemList")) {
|
||||
pt::ptree item;
|
||||
item.put("resref", utcItem->getString("InventoryRes"));
|
||||
item.put("dropable", utcItem->getBool("Dropable"));
|
||||
items.push_back(make_pair("", item));
|
||||
}
|
||||
|
||||
pt::ptree scripts;
|
||||
putIfNotEmpty("heartbeat", utcGffs->getString("ScriptHeartbeat"), scripts);
|
||||
putIfNotEmpty("on_notice", utcGffs->getString("ScriptOnNotice"), scripts);
|
||||
putIfNotEmpty("spell_at", utcGffs->getString("ScriptSpellAt"), scripts);
|
||||
putIfNotEmpty("attacked", utcGffs->getString("ScriptAttacked"), scripts);
|
||||
putIfNotEmpty("damaged", utcGffs->getString("ScriptDamaged"), scripts);
|
||||
putIfNotEmpty("end_round", utcGffs->getString("ScriptEndRound"), scripts);
|
||||
putIfNotEmpty("end_dialogu", utcGffs->getString("ScriptEndDialogu"), scripts);
|
||||
putIfNotEmpty("dialogue", utcGffs->getString("ScriptDialogue"), scripts);
|
||||
putIfNotEmpty("spawn", utcGffs->getString("ScriptSpawn"), scripts);
|
||||
putIfNotEmpty("rested", utcGffs->getString("ScriptRested"), scripts);
|
||||
putIfNotEmpty("death", utcGffs->getString("ScriptDeath"), scripts);
|
||||
putIfNotEmpty("user_define", utcGffs->getString("ScriptUserDefine"), scripts);
|
||||
putIfNotEmpty("on_blocked", utcGffs->getString("ScriptOnBlocked"), scripts);
|
||||
|
||||
pt::ptree blueprint;
|
||||
blueprint.put("resref", utcGffs->getString("TemplateResRef"));
|
||||
blueprint.put("tag", utcGffs->getString("Tag"));
|
||||
putIfNotEmpty("first_name", getString(utcGffs->getInt("FirstName")), blueprint);
|
||||
putIfNotEmpty("last_name", getString(utcGffs->getInt("LastName")), blueprint);
|
||||
blueprint.put("appearance", appearance.getString(utcGffs->getInt("Appearance_Type"), "label"));
|
||||
blueprint.put("faction", describeFaction(utcGffs->getInt("FactionID")));
|
||||
putIfNotEmpty("conversation", utcGffs->getString("Conversation"), blueprint);
|
||||
if (!equipment.empty()) {
|
||||
blueprint.add_child("equipment", equipment);
|
||||
}
|
||||
if (!items.empty()) {
|
||||
blueprint.add_child("items", items);
|
||||
}
|
||||
if (!scripts.empty()) {
|
||||
blueprint.add_child("scripts", scripts);
|
||||
}
|
||||
|
||||
pt::ptree creature;
|
||||
creature.add_child("position", position);
|
||||
creature.add_child("orientation", orientation);
|
||||
creature.add_child("blueprint", blueprint);
|
||||
|
||||
return move(creature);
|
||||
}
|
||||
|
||||
pt::ptree ModuleProbe::describeDoor(const GffStruct &gitDoor) {
|
||||
GffFile utd;
|
||||
utd.load(wrap(getResource(gitDoor.getString("TemplateResRef"), ResourceType::Utd)));
|
||||
auto utdGffs = utd.top();
|
||||
|
||||
pt::ptree scripts;
|
||||
putIfNotEmpty("on_closed", utdGffs->getString("OnClosed"), scripts);
|
||||
putIfNotEmpty("on_damaged", utdGffs->getString("OnDamaged"), scripts);
|
||||
putIfNotEmpty("on_death", utdGffs->getString("OnDeath"), scripts);
|
||||
putIfNotEmpty("on_disarm", utdGffs->getString("OnDisarm"), scripts);
|
||||
putIfNotEmpty("on_hearbeat", utdGffs->getString("OnHeartbeat"), scripts);
|
||||
putIfNotEmpty("on_lock", utdGffs->getString("OnLock"), scripts);
|
||||
putIfNotEmpty("on_melee_attacked", utdGffs->getString("OnMeleeAttacked"), scripts);
|
||||
putIfNotEmpty("on_open", utdGffs->getString("OnOpen"), scripts);
|
||||
putIfNotEmpty("on_spell_cast_at", utdGffs->getString("OnSpellCastAt"), scripts);
|
||||
putIfNotEmpty("on_trap_triggered", utdGffs->getString("OnTrapTriggered"), scripts);
|
||||
putIfNotEmpty("on_unlock", utdGffs->getString("OnUnlock"), scripts);
|
||||
putIfNotEmpty("on_user_defined", utdGffs->getString("OnUserDefined"), scripts);
|
||||
putIfNotEmpty("on_click", utdGffs->getString("OnClick"), scripts);
|
||||
putIfNotEmpty("on_fail_to_open", utdGffs->getString("OnFailToOpen"), scripts);
|
||||
|
||||
pt::ptree blueprint;
|
||||
blueprint.put("resref", utdGffs->getString("TemplateResRef"));
|
||||
blueprint.put("loc_name", getString(utdGffs->getInt("LocName")));
|
||||
blueprint.put("static", utdGffs->getBool("Static"));
|
||||
putIfNotEmpty("conversation", utdGffs->getString("Conversation"), blueprint);
|
||||
blueprint.put("locked", utdGffs->getBool("Locked"));
|
||||
blueprint.put("key_required", utdGffs->getBool("KeyRequired"));
|
||||
if (!scripts.empty()) {
|
||||
blueprint.add_child("scripts", scripts);
|
||||
}
|
||||
|
||||
pt::ptree door;
|
||||
door.put("x", gitDoor.getFloat("X"));
|
||||
door.put("y", gitDoor.getFloat("Y"));
|
||||
door.put("z", gitDoor.getFloat("Z"));
|
||||
door.put("bearing", gitDoor.getFloat("Bearing"));
|
||||
door.put("tag", gitDoor.getString("Tag"));
|
||||
putIfNotEmpty("linked_to_module", gitDoor.getString("LinkedToModule"), door);
|
||||
putIfNotEmpty("linked_to", gitDoor.getString("LinkedTo"), door);
|
||||
door.add_child("blueprint", blueprint);
|
||||
|
||||
return move(door);
|
||||
}
|
||||
|
||||
pt::ptree ModuleProbe::describePlaceable(const GffStruct &gitPlaceable, const TwoDaTable &placeables) {
|
||||
GffFile utp;
|
||||
utp.load(wrap(getResource(gitPlaceable.getString("TemplateResRef"), ResourceType::Utp)));
|
||||
auto utpGffs = utp.top();
|
||||
|
||||
pt::ptree items;
|
||||
for (auto &utcItem : utpGffs->getList("ItemList")) {
|
||||
pt::ptree item;
|
||||
item.put("resref", utcItem->getString("InventoryRes"));
|
||||
items.push_back(make_pair("", item));
|
||||
}
|
||||
|
||||
pt::ptree scripts;
|
||||
putIfNotEmpty("on_closed", utpGffs->getString("OnClosed"), scripts);
|
||||
putIfNotEmpty("on_damaged", utpGffs->getString("OnDamaged"), scripts);
|
||||
putIfNotEmpty("on_death", utpGffs->getString("OnDeath"), scripts);
|
||||
putIfNotEmpty("on_disarm", utpGffs->getString("OnDisarm"), scripts);
|
||||
putIfNotEmpty("on_heartbeat", utpGffs->getString("OnHeartbeat"), scripts);
|
||||
putIfNotEmpty("on_lock", utpGffs->getString("OnLock"), scripts);
|
||||
putIfNotEmpty("on_melee_attacked", utpGffs->getString("OnMeleeAttacked"), scripts);
|
||||
putIfNotEmpty("on_open", utpGffs->getString("OnOpen"), scripts);
|
||||
putIfNotEmpty("on_spell_cast_at", utpGffs->getString("OnSpellCastAt"), scripts);
|
||||
putIfNotEmpty("on_trap_triggered", utpGffs->getString("OnTrapTriggered"), scripts);
|
||||
putIfNotEmpty("on_unlock", utpGffs->getString("OnUnlock"), scripts);
|
||||
putIfNotEmpty("on_user_defined", utpGffs->getString("OnUserDefined"), scripts);
|
||||
putIfNotEmpty("on_end_dialogue", utpGffs->getString("OnEndDialogue"), scripts);
|
||||
putIfNotEmpty("on_inv_disturbed", utpGffs->getString("OnInvDisturbed"), scripts);
|
||||
putIfNotEmpty("on_used", utpGffs->getString("OnUsed"), scripts);
|
||||
|
||||
pt::ptree blueprint;
|
||||
blueprint.put("resref", utpGffs->getString("TemplateResRef"));
|
||||
blueprint.put("tag", utpGffs->getString("Tag"));
|
||||
blueprint.put("loc_name", getString(utpGffs->getInt("LocName")));
|
||||
blueprint.put("appearance", placeables.getString(utpGffs->getInt("Appearance"), "label"));
|
||||
blueprint.put("static", utpGffs->getBool("Static"));
|
||||
putIfNotEmpty("conversation", utpGffs->getString("Conversation"), blueprint);
|
||||
blueprint.put("locked", utpGffs->getBool("Locked"));
|
||||
blueprint.put("key_required", utpGffs->getBool("KeyRequired"));
|
||||
if (!items.empty()) {
|
||||
blueprint.add_child("items", items);
|
||||
}
|
||||
if (!scripts.empty()) {
|
||||
blueprint.add_child("scripts", scripts);
|
||||
}
|
||||
|
||||
pt::ptree placeable;
|
||||
placeable.put("x", gitPlaceable.getFloat("X"));
|
||||
placeable.put("y", gitPlaceable.getFloat("Y"));
|
||||
placeable.put("z", gitPlaceable.getFloat("Z"));
|
||||
placeable.put("bearing", gitPlaceable.getFloat("Bearing"));
|
||||
placeable.add_child("blueprint", blueprint);
|
||||
|
||||
return move(placeable);
|
||||
}
|
||||
|
||||
pt::ptree ModuleProbe::describeTrigger(const GffStruct &gitTrigger) {
|
||||
GffFile utt;
|
||||
utt.load(wrap(getResource(gitTrigger.getString("TemplateResRef"), ResourceType::Utt)));
|
||||
auto uttGffs = utt.top();
|
||||
|
||||
pt::ptree position;
|
||||
position.put("x", gitTrigger.getFloat("XPosition"));
|
||||
position.put("y", gitTrigger.getFloat("YPosition"));
|
||||
position.put("z", gitTrigger.getFloat("ZPosition"));
|
||||
|
||||
pt::ptree orientation;
|
||||
orientation.put("x", gitTrigger.getFloat("XOrientation"));
|
||||
orientation.put("y", gitTrigger.getFloat("YOrientation"));
|
||||
orientation.put("z", gitTrigger.getFloat("ZOrientation"));
|
||||
|
||||
pt::ptree geometry;
|
||||
for (auto &gitPoint : gitTrigger.getList("Geometry")) {
|
||||
pt::ptree point;
|
||||
point.put("point_x", gitPoint->getFloat("PointX"));
|
||||
point.put("point_y", gitPoint->getFloat("PointY"));
|
||||
point.put("point_z", gitPoint->getFloat("PointZ"));
|
||||
geometry.push_back(make_pair("", point));
|
||||
}
|
||||
|
||||
pt::ptree scripts;
|
||||
putIfNotEmpty("on_disarm", uttGffs->getString("OnDisarm"), scripts);
|
||||
putIfNotEmpty("on_trap_triggered", uttGffs->getString("OnTrapTriggered"), scripts);
|
||||
putIfNotEmpty("on_click", uttGffs->getString("OnClick"), scripts);
|
||||
putIfNotEmpty("heartbeat", uttGffs->getString("ScriptHeartbeat"), scripts);
|
||||
putIfNotEmpty("on_enter", uttGffs->getString("ScriptOnEnter"), scripts);
|
||||
putIfNotEmpty("on_exit", uttGffs->getString("ScriptOnExit"), scripts);
|
||||
putIfNotEmpty("user_define", uttGffs->getString("ScriptUserDefine"), scripts);
|
||||
|
||||
pt::ptree blueprint;
|
||||
blueprint.put("resref", uttGffs->getString("TemplateResRef"));
|
||||
blueprint.put("tag", uttGffs->getString("Tag"));
|
||||
blueprint.put("localized_name", getString(uttGffs->getInt("LocalizedName")));
|
||||
if (!scripts.empty()) {
|
||||
blueprint.add_child("scripts", scripts);
|
||||
}
|
||||
|
||||
pt::ptree trigger;
|
||||
trigger.add_child("position", position);
|
||||
trigger.add_child("orientation", orientation);
|
||||
trigger.add_child("geometry", geometry);
|
||||
trigger.add_child("blueprint", blueprint);
|
||||
|
||||
return move(trigger);
|
||||
}
|
||||
|
||||
pt::ptree ModuleProbe::describeWaypoint(const GffStruct &gitWaypoint) {
|
||||
pt::ptree position;
|
||||
position.put("x", gitWaypoint.getFloat("XPosition"));
|
||||
position.put("y", gitWaypoint.getFloat("YPosition"));
|
||||
position.put("z", gitWaypoint.getFloat("ZPosition"));
|
||||
|
||||
pt::ptree orientation;
|
||||
orientation.put("x", gitWaypoint.getFloat("XOrientation"));
|
||||
orientation.put("y", gitWaypoint.getFloat("YOrientation"));
|
||||
|
||||
pt::ptree waypoint;
|
||||
waypoint.put("blueprint_resref", gitWaypoint.getString("TemplateResRef"));
|
||||
waypoint.put("tag", gitWaypoint.getString("Tag"));
|
||||
waypoint.put("localized_name", getString(gitWaypoint.getInt("LocalizedName")));
|
||||
waypoint.add_child("position", position);
|
||||
waypoint.add_child("orientation", orientation);
|
||||
|
||||
return move(waypoint);
|
||||
}
|
||||
|
||||
pt::ptree ModuleProbe::describeSound(const GffStruct &gitSound) {
|
||||
GffFile uts;
|
||||
uts.load(wrap(getResource(gitSound.getString("TemplateResRef"), ResourceType::Uts)));
|
||||
auto utsGffs = uts.top();
|
||||
|
||||
pt::ptree position;
|
||||
position.put("x", gitSound.getFloat("XPosition"));
|
||||
position.put("y", gitSound.getFloat("YPosition"));
|
||||
position.put("z", gitSound.getFloat("ZPosition"));
|
||||
|
||||
TwoDaFile priorityGroups;
|
||||
priorityGroups.load(wrap(getResource("prioritygroups", ResourceType::TwoDa)));
|
||||
auto priorityGroupsTable = priorityGroups.table();
|
||||
|
||||
pt::ptree sounds;
|
||||
for (auto &utsSound : utsGffs->getList("Sounds")) {
|
||||
pt::ptree sound;
|
||||
sound.put("resref", utsSound->getString("Sound"));
|
||||
sounds.push_back(make_pair("", sound));
|
||||
}
|
||||
|
||||
pt::ptree blueprint;
|
||||
blueprint.put("resref", utsGffs->getString("TemplateResRef"));
|
||||
blueprint.put("tag", utsGffs->getString("Tag"));
|
||||
blueprint.put("loc_name", getString(utsGffs->getInt("LocName")));
|
||||
blueprint.put("active", utsGffs->getBool("Active"));
|
||||
blueprint.put("continous", utsGffs->getBool("Continous"));
|
||||
blueprint.put("looping", utsGffs->getBool("Looping"));
|
||||
blueprint.put("positional", utsGffs->getBool("Positional"));
|
||||
blueprint.put("priority", priorityGroupsTable->getString(utsGffs->getInt("Priority"), "label"));
|
||||
blueprint.put("interval", utsGffs->getInt("Interval"));
|
||||
if (!sounds.empty()) {
|
||||
blueprint.add_child("sounds", sounds);
|
||||
}
|
||||
|
||||
pt::ptree sound;
|
||||
sound.add_child("position", position);
|
||||
sound.add_child("blueprint", blueprint);
|
||||
|
||||
return move(sound);
|
||||
}
|
||||
|
||||
void ModuleProbe::writeDescription(const string &moduleName, const pt::ptree &tree, const fs::path &destPath) {
|
||||
fs::path jsonPath(destPath);
|
||||
jsonPath.append(moduleName + ".json");
|
||||
|
||||
fs::ofstream json(jsonPath);
|
||||
pt::write_json(json, tree);
|
||||
}
|
||||
|
||||
shared_ptr<ByteArray> ModuleProbe::getResource(const string &resRef, ResourceType type) {
|
||||
shared_ptr<ByteArray> result;
|
||||
string cacheKey(resRef + "." + to_string(static_cast<int>(type)));
|
||||
|
||||
auto maybeResource = _resourceCache.find(cacheKey);
|
||||
if (maybeResource != _resourceCache.end()) return maybeResource->second;
|
||||
|
||||
result = _rimMain.find(resRef, type);
|
||||
|
||||
if (!result) {
|
||||
result = _rimBlueprints.find(resRef, type);
|
||||
}
|
||||
if (!result) {
|
||||
result = _keyBifProvider.find(resRef, type);
|
||||
}
|
||||
if (!result) {
|
||||
throw runtime_error("Resource not found: " + resRef + " " + to_string(static_cast<int>(type)));
|
||||
}
|
||||
|
||||
_resourceCache.insert(make_pair(cacheKey, result));
|
||||
|
||||
return move(result);
|
||||
}
|
||||
|
||||
string ModuleProbe::getString(int strRef) const {
|
||||
if (strRef == -1) return "";
|
||||
|
||||
return _talkTable->getString(strRef).text;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
|
@ -1,74 +0,0 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "../src/resource/format/2dafile.h"
|
||||
#include "../src/resource/format/gfffile.h"
|
||||
#include "../src/resource/keybifprovider.h"
|
||||
#include "../src/resource/format/rimfile.h"
|
||||
#include "../src/resource/format/tlkfile.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tools {
|
||||
|
||||
/**
|
||||
* Tool to process a game module and produce a JSON file describing it.
|
||||
*/
|
||||
class ModuleProbe {
|
||||
public:
|
||||
/**
|
||||
* Finds a module with the specified name in the specified game path,
|
||||
* processes it and produces a JSON file in the specified destination
|
||||
* directory, describing it.
|
||||
*/
|
||||
void probe(const std::string &name, const boost::filesystem::path &gamePath, const boost::filesystem::path &destPath);
|
||||
|
||||
private:
|
||||
boost::filesystem::path _gamePath;
|
||||
resource::KeyBifResourceProvider _keyBifProvider;
|
||||
std::shared_ptr<resource::TalkTable> _talkTable;
|
||||
resource::RimFile _rimMain;
|
||||
resource::RimFile _rimBlueprints;
|
||||
std::unordered_map<std::string, std::shared_ptr<ByteArray>> _resourceCache;
|
||||
|
||||
void loadResources(const std::string &moduleName);
|
||||
boost::property_tree::ptree describeModule();
|
||||
boost::property_tree::ptree describeArea(const std::string &name, const resource::TwoDaTable &appearance, const resource::TwoDaTable &placeables);
|
||||
boost::property_tree::ptree describeCreature(const resource::GffStruct &gitCreature, const resource::TwoDaTable &appearance);
|
||||
boost::property_tree::ptree describeDoor(const resource::GffStruct &gitDoor);
|
||||
boost::property_tree::ptree describePlaceable(const resource::GffStruct &gitPlaceable, const resource::TwoDaTable &placeables);
|
||||
boost::property_tree::ptree describeTrigger(const resource::GffStruct &gitTrigger);
|
||||
boost::property_tree::ptree describeWaypoint(const resource::GffStruct &gitWaypoint);
|
||||
boost::property_tree::ptree describeSound(const resource::GffStruct &gitSound);
|
||||
void writeDescription(const std::string &moduleName, const boost::property_tree::ptree &tree, const boost::filesystem::path &destPath);
|
||||
|
||||
std::shared_ptr<ByteArray> getResource(const std::string &resRef, resource::ResourceType type);
|
||||
std::string getString(int strRef) const;
|
||||
};
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
|
@ -17,14 +17,17 @@
|
|||
|
||||
#include "program.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <iostream>
|
||||
#include <unordered_map>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
#include <boost/program_options.hpp>
|
||||
|
||||
#include "../src/common/pathutil.h"
|
||||
|
||||
#include "moduleprobe.h"
|
||||
#include "tools.h"
|
||||
#include "types.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -39,6 +42,16 @@ namespace tools {
|
|||
|
||||
static const char kConfigFilename[] = "reone-tools.cfg";
|
||||
|
||||
static const unordered_map<string, Operation> g_operations {
|
||||
{ "list", Operation::List },
|
||||
{ "extract", Operation::Extract },
|
||||
{ "to-json", Operation::ToJSON },
|
||||
{ "to-tga", Operation::ToTGA },
|
||||
{ "to-2da", Operation::To2DA },
|
||||
{ "to-gff", Operation::ToGFF },
|
||||
{ "to-tlk", Operation::ToTLK }
|
||||
};
|
||||
|
||||
Program::Program(int argc, char **argv) : _argc(argc), _argv(argv) {
|
||||
}
|
||||
|
||||
|
@ -46,44 +59,21 @@ int Program::run() {
|
|||
initOptions();
|
||||
loadOptions();
|
||||
determineGameID();
|
||||
loadTools();
|
||||
|
||||
switch (_command) {
|
||||
case Command::List:
|
||||
case Command::Extract:
|
||||
case Command::ToJson:
|
||||
case Command::ToTga:
|
||||
case Command::ToTwoDa:
|
||||
case Command::ToGFF:
|
||||
initFileTool();
|
||||
switch (_command) {
|
||||
case Command::List:
|
||||
_tool->list(_target, _keyPath);
|
||||
break;
|
||||
case Command::Extract:
|
||||
_tool->extract(_target, _keyPath, _destPath);
|
||||
break;
|
||||
case Command::ToJson:
|
||||
_tool->toJson(_target, _destPath);
|
||||
break;
|
||||
case Command::ToTga:
|
||||
_tool->toTga(_target, _destPath);
|
||||
break;
|
||||
case Command::ToTwoDa:
|
||||
_tool->to2DA(_target, _destPath);
|
||||
break;
|
||||
case Command::ToGFF:
|
||||
_tool->toGFF(_target, _destPath);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case Command::ModuleProbe:
|
||||
ModuleProbe().probe(_target, _gamePath, _destPath);
|
||||
break;
|
||||
default:
|
||||
switch (_operation) {
|
||||
case Operation::None:
|
||||
cout << _cmdLineOpts << endl;
|
||||
break;
|
||||
default: {
|
||||
auto tool = getTool();
|
||||
if (tool) {
|
||||
tool->invoke(_operation, _target, _gamePath, _destPath);
|
||||
} else {
|
||||
cout << "Unable to choose a tool for the specified operation" << endl;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@ -95,14 +85,13 @@ void Program::initOptions() {
|
|||
("dest", po::value<string>(), "path to destination directory");
|
||||
|
||||
_cmdLineOpts.add(_commonOpts).add_options()
|
||||
("help", "print this message")
|
||||
("list", "list file contents")
|
||||
("extract", "extract file contents")
|
||||
("to-json", "convert 2DA, GFF or TLK file to JSON")
|
||||
("to-tga", "convert TPC image to TGA")
|
||||
("to-2da", "convert JSON to 2DA")
|
||||
("to-gff", "convert JSON to GFF")
|
||||
("modprobe", "probe module and produce a JSON file describing it")
|
||||
("to-tlk", "convert JSON to TLK")
|
||||
("target", po::value<string>(), "target name or path to input file");
|
||||
}
|
||||
|
||||
|
@ -137,24 +126,13 @@ void Program::loadOptions() {
|
|||
_gamePath = vars.count("game") > 0 ? vars["game"].as<string>() : fs::current_path();
|
||||
_destPath = getDestination(vars);
|
||||
_target = vars.count("target") > 0 ? vars["target"].as<string>() : "";
|
||||
_keyPath = getPathIgnoreCase(_gamePath, "chitin.key");
|
||||
|
||||
if (vars.count("help")) {
|
||||
_command = Command::Help;
|
||||
} else if (vars.count("list")) {
|
||||
_command = Command::List;
|
||||
} else if (vars.count("extract")) {
|
||||
_command = Command::Extract;
|
||||
} else if (vars.count("to-json")) {
|
||||
_command = Command::ToJson;
|
||||
} else if (vars.count("to-tga")) {
|
||||
_command = Command::ToTga;
|
||||
} else if (vars.count("to-2da")) {
|
||||
_command = Command::ToTwoDa;
|
||||
} else if (vars.count("to-gff")) {
|
||||
_command = Command::ToGFF;
|
||||
} else if (vars.count("modprobe")) {
|
||||
_command = Command::ModuleProbe;
|
||||
// Determine operation from program options
|
||||
for (auto &operation : g_operations) {
|
||||
if (vars.count(operation.first)) {
|
||||
_operation = operation.second;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -163,22 +141,21 @@ void Program::determineGameID() {
|
|||
_gameId = exePath.empty() ? GameID::KotOR : GameID::TSL;
|
||||
}
|
||||
|
||||
void Program::initFileTool() {
|
||||
switch (_command) {
|
||||
case Command::List:
|
||||
case Command::Extract:
|
||||
case Command::ToJson:
|
||||
case Command::ToTga:
|
||||
case Command::ToTwoDa:
|
||||
case Command::ToGFF:
|
||||
if (!fs::exists(_target)) {
|
||||
throw runtime_error("Input file does not exist: " + _target);
|
||||
}
|
||||
_tool = getFileToolByPath(_gameId, _target);
|
||||
break;
|
||||
default:
|
||||
throw logic_error("Unsupported file tool command: " + to_string(static_cast<int>(_command)));
|
||||
void Program::loadTools() {
|
||||
_tools.push_back(make_shared<KeyBifTool>());
|
||||
_tools.push_back(make_shared<ErfTool>());
|
||||
_tools.push_back(make_shared<RimTool>());
|
||||
_tools.push_back(make_shared<TwoDaTool>());
|
||||
_tools.push_back(make_shared<TlkTool>());
|
||||
_tools.push_back(make_shared<GffTool>());
|
||||
_tools.push_back(make_shared<TpcTool>());
|
||||
}
|
||||
|
||||
shared_ptr<ITool> Program::getTool() const {
|
||||
for (auto &tool : _tools) {
|
||||
if (tool->supports(_operation, _target)) return tool;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
|
|
@ -23,7 +23,10 @@
|
|||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/program_options/options_description.hpp>
|
||||
|
||||
#include "../src/resource/types.h"
|
||||
|
||||
#include "tools.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
|
@ -36,35 +39,18 @@ public:
|
|||
int run();
|
||||
|
||||
private:
|
||||
enum class Command {
|
||||
None,
|
||||
Help,
|
||||
List,
|
||||
Extract,
|
||||
ToJson,
|
||||
ToTga,
|
||||
ToTwoDa,
|
||||
ToGFF,
|
||||
ModuleProbe
|
||||
};
|
||||
int _argc;
|
||||
char **_argv;
|
||||
|
||||
boost::program_options::options_description _commonOpts;
|
||||
boost::program_options::options_description _cmdLineOpts { "Usage" };
|
||||
|
||||
boost::filesystem::path _gamePath;
|
||||
boost::filesystem::path _destPath;
|
||||
std::string _target;
|
||||
boost::filesystem::path _keyPath;
|
||||
Operation _operation { Operation::None };
|
||||
resource::GameID _gameId { resource::GameID::KotOR };
|
||||
Command _command { Command::None };
|
||||
std::unique_ptr<FileTool> _tool;
|
||||
|
||||
// Command line arguments
|
||||
|
||||
int _argc { 0 };
|
||||
char **_argv { nullptr };
|
||||
|
||||
// END Command line arguments
|
||||
|
||||
boost::program_options::options_description _commonOpts;
|
||||
boost::program_options::options_description _cmdLineOpts { "Usage" };
|
||||
std::vector<std::shared_ptr<ITool>> _tools;
|
||||
|
||||
Program(const Program &) = delete;
|
||||
Program &operator=(const Program &) = delete;
|
||||
|
@ -72,7 +58,9 @@ private:
|
|||
void initOptions();
|
||||
void loadOptions();
|
||||
void determineGameID();
|
||||
void initFileTool();
|
||||
void loadTools();
|
||||
|
||||
std::shared_ptr<ITool> getTool() const;
|
||||
};
|
||||
|
||||
} // namespace tools
|
||||
|
|
|
@ -1,26 +1,24 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
#include <iostream>
|
||||
|
||||
#include "../src/common/log.h"
|
||||
#include "../src/resource/format/rimfile.h"
|
||||
#include "../src/resource/typeutil.h"
|
||||
|
||||
using namespace std;
|
||||
|
@ -33,33 +31,47 @@ namespace reone {
|
|||
|
||||
namespace tools {
|
||||
|
||||
void RimTool::list(const fs::path &path, const fs::path &keyPath) const {
|
||||
void RimTool::invoke(Operation operation, const fs::path &target, const fs::path &gamePath, const fs::path &destPath) {
|
||||
RimFile rim;
|
||||
rim.load(path);
|
||||
rim.load(target);
|
||||
|
||||
for (auto &res : rim.resources()) {
|
||||
info(boost::format("%16s\t%4s") % res.resRef % getExtByResType(res.type));
|
||||
if (operation == Operation::List) {
|
||||
list(rim);
|
||||
} else if (operation == Operation::Extract) {
|
||||
extract(rim, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
void RimTool::extract(const fs::path &path, const fs::path &keyPath, const fs::path &destPath) const {
|
||||
RimFile rim;
|
||||
rim.load(path);
|
||||
void RimTool::list(const RimFile &rim) {
|
||||
for (auto &res : rim.resources()) {
|
||||
cout << res.resRef << " " << getExtByResType(res.resType) << endl;
|
||||
}
|
||||
}
|
||||
|
||||
auto &resources = rim.resources();
|
||||
for (int i = 0; i < resources.size(); ++i) {
|
||||
const RimFile::Resource &resource = resources[i];
|
||||
info(boost::format("Extracting %16s\t%4s") % resource.resRef % getExtByResType(resource.type));
|
||||
void RimTool::extract(RimFile &rim, const fs::path &destPath) {
|
||||
if (!fs::is_directory(destPath) || !fs::exists(destPath)) return;
|
||||
|
||||
for (size_t i = 0; i < rim.resources().size(); ++i) {
|
||||
const RimFile::Resource &resEntry = rim.resources()[i];
|
||||
string ext(getExtByResType(resEntry.resType));
|
||||
cout << "Extracting " << resEntry.resRef << " " << ext << endl;
|
||||
ByteArray data(rim.getResourceData(i));
|
||||
|
||||
fs::path resPath(destPath);
|
||||
resPath.append(resource.resRef + "." + getExtByResType(resource.type));
|
||||
resPath.append(resEntry.resRef + "." + ext);
|
||||
|
||||
ByteArray data(rim.getResourceData(i));
|
||||
fs::ofstream res(resPath, ios::binary);
|
||||
res.write(data.data(), data.size());
|
||||
res.write(&data[0], data.size());
|
||||
}
|
||||
}
|
||||
|
||||
bool RimTool::supports(Operation operation, const fs::path &target) const {
|
||||
return
|
||||
!fs::is_directory(target) &&
|
||||
target.extension() == ".rim" &&
|
||||
(operation == Operation::List || operation == Operation::Extract);
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -32,7 +32,13 @@ namespace reone {
|
|||
|
||||
namespace tools {
|
||||
|
||||
void TlkTool::toJson(const fs::path &path, const fs::path &destPath) const {
|
||||
void TlkTool::invoke(Operation operation, const fs::path &target, const fs::path &gamePath, const fs::path &destPath) {
|
||||
if (operation == Operation::ToJSON) {
|
||||
toJSON(target, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
void TlkTool::toJSON(const fs::path &path, const fs::path &destPath) {
|
||||
pt::ptree tree;
|
||||
pt::ptree children;
|
||||
|
||||
|
@ -58,6 +64,13 @@ void TlkTool::toJson(const fs::path &path, const fs::path &destPath) const {
|
|||
pt::write_json(json, tree);
|
||||
}
|
||||
|
||||
bool TlkTool::supports(Operation operation, const fs::path &target) const {
|
||||
return
|
||||
!fs::is_directory(target) &&
|
||||
target.extension() == ".tlk" &&
|
||||
operation == Operation::ToJSON;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -1,90 +0,0 @@
|
|||
/*
|
||||
* 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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::resource;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tools {
|
||||
|
||||
void FileTool::list(const fs::path &path, const fs::path &keyPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
void FileTool::throwNotImplemented() const {
|
||||
throw logic_error("Not implemented");
|
||||
}
|
||||
|
||||
void FileTool::extract(const fs::path &path, const fs::path &keyPath, const fs::path &destPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
void FileTool::toJson(const fs::path &path, const fs::path &destPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
void FileTool::toTga(const fs::path &path, const fs::path &destPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
void FileTool::to2DA(const fs::path &path, const fs::path &destPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
void FileTool::toGFF(const fs::path &path, const fs::path &destPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
unique_ptr<FileTool> getFileToolByPath(GameID gameId, const fs::path &path) {
|
||||
if (fs::is_directory(path)) {
|
||||
throw invalid_argument("path must not point to a directory");
|
||||
}
|
||||
unique_ptr<FileTool> result;
|
||||
|
||||
string ext(path.extension().string());
|
||||
if (ext == ".key") {
|
||||
result = make_unique<KeyTool>();
|
||||
} else if (ext == ".bif") {
|
||||
result = make_unique<BifTool>();
|
||||
} else if (ext == ".erf" || ext == ".mod" || ext == ".sav") {
|
||||
result = make_unique<ErfTool>();
|
||||
} else if (ext == ".rim") {
|
||||
result = make_unique<RimTool>();
|
||||
} else if (ext == ".2da") {
|
||||
result = make_unique<TwoDaTool>();
|
||||
} else if (ext == ".tlk") {
|
||||
result = make_unique<TlkTool>();
|
||||
} else if (ext == ".tpc") {
|
||||
result = make_unique<TpcTool>();
|
||||
} else if (ext == ".json") {
|
||||
result = make_unique<JsonTool>();
|
||||
} else {
|
||||
result = make_unique<GffTool>();
|
||||
}
|
||||
|
||||
return move(result);
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
173
tools/tools.h
173
tools/tools.h
|
@ -17,105 +17,132 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/property_tree/ptree.hpp>
|
||||
|
||||
#include "../src/resource/format/2dafile.h"
|
||||
#include "../src/resource/format/biffile.h"
|
||||
#include "../src/resource/format/erffile.h"
|
||||
#include "../src/resource/format/keyfile.h"
|
||||
#include "../src/resource/format/rimfile.h"
|
||||
|
||||
#include "types.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace resource {
|
||||
|
||||
class GffStruct;
|
||||
|
||||
}
|
||||
|
||||
namespace tools {
|
||||
|
||||
/**
|
||||
* Abstract tool, operating on a single file. Each implementation covers a
|
||||
* particular file format.
|
||||
*
|
||||
* Operations:
|
||||
* - list - list file contents
|
||||
* - extract - extract file contents
|
||||
* - to-json - convert file to JSON
|
||||
* - to-tga - convert image to TGA
|
||||
* - to-2da - convert file to 2DA
|
||||
* - to-gff - convert file to GFF
|
||||
*/
|
||||
class FileTool {
|
||||
class ITool {
|
||||
public:
|
||||
virtual void list(const boost::filesystem::path &path, const boost::filesystem::path &keyPath) const;
|
||||
virtual void extract(const boost::filesystem::path &path, const boost::filesystem::path &keyPath, const boost::filesystem::path &destPath) const;
|
||||
virtual void toJson(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const;
|
||||
virtual void toTga(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const;
|
||||
virtual void to2DA(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const;
|
||||
virtual void toGFF(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const;
|
||||
virtual void invoke(
|
||||
Operation operation,
|
||||
const boost::filesystem::path &target,
|
||||
const boost::filesystem::path &gamePath,
|
||||
const boost::filesystem::path &destPath) = 0;
|
||||
|
||||
virtual bool supports(Operation operation, const boost::filesystem::path &target) const = 0;
|
||||
};
|
||||
|
||||
class KeyBifTool : public ITool {
|
||||
public:
|
||||
void invoke(
|
||||
Operation operation,
|
||||
const boost::filesystem::path &target,
|
||||
const boost::filesystem::path &gamePath,
|
||||
const boost::filesystem::path &destPath) override;
|
||||
|
||||
bool supports(Operation operation, const boost::filesystem::path &target) const override;
|
||||
|
||||
private:
|
||||
void throwNotImplemented() const;
|
||||
void listKEY(const resource::KeyFile &key);
|
||||
void listBIF(const resource::KeyFile &key, const resource::BifFile &bif, int bifIdx);
|
||||
void extractBIF(const resource::KeyFile &key, resource::BifFile &bif, int bifIdx, const boost::filesystem::path &destPath);
|
||||
};
|
||||
|
||||
std::unique_ptr<FileTool> getFileToolByPath(resource::GameID gameId, const boost::filesystem::path &path);
|
||||
|
||||
class KeyTool : public FileTool {
|
||||
class ErfTool : public ITool {
|
||||
public:
|
||||
void list(const boost::filesystem::path &path, const boost::filesystem::path &keyPath) const override;
|
||||
};
|
||||
void invoke(
|
||||
Operation operation,
|
||||
const boost::filesystem::path &target,
|
||||
const boost::filesystem::path &gamePath,
|
||||
const boost::filesystem::path &destPath) override;
|
||||
|
||||
class BifTool : public FileTool {
|
||||
public:
|
||||
void list(const boost::filesystem::path &path, const boost::filesystem::path &keyPath) const override;
|
||||
void extract(const boost::filesystem::path &path, const boost::filesystem::path &keyPath, const boost::filesystem::path &destPath) const override;
|
||||
bool supports(Operation operation, const boost::filesystem::path &target) const override;
|
||||
|
||||
private:
|
||||
int getFileIndexByFilename(const std::vector<resource::KeyFile::FileEntry> &files, const std::string &filename) const;
|
||||
void list(const resource::ErfFile &erf);
|
||||
void extract(resource::ErfFile &erf, const boost::filesystem::path &destPath);
|
||||
};
|
||||
|
||||
class ErfTool : public FileTool {
|
||||
class RimTool : public ITool {
|
||||
public:
|
||||
void list(const boost::filesystem::path &path, const boost::filesystem::path &keyPath) const override;
|
||||
void extract(const boost::filesystem::path &path, const boost::filesystem::path &keyPath, const boost::filesystem::path &destPath) const override;
|
||||
};
|
||||
void invoke(
|
||||
Operation operation,
|
||||
const boost::filesystem::path &target,
|
||||
const boost::filesystem::path &gamePath,
|
||||
const boost::filesystem::path &destPath) override;
|
||||
|
||||
class RimTool : public FileTool {
|
||||
public:
|
||||
void list(const boost::filesystem::path &path, const boost::filesystem::path &keyPath) const override;
|
||||
void extract(const boost::filesystem::path &path, const boost::filesystem::path &keyPath, const boost::filesystem::path &destPath) const override;
|
||||
};
|
||||
|
||||
class TwoDaTool : public FileTool {
|
||||
public:
|
||||
void toJson(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
};
|
||||
|
||||
class TlkTool : public FileTool {
|
||||
public:
|
||||
void toJson(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
};
|
||||
|
||||
class JsonTool : public FileTool {
|
||||
public:
|
||||
void to2DA(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
void toGFF(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
};
|
||||
|
||||
class GffTool : public FileTool {
|
||||
public:
|
||||
void toJson(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
bool supports(Operation operation, const boost::filesystem::path &target) const override;
|
||||
|
||||
private:
|
||||
boost::property_tree::ptree getPropertyTree(const resource::GffStruct &gffs) const;
|
||||
void list(const resource::RimFile &rim);
|
||||
void extract(resource::RimFile &rim, const boost::filesystem::path &destPath);
|
||||
};
|
||||
|
||||
class TpcTool : public FileTool {
|
||||
class TwoDaTool : public ITool {
|
||||
public:
|
||||
void toTga(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
void invoke(
|
||||
Operation operation,
|
||||
const boost::filesystem::path &target,
|
||||
const boost::filesystem::path &gamePath,
|
||||
const boost::filesystem::path &destPath) override;
|
||||
|
||||
bool supports(Operation operation, const boost::filesystem::path &target) const override;
|
||||
|
||||
private:
|
||||
void toJSON(const boost::filesystem::path &path, const boost::filesystem::path &destPath);
|
||||
void to2DA(const boost::filesystem::path &path, const boost::filesystem::path &destPath);
|
||||
};
|
||||
|
||||
class GffTool : public ITool {
|
||||
public:
|
||||
void invoke(
|
||||
Operation operation,
|
||||
const boost::filesystem::path &target,
|
||||
const boost::filesystem::path &gamePath,
|
||||
const boost::filesystem::path &destPath) override;
|
||||
|
||||
bool supports(Operation operation, const boost::filesystem::path &target) const override;
|
||||
|
||||
private:
|
||||
void toJSON(const boost::filesystem::path &path, const boost::filesystem::path &destPath);
|
||||
};
|
||||
|
||||
class TlkTool : public ITool {
|
||||
public:
|
||||
void invoke(
|
||||
Operation operation,
|
||||
const boost::filesystem::path &target,
|
||||
const boost::filesystem::path &gamePath,
|
||||
const boost::filesystem::path &destPath) override;
|
||||
|
||||
bool supports(Operation operation, const boost::filesystem::path &target) const override;
|
||||
|
||||
private:
|
||||
void toJSON(const boost::filesystem::path &path, const boost::filesystem::path &destPath);
|
||||
};
|
||||
|
||||
class TpcTool : public ITool {
|
||||
public:
|
||||
void invoke(
|
||||
Operation operation,
|
||||
const boost::filesystem::path &target,
|
||||
const boost::filesystem::path &gamePath,
|
||||
const boost::filesystem::path &destPath) override;
|
||||
|
||||
bool supports(Operation operation, const boost::filesystem::path &target) const override;
|
||||
|
||||
private:
|
||||
void toTGA(const boost::filesystem::path &path, const boost::filesystem::path &destPath);
|
||||
};
|
||||
|
||||
} // namespace tools
|
||||
|
|
|
@ -40,6 +40,12 @@ namespace reone {
|
|||
|
||||
namespace tools {
|
||||
|
||||
void TpcTool::invoke(Operation operation, const fs::path &target, const fs::path &gamePath, const fs::path &destPath) {
|
||||
if (operation == Operation::ToTGA) {
|
||||
toTGA(target, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
static void convertTpcToTga(const Texture &texture, TGAHeader &header, TGAData &data) {
|
||||
int layerCount = static_cast<int>(texture.layers().size());
|
||||
if (layerCount == 0) {
|
||||
|
@ -124,7 +130,7 @@ static void convertTpcToTga(const Texture &texture, TGAHeader &header, TGAData &
|
|||
data.flags = TGA_IMAGE_DATA | TGA_RGB;
|
||||
}
|
||||
|
||||
void TpcTool::toTga(const fs::path &path, const fs::path &destPath) const {
|
||||
void TpcTool::toTGA(const fs::path &path, const fs::path &destPath) {
|
||||
// Read TPC
|
||||
|
||||
TpcFile tpc("", TextureType::GUI, true);
|
||||
|
@ -161,6 +167,13 @@ void TpcTool::toTga(const fs::path &path, const fs::path &destPath) const {
|
|||
TGAClose(tga);
|
||||
}
|
||||
|
||||
bool TpcTool::supports(Operation operation, const fs::path &target) const {
|
||||
return
|
||||
!fs::is_directory(target) &&
|
||||
target.extension() == ".tpc" &&
|
||||
operation == Operation::ToTGA;
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
||||
} // namespace reone
|
||||
|
|
|
@ -15,28 +15,22 @@
|
|||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "tools.h"
|
||||
|
||||
#include <boost/format.hpp>
|
||||
|
||||
#include "../src/common/log.h"
|
||||
|
||||
using namespace reone::resource;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
#pragma once
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace tools {
|
||||
|
||||
void KeyTool::list(const fs::path &path, const fs::path &keyPath) const {
|
||||
KeyFile key;
|
||||
key.load(path);
|
||||
|
||||
for (auto &file : key.files()) {
|
||||
info(file.filename);
|
||||
}
|
||||
}
|
||||
enum class Operation {
|
||||
None,
|
||||
List,
|
||||
Extract,
|
||||
ToJSON,
|
||||
ToTGA,
|
||||
To2DA,
|
||||
ToGFF,
|
||||
ToTLK
|
||||
};
|
||||
|
||||
} // namespace tools
|
||||
|
Loading…
Reference in a new issue