madden-franchise 3.2.5 → 3.2.6
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/FranchiseFileField.js +58 -66
- package/FranchiseFileTable.js +151 -135
- package/FranchiseFileTable2Field.js +18 -15
- package/package.json +1 -1
- package/strategies/common/CommonAlgorithms.js +7 -3
package/FranchiseFileField.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
const {
|
|
1
|
+
const {
|
|
2
|
+
BitView
|
|
3
|
+
} = require('bit-buffer');
|
|
2
4
|
|
|
3
5
|
const utilService = require('./services/utilService');
|
|
4
6
|
const FranchiseFileTable2Field = require('./FranchiseFileTable2Field');
|
|
@@ -17,26 +19,26 @@ class FranchiseFileField {
|
|
|
17
19
|
this.secondTableField = new FranchiseFileTable2Field(this._recordBuffer.readUInt32BE(offset.offset / 8), offset.maxLength);
|
|
18
20
|
this.secondTableField.fieldReference = this;
|
|
19
21
|
}
|
|
20
|
-
|
|
22
|
+
|
|
21
23
|
if (offset.valueInThirdTable) {
|
|
22
24
|
this.thirdTableField = new FranchiseFileTable3Field(this._recordBuffer.readUInt32BE(offset.offset / 8), offset.maxLength);
|
|
23
25
|
this.thirdTableField.fieldReference = this;
|
|
24
26
|
}
|
|
25
27
|
};
|
|
26
28
|
|
|
27
|
-
get key
|
|
29
|
+
get key() {
|
|
28
30
|
return this._key;
|
|
29
31
|
};
|
|
30
32
|
|
|
31
|
-
get offset
|
|
33
|
+
get offset() {
|
|
32
34
|
return this._offset;
|
|
33
35
|
};
|
|
34
36
|
|
|
35
|
-
get value
|
|
37
|
+
get value() {
|
|
36
38
|
if (this._unformattedValue === null) {
|
|
37
39
|
this._setUnformattedValueIfEmpty();
|
|
38
40
|
}
|
|
39
|
-
|
|
41
|
+
|
|
40
42
|
if (this._value === null) {
|
|
41
43
|
this._value = this._parseFieldValue(this._unformattedValue, this._offset);
|
|
42
44
|
}
|
|
@@ -44,11 +46,11 @@ class FranchiseFileField {
|
|
|
44
46
|
return this._value;
|
|
45
47
|
};
|
|
46
48
|
|
|
47
|
-
get isReference
|
|
49
|
+
get isReference() {
|
|
48
50
|
return this._offset.isReference;
|
|
49
51
|
};
|
|
50
52
|
|
|
51
|
-
get referenceData
|
|
53
|
+
get referenceData() {
|
|
52
54
|
if (this._unformattedValue === null) {
|
|
53
55
|
this._setUnformattedValueIfEmpty();
|
|
54
56
|
}
|
|
@@ -56,11 +58,11 @@ class FranchiseFileField {
|
|
|
56
58
|
if (this.isReference) {
|
|
57
59
|
return utilService.getReferenceDataFromBitview(this._unformattedValue, this.offset.offset);
|
|
58
60
|
}
|
|
59
|
-
|
|
61
|
+
|
|
60
62
|
return null;
|
|
61
63
|
};
|
|
62
64
|
|
|
63
|
-
set value
|
|
65
|
+
set value(value) {
|
|
64
66
|
if (this._unformattedValue === null) {
|
|
65
67
|
this._setUnformattedValueIfEmpty();
|
|
66
68
|
}
|
|
@@ -70,50 +72,46 @@ class FranchiseFileField {
|
|
|
70
72
|
|
|
71
73
|
if (this.offset.valueInSecondTable) {
|
|
72
74
|
this.secondTableField.value = value.toString();
|
|
73
|
-
}
|
|
74
|
-
else if (this.offset.valueInThirdTable) {
|
|
75
|
+
} else if (this.offset.valueInThirdTable) {
|
|
75
76
|
if (typeof value === 'object') {
|
|
76
77
|
const newVal = JSON.stringify(value);
|
|
77
78
|
this._value = newVal;
|
|
78
79
|
this.thirdTableField.value = newVal;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
80
|
+
} else {
|
|
81
81
|
this.thirdTableField.value = value.toString();
|
|
82
82
|
}
|
|
83
|
-
}
|
|
84
|
-
else {
|
|
83
|
+
} else {
|
|
85
84
|
let actualValue;
|
|
86
85
|
|
|
87
86
|
if (this.offset.isReference) {
|
|
88
|
-
if (!utilService.isString(value)) {
|
|
89
|
-
|
|
87
|
+
if (!utilService.isString(value)) {
|
|
88
|
+
throw new Error(`Argument must be of type string. You passed in a ${typeof unformattedValue}.`);
|
|
89
|
+
} else if (!utilService.stringOnlyContainsBinaryDigits(value)) {
|
|
90
|
+
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.`)
|
|
91
|
+
}
|
|
90
92
|
const referenceData = utilService.getReferenceData(value);
|
|
91
93
|
this._unformattedValue.setBits(this.offset.offset, referenceData.tableId, 15);
|
|
92
94
|
this._unformattedValue.setBits((this.offset.offset + 15), referenceData.rowNumber, 17);
|
|
93
|
-
}
|
|
94
|
-
else if (this.offset.enum) {
|
|
95
|
+
} else if (this.offset.enum) {
|
|
95
96
|
try {
|
|
96
97
|
let theEnum = this._getEnumFromValue(value);
|
|
97
|
-
|
|
98
|
+
|
|
98
99
|
// Enums can have negative values and Madden negative numbers are not standard. We need to convert it here.
|
|
99
100
|
// Ex: In Madden, binary "1000" = -1 for an enum with a max length of 4. But for everything else, "1000" = 8, so we need to get the "real" value here.
|
|
100
101
|
const decimalEquivalent = utilService.bin2dec(theEnum.unformattedValue);
|
|
101
102
|
this._unformattedValue.setBits(this.offset.offset, decimalEquivalent, this.offset.length);
|
|
102
103
|
this._value = theEnum.name;
|
|
103
|
-
}
|
|
104
|
-
catch (err) {
|
|
104
|
+
} catch (err) {
|
|
105
105
|
// if user tries entering an invalid enum value, check if it's an empty record reference (will be binary)
|
|
106
106
|
if (utilService.stringOnlyContainsBinaryDigits(value)) {
|
|
107
107
|
this._value = value;
|
|
108
108
|
this._unformattedValue.setBits(this.offset.offset, value, this.offset.length);
|
|
109
|
-
}
|
|
110
|
-
else {
|
|
109
|
+
} else {
|
|
111
110
|
this._value = null;
|
|
112
111
|
throw err;
|
|
113
112
|
}
|
|
114
113
|
}
|
|
115
|
-
}
|
|
116
|
-
else {
|
|
114
|
+
} else {
|
|
117
115
|
switch (this.offset.type) {
|
|
118
116
|
case 's_int':
|
|
119
117
|
actualValue = parseInt(value);
|
|
@@ -128,8 +126,7 @@ class FranchiseFileField {
|
|
|
128
126
|
if (this.offset.minValue || this.offset.maxValue) {
|
|
129
127
|
// return utilService.dec2bin(formatted, offset.length);
|
|
130
128
|
this._unformattedValue.setBits(this.offset.offset, actualValue, this.offset.length);
|
|
131
|
-
}
|
|
132
|
-
else {
|
|
129
|
+
} else {
|
|
133
130
|
const maxValueBinary = getMaxValueBinary(this.offset);
|
|
134
131
|
const maxValue = utilService.bin2dec(maxValueBinary);
|
|
135
132
|
// return utilService.dec2bin(formatted + maxValue, offset.length);
|
|
@@ -161,7 +158,7 @@ class FranchiseFileField {
|
|
|
161
158
|
}
|
|
162
159
|
};
|
|
163
160
|
|
|
164
|
-
get unformattedValue
|
|
161
|
+
get unformattedValue() {
|
|
165
162
|
if (this._unformattedValue === null) {
|
|
166
163
|
this._setUnformattedValueIfEmpty();
|
|
167
164
|
}
|
|
@@ -169,7 +166,7 @@ class FranchiseFileField {
|
|
|
169
166
|
return this._unformattedValue;
|
|
170
167
|
};
|
|
171
168
|
|
|
172
|
-
set unformattedValue
|
|
169
|
+
set unformattedValue(unformattedValue) {
|
|
173
170
|
this.setUnformattedValueWithoutChangeEvent(unformattedValue);
|
|
174
171
|
this._value = null;
|
|
175
172
|
this._parent.onEvent('change', this);
|
|
@@ -183,6 +180,10 @@ class FranchiseFileField {
|
|
|
183
180
|
this._isChanged = changed;
|
|
184
181
|
};
|
|
185
182
|
|
|
183
|
+
_bubbleChangeToParent() {
|
|
184
|
+
this._parent.onEvent('change', this);
|
|
185
|
+
};
|
|
186
|
+
|
|
186
187
|
clearCachedValues() {
|
|
187
188
|
this._value = null;
|
|
188
189
|
this._unformattedValue = null;
|
|
@@ -195,8 +196,9 @@ class FranchiseFileField {
|
|
|
195
196
|
};
|
|
196
197
|
|
|
197
198
|
setUnformattedValueWithoutChangeEvent(unformattedValue, suppressErrors) {
|
|
198
|
-
if (!(unformattedValue instanceof BitView)) {
|
|
199
|
-
|
|
199
|
+
if (!(unformattedValue instanceof BitView)) {
|
|
200
|
+
throw new Error(`Argument must be of type BitView. You passed in a(n) ${typeof unformattedValue}.`);
|
|
201
|
+
} else {
|
|
200
202
|
this._unformattedValue = unformattedValue;
|
|
201
203
|
this._value = null;
|
|
202
204
|
}
|
|
@@ -204,23 +206,20 @@ class FranchiseFileField {
|
|
|
204
206
|
|
|
205
207
|
_getEnumFromValue(value) {
|
|
206
208
|
const enumName = this.offset.enum.getMemberByName(value);
|
|
207
|
-
|
|
209
|
+
|
|
208
210
|
if (enumName) {
|
|
209
211
|
return enumName;
|
|
210
|
-
}
|
|
211
|
-
else {
|
|
212
|
+
} else {
|
|
212
213
|
const formattedEnum = this.offset.enum.getMemberByValue(value)
|
|
213
214
|
|
|
214
215
|
if (formattedEnum) {
|
|
215
216
|
return formattedEnum;
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
217
|
+
} else {
|
|
218
218
|
const unformattedEnum = this.offset.enum.getMemberByUnformattedValue(value);
|
|
219
219
|
|
|
220
220
|
if (unformattedEnum) {
|
|
221
221
|
return unformattedEnum;
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
222
|
+
} else {
|
|
224
223
|
return this.offset.enum.members[0];
|
|
225
224
|
}
|
|
226
225
|
}
|
|
@@ -230,31 +229,26 @@ class FranchiseFileField {
|
|
|
230
229
|
_parseFieldValue(unformatted, offset) {
|
|
231
230
|
if (offset.valueInSecondTable) {
|
|
232
231
|
return this.secondTableField.value;
|
|
233
|
-
}
|
|
234
|
-
else if (offset.valueInThirdTable) {
|
|
232
|
+
} else if (offset.valueInThirdTable) {
|
|
235
233
|
return this.thirdTableField.value;
|
|
236
|
-
}
|
|
237
|
-
else if (offset.enum) {
|
|
234
|
+
} else if (offset.enum) {
|
|
238
235
|
const enumUnformattedValue = utilService.dec2bin(this.unformattedValue.getBits(this.offset.offset, this.offset.length), offset.enum._maxLength);
|
|
239
|
-
|
|
236
|
+
|
|
240
237
|
try {
|
|
241
238
|
const theEnum = offset.enum.getMemberByUnformattedValue(enumUnformattedValue);
|
|
242
|
-
|
|
239
|
+
|
|
243
240
|
if (theEnum) {
|
|
244
241
|
return theEnum.name;
|
|
245
242
|
}
|
|
246
|
-
}
|
|
247
|
-
catch (err) {
|
|
243
|
+
} catch (err) {
|
|
248
244
|
// console.log(err);
|
|
249
245
|
}
|
|
250
|
-
|
|
246
|
+
|
|
251
247
|
return enumUnformattedValue;
|
|
252
|
-
}
|
|
253
|
-
else if (offset.isReference) {
|
|
248
|
+
} else if (offset.isReference) {
|
|
254
249
|
const referenceData = utilService.getReferenceDataFromBitview(this._unformattedValue, this.offset.offset);
|
|
255
250
|
return utilService.getBinaryReferenceData(referenceData.tableId, referenceData.rowNumber);
|
|
256
|
-
}
|
|
257
|
-
else {
|
|
251
|
+
} else {
|
|
258
252
|
switch (offset.type) {
|
|
259
253
|
case 's_int':
|
|
260
254
|
// return utilService.bin2dec(unformatted) + offset.minValue;
|
|
@@ -262,28 +256,26 @@ class FranchiseFileField {
|
|
|
262
256
|
case 'int':
|
|
263
257
|
if (offset.minValue || offset.maxValue) {
|
|
264
258
|
return unformatted.getBits(offset.offset, offset.length);
|
|
265
|
-
}
|
|
266
|
-
else {
|
|
259
|
+
} else {
|
|
267
260
|
// This is for int[] tables.
|
|
268
|
-
|
|
261
|
+
|
|
269
262
|
const maxValueBinary = getMaxValueBinary(offset);
|
|
270
263
|
const maxValue = utilService.bin2dec(maxValueBinary);
|
|
271
264
|
const newValue = unformatted.getBits(offset.offset, offset.length);
|
|
272
|
-
|
|
265
|
+
|
|
273
266
|
if (newValue === 0) {
|
|
274
267
|
return 0;
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
268
|
+
} else {
|
|
277
269
|
return newValue - maxValue;
|
|
278
270
|
}
|
|
279
271
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
272
|
+
case 'bool':
|
|
273
|
+
return unformatted.getBits(offset.offset, 1) ? true : false;
|
|
274
|
+
case 'float':
|
|
275
|
+
// return utilService.bin2Float(unformatted);
|
|
276
|
+
return unformatted.getFloat32(offset.offset, offset.length);
|
|
277
|
+
default:
|
|
278
|
+
return unformatted;
|
|
287
279
|
}
|
|
288
280
|
}
|
|
289
281
|
};
|
package/FranchiseFileTable.js
CHANGED
|
@@ -14,7 +14,7 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
14
14
|
this.strategyBase = strategy;
|
|
15
15
|
this.strategy = this.strategyBase.table;
|
|
16
16
|
this.recordsRead = false;
|
|
17
|
-
this._gameYear = gameYear;
|
|
17
|
+
this._gameYear = gameYear;
|
|
18
18
|
this.header = this.strategy.parseHeader(this.data);
|
|
19
19
|
this.name = this.header.name;
|
|
20
20
|
this.isArray = this.header.isArray;
|
|
@@ -28,12 +28,12 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
28
28
|
this._settings = settings;
|
|
29
29
|
};
|
|
30
30
|
|
|
31
|
-
get hexData
|
|
31
|
+
get hexData() {
|
|
32
32
|
this.updateBuffer();
|
|
33
33
|
return this.data;
|
|
34
34
|
};
|
|
35
35
|
|
|
36
|
-
set schema
|
|
36
|
+
set schema(schema) {
|
|
37
37
|
// console.time('set schema');
|
|
38
38
|
this._schema = schema;
|
|
39
39
|
const modifiedHeaderAttributes = this.strategy.parseHeaderAttributesFromSchema(schema, this.data, this.header);
|
|
@@ -45,23 +45,23 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
45
45
|
// console.timeEnd('set schema');
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
-
get schema
|
|
48
|
+
get schema() {
|
|
49
49
|
return this._schema;
|
|
50
50
|
};
|
|
51
51
|
|
|
52
52
|
getBinaryReferenceToRecord(index) {
|
|
53
53
|
return utilService.getBinaryReferenceData(this.header.tableId, index);
|
|
54
54
|
};
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
updateBuffer() {
|
|
57
57
|
// need to check table2 & table3 data first because it may change offsets of the legit records.
|
|
58
58
|
let table2Data = this.strategy.getTable2BinaryData(this.table2Records, this.data.slice(this.header.table2StartIndex));
|
|
59
59
|
let table3Data = [];
|
|
60
|
-
|
|
60
|
+
|
|
61
61
|
if (this.header.table3StartIndex) {
|
|
62
62
|
table3Data = this.strategy.getTable3BinaryData(this.table3Records, this.data.slice(this.header.table3StartIndex));
|
|
63
63
|
}
|
|
64
|
-
|
|
64
|
+
|
|
65
65
|
// update table2 length and table total length in table header (only if records have been read)
|
|
66
66
|
if (this.recordsRead) {
|
|
67
67
|
let table2DataLength = 0;
|
|
@@ -81,12 +81,15 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
81
81
|
|
|
82
82
|
this.header.table3Length = table3DataLength;
|
|
83
83
|
|
|
84
|
-
this.data.writeUInt32BE(this.header.table2Length
|
|
84
|
+
this.data.writeUInt32BE(this.header.table2Length, this.header.offsetStart - 44);
|
|
85
85
|
this.data.writeUInt32BE(this.header.table3Length, this.header.offsetStart - 40);
|
|
86
86
|
this.data.writeUInt32BE(this.header.tableTotalLength, this.header.offsetStart - 24);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
const changedRecords = this.records.filter((record) => {
|
|
89
|
+
const changedRecords = this.records.filter((record) => {
|
|
90
|
+
return record.isChanged;
|
|
91
|
+
});
|
|
92
|
+
|
|
90
93
|
let currentOffset = 0;
|
|
91
94
|
let bufferArrays = [];
|
|
92
95
|
|
|
@@ -109,10 +112,10 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
109
112
|
let record = changedRecords[i];
|
|
110
113
|
record.isChanged = false;
|
|
111
114
|
const recordOffset = this.header.table1StartIndex + (record.index * this.header.record1Size);
|
|
112
|
-
|
|
115
|
+
|
|
113
116
|
bufferArrays.push(this.data.slice(currentOffset, recordOffset));
|
|
114
117
|
const recordHexData = record.hexData;
|
|
115
|
-
|
|
118
|
+
|
|
116
119
|
bufferArrays.push(recordHexData);
|
|
117
120
|
currentOffset = recordOffset + recordHexData.length;
|
|
118
121
|
}
|
|
@@ -172,7 +175,7 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
172
175
|
const firstOffset = this.offsetTable[0];
|
|
173
176
|
if (firstOffset.type !== 'string') {
|
|
174
177
|
isEmptyReference = true;
|
|
175
|
-
|
|
178
|
+
|
|
176
179
|
// Save the row number that this record points to.
|
|
177
180
|
emptyRecordReferenceIndicies.push(firstFourBytesReference.rowNumber);
|
|
178
181
|
}
|
|
@@ -183,7 +186,9 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
183
186
|
|
|
184
187
|
// We need to determine the starting node.
|
|
185
188
|
// To do that, we need to find the empty record which no other empty record points to.
|
|
186
|
-
const unreachableRecords = this.records.filter((record) => {
|
|
189
|
+
const unreachableRecords = this.records.filter((record) => {
|
|
190
|
+
return record.isEmpty;
|
|
191
|
+
}).filter((record) => {
|
|
187
192
|
return emptyRecordReferenceIndicies.indexOf(record.index) === -1;
|
|
188
193
|
});
|
|
189
194
|
|
|
@@ -193,11 +198,10 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
193
198
|
return record.index;
|
|
194
199
|
});
|
|
195
200
|
|
|
196
|
-
console.warn(`(${this.header.tableId}) ${this.name} - More than one unreachable records found: `
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
+
console.warn(`(${this.header.tableId}) ${this.name} - More than one unreachable records found: ` +
|
|
202
|
+
`(${unreachableIndicies.join(', ')}). The game will most likely crash if you do not fix this problem. ` +
|
|
203
|
+
`The nextRecordToUse has NOT been updated.`);
|
|
204
|
+
} else {
|
|
201
205
|
let nextRecordToUse = this.header.recordCapacity;
|
|
202
206
|
|
|
203
207
|
if (unreachableRecords.length === 1) {
|
|
@@ -235,7 +239,7 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
235
239
|
};
|
|
236
240
|
|
|
237
241
|
// 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.
|
|
238
|
-
readRecords
|
|
242
|
+
readRecords(attribsToLoad) {
|
|
239
243
|
return new Promise((resolve, reject) => {
|
|
240
244
|
if (!this.recordsRead || isLoadingNewOffsets(this.loadedOffsets, attribsToLoad, this.offsetTable)) {
|
|
241
245
|
if (this.schema) {
|
|
@@ -261,10 +265,10 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
261
265
|
'valueInSecondTable': false,
|
|
262
266
|
'valueInThirdTable': false,
|
|
263
267
|
}
|
|
264
|
-
|
|
268
|
+
|
|
265
269
|
offset.isReference = !offset.enum && (offset.type[0] == offset.type[0].toUpperCase() || offset.type.includes('[]')) ? true : false,
|
|
266
270
|
|
|
267
|
-
|
|
271
|
+
offsetTable.push(offset);
|
|
268
272
|
}
|
|
269
273
|
|
|
270
274
|
for (let i = 0; i < this.header.data1RecordCount; i++) {
|
|
@@ -276,23 +280,25 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
276
280
|
} else {
|
|
277
281
|
reject('Cannot read records: Schema is not defined.');
|
|
278
282
|
}
|
|
279
|
-
|
|
283
|
+
|
|
280
284
|
let offsetTableToUse = this.offsetTable;
|
|
281
285
|
const mandatoryOffsetsToLoad = this.strategy.getMandatoryOffsets(this.offsetTable);
|
|
282
|
-
|
|
286
|
+
|
|
283
287
|
if (attribsToLoad) {
|
|
284
288
|
// get any new attributes to load plus the existing loaded offsets
|
|
285
|
-
offsetTableToUse = offsetTableToUse.filter((attrib) => {
|
|
286
|
-
return mandatoryOffsetsToLoad.includes(attrib.name)
|
|
287
|
-
|
|
288
|
-
|
|
289
|
+
offsetTableToUse = offsetTableToUse.filter((attrib) => {
|
|
290
|
+
return mandatoryOffsetsToLoad.includes(attrib.name) ||
|
|
291
|
+
attribsToLoad.includes(attrib.name) ||
|
|
292
|
+
this.loadedOffsets.find((offset) => {
|
|
293
|
+
return offset.name === attrib.name;
|
|
294
|
+
});
|
|
289
295
|
});
|
|
290
296
|
}
|
|
291
|
-
|
|
297
|
+
|
|
292
298
|
this.loadedOffsets = offsetTableToUse;
|
|
293
299
|
|
|
294
300
|
this.records = readRecords(this.data, this.header, offsetTableToUse, this);
|
|
295
|
-
|
|
301
|
+
|
|
296
302
|
if (this.header.hasSecondTable) {
|
|
297
303
|
this._parseTable2Values(this.data, this.header, this.records);
|
|
298
304
|
}
|
|
@@ -379,8 +385,7 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
379
385
|
// without emitting an event
|
|
380
386
|
this._changeRecordBuffers(lastEmptyRecordIndex, record.index);
|
|
381
387
|
this._changeRecordBuffers(record.index, this.header.recordCapacity);
|
|
382
|
-
}
|
|
383
|
-
else {
|
|
388
|
+
} else {
|
|
384
389
|
// In this case, the record that was emptied is the first empty record in the table
|
|
385
390
|
this.emptyRecords.set(record.index, {
|
|
386
391
|
previous: null,
|
|
@@ -392,7 +397,7 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
392
397
|
this.setNextRecordToUse(record.index);
|
|
393
398
|
this._changeRecordBuffers(record.index, this.header.recordCapacity);
|
|
394
399
|
}
|
|
395
|
-
|
|
400
|
+
|
|
396
401
|
this.emit('change');
|
|
397
402
|
}
|
|
398
403
|
};
|
|
@@ -400,10 +405,12 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
400
405
|
_parseTable2Values(data, header, records) {
|
|
401
406
|
const that = this;
|
|
402
407
|
const secondTableData = data.slice(header.table2StartIndex);
|
|
403
|
-
|
|
408
|
+
|
|
404
409
|
records.forEach((record) => {
|
|
405
|
-
const fieldsReferencingSecondTable = record.fieldsArray.filter((field) => {
|
|
406
|
-
|
|
410
|
+
const fieldsReferencingSecondTable = record.fieldsArray.filter((field) => {
|
|
411
|
+
return field.secondTableField;
|
|
412
|
+
});
|
|
413
|
+
|
|
407
414
|
fieldsReferencingSecondTable.forEach((field) => {
|
|
408
415
|
field.secondTableField.unformattedValue = that.strategyBase.table2Field.getInitialUnformattedValue(field, secondTableData);
|
|
409
416
|
field.secondTableField.strategy = that.strategyBase.table2Field;
|
|
@@ -412,14 +419,16 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
412
419
|
});
|
|
413
420
|
});
|
|
414
421
|
};
|
|
415
|
-
|
|
422
|
+
|
|
416
423
|
_parseTable3Values(data, header, records) {
|
|
417
424
|
const that = this;
|
|
418
425
|
const thirdTableData = data.slice(header.table3StartIndex);
|
|
419
426
|
|
|
420
427
|
records.forEach((record) => {
|
|
421
|
-
const fieldsReferencingThirdTable = record.fieldsArray.filter((field) => {
|
|
422
|
-
|
|
428
|
+
const fieldsReferencingThirdTable = record.fieldsArray.filter((field) => {
|
|
429
|
+
return field.thirdTableField;
|
|
430
|
+
});
|
|
431
|
+
|
|
423
432
|
fieldsReferencingThirdTable.forEach((field) => {
|
|
424
433
|
field.thirdTableField.unformattedValue = that.strategyBase.table3Field.getInitialUnformattedValue(field, thirdTableData);
|
|
425
434
|
field.thirdTableField.strategy = that.strategyBase.table3Field;
|
|
@@ -469,7 +478,7 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
469
478
|
// First, check if the record's length is greater than 4 bytes (32 bits)
|
|
470
479
|
// If less than 4 bytes, it can never become empty...probably. :)
|
|
471
480
|
if (this.header.record1Size >= 4) {
|
|
472
|
-
|
|
481
|
+
|
|
473
482
|
// Ex: Empty record list looks like object: A -> B -> C
|
|
474
483
|
// When B's value is changed, the records need updated to: A -> C
|
|
475
484
|
const emptyRecordReference = this.emptyRecords.get(object.index);
|
|
@@ -478,99 +487,100 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
478
487
|
// Automatically un-empty the row if the setting is enabled and the changed record was empty.
|
|
479
488
|
if (changedRecordWasEmpty) {
|
|
480
489
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
490
|
+
// Check if the record's first four bytes still have a reference to the 0th table.
|
|
491
|
+
// If so, then the record is still considered empty.
|
|
492
|
+
|
|
493
|
+
// We need to check the buffer because the first field is not always a reference.
|
|
494
|
+
// const referenceData = utilService.getReferenceDataFromBuffer(object.data.slice(0, 4));
|
|
495
|
+
// if (referenceData.tableId !== 0 || referenceData.rowNumber > this.header.recordCapacity) {
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
// if the changed field isn't included in the first 32 bits, zero out the first 32 bits.
|
|
499
|
+
// Otherwise, it's not necessary to zero out.
|
|
500
|
+
const changedFieldsInFirst4Bytes = object.fieldsArray.filter((field) => {
|
|
501
|
+
return field.isChanged && field.offset.indexOffset < 32;
|
|
502
|
+
});
|
|
503
|
+
if (this._settings.autoUnempty && changedFieldsInFirst4Bytes.length === 0) {
|
|
504
|
+
// set first 4 bytes to 0
|
|
505
|
+
this._changeRecordBuffers(object.index, 0);
|
|
506
|
+
|
|
507
|
+
// invalidate the cached values since we set the buffer directly
|
|
508
|
+
const fieldsInFirst4Bytes = object.fieldsArray.filter((field) => {
|
|
509
|
+
return field.offset.indexOffset < 32;
|
|
510
|
+
});
|
|
511
|
+
fieldsInFirst4Bytes.forEach((field) => {
|
|
512
|
+
field.clearCachedValues();
|
|
513
|
+
});
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
// If autoUnempty is disabled, only un-empty the row if a field in the first 4 bytes changed.
|
|
517
|
+
// If autoUnempty is enabled, un-empty the row if ANY field changed.
|
|
518
|
+
if (this._settings.autoUnempty || changedFieldsInFirst4Bytes.length > 0) {
|
|
519
|
+
// if the record contains any string values, point the string values to
|
|
520
|
+
// their correct offsets
|
|
521
|
+
this.strategy.recalculateStringOffsets(this, object);
|
|
522
|
+
this.strategy.recalculateBlobOffsets(this, object);
|
|
523
|
+
|
|
524
|
+
// Delete the empty record entry because it is no longer empty
|
|
525
|
+
this.emptyRecords.delete(object.index);
|
|
526
|
+
|
|
527
|
+
// Set the isEmpty back to false because it's no longer empty
|
|
528
|
+
object.isEmpty = false;
|
|
529
|
+
|
|
530
|
+
// Check if there is a previous empty record
|
|
531
|
+
const previousEmptyReference = this.emptyRecords.get(emptyRecordReference.previous);
|
|
532
|
+
|
|
533
|
+
if (previousEmptyReference) {
|
|
534
|
+
// Set the previous empty record to point to the old reference's next node
|
|
535
|
+
this.emptyRecords.set(emptyRecordReference.previous, {
|
|
536
|
+
previous: this.emptyRecords.get(emptyRecordReference.previous).previous,
|
|
537
|
+
next: emptyRecordReference.next
|
|
500
538
|
});
|
|
539
|
+
|
|
540
|
+
// change the table buffer and record buffer to reflect object change
|
|
541
|
+
this._changeRecordBuffers(emptyRecordReference.previous, emptyRecordReference.next);
|
|
501
542
|
}
|
|
502
|
-
|
|
503
|
-
// If autoUnempty is disabled, only un-empty the row if a field in the first 4 bytes changed.
|
|
504
|
-
// If autoUnempty is enabled, un-empty the row if ANY field changed.
|
|
505
|
-
if (this._settings.autoUnempty || changedFieldsInFirst4Bytes.length > 0) {
|
|
506
|
-
// if the record contains any string values, point the string values to
|
|
507
|
-
// their correct offsets
|
|
508
|
-
this.strategy.recalculateStringOffsets(this, object);
|
|
509
|
-
this.strategy.recalculateBlobOffsets(this, object);
|
|
510
|
-
|
|
511
|
-
// Delete the empty record entry because it is no longer empty
|
|
512
|
-
this.emptyRecords.delete(object.index);
|
|
513
|
-
|
|
514
|
-
// Set the isEmpty back to false because it's no longer empty
|
|
515
|
-
object.isEmpty = false;
|
|
516
|
-
|
|
517
|
-
// Check if there is a previous empty record
|
|
518
|
-
const previousEmptyReference = this.emptyRecords.get(emptyRecordReference.previous);
|
|
519
|
-
|
|
520
|
-
if (previousEmptyReference) {
|
|
521
|
-
// Set the previous empty record to point to the old reference's next node
|
|
522
|
-
this.emptyRecords.set(emptyRecordReference.previous, {
|
|
523
|
-
previous: this.emptyRecords.get(emptyRecordReference.previous).previous,
|
|
524
|
-
next: emptyRecordReference.next
|
|
525
|
-
});
|
|
526
|
-
|
|
527
|
-
// change the table buffer and record buffer to reflect object change
|
|
528
|
-
this._changeRecordBuffers(emptyRecordReference.previous, emptyRecordReference.next);
|
|
529
|
-
}
|
|
530
543
|
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
if (!previousEmptyReference) {
|
|
542
|
-
// If no previous empty record exists and a next record exists, we need to update the header to
|
|
543
|
-
// point to object record as the next record to use.
|
|
544
|
-
this.setNextRecordToUse(emptyRecordReference.next);
|
|
545
|
-
}
|
|
546
|
-
}
|
|
544
|
+
// If there is a next empty reference, update the previous value accordingly to now point
|
|
545
|
+
// to the current record's previous index.
|
|
546
|
+
const nextEmptyReference = this.emptyRecords.get(emptyRecordReference.next);
|
|
547
|
+
|
|
548
|
+
if (nextEmptyReference) {
|
|
549
|
+
this.emptyRecords.set(emptyRecordReference.next, {
|
|
550
|
+
previous: emptyRecordReference.previous,
|
|
551
|
+
next: this.emptyRecords.get(emptyRecordReference.next).next
|
|
552
|
+
});
|
|
547
553
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
this.setNextRecordToUse(this.header.recordCapacity);
|
|
554
|
+
if (!previousEmptyReference) {
|
|
555
|
+
// If no previous empty record exists and a next record exists, we need to update the header to
|
|
556
|
+
// point to object record as the next record to use.
|
|
557
|
+
this.setNextRecordToUse(emptyRecordReference.next);
|
|
553
558
|
}
|
|
554
559
|
}
|
|
560
|
+
|
|
561
|
+
// If there are no previous or next empty references
|
|
562
|
+
// Then there are no more empty references in the table
|
|
563
|
+
// Update the table header nextRecordToUse back to the table record capacity
|
|
564
|
+
if (!previousEmptyReference && !nextEmptyReference) {
|
|
565
|
+
this.setNextRecordToUse(this.header.recordCapacity);
|
|
566
|
+
}
|
|
567
|
+
}
|
|
555
568
|
// }
|
|
556
569
|
}
|
|
557
570
|
}
|
|
558
571
|
|
|
559
572
|
this.emit('change');
|
|
560
|
-
}
|
|
561
|
-
else if (name === 'empty') {
|
|
573
|
+
} else if (name === 'empty') {
|
|
562
574
|
this._onRecordEmpty(object);
|
|
563
575
|
this.emit('change');
|
|
564
576
|
}
|
|
565
|
-
}
|
|
566
|
-
else if (object instanceof FranchiseFileTable2Field || object instanceof FranchiseFileTable3Field) {
|
|
577
|
+
} else if (object instanceof FranchiseFileTable2Field || object instanceof FranchiseFileTable3Field) {
|
|
567
578
|
object.isChanged = true;
|
|
568
|
-
|
|
579
|
+
|
|
569
580
|
// When a table2 field changes, we need to check if the record is empty. If so, we need to mark it as not empty.
|
|
570
581
|
if (object.fieldReference) {
|
|
571
582
|
this.onEvent('change', object.fieldReference._parent);
|
|
572
|
-
}
|
|
573
|
-
else {
|
|
583
|
+
} else {
|
|
574
584
|
// Only emit change here if the field reference is empty.
|
|
575
585
|
// the onEvent call will emit a change in the above condition.
|
|
576
586
|
this.emit('change');
|
|
@@ -591,24 +601,23 @@ function readOffsetTable(data, schema, header) {
|
|
|
591
601
|
return offset.final || offset.const || offset.type.indexOf('()') >= 0 || offset.type === 'ITransaction_Sleep';
|
|
592
602
|
};
|
|
593
603
|
|
|
594
|
-
for(let i = 0; i < offsetTable.length; i++) {
|
|
604
|
+
for (let i = 0; i < offsetTable.length; i++) {
|
|
595
605
|
let curOffset = offsetTable[i];
|
|
596
|
-
let nextOffset = offsetTable.length > i + 1 ? offsetTable[i+1] : null;
|
|
606
|
+
let nextOffset = offsetTable.length > i + 1 ? offsetTable[i + 1] : null;
|
|
597
607
|
|
|
598
608
|
if (nextOffset) {
|
|
599
|
-
let curIndex = i+2;
|
|
600
|
-
while(nextOffset && isSkippedOffset(nextOffset)) {
|
|
609
|
+
let curIndex = i + 2;
|
|
610
|
+
while (nextOffset && isSkippedOffset(nextOffset)) {
|
|
601
611
|
nextOffset = offsetTable[curIndex];
|
|
602
612
|
curIndex += 1;
|
|
603
613
|
}
|
|
604
614
|
|
|
605
615
|
if (nextOffset) {
|
|
606
|
-
curOffset.length = nextOffset.indexOffset - curOffset.indexOffset;
|
|
616
|
+
curOffset.length = nextOffset.indexOffset - curOffset.indexOffset;
|
|
607
617
|
} else {
|
|
608
618
|
curOffset.length = (header.record1Size * 8) - curOffset.indexOffset;
|
|
609
|
-
}
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
612
621
|
curOffset.length = (header.record1Size * 8) - curOffset.indexOffset;
|
|
613
622
|
}
|
|
614
623
|
|
|
@@ -619,7 +628,7 @@ function readOffsetTable(data, schema, header) {
|
|
|
619
628
|
|
|
620
629
|
let currentOffsetIndex = 0;
|
|
621
630
|
let chunked32bit = [];
|
|
622
|
-
|
|
631
|
+
|
|
623
632
|
for (let i = 0; i < header.record1Size * 8; i += 32) {
|
|
624
633
|
let chunkedOffsets = [];
|
|
625
634
|
let offsetLength = i % 32;
|
|
@@ -632,15 +641,15 @@ function readOffsetTable(data, schema, header) {
|
|
|
632
641
|
currentOffsetIndex += 1;
|
|
633
642
|
continue;
|
|
634
643
|
}
|
|
635
|
-
|
|
644
|
+
|
|
636
645
|
offsetLength += currentOffset.length;
|
|
637
646
|
chunkedOffsets.push(currentOffset);
|
|
638
|
-
|
|
647
|
+
|
|
639
648
|
currentOffsetIndex += 1;
|
|
640
649
|
} else {
|
|
641
650
|
break;
|
|
642
651
|
}
|
|
643
|
-
} while((currentOffsetIndex < offsetTable.length) && offsetLength < 32);
|
|
652
|
+
} while ((currentOffsetIndex < offsetTable.length) && offsetLength < 32);
|
|
644
653
|
|
|
645
654
|
chunked32bit.push(chunkedOffsets);
|
|
646
655
|
}
|
|
@@ -651,16 +660,20 @@ function readOffsetTable(data, schema, header) {
|
|
|
651
660
|
offsetArray[offsetArray.length - 1].offset = currentOffset;
|
|
652
661
|
|
|
653
662
|
for (let i = offsetArray.length - 2; i >= 0; i--) {
|
|
654
|
-
let previousOffset = offsetArray[i+1];
|
|
663
|
+
let previousOffset = offsetArray[i + 1];
|
|
655
664
|
let offset = offsetArray[i];
|
|
656
665
|
offset.offset = previousOffset.offset + previousOffset.length;
|
|
657
666
|
}
|
|
658
667
|
}
|
|
659
668
|
});
|
|
660
669
|
|
|
661
|
-
offsetTable = offsetTable.filter((offset) => {
|
|
662
|
-
|
|
663
|
-
|
|
670
|
+
offsetTable = offsetTable.filter((offset) => {
|
|
671
|
+
return !(isSkippedOffset(offset))
|
|
672
|
+
});
|
|
673
|
+
offsetTable.sort((a, b) => {
|
|
674
|
+
return a.offset - b.offset;
|
|
675
|
+
});
|
|
676
|
+
|
|
664
677
|
for (let i = 0; i < offsetTable.length; i++) {
|
|
665
678
|
schema.attributes[offsetTable[i].index].offsetIndex = i;
|
|
666
679
|
}
|
|
@@ -668,7 +681,9 @@ function readOffsetTable(data, schema, header) {
|
|
|
668
681
|
return offsetTable;
|
|
669
682
|
|
|
670
683
|
function sortOffsetTableByIndexOffset() {
|
|
671
|
-
offsetTable.sort((a, b) => {
|
|
684
|
+
offsetTable.sort((a, b) => {
|
|
685
|
+
return a.indexOffset - b.indexOffset;
|
|
686
|
+
});
|
|
672
687
|
};
|
|
673
688
|
|
|
674
689
|
function parseOffsetTableFromData() {
|
|
@@ -722,16 +737,17 @@ function readRecords(data, header, offsetTable, table) {
|
|
|
722
737
|
};
|
|
723
738
|
|
|
724
739
|
function isLoadingNewOffsets(currentlyLoaded, attribsToLoad, offsetTable) {
|
|
725
|
-
const names = currentlyLoaded.map((currentlyLoadedOffset) => {
|
|
740
|
+
const names = currentlyLoaded.map((currentlyLoadedOffset) => {
|
|
741
|
+
return currentlyLoadedOffset.name;
|
|
742
|
+
});
|
|
726
743
|
|
|
727
744
|
if (attribsToLoad) {
|
|
728
745
|
let newAttribs = attribsToLoad.filter((attrib) => {
|
|
729
746
|
return !names.includes(attrib);
|
|
730
747
|
});
|
|
731
|
-
|
|
748
|
+
|
|
732
749
|
return newAttribs.length > 0;
|
|
733
|
-
}
|
|
734
|
-
else {
|
|
750
|
+
} else {
|
|
735
751
|
return currentlyLoaded.length !== offsetTable.length;
|
|
736
752
|
}
|
|
737
753
|
};
|
|
@@ -2,7 +2,7 @@ const EventEmitter = require('events').EventEmitter;
|
|
|
2
2
|
const utilService = require('./services/utilService');
|
|
3
3
|
|
|
4
4
|
class FranchiseFileTable2Field {
|
|
5
|
-
constructor
|
|
5
|
+
constructor(index, maxLength, parent) {
|
|
6
6
|
this._value = '';
|
|
7
7
|
this.rawIndex = index;
|
|
8
8
|
this.isChanged = false;
|
|
@@ -15,11 +15,11 @@ class FranchiseFileTable2Field {
|
|
|
15
15
|
this._parent = parent;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
get unformattedValue
|
|
18
|
+
get unformattedValue() {
|
|
19
19
|
return this._unformattedValue;
|
|
20
20
|
};
|
|
21
21
|
|
|
22
|
-
set unformattedValue
|
|
22
|
+
set unformattedValue(value) {
|
|
23
23
|
this._unformattedValue = value;
|
|
24
24
|
|
|
25
25
|
if (this.lengthAtLastSave === null) {
|
|
@@ -32,52 +32,55 @@ class FranchiseFileTable2Field {
|
|
|
32
32
|
}
|
|
33
33
|
};
|
|
34
34
|
|
|
35
|
-
get value
|
|
35
|
+
get value() {
|
|
36
36
|
if (this._value === null) {
|
|
37
|
-
this._value = this._unformattedValue.toString().replace(/\0.*$/g,'');
|
|
37
|
+
this._value = this._unformattedValue.toString().replace(/\0.*$/g, '');
|
|
38
38
|
}
|
|
39
39
|
|
|
40
40
|
return this._value;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
|
-
set value
|
|
43
|
+
set value(value) {
|
|
44
44
|
this._value = value;
|
|
45
45
|
|
|
46
46
|
if (value.length > this.maxLength) {
|
|
47
47
|
value = value.substring(0, this.maxLength);
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
|
|
50
50
|
this._unformattedValue = this._strategy.setUnformattedValueFromFormatted(value, this.maxLength);
|
|
51
51
|
|
|
52
52
|
if (this.lengthAtLastSave === null) {
|
|
53
53
|
this.lengthAtLastSave = getLengthOfUnformattedValue(this._unformattedValue);
|
|
54
54
|
}
|
|
55
|
-
|
|
55
|
+
|
|
56
56
|
this._parent.onEvent('change', this);
|
|
57
57
|
};
|
|
58
58
|
|
|
59
|
-
get hexData
|
|
59
|
+
get hexData() {
|
|
60
60
|
return this._unformattedValue;
|
|
61
61
|
};
|
|
62
62
|
|
|
63
|
-
get strategy
|
|
63
|
+
get strategy() {
|
|
64
64
|
return this._strategy;
|
|
65
65
|
};
|
|
66
66
|
|
|
67
|
-
set strategy
|
|
67
|
+
set strategy(strategy) {
|
|
68
68
|
this._strategy = strategy;
|
|
69
69
|
};
|
|
70
70
|
|
|
71
|
-
get offset
|
|
71
|
+
get offset() {
|
|
72
72
|
return this._offset;
|
|
73
73
|
};
|
|
74
74
|
|
|
75
|
-
set offset
|
|
75
|
+
set offset(offset) {
|
|
76
|
+
const offsetChanged = this._offset !== offset;
|
|
76
77
|
this._offset = offset;
|
|
77
78
|
this.index = offset;
|
|
78
79
|
|
|
79
|
-
if (this.fieldReference) {
|
|
80
|
+
if (offsetChanged && this.fieldReference) {
|
|
80
81
|
this.fieldReference.unformattedValue.setBits(this.fieldReference.offset.offset, offset, 32);
|
|
82
|
+
this.fieldReference.isChanged = true;
|
|
83
|
+
this.fieldReference._bubbleChangeToParent();
|
|
81
84
|
}
|
|
82
85
|
};
|
|
83
86
|
|
|
@@ -93,5 +96,5 @@ class FranchiseFileTable2Field {
|
|
|
93
96
|
module.exports = FranchiseFileTable2Field;
|
|
94
97
|
|
|
95
98
|
function getLengthOfUnformattedValue(value) {
|
|
96
|
-
|
|
99
|
+
return value.length;
|
|
97
100
|
};
|
package/package.json
CHANGED
|
@@ -2,7 +2,9 @@ let CommonAlgorithms = {};
|
|
|
2
2
|
|
|
3
3
|
CommonAlgorithms.save = (units, oldData) => {
|
|
4
4
|
// first check if any records changed. If not, we can return immediately because nothing changed.
|
|
5
|
-
const changedUnits = units.find((unit) => {
|
|
5
|
+
const changedUnits = units.find((unit) => {
|
|
6
|
+
return unit.isChanged;
|
|
7
|
+
});
|
|
6
8
|
|
|
7
9
|
if (!changedUnits) {
|
|
8
10
|
return oldData;
|
|
@@ -14,7 +16,9 @@ CommonAlgorithms.save = (units, oldData) => {
|
|
|
14
16
|
let bufferArrays = [];
|
|
15
17
|
|
|
16
18
|
// Ensure the units are sorted by index in the actual file. Otherwise, we may overwrite data
|
|
17
|
-
units.sort((a, b) => {
|
|
19
|
+
units.sort((a, b) => {
|
|
20
|
+
return a.index - b.index;
|
|
21
|
+
});
|
|
18
22
|
|
|
19
23
|
units.forEach((unit, index) => {
|
|
20
24
|
if (unit.offset === 0 && index > 0) {
|
|
@@ -50,7 +54,7 @@ CommonAlgorithms.save = (units, oldData) => {
|
|
|
50
54
|
unit.isChanged = false;
|
|
51
55
|
}
|
|
52
56
|
});
|
|
53
|
-
|
|
57
|
+
|
|
54
58
|
// Next, we need to push the remainder of data onto the array.
|
|
55
59
|
|
|
56
60
|
// For example, think if a user changed the 3rd record out of 100.
|