@etothepii/satisfactory-file-parser 0.1.16 → 0.1.18
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 +3 -5
- package/build/parser/byte/byte-reader.class.d.ts +2 -1
- package/build/parser/byte/byte-reader.class.js +5 -7
- package/build/parser/file.types.d.ts +5 -1
- package/build/parser/file.types.js +5 -0
- package/build/parser/parser.js +5 -0
- package/build/parser/satisfactory/blueprint/blueprint-reader.js +7 -6
- package/build/parser/satisfactory/blueprint/blueprint.types.d.ts +2 -1
- package/build/parser/satisfactory/objects/Property.d.ts +4 -6
- package/build/parser/satisfactory/objects/Property.js +65 -5
- package/build/parser/satisfactory/objects/SaveObject.d.ts +1 -1
- package/build/parser/satisfactory/objects/SaveObject.js +1 -1
- package/build/parser/satisfactory/objects/ue/FMD5Hash.d.ts +7 -0
- package/build/parser/satisfactory/objects/ue/FMD5Hash.js +19 -0
- package/build/parser/satisfactory/save/asynchronous-level.class.js +2 -2
- package/build/parser/satisfactory/save/level.class.d.ts +2 -1
- package/build/parser/satisfactory/save/level.class.js +22 -14
- package/build/parser/satisfactory/save/satisfactory-save.d.ts +2 -1
- package/build/parser/satisfactory/save/satisfactory-save.js +1 -0
- package/build/parser/satisfactory/save/save-reader.d.ts +8 -4
- package/build/parser/satisfactory/save/save-reader.js +54 -38
- package/build/parser/satisfactory/save/save-writer.d.ts +2 -1
- package/build/parser/satisfactory/save/save-writer.js +38 -25
- package/build/parser/satisfactory/save/save.types.d.ts +4 -1
- package/build/parser/stream/save-stream-json-stringifier.js +2 -0
- package/build/parser/stream/save-stream-reader.class.js +3 -3
- package/build/parser/stream/save-stream-writer.class.d.ts +3 -0
- package/build/parser/stream/save-stream-writer.class.js +16 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -13,13 +13,11 @@ The version support of the packages is indicated below.
|
|
|
13
13
|
|
|
14
14
|
Game Version Files of U5 and below are NOT supported.
|
|
15
15
|
|
|
16
|
-
U8 has read and write support for blueprints, but only read support for saves. More coming soon.
|
|
17
|
-
|
|
18
16
|
| Game Version | Package |
|
|
19
17
|
|:--------------:|:-----------------------------|
|
|
20
18
|
| <= U5 | ❌ |
|
|
21
|
-
| U6 + U7 | ✅ 0.0.1 - 0.0.34
|
|
22
|
-
| U8 |
|
|
19
|
+
| U6 + U7 | ✅ 0.0.1 - 0.0.34 |
|
|
20
|
+
| U8 | ✅ >= 0.1.18 |
|
|
23
21
|
|
|
24
22
|
|
|
25
23
|
## Installation via npm
|
|
@@ -96,7 +94,7 @@ fs.writeFileSync('./MyModifiedSave.sbpcfg', Buffer.from(summary.configFileBinary
|
|
|
96
94
|
## License
|
|
97
95
|
MIT License
|
|
98
96
|
|
|
99
|
-
Copyright (c) 2023
|
|
97
|
+
Copyright (c) 2023 etothepii
|
|
100
98
|
|
|
101
99
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
102
100
|
of this software and associated documentation files (the "Software"), to deal
|
|
@@ -13,7 +13,8 @@ export declare abstract class ByteReader implements BinaryReadable {
|
|
|
13
13
|
skipBytes(byteLength?: number): void;
|
|
14
14
|
readByte(): number;
|
|
15
15
|
readBytes(count: number): Uint8Array;
|
|
16
|
-
|
|
16
|
+
private uint8ToHexRepresentation;
|
|
17
|
+
readHex(byteLength: number, hexSeparator?: string): string;
|
|
17
18
|
readInt8(): number;
|
|
18
19
|
readUint8(): number;
|
|
19
20
|
readInt16(): number;
|
|
@@ -34,13 +34,11 @@ class ByteReader {
|
|
|
34
34
|
readBytes(count) {
|
|
35
35
|
return new Uint8Array(new Array(count).fill(0).map(pl => this.bufferView.getUint8(this.currentByte++)));
|
|
36
36
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
}
|
|
43
|
-
return hexPart.join('');
|
|
37
|
+
uint8ToHexRepresentation(byte) {
|
|
38
|
+
return ('0' + byte.toString(16)).slice(-2);
|
|
39
|
+
}
|
|
40
|
+
readHex(byteLength, hexSeparator = '') {
|
|
41
|
+
return Array.from(this.readBytes(byteLength)).map(byte => this.uint8ToHexRepresentation(byte)).join(hexSeparator);
|
|
44
42
|
}
|
|
45
43
|
readInt8() {
|
|
46
44
|
let data = this.bufferView.getInt8(this.currentByte++);
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
export declare enum CompressionAlgorithmCode {
|
|
2
|
+
ZLIB = 3
|
|
3
|
+
}
|
|
1
4
|
export type ChunkCompressionInfo = {
|
|
5
|
+
compressionAlgorithm?: CompressionAlgorithmCode;
|
|
2
6
|
chunkHeaderSize: number;
|
|
3
7
|
packageFileTag: number;
|
|
4
|
-
|
|
8
|
+
maxUncompressedChunkContentSize: number;
|
|
5
9
|
};
|
|
6
10
|
export type ChunkSummary = {
|
|
7
11
|
uncompressedSize: number;
|
|
@@ -1,2 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CompressionAlgorithmCode = void 0;
|
|
4
|
+
var CompressionAlgorithmCode;
|
|
5
|
+
(function (CompressionAlgorithmCode) {
|
|
6
|
+
CompressionAlgorithmCode[CompressionAlgorithmCode["ZLIB"] = 3] = "ZLIB";
|
|
7
|
+
})(CompressionAlgorithmCode = exports.CompressionAlgorithmCode || (exports.CompressionAlgorithmCode = {}));
|
package/build/parser/parser.js
CHANGED
|
@@ -11,6 +11,8 @@ class Parser {
|
|
|
11
11
|
const writer = new save_writer_1.SaveWriter();
|
|
12
12
|
save_writer_1.SaveWriter.WriteHeader(writer, save.header);
|
|
13
13
|
const posAfterHeader = writer.getBufferPosition();
|
|
14
|
+
save_writer_1.SaveWriter.WriteSaveBodyHash(writer, save.gridHash);
|
|
15
|
+
save_writer_1.SaveWriter.WriteGrids(writer, save.grids);
|
|
14
16
|
save_writer_1.SaveWriter.WriteLevels(writer, save, save.header.buildVersion);
|
|
15
17
|
writer.endWriting();
|
|
16
18
|
const chunkSummary = writer.generateChunks(save.compressionInfo, posAfterHeader, onBinaryBeforeCompressing, onHeader, onChunk);
|
|
@@ -22,7 +24,10 @@ class Parser {
|
|
|
22
24
|
const save = new satisfactory_save_1.SatisfactorySave(header);
|
|
23
25
|
const inflateResult = reader.inflateChunks();
|
|
24
26
|
onDecompressedSaveBody(reader.getBuffer());
|
|
27
|
+
const gridHash = reader.readSaveBodyHash();
|
|
28
|
+
save.gridHash = gridHash;
|
|
25
29
|
const grids = reader.readGrids();
|
|
30
|
+
save.grids = grids;
|
|
26
31
|
save.levels = reader.readLevels();
|
|
27
32
|
save.compressionInfo = reader.compressionInfo;
|
|
28
33
|
save.trailingCollectedObjects = reader.trailingCollectedObjects;
|
|
@@ -18,14 +18,14 @@ class BlueprintReader extends byte_reader_class_1.ByteReader {
|
|
|
18
18
|
super(bluePrintBuffer, alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
19
19
|
this.compressionInfo = {
|
|
20
20
|
packageFileTag: 0,
|
|
21
|
-
|
|
21
|
+
maxUncompressedChunkContentSize: 0,
|
|
22
22
|
chunkHeaderSize: save_reader_1.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
static ReadHeader(reader) {
|
|
26
26
|
const blueprintTypeOrWhat = reader.readBytes(4);
|
|
27
27
|
const versionThing = reader.readBytes(2 * 4);
|
|
28
|
-
const
|
|
28
|
+
const dimensions = (0, util_types_1.ParseVec3f)(reader);
|
|
29
29
|
let itemTypeCount = reader.readInt32();
|
|
30
30
|
const itemCosts = new Array(itemTypeCount).fill(['', 0]);
|
|
31
31
|
for (let i = 0; i < itemTypeCount; i++) {
|
|
@@ -42,6 +42,7 @@ class BlueprintReader extends byte_reader_class_1.ByteReader {
|
|
|
42
42
|
recipeRefs[i] = recipeName;
|
|
43
43
|
}
|
|
44
44
|
return {
|
|
45
|
+
designerDimension: dimensions,
|
|
45
46
|
recipeReferences: recipeRefs,
|
|
46
47
|
itemCosts
|
|
47
48
|
};
|
|
@@ -60,11 +61,11 @@ class BlueprintReader extends byte_reader_class_1.ByteReader {
|
|
|
60
61
|
if (this.compressionInfo.packageFileTag <= 0) {
|
|
61
62
|
this.compressionInfo.packageFileTag = chunkHeader.getUint32(0, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
62
63
|
}
|
|
63
|
-
if (this.compressionInfo.
|
|
64
|
-
this.compressionInfo.
|
|
64
|
+
if (this.compressionInfo.maxUncompressedChunkContentSize <= 0) {
|
|
65
|
+
this.compressionInfo.maxUncompressedChunkContentSize = chunkHeader.getInt32(8, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
65
66
|
}
|
|
66
|
-
const chunkCompressedLength = chunkHeader.getInt32(
|
|
67
|
-
const chunkUncompressedLength = chunkHeader.getInt32(
|
|
67
|
+
const chunkCompressedLength = chunkHeader.getInt32(33, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
68
|
+
const chunkUncompressedLength = chunkHeader.getInt32(25, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
68
69
|
totalUncompressedBodySize += chunkUncompressedLength;
|
|
69
70
|
const currentChunkSize = chunkCompressedLength;
|
|
70
71
|
let currentChunk = this.fileBuffer.slice(this.currentByte, this.currentByte + currentChunkSize);
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import { ChunkCompressionInfo } from "../../file.types";
|
|
2
2
|
import { SaveComponent } from "../objects/SaveComponent";
|
|
3
3
|
import { SaveEntity } from "../objects/SaveEntity";
|
|
4
|
-
import { col4 } from "../structs/util.types";
|
|
4
|
+
import { col4, vec3 } from "../structs/util.types";
|
|
5
5
|
export interface BlueprintConfig {
|
|
6
6
|
description: string;
|
|
7
7
|
color: col4;
|
|
8
8
|
iconID: number;
|
|
9
9
|
}
|
|
10
10
|
export type BlueprintHeader = {
|
|
11
|
+
designerDimension?: vec3;
|
|
11
12
|
itemCosts: [string, number][];
|
|
12
13
|
recipeReferences: string[];
|
|
13
14
|
};
|
|
@@ -126,10 +126,7 @@ export declare class ObjectProperty extends BasicProperty {
|
|
|
126
126
|
static ReadValue(reader: BinaryReadable): ObjectReference;
|
|
127
127
|
static CalcOverhead(property: ObjectProperty): number;
|
|
128
128
|
static Serialize(writer: ByteWriter, property: ObjectProperty): void;
|
|
129
|
-
static SerializeValue(writer: ByteWriter, value:
|
|
130
|
-
levelName: string;
|
|
131
|
-
pathName: string;
|
|
132
|
-
}): void;
|
|
129
|
+
static SerializeValue(writer: ByteWriter, value: ObjectReference): void;
|
|
133
130
|
}
|
|
134
131
|
export declare class EnumProperty extends BasicProperty {
|
|
135
132
|
value: {
|
|
@@ -245,16 +242,17 @@ export declare class SetProperty<T> extends BasicProperty {
|
|
|
245
242
|
static CalcOverhead(property: SetProperty<any>): number;
|
|
246
243
|
static Serialize(writer: ByteWriter, property: SetProperty<any>): void;
|
|
247
244
|
}
|
|
248
|
-
export type
|
|
245
|
+
export type MAP_STRUCT_KEY_PROXY = [number, number, number, number, number, number, number, number, number, number, number, number];
|
|
246
|
+
export type GENERIC_MAP_KEY_TYPE = number | ObjectReference | boolean | GENERIC_STRUCT_PROPERTY_VALUE | MAP_STRUCT_KEY_PROXY;
|
|
249
247
|
export type GENERIC_MAP_VALUE_TYPE = number | ObjectReference | boolean | GENERIC_STRUCT_PROPERTY_VALUE;
|
|
250
248
|
export declare class MapProperty extends BasicProperty {
|
|
251
249
|
keyType: string;
|
|
252
250
|
valueType: string;
|
|
251
|
+
structKeyProxy: MAP_STRUCT_KEY_PROXY;
|
|
253
252
|
modeType: number;
|
|
254
253
|
modeUnk1: string | undefined;
|
|
255
254
|
modeUnk2: string;
|
|
256
255
|
modeUnk3: string;
|
|
257
|
-
remainingData: number[];
|
|
258
256
|
values: [key: GENERIC_MAP_KEY_TYPE, value: GENERIC_MAP_VALUE_TYPE][];
|
|
259
257
|
constructor(keyType: string, valueType: string, ueType: string, index: number);
|
|
260
258
|
static Parse(reader: BinaryReadable, propertyName: string, buildVersion: number, size: number, ueType?: string, index?: number): MapProperty;
|
|
@@ -858,7 +858,7 @@ class SetProperty extends BasicProperty {
|
|
|
858
858
|
property = new SetProperty(subtype, new Array(elementCount).fill(0).map(() => StrProperty.ReadValue(reader)), ueType, index);
|
|
859
859
|
break;
|
|
860
860
|
default:
|
|
861
|
-
throw new Error(
|
|
861
|
+
throw new Error(`Not Implemented SetProperty of ${subtype}.`);
|
|
862
862
|
}
|
|
863
863
|
return property;
|
|
864
864
|
}
|
|
@@ -872,8 +872,13 @@ class SetProperty extends BasicProperty {
|
|
|
872
872
|
writer.writeInt32(property.values.length);
|
|
873
873
|
switch (property.subtype) {
|
|
874
874
|
case "IntProperty":
|
|
875
|
+
case "Int32Property":
|
|
875
876
|
property.values.forEach(v => Int32Property.SerializeValue(writer, v));
|
|
876
877
|
break;
|
|
878
|
+
case "UIntProperty":
|
|
879
|
+
case "UInt32Property":
|
|
880
|
+
property.values.forEach(v => Uint32Property.SerializeValue(writer, v));
|
|
881
|
+
break;
|
|
877
882
|
case "ObjectProperty":
|
|
878
883
|
property.values.forEach(v => ObjectProperty.SerializeValue(writer, v));
|
|
879
884
|
break;
|
|
@@ -884,7 +889,7 @@ class SetProperty extends BasicProperty {
|
|
|
884
889
|
property.values.forEach(v => (0, util_types_1.SerializeVec3f)(writer, v));
|
|
885
890
|
break;
|
|
886
891
|
default:
|
|
887
|
-
throw new Error(
|
|
892
|
+
throw new Error(`Not Implemented SetProperty of ${property.subtype}.`);
|
|
888
893
|
}
|
|
889
894
|
}
|
|
890
895
|
}
|
|
@@ -894,10 +899,10 @@ class MapProperty extends BasicProperty {
|
|
|
894
899
|
super('MapProperty', ueType, undefined, index);
|
|
895
900
|
this.keyType = keyType;
|
|
896
901
|
this.valueType = valueType;
|
|
902
|
+
this.structKeyProxy = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
897
903
|
this.modeType = 0;
|
|
898
904
|
this.modeUnk2 = '';
|
|
899
905
|
this.modeUnk3 = '';
|
|
900
|
-
this.remainingData = [];
|
|
901
906
|
this.values = [];
|
|
902
907
|
}
|
|
903
908
|
static Parse(reader, propertyName, buildVersion, size, ueType = 'MapProperty', index = 0) {
|
|
@@ -913,7 +918,7 @@ class MapProperty extends BasicProperty {
|
|
|
913
918
|
case 'StructProperty':
|
|
914
919
|
if (propertyName === 'mSaveData' || propertyName === 'mUnresolvedSaveData') {
|
|
915
920
|
key = propertyName;
|
|
916
|
-
reader.
|
|
921
|
+
property.structKeyProxy = Array.from(reader.readBytes(12));
|
|
917
922
|
}
|
|
918
923
|
else {
|
|
919
924
|
key = (0, exports.ParseDynamicStructData)(reader, 0, property.keyType);
|
|
@@ -975,7 +980,62 @@ class MapProperty extends BasicProperty {
|
|
|
975
980
|
writer.writeString(property.valueType);
|
|
976
981
|
writer.writeByte(0);
|
|
977
982
|
writer.writeInt32(property.modeType);
|
|
978
|
-
writer.
|
|
983
|
+
writer.writeInt32(property.values.length);
|
|
984
|
+
for (const entry of property.values) {
|
|
985
|
+
switch (property.keyType) {
|
|
986
|
+
case 'StructProperty':
|
|
987
|
+
if (property.name === 'mSaveData' || property.name === 'mUnresolvedSaveData') {
|
|
988
|
+
writer.writeBytesArray(property.structKeyProxy);
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
(0, exports.SerializeDynamicStructData)(writer, 0, entry[0]);
|
|
992
|
+
}
|
|
993
|
+
break;
|
|
994
|
+
case 'ObjectProperty':
|
|
995
|
+
ObjectProperty.SerializeValue(writer, entry[0]);
|
|
996
|
+
break;
|
|
997
|
+
case 'StrProperty':
|
|
998
|
+
case 'NameProperty':
|
|
999
|
+
StrProperty.SerializeValue(writer, entry[0]);
|
|
1000
|
+
break;
|
|
1001
|
+
case 'EnumProperty':
|
|
1002
|
+
EnumProperty.SerializeValue(writer, entry[0]);
|
|
1003
|
+
break;
|
|
1004
|
+
case 'IntProperty':
|
|
1005
|
+
case 'Int32Property':
|
|
1006
|
+
Int32Property.SerializeValue(writer, entry[0]);
|
|
1007
|
+
break;
|
|
1008
|
+
case 'ByteProperty':
|
|
1009
|
+
ByteProperty.SerializeValue(writer, entry[0]);
|
|
1010
|
+
break;
|
|
1011
|
+
default:
|
|
1012
|
+
throw new Error(`not implemented map key type ${property.valueType}`);
|
|
1013
|
+
}
|
|
1014
|
+
switch (property.valueType) {
|
|
1015
|
+
case 'StructProperty':
|
|
1016
|
+
(0, exports.SerializeDynamicStructData)(writer, 0, entry[1]);
|
|
1017
|
+
break;
|
|
1018
|
+
case 'ObjectProperty':
|
|
1019
|
+
ObjectProperty.SerializeValue(writer, entry[1]);
|
|
1020
|
+
break;
|
|
1021
|
+
case 'StrProperty':
|
|
1022
|
+
case 'NameProperty':
|
|
1023
|
+
StrProperty.SerializeValue(writer, entry[1]);
|
|
1024
|
+
break;
|
|
1025
|
+
case 'EnumProperty':
|
|
1026
|
+
EnumProperty.SerializeValue(writer, entry[1]);
|
|
1027
|
+
break;
|
|
1028
|
+
case 'IntProperty':
|
|
1029
|
+
case 'Int32Property':
|
|
1030
|
+
Int32Property.SerializeValue(writer, entry[1]);
|
|
1031
|
+
break;
|
|
1032
|
+
case 'ByteProperty':
|
|
1033
|
+
ByteProperty.SerializeValue(writer, entry[1]);
|
|
1034
|
+
break;
|
|
1035
|
+
default:
|
|
1036
|
+
throw new Error(`not implemented map value type ${property.valueType}`);
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
979
1039
|
}
|
|
980
1040
|
}
|
|
981
1041
|
exports.MapProperty = MapProperty;
|
|
@@ -14,7 +14,7 @@ export declare abstract class SaveObject implements SaveObjectHeader {
|
|
|
14
14
|
properties: PropertiesMap;
|
|
15
15
|
specialProperties: SpecialAnyProperty;
|
|
16
16
|
trailingData: number[];
|
|
17
|
-
|
|
17
|
+
saveOrBlueprintIndicator: number;
|
|
18
18
|
unknownType2: number;
|
|
19
19
|
constructor(typePath: string, rootObject: string, instanceName: string);
|
|
20
20
|
protected static ParseHeader(reader: BinaryReadable, obj: SaveObject): void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { ByteReader, ByteWriter } from '../../../..';
|
|
2
|
+
export type MD5Hash = {
|
|
3
|
+
isValid: boolean;
|
|
4
|
+
hash?: number[];
|
|
5
|
+
};
|
|
6
|
+
export declare const readMD5Hash: (reader: ByteReader) => MD5Hash;
|
|
7
|
+
export declare const writeMD5Hash: (writer: ByteWriter, md5Hash: MD5Hash) => void;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.writeMD5Hash = exports.readMD5Hash = void 0;
|
|
4
|
+
const readMD5Hash = (reader) => {
|
|
5
|
+
const md5Hash = { isValid: false };
|
|
6
|
+
md5Hash.isValid = reader.readInt32() === 1;
|
|
7
|
+
if (md5Hash.isValid) {
|
|
8
|
+
md5Hash.hash = Array.from(reader.readBytes(16));
|
|
9
|
+
}
|
|
10
|
+
return md5Hash;
|
|
11
|
+
};
|
|
12
|
+
exports.readMD5Hash = readMD5Hash;
|
|
13
|
+
const writeMD5Hash = (writer, md5Hash) => {
|
|
14
|
+
writer.writeInt32(md5Hash.isValid ? 1 : 0);
|
|
15
|
+
if (md5Hash.isValid) {
|
|
16
|
+
writer.writeBytesArray(md5Hash.hash);
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
exports.writeMD5Hash = writeMD5Hash;
|
|
@@ -52,13 +52,13 @@ class AsynchronousLevel {
|
|
|
52
52
|
await writer.openLevel(levelName);
|
|
53
53
|
level_class_1.Level.ReadObjectHeaders(reader, level.objects, reader.onProgressCallback);
|
|
54
54
|
reader.onProgressCallback(reader.getBufferProgress());
|
|
55
|
-
level.collectables = level_class_1.Level.ReadCollectablesList(reader);
|
|
55
|
+
level.collectables = level_class_1.Level.ReadCollectablesList(reader, undefined);
|
|
56
56
|
console.log(`before object contents buffer pos ${reader.getBufferPosition()}`);
|
|
57
57
|
await AsynchronousLevel.StreamReadObjectContents(reader, level.objects, buildVersion, writer, reader.onProgressCallback);
|
|
58
58
|
await writer.switchInLevelToCollectables();
|
|
59
59
|
await writer.writeCollectables(...level.collectables);
|
|
60
60
|
reader.onProgressCallback(reader.getBufferProgress());
|
|
61
|
-
level_class_1.Level.ReadCollectablesList(reader);
|
|
61
|
+
level_class_1.Level.ReadCollectablesList(reader, undefined);
|
|
62
62
|
await writer.endLevel();
|
|
63
63
|
return level;
|
|
64
64
|
}
|
|
@@ -9,6 +9,7 @@ export declare class Level {
|
|
|
9
9
|
name: string;
|
|
10
10
|
objects: (SaveEntity | SaveComponent)[];
|
|
11
11
|
collectables: ObjectReference[];
|
|
12
|
+
remainingStuffAfterHeaders: Uint8Array;
|
|
12
13
|
constructor(name: string);
|
|
13
14
|
static SerializeObjectHeaders(writer: ByteWriter, objects: (SaveEntity | SaveComponent)[]): void;
|
|
14
15
|
static WriteLevel(writer: ByteWriter, level: Level, buildVersion: number): void;
|
|
@@ -17,5 +18,5 @@ export declare class Level {
|
|
|
17
18
|
static ReadObjectContents(levelName: string, reader: BinaryReadable, objectsList: SaveObject[], buildVersion: number, onProgressCallback: (progress: number, msg?: string) => void): Promise<void>;
|
|
18
19
|
static ReadLevel(reader: SaveReader, levelName: string, buildVersion: number): Level;
|
|
19
20
|
static SerializeCollectablesList(writer: ByteWriter, collectables: ObjectReference[]): void;
|
|
20
|
-
static ReadCollectablesList(reader: BinaryReadable): ObjectReference[];
|
|
21
|
+
static ReadCollectablesList(reader: BinaryReadable, printSmthWhenItsCollectables?: string | undefined): ObjectReference[];
|
|
21
22
|
}
|
|
@@ -9,6 +9,7 @@ class Level {
|
|
|
9
9
|
this.name = name;
|
|
10
10
|
this.objects = [];
|
|
11
11
|
this.collectables = [];
|
|
12
|
+
this.remainingStuffAfterHeaders = new Uint8Array();
|
|
12
13
|
}
|
|
13
14
|
static SerializeObjectHeaders(writer, objects) {
|
|
14
15
|
writer.writeInt32(objects.length);
|
|
@@ -31,17 +32,21 @@ class Level {
|
|
|
31
32
|
static WriteLevel(writer, level, buildVersion) {
|
|
32
33
|
const lenIndicatorHeaderAndCollectableSize = writer.getBufferPosition();
|
|
33
34
|
writer.writeInt32(0);
|
|
35
|
+
writer.writeInt32(0);
|
|
34
36
|
Level.SerializeObjectHeaders(writer, level.objects);
|
|
35
37
|
Level.SerializeCollectablesList(writer, level.collectables);
|
|
36
|
-
writer.writeBinarySizeFromPosition(lenIndicatorHeaderAndCollectableSize, lenIndicatorHeaderAndCollectableSize +
|
|
38
|
+
writer.writeBinarySizeFromPosition(lenIndicatorHeaderAndCollectableSize, lenIndicatorHeaderAndCollectableSize + 8);
|
|
37
39
|
Level.SerializeObjectContents(writer, level.objects, buildVersion, level.name);
|
|
38
40
|
Level.SerializeCollectablesList(writer, level.collectables);
|
|
39
41
|
}
|
|
40
42
|
static SerializeObjectContents(writer, objects, buildVersion, levelName) {
|
|
41
43
|
const lenIndicatorEntities = writer.getBufferPosition();
|
|
42
44
|
writer.writeInt32(0);
|
|
45
|
+
writer.writeInt32(0);
|
|
43
46
|
writer.writeInt32(objects.length);
|
|
44
47
|
for (const obj of objects) {
|
|
48
|
+
writer.writeInt32(obj.saveOrBlueprintIndicator);
|
|
49
|
+
writer.writeInt32(obj.unknownType2);
|
|
45
50
|
const lenReplacementPosition = writer.getBufferPosition();
|
|
46
51
|
writer.writeInt32(0);
|
|
47
52
|
if ((0, SaveEntity_1.isSaveEntity)(obj)) {
|
|
@@ -52,7 +57,7 @@ class Level {
|
|
|
52
57
|
}
|
|
53
58
|
writer.writeBinarySizeFromPosition(lenReplacementPosition, lenReplacementPosition + 4);
|
|
54
59
|
}
|
|
55
|
-
writer.writeBinarySizeFromPosition(lenIndicatorEntities, lenIndicatorEntities +
|
|
60
|
+
writer.writeBinarySizeFromPosition(lenIndicatorEntities, lenIndicatorEntities + 8);
|
|
56
61
|
}
|
|
57
62
|
static ReadObjectHeaders(reader, objectsList, onProgressCallback) {
|
|
58
63
|
let countObjectHeaders = reader.readInt32();
|
|
@@ -88,7 +93,7 @@ class Level {
|
|
|
88
93
|
onProgressCallback(reader.getBufferProgress());
|
|
89
94
|
}
|
|
90
95
|
const obj = objectsList[i];
|
|
91
|
-
obj.
|
|
96
|
+
obj.saveOrBlueprintIndicator = reader.readInt32();
|
|
92
97
|
obj.unknownType2 = reader.readInt32();
|
|
93
98
|
const binarySize = reader.readInt32();
|
|
94
99
|
const before = reader.getBufferPosition();
|
|
@@ -106,18 +111,23 @@ class Level {
|
|
|
106
111
|
}
|
|
107
112
|
static ReadLevel(reader, levelName, buildVersion) {
|
|
108
113
|
const level = new Level(levelName);
|
|
109
|
-
const levelStartPos = reader.getBufferPosition();
|
|
110
114
|
const headersBinLen = reader.readInt32();
|
|
111
115
|
const unk = reader.readInt32();
|
|
112
116
|
const posBeforeHeaders = reader.getBufferPosition();
|
|
113
117
|
Level.ReadObjectHeaders(reader, level.objects, reader.onProgressCallback);
|
|
114
|
-
|
|
118
|
+
let remainingSize = headersBinLen - (reader.getBufferPosition() - posBeforeHeaders);
|
|
115
119
|
if (remainingSize > 0) {
|
|
116
|
-
|
|
120
|
+
const doubledCollectablesIgnored = Level.ReadCollectablesList(reader, 'collectables at the header section! ' + levelName);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
}
|
|
124
|
+
remainingSize = headersBinLen - (reader.getBufferPosition() - posBeforeHeaders);
|
|
125
|
+
if (remainingSize !== 0) {
|
|
126
|
+
console.warn('remaining size not 0. Save may be corrupt.', remainingSize, levelName);
|
|
117
127
|
}
|
|
118
128
|
const objectContentsBinLen = reader.readInt32();
|
|
129
|
+
const unk2 = reader.readInt32();
|
|
119
130
|
reader.onProgressCallback(reader.getBufferProgress());
|
|
120
|
-
level.collectables = Level.ReadCollectablesList(reader);
|
|
121
131
|
const posBeforeContents = reader.getBufferPosition();
|
|
122
132
|
Level.ReadObjectContents(levelName, reader, level.objects, buildVersion, reader.onProgressCallback);
|
|
123
133
|
const posAfterContents = reader.getBufferPosition();
|
|
@@ -125,7 +135,7 @@ class Level {
|
|
|
125
135
|
console.warn('save seems corrupt.', this.name);
|
|
126
136
|
}
|
|
127
137
|
reader.onProgressCallback(reader.getBufferProgress());
|
|
128
|
-
level.collectables = Level.ReadCollectablesList(reader);
|
|
138
|
+
level.collectables = Level.ReadCollectablesList(reader, 'collectables 2! ' + levelName);
|
|
129
139
|
return level;
|
|
130
140
|
}
|
|
131
141
|
static SerializeCollectablesList(writer, collectables) {
|
|
@@ -134,19 +144,17 @@ class Level {
|
|
|
134
144
|
ObjectReference_1.ObjectReference.Serialize(writer, collectable);
|
|
135
145
|
}
|
|
136
146
|
}
|
|
137
|
-
static ReadCollectablesList(reader) {
|
|
147
|
+
static ReadCollectablesList(reader, printSmthWhenItsCollectables) {
|
|
138
148
|
const collected = [];
|
|
139
149
|
let countSmthing = reader.readInt32();
|
|
140
150
|
if (countSmthing > 0) {
|
|
141
|
-
const lookahead = reader.readInt32();
|
|
142
|
-
if (lookahead === 0) {
|
|
143
|
-
return collected;
|
|
144
|
-
}
|
|
145
|
-
reader.skipBytes(-4);
|
|
146
151
|
for (let i = 0; i < countSmthing; i++) {
|
|
147
152
|
const collectable = ObjectReference_1.ObjectReference.Parse(reader);
|
|
148
153
|
collected.push(collectable);
|
|
149
154
|
}
|
|
155
|
+
if (process.env.NODE_ENV === 'debug' && countSmthing > 0) {
|
|
156
|
+
console.log(printSmthWhenItsCollectables, countSmthing, collected.map(coll => coll.pathName).join(', '));
|
|
157
|
+
}
|
|
150
158
|
}
|
|
151
159
|
return collected;
|
|
152
160
|
}
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { ChunkCompressionInfo } from "../../file.types";
|
|
2
2
|
import { ObjectReference } from "../objects/ObjectReference";
|
|
3
3
|
import { Level } from "./level.class";
|
|
4
|
-
import { Grids } from "./save-reader";
|
|
4
|
+
import { ByteArray4, Grids } from "./save-reader";
|
|
5
5
|
import { SatisfactorySaveHeader } from "./save.types";
|
|
6
6
|
export declare class SatisfactorySave {
|
|
7
7
|
header: SatisfactorySaveHeader;
|
|
8
|
+
gridHash: ByteArray4;
|
|
8
9
|
grids: Grids;
|
|
9
10
|
levels: Level[];
|
|
10
11
|
trailingCollectedObjects: ObjectReference[];
|
|
@@ -6,12 +6,15 @@ import { Level } from "./level.class";
|
|
|
6
6
|
import { RoughSaveVersion, SatisfactorySaveHeader } from "./save.types";
|
|
7
7
|
export type ReadMode = 'stream' | 'whole';
|
|
8
8
|
export declare const DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = 49;
|
|
9
|
+
export type ByteArray4 = [number, number, number, number];
|
|
9
10
|
export type Grids = {
|
|
10
11
|
[parentName: string]: {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
checksum: number;
|
|
13
|
+
cellSize: number;
|
|
14
|
+
gridHash: number;
|
|
15
|
+
children: {
|
|
16
|
+
[name: string]: number;
|
|
17
|
+
};
|
|
15
18
|
};
|
|
16
19
|
};
|
|
17
20
|
export declare class SaveReader extends ByteReader {
|
|
@@ -28,6 +31,7 @@ export declare class SaveReader extends ByteReader {
|
|
|
28
31
|
concatenatedChunkLength: number;
|
|
29
32
|
numChunks: number;
|
|
30
33
|
};
|
|
34
|
+
readSaveBodyHash: () => ByteArray4;
|
|
31
35
|
readGrids: () => Grids;
|
|
32
36
|
readLevels(): Level[];
|
|
33
37
|
readLevelsAsynchronously(writer: SaveStreamWriter): Promise<Level[]>;
|
|
@@ -1,4 +1,27 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || function (mod) {
|
|
19
|
+
if (mod && mod.__esModule) return mod;
|
|
20
|
+
var result = {};
|
|
21
|
+
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
22
|
+
__setModuleDefault(result, mod);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
2
25
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
26
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
27
|
};
|
|
@@ -8,6 +31,7 @@ const pako_1 = __importDefault(require("pako"));
|
|
|
8
31
|
const alignment_enum_1 = require("../../byte/alignment.enum");
|
|
9
32
|
const byte_reader_class_1 = require("../../byte/byte-reader.class");
|
|
10
33
|
const parser_error_1 = require("../../error/parser.error");
|
|
34
|
+
const FMD5Hash = __importStar(require("../objects/ue/FMD5Hash"));
|
|
11
35
|
const asynchronous_level_class_1 = require("./asynchronous-level.class");
|
|
12
36
|
const level_class_1 = require("./level.class");
|
|
13
37
|
exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = 49;
|
|
@@ -19,43 +43,38 @@ class SaveReader extends byte_reader_class_1.ByteReader {
|
|
|
19
43
|
this.trailingCollectedObjects = [];
|
|
20
44
|
this.compressionInfo = {
|
|
21
45
|
packageFileTag: 0,
|
|
22
|
-
|
|
46
|
+
maxUncompressedChunkContentSize: 0,
|
|
23
47
|
chunkHeaderSize: exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
|
|
24
48
|
};
|
|
25
|
-
this.
|
|
49
|
+
this.readSaveBodyHash = () => {
|
|
26
50
|
const unk = this.readInt32();
|
|
27
51
|
const cannotBeLevelCountWhatIsIt = this.readInt32();
|
|
28
52
|
const noneString = this.readString();
|
|
29
53
|
const unk2 = this.readInt32();
|
|
30
|
-
const
|
|
54
|
+
const gridValidationHash = Array.from(this.readBytes(4));
|
|
31
55
|
const unk3 = this.readInt32();
|
|
32
56
|
const noneString2 = this.readString();
|
|
57
|
+
return gridValidationHash;
|
|
58
|
+
};
|
|
59
|
+
this.readGrids = () => {
|
|
33
60
|
const grids = {};
|
|
34
|
-
const
|
|
35
|
-
const
|
|
61
|
+
const readGrid = (childrenCount) => {
|
|
62
|
+
const parentChecksumIdOrSmth = this.readUint32();
|
|
36
63
|
const parentName = this.readString();
|
|
37
|
-
const
|
|
38
|
-
const
|
|
39
|
-
grids[parentName] = {};
|
|
64
|
+
const cellSize = this.readInt32();
|
|
65
|
+
const gridHash = this.readUint32();
|
|
66
|
+
grids[parentName] = { checksum: parentChecksumIdOrSmth, children: {}, cellSize, gridHash };
|
|
40
67
|
for (let i = 0; i < childrenCount; i++) {
|
|
41
|
-
const
|
|
68
|
+
const cellHash = this.readUint32();
|
|
42
69
|
const levelInstanceName = this.readString();
|
|
43
|
-
|
|
44
|
-
const arr = grids[parentName][Number(lod)];
|
|
45
|
-
if (!arr) {
|
|
46
|
-
grids[parentName][Number(lod)] = [];
|
|
47
|
-
}
|
|
48
|
-
grids[parentName][Number(lod)].push({
|
|
49
|
-
name: levelInstanceName,
|
|
50
|
-
size: binaryLenIGuess
|
|
51
|
-
});
|
|
70
|
+
grids[parentName].children[levelInstanceName] = cellHash;
|
|
52
71
|
}
|
|
53
72
|
};
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
73
|
+
readGrid(1379);
|
|
74
|
+
readGrid(0);
|
|
75
|
+
readGrid(972);
|
|
76
|
+
readGrid(0);
|
|
77
|
+
readGrid(0);
|
|
59
78
|
const unk5 = this.readInt32();
|
|
60
79
|
return grids;
|
|
61
80
|
};
|
|
@@ -98,10 +117,17 @@ class SaveReader extends byte_reader_class_1.ByteReader {
|
|
|
98
117
|
if (this.header.saveHeaderType >= 10) {
|
|
99
118
|
this.header.saveIdentifier = this.readString();
|
|
100
119
|
}
|
|
120
|
+
if (this.header.saveHeaderType >= 11) {
|
|
121
|
+
this.header.partitionEnabledFlag = this.readInt32() === 1;
|
|
122
|
+
}
|
|
123
|
+
if (this.header.saveHeaderType >= 12) {
|
|
124
|
+
this.header.consistencyHashBytes = FMD5Hash.readMD5Hash(this);
|
|
125
|
+
}
|
|
101
126
|
if (this.header.saveHeaderType >= 13) {
|
|
102
|
-
this.header.
|
|
127
|
+
this.header.creativeModeEnabled = this.readInt32() == 1;
|
|
103
128
|
}
|
|
104
129
|
if (this.header.saveVersion >= 21) {
|
|
130
|
+
console.log('save version', this.header.saveVersion, 'header type', this.header.saveHeaderType, 'buildversion', this.header.buildVersion);
|
|
105
131
|
}
|
|
106
132
|
else {
|
|
107
133
|
throw new parser_error_1.UnsupportedVersionError("The save version is too old to support encoding currently. Save in newer game version.");
|
|
@@ -122,8 +148,8 @@ class SaveReader extends byte_reader_class_1.ByteReader {
|
|
|
122
148
|
if (this.compressionInfo.packageFileTag <= 0) {
|
|
123
149
|
this.compressionInfo.packageFileTag = chunkHeader.getUint32(0, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
124
150
|
}
|
|
125
|
-
if (this.compressionInfo.
|
|
126
|
-
this.compressionInfo.
|
|
151
|
+
if (this.compressionInfo.maxUncompressedChunkContentSize <= 0) {
|
|
152
|
+
this.compressionInfo.maxUncompressedChunkContentSize = chunkHeader.getInt32(8, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
127
153
|
}
|
|
128
154
|
const chunkCompressedLength = chunkHeader.getInt32(33, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
129
155
|
const chunkUncompressedLength = chunkHeader.getInt32(25, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
@@ -179,18 +205,8 @@ class SaveReader extends byte_reader_class_1.ByteReader {
|
|
|
179
205
|
let levelSingleName = this.readString();
|
|
180
206
|
levels.push(level_class_1.Level.ReadLevel(this, levelSingleName, this.header.buildVersion));
|
|
181
207
|
}
|
|
182
|
-
level_class_1.Level.
|
|
183
|
-
|
|
184
|
-
if (this.getBufferPosition() < this.getBufferLength()) {
|
|
185
|
-
const appendedLevel = new level_class_1.Level(this.header.mapName);
|
|
186
|
-
levels.push(appendedLevel);
|
|
187
|
-
level_class_1.Level.ReadObjectHeaders(this, appendedLevel.objects, this.onProgressCallback);
|
|
188
|
-
const unk = this.readInt32();
|
|
189
|
-
appendedLevel.collectables = level_class_1.Level.ReadCollectablesList(this);
|
|
190
|
-
level_class_1.Level.ReadObjectContents(appendedLevel.name, this, appendedLevel.objects, this.header.buildVersion, this.onProgressCallback);
|
|
191
|
-
level_class_1.Level.ReadCollectablesList(this);
|
|
192
|
-
level_class_1.Level.ReadCollectablesList(this);
|
|
193
|
-
}
|
|
208
|
+
levels.push(level_class_1.Level.ReadLevel(this, this.header.mapName, this.header.buildVersion));
|
|
209
|
+
const trailingStuffToIgnore = level_class_1.Level.ReadCollectablesList(this, 'collectables 6!');
|
|
194
210
|
this.onProgressCallback(this.getBufferProgress(), 'finished parsing.');
|
|
195
211
|
return levels;
|
|
196
212
|
}
|
|
@@ -2,11 +2,12 @@ import { Alignment } from "../../byte/alignment.enum";
|
|
|
2
2
|
import { ByteWriter } from "../../byte/byte-writer.class";
|
|
3
3
|
import { ChunkCompressionInfo, ChunkSummary } from "../../file.types";
|
|
4
4
|
import { SatisfactorySave } from "./satisfactory-save";
|
|
5
|
-
import { Grids } from "./save-reader";
|
|
5
|
+
import { ByteArray4, Grids } from "./save-reader";
|
|
6
6
|
import { SatisfactorySaveHeader } from "./save.types";
|
|
7
7
|
export declare class SaveWriter extends ByteWriter {
|
|
8
8
|
constructor();
|
|
9
9
|
static WriteHeader(writer: ByteWriter, header: SatisfactorySaveHeader): void;
|
|
10
|
+
static WriteSaveBodyHash: (writer: ByteWriter, hash: ByteArray4) => void;
|
|
10
11
|
static WriteGrids: (writer: ByteWriter, grids: Grids) => void;
|
|
11
12
|
static WriteLevels(writer: ByteWriter, save: SatisfactorySave, buildVersion: number): void;
|
|
12
13
|
static GenerateCompressedChunksFromData(bufferArray: ArrayBuffer, compressionInfo: ChunkCompressionInfo, onBinaryBeforeCompressing: (binary: ArrayBuffer) => void, onChunk: (chunk: Uint8Array) => void, alignment?: Alignment): ChunkSummary[];
|
|
@@ -8,7 +8,10 @@ const pako_1 = __importDefault(require("pako"));
|
|
|
8
8
|
const alignment_enum_1 = require("../../byte/alignment.enum");
|
|
9
9
|
const byte_writer_class_1 = require("../../byte/byte-writer.class");
|
|
10
10
|
const parser_error_1 = require("../../error/parser.error");
|
|
11
|
+
const file_types_1 = require("../../file.types");
|
|
12
|
+
const FMD5Hash_1 = require("../objects/ue/FMD5Hash");
|
|
11
13
|
const level_class_1 = require("./level.class");
|
|
14
|
+
const save_reader_1 = require("./save-reader");
|
|
12
15
|
class SaveWriter extends byte_writer_class_1.ByteWriter {
|
|
13
16
|
constructor() {
|
|
14
17
|
super(alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
@@ -21,7 +24,7 @@ class SaveWriter extends byte_writer_class_1.ByteWriter {
|
|
|
21
24
|
writer.writeString(header.mapOptions);
|
|
22
25
|
writer.writeString(header.sessionName);
|
|
23
26
|
writer.writeInt32(header.playDurationSeconds);
|
|
24
|
-
writer.writeInt64(BigInt(header.saveDateTime));
|
|
27
|
+
writer.writeInt64(BigInt(header.saveDateTime) * 10000n + save_reader_1.SaveReader.EPOCH_TICKS);
|
|
25
28
|
writer.writeByte(header.sessionVisibility);
|
|
26
29
|
if (header.saveHeaderType >= 7) {
|
|
27
30
|
writer.writeInt32(header.fEditorObjectVersion);
|
|
@@ -38,8 +41,14 @@ class SaveWriter extends byte_writer_class_1.ByteWriter {
|
|
|
38
41
|
if (header.saveHeaderType >= 10) {
|
|
39
42
|
writer.writeString(header.saveIdentifier);
|
|
40
43
|
}
|
|
44
|
+
if (header.saveHeaderType >= 11) {
|
|
45
|
+
writer.writeInt32(header.partitionEnabledFlag ? 1 : 0);
|
|
46
|
+
}
|
|
47
|
+
if (header.saveHeaderType >= 12) {
|
|
48
|
+
(0, FMD5Hash_1.writeMD5Hash)(writer, header.consistencyHashBytes);
|
|
49
|
+
}
|
|
41
50
|
if (header.saveHeaderType >= 13) {
|
|
42
|
-
writer.
|
|
51
|
+
writer.writeInt32(header.creativeModeEnabled ? 1 : 0);
|
|
43
52
|
}
|
|
44
53
|
if (header.saveVersion >= 21) {
|
|
45
54
|
}
|
|
@@ -55,7 +64,7 @@ class SaveWriter extends byte_writer_class_1.ByteWriter {
|
|
|
55
64
|
}
|
|
56
65
|
level_class_1.Level.WriteLevel(writer, level, buildVersion);
|
|
57
66
|
}
|
|
58
|
-
level_class_1.Level.SerializeCollectablesList(writer, save.trailingCollectedObjects);
|
|
67
|
+
level_class_1.Level.SerializeCollectablesList(writer, save.trailingCollectedObjects ?? []);
|
|
59
68
|
}
|
|
60
69
|
static GenerateCompressedChunksFromData(bufferArray, compressionInfo, onBinaryBeforeCompressing, onChunk, alignment = alignment_enum_1.Alignment.LITTLE_ENDIAN) {
|
|
61
70
|
const errors = [];
|
|
@@ -63,12 +72,12 @@ class SaveWriter extends byte_writer_class_1.ByteWriter {
|
|
|
63
72
|
const saveBody = new Uint8Array(bufferArray.byteLength + 4);
|
|
64
73
|
saveBody.set(new Uint8Array(bufferArray), 4);
|
|
65
74
|
const miniView = new DataView(saveBody.buffer);
|
|
66
|
-
miniView.setInt32(0, totalUncompressedSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
75
|
+
miniView.setInt32(0, totalUncompressedSize - 4, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
67
76
|
onBinaryBeforeCompressing(saveBody.buffer);
|
|
68
77
|
let handledByte = 0;
|
|
69
78
|
const chunkSummary = [];
|
|
70
79
|
while (handledByte < saveBody.byteLength) {
|
|
71
|
-
const uncompressedContentSize = Math.min(compressionInfo.
|
|
80
|
+
const uncompressedContentSize = Math.min(compressionInfo.maxUncompressedChunkContentSize, saveBody.byteLength - handledByte);
|
|
72
81
|
const uncompressedChunk = saveBody.buffer.slice(handledByte, handledByte + uncompressedContentSize);
|
|
73
82
|
let compressedChunk = new Uint8Array(0);
|
|
74
83
|
try {
|
|
@@ -81,17 +90,18 @@ class SaveWriter extends byte_writer_class_1.ByteWriter {
|
|
|
81
90
|
chunk.set(compressedChunk, compressionInfo.chunkHeaderSize);
|
|
82
91
|
const view = new DataView(chunk.buffer);
|
|
83
92
|
view.setInt32(0, compressionInfo.packageFileTag, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
84
|
-
view.setInt32(4,
|
|
85
|
-
view.setInt32(8, compressionInfo.
|
|
93
|
+
view.setInt32(4, 0x22222222, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
94
|
+
view.setInt32(8, compressionInfo.maxUncompressedChunkContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
86
95
|
view.setInt32(12, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
87
|
-
view.
|
|
88
|
-
view.setInt32(
|
|
89
|
-
view.setInt32(
|
|
90
|
-
view.setInt32(
|
|
91
|
-
view.setInt32(
|
|
92
|
-
view.setInt32(
|
|
93
|
-
view.setInt32(
|
|
94
|
-
view.setInt32(
|
|
96
|
+
view.setUint8(16, file_types_1.CompressionAlgorithmCode.ZLIB);
|
|
97
|
+
view.setInt32(17, compressedChunk.byteLength, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
98
|
+
view.setInt32(21, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
99
|
+
view.setInt32(25, uncompressedContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
100
|
+
view.setInt32(29, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
101
|
+
view.setInt32(33, compressedChunk.byteLength, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
102
|
+
view.setInt32(37, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
103
|
+
view.setInt32(41, uncompressedContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
104
|
+
view.setInt32(45, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
95
105
|
onChunk(chunk);
|
|
96
106
|
chunkSummary.push({
|
|
97
107
|
uncompressedSize: uncompressedContentSize + compressionInfo.chunkHeaderSize,
|
|
@@ -112,23 +122,26 @@ class SaveWriter extends byte_writer_class_1.ByteWriter {
|
|
|
112
122
|
return chunkSummary;
|
|
113
123
|
}
|
|
114
124
|
}
|
|
115
|
-
SaveWriter.
|
|
116
|
-
writer.writeInt32(0);
|
|
125
|
+
SaveWriter.WriteSaveBodyHash = (writer, hash) => {
|
|
117
126
|
writer.writeInt32(0);
|
|
127
|
+
writer.writeInt32(6);
|
|
118
128
|
writer.writeString('None');
|
|
119
129
|
writer.writeInt32(0);
|
|
120
|
-
writer.
|
|
121
|
-
writer.writeInt32(
|
|
130
|
+
writer.writeBytesArray(hash);
|
|
131
|
+
writer.writeInt32(1);
|
|
122
132
|
writer.writeString('None');
|
|
133
|
+
};
|
|
134
|
+
SaveWriter.WriteGrids = (writer, grids) => {
|
|
123
135
|
for (const parentEntry of Object.entries(grids)) {
|
|
124
|
-
writer.writeInt32(
|
|
136
|
+
writer.writeInt32(parentEntry[1].checksum);
|
|
125
137
|
writer.writeString(parentEntry[0]);
|
|
126
|
-
writer.writeInt32(
|
|
127
|
-
writer.
|
|
128
|
-
for (const child of Object.entries(parentEntry[1]
|
|
129
|
-
writer.writeUint32(child
|
|
130
|
-
writer.writeString(child
|
|
138
|
+
writer.writeInt32(parentEntry[1].cellSize);
|
|
139
|
+
writer.writeUint32(parentEntry[1].gridHash);
|
|
140
|
+
for (const child of Object.entries(parentEntry[1].children)) {
|
|
141
|
+
writer.writeUint32(child[1]);
|
|
142
|
+
writer.writeString(child[0]);
|
|
131
143
|
}
|
|
132
144
|
}
|
|
145
|
+
writer.writeInt32(0);
|
|
133
146
|
};
|
|
134
147
|
exports.SaveWriter = SaveWriter;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { MD5Hash } from '../objects/ue/FMD5Hash';
|
|
1
2
|
export interface ModData {
|
|
2
3
|
Reference: string;
|
|
3
4
|
Name: string;
|
|
@@ -21,7 +22,9 @@ export interface SatisfactorySaveHeader {
|
|
|
21
22
|
isModdedSave?: number;
|
|
22
23
|
saveIdentifier?: string;
|
|
23
24
|
fEditorObjectVersion?: number;
|
|
24
|
-
|
|
25
|
+
partitionEnabledFlag?: boolean;
|
|
26
|
+
consistencyHashBytes?: MD5Hash;
|
|
27
|
+
creativeModeEnabled?: boolean;
|
|
25
28
|
}
|
|
26
29
|
export type RoughSaveVersion = '<U6' | 'U6/U7' | 'U8+';
|
|
27
30
|
export type ResourceNodeEntry = {
|
|
@@ -10,6 +10,8 @@ class SaveStreamJsonStringifier {
|
|
|
10
10
|
if (save.compressionInfo) {
|
|
11
11
|
await writer.writeCompressionInfo(save.compressionInfo);
|
|
12
12
|
}
|
|
13
|
+
await writer.writeGridHash(save.gridHash);
|
|
14
|
+
await writer.writeGrids(save.grids);
|
|
13
15
|
await writer.openLevels();
|
|
14
16
|
const objectBatchSize = 10000;
|
|
15
17
|
for (const level of save.levels) {
|
|
@@ -14,7 +14,7 @@ class SaveStreamReader extends byte_stream_reader_class_1.ByteStreamReader {
|
|
|
14
14
|
super(reader, onCloseCallback, 30000, maxBufferThreshold, alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
15
15
|
this.compressionInfo = {
|
|
16
16
|
packageFileTag: 0,
|
|
17
|
-
|
|
17
|
+
maxUncompressedChunkContentSize: 0,
|
|
18
18
|
chunkHeaderSize: SaveStreamReader.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
|
|
19
19
|
};
|
|
20
20
|
}
|
|
@@ -73,8 +73,8 @@ class SaveStreamReader extends byte_stream_reader_class_1.ByteStreamReader {
|
|
|
73
73
|
if (this.compressionInfo.packageFileTag <= 0) {
|
|
74
74
|
this.compressionInfo.packageFileTag = this.operatingDataView.getInt32(0, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
75
75
|
}
|
|
76
|
-
if (this.compressionInfo.
|
|
77
|
-
this.compressionInfo.
|
|
76
|
+
if (this.compressionInfo.maxUncompressedChunkContentSize <= 0) {
|
|
77
|
+
this.compressionInfo.maxUncompressedChunkContentSize = this.operatingDataView.getInt32(8, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
78
78
|
}
|
|
79
79
|
const chunkCompressedLength = this.operatingDataView.getInt32(32, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
80
80
|
const chunkUncompressedLength = this.operatingDataView.getInt32(40, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { WritableStreamDefaultWriter } from "stream/web";
|
|
3
3
|
import { ChunkCompressionInfo, ObjectReference, SatisfactorySaveHeader } from "../..";
|
|
4
4
|
import { SaveObject } from "../satisfactory/objects/SaveObject";
|
|
5
|
+
import { ByteArray4, Grids } from '../satisfactory/save/save-reader';
|
|
5
6
|
export declare class SaveStreamWriter {
|
|
6
7
|
private writer;
|
|
7
8
|
private mode;
|
|
@@ -10,6 +11,8 @@ export declare class SaveStreamWriter {
|
|
|
10
11
|
beginSave(): Promise<void>;
|
|
11
12
|
writeHeader(header: SatisfactorySaveHeader): Promise<void>;
|
|
12
13
|
writeCompressionInfo(compressionInfo: ChunkCompressionInfo): Promise<void>;
|
|
14
|
+
writeGridHash(gridHash: ByteArray4): Promise<void>;
|
|
15
|
+
writeGrids(grids: Grids): Promise<void>;
|
|
13
16
|
openLevels(): Promise<void>;
|
|
14
17
|
openLevel(levelName: string): Promise<void>;
|
|
15
18
|
writeObjects(...objects: SaveObject[]): Promise<void>;
|
|
@@ -30,9 +30,23 @@ class SaveStreamWriter {
|
|
|
30
30
|
await this.writer.write(`, "compressionInfo": ${JSON.stringify(compressionInfo)}`);
|
|
31
31
|
this.mode = 'WROTE_COMPRESSION_INFO';
|
|
32
32
|
}
|
|
33
|
-
async
|
|
33
|
+
async writeGridHash(gridHash) {
|
|
34
34
|
if (this.mode !== 'WROTE_COMPRESSION_INFO') {
|
|
35
|
-
throw new Error(`Wrong order of commands.
|
|
35
|
+
throw new Error(`Wrong order of commands. Save hbody hash has to come after compression info. mode is ${this.mode}.`);
|
|
36
|
+
}
|
|
37
|
+
await this.writer.write(`, "gridHash": ${JSON.stringify(gridHash)}`);
|
|
38
|
+
this.mode = 'WROTE_GRID_HASH';
|
|
39
|
+
}
|
|
40
|
+
async writeGrids(grids) {
|
|
41
|
+
if (this.mode !== 'WROTE_GRID_HASH') {
|
|
42
|
+
throw new Error(`Wrong order of commands. Grids have to come after save body hash info. mode is ${this.mode}.`);
|
|
43
|
+
}
|
|
44
|
+
await this.writer.write(`, "grids": ${JSON.stringify(grids)}`);
|
|
45
|
+
this.mode = 'WROTE_GRIDS';
|
|
46
|
+
}
|
|
47
|
+
async openLevels() {
|
|
48
|
+
if (this.mode !== 'WROTE_GRIDS') {
|
|
49
|
+
throw new Error(`Wrong order of commands. Levels have to come after grids info. mode is ${this.mode}.`);
|
|
36
50
|
}
|
|
37
51
|
await this.writer.write(`, "levels": [`);
|
|
38
52
|
this.mode = 'OPENED_LEVELS';
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@etothepii/satisfactory-file-parser",
|
|
3
3
|
"author": "etothepii",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.18",
|
|
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",
|