@etothepii/satisfactory-file-parser 0.4.16 → 0.4.19
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/LICENSE +21 -0
- package/README.md +23 -9
- package/build/parser/parser.d.ts +0 -1
- package/build/parser/parser.js +0 -17
- package/build/parser/satisfactory/edit/edit-constants.d.ts +4 -0
- package/build/parser/satisfactory/edit/edit-constants.js +4 -0
- package/build/parser/satisfactory/objects/DataFields.js +1 -2
- package/build/parser/satisfactory/objects/property/generic/StructProperty.d.ts +2 -5
- package/build/parser/satisfactory/objects/property/generic/StructProperty.js +23 -14
- package/build/parser/satisfactory/save/level.class.d.ts +0 -3
- package/build/parser/satisfactory/save/level.class.js +1 -55
- package/build/parser/satisfactory/save/satisfactory-save.d.ts +0 -2
- package/build/parser/satisfactory/save/satisfactory-save.js +0 -1
- package/build/parser/satisfactory/save/save-reader.d.ts +0 -6
- package/build/parser/satisfactory/save/save-reader.js +3 -35
- package/build/parser/stream/reworked/readable-stream-parser.js +6 -0
- package/package.json +4 -4
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 etothepii
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -86,14 +86,19 @@ fs.writeFileSync('./MyModifiedSave.sav', Buffer.concat([fileHeader!, ...bodyChun
|
|
|
86
86
|
```
|
|
87
87
|
|
|
88
88
|
|
|
89
|
-
##
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
import * as fs from 'fs';
|
|
93
|
-
import { Parser } from "@etothepii/satisfactory-file-parser";
|
|
89
|
+
## Inspecting Save Objects
|
|
90
|
+
Once you have the complete parsing done, the JSON is a `SatisfactorySave` object.
|
|
91
|
+
You can for example loop through players and print their cached names and positions.
|
|
94
92
|
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
```js
|
|
94
|
+
import { isSaveEntity, SatisfactorySave, SaveEntity, StrProperty } from '@etothepii/satisfactory-file-parser';
|
|
95
|
+
|
|
96
|
+
const objects = save.levels.flatMap(level => level.objects);
|
|
97
|
+
const players = objects.filter(obj => isSaveEntity(obj) && obj.typePath === '/Game/FactoryGame/Character/Player/Char_Player.Char_Player_C') as SaveEntity[];
|
|
98
|
+
for (const player of players) {
|
|
99
|
+
const name = (player.properties.mCachedPlayerName as StrProperty).value;
|
|
100
|
+
console.log(name, player.transform.translation);
|
|
101
|
+
}
|
|
97
102
|
```
|
|
98
103
|
|
|
99
104
|
|
|
@@ -138,6 +143,16 @@ So far this was just a private hobby project. But i figure some people actually
|
|
|
138
143
|
If you find a bug or have feedback about the parser, you can just hit me up on the satisfactory discord `etothepii`.
|
|
139
144
|
|
|
140
145
|
# Changelog
|
|
146
|
+
### [0.4.18] (2024-10-06)
|
|
147
|
+
#### Migrated repo to public github
|
|
148
|
+
|
|
149
|
+
### [0.4.17] (2024-10-05)
|
|
150
|
+
#### updated README
|
|
151
|
+
#### bugfix
|
|
152
|
+
* `ClientIdentityInfo` field names and structure got changed, since the meaning is now more clear.
|
|
153
|
+
* removed trailing object list from satisfactory save object.
|
|
154
|
+
* deleted entities references get serialized again, just based on collectables list.
|
|
155
|
+
|
|
141
156
|
### [0.4.16] (2024-10-03)
|
|
142
157
|
#### bugfix
|
|
143
158
|
* exporting `isSaveEntity` and `isSaveComponent` again.
|
|
@@ -149,8 +164,7 @@ If you find a bug or have feedback about the parser, you can just hit me up on t
|
|
|
149
164
|
### [0.4.14] (2024-10-02)
|
|
150
165
|
#### updated README
|
|
151
166
|
* updated the code examples with more context
|
|
152
|
-
|
|
153
|
-
### Internal renamings (won't affect you if you stick to the code examples)
|
|
167
|
+
#### Internal renamings (won't affect you if you stick to the code examples)
|
|
154
168
|
* improved the interface for abstract parser classes
|
|
155
169
|
* extended some error log messages
|
|
156
170
|
* added an additional check when parsing struct property InventoryItem, since ported saves often have a few more bytes.
|
package/build/parser/parser.d.ts
CHANGED
|
@@ -3,7 +3,6 @@ import { Blueprint } from "./satisfactory/blueprint/blueprint.types";
|
|
|
3
3
|
import { SatisfactorySave } from "./satisfactory/save/satisfactory-save";
|
|
4
4
|
export declare class Parser {
|
|
5
5
|
static WriteSave(save: SatisfactorySave, onBinaryBeforeCompressing: (buffer: ArrayBuffer) => void, onHeader: (header: Uint8Array) => void, onChunk: (chunk: Uint8Array) => void): ChunkSummary[];
|
|
6
|
-
static ParseSaveFile(name: string, file: Buffer, onDecompressedSaveBody?: (buffer: ArrayBuffer) => void, onProgress?: (progress: number, message?: string) => void): SatisfactorySave;
|
|
7
6
|
static WriteBlueprintFiles(blueprint: Blueprint, onMainFileBinaryBeforeCompressing?: (binary: ArrayBuffer) => void, onMainFileHeader?: (header: Uint8Array) => void, onMainFileChunk?: (chunk: Uint8Array) => void): {
|
|
8
7
|
mainFileChunkSummary: ChunkSummary[];
|
|
9
8
|
configFileBinary: ArrayBuffer;
|
package/build/parser/parser.js
CHANGED
|
@@ -3,8 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.Parser = void 0;
|
|
4
4
|
const blueprint_reader_1 = require("./satisfactory/blueprint/blueprint-reader");
|
|
5
5
|
const blueprint_writer_1 = require("./satisfactory/blueprint/blueprint-writer");
|
|
6
|
-
const satisfactory_save_1 = require("./satisfactory/save/satisfactory-save");
|
|
7
|
-
const save_reader_1 = require("./satisfactory/save/save-reader");
|
|
8
6
|
const save_writer_1 = require("./satisfactory/save/save-writer");
|
|
9
7
|
class Parser {
|
|
10
8
|
static WriteSave(save, onBinaryBeforeCompressing, onHeader, onChunk) {
|
|
@@ -18,21 +16,6 @@ class Parser {
|
|
|
18
16
|
const chunkSummary = writer.generateChunks(save.compressionInfo, posAfterHeader, onBinaryBeforeCompressing, onHeader, onChunk);
|
|
19
17
|
return chunkSummary;
|
|
20
18
|
}
|
|
21
|
-
static ParseSaveFile(name, file, onDecompressedSaveBody = () => { }, onProgress = () => { }) {
|
|
22
|
-
const reader = new save_reader_1.SaveReader(new Uint8Array(file).buffer, onProgress);
|
|
23
|
-
const header = reader.readHeader();
|
|
24
|
-
const save = new satisfactory_save_1.SatisfactorySave(name, header);
|
|
25
|
-
const inflateResult = reader.inflateChunks();
|
|
26
|
-
onDecompressedSaveBody(reader.getBuffer());
|
|
27
|
-
const gridHash = reader.readSaveBodyHash();
|
|
28
|
-
save.gridHash = gridHash;
|
|
29
|
-
const grids = reader.readGrids();
|
|
30
|
-
save.grids = grids;
|
|
31
|
-
save.levels = reader.readLevels();
|
|
32
|
-
save.compressionInfo = reader.compressionInfo;
|
|
33
|
-
save.trailingCollectedObjects = reader.trailingCollectedObjects;
|
|
34
|
-
return save;
|
|
35
|
-
}
|
|
36
19
|
static WriteBlueprintFiles(blueprint, onMainFileBinaryBeforeCompressing = () => { }, onMainFileHeader = () => { }, onMainFileChunk = () => { }) {
|
|
37
20
|
const blueprintWriter = new blueprint_writer_1.BlueprintWriter();
|
|
38
21
|
blueprint_writer_1.BlueprintWriter.SerializeHeader(blueprintWriter, blueprint.header);
|
|
@@ -1807,6 +1807,10 @@ export declare const EDIT: {
|
|
|
1807
1807
|
readonly TYPE_PATH: "/Game/FactoryGame/Buildable/Factory/ProjectAssembly/BP_ProjectAssembly.BP_ProjectAssembly_C";
|
|
1808
1808
|
readonly CLASS_NAME: "BP_ProjectAssembly_C";
|
|
1809
1809
|
};
|
|
1810
|
+
readonly SPACE_ELEVATOR: {
|
|
1811
|
+
readonly TYPE_PATH: "/Game/FactoryGame/Buildable/Factory/SpaceElevator/Build_SpaceElevator.Build_SpaceElevator_C";
|
|
1812
|
+
readonly CLASS_NAME: "Build_SpaceElevator_C";
|
|
1813
|
+
};
|
|
1810
1814
|
readonly EXTRACTOR: {
|
|
1811
1815
|
readonly MINER_MK_1: {
|
|
1812
1816
|
readonly TYPE_PATH: "/Game/FactoryGame/Buildable/Factory/MinerMK1/Build_MinerMk1.Build_MinerMk1_C";
|
|
@@ -1813,6 +1813,10 @@ exports.EDIT = {
|
|
|
1813
1813
|
TYPE_PATH: '/Game/FactoryGame/Buildable/Factory/ProjectAssembly/BP_ProjectAssembly.BP_ProjectAssembly_C',
|
|
1814
1814
|
CLASS_NAME: 'BP_ProjectAssembly_C'
|
|
1815
1815
|
},
|
|
1816
|
+
SPACE_ELEVATOR: {
|
|
1817
|
+
TYPE_PATH: '/Game/FactoryGame/Buildable/Factory/SpaceElevator/Build_SpaceElevator.Build_SpaceElevator_C',
|
|
1818
|
+
CLASS_NAME: 'Build_SpaceElevator_C'
|
|
1819
|
+
},
|
|
1816
1820
|
EXTRACTOR: {
|
|
1817
1821
|
MINER_MK_1: {
|
|
1818
1822
|
TYPE_PATH: '/Game/FactoryGame/Buildable/Factory/MinerMK1/Build_MinerMk1.Build_MinerMk1_C'
|
|
@@ -50,8 +50,7 @@ class DataFields {
|
|
|
50
50
|
reader.readInt32();
|
|
51
51
|
const remainingLen = length - (reader.getBufferPosition() - start);
|
|
52
52
|
obj.specialProperties = DataFields.ParseAdditionalSpecialProperties(reader, typePath, remainingLen);
|
|
53
|
-
const
|
|
54
|
-
const remainingSize = length - (end - start);
|
|
53
|
+
const remainingSize = length - (reader.getBufferPosition() - start);
|
|
55
54
|
if (remainingSize > 0) {
|
|
56
55
|
obj.trailingData = Array.from(reader.readBytes(remainingSize));
|
|
57
56
|
}
|
|
@@ -39,11 +39,8 @@ export type DynamicStructPropertyValue = {
|
|
|
39
39
|
properties: PropertiesMap;
|
|
40
40
|
};
|
|
41
41
|
export type ClientIdentityInfo = {
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
clientUnk2: number;
|
|
45
|
-
clientHashSize: number;
|
|
46
|
-
someClientHash: number[];
|
|
42
|
+
offlineId: string;
|
|
43
|
+
accountIds: Record<number, number[]>;
|
|
47
44
|
};
|
|
48
45
|
export type GENERIC_STRUCT_PROPERTY_VALUE = BasicMultipleStructPropertyValue | BasicStructPropertyValue | BoxStructPropertyValue | RailroadTrackPositionStructPropertyValue | InventoryItemStructPropertyValue | FICFrameRangeStructPropertyValue | ClientIdentityInfo | DynamicStructPropertyValue | col4 | vec3 | vec4 | string;
|
|
49
46
|
export declare class StructProperty extends AbstractBaseProperty {
|
|
@@ -35,7 +35,7 @@ class StructProperty extends BasicProperty_1.AbstractBaseProperty {
|
|
|
35
35
|
struct.value = StructProperty.ParseValue(reader, struct.subtype, size);
|
|
36
36
|
const readBytes = reader.getBufferPosition() - before;
|
|
37
37
|
if (readBytes !== size) {
|
|
38
|
-
if (size - readBytes === 4
|
|
38
|
+
if (size - readBytes === 4) {
|
|
39
39
|
reader.skipBytes(size - readBytes);
|
|
40
40
|
}
|
|
41
41
|
else {
|
|
@@ -64,7 +64,7 @@ class StructProperty extends BasicProperty_1.AbstractBaseProperty {
|
|
|
64
64
|
value = (size === 16) ? util_types_1.vec4.ParseF(reader) : util_types_1.vec4.Parse(reader);
|
|
65
65
|
break;
|
|
66
66
|
case 'Box':
|
|
67
|
-
value = (size === 25) ? {
|
|
67
|
+
value = ((size === 25) ? {
|
|
68
68
|
min: util_types_1.vec3.ParseF(reader),
|
|
69
69
|
max: util_types_1.vec3.ParseF(reader),
|
|
70
70
|
isValid: reader.readByte() >= 1
|
|
@@ -72,7 +72,7 @@ class StructProperty extends BasicProperty_1.AbstractBaseProperty {
|
|
|
72
72
|
min: util_types_1.vec3.Parse(reader),
|
|
73
73
|
max: util_types_1.vec3.Parse(reader),
|
|
74
74
|
isValid: reader.readByte() >= 1
|
|
75
|
-
};
|
|
75
|
+
});
|
|
76
76
|
break;
|
|
77
77
|
case 'RailroadTrackPosition':
|
|
78
78
|
value = {
|
|
@@ -89,12 +89,19 @@ class StructProperty extends BasicProperty_1.AbstractBaseProperty {
|
|
|
89
89
|
value = reader.readString();
|
|
90
90
|
break;
|
|
91
91
|
case 'ClientIdentityInfo':
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
92
|
+
const offlineId = reader.readString();
|
|
93
|
+
const numAccountIds = reader.readInt32();
|
|
94
|
+
const accountIds = {};
|
|
95
|
+
for (let i = 0; i < numAccountIds; i++) {
|
|
96
|
+
const platformFlagMaybe = reader.readByte();
|
|
97
|
+
const idSize = reader.readInt32();
|
|
98
|
+
const accountId = Array.from(reader.readBytes(idSize));
|
|
99
|
+
accountIds[platformFlagMaybe] = accountId;
|
|
100
|
+
}
|
|
101
|
+
value = {
|
|
102
|
+
offlineId,
|
|
103
|
+
accountIds
|
|
104
|
+
};
|
|
98
105
|
break;
|
|
99
106
|
case 'InventoryItem':
|
|
100
107
|
const before = reader.getBufferPosition();
|
|
@@ -209,11 +216,13 @@ class StructProperty extends BasicProperty_1.AbstractBaseProperty {
|
|
|
209
216
|
break;
|
|
210
217
|
case 'ClientIdentityInfo':
|
|
211
218
|
value = value;
|
|
212
|
-
writer.writeString(value.
|
|
213
|
-
writer.writeInt32(value.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
219
|
+
writer.writeString(value.offlineId);
|
|
220
|
+
writer.writeInt32(Object.values(value.accountIds).length);
|
|
221
|
+
for (const [platformFlagMaybe, accountId] of Object.entries(value.accountIds)) {
|
|
222
|
+
writer.writeByte(Number(platformFlagMaybe));
|
|
223
|
+
writer.writeInt32(accountId.length);
|
|
224
|
+
writer.writeBytesArray(accountId);
|
|
225
|
+
}
|
|
217
226
|
break;
|
|
218
227
|
case 'InventoryItem':
|
|
219
228
|
value = value;
|
|
@@ -4,7 +4,6 @@ import { SaveComponent } from "../objects/SaveComponent";
|
|
|
4
4
|
import { SaveEntity } from "../objects/SaveEntity";
|
|
5
5
|
import { SaveObject } from "../objects/SaveObject";
|
|
6
6
|
import { ObjectReference } from "../objects/values/ObjectReference";
|
|
7
|
-
import { SaveReader } from "./save-reader";
|
|
8
7
|
export declare class Level {
|
|
9
8
|
name: string;
|
|
10
9
|
objects: (SaveEntity | SaveComponent)[];
|
|
@@ -14,8 +13,6 @@ export declare class Level {
|
|
|
14
13
|
static WriteLevel(writer: ByteWriter, level: Level, buildVersion: number): void;
|
|
15
14
|
static SerializeObjectContents(writer: ByteWriter, objects: (SaveEntity | SaveComponent)[], buildVersion: number, levelName: string): void;
|
|
16
15
|
static ReadObjectHeaders(reader: BinaryReadable, objectsList: SaveObject[], onProgressCallback: (progress: number, msg?: string) => void): void;
|
|
17
|
-
static ReadObjectContents(levelName: string, reader: BinaryReadable, objectsList: SaveObject[], buildVersion: number, onProgressCallback: (progress: number, msg?: string) => void): Promise<void>;
|
|
18
|
-
static ReadLevel(reader: SaveReader, levelName: string, buildVersion: number): Level;
|
|
19
16
|
static SerializeCollectablesList(writer: ByteWriter, collectables: ObjectReference[]): void;
|
|
20
17
|
static ReadCollectablesList(reader: BinaryReadable): ObjectReference[];
|
|
21
18
|
}
|
|
@@ -33,6 +33,7 @@ class Level {
|
|
|
33
33
|
writer.writeInt32(0);
|
|
34
34
|
writer.writeInt32(0);
|
|
35
35
|
Level.SerializeObjectHeaders(writer, level.objects);
|
|
36
|
+
Level.SerializeCollectablesList(writer, level.collectables);
|
|
36
37
|
writer.writeBinarySizeFromPosition(lenIndicatorHeaderAndDestroyedEntitiesSize, lenIndicatorHeaderAndDestroyedEntitiesSize + 8);
|
|
37
38
|
Level.SerializeObjectContents(writer, level.objects, buildVersion, level.name);
|
|
38
39
|
Level.SerializeCollectablesList(writer, level.collectables);
|
|
@@ -81,61 +82,6 @@ class Level {
|
|
|
81
82
|
}
|
|
82
83
|
}
|
|
83
84
|
}
|
|
84
|
-
static async ReadObjectContents(levelName, reader, objectsList, buildVersion, onProgressCallback) {
|
|
85
|
-
const countEntities = reader.readInt32();
|
|
86
|
-
if (countEntities !== objectsList.length) {
|
|
87
|
-
throw new Error(`possibly corrupt. entity content count ${countEntities} does not equal object count of ${objectsList.length}`);
|
|
88
|
-
}
|
|
89
|
-
for (let i = 0; i < countEntities; i++) {
|
|
90
|
-
if (countEntities >= 10000 && i % 10000 === 0) {
|
|
91
|
-
onProgressCallback(reader.getBufferProgress(), `read object count [${(i + 1)}/${(countEntities + 1)}] in level ${levelName}`);
|
|
92
|
-
}
|
|
93
|
-
const obj = objectsList[i];
|
|
94
|
-
obj.objectVersion = reader.readInt32();
|
|
95
|
-
obj.unknownType2 = reader.readInt32();
|
|
96
|
-
const binarySize = reader.readInt32();
|
|
97
|
-
const before = reader.getBufferPosition();
|
|
98
|
-
if ((0, SaveEntity_1.isSaveEntity)(obj)) {
|
|
99
|
-
SaveEntity_1.SaveEntity.ParseData(obj, binarySize, reader, buildVersion, obj.typePath);
|
|
100
|
-
}
|
|
101
|
-
else if ((0, SaveComponent_1.isSaveComponent)(obj)) {
|
|
102
|
-
SaveComponent_1.SaveComponent.ParseData(obj, binarySize, reader, buildVersion, obj.typePath);
|
|
103
|
-
}
|
|
104
|
-
const after = reader.getBufferPosition();
|
|
105
|
-
if (after - before !== binarySize) {
|
|
106
|
-
console.warn('entity may be corrupt', obj.instanceName);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
static ReadLevel(reader, levelName, buildVersion) {
|
|
111
|
-
const level = new Level(levelName);
|
|
112
|
-
const headersBinLen = reader.readInt32();
|
|
113
|
-
const unk = reader.readInt32();
|
|
114
|
-
const posBeforeHeaders = reader.getBufferPosition();
|
|
115
|
-
Level.ReadObjectHeaders(reader, level.objects, reader.onProgressCallback);
|
|
116
|
-
let remainingSize = headersBinLen - (reader.getBufferPosition() - posBeforeHeaders);
|
|
117
|
-
if (remainingSize > 0) {
|
|
118
|
-
const doubledCollectablesIgnored = Level.ReadCollectablesList(reader);
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
}
|
|
122
|
-
remainingSize = headersBinLen - (reader.getBufferPosition() - posBeforeHeaders);
|
|
123
|
-
if (remainingSize !== 0) {
|
|
124
|
-
console.warn('remaining size not 0. Save may be corrupt.', remainingSize, levelName);
|
|
125
|
-
}
|
|
126
|
-
const objectContentsBinLen = reader.readInt32();
|
|
127
|
-
const unk2 = reader.readInt32();
|
|
128
|
-
reader.onProgressCallback(reader.getBufferProgress());
|
|
129
|
-
const posBeforeContents = reader.getBufferPosition();
|
|
130
|
-
Level.ReadObjectContents(levelName, reader, level.objects, buildVersion, reader.onProgressCallback);
|
|
131
|
-
const posAfterContents = reader.getBufferPosition();
|
|
132
|
-
if (posAfterContents - posBeforeContents !== objectContentsBinLen) {
|
|
133
|
-
console.warn('save seems corrupt.', this.name);
|
|
134
|
-
}
|
|
135
|
-
reader.onProgressCallback(reader.getBufferProgress());
|
|
136
|
-
level.collectables = Level.ReadCollectablesList(reader);
|
|
137
|
-
return level;
|
|
138
|
-
}
|
|
139
85
|
static SerializeCollectablesList(writer, collectables) {
|
|
140
86
|
writer.writeInt32(collectables.length);
|
|
141
87
|
for (const collectable of collectables) {
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { ChunkCompressionInfo } from "../../file.types";
|
|
2
|
-
import { ObjectReference } from "../objects/values/ObjectReference";
|
|
3
2
|
import { Level } from "./level.class";
|
|
4
3
|
import { ByteArray4, Grids } from "./save-reader";
|
|
5
4
|
import { SatisfactorySaveHeader } from "./save.types";
|
|
@@ -9,7 +8,6 @@ export declare class SatisfactorySave {
|
|
|
9
8
|
gridHash: ByteArray4;
|
|
10
9
|
grids: Grids;
|
|
11
10
|
levels: Level[];
|
|
12
|
-
trailingCollectedObjects: ObjectReference[];
|
|
13
11
|
compressionInfo?: ChunkCompressionInfo;
|
|
14
12
|
constructor(name: string, header: SatisfactorySaveHeader);
|
|
15
13
|
}
|
|
@@ -1,9 +1,6 @@
|
|
|
1
1
|
import { ByteReader } from "../../byte/byte-reader.class";
|
|
2
2
|
import { ChunkCompressionInfo } from "../../file.types";
|
|
3
|
-
import { ObjectReference } from "../objects/values/ObjectReference";
|
|
4
|
-
import { Level } from "./level.class";
|
|
5
3
|
import { RoughSaveVersion, SatisfactorySaveHeader } from "./save.types";
|
|
6
|
-
export type ReadMode = 'stream' | 'whole';
|
|
7
4
|
export declare const DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = 49;
|
|
8
5
|
export type ByteArray4 = [number, number, number, number];
|
|
9
6
|
export type Grids = {
|
|
@@ -19,8 +16,6 @@ export type Grids = {
|
|
|
19
16
|
export declare class SaveReader extends ByteReader {
|
|
20
17
|
onProgressCallback: (progress: number, msg?: string) => void;
|
|
21
18
|
header: SatisfactorySaveHeader | undefined;
|
|
22
|
-
levels: Level[];
|
|
23
|
-
trailingCollectedObjects: ObjectReference[];
|
|
24
19
|
compressionInfo: ChunkCompressionInfo;
|
|
25
20
|
static readonly EPOCH_TICKS = 621355968000000000n;
|
|
26
21
|
constructor(fileBuffer: ArrayBuffer, onProgressCallback?: (progress: number, msg?: string) => void);
|
|
@@ -32,5 +27,4 @@ export declare class SaveReader extends ByteReader {
|
|
|
32
27
|
};
|
|
33
28
|
readSaveBodyHash: () => ByteArray4;
|
|
34
29
|
readGrids: () => Grids;
|
|
35
|
-
readLevels(): Level[];
|
|
36
30
|
}
|
|
@@ -9,24 +9,21 @@ const alignment_enum_1 = require("../../byte/alignment.enum");
|
|
|
9
9
|
const byte_reader_class_1 = require("../../byte/byte-reader.class");
|
|
10
10
|
const parser_error_1 = require("../../error/parser.error");
|
|
11
11
|
const MD5Hash_1 = require("../objects/ue/MD5Hash");
|
|
12
|
-
const level_class_1 = require("./level.class");
|
|
13
12
|
exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = 49;
|
|
14
13
|
class SaveReader extends byte_reader_class_1.ByteReader {
|
|
15
14
|
constructor(fileBuffer, onProgressCallback = () => { }) {
|
|
16
15
|
super(fileBuffer, alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
17
16
|
this.onProgressCallback = onProgressCallback;
|
|
18
|
-
this.levels = [];
|
|
19
|
-
this.trailingCollectedObjects = [];
|
|
20
17
|
this.compressionInfo = {
|
|
21
18
|
packageFileTag: 0,
|
|
22
19
|
maxUncompressedChunkContentSize: 0,
|
|
23
20
|
chunkHeaderSize: exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
|
|
24
21
|
};
|
|
25
22
|
this.readSaveBodyHash = () => {
|
|
26
|
-
|
|
23
|
+
this.readInt32();
|
|
27
24
|
const cannotBeLevelCountWhatIsIt = this.readInt32();
|
|
28
25
|
const noneString = this.readString();
|
|
29
|
-
|
|
26
|
+
this.readInt32();
|
|
30
27
|
const gridValidationHash = Array.from(this.readBytes(4));
|
|
31
28
|
const unk3 = this.readInt32();
|
|
32
29
|
const noneString2 = this.readString();
|
|
@@ -51,7 +48,7 @@ class SaveReader extends byte_reader_class_1.ByteReader {
|
|
|
51
48
|
readGrid(758);
|
|
52
49
|
readGrid(0);
|
|
53
50
|
readGrid(0);
|
|
54
|
-
|
|
51
|
+
this.readInt32();
|
|
55
52
|
return grids;
|
|
56
53
|
};
|
|
57
54
|
}
|
|
@@ -163,35 +160,6 @@ class SaveReader extends byte_reader_class_1.ByteReader {
|
|
|
163
160
|
numChunks: currentChunks.length
|
|
164
161
|
};
|
|
165
162
|
}
|
|
166
|
-
readLevels() {
|
|
167
|
-
if (!this.header) {
|
|
168
|
-
throw new parser_error_1.ParserError('ParserError', 'Header must be set before objects can be read.');
|
|
169
|
-
}
|
|
170
|
-
const roughSaveVersion = SaveReader.getRoughSaveVersion(this.header.saveVersion, this.header.saveHeaderType);
|
|
171
|
-
if (roughSaveVersion === '<U6') {
|
|
172
|
-
throw new parser_error_1.UnsupportedVersionError('Game Version < U6 is not supported.');
|
|
173
|
-
}
|
|
174
|
-
else if (roughSaveVersion === 'U6/U7') {
|
|
175
|
-
throw new parser_error_1.UnsupportedVersionError('Game Version U6/U7 is not supported in this package version. Consider downgrading to the latest package version supporting it, which is 0.0.34');
|
|
176
|
-
}
|
|
177
|
-
else if (roughSaveVersion === 'U8') {
|
|
178
|
-
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.3.7');
|
|
179
|
-
}
|
|
180
|
-
const levels = [];
|
|
181
|
-
const levelCount = this.readInt32();
|
|
182
|
-
this.onProgressCallback(this.getBufferProgress(), `reading pack of ${levelCount} levels.`);
|
|
183
|
-
for (let i = 0; i < levelCount; i++) {
|
|
184
|
-
let levelSingleName = this.readString();
|
|
185
|
-
if (i % 500 === 0) {
|
|
186
|
-
this.onProgressCallback(this.getBufferProgress(), `reading level [${(i + 1)}/${(levelCount + 1)}] ${levelSingleName}`);
|
|
187
|
-
}
|
|
188
|
-
levels.push(level_class_1.Level.ReadLevel(this, levelSingleName, this.header.buildVersion));
|
|
189
|
-
}
|
|
190
|
-
levels.push(level_class_1.Level.ReadLevel(this, this.header.mapName, this.header.buildVersion));
|
|
191
|
-
const trailingStuffToIgnore = level_class_1.Level.ReadCollectablesList(this);
|
|
192
|
-
this.onProgressCallback(this.getBufferProgress(), 'finished parsing.');
|
|
193
|
-
return levels;
|
|
194
|
-
}
|
|
195
163
|
}
|
|
196
164
|
exports.SaveReader = SaveReader;
|
|
197
165
|
SaveReader.EPOCH_TICKS = 621355968000000000n;
|
|
@@ -72,6 +72,12 @@ class ReadableStreamParser {
|
|
|
72
72
|
const objectCountToRead = Math.min(countObjectHeaders - totalReadObjectsInLevel, batchingSizeOfObjects);
|
|
73
73
|
const objects = _a.ReadNObjectHeaders(reader, objectCountToRead);
|
|
74
74
|
afterHeadersOfBatch = reader.getBufferPosition();
|
|
75
|
+
if (countObjectHeaders === totalReadObjectsInLevel + objects.length) {
|
|
76
|
+
const bytesLeft = afterAllHeaders - reader.getBufferPosition();
|
|
77
|
+
if (bytesLeft > 0) {
|
|
78
|
+
_a.ReadDestroyedEntityReferences(reader);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
75
81
|
if (totalReadObjectsInLevel === 0) {
|
|
76
82
|
reader.skipBytes(afterAllHeaders - reader.getBufferPosition());
|
|
77
83
|
const objectContentsBinLen = reader.readInt32();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etothepii/satisfactory-file-parser",
|
|
3
3
|
"author": "etothepii",
|
|
4
|
-
"version": "0.4.
|
|
4
|
+
"version": "0.4.19",
|
|
5
5
|
"description": "A file parser for satisfactory files. Includes save files and blueprint files.",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
7
7
|
"main": "./build/index.js",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
},
|
|
22
22
|
"repository": {
|
|
23
23
|
"type": "git",
|
|
24
|
-
"url": "
|
|
24
|
+
"url": "https://github.com/etothepii4/satisfactory-file-parser.git"
|
|
25
25
|
},
|
|
26
26
|
"license": "MIT",
|
|
27
27
|
"bugs": {
|
|
28
|
-
"url": "https://
|
|
28
|
+
"url": "https://github.com/etothepii4/satisfactory-file-parser/issues"
|
|
29
29
|
},
|
|
30
|
-
"homepage": "https://
|
|
30
|
+
"homepage": "https://github.com/etothepii4/satisfactory-file-parser#readme",
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/jest": "^29.5.0",
|
|
33
33
|
"@types/node": "^18.15.10",
|