@etothepii/satisfactory-file-parser 0.1.2 → 0.1.4

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 CHANGED
@@ -19,7 +19,7 @@ U8 has only read support so far and only for save files, not for blueprint files
19
19
  |:--------------:|:-----------------------------|
20
20
  | <= U5 | ❌ |
21
21
  | U6 + U7 | ✅ 0.0.1 - 0.0.34 |
22
- | U8 | ⚠️ >= 0.1.0 ( .sav file Reading only so far) |
22
+ | U8 | ⚠️ >= 0.1.3 (Reading only) |
23
23
 
24
24
 
25
25
  ## Installation via npm
@@ -2,7 +2,6 @@
2
2
  import { ChunkSummary } from "./file.types";
3
3
  import { Blueprint } from "./satisfactory/blueprint/blueprint.types";
4
4
  import { SatisfactorySave } from "./satisfactory/save/satisfactory-save";
5
- import { SaveProjectionConfig } from "./satisfactory/save/save-reader";
6
5
  export declare class Parser {
7
6
  static WriteSave(save: SatisfactorySave, onBinaryBeforeCompressing: (buffer: ArrayBuffer) => void, onHeader: (header: Uint8Array) => void, onChunk: (chunk: Uint8Array) => void): ChunkSummary[];
8
7
  static ParseSaveFile(file: Buffer, onDecompressedSaveBody?: (buffer: ArrayBuffer) => void, onProgress?: (progress: number, message?: string) => void): SatisfactorySave;
@@ -11,6 +10,5 @@ export declare class Parser {
11
10
  configFileBinary: ArrayBuffer;
12
11
  };
13
12
  static ParseBlueprintFiles(name: string, blueprintFile: Buffer, blueprintConfigFile: Buffer, onDecompressedBlueprintBody?: (buffer: ArrayBuffer) => void): Blueprint;
14
- static ProjectSave(save: SatisfactorySave, config: SaveProjectionConfig): SatisfactorySave;
15
13
  static JSONStringifyModified: (obj: any, indent?: number) => string;
16
14
  }
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Parser = void 0;
4
4
  const blueprint_reader_1 = require("./satisfactory/blueprint/blueprint-reader");
5
5
  const blueprint_writer_1 = require("./satisfactory/blueprint/blueprint-writer");
6
- const level_class_1 = require("./satisfactory/save/level.class");
7
6
  const satisfactory_save_1 = require("./satisfactory/save/satisfactory-save");
8
7
  const save_reader_1 = require("./satisfactory/save/save-reader");
9
8
  const save_writer_1 = require("./satisfactory/save/save-writer");
@@ -23,7 +22,8 @@ class Parser {
23
22
  const save = new satisfactory_save_1.SatisfactorySave(header);
24
23
  const inflateResult = reader.inflateChunks();
25
24
  onDecompressedSaveBody(reader.getBuffer());
26
- save.levels = reader.readLevelsU8();
25
+ reader.readGrids();
26
+ save.levels = reader.readLevels();
27
27
  save.compressionInfo = reader.compressionInfo;
28
28
  save.trailingCollectedObjects = reader.trailingCollectedObjects;
29
29
  return save;
@@ -62,18 +62,6 @@ class Parser {
62
62
  };
63
63
  return blueprint;
64
64
  }
65
- static ProjectSave(save, config) {
66
- return {
67
- header: save.header,
68
- trailingCollectedObjects: save.trailingCollectedObjects,
69
- levels: save.levels.map(level => {
70
- const lvl = new level_class_1.Level(level.name);
71
- lvl.collectables = level.collectables;
72
- lvl.objects = level.objects.filter(obj => (0, save_reader_1.projectionFilterApplies)(config, obj));
73
- return lvl;
74
- })
75
- };
76
- }
77
65
  }
78
66
  Parser.JSONStringifyModified = (obj, indent = 0) => JSON.stringify(obj, (key, value) => {
79
67
  if (typeof value === 'bigint') {
@@ -10,6 +10,7 @@ export declare class BlueprintReader extends ByteReader {
10
10
  static ReadHeader(reader: BinaryReadable): BlueprintHeader;
11
11
  inflateChunks(): any;
12
12
  static ParseObjects(reader: ByteReader): (SaveEntity | SaveComponent)[];
13
+ private static ReadBlueprintObjectContents;
13
14
  }
14
15
  export declare class BlueprintConfigReader extends ByteReader {
15
16
  bluePrintConfigBuffer: ArrayBuffer;
@@ -8,7 +8,10 @@ const pako_1 = __importDefault(require("pako"));
8
8
  const alignment_enum_1 = require("../../byte/alignment.enum");
9
9
  const byte_reader_class_1 = require("../../byte/byte-reader.class");
10
10
  const parser_error_1 = require("../../error/parser.error");
11
+ const SaveComponent_1 = require("../objects/SaveComponent");
12
+ const SaveEntity_1 = require("../objects/SaveEntity");
11
13
  const level_class_1 = require("../save/level.class");
14
+ const save_reader_1 = require("../save/save-reader");
12
15
  const util_types_1 = require("../structs/util.types");
13
16
  class BlueprintReader extends byte_reader_class_1.ByteReader {
14
17
  constructor(bluePrintBuffer) {
@@ -16,7 +19,7 @@ class BlueprintReader extends byte_reader_class_1.ByteReader {
16
19
  this.compressionInfo = {
17
20
  packageFileTag: 0,
18
21
  maxChunkContentSize: 0,
19
- chunkHeaderSize: 48
22
+ chunkHeaderSize: save_reader_1.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
20
23
  };
21
24
  }
22
25
  static ReadHeader(reader) {
@@ -59,8 +62,8 @@ class BlueprintReader extends byte_reader_class_1.ByteReader {
59
62
  if (this.compressionInfo.maxChunkContentSize <= 0) {
60
63
  this.compressionInfo.maxChunkContentSize = chunkHeader.getInt32(8, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
61
64
  }
62
- const chunkCompressedLength = chunkHeader.getInt32(32, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
63
- const chunkUncompressedLength = chunkHeader.getInt32(40, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
65
+ const chunkCompressedLength = chunkHeader.getInt32(33, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
66
+ const chunkUncompressedLength = chunkHeader.getInt32(25, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
64
67
  totalUncompressedBodySize += chunkUncompressedLength;
65
68
  const currentChunkSize = chunkCompressedLength;
66
69
  let currentChunk = this.fileBuffer.slice(this.currentByte, this.currentByte + currentChunkSize);
@@ -98,9 +101,28 @@ class BlueprintReader extends byte_reader_class_1.ByteReader {
98
101
  const objectHeadersBinarySize = reader.readInt32();
99
102
  let objects = [];
100
103
  level_class_1.Level.ReadObjectHeaders(reader, objects, () => { });
101
- level_class_1.Level.ReadObjectContents('', reader, objects, 0, () => { });
104
+ const suomeChecksumThing = reader.readInt32();
105
+ BlueprintReader.ReadBlueprintObjectContents('', reader, objects, 0);
106
+ const pos = reader.getBufferPosition();
107
+ console.log(pos, reader.getBufferLength(), reader.getBufferProgress());
102
108
  return objects;
103
109
  }
110
+ static ReadBlueprintObjectContents(levelName, reader, objectsList, buildVersion) {
111
+ const countEntities = reader.readInt32();
112
+ for (let i = 0; i < countEntities; i++) {
113
+ const len = reader.readInt32();
114
+ if (len === 0) {
115
+ throw new parser_error_1.CorruptSaveError(`check number is a wrong value (${len}). This normally indicates a corrupt entity or save.`);
116
+ }
117
+ const obj = objectsList[i];
118
+ if ((0, SaveEntity_1.isSaveEntity)(obj)) {
119
+ SaveEntity_1.SaveEntity.ParseData(obj, len, reader, buildVersion, obj.typePath);
120
+ }
121
+ else if ((0, SaveComponent_1.isSaveComponent)(obj)) {
122
+ SaveComponent_1.SaveComponent.ParseData(obj, len, reader, buildVersion, obj.typePath);
123
+ }
124
+ }
125
+ }
104
126
  }
105
127
  exports.BlueprintReader = BlueprintReader;
106
128
  class BlueprintConfigReader extends byte_reader_class_1.ByteReader {
@@ -4,6 +4,7 @@ import { SaveWriter } from "../save/save-writer";
4
4
  import { vec3 } from "../structs/util.types";
5
5
  import { ObjectReference } from "./ObjectReference";
6
6
  import { AbstractBaseProperty } from "./Property";
7
+ import { SaveObject } from "./SaveObject";
7
8
  export type SpecialAnyProperty = {} | PowerLineSpecialProperty;
8
9
  export type PowerLineSpecialProperty = {
9
10
  num: number;
@@ -27,15 +28,11 @@ export type ConveyorSpecialProperty = {
27
28
  items: ItemSpecialProperty[];
28
29
  };
29
30
  export declare class DataFields {
30
- properties: AbstractBaseProperty[];
31
- specialProperties: SpecialAnyProperty;
32
- trailingData: number[];
33
- shouldBeNulled: boolean;
34
31
  constructor();
35
- static Parse(length: number, reader: BinaryReadable, buildVersion: number, typePath: string): DataFields;
32
+ static ParseProperties(obj: SaveObject, length: number, reader: BinaryReadable, buildVersion: number, typePath: string): void;
36
33
  static ParseAdditionalSpecialProperties(reader: BinaryReadable, typePath: string, remainingLen: number): SpecialAnyProperty;
37
34
  static ParseProperty(reader: BinaryReadable, buildVersion: number, propertyName: string): AbstractBaseProperty | null;
38
- static Serialize(writer: SaveWriter, fields: DataFields, buildVersion: number, typePath: string): void;
35
+ static Serialize(obj: SaveObject, writer: SaveWriter, buildVersion: number, typePath: string): void;
39
36
  static SerializeAdditionalSpecialProperties(writer: ByteWriter, typePath: string, property: SpecialAnyProperty): void;
40
37
  static SerializeProperty(writer: ByteWriter, property: AbstractBaseProperty, propertyName: string, buildVersion: number): void;
41
38
  }
@@ -5,39 +5,32 @@ const util_types_1 = require("../structs/util.types");
5
5
  const Property_1 = require("./Property");
6
6
  class DataFields {
7
7
  constructor() {
8
- this.properties = [];
9
- this.specialProperties = {};
10
- this.trailingData = [];
11
- this.shouldBeNulled = false;
12
8
  }
13
- static Parse(length, reader, buildVersion, typePath) {
9
+ static ParseProperties(obj, length, reader, buildVersion, typePath) {
14
10
  const start = reader.getBufferPosition();
15
- const fields = new DataFields();
11
+ obj.properties = {};
16
12
  if (length === 0) {
17
- fields.shouldBeNulled = true;
18
- return fields;
13
+ return;
19
14
  }
20
15
  let propertyName = reader.readString();
21
16
  while (propertyName !== 'None') {
22
17
  const property = DataFields.ParseProperty(reader, buildVersion, propertyName);
23
- fields.properties.push(property);
18
+ obj.properties[property.name] = property;
24
19
  propertyName = reader.readString();
25
20
  }
26
- let int1 = reader.readInt32();
27
- if (int1 !== 0) {
21
+ let padding = reader.readInt32();
22
+ if (padding !== 0) {
28
23
  }
29
24
  const remainingLen = length - (reader.getBufferPosition() - start);
30
- const oldRemainingLen = start + length - reader.getBufferPosition() - 4;
31
- fields.specialProperties = DataFields.ParseAdditionalSpecialProperties(reader, typePath, remainingLen);
25
+ obj.specialProperties = DataFields.ParseAdditionalSpecialProperties(reader, typePath, remainingLen);
32
26
  const end = reader.getBufferPosition();
33
27
  const newWayOfCalculatingReminingSize = length - (end - start);
34
28
  if (newWayOfCalculatingReminingSize > 0) {
35
- reader.readBytes(newWayOfCalculatingReminingSize);
29
+ obj.trailingData = Array.from(reader.readBytes(newWayOfCalculatingReminingSize));
36
30
  }
37
31
  else if (newWayOfCalculatingReminingSize < 0) {
38
- console.warn(`Unexpected. Did i read too much or is save corrupt !? bytes left to read is ${newWayOfCalculatingReminingSize}`);
32
+ throw new Error(`Unexpected. Read more bytes than are indicated for this etity. bytes left to read is ${newWayOfCalculatingReminingSize}`);
39
33
  }
40
- return fields;
41
34
  }
42
35
  static ParseAdditionalSpecialProperties(reader, typePath, remainingLen) {
43
36
  let property;
@@ -180,15 +173,15 @@ class DataFields {
180
173
  }
181
174
  return currentProperty;
182
175
  }
183
- static Serialize(writer, fields, buildVersion, typePath) {
184
- for (const property of fields.properties) {
176
+ static Serialize(obj, writer, buildVersion, typePath) {
177
+ for (const property of Object.values(obj.properties)) {
185
178
  writer.writeString(property.name);
186
179
  DataFields.SerializeProperty(writer, property, property.name, buildVersion);
187
180
  }
188
181
  writer.writeString('None');
189
182
  writer.writeInt32(0);
190
- DataFields.SerializeAdditionalSpecialProperties(writer, typePath, fields.specialProperties);
191
- writer.writeBytesArray(fields.trailingData);
183
+ DataFields.SerializeAdditionalSpecialProperties(writer, typePath, obj.specialProperties);
184
+ writer.writeBytesArray(obj.trailingData);
192
185
  }
193
186
  static SerializeAdditionalSpecialProperties(writer, typePath, property) {
194
187
  switch (typePath) {
@@ -34,23 +34,15 @@ class SaveEntity extends SaveObject_1.SaveObject {
34
34
  obj.wasPlacedInLevel = reader.readInt32() == 1;
35
35
  }
36
36
  static ParseData(entity, length, reader, buildVersion, typePath) {
37
- const typeOrWhat = reader.readInt32();
38
- const something = reader.readInt32();
39
37
  const afterSizeIndicator = reader.getBufferPosition();
40
- var newLen = length - 12;
41
38
  entity.parentObjectRoot = reader.readString();
42
- if (entity.parentObjectRoot.length > 0)
43
- newLen -= entity.parentObjectRoot.length + 1;
44
39
  entity.parentObjectName = reader.readString();
45
- if (entity.parentObjectName.length > 0)
46
- newLen -= entity.parentObjectName.length + 1;
47
40
  var componentCount = reader.readInt32();
48
41
  for (let i = 0; i < componentCount; i++) {
49
42
  var componentRef = ObjectReference_1.ObjectReference.Parse(reader);
50
43
  entity.components.push(componentRef);
51
- newLen -= 10 + componentRef.levelName.length + componentRef.pathName.length;
52
44
  }
53
- const remainingSize = something - (reader.getBufferPosition() - afterSizeIndicator);
45
+ const remainingSize = length - (reader.getBufferPosition() - afterSizeIndicator);
54
46
  return SaveObject_1.SaveObject.ParseData(entity, remainingSize, reader, buildVersion, typePath);
55
47
  }
56
48
  static SerializeHeader(writer, entity) {
@@ -1,6 +1,7 @@
1
1
  import { BinaryReadable } from "../../byte/binary-readable.interface";
2
2
  import { ByteWriter } from "../../byte/byte-writer.class";
3
- import { DataFields } from "./DataFields";
3
+ import { SpecialAnyProperty } from "./DataFields";
4
+ import { AbstractBaseProperty } from "./Property";
4
5
  export interface SaveObjectHeader {
5
6
  typePath: string;
6
7
  rootObject: string;
@@ -10,7 +11,13 @@ export declare abstract class SaveObject implements SaveObjectHeader {
10
11
  typePath: string;
11
12
  rootObject: string;
12
13
  instanceName: string;
13
- dataFields: DataFields;
14
+ properties: {
15
+ [name: string]: AbstractBaseProperty;
16
+ };
17
+ specialProperties: SpecialAnyProperty;
18
+ trailingData: number[];
19
+ unknownType1: number;
20
+ unknownType2: number;
14
21
  constructor(typePath: string, rootObject: string, instanceName: string);
15
22
  protected static ParseHeader(reader: BinaryReadable, obj: SaveObject): void;
16
23
  protected static SerializeHeader(writer: ByteWriter, obj: SaveObject): void;
@@ -7,7 +7,11 @@ class SaveObject {
7
7
  this.typePath = typePath;
8
8
  this.rootObject = rootObject;
9
9
  this.instanceName = instanceName;
10
- this.dataFields = new DataFields_1.DataFields();
10
+ this.properties = {};
11
+ this.specialProperties = {};
12
+ this.trailingData = [];
13
+ this.unknownType1 = 0;
14
+ this.unknownType2 = 0;
11
15
  }
12
16
  static ParseHeader(reader, obj) {
13
17
  obj.typePath = reader.readString();
@@ -20,10 +24,10 @@ class SaveObject {
20
24
  writer.writeString(obj.instanceName);
21
25
  }
22
26
  static ParseData(obj, length, reader, buildVersion, typePath) {
23
- obj.dataFields = DataFields_1.DataFields.Parse(length, reader, buildVersion, typePath);
27
+ DataFields_1.DataFields.ParseProperties(obj, length, reader, buildVersion, typePath);
24
28
  }
25
29
  static SerializeData(writer, obj, buildVersion) {
26
- DataFields_1.DataFields.Serialize(writer, obj.dataFields, buildVersion, obj.typePath);
30
+ DataFields_1.DataFields.Serialize(obj, writer, buildVersion, obj.typePath);
27
31
  }
28
32
  }
29
33
  exports.SaveObject = SaveObject;
@@ -77,10 +77,8 @@ class Level {
77
77
  break;
78
78
  }
79
79
  }
80
- const unk = reader.readInt32();
81
80
  }
82
81
  static async ReadObjectContents(levelName, reader, objectsList, buildVersion, onProgressCallback) {
83
- const posBefore = reader.getBufferPosition();
84
82
  const countEntities = reader.readInt32();
85
83
  if (countEntities !== objectsList.length) {
86
84
  throw new Error(`possibly corrupt. entity content count ${countEntities} does not equal object count of ${objectsList.length}`);
@@ -89,56 +87,45 @@ class Level {
89
87
  if (i % 1000 === 0) {
90
88
  onProgressCallback(reader.getBufferProgress());
91
89
  }
92
- const len = reader.readInt32();
93
- if (len === 0) {
94
- throw new Error(`check number is a wrong value (${len}). This normally indicates a corrupt entity or save.`);
95
- }
96
- const before = reader.getBufferPosition();
97
90
  const obj = objectsList[i];
91
+ obj.unknownType1 = reader.readInt32();
92
+ obj.unknownType2 = reader.readInt32();
93
+ const binarySize = reader.readInt32();
94
+ const before = reader.getBufferPosition();
98
95
  if ((0, SaveEntity_1.isSaveEntity)(obj)) {
99
- SaveEntity_1.SaveEntity.ParseData(obj, len, reader, buildVersion, obj.typePath);
96
+ SaveEntity_1.SaveEntity.ParseData(obj, binarySize, reader, buildVersion, obj.typePath);
100
97
  }
101
98
  else if ((0, SaveComponent_1.isSaveComponent)(obj)) {
102
- const wtf1 = reader.readInt32();
103
- const wtf2 = reader.readInt32();
104
- SaveComponent_1.SaveComponent.ParseData(obj, wtf2, reader, buildVersion, obj.typePath);
99
+ SaveComponent_1.SaveComponent.ParseData(obj, binarySize, reader, buildVersion, obj.typePath);
105
100
  }
106
101
  const after = reader.getBufferPosition();
107
- if (after - before !== len) {
102
+ if (after - before !== binarySize) {
103
+ console.warn('entity may be corrupt', obj.instanceName);
108
104
  }
109
105
  }
110
- const posAfter = reader.getBufferPosition();
111
106
  }
112
107
  static ReadLevel(reader, levelName, buildVersion) {
113
108
  const level = new Level(levelName);
114
109
  const levelStartPos = reader.getBufferPosition();
115
- const placeholderHead = reader.readInt32();
116
- if (placeholderHead === 8) {
117
- const morePlaceholder = reader.readBytes(28);
118
- }
119
- else {
120
- reader.skipBytes(-4);
121
- }
122
- const lookahead1 = reader.readInt32();
123
- const lookahead2 = reader.readInt32();
124
- if (lookahead1 !== 0 && lookahead2 !== 0) {
125
- reader.skipBytes(-8);
126
- return level;
127
- }
128
- else if (lookahead1 !== 0 && lookahead2 === 0) {
129
- reader.skipBytes(-8);
130
- }
131
- else {
132
- throw new Error('Unexpected. what now? TODO FIXME');
133
- }
134
- const binLen = reader.readInt32();
110
+ const headersBinLen = reader.readInt32();
135
111
  const unk = reader.readInt32();
112
+ const posBeforeHeaders = reader.getBufferPosition();
136
113
  Level.ReadObjectHeaders(reader, level.objects, reader.onProgressCallback);
114
+ const remainingSize = headersBinLen - (reader.getBufferPosition() - posBeforeHeaders);
115
+ if (remainingSize > 0) {
116
+ reader.readBytes(remainingSize);
117
+ }
118
+ const objectContentsBinLen = reader.readInt32();
137
119
  reader.onProgressCallback(reader.getBufferProgress());
138
120
  level.collectables = Level.ReadCollectablesList(reader);
121
+ const posBeforeContents = reader.getBufferPosition();
139
122
  Level.ReadObjectContents(levelName, reader, level.objects, buildVersion, reader.onProgressCallback);
123
+ const posAfterContents = reader.getBufferPosition();
124
+ if (posAfterContents - posBeforeContents !== objectContentsBinLen) {
125
+ console.warn('save seems corrupt.', this.name);
126
+ }
140
127
  reader.onProgressCallback(reader.getBufferProgress());
141
- Level.ReadCollectablesList(reader);
128
+ level.collectables = Level.ReadCollectablesList(reader);
142
129
  return level;
143
130
  }
144
131
  static SerializeCollectablesList(writer, collectables) {
@@ -1,9 +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
5
  import { SatisfactorySaveHeader } from "./save.types";
5
6
  export declare class SatisfactorySave {
6
7
  header: SatisfactorySaveHeader;
8
+ grids: Grids;
7
9
  levels: Level[];
8
10
  trailingCollectedObjects: ObjectReference[];
9
11
  compressionInfo?: ChunkCompressionInfo;
@@ -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.grids = {};
6
7
  this.levels = [];
7
8
  this.trailingCollectedObjects = [];
8
9
  this.header = header;
@@ -2,21 +2,15 @@ import { ByteReader } from "../../byte/byte-reader.class";
2
2
  import { ChunkCompressionInfo } from "../../file.types";
3
3
  import { SaveStreamWriter } from "../../stream/save-stream-writer.class";
4
4
  import { ObjectReference } from "../objects/ObjectReference";
5
- import { SaveComponent } from "../objects/SaveComponent";
6
- import { SaveEntity } from "../objects/SaveEntity";
7
5
  import { Level } from "./level.class";
8
6
  import { RoughSaveVersion, SatisfactorySaveHeader } from "./save.types";
9
- export type EntityPathFilter = {
10
- behavior: 'whitelist' | 'blacklist';
11
- list: string[];
12
- };
13
- export type SaveProjectionConfig = {
14
- entityPathFilter: EntityPathFilter;
15
- includeComponents: boolean;
16
- };
17
- export declare const projectionFilterApplies: (config: SaveProjectionConfig, object: SaveComponent | SaveEntity) => boolean;
18
7
  export type ReadMode = 'stream' | 'whole';
19
8
  export declare const DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = 49;
9
+ export type Grids = {
10
+ [parentName: string]: {
11
+ [level: number]: string[];
12
+ };
13
+ };
20
14
  export declare class SaveReader extends ByteReader {
21
15
  onProgressCallback: (progress: number, msg?: string) => void;
22
16
  header: SatisfactorySaveHeader | undefined;
@@ -31,6 +25,7 @@ export declare class SaveReader extends ByteReader {
31
25
  concatenatedChunkLength: number;
32
26
  numChunks: number;
33
27
  };
34
- readLevelsU8(): Level[];
28
+ readGrids: () => Grids;
29
+ readLevels(): Level[];
35
30
  readLevelsAsynchronously(writer: SaveStreamWriter): Promise<Level[]>;
36
31
  }
@@ -3,33 +3,13 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.SaveReader = exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = exports.projectionFilterApplies = void 0;
6
+ exports.SaveReader = exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = void 0;
7
7
  const pako_1 = __importDefault(require("pako"));
8
8
  const alignment_enum_1 = require("../../byte/alignment.enum");
9
9
  const byte_reader_class_1 = require("../../byte/byte-reader.class");
10
10
  const parser_error_1 = require("../../error/parser.error");
11
- const SaveComponent_1 = require("../objects/SaveComponent");
12
- const SaveEntity_1 = require("../objects/SaveEntity");
13
11
  const asynchronous_level_class_1 = require("./asynchronous-level.class");
14
12
  const level_class_1 = require("./level.class");
15
- const projectionFilterApplies = (config, object) => {
16
- let cond1 = false;
17
- if ((0, SaveEntity_1.isSaveEntity)(object) || (config.includeComponents && (0, SaveComponent_1.isSaveComponent)(object))) {
18
- cond1 = true;
19
- }
20
- let cond2 = true;
21
- if ((0, SaveEntity_1.isSaveEntity)(object)) {
22
- cond2 = false;
23
- if (config.entityPathFilter.behavior === 'whitelist') {
24
- cond2 = config.entityPathFilter.list.find(en => object.typePath.startsWith(en)) !== undefined;
25
- }
26
- else if (config.entityPathFilter.behavior === 'blacklist') {
27
- cond2 = config.entityPathFilter.list.find(en => object.typePath.startsWith(en)) === undefined;
28
- }
29
- }
30
- return cond1 && cond2;
31
- };
32
- exports.projectionFilterApplies = projectionFilterApplies;
33
13
  exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE = 49;
34
14
  class SaveReader extends byte_reader_class_1.ByteReader {
35
15
  constructor(fileBuffer, onProgressCallback = () => { }) {
@@ -42,6 +22,40 @@ class SaveReader extends byte_reader_class_1.ByteReader {
42
22
  maxChunkContentSize: 0,
43
23
  chunkHeaderSize: exports.DEFAULT_SATISFACTORY_CHUNK_HEADER_SIZE
44
24
  };
25
+ this.readGrids = () => {
26
+ const unk = this.readInt32();
27
+ const cannotBeLevelCountWhatIsIt = this.readInt32();
28
+ const noneString = this.readString();
29
+ const unk2 = this.readInt32();
30
+ const hm1 = this.readInt32();
31
+ const unk3 = this.readInt32();
32
+ const noneString2 = this.readString();
33
+ const grids = {};
34
+ const readLevelsList = (childrenCount) => {
35
+ const hmm = this.readInt32();
36
+ const parentName = this.readString();
37
+ const whatever1 = this.readInt32();
38
+ const whatever2 = this.readInt32();
39
+ grids[parentName] = {};
40
+ for (let i = 0; i < childrenCount; i++) {
41
+ const binaryLenIGuess = this.readInt32();
42
+ 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(levelInstanceName);
49
+ }
50
+ };
51
+ readLevelsList(1379);
52
+ readLevelsList(0);
53
+ readLevelsList(972);
54
+ readLevelsList(0);
55
+ readLevelsList(0);
56
+ const unk5 = this.readInt32();
57
+ return grids;
58
+ };
45
59
  }
46
60
  readHeader() {
47
61
  this.header = {
@@ -82,10 +96,7 @@ class SaveReader extends byte_reader_class_1.ByteReader {
82
96
  this.header.saveIdentifier = this.readString();
83
97
  }
84
98
  if (this.header.saveHeaderType >= 13) {
85
- const unk1 = this.readInt32();
86
- const unk2 = this.readInt32();
87
- const unk3 = this.readBytes(16);
88
- const unk4 = this.readInt32();
99
+ this.header.unknownStuff = Array.from(this.readBytes(28));
89
100
  }
90
101
  if (this.header.saveVersion >= 21) {
91
102
  }
@@ -148,7 +159,7 @@ class SaveReader extends byte_reader_class_1.ByteReader {
148
159
  numChunks: currentChunks.length
149
160
  };
150
161
  }
151
- readLevelsU8() {
162
+ readLevels() {
152
163
  if (!this.header) {
153
164
  throw new parser_error_1.ParserError('ParserError', 'Header must be set before objects can be read.');
154
165
  }
@@ -158,36 +169,6 @@ class SaveReader extends byte_reader_class_1.ByteReader {
158
169
  if (this.header.saveHeaderType < 13) {
159
170
  throw new parser_error_1.UnsupportedVersionError('Game Version < U8 is not supported in this package version. Consider downgrading to the latest package version supporting it, which is 0.0.34');
160
171
  }
161
- const unk = this.readInt32();
162
- const cannotBeLevelCountWhatIsIt = this.readInt32();
163
- const noneString = this.readString();
164
- const unk2 = this.readInt32();
165
- const hm1 = this.readInt32();
166
- const unk3 = this.readInt32();
167
- const noneString2 = this.readString();
168
- const grids = {};
169
- const readLevelsList = (childrenCount) => {
170
- const hmm = this.readInt32();
171
- const parentName = this.readString();
172
- const whatever = this.readBytes(8);
173
- grids[parentName] = {};
174
- for (let i = 0; i < childrenCount; i++) {
175
- const binaryLenIGuess = this.readInt32();
176
- const levelInstanceName = this.readString();
177
- const lod = /\_L([0-9]+)\_/.exec(levelInstanceName)[1];
178
- const arr = grids[parentName][Number(lod)];
179
- if (!arr) {
180
- grids[parentName][Number(lod)] = [];
181
- }
182
- grids[parentName][Number(lod)].push(levelInstanceName);
183
- }
184
- };
185
- readLevelsList(1379);
186
- readLevelsList(0);
187
- readLevelsList(972);
188
- readLevelsList(0);
189
- readLevelsList(0);
190
- const unk5 = this.readInt32();
191
172
  const levels = [];
192
173
  const levelCount = this.readInt32();
193
174
  this.onProgressCallback(this.getBufferProgress(), `reading pack of ${levelCount} levels.`);
@@ -201,6 +182,7 @@ class SaveReader extends byte_reader_class_1.ByteReader {
201
182
  const appendedLevel = new level_class_1.Level(this.header.mapName);
202
183
  levels.push(appendedLevel);
203
184
  level_class_1.Level.ReadObjectHeaders(this, appendedLevel.objects, this.onProgressCallback);
185
+ const unk = this.readInt32();
204
186
  appendedLevel.collectables = level_class_1.Level.ReadCollectablesList(this);
205
187
  level_class_1.Level.ReadObjectContents(appendedLevel.name, this, appendedLevel.objects, this.header.buildVersion, this.onProgressCallback);
206
188
  level_class_1.Level.ReadCollectablesList(this);
@@ -38,6 +38,9 @@ class SaveWriter extends byte_writer_class_1.ByteWriter {
38
38
  if (header.saveHeaderType >= 10) {
39
39
  writer.writeString(header.saveIdentifier);
40
40
  }
41
+ if (header.saveHeaderType >= 13) {
42
+ writer.writeBytesArray(header.unknownStuff);
43
+ }
41
44
  if (header.saveVersion >= 21) {
42
45
  }
43
46
  else {
@@ -21,6 +21,7 @@ export interface SatisfactorySaveHeader {
21
21
  isModdedSave?: number;
22
22
  saveIdentifier?: string;
23
23
  fEditorObjectVersion?: number;
24
+ unknownStuff?: number[];
24
25
  }
25
26
  export type RoughSaveVersion = '<U6' | 'U6/U7' | 'U8+';
26
27
  export type ResourceNodeEntry = {
@@ -0,0 +1,40 @@
1
+ /// <reference types="node" />
2
+ import { Readable } from "stream";
3
+ import { Alignment } from "../byte/alignment.enum";
4
+ export declare class StreamParserReader {
5
+ minBufferSize: number;
6
+ private buffer;
7
+ view: DataView;
8
+ private hasInputStreamEnded;
9
+ currentByte: number;
10
+ alignment: Alignment;
11
+ private inputBuffer;
12
+ private input;
13
+ hasDataToRead: boolean;
14
+ private onInputDataAvailableCallback;
15
+ constructor(minBufferSize: number);
16
+ getAmountLeftToRead: () => number;
17
+ parseLogic: () => Promise<void>;
18
+ private allocate;
19
+ private waitForAmountLeftToRead;
20
+ private wait;
21
+ private shiftBufferIntoOperatingDataView;
22
+ private readTilBufferFullOrInputEmpty;
23
+ startReading(input: Readable): void;
24
+ skipBytes(byteLength?: number): void;
25
+ readByte(): number;
26
+ readBytes(count: number): Uint8Array;
27
+ readHex(hexLength: number): string;
28
+ readInt8(): number;
29
+ readUint8(): number;
30
+ readInt16(): number;
31
+ readUint16(): number;
32
+ readInt32(): number;
33
+ readUint32(): number;
34
+ readLong(): bigint;
35
+ readInt64(): bigint;
36
+ readUint64(): bigint;
37
+ readFloat32(): number;
38
+ readDouble(): number;
39
+ readString(): string;
40
+ }
@@ -0,0 +1,225 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StreamParserReader = void 0;
4
+ const alignment_enum_1 = require("../byte/alignment.enum");
5
+ class StreamParserReader {
6
+ constructor(minBufferSize) {
7
+ this.minBufferSize = minBufferSize;
8
+ this.alignment = alignment_enum_1.Alignment.LITTLE_ENDIAN;
9
+ this.getAmountLeftToRead = () => {
10
+ return this.view.byteLength - this.currentByte;
11
+ };
12
+ this.parseLogic = async () => {
13
+ console.log('before allocate 1', this.getAmountLeftToRead(), this.inputBuffer.length);
14
+ await this.allocate(100000);
15
+ const unknownStuff = [
16
+ this.readInt32(),
17
+ this.readInt32(),
18
+ this.readString(),
19
+ this.readInt32(),
20
+ this.readInt32(),
21
+ this.readInt32(),
22
+ this.readString()
23
+ ];
24
+ console.log('before allocate 2', unknownStuff, this.getAmountLeftToRead(), this.inputBuffer.length);
25
+ await this.allocate(10000);
26
+ };
27
+ this.allocate = async (amount) => {
28
+ if (amount > this.minBufferSize) {
29
+ throw new Error(`Can not attempt to allocate (${amount}) more than the size of the buffer (${this.minBufferSize}) is.`);
30
+ }
31
+ if (this.getAmountLeftToRead() >= amount) {
32
+ return Promise.resolve();
33
+ }
34
+ return new Promise((resolve, reject) => {
35
+ console.log('Waiting for allocate', amount, this.getAmountLeftToRead());
36
+ this.readTilBufferFullOrInputEmpty();
37
+ console.log('shift?', this.inputBuffer.length);
38
+ if (this.inputBuffer.length > 0 && this.inputBuffer.length + this.getAmountLeftToRead() >= amount) {
39
+ this.shiftBufferIntoOperatingDataView();
40
+ return resolve();
41
+ }
42
+ this.onInputDataAvailableCallback = () => {
43
+ console.log('on available callback.', this.inputBuffer.length, this.getAmountLeftToRead(), amount);
44
+ if (this.inputBuffer.length > 0 && this.inputBuffer.length + this.getAmountLeftToRead() >= amount) {
45
+ this.shiftBufferIntoOperatingDataView();
46
+ console.log(this.getAmountLeftToRead());
47
+ this.onInputDataAvailableCallback = undefined;
48
+ return resolve();
49
+ }
50
+ };
51
+ });
52
+ };
53
+ this.waitForAmountLeftToRead = async (amount, callback = () => { }) => {
54
+ while (this.getAmountLeftToRead() < amount) {
55
+ if (this.hasInputStreamEnded) {
56
+ throw new Error(`Unexpected end of stream. Waited to read ${amount} but stream ended prematurely.`);
57
+ }
58
+ console.log('###waiting... for ', amount);
59
+ this.readTilBufferFullOrInputEmpty();
60
+ if (this.inputBuffer.length > 0) {
61
+ console.log('###shift.');
62
+ this.shiftBufferIntoOperatingDataView();
63
+ }
64
+ await this.wait(20);
65
+ }
66
+ console.log('amount left to read is no longer smaller than', amount, this.getAmountLeftToRead());
67
+ callback();
68
+ };
69
+ this.wait = (ms) => new Promise((resolve, reject) => {
70
+ setTimeout(() => {
71
+ return resolve(ms);
72
+ }, ms);
73
+ });
74
+ this.shiftBufferIntoOperatingDataView = (amountOfTrailingBytesToKeep = 300) => {
75
+ const resultingTrailingBytes = Math.min(this.currentByte, amountOfTrailingBytesToKeep);
76
+ const trailingPlusLeftToRead = new Uint8Array(this.buffer.slice(this.currentByte - resultingTrailingBytes));
77
+ const { length, ...chunks } = this.inputBuffer;
78
+ const inBuffer = new Uint8Array(Buffer.concat(Object.values(chunks)));
79
+ this.inputBuffer = { length: 0 };
80
+ this.buffer = new Uint8Array(trailingPlusLeftToRead.byteLength + inBuffer.byteLength);
81
+ this.buffer.set(trailingPlusLeftToRead, 0);
82
+ this.buffer.set(inBuffer, trailingPlusLeftToRead.byteLength);
83
+ this.currentByte = resultingTrailingBytes;
84
+ this.view = new DataView(this.buffer.buffer);
85
+ };
86
+ this.readTilBufferFullOrInputEmpty = () => {
87
+ let chunk;
88
+ console.log(this.hasDataToRead, this.inputBuffer.length, this.minBufferSize);
89
+ while (this.hasDataToRead && this.inputBuffer.length < this.minBufferSize && (chunk = this.input.read()) != null) {
90
+ console.log('reading chunk', chunk);
91
+ this.inputBuffer[chunk.byteLength] = chunk;
92
+ this.inputBuffer.length += chunk.length;
93
+ }
94
+ if (chunk === null) {
95
+ console.log('chunk is null, input empty for now.');
96
+ this.hasDataToRead = false;
97
+ }
98
+ else if (this.inputBuffer.length >= this.minBufferSize) {
99
+ console.log('buffer is full, ignoring for now.', this.inputBuffer.length);
100
+ }
101
+ console.log('data available callback?', this.onInputDataAvailableCallback);
102
+ if (this.onInputDataAvailableCallback) {
103
+ this.onInputDataAvailableCallback();
104
+ }
105
+ };
106
+ this.buffer = new Uint8Array(0);
107
+ this.view = new DataView(this.buffer.buffer);
108
+ this.currentByte = 0;
109
+ this.hasInputStreamEnded = false;
110
+ this.inputBuffer = { length: 0 };
111
+ this.hasDataToRead = false;
112
+ }
113
+ startReading(input) {
114
+ this.input = input;
115
+ this.input.pause();
116
+ this.input.on('readable', () => {
117
+ console.log('on readable. Reading til buffer full or input empty.');
118
+ this.hasDataToRead = true;
119
+ this.readTilBufferFullOrInputEmpty();
120
+ });
121
+ this.input.on('end', () => {
122
+ this.hasDataToRead = false;
123
+ this.hasInputStreamEnded = true;
124
+ });
125
+ this.input.on('close', () => {
126
+ this.hasDataToRead = false;
127
+ this.hasInputStreamEnded = true;
128
+ });
129
+ }
130
+ skipBytes(byteLength = 1) {
131
+ this.currentByte += byteLength;
132
+ return;
133
+ }
134
+ readByte() {
135
+ return parseInt(this.view.getUint8(this.currentByte++).toString());
136
+ }
137
+ readBytes(count) {
138
+ return new Uint8Array(new Array(count).fill(0).map(pl => this.view.getUint8(this.currentByte++)));
139
+ }
140
+ readHex(hexLength) {
141
+ let hexPart = [];
142
+ for (let i = 0; i < hexLength; i++) {
143
+ let currentHex = String.fromCharCode(this.view.getUint8(this.currentByte++));
144
+ hexPart.push(currentHex);
145
+ }
146
+ return hexPart.join('');
147
+ }
148
+ readInt8() {
149
+ let data = this.view.getInt8(this.currentByte++);
150
+ return data;
151
+ }
152
+ readUint8() {
153
+ let data = this.view.getUint8(this.currentByte++);
154
+ return data;
155
+ }
156
+ readInt16() {
157
+ let data = this.view.getInt16(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
158
+ this.currentByte += 2;
159
+ return data;
160
+ }
161
+ readUint16() {
162
+ let data = this.view.getUint16(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
163
+ this.currentByte += 2;
164
+ return data;
165
+ }
166
+ readInt32() {
167
+ let data = this.view.getInt32(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
168
+ this.currentByte += 4;
169
+ return data;
170
+ }
171
+ readUint32() {
172
+ let data = this.view.getUint32(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
173
+ this.currentByte += 4;
174
+ return data;
175
+ }
176
+ readLong() {
177
+ let data = this.view.getBigInt64(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
178
+ this.currentByte += 8;
179
+ return data;
180
+ }
181
+ readInt64() {
182
+ return this.readLong();
183
+ }
184
+ readUint64() {
185
+ let data = this.view.getBigUint64(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
186
+ this.currentByte += 8;
187
+ return data;
188
+ }
189
+ readFloat32() {
190
+ let data = this.view.getFloat32(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
191
+ this.currentByte += 4;
192
+ return data;
193
+ }
194
+ readDouble() {
195
+ let data = this.view.getFloat64(this.currentByte, this.alignment === alignment_enum_1.Alignment.LITTLE_ENDIAN);
196
+ this.currentByte += 8;
197
+ return data;
198
+ }
199
+ readString() {
200
+ let strLength = this.readInt32();
201
+ if (strLength === 0) {
202
+ return '';
203
+ }
204
+ if (strLength > (this.view.buffer.byteLength - this.currentByte)) {
205
+ let errorMessage = `Cannot read string of length ${strLength} at position ${this.currentByte} as it exceeds the end at ${this.view.buffer.byteLength}`;
206
+ throw new Error(errorMessage);
207
+ }
208
+ if (strLength < 0) {
209
+ const string = new Array(-strLength - 1).fill('').map(c => String.fromCharCode(this.readUint16()));
210
+ this.currentByte += 2;
211
+ return string.join('');
212
+ }
213
+ try {
214
+ const string = new Array(strLength - 1).fill('').map(c => String.fromCharCode(this.readUint8()));
215
+ this.currentByte += 1;
216
+ return string.join('');
217
+ }
218
+ catch (error) {
219
+ let errorMessage = `Cannot read UTF8 string of length ${strLength} at position ${this.currentByte}.`;
220
+ console.log(errorMessage, error);
221
+ throw error;
222
+ }
223
+ }
224
+ }
225
+ exports.StreamParserReader = StreamParserReader;
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.2",
4
+ "version": "0.1.4",
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",