From 94047006eccc62cd09dcbfc5e6e73f847f0d91f3 Mon Sep 17 00:00:00 2001 From: C-3PO Date: Wed, 4 Jul 2018 20:11:04 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=8E=A8=20Fix=20typos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 60 ++++++++++++++------------- src/cdn/funcs/resolveDns.ts | 2 +- src/cdn/heartbeatDns.ts | 2 +- src/interfaces/ISsnFileEntry.ts | 2 +- src/ssn/extractFile.ts | 4 +- src/ssn/findReleasePath.ts | 42 +++++++++---------- src/ssn/reader/readSsnFile.ts | 8 ++-- src/ssn/verify/verifyPatchmanifest.ts | 2 +- 8 files changed, 62 insertions(+), 60 deletions(-) diff --git a/README.md b/README.md index 0cf76f1..1c670ec 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,37 @@ SSN uses the following terms; make sure you are familiar with them since we use - __```environment```__: An environment consists of multiple products. For example, the ```live``` environment has products like ```assets_swtor_main``` while the ```pts``` environment has ```assets_swtor_test_main```. - __```manifest```__: Each product has a manifest, a single file that lists all releases and all patches, and specifies which release is the current release (that the launcher must install). +# SSN file format and encryption + +All files used by SSN are password-protected .zip files with a .exe header. The files are stored in the .zip section, while the .exe header (and the signatures at the end) are used to verify the integrity of the file. We are not interested in checking the integrity, so we only look at the .zip file. Check the [.zip specification](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) for more information on the .zip format. + +For each file, the extra field contains the following information: + +- For password protected files, the password needed to decrypt the file. The password itself is encoded and even if you have decoded it, it is usually in binary. Most .zip software only accepts textual passwords, so I recommend writing a custom .zip reader. +- Whether this file is stored in full, or whether only the differences are stored in vcdiff/xdelta3 encoding. Also flags whether a file was deleted, and it includes the hashes of both the old and the new file. + +## Manifests (`.patchmanifest`) + +A .patchmanifest SSN file contains a single file `manifest.xml` which includes a list of releases and patches, and specifies which manifest is the current one. + +## Patches (`.solidpkg`) + +A .solidpkg file contains a single file `metafile.solid`, which is in Bencode format (similar to .torrent files) and includes the names, sizes and hashes of all files with the patch data (.zip, .z01, .z02 etc.). + +## Version files (`.version`) + +Once a patch is installed, you can look into the .version file to figure out which release it is, and use the hashes to verify that the installation is correct. + +# File download and CDN + +Manifests are hosted on manifest.swtor.com, while patches are hosted on cdn-patch.swtor.com. They can only be downloaded via HTTP (port 80) but the files are integrity-checked via their included signatures. + +Manifests can be downloaded as follows: `http://manifest.swtor.com/patch/${product}.patchmanifest` + +Then knowing which patch you need, you can get the patch as follows: `http://cdn-patch.swtor.com/patch/${product}/${product}_${from}to${to}.solidpkg` + +# Appendix + ## Products used by SWTOR SWTOR uses the following products: @@ -71,32 +102,3 @@ The following products are deprecated and no longer used. - ```retailclient_cstraining```: No longer used. - ```retailclient_liveeptest```: No longer used. - ```retailclient_squadron157```: The client used during the closed beta by the Squadron157 testing group to play the game. - -# SSN file format and encryption - -All files used by SSN are password-protected .zip files with a .exe header. The files are stored in the .zip section, while the .exe header (and the signatures at the end) are used to verify the integrity of the file. We are not interested in checking the integrity, so we only look at the .zip file. Check the [.zip specification](https://pkware.cachefly.net/webdocs/casestudies/APPNOTE.TXT) for more information on the .zip format. - -For each file, the extra field contains the following information: - -- For password protected files, the password needed to decrypt the file. The password itself is encoded and even if you have decoded it, it is usually in binary. Most .zip software only accepts textual passwords, so I recommend writing a custom .zip reader. -- Whether this file is stored in full, or whether only the differences are stored in vcdiff/xdelta3 encoding. Also flags whether a file was deleted, and it includes the hashes of both the old and the new file. - -## Manifests (`.patchmanifest`) - -A .patchmanifest SSN file contains a single file `manifest.xml` which includes a list of releases and patches, and specifies which manifest is the current one. - -## Patches (`.solidpkg`) - -A .solidpkg file contains a single file `metafile.solid`, which is in Bencode format (similar to .torrent files) and includes the names, sizes and hashes of all files with the patch data (.zip, .z01, .z02 etc.). - -## Version files (`.version`) - -Once a patch is installed, you can look into the .version file to figure out which release it is, and use the hashes to verify that the installation is correct. - -# File download and CDN - -Manifests are hosted on manifest.swtor.com, while patches are hosted on cdn-patch.swtor.com. They can only be downloaded via HTTP (port 80) but the files are integrity-checked via their included signatures. - -Manifests can be downloaded as follows: `http://manifest.swtor.com/patch/${product}.patchmanifest` - -Then knowing which patch you need, you can get the patch as follows: `http://cdn-patch.swtor.com/patch/${product}/${product}_${from}to${to}.solidpkg` diff --git a/src/cdn/funcs/resolveDns.ts b/src/cdn/funcs/resolveDns.ts index 2dfcec3..38d2e77 100644 --- a/src/cdn/funcs/resolveDns.ts +++ b/src/cdn/funcs/resolveDns.ts @@ -31,7 +31,7 @@ import * as dns from 'dns'; import { IDnsResult } from '../../interfaces/IDnsResult'; //TODO: send e-mail with the error -const assert = (cond: boolean) => { if (!cond) { console.warn('Assert failed'); } }; +const assert = (condition: boolean) => { if (!condition) { console.warn('Assert failed'); } }; /** Looks up the given domain and returns a list of IP addresses, along with their time-to-live */ async function resolveDns(domain: string): Promise { diff --git a/src/cdn/heartbeatDns.ts b/src/cdn/heartbeatDns.ts index c01783d..cc23a11 100644 --- a/src/cdn/heartbeatDns.ts +++ b/src/cdn/heartbeatDns.ts @@ -15,7 +15,7 @@ async function heartbeatDns(domain: string) { //Get list of current patch servers const dnsResults = await resolveDns(domain); - //Remeber time when response came in + //Remember time when response came in const now = Date.now() - startTime; //Schedule next check based on time-to-live, but never longer than 1 minute diff --git a/src/interfaces/ISsnFileEntry.ts b/src/interfaces/ISsnFileEntry.ts index ef431e4..e822214 100644 --- a/src/interfaces/ISsnFileEntry.ts +++ b/src/interfaces/ISsnFileEntry.ts @@ -25,7 +25,7 @@ interface ISsnFileEntry { /** Uncompressed size */ size: number; /** Compressed size */ - comprSize: number; + compressedSize: number; /** Decryption keys needed to decrypt the file */ decryptionKeys: [number, number, number] | undefined; /** Number of the disk where the file is stored (0=.z01, 1=.z02 etc.) */ diff --git a/src/ssn/extractFile.ts b/src/ssn/extractFile.ts index 33e3605..89699a6 100644 --- a/src/ssn/extractFile.ts +++ b/src/ssn/extractFile.ts @@ -22,11 +22,11 @@ export default async function extractFile(file: ISsnFileEntry, dvArray: DataView byteReader.seek(localFilenameSize + localExtraSize); //Extract actual file contents - let dvFinal = byteReader.extractDv(file.comprSize); + let dvFinal = byteReader.extractDv(file.compressedSize); //Decrypt file if necessary if (file.decryptionKeys !== undefined) { - dvFinal = decryptFile(dvFinal, file.comprSize, file.decryptionKeys); + dvFinal = decryptFile(dvFinal, file.compressedSize, file.decryptionKeys); } //Decompress file diff --git a/src/ssn/findReleasePath.ts b/src/ssn/findReleasePath.ts index 208ce13..a0801ed 100644 --- a/src/ssn/findReleasePath.ts +++ b/src/ssn/findReleasePath.ts @@ -2,7 +2,7 @@ import { Product } from '../interfaces/ISettings'; import verifyProductName from '../ssn/verify/verifyProductName'; /** For the given release in the given product, returns from which releases we can patch to this release. */ -function getFroms({ product, to: releaseTo}: {product: Product, to: number}) { +function getFromList({ product, to: releaseTo}: {product: Product, to: number}) { if (releaseTo < 0) { return []; } @@ -11,24 +11,24 @@ function getFroms({ product, to: releaseTo}: {product: Product, to: number}) { if (product.startsWith('patcher')) { return [-1]; } else { - const froms: number[] = []; + const fromList: number[] = []; //Always X-1toX - froms.push(releaseTo - 1); + fromList.push(releaseTo - 1); //Also 0toX, unless X is 0. And no need to add 0to1 a second time. - if (releaseTo >= 2) { froms.push(0); } + if (releaseTo >= 2) { fromList.push(0); } if ((releaseTo % 5) === 0) { //Also X-5toX if X % 5 - if (releaseTo >= 10) { froms.push(releaseTo - 5); } + if (releaseTo >= 10) { fromList.push(releaseTo - 5); } //Also X-20toX if X % 5 - if (releaseTo >= 25) { froms.push(releaseTo - 20); } + if (releaseTo >= 25) { fromList.push(releaseTo - 20); } //Also downgrade from the following four releases - froms.push(releaseTo + 1); - froms.push(releaseTo + 2); - froms.push(releaseTo + 3); - froms.push(releaseTo + 4); + fromList.push(releaseTo + 1); + fromList.push(releaseTo + 2); + fromList.push(releaseTo + 3); + fromList.push(releaseTo + 4); } else { //For some of the older releases, an update from _5 or _0 is possible /* e.g. in asset_swtor_main: @@ -40,10 +40,10 @@ function getFroms({ product, to: releaseTo}: {product: Product, to: number}) { 30to32, 30to33, 30to34, 35to37, etc. , 85to87 */ - if (releaseTo >= 7 && (releaseTo % 5) > 1) { froms.push(releaseTo - (releaseTo % 5)); } + if (releaseTo >= 7 && (releaseTo % 5) > 1) { fromList.push(releaseTo - (releaseTo % 5)); } } - return froms; + return fromList; } } @@ -65,21 +65,21 @@ export default function findReleasePath({ product, from, to}: {product: Product, throw new Error('Cannot patch backwards; to must be greater than from.'); } - const froms = getFroms({ product, to }); + const fromList = getFromList({ product, to }); //If we can patch, return immediately - if (froms.includes(from)) { + if (fromList.includes(from)) { return [[from, to]]; } - //Otherwise, check all froms recursively, by checking interim releases - const smallerFroms = froms.filter((num) => num > from); - //Always prefer shortest release paths (e.g. 1->3 vs. 1->2->3) by ensuring we check smallest froms first - smallerFroms.sort(); + //Otherwise, check all from values recursively, by checking interim releases + const smallerFromList = fromList.filter((num) => num > from); + //Always prefer shortest release paths (e.g. 1->3 vs. 1->2->3) by ensuring we check smallest from values first + smallerFromList.sort(); - for (let i = 0, il = smallerFroms.length; i < il; i += 1) { - const interim = smallerFroms[i]; - //TODO: This sometimes causes a "Maximum call stack size exceeded" error, e.g. for `{ product: '*', from: 1, to: 11 }` + for (let i = 0, il = smallerFromList.length; i < il; i += 1) { + const interim = smallerFromList[i]; + //FIXME: This sometimes causes a "Maximum call stack size exceeded" error, e.g. for `{ product: '*', from: 1, to: 11 }` const releasePath = findReleasePath({ product, from, to: interim} ); if (releasePath.length > 0) { return [...releasePath, [interim, to]]; diff --git a/src/ssn/reader/readSsnFile.ts b/src/ssn/reader/readSsnFile.ts index 0601a81..ed3f71f 100644 --- a/src/ssn/reader/readSsnFile.ts +++ b/src/ssn/reader/readSsnFile.ts @@ -90,9 +90,9 @@ export default function readSsnFile(buffer: ArrayBuffer): ISsnFileEntry[] { /** crc-32 */ const fileCrc = dv.getUint32(pos, true); pos += 4; /** compressed size */ - const comprSize = dv.getUint32(pos, true); pos += 4; + const compressedSize = dv.getUint32(pos, true); pos += 4; /** decompressed size */ - const decomprSize = dv.getUint32(pos, true); pos += 4; + const decompressedSize = dv.getUint32(pos, true); pos += 4; /** file name length */ const fileNameLength = dv.getUint16(pos, true); pos += 2; /** extra field length */ @@ -146,7 +146,7 @@ export default function readSsnFile(buffer: ArrayBuffer): ISsnFileEntry[] { // ------- CREATE ENTRY --------- const fileEntry: ISsnFileEntry = { - comprSize, + compressedSize, crc: fileCrc, decryptionKeys: undefined, diffDestLength, @@ -158,7 +158,7 @@ export default function readSsnFile(buffer: ArrayBuffer): ISsnFileEntry[] { offset: (centralDirOffset > 0) ? //If files are included in this archive, the centralDirOffset will not start from the beginning posCentralDirStart - centralDirOffset + relOffset : //if file is in this archive relOffset, //if we need to look in a disk (e.g. .z01 for this file) - size: decomprSize, + size: decompressedSize, }; //If file is encrypted diff --git a/src/ssn/verify/verifyPatchmanifest.ts b/src/ssn/verify/verifyPatchmanifest.ts index c5149e5..6ec9678 100644 --- a/src/ssn/verify/verifyPatchmanifest.ts +++ b/src/ssn/verify/verifyPatchmanifest.ts @@ -5,7 +5,7 @@ import { Product } from '../../interfaces/ISettings'; export default function verifyPatchmanifest(manifestFile: xmlJs.Element, product: Product): any { // if (manifestFile.declaration === undefined || manifestFile.declaration.attributes === undefined || Object.keys(manifestFile.declaration.attributes).length !== 2 || manifestFile.declaration.attributes.version !== '1.0' || manifestFile.declaration.attributes.encoding !== 'utf-8') { - throw new Error('Expected declration with version 1.0 and utf-8 encoding.'); + throw new Error('Expected declaration with version 1.0 and utf-8 encoding.'); } //