summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTheMas3212 <mas3212@gmail.com>2023-09-21 09:31:34 +1000
committerTheMas3212 <mas3212@gmail.com>2023-09-21 09:31:34 +1000
commit88f4b782cde59111a047ccd97fa6712d0fc56c89 (patch)
tree8c8e27235f17c1fe87ed08fd8cf743b9db6fb5bd
downloadtasd-js-88f4b782cde59111a047ccd97fa6712d0fc56c89.tar.gz
tasd-js-88f4b782cde59111a047ccd97fa6712d0fc56c89.zip
Inital Commit
-rw-r--r--.eslintrc.json13
-rw-r--r--.gitignore6
-rw-r--r--README2
-rw-r--r--package.json26
-rw-r--r--src/lib.ts219
-rw-r--r--tsconfig.json21
-rw-r--r--webpack.config.js21
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
diff --git a/README b/README
new file mode 100644
index 0000000..a49e8a6
--- /dev/null
+++ b/README
@@ -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