@etothepii/satisfactory-file-parser 0.0.33 → 0.1.1
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/README.md +12 -8
- package/build/index.js +41 -51
- package/build/parser/byte/alignment.enum.js +8 -18
- package/build/parser/byte/binary-operable.interface.js +2 -12
- package/build/parser/byte/binary-readable.interface.js +2 -12
- package/build/parser/byte/byte-reader.class.js +122 -132
- package/build/parser/byte/byte-writer.class.js +133 -143
- package/build/parser/error/parser.error.js +40 -50
- package/build/parser/file.types.js +2 -12
- package/build/parser/parser.js +84 -95
- package/build/parser/satisfactory/blueprint/blueprint-reader.js +109 -119
- package/build/parser/satisfactory/blueprint/blueprint-writer.js +56 -66
- package/build/parser/satisfactory/blueprint/blueprint.types.js +2 -12
- package/build/parser/satisfactory/objects/DataFields.js +306 -307
- package/build/parser/satisfactory/objects/ObjectReference.js +14 -24
- package/build/parser/satisfactory/objects/Property.js +974 -977
- package/build/parser/satisfactory/objects/SaveComponent.js +28 -38
- package/build/parser/satisfactory/objects/SaveEntity.js +69 -75
- package/build/parser/satisfactory/objects/SaveObject.js +26 -36
- package/build/parser/satisfactory/save/asynchronous-level.class.js +60 -70
- package/build/parser/satisfactory/save/level.class.d.ts +1 -1
- package/build/parser/satisfactory/save/level.class.js +147 -128
- package/build/parser/satisfactory/save/satisfactory-save.js +10 -20
- package/build/parser/satisfactory/save/save-reader.d.ts +2 -2
- package/build/parser/satisfactory/save/save-reader.js +209 -171
- package/build/parser/satisfactory/save/save-writer.js +97 -107
- package/build/parser/satisfactory/save/save.types.js +2 -12
- package/build/parser/satisfactory/structs/util.types.d.ts +6 -0
- package/build/parser/satisfactory/structs/util.types.js +122 -96
- package/build/parser/stream/byte-stream-reader.class.js +217 -227
- package/build/parser/stream/json-stream-state-writer.js +15 -25
- package/build/parser/stream/json-stream-writable.js +72 -82
- package/build/parser/stream/json-stream-writer.js +122 -132
- package/build/parser/stream/save-stream-json-stringifier.js +29 -39
- package/build/parser/stream/save-stream-reader.class.js +106 -116
- package/build/parser/stream/save-stream-writer.class.js +90 -100
- package/build/parser/stream/stream-level.class.js +93 -103
- package/package.json +2 -2
|
@@ -1,188 +1,226 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
2
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
3
4
|
};
|
|
4
|
-
(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SaveReader = exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = exports.projectionFilterApplies = void 0;
|
|
7
|
+
const pako_1 = __importDefault(require("pako"));
|
|
8
|
+
const alignment_enum_1 = require("../../byte/alignment.enum");
|
|
9
|
+
const byte_reader_class_1 = require("../../byte/byte-reader.class");
|
|
10
|
+
const parser_error_1 = require("../../error/parser.error");
|
|
11
|
+
const SaveComponent_1 = require("../objects/SaveComponent");
|
|
12
|
+
const SaveEntity_1 = require("../objects/SaveEntity");
|
|
13
|
+
const asynchronous_level_class_1 = require("./asynchronous-level.class");
|
|
14
|
+
const level_class_1 = require("./level.class");
|
|
15
|
+
const projectionFilterApplies = (config, object) => {
|
|
16
|
+
let cond1 = false;
|
|
17
|
+
if ((0, SaveEntity_1.isSaveEntity)(object) || (config.includeComponents && (0, SaveComponent_1.isSaveComponent)(object))) {
|
|
18
|
+
cond1 = true;
|
|
8
19
|
}
|
|
9
|
-
|
|
10
|
-
|
|
20
|
+
let cond2 = true;
|
|
21
|
+
if ((0, SaveEntity_1.isSaveEntity)(object)) {
|
|
22
|
+
cond2 = false;
|
|
23
|
+
if (config.entityPathFilter.behavior === 'whitelist') {
|
|
24
|
+
cond2 = config.entityPathFilter.list.find(en => object.typePath.startsWith(en)) !== undefined;
|
|
25
|
+
}
|
|
26
|
+
else if (config.entityPathFilter.behavior === 'blacklist') {
|
|
27
|
+
cond2 = config.entityPathFilter.list.find(en => object.typePath.startsWith(en)) === undefined;
|
|
28
|
+
}
|
|
11
29
|
}
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
30
|
+
return cond1 && cond2;
|
|
31
|
+
};
|
|
32
|
+
exports.projectionFilterApplies = projectionFilterApplies;
|
|
33
|
+
exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = 49;
|
|
34
|
+
class SaveReader extends byte_reader_class_1.ByteReader {
|
|
35
|
+
constructor(fileBuffer, onProgressCallback = () => { }) {
|
|
36
|
+
super(fileBuffer, alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
37
|
+
this.onProgressCallback = onProgressCallback;
|
|
38
|
+
this.levels = [];
|
|
39
|
+
this.trailingCollectedObjects = [];
|
|
40
|
+
this.compressionInfo = {
|
|
41
|
+
packageFileTag: 0,
|
|
42
|
+
maxChunkContentSize: 0,
|
|
43
|
+
chunkHeaderSize: exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
readHeader() {
|
|
47
|
+
this.header = {
|
|
48
|
+
saveHeaderType: 0,
|
|
49
|
+
saveVersion: 0,
|
|
50
|
+
buildVersion: 0,
|
|
51
|
+
mapName: "DEFAULT",
|
|
52
|
+
mapOptions: "",
|
|
53
|
+
sessionName: "",
|
|
54
|
+
playDurationSeconds: 0,
|
|
55
|
+
saveDateTime: "0",
|
|
56
|
+
sessionVisibility: 0
|
|
57
|
+
};
|
|
58
|
+
this.header.saveHeaderType = this.readInt32();
|
|
59
|
+
this.header.saveVersion = this.readInt32();
|
|
60
|
+
this.header.buildVersion = this.readInt32();
|
|
61
|
+
this.header.mapName = this.readString();
|
|
62
|
+
this.header.mapOptions = this.readString();
|
|
63
|
+
this.header.sessionName = this.readString();
|
|
64
|
+
this.header.playDurationSeconds = this.readInt32();
|
|
65
|
+
const rawSaveDateTimeInTicks = this.readLong();
|
|
66
|
+
const unixMilliseconds = (rawSaveDateTimeInTicks - SaveReader.EPOCH_TICKS) / 10000n;
|
|
67
|
+
this.header.saveDateTime = unixMilliseconds.toString();
|
|
68
|
+
this.header.sessionVisibility = this.readByte();
|
|
69
|
+
if (this.header.saveHeaderType >= 7) {
|
|
70
|
+
this.header.fEditorObjectVersion = this.readInt32();
|
|
38
71
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
class SaveReader extends byte_reader_class_1.ByteReader {
|
|
44
|
-
constructor(fileBuffer, onProgressCallback = () => { }) {
|
|
45
|
-
super(fileBuffer, alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
46
|
-
this.onProgressCallback = onProgressCallback;
|
|
47
|
-
this.levels = [];
|
|
48
|
-
this.trailingCollectedObjects = [];
|
|
49
|
-
this.compressionInfo = {
|
|
50
|
-
packageFileTag: 0,
|
|
51
|
-
maxChunkContentSize: 0,
|
|
52
|
-
chunkHeaderSize: exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
readHeader() {
|
|
56
|
-
this.header = {
|
|
57
|
-
saveHeaderType: 0,
|
|
58
|
-
saveVersion: 0,
|
|
59
|
-
buildVersion: 0,
|
|
60
|
-
mapName: "DEFAULT",
|
|
61
|
-
mapOptions: "",
|
|
62
|
-
sessionName: "",
|
|
63
|
-
playDurationSeconds: 0,
|
|
64
|
-
saveDateTime: "0",
|
|
65
|
-
sessionVisibility: 0
|
|
66
|
-
};
|
|
67
|
-
this.header.saveHeaderType = this.readInt32();
|
|
68
|
-
this.header.saveVersion = this.readInt32();
|
|
69
|
-
this.header.buildVersion = this.readInt32();
|
|
70
|
-
this.header.mapName = this.readString();
|
|
71
|
-
this.header.mapOptions = this.readString();
|
|
72
|
-
this.header.sessionName = this.readString();
|
|
73
|
-
this.header.playDurationSeconds = this.readInt32();
|
|
74
|
-
const rawSaveDateTimeInTicks = this.readLong();
|
|
75
|
-
const unixMilliseconds = (rawSaveDateTimeInTicks - SaveReader.EPOCH_TICKS) / 10000n;
|
|
76
|
-
this.header.saveDateTime = unixMilliseconds.toString();
|
|
77
|
-
this.header.sessionVisibility = this.readByte();
|
|
78
|
-
if (this.header.saveHeaderType >= 7) {
|
|
79
|
-
this.header.fEditorObjectVersion = this.readInt32();
|
|
80
|
-
}
|
|
81
|
-
if (this.header.saveHeaderType >= 8) {
|
|
82
|
-
this.header.rawModMetadataString = this.readString();
|
|
83
|
-
try {
|
|
84
|
-
this.header.modMetadata = JSON.parse(this.header.rawModMetadataString);
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
}
|
|
88
|
-
this.header.isModdedSave = this.readInt32();
|
|
89
|
-
}
|
|
90
|
-
if (this.header.saveHeaderType >= 10) {
|
|
91
|
-
this.header.saveIdentifier = this.readString();
|
|
72
|
+
if (this.header.saveHeaderType >= 8) {
|
|
73
|
+
this.header.rawModMetadataString = this.readString();
|
|
74
|
+
try {
|
|
75
|
+
this.header.modMetadata = JSON.parse(this.header.rawModMetadataString);
|
|
92
76
|
}
|
|
93
|
-
|
|
77
|
+
catch (error) {
|
|
94
78
|
}
|
|
95
|
-
|
|
96
|
-
throw new parser_error_1.UnsupportedVersionError("The save version is too old to support encoding currently. Save in newer game version.");
|
|
97
|
-
}
|
|
98
|
-
return this.header;
|
|
79
|
+
this.header.isModdedSave = this.readInt32();
|
|
99
80
|
}
|
|
100
|
-
|
|
101
|
-
this.
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
currentChunks.push(currentInflatedChunk);
|
|
130
|
-
}
|
|
131
|
-
catch (err) {
|
|
132
|
-
throw new parser_error_1.CompressionLibraryError("Failed to inflate compressed save data. " + err);
|
|
133
|
-
}
|
|
81
|
+
if (this.header.saveHeaderType >= 10) {
|
|
82
|
+
this.header.saveIdentifier = this.readString();
|
|
83
|
+
}
|
|
84
|
+
if (this.header.saveHeaderType >= 13) {
|
|
85
|
+
const unk1 = this.readInt32();
|
|
86
|
+
const unk2 = this.readInt32();
|
|
87
|
+
const unk3 = this.readBytes(16);
|
|
88
|
+
const unk4 = this.readInt32();
|
|
89
|
+
}
|
|
90
|
+
if (this.header.saveVersion >= 21) {
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
throw new parser_error_1.UnsupportedVersionError("The save version is too old to support encoding currently. Save in newer game version.");
|
|
94
|
+
}
|
|
95
|
+
return this.header;
|
|
96
|
+
}
|
|
97
|
+
inflateChunks() {
|
|
98
|
+
this.fileBuffer = this.fileBuffer.slice(this.currentByte);
|
|
99
|
+
this.handledByte = 0;
|
|
100
|
+
this.currentByte = 0;
|
|
101
|
+
this.maxByte = this.fileBuffer.byteLength;
|
|
102
|
+
let currentChunks = [];
|
|
103
|
+
let totalUncompressedBodySize = 0;
|
|
104
|
+
while (this.handledByte < this.maxByte) {
|
|
105
|
+
let chunkHeader = new DataView(this.fileBuffer.slice(0, this.compressionInfo.chunkHeaderSize));
|
|
106
|
+
this.currentByte = this.compressionInfo.chunkHeaderSize;
|
|
107
|
+
this.handledByte += this.compressionInfo.chunkHeaderSize;
|
|
108
|
+
if (this.compressionInfo.packageFileTag <= 0) {
|
|
109
|
+
this.compressionInfo.packageFileTag = chunkHeader.getInt32(0, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
134
110
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
let currentLength = 0;
|
|
138
|
-
for (let i = 0; i < currentChunks.length; i++) {
|
|
139
|
-
bigWholeChunk.set(currentChunks[i], currentLength);
|
|
140
|
-
currentLength += currentChunks[i].length;
|
|
111
|
+
if (this.compressionInfo.maxChunkContentSize <= 0) {
|
|
112
|
+
this.compressionInfo.maxChunkContentSize = chunkHeader.getInt32(8, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
141
113
|
}
|
|
114
|
+
const chunkCompressedLength = chunkHeader.getInt32(33, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
115
|
+
const chunkUncompressedLength = chunkHeader.getInt32(25, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
116
|
+
totalUncompressedBodySize += chunkUncompressedLength;
|
|
117
|
+
const currentChunkSize = chunkCompressedLength;
|
|
118
|
+
let currentChunk = this.fileBuffer.slice(this.currentByte, this.currentByte + currentChunkSize);
|
|
119
|
+
this.handledByte += currentChunkSize;
|
|
120
|
+
this.currentByte += currentChunkSize;
|
|
121
|
+
this.fileBuffer = this.fileBuffer.slice(this.currentByte);
|
|
142
122
|
this.currentByte = 0;
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
throw new parser_error_1.CorruptSaveError(`Possibly corrupt. Indicated size of total save body (${dataLength}) does not match the uncompressed real size of ${totalUncompressedBodySize}.`);
|
|
148
|
-
}
|
|
149
|
-
return {
|
|
150
|
-
concatenatedChunkLength: newChunkLength,
|
|
151
|
-
numChunks: currentChunks.length
|
|
152
|
-
};
|
|
153
|
-
}
|
|
154
|
-
readLevels() {
|
|
155
|
-
if (!this.header) {
|
|
156
|
-
throw new parser_error_1.ParserError('ParserError', 'Header must be set before objects can be read.');
|
|
123
|
+
try {
|
|
124
|
+
let currentInflatedChunk = null;
|
|
125
|
+
currentInflatedChunk = pako_1.default.inflate(currentChunk);
|
|
126
|
+
currentChunks.push(currentInflatedChunk);
|
|
157
127
|
}
|
|
158
|
-
|
|
159
|
-
throw new parser_error_1.
|
|
128
|
+
catch (err) {
|
|
129
|
+
throw new parser_error_1.CompressionLibraryError("Failed to inflate compressed save data. " + err);
|
|
160
130
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
131
|
+
}
|
|
132
|
+
let newChunkLength = currentChunks.map(cc => cc.length).reduce((prev, cur) => prev + cur);
|
|
133
|
+
const bigWholeChunk = new Uint8Array(newChunkLength);
|
|
134
|
+
let currentLength = 0;
|
|
135
|
+
for (let i = 0; i < currentChunks.length; i++) {
|
|
136
|
+
bigWholeChunk.set(currentChunks[i], currentLength);
|
|
137
|
+
currentLength += currentChunks[i].length;
|
|
138
|
+
}
|
|
139
|
+
this.currentByte = 0;
|
|
140
|
+
this.maxByte = bigWholeChunk.buffer.byteLength;
|
|
141
|
+
this.bufferView = new DataView(bigWholeChunk.buffer);
|
|
142
|
+
const dataLength = this.readInt32();
|
|
143
|
+
if (totalUncompressedBodySize !== dataLength + 8) {
|
|
144
|
+
throw new parser_error_1.CorruptSaveError(`Possibly corrupt. Indicated size of total save body (${dataLength + 8}) does not match the uncompressed real size of ${totalUncompressedBodySize}.`);
|
|
145
|
+
}
|
|
146
|
+
return {
|
|
147
|
+
concatenatedChunkLength: newChunkLength,
|
|
148
|
+
numChunks: currentChunks.length
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
readLevelsU8() {
|
|
152
|
+
if (!this.header) {
|
|
153
|
+
throw new parser_error_1.ParserError('ParserError', 'Header must be set before objects can be read.');
|
|
154
|
+
}
|
|
155
|
+
if (this.header.saveVersion < 29) {
|
|
156
|
+
throw new parser_error_1.UnsupportedVersionError('Game Version < U6 is not supported.');
|
|
157
|
+
}
|
|
158
|
+
if (this.header.saveHeaderType < 13) {
|
|
159
|
+
throw new parser_error_1.UnsupportedVersionError('Game Version < U8 is not supported in this package version. Consider downgrading to the latest package version supporting it, which is 0.0.34');
|
|
160
|
+
}
|
|
161
|
+
const unk = this.readInt32();
|
|
162
|
+
const cannotBeLevelCountWhatIsIt = this.readInt32();
|
|
163
|
+
const noneString = this.readString();
|
|
164
|
+
const unk2 = this.readInt32();
|
|
165
|
+
const hm1 = this.readInt32();
|
|
166
|
+
const unk3 = this.readInt32();
|
|
167
|
+
const noneString2 = this.readString();
|
|
168
|
+
const grids = {};
|
|
169
|
+
const readLevelsList = (childrenCount) => {
|
|
170
|
+
const hmm = this.readInt32();
|
|
171
|
+
const parentName = this.readString();
|
|
172
|
+
const whatever = this.readBytes(8);
|
|
173
|
+
grids[parentName] = {};
|
|
174
|
+
for (let i = 0; i < childrenCount; i++) {
|
|
175
|
+
const binaryLenIGuess = this.readInt32();
|
|
176
|
+
const levelInstanceName = this.readString();
|
|
177
|
+
const lod = /\_L([0-9]+)\_/.exec(levelInstanceName)[1];
|
|
178
|
+
const arr = grids[parentName][Number(lod)];
|
|
179
|
+
if (!arr) {
|
|
180
|
+
grids[parentName][Number(lod)] = [];
|
|
181
|
+
}
|
|
182
|
+
grids[parentName][Number(lod)].push(levelInstanceName);
|
|
181
183
|
}
|
|
182
|
-
|
|
183
|
-
|
|
184
|
+
};
|
|
185
|
+
readLevelsList(1379);
|
|
186
|
+
readLevelsList(0);
|
|
187
|
+
readLevelsList(972);
|
|
188
|
+
readLevelsList(0);
|
|
189
|
+
readLevelsList(0);
|
|
190
|
+
const unk5 = this.readInt32();
|
|
191
|
+
const levels = [];
|
|
192
|
+
const levelCount = this.readInt32();
|
|
193
|
+
this.onProgressCallback(this.getBufferProgress(), `reading pack of ${levelCount} levels.`);
|
|
194
|
+
for (let i = 0; i < levelCount; i++) {
|
|
195
|
+
let levelSingleName = this.readString();
|
|
196
|
+
levels.push(level_class_1.Level.ReadLevel(this, levelSingleName, this.header.buildVersion));
|
|
197
|
+
}
|
|
198
|
+
level_class_1.Level.ReadCollectablesList(this);
|
|
199
|
+
this.onProgressCallback(this.getBufferProgress(), `finished reading levels pack of ${levelCount}.`);
|
|
200
|
+
if (this.getBufferPosition() < this.getBufferLength()) {
|
|
201
|
+
const appendedLevel = new level_class_1.Level(this.header.mapName);
|
|
202
|
+
levels.push(appendedLevel);
|
|
203
|
+
level_class_1.Level.ReadObjectHeaders(this, appendedLevel.objects, this.onProgressCallback);
|
|
204
|
+
appendedLevel.collectables = level_class_1.Level.ReadCollectablesList(this);
|
|
205
|
+
level_class_1.Level.ReadObjectContents(appendedLevel.name, this, appendedLevel.objects, this.header.buildVersion, this.onProgressCallback);
|
|
206
|
+
level_class_1.Level.ReadCollectablesList(this);
|
|
207
|
+
level_class_1.Level.ReadCollectablesList(this);
|
|
208
|
+
}
|
|
209
|
+
this.onProgressCallback(this.getBufferProgress(), 'finished parsing.');
|
|
210
|
+
return levels;
|
|
211
|
+
}
|
|
212
|
+
async readLevelsAsynchronously(writer) {
|
|
213
|
+
const numSubLevels = this.readInt32();
|
|
214
|
+
this.levels = new Array(numSubLevels + 1);
|
|
215
|
+
await writer.openLevels();
|
|
216
|
+
for (let j = 0; j <= numSubLevels; j++) {
|
|
217
|
+
let levelName = (j === numSubLevels) ? '' + this.header.mapName : this.readString();
|
|
218
|
+
this.onProgressCallback(this.getBufferProgress(), `reading level [${(j + 1)}/${(numSubLevels + 1)}] ${levelName}`);
|
|
219
|
+
this.levels[j] = await asynchronous_level_class_1.AsynchronousLevel.StreamReadLevel(this, levelName, this.header.buildVersion, writer);
|
|
184
220
|
}
|
|
221
|
+
await writer.endLevels();
|
|
222
|
+
return this.levels;
|
|
185
223
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
224
|
+
}
|
|
225
|
+
SaveReader.EPOCH_TICKS = 621355968000000000n;
|
|
226
|
+
exports.SaveReader = SaveReader;
|
|
@@ -1,122 +1,112 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
2
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
3
4
|
};
|
|
4
|
-
(
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.SaveWriter = void 0;
|
|
7
|
+
const pako_1 = __importDefault(require("pako"));
|
|
8
|
+
const alignment_enum_1 = require("../../byte/alignment.enum");
|
|
9
|
+
const byte_writer_class_1 = require("../../byte/byte-writer.class");
|
|
10
|
+
const parser_error_1 = require("../../error/parser.error");
|
|
11
|
+
const level_class_1 = require("./level.class");
|
|
12
|
+
class SaveWriter extends byte_writer_class_1.ByteWriter {
|
|
13
|
+
constructor() {
|
|
14
|
+
super(alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
8
15
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
class SaveWriter extends byte_writer_class_1.ByteWriter {
|
|
22
|
-
constructor() {
|
|
23
|
-
super(alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
16
|
+
static WriteHeader(writer, header) {
|
|
17
|
+
writer.writeInt32(header.saveHeaderType);
|
|
18
|
+
writer.writeInt32(header.saveVersion);
|
|
19
|
+
writer.writeInt32(header.buildVersion);
|
|
20
|
+
writer.writeString(header.mapName);
|
|
21
|
+
writer.writeString(header.mapOptions);
|
|
22
|
+
writer.writeString(header.sessionName);
|
|
23
|
+
writer.writeInt32(header.playDurationSeconds);
|
|
24
|
+
writer.writeInt64(BigInt(header.saveDateTime));
|
|
25
|
+
writer.writeByte(header.sessionVisibility);
|
|
26
|
+
if (header.saveHeaderType >= 7) {
|
|
27
|
+
writer.writeInt32(header.fEditorObjectVersion);
|
|
24
28
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
writer.writeInt32(header.buildVersion);
|
|
29
|
-
writer.writeString(header.mapName);
|
|
30
|
-
writer.writeString(header.mapOptions);
|
|
31
|
-
writer.writeString(header.sessionName);
|
|
32
|
-
writer.writeInt32(header.playDurationSeconds);
|
|
33
|
-
writer.writeInt64(BigInt(header.saveDateTime));
|
|
34
|
-
writer.writeByte(header.sessionVisibility);
|
|
35
|
-
if (header.saveHeaderType >= 7) {
|
|
36
|
-
writer.writeInt32(header.fEditorObjectVersion);
|
|
37
|
-
}
|
|
38
|
-
if (header.saveHeaderType >= 8) {
|
|
39
|
-
if (header.modMetadata) {
|
|
40
|
-
writer.writeString(JSON.stringify(header.modMetadata));
|
|
41
|
-
}
|
|
42
|
-
else {
|
|
43
|
-
writer.writeString(header.rawModMetadataString);
|
|
44
|
-
}
|
|
45
|
-
writer.writeInt32(header.isModdedSave);
|
|
46
|
-
}
|
|
47
|
-
if (header.saveHeaderType >= 10) {
|
|
48
|
-
writer.writeString(header.saveIdentifier);
|
|
49
|
-
}
|
|
50
|
-
if (header.saveVersion >= 21) {
|
|
29
|
+
if (header.saveHeaderType >= 8) {
|
|
30
|
+
if (header.modMetadata) {
|
|
31
|
+
writer.writeString(JSON.stringify(header.modMetadata));
|
|
51
32
|
}
|
|
52
33
|
else {
|
|
53
|
-
|
|
34
|
+
writer.writeString(header.rawModMetadataString);
|
|
54
35
|
}
|
|
36
|
+
writer.writeInt32(header.isModdedSave);
|
|
55
37
|
}
|
|
56
|
-
|
|
57
|
-
writer.
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
writer.writeString(level.name);
|
|
61
|
-
}
|
|
62
|
-
level_class_1.Level.WriteLevel(writer, level, buildVersion);
|
|
63
|
-
}
|
|
64
|
-
level_class_1.Level.SerializeCollectablesList(writer, save.trailingCollectedObjects);
|
|
38
|
+
if (header.saveHeaderType >= 10) {
|
|
39
|
+
writer.writeString(header.saveIdentifier);
|
|
40
|
+
}
|
|
41
|
+
if (header.saveVersion >= 21) {
|
|
65
42
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
const chunkSummary = [];
|
|
76
|
-
while (handledByte < saveBody.byteLength) {
|
|
77
|
-
const uncompressedContentSize = Math.min(compressionInfo.maxChunkContentSize, saveBody.byteLength - handledByte);
|
|
78
|
-
const uncompressedChunk = saveBody.buffer.slice(handledByte, handledByte + uncompressedContentSize);
|
|
79
|
-
let compressedChunk = new Uint8Array(0);
|
|
80
|
-
try {
|
|
81
|
-
compressedChunk = pako_1.default.deflate(uncompressedChunk);
|
|
82
|
-
}
|
|
83
|
-
catch (err) {
|
|
84
|
-
throw new parser_error_1.CompressionLibraryError("Could not compress save data. " + err);
|
|
85
|
-
}
|
|
86
|
-
const chunk = new Uint8Array(compressionInfo.chunkHeaderSize + compressedChunk.byteLength);
|
|
87
|
-
chunk.set(compressedChunk, compressionInfo.chunkHeaderSize);
|
|
88
|
-
const view = new DataView(chunk.buffer);
|
|
89
|
-
view.setInt32(0, compressionInfo.packageFileTag, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
90
|
-
view.setInt32(4, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
91
|
-
view.setInt32(8, compressionInfo.maxChunkContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
92
|
-
view.setInt32(12, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
93
|
-
view.setInt32(16, compressedChunk.byteLength, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
94
|
-
view.setInt32(20, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
95
|
-
view.setInt32(24, uncompressedContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
96
|
-
view.setInt32(28, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
97
|
-
view.setInt32(32, compressedChunk.byteLength, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
98
|
-
view.setInt32(36, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
99
|
-
view.setInt32(40, uncompressedContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
100
|
-
view.setInt32(44, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
101
|
-
onChunk(chunk);
|
|
102
|
-
chunkSummary.push({
|
|
103
|
-
uncompressedSize: uncompressedContentSize + compressionInfo.chunkHeaderSize,
|
|
104
|
-
compressedSize: compressedChunk.byteLength + compressionInfo.chunkHeaderSize
|
|
105
|
-
});
|
|
106
|
-
handledByte += uncompressedContentSize;
|
|
43
|
+
else {
|
|
44
|
+
throw new parser_error_1.UnsupportedVersionError("The save version is too old to be supported currently.");
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
static WriteLevels(writer, save, buildVersion) {
|
|
48
|
+
writer.writeInt32(save.levels.length - 1);
|
|
49
|
+
for (const level of save.levels) {
|
|
50
|
+
if (level.name !== save.header.mapName) {
|
|
51
|
+
writer.writeString(level.name);
|
|
107
52
|
}
|
|
108
|
-
|
|
53
|
+
level_class_1.Level.WriteLevel(writer, level, buildVersion);
|
|
109
54
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
55
|
+
level_class_1.Level.SerializeCollectablesList(writer, save.trailingCollectedObjects);
|
|
56
|
+
}
|
|
57
|
+
static GenerateCompressedChunksFromData(bufferArray, compressionInfo, onBinaryBeforeCompressing, onChunk, alignment = alignment_enum_1.Alignment.LITTLE_ENDIAN) {
|
|
58
|
+
const errors = [];
|
|
59
|
+
const totalUncompressedSize = bufferArray.byteLength;
|
|
60
|
+
const saveBody = new Uint8Array(bufferArray.byteLength + 4);
|
|
61
|
+
saveBody.set(new Uint8Array(bufferArray), 4);
|
|
62
|
+
const miniView = new DataView(saveBody.buffer);
|
|
63
|
+
miniView.setInt32(0, totalUncompressedSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
64
|
+
onBinaryBeforeCompressing(saveBody.buffer);
|
|
65
|
+
let handledByte = 0;
|
|
66
|
+
const chunkSummary = [];
|
|
67
|
+
while (handledByte < saveBody.byteLength) {
|
|
68
|
+
const uncompressedContentSize = Math.min(compressionInfo.maxChunkContentSize, saveBody.byteLength - handledByte);
|
|
69
|
+
const uncompressedChunk = saveBody.buffer.slice(handledByte, handledByte + uncompressedContentSize);
|
|
70
|
+
let compressedChunk = new Uint8Array(0);
|
|
71
|
+
try {
|
|
72
|
+
compressedChunk = pako_1.default.deflate(uncompressedChunk);
|
|
113
73
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
-
|
|
74
|
+
catch (err) {
|
|
75
|
+
throw new parser_error_1.CompressionLibraryError("Could not compress save data. " + err);
|
|
76
|
+
}
|
|
77
|
+
const chunk = new Uint8Array(compressionInfo.chunkHeaderSize + compressedChunk.byteLength);
|
|
78
|
+
chunk.set(compressedChunk, compressionInfo.chunkHeaderSize);
|
|
79
|
+
const view = new DataView(chunk.buffer);
|
|
80
|
+
view.setInt32(0, compressionInfo.packageFileTag, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
81
|
+
view.setInt32(4, 572662306, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
82
|
+
view.setInt32(8, compressionInfo.maxChunkContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
83
|
+
view.setInt32(12, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
84
|
+
view.setInt32(16, compressedChunk.byteLength, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
85
|
+
view.setInt32(20, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
86
|
+
view.setInt32(24, uncompressedContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
87
|
+
view.setInt32(28, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
88
|
+
view.setInt32(32, compressedChunk.byteLength, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
89
|
+
view.setInt32(36, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
90
|
+
view.setInt32(40, uncompressedContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
91
|
+
view.setInt32(44, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
92
|
+
onChunk(chunk);
|
|
93
|
+
chunkSummary.push({
|
|
94
|
+
uncompressedSize: uncompressedContentSize + compressionInfo.chunkHeaderSize,
|
|
95
|
+
compressedSize: compressedChunk.byteLength + compressionInfo.chunkHeaderSize
|
|
96
|
+
});
|
|
97
|
+
handledByte += uncompressedContentSize;
|
|
98
|
+
}
|
|
99
|
+
return chunkSummary;
|
|
100
|
+
}
|
|
101
|
+
generateChunks(compressionInfo, posAfterHeader, onBinaryBeforeCompressing, onHeader, onChunk) {
|
|
102
|
+
if (posAfterHeader <= 0) {
|
|
103
|
+
throw new parser_error_1.ParserError('ParserError', 'Seems like this buffer has no header. Please write the header first before you can generate chunks.');
|
|
119
104
|
}
|
|
105
|
+
const header = new Uint8Array(this.bufferArray.slice(0, posAfterHeader));
|
|
106
|
+
onHeader(header);
|
|
107
|
+
this.bufferArray = this.bufferArray.slice(posAfterHeader);
|
|
108
|
+
const chunkSummary = SaveWriter.GenerateCompressedChunksFromData(this.bufferArray, compressionInfo, onBinaryBeforeCompressing, onChunk, this.alignment);
|
|
109
|
+
return chunkSummary;
|
|
120
110
|
}
|
|
121
|
-
|
|
122
|
-
|
|
111
|
+
}
|
|
112
|
+
exports.SaveWriter = SaveWriter;
|
|
@@ -1,12 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
var v = factory(require, exports);
|
|
4
|
-
if (v !== undefined) module.exports = v;
|
|
5
|
-
}
|
|
6
|
-
else if (typeof define === "function" && define.amd) {
|
|
7
|
-
define(["require", "exports"], factory);
|
|
8
|
-
}
|
|
9
|
-
})(function (require, exports) {
|
|
10
|
-
"use strict";
|
|
11
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
-
});
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|