UNPKG

p8-data-cart

Version:

Simple tools for generating Pico-8 data carts.

214 lines (172 loc) 6.38 kB
import { CartData } from "./cart-data.js"; import { musicDataToCartBytes, runtimeBytesToMusicData } from "./music.js"; import { runtimeBytesToSfxData, sfxDataToCartBytes } from "./sfx.js"; function writeHeader(): string { return 'pico-8 cartridge // http://www.pico-8.com'; } function writeVersion(versionNumber: number): string { return 'version ' + versionNumber; } function writeNybble(value: number): string { switch (value) { case 0x0: return '0'; case 0x1: return '1'; case 0x2: return '2'; case 0x3: return '3'; case 0x4: return '4'; case 0x5: return '5'; case 0x6: return '6'; case 0x7: return '7'; case 0x8: return '8'; case 0x9: return '9'; case 0xa: return 'a'; case 0xb: return 'b'; case 0xc: return 'c'; case 0xd: return 'd'; case 0xe: return 'e'; case 0xf: return 'f'; default: throw new Error('Cannot write value as a nybble: ' + value); } } function writeByteExtended(value: number): string { switch (value) { case 0x0: return '0'; case 0x1: return '1'; case 0x2: return '2'; case 0x3: return '3'; case 0x4: return '4'; case 0x5: return '5'; case 0x6: return '6'; case 0x7: return '7'; case 0x8: return '8'; case 0x9: return '9'; case 0xa: return 'a'; case 0xb: return 'b'; case 0xc: return 'c'; case 0xd: return 'd'; case 0xe: return 'e'; case 0xf: return 'f'; case 0x80: return 'g'; case 0x81: return 'h'; case 0x82: return 'i'; case 0x83: return 'j'; case 0x84: return 'k'; case 0x85: return 'l'; case 0x86: return 'm'; case 0x87: return 'n'; case 0x88: return 'o'; case 0x89: return 'p'; case 0x8a: return 'q'; case 0x8b: return 'r'; case 0x8c: return 's'; case 0x8d: return 't'; case 0x8e: return 'u'; case 0x8f: return 'v'; default: throw new Error('Cannot write value as extended hexadecimal number: ' + value); } } function writeByte(value: number): string { const integerValue = Math.floor(value); return writeNybble(integerValue >> 4) + writeNybble(integerValue & 0xf); } function transposeByte(value: number): number { return (value & 0xf0) >> 4 | (value & 0x0f) << 4; } function trimEmptyLines(bytes: Uint8Array, bytesPerLine: number): Uint8Array { let zeroTrailIndex = -1; for (let i = bytes.length - 1; i >= 0; i--) { if (bytes[i] !== 0) { zeroTrailIndex = i + 1; break; } } if (zeroTrailIndex === -1) return new Uint8Array(0); const stopIndex = Math.ceil(zeroTrailIndex / bytesPerLine) * bytesPerLine; return bytes.slice(0, stopIndex); } function writeDataSection(bytes: Uint8Array, bytesPerLine: number, transposeBytes: boolean = false): string { bytes = trimEmptyLines(bytes, bytesPerLine); let sectionString = ''; for (let i = 0; i < bytes.length; i++) { const byteValue = transposeBytes ? transposeByte(bytes[i]) : bytes[i]; if (i > 0 && i % bytesPerLine === 0) sectionString += '\n'; sectionString += writeByte(byteValue); } return sectionString; } function writeDataSectionExtended(bytes: Uint8Array, bytesPerLine: number): string { bytes = trimEmptyLines(bytes, bytesPerLine); let sectionString = ''; for (let i = 0; i < bytes.length; i++) { const byteValue = bytes[i]; if (i > 0 && i % bytesPerLine === 0) sectionString += '\n'; sectionString += writeByteExtended(byteValue); } return sectionString; } function writeSection(label: string, content: string): string { if (!content) return ''; return `__${label}__\n${content}\n`; } function writeLuaSection(luaCode: string): string { return writeSection('lua', luaCode); } function writeGfxSection(bytes: Uint8Array): string { return writeSection('gfx', writeDataSection(bytes, 64, true)); } function writeLabelSection(bytes: Uint8Array): string { return writeSection('label', writeDataSectionExtended(bytes, 128)); } function writeGffSection(bytes: Uint8Array): string { return writeSection('gff', writeDataSection(bytes, 128)); } function writeMapSection(bytes: Uint8Array): string { return writeSection('map', writeDataSection(bytes, 128)); } function writeSfxSection(bytes: Uint8Array): string { const sfxData = runtimeBytesToSfxData(new Uint8Array(bytes)); const cartBytes = sfxDataToCartBytes(sfxData); return writeSection('sfx', writeDataSection(cartBytes, 84)); } function writeMusicSection(bytes: Uint8Array): string { bytes = trimEmptyLines(bytes, 4); const musicData = runtimeBytesToMusicData(bytes); const cartBytes = musicDataToCartBytes(musicData); let musicDataString = ''; for (let i = 0; i < cartBytes.length; i += 5) { musicDataString += writeByte(cartBytes[i + 0]); musicDataString += ' '; musicDataString += writeByte(cartBytes[i + 1]); musicDataString += writeByte(cartBytes[i + 2]); musicDataString += writeByte(cartBytes[i + 3]); musicDataString += writeByte(cartBytes[i + 4]); musicDataString += '\n'; } return writeSection('music', musicDataString); } export function writeCart(cartData: CartData): string { let cartContents = ''; cartContents += writeHeader() + '\n'; cartContents += writeVersion(cartData.version) + '\n'; if (cartData.lua !== undefined) cartContents += writeLuaSection(cartData.lua); if (cartData.gfx !== undefined) cartContents += writeGfxSection(cartData.gfx); if (cartData.label !== undefined) cartContents += writeLabelSection(cartData.label); if (cartData.gff !== undefined) cartContents += writeGffSection(cartData.gff); if (cartData.map !== undefined) cartContents += writeMapSection(cartData.map); if (cartData.sfx !== undefined) cartContents += writeSfxSection(cartData.sfx); if (cartData.music !== undefined) cartContents += writeMusicSection(cartData.music); return cartContents; }