Skip to content

Commit a13236d

Browse files
committed
Add a tool for diffing creatures.
1 parent a4db647 commit a13236d

3 files changed

Lines changed: 218 additions & 0 deletions

File tree

README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ The library is completely free for anyone to copy, use, and change with or witho
1717
There are some tools in the Tools subdirectory:
1818

1919
- 2da_merge allows merging a 2da into a base 2da, overwriting rows that are already present or inserting ones that are not.
20+
- diff_creature diffs two creature GFF files and produces a report showing any changes to key fields (like attributes, HP, AC, or local variables).
2021
- generate_placeable_blueprints allows the user to generate a series of blueprints from placeables defined in 2da using a base blueprint
2122
- key_bif_extractor allows extracting all resources in a KEY from their BIFs.
2223
- erf_extractor allows extracting all resources from an ERF.

Tools/CMakeLists.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ add_executable(2da_merge Tool_2daMerge.cpp)
22
target_link_libraries(2da_merge FileFormats)
33
set_target_properties(2da_merge PROPERTIES FOLDER "Tools")
44

5+
add_executable(diff_creature "Tool_DiffCreature.cpp")
6+
target_link_libraries(diff_creature FileFormats)
7+
set_target_properties(diff_creature PROPERTIES FOLDER "Tools")
8+
59
add_executable(erf_extractor Tool_ErfExtractor.cpp)
610
target_link_libraries(erf_extractor FileFormats)
711
set_target_properties(erf_extractor PROPERTIES FOLDER "Tools")

Tools/Tool_DiffCreature.cpp

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
1+
#include "FileFormats/2da.hpp"
2+
#include "FileFormats/Gff.hpp"
3+
4+
#include <format>
5+
6+
#if OS_WINDOWS
7+
#include "Windows.h"
8+
#else
9+
#include <sys/stat.h>
10+
#endif
11+
12+
using namespace FileFormats;
13+
14+
namespace {
15+
16+
// Recursively make the provided directory.
17+
void RecursivelyEnsureDir(std::string const& dir)
18+
{
19+
for (std::size_t slashIndex = dir.find_first_of("\\/");
20+
slashIndex != std::string::npos;
21+
slashIndex = dir.find_first_of("\\/", slashIndex + 1))
22+
{
23+
std::string dirToMake = dir.substr(0, slashIndex);
24+
25+
#if OS_WINDOWS
26+
CreateDirectoryA(dirToMake.c_str(), NULL);
27+
#else
28+
mkdir(dirToMake.c_str(), S_IRWXU | S_IRWXG | S_IROTH | S_IXOTH);
29+
#endif
30+
}
31+
}
32+
33+
bool g_any_change = false;
34+
35+
template <typename T>
36+
std::string DiffField(const char* fieldName, T oldVal, T newVal)
37+
{
38+
if (oldVal != newVal)
39+
{
40+
g_any_change = true;
41+
return std::format("{}: {} -> {}\n", fieldName, oldVal, newVal);
42+
}
43+
44+
return std::format("{}: {}\n", fieldName, oldVal);
45+
}
46+
47+
template <typename T>
48+
std::string DiffLocalVar(const Gff::Friendly::GffStruct* oldVariable, const Gff::Friendly::GffStruct* newVariable)
49+
{
50+
T oldVal, newVal;
51+
newVariable->ReadField("Value", &newVal);
52+
53+
if (oldVariable)
54+
{
55+
oldVariable->ReadField("Value", &oldVal);
56+
}
57+
else
58+
{
59+
oldVal = newVal;
60+
}
61+
62+
return DiffField("Value", oldVal, newVal);
63+
}
64+
65+
}
66+
67+
int DiffCreatures(const char* firstCreaturePath, const char* secondCreaturePath, const char* outputPath)
68+
{
69+
Gff::Raw::Gff firstCreatureGffRaw;
70+
71+
if (!Gff::Raw::Gff::ReadFromFile(firstCreaturePath, &firstCreatureGffRaw))
72+
{
73+
std::printf("Failed to load gff from %s.\n", firstCreaturePath);
74+
return 1;
75+
}
76+
77+
Gff::Raw::Gff secondCreatureGffRaw;
78+
79+
if (!Gff::Raw::Gff::ReadFromFile(secondCreaturePath, &secondCreatureGffRaw))
80+
{
81+
std::printf("Failed to load gff from %s.\n", secondCreaturePath);
82+
return 1;
83+
}
84+
85+
Gff::Friendly::Gff firstCreatureGff(std::move(firstCreatureGffRaw));
86+
Gff::Friendly::Gff secondCreatureGff(std::move(secondCreatureGffRaw));
87+
88+
std::string output;
89+
90+
Gff::Friendly::Type_CExoLocString name;
91+
secondCreatureGff.GetTopLevelStruct().ReadField("FirstName", &name);
92+
93+
if (!name.m_SubStrings.empty())
94+
{
95+
output += std::format("{}\n\n", name.m_SubStrings[0].m_String);
96+
}
97+
98+
Gff::Friendly::Type_BYTE old_str, new_str;
99+
Gff::Friendly::Type_BYTE old_dex, new_dex;
100+
Gff::Friendly::Type_BYTE old_con, new_con;
101+
Gff::Friendly::Type_BYTE old_int, new_int;
102+
Gff::Friendly::Type_BYTE old_wis, new_wis;
103+
Gff::Friendly::Type_BYTE old_cha, new_cha;
104+
Gff::Friendly::Type_BYTE old_ac, new_ac;
105+
Gff::Friendly::Type_SHORT old_max_hp, new_max_hp;
106+
107+
firstCreatureGff.GetTopLevelStruct().ReadField("Str", &old_str);
108+
secondCreatureGff.GetTopLevelStruct().ReadField("Str", &new_str);
109+
110+
firstCreatureGff.GetTopLevelStruct().ReadField("Dex", &old_dex);
111+
secondCreatureGff.GetTopLevelStruct().ReadField("Dex", &new_dex);
112+
113+
firstCreatureGff.GetTopLevelStruct().ReadField("Con", &old_con);
114+
secondCreatureGff.GetTopLevelStruct().ReadField("Con", &new_con);
115+
116+
firstCreatureGff.GetTopLevelStruct().ReadField("Int", &old_int);
117+
secondCreatureGff.GetTopLevelStruct().ReadField("Int", &new_int);
118+
119+
firstCreatureGff.GetTopLevelStruct().ReadField("Wis", &old_wis);
120+
secondCreatureGff.GetTopLevelStruct().ReadField("Wis", &new_wis);
121+
122+
firstCreatureGff.GetTopLevelStruct().ReadField("Cha", &old_cha);
123+
secondCreatureGff.GetTopLevelStruct().ReadField("Cha", &new_cha);
124+
125+
firstCreatureGff.GetTopLevelStruct().ReadField("NaturalAC", &old_ac);
126+
secondCreatureGff.GetTopLevelStruct().ReadField("NaturalAC", &new_ac);
127+
128+
firstCreatureGff.GetTopLevelStruct().ReadField("MaxHitPoints", &old_max_hp);
129+
secondCreatureGff.GetTopLevelStruct().ReadField("MaxHitPoints", &new_max_hp);
130+
131+
output += DiffField("Str", old_str, new_str);
132+
output += DiffField("Dex", old_dex, new_dex);
133+
output += DiffField("Con", old_con, new_con);
134+
output += DiffField("Int", old_int, new_int);
135+
output += DiffField("Wis", old_wis, new_wis);
136+
output += DiffField("Cha", old_cha, new_cha);
137+
output += DiffField("NaturalAC", old_ac, new_ac);
138+
output += DiffField("MaximumHP", old_max_hp, new_max_hp);
139+
140+
output += "\n";
141+
142+
Gff::Friendly::Type_List oldVariables;
143+
Gff::Friendly::Type_List newVariables;
144+
145+
firstCreatureGff.GetTopLevelStruct().ReadField("VarTable", &oldVariables);
146+
secondCreatureGff.GetTopLevelStruct().ReadField("VarTable", &newVariables);
147+
148+
for (const Gff::Friendly::GffStruct& variable : newVariables.GetStructs())
149+
{
150+
// This code won't cope correctly with two localvars with same name and different type.
151+
152+
Gff::Friendly::Type_CExoString variableName;
153+
variable.ReadField("Name", &variableName);
154+
155+
Gff::Friendly::Type_DWORD type;
156+
variable.ReadField("Type", &type);
157+
158+
const Gff::Friendly::GffStruct* oldVariable = nullptr;
159+
160+
// Locate the new variable in the old table to see if it's an add.
161+
for (const Gff::Friendly::GffStruct& oldVariableCandidate : oldVariables.GetStructs())
162+
{
163+
Gff::Friendly::Type_CExoString oldVariableName;
164+
oldVariableCandidate.ReadField("Name", &oldVariableName);
165+
166+
if (variableName.m_String == oldVariableName.m_String)
167+
{
168+
oldVariable = &oldVariableCandidate;
169+
break;
170+
}
171+
}
172+
173+
output += std::format("{}LocalVar {} ", oldVariable ? "" : "ADDED ", variableName.m_String);
174+
175+
if (type == 1)
176+
{
177+
output += DiffLocalVar<Gff::Friendly::Type_INT>(oldVariable, &variable);
178+
}
179+
else if (type == 2)
180+
{
181+
output += DiffLocalVar<Gff::Friendly::Type_FLOAT>(oldVariable, &variable);
182+
}
183+
else
184+
{
185+
output += "UNSUPPORTED TYPE\n";
186+
}
187+
}
188+
189+
if (g_any_change)
190+
{
191+
RecursivelyEnsureDir(outputPath);
192+
FILE* f = fopen(outputPath, "w");
193+
194+
if (f)
195+
{
196+
fprintf(f, "%s", output.c_str());
197+
fclose(f);
198+
}
199+
}
200+
201+
return 0;
202+
}
203+
204+
int main(int argc, char** argv)
205+
{
206+
if (argc < 4)
207+
{
208+
std::printf("diff_creature [first_gff] [second_gff] [output_path]");
209+
return 1;
210+
}
211+
212+
return DiffCreatures(argv[1], argv[2], argv[3]);
213+
}

0 commit comments

Comments
 (0)