Implement JSON to TLK tool
While at it, make TLK to JSON tool output SoundResRef.
This commit is contained in:
parent
ddb680c120
commit
6905d35e1f
13 changed files with 223 additions and 28 deletions
|
@ -114,6 +114,7 @@ set(RESOURCE_HEADERS
|
|||
src/resource/format/rimwriter.h
|
||||
src/resource/format/ssfreader.h
|
||||
src/resource/format/tlkreader.h
|
||||
src/resource/format/tlkwriter.h
|
||||
src/resource/format/visreader.h
|
||||
src/resource/keybifprovider.h
|
||||
src/resource/resourceprovider.h
|
||||
|
@ -143,6 +144,7 @@ set(RESOURCE_SOURCES
|
|||
src/resource/format/rimwriter.cpp
|
||||
src/resource/format/ssfreader.cpp
|
||||
src/resource/format/tlkreader.cpp
|
||||
src/resource/format/tlkwriter.cpp
|
||||
src/resource/format/visreader.cpp
|
||||
src/resource/keybifprovider.cpp
|
||||
src/resource/resources.cpp
|
||||
|
|
|
@ -45,6 +45,11 @@ public:
|
|||
void putBytes(const ByteArray &bytes);
|
||||
void putBytes(int count, uint8_t val = 0);
|
||||
|
||||
template <class T>
|
||||
void putStruct(const T &val) {
|
||||
_stream->write(reinterpret_cast<const char *>(&val), sizeof(T));
|
||||
}
|
||||
|
||||
size_t tell() const;
|
||||
|
||||
private:
|
||||
|
|
|
@ -44,7 +44,6 @@ void TlkReader::doLoad() {
|
|||
|
||||
void TlkReader::loadStrings() {
|
||||
_table = make_shared<TalkTable>();
|
||||
_table->_strings.reserve(_stringCount);
|
||||
|
||||
for (uint32_t i = 0; i < _stringCount; ++i) {
|
||||
uint32_t flags = readUint32();
|
||||
|
@ -58,15 +57,14 @@ void TlkReader::loadStrings() {
|
|||
uint32_t stringSize = readUint32();
|
||||
float soundLength = readFloat();
|
||||
|
||||
TalkTableString string;
|
||||
TalkTableString tableString;
|
||||
if (flags & StringFlags::textPresent) {
|
||||
string.text = readString(_stringsOffset + stringOffset, stringSize);
|
||||
tableString.text = readString(_stringsOffset + stringOffset, stringSize);
|
||||
}
|
||||
if (flags & StringFlags::soundPresent) {
|
||||
string.soundResRef = soundResRef;
|
||||
tableString.soundResRef = soundResRef;
|
||||
}
|
||||
|
||||
_table->_strings.push_back(string);
|
||||
_table->addString(move(tableString));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
87
src/resource/format/tlkwriter.cpp
Normal file
87
src/resource/format/tlkwriter.cpp
Normal file
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
* 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 "tlkwriter.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include <boost/filesystem.hpp>
|
||||
|
||||
#include "../../common/streamwriter.h"
|
||||
#include "../../common/stringutil.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace fs = boost::filesystem;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace resource {
|
||||
|
||||
struct StringFlags {
|
||||
static constexpr int textPresent = 1;
|
||||
static constexpr int soundPresent = 2;
|
||||
static constexpr int soundLengthPresent = 4;
|
||||
};
|
||||
|
||||
TlkWriter::TlkWriter(shared_ptr<TalkTable> talkTable) : _talkTable(talkTable) {
|
||||
if (!talkTable) {
|
||||
throw invalid_argument("talkTable must not be null");
|
||||
}
|
||||
}
|
||||
|
||||
void TlkWriter::save(const fs::path &path) {
|
||||
vector<StringDataElement> strData;
|
||||
|
||||
uint32_t offString = 0;
|
||||
for (int i = 0; i < _talkTable->getStringCount(); ++i) {
|
||||
const TalkTableString &str = _talkTable->getString(i);
|
||||
auto strSize = static_cast<uint32_t>(str.text.length());
|
||||
|
||||
StringDataElement strDataElem;
|
||||
strDataElem.flags = 7; // all strings in dialog.tlk have this
|
||||
strncpy(strDataElem.soundResRef, str.soundResRef.c_str(), str.soundResRef.length());
|
||||
memset(strDataElem.soundResRef, 0, 16);
|
||||
strDataElem.offString = offString;
|
||||
strDataElem.stringSize = strSize;
|
||||
strData.push_back(move(strDataElem));
|
||||
|
||||
offString += strSize + 1;
|
||||
}
|
||||
|
||||
auto tlk = make_shared<fs::ofstream>(path, ios::binary);
|
||||
StreamWriter writer(tlk);
|
||||
|
||||
FileHeader fileHeader;
|
||||
fileHeader.languageId = 0;
|
||||
fileHeader.numStrings = _talkTable->getStringCount();
|
||||
fileHeader.offStringEntries = 8 + sizeof(FileHeader) + _talkTable->getStringCount() * sizeof(StringDataElement);
|
||||
|
||||
writer.putString("TLK V3.0");
|
||||
writer.putStruct(fileHeader);
|
||||
|
||||
for (int i = 0; i < _talkTable->getStringCount(); ++i) {
|
||||
writer.putStruct(strData[i]);
|
||||
}
|
||||
for (int i = 0; i < _talkTable->getStringCount(); ++i) {
|
||||
writer.putCString(_talkTable->getString(i).text);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace resource
|
||||
|
||||
} // namespace reone
|
60
src/resource/format/tlkwriter.h
Normal file
60
src/resource/format/tlkwriter.h
Normal file
|
@ -0,0 +1,60 @@
|
|||
/*
|
||||
* 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 <cstdint>
|
||||
#include <memory>
|
||||
|
||||
#include <boost/filesystem/path.hpp>
|
||||
#include <boost/noncopyable.hpp>
|
||||
|
||||
#include "../talktable.h"
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace resource {
|
||||
|
||||
class TlkWriter : boost::noncopyable {
|
||||
public:
|
||||
TlkWriter(std::shared_ptr<TalkTable> talkTable);
|
||||
|
||||
void save(const boost::filesystem::path &path);
|
||||
|
||||
private:
|
||||
struct FileHeader {
|
||||
uint32_t languageId { 0 };
|
||||
uint32_t numStrings { 0 };
|
||||
uint32_t offStringEntries { 0 };
|
||||
};
|
||||
|
||||
struct StringDataElement {
|
||||
uint32_t flags { 0 };
|
||||
char soundResRef[16];
|
||||
uint32_t volumeVariance { 0 };
|
||||
uint32_t pitchVariance { 0 };
|
||||
uint32_t offString { 0 };
|
||||
uint32_t stringSize { 0 };
|
||||
float soundLength { 0.0f };
|
||||
};
|
||||
|
||||
std::shared_ptr<TalkTable> _talkTable;
|
||||
};
|
||||
|
||||
} // namespace resource
|
||||
|
||||
} // namespace reone
|
|
@ -17,16 +17,27 @@
|
|||
|
||||
#include "talktable.h"
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
using namespace std;
|
||||
|
||||
namespace reone {
|
||||
|
||||
namespace resource {
|
||||
|
||||
void TalkTable::addString(TalkTableString &&string) {
|
||||
_strings.push_back(string);
|
||||
}
|
||||
|
||||
int TalkTable::getStringCount() const {
|
||||
return static_cast<int>(_strings.size());
|
||||
}
|
||||
|
||||
const TalkTableString &TalkTable::getString(int32_t ref) const {
|
||||
return _strings[ref];
|
||||
const TalkTableString &TalkTable::getString(int index) const {
|
||||
if (index < 0 || index >= static_cast<int>(_strings.size())) {
|
||||
throw out_of_range("index is out of range");
|
||||
}
|
||||
return _strings[index];
|
||||
}
|
||||
|
||||
} // namespace resource
|
||||
|
|
|
@ -33,13 +33,13 @@ struct TalkTableString {
|
|||
|
||||
struct TalkTable : boost::noncopyable {
|
||||
public:
|
||||
void addString(TalkTableString &&string);
|
||||
|
||||
int getStringCount() const;
|
||||
const TalkTableString &getString(int32_t ref) const;
|
||||
const TalkTableString &getString(int index) const;
|
||||
|
||||
private:
|
||||
std::vector<TalkTableString> _strings;
|
||||
|
||||
friend class TlkReader;
|
||||
};
|
||||
|
||||
} // namespace resource
|
||||
|
|
|
@ -236,14 +236,14 @@ static unique_ptr<GffStruct> treeToGffStruct(const pt::ptree &tree) {
|
|||
}
|
||||
|
||||
void GffTool::toGFF(const fs::path &path, const fs::path &destPath) {
|
||||
pt::ptree tree;
|
||||
pt::read_json(path.string(), tree);
|
||||
|
||||
if (path.extension() != ".json") {
|
||||
cerr << "Input file must have extension '.json'" << endl;
|
||||
return;
|
||||
}
|
||||
|
||||
pt::ptree tree;
|
||||
pt::read_json(path.string(), tree);
|
||||
|
||||
fs::path extensionless(path);
|
||||
extensionless.replace_extension();
|
||||
ResourceType resType = getResTypeByExt(extensionless.extension().string().substr(1));
|
||||
|
|
|
@ -53,7 +53,8 @@ static const unordered_map<string, Operation> g_operations {
|
|||
{ "to-erf", Operation::ToERF },
|
||||
{ "to-mod", Operation::ToMOD },
|
||||
{ "to-pth", Operation::ToPTH },
|
||||
{ "to-ascii", Operation::ToASCII }
|
||||
{ "to-ascii", Operation::ToASCII },
|
||||
{ "to-tlk", Operation::ToTLK }
|
||||
};
|
||||
|
||||
Program::Program(int argc, char **argv) : _argc(argc), _argv(argv) {
|
||||
|
@ -66,7 +67,7 @@ int Program::run() {
|
|||
|
||||
switch (_operation) {
|
||||
case Operation::None:
|
||||
cout << _cmdLineOpts << endl;
|
||||
cout << _optsCmdLine << endl;
|
||||
break;
|
||||
default: {
|
||||
auto tool = getTool();
|
||||
|
@ -83,11 +84,11 @@ int Program::run() {
|
|||
}
|
||||
|
||||
void Program::initOptions() {
|
||||
_commonOpts.add_options()
|
||||
_optsCommon.add_options()
|
||||
("game", po::value<string>(), "path to game directory")
|
||||
("dest", po::value<string>(), "path to destination directory");
|
||||
|
||||
_cmdLineOpts.add(_commonOpts).add_options()
|
||||
_optsCmdLine.add(_optsCommon).add_options()
|
||||
("list", "list file contents")
|
||||
("extract", "extract file contents")
|
||||
("to-json", "convert 2DA, GFF or TLK file to JSON")
|
||||
|
@ -99,6 +100,7 @@ void Program::initOptions() {
|
|||
("to-mod", "create MOD archive from directory")
|
||||
("to-pth", "convert ASCII PTH to binary PTH")
|
||||
("to-ascii", "convert binary PTH to ASCII")
|
||||
("to-tlk", "convert JSON to TLK")
|
||||
("target", po::value<string>(), "target name or path to input file");
|
||||
}
|
||||
|
||||
|
@ -119,14 +121,14 @@ void Program::loadOptions() {
|
|||
positional.add("target", 1);
|
||||
|
||||
po::parsed_options parsedCmdLineOpts = po::command_line_parser(_argc, _argv)
|
||||
.options(_cmdLineOpts)
|
||||
.options(_optsCmdLine)
|
||||
.positional(positional)
|
||||
.run();
|
||||
|
||||
po::variables_map vars;
|
||||
po::store(parsedCmdLineOpts, vars);
|
||||
if (fs::exists(kConfigFilename)) {
|
||||
po::store(po::parse_config_file<char>(kConfigFilename, _commonOpts), vars);
|
||||
po::store(po::parse_config_file<char>(kConfigFilename, _optsCommon), vars);
|
||||
}
|
||||
po::notify(vars);
|
||||
|
||||
|
|
|
@ -43,8 +43,8 @@ private:
|
|||
int _argc;
|
||||
char **_argv;
|
||||
|
||||
boost::program_options::options_description _commonOpts;
|
||||
boost::program_options::options_description _cmdLineOpts { "Usage" };
|
||||
boost::program_options::options_description _optsCommon;
|
||||
boost::program_options::options_description _optsCmdLine { "Usage" };
|
||||
|
||||
boost::filesystem::path _gamePath;
|
||||
boost::filesystem::path _destPath;
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
#include <boost/property_tree/json_parser.hpp>
|
||||
|
||||
#include "../src/resource/format/tlkreader.h"
|
||||
#include "../src/resource/format/tlkwriter.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
|
@ -35,6 +36,8 @@ namespace tools {
|
|||
void TlkTool::invoke(Operation operation, const fs::path &target, const fs::path &gamePath, const fs::path &destPath) {
|
||||
if (operation == Operation::ToJSON) {
|
||||
toJSON(target, destPath);
|
||||
} else if (operation == Operation::ToTLK) {
|
||||
toTLK(target, destPath);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -47,11 +50,12 @@ void TlkTool::toJSON(const fs::path &path, const fs::path &destPath) {
|
|||
|
||||
shared_ptr<TalkTable> table(tlk.table());
|
||||
for (int i = 0; i < table->getStringCount(); ++i) {
|
||||
string s(table->getString(i).text);
|
||||
const TalkTableString &tableString = table->getString(i);
|
||||
|
||||
pt::ptree child;
|
||||
child.put("index", i);
|
||||
child.put("string", s);
|
||||
child.put("_index", i);
|
||||
child.put("text", tableString.text);
|
||||
child.put("soundResRef", tableString.soundResRef);
|
||||
|
||||
children.push_back(make_pair("", child));
|
||||
}
|
||||
|
@ -63,11 +67,35 @@ void TlkTool::toJSON(const fs::path &path, const fs::path &destPath) {
|
|||
pt::write_json(jsonPath.string(), tree);
|
||||
}
|
||||
|
||||
void TlkTool::toTLK(const fs::path &path, const fs::path &destPath) {
|
||||
pt::ptree tree;
|
||||
pt::read_json(path.string(), tree);
|
||||
|
||||
auto talkTable = make_shared<TalkTable>();
|
||||
for (auto &str : tree.get_child("strings")) {
|
||||
TalkTableString talkTableString;
|
||||
talkTableString.text = str.second.get("text", "");
|
||||
talkTableString.soundResRef = str.second.get("soundResRef", "");
|
||||
talkTable->addString(move(talkTableString));
|
||||
}
|
||||
|
||||
fs::path extensionless(path);
|
||||
extensionless.replace_extension(); // remove .json
|
||||
if (extensionless.extension() == ".tlk") {
|
||||
extensionless.replace_extension();
|
||||
}
|
||||
|
||||
fs::path tlkPath(destPath);
|
||||
tlkPath.append(extensionless.filename().string() + ".tlk");
|
||||
|
||||
TlkWriter writer(move(talkTable));
|
||||
writer.save(tlkPath);
|
||||
}
|
||||
|
||||
bool TlkTool::supports(Operation operation, const fs::path &target) const {
|
||||
return
|
||||
!fs::is_directory(target) &&
|
||||
target.extension() == ".tlk" &&
|
||||
operation == Operation::ToJSON;
|
||||
((target.extension() == ".tlk" && operation == Operation::ToJSON) || (target.extension() == ".json" && operation == Operation::ToTLK));
|
||||
}
|
||||
|
||||
} // namespace tools
|
||||
|
|
|
@ -132,6 +132,7 @@ public:
|
|||
|
||||
private:
|
||||
void toJSON(const boost::filesystem::path &path, const boost::filesystem::path &destPath);
|
||||
void toTLK(const boost::filesystem::path &path, const boost::filesystem::path &destPath);
|
||||
};
|
||||
|
||||
class TpcTool : public ITool {
|
||||
|
|
|
@ -33,7 +33,8 @@ enum class Operation {
|
|||
ToERF,
|
||||
ToMOD,
|
||||
ToPTH,
|
||||
ToASCII
|
||||
ToASCII,
|
||||
ToTLK
|
||||
};
|
||||
|
||||
} // namespace tools
|
||||
|
|
Loading…
Reference in a new issue