@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.
Files changed (29) hide show
  1. package/README.md +3 -5
  2. package/build/parser/byte/byte-reader.class.d.ts +2 -1
  3. package/build/parser/byte/byte-reader.class.js +5 -7
  4. package/build/parser/file.types.d.ts +5 -1
  5. package/build/parser/file.types.js +5 -0
  6. package/build/parser/parser.js +5 -0
  7. package/build/parser/satisfactory/blueprint/blueprint-reader.js +7 -6
  8. package/build/parser/satisfactory/blueprint/blueprint.types.d.ts +2 -1
  9. package/build/parser/satisfactory/objects/Property.d.ts +4 -6
  10. package/build/parser/satisfactory/objects/Property.js +65 -5
  11. package/build/parser/satisfactory/objects/SaveObject.d.ts +1 -1
  12. package/build/parser/satisfactory/objects/SaveObject.js +1 -1
  13. package/build/parser/satisfactory/objects/ue/FMD5Hash.d.ts +7 -0
  14. package/build/parser/satisfactory/objects/ue/FMD5Hash.js +19 -0
  15. package/build/parser/satisfactory/save/asynchronous-level.class.js +2 -2
  16. package/build/parser/satisfactory/save/level.class.d.ts +2 -1
  17. package/build/parser/satisfactory/save/level.class.js +22 -14
  18. package/build/parser/satisfactory/save/satisfactory-save.d.ts +2 -1
  19. package/build/parser/satisfactory/save/satisfactory-save.js +1 -0
  20. package/build/parser/satisfactory/save/save-reader.d.ts +8 -4
  21. package/build/parser/satisfactory/save/save-reader.js +54 -38
  22. package/build/parser/satisfactory/save/save-writer.d.ts +2 -1
  23. package/build/parser/satisfactory/save/save-writer.js +38 -25
  24. package/build/parser/satisfactory/save/save.types.d.ts +4 -1
  25. package/build/parser/stream/save-stream-json-stringifier.js +2 -0
  26. package/build/parser/stream/save-stream-reader.class.js +3 -3
  27. package/build/parser/stream/save-stream-writer.class.d.ts +3 -0
  28. package/build/parser/stream/save-stream-writer.class.js +16 -2
  29. 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 | ⚠️ >= 0.1.16 (readig for both saves and blueprints, writing only for blueprints) |
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 etotheepii
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
- readHex(hexLength: number): string;
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
- readHex(hexLength) {
38
- let hexPart = [];
39
- for (let i = 0; i < hexLength; i++) {
40
- let currentHex = String.fromCharCode(this.bufferView.getUint8(this.currentByte++));
41
- hexPart.push(currentHex);
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
- maxChunkContentSize: number;
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 = {}));
@@ -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
- maxChunkContentSize: 0,
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 probablyDimensions = (0, util_types_1.ParseVec3f)(reader);
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.maxChunkContentSize <= 0) {
64
- this.compressionInfo.maxChunkContentSize = chunkHeader.getInt32(8, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
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(32, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
67
- const chunkUncompressedLength = chunkHeader.getInt32(24, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
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 GENERIC_MAP_KEY_TYPE = number | ObjectReference | boolean | GENERIC_STRUCT_PROPERTY_VALUE;
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('Not Implemented.');
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('Not Implemented.');
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.skipBytes(12);
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.writeBytesArray(property.remainingData);
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
- unknownType1: number;
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;
@@ -10,7 +10,7 @@ class SaveObject {
10
10
  this.properties = {};
11
11
  this.specialProperties = {};
12
12
  this.trailingData = [];
13
- this.unknownType1 = 0;
13
+ this.saveOrBlueprintIndicator = 0;
14
14
  this.unknownType2 = 0;
15
15
  }
16
16
  static ParseHeader(reader, obj) {
@@ -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 + 4);
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 + 4);
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.unknownType1 = reader.readInt32();
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
- const remainingSize = headersBinLen - (reader.getBufferPosition() - posBeforeHeaders);
118
+ let remainingSize = headersBinLen - (reader.getBufferPosition() - posBeforeHeaders);
115
119
  if (remainingSize > 0) {
116
- reader.readBytes(remainingSize);
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[];
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.SatisfactorySave = void 0;
4
4
  class SatisfactorySave {
5
5
  constructor(header) {
6
+ this.gridHash = [0, 0, 0, 0];
6
7
  this.grids = {};
7
8
  this.levels = [];
8
9
  this.trailingCollectedObjects = [];
@@ -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
- [level: number]: {
12
- name: string;
13
- size: number;
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
- maxChunkContentSize: 0,
46
+ maxUncompressedChunkContentSize: 0,
23
47
  chunkHeaderSize: exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
24
48
  };
25
- this.readGrids = () => {
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 hm1 = this.readInt32();
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 readLevelsList = (childrenCount) => {
35
- const hmm = this.readInt32();
61
+ const readGrid = (childrenCount) => {
62
+ const parentChecksumIdOrSmth = this.readUint32();
36
63
  const parentName = this.readString();
37
- const whatever1 = this.readInt32();
38
- const whatever2 = this.readInt32();
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 binaryLenIGuess = this.readUint32();
68
+ const cellHash = this.readUint32();
42
69
  const levelInstanceName = this.readString();
43
- const lod = /\_L([0-9]+)\_/.exec(levelInstanceName)[1];
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
- readLevelsList(1379);
55
- readLevelsList(0);
56
- readLevelsList(972);
57
- readLevelsList(0);
58
- readLevelsList(0);
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.unknownStuff = Array.from(this.readBytes(28));
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.maxChunkContentSize <= 0) {
126
- this.compressionInfo.maxChunkContentSize = chunkHeader.getInt32(8, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
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.ReadCollectablesList(this);
183
- this.onProgressCallback(this.getBufferProgress(), `finished reading levels pack of ${levelCount}.`);
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.writeBytesArray(header.unknownStuff);
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.maxChunkContentSize, saveBody.byteLength - handledByte);
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, 572662306, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
85
- view.setInt32(8, compressionInfo.maxChunkContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
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.setInt32(16, compressedChunk.byteLength, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
88
- view.setInt32(20, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
89
- view.setInt32(24, uncompressedContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
90
- view.setInt32(28, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
91
- view.setInt32(32, compressedChunk.byteLength, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
92
- view.setInt32(36, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
93
- view.setInt32(40, uncompressedContentSize, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
94
- view.setInt32(44, 0, alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
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.WriteGrids = (writer, grids) => {
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.writeInt32(0);
121
- writer.writeInt32(0);
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(0);
136
+ writer.writeInt32(parentEntry[1].checksum);
125
137
  writer.writeString(parentEntry[0]);
126
- writer.writeInt32(0);
127
- writer.writeInt32(0);
128
- for (const child of Object.entries(parentEntry[1]).flatMap(entry => entry[1])) {
129
- writer.writeUint32(child.size);
130
- writer.writeString(child.name);
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
- unknownStuff?: number[];
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
- maxChunkContentSize: 0,
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.maxChunkContentSize <= 0) {
77
- this.compressionInfo.maxChunkContentSize = this.operatingDataView.getInt32(8, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
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 openLevels() {
33
+ async writeGridHash(gridHash) {
34
34
  if (this.mode !== 'WROTE_COMPRESSION_INFO') {
35
- throw new Error(`Wrong order of commands. Levels have to come after compression info. mode is ${this.mode}.`);
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.16",
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",