madden-franchise 2.5.6 → 3.0.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/.vscode/launch.json +17 -0
- package/FranchiseEnum.js +5 -1
- package/FranchiseFileField.js +174 -149
- package/FranchiseFileRecord.js +77 -59
- package/FranchiseFileTable.js +190 -184
- package/FranchiseFileTable2Field.js +27 -18
- package/package.json +2 -1
- package/scripts/getTableId.js +14 -5
- package/services/utilService.js +15 -0
- package/strategies/common/table2Field/FTCTable2FieldStrategy.js +3 -5
- package/strategies/common/table2Field/FranchiseTable2FieldStrategy.js +3 -5
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
|
3
|
+
// Hover to view descriptions of existing attributes.
|
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
|
5
|
+
"version": "0.2.0",
|
|
6
|
+
"configurations": [
|
|
7
|
+
{
|
|
8
|
+
"type": "pwa-node",
|
|
9
|
+
"request": "launch",
|
|
10
|
+
"name": "Launch Program",
|
|
11
|
+
"skipFiles": [
|
|
12
|
+
"<node_internals>/**"
|
|
13
|
+
],
|
|
14
|
+
"program": "${file}"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
package/FranchiseEnum.js
CHANGED
|
@@ -45,7 +45,11 @@ class FranchiseEnum {
|
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
getMemberByValue(value) {
|
|
48
|
-
|
|
48
|
+
const matches = this._members.filter((member) => { return member.name !== 'First_' && member.name !== 'Last_' && member.value === value; });
|
|
49
|
+
if (matches.length === 0) { throw new Error(`Argument is not a valid enum value for this field. You passed in ${value}. Field name: ${this.name}`); }
|
|
50
|
+
|
|
51
|
+
const matchesNoUnderscore = matches.find((member) => { return member.name[member.name.length - 1] !== '_'});
|
|
52
|
+
return matchesNoUnderscore ? matchesNoUnderscore : matches[0];
|
|
49
53
|
};
|
|
50
54
|
|
|
51
55
|
getMemberByUnformattedValue(value) {
|
package/FranchiseFileField.js
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
const
|
|
1
|
+
const { BitView } = require('bit-buffer');
|
|
2
|
+
|
|
2
3
|
const utilService = require('./services/utilService');
|
|
3
4
|
const FranchiseFileTable2Field = require('./FranchiseFileTable2Field');
|
|
4
5
|
|
|
5
|
-
class FranchiseFileField
|
|
6
|
-
constructor(key, value, offset) {
|
|
7
|
-
super();
|
|
6
|
+
class FranchiseFileField {
|
|
7
|
+
constructor(key, value, offset, parent) {
|
|
8
8
|
this._key = key;
|
|
9
|
-
this.
|
|
10
|
-
this.
|
|
9
|
+
this._recordBuffer = value;
|
|
10
|
+
this._unformattedValue = null;
|
|
11
11
|
this._offset = offset;
|
|
12
|
+
this._parent = parent;
|
|
12
13
|
|
|
13
14
|
if (offset.valueInSecondTable) {
|
|
14
|
-
this.secondTableField = new FranchiseFileTable2Field(
|
|
15
|
+
this.secondTableField = new FranchiseFileTable2Field(this._recordBuffer.readUInt32BE(offset.offset / 8), offset.maxLength);
|
|
15
16
|
this.secondTableField.fieldReference = this;
|
|
16
|
-
|
|
17
|
-
this.secondTableField.on('change', function () {
|
|
18
|
-
this._value = this.secondTableField.value;
|
|
19
|
-
}.bind(this));
|
|
20
17
|
}
|
|
21
18
|
};
|
|
22
19
|
|
|
@@ -29,6 +26,14 @@ class FranchiseFileField extends EventEmitter {
|
|
|
29
26
|
};
|
|
30
27
|
|
|
31
28
|
get value () {
|
|
29
|
+
if (this._unformattedValue === null) {
|
|
30
|
+
this._setUnformattedValueIfEmpty();
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (this._value === null) {
|
|
34
|
+
this._value = this._parseFieldValue(this._unformattedValue, this._offset);
|
|
35
|
+
}
|
|
36
|
+
|
|
32
37
|
return this._value;
|
|
33
38
|
};
|
|
34
39
|
|
|
@@ -37,193 +42,213 @@ class FranchiseFileField extends EventEmitter {
|
|
|
37
42
|
};
|
|
38
43
|
|
|
39
44
|
get referenceData () {
|
|
45
|
+
if (this._unformattedValue === null) {
|
|
46
|
+
this._setUnformattedValueIfEmpty();
|
|
47
|
+
}
|
|
48
|
+
|
|
40
49
|
if (this.isReference) {
|
|
41
|
-
return utilService.
|
|
50
|
+
return utilService.getReferenceDataFromBitview(this._unformattedValue, this.offset.offset);
|
|
42
51
|
}
|
|
43
52
|
|
|
44
53
|
return null;
|
|
45
54
|
};
|
|
46
55
|
|
|
47
|
-
set value (value) {
|
|
56
|
+
set value (value) {
|
|
57
|
+
if (this._unformattedValue === null) {
|
|
58
|
+
this._setUnformattedValueIfEmpty();
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
this._value = value;
|
|
62
|
+
|
|
48
63
|
if (this.offset.valueInSecondTable) {
|
|
49
64
|
this.secondTableField.value = value.toString();
|
|
50
65
|
} else {
|
|
66
|
+
let actualValue;
|
|
67
|
+
|
|
51
68
|
if (this.offset.isReference) {
|
|
52
69
|
if (!utilService.isString(value)) { throw new Error(`Argument must be of type string. You passed in a ${typeof unformattedValue}.`); }
|
|
53
70
|
else if (!utilService.stringOnlyContainsBinaryDigits(value)) { 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.`)}
|
|
71
|
+
const referenceData = utilService.getReferenceData(value);
|
|
72
|
+
this._unformattedValue.setBits(this.offset.offset, referenceData.tableId, 15);
|
|
73
|
+
this._unformattedValue.setBits((this.offset.offset + 15), referenceData.rowNumber, 17);
|
|
74
|
+
}
|
|
75
|
+
else if (this.offset.enum) {
|
|
76
|
+
try {
|
|
77
|
+
let theEnum = this._getEnumFromValue(value);
|
|
78
|
+
|
|
79
|
+
// Enums can have negative values and Madden negative numbers are not standard. We need to convert it here.
|
|
80
|
+
// 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.
|
|
81
|
+
const decimalEquivalent = utilService.bin2dec(theEnum.unformattedValue);
|
|
82
|
+
this._unformattedValue.setBits(this.offset.offset, decimalEquivalent, this.offset.length);
|
|
83
|
+
this._value = theEnum.name;
|
|
84
|
+
}
|
|
85
|
+
catch (err) {
|
|
86
|
+
this._value = null;
|
|
87
|
+
throw err;
|
|
88
|
+
}
|
|
54
89
|
}
|
|
90
|
+
else {
|
|
91
|
+
switch (this.offset.type) {
|
|
92
|
+
case 's_int':
|
|
93
|
+
actualValue = parseInt(value);
|
|
94
|
+
this._value = actualValue;
|
|
95
|
+
this._unformattedValue.setBits(this.offset.offset, actualValue - this.offset.minValue, this.offset.length);
|
|
96
|
+
break;
|
|
97
|
+
default:
|
|
98
|
+
case 'int':
|
|
99
|
+
actualValue = parseInt(value);
|
|
100
|
+
this._value = actualValue;
|
|
101
|
+
|
|
102
|
+
if (this.offset.minValue || this.offset.maxValue) {
|
|
103
|
+
// return utilService.dec2bin(formatted, offset.length);
|
|
104
|
+
this._unformattedValue.setBits(this.offset.offset, actualValue, this.offset.length);
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
const maxValueBinary = getMaxValueBinary(this.offset);
|
|
108
|
+
const maxValue = utilService.bin2dec(maxValueBinary);
|
|
109
|
+
// return utilService.dec2bin(formatted + maxValue, offset.length);
|
|
110
|
+
this._unformattedValue.setBits(this.offset.offset, actualValue + maxValue, this.offset.length);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
case 'bool':
|
|
114
|
+
// return (formatted == 1 || (formatted.toString().toLowerCase() == 'true')) ? '1' : '0';
|
|
115
|
+
actualValue = (value == 1 || (value.toString().toLowerCase() == 'true'));
|
|
116
|
+
this._value = actualValue;
|
|
117
|
+
this._unformattedValue.setBits(this.offset.offset, actualValue, 1);
|
|
118
|
+
break;
|
|
119
|
+
case 'float':
|
|
120
|
+
actualValue = parseFloat(value);
|
|
121
|
+
this._value = actualValue;
|
|
122
|
+
// return utilService.float2Bin(formatted);
|
|
123
|
+
// this._unformattedValue.setBits(this.offset.offset, value, this.offset.length);
|
|
124
|
+
this._unformattedValue.setFloat32(this.offset.offset, actualValue);
|
|
125
|
+
break;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// this._value = setFormattedValue(value, this._offset);
|
|
130
|
+
// this._unformattedValue = parseFormattedValue(value, this._offset);
|
|
131
|
+
|
|
55
132
|
|
|
56
|
-
this.
|
|
57
|
-
this.
|
|
58
|
-
this.emit('change');
|
|
133
|
+
// this.emit('change');
|
|
134
|
+
this._parent.onEvent('change', this);
|
|
59
135
|
}
|
|
60
136
|
};
|
|
61
137
|
|
|
62
138
|
get unformattedValue () {
|
|
139
|
+
if (this._unformattedValue === null) {
|
|
140
|
+
this._setUnformattedValueIfEmpty();
|
|
141
|
+
}
|
|
142
|
+
|
|
63
143
|
return this._unformattedValue;
|
|
64
144
|
};
|
|
65
145
|
|
|
66
146
|
set unformattedValue (unformattedValue) {
|
|
67
147
|
this.setUnformattedValueWithoutChangeEvent(unformattedValue);
|
|
68
|
-
this.
|
|
148
|
+
this._value = null;
|
|
149
|
+
this._parent.onEvent('change', this);
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
_setUnformattedValueIfEmpty() {
|
|
153
|
+
this._value = null;
|
|
154
|
+
this._unformattedValue = new BitView(this._recordBuffer, this._recordBuffer.byteOffset);
|
|
155
|
+
this._unformattedValue.bigEndian = true;
|
|
69
156
|
};
|
|
70
157
|
|
|
71
158
|
setUnformattedValueWithoutChangeEvent(unformattedValue, suppressErrors) {
|
|
72
|
-
if (!
|
|
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.`)}
|
|
159
|
+
if (!(unformattedValue instanceof BitView)) { throw new Error(`Argument must be of type BitView. You passed in a(n) ${typeof unformattedValue}.`); }
|
|
74
160
|
else {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
if (this.offset.valueInSecondTable) {
|
|
78
|
-
value = this.secondTableField.value;
|
|
79
|
-
}
|
|
80
|
-
else {
|
|
81
|
-
value = parseFieldValue(unformattedValue.padStart(this._offset.length, '0'), this._offset);
|
|
82
|
-
}
|
|
83
|
-
|
|
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') && !suppressErrors) {
|
|
86
|
-
throw new Error(`Argument is not a valid unformatted value for this field. You passed in ${value}.`)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
this._value = value;
|
|
90
|
-
|
|
91
|
-
if (this._offset.enum) {
|
|
92
|
-
this._unformattedValue = this._offset.enum.getMemberByName(this._value).unformattedValue.padStart(this._offset.length, '0');
|
|
93
|
-
}
|
|
94
|
-
else {
|
|
95
|
-
this._unformattedValue = unformattedValue;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
};
|
|
100
|
-
|
|
101
|
-
module.exports = FranchiseFileField;
|
|
102
|
-
|
|
103
|
-
function setFormattedValue(value, offset) {
|
|
104
|
-
if (offset.enum) {
|
|
105
|
-
const theEnum = offset.enum.getMemberByName(value);
|
|
106
|
-
if (!theEnum) {
|
|
107
|
-
const theEnumByValue = offset.enum.getMemberByValue(value);
|
|
108
|
-
|
|
109
|
-
if (!theEnumByValue) {
|
|
110
|
-
throw new Error(`Argument is not a valid enum value for this field. You passed in ${value}.`);
|
|
111
|
-
}
|
|
112
|
-
else {
|
|
113
|
-
return theEnumByValue.name;
|
|
114
|
-
}
|
|
115
|
-
} else {
|
|
116
|
-
return theEnum.name;
|
|
161
|
+
this._unformattedValue = unformattedValue;
|
|
162
|
+
this._value = null;
|
|
117
163
|
}
|
|
118
164
|
}
|
|
119
165
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
case 'int':
|
|
123
|
-
return parseInt(value);
|
|
124
|
-
case 'bool':
|
|
125
|
-
return value == 1 || (value.toString().toLowerCase() == 'true');
|
|
126
|
-
case 'float':
|
|
127
|
-
return parseFloat(value);
|
|
128
|
-
default:
|
|
129
|
-
return value.toString();
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
|
|
133
|
-
function parseFieldValue(unformatted, offset) {
|
|
134
|
-
if (offset.enum) {
|
|
135
|
-
try {
|
|
136
|
-
const theEnum = offset.enum.getMemberByUnformattedValue(unformatted);
|
|
137
|
-
|
|
138
|
-
if (theEnum) {
|
|
139
|
-
return theEnum.name;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
catch (err) {
|
|
143
|
-
// console.log(err);
|
|
144
|
-
}
|
|
166
|
+
_getEnumFromValue(value) {
|
|
167
|
+
const enumName = this.offset.enum.getMemberByName(value);
|
|
145
168
|
|
|
146
|
-
return unformatted;
|
|
147
|
-
}
|
|
148
|
-
else {
|
|
149
|
-
switch (offset.type) {
|
|
150
|
-
case 's_int':
|
|
151
|
-
return utilService.bin2dec(unformatted) + offset.minValue;
|
|
152
|
-
case 'int':
|
|
153
|
-
if (offset.minValue || offset.maxValue) {
|
|
154
|
-
return utilService.bin2dec(unformatted);
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
const maxValueBinary = getMaxValueBinary(offset);
|
|
158
|
-
const maxValue = utilService.bin2dec(maxValueBinary);
|
|
159
|
-
const newValue = utilService.bin2dec(unformatted);
|
|
160
|
-
|
|
161
|
-
if (newValue === 0) {
|
|
162
|
-
return 0;
|
|
163
|
-
}
|
|
164
|
-
else {
|
|
165
|
-
return newValue - maxValue;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
case 'bool':
|
|
169
|
-
return unformatted[0] === '1' ? true : false;
|
|
170
|
-
case 'float':
|
|
171
|
-
return utilService.bin2Float(unformatted);
|
|
172
|
-
default:
|
|
173
|
-
return unformatted;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
};
|
|
177
|
-
|
|
178
|
-
function parseFormattedValue(formatted, offset) {
|
|
179
|
-
if (offset.enum) {
|
|
180
|
-
const enumName = offset.enum.getMemberByName(formatted);
|
|
181
|
-
|
|
182
169
|
if (enumName) {
|
|
183
|
-
return enumName
|
|
170
|
+
return enumName;
|
|
184
171
|
}
|
|
185
172
|
else {
|
|
186
|
-
const formattedEnum = offset.enum.getMemberByValue(
|
|
173
|
+
const formattedEnum = this.offset.enum.getMemberByValue(value)
|
|
187
174
|
|
|
188
175
|
if (formattedEnum) {
|
|
189
|
-
return formattedEnum
|
|
176
|
+
return formattedEnum;
|
|
190
177
|
}
|
|
191
178
|
else {
|
|
192
|
-
const unformattedEnum = offset.enum.getMemberByUnformattedValue(
|
|
179
|
+
const unformattedEnum = this.offset.enum.getMemberByUnformattedValue(value);
|
|
193
180
|
|
|
194
181
|
if (unformattedEnum) {
|
|
195
|
-
return unformattedEnum
|
|
182
|
+
return unformattedEnum;
|
|
196
183
|
}
|
|
197
184
|
else {
|
|
198
|
-
return offset.enum.members[0]
|
|
185
|
+
return this.offset.enum.members[0];
|
|
199
186
|
}
|
|
200
187
|
}
|
|
201
188
|
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
return
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
_parseFieldValue(unformatted, offset) {
|
|
192
|
+
if (offset.valueInSecondTable) {
|
|
193
|
+
return this.secondTableField.value;
|
|
194
|
+
}
|
|
195
|
+
else if (offset.enum) {
|
|
196
|
+
const enumUnformattedValue = utilService.dec2bin(this.unformattedValue.getBits(this.offset.offset, this.offset.length), offset.enum._maxLength);
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const theEnum = offset.enum.getMemberByUnformattedValue(enumUnformattedValue);
|
|
200
|
+
|
|
201
|
+
if (theEnum) {
|
|
202
|
+
return theEnum.name;
|
|
216
203
|
}
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
// console.log(err);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
return enumUnformattedValue;
|
|
223
210
|
}
|
|
224
|
-
|
|
211
|
+
else if (offset.isReference) {
|
|
212
|
+
const referenceData = utilService.getReferenceDataFromBitview(this._unformattedValue, this.offset.offset);
|
|
213
|
+
return utilService.getBinaryReferenceData(referenceData.tableId, referenceData.rowNumber);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
switch (offset.type) {
|
|
217
|
+
case 's_int':
|
|
218
|
+
// return utilService.bin2dec(unformatted) + offset.minValue;
|
|
219
|
+
return unformatted.getBits(offset.offset, offset.length) + offset.minValue;
|
|
220
|
+
case 'int':
|
|
221
|
+
if (offset.minValue || offset.maxValue) {
|
|
222
|
+
return unformatted.getBits(offset.offset, offset.length);
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
// This is for int[] tables.
|
|
226
|
+
|
|
227
|
+
const maxValueBinary = getMaxValueBinary(offset);
|
|
228
|
+
const maxValue = utilService.bin2dec(maxValueBinary);
|
|
229
|
+
const newValue = unformatted.getBits(offset.offset, offset.length);
|
|
230
|
+
|
|
231
|
+
if (newValue === 0) {
|
|
232
|
+
return 0;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
return newValue - maxValue;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
case 'bool':
|
|
239
|
+
return unformatted.getBits(offset.offset, 1) ? true : false;
|
|
240
|
+
case 'float':
|
|
241
|
+
// return utilService.bin2Float(unformatted);
|
|
242
|
+
return unformatted.getFloat32(offset.offset, offset.length);
|
|
243
|
+
default:
|
|
244
|
+
return unformatted;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
};
|
|
225
248
|
};
|
|
226
249
|
|
|
250
|
+
module.exports = FranchiseFileField;
|
|
251
|
+
|
|
227
252
|
function getMaxValueBinary(offset) {
|
|
228
253
|
let maxValue = '1';
|
|
229
254
|
for (let j = 0; j < (offset.length - 1); j++) {
|
package/FranchiseFileRecord.js
CHANGED
|
@@ -1,79 +1,62 @@
|
|
|
1
|
-
const EventEmitter = require('events').EventEmitter;
|
|
2
1
|
const utilService = require('./services/utilService');
|
|
3
2
|
const FranchiseFileField = require('./FranchiseFileField');
|
|
4
3
|
|
|
5
|
-
class FranchiseFileRecord
|
|
6
|
-
constructor(data, index, offsetTable) {
|
|
7
|
-
super();
|
|
4
|
+
class FranchiseFileRecord {
|
|
5
|
+
constructor(data, index, offsetTable, parent) {
|
|
8
6
|
this._data = data;
|
|
9
7
|
this._offsetTable = offsetTable;
|
|
10
8
|
this.index = index;
|
|
11
|
-
this.
|
|
9
|
+
this._fieldsArray = [];
|
|
10
|
+
this._fields = this.parseRecordFields(data, offsetTable, this);
|
|
12
11
|
this.isChanged = false;
|
|
13
12
|
this.arraySize = null;
|
|
14
13
|
this.isEmpty = false;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
this
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
14
|
+
this._parent = parent;
|
|
15
|
+
|
|
16
|
+
return new Proxy(this, {
|
|
17
|
+
get: function (target, prop, receiver) {
|
|
18
|
+
return target.fields[prop] !== undefined ? target.fields[prop].value : target[prop] !== undefined ? target[prop] : null;
|
|
19
|
+
},
|
|
20
|
+
set: function (target, prop, receiver) {
|
|
21
|
+
if (target.fields[prop] !== undefined) {
|
|
22
|
+
target.fields[prop].value = receiver;
|
|
24
23
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
field.on('change', function () {
|
|
28
|
-
that._data = utilService.replaceAt(that._data, this.offset.offset, this.unformattedValue);
|
|
29
|
-
|
|
30
|
-
// NOTE: At this time, we can only change the size of arrays of references.
|
|
31
|
-
// I'm not sure how to change the size of non-reference arrays, or if it's even possible.
|
|
32
|
-
if (that.arraySize !== null && that.arraySize !== undefined) {
|
|
33
|
-
const referenceData = this.referenceData;
|
|
34
|
-
|
|
35
|
-
// If the field is outside of the previous array size and was edited to a valid reference,
|
|
36
|
-
// then reset the array size
|
|
37
|
-
if (this.offset.index >= that.arraySize) {
|
|
38
|
-
if (this.isReference) {
|
|
39
|
-
if (referenceData.tableId !== 0 || referenceData.rowNumber !== 0) {
|
|
40
|
-
that.arraySize = this.offset.index + 1;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
// If the value was changed to 0s, then shrink the array size to this index.
|
|
46
|
-
else if (this.isReference) {
|
|
47
|
-
if (referenceData.tableId === 0 && referenceData.rowNumber === 0) {
|
|
48
|
-
that.arraySize = this.offset.index;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
24
|
+
else {
|
|
25
|
+
target[prop] = receiver;
|
|
51
26
|
}
|
|
52
27
|
|
|
53
|
-
|
|
54
|
-
}
|
|
55
|
-
})
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
})
|
|
56
31
|
};
|
|
57
32
|
|
|
58
33
|
get hexData () {
|
|
59
|
-
return
|
|
34
|
+
return this._data;
|
|
60
35
|
};
|
|
61
36
|
|
|
62
37
|
get fields () {
|
|
63
38
|
return this._fields;
|
|
64
39
|
};
|
|
65
40
|
|
|
41
|
+
get fieldsArray () {
|
|
42
|
+
return this._fieldsArray;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
get data() {
|
|
46
|
+
return this._data;
|
|
47
|
+
};
|
|
48
|
+
|
|
66
49
|
set data (data) {
|
|
67
50
|
this._data = data;
|
|
68
51
|
|
|
69
|
-
this.
|
|
52
|
+
this._fieldsArray.forEach((field) => {
|
|
70
53
|
const unformattedValue = data.slice(field.offset.offset, field.offset.offset + field.offset.length);
|
|
71
54
|
field.setUnformattedValueWithoutChangeEvent(unformattedValue);
|
|
72
55
|
});
|
|
73
56
|
};
|
|
74
57
|
|
|
75
58
|
getFieldByKey(key) {
|
|
76
|
-
return this._fields
|
|
59
|
+
return this._fields[key];
|
|
77
60
|
};
|
|
78
61
|
|
|
79
62
|
getValueByKey(key) {
|
|
@@ -86,22 +69,57 @@ class FranchiseFileRecord extends EventEmitter {
|
|
|
86
69
|
return field ? field.referenceData : null;
|
|
87
70
|
};
|
|
88
71
|
|
|
72
|
+
parseRecordFields(data, offsetTable, record) {
|
|
73
|
+
let fields = {};
|
|
74
|
+
|
|
75
|
+
for (let j = 0; j < offsetTable.length; j++) {
|
|
76
|
+
const offset = offsetTable[j];
|
|
77
|
+
|
|
78
|
+
// Push the entire record buffer to the field. No need to perform a calculation
|
|
79
|
+
// to subarray the buffer, BitView will take care of it in the Field.
|
|
80
|
+
fields[offset.name] = new FranchiseFileField(offset.name, data, offset, record);
|
|
81
|
+
|
|
82
|
+
this._fieldsArray.push(fields[offset.name]);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return fields;
|
|
86
|
+
};
|
|
87
|
+
|
|
89
88
|
empty() {
|
|
90
|
-
this.
|
|
89
|
+
this._parent.onEvent('empty', this);
|
|
91
90
|
this.isEmpty = true;
|
|
92
91
|
};
|
|
93
|
-
};
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
93
|
+
onEvent(name, field) {
|
|
94
|
+
if (name === 'change') {
|
|
95
|
+
// this._data = utilService.replaceAt(this._data, field.offset.offset, field.unformattedValue);
|
|
96
|
+
|
|
97
|
+
// NOTE: At field time, we can only change the size of arrays of references.
|
|
98
|
+
// I'm not sure how to change the size of non-reference arrays, or if it's even possible.
|
|
99
|
+
if (this.arraySize !== null && this.arraySize !== undefined) {
|
|
100
|
+
const referenceData = field.referenceData;
|
|
101
|
+
|
|
102
|
+
// If the field is outside of the previous array size and was edited to a valid reference,
|
|
103
|
+
// then reset the array size
|
|
104
|
+
if (field.offset.index >= this.arraySize) {
|
|
105
|
+
if (field.isReference) {
|
|
106
|
+
if (referenceData.tableId !== 0 || referenceData.rowNumber !== 0) {
|
|
107
|
+
this.arraySize = field.offset.index + 1;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// If the value was changed to 0s, then shrink the array size to field index.
|
|
113
|
+
else if (field.isReference) {
|
|
114
|
+
if (referenceData.tableId === 0 && referenceData.rowNumber === 0) {
|
|
115
|
+
this.arraySize = field.offset.index;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
99
119
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
120
|
+
this._parent.onEvent('change', this);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
};
|
|
105
124
|
|
|
106
|
-
|
|
107
|
-
};
|
|
125
|
+
module.exports = FranchiseFileRecord;
|
package/FranchiseFileTable.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const EventEmitter = require('events').EventEmitter;
|
|
2
2
|
const utilService = require('./services/utilService');
|
|
3
3
|
const FranchiseFileRecord = require('./FranchiseFileRecord');
|
|
4
|
+
const FranchiseFileTable2Field = require('./FranchiseFileTable2Field');
|
|
4
5
|
|
|
5
6
|
class FranchiseFileTable extends EventEmitter {
|
|
6
7
|
constructor(data, offset, gameYear, strategy) {
|
|
@@ -135,7 +136,7 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
135
136
|
|
|
136
137
|
this.records.forEach((record) => {
|
|
137
138
|
let isEmptyReference = false;
|
|
138
|
-
const firstFourBytesReference = utilService.
|
|
139
|
+
const firstFourBytesReference = utilService.getReferenceDataFromBuffer(record.data.slice(0, 4));
|
|
139
140
|
|
|
140
141
|
if (firstFourBytesReference.tableId === 0 && firstFourBytesReference.rowNumber !== 0) {
|
|
141
142
|
// Could be a an empty record reference or a table2 field.
|
|
@@ -246,7 +247,6 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
246
247
|
reject('Cannot read records: Schema is not defined.');
|
|
247
248
|
}
|
|
248
249
|
|
|
249
|
-
|
|
250
250
|
let offsetTableToUse = this.offsetTable;
|
|
251
251
|
const mandatoryOffsetsToLoad = this.strategy.getMandatoryOffsets(this.offsetTable);
|
|
252
252
|
|
|
@@ -260,7 +260,8 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
260
260
|
}
|
|
261
261
|
|
|
262
262
|
this.loadedOffsets = offsetTableToUse;
|
|
263
|
-
|
|
263
|
+
|
|
264
|
+
this.records = readRecords(this.data, this.header, offsetTableToUse, this);
|
|
264
265
|
|
|
265
266
|
if (this.header.hasSecondTable) {
|
|
266
267
|
this._parseTable2Values(this.data, this.header, this.records);
|
|
@@ -276,172 +277,6 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
276
277
|
if (this.emptyRecords.get(index)) {
|
|
277
278
|
record.isEmpty = true;
|
|
278
279
|
}
|
|
279
|
-
|
|
280
|
-
const that = this;
|
|
281
|
-
|
|
282
|
-
record.on('change', function (changedOffset) {
|
|
283
|
-
this.isChanged = true;
|
|
284
|
-
|
|
285
|
-
if (that.isArray) {
|
|
286
|
-
that.arraySizes[index] = this.arraySize;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
// When a record changes, we need to check if it was previously empty
|
|
290
|
-
// If so, we need to consider the record as no longer empty
|
|
291
|
-
// So we need to adjust the empty records
|
|
292
|
-
|
|
293
|
-
// First, check if the record's length is greater than 4 bytes (32 bits)
|
|
294
|
-
// If less than 4 bytes, it can never become empty...probably. :)
|
|
295
|
-
if (that.header.record1Size >= 4) {
|
|
296
|
-
|
|
297
|
-
// Ex: Empty record list looks like this: A -> B -> C
|
|
298
|
-
// When B's value is changed, the records need updated to: A -> C
|
|
299
|
-
const emptyRecordReference = that.emptyRecords.get(this.index);
|
|
300
|
-
const changedRecordWasEmpty = emptyRecordReference !== null && emptyRecordReference !== undefined;
|
|
301
|
-
|
|
302
|
-
if (changedRecordWasEmpty) {
|
|
303
|
-
// Check if the record's first four bytes still have a reference to the 0th table.
|
|
304
|
-
// If so, then the record is still considered empty.
|
|
305
|
-
|
|
306
|
-
// We need to check the buffer because the first field is not always a reference.
|
|
307
|
-
const referenceData = utilService.getReferenceData(this._data.slice(0, 32));
|
|
308
|
-
if (referenceData.tableId !== 0) {
|
|
309
|
-
|
|
310
|
-
// Delete the empty record entry because it is no longer empty
|
|
311
|
-
that.emptyRecords.delete(this.index);
|
|
312
|
-
|
|
313
|
-
// Set the isEmpty back to false because it's no longer empty
|
|
314
|
-
this.isEmpty = false;
|
|
315
|
-
|
|
316
|
-
// Check if there is a previous empty record
|
|
317
|
-
const previousEmptyReference = that.emptyRecords.get(emptyRecordReference.previous);
|
|
318
|
-
|
|
319
|
-
if (previousEmptyReference) {
|
|
320
|
-
// Set the previous empty record to point to the old reference's next node
|
|
321
|
-
that.emptyRecords.set(emptyRecordReference.previous, {
|
|
322
|
-
previous: that.emptyRecords.get(emptyRecordReference.previous).previous,
|
|
323
|
-
next: emptyRecordReference.next
|
|
324
|
-
});
|
|
325
|
-
|
|
326
|
-
// change the table buffer and record buffer to reflect this change
|
|
327
|
-
changeRecordBuffers(emptyRecordReference.previous, emptyRecordReference.next);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
// If there is a next empty reference, update the previous value accordingly to now point
|
|
331
|
-
// to the current record's previous index.
|
|
332
|
-
const nextEmptyReference = that.emptyRecords.get(emptyRecordReference.next);
|
|
333
|
-
|
|
334
|
-
if (nextEmptyReference) {
|
|
335
|
-
that.emptyRecords.set(emptyRecordReference.next, {
|
|
336
|
-
previous: emptyRecordReference.previous,
|
|
337
|
-
next: that.emptyRecords.get(emptyRecordReference.next).next
|
|
338
|
-
});
|
|
339
|
-
|
|
340
|
-
if (!previousEmptyReference) {
|
|
341
|
-
// If no previous empty record exists and a next record exists, we need to update the header to
|
|
342
|
-
// point to this record as the next record to use.
|
|
343
|
-
that.setNextRecordToUse(emptyRecordReference.next);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// If there are no previous or next empty references
|
|
348
|
-
// Then there are no more empty references in the table
|
|
349
|
-
// Update the table header nextRecordToUse back to the table record capacity
|
|
350
|
-
if (!previousEmptyReference && !nextEmptyReference) {
|
|
351
|
-
that.setNextRecordToUse(that.header.recordCapacity);
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
}
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
that.emit('change');
|
|
358
|
-
});
|
|
359
|
-
|
|
360
|
-
record.on('empty', function () {
|
|
361
|
-
onRecordEmpty(this);
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
function onRecordEmpty(record) {
|
|
365
|
-
// First, check if the record is already empty. If so, don't do anything...
|
|
366
|
-
// If not empty, then we need to empty it.
|
|
367
|
-
if (!record.isEmpty) {
|
|
368
|
-
record.isChanged = true;
|
|
369
|
-
const lastEmptyRecordMapEntry = Array.from(that.emptyRecords).pop();
|
|
370
|
-
|
|
371
|
-
// When we empty a record, we need to check if another empty record exists in the table.
|
|
372
|
-
if (lastEmptyRecordMapEntry !== null && lastEmptyRecordMapEntry !== undefined) {
|
|
373
|
-
|
|
374
|
-
// If an empty record already exists, we just need to get the last empty record
|
|
375
|
-
// and update its index to point to the current record that we want to empty.
|
|
376
|
-
const lastEmptyRecordIndex = lastEmptyRecordMapEntry[0];
|
|
377
|
-
|
|
378
|
-
that.emptyRecords.set(lastEmptyRecordIndex, {
|
|
379
|
-
previous: lastEmptyRecordMapEntry[1].previous,
|
|
380
|
-
next: record.index
|
|
381
|
-
});
|
|
382
|
-
|
|
383
|
-
// Then we need to update the current record index to point to the record capacity.
|
|
384
|
-
that.emptyRecords.set(record.index, {
|
|
385
|
-
previous: lastEmptyRecordIndex,
|
|
386
|
-
next: that.header.recordCapacity
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
// Finally, we need to update the buffers to reflect this data.
|
|
390
|
-
// First, place the new referenced index (will be the first 4 bytes)
|
|
391
|
-
// Next, fill the rest of the record with 0s (the last bytes of the record)
|
|
392
|
-
|
|
393
|
-
// And update both record's data. This will set the unformatted and formatted values
|
|
394
|
-
// without emitting an event
|
|
395
|
-
changeRecordBuffers(lastEmptyRecordIndex, record.index);
|
|
396
|
-
changeRecordBuffers(record.index, that.header.recordCapacity);
|
|
397
|
-
}
|
|
398
|
-
else {
|
|
399
|
-
// In this case, the record that was emptied is the first empty record in the table
|
|
400
|
-
that.emptyRecords.set(record.index, {
|
|
401
|
-
previous: null,
|
|
402
|
-
next: that.header.recordCapacity
|
|
403
|
-
});
|
|
404
|
-
|
|
405
|
-
// Finally update the table header and buffer so that the game uses this new empty
|
|
406
|
-
// record as the next record to use (or fill)
|
|
407
|
-
that.setNextRecordToUse(record.index);
|
|
408
|
-
changeRecordBuffers(record.index, that.header.recordCapacity);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
that.emit('change');
|
|
412
|
-
}
|
|
413
|
-
};
|
|
414
|
-
|
|
415
|
-
function changeRecordBuffers(index, emptyRecordReference) {
|
|
416
|
-
setBufferToEmptyRecordReference(index, emptyRecordReference);
|
|
417
|
-
setRecordInternalBuffer(index, emptyRecordReference);
|
|
418
|
-
};
|
|
419
|
-
|
|
420
|
-
function setBufferToEmptyRecordReference(index, emptyRecordReference) {
|
|
421
|
-
const recordStartIndex = that.header.table1StartIndex + (index * that.header.record1Size)
|
|
422
|
-
that.data.writeUInt32BE(emptyRecordReference, recordStartIndex);
|
|
423
|
-
// that.data.fill(0, recordStartIndex + 4, recordStartIndex + that.header.record1Size);
|
|
424
|
-
};
|
|
425
|
-
|
|
426
|
-
function setRecordInternalBuffer(index, emptyRecordReference) {
|
|
427
|
-
let newData = utilService.dec2bin(emptyRecordReference, 32);
|
|
428
|
-
|
|
429
|
-
const recordSizeInBits = that.header.record1Size * 8;
|
|
430
|
-
if (recordSizeInBits > 32) {
|
|
431
|
-
newData += that.records[index]._data.slice(32);
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
that.records[index].data = newData;
|
|
435
|
-
};
|
|
436
|
-
});
|
|
437
|
-
|
|
438
|
-
this.table2Records.forEach((record, index) => {
|
|
439
|
-
const that = this;
|
|
440
|
-
|
|
441
|
-
record.on('change', function (secondTableField) {
|
|
442
|
-
this.isChanged = true;
|
|
443
|
-
that.emit('change');
|
|
444
|
-
});
|
|
445
280
|
});
|
|
446
281
|
|
|
447
282
|
this.recordsRead = true;
|
|
@@ -453,10 +288,8 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
453
288
|
};
|
|
454
289
|
|
|
455
290
|
_parseEmptyRecords() {
|
|
456
|
-
const firstEmptyRecord = this.header.nextRecordToUse;
|
|
457
|
-
const sizeOfEachRecord = this.header.record1Size;
|
|
458
|
-
|
|
459
291
|
let emptyRecords = new Map();
|
|
292
|
+
const firstEmptyRecord = this.header.nextRecordToUse;
|
|
460
293
|
|
|
461
294
|
let previousEmptyRecordIndex = null;
|
|
462
295
|
let currentEmptyRecordIndex = firstEmptyRecord;
|
|
@@ -464,7 +297,7 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
464
297
|
if (firstEmptyRecord !== this.header.recordCapacity) {
|
|
465
298
|
while (currentEmptyRecordIndex !== this.header.recordCapacity) {
|
|
466
299
|
// let nextEmptyRecordIndex = this.data.readUInt32BE(this.header.table1StartIndex + (currentEmptyRecordIndex * sizeOfEachRecord));
|
|
467
|
-
let nextEmptyRecordIndex =
|
|
300
|
+
let nextEmptyRecordIndex = this.records[currentEmptyRecordIndex].data.readUInt32BE(0);
|
|
468
301
|
|
|
469
302
|
emptyRecords.set(currentEmptyRecordIndex, {
|
|
470
303
|
previous: previousEmptyRecordIndex,
|
|
@@ -479,21 +312,186 @@ class FranchiseFileTable extends EventEmitter {
|
|
|
479
312
|
return emptyRecords;
|
|
480
313
|
};
|
|
481
314
|
|
|
315
|
+
_onRecordEmpty(record) {
|
|
316
|
+
// First, check if the record is already empty. If so, don't do anything...
|
|
317
|
+
// If not empty, then we need to empty it.
|
|
318
|
+
if (!record.isEmpty) {
|
|
319
|
+
record.isChanged = true;
|
|
320
|
+
const lastEmptyRecordMapEntry = Array.from(this.emptyRecords).pop();
|
|
321
|
+
|
|
322
|
+
// When we empty a record, we need to check if another empty record exists in the table.
|
|
323
|
+
if (lastEmptyRecordMapEntry !== null && lastEmptyRecordMapEntry !== undefined) {
|
|
324
|
+
|
|
325
|
+
// If an empty record already exists, we just need to get the last empty record
|
|
326
|
+
// and update its index to point to the current record that we want to empty.
|
|
327
|
+
const lastEmptyRecordIndex = lastEmptyRecordMapEntry[0];
|
|
328
|
+
|
|
329
|
+
this.emptyRecords.set(lastEmptyRecordIndex, {
|
|
330
|
+
previous: lastEmptyRecordMapEntry[1].previous,
|
|
331
|
+
next: record.index
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
// Then we need to update the current record index to point to the record capacity.
|
|
335
|
+
this.emptyRecords.set(record.index, {
|
|
336
|
+
previous: lastEmptyRecordIndex,
|
|
337
|
+
next: this.header.recordCapacity
|
|
338
|
+
});
|
|
339
|
+
|
|
340
|
+
// Finally, we need to update the buffers to reflect this data.
|
|
341
|
+
// First, place the new referenced index (will be the first 4 bytes)
|
|
342
|
+
// Next, fill the rest of the record with 0s (the last bytes of the record)
|
|
343
|
+
|
|
344
|
+
// And update both record's data. This will set the unformatted and formatted values
|
|
345
|
+
// without emitting an event
|
|
346
|
+
this._changeRecordBuffers(lastEmptyRecordIndex, record.index);
|
|
347
|
+
this._changeRecordBuffers(record.index, this.header.recordCapacity);
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
// In this case, the record that was emptied is the first empty record in the table
|
|
351
|
+
this.emptyRecords.set(record.index, {
|
|
352
|
+
previous: null,
|
|
353
|
+
next: this.header.recordCapacity
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
// Finally update the table header and buffer so that the game uses this new empty
|
|
357
|
+
// record as the next record to use (or fill)
|
|
358
|
+
this.setNextRecordToUse(record.index);
|
|
359
|
+
this._changeRecordBuffers(record.index, this.header.recordCapacity);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
this.emit('change');
|
|
363
|
+
}
|
|
364
|
+
};
|
|
482
365
|
|
|
483
366
|
_parseTable2Values(data, header, records) {
|
|
484
367
|
const that = this;
|
|
485
368
|
const secondTableData = data.slice(header.table2StartIndex);
|
|
486
369
|
|
|
487
370
|
records.forEach((record) => {
|
|
488
|
-
const fieldsReferencingSecondTable = record.
|
|
371
|
+
const fieldsReferencingSecondTable = record.fieldsArray.filter((field) => { return field.secondTableField; });
|
|
489
372
|
|
|
490
373
|
fieldsReferencingSecondTable.forEach((field) => {
|
|
491
374
|
field.secondTableField.unformattedValue = that.strategyBase.table2Field.getInitialUnformattedValue(field, secondTableData);
|
|
492
375
|
field.secondTableField.strategy = that.strategyBase.table2Field;
|
|
493
376
|
that.table2Records.push(field.secondTableField);
|
|
377
|
+
field.secondTableField.parent = that;
|
|
494
378
|
});
|
|
495
379
|
});
|
|
496
380
|
};
|
|
381
|
+
|
|
382
|
+
_changeRecordBuffers(index, emptyRecordReference) {
|
|
383
|
+
this._setBufferToEmptyRecordReference(index, emptyRecordReference);
|
|
384
|
+
this._setRecordInternalBuffer(index, emptyRecordReference);
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
_setBufferToEmptyRecordReference(index, emptyRecordReference) {
|
|
388
|
+
const recordStartIndex = this.header.table1StartIndex + (index * this.header.record1Size)
|
|
389
|
+
this.data.writeUInt32BE(emptyRecordReference, recordStartIndex);
|
|
390
|
+
// that.data.fill(0, recordStartIndex + 4, recordStartIndex + that.header.record1Size);
|
|
391
|
+
};
|
|
392
|
+
|
|
393
|
+
_setRecordInternalBuffer(index, emptyRecordReference) {
|
|
394
|
+
// let newData = utilService.dec2bin(emptyRecordReference, 32);
|
|
395
|
+
|
|
396
|
+
// const recordSizeInBits = this.header.record1Size * 8;
|
|
397
|
+
// if (recordSizeInBits > 32) {
|
|
398
|
+
// newData += this.records[index]._data.slice(32);
|
|
399
|
+
// }
|
|
400
|
+
|
|
401
|
+
// console.log(newData);
|
|
402
|
+
|
|
403
|
+
this.records[index]._data.writeUInt32BE(emptyRecordReference, 0);
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
onEvent(name, object) {
|
|
407
|
+
if (object instanceof FranchiseFileRecord) {
|
|
408
|
+
if (name === 'change') {
|
|
409
|
+
object.isChanged = true;
|
|
410
|
+
|
|
411
|
+
if (this.isArray) {
|
|
412
|
+
this.arraySizes[object.index] = object.arraySize;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
// When a record changes, we need to check if it was previously empty
|
|
416
|
+
// If so, we need to consider the record as no longer empty
|
|
417
|
+
// So we need to adjust the empty records
|
|
418
|
+
|
|
419
|
+
// First, check if the record's length is greater than 4 bytes (32 bits)
|
|
420
|
+
// If less than 4 bytes, it can never become empty...probably. :)
|
|
421
|
+
if (this.header.record1Size >= 4) {
|
|
422
|
+
|
|
423
|
+
// Ex: Empty record list looks like object: A -> B -> C
|
|
424
|
+
// When B's value is changed, the records need updated to: A -> C
|
|
425
|
+
const emptyRecordReference = this.emptyRecords.get(object.index);
|
|
426
|
+
const changedRecordWasEmpty = emptyRecordReference !== null && emptyRecordReference !== undefined;
|
|
427
|
+
|
|
428
|
+
if (changedRecordWasEmpty) {
|
|
429
|
+
// Check if the record's first four bytes still have a reference to the 0th table.
|
|
430
|
+
// If so, then the record is still considered empty.
|
|
431
|
+
|
|
432
|
+
// We need to check the buffer because the first field is not always a reference.
|
|
433
|
+
const referenceData = utilService.getReferenceDataFromBuffer(object.data.slice(0, 4));
|
|
434
|
+
if (referenceData.tableId !== 0) {
|
|
435
|
+
|
|
436
|
+
// Delete the empty record entry because it is no longer empty
|
|
437
|
+
this.emptyRecords.delete(object.index);
|
|
438
|
+
|
|
439
|
+
// Set the isEmpty back to false because it's no longer empty
|
|
440
|
+
object.isEmpty = false;
|
|
441
|
+
|
|
442
|
+
// Check if there is a previous empty record
|
|
443
|
+
const previousEmptyReference = this.emptyRecords.get(emptyRecordReference.previous);
|
|
444
|
+
|
|
445
|
+
if (previousEmptyReference) {
|
|
446
|
+
// Set the previous empty record to point to the old reference's next node
|
|
447
|
+
this.emptyRecords.set(emptyRecordReference.previous, {
|
|
448
|
+
previous: this.emptyRecords.get(emptyRecordReference.previous).previous,
|
|
449
|
+
next: emptyRecordReference.next
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// change the table buffer and record buffer to reflect object change
|
|
453
|
+
this._changeRecordBuffers(emptyRecordReference.previous, emptyRecordReference.next);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
// If there is a next empty reference, update the previous value accordingly to now point
|
|
457
|
+
// to the current record's previous index.
|
|
458
|
+
const nextEmptyReference = this.emptyRecords.get(emptyRecordReference.next);
|
|
459
|
+
|
|
460
|
+
if (nextEmptyReference) {
|
|
461
|
+
this.emptyRecords.set(emptyRecordReference.next, {
|
|
462
|
+
previous: emptyRecordReference.previous,
|
|
463
|
+
next: this.emptyRecords.get(emptyRecordReference.next).next
|
|
464
|
+
});
|
|
465
|
+
|
|
466
|
+
if (!previousEmptyReference) {
|
|
467
|
+
// If no previous empty record exists and a next record exists, we need to update the header to
|
|
468
|
+
// point to object record as the next record to use.
|
|
469
|
+
this.setNextRecordToUse(emptyRecordReference.next);
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// If there are no previous or next empty references
|
|
474
|
+
// Then there are no more empty references in the table
|
|
475
|
+
// Update the table header nextRecordToUse back to the table record capacity
|
|
476
|
+
if (!previousEmptyReference && !nextEmptyReference) {
|
|
477
|
+
this.setNextRecordToUse(this.header.recordCapacity);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
this.emit('change');
|
|
484
|
+
}
|
|
485
|
+
else if (name === 'empty') {
|
|
486
|
+
this._onRecordEmpty(object);
|
|
487
|
+
this.emit('change');
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
else if (object instanceof FranchiseFileTable2Field) {
|
|
491
|
+
object.isChanged = true;
|
|
492
|
+
this.emit('change');
|
|
493
|
+
}
|
|
494
|
+
};
|
|
497
495
|
};
|
|
498
496
|
|
|
499
497
|
module.exports = FranchiseFileTable;
|
|
@@ -504,13 +502,17 @@ function readOffsetTable(data, schema, header) {
|
|
|
504
502
|
// console.log(offsetTable.sort((a,b) => { return a.indexOffset - b.indexOffset}))
|
|
505
503
|
sortOffsetTableByIndexOffset();
|
|
506
504
|
|
|
505
|
+
function isSkippedOffset(offset) {
|
|
506
|
+
return offset.final || offset.const || offset.type.indexOf('()') >= 0 || offset.type === 'ITransaction_Sleep';
|
|
507
|
+
};
|
|
508
|
+
|
|
507
509
|
for(let i = 0; i < offsetTable.length; i++) {
|
|
508
510
|
let curOffset = offsetTable[i];
|
|
509
511
|
let nextOffset = offsetTable.length > i + 1 ? offsetTable[i+1] : null;
|
|
510
512
|
|
|
511
513
|
if (nextOffset) {
|
|
512
514
|
let curIndex = i+2;
|
|
513
|
-
while(nextOffset && (nextOffset
|
|
515
|
+
while(nextOffset && isSkippedOffset(nextOffset)) {
|
|
514
516
|
nextOffset = offsetTable[curIndex];
|
|
515
517
|
curIndex += 1;
|
|
516
518
|
}
|
|
@@ -541,7 +543,7 @@ function readOffsetTable(data, schema, header) {
|
|
|
541
543
|
const currentOffset = offsetTable[currentOffsetIndex];
|
|
542
544
|
|
|
543
545
|
if (currentOffset) {
|
|
544
|
-
if (currentOffset
|
|
546
|
+
if (isSkippedOffset(currentOffset)) {
|
|
545
547
|
currentOffsetIndex += 1;
|
|
546
548
|
continue;
|
|
547
549
|
}
|
|
@@ -571,7 +573,7 @@ function readOffsetTable(data, schema, header) {
|
|
|
571
573
|
}
|
|
572
574
|
});
|
|
573
575
|
|
|
574
|
-
offsetTable = offsetTable.filter((offset) => { return !
|
|
576
|
+
offsetTable = offsetTable.filter((offset) => { return !(isSkippedOffset(offset)) });
|
|
575
577
|
offsetTable.sort((a,b) => { return a.offset - b.offset; });
|
|
576
578
|
|
|
577
579
|
for (let i = 0; i < offsetTable.length; i++) {
|
|
@@ -614,15 +616,19 @@ function readOffsetTable(data, schema, header) {
|
|
|
614
616
|
};
|
|
615
617
|
};
|
|
616
618
|
|
|
617
|
-
function readRecords(data, header, offsetTable) {
|
|
618
|
-
const binaryData = utilService.getBitArray(data.slice(header.table1StartIndex, header.table2StartIndex));
|
|
619
|
-
|
|
619
|
+
function readRecords(data, header, offsetTable, table) {
|
|
620
|
+
// const binaryData = utilService.getBitArray(data.slice(header.table1StartIndex, header.table2StartIndex));
|
|
620
621
|
let records = [];
|
|
621
622
|
|
|
622
|
-
if (
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
623
|
+
if (data) {
|
|
624
|
+
let index = 0;
|
|
625
|
+
|
|
626
|
+
for (let i = header.table1StartIndex; i < header.table2StartIndex; i += header.record1Size) {
|
|
627
|
+
// const recordBinary = binaryData.slice(i, i + (header.record1Size * 8));
|
|
628
|
+
let record = new FranchiseFileRecord(data.slice(i, i + header.record1Size), index, offsetTable, table);
|
|
629
|
+
records.push(record);
|
|
630
|
+
|
|
631
|
+
index += 1;
|
|
626
632
|
}
|
|
627
633
|
}
|
|
628
634
|
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
const EventEmitter = require('events').EventEmitter;
|
|
2
2
|
const utilService = require('./services/utilService');
|
|
3
3
|
|
|
4
|
-
class FranchiseFileTable2Field
|
|
5
|
-
constructor (index, maxLength) {
|
|
6
|
-
super();
|
|
4
|
+
class FranchiseFileTable2Field {
|
|
5
|
+
constructor (index, maxLength, parent) {
|
|
7
6
|
this._value = '';
|
|
8
7
|
this.rawIndex = index;
|
|
9
8
|
this.isChanged = false;
|
|
@@ -11,8 +10,9 @@ class FranchiseFileTable2Field extends EventEmitter {
|
|
|
11
10
|
this.fieldReference = null;
|
|
12
11
|
this.lengthAtLastSave = null;
|
|
13
12
|
this._unformattedValue = null;
|
|
14
|
-
this.index =
|
|
13
|
+
this.index = index;
|
|
15
14
|
this._offset = this.index;
|
|
15
|
+
this._parent = parent;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
get unformattedValue () {
|
|
@@ -25,38 +25,39 @@ class FranchiseFileTable2Field extends EventEmitter {
|
|
|
25
25
|
if (this.lengthAtLastSave === null) {
|
|
26
26
|
this.lengthAtLastSave = getLengthOfUnformattedValue(this._unformattedValue);
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
this._value = formattedValue.replace(/\0.*$/g,'');
|
|
36
|
-
this.emit('change');
|
|
28
|
+
|
|
29
|
+
this._value = null;
|
|
30
|
+
if (this._parent) {
|
|
31
|
+
this._parent.onEvent('change', this);
|
|
32
|
+
}
|
|
37
33
|
};
|
|
38
34
|
|
|
39
35
|
get value () {
|
|
36
|
+
if (this._value === null) {
|
|
37
|
+
this._value = this._unformattedValue.toString().replace(/\0.*$/g,'');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
40
|
return this._value;
|
|
41
41
|
};
|
|
42
42
|
|
|
43
43
|
set value (value) {
|
|
44
|
+
this._value = value;
|
|
45
|
+
|
|
44
46
|
if (value.length > this.maxLength) {
|
|
45
47
|
value = value.substring(0, this.maxLength);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
|
-
this._value = value;
|
|
49
50
|
this._unformattedValue = this._strategy.setUnformattedValueFromFormatted(value, this.maxLength);
|
|
50
51
|
|
|
51
52
|
if (this.lengthAtLastSave === null) {
|
|
52
53
|
this.lengthAtLastSave = getLengthOfUnformattedValue(this._unformattedValue);
|
|
53
54
|
}
|
|
54
55
|
|
|
55
|
-
this.
|
|
56
|
+
this._parent.onEvent('change', this);
|
|
56
57
|
};
|
|
57
58
|
|
|
58
59
|
get hexData () {
|
|
59
|
-
return
|
|
60
|
+
return this._unformattedValue;
|
|
60
61
|
};
|
|
61
62
|
|
|
62
63
|
get strategy () {
|
|
@@ -76,13 +77,21 @@ class FranchiseFileTable2Field extends EventEmitter {
|
|
|
76
77
|
this.index = offset;
|
|
77
78
|
|
|
78
79
|
if (this.fieldReference) {
|
|
79
|
-
this.fieldReference.unformattedValue
|
|
80
|
+
this.fieldReference.unformattedValue.setBits(this.fieldReference.offset.offset, offset, 32);
|
|
80
81
|
}
|
|
81
82
|
};
|
|
83
|
+
|
|
84
|
+
get parent() {
|
|
85
|
+
return this._parent;
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
set parent(parent) {
|
|
89
|
+
this._parent = parent;
|
|
90
|
+
};
|
|
82
91
|
};
|
|
83
92
|
|
|
84
93
|
module.exports = FranchiseFileTable2Field;
|
|
85
94
|
|
|
86
95
|
function getLengthOfUnformattedValue(value) {
|
|
87
|
-
return value.length
|
|
96
|
+
return value.length;
|
|
88
97
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "madden-franchise",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "3.0.0",
|
|
4
4
|
"description": "Tools to read a madden franchise file and get data from it",
|
|
5
5
|
"main": "FranchiseFile.js",
|
|
6
6
|
"scripts": {
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"author": "matthewpanetta",
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"dependencies": {
|
|
16
|
+
"bit-buffer": "^0.2.5",
|
|
16
17
|
"node-xml-stream-parser": "^1.0.12"
|
|
17
18
|
},
|
|
18
19
|
"repository": {
|
package/scripts/getTableId.js
CHANGED
|
@@ -2,8 +2,17 @@ const FranchiseFile = require('../FranchiseFile');
|
|
|
2
2
|
|
|
3
3
|
const franchisePath = 'C:\\Users\\Matt\\Documents\\Madden NFL 20\\settings\\CAREER-M03TEST_MOD';
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
file
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
(async () => {
|
|
6
|
+
const file = new FranchiseFile(franchisePath);
|
|
7
|
+
file.on('ready', async () => {
|
|
8
|
+
const tableToFind = file.getTableById(7391);
|
|
9
|
+
console.log(tableToFind);
|
|
10
|
+
|
|
11
|
+
const user = file.getTableByName('FranchiseUser');
|
|
12
|
+
await user.readRecords();
|
|
13
|
+
const row = user.records.filter(record => !record.isEmpty).length;
|
|
14
|
+
console.log(row);
|
|
15
|
+
const teamSetting = user.records[row].getFieldByKey('TeamSetting');
|
|
16
|
+
console.log(teamSetting);
|
|
17
|
+
});
|
|
18
|
+
})();
|
package/services/utilService.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
const { BitView } = require("bit-buffer");
|
|
2
|
+
|
|
1
3
|
let utilService = {};
|
|
2
4
|
|
|
3
5
|
utilService.intersection = function (arrayOfArrays) {
|
|
@@ -223,6 +225,19 @@ utilService.getReferenceData = function (value) {
|
|
|
223
225
|
}
|
|
224
226
|
};
|
|
225
227
|
|
|
228
|
+
utilService.getReferenceDataFromBuffer = (buf) => {
|
|
229
|
+
let bv = new BitView(buf, buf.byteOffset);
|
|
230
|
+
bv.bigEndian = true;
|
|
231
|
+
return utilService.getReferenceDataFromBitview(bv);
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
utilService.getReferenceDataFromBitview = (bv, start = 0) => {
|
|
235
|
+
return {
|
|
236
|
+
tableId: bv.getBits(start, 15),
|
|
237
|
+
rowNumber: bv.getBits(start+15, 17)
|
|
238
|
+
};
|
|
239
|
+
};
|
|
240
|
+
|
|
226
241
|
utilService.getBinaryReferenceData = function (tableId, rowNumber) {
|
|
227
242
|
const referenceBinary = utilService.dec2bin(tableId, 15);
|
|
228
243
|
const recordIndexBinary = utilService.dec2bin(rowNumber, 17);
|
|
@@ -12,7 +12,7 @@ FTCTable2FieldStrategy.getInitialUnformattedValue = (field, data) => {
|
|
|
12
12
|
fieldData = fieldData.slice(0, endOfFieldIndex + 1);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
return
|
|
15
|
+
return fieldData;
|
|
16
16
|
};
|
|
17
17
|
|
|
18
18
|
FTCTable2FieldStrategy.setUnformattedValueFromFormatted = (formattedValue, maxLength) => {
|
|
@@ -20,10 +20,8 @@ FTCTable2FieldStrategy.setUnformattedValueFromFormatted = (formattedValue, maxLe
|
|
|
20
20
|
// FTC strings cannot equal max length because the last character must be the null character.
|
|
21
21
|
formattedValue = formattedValue.substring(0, maxLength - 1);
|
|
22
22
|
}
|
|
23
|
-
|
|
24
|
-
return
|
|
25
|
-
return char.charCodeAt(0).toString(2).padStart(8, '0');
|
|
26
|
-
}).join('') + '00000000';
|
|
23
|
+
|
|
24
|
+
return Buffer.from(formattedValue + '\u0000');
|
|
27
25
|
};
|
|
28
26
|
|
|
29
27
|
module.exports = FTCTable2FieldStrategy;
|
|
@@ -3,7 +3,7 @@ const utilService = require('../../../services/utilService');
|
|
|
3
3
|
let FranchiseTable2FieldStrategy = {};
|
|
4
4
|
|
|
5
5
|
FranchiseTable2FieldStrategy.getInitialUnformattedValue = (field, data) => {
|
|
6
|
-
return
|
|
6
|
+
return data.slice(field.secondTableField.index, (field.secondTableField.index + field.offset.maxLength));
|
|
7
7
|
};
|
|
8
8
|
|
|
9
9
|
FranchiseTable2FieldStrategy.setUnformattedValueFromFormatted = (formattedValue, maxLength) => {
|
|
@@ -18,10 +18,8 @@ FranchiseTable2FieldStrategy.setUnformattedValueFromFormatted = (formattedValue,
|
|
|
18
18
|
for (let i = 0; i < numberOfNullCharactersToAdd; i++) {
|
|
19
19
|
valuePadded += String.fromCharCode(0);
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
return
|
|
23
|
-
return char.charCodeAt(0).toString(2).padStart(8, '0');
|
|
24
|
-
}).join('');
|
|
21
|
+
|
|
22
|
+
return Buffer.from(valuePadded);
|
|
25
23
|
};
|
|
26
24
|
|
|
27
25
|
module.exports = FranchiseTable2FieldStrategy;
|