450 lines
11 KiB
Lua
450 lines
11 KiB
Lua
local api=require("api")
|
|
|
|
local compression_map={}
|
|
for entry in ("\n 0123456789abcdefghijklmnopqrstuvwxyz!#%(){}[]<>+=/*:;.,~_"):gmatch(".") do
|
|
table.insert(compression_map, entry)
|
|
end
|
|
local function decompress(code)
|
|
-- decompress code
|
|
local lua=""
|
|
local mode=0
|
|
local copy=nil
|
|
local i=8
|
|
local codelen=bit.lshift(code:byte(5, 5), 8)+code:byte(6, 6)
|
|
log('codelen', codelen)
|
|
while #lua<codelen do
|
|
i=i+1
|
|
local byte=string.byte(code, i, i)
|
|
if byte==nil then
|
|
error('reached end of code')
|
|
else
|
|
if mode==1 then
|
|
lua=lua..code:sub(i, i)
|
|
mode=0
|
|
elseif mode==2 then
|
|
-- copy from buffer
|
|
local offset=(copy-0x3c)*16+bit.band(byte, 0xf)
|
|
local length=bit.rshift(byte, 4)+2
|
|
local offset=#lua-offset
|
|
local buffer=lua:sub(offset+1, offset+length)
|
|
lua=lua..buffer
|
|
mode=0
|
|
elseif byte==0x00 then
|
|
-- output next byte
|
|
mode=1
|
|
elseif byte>=0x01 and byte<=0x3b then
|
|
-- output this byte from map
|
|
lua=lua..compression_map[byte]
|
|
elseif byte>=0x3c then
|
|
-- copy previous bytes
|
|
mode=2
|
|
copy=byte
|
|
end
|
|
end
|
|
end
|
|
return lua
|
|
end
|
|
|
|
local cart={}
|
|
|
|
function cart.load_p8(filename)
|
|
log("Loading", filename)
|
|
|
|
local lua=""
|
|
pico8.quads={}
|
|
pico8.spritesheet_data=love.image.newImageData(128, 128)
|
|
pico8.spritesheet_data:mapPixel(function() return 0, 0, 0, 1 end)
|
|
pico8.map={}
|
|
for y=0, 63 do
|
|
pico8.map[y]={}
|
|
for x=0, 127 do
|
|
pico8.map[y][x]=0
|
|
end
|
|
end
|
|
pico8.spriteflags={}
|
|
for i=0, 255 do
|
|
pico8.spriteflags[i]=0
|
|
end
|
|
pico8.sfx={}
|
|
for i=0, 63 do
|
|
pico8.sfx[i]={
|
|
editor_mode=0,
|
|
speed=16,
|
|
loop_start=0,
|
|
loop_end=0
|
|
}
|
|
for j=0, 31 do
|
|
pico8.sfx[i][j]={0, 0, 0, 0}
|
|
end
|
|
end
|
|
pico8.music={}
|
|
for i=0, 63 do
|
|
pico8.music[i]={
|
|
loop=0,
|
|
[0]=65,
|
|
[1]=66,
|
|
[2]=67,
|
|
[3]=68
|
|
}
|
|
end
|
|
|
|
local header=love.filesystem.read(filename, 8)
|
|
if header=="\137PNG\r\n\26\n" then
|
|
local data=love.image.newImageData(filename)
|
|
if data:getWidth()~=160 or data:getHeight()~=205 then
|
|
error("Image is the wrong size")
|
|
end
|
|
|
|
local outX=0
|
|
local outY=0
|
|
local inbyte=0
|
|
local lastbyte=nil
|
|
local mapY=32
|
|
local mapX=0
|
|
local version=nil
|
|
local compressed=false
|
|
local sprite=0
|
|
for y=0, 204 do
|
|
for x=0, 159 do
|
|
local r, g, b, a=data:getPixel(x, y)
|
|
r, g, b, a=r*255, g*255, b*255, a*255
|
|
-- extract lowest bits
|
|
r=bit.band(r, 0x0003)
|
|
g=bit.band(g, 0x0003)
|
|
b=bit.band(b, 0x0003)
|
|
a=bit.band(a, 0x0003)
|
|
local byte=bit.lshift(a, 6)+bit.lshift(r, 4)+bit.lshift(g, 2)+b
|
|
local lo=bit.band(byte, 0x0f)
|
|
local hi=bit.rshift(byte, 4)
|
|
if inbyte<0x2000 then
|
|
-- spritesheet
|
|
if outY>=64 then
|
|
pico8.map[mapY][mapX]=byte
|
|
mapX=mapX+1
|
|
if mapX==128 then
|
|
mapX=0
|
|
mapY=mapY+1
|
|
end
|
|
end
|
|
pico8.spritesheet_data:setPixel(outX, outY, lo/15, 0, 0, 1)
|
|
outX=outX+1
|
|
pico8.spritesheet_data:setPixel(outX, outY, hi/15, 0, 0, 1)
|
|
outX=outX+1
|
|
if outX==128 then
|
|
outY=outY+1
|
|
outX=0
|
|
if outY==128 then
|
|
-- end of spritesheet, generate quads
|
|
pico8.spritesheet=love.graphics.newImage(pico8.spritesheet_data)
|
|
local sprite=0
|
|
for yy=0, 15 do
|
|
for xx=0, 15 do
|
|
pico8.quads[sprite]=love.graphics.newQuad(xx*8, yy*8, 8, 8, pico8.spritesheet:getDimensions())
|
|
sprite=sprite+1
|
|
end
|
|
end
|
|
mapY=0
|
|
mapX=0
|
|
end
|
|
end
|
|
elseif inbyte<0x3000 then
|
|
-- map data
|
|
pico8.map[mapY][mapX]=byte
|
|
mapX=mapX+1
|
|
if mapX==128 then
|
|
mapX=0
|
|
mapY=mapY+1
|
|
end
|
|
elseif inbyte<0x3100 then
|
|
-- sprite flags
|
|
pico8.spriteflags[sprite]=byte
|
|
sprite=sprite+1
|
|
elseif inbyte<0x3200 then
|
|
-- music
|
|
local _music=math.floor((inbyte-0x3100)/4)
|
|
pico8.music[_music][inbyte%4]=bit.band(byte, 0x7F)
|
|
pico8.music[_music].loop=bit.bor(bit.rshift(bit.band(byte, 0x80), 7-inbyte%4), pico8.music[_music].loop)
|
|
elseif inbyte<0x4300 then
|
|
-- sfx
|
|
local _sfx=math.floor((inbyte-0x3200)/68)
|
|
local step=(inbyte-0x3200)%68
|
|
if step<64 and inbyte%2==1 then
|
|
local note=bit.lshift(byte, 8)+lastbyte
|
|
pico8.sfx[_sfx][(step-1)/2]={bit.band(note, 0x3f), bit.rshift(bit.band(note, 0x1c0), 6), bit.rshift(bit.band(note, 0xe00), 9), bit.rshift(bit.band(note, 0x7000), 12)}
|
|
elseif step==64 then
|
|
pico8.sfx[_sfx].editor_mode=byte
|
|
elseif step==65 then
|
|
pico8.sfx[_sfx].speed=byte
|
|
elseif step==66 then
|
|
pico8.sfx[_sfx].loop_start=byte
|
|
elseif step==67 then
|
|
pico8.sfx[_sfx].loop_end=byte
|
|
end
|
|
elseif inbyte<0x8000 then
|
|
-- code, possibly compressed
|
|
if inbyte==0x4300 then
|
|
compressed=(byte==58)
|
|
end
|
|
lua=lua..string.char(byte)
|
|
elseif inbyte==0x8000 then
|
|
version=byte
|
|
end
|
|
lastbyte=byte
|
|
inbyte=inbyte+1
|
|
end
|
|
end
|
|
|
|
-- decompress code
|
|
log('version', version)
|
|
if version>8 then
|
|
error(string.format('unknown file version %d', version))
|
|
end
|
|
|
|
if compressed then
|
|
lua=decompress(lua)
|
|
elseif lua:find("\0", nil, true) then
|
|
lua=lua:match("(.-)%z")
|
|
end
|
|
else
|
|
local f=love.filesystem.newFile(filename, 'r')
|
|
if not f then
|
|
error(string.format("Unable to open: %s", filename))
|
|
end
|
|
local data, size=f:read()
|
|
f:close()
|
|
if not data then
|
|
error("invalid cart")
|
|
end
|
|
|
|
-- strip carriage returns pico-8 style
|
|
data=data:gsub("\r.", "\n")
|
|
-- tack on a fake header
|
|
if data:sub(-1) ~= "\n" then
|
|
data=data.."\n"
|
|
end
|
|
data=data.."__eof__\n"
|
|
|
|
-- check for header and vesion
|
|
local header="pico-8 cartridge // http://www.pico-8.com\nversion "
|
|
local start=data:find("pico%-8 cartridge // http://www.pico%-8%.com\nversion ")
|
|
if start==nil then
|
|
error("invalid cart")
|
|
end
|
|
local next_line=data:find("\n", start+#header)
|
|
local version_str=data:sub(start+#header, next_line-1)
|
|
local version=tonumber(version_str)
|
|
log("version", version)
|
|
|
|
-- extract the lua
|
|
lua=data:match("\n__lua__.-\n(.-)\n__") or ""
|
|
|
|
-- load the sprites into an imagedata
|
|
-- generate a quad for each sprite index
|
|
local gfxdata=data:match("\n__gfx__.-\n(.-\n)\n-__")
|
|
|
|
if gfxdata then
|
|
local row=0
|
|
|
|
for line in gfxdata:gmatch("(.-)\n") do
|
|
local col=0
|
|
for v in line:gmatch(".") do
|
|
v=tonumber(v, 16)
|
|
pico8.spritesheet_data:setPixel(col, row, v/15, 0, 0, 1)
|
|
|
|
col=col+1
|
|
if col==128 then break end
|
|
end
|
|
row=row+1
|
|
if row==128 then break end
|
|
end
|
|
end
|
|
|
|
local shared=0
|
|
|
|
if version>3 then
|
|
local tx, ty=0, 32
|
|
for sy=64, 127 do
|
|
for sx=0, 127, 2 do
|
|
-- get the two pixel values and merge them
|
|
local lo=pico8.spritesheet_data:getPixel(sx, sy)*15
|
|
local hi=pico8.spritesheet_data:getPixel(sx+1, sy)*15
|
|
local v=bit.bor(bit.lshift(hi, 4), lo)
|
|
pico8.map[ty][tx]=v
|
|
shared=shared+1
|
|
tx=tx+1
|
|
if tx==128 then
|
|
tx=0
|
|
ty=ty+1
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for y=0, 15 do
|
|
for x=0, 15 do
|
|
pico8.quads[y*16+x]=love.graphics.newQuad(8*x, 8*y, 8, 8, 128, 128)
|
|
end
|
|
end
|
|
|
|
pico8.spritesheet=love.graphics.newImage(pico8.spritesheet_data)
|
|
|
|
-- load the sprite flags
|
|
local gffdata=data:match("\n__gff__.-\n(.-\n)\n-__")
|
|
|
|
if gffdata then
|
|
local sprite=0
|
|
local gffpat=(version<=2 and "." or "..")
|
|
|
|
for line in gffdata:gmatch("(.-)\n") do
|
|
local col=0
|
|
|
|
for v in line:gmatch(gffpat) do
|
|
v=tonumber(v, 16)
|
|
pico8.spriteflags[sprite+col]=v
|
|
col=col+1
|
|
if col==128 then break end
|
|
end
|
|
|
|
sprite=sprite+128
|
|
if sprite==256 then break end
|
|
end
|
|
end
|
|
|
|
-- convert the tile data to a table
|
|
local mapdata=data:match("\n__map__.-\n(.-\n)\n-__")
|
|
|
|
if mapdata then
|
|
local row=0
|
|
local tiles=0
|
|
|
|
for line in mapdata:gmatch("(.-)\n") do
|
|
local col=0
|
|
for v in line:gmatch("..") do
|
|
v=tonumber(v, 16)
|
|
pico8.map[row][col]=v
|
|
col=col+1
|
|
tiles=tiles+1
|
|
if col==128 then break end
|
|
end
|
|
row=row+1
|
|
if row==32 then break end
|
|
end
|
|
-- assert(tiles+shared==128*64, string.format("%d + %d != %d (== %d)", tiles, shared, 128*64, (tiles+shared)))
|
|
end
|
|
|
|
-- load sfx
|
|
local sfxdata=data:match("\n__sfx__.-\n(.-\n)\n-__")
|
|
|
|
if sfxdata then
|
|
local _sfx=0
|
|
|
|
for line in sfxdata:gmatch("(.-)\n") do
|
|
pico8.sfx[_sfx].editor_mode=tonumber(line:sub(1, 2), 16)
|
|
pico8.sfx[_sfx].speed=tonumber(line:sub(3, 4), 16)
|
|
pico8.sfx[_sfx].loop_start=tonumber(line:sub(5, 6), 16)
|
|
pico8.sfx[_sfx].loop_end=tonumber(line:sub(7, 8), 16)
|
|
local step=0
|
|
for i=9, #line, 5 do
|
|
local v=line:sub(i, i+4)
|
|
assert(#v==5)
|
|
local note =tonumber(line:sub(i, i+1), 16)
|
|
local instr=tonumber(line:sub(i+2, i+2), 16)
|
|
local vol =tonumber(line:sub(i+3, i+3), 16)
|
|
local fx =tonumber(line:sub(i+4, i+4), 16)
|
|
pico8.sfx[_sfx][step]={note, instr, vol, fx}
|
|
step=step+1
|
|
if step==32 then break end
|
|
end
|
|
_sfx=_sfx+1
|
|
if _sfx==64 then break end
|
|
end
|
|
end
|
|
|
|
-- load music
|
|
local musicdata=data:match("\n__music__.-\n(.-\n)\n-__")
|
|
|
|
if musicdata then
|
|
local _music=0
|
|
|
|
for line in musicdata:gmatch("(.-)\n") do
|
|
local music=pico8.music[_music]
|
|
music.loop=tonumber(line:sub(1, 2), 16)
|
|
music[0]=tonumber(line:sub(4, 5), 16)
|
|
music[1]=tonumber(line:sub(6, 7), 16)
|
|
music[2]=tonumber(line:sub(8, 9), 16)
|
|
music[3]=tonumber(line:sub(10, 11), 16)
|
|
_music=_music+1
|
|
if _music==64 then break end
|
|
end
|
|
end
|
|
end
|
|
|
|
-- patch the lua
|
|
lua=lua:gsub("!=", "~=").."\n"
|
|
-- rewrite shorthand if statements eg. if (not b) i=1 j=2
|
|
lua=lua:gsub("if%s*(%b())%s*([^\n]*)\n", function(a, b)
|
|
local nl=a:find('\n', nil, true)
|
|
local th=b:find('%f[%w]then%f[%W]')
|
|
local an=b:find('%f[%w]and%f[%W]')
|
|
local o=b:find('%f[%w]or%f[%W]')
|
|
local ce=b:find('--', nil, true)
|
|
if not (nl or th or an or o) then
|
|
if ce then
|
|
local c, t=b:match("(.-)(%s-%-%-.*)")
|
|
return "if "..a:sub(2, -2).." then "..c.." end"..t.."\n"
|
|
else
|
|
return "if "..a:sub(2, -2).." then "..b.." end\n"
|
|
end
|
|
end
|
|
end)
|
|
-- rewrite assignment operators
|
|
lua=lua:gsub("(%S+)%s*([%+-%*/%%])=", "%1 = %1 %2 ")
|
|
-- convert binary literals to hex literals
|
|
lua=lua:gsub("([^%w_])0[bB]([01.]+)", function(a, b)
|
|
local p1, p2=b, ""
|
|
if b:find('.', nil, true) then
|
|
p1, p2=b:match("(.-)%.(.*)")
|
|
end
|
|
-- pad to 4 characters
|
|
p2=p2..string.rep("0", 3-((#p2-1)%4))
|
|
p1, p2=tonumber(p1, 2), tonumber(p2, 2)
|
|
if p1 and p2 then
|
|
return string.format("%s0x%x.%x", a, p1, p2)
|
|
end
|
|
end)
|
|
|
|
local cart_env={}
|
|
for k, v in pairs(api) do
|
|
cart_env[k]=v
|
|
end
|
|
cart_env._ENV=cart_env -- Lua 5.2 compatibility hack
|
|
|
|
local ok, f, e=pcall(load, lua, "@"..filename)
|
|
if not ok or f==nil then
|
|
local ln=1
|
|
lua="1:"..lua:gsub("\n", function(a) ln=ln+1 return "\n"..ln..":" end)
|
|
log('=======8<========')
|
|
log(lua)
|
|
log('=======>8========')
|
|
error("Error loading lua: "..tostring(e),0)
|
|
else
|
|
local result
|
|
setfenv(f, cart_env)
|
|
love.graphics.setShader(pico8.draw_shader)
|
|
love.graphics.setCanvas(pico8.screen)
|
|
love.graphics.origin()
|
|
restore_clip()
|
|
ok, result=pcall(f)
|
|
if not ok then
|
|
error("Error running lua: "..tostring(result))
|
|
else
|
|
log("lua completed")
|
|
end
|
|
end
|
|
log("finished loading cart", filename)
|
|
|
|
return cart_env
|
|
end
|
|
|
|
return cart
|