@zebbedaja/er-save-parser 0.0.6 → 0.0.7

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
@@ -62,7 +62,7 @@ async function parseFromFile(file: File): Promise<Save> {
62
62
  ### `Save` (root)
63
63
  | Field | Type | Description |
64
64
  |---|---|---|
65
- | `magicBytes` | `string` | Hex string, always `424e4434` (`BND4`) |
65
+ | `magicBytes` | `string` | Hex string, e.g. `424e4434` (`BND4`) |
66
66
  | `checksum` | `string` | Save file checksum |
67
67
  | `version` | `number` | Save file version |
68
68
  | `steamId` | `string` | Associated Steam64 ID |
@@ -155,7 +155,7 @@ try {
155
155
  ```
156
156
 
157
157
  The parser will throw if:
158
- - The file magic bytes don't match `BND4`
158
+ - The file magic bytes don't match `BND4` or `SL2\x00`
159
159
  - Event flags reference blocks not found in the BST map
160
160
  - Calculated byte positions exceed save data bounds
161
161
 
package/dist/index.d.mts CHANGED
@@ -139,7 +139,6 @@ interface EventFlag {
139
139
  }
140
140
  //#endregion
141
141
  //#region src/parser.d.ts
142
- declare function fn(): string;
143
142
  declare function parse(buffer: ArrayBuffer): Save;
144
143
  //#endregion
145
- export { Character, EventFlag, ProfileSummary, Save, Settings, Slot, fn, parse };
144
+ export { type Character, type EventFlag, type ProfileSummary, type Save, type Settings, type Slot, parse };
package/dist/index.mjs CHANGED
@@ -1,4 +1,11 @@
1
1
  //#region src/util.ts
2
+ /**
3
+ * Compare two ArrayBuffers for byte-by-byte equality.
4
+ *
5
+ * @param buf1 - The first ArrayBuffer to compare
6
+ * @param buf2 - The second ArrayBuffer to compare
7
+ * @returns True if both buffers have identical byte content
8
+ */
2
9
  function arrayBuffersEqual(buf1, buf2) {
3
10
  if (buf1.byteLength !== buf2.byteLength) return false;
4
11
  const view1 = new Uint8Array(buf1);
@@ -6,25 +13,58 @@ function arrayBuffersEqual(buf1, buf2) {
6
13
  for (let i = 0; i < view1.length; i++) if (view1[i] !== view2[i]) return false;
7
14
  return true;
8
15
  }
16
+ /**
17
+ * Convert a string into an array of byte values.
18
+ *
19
+ * @param string - The string to convert
20
+ * @returns An array of ASCII/Unicode byte values for each character
21
+ */
9
22
  function stringToBytes(string) {
10
23
  return [...string].map((character) => character.charCodeAt(0));
11
24
  }
25
+ /**
26
+ * Convert an ArrayBuffer to a lowercase hexadecimal string.
27
+ *
28
+ * @param buffer - The ArrayBuffer to convert
29
+ * @returns A lowercase hexadecimal string representation of the buffer
30
+ */
12
31
  function toHexString(buffer) {
13
32
  const bytes = new Uint8Array(buffer);
14
33
  return Array.from(bytes).map((b) => b.toString(16).padStart(2, "0")).join("");
15
34
  }
35
+ /**
36
+ * Remove all null ('\x00') characters from a string.
37
+ *
38
+ * @param text - The string to clean
39
+ * @returns The input string with all null ('\x00') characters removed
40
+ */
16
41
  const trim = (text) => {
17
42
  return text?.replaceAll("\0", "");
18
43
  };
19
- const parseToMap = (text) => {
44
+ /**
45
+ * Parse a string of delimiter-separated "key,value" pairs into a Map.
46
+ *
47
+ * @param text - A string of delimiter-separated pairs, one per line
48
+ * @param delimiter - The character separating key and value (default: ",")
49
+ * @returns A Map with numeric keys and values parsed from the input
50
+ */
51
+ const parseToMap = (text, delimiter = ",") => {
20
52
  const map = /* @__PURE__ */ new Map();
21
53
  const lines = text.trim().split("\n");
22
54
  for (const line of lines) {
23
- const [key, value] = line.trim().split(",");
55
+ const [key, value] = line.trim().split(delimiter);
24
56
  if (key && value !== void 0) map.set(Number(key), Number(value));
25
57
  }
26
58
  return map;
27
59
  };
60
+ /**
61
+ * Determine whether a specific event flag is set.
62
+ *
63
+ * @param bstMap - A map of block IDs to their binary offsets
64
+ * @param eventFlags - The raw event_flags byte array from the save data
65
+ * @param eventId - The event ID to check
66
+ * @returns True if the event flag is set (active), false otherwise
67
+ */
28
68
  const getEventFlagState = (bstMap, eventFlags, eventId) => {
29
69
  const FLAG_DIVISOR = 1e3;
30
70
  const BLOCK_SIZE = 125;
@@ -42,6 +82,11 @@ const getEventFlagState = (bstMap, eventFlags, eventId) => {
42
82
  //#endregion
43
83
  //#region src/event-flags.ts
44
84
  const eventFlags = [
85
+ {
86
+ name: "Playthrough Complete: Age of Fracture",
87
+ id: 20,
88
+ category: "ending"
89
+ },
45
90
  {
46
91
  name: "Playthrough Complete: Age of Stars",
47
92
  id: 21,
@@ -52,6 +97,111 @@ const eventFlags = [
52
97
  id: 22,
53
98
  category: "ending"
54
99
  },
100
+ {
101
+ name: "Story: Start",
102
+ id: 100,
103
+ category: "story"
104
+ },
105
+ {
106
+ name: "Story: Reached Tutorial",
107
+ id: 101,
108
+ category: "story"
109
+ },
110
+ {
111
+ name: "Story: Reached Limgrave",
112
+ id: 102,
113
+ category: "story"
114
+ },
115
+ {
116
+ name: "Story: Reached Roundtable Hold",
117
+ id: 104,
118
+ category: "story"
119
+ },
120
+ {
121
+ name: "Story: Roundtable Hold Introduction",
122
+ id: 105,
123
+ category: "story"
124
+ },
125
+ {
126
+ name: "Story: Received the Frenzied Flame",
127
+ id: 108,
128
+ category: "story"
129
+ },
130
+ {
131
+ name: "Story: Received the Frenzied Flame (back)",
132
+ id: 109,
133
+ category: "story"
134
+ },
135
+ {
136
+ name: "Story: Reached Forge of the Giants",
137
+ id: 110,
138
+ category: "story"
139
+ },
140
+ {
141
+ name: "Story: Forge of the Giants - Melina A",
142
+ id: 111,
143
+ category: "story"
144
+ },
145
+ {
146
+ name: "Story: Forge of the Giants - Melina B",
147
+ id: 112,
148
+ category: "story"
149
+ },
150
+ {
151
+ name: "Story: Ranni",
152
+ id: 114,
153
+ category: "story"
154
+ },
155
+ {
156
+ name: "Story: Frenzied Flame Nullified",
157
+ id: 116,
158
+ category: "story"
159
+ },
160
+ {
161
+ name: "Story: Erdtree on Fire",
162
+ id: 118,
163
+ category: "story"
164
+ },
165
+ {
166
+ name: "Story: Watched Ending",
167
+ id: 120,
168
+ category: "story"
169
+ },
170
+ {
171
+ name: "Acquired Godrick's Great Rune",
172
+ id: 191,
173
+ category: "great-rune"
174
+ },
175
+ {
176
+ name: "Acquired Radahn's Great Rune",
177
+ id: 192,
178
+ category: "great-rune"
179
+ },
180
+ {
181
+ name: "Acquired Morgott's Great Rune",
182
+ id: 193,
183
+ category: "great-rune"
184
+ },
185
+ {
186
+ name: "Acquired Rykard's Great Rune",
187
+ id: 194,
188
+ category: "great-rune"
189
+ },
190
+ {
191
+ name: "Acquired Mohg's Great Rune",
192
+ id: 195,
193
+ category: "great-rune"
194
+ },
195
+ {
196
+ name: "Acquired Malenia's Great Rune",
197
+ id: 196,
198
+ category: "great-rune"
199
+ },
200
+ {
201
+ name: "Acquired Great Rune of the Unborn",
202
+ id: 197,
203
+ category: "great-rune"
204
+ },
55
205
  {
56
206
  id: 10000800,
57
207
  name: "Godrick the Grafted",
@@ -13222,9 +13372,6 @@ const bstFile = `1045540,6223
13222
13372
  const USER_10_DATA_START = 26215328;
13223
13373
  const ACTIVE_PROFILES_START = 26221828;
13224
13374
  const SLOT_COUNT = 10;
13225
- function fn() {
13226
- return "Hello, tsdown!";
13227
- }
13228
13375
  function parse(buffer) {
13229
13376
  const dataView = new DataView(buffer);
13230
13377
  const utf16leDecoder = new TextDecoder("utf-16le");
@@ -13233,7 +13380,7 @@ function parse(buffer) {
13233
13380
  const save = {};
13234
13381
  const magicBytes = buffer.slice(offset, offset + 4);
13235
13382
  offset += 4;
13236
- if (!arrayBuffersEqual(magicBytes, new Uint8Array(stringToBytes("BND4")).buffer)) throw new Error(`File type not supported, magic bytes: ${toHexString(magicBytes)} (${utf8Decoder.decode(magicBytes)})`);
13383
+ if (!arrayBuffersEqual(magicBytes, new Uint8Array(stringToBytes("BND4")).buffer) && !arrayBuffersEqual(magicBytes, new Uint8Array(stringToBytes("SL2\0")).buffer)) throw new Error(`File type not supported, magic bytes: ${toHexString(magicBytes)} (${utf8Decoder.decode(magicBytes)})`);
13237
13384
  save.magicBytes = toHexString(magicBytes);
13238
13385
  offset += 764;
13239
13386
  save.slots = [];
@@ -13378,6 +13525,7 @@ function parse(buffer) {
13378
13525
  }
13379
13526
  slot.regions = {};
13380
13527
  slot.regions.regionCount = regionCount;
13528
+ slot.regions.regionIds = regionIds;
13381
13529
  offset += 40;
13382
13530
  offset += 1;
13383
13531
  offset += 68;
@@ -13494,4 +13642,4 @@ function parse(buffer) {
13494
13642
  return save;
13495
13643
  }
13496
13644
  //#endregion
13497
- export { fn, parse };
13645
+ export { parse };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@zebbedaja/er-save-parser",
3
3
  "type": "module",
4
- "version": "0.0.6",
4
+ "version": "0.0.7",
5
5
  "description": "A parser for Elden Ring save files written in TypesScript.",
6
6
  "author": "zebbedaja",
7
7
  "license": "MIT",
@@ -33,15 +33,15 @@
33
33
  },
34
34
  "devDependencies": {
35
35
  "@eslint/js": "^10.0.1",
36
- "@types/node": "^25.7.0",
36
+ "@types/node": "^25.9.1",
37
37
  "@typescript/native-preview": "7.0.0-dev.20260509.2",
38
38
  "bumpp": "^11.1.0",
39
- "eslint": "^10.3.0",
39
+ "eslint": "^10.4.1",
40
40
  "eslint-config-prettier": "^10.1.8",
41
41
  "prettier": "^3.8.3",
42
- "tsdown": "^0.22.0",
42
+ "tsdown": "^0.22.1",
43
43
  "typescript": "^6.0.3",
44
- "typescript-eslint": "^8.59.3",
45
- "vitest": "^4.1.6"
44
+ "typescript-eslint": "^8.60.0",
45
+ "vitest": "^4.1.7"
46
46
  }
47
47
  }