madden-franchise 2.2.7 → 2.3.3

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.
@@ -64,6 +64,11 @@ class FranchiseFileField extends EventEmitter {
64
64
  };
65
65
 
66
66
  set unformattedValue (unformattedValue) {
67
+ this.setUnformattedValueWithoutChangeEvent(unformattedValue);
68
+ this.emit('change');
69
+ };
70
+
71
+ setUnformattedValueWithoutChangeEvent(unformattedValue) {
67
72
  if (!utilService.isString(unformattedValue)) { throw new Error(`Argument must be of type string. You passed in a ${typeof unformattedValue}.`); }
68
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.`)}
69
74
  else {
@@ -89,10 +94,8 @@ class FranchiseFileField extends EventEmitter {
89
94
  else {
90
95
  this._unformattedValue = unformattedValue;
91
96
  }
92
-
93
- this.emit('change');
94
97
  }
95
- };
98
+ }
96
99
  };
97
100
 
98
101
  module.exports = FranchiseFileField;
@@ -11,6 +11,7 @@ class FranchiseFileRecord extends EventEmitter {
11
11
  this._fields = parseRecordFields(data, offsetTable);
12
12
  this.isChanged = false;
13
13
  this.arraySize = null;
14
+ this.isEmpty = false;
14
15
 
15
16
  const that = this;
16
17
  this._fields.forEach((field) => {
@@ -58,6 +59,19 @@ class FranchiseFileRecord extends EventEmitter {
58
59
  return Buffer.from(utilService.binaryBlockToDecimalBlock(this._data));
59
60
  };
60
61
 
62
+ get fields () {
63
+ return this._fields;
64
+ };
65
+
66
+ set data (data) {
67
+ this._data = data;
68
+
69
+ this._fields.forEach((field) => {
70
+ const unformattedValue = data.slice(field.offset.offset, field.offset.offset + field.offset.length);
71
+ field.setUnformattedValueWithoutChangeEvent(unformattedValue);
72
+ });
73
+ };
74
+
61
75
  getFieldByKey(key) {
62
76
  return this._fields.find((field) => { return field.key === key; });
63
77
  };
@@ -72,8 +86,9 @@ class FranchiseFileRecord extends EventEmitter {
72
86
  return field ? field.referenceData : null;
73
87
  };
74
88
 
75
- get fields () {
76
- return this._fields;
89
+ empty() {
90
+ this.emit('empty');
91
+ this.isEmpty = true;
77
92
  };
78
93
  };
79
94
 
@@ -1,4 +1,3 @@
1
- const assert = require('assert');
2
1
  const EventEmitter = require('events').EventEmitter;
3
2
  const utilService = require('./services/utilService');
4
3
  const FranchiseFileRecord = require('./FranchiseFileRecord');
@@ -22,6 +21,7 @@ class FranchiseFileTable extends EventEmitter {
22
21
  this.records = [];
23
22
  this.table2Records = [];
24
23
  this.arraySizes = [];
24
+ this.emptyRecords = new Map();
25
25
  };
26
26
 
27
27
  get hexData () {
@@ -122,9 +122,10 @@ class FranchiseFileTable extends EventEmitter {
122
122
  } else {
123
123
  reject('Cannot read records: Schema is not defined.');
124
124
  }
125
+
126
+ this.emptyRecords = this._parseEmptyRecords();
125
127
 
126
128
  let offsetTableToUse = this.offsetTable;
127
-
128
129
  const mandatoryOffsetsToLoad = this.strategy.getMandatoryOffsets(this.offsetTable);
129
130
 
130
131
  if (attribsToLoad) {
@@ -148,12 +149,169 @@ class FranchiseFileTable extends EventEmitter {
148
149
  record.arraySize = this.arraySizes[index];
149
150
  }
150
151
 
152
+ if (this.emptyRecords.get(index)) {
153
+ record.isEmpty = true;
154
+ }
155
+
151
156
  const that = this;
152
157
  record.on('change', function (changedOffset) {
153
158
  this.isChanged = true;
154
- that.arraySizes[index] = this.arraySize;
159
+
160
+ if (that.isArray) {
161
+ that.arraySizes[index] = this.arraySize;
162
+ }
163
+
164
+ // When a record changes, we need to check if it was previously empty
165
+ // If so, we need to consider the record as no longer empty
166
+ // So we need to adjust the empty records
167
+
168
+ // First, check if the record's length is greater than 4 bytes (32 bits)
169
+ // If less than 4 bytes, it can never become empty...probably. :)
170
+ if (that.header.record1Size >= 4) {
171
+
172
+ // Ex: Empty record list looks like this: A -> B -> C
173
+ // When B's value is changed, the records need updated to: A -> C
174
+ const emptyRecordReference = that.emptyRecords.get(this.index);
175
+ const changedRecordWasEmpty = emptyRecordReference !== null && emptyRecordReference !== undefined;
176
+
177
+ if (changedRecordWasEmpty) {
178
+ // Check if the record's first four bytes still have a reference to the 0th table.
179
+ // If so, then the record is still considered empty.
180
+
181
+ // We need to check the buffer because the first field is not always a reference.
182
+ const referenceData = utilService.getReferenceData(this._data.slice(0, 32));
183
+ if (referenceData.tableId !== 0) {
184
+
185
+ // Delete the empty record entry because it is no longer empty
186
+ that.emptyRecords.delete(this.index);
187
+
188
+ // Set the isEmpty back to false because it's no longer empty
189
+ this.isEmpty = false;
190
+
191
+ // Check if there is a previous empty record
192
+ const previousEmptyReference = that.emptyRecords.get(emptyRecordReference.previous);
193
+
194
+ if (previousEmptyReference) {
195
+ // Set the previous empty record to point to the old reference's next node
196
+ that.emptyRecords.set(emptyRecordReference.previous, {
197
+ previous: that.emptyRecords.get(emptyRecordReference.previous).previous,
198
+ next: emptyRecordReference.next
199
+ });
200
+
201
+ // change the table buffer and record buffer to reflect this change
202
+ changeRecordBuffers(emptyRecordReference.previous, emptyRecordReference.next);
203
+ }
204
+
205
+ // If there is a next empty reference, update the previous value accordingly to now point
206
+ // to the current record's previous index.
207
+ const nextEmptyReference = that.emptyRecords.get(emptyRecordReference.next);
208
+
209
+ if (nextEmptyReference) {
210
+ that.emptyRecords.set(emptyRecordReference.next, {
211
+ previous: emptyRecordReference.previous,
212
+ next: that.emptyRecords.get(emptyRecordReference.next).next
213
+ });
214
+
215
+ if (!previousEmptyReference) {
216
+ // If no previous empty record exists and a next record exists, we need to update the header to
217
+ // point to this record as the next record to use.
218
+ updateNextRecordToUseHeaderAndBuffer(emptyRecordReference.next);
219
+ }
220
+ }
221
+
222
+ // If there are no previous or next empty references
223
+ // Then there are no more empty references in the table
224
+ // Update the table header nextRecordToUse back to the table record capacity
225
+ if (!previousEmptyReference && !nextEmptyReference) {
226
+ updateNextRecordToUseHeaderAndBuffer(that.header.recordCapacity);
227
+ }
228
+ }
229
+ }
230
+ }
231
+
155
232
  that.emit('change');
156
233
  });
234
+
235
+ record.on('empty', function () {
236
+ // First, check if the record is already empty. If so, don't do anything...
237
+ // If not empty, then we need to empty it.
238
+ if (!this.isEmpty) {
239
+ this.isChanged = true;
240
+ const lastEmptyRecordMapEntry = Array.from(that.emptyRecords).pop();
241
+
242
+ // When we empty a record, we need to check if another empty record exists in the table.
243
+ if (lastEmptyRecordMapEntry !== null && lastEmptyRecordMapEntry !== undefined) {
244
+
245
+ // If an empty record already exists, we just need to get the last empty record
246
+ // and update its index to point to the current record that we want to empty.
247
+ const lastEmptyRecordIndex = lastEmptyRecordMapEntry[0];
248
+
249
+ that.emptyRecords.set(lastEmptyRecordIndex, {
250
+ previous: lastEmptyRecordMapEntry[1].previous,
251
+ next: this.index
252
+ });
253
+
254
+ // Then we need to update the current record index to point to the record capacity.
255
+ that.emptyRecords.set(this.index, {
256
+ previous: lastEmptyRecordIndex,
257
+ next: that.header.recordCapacity
258
+ });
259
+
260
+ // Finally, we need to update the buffers to reflect this data.
261
+ // First, place the new referenced index (will be the first 4 bytes)
262
+ // Next, fill the rest of the record with 0s (the last bytes of the record)
263
+
264
+ // And update both record's data. This will set the unformatted and formatted values
265
+ // without emitting an event
266
+ changeRecordBuffers(lastEmptyRecordIndex, this.index);
267
+ changeRecordBuffers(this.index, that.header.recordCapacity);
268
+ }
269
+ else {
270
+ // In this case, the record that was emptied is the first empty record in the table
271
+ that.emptyRecords.set(this.index, {
272
+ previous: null,
273
+ next: that.header.recordCapacity
274
+ });
275
+
276
+ // Finally update the table header and buffer so that the game uses this new empty
277
+ // record as the next record to use (or fill)
278
+ updateNextRecordToUseHeaderAndBuffer(this.index);
279
+ changeRecordBuffers(this.index, that.header.recordCapacity);
280
+ }
281
+
282
+ that.emit('change');
283
+ }
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
+ };
293
+
294
+ function changeRecordBuffers(index, emptyRecordReference) {
295
+ setBufferToEmptyRecordReference(index, emptyRecordReference);
296
+ setRecordInternalBuffer(index, emptyRecordReference);
297
+ };
298
+
299
+ function setBufferToEmptyRecordReference(index, emptyRecordReference) {
300
+ const recordStartIndex = that.header.table1StartIndex + (index * that.header.record1Size)
301
+ that.data.writeUInt32BE(emptyRecordReference, recordStartIndex);
302
+ that.data.fill(0, recordStartIndex + 4, recordStartIndex + that.header.record1Size);
303
+ };
304
+
305
+ function setRecordInternalBuffer(index, emptyRecordReference) {
306
+ let newData = utilService.dec2bin(emptyRecordReference, 32);
307
+
308
+ const recordSizeInBits = that.header.record1Size * 8;
309
+ if (recordSizeInBits > 32) {
310
+ newData += utilService.dec2bin(0, recordSizeInBits - 32);
311
+ }
312
+
313
+ that.records[index].data = newData;
314
+ };
157
315
  });
158
316
 
159
317
  this.table2Records.forEach((record, index) => {
@@ -173,6 +331,33 @@ class FranchiseFileTable extends EventEmitter {
173
331
  });
174
332
  };
175
333
 
334
+ _parseEmptyRecords() {
335
+ const firstEmptyRecord = this.header.nextRecordToUse;
336
+ const sizeOfEachRecord = this.header.record1Size;
337
+
338
+ let emptyRecords = new Map();
339
+
340
+ let previousEmptyRecordIndex = null;
341
+ let currentEmptyRecordIndex = firstEmptyRecord;
342
+
343
+ if (this.header.nextRecordToUse !== this.header.recordCapacity) {
344
+ while (currentEmptyRecordIndex !== this.header.recordCapacity) {
345
+ let nextEmptyRecordIndex = this.data.readUInt32BE(this.header.table1StartIndex + (currentEmptyRecordIndex * sizeOfEachRecord));
346
+
347
+ emptyRecords.set(currentEmptyRecordIndex, {
348
+ previous: previousEmptyRecordIndex,
349
+ next: nextEmptyRecordIndex
350
+ });
351
+
352
+ previousEmptyRecordIndex = currentEmptyRecordIndex;
353
+ currentEmptyRecordIndex = nextEmptyRecordIndex;
354
+ }
355
+ }
356
+
357
+ return emptyRecords;
358
+ };
359
+
360
+
176
361
  _parseTable2Values(data, header, records) {
177
362
  const that = this;
178
363
  const secondTableData = data.slice(header.table2StartIndex);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "madden-franchise",
3
- "version": "2.2.7",
3
+ "version": "2.3.3",
4
4
  "description": "Tools to read a madden franchise file and get data from it",
5
5
  "main": "FranchiseFile.js",
6
6
  "scripts": {
@@ -45,7 +45,7 @@ M19TableHeaderStrategy.parseHeader = (data) => {
45
45
  const data2RecordCapacity = data.readUInt32BE(headerOffset+48);
46
46
  const data2IndexEntries = data.readUInt32BE(headerOffset+52);
47
47
  const unknown4 = data.readUInt32BE(headerOffset+56);
48
- const data2RecordCount = data.readUInt32BE(headerOffset+60);
48
+ const nextRecordToUse = data.readUInt32BE(headerOffset+60);
49
49
 
50
50
  let offsetStart = 0xE4 + tableStoreLength;
51
51
  const hasSecondTable = tableTotalLength > table1Length;
@@ -95,10 +95,10 @@ M19TableHeaderStrategy.parseHeader = (data) => {
95
95
  'hasSecondTable': hasSecondTable,
96
96
  'table1StartIndex': tableStoreLength === 0 && !isArray ? headerSize : headerSize + (data1RecordCount * 4),
97
97
  'table2StartIndex': tableStoreLength === 0 && !isArray ? headerSize + (data1RecordCount * records1Size) : headerSize + (data1RecordCount * 4) + (data1RecordCount * records1Size),
98
- 'data2recordWords': data2RecordWords,
99
- 'data2RecordCapacity': data2RecordCapacity,
100
- 'data2IndexEntries': data2IndexEntries,
101
- 'data2RecordCount': data2RecordCount
98
+ 'recordWords': data2RecordWords,
99
+ 'recordCapacity': data2RecordCapacity,
100
+ 'numMembers': data2IndexEntries,
101
+ 'nextRecordToUse': nextRecordToUse
102
102
  };
103
103
  };
104
104
 
@@ -46,7 +46,7 @@ M20TableHeaderStrategy.parseHeader = (data) => {
46
46
  const data2RecordCapacity = data.readUInt32BE(headerOffset+48);
47
47
  const data2IndexEntries = data.readUInt32BE(headerOffset+52);
48
48
  const unknown4 = data.readUInt32BE(headerOffset+56);
49
- const data2RecordCount = data.readUInt32BE(headerOffset+60);
49
+ const nextRecordToUse = data.readUInt32BE(headerOffset+60);
50
50
 
51
51
  let offsetStart = 0xE8 + tableStoreLength;
52
52
  const hasSecondTable = tableTotalLength > table1Length;
@@ -104,10 +104,10 @@ M20TableHeaderStrategy.parseHeader = (data) => {
104
104
  'hasSecondTable': hasSecondTable,
105
105
  'table1StartIndex': table1StartIndex,
106
106
  'table2StartIndex': table2StartIndex,
107
- 'data2recordWords': data2RecordWords,
108
- 'data2RecordCapacity': data2RecordCapacity,
109
- 'data2IndexEntries': data2IndexEntries,
110
- 'data2RecordCount': data2RecordCount
107
+ 'recordWords': data2RecordWords,
108
+ 'recordCapacity': data2RecordCapacity,
109
+ 'numMembers': data2IndexEntries,
110
+ 'nextRecordToUse': nextRecordToUse
111
111
  };
112
112
  };
113
113