feat: Add ModuleProbe tool
ModuleProbe generates a JSON file describing a game module.
This commit is contained in:
parent
ceb4c21387
commit
6fd165cd38
7 changed files with 691 additions and 50 deletions
|
@ -659,6 +659,7 @@ endif()
|
|||
|
||||
if(BUILD_TOOLS)
|
||||
set(TOOLS_HEADERS
|
||||
tools/moduleprobe.h
|
||||
tools/program.h
|
||||
tools/tools.h)
|
||||
|
||||
|
@ -669,6 +670,7 @@ if(BUILD_TOOLS)
|
|||
tools/erftool.cpp
|
||||
tools/gfftool.cpp
|
||||
tools/keytool.cpp
|
||||
tools/moduleprobe.cpp
|
||||
tools/program.cpp
|
||||
tools/rimtool.cpp
|
||||
tools/tlktool.cpp
|
||||
|
|
540
tools/moduleprobe.cpp
Normal file
540
tools/moduleprobe.cpp
Normal file
|
@ -0,0 +1,540 @@
|
|||
/*
|
||||
* 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/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;
|
||||
loadResources(name);
|
||||
pt::ptree description(describeModule());
|
||||
writeDescription(name, description, destPath);
|
||||
}
|
||||
|
||||
void ModuleProbe::loadResources(const string &moduleName) {
|
||||
fs::path keyPath(getPathIgnoreCase(_gamePath, "chitin.key", false));
|
||||
if (!fs::exists(keyPath)) {
|
||||
throw runtime_error("Key file not found: " + keyPath.string());
|
||||
}
|
||||
_keyFile.load(keyPath);
|
||||
|
||||
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::ModuleInfo)));
|
||||
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();
|
||||
|
||||
// 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)));
|
||||
}
|
||||
description.add_child("areas", areas);
|
||||
|
||||
// END Areas
|
||||
|
||||
return move(description);
|
||||
}
|
||||
|
||||
pt::ptree ModuleProbe::describeArea(const string &name, const TwoDaTable &appearance) {
|
||||
GffFile are;
|
||||
are.load(wrap(_rimMain.find(name, ResourceType::Area)));
|
||||
auto areGffs = are.top();
|
||||
|
||||
GffFile git;
|
||||
git.load(wrap(_rimMain.find(name, ResourceType::GameInstance)));
|
||||
auto gitGffs = git.top();
|
||||
|
||||
LytFile lyt;
|
||||
lyt.load(wrap(getResource(name, ResourceType::AreaLayout)));
|
||||
|
||||
// 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 placeables;
|
||||
for (auto &gitPlaceable : gitGffs->getList("Placeable List")) {
|
||||
placeables.push_back(make_pair("", describePlaceable(*gitPlaceable)));
|
||||
}
|
||||
|
||||
// END Placeables
|
||||
|
||||
// Triggers
|
||||
|
||||
pt::ptree triggers;
|
||||
for (auto &gitTrigger : gitGffs->getList("TriggerList")) {
|
||||
triggers.push_back(make_pair("", describeTrigger(*gitTrigger)));
|
||||
}
|
||||
|
||||
// END Triggers
|
||||
|
||||
// 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")));
|
||||
area.add_child("rooms", rooms);
|
||||
area.add_child("creatures", creatures);
|
||||
area.add_child("doors", doors);
|
||||
area.add_child("placeables", placeables);
|
||||
area.add_child("triggers", triggers);
|
||||
if (!scripts.empty()) {
|
||||
area.add_child("scripts", scripts);
|
||||
}
|
||||
|
||||
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::CreatureBlueprint)));
|
||||
auto utcGffs = utc.top();
|
||||
|
||||
pt::ptree appearanceTree;
|
||||
appearanceTree.put("label", appearance.getString(utcGffs->getInt("Appearance_Type"), "label"));
|
||||
|
||||
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.add_child("appearance", appearanceTree);
|
||||
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::DoorBlueprint)));
|
||||
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) {
|
||||
GffFile utp;
|
||||
utp.load(wrap(getResource(gitPlaceable.getString("TemplateResRef"), ResourceType::PlaceableBlueprint)));
|
||||
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("loc_name", getString(utpGffs->getInt("LocName")));
|
||||
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::TriggerBlueprint)));
|
||||
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("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("points", geometry);
|
||||
trigger.add_child("blueprint", blueprint);
|
||||
|
||||
return move(trigger);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
KeyFile::KeyEntry key;
|
||||
if (_keyFile.find(resRef, type, key)) {
|
||||
auto maybeBif = _bifByIndex.find(key.bifIdx);
|
||||
if (maybeBif != _bifByIndex.end()) {
|
||||
result = make_shared<ByteArray>(maybeBif->second->getResourceData(key.resIdx));
|
||||
|
||||
} else {
|
||||
fs::path bifPath(_gamePath);
|
||||
bifPath.append(_keyFile.getFilename(key.bifIdx));
|
||||
|
||||
auto bif = make_unique<BifFile>();
|
||||
bif->load(bifPath);
|
||||
|
||||
result = make_shared<ByteArray>(bif->getResourceData(key.resIdx));
|
||||
|
||||
_bifByIndex.insert(make_pair(key.bifIdx, move(bif)));
|
||||
}
|
||||
} else {
|
||||
result = _rimMain.find(resRef, type);
|
||||
if (!result) {
|
||||
result = _rimBlueprints.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
|
74
tools/moduleprobe.h
Normal file
74
tools/moduleprobe.h
Normal file
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* 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/2dafile.h"
|
||||
#include "../src/resource/biffile.h"
|
||||
#include "../src/resource/gfffile.h"
|
||||
#include "../src/resource/keyfile.h"
|
||||
#include "../src/resource/rimfile.h"
|
||||
#include "../src/resource/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::KeyFile _keyFile;
|
||||
std::unordered_map<int, std::unique_ptr<resource::BifFile>> _bifByIndex;
|
||||
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);
|
||||
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);
|
||||
boost::property_tree::ptree describeTrigger(const resource::GffStruct &gitTrigger);
|
||||
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
|
|
@ -24,6 +24,8 @@
|
|||
|
||||
#include "../src/common/pathutil.h"
|
||||
|
||||
#include "moduleprobe.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
using namespace reone::resource;
|
||||
|
@ -42,17 +44,27 @@ int Program::run() {
|
|||
initOptions();
|
||||
loadOptions();
|
||||
initGameVersion();
|
||||
initTool();
|
||||
|
||||
switch (_command) {
|
||||
case Command::List:
|
||||
_tool->list(_inputFilePath, _keyPath);
|
||||
break;
|
||||
case Command::Extract:
|
||||
_tool->extract(_inputFilePath, _keyPath, _destPath);
|
||||
break;
|
||||
case Command::Convert:
|
||||
_tool->convert(_inputFilePath, _destPath);
|
||||
initFileTool();
|
||||
switch (_command) {
|
||||
case Command::List:
|
||||
_tool->list(_target, _keyPath);
|
||||
break;
|
||||
case Command::Extract:
|
||||
_tool->extract(_target, _keyPath, _destPath);
|
||||
break;
|
||||
case Command::Convert:
|
||||
_tool->convert(_target, _destPath);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
case Command::ModuleProbe:
|
||||
ModuleProbe().probe(_target, _gamePath, _destPath);
|
||||
break;
|
||||
default:
|
||||
cout << _cmdLineOpts << endl;
|
||||
|
@ -68,17 +80,18 @@ void Program::initOptions() {
|
|||
("list", "list file contents")
|
||||
("extract", "extract file contents")
|
||||
("convert", "convert 2DA or GFF file to JSON")
|
||||
("modprobe", "probe module and produce a JSON file describing it")
|
||||
("game", po::value<string>(), "path to game directory")
|
||||
("dest", po::value<string>(), "path to destination directory")
|
||||
("input-file", po::value<string>(), "path to input file");
|
||||
("target", po::value<string>(), "target name or path to input file");
|
||||
}
|
||||
|
||||
static fs::path getDestination(const po::variables_map &vars) {
|
||||
fs::path result;
|
||||
if (vars.count("dest") > 0) {
|
||||
result = vars["dest"].as<string>();
|
||||
} else if (vars.count("input-file") > 0) {
|
||||
result = fs::path(vars["input-file"].as<string>()).parent_path();
|
||||
} else if (vars.count("target") > 0) {
|
||||
result = fs::path(vars["target"].as<string>()).parent_path();
|
||||
} else {
|
||||
result = fs::current_path();
|
||||
}
|
||||
|
@ -87,7 +100,7 @@ static fs::path getDestination(const po::variables_map &vars) {
|
|||
|
||||
void Program::loadOptions() {
|
||||
po::positional_options_description positional;
|
||||
positional.add("input-file", 1);
|
||||
positional.add("target", 1);
|
||||
|
||||
po::parsed_options parsedCmdLineOpts = po::command_line_parser(_argc, _argv)
|
||||
.options(_cmdLineOpts)
|
||||
|
@ -100,7 +113,7 @@ void Program::loadOptions() {
|
|||
|
||||
_gamePath = vars.count("game") > 0 ? vars["game"].as<string>() : fs::current_path();
|
||||
_destPath = getDestination(vars);
|
||||
_inputFilePath = vars.count("input-file") > 0 ? vars["input-file"].as<string>() : "";
|
||||
_target = vars.count("target") > 0 ? vars["target"].as<string>() : "";
|
||||
_keyPath = getPathIgnoreCase(_gamePath, "chitin.key");
|
||||
|
||||
if (vars.count("help")) {
|
||||
|
@ -111,6 +124,8 @@ void Program::loadOptions() {
|
|||
_command = Command::Extract;
|
||||
} else if (vars.count("convert")) {
|
||||
_command = Command::Convert;
|
||||
} else if (vars.count("modprobe")) {
|
||||
_command = Command::ModuleProbe;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -119,18 +134,18 @@ void Program::initGameVersion() {
|
|||
_version = exePath.empty() ? GameVersion::KotOR : GameVersion::TheSithLords;
|
||||
}
|
||||
|
||||
void Program::initTool() {
|
||||
void Program::initFileTool() {
|
||||
switch (_command) {
|
||||
case Command::List:
|
||||
case Command::Extract:
|
||||
case Command::Convert:
|
||||
if (!fs::exists(_inputFilePath)) {
|
||||
throw runtime_error("Input file does not exist: " + _inputFilePath.string());
|
||||
if (!fs::exists(_target)) {
|
||||
throw runtime_error("Input file does not exist: " + _target);
|
||||
}
|
||||
_tool = getToolByPath(_version, _inputFilePath);
|
||||
_tool = getFileToolByPath(_version, _target);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
throw logic_error("Unsupported file tool command: " + to_string(static_cast<int>(_command)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
|
@ -40,16 +41,17 @@ private:
|
|||
Help,
|
||||
List,
|
||||
Extract,
|
||||
Convert
|
||||
Convert,
|
||||
ModuleProbe
|
||||
};
|
||||
|
||||
boost::filesystem::path _gamePath;
|
||||
boost::filesystem::path _destPath;
|
||||
boost::filesystem::path _inputFilePath;
|
||||
std::string _target;
|
||||
boost::filesystem::path _keyPath;
|
||||
resource::GameVersion _version { resource::GameVersion::KotOR };
|
||||
Command _command { Command::None };
|
||||
std::unique_ptr<Tool> _tool;
|
||||
std::unique_ptr<FileTool> _tool;
|
||||
|
||||
// Command line arguments
|
||||
|
||||
|
@ -66,7 +68,7 @@ private:
|
|||
void initOptions();
|
||||
void loadOptions();
|
||||
void initGameVersion();
|
||||
void initTool();
|
||||
void initFileTool();
|
||||
};
|
||||
|
||||
} // namespace tools
|
||||
|
|
|
@ -27,41 +27,48 @@ namespace reone {
|
|||
|
||||
namespace tools {
|
||||
|
||||
void Tool::list(const fs::path &path, const fs::path &keyPath) const {
|
||||
void FileTool::list(const fs::path &path, const fs::path &keyPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
void Tool::extract(const fs::path &path, const fs::path &keyPath, const fs::path &destPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
void Tool::convert(const fs::path &path, const fs::path &destPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
void Tool::throwNotImplemented() const {
|
||||
void FileTool::throwNotImplemented() const {
|
||||
throw logic_error("Not implemented");
|
||||
}
|
||||
|
||||
unique_ptr<Tool> getToolByPath(GameVersion version, const fs::path &path) {
|
||||
void FileTool::extract(const fs::path &path, const fs::path &keyPath, const fs::path &destPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
void FileTool::convert(const fs::path &path, const fs::path &destPath) const {
|
||||
throwNotImplemented();
|
||||
}
|
||||
|
||||
unique_ptr<FileTool> getFileToolByPath(GameVersion version, 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") {
|
||||
return make_unique<KeyTool>();
|
||||
result = make_unique<KeyTool>();
|
||||
} else if (ext == ".bif") {
|
||||
return make_unique<BifTool>();
|
||||
result = make_unique<BifTool>();
|
||||
} else if (ext == ".erf" || ext == ".mod" || ext == ".sav") {
|
||||
return make_unique<ErfTool>();
|
||||
result = make_unique<ErfTool>();
|
||||
} else if (ext == ".rim") {
|
||||
return make_unique<RimTool>();
|
||||
result = make_unique<RimTool>();
|
||||
} else if (ext == ".2da") {
|
||||
return make_unique<TwoDaTool>();
|
||||
result = make_unique<TwoDaTool>();
|
||||
} else if (ext == ".tlk") {
|
||||
return make_unique<TlkTool>();
|
||||
result = make_unique<TlkTool>();
|
||||
} else if (ext == ".tpc") {
|
||||
return make_unique<TpcTool>();
|
||||
result = make_unique<TpcTool>();
|
||||
} else {
|
||||
return make_unique<GffTool>();
|
||||
result = make_unique<GffTool>();
|
||||
}
|
||||
|
||||
return move(result);
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
|
|
@ -37,14 +37,15 @@ class GffStruct;
|
|||
namespace tools {
|
||||
|
||||
/**
|
||||
* Abstract tool class. Each implementation covers a single file format.
|
||||
* Abstract tool, operating on a single file. Each implementation covers a
|
||||
* particular file format.
|
||||
*
|
||||
* Operations:
|
||||
* - list — list file contents
|
||||
* - extract — extract file contents
|
||||
* - convert — convert file to a more practical format, e.g. JSON, TGA
|
||||
*/
|
||||
class Tool {
|
||||
class FileTool {
|
||||
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;
|
||||
|
@ -54,14 +55,14 @@ private:
|
|||
void throwNotImplemented() const;
|
||||
};
|
||||
|
||||
std::unique_ptr<Tool> getToolByPath(resource::GameVersion version, const boost::filesystem::path &path);
|
||||
std::unique_ptr<FileTool> getFileToolByPath(resource::GameVersion version, const boost::filesystem::path &path);
|
||||
|
||||
class KeyTool : public Tool {
|
||||
class KeyTool : public FileTool {
|
||||
public:
|
||||
void list(const boost::filesystem::path &path, const boost::filesystem::path &keyPath) const override;
|
||||
};
|
||||
|
||||
class BifTool : public Tool {
|
||||
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;
|
||||
|
@ -70,29 +71,29 @@ private:
|
|||
int getFileIndexByFilename(const std::vector<resource::KeyFile::FileEntry> &files, const std::string &filename) const;
|
||||
};
|
||||
|
||||
class ErfTool : public Tool {
|
||||
class ErfTool : 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 RimTool : public Tool {
|
||||
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 Tool {
|
||||
class TwoDaTool : public FileTool {
|
||||
public:
|
||||
void convert(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
};
|
||||
|
||||
class TlkTool : public Tool {
|
||||
class TlkTool : public FileTool {
|
||||
public:
|
||||
void convert(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
};
|
||||
|
||||
class GffTool : public Tool {
|
||||
class GffTool : public FileTool {
|
||||
public:
|
||||
void convert(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
|
||||
|
@ -100,7 +101,7 @@ private:
|
|||
boost::property_tree::ptree getPropertyTree(const resource::GffStruct &gffs) const;
|
||||
};
|
||||
|
||||
class TpcTool : public Tool {
|
||||
class TpcTool : public FileTool {
|
||||
public:
|
||||
void convert(const boost::filesystem::path &path, const boost::filesystem::path &destPath) const override;
|
||||
};
|
||||
|
|
Loading…
Reference in a new issue