#!/usr/bin/env -S deno run --allow-read const ppmCorrespondanceTables = { map: { 0x00_00_00: 0, // Empty 0xFF_00_00: 1, // Wall 0xFF_FF_00: 2, // Coin }, sprite: { 0x00_00_00: 0, // 0b000_000_00 0x00_00_FF: 3, // 0b000_000_11 0x00_FF_00: 28, // 0b000_111_00 0x99_50_00: 68, // 0b010_001_00 0x77_77_77: 110, // 0b011_011_10 0xB3_B3_B3: 146, // 0b100_100_10 0xFF_00_00: 224, // 0b111_000_00 0xFF_00_FF: 227, // 0b111_000_11 0xFF_80_00: 240, // 0b111_100_00 0xFF_FF_00: 248, // 0b111_110_00 0xFF_FF_FF: 255, // 0b111_111_11 }, }; /** * Return an ASM constants (U8) representation of the given data * * If needed, the representation will be padded to ensure the following code stays aligned on the instruction's length * * @param label The label to write before the first raw data * @param data An array of bytes to interprete as 24 bits values * @param matchingValues An associative array with a string representation for each possible 24 bit data value * @param instructionLength The size / length in byte of an instruction. Used to pad result to ensure data stays aligned * * @returns The ASM representation of that raw data, pre- and post-fixed by the given label */ function ppmBytesToCorrespondingAsmConstU8( label: string, data: Uint8Array, matchingValues: Record, instructionLength: number, ): string { if (data.length % 3 !== 0) { console.error( "Length of data passed to bytesToAsmConstU8 are not a multiple of 3 (it needs to, as each resulting byte need a Red, a Green and a Blue value)", ); Deno.exit(1); } const lines = [`${label}:`]; for (let i = 0; i < data.length; i += 3) { const pixelValue = (data[i] << 16) + (data[i + 1] << 8) + (data[i + 2]); if (!Object.hasOwn(matchingValues, pixelValue)) { console.error( `No matching value defined for "${ pixelValue.toString(2) }" (which is ${label}'s ${i} pixel)`, ); Deno.exit(1); } lines.push(`U8 ${matchingValues[pixelValue]}`); } lines.push(`${label}_end:`); const bytesNb = data.length / 3; const unalignedBytesNb = bytesNb % instructionLength; for (let i = 0; i < instructionLength - unalignedBytesNb; i++) { lines.push( `U8 0 ; padding to preserve ${8 * instructionLength} bits alignment`, ); } return lines.join("\n"); } /** * Return the image pixels data (excluding all other fields) from a given PPM file * * The implementation differ from the format definition (https://netpbm.sourceforge.net/doc/ppm.html) but match the file created by GIMP. */ async function getDataFromPPM(path: string): Promise { const endOfComment = [ 0x0a, 0x0d, ]; const whitespaces = [ 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x20, ]; const numbers = [ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, ]; const hashtag = 0x23; const content = await Deno.readFile(path); let offset = 0; // magic number while (!whitespaces.includes(content[offset])) { offset++; } offset++; // whitespace // potential comment if (content[offset] === hashtag) { while (!endOfComment.includes(content[offset])) { offset++; } offset++; // end of comment } // image width while (numbers.includes(content[offset])) { offset++; } offset++; // whitespace // image height while (numbers.includes(content[offset])) { offset++; } offset++; // whitespace // maximum color value while (numbers.includes(content[offset])) { offset++; } offset++; // whitespace return content.slice(offset, content.length); } function printUsage(into: (message: string) => void) { into(`Usage: \tppmToAsm.ts {instruction length} {map|sprite} {label} {file} \t\tConvert the given PPM file into a Symfony assembly representation using the given correspondance table. Result will be padded with "U8 0"s to ensure alignment to given instruction length. \tppmToAsm.ts --help \t\tPrint this message`); } /** * SCRIPT */ if (Deno.args.length !== 4) { printUsage(console.error); Deno.exit(1); } if (!(Deno.args[1] in ppmCorrespondanceTables)) { console.error(`"${Deno.args[1]}" is not a valid correspondance table.\n`); printUsage(console.error); Deno.exit(1); } const mode = Deno.args[1] as keyof typeof ppmCorrespondanceTables; const correspondanceTable = ppmCorrespondanceTables[mode]; const label = Deno.args[2]; const path = Deno.args[3]; const instructionLength = parseInt(Deno.args[0]); const ppmData = await getDataFromPPM(path); const asm = ppmBytesToCorrespondingAsmConstU8(label, ppmData, correspondanceTable, instructionLength); console.log(asm);