From b22dc6ae7a7fb72918ffe31ca381752a2545c75b Mon Sep 17 00:00:00 2001 From: d2r-app Date: Thu, 1 Jun 2023 09:09:11 -0400 Subject: [PATCH] Add Atma/GoMule stash support --- examples/stash/Atma_GoMule.d2x | Bin 0 -> 42778 bytes src/d2/stash.ts | 170 ++++++++++++++++++++++----------- src/d2/types.ts | 8 +- src/index.ts | 1 + tests/d2/stash.spec.ts | 23 ++++- 5 files changed, 141 insertions(+), 61 deletions(-) create mode 100644 examples/stash/Atma_GoMule.d2x diff --git a/examples/stash/Atma_GoMule.d2x b/examples/stash/Atma_GoMule.d2x new file mode 100644 index 0000000000000000000000000000000000000000..ef92b393c17f3a2f7261b018be2f160782b689e5 GIT binary patch literal 42778 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 vr~vYp8#GlDSH$1Q$BW0gQYKUOqq%%IoN { - const stash = {} as types.IStash; + const stash = { pages: [] as types.IStashPage[] } as types.IStash; const reader = new BitReader(buffer); - const config = Object.assign(defaultConfig, userConfig); - const firstHeader = reader.ReadUInt32(); + stash.type = readStashHeader(reader); reader.SeekByte(0); - if (firstHeader == 0xaa55aa55) { - stash.pages = []; + // Resurrected + if (stash.type === types.EStashType.D2R) { let pageCount = 0; while (reader.offset < reader.bits.length) { pageCount++; - await readStashHeader(stash, reader); + await readStashMeta(stash, reader); const saveVersion = version || parseInt(stash.version); if (!constants) { constants = getConstantData(saveVersion); @@ -34,60 +31,110 @@ export async function read( await readStashPart(stash, reader, saveVersion, constants); } stash.pageCount = pageCount; - } else { - await readStashHeader(stash, reader); + } + // PlugY/Atma/Goule + else { + await readStashMeta(stash, reader); const saveVersion = version || parseInt(stash.version); if (!constants) { constants = getConstantData(saveVersion); } - await readStashPages(stash, reader, saveVersion, constants); + // PluggY + if (stash.type === types.EStashType.SSS || stash.type === types.EStashType.CSTM) { + await readStashPages(stash, reader, saveVersion, constants); + } + // Atma/GoMule + else if (stash.type === types.EStashType.D2X) { + const stashItems = [] as types.IItem[]; + reader.SeekByte(11); + for (let i = 0; i < stash.itemCount; i++) { + stashItems.push(await items.readItem(reader, saveVersion, constants, {})); + } + stash.pages.push({ items: stashItems } as types.IStashPage); + } } return stash; } -async function readStashHeader(stash: types.IStash, reader: BitReader) { - const header = reader.ReadUInt32(); - switch (header) { - // Resurrected - case 0xaa55aa55: - stash.type = types.EStashType.shared; - stash.hardcore = reader.ReadUInt32() == 0; - stash.version = reader.ReadUInt32().toString(); - stash.sharedGold = reader.ReadUInt32(); - reader.ReadUInt32(); // size of the sector - reader.SkipBytes(44); // empty - break; - // LoD - case 0x535353: // SSS - case 0x4d545343: // CSTM - stash.version = reader.ReadString(2); - if (stash.version !== "01" && stash.version !== "02") { - throw new Error(`unkown stash version ${stash.version} at position ${reader.offset - 2 * 8}`); - } - - stash.type = header === 0x535353 ? types.EStashType.shared : types.EStashType.private; - - if (stash.type === types.EStashType.shared && stash.version == "02") { - stash.sharedGold = reader.ReadUInt32(); - } - - if (stash.type === types.EStashType.private) { - reader.ReadUInt32(); - stash.sharedGold = 0; - } +function readStashHeader(reader: BitReader): types.EStashType { + reader.SeekBit(0); + const header32 = reader.ReadUInt32(); + // Resurrected + if (header32 === 0xaa55aa55) { + return types.EStashType.D2R; + } + // SSS (PlugY) + else if (header32 === 0x535353) { + return types.EStashType.SSS; + } + // CSTM (PlugY) + else if (header32 === 0x4d545343) { + return types.EStashType.CSTM; + } + // Check 24 bit header + reader.SeekBit(0); + const header24 = reader.ReadUInt32(24); + // D2X (Atma/GoMule) + if (header24 === 0x583244) { + return types.EStashType.D2X; + } + const header24Hex = header24?.toString(16); + const header32Hex = header32?.toString(16); + throw new Error(`unknown stash header at position 0: 24bit = 0x${header24Hex}, 32bit = 0x${header32Hex}`); +} - stash.pageCount = reader.ReadUInt32(); - break; - default: - debugger; - throw new Error( - `shared stash header 'SSS' / 0xAA55AA55 / private stash header 'CSTM' not found at position ${reader.offset - 3 * 8}` - ); +async function readStashMeta(stash: types.IStash, reader: BitReader) { + // Skip header, already parsed into stash type + reader.SkipBytes(4); + // Resurrected + if (stash.type === types.EStashType.D2R) { + stash.shared = true; + stash.hardcore = reader.ReadUInt32() == 0; + stash.version = reader.ReadUInt32().toString(); + stash.sharedGold = reader.ReadUInt32(); + reader.ReadUInt32(); // size of the sector + reader.SkipBytes(44); // empty + } + // SSS or CSTM (PlugY) + else if (stash.type === types.EStashType.SSS || stash.type === types.EStashType.CSTM) { + if (stash.type === types.EStashType.SSS) { + stash.type = types.EStashType.SSS; + stash.shared = true; + } + else if (stash.type === types.EStashType.CSTM) { + stash.type = types.EStashType.CSTM; + stash.shared = false; + } + stash.version = reader.ReadString(2); + if (stash.version !== "01" && stash.version !== "02") { + throw new Error(`unknown stash version ${stash.version} at position ${reader.offset - 16}`); + } + if (stash.shared && stash.version == "02") { + stash.sharedGold = reader.ReadUInt32(); + } + if (!stash.shared) { + reader.ReadUInt32(); + stash.sharedGold = 0; + } + stash.pageCount = reader.ReadUInt32(); + } + // D2X (Atma/GoMule) + else if (stash.type === types.EStashType.D2X) { + stash.type = types.EStashType.D2X; + stash.shared = true; + reader.SeekByte(3); + stash.itemCount = reader.ReadUInt16(); + stash.version = reader.ReadUInt16().toString(); + if (stash.version !== "99") { + throw new Error(`unknown stash version ${stash.version} at position ${reader.offset - 16}`); + } + } + else { + throw new Error(`unknown stash type: ${stash.type}`); } } async function readStashPages(stash: types.IStash, reader: BitReader, version: number, constants: types.IConstantData) { - stash.pages = []; for (let i = 0; i < stash.pageCount; i++) { await readStashPage(stash, reader, version, constants); } @@ -134,33 +181,40 @@ export async function write( if (!constants) { constants = getConstantData(version); } - if (version > 0x61) { + // Resurrected + if (data.type === types.EStashType.D2R) { for (const page of data.pages) { writer.WriteArray(await writeStashSection(data, page, constants, config)); } - } else { + } + // SSS or CSTM (PlugY) + else if (data.type === types.EStashType.SSS || data.type === types.EStashType.CSTM) { writer.WriteArray(await writeStashHeader(data)); writer.WriteArray(await writeStashPages(data, version, constants, config)); } + // D2X (Atma/GoMule) + else if (data.type === types.EStashType.D2X) { + throw new Error('No write support for D2X (Atma/GoMule)'); + } return writer.ToArray(); } async function writeStashHeader(data: types.IStash): Promise { const writer = new BitWriter(); - if (data.type === types.EStashType.private) { - writer.WriteString("CSTM", 4); - } else { + if (data.shared) { writer.WriteString("SSS", 4); + } else { + writer.WriteString("CSTM", 4); } writer.WriteString(data.version, data.version.length); - if (data.type === types.EStashType.private) { - writer.WriteString("", 4); - } else { + if (data.shared) { if (data.version == "02") { writer.WriteUInt32(data.sharedGold); } + } else { + writer.WriteString("", 4); } writer.WriteUInt32(data.pages.length); return writer.ToArray(); diff --git a/src/d2/types.ts b/src/d2/types.ts index eb1491c..774fa6c 100644 --- a/src/d2/types.ts +++ b/src/d2/types.ts @@ -411,14 +411,18 @@ export interface IMagicProperty { } export enum EStashType { - shared, - private, + D2R, + SSS, + CSTM, + D2X, } export interface IStash { version: string; type: EStashType; pageCount: number; + itemCount: number; + shared: boolean; sharedGold: number; hardcore: boolean; pages: IStashPage[]; diff --git a/src/index.ts b/src/index.ts index 36dc3db..211d361 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,6 +2,7 @@ export * from "./d2/d2s"; export { readHeader, readHeaderData, writeHeader, writeHeaderData, fixHeader } from "./d2/header"; export { readAttributes, writeAttributes } from "./d2/attributes"; export { readSkills, writeSkills } from "./d2/skills"; +export { read as readStash, write as writeStash } from "./d2/stash"; export { enhanceAttributes, enhanceItems, enhancePlayerAttributes } from "./d2/attribute_enhancer"; export { getConstantData, setConstantData } from "./d2/constants"; export * from "./data/parser"; diff --git a/tests/d2/stash.spec.ts b/tests/d2/stash.spec.ts index 39dd65f..37abf81 100644 --- a/tests/d2/stash.spec.ts +++ b/tests/d2/stash.spec.ts @@ -1,4 +1,4 @@ -import { expect, should } from "chai"; +import { expect } from "chai"; import { read, write } from "../../src/d2/stash"; import { constants } from "../../src/data/versions/96_constant_data"; import * as path from "path"; @@ -69,4 +69,25 @@ describe("stash", () => { expect(buffer.length, "file size").to.eq(newBuffer.length); expect(newJson, "json").to.deep.eq(jsonData); }); + + it("should read D2X shared stash file (Atma/GoMule)", async () => { + const buffer = fs.readFileSync(path.join(__dirname, "../../examples/stash/Atma_GoMule.d2x")); + const jsonData = await read(buffer, version99.constants, 0x62); + expect(jsonData.version).to.eq("99"); + expect(jsonData.itemCount).to.eq(2599); + expect(jsonData.pages[0]?.items?.length).to.eq(2599); + expect(jsonData.pages[0].items[2598].unique_name).to.eq("Twitchthroe"); + }); + + it("should not write D2X shared stash file (Atma/GoMule)", async () => { + const buffer = fs.readFileSync(path.join(__dirname, "../../examples/stash/Atma_GoMule.d2x")); + const jsonData = await read(buffer, version99.constants, 0x62); + let err: unknown; + try { + await write(jsonData, constants, 0x62); + } catch (e) { + err = e; + } + expect(err).to.be.instanceOf(Error); + }); });