refactor: Streamline and cleanup tools

This commit is contained in:
Vsevolod Kremianskii 2021-02-09 14:53:53 +07:00
parent 41dca204fa
commit 2f4d568c15
19 changed files with 472 additions and 1227 deletions

View file

@ -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})

View file

@ -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;

View file

@ -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; }

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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
View 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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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