Skip to content

Instantly share code, notes, and snippets.

@eevee
Created March 23, 2017 12:14
Show Gist options
  • Save eevee/74901a4e866df929a92c811e5885c6fb to your computer and use it in GitHub Desktop.
Save eevee/74901a4e866df929a92c811e5885c6fb to your computer and use it in GitHub Desktop.
Python script to convert exported JavaScript back into a PICO-8 cartridge
import os.path
import re
import sys
# LZ-ish decompression scheme borrowed from picolove:
# https://github.com/gamax92/picolove/blob/master/cart.lua
compression_map = b"\n 0123456789abcdefghijklmnopqrstuvwxyz!#%(){}[]<>+=/*:;.,~_"
def decompress(code):
lua = bytearray()
mode = 0
copy = None
i = 7
codelen = (code[4] << 8) | code[5]
while len(lua) < codelen:
i = i + 1
byte = code[i]
if mode == 1:
lua.append(byte)
mode = 0
elif mode == 2:
# copy from buffer
offset = len(lua) - ((copy - 0x3c) * 16 + (byte & 0xf))
length = (byte >> 4) + 2
lua.extend(lua[offset:offset + length])
mode = 0
elif byte == 0x00:
# output next byte
mode = 1
elif 0x01 <= byte <= 0x3b:
# output this byte from map
lua.append(compression_map[byte - 1])
elif byte >= 0x3c:
# copy previous bytes
mode = 2
copy = byte
return bytes(lua)
def main():
if len(sys.argv) != 3:
print("usage: pico8jstocart.py infile.js outfile.p8")
sys.exit(1)
infile, outfile = sys.argv[1:]
if os.path.exists(outfile):
print("Cowardly refusing to overwrite", outfile)
sys.exit(1)
with open(infile) as f:
jsblob = f.read()
m = re.search(r'\bvar _cartdat=[[]([0-9,\n]+)[]]', jsblob)
if m:
jsdata = m.group(1)
else:
raise ValueError("Can't find _cartdat")
data = bytes(int(number) for number in jsdata.split(','))
with open(outfile, 'w', encoding='latin1') as f:
def write(*args):
print(*args, file=f)
write("pico-8 cartridge // http://www.pico-8.com")
write("version 8")
# ROM layout largely matches the documented RAM layout:
# 0x0000 sprites
# 0x1000 shared sprite/map region
# 0x2000 map
# 0x3000 sprite flags
# 0x3100 music
# 0x3200 sounds
# 0x4300 Lua
# Lua is first in the cartridge, last in ROM
lua = data[67 * 256:]
if lua[:4] == b':c:\x00':
lua = decompress(lua)
lua = lua.decode('latin1')
write("__lua__")
write(lua)
# Next is the spritesheet. Somewhat inconveniently, the nybbles need
# flipping, because little-endian.
write("__gfx__")
for i in range(128):
write(''.join(data[j:j+1].hex()[::-1] for j in range(i * 64, (i + 1) * 64)))
# Sprite flags can be written out pretty much verbatim
write("__gff__")
write(data[0x3000:0x3080].hex())
write(data[0x3080:0x3100].hex())
# Map too!
write("__map__")
for offset in range(0x2000, 0x3000, 128):
write(data[offset:offset+128].hex())
# Sound is more of a pain. It's stored in the ROM as a compact 4 nybbles
# per note, but the cartridge uses an expanded format with 5 nybbles per
# note.
write("__sfx__")
for offset in range(0x3200, 0x4300, 68):
sfxdata = data[offset:offset+64]
# Flags are at the end of the record in the ROM, but the beginning in
# the cart. Luckily they can be dumped as-is.
flags = data[offset+64:offset+68]
buf = [flags.hex()]
for n in range(0, len(sfxdata), 2):
b0, b1 = sfxdata[n:n+2]
note = b0 & 0x3f
props = (b1 << 2) | (b0 >> 6)
instrument = (props >> 0) & 0x7
volume = (props >> 3) & 0x7
effect = (props >> 6) & 0x7
buf.append(f"{note:02x}{instrument:01x}{volume:01x}{effect:01x}")
write(''.join(buf))
# Music also comes in a compressed form, but a rather simpler one
write("__music__")
for offset in range(0x3100, 0x3200, 4):
tracks = bytearray(data[offset:offset+4])
flags = 0
for t, track in enumerate(tracks):
if track & 0x80:
flags |= 1 << t
tracks[t] ^= 0x80
write(f"{flags:02x}", tracks.hex())
# And finally, the cart ends with a blank line. And done!
write()
if __name__ == '__main__':
main()
@jocsencat
Copy link

It doesn't work properly for the lua code. The rest is exported nicely, but the lua code isn't properly decoded. I've tried to solve it, changing the encoding from latin1 to utf-8, ascii, etc, but it doesn't produce any legible output, so pico8 doesn't pick it up either.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment