madden-franchise 2.3.3 → 2.3.7

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.
@@ -68,7 +68,7 @@ class FranchiseFileField extends EventEmitter {
68
68
  this.emit('change');
69
69
  };
70
70
 
71
- setUnformattedValueWithoutChangeEvent(unformattedValue) {
71
+ setUnformattedValueWithoutChangeEvent(unformattedValue, suppressErrors) {
72
72
  if (!utilService.isString(unformattedValue)) { throw new Error(`Argument must be of type string. You passed in a ${typeof unformattedValue}.`); }
73
73
  else if (!utilService.stringOnlyContainsBinaryDigits(unformattedValue)) { throw new Error(`Argument must only contain binary digits 1 and 0. If you would like to set the value, please set the 'value' attribute instead.`)}
74
74
  else {
@@ -82,7 +82,7 @@ class FranchiseFileField extends EventEmitter {
82
82
  }
83
83
 
84
84
  // check for 'allowed' error - this will be true if the unformatted value is invalid.
85
- if (this._offset.enum && value === unformattedValue.padStart(this._offset.length, '0')) {
85
+ if (this._offset.enum && value === unformattedValue.padStart(this._offset.length, '0') && !suppressErrors) {
86
86
  throw new Error(`Argument is not a valid unformatted value for this field. You passed in ${value}.`)
87
87
  }
88
88
 
@@ -25,6 +25,27 @@ class FranchiseFileTable extends EventEmitter {
25
25
  };
26
26
 
27
27
  get hexData () {
28
+ this.updateBuffer();
29
+ return this.data;
30
+ };
31
+
32
+ set schema (schema) {
33
+ // console.time('set schema');
34
+ this._schema = schema;
35
+ const modifiedHeaderAttributes = this.strategy.parseHeaderAttributesFromSchema(schema, this.data, this.header);
36
+
37
+ this.header.headerSize = modifiedHeaderAttributes.headerSize;
38
+ this.header.record1Size = modifiedHeaderAttributes.record1Size;
39
+ this.header.table1StartIndex = modifiedHeaderAttributes.table1StartIndex;
40
+ this.header.table2StartIndex = modifiedHeaderAttributes.table2StartIndex;
41
+ // console.timeEnd('set schema');
42
+ };
43
+
44
+ get schema () {
45
+ return this._schema;
46
+ };
47
+
48
+ updateBuffer() {
28
49
  // need to check table2 data first because it may change offsets of the legit records.
29
50
  const table2Data = this.strategy.getTable2BinaryData(this.table2Records, this.data.slice(this.header.table2StartIndex));
30
51
 
@@ -62,23 +83,23 @@ class FranchiseFileTable extends EventEmitter {
62
83
  bufferArrays = bufferArrays.concat(table2Data);
63
84
 
64
85
  this.data = Buffer.concat(bufferArrays);
65
- return this.data;
66
86
  };
67
87
 
68
- set schema (schema) {
69
- // console.time('set schema');
70
- this._schema = schema;
71
- const modifiedHeaderAttributes = this.strategy.parseHeaderAttributesFromSchema(schema, this.data, this.header);
88
+ setNextRecordToUse(index, resetEmptyRecordMap) {
89
+ // We need to update the table header to use this row next
90
+ this.header.nextRecordToUse = index;
72
91
 
73
- this.header.headerSize = modifiedHeaderAttributes.headerSize;
74
- this.header.record1Size = modifiedHeaderAttributes.record1Size;
75
- this.header.table1StartIndex = modifiedHeaderAttributes.table1StartIndex;
76
- this.header.table2StartIndex = modifiedHeaderAttributes.table2StartIndex;
77
- // console.timeEnd('set schema');
78
- };
92
+ // And finally update the buffer to reflect this change
93
+ this.data.writeUInt32BE(index, this.header.headerOffset - 4);
79
94
 
80
- get schema () {
81
- return this._schema;
95
+ // Recalculate the empty record map if the option is set and the
96
+ // records have already been read.
97
+ if (resetEmptyRecordMap && this.recordsRead) {
98
+ this.updateBuffer();
99
+ this.emptyRecords = this._parseEmptyRecords();
100
+ }
101
+
102
+ this.emit('change');
82
103
  };
83
104
 
84
105
  // attribsToLoad is an array of attribute names (strings) to load. It is optional - if nothing is provided to the function it will load all attributes.
@@ -154,6 +175,7 @@ class FranchiseFileTable extends EventEmitter {
154
175
  }
155
176
 
156
177
  const that = this;
178
+
157
179
  record.on('change', function (changedOffset) {
158
180
  this.isChanged = true;
159
181
 
@@ -215,7 +237,7 @@ class FranchiseFileTable extends EventEmitter {
215
237
  if (!previousEmptyReference) {
216
238
  // If no previous empty record exists and a next record exists, we need to update the header to
217
239
  // point to this record as the next record to use.
218
- updateNextRecordToUseHeaderAndBuffer(emptyRecordReference.next);
240
+ that.setNextRecordToUse(emptyRecordReference.next);
219
241
  }
220
242
  }
221
243
 
@@ -223,7 +245,28 @@ class FranchiseFileTable extends EventEmitter {
223
245
  // Then there are no more empty references in the table
224
246
  // Update the table header nextRecordToUse back to the table record capacity
225
247
  if (!previousEmptyReference && !nextEmptyReference) {
226
- updateNextRecordToUseHeaderAndBuffer(that.header.recordCapacity);
248
+ that.setNextRecordToUse(that.header.recordCapacity);
249
+ }
250
+ }
251
+ }
252
+ else {
253
+ // The field was not empty, let's check if it is now
254
+ const referenceData = utilService.getReferenceData(this._data.slice(0, 32));
255
+ if (referenceData.tableId === 0) {
256
+ // The record has a reference to table id 0, now we need to see if its referencing the 0th record or not
257
+ // We have to be careful not to mistake a null reference for an empty reference to record 0
258
+ if (referenceData.rowNumber !== 0) {
259
+ // If the value is referencing a row number greater than 0, we need to treat this record as an empty reference.
260
+ // onRecordEmpty(record);
261
+ }
262
+ else {
263
+ // The record was updated to either be a null reference or an empty record reference to row 0.
264
+ // First let's make sure we aren't editing row 0...if we are, then the value is a null reference
265
+ // because a record cannot point to itself.
266
+ if (this.index > 0) {
267
+ // Now that we know that the edit isn't coming from row 0, let's check if row 0 is an empty reference.
268
+ // If it is NOT an empty reference, then the updated value is not an empty reference.
269
+ }
227
270
  }
228
271
  }
229
272
  }
@@ -233,10 +276,14 @@ class FranchiseFileTable extends EventEmitter {
233
276
  });
234
277
 
235
278
  record.on('empty', function () {
279
+ onRecordEmpty(this);
280
+ });
281
+
282
+ function onRecordEmpty(record) {
236
283
  // First, check if the record is already empty. If so, don't do anything...
237
284
  // If not empty, then we need to empty it.
238
- if (!this.isEmpty) {
239
- this.isChanged = true;
285
+ if (!record.isEmpty) {
286
+ record.isChanged = true;
240
287
  const lastEmptyRecordMapEntry = Array.from(that.emptyRecords).pop();
241
288
 
242
289
  // When we empty a record, we need to check if another empty record exists in the table.
@@ -248,11 +295,11 @@ class FranchiseFileTable extends EventEmitter {
248
295
 
249
296
  that.emptyRecords.set(lastEmptyRecordIndex, {
250
297
  previous: lastEmptyRecordMapEntry[1].previous,
251
- next: this.index
298
+ next: record.index
252
299
  });
253
300
 
254
301
  // Then we need to update the current record index to point to the record capacity.
255
- that.emptyRecords.set(this.index, {
302
+ that.emptyRecords.set(record.index, {
256
303
  previous: lastEmptyRecordIndex,
257
304
  next: that.header.recordCapacity
258
305
  });
@@ -263,32 +310,24 @@ class FranchiseFileTable extends EventEmitter {
263
310
 
264
311
  // And update both record's data. This will set the unformatted and formatted values
265
312
  // without emitting an event
266
- changeRecordBuffers(lastEmptyRecordIndex, this.index);
267
- changeRecordBuffers(this.index, that.header.recordCapacity);
313
+ changeRecordBuffers(lastEmptyRecordIndex, record.index);
314
+ changeRecordBuffers(record.index, that.header.recordCapacity);
268
315
  }
269
316
  else {
270
317
  // In this case, the record that was emptied is the first empty record in the table
271
- that.emptyRecords.set(this.index, {
318
+ that.emptyRecords.set(record.index, {
272
319
  previous: null,
273
320
  next: that.header.recordCapacity
274
321
  });
275
322
 
276
323
  // Finally update the table header and buffer so that the game uses this new empty
277
324
  // record as the next record to use (or fill)
278
- updateNextRecordToUseHeaderAndBuffer(this.index);
279
- changeRecordBuffers(this.index, that.header.recordCapacity);
325
+ that.setNextRecordToUse(record.index);
326
+ changeRecordBuffers(record.index, that.header.recordCapacity);
280
327
  }
281
328
 
282
329
  that.emit('change');
283
330
  }
284
- });
285
-
286
- function updateNextRecordToUseHeaderAndBuffer(nextRecordToUse) {
287
- // We need to update the table header to use this row next
288
- that.header.nextRecordToUse = nextRecordToUse;
289
-
290
- // And finally update the buffer to reflect this change
291
- that.data.writeUInt32BE(nextRecordToUse, that.header.headerOffset - 4);
292
331
  };
293
332
 
294
333
  function changeRecordBuffers(index, emptyRecordReference) {
@@ -299,7 +338,7 @@ class FranchiseFileTable extends EventEmitter {
299
338
  function setBufferToEmptyRecordReference(index, emptyRecordReference) {
300
339
  const recordStartIndex = that.header.table1StartIndex + (index * that.header.record1Size)
301
340
  that.data.writeUInt32BE(emptyRecordReference, recordStartIndex);
302
- that.data.fill(0, recordStartIndex + 4, recordStartIndex + that.header.record1Size);
341
+ // that.data.fill(0, recordStartIndex + 4, recordStartIndex + that.header.record1Size);
303
342
  };
304
343
 
305
344
  function setRecordInternalBuffer(index, emptyRecordReference) {
@@ -307,7 +346,7 @@ class FranchiseFileTable extends EventEmitter {
307
346
 
308
347
  const recordSizeInBits = that.header.record1Size * 8;
309
348
  if (recordSizeInBits > 32) {
310
- newData += utilService.dec2bin(0, recordSizeInBits - 32);
349
+ newData += that.records[index]._data.slice(32);
311
350
  }
312
351
 
313
352
  that.records[index].data = newData;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "madden-franchise",
3
- "version": "2.3.3",
3
+ "version": "2.3.7",
4
4
  "description": "Tools to read a madden franchise file and get data from it",
5
5
  "main": "FranchiseFile.js",
6
6
  "scripts": {
@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const zlib = require('zlib');
3
3
 
4
- const filePath = 'D:\\Projects\\Madden 22\\Expressions\\franchise-expressions-binary.frt';
4
+ const filePath = 'D:\\Projects\\Madden 22\\Expressions\\sabo-expression-binary.frt';
5
5
  const outputPath = 'D:\\Projects\\Madden 22\\Expressions\\modified-Franchise-Expression-binary.FTC'
6
6
 
7
7
  const stream = fs.createReadStream(filePath)
@@ -13,6 +13,9 @@ CommonAlgorithms.save = (units, oldData) => {
13
13
  let oldOffsetCounter = 0;
14
14
  let bufferArrays = [];
15
15
 
16
+ // Ensure the units are sorted by index in the actual file. Otherwise, we may overwrite data
17
+ units.sort((a, b) => { return a.index - b.index; });
18
+
16
19
  units.forEach((unit, index) => {
17
20
  if (unit.offset === 0 && index > 0) {
18
21
  // there are usually trailing records at the end of the table that reference
@@ -3,7 +3,8 @@ let FranchiseTableStrategy = {};
3
3
  FranchiseTableStrategy.getTable2BinaryData = (table2Records, fullTable2Buffer) => {
4
4
  let table2Data = [];
5
5
 
6
- const changedTable2Records = table2Records.filter((record) => { return record.isChanged; });
6
+ // Make sure to sort the table2 records by index
7
+ const changedTable2Records = table2Records.filter((record) => { return record.isChanged; }).sort((a, b) => { return a.index - b.index; });
7
8
  let currentOffset = 0;
8
9
 
9
10
  for (let i = 0; i < changedTable2Records.length; i++) {