madden-franchise 3.2.6 → 3.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/FranchiseFile.js +291 -230
- package/FranchiseFileField.js +18 -11
- package/FranchiseFileTable.js +57 -15
- package/FranchiseFileTable2Field.js +0 -3
- package/README.md +232 -91
- package/package.json +1 -1
- package/scripts/data/PlayerSchema.json +1 -0
- package/scripts/data/SeasonScheduleManager.SeasonScheduleTunableDataOffsetTable.json +1 -0
- package/scripts/data/SeasonScheduleManager.SeasonScheduleTunableDataSchema.json +1 -0
- package/scripts/data/playerOffsetTable.json +1 -0
- package/scripts/deflate.js +5 -5
- package/scripts/inflate.js +10 -7
- package/scripts/scratchpad.js +53 -46
package/FranchiseFile.js
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
|
-
const fs = require(
|
|
2
|
-
const zlib = require(
|
|
3
|
-
const Constants = require(
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const zlib = require("zlib");
|
|
3
|
+
const Constants = require("./Constants");
|
|
4
4
|
// const debug = require('debug')('madden-franchise');
|
|
5
|
-
const EventEmitter = require(
|
|
6
|
-
const FranchiseSchema = require(
|
|
7
|
-
const utilService = require(
|
|
8
|
-
const FranchiseFileTable = require(
|
|
9
|
-
const StrategyPicker = require(
|
|
10
|
-
const FranchiseFileSettings = require(
|
|
11
|
-
const schemaPickerService = require(
|
|
12
|
-
|
|
13
|
-
const COMPRESSED_FILE_LENGTH = 2936094;
|
|
5
|
+
const EventEmitter = require("events").EventEmitter;
|
|
6
|
+
const FranchiseSchema = require("./FranchiseSchema");
|
|
7
|
+
const utilService = require("./services/utilService");
|
|
8
|
+
const FranchiseFileTable = require("./FranchiseFileTable");
|
|
9
|
+
const StrategyPicker = require("./strategies/StrategyPicker");
|
|
10
|
+
const FranchiseFileSettings = require("./FranchiseFileSettings");
|
|
11
|
+
const schemaPickerService = require("./services/schemaPicker");
|
|
12
|
+
|
|
14
13
|
const COMPRESSED_DATA_OFFSET = 0x52;
|
|
15
14
|
|
|
16
15
|
class FranchiseFile extends EventEmitter {
|
|
16
|
+
static create(filePath, settings) {
|
|
17
|
+
return new Promise((resolve, reject) => {
|
|
18
|
+
const file = new FranchiseFile(filePath, settings);
|
|
19
|
+
|
|
20
|
+
if (file.settings.autoParse) {
|
|
21
|
+
file.on('ready', () => resolve(file))
|
|
22
|
+
file.on('error', () => reject(err))
|
|
23
|
+
} else {
|
|
24
|
+
resolve(file);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
17
29
|
constructor(filePath, settings) {
|
|
18
30
|
super();
|
|
19
31
|
this._settings = new FranchiseFileSettings(settings);
|
|
@@ -24,11 +36,14 @@ class FranchiseFile extends EventEmitter {
|
|
|
24
36
|
} else {
|
|
25
37
|
this._filePath = filePath;
|
|
26
38
|
}
|
|
27
|
-
|
|
39
|
+
|
|
28
40
|
this._rawContents = fs.readFileSync(filePath);
|
|
29
41
|
this._type = getFileType(this._rawContents);
|
|
30
42
|
this._gameYear = this._type.year;
|
|
31
|
-
this._expectedSchemaVersion = getSchemaMetadata(
|
|
43
|
+
this._expectedSchemaVersion = getSchemaMetadata(
|
|
44
|
+
this.rawContents,
|
|
45
|
+
this._type
|
|
46
|
+
);
|
|
32
47
|
|
|
33
48
|
if (this._type.compressed) {
|
|
34
49
|
this.packedFileContents = this._rawContents;
|
|
@@ -38,37 +53,47 @@ class FranchiseFile extends EventEmitter {
|
|
|
38
53
|
const newType = getFileType(this.unpackedFileContents);
|
|
39
54
|
this._type.year = newType.year;
|
|
40
55
|
this._gameYear = this._type.year;
|
|
41
|
-
this._expectedSchemaVersion = getSchemaMetadata(
|
|
56
|
+
this._expectedSchemaVersion = getSchemaMetadata(
|
|
57
|
+
this.unpackedFileContents,
|
|
58
|
+
newType
|
|
59
|
+
);
|
|
42
60
|
}
|
|
43
|
-
}
|
|
44
|
-
else {
|
|
61
|
+
} else {
|
|
45
62
|
this.unpackedFileContents = this._rawContents;
|
|
46
63
|
}
|
|
47
64
|
|
|
48
65
|
if (this._settings.autoParse) {
|
|
49
66
|
this.parse();
|
|
50
67
|
}
|
|
51
|
-
}
|
|
68
|
+
}
|
|
52
69
|
|
|
53
70
|
parse() {
|
|
54
71
|
const that = this;
|
|
55
72
|
this.strategy = StrategyPicker.pick(this.type);
|
|
56
73
|
|
|
57
74
|
let schemaPromise = new Promise((resolve, reject) => {
|
|
58
|
-
const schemaMeta = this.settings.schemaOverride
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
75
|
+
const schemaMeta = this.settings.schemaOverride
|
|
76
|
+
? this.settings.schemaOverride
|
|
77
|
+
: this.expectedSchemaVersion;
|
|
78
|
+
|
|
79
|
+
const schemaPath =
|
|
80
|
+
this.settings.schemaOverride && this.settings.schemaOverride.path
|
|
81
|
+
? this.settings.schemaOverride.path
|
|
82
|
+
: schemaPickerService.pick(
|
|
83
|
+
this._gameYear,
|
|
84
|
+
schemaMeta.major,
|
|
85
|
+
schemaMeta.minor,
|
|
86
|
+
this.settings
|
|
87
|
+
).path;
|
|
62
88
|
|
|
63
89
|
try {
|
|
64
90
|
this.schemaList = new FranchiseSchema(schemaPath);
|
|
65
|
-
this.schemaList.on(
|
|
91
|
+
this.schemaList.on("schemas:done", () => {
|
|
66
92
|
resolve();
|
|
67
93
|
});
|
|
68
94
|
|
|
69
95
|
this.schemaList.evaluate();
|
|
70
|
-
}
|
|
71
|
-
catch (err) {
|
|
96
|
+
} catch (err) {
|
|
72
97
|
reject(err);
|
|
73
98
|
}
|
|
74
99
|
});
|
|
@@ -82,7 +107,7 @@ class FranchiseFile extends EventEmitter {
|
|
|
82
107
|
const altFirstCheck = 0x41;
|
|
83
108
|
const altSecondCheck = 0x53;
|
|
84
109
|
const altThirdCheck = 0x54;
|
|
85
|
-
const altFourthCheck =
|
|
110
|
+
const altFourthCheck = 0x4f;
|
|
86
111
|
|
|
87
112
|
const alt2FirstCheck = 0x53;
|
|
88
113
|
const alt2SecondCheck = 0x50;
|
|
@@ -91,44 +116,58 @@ class FranchiseFile extends EventEmitter {
|
|
|
91
116
|
|
|
92
117
|
const tableIndicies = [];
|
|
93
118
|
|
|
94
|
-
for (let i = 0; i <= this.unpackedFileContents.length - 4; i+=1) {
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
119
|
+
for (let i = 0; i <= this.unpackedFileContents.length - 4; i += 1) {
|
|
120
|
+
if (
|
|
121
|
+
(this.unpackedFileContents[i] === firstCheck &&
|
|
122
|
+
this.unpackedFileContents[i + 1] === secondCheck &&
|
|
123
|
+
this.unpackedFileContents[i + 2] === thirdCheck &&
|
|
124
|
+
this.unpackedFileContents[i + 3] === fourthCheck) ||
|
|
125
|
+
(this.unpackedFileContents[i] === altFirstCheck &&
|
|
126
|
+
this.unpackedFileContents[i + 1] === altSecondCheck &&
|
|
127
|
+
this.unpackedFileContents[i + 2] === altThirdCheck &&
|
|
128
|
+
this.unpackedFileContents[i + 3] === altFourthCheck) ||
|
|
129
|
+
(this.unpackedFileContents[i] === alt2FirstCheck &&
|
|
130
|
+
this.unpackedFileContents[i + 1] === alt2SecondCheck &&
|
|
131
|
+
this.unpackedFileContents[i + 2] === alt2ThirdCheck &&
|
|
132
|
+
this.unpackedFileContents[i + 3] === alt2FourthCheck)
|
|
133
|
+
) {
|
|
134
|
+
const tableStart = i - getTableStartOffsetByGameYear(this._gameYear);
|
|
135
|
+
tableIndicies.push(tableStart);
|
|
136
|
+
}
|
|
110
137
|
}
|
|
111
138
|
|
|
112
139
|
this.tables = [];
|
|
113
140
|
|
|
114
141
|
for (let i = 0; i < tableIndicies.length; i++) {
|
|
115
142
|
const currentTable = tableIndicies[i];
|
|
116
|
-
const nextTable =
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
143
|
+
const nextTable =
|
|
144
|
+
tableIndicies.length > i + 1
|
|
145
|
+
? tableIndicies[i + 1]
|
|
146
|
+
: this.unpackedFileContents.length - 8; // Ignore trailing 8 bytes on last table
|
|
147
|
+
|
|
148
|
+
const tableData = this.unpackedFileContents.slice(
|
|
149
|
+
currentTable,
|
|
150
|
+
nextTable
|
|
151
|
+
);
|
|
152
|
+
|
|
153
|
+
const newFranchiseTable = new FranchiseFileTable(
|
|
154
|
+
tableData,
|
|
155
|
+
currentTable,
|
|
156
|
+
this._gameYear,
|
|
157
|
+
this.strategy,
|
|
158
|
+
this.settings
|
|
159
|
+
);
|
|
121
160
|
newFranchiseTable.index = i;
|
|
122
161
|
this.tables.push(newFranchiseTable);
|
|
123
162
|
|
|
124
|
-
newFranchiseTable.on(
|
|
163
|
+
newFranchiseTable.on("change", function () {
|
|
125
164
|
this.isChanged = true;
|
|
126
165
|
|
|
127
166
|
if (that.settings.saveOnChange) {
|
|
128
167
|
that.packFile();
|
|
129
168
|
}
|
|
130
169
|
|
|
131
|
-
that.emit(
|
|
170
|
+
that.emit("change", newFranchiseTable);
|
|
132
171
|
});
|
|
133
172
|
}
|
|
134
173
|
|
|
@@ -142,60 +181,69 @@ class FranchiseFile extends EventEmitter {
|
|
|
142
181
|
const assetTableEntries = this.unpackedFileContents.readUInt32BE(36);
|
|
143
182
|
|
|
144
183
|
let currentOffset = assetTableOffset;
|
|
145
|
-
|
|
184
|
+
|
|
146
185
|
for (let i = 0; i < assetTableEntries; i++) {
|
|
147
186
|
const assetId = this.unpackedFileContents.readUInt32BE(currentOffset);
|
|
148
|
-
const reference = this.unpackedFileContents.readUInt32BE(
|
|
187
|
+
const reference = this.unpackedFileContents.readUInt32BE(
|
|
188
|
+
currentOffset + 4
|
|
189
|
+
);
|
|
149
190
|
|
|
150
191
|
this.assetTable.push({
|
|
151
|
-
|
|
152
|
-
|
|
192
|
+
assetId: assetId,
|
|
193
|
+
reference: reference,
|
|
153
194
|
});
|
|
154
195
|
|
|
155
196
|
currentOffset += 8;
|
|
156
|
-
}
|
|
197
|
+
}
|
|
157
198
|
|
|
158
199
|
resolve();
|
|
159
200
|
});
|
|
160
201
|
|
|
161
|
-
Promise.all([schemaPromise, tablePromise, assetTablePromise])
|
|
162
|
-
|
|
163
|
-
|
|
202
|
+
Promise.all([schemaPromise, tablePromise, assetTablePromise])
|
|
203
|
+
.then(() => {
|
|
204
|
+
that.tables.forEach((table, index) => {
|
|
205
|
+
const schema = that.schemaList.getSchema(table.name);
|
|
164
206
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
207
|
+
if (schema) {
|
|
208
|
+
table.schema = schema;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
169
211
|
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
212
|
+
that.isLoaded = true;
|
|
213
|
+
that.emit("ready");
|
|
214
|
+
})
|
|
215
|
+
.catch((err) => {
|
|
216
|
+
console.log(err);
|
|
217
|
+
that.emit("error", err);
|
|
218
|
+
});
|
|
219
|
+
}
|
|
177
220
|
|
|
178
221
|
save(outputFilePath, options) {
|
|
179
222
|
return this.packFile(outputFilePath, options);
|
|
180
|
-
}
|
|
223
|
+
}
|
|
181
224
|
|
|
182
225
|
packFile(outputFilePath, options) {
|
|
183
226
|
const that = this;
|
|
184
|
-
this.emit(
|
|
227
|
+
this.emit("saving");
|
|
185
228
|
|
|
186
229
|
return new Promise((resolve, reject) => {
|
|
187
|
-
this.unpackedFileContents = this.strategy.file.generateUnpackedContents(
|
|
230
|
+
this.unpackedFileContents = this.strategy.file.generateUnpackedContents(
|
|
231
|
+
this.tables,
|
|
232
|
+
this.unpackedFileContents
|
|
233
|
+
);
|
|
188
234
|
|
|
189
235
|
let destination = outputFilePath ? outputFilePath : this.filePath;
|
|
190
|
-
|
|
236
|
+
|
|
191
237
|
_packFile(this.unpackedFileContents, options).then((data) => {
|
|
192
|
-
const dataToSave = this.strategy.file.postPackFile(
|
|
238
|
+
const dataToSave = this.strategy.file.postPackFile(
|
|
239
|
+
this.packedFileContents,
|
|
240
|
+
data
|
|
241
|
+
);
|
|
193
242
|
|
|
194
243
|
if (options && options.sync) {
|
|
195
244
|
_saveSync(destination, dataToSave);
|
|
196
245
|
postSaveActions();
|
|
197
|
-
}
|
|
198
|
-
else {
|
|
246
|
+
} else {
|
|
199
247
|
_save(destination, dataToSave, (err) => {
|
|
200
248
|
postSaveActions(err);
|
|
201
249
|
});
|
|
@@ -204,130 +252,153 @@ class FranchiseFile extends EventEmitter {
|
|
|
204
252
|
function postSaveActions(err) {
|
|
205
253
|
if (err) {
|
|
206
254
|
reject(err);
|
|
207
|
-
that.emit(
|
|
255
|
+
that.emit("save-error");
|
|
208
256
|
}
|
|
209
257
|
|
|
210
|
-
resolve(
|
|
211
|
-
that.emit(
|
|
258
|
+
resolve("saved");
|
|
259
|
+
that.emit("saved");
|
|
212
260
|
}
|
|
213
261
|
});
|
|
214
262
|
});
|
|
215
|
-
}
|
|
263
|
+
}
|
|
216
264
|
|
|
217
|
-
get rawContents
|
|
265
|
+
get rawContents() {
|
|
218
266
|
return this._rawContents;
|
|
219
|
-
}
|
|
267
|
+
}
|
|
220
268
|
|
|
221
|
-
get openedFranchiseFile
|
|
269
|
+
get openedFranchiseFile() {
|
|
222
270
|
return this._openedFranchiseFile;
|
|
223
|
-
}
|
|
271
|
+
}
|
|
224
272
|
|
|
225
|
-
get filePath
|
|
273
|
+
get filePath() {
|
|
226
274
|
return this._filePath;
|
|
227
|
-
}
|
|
275
|
+
}
|
|
228
276
|
|
|
229
|
-
get schema
|
|
277
|
+
get schema() {
|
|
230
278
|
return this.schemaList;
|
|
231
|
-
}
|
|
279
|
+
}
|
|
232
280
|
|
|
233
|
-
get expectedSchemaVersion
|
|
281
|
+
get expectedSchemaVersion() {
|
|
234
282
|
return this._expectedSchemaVersion;
|
|
235
|
-
}
|
|
283
|
+
}
|
|
236
284
|
|
|
237
|
-
get settings
|
|
285
|
+
get settings() {
|
|
238
286
|
return this._settings;
|
|
239
|
-
}
|
|
287
|
+
}
|
|
240
288
|
|
|
241
|
-
get gameYear
|
|
289
|
+
get gameYear() {
|
|
242
290
|
return this._gameYear;
|
|
243
|
-
}
|
|
291
|
+
}
|
|
244
292
|
|
|
245
|
-
get type
|
|
293
|
+
get type() {
|
|
246
294
|
return this._type;
|
|
247
|
-
}
|
|
295
|
+
}
|
|
248
296
|
|
|
249
|
-
set filePath
|
|
297
|
+
set filePath(path) {
|
|
250
298
|
this._filePath = path;
|
|
251
|
-
}
|
|
299
|
+
}
|
|
252
300
|
|
|
253
|
-
set settings
|
|
301
|
+
set settings(settings) {
|
|
254
302
|
this._settings = new FranchiseFileSettings(settings);
|
|
255
|
-
}
|
|
303
|
+
}
|
|
256
304
|
|
|
257
|
-
getTableByName
|
|
258
|
-
return this.tables.find((table) => {
|
|
259
|
-
|
|
305
|
+
getTableByName(name) {
|
|
306
|
+
return this.tables.find((table) => {
|
|
307
|
+
return table.name === name;
|
|
308
|
+
});
|
|
309
|
+
}
|
|
260
310
|
|
|
261
|
-
getAllTablesByName
|
|
262
|
-
return this.tables.filter((table) => {
|
|
263
|
-
|
|
311
|
+
getAllTablesByName(name) {
|
|
312
|
+
return this.tables.filter((table) => {
|
|
313
|
+
return table.name === name;
|
|
314
|
+
});
|
|
315
|
+
}
|
|
264
316
|
|
|
265
|
-
getTableById
|
|
266
|
-
return this.tables.find((table) => {
|
|
267
|
-
|
|
317
|
+
getTableById(id) {
|
|
318
|
+
return this.tables.find((table) => {
|
|
319
|
+
return table.header && table.header.tableId === id;
|
|
320
|
+
});
|
|
321
|
+
}
|
|
268
322
|
|
|
269
|
-
getTableByIndex
|
|
323
|
+
getTableByIndex(index) {
|
|
270
324
|
return this.tables[index];
|
|
271
|
-
}
|
|
325
|
+
}
|
|
272
326
|
|
|
273
|
-
getTableByUniqueId
|
|
274
|
-
return this.tables.find((table) => {
|
|
275
|
-
|
|
327
|
+
getTableByUniqueId(id) {
|
|
328
|
+
return this.tables.find((table) => {
|
|
329
|
+
return table.header && table.header.uniqueId === id;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
276
332
|
|
|
277
|
-
getReferencedRecord
|
|
333
|
+
getReferencedRecord(referenceValue) {
|
|
278
334
|
const reference = utilService.getReferenceData(referenceValue);
|
|
279
335
|
return this.getTableById(reference.tableId)?.records[reference.rowNumber];
|
|
280
|
-
}
|
|
336
|
+
}
|
|
281
337
|
|
|
282
|
-
getReferenceFromAssetId
|
|
338
|
+
getReferenceFromAssetId(assetId) {
|
|
283
339
|
const assetEntry = this.assetTable.find((assetEntry) => {
|
|
284
340
|
return assetEntry.assetId === assetId;
|
|
285
341
|
});
|
|
286
342
|
|
|
287
343
|
if (assetEntry) {
|
|
288
|
-
const referenceBinaryString = assetEntry.reference
|
|
344
|
+
const referenceBinaryString = assetEntry.reference
|
|
345
|
+
.toString(2)
|
|
346
|
+
.padStart(32);
|
|
289
347
|
return utilService.getReferenceData(referenceBinaryString);
|
|
290
|
-
}
|
|
291
|
-
else {
|
|
348
|
+
} else {
|
|
292
349
|
return null;
|
|
293
350
|
}
|
|
294
|
-
}
|
|
351
|
+
}
|
|
295
352
|
|
|
296
353
|
getReferencesToRecord(tableId, recordIndex) {
|
|
297
354
|
const referencedTable = this.getTableById(tableId);
|
|
298
355
|
|
|
299
356
|
if (referencedTable) {
|
|
300
|
-
const fullBinary = utilService.getBinaryReferenceData(
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
table
|
|
326
|
-
|
|
327
|
-
|
|
357
|
+
const fullBinary = utilService.getBinaryReferenceData(
|
|
358
|
+
tableId,
|
|
359
|
+
recordIndex
|
|
360
|
+
);
|
|
361
|
+
const hex = utilService.bin2hex(fullBinary).padStart(8, "0");
|
|
362
|
+
|
|
363
|
+
return this.tables
|
|
364
|
+
.filter((table) => {
|
|
365
|
+
if (table.schema) {
|
|
366
|
+
return (
|
|
367
|
+
table.schema &&
|
|
368
|
+
table.schema.attributes.find((attribute) => {
|
|
369
|
+
return attribute.type === referencedTable.name;
|
|
370
|
+
})
|
|
371
|
+
);
|
|
372
|
+
} else if (table.isArray && referencedTable.schema) {
|
|
373
|
+
// If the referenced table has a schema, we can check its base name.
|
|
374
|
+
// Some array table names are by the base name like EnumTable[] can contain AwardTypeEnumTableEntry
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
table.name.slice(0, table.name.length - 2) ===
|
|
378
|
+
referencedTable.name ||
|
|
379
|
+
table.name.slice(0, table.name.length - 2) ===
|
|
380
|
+
referencedTable.schema.base
|
|
381
|
+
);
|
|
382
|
+
} else if (table.isArray) {
|
|
383
|
+
return (
|
|
384
|
+
table.name.slice(0, table.name.length - 2) ===
|
|
385
|
+
referencedTable.name
|
|
386
|
+
);
|
|
387
|
+
}
|
|
388
|
+
})
|
|
389
|
+
.filter((table) => {
|
|
390
|
+
return table.data.indexOf(hex, 0, "hex") !== -1;
|
|
391
|
+
})
|
|
392
|
+
.map((table) => {
|
|
393
|
+
return {
|
|
394
|
+
tableId: table.header.tableId,
|
|
395
|
+
name: table.name,
|
|
396
|
+
table: table,
|
|
397
|
+
};
|
|
398
|
+
});
|
|
328
399
|
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
331
402
|
|
|
332
403
|
module.exports = FranchiseFile;
|
|
333
404
|
|
|
@@ -340,9 +411,9 @@ function getTableStartOffsetByGameYear(gameYear) {
|
|
|
340
411
|
default:
|
|
341
412
|
return 0x94;
|
|
342
413
|
}
|
|
343
|
-
}
|
|
414
|
+
}
|
|
344
415
|
|
|
345
|
-
function unpackFile
|
|
416
|
+
function unpackFile(data, type) {
|
|
346
417
|
let offset = 0;
|
|
347
418
|
|
|
348
419
|
if (type.format === Constants.FORMAT.FRANCHISE) {
|
|
@@ -350,36 +421,39 @@ function unpackFile (data, type) {
|
|
|
350
421
|
}
|
|
351
422
|
|
|
352
423
|
return zlib.inflateSync(data.slice(offset));
|
|
353
|
-
}
|
|
424
|
+
}
|
|
354
425
|
|
|
355
|
-
function _packFile
|
|
426
|
+
function _packFile(data, options) {
|
|
356
427
|
return new Promise((resolve, reject) => {
|
|
357
428
|
if (options && options.sync) {
|
|
358
429
|
const newData = zlib.deflateSync(data, {
|
|
359
|
-
windowBits: 15
|
|
430
|
+
windowBits: 15,
|
|
360
431
|
});
|
|
361
|
-
|
|
432
|
+
|
|
362
433
|
resolve(newData);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
434
|
+
} else {
|
|
435
|
+
zlib.deflate(
|
|
436
|
+
data,
|
|
437
|
+
{
|
|
438
|
+
windowBits: 15,
|
|
439
|
+
},
|
|
440
|
+
function (err, newData) {
|
|
441
|
+
if (err) reject(err);
|
|
442
|
+
|
|
443
|
+
resolve(newData);
|
|
444
|
+
}
|
|
445
|
+
);
|
|
372
446
|
}
|
|
373
447
|
});
|
|
374
|
-
}
|
|
448
|
+
}
|
|
375
449
|
|
|
376
|
-
function _save
|
|
450
|
+
function _save(destination, packedContents, callback) {
|
|
377
451
|
fs.writeFile(destination, packedContents, callback);
|
|
378
|
-
}
|
|
452
|
+
}
|
|
379
453
|
|
|
380
|
-
function _saveSync
|
|
454
|
+
function _saveSync(destination, packedContents) {
|
|
381
455
|
fs.writeFileSync(destination, packedContents);
|
|
382
|
-
}
|
|
456
|
+
}
|
|
383
457
|
|
|
384
458
|
function getFileType(data) {
|
|
385
459
|
const isDataCompressed = isCompressed(data);
|
|
@@ -387,59 +461,56 @@ function getFileType(data) {
|
|
|
387
461
|
const year = getGameYear(data, isDataCompressed, format);
|
|
388
462
|
|
|
389
463
|
return {
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
464
|
+
format: format,
|
|
465
|
+
compressed: isDataCompressed,
|
|
466
|
+
year: year,
|
|
393
467
|
};
|
|
394
|
-
}
|
|
468
|
+
}
|
|
395
469
|
|
|
396
470
|
function isCompressed(data) {
|
|
397
|
-
const DECOMPRESSED_HEADER = Buffer.from([0x46, 0x72, 0x54,
|
|
471
|
+
const DECOMPRESSED_HEADER = Buffer.from([0x46, 0x72, 0x54, 0x6b]); // FrTk
|
|
398
472
|
|
|
399
473
|
if (Buffer.compare(data.slice(0, 4), DECOMPRESSED_HEADER) === 0) {
|
|
400
474
|
return false;
|
|
401
475
|
}
|
|
402
476
|
|
|
403
477
|
return true;
|
|
404
|
-
}
|
|
478
|
+
}
|
|
405
479
|
|
|
406
480
|
function getFormat(data, isCompressed) {
|
|
407
481
|
if (isCompressed) {
|
|
408
|
-
const ZLIB_HEADER = Buffer.from([0x78,
|
|
482
|
+
const ZLIB_HEADER = Buffer.from([0x78, 0x9c]);
|
|
409
483
|
|
|
410
484
|
if (Buffer.compare(data.slice(0, 2), ZLIB_HEADER) === 0) {
|
|
411
|
-
return
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
return 'franchise';
|
|
485
|
+
return "franchise-common";
|
|
486
|
+
} else {
|
|
487
|
+
return "franchise";
|
|
415
488
|
}
|
|
416
|
-
}
|
|
417
|
-
else {
|
|
489
|
+
} else {
|
|
418
490
|
// very simple check based on file length.
|
|
419
491
|
// This assumes the common files are smaller than 9,000 KB.
|
|
420
492
|
if (data.length > 0x895440) {
|
|
421
|
-
return
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
return 'franchise-common';
|
|
493
|
+
return "franchise";
|
|
494
|
+
} else {
|
|
495
|
+
return "franchise-common";
|
|
425
496
|
}
|
|
426
497
|
}
|
|
427
|
-
}
|
|
498
|
+
}
|
|
428
499
|
|
|
429
500
|
function getGameYear(data, isCompressed, format) {
|
|
430
501
|
const schemaMax = [
|
|
431
502
|
{
|
|
432
|
-
|
|
433
|
-
|
|
503
|
+
year: 19,
|
|
504
|
+
max: 95,
|
|
434
505
|
},
|
|
435
506
|
{
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
}
|
|
507
|
+
year: 24,
|
|
508
|
+
max: 999,
|
|
509
|
+
},
|
|
439
510
|
];
|
|
440
511
|
|
|
441
|
-
if(isCompressed) {
|
|
442
|
-
// look at the max schemas per year. M19 schemas will be less than or equal to 95,
|
|
512
|
+
if (isCompressed) {
|
|
513
|
+
// look at the max schemas per year. M19 schemas will be less than or equal to 95,
|
|
443
514
|
// while M20 schemas can be anywhere from 96 to 999 because the last schema hasn't been made yet.
|
|
444
515
|
// Once M21 releases, the M20 schema max will be updated with the final number.
|
|
445
516
|
|
|
@@ -455,51 +526,43 @@ function getGameYear(data, isCompressed, format) {
|
|
|
455
526
|
|
|
456
527
|
if (yearIdentifier[0] === 0x52) {
|
|
457
528
|
return 19;
|
|
458
|
-
}
|
|
459
|
-
else if (yearIdentifier[2] === 0x30) {
|
|
529
|
+
} else if (yearIdentifier[2] === 0x30) {
|
|
460
530
|
return 20;
|
|
461
|
-
}
|
|
462
|
-
else if (yearIdentifier[2] === 0x31) {
|
|
531
|
+
} else if (yearIdentifier[2] === 0x31) {
|
|
463
532
|
return 21;
|
|
464
|
-
}
|
|
465
|
-
else if (yearIdentifier[2] === 0x32) {
|
|
533
|
+
} else if (yearIdentifier[2] === 0x32) {
|
|
466
534
|
return 22;
|
|
467
|
-
}
|
|
468
|
-
else if (yearIdentifier[2] === 0x33) {
|
|
535
|
+
} else if (yearIdentifier[2] === 0x33) {
|
|
469
536
|
return 23;
|
|
470
|
-
}
|
|
471
|
-
else if (yearIdentifier[2] === 0x34 || yearIdentifier[2] === 'd') {
|
|
537
|
+
} else if (yearIdentifier[2] === 0x34 || yearIdentifier[2] === "d") {
|
|
472
538
|
return 24;
|
|
473
|
-
}
|
|
474
|
-
else {
|
|
539
|
+
} else {
|
|
475
540
|
const schemaMajor = getCompressedSchema(data).major;
|
|
476
|
-
const year = schemaMax.find((schema) => {
|
|
541
|
+
const year = schemaMax.find((schema) => {
|
|
542
|
+
return schema.max >= schemaMajor;
|
|
543
|
+
}).year;
|
|
477
544
|
return year;
|
|
478
545
|
}
|
|
479
|
-
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
546
|
+
} else {
|
|
482
547
|
const schemaMajor = getDecompressedM20Schema(data).major;
|
|
483
|
-
|
|
548
|
+
|
|
484
549
|
if (schemaMajor === 0) {
|
|
485
550
|
// M19 did not include schema info in uncompressed files.
|
|
486
551
|
return 19;
|
|
487
|
-
}
|
|
488
|
-
else {
|
|
552
|
+
} else {
|
|
489
553
|
// M21 and M20 have very similar formats. We can tell M21 because it has a table with 'M21' in the name.
|
|
490
|
-
if (data.indexOf(
|
|
554
|
+
if (data.indexOf("M21") > -1) {
|
|
491
555
|
return 21;
|
|
492
|
-
}
|
|
493
|
-
else {
|
|
556
|
+
} else {
|
|
494
557
|
return null;
|
|
495
558
|
}
|
|
496
559
|
}
|
|
497
560
|
}
|
|
498
|
-
}
|
|
561
|
+
}
|
|
499
562
|
|
|
500
563
|
function getSchemaMetadata(data, type) {
|
|
501
564
|
let schemaMeta = {
|
|
502
|
-
|
|
565
|
+
gameYear: type.year,
|
|
503
566
|
};
|
|
504
567
|
|
|
505
568
|
if (type.compressed) {
|
|
@@ -512,14 +575,12 @@ function getSchemaMetadata(data, type) {
|
|
|
512
575
|
const schemaData = getCompressedSchema(data);
|
|
513
576
|
schemaMeta.major = schemaData.major;
|
|
514
577
|
schemaMeta.minor = schemaData.minor;
|
|
515
|
-
}
|
|
516
|
-
else {
|
|
578
|
+
} else {
|
|
517
579
|
if (type.year === 19) {
|
|
518
580
|
// M19 did not include schema info in uncompressed files.
|
|
519
581
|
schemaMeta.major = 0;
|
|
520
582
|
schemaMeta.minor = 0;
|
|
521
|
-
}
|
|
522
|
-
else {
|
|
583
|
+
} else {
|
|
523
584
|
const schemaData = getDecompressedM20Schema(data);
|
|
524
585
|
schemaMeta.major = schemaData.major;
|
|
525
586
|
schemaMeta.minor = schemaData.minor;
|
|
@@ -527,18 +588,18 @@ function getSchemaMetadata(data, type) {
|
|
|
527
588
|
}
|
|
528
589
|
|
|
529
590
|
return schemaMeta;
|
|
530
|
-
}
|
|
591
|
+
}
|
|
531
592
|
|
|
532
593
|
function getCompressedSchema(data) {
|
|
533
594
|
return {
|
|
534
|
-
|
|
535
|
-
|
|
595
|
+
major: data.readUInt32LE(0x3e),
|
|
596
|
+
minor: data.readUInt32LE(0x42),
|
|
536
597
|
};
|
|
537
|
-
}
|
|
598
|
+
}
|
|
538
599
|
|
|
539
600
|
function getDecompressedM20Schema(data) {
|
|
540
601
|
return {
|
|
541
|
-
|
|
542
|
-
|
|
602
|
+
major: data.readUInt32BE(0x2c),
|
|
603
|
+
minor: data.readUInt32BE(0x28),
|
|
543
604
|
};
|
|
544
|
-
}
|
|
605
|
+
}
|