@d2runewizard/d2s 2.0.133 → 2.0.135

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 (41) hide show
  1. package/lib/binary/bitreader.d.ts +23 -23
  2. package/lib/binary/bitreader.js +107 -107
  3. package/lib/binary/bitwriter.d.ts +21 -21
  4. package/lib/binary/bitwriter.js +117 -117
  5. package/lib/binary/constants.d.ts +4 -4
  6. package/lib/binary/constants.js +8 -8
  7. package/lib/binary/seekToSequence.d.ts +2 -2
  8. package/lib/binary/seekToSequence.js +12 -12
  9. package/lib/d2/attribute_enhancer.d.ts +5 -5
  10. package/lib/d2/attribute_enhancer.js +598 -598
  11. package/lib/d2/attributes.d.ts +4 -4
  12. package/lib/d2/attributes.js +98 -98
  13. package/lib/d2/constants.d.ts +4 -4
  14. package/lib/d2/constants.js +15 -15
  15. package/lib/d2/d2s.d.ts +10 -10
  16. package/lib/d2/d2s.js +115 -115
  17. package/lib/d2/header.d.ts +8 -8
  18. package/lib/d2/header.js +94 -94
  19. package/lib/d2/items.d.ts +19 -19
  20. package/lib/d2/items.js +879 -870
  21. package/lib/d2/items.js.map +1 -1
  22. package/lib/d2/skills.d.ts +4 -4
  23. package/lib/d2/skills.js +45 -45
  24. package/lib/d2/stash.d.ts +3 -3
  25. package/lib/d2/stash.js +380 -380
  26. package/lib/d2/types.d.ts +467 -462
  27. package/lib/d2/types.js +14 -14
  28. package/lib/d2/versions/default_header.d.ts +5 -5
  29. package/lib/d2/versions/default_header.js +750 -750
  30. package/lib/data/parser.d.ts +3 -3
  31. package/lib/data/parser.js +626 -626
  32. package/lib/data/versions/105_constant_data.d.ts +16150 -16150
  33. package/lib/data/versions/105_constant_data.js +24316 -24316
  34. package/lib/data/versions/105_constant_data.js.map +1 -1
  35. package/lib/data/versions/96_constant_data.d.ts +14427 -14427
  36. package/lib/data/versions/96_constant_data.js +4 -4
  37. package/lib/data/versions/99_constant_data.d.ts +15572 -15572
  38. package/lib/data/versions/99_constant_data.js +4 -4
  39. package/lib/index.d.ts +8 -8
  40. package/lib/index.js +46 -46
  41. package/package.json +50 -50
package/lib/d2/stash.js CHANGED
@@ -1,381 +1,381 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
- }) : (function(o, m, k, k2) {
6
- if (k2 === undefined) k2 = k;
7
- o[k2] = m[k];
8
- }));
9
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
- Object.defineProperty(o, "default", { enumerable: true, value: v });
11
- }) : function(o, v) {
12
- o["default"] = v;
13
- });
14
- var __importStar = (this && this.__importStar) || function (mod) {
15
- if (mod && mod.__esModule) return mod;
16
- var result = {};
17
- if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
- __setModuleDefault(result, mod);
19
- return result;
20
- };
21
- Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.write = exports.read = void 0;
23
- const types = __importStar(require("./types"));
24
- const bitwriter_1 = require("../binary/bitwriter");
25
- const items = __importStar(require("./items"));
26
- const attribute_enhancer_1 = require("./attribute_enhancer");
27
- const bitreader_1 = require("../binary/bitreader");
28
- const constants_1 = require("./constants");
29
- const defaultConfig = {
30
- extendedStash: false,
31
- };
32
- async function read(buffer, constants, version, userConfig) {
33
- const stash = {};
34
- const reader = new bitreader_1.BitReader(buffer);
35
- const config = Object.assign(defaultConfig, userConfig);
36
- const firstHeader = reader.ReadUInt32();
37
- reader.SeekByte(0);
38
- if (firstHeader == 0xaa55aa55) {
39
- stash.pages = [];
40
- stash.sharedGold = 0;
41
- let pageCount = 0;
42
- while (reader.offset < reader.bits.length && pageCount < 6) {
43
- const pageIndex = pageCount;
44
- pageCount++;
45
- await readStashHeader(stash, reader, pageIndex);
46
- const saveVersion = version || parseInt(stash.version);
47
- if (!constants) {
48
- constants = constants_1.getConstantData(saveVersion);
49
- }
50
- await readStashPart(stash, reader, saveVersion, constants, pageIndex);
51
- }
52
- const saveVersion = version || parseInt(stash.version);
53
- if (!constants) {
54
- constants = constants_1.getConstantData(saveVersion);
55
- }
56
- await readChronicle(stash, reader, saveVersion, constants);
57
- stash.pageCount = pageCount;
58
- }
59
- else {
60
- await readStashHeader(stash, reader);
61
- const saveVersion = version || parseInt(stash.version);
62
- if (!constants) {
63
- constants = constants_1.getConstantData(saveVersion);
64
- }
65
- await readStashPages(stash, reader, saveVersion, constants);
66
- }
67
- return stash;
68
- }
69
- exports.read = read;
70
- async function readStashHeader(stash, reader, pageIndex) {
71
- const header = reader.ReadUInt32();
72
- switch (header) {
73
- // Resurrected
74
- case 0xaa55aa55:
75
- stash.type = types.EStashType.shared;
76
- stash.hardcore = reader.ReadUInt32() == 0;
77
- stash.version = reader.ReadUInt32().toString();
78
- const version = parseInt(stash.version);
79
- stash.sharedGold += reader.ReadUInt32();
80
- reader.ReadUInt32(); // size of the sector
81
- if (version === 0x69 && pageIndex !== undefined) {
82
- const isStackable = reader.ReadByte();
83
- stash.pages[pageIndex] = {
84
- items: [],
85
- name: "",
86
- type: 0,
87
- isStackable,
88
- };
89
- reader.SkipBytes(43);
90
- }
91
- else {
92
- reader.SkipBytes(44); // empty
93
- }
94
- break;
95
- // LoD
96
- case 0x535353: // SSS
97
- case 0x4d545343: // CSTM
98
- stash.version = reader.ReadString(2);
99
- if (stash.version !== "01" && stash.version !== "02") {
100
- throw new Error(`unkown stash version ${stash.version} at position ${reader.offset - 2 * 8}`);
101
- }
102
- stash.type = header === 0x535353 ? types.EStashType.shared : types.EStashType.private;
103
- if (stash.type === types.EStashType.shared && stash.version == "02") {
104
- stash.sharedGold = reader.ReadUInt32();
105
- }
106
- if (stash.type === types.EStashType.private) {
107
- reader.ReadUInt32();
108
- stash.sharedGold = 0;
109
- }
110
- stash.pageCount = reader.ReadUInt32();
111
- break;
112
- default:
113
- debugger;
114
- throw new Error(`shared stash header 'SSS' / 0xAA55AA55 / private stash header 'CSTM' not found at position ${reader.offset - 3 * 8}`);
115
- }
116
- }
117
- async function readStashPages(stash, reader, version, constants) {
118
- stash.pages = [];
119
- for (let i = 0; i < stash.pageCount; i++) {
120
- await readStashPage(stash, reader, version, constants);
121
- }
122
- }
123
- const CHRONICLE_SECTION_MAGIC = 0xaa55aa55;
124
- const CHRONICLE_MAGIC = 0xc0eaedc0;
125
- const CHRONICLE_VERSION = 1;
126
- const emptyChronicle = (trailing = new Uint8Array(0)) => ({
127
- setItems: [],
128
- uniqueItems: [],
129
- runewords: [],
130
- _unknown_data: {
131
- envelopePadding: new Uint8Array(44),
132
- trailing,
133
- originalCounts: { setItems: 0, uniqueItems: 0, runewords: 0 },
134
- },
135
- });
136
- async function readChronicle(stash, reader, version, constants) {
137
- const remainingBytes = (reader.bits.length - reader.offset) / 8;
138
- // The chronicle section is wrapped in the same 64-byte sector envelope used by regular
139
- // stash pages (magic, hardcore flag, save version, unused gold slot, size of sector, padding).
140
- if (remainingBytes < 64 || reader.ReadUInt32() !== CHRONICLE_SECTION_MAGIC) {
141
- stash.chronicle = emptyChronicle(reader.ReadBytes(Math.max(0, remainingBytes - 4)));
142
- return;
143
- }
144
- reader.ReadUInt32(); // hardcore flag, mirrors the stash header, unused here
145
- reader.ReadUInt32(); // save version, mirrors stash.version
146
- reader.ReadUInt32(); // unused (gold slot on regular pages)
147
- reader.ReadUInt32(); // size of sector
148
- // Not fully understood (mirrors the isStackable-byte + padding slot on real v0x69 pages); preserved verbatim rather than assumed to be zero.
149
- const envelopePadding = reader.ReadBytes(44);
150
- if (reader.ReadUInt32() !== CHRONICLE_MAGIC) {
151
- stash.chronicle = emptyChronicle();
152
- return;
153
- }
154
- reader.ReadUInt16(); // chronicle format version
155
- const setCount = reader.ReadUInt16();
156
- const uniqueCount = reader.ReadUInt16();
157
- const runewordCount = reader.ReadUInt16();
158
- reader.SkipBytes(8); // reserved
159
- const readRawEntries = (count) => {
160
- const entries = [];
161
- for (let i = 0; i < count; i++) {
162
- const itemId = reader.ReadUInt32();
163
- const monster = reader.ReadUInt16();
164
- const foundAt = reader.ReadUInt32() * 60; // minutes since epoch -> unix seconds
165
- entries.push({ itemId, monster, foundAt });
166
- }
167
- return entries;
168
- };
169
- const setEntries = readRawEntries(setCount);
170
- const uniqueEntries = readRawEntries(uniqueCount);
171
- const runewordEntries = readRawEntries(runewordCount);
172
- // Not fully understood (its content shifts and changes as entries are added/removed, likely some
173
- // kind of progress/summary cache); preserved verbatim so an unmodified read+write roundtrips exactly.
174
- // Editing the entry lists and writing back will NOT correctly regenerate this region.
175
- const trailing = reader.ReadBytes((reader.bits.length - reader.offset) / 8);
176
- stash.chronicle = {
177
- setItems: setEntries.map(({ itemId, monster, foundAt }) => ({
178
- item: resolveChronicleItem(itemId, constants.set_items),
179
- monster,
180
- foundAt,
181
- })),
182
- uniqueItems: uniqueEntries.map(({ itemId, monster, foundAt }) => ({
183
- item: resolveChronicleItem(itemId, constants.unq_items),
184
- monster,
185
- foundAt,
186
- })),
187
- runewords: runewordEntries.map(({ itemId, monster, foundAt }) => ({
188
- item: resolveChronicleItem(itemId, constants.runewords, RUNEWORD_ID_OFFSET, RUNEWORD_ID_OVERRIDES),
189
- monster,
190
- foundAt,
191
- })),
192
- _unknown_data: {
193
- envelopePadding,
194
- trailing,
195
- originalCounts: { setItems: setCount, uniqueItems: uniqueCount, runewords: runewordCount },
196
- },
197
- };
198
- }
199
- // Runeword chronicle ids don't map 1:1 onto constants.runewords' own indices. Most fall at a
200
- // fixed offset (constants.runewords has leading null placeholders before the first real entry),
201
- // but a small, patch-specific cluster of ids use unrelated ids entirely and have no known formula
202
- // - those need to be hardcoded here as they're discovered/reported. As of 105_constant_data,
203
- // the still-unmapped ids are: 10910, 27360-27367, 27650-27652, 27974.
204
- const RUNEWORD_ID_OFFSET = 20480;
205
- const RUNEWORD_ID_OVERRIDES = {
206
- 10910: 48,
207
- 27360: 196,
208
- 27361: 197,
209
- 27362: 198,
210
- 27363: 199,
211
- 27364: 200,
212
- 27365: 201,
213
- 27366: 202,
214
- 27367: 203,
215
- 27650: 204,
216
- 27651: 205,
217
- 27652: 206,
218
- 27974: 207,
219
- };
220
- function resolveChronicleItem(id, table, offset = 0, overrides = {}) {
221
- const index = overrides[id] !== undefined ? overrides[id] : id - offset;
222
- const data = table === null || table === void 0 ? void 0 : table[index];
223
- return Object.assign({ id }, data);
224
- }
225
- async function writeChronicle(stash, version) {
226
- var _a;
227
- if (!stash.chronicle) {
228
- return new Uint8Array();
229
- }
230
- const { setItems, uniqueItems, runewords, _unknown_data } = stash.chronicle;
231
- const writer = new bitwriter_1.BitWriter();
232
- writer.WriteUInt32(CHRONICLE_SECTION_MAGIC);
233
- writer.WriteUInt32(stash.hardcore ? 0 : version === 0x69 ? 2 : 1);
234
- writer.WriteUInt32(version);
235
- writer.WriteUInt32(0); // unused (gold slot on regular pages)
236
- writer.WriteUInt32(0); // size of the sector, fixed below
237
- writer.WriteBytes((_a = _unknown_data === null || _unknown_data === void 0 ? void 0 : _unknown_data.envelopePadding) !== null && _a !== void 0 ? _a : new Uint8Array(44));
238
- writer.WriteUInt32(CHRONICLE_MAGIC);
239
- writer.WriteUInt16(CHRONICLE_VERSION);
240
- writer.WriteUInt16(setItems.length);
241
- writer.WriteUInt16(uniqueItems.length);
242
- writer.WriteUInt16(runewords.length);
243
- writer.WriteBytes(new Uint8Array(8).fill(0)); // reserved
244
- const writeEntry = (itemId, monster, foundAt) => {
245
- writer.WriteUInt32(itemId);
246
- writer.WriteUInt16(monster);
247
- writer.WriteUInt32(Math.floor(foundAt / 60)); // unix seconds -> minutes since epoch
248
- };
249
- // Entries are stored newest-first in real saves; enforce that order on write regardless of how
250
- // the caller built the list, rather than relying on callers to insert/remove at the right spot.
251
- const newestFirst = (entries) => [...entries].sort((a, b) => b.foundAt - a.foundAt);
252
- for (const entry of newestFirst(setItems))
253
- writeEntry(entry.item.id, entry.monster, entry.foundAt);
254
- for (const entry of newestFirst(uniqueItems))
255
- writeEntry(entry.item.id, entry.monster, entry.foundAt);
256
- for (const entry of newestFirst(runewords))
257
- writeEntry(entry.item.id, entry.monster, entry.foundAt);
258
- // Not fully understood. If the entry lists are unchanged from what was read, re-emit the
259
- // captured bytes verbatim so an unmodified roundtrip matches byte-for-byte. If entries were
260
- // added/removed, those captured bytes are stale (this region's content shifts with entry count),
261
- // so zero it instead - a 637-entry real save had this region fully zeroed, suggesting all-zero is
262
- // an accepted state, whereas carrying forward stale bytes for a different entry count is not.
263
- const countsUnchanged = (_unknown_data === null || _unknown_data === void 0 ? void 0 : _unknown_data.originalCounts) &&
264
- _unknown_data.originalCounts.setItems === setItems.length &&
265
- _unknown_data.originalCounts.uniqueItems === uniqueItems.length &&
266
- _unknown_data.originalCounts.runewords === runewords.length;
267
- writer.WriteBytes(countsUnchanged ? _unknown_data.trailing : new Uint8Array(64));
268
- const size = writer.offset;
269
- writer.SeekByte(16);
270
- writer.WriteUInt32(Math.ceil(size / 8));
271
- return writer.ToArray();
272
- }
273
- async function readStashPage(stash, reader, version, constants) {
274
- const page = {
275
- items: [],
276
- name: "",
277
- type: 0,
278
- };
279
- const header = reader.ReadString(2);
280
- if (header !== "ST") {
281
- throw new Error(`can not read stash page header ST at position ${reader.offset - 2 * 8}`);
282
- }
283
- page.type = reader.ReadUInt32();
284
- page.name = reader.ReadNullTerminatedString();
285
- page.items = await items.readItems(reader, version, constants, defaultConfig);
286
- attribute_enhancer_1.enhanceItems(page.items, constants, 1);
287
- stash.pages.push(page);
288
- }
289
- async function readStashPart(stash, reader, version, constants, pageIndex) {
290
- const currentPage = pageIndex !== undefined ? stash.pages[pageIndex] : undefined;
291
- const page = currentPage || {
292
- items: [],
293
- name: "",
294
- type: 0,
295
- };
296
- page.items = await items.readItems(reader, version, constants, defaultConfig);
297
- attribute_enhancer_1.enhanceItems(page.items, constants, 1);
298
- if (!currentPage) {
299
- stash.pages.push(page);
300
- }
301
- }
302
- async function write(data, constants, version, userConfig) {
303
- const config = Object.assign(defaultConfig, userConfig);
304
- const writer = new bitwriter_1.BitWriter();
305
- if (!constants) {
306
- constants = constants_1.getConstantData(version);
307
- }
308
- if (version > 0x61) {
309
- for (const page of data.pages) {
310
- writer.WriteArray(await writeStashSection(data, page, constants, config));
311
- }
312
- if (version === 0x69 && data.chronicle) {
313
- writer.WriteArray(await writeChronicle(data, version));
314
- }
315
- }
316
- else {
317
- writer.WriteArray(await writeStashHeader(data));
318
- writer.WriteArray(await writeStashPages(data, version, constants, config));
319
- }
320
- return writer.ToArray();
321
- }
322
- exports.write = write;
323
- async function writeStashHeader(data) {
324
- const writer = new bitwriter_1.BitWriter();
325
- if (data.type === types.EStashType.private) {
326
- writer.WriteString("CSTM", 4);
327
- }
328
- else {
329
- writer.WriteString("SSS", 4);
330
- }
331
- writer.WriteString(data.version, data.version.length);
332
- if (data.type === types.EStashType.private) {
333
- writer.WriteString("", 4);
334
- }
335
- else {
336
- if (data.version == "02") {
337
- writer.WriteUInt32(data.sharedGold);
338
- }
339
- }
340
- writer.WriteUInt32(data.pages.length);
341
- return writer.ToArray();
342
- }
343
- async function writeStashSection(data, page, constants, userConfig) {
344
- const writer = new bitwriter_1.BitWriter();
345
- const version = parseInt(data.version);
346
- writer.WriteUInt32(0xaa55aa55);
347
- writer.WriteUInt32(data.hardcore ? 0 : version === 0x69 ? 2 : 1); // change to 0x02
348
- writer.WriteUInt32(version);
349
- const maxGold = Math.min(data.sharedGold, 2500000);
350
- data.sharedGold = Math.max(0, data.sharedGold - maxGold);
351
- writer.WriteUInt32(maxGold);
352
- writer.WriteUInt32(0); // size of the sector, to be fixed later
353
- if (version === 0x69) {
354
- writer.WriteByte(page.isStackable || 0);
355
- writer.WriteBytes(new Uint8Array(43).fill(0)); // empty
356
- }
357
- else {
358
- writer.WriteBytes(new Uint8Array(44).fill(0)); // empty
359
- }
360
- writer.WriteArray(await items.writeItems(page.items, parseInt(data.version), constants, userConfig));
361
- const size = writer.offset;
362
- writer.SeekByte(16);
363
- writer.WriteUInt32(Math.ceil(size / 8));
364
- return writer.ToArray();
365
- }
366
- async function writeStashPages(data, version, constants, config) {
367
- const writer = new bitwriter_1.BitWriter();
368
- for (let i = 0; i < data.pages.length; i++) {
369
- writer.WriteArray(await writeStashPage(data.pages[i], version, constants, config));
370
- }
371
- return writer.ToArray();
372
- }
373
- async function writeStashPage(data, version, constants, config) {
374
- const writer = new bitwriter_1.BitWriter();
375
- writer.WriteString("ST", 2);
376
- writer.WriteUInt32(data.type);
377
- writer.WriteString(data.name, data.name.length + 1);
378
- writer.WriteArray(await items.writeItems(data.items, version, constants, config));
379
- return writer.ToArray();
380
- }
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
20
+ };
21
+ Object.defineProperty(exports, "__esModule", { value: true });
22
+ exports.write = exports.read = void 0;
23
+ const types = __importStar(require("./types"));
24
+ const bitwriter_1 = require("../binary/bitwriter");
25
+ const items = __importStar(require("./items"));
26
+ const attribute_enhancer_1 = require("./attribute_enhancer");
27
+ const bitreader_1 = require("../binary/bitreader");
28
+ const constants_1 = require("./constants");
29
+ const defaultConfig = {
30
+ extendedStash: false,
31
+ };
32
+ async function read(buffer, constants, version, userConfig) {
33
+ const stash = {};
34
+ const reader = new bitreader_1.BitReader(buffer);
35
+ const config = Object.assign(defaultConfig, userConfig);
36
+ const firstHeader = reader.ReadUInt32();
37
+ reader.SeekByte(0);
38
+ if (firstHeader == 0xaa55aa55) {
39
+ stash.pages = [];
40
+ stash.sharedGold = 0;
41
+ let pageCount = 0;
42
+ while (reader.offset < reader.bits.length && pageCount < 6) {
43
+ const pageIndex = pageCount;
44
+ pageCount++;
45
+ await readStashHeader(stash, reader, pageIndex);
46
+ const saveVersion = version || parseInt(stash.version);
47
+ if (!constants) {
48
+ constants = constants_1.getConstantData(saveVersion);
49
+ }
50
+ await readStashPart(stash, reader, saveVersion, constants, pageIndex);
51
+ }
52
+ const saveVersion = version || parseInt(stash.version);
53
+ if (!constants) {
54
+ constants = constants_1.getConstantData(saveVersion);
55
+ }
56
+ await readChronicle(stash, reader, saveVersion, constants);
57
+ stash.pageCount = pageCount;
58
+ }
59
+ else {
60
+ await readStashHeader(stash, reader);
61
+ const saveVersion = version || parseInt(stash.version);
62
+ if (!constants) {
63
+ constants = constants_1.getConstantData(saveVersion);
64
+ }
65
+ await readStashPages(stash, reader, saveVersion, constants);
66
+ }
67
+ return stash;
68
+ }
69
+ exports.read = read;
70
+ async function readStashHeader(stash, reader, pageIndex) {
71
+ const header = reader.ReadUInt32();
72
+ switch (header) {
73
+ // Resurrected
74
+ case 0xaa55aa55:
75
+ stash.type = types.EStashType.shared;
76
+ stash.hardcore = reader.ReadUInt32() == 0;
77
+ stash.version = reader.ReadUInt32().toString();
78
+ const version = parseInt(stash.version);
79
+ stash.sharedGold += reader.ReadUInt32();
80
+ reader.ReadUInt32(); // size of the sector
81
+ if (version === 0x69 && pageIndex !== undefined) {
82
+ const isStackable = reader.ReadByte();
83
+ stash.pages[pageIndex] = {
84
+ items: [],
85
+ name: "",
86
+ type: 0,
87
+ isStackable,
88
+ };
89
+ reader.SkipBytes(43);
90
+ }
91
+ else {
92
+ reader.SkipBytes(44); // empty
93
+ }
94
+ break;
95
+ // LoD
96
+ case 0x535353: // SSS
97
+ case 0x4d545343: // CSTM
98
+ stash.version = reader.ReadString(2);
99
+ if (stash.version !== "01" && stash.version !== "02") {
100
+ throw new Error(`unkown stash version ${stash.version} at position ${reader.offset - 2 * 8}`);
101
+ }
102
+ stash.type = header === 0x535353 ? types.EStashType.shared : types.EStashType.private;
103
+ if (stash.type === types.EStashType.shared && stash.version == "02") {
104
+ stash.sharedGold = reader.ReadUInt32();
105
+ }
106
+ if (stash.type === types.EStashType.private) {
107
+ reader.ReadUInt32();
108
+ stash.sharedGold = 0;
109
+ }
110
+ stash.pageCount = reader.ReadUInt32();
111
+ break;
112
+ default:
113
+ debugger;
114
+ throw new Error(`shared stash header 'SSS' / 0xAA55AA55 / private stash header 'CSTM' not found at position ${reader.offset - 3 * 8}`);
115
+ }
116
+ }
117
+ async function readStashPages(stash, reader, version, constants) {
118
+ stash.pages = [];
119
+ for (let i = 0; i < stash.pageCount; i++) {
120
+ await readStashPage(stash, reader, version, constants);
121
+ }
122
+ }
123
+ const CHRONICLE_SECTION_MAGIC = 0xaa55aa55;
124
+ const CHRONICLE_MAGIC = 0xc0eaedc0;
125
+ const CHRONICLE_VERSION = 1;
126
+ const emptyChronicle = (trailing = new Uint8Array(0)) => ({
127
+ setItems: [],
128
+ uniqueItems: [],
129
+ runewords: [],
130
+ _unknown_data: {
131
+ envelopePadding: new Uint8Array(44),
132
+ trailing,
133
+ originalCounts: { setItems: 0, uniqueItems: 0, runewords: 0 },
134
+ },
135
+ });
136
+ async function readChronicle(stash, reader, version, constants) {
137
+ const remainingBytes = (reader.bits.length - reader.offset) / 8;
138
+ // The chronicle section is wrapped in the same 64-byte sector envelope used by regular
139
+ // stash pages (magic, hardcore flag, save version, unused gold slot, size of sector, padding).
140
+ if (remainingBytes < 64 || reader.ReadUInt32() !== CHRONICLE_SECTION_MAGIC) {
141
+ stash.chronicle = emptyChronicle(reader.ReadBytes(Math.max(0, remainingBytes - 4)));
142
+ return;
143
+ }
144
+ reader.ReadUInt32(); // hardcore flag, mirrors the stash header, unused here
145
+ reader.ReadUInt32(); // save version, mirrors stash.version
146
+ reader.ReadUInt32(); // unused (gold slot on regular pages)
147
+ reader.ReadUInt32(); // size of sector
148
+ // Not fully understood (mirrors the isStackable-byte + padding slot on real v0x69 pages); preserved verbatim rather than assumed to be zero.
149
+ const envelopePadding = reader.ReadBytes(44);
150
+ if (reader.ReadUInt32() !== CHRONICLE_MAGIC) {
151
+ stash.chronicle = emptyChronicle();
152
+ return;
153
+ }
154
+ reader.ReadUInt16(); // chronicle format version
155
+ const setCount = reader.ReadUInt16();
156
+ const uniqueCount = reader.ReadUInt16();
157
+ const runewordCount = reader.ReadUInt16();
158
+ reader.SkipBytes(8); // reserved
159
+ const readRawEntries = (count) => {
160
+ const entries = [];
161
+ for (let i = 0; i < count; i++) {
162
+ const itemId = reader.ReadUInt32();
163
+ const monster = reader.ReadUInt16();
164
+ const foundAt = reader.ReadUInt32() * 60; // minutes since epoch -> unix seconds
165
+ entries.push({ itemId, monster, foundAt });
166
+ }
167
+ return entries;
168
+ };
169
+ const setEntries = readRawEntries(setCount);
170
+ const uniqueEntries = readRawEntries(uniqueCount);
171
+ const runewordEntries = readRawEntries(runewordCount);
172
+ // Not fully understood (its content shifts and changes as entries are added/removed, likely some
173
+ // kind of progress/summary cache); preserved verbatim so an unmodified read+write roundtrips exactly.
174
+ // Editing the entry lists and writing back will NOT correctly regenerate this region.
175
+ const trailing = reader.ReadBytes((reader.bits.length - reader.offset) / 8);
176
+ stash.chronicle = {
177
+ setItems: setEntries.map(({ itemId, monster, foundAt }) => ({
178
+ item: resolveChronicleItem(itemId, constants.set_items),
179
+ monster,
180
+ foundAt,
181
+ })),
182
+ uniqueItems: uniqueEntries.map(({ itemId, monster, foundAt }) => ({
183
+ item: resolveChronicleItem(itemId, constants.unq_items),
184
+ monster,
185
+ foundAt,
186
+ })),
187
+ runewords: runewordEntries.map(({ itemId, monster, foundAt }) => ({
188
+ item: resolveChronicleItem(itemId, constants.runewords, RUNEWORD_ID_OFFSET, RUNEWORD_ID_OVERRIDES),
189
+ monster,
190
+ foundAt,
191
+ })),
192
+ _unknown_data: {
193
+ envelopePadding,
194
+ trailing,
195
+ originalCounts: { setItems: setCount, uniqueItems: uniqueCount, runewords: runewordCount },
196
+ },
197
+ };
198
+ }
199
+ // Runeword chronicle ids don't map 1:1 onto constants.runewords' own indices. Most fall at a
200
+ // fixed offset (constants.runewords has leading null placeholders before the first real entry),
201
+ // but a small, patch-specific cluster of ids use unrelated ids entirely and have no known formula
202
+ // - those need to be hardcoded here as they're discovered/reported. As of 105_constant_data,
203
+ // the still-unmapped ids are: 10910, 27360-27367, 27650-27652, 27974.
204
+ const RUNEWORD_ID_OFFSET = 20480;
205
+ const RUNEWORD_ID_OVERRIDES = {
206
+ 10910: 48,
207
+ 27360: 196,
208
+ 27361: 197,
209
+ 27362: 198,
210
+ 27363: 199,
211
+ 27364: 200,
212
+ 27365: 201,
213
+ 27366: 202,
214
+ 27367: 203,
215
+ 27650: 204,
216
+ 27651: 205,
217
+ 27652: 206,
218
+ 27974: 207,
219
+ };
220
+ function resolveChronicleItem(id, table, offset = 0, overrides = {}) {
221
+ const index = overrides[id] !== undefined ? overrides[id] : id - offset;
222
+ const data = table === null || table === void 0 ? void 0 : table[index];
223
+ return Object.assign({ id }, data);
224
+ }
225
+ async function writeChronicle(stash, version) {
226
+ var _a;
227
+ if (!stash.chronicle) {
228
+ return new Uint8Array();
229
+ }
230
+ const { setItems, uniqueItems, runewords, _unknown_data } = stash.chronicle;
231
+ const writer = new bitwriter_1.BitWriter();
232
+ writer.WriteUInt32(CHRONICLE_SECTION_MAGIC);
233
+ writer.WriteUInt32(stash.hardcore ? 0 : version === 0x69 ? 2 : 1);
234
+ writer.WriteUInt32(version);
235
+ writer.WriteUInt32(0); // unused (gold slot on regular pages)
236
+ writer.WriteUInt32(0); // size of the sector, fixed below
237
+ writer.WriteBytes((_a = _unknown_data === null || _unknown_data === void 0 ? void 0 : _unknown_data.envelopePadding) !== null && _a !== void 0 ? _a : new Uint8Array(44));
238
+ writer.WriteUInt32(CHRONICLE_MAGIC);
239
+ writer.WriteUInt16(CHRONICLE_VERSION);
240
+ writer.WriteUInt16(setItems.length);
241
+ writer.WriteUInt16(uniqueItems.length);
242
+ writer.WriteUInt16(runewords.length);
243
+ writer.WriteBytes(new Uint8Array(8).fill(0)); // reserved
244
+ const writeEntry = (itemId, monster, foundAt) => {
245
+ writer.WriteUInt32(itemId);
246
+ writer.WriteUInt16(monster);
247
+ writer.WriteUInt32(Math.floor(foundAt / 60)); // unix seconds -> minutes since epoch
248
+ };
249
+ // Entries are stored newest-first in real saves; enforce that order on write regardless of how
250
+ // the caller built the list, rather than relying on callers to insert/remove at the right spot.
251
+ const newestFirst = (entries) => [...entries].sort((a, b) => b.foundAt - a.foundAt);
252
+ for (const entry of newestFirst(setItems))
253
+ writeEntry(entry.item.id, entry.monster, entry.foundAt);
254
+ for (const entry of newestFirst(uniqueItems))
255
+ writeEntry(entry.item.id, entry.monster, entry.foundAt);
256
+ for (const entry of newestFirst(runewords))
257
+ writeEntry(entry.item.id, entry.monster, entry.foundAt);
258
+ // Not fully understood. If the entry lists are unchanged from what was read, re-emit the
259
+ // captured bytes verbatim so an unmodified roundtrip matches byte-for-byte. If entries were
260
+ // added/removed, those captured bytes are stale (this region's content shifts with entry count),
261
+ // so zero it instead - a 637-entry real save had this region fully zeroed, suggesting all-zero is
262
+ // an accepted state, whereas carrying forward stale bytes for a different entry count is not.
263
+ const countsUnchanged = (_unknown_data === null || _unknown_data === void 0 ? void 0 : _unknown_data.originalCounts) &&
264
+ _unknown_data.originalCounts.setItems === setItems.length &&
265
+ _unknown_data.originalCounts.uniqueItems === uniqueItems.length &&
266
+ _unknown_data.originalCounts.runewords === runewords.length;
267
+ writer.WriteBytes(countsUnchanged ? _unknown_data.trailing : new Uint8Array(64));
268
+ const size = writer.offset;
269
+ writer.SeekByte(16);
270
+ writer.WriteUInt32(Math.ceil(size / 8));
271
+ return writer.ToArray();
272
+ }
273
+ async function readStashPage(stash, reader, version, constants) {
274
+ const page = {
275
+ items: [],
276
+ name: "",
277
+ type: 0,
278
+ };
279
+ const header = reader.ReadString(2);
280
+ if (header !== "ST") {
281
+ throw new Error(`can not read stash page header ST at position ${reader.offset - 2 * 8}`);
282
+ }
283
+ page.type = reader.ReadUInt32();
284
+ page.name = reader.ReadNullTerminatedString();
285
+ page.items = await items.readItems(reader, version, constants, defaultConfig);
286
+ attribute_enhancer_1.enhanceItems(page.items, constants, 1);
287
+ stash.pages.push(page);
288
+ }
289
+ async function readStashPart(stash, reader, version, constants, pageIndex) {
290
+ const currentPage = pageIndex !== undefined ? stash.pages[pageIndex] : undefined;
291
+ const page = currentPage || {
292
+ items: [],
293
+ name: "",
294
+ type: 0,
295
+ };
296
+ page.items = await items.readItems(reader, version, constants, defaultConfig);
297
+ attribute_enhancer_1.enhanceItems(page.items, constants, 1);
298
+ if (!currentPage) {
299
+ stash.pages.push(page);
300
+ }
301
+ }
302
+ async function write(data, constants, version, userConfig) {
303
+ const config = Object.assign(defaultConfig, userConfig);
304
+ const writer = new bitwriter_1.BitWriter();
305
+ if (!constants) {
306
+ constants = constants_1.getConstantData(version);
307
+ }
308
+ if (version > 0x61) {
309
+ for (const page of data.pages) {
310
+ writer.WriteArray(await writeStashSection(data, page, constants, config));
311
+ }
312
+ if (version === 0x69 && data.chronicle) {
313
+ writer.WriteArray(await writeChronicle(data, version));
314
+ }
315
+ }
316
+ else {
317
+ writer.WriteArray(await writeStashHeader(data));
318
+ writer.WriteArray(await writeStashPages(data, version, constants, config));
319
+ }
320
+ return writer.ToArray();
321
+ }
322
+ exports.write = write;
323
+ async function writeStashHeader(data) {
324
+ const writer = new bitwriter_1.BitWriter();
325
+ if (data.type === types.EStashType.private) {
326
+ writer.WriteString("CSTM", 4);
327
+ }
328
+ else {
329
+ writer.WriteString("SSS", 4);
330
+ }
331
+ writer.WriteString(data.version, data.version.length);
332
+ if (data.type === types.EStashType.private) {
333
+ writer.WriteString("", 4);
334
+ }
335
+ else {
336
+ if (data.version == "02") {
337
+ writer.WriteUInt32(data.sharedGold);
338
+ }
339
+ }
340
+ writer.WriteUInt32(data.pages.length);
341
+ return writer.ToArray();
342
+ }
343
+ async function writeStashSection(data, page, constants, userConfig) {
344
+ const writer = new bitwriter_1.BitWriter();
345
+ const version = parseInt(data.version);
346
+ writer.WriteUInt32(0xaa55aa55);
347
+ writer.WriteUInt32(data.hardcore ? 0 : version === 0x69 ? 2 : 1); // change to 0x02
348
+ writer.WriteUInt32(version);
349
+ const maxGold = Math.min(data.sharedGold, 2500000);
350
+ data.sharedGold = Math.max(0, data.sharedGold - maxGold);
351
+ writer.WriteUInt32(maxGold);
352
+ writer.WriteUInt32(0); // size of the sector, to be fixed later
353
+ if (version === 0x69) {
354
+ writer.WriteByte(page.isStackable || 0);
355
+ writer.WriteBytes(new Uint8Array(43).fill(0)); // empty
356
+ }
357
+ else {
358
+ writer.WriteBytes(new Uint8Array(44).fill(0)); // empty
359
+ }
360
+ writer.WriteArray(await items.writeItems(page.items, parseInt(data.version), constants, userConfig));
361
+ const size = writer.offset;
362
+ writer.SeekByte(16);
363
+ writer.WriteUInt32(Math.ceil(size / 8));
364
+ return writer.ToArray();
365
+ }
366
+ async function writeStashPages(data, version, constants, config) {
367
+ const writer = new bitwriter_1.BitWriter();
368
+ for (let i = 0; i < data.pages.length; i++) {
369
+ writer.WriteArray(await writeStashPage(data.pages[i], version, constants, config));
370
+ }
371
+ return writer.ToArray();
372
+ }
373
+ async function writeStashPage(data, version, constants, config) {
374
+ const writer = new bitwriter_1.BitWriter();
375
+ writer.WriteString("ST", 2);
376
+ writer.WriteUInt32(data.type);
377
+ writer.WriteString(data.name, data.name.length + 1);
378
+ writer.WriteArray(await items.writeItems(data.items, version, constants, config));
379
+ return writer.ToArray();
380
+ }
381
381
  //# sourceMappingURL=stash.js.map