Skip to content

Commit c8ea285

Browse files
authored
GMS Tool refactor (#2)
GMS Tool: * Load from ZIP * Export uncompressed GMS * Export decompiled LOC LOCC: * Created compiler & decompiler * Added tests
1 parent d03a5a4 commit c8ea285

File tree

101 files changed

+10269
-254
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

101 files changed

+10269
-254
lines changed

Diff for: .gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,7 @@
22
.vscode
33
build
44
cmake-build-*
5-
venv
5+
venv
6+
7+
# Ignore zlib generated header
8+
/Modules/zlib/zconf.h.included

Diff for: .gitmodules

+9
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,12 @@
77
[submodule "Modules/zlib"]
88
path = Modules/zlib
99
url = https://github.com/madler/zlib
10+
[submodule "Modules/spdlog"]
11+
path = Modules/spdlog
12+
url = https://github.com/gabime/spdlog
13+
[submodule "Modules/gtest"]
14+
path = Modules/gtest
15+
url = https://github.com/google/googletest
16+
[submodule "Modules/CLI11"]
17+
path = Modules/CLI11
18+
url = https://github.com/CLIUtils/CLI11.git

Diff for: CMakeLists.txt

+10-1
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,20 @@ SET(JSON_Install OFF CACHE BOOL "Do not generate install target in nlohmann")
1616
SET(JSON_BuildTests OFF CACHE BOOL "Do not build tests of nlohmann")
1717
SET(FMT_INSTALL OFF CACHE BOOL "Do not generate install targets of fmt")
1818
SET(FMT_TEST OFF CACHE BOOL "Do not generate install targets of fmt")
19+
SET(ZLIB_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/Modules/zlib")
20+
SET(SPDLOG_FMT_EXTERNAL_HO ON CACHE BOOL "Do not use own fmt")
21+
SET(BUILD_SHARED_LIBS OFF)
1922

2023
# Modules
2124
add_subdirectory(Modules/fmt)
2225
add_subdirectory(Modules/zlib)
26+
add_subdirectory(Modules/CLI11)
27+
add_subdirectory(Modules/gtest)
28+
add_subdirectory(Modules/spdlog)
29+
add_subdirectory(Modules/minizip)
2330
add_subdirectory(Modules/nlohmann)
31+
add_subdirectory(Modules/BMLOC)
2432

2533
# Our projects
26-
add_subdirectory(Tools/GMSInfo)
34+
add_subdirectory(Tools/GMSInfo)
35+
add_subdirectory(Tools/LOCC)

Diff for: Modules/BMLOC/CMakeLists.txt

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
cmake_minimum_required(VERSION 3.16)
2+
project(BMLOC)
3+
4+
set(CMAKE_CXX_STANDARD 20)
5+
6+
file(GLOB_RECURSE BMLOC_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp)
7+
add_library(BMLOC STATIC ${BMLOC_SOURCES})
8+
add_library(BMFormats::Localization ALIAS BMLOC)
9+
10+
target_compile_definitions(BMLOC PRIVATE -D_CRT_SECURE_NO_WARNINGS=1)
11+
target_include_directories(BMLOC PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
12+
target_include_directories(BMLOC PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/internal/include)
13+
target_link_libraries(BMLOC PUBLIC nlohmann_json)
14+
15+
# Tests
16+
enable_testing()
17+
18+
file(GLOB_RECURSE BLOC_TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests/*.cpp)
19+
20+
add_executable(BMLOC_Tests ${BLOC_TEST_SOURCES})
21+
target_link_libraries(BMLOC_Tests BMFormats::Localization gtest gmock gtest_main)
22+
23+
add_test(BMLOC_AllTests BMLOC_Tests)

Diff for: Modules/BMLOC/README.md

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
BMLOC
2+
------
3+
4+
Blood Money LOC file support

Diff for: Modules/BMLOC/include/BM/LOC/LOCJson.h

+195
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
#pragma once
2+
3+
4+
#include <nlohmann/json.hpp>
5+
6+
#include <BM/LOC/LOCTypes.h>
7+
#include <BM/LOC/LOCTree.h>
8+
9+
namespace nlohmann
10+
{
11+
template <>
12+
struct adl_serializer<BM::LOC::HBM_MissionObjectiveType>
13+
{
14+
static void to_json(json& j, const BM::LOC::HBM_MissionObjectiveType& type)
15+
{
16+
switch (type)
17+
{
18+
case BM::LOC::HBM_Target:
19+
case BM::LOC::HBM_Retrieve:
20+
case BM::LOC::HBM_Escape:
21+
case BM::LOC::HBM_Dispose:
22+
case BM::LOC::HBM_Protect:
23+
case BM::LOC::HBM_Optional:
24+
j = static_cast<char>(type);
25+
break;
26+
default:
27+
j = static_cast<uint8_t>(type);
28+
break;
29+
}
30+
}
31+
32+
static void from_json(const json& j, BM::LOC::HBM_MissionObjectiveType& r)
33+
{
34+
if (j.is_string())
35+
{
36+
r = static_cast<BM::LOC::HBM_MissionObjectiveType>(j.get<std::string>()[0]);
37+
}
38+
else
39+
{
40+
r = static_cast<BM::LOC::HBM_MissionObjectiveType>(j.get<uint8_t>());
41+
}
42+
}
43+
};
44+
45+
template <>
46+
struct adl_serializer<BM::LOC::TreeNodeType>
47+
{
48+
static constexpr const char* kValOrData = "data";
49+
static constexpr const char* kNode = "node";
50+
51+
static void to_json(json& j, const BM::LOC::TreeNodeType& type)
52+
{
53+
switch (type)
54+
{
55+
case BM::LOC::TreeNodeType::VALUE_OR_DATA: j = kValOrData; break;
56+
case BM::LOC::TreeNodeType::NODE_WITH_CHILDREN: j = kNode; break;
57+
default: j = static_cast<int>(type); break;
58+
}
59+
}
60+
61+
static void from_json(const json& j, BM::LOC::TreeNodeType& r)
62+
{
63+
if (j == kValOrData) r = BM::LOC::TreeNodeType::VALUE_OR_DATA;
64+
else if (j == kNode) r = BM::LOC::TreeNodeType::NODE_WITH_CHILDREN;
65+
else r = static_cast<BM::LOC::TreeNodeType>(j.get<int>());
66+
}
67+
};
68+
69+
template <>
70+
struct adl_serializer<BM::LOC::LOCTreeNode>
71+
{
72+
static constexpr const char* kNameToken = "name";
73+
static constexpr const char* kValueToken = "value";
74+
static constexpr const char* kTypeToken = "type";
75+
static constexpr const char* kNumChildrenToken = "numChildren";
76+
static constexpr const char* kChildrenListToken = "children";
77+
static constexpr const char* kOriginalTypeByteToken = "org_tbyte";
78+
79+
static void to_json(json& j, const BM::LOC::LOCTreeNode* node)
80+
{
81+
adl_serializer<BM::LOC::TreeNodeType>::to_json(j[kTypeToken], node->nodeType);
82+
83+
switch (node->nodeType)
84+
{
85+
case BM::LOC::VALUE_OR_DATA:
86+
j[kNameToken] = node->name;
87+
88+
if (!node->value.empty())
89+
{
90+
// If not string rewrite to byte export (calc length between next node in this level or in parent' level)
91+
std::string_view value { node->value };
92+
j[kValueToken] = value;
93+
}
94+
95+
if (node->originalTypeRawData.has_value()) // our value was overridden, need to save original byte
96+
{
97+
j[kOriginalTypeByteToken] = node->originalTypeRawData.value();
98+
}
99+
break;
100+
case BM::LOC::NODE_WITH_CHILDREN:
101+
if (!node->IsRoot()) { j[kNameToken] = node->name; }
102+
assert(node->numChild == node->children.size());
103+
104+
j[kNumChildrenToken] = node->numChild;
105+
106+
for (const auto& child : node->children)
107+
{
108+
json jc;
109+
nlohmann::adl_serializer<BM::LOC::LOCTreeNode>::to_json(jc, child);
110+
j[kChildrenListToken].push_back(jc);
111+
}
112+
break;
113+
}
114+
}
115+
116+
static void from_json(const json& j, BM::LOC::LOCTreeNode* node)
117+
{
118+
node->currentBufferPtr = nullptr;
119+
120+
if (auto nameIterator = j.find(kNameToken); nameIterator != j.end())
121+
{
122+
node->name = nameIterator->get<std::string>();
123+
}
124+
else if (!node->IsRoot())
125+
{
126+
throw std::exception { "Not allowed to store non-root anonymous node!" };
127+
}
128+
129+
if (auto nodeTypeIter = j.find(kTypeToken); nodeTypeIter != j.end())
130+
{
131+
nlohmann::adl_serializer<BM::LOC::TreeNodeType>::from_json(j[kTypeToken], node->nodeType);
132+
}
133+
else
134+
{
135+
throw std::exception { "Each node should contain type!" };
136+
}
137+
138+
switch (node->nodeType)
139+
{
140+
case BM::LOC::VALUE_OR_DATA:
141+
if (auto valueIterator = j.find(kValueToken); valueIterator != j.end())
142+
{
143+
node->value = valueIterator->get<std::string>();
144+
} else throw std::exception { "Key 'value' not found for VALUE node!" };
145+
146+
if (auto originalByteIterator = j.find(kOriginalTypeByteToken); originalByteIterator != j.end())
147+
{
148+
node->originalTypeRawData = originalByteIterator->get<uint8_t>();
149+
}
150+
break;
151+
case BM::LOC::NODE_WITH_CHILDREN:
152+
{
153+
if (auto numChildIterator = j.find(kNumChildrenToken); numChildIterator != j.end())
154+
{
155+
node->numChild = numChildIterator->get<size_t>();
156+
node->children.reserve(node->numChild);
157+
}
158+
else throw std::exception { "Key 'numChild' not found for NWC node!" };
159+
160+
if (auto childrenIterator = j.find(kChildrenListToken); childrenIterator != j.end())
161+
{
162+
if (!childrenIterator->is_array())
163+
{
164+
throw std::exception { "Key 'children' not array for NWC node!" };
165+
}
166+
167+
const int requiredNumChild = node->numChild;
168+
169+
for (int i = 0; i < requiredNumChild; i++)
170+
{
171+
auto child = new BM::LOC::LOCTreeNode(node, nullptr);
172+
173+
try
174+
{
175+
auto jsonChild = childrenIterator->at(i);
176+
nlohmann::adl_serializer<BM::LOC::LOCTreeNode>::from_json(jsonChild, child);
177+
node->AddChild(child);
178+
}
179+
catch (const nlohmann::json::out_of_range& outOfRange)
180+
{
181+
throw std::exception { "Bad 'numChild' value in json for NWC node!" };
182+
}
183+
}
184+
} //Allowed to have 0 child nodes
185+
}
186+
break;
187+
default:
188+
{
189+
throw std::exception { "Unknown node type!" };
190+
}
191+
break;
192+
}
193+
}
194+
};
195+
}

Diff for: Modules/BMLOC/include/BM/LOC/LOCSupportMode.h

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#pragma once
2+
3+
namespace BM::LOC
4+
{
5+
enum class LOCSupportMode
6+
{
7+
Hitman_BloodMoney = 0x0004,
8+
Hitman_Contracts = 0x0003,
9+
Hitman_2SA = 0x0002,
10+
Hitman_A47 = 0x0001,
11+
// Generic mode
12+
Generic = Hitman_BloodMoney
13+
};
14+
}

Diff for: Modules/BMLOC/include/BM/LOC/LOCTree.h

+80
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#pragma once
2+
3+
#include <unordered_map>
4+
#include <string_view>
5+
#include <string>
6+
#include <vector>
7+
#include <memory>
8+
#include <optional>
9+
10+
#include <cstdint>
11+
12+
#include <BM/LOC/LOCTypes.h>
13+
#include <BM/LOC/LOCSupportMode.h>
14+
15+
namespace BM::LOC
16+
{
17+
struct LOCTreeNode
18+
{
19+
// Editor defs
20+
/**
21+
* @struct MemoryMarkup
22+
* @brief In-memory location of the node. This value used only in the compiler!
23+
*/
24+
struct MemoryMarkup
25+
{
26+
uint32_t StartsAt {0 }; ///< Node starts at
27+
uint32_t EndsAt { 0 }; ///< Node ends at
28+
29+
MemoryMarkup() = default;
30+
MemoryMarkup(uint32_t o, uint32_t e) : StartsAt(o), EndsAt(e) {}
31+
};
32+
33+
// Defs
34+
static constexpr const char* kNoName = "<NONAME>";
35+
36+
// Base info
37+
LOCTreeNode* parent {nullptr}; //Pointer to parent node
38+
char* currentBufferPtr {nullptr}; //Available only in decompiler (be aware, if original buffer deallocated this pointer will be broken)
39+
std::string name{kNoName}; //Name of node (could be empty for ROOT node)
40+
std::string value; //Always string? Check it later
41+
TreeNodeType nodeType{0}; //See TreeNodeType for details
42+
std::optional<uint8_t> originalTypeRawData; //Original raw data if it was overridden by decompiler (just for reconstruction)
43+
std::optional<MemoryMarkup> memoryMarkup; //Only for compiler for fast memory position search
44+
45+
// Tree data
46+
size_t numChild {0}; //Number of children nodes
47+
std::vector<LOCTreeNode*> children {}; //List of children nodes (please, add node through AddChild!)
48+
49+
// Methods
50+
LOCTreeNode(LOCTreeNode* p, char* b);
51+
~LOCTreeNode();
52+
void AddChild(LOCTreeNode* node);
53+
void RemoveChild(LOCTreeNode* node);
54+
[[nodiscard]] bool IsRoot() const;
55+
[[nodiscard]] bool IsEmpty() const;
56+
[[nodiscard]] bool IsData() const;
57+
[[nodiscard]] bool IsContainer() const;
58+
59+
// Parser
60+
static LOCTreeNode* ReadFromMemory(char* buffer, size_t bufferSize, LOCSupportMode supportMode = LOCSupportMode::Generic);
61+
62+
// Compiler
63+
using CacheDataBase = std::unordered_map<std::string, std::string>;
64+
static void GenerateCacheDataBase(LOCTreeNode* root, CacheDataBase& cache);
65+
static bool Compile(LOCTreeNode* root, std::vector<uint8_t>& compiledBuffer);
66+
67+
// Serializer
68+
static void CompileAndSave(LOCTreeNode* root, std::string_view pathToFile);
69+
70+
// Comparator
71+
static bool Compare(LOCTreeNode* a, LOCTreeNode* b);
72+
73+
private:
74+
/**
75+
* @fn SortKeys
76+
* @brief Should be called after AddChild or RemoveChild!
77+
*/
78+
void SortKeys();
79+
};
80+
}

Diff for: Modules/BMLOC/include/BM/LOC/LOCTreeCompiler.h

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
#pragma once
2+
3+
#include <BM/LOC/LOCTree.h>
4+
#include <BM/LOC/LOCSupportMode.h>
5+
6+
#include <vector>
7+
8+
#include <cstdint>
9+
10+
namespace BM::LOC
11+
{
12+
class LOCTreeCompiler
13+
{
14+
public:
15+
using Buffer = std::vector<uint8_t>;
16+
17+
static bool Compile(Buffer& buffer, LOCTreeNode* rootNode, LOCSupportMode supportMode = LOCSupportMode::Generic);
18+
static void MarkupTree(LOCTreeNode* rootNode);
19+
};
20+
}

0 commit comments

Comments
 (0)