Skip to content

Commit

Permalink
Added support of M05 specific LOC tag
Browse files Browse the repository at this point in the history
  • Loading branch information
DronCode committed Jun 11, 2024
1 parent 9445d65 commit a774f9a
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 36 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ cmake-build-*
BMEdit/Editor/UI/UI/*.autosave
ThirdParty/zlib/zconf.h.included
Test
venv
venv
Notes.txt
15 changes: 14 additions & 1 deletion BMEdit/Editor/Source/Models/LocalizationTreeModel.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,11 @@ namespace models
return QString::fromStdString(node->value);
}

if (index.column() == 1 && node->type == gamelib::loc::LOCTreeNodeType::EMPTY_BLOCK)
{
return QString("(EMPTY)");
}

return {};
}

Expand Down Expand Up @@ -199,11 +204,19 @@ namespace models

if (!node) return flags;

if (index.column() == 0 || (index.column() == 1 && node->canHaveValue()))
if (index.column() == 0)
{
flags |= Qt::ItemIsEditable;
}

if (index.column() == 1)
{
if (node->canHaveValue())
{
flags |= Qt::ItemIsEditable;
}
}

return flags;
}

Expand Down
6 changes: 5 additions & 1 deletion BMEdit/GameLib/Include/GameLib/LOC/LOCTreeNode.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <string>
#include <memory>
#include <vector>
#include <unordered_map>


namespace ZBio::ZBinaryReader
Expand All @@ -23,7 +24,9 @@ namespace gamelib::loc
EMPTY_BLOCK = 0x8, ///< Empty chunk, no data at all
LOCALIZED_STRING = 0x9, ///< Key value (string to aligned string)
CHILDREN = 0x10, ///< Container (amount & list of offsets)
SUBTITLES_FIN = 0x28, ///< Subtitles finish string. NOTE: Maybe it's finish subtitle, will rename it later
SUBTITLES = 0x29, ///< Subtitles value (long text with extra parameters) | 0x20 mask means that extra data exists
SUBTITLES_HINT = 0x2B ///< Another subtitles data with extra string hint
};

enum LOCMissionObjectiveType : char
Expand Down Expand Up @@ -66,12 +69,13 @@ namespace gamelib::loc
struct SubtitleData
{
uint8_t unkData[8];
std::string extraHint {};
} subtitle;

[[nodiscard]] bool canHaveValue() const;
[[nodiscard]] bool canHaveChildren() const;

static void deserialize(const LOCTreeNode::Ptr &node, ZBio::ZBinaryReader::BinaryReader* binaryReader);
static void serialize(const LOCTreeNode::Ptr& node, ZBio::ZBinaryWriter::BinaryWriter* binaryWriter);
static void serialize(const LOCTreeNode::Ptr& node, ZBio::ZBinaryWriter::BinaryWriter* binaryWriter, std::vector<std::pair<size_t, uint32_t>>& replacement);
};
}
84 changes: 64 additions & 20 deletions BMEdit/GameLib/Source/GameLib/LOC/LOCTreeNode.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,19 @@
#include <GameLib/ZBioHelpers.h>
#include <ZBinaryReader.hpp>
#include <ZBinaryWriter.hpp>
#include <fmt/format.h>
#include <cassert>


namespace gamelib::loc
{
bool LOCTreeNode::canHaveValue() const
{
return type == LOCTreeNodeType::LOCALIZED_STRING || type == LOCTreeNodeType::SUBTITLES;
return
type == LOCTreeNodeType::LOCALIZED_STRING ||
type == LOCTreeNodeType::SUBTITLES ||
type == LOCTreeNodeType::SUBTITLES_HINT ||
type == LOCTreeNodeType::SUBTITLES_FIN;
}

bool LOCTreeNode::canHaveChildren() const
Expand All @@ -23,9 +28,12 @@ namespace gamelib::loc
node->name = binaryReader->readCString();

const auto type = binaryReader->read<int8_t, ZBio::Endianness::LE>();
if (type != LOCTreeNodeType::CHILDREN && type != LOCTreeNodeType::LOCALIZED_STRING && type != LOCTreeNodeType::SUBTITLES && type != LOCTreeNodeType::EMPTY_BLOCK)
if (type != LOCTreeNodeType::CHILDREN && type != LOCTreeNodeType::LOCALIZED_STRING &&
type != LOCTreeNodeType::SUBTITLES && type != LOCTreeNodeType::EMPTY_BLOCK &&
type != LOCTreeNodeType::SUBTITLES_FIN && type != LOCTreeNodeType::SUBTITLES_HINT)
{
throw std::runtime_error("Invalid LOC format: expected to have 0x0 or 0x10, got smth else");
auto errorMessage = fmt::format("Invalid LOC format: unexpected entity code 0x{:02X} at offset {} (0x{:X})", type, binaryReader->tell(), binaryReader->tell());
throw std::runtime_error(errorMessage);
}

// Store type
Expand Down Expand Up @@ -72,13 +80,38 @@ namespace gamelib::loc
ZBioHelpers::seekBy(binaryReader, 4); // Need seek by 4 because value strings are "aligned".
// Formula: len + 1 + 4 (+1 - zero terminator, 4 - "alignment")
}
else if (node->type == LOCTreeNodeType::SUBTITLES)
else if (node->type == LOCTreeNodeType::SUBTITLES || node->type == LOCTreeNodeType::SUBTITLES_HINT || node->type == LOCTreeNodeType::SUBTITLES_FIN)
{
// Subtitles text
node->value = binaryReader->readCString();

// Read subtitle data. I really don't know what that value means, but as 2xu32 each same to each
binaryReader->read<uint8_t, ZBio::Endianness::LE>(&node->subtitle.unkData[0], 8);
if (node->type == LOCTreeNodeType::SUBTITLES_HINT)
{
// Read extra hint string
node->subtitle.extraHint = binaryReader->readCString();
}

if (node->type == LOCTreeNodeType::SUBTITLES || node->type == LOCTreeNodeType::SUBTITLES_HINT)
{
// Read subtitle data. In most cases there are 2xu32, but when first u32 zeroed next u32 not presented
// I love IOI because they don't give me an opportunity to relax...
uint32_t first = binaryReader->read<uint32_t, ZBio::Endianness::LE>();
uint32_t second = 0;
if (first != 0)
{
// Ok, read second
second = binaryReader->read<uint32_t, ZBio::Endianness::LE>();
}

*reinterpret_cast<uint32_t*>(&node->subtitle.unkData[0]) = first;
*reinterpret_cast<uint32_t*>(&node->subtitle.unkData[4]) = second;
}

if (node->type == LOCTreeNodeType::SUBTITLES_FIN)
{
// Always only 1 u32
binaryReader->read<uint8_t, ZBio::Endianness::LE>(&node->subtitle.unkData[0], 4);
}
}
else if (node->type == LOCTreeNodeType::EMPTY_BLOCK)
{
Expand All @@ -90,7 +123,7 @@ namespace gamelib::loc
}
}

void LOCTreeNode::serialize(const LOCTreeNode::Ptr &node, ZBio::ZBinaryWriter::BinaryWriter *binaryWriter) // NOLINT(*-no-recursion)
void LOCTreeNode::serialize(const LOCTreeNode::Ptr &node, ZBio::ZBinaryWriter::BinaryWriter *binaryWriter, std::vector<std::pair<size_t, uint32_t>>& replacement) // NOLINT(*-no-recursion)
{
// Write name (not aligned)
binaryWriter->writeCString(node->name);
Expand All @@ -102,19 +135,19 @@ namespace gamelib::loc
{
// Sort children
auto children = node->children; // Need copy
std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; });
//std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; });

// Write count
const auto childrenCount = children.size() > 0xFF ? 0xFF : static_cast<uint8_t>(children.size());
binaryWriter->write<uint8_t, ZBio::Endianness::LE>(childrenCount);

// Write offsets
std::vector<uint32_t> offsets{};
std::vector<uint32_t> offsets { 0u };

for (int i = 1; i < childrenCount; i++)
{
offsets.push_back(binaryWriter->tell());
binaryWriter->write<uint32_t, ZBio::Endianness::LE>(0); // Fake offset (will be fixed)
binaryWriter->write<uint32_t, ZBio::Endianness::LE>(0xDDDDDDDDu);
}

// Write node by node & restore offsets
Expand All @@ -124,18 +157,13 @@ namespace gamelib::loc
{
if (i > 0)
{
// Restore offset
// Save replacement instruction
uint32_t newOffset = binaryWriter->tell();

ZBioSeekGuard<ZBio::ZBinaryWriter::BinaryWriter> guard { binaryWriter };
binaryWriter->seek(offsets[i - 1]);

// Update offset
binaryWriter->write<uint32_t, ZBio::Endianness::LE>(newOffset - baseOffset); // Offset calculated from node #0
replacement.emplace_back(offsets[i], newOffset - baseOffset);
}

// Write node itself
LOCTreeNode::serialize(children[i], binaryWriter);
LOCTreeNode::serialize(children[i], binaryWriter, replacement);
}
}
else if (node->type == LOCTreeNodeType::LOCALIZED_STRING)
Expand All @@ -146,13 +174,29 @@ namespace gamelib::loc
// Write 4 byte alignment
binaryWriter->write<uint32_t, ZBio::Endianness::LE>(0);
}
else if (node->type == LOCTreeNodeType::SUBTITLES)
else if (node->type == LOCTreeNodeType::SUBTITLES || node->type == LOCTreeNodeType::SUBTITLES_HINT)
{
// Write unaligned string
binaryWriter->writeCString(node->value);

if (node->type == LOCTreeNodeType::SUBTITLES_HINT)
{
// For hinted string need to write extra hint
binaryWriter->writeCString(node->subtitle.extraHint);
}

uint32_t first = *reinterpret_cast<uint32_t*>(&node->subtitle.unkData[0]);
uint32_t second = *reinterpret_cast<uint32_t*>(&node->subtitle.unkData[4]);

// Write subtitles data
binaryWriter->write<uint8_t, ZBio::Endianness::LE>(&node->subtitle.unkData[0], 8);
binaryWriter->write<uint32_t, ZBio::Endianness::LE>(first);
if (first)
binaryWriter->write<uint32_t, ZBio::Endianness::LE>(second);
}
else if (node->type == LOCTreeNodeType::SUBTITLES_FIN)
{
// Store tutorial data here
binaryWriter->write<uint8_t, ZBio::Endianness::LE>(&node->subtitle.unkData[0], 4);
}
else if (node->type == LOCTreeNodeType::EMPTY_BLOCK)
{
Expand Down
30 changes: 17 additions & 13 deletions BMEdit/GameLib/Source/GameLib/LOC/LOCWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,21 +12,23 @@ namespace gamelib::loc
auto writerSink = std::make_unique<ZBio::ZBinaryWriter::BufferSink>();
auto binaryWriter = ZBio::ZBinaryWriter::BinaryWriter(std::move(writerSink));

std::vector<std::pair<size_t, uint32_t>> replacement {};

// Sort children by name. It's required because game wants to interact with sorted tree
auto children = root->children;
std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; });
//std::sort(children.begin(), children.end(), [](const LOCTreeNode::Ptr& a, const LOCTreeNode::Ptr& b) { return a->name < b->name; });

// Write base
binaryWriter.write<uint8_t, ZBio::Endianness::LE>(static_cast<uint8_t>(children.size()));

std::vector<uint32_t> offsets {};
std::vector<uint32_t> offsets { 0u }; // zero stored by default

// Fill offsets if root child more than 1
for (int i = 1; i < children.size(); i++)
{
// Here we need to know an offset of subject after first created node. Instead of that we will save current offset and then jump back
offsets.push_back(binaryWriter.tell());
binaryWriter.write<uint32_t, ZBio::Endianness::LE>(0); // Temp value, will be replaced later
binaryWriter.write<uint32_t, ZBio::Endianness::LE>(0xDDDDDDDDu); // Temp value, will be replaced later
}

// And now we've ready to write node by node
Expand All @@ -36,23 +38,25 @@ namespace gamelib::loc
{
if (i > 0)
{
// Save current offset
// Save replacement
uint32_t newOffset = binaryWriter.tell();

// Seek back to origin
ZBioSeekGuard<ZBio::ZBinaryWriter::BinaryWriter> guard { &binaryWriter };
binaryWriter.seek(offsets[i - 1]);

// Update offset
binaryWriter.write<uint32_t, ZBio::Endianness::LE>(newOffset - baseOffset); // Offset calculated from node #0
replacement.emplace_back(offsets[i], newOffset - baseOffset);
}

// Write node itself
LOCTreeNode::serialize(children[i], &binaryWriter);
LOCTreeNode::serialize(children[i], &binaryWriter, replacement);
}

// Done
// Take value
auto raw = binaryWriter.release().value();

// Apply replacements
for (const auto& [offset, value] : replacement)
{
*reinterpret_cast<uint32_t*>(raw.data() + offset) = value;
}

// Done
std::copy(raw.begin(), raw.end(), std::back_inserter(outBuffer));
}
}

0 comments on commit a774f9a

Please sign in to comment.