-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathMakeXORPatch.cpp
More file actions
191 lines (176 loc) · 10.1 KB
/
MakeXORPatch.cpp
File metadata and controls
191 lines (176 loc) · 10.1 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
//--------------------------------------------//
// MakeXORPatch //
// License: Public Domain (www.unlicense.org) //
//--------------------------------------------//
#include <memory.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#if defined(_MSC_VER)
#include <sys/utime.h>
typedef unsigned __int64 Bit64u;
#else
#include <utime.h>
typedef unsigned long long Bit64u;
#endif
#ifdef WIN32
#define CROSS_FILESPLIT '\\'
#else
#define CROSS_FILESPLIT '/'
#endif
// Use 64-bit fseek and ftell
#if defined(_MSC_VER) && _MSC_VER >= 1400 // VC2005 and up have a special 64-bit fseek
#define fseek_wrap(fp, offset, whence) _fseeki64(fp, (__int64)offset, whence)
#define ftell_wrap(fp) _ftelli64(fp)
#elif defined(HAVE_64BIT_OFFSETS) || (defined(_POSIX_C_SOURCE) && (_POSIX_C_SOURCE - 0) >= 200112) || (defined(__POSIX_VISIBLE) && __POSIX_VISIBLE >= 200112) || (defined(_POSIX_VERSION) && _POSIX_VERSION >= 200112) || __USE_LARGEFILE || (defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64)
#define fseek_wrap(fp, offset, whence) fseeko(fp, (off_t)offset, whence)
#define ftell_wrap(fp) ftello(fp)
#else
#define fseek_wrap(fp, offset, whence) fseek(fp, (long)offset, whence)
#define ftell_wrap(fp) ftell(fp)
#endif
#define XOR_WRITE_LE32(p,v) { ((unsigned char*)(p))[0] = (unsigned char)((unsigned)(v) & 0xFF); ((unsigned char*)(p))[1] = (unsigned char)(((unsigned)(v) >> 8) & 0xFF); ((unsigned char*)(p))[2] = (unsigned char)(((unsigned)(v) >> 16) & 0xFF); ((unsigned char*)(p))[3] = (unsigned char)((unsigned)(v) >> 24); }
#define XOR_READ_LE32(p) ((unsigned)(((const unsigned char *)(p))[0]) | ((unsigned)(((const unsigned char *)(p))[1]) << 8U) | ((unsigned)(((const unsigned char *)(p))[2]) << 16U) | ((unsigned)(((const unsigned char *)(p))[3]) << 24U))
static void XORAndClose(FILE* fBase, FILE* fXORPair, FILE* fOutput, const char* targetPath, const char* outputPath, Bit64u targetSize, bool writeFinalZeroes)
{
Bit64u zeroes = 0;
for (unsigned char byteBase = 0, byteTarget; targetSize; targetSize--)
{
if (!fread(&byteTarget, 1, 1, fXORPair)) byteTarget = 0;
if (!fread(&byteBase, 1, 1, fBase)) { fseek_wrap(fXORPair, 0, SEEK_SET); fread(&byteBase, 1, 1, fBase); }
unsigned char byteOutput = byteBase ^ byteTarget;
if (!byteOutput) { zeroes++; continue; }
if (zeroes) { for (Bit64u nulls = 0, n; (n = (zeroes > 7 ? 8 : zeroes)) != 0; zeroes -= n) fwrite(&nulls, 1, (int)n, fOutput); }
fwrite(&byteOutput, 1, 1, fOutput);
}
if (zeroes && writeFinalZeroes) { for (Bit64u nulls = 0, n; (n = (zeroes > 7 ? 8 : zeroes)) != 0; zeroes -= n) fwrite(&nulls, 1, (int)n, fOutput); }
fclose(fOutput);
// Copy modification time stamp from target to newly output file
struct stat targetStat;
struct utimbuf new_times;
stat(targetPath, &targetStat);
new_times.actime = targetStat.st_atime;
new_times.modtime = targetStat.st_mtime;
utime(outputPath, &new_times);
}
int main(int argc, char *argv[])
{
if (argc == 4) // Create XOR Patch
{
const char *basePath = argv[1], *targetPath = argv[2], *outputPath = argv[3], *relPath = basePath;
if ((basePath[0] == '/' && targetPath[0] != '/') || (basePath[0] == '\\' && targetPath[0] != '\\') || (basePath[1] == ':' && targetPath[1] != ':'))
{
fprintf(stderr, "Both BASE and TARGET paths must be absolute or relative.\n\nRun tool with:\n %s <BASE FILE> <TARGET FILE> <OUTPUT .XOR PATCH FILE>\n\n", (argc ? argv[0] : "MakeXORPatch"));
return 1;
}
if (!strcmp(basePath, targetPath)) { fprintf(stderr, "Base file '%s' can't be same as target file\n\n", basePath); return 1; }
if (fopen(outputPath, "rb")) { fprintf(stderr, "Output file '%s' already exists\n\n", outputPath); return 1; }
FILE* fBase = fopen(basePath, "rb"); if (!fBase) { fprintf(stderr, "Unable to open base file '%s'\n\n", basePath); return 1; }
FILE* fTarget = fopen(targetPath, "rb"); if (!fTarget) { fprintf(stderr, "Unable to open target file '%s'\n\n", targetPath); return 1; }
FILE* fOutput = fopen(outputPath, "wb"); if (!fOutput) { fprintf(stderr, "Unable to open output file '%s'\n\n", outputPath); return 1; }
fseek_wrap(fBase, 0, SEEK_END); Bit64u baseSize = (Bit64u)ftell_wrap(fBase); fseek_wrap(fBase, 0, SEEK_SET);
fseek_wrap(fTarget, 0, SEEK_END); Bit64u targetSize = (Bit64u)ftell_wrap(fTarget); fseek_wrap(fTarget, 0, SEEK_SET);
if (!baseSize) { fprintf(stderr, "Base file '%s' is an empty file and cannot be used\n\n", basePath); return 1; }
if (!targetSize) { fprintf(stderr, "Target file '%s' is an empty file and cannot be used\n\n", targetPath); return 1; }
int relativeParentDirs = 0;
for (int lastSlash = 0, i = 0;; i++)
{
if (basePath[i] == targetPath[i])
{
if (basePath[i] == '\\' || basePath[i] == '/') lastSlash = i;
}
else
{
if (lastSlash) relPath = basePath + lastSlash + 1;
for (; targetPath[i]; i++) { if (targetPath[i] == '\\' || targetPath[i] == '/') relativeParentDirs++; }
break;
}
}
printf("Generating XOR Patch '%s' with reference to base file '", outputPath);
for (int j = 0; j != relativeParentDirs; j++) printf("../");
for (const char* k = relPath; *k; k++) printf("%c", (*k == '\\' ? '/' : *k));
printf("' ...\n");
unsigned char hdrMagic[4];
XOR_WRITE_LE32(hdrMagic, 0x50524F58);
fwrite(hdrMagic, sizeof(hdrMagic), 1, fOutput);
// Try to find a base offset where some (or all) bytes at the end match with the target so the size of the output file can be reduced
Bit64u baseOffset = 0;
if (baseSize < 10*1024*1024 && baseSize > targetSize)
{
size_t maxPattern = (targetSize > 32768 ? 32768 : (size_t)targetSize), bestMatch = 0;
unsigned char *buf = (unsigned char*)malloc((size_t)(maxPattern + baseSize)), *targetPattern = buf, *baseData = buf + maxPattern;
fseek_wrap(fTarget, targetSize - maxPattern, SEEK_SET);
size_t patternLen = (size_t)fread(targetPattern, 1, maxPattern, fTarget), minPattern = (patternLen > 8 ? 8 : patternLen);
fseek_wrap(fTarget, 0, SEEK_SET);
fread(baseData, (size_t)baseSize, 1, fBase);
fseek_wrap(fBase, 0, SEEK_SET);
const unsigned char *targetMinPattern = targetPattern + patternLen - minPattern;
for (Bit64u i = targetSize - minPattern, iLast = baseSize - minPattern; i <= iLast; i++)
{
if (memcmp(baseData + i, targetMinPattern, minPattern)) continue;
size_t match = minPattern;
for (const unsigned char *p = baseData + i - 1, *pEnd = p + minPattern - patternLen, *q = targetMinPattern - 1; p != pEnd && *(p--) == *(q--);) match++;
if (match < bestMatch) continue;
baseOffset = i + minPattern - patternLen;
bestMatch = match;
if (match == patternLen) break;
}
fseek_wrap(fBase, baseOffset, SEEK_SET);
if (baseOffset) printf("Found %u matching trailing bytes at base offset %u\n", (unsigned)bestMatch, (unsigned)baseOffset);
}
for (int l = 0; l != relativeParentDirs; l++) fwrite("../", 3, 1, fOutput);
for (const char* m = relPath; ; m++) { char c = (*m == '\\' ? '/' : *m); fwrite(&c, 1, 1, fOutput); if (!c) break; }
for (Bit64u n1 = baseOffset;;) { unsigned char b = (unsigned char)((n1 & 0x7F) | ((n1 > 0x7F) ? 0x80 : 0)); fwrite(&b, 1, 1, fOutput); if (!(n1 >>= 7)) break; } // base offset encoded as LEB128
for (Bit64u n2 = targetSize;;) { unsigned char b = (unsigned char)((n2 & 0x7F) | ((n2 > 0x7F) ? 0x80 : 0)); fwrite(&b, 1, 1, fOutput); if (!(n2 >>= 7)) break; } // target size encoded as LEB128
XORAndClose(fBase, fTarget, fOutput, targetPath, outputPath, targetSize, false);
}
else if (argc == 2 || argc == 3)
{
const char *xorPath = argv[1];
FILE* fXOR = fopen(xorPath, "rb"); if (!fXOR) { fprintf(stderr, "Unable to open XOR file '%s'\n\n", xorPath); return 1; }
unsigned char hdrMagic[4];
if (!fread(hdrMagic, sizeof(hdrMagic), 1, fXOR) || XOR_READ_LE32(hdrMagic) != 0x50524F58) { fprintf(stderr, "XOR file '%s' is not an XOR patch file\n\n", xorPath); return 1; }
char basePath[512];
unsigned basePathLen = 0;
for (char c; fread(&c, 1, 1, fXOR) && c; basePathLen++) basePath[basePathLen] = ((c == '\\' || c == '/') ? CROSS_FILESPLIT : c);
basePath[basePathLen] = '\0';
Bit64u baseOffset = 0, targetSize = 0;
for (unsigned char b1, num1 = 0; fread(&b1, 1, 1, fXOR);) { baseOffset |= (Bit64u)(b1 & 0x7f) << (7 * (num1++)); if (b1 < 0x80) break; }
for (unsigned char b2, num2 = 0; fread(&b2, 1, 1, fXOR);) { targetSize |= (Bit64u)(b2 & 0x7f) << (7 * (num2++)); if (b2 < 0x80) break; }
if (argc == 3) // Apply XOR Patch
{
const char *outputPath = argv[2];
if (fopen(outputPath, "rb")) { fprintf(stderr, "Output file '%s' already exists\n\n", outputPath); return 1; }
FILE* fBase = fopen(basePath, "rb"); if (!fBase) { fprintf(stderr, "Unable to open referenced base file '%s'\n\n", basePath); return 1; }
FILE* fOutput = fopen(outputPath, "wb"); if (!fOutput) { fprintf(stderr, "Unable to open output file '%s'\n\n", outputPath); return 1; }
fseek_wrap(fBase, baseOffset, SEEK_SET);
XORAndClose(fBase, fXOR, fOutput, xorPath, outputPath, targetSize, true);
}
else if (argc == 2) // Get XOR Patch info
{
printf("XOR Patch File: %s\n", xorPath);
printf("Referenced Base File: %s\n", basePath);
printf("Base File Offset: %u\n", (unsigned)baseOffset);
printf("Default Target Name: %.*s\n", (strlen(xorPath) > 4 ? (int)(strlen(xorPath)-4) : 0), xorPath);
printf("Target Result Size: %u\n", (unsigned)targetSize);
struct stat xorStat;
stat(xorPath, &xorStat);
struct tm xorMTime = *localtime(&xorStat.st_mtime);
printf("Target Time Stamp: %04d-%02d-%02d %02d:%02d:%02d\n", xorMTime.tm_year + 1900, xorMTime.tm_mon + 1, xorMTime.tm_mday, xorMTime.tm_hour, xorMTime.tm_min, xorMTime.tm_sec);
printf("\n");
}
}
else
{
fprintf(stderr, "Wrong arguments.\n\n");
fprintf(stderr, " Create XOR Patch: %s <BASE FILE> <TARGET FILE> <OUTPUT .XOR PATCH FILE>\n\n", (argc ? argv[0] : "MakeXORPatch"));
fprintf(stderr, " Apply XOR Patch: %s <.XOR PATCH FILE> <OUTPUT TARGET FILE>\n\n", (argc ? argv[0] : "MakeXORPatch"));
fprintf(stderr, " Get XOR Patch info: %s <.XOR PATCH FILE>\n\n", (argc ? argv[0] : "MakeXORPatch"));
return 1;
}
return 0;
}