Skip to content

Commit 317d2cb

Browse files
committed
Add DataChunkConverter to GeneralsMD tools
1 parent 4a3a1ae commit 317d2cb

File tree

3 files changed

+283
-0
lines changed

3 files changed

+283
-0
lines changed

GeneralsMD/Code/Tools/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Build useful tool binaries.
44
if(RTS_BUILD_ZEROHOUR_TOOLS)
5+
add_subdirectory(DataChunkConverter)
56
add_subdirectory(GUIEdit)
67
add_subdirectory(ImagePacker)
78
add_subdirectory(MapCacheBuilder)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
if (NOT IS_VS6_BUILD)
2+
add_executable(z_datachunkconverter WIN32)
3+
set_target_properties(z_datachunkconverter PROPERTIES OUTPUT_NAME datachunkconverter)
4+
5+
target_sources(z_datachunkconverter PRIVATE
6+
DataChunkConverter.cpp
7+
)
8+
9+
target_include_directories(z_datachunkconverter PRIVATE
10+
${CMAKE_CURRENT_SOURCE_DIR}/../../GameEngine/Include/Precompiled
11+
)
12+
13+
target_link_libraries(z_datachunkconverter PRIVATE
14+
z_gameengine
15+
z_gameenginedevice
16+
gi_always
17+
)
18+
endif()
Lines changed: 264 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,264 @@
1+
/*
2+
** Command & Conquer Generals Zero Hour(tm)
3+
** Copyright 2025 TheSuperHackers
4+
**
5+
** This program is free software: you can redistribute it and/or modify
6+
** it under the terms of the GNU General Public License as published by
7+
** the Free Software Foundation, either version 3 of the License, or
8+
** (at your option) any later version.
9+
**
10+
** This program is distributed in the hope that it will be useful,
11+
** but WITHOUT ANY WARRANTY; without even the implied warranty of
12+
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+
** GNU General Public License for more details.
14+
**
15+
** You should have received a copy of the GNU General Public License
16+
** along with this program. If not, see <http://www.gnu.org/licenses/>.
17+
*/
18+
19+
#include "PreRTS.h"
20+
21+
#include "Common/DataChunk.h"
22+
#include "Common/JSONChunkInput.h"
23+
#include "Common/JSONChunkOutput.h"
24+
#include "GameLogic/Scripts.h"
25+
#include "Common/FileSystem.h"
26+
#include "Common/GlobalData.h"
27+
#include "Common/GameMemory.h"
28+
#include "Common/GameText.h"
29+
#include "Common/ScienceStore.h"
30+
#include "Common/MultiplayerSettings.h"
31+
#include "Common/TerrainTypes.h"
32+
#include "Common/ThingTemplate.h"
33+
#include "Common/NameKeyGenerator.h"
34+
#include "Common/Errors.h"
35+
#include "Common/GameState.h"
36+
#include "Common/KindOf.h"
37+
#include "Common/Radar.h"
38+
#include "Common/Player.h"
39+
#include "Common/MapReaderWriterInfo.h"
40+
#include "GameLogic/AI.h"
41+
#include "GameLogic/Object.h"
42+
#include "GameLogic/ScriptEngine.h"
43+
#include "GameLogic/SidesList.h"
44+
#include "GameClient/ShellHooks.h"
45+
46+
#include <stdio.h>
47+
#include <string.h>
48+
#include <stdlib.h>
49+
50+
class SimpleFileOutputStream : public OutputStream
51+
{
52+
FILE* m_file;
53+
public:
54+
SimpleFileOutputStream(FILE* file) : m_file(file) {}
55+
Int write(const void *pData, Int numBytes) override
56+
{
57+
return fwrite(pData, 1, numBytes, m_file);
58+
}
59+
};
60+
61+
static void printUsage(const char* exeName)
62+
{
63+
printf("Usage: %s -in <input_file> -out <output_file>\n", exeName);
64+
printf("Converts SCB files to JSON and vice versa.\n");
65+
printf("File format is auto-detected based on extension (.scb or .json)\n");
66+
}
67+
68+
static Bool convertSCBToJSON(const char* inputPath, const char* outputPath)
69+
{
70+
CachedFileInputStream inputStream;
71+
if (!inputStream.open(AsciiString(inputPath))) {
72+
printf("Error: Could not open input file: %s\n", inputPath);
73+
return false;
74+
}
75+
76+
ChunkInputStream *pStrm = &inputStream;
77+
pStrm->absoluteSeek(0);
78+
DataChunkInput file(pStrm);
79+
80+
if (!file.isValidFileType()) {
81+
printf("Error: Invalid SCB file format\n");
82+
inputStream.close();
83+
return false;
84+
}
85+
86+
ScriptList *scripts[MAX_PLAYER_COUNT];
87+
for (Int i = 0; i < MAX_PLAYER_COUNT; i++) {
88+
scripts[i] = NULL;
89+
}
90+
91+
file.registerParser("PlayerScriptsList", AsciiString::TheEmptyString, ScriptList::ParseScriptsDataChunk);
92+
if (!file.parse(NULL)) {
93+
printf("Error: Failed to parse SCB file\n");
94+
inputStream.close();
95+
return false;
96+
}
97+
98+
Int numLists = ScriptList::getReadScripts(scripts);
99+
inputStream.close();
100+
101+
JSONChunkOutput jsonWriter;
102+
ScriptList::WriteScriptsDataChunkJSON(jsonWriter, scripts, numLists);
103+
104+
std::string jsonStr = jsonWriter.getJSONString();
105+
106+
FILE* outFile = fopen(outputPath, "w");
107+
if (!outFile) {
108+
printf("Error: Could not open output file: %s\n", outputPath);
109+
for (Int i = 0; i < numLists; i++) {
110+
if (scripts[i]) {
111+
deleteInstance(scripts[i]);
112+
}
113+
}
114+
return false;
115+
}
116+
117+
fwrite(jsonStr.c_str(), 1, jsonStr.length(), outFile);
118+
fclose(outFile);
119+
120+
for (Int i = 0; i < numLists; i++) {
121+
if (scripts[i]) {
122+
deleteInstance(scripts[i]);
123+
}
124+
}
125+
126+
return true;
127+
}
128+
129+
static Bool convertJSONToSCB(const char* inputPath, const char* outputPath)
130+
{
131+
FILE* inFile = fopen(inputPath, "rb");
132+
if (!inFile) {
133+
printf("Error: Could not open input file: %s\n", inputPath);
134+
return false;
135+
}
136+
137+
fseek(inFile, 0, SEEK_END);
138+
long fileSize = ftell(inFile);
139+
fseek(inFile, 0, SEEK_SET);
140+
141+
char* jsonData = new char[fileSize + 1];
142+
size_t bytesRead = fread(jsonData, 1, fileSize, inFile);
143+
fclose(inFile);
144+
jsonData[bytesRead] = '\0';
145+
146+
JSONChunkInput jsonInput(jsonData, bytesRead);
147+
delete[] jsonData;
148+
149+
if (!jsonInput.isValidFileType()) {
150+
printf("Error: Invalid JSON file format\n");
151+
return false;
152+
}
153+
154+
ScriptList *scripts[MAX_PLAYER_COUNT];
155+
for (Int i = 0; i < MAX_PLAYER_COUNT; i++) {
156+
scripts[i] = NULL;
157+
}
158+
159+
jsonInput.registerParser("PlayerScriptsList", AsciiString::TheEmptyString, ScriptList::ParseScriptsDataChunkJSON);
160+
if (!jsonInput.parse(NULL)) {
161+
printf("Error: Failed to parse JSON file\n");
162+
return false;
163+
}
164+
165+
Int numLists = ScriptList::getReadScripts(scripts);
166+
167+
FILE* outFile = fopen(outputPath, "wb");
168+
if (!outFile) {
169+
printf("Error: Could not open output file: %s\n", outputPath);
170+
for (Int i = 0; i < numLists; i++) {
171+
if (scripts[i]) {
172+
deleteInstance(scripts[i]);
173+
}
174+
}
175+
return false;
176+
}
177+
178+
SimpleFileOutputStream outputStream(outFile);
179+
DataChunkOutput chunkWriter(&outputStream);
180+
ScriptList::WriteScriptsDataChunk(chunkWriter, scripts, numLists);
181+
fclose(outFile);
182+
183+
for (Int i = 0; i < numLists; i++) {
184+
if (scripts[i]) {
185+
deleteInstance(scripts[i]);
186+
}
187+
}
188+
189+
return true;
190+
}
191+
192+
int main(int argc, char* argv[])
193+
{
194+
if (argc != 5) {
195+
printUsage(argv[0]);
196+
return 1;
197+
}
198+
199+
const char* inputPath = NULL;
200+
const char* outputPath = NULL;
201+
202+
for (int i = 1; i < argc; i++) {
203+
if (strcmp(argv[i], "-in") == 0 && i + 1 < argc) {
204+
inputPath = argv[++i];
205+
} else if (strcmp(argv[i], "-out") == 0 && i + 1 < argc) {
206+
outputPath = argv[++i];
207+
}
208+
}
209+
210+
if (!inputPath || !outputPath) {
211+
printUsage(argv[0]);
212+
return 1;
213+
}
214+
215+
const char* inputExt = strrchr(inputPath, '.');
216+
const char* outputExt = strrchr(outputPath, '.');
217+
218+
if (!inputExt || !outputExt) {
219+
printf("Error: File extensions required (.scb or .json)\n");
220+
return 1;
221+
}
222+
223+
Bool isInputSCB = (strcmp(inputExt, ".scb") == 0);
224+
Bool isInputJSON = (strcmp(inputExt, ".json") == 0);
225+
Bool isOutputSCB = (strcmp(outputExt, ".scb") == 0);
226+
Bool isOutputJSON = (strcmp(outputExt, ".json") == 0);
227+
228+
if ((isInputSCB && !isOutputJSON) || (isInputJSON && !isOutputSCB)) {
229+
printf("Error: Input and output formats must be different (SCB <-> JSON)\n");
230+
return 1;
231+
}
232+
233+
if (!isInputSCB && !isInputJSON) {
234+
printf("Error: Input file must be .scb or .json\n");
235+
return 1;
236+
}
237+
238+
initSubsystem(TheFileSystem, new FileSystem);
239+
initSubsystem(TheLocalFileSystem, (LocalFileSystem*)new Win32LocalFileSystem);
240+
initSubsystem(TheArchiveFileSystem, (ArchiveFileSystem*)new Win32BIGFileSystem);
241+
initSubsystem(TheWritableGlobalData, new GlobalData(), "Data\\INI\\Default\\GameData", "Data\\INI\\GameData");
242+
initSubsystem(TheGameText, CreateGameTextInterface());
243+
initSubsystem(TheScienceStore, new ScienceStore(), "Data\\INI\\Default\\Science", "Data\\INI\\Science");
244+
initSubsystem(TheMultiplayerSettings, new MultiplayerSettings(), "Data\\INI\\Default\\Multiplayer", "Data\\INI\\Multiplayer");
245+
initSubsystem(TheTerrainTypes, new TerrainTypeCollection(), "Data\\INI\\Default\\TerrainTypes", "Data\\INI\\TerrainTypes");
246+
initSubsystem(TheThingFactory, new ThingFactory(), "Data\\INI\\Default\\Thing", "Data\\INI\\Thing");
247+
initSubsystem(TheNameKeyGenerator, new NameKeyGenerator());
248+
initSubsystem(TheScriptEngine, new ScriptEngine());
249+
250+
Bool success = false;
251+
if (isInputSCB) {
252+
success = convertSCBToJSON(inputPath, outputPath);
253+
} else {
254+
success = convertJSONToSCB(inputPath, outputPath);
255+
}
256+
257+
if (success) {
258+
printf("Conversion successful: %s -> %s\n", inputPath, outputPath);
259+
return 0;
260+
} else {
261+
printf("Conversion failed\n");
262+
return 1;
263+
}
264+
}

0 commit comments

Comments
 (0)