Skip to content

Instantly share code, notes, and snippets.

@x4fx77x4f
Created March 27, 2024 18:13
Show Gist options
  • Save x4fx77x4f/927311c7601ce4870aee722f5856138a to your computer and use it in GitHub Desktop.
Save x4fx77x4f/927311c7601ce4870aee722f5856138a to your computer and use it in GitHub Desktop.
Picotron .p64.rom extraction and creation
#!/usr/bin/env luajit
local argparse = require("argparse")(nil, [[Create a Picotron .p64.rom.
Dependencies:
sudo apt install liblz4-1 luajit luarocks
luarocks install luaposix --local]])
argparse:argument("input_path", "Directory to read files from.")
argparse:argument("output_path", "Path to save .p64.rom to.")
local arguments = argparse:parse()
local input_path = arguments.input_path
local output_path = arguments.output_path
local output_file = assert(io.open(output_path, "w+b"))
local bit = require("bit")
local ffi = require("ffi")
local posix_dirent = require("posix.dirent")
local posix_sys_stat = require("posix.sys.stat")
local printf_file = io.stdout
if printf_file == output_file then
printf_file = io.stderr
end
local function printf(...)
return assert(printf_file:write(string.format(...)))
end
ffi.cdef([[
const char *LZ4_versionString(void);
int LZ4_compress_default(const char *, char *, int, int);
]])
local C = ffi.load("lz4")
printf("ffi.string(C.LZ4_versionString()): %q\n", ffi.string(C.LZ4_versionString()))
local function write_u8(stream, data)
return assert(stream:write(string.char(data)))
end
local function write_u32_le(stream, data)
assert(data >= 0 and data <= 2^32-1 and data%1 == 0)
return assert(stream:write(string.char(
bit.band(data, 0xff),
bit.band(bit.rshift(data, 8), 0xff),
bit.band(bit.rshift(data, 16), 0xff),
bit.rshift(data, 24)
)))
end
local function write_c_string(stream, data)
assert(string.find(data, "\x00", 1, true) == nil)
assert(stream:write(data))
return assert(stream:write("\x00"))
end
local uncompressed = {}
function uncompressed:write(data)
self[#self+1] = data
return true
end
if string.byte(input_path, -1) ~= 0x2f then -- '/'
input_path = input_path.."/"
end
local function traverse(path)
local children = posix_dirent.dir(input_path..path)
table.sort(children)
for child_index = 1, #children do
local child = children[child_index]
if posix_sys_stat.S_ISREG(assert(posix_sys_stat.stat(input_path..path..child)).st_mode) ~= 0 then
write_c_string(uncompressed, path..child)
local file = assert(io.open(input_path..path..child, "rb"))
local data = assert(file:read("*a"))
file:close()
local data_length = #data
if data_length < 0xff then
write_u8(uncompressed, data_length)
else
write_u8(uncompressed, 0xff)
write_u32_le(uncompressed, data_length)
end
uncompressed:write(data)
elseif child ~= "." and child ~= ".." then
write_c_string(uncompressed, path..child.."/")
traverse(path..child.."/")
end
end
end
traverse("")
uncompressed = table.concat(uncompressed)
local compressed_size = 1e6 -- 1 MB
local compressed = ffi.new("uint8_t[?]", compressed_size)
local compressed_length = C.LZ4_compress_default(uncompressed, compressed, #uncompressed, compressed_size)
printf("compressed_length: %s\n", compressed_length)
assert(compressed_length >= 0)
compressed = ffi.string(compressed, compressed_length)
assert(output_file:write("p64"))
write_u8(output_file, 2) -- unknown
write_u32_le(output_file, compressed_length)
assert(output_file:write(compressed))
output_file:close()
#!/usr/bin/env luajit
local argparse = require("argparse")(nil, [[Extract a Picotron .p64.rom.
Dependencies:
sudo apt install liblz4-1 luajit luarocks
luarocks install luaposix --local]])
argparse:argument("input_path", "Path to .p64.rom.")
argparse:argument("output_path", "Directory to save files to.")
argparse:flag("--dry", "Don't create any files or directories.")
local arguments = argparse:parse()
local input_path = arguments.input_path
local input_file = assert(io.open(input_path, "rb"))
local output_path = arguments.output_path
local dry = arguments.dry
local bit = require("bit")
local ffi = require("ffi")
local posix = require("posix")
local printf_file = io.stdout
if printf_file == output_file then
printf_file = io.stderr
end
local function printf(...)
return assert(printf_file:write(string.format(...)))
end
local function unpack_u32_le(data)
local byte_4, byte_3, byte_2, byte_1 = string.byte(data, 1, 4)
return bit.bor(
bit.lshift(byte_1, 24),
bit.lshift(byte_2, 16),
bit.lshift(byte_3, 8),
byte_4
)
end
local function file_read(file, length)
local data = assert(file:read(length))
return data
end
local function file_read_u8(file)
return string.byte(file_read(file, 1))
end
local function file_read_u32_le(file)
return unpack_u32_le(file_read(file, 4))
end
local function file_read_c_string(file)
local data = {}
local data_index = 1
while true do
local byte = file_read(file, 1)
if byte == "\x00" then
break
end
data[data_index] = byte
data_index = data_index+1
end
return table.concat(data)
end
local function stream_string_new(data)
return {
data = data,
offset = 1,
}
end
local function stream_string_read(stream_string, length)
local data = stream_string.data
local start = stream_string.offset
local stop = start+length-1
assert(stop <= #data)
stream_string.offset = stop+1
return string.sub(data, start, stop)
end
local function stream_string_read_u8(stream_string)
return string.byte(stream_string_read(stream_string, 1))
end
local function stream_string_read_u32_le(stream_string)
return unpack_u32_le(stream_string_read(stream_string, 4))
end
local function stream_string_read_c_string(stream_string)
local data = stream_string.data
local start = stream_string.offset
local stop = string.find(data, "\x00", start, true)
assert(stop ~= nil)
stream_string.offset = stop+1
stop = stop-1
return string.sub(data, start, stop)
end
ffi.cdef([[
const char *LZ4_versionString(void);
int LZ4_decompress_safe(const char *, char *, int, int);
]])
local C = ffi.load("lz4")
printf("ffi.string(C.LZ4_versionString()): %q\n", ffi.string(C.LZ4_versionString()))
local magic = file_read(input_file, 3)
printf("magic: %q\n", magic)
assert(magic == "p64")
local unknown = file_read_u8(input_file)
printf("unknown: 0x%02x\n", unknown)
local compressed_length = file_read_u32_le(input_file)
printf("compressed_length: %s\n", compressed_length)
local decompressed_size = 1e6 -- 1 MB
local decompressed = ffi.new("uint8_t[?]", decompressed_size)
local compressed = file_read(input_file, compressed_length)
local actual_offset = input_file:seek()
printf("actual_offset: 0x%x\n", actual_offset)
local expected_offset = input_file:seek("end")
printf("expected_offset: 0x%x\n", expected_offset)
assert(actual_offset == expected_offset)
input_file:close()
local decompressed_length = C.LZ4_decompress_safe(compressed, decompressed, compressed_length, decompressed_size)
printf("decompressed_length: %s\n", decompressed_length)
assert(decompressed_length >= 0)
decompressed = ffi.string(decompressed, decompressed_length)
local stop_offset = #decompressed+1
decompressed = stream_string_new(decompressed)
if string.byte(output_path, -1) ~= 0x2f then -- '/'
output_path = output_path.."/"
end
while decompressed.offset ~= stop_offset do
local path = stream_string_read_c_string(decompressed)
printf("path: %q\n", path)
assert(#path < 400, "\"filename parsing failed\"")
assert(string.find(path, "..", 1, true) == nil, "\"bad file description\"")
if string.byte(path, -1) == 0x2f then -- '/'
local path = output_path..path
if dry then
printf("would have created directory %q\n", path)
else
assert(posix.mkdir(path))
end
else
local data_length = stream_string_read_u8(decompressed)
printf("data_length: %s\n", data_length)
if data_length == 0xff then
data_length = stream_string_read_u32_le(decompressed)
printf("data_length: %s\n", data_length)
end
local data = stream_string_read(decompressed, data_length)
local path = output_path..path
if dry then
printf("would have created file %q\n", path)
else
local output_file = assert(io.open(path, "w+b"))
assert(output_file:write(data))
output_file:close()
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment