diff options
| author | TheMas3212 <mas3212@gmail.com> | 2023-09-21 09:31:34 +1000 |
|---|---|---|
| committer | TheMas3212 <mas3212@gmail.com> | 2023-09-21 09:31:34 +1000 |
| commit | 88f4b782cde59111a047ccd97fa6712d0fc56c89 (patch) | |
| tree | 8c8e27235f17c1fe87ed08fd8cf743b9db6fb5bd | |
| download | tasd-js-88f4b782cde59111a047ccd97fa6712d0fc56c89.tar.gz tasd-js-88f4b782cde59111a047ccd97fa6712d0fc56c89.zip | |
Inital Commit
| -rw-r--r-- | .eslintrc.json | 13 | ||||
| -rw-r--r-- | .gitignore | 6 | ||||
| -rw-r--r-- | README | 2 | ||||
| -rw-r--r-- | package.json | 26 | ||||
| -rw-r--r-- | src/lib.ts | 219 | ||||
| -rw-r--r-- | tsconfig.json | 21 | ||||
| -rw-r--r-- | webpack.config.js | 21 |
7 files changed, 308 insertions, 0 deletions
diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..b4f98f4 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,13 @@ +{ + "parser": "@typescript-eslint/parser", + "rules": { + "semi": ["error", "always"] + }, + "parserOptions": { + "ecmaVersion": 9, + "sourceType": "module", + "ecmaFeatures": { + "modules": true + } + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..056a544 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +/node_modules +/.vscode +/dist +package-lock.json +!.empty_directory +*.tasd
\ No newline at end of file @@ -0,0 +1,2 @@ +Javascript/Typescript Library for working with TASD files +https://github.com/bigbass1997/TASD-Spec
\ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..6a81438 --- /dev/null +++ b/package.json @@ -0,0 +1,26 @@ +{ + "name": "@themas3212/tasd", + "version": "0.0.1", + "description": "library for working with TASD files", + "private": true, + "main": "dist/lib.js", + "browser": "dist'/lib.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "build": "webpack --mode production" + }, + "author": "TheMas3212", + "license": "MIT", + "files": [ + "dist/lib.js", + "dist/lib.d.ts" + ], + "devDependencies": { + "@typescript-eslint/parser": "^5.31.0", + "eslint": "^8.20.0", + "ts-loader": "^9.4.4", + "typescript": "^4.7.4", + "webpack": "^5.88.2", + "webpack-cli": "^5.1.4" + } +} diff --git a/src/lib.ts b/src/lib.ts new file mode 100644 index 0000000..ca3c518 --- /dev/null +++ b/src/lib.ts @@ -0,0 +1,219 @@ +export class TASD { + static MAGIC = new Uint8Array([0x54, 0x41, 0x53, 0x44]); + public version: number; + public g_keylen: number; + public packets: TASDPacket[]; + constructor(_version: number, _keylen: number) { + this.version = _version; + this.g_keylen = _keylen; + this.packets = []; + } + static parseFile(buffer: Uint8Array) { + if (buffer.length < 7) throw new Error('Input Too Short'); + for (let i = 0; i < TASD.MAGIC.length; i++) { + if (buffer[i] !== TASD.MAGIC[i]) throw new Error('Invalid Magic Bytes'); + } + const version = readUint16(buffer, 4); + if (version !== 1) throw new Error('Unsupported Version'); + const g_keylen = readUint8(buffer, 6); + if (g_keylen !== 2) throw new Error('G_KEYLEN not equal to 2, unsupported'); + const file = new TASD(version, g_keylen); + let index = 7; + while (index < buffer.length) { + const p_key = readUintN(buffer, index, g_keylen); + index += g_keylen; + const p_pexp = readUint8(buffer, index); + index += 1; + const p_plen = readUintN(buffer, index, p_pexp); + index += p_pexp; + const p_payload = buffer.subarray(index, index+p_plen); + index += p_plen; + file.packets.push({ type: p_key, payload: p_payload }); + } + return file; + } + toBuffer() { + let size = 7; // Header + for (const packet of this.packets) { + size += this.g_keylen; // Packet Key + size += 1; // Packet PEXP + size += minUInt(packet.payload.length); // Packet PLen + size += packet.payload.length; + } + const buffer = new Uint8Array(size); + buffer.set(TASD.MAGIC, 0); + writeUint16(this.version, buffer, 4); + writeUint8(this.g_keylen, buffer, 6); + let index = 7; + for (const packet of this.packets) { + writeUintN(packet.type, buffer, index, this.g_keylen); + index += this.g_keylen; + const plen = packet.payload.length; + const pexp = minUInt(plen); + writeUint8(pexp, buffer, index); + index += 1; + writeUintN(plen, buffer, index, pexp); + index += pexp; + buffer.set(packet.payload, index); + index += plen; + } + return buffer; + } + pprint() { + console.log('VERSION', this.version); + console.log('G_KEYLEN', this.g_keylen); + for (const packet of this.packets) { + console.log('PACKET', packetName(packet.type), packet.payload.length, 'Bytes'); + } + } + compactInputs() { + if (this.packets.some(packet => packet.type === PACKET_TYPES.INPUT_MOMENT)) return; + const otherPackets = this.packets.filter(packet => packet.type !== PACKET_TYPES.INPUT_CHUNK); + const inputPackets = this.packets.filter(packet => packet.type === PACKET_TYPES.INPUT_CHUNK); + const ports: Uint8Array[][] = []; + for (const packet of otherPackets.filter(packet => packet.type === PACKET_TYPES.PORT_CONTROLLER)) { + ports[packet.payload[0]] = []; + } + for (const packet of inputPackets) { + ports[packet.payload[0]].push(packet.payload.subarray(1)); + } + for (let port = 1; port < 256; port++) { + if (ports[port]) { + const portN = new Uint8Array([port]); + otherPackets.push({ type: PACKET_TYPES.INPUT_CHUNK, payload: concatUint8Array([portN, ...ports[port]]) }); + } + } + this.packets = otherPackets; + } +} + +export type TASDPacket = { + type: number; + payload: Uint8Array; +} + +export const PACKET_TYPES = { + // General Keys + 'CONSOLE_TYPE': 0x00_01, + 'CONSOLE_REGION': 0x00_02, + 'GAME_TITLE': 0x00_03, + 'ROM_NAME': 0x00_04, + 'ATTRIBUTION': 0x00_05, + 'CATEGORY': 0x00_06, + 'EMULATOR_NAME': 0x00_07, + 'EMULATOR_VERSION': 0x00_08, + 'EMULATOR_CORE': 0x00_09, + 'TAS_LAST_MODIFIED': 0x00_0A, + 'DUMP_CREATED': 0x00_0B, + 'DUMP_LAST_MODIFIED': 0x00_0C, + 'TOTAL_FRAMES': 0x00_0D, + 'RERECORDS': 0x00_0E, + 'SOURCE_LINK': 0x00_0F, + 'BLANK_FRAMES': 0x00_10, + 'VERIFIED': 0x00_11, + 'MEMORY_INIT': 0x00_12, + 'GAME_IDENTIFIER': 0x00_13, + 'MOVIE_LICENSE': 0x00_14, + 'MOVIE_FILE': 0x00_15, + 'PORT_CONTROLLER': 0x00_F0, + // NES Specific Keys + 'NES_LATCH_FILTER': 0x01_01, + 'NES_CLOCK_FILTER': 0x01_02, + 'NES_OVERREAD': 0x01_03, + 'NES_GAME_GENIE_CODE': 0x01_04, + // SNES Specific Keys + 'SNES_CLOCK_FILTER': 0x02_02, + 'SNES_OVERREAD': 0x02_03, + 'SNES_GAME_GENIE_CODE': 0x02_04, + 'SNES_LATCH_TRAIN': 0x02_05, + // Genesis Specific Keys + 'GENESIS_GAME_GENIE_CODE': 0x08_04, + // Input Frame/Timing Keys + 'INPUT_CHUNK': 0xFE_01, + 'INPUT_MOMENT': 0xFE_02, + 'TRANSITION': 0xFE_03, + 'LAG_FRAME_CHUNK': 0xFE_04, + 'MOVIE_TRANSITION': 0xFE_05, + // Extraneous Keys + 'COMMENT': 0xFF_01, + 'EXPERIMENTAL': 0xFF_FE, + 'UNSPECIFIED': 0xFF_FF, +} as const; +export function packetName(key: number) { + for (const name of Object.keys(PACKET_TYPES)) { + if (PACKET_TYPES[name as keyof typeof PACKET_TYPES] === key) return name; + } + return 'Unknown Packet ' + key.toString(16).toUpperCase(); +} + +export function readUint8(buffer: Uint8Array, index: number) { + return buffer[index]; +} +export function readUint16(buffer: Uint8Array, index: number) { + return (buffer[index] << 8) + buffer[index+1]; +} +export function readUint24(buffer: Uint8Array, index: number) { + return (buffer[index] << 16) + (buffer[index+1] << 8) + buffer[index+2]; +} +export function readUint32(buffer: Uint8Array, index: number) { + return (buffer[index] << 24) + (buffer[index+1] << 16) + (buffer[index+2] << 8) + buffer[index+3]; +} +export function readUintN(buffer: Uint8Array, index: number, n: number) { + switch (n) { + case 1: return readUint8(buffer, index); + case 2: return readUint16(buffer, index); + case 3: return readUint24(buffer, index); + case 4: return readUint32(buffer, index); + default: throw new Error('Unsupported UInt size'); + } +} + +export function writeUint8(uint: number, buffer: Uint8Array, index: number) { + buffer[index] = uint & 0xFF; +} +export function writeUint16(uint: number, buffer: Uint8Array, index: number) { + buffer[index] = (uint & 0xFF00) >> 8; + buffer[index+1] = uint & 0xFF; +} +export function writeUint24(uint: number, buffer: Uint8Array, index: number) { + buffer[index] = (uint & 0xFF0000) >> 16; + buffer[index+1] = (uint & 0xFF00) >> 8; + buffer[index+2] = uint & 0xFF; +} +export function writeUint32(uint: number, buffer: Uint8Array, index: number) { + buffer[index] = (uint & 0xFF000000) >> 24; + buffer[index+1] = (uint & 0xFF0000) >> 16; + buffer[index+2] = (uint & 0xFF00) >> 8; + buffer[index+3] = uint & 0xFF; +} +export function writeUintN(uint: number, buffer: Uint8Array, index: number, size: number) { + switch (size) { + case 1: return writeUint8(uint, buffer, index); + case 2: return writeUint16(uint, buffer, index); + case 3: return writeUint24(uint, buffer, index); + case 4: return writeUint32(uint, buffer, index); + default: throw new Error('Unsupported UInt size'); + } +} + +export function minUInt(uint: number) { + if (uint < 256) return 1; + if (uint < 65536) return 2; + if (uint < 16777216) return 3; + if (uint < 4294967296) return 4; + throw new Error('Unsupported UInt size'); +} + +export function concatUint8Array(arrays: Uint8Array[]): Uint8Array { + let size = 0; + for (const array of arrays) { + size += array.length; + } + const buffer = new Uint8Array(size); + let index = 0; + for (const array of arrays) { + buffer.set(array, index); + index += array.length; + } + return buffer; +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..ef2f1be --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "module": "CommonJS", + "noImplicitAny": true, + "sourceMap": true, + "outDir": "dist", + "declaration": true, + "target": "es6", + "strict": true + }, + "lib": [ + "es2015" + ], + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "**/*.spec.ts" + ] + } diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 0000000..df8797a --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,21 @@ +const path = require('path'); + +module.exports = { + entry: './src/lib.ts', + module: { + rules: [ + { + test: /\.ts?$/, + use: 'ts-loader', + exclude: /node_modules/, + }, + ], + }, + resolve: { + extensions: ['.tsx', '.ts', '.js'], + }, + output: { + filename: 'lib.js', + path: path.resolve(__dirname, 'dist'), + } +};
\ No newline at end of file |
