diff --git a/src/installPatch.ts b/src/installPatch.ts index d540e71..bf758b3 100644 --- a/src/installPatch.ts +++ b/src/installPatch.ts @@ -1,29 +1,14 @@ -import { TextDecoder } from 'util'; -import extractFile from './ssn/extractFile'; import getPatchmanifest from './ssn/getPatchmanifest'; import getSolidpkg from './ssn/getSolidpkg'; -import readSsnFile from './ssn/readSsnFile'; - -const Decoder = new TextDecoder('utf-8'); (async () => { //----- PATCHMANIFEST ----- //.patchmanifest files contain a single XML file called "manifest.xml" - const patchmanifestBuffer = await getPatchmanifest('assets_swtor_de_de'); - console.log(patchmanifestBuffer.byteLength, patchmanifestBuffer); - - const patchmanifestFiles = readSsnFile(patchmanifestBuffer); - console.log(patchmanifestFiles); - - const patchmanifestFile = extractFile(patchmanifestFiles[0], [new DataView(patchmanifestBuffer)]); - const patchmanifestXml = Decoder.decode(patchmanifestFile); + const patchmanifestXml = await getPatchmanifest('assets_swtor_de_de'); console.log(patchmanifestXml); //----- SOLIDPKG ----- //.solidpkg files contain a single Bencode file called "metafile.solid" - const solidpkgBuffer = await getSolidpkg('assets_swtor_de_de', -1, 0); - console.log(solidpkgBuffer.byteLength, solidpkgBuffer); - - const solidPkgFiles = readSsnFile(solidpkgBuffer); - console.log(solidPkgFiles); + const solidFile = await getSolidpkg('assets_swtor_de_de', -1, 0); + console.log(solidFile); })(); diff --git a/src/ssn/extractFile.ts b/src/ssn/extractFile.ts index b7e5fa3..7cd39f0 100644 --- a/src/ssn/extractFile.ts +++ b/src/ssn/extractFile.ts @@ -41,7 +41,7 @@ class ByteReader { /** Extracts the given file from the given DataView array and returns it as an ArrayBuffer. * Will throw an error when end of final DataView is reached. */ -export default function extractFile(file: ISsnFileEntry, dvArray: DataView[]): ArrayBuffer { +export default async function extractFile(file: ISsnFileEntry, dvArray: DataView[]): Promise { //use ByteReader for reading a uint8 and seeking forward across DataView boundaries const byteReader = new ByteReader(dvArray, file.diskNumberStart, file.offset); @@ -61,6 +61,10 @@ export default function extractFile(file: ISsnFileEntry, dvArray: DataView[]): A } //uncompress file - const uncompressedBuffer = zlib.inflateRawSync(dvFinal); + const uncompressedBuffer: Buffer = await new Promise((resolve) => { + zlib.inflateRaw(dvFinal, (error, result) => { + resolve(result); + }); + }) as Buffer; return uncompressedBuffer.buffer as ArrayBuffer; } diff --git a/src/ssn/getPatchmanifest.ts b/src/ssn/getPatchmanifest.ts index 0635ee8..bf557be 100644 --- a/src/ssn/getPatchmanifest.ts +++ b/src/ssn/getPatchmanifest.ts @@ -1,12 +1,36 @@ +import { TextDecoder } from 'util'; import getUrlContents from '../getUrlContents'; import { Product } from '../interfaces/ISettings'; import verifyProductName from '../verifyProductName'; +import extractFile from './extractFile'; +import readSsnFile from './readSsnFile'; -export default function getPatchmanifest(product: Product): Promise { +const Decoder = new TextDecoder('utf-8'); + +export default async function getPatchmanifest(product: Product): Promise { //Verify function arguments if (!verifyProductName(product)) { throw new Error(`"${product}" is not a valid product.`); } - return getUrlContents({ host: 'manifest.swtor.com', path: `/patch/${product}.patchmanifest` }); + //Download .patchmanifest file + const ssnFile = await getUrlContents({ host: 'manifest.swtor.com', path: `/patch/${product}.patchmanifest` }); + + //Parse .patchmanifest file + const fileEntries = readSsnFile(ssnFile); + + if (fileEntries.length !== 1) { + throw new Error(`Expected .patchmanifest to contain 1 file but it had "${fileEntries.length}" files.`); + } + + const firstFile = fileEntries[0]; + if (firstFile.name !== 'manifest.xml') { + throw new Error(`Expected .patchmanifest to contain a file called manifest.xml but it is called "${firstFile.name}".`); + } + + //Extract manifest.xml file + const patchmanifestFile = await extractFile(firstFile, [new DataView(ssnFile)]); + const patchmanifestXml = Decoder.decode(patchmanifestFile); + + return patchmanifestXml; } diff --git a/src/ssn/getSolidpkg.ts b/src/ssn/getSolidpkg.ts index 7253b1c..d2870ae 100644 --- a/src/ssn/getSolidpkg.ts +++ b/src/ssn/getSolidpkg.ts @@ -1,13 +1,14 @@ import getUrlContents from '../getUrlContents'; import { Product } from '../interfaces/ISettings'; import verifyProductName from '../verifyProductName'; +import extractFile from './extractFile'; +import readSsnFile from './readSsnFile'; -export default function getSolidpkg(product: Product, from: number, to: number): Promise { +export default async function getSolidpkg(product: Product, from: number, to: number): Promise { //Verify function arguments if (!verifyProductName(product)) { throw new Error(`"${product}" is not a valid product.`); } - if (typeof from !== 'number' || (from | 0) !== from || from < -1) { throw new Error(`from must be an integer greater than or equal to -1 but it is "${from}"`); } @@ -18,5 +19,23 @@ export default function getSolidpkg(product: Product, from: number, to: number): throw new Error(`from must be less than to but "${from}" is not less than "${to}".`); } - return getUrlContents({ host: 'cdn-patch.swtor.com', path: `/patch/${product}/${product}_${from}to${to}.solidpkg` }); + //Download .solidpkg file + const ssnFile = await getUrlContents({ host: 'cdn-patch.swtor.com', path: `/patch/${product}/${product}_${from}to${to}.solidpkg` }); + + //Parse .solidpkg file + const fileEntries = readSsnFile(ssnFile); + + if (fileEntries.length !== 1) { + throw new Error(`Expected .solidpkg to contain 1 file but it had "${fileEntries.length}" files.`); + } + + const firstFile = fileEntries[0]; + if (firstFile.name !== 'metafile.solid') { + throw new Error(`Expected .solidpkg to contain a file called metafile.solid but it is called "${firstFile.name}".`); + } + + //Extract metafile.solid file + const solidFile = await extractFile(firstFile, [new DataView(ssnFile)]); + + return solidFile; }