initial commit

This commit is contained in:
bryanthaboi
2026-07-17 20:30:02 -04:00
commit a5d2e77e7d
298 changed files with 100561 additions and 0 deletions
+143
View File
@@ -0,0 +1,143 @@
local ImageWriter = {}
local SHADES = {
{ 1, 1, 1, 1 },
{ 2 / 3, 2 / 3, 2 / 3, 1 },
{ 1 / 3, 1 / 3, 1 / 3, 1 },
{ 0, 0, 0, 1 },
}
local function assertDimensions(raw, width, height, bits)
assert(width % 8 == 0 and height % 8 == 0,
("%dbpp dimensions must be tile-aligned: %dx%d")
:format(bits, width, height))
local expected = width * height * bits / 8
assert(#raw == expected,
("%dbpp payload is %d bytes, expected %d")
:format(bits, #raw, expected))
end
function ImageWriter.decode2bpp(raw, width, height, transparent)
assertDimensions(raw, width, height, 2)
local image = love.image.newImageData(width, height)
local tilesPerRow = width / 8
for tile = 0, #raw / 16 - 1 do
local tileX = tile % tilesPerRow * 8
local tileY = math.floor(tile / tilesPerRow) * 8
for y = 0, 7 do
local low = raw[tile * 16 + y * 2 + 1]
local high = raw[tile * 16 + y * 2 + 2]
for x = 0, 7 do
local divisor = 2 ^ (7 - x)
local shade = math.floor(high / divisor) % 2 * 2
+ math.floor(low / divisor) % 2
local color = SHADES[shade + 1]
local alpha = color[4]
if transparent and shade == 0 then alpha = 0 end
image:setPixel(tileX + x, tileY + y,
color[1], color[2], color[3], alpha)
end
end
end
return image
end
function ImageWriter.decode1bpp(raw, width, height, transparent)
assertDimensions(raw, width, height, 1)
local image = love.image.newImageData(width, height)
local tilesPerRow = width / 8
for tile = 0, #raw / 8 - 1 do
local tileX = tile % tilesPerRow * 8
local tileY = math.floor(tile / tilesPerRow) * 8
for y = 0, 7 do
local row = raw[tile * 8 + y + 1]
for x = 0, 7 do
local filled = math.floor(row / 2 ^ (7 - x)) % 2 ~= 0
local value = filled and 0 or 1
local alpha = 1
if transparent and not filled then alpha = 0 end
image:setPixel(tileX + x, tileY + y,
value, value, value, alpha)
end
end
end
return image
end
function ImageWriter.blank(width, height, r, g, b, a)
local image = love.image.newImageData(width, height)
image:mapPixel(function() return r or 0, g or 0, b or 0, a or 0 end)
return image
end
function ImageWriter.blit(target, source, targetX, targetY,
sourceX, sourceY, width, height, flipX)
sourceX, sourceY = sourceX or 0, sourceY or 0
width, height = width or source:getWidth(), height or source:getHeight()
for y = 0, height - 1 do
for x = 0, width - 1 do
local sampleX = flipX and sourceX + width - 1 - x or sourceX + x
target:setPixel(targetX + x, targetY + y,
source:getPixel(sampleX, sourceY + y))
end
end
end
function ImageWriter.matteColor0(image)
local width, height = image:getDimensions()
local queueX, queueY, head = {}, {}, 1
local seen = {}
local function add(x, y)
local key = y * width + x
if seen[key] then return end
local r, g, b, a = image:getPixel(x, y)
if r == 1 and g == 1 and b == 1 and a == 1 then
seen[key] = true
queueX[#queueX + 1], queueY[#queueY + 1] = x, y
end
end
for x = 0, width - 1 do add(x, 0); add(x, height - 1) end
for y = 0, height - 1 do add(0, y); add(width - 1, y) end
while head <= #queueX do
local x, y = queueX[head], queueY[head]
head = head + 1
image:setPixel(x, y, 1, 1, 1, 0)
if x > 0 then add(x - 1, y) end
if x + 1 < width then add(x + 1, y) end
if y > 0 then add(x, y - 1) end
if y + 1 < height then add(x, y + 1) end
end
return image
end
function ImageWriter.columnsToRows(raw, tilesWide, tilesHigh, bytesPerTile)
bytesPerTile = bytesPerTile or 16
local out = {}
for y = 0, tilesHigh - 1 do
for x = 0, tilesWide - 1 do
local source = (x * tilesHigh + y) * bytesPerTile
local target = (y * tilesWide + x) * bytesPerTile
for offset = 1, bytesPerTile do
out[target + offset] = raw[source + offset]
end
end
end
return out
end
function ImageWriter.save(image, path)
local parent = path:match("^(.*)/[^/]+$")
if parent then
local ok, err = love.filesystem.createDirectory(parent)
if not ok then error("could not create " .. parent .. ": " .. tostring(err)) end
end
local ok, fileData = pcall(image.encode, image, "png")
if not ok then error("could not encode " .. path .. ": " .. tostring(fileData)) end
local written, writeError = love.filesystem.write(path, fileData)
if not written then
error("could not write " .. path .. ": " .. tostring(writeError))
end
return fileData
end
return ImageWriter
+99
View File
@@ -0,0 +1,99 @@
local LuaWriter = {}
local KEYWORDS = {
["and"] = true, ["break"] = true, ["do"] = true, ["else"] = true,
["elseif"] = true, ["end"] = true, ["false"] = true, ["for"] = true,
["function"] = true, ["goto"] = true, ["if"] = true, ["in"] = true,
["local"] = true, ["nil"] = true, ["not"] = true, ["or"] = true,
["repeat"] = true, ["return"] = true, ["then"] = true, ["true"] = true,
["until"] = true, ["while"] = true,
}
local function quote(value)
local escaped = value:gsub('[%z\1-\31\\"]', function(character)
if character == "\\" then return "\\\\" end
if character == '"' then return '\\"' end
if character == "\n" then return "\\n" end
if character == "\r" then return "\\r" end
if character == "\t" then return "\\t" end
return ("\\%03d"):format(character:byte())
end)
return '"' .. escaped .. '"'
end
local function keyText(key)
if type(key) == "string"
and key:match("^[A-Za-z_][A-Za-z0-9_]*$")
and not KEYWORDS[key] then
return key
end
return "[" .. (type(key) == "string" and quote(key) or tostring(key)) .. "]"
end
local function isArray(value)
local count, maximum = 0, 0
for key in pairs(value) do
if type(key) ~= "number" or key < 1 or key % 1 ~= 0 then
return false, 0
end
count = count + 1
maximum = math.max(maximum, key)
end
return count == maximum, maximum
end
local function sortedKeys(value)
local keys = {}
for key in pairs(value) do keys[#keys + 1] = key end
table.sort(keys, function(a, b)
if type(a) == type(b) then return a < b end
return type(a) == "number"
end)
return keys
end
local function encode(value, indent, seen)
local kind = type(value)
if value == nil then return "nil" end
if kind == "boolean" or kind == "number" then return tostring(value) end
if kind == "string" then return quote(value) end
if kind ~= "table" then
error("cannot serialize " .. kind)
end
if seen[value] then error("cannot serialize a cyclic table") end
seen[value] = true
local pad = string.rep(" ", indent)
local childPad = string.rep(" ", indent + 1)
local out = {}
local array, length = isArray(value)
if array then
for index = 1, length do
out[#out + 1] = childPad .. encode(value[index], indent + 1, seen) .. ","
end
else
for _, key in ipairs(sortedKeys(value)) do
out[#out + 1] = childPad .. keyText(key) .. " = "
.. encode(value[key], indent + 1, seen) .. ","
end
end
seen[value] = nil
if #out == 0 then return "{}" end
return "{\n" .. table.concat(out, "\n") .. "\n" .. pad .. "}"
end
function LuaWriter.encode(value)
return "return " .. encode(value, 0, {}) .. "\n"
end
function LuaWriter.write(path, value)
local parent = path:match("^(.*)/[^/]+$")
if parent then
local ok, err = love.filesystem.createDirectory(parent)
if not ok then error("could not create " .. parent .. ": " .. tostring(err)) end
end
local ok, err = love.filesystem.write(path, LuaWriter.encode(value))
if not ok then error("could not write " .. path .. ": " .. tostring(err)) end
end
return LuaWriter
+211
View File
@@ -0,0 +1,211 @@
local Rom = {}
Rom.__index = Rom
local BANK_SIZE = 0x4000
function Rom.new(data)
assert(type(data) == "string", "ROM data must be a string")
return setmetatable({ data = data }, Rom)
end
function Rom.offset(bank, address)
if bank == 0 then
assert(address >= 0 and address < BANK_SIZE,
("ROM0 address out of range: $%04X"):format(address))
return address
end
assert(address >= BANK_SIZE and address < BANK_SIZE * 2,
("bank %02X address out of range: $%04X"):format(bank, address))
return bank * BANK_SIZE + address - BANK_SIZE
end
function Rom:byte(bank, address)
local value = self.data:byte(Rom.offset(bank, address) + 1)
assert(value, ("ROM read past end at %02X:%04X"):format(bank, address))
return value
end
function Rom:word(bank, address)
return self:byte(bank, address) + self:byte(bank, address + 1) * 0x100
end
function Rom:bytes(bank, address, length)
local first = Rom.offset(bank, address) + 1
local last = first + length - 1
assert(last <= #self.data,
("ROM read past end at %02X:%04X + %d"):format(bank, address, length))
local out = {}
for index = 1, length do
out[index] = self.data:byte(first + index - 1)
end
return out
end
function Rom:decodeText(raw, charmap, stop)
local out = {}
stop = stop or 0x50
for _, value in ipairs(raw) do
if value == stop then break end
out[#out + 1] = charmap[tostring(value)]
or ("{BYTE:%02X}"):format(value)
end
return table.concat(out)
end
function Rom:readString(bank, address, charmap, stop, maxLength)
local out = {}
stop = stop or 0x50
maxLength = maxLength or 4096
for offset = 0, maxLength - 1 do
local value = self:byte(bank, address + offset)
if value == stop then return table.concat(out), offset + 1 end
out[#out + 1] = charmap[tostring(value)]
or ("{BYTE:%02X}"):format(value)
end
error(("unterminated string at %02X:%04X"):format(bank, address))
end
function Rom.bcd(raw)
local value = 0
for _, byte in ipairs(raw) do
value = value * 100 + math.floor(byte / 16) * 10 + byte % 16
end
return value
end
local BitReader = {}
BitReader.__index = BitReader
function BitReader.new(data)
return setmetatable({ data = data, byte = 1, bit = 7 }, BitReader)
end
function BitReader:read(count)
local value = 0
for _ = 1, count or 1 do
local byte = self.data[self.byte]
if not byte then error("compressed picture ended unexpectedly") end
value = value * 2 + math.floor(byte / 2 ^ self.bit) % 2
self.bit = self.bit - 1
if self.bit < 0 then
self.byte = self.byte + 1
self.bit = 7
end
end
return value
end
local function fillPicPlane(reader, width)
local mode = reader:read()
local groupCount = width * width * 0x20
local groups = {}
while #groups < groupCount do
if mode ~= 0 then
while #groups < groupCount do
local group = reader:read(2)
if group == 0 then break end
groups[#groups + 1] = group
end
else
local prefix = 0
while reader:read() ~= 0 do
prefix = prefix + 1
if prefix >= 16 then error("invalid compressed picture zero run") end
end
local zeroCount = 2 ^ (prefix + 1) - 1 + reader:read(prefix + 1)
for _ = 1, math.min(zeroCount, groupCount - #groups) do
groups[#groups + 1] = 0
end
end
mode = 1 - mode
end
local reordered = {}
for y = 0, width - 1 do
for x = 0, width * 8 - 1 do
for group = 0, 3 do
local source = (y * 4 + group) * width * 8 + x
reordered[#reordered + 1] = groups[source + 1]
end
end
end
local packed = {}
for index = 0, width * width * 8 - 1 do
local start = index * 4
packed[index + 1] = reordered[start + 1] * 0x40
+ reordered[start + 2] * 0x10
+ reordered[start + 3] * 4
+ reordered[start + 4]
end
return packed
end
local PIC_CODES = {
{ 0x0, 0x1, 0x3, 0x2, 0x7, 0x6, 0x4, 0x5,
0xF, 0xE, 0xC, 0xD, 0x8, 0x9, 0xB, 0xA },
{ 0xF, 0xE, 0xC, 0xD, 0x8, 0x9, 0xB, 0xA,
0x0, 0x1, 0x3, 0x2, 0x7, 0x6, 0x4, 0x5 },
}
local function unfilterPicPlane(plane, width)
for x = 0, width * 8 - 1 do
local bit = 0
for y = 0, width - 1 do
local index = y * width * 8 + x + 1
local high = PIC_CODES[bit + 1][math.floor(plane[index] / 16) + 1]
bit = high % 2
local low = PIC_CODES[bit + 1][plane[index] % 16 + 1]
bit = low % 2
plane[index] = high * 16 + low
end
end
end
local function transposePicTiles(data, width)
local tileCount = width * width
for index = 0, tileCount - 1 do
local other = (index * width + math.floor(index / width)) % tileCount
if index < other then
for offset = 1, 16 do
local left = index * 16 + offset
local right = other * 16 + offset
data[left], data[right] = data[right], data[left]
end
end
end
end
function Rom.decompressPic(data)
local reader = BitReader.new(data)
local width, height = reader:read(4), reader:read(4)
if width == 0 or width ~= height then
error(("compressed picture is not a non-empty square (%dx%d)")
:format(width, height))
end
local order = reader:read()
local planes = {}
planes[order + 1] = fillPicPlane(reader, width)
local mode = reader:read()
if mode ~= 0 then mode = mode + reader:read() end
planes[(1 - order) + 1] = fillPicPlane(reader, width)
unfilterPicPlane(planes[order + 1], width)
if mode ~= 1 then unfilterPicPlane(planes[(1 - order) + 1], width) end
if mode ~= 0 then
for index = 1, width * width * 8 do
planes[(1 - order) + 1][index] =
bit.bxor(planes[(1 - order) + 1][index], planes[order + 1][index])
end
end
local output = {}
for index = 1, width * width * 8 do
output[#output + 1] = planes[1][index]
output[#output + 1] = planes[2][index]
end
transposePicTiles(output, width)
return output, width
end
return Rom
File diff suppressed because it is too large Load Diff
+406
View File
@@ -0,0 +1,406 @@
local RomImporter = {}
RomImporter.__index = RomImporter
local ROM_SHA1 = "ea9bcae617fdf159b045185467ae58b2e4a48b9a"
local CACHE_MARKER = "rom-cache-v5:" .. ROM_SHA1
local MARKER_PATH = "rom-cache.complete"
local COMMUNITY_URL = "https://bois.icu"
local TRUST_WARNING = "if you did not get this from bryanthaboi's github " ..
"or a link from the discord that bryanthaboi himself posted, just know " ..
"it might have been tampered with. go to the discord to verify " ..
COMMUNITY_URL .. " (or click the logo above)"
local REQUIRED_FILES = {
"data/generated/constants.lua",
"data/generated/maps.lua",
"data/generated/text.lua",
"data/generated/field.lua",
"data/generated/battle_anims.lua",
"assets/generated/title/pokemon_logo.png",
"assets/generated/fonts/font.png",
"assets/generated/battle/front/pikachu.png",
"assets/generated/battle/anims/move_anim_0.png",
"assets/generated/battle/anims/move_anim_1.png",
"assets/generated/audio/programs.bin",
}
local function allRequiredFilesExist()
for _, path in ipairs(REQUIRED_FILES) do
if not love.filesystem.getInfo(path, "file") then return false end
end
return true
end
local function sourceTreeHasData()
if not allRequiredFilesExist() or not love.filesystem.getRealDirectory then
return false
end
local real = love.filesystem.getRealDirectory(REQUIRED_FILES[1])
return real == love.filesystem.getSource()
end
function RomImporter.isReady()
if sourceTreeHasData() then return true end
return love.filesystem.read(MARKER_PATH) == CACHE_MARKER
and allRequiredFilesExist()
end
local function removeTree(path)
local info = love.filesystem.getInfo(path)
if not info then return end
if info.type == "directory" then
for _, child in ipairs(love.filesystem.getDirectoryItems(path)) do
removeTree(path .. "/" .. child)
end
end
if love.filesystem.getRealDirectory
and love.filesystem.getRealDirectory(path)
~= love.filesystem.getSaveDirectory() then
return
end
local ok, err = love.filesystem.remove(path)
if ok == false then
error("could not remove stale cache: " .. tostring(err))
end
end
local function decodeManifest()
local raw, readError = love.filesystem.read("tools/rom_manifest.json")
if not raw then error("ROM import metadata is missing: " .. tostring(readError)) end
local Json = require("src.link.Json")
local manifest, decodeError = Json.decode(raw)
if not manifest then error("ROM import metadata is invalid: " .. tostring(decodeError)) end
assert(manifest.romSha1 == ROM_SHA1, "ROM import metadata version mismatch")
return manifest
end
local function sha1(data)
local digest = love.data.hash("sha1", data)
if type(digest) == "userdata" and digest.getString then
digest = digest:getString()
end
return love.data.encode("string", "hex", digest)
end
local function readExternalPath(path)
local file, openError = io.open(path, "rb")
if not file then return nil, openError end
local data = file:read("*a")
file:close()
return data
end
local function readDroppedFile(file)
local ok, openError = file:open("r")
if not ok then return nil, openError end
local data, readError = file:read(file:getSize())
file:close()
return data, readError
end
local function trim(value)
return value and value:gsub("^%s+", ""):gsub("%s+$", "") or ""
end
local function commandOutput(command)
local pipe = io.popen(command, "r")
if not pipe then return nil end
local result = pipe:read("*a")
pipe:close()
result = trim(result)
return result ~= "" and result or nil
end
local function chooseRom()
local platform = love.system.getOS()
if platform == "OS X" then
return commandOutput(
[[osascript -e 'POSIX path of (choose file with prompt "Choose your Pokemon Red ROM" of type {"gb"})' 2>/dev/null]])
elseif platform == "Windows" then
local script = table.concat({
"Add-Type -AssemblyName System.Windows.Forms;",
"$d=New-Object System.Windows.Forms.OpenFileDialog;",
"$d.Title='Choose your Pokemon Red ROM';",
"$d.Filter='Game Boy ROM (*.gb)|*.gb|All files (*.*)|*.*';",
"if($d.ShowDialog() -eq 'OK'){[Console]::Write($d.FileName)}",
})
return commandOutput(
'powershell -NoProfile -STA -Command "' .. script .. '"')
elseif platform == "Linux" then
local path = commandOutput(
[[zenity --file-selection --title="Choose your Pokemon Red ROM" --file-filter="Game Boy ROM | *.gb" 2>/dev/null]])
if path then return path end
return commandOutput(
[[kdialog --getopenfilename "$HOME" "*.gb|Game Boy ROM" 2>/dev/null]])
end
return nil
end
function RomImporter.new(onComplete)
return setmetatable({
onComplete = onComplete,
logo = love.graphics.newImage("assets/logo/logo.png"),
bcg = love.graphics.newImage("assets/logo/bcg.png"),
state = "waiting",
status = "Choose or drop a Pokemon Red ROM",
detail = "The ROM is verified before any files are created.",
progress = 0,
stageCurrent = 0,
stageTotal = 1,
pulse = 0,
button = {},
}, RomImporter)
end
function RomImporter:setError(message)
self.state = "error"
self.status = "That ROM could not be imported"
self.detail = tostring(message)
self.progress = 0
self.worker = nil
self.romData = nil
end
function RomImporter:startData(data, displayName)
if self.state == "working" then return end
if type(data) ~= "string" then
self:setError("The selected file could not be read.")
return
end
if #data ~= 1024 * 1024 then
self:setError(("Expected a 1 MiB Pokemon Red ROM; this file is %.2f MiB.")
:format(#data / 1024 / 1024))
return
end
self.state = "working"
self.status = "Verifying ROM"
self.detail = displayName or "Pokemon Red"
self.progress = 0
self.romData = data
self.worker = coroutine.create(function()
local actualHash = sha1(self.romData)
if actualHash ~= ROM_SHA1 then
error(("Unsupported ROM (SHA-1 %s). Use an unmodified US Pokemon Red ROM.")
:format(actualHash))
end
self.status = "Preparing private game data"
coroutine.yield()
removeTree("data/generated")
removeTree("assets/generated")
love.filesystem.remove(MARKER_PATH)
local manifest = decodeManifest()
local RomExtractor = require("src.import.RomExtractor")
local extractor = RomExtractor.new(self.romData, manifest,
function(progress, total, stage, current, stageTotal)
self.status = stage
self.progress = progress / total
self.stageCurrent = current
self.stageTotal = stageTotal
coroutine.yield()
end)
extractor:run()
self.romData = nil
collectgarbage("collect")
local ok, writeError = love.filesystem.write(MARKER_PATH, CACHE_MARKER)
if not ok then error("could not finish the private cache: " .. tostring(writeError)) end
self.state = "complete"
self.status = "Ready"
self.detail = "Starting Pokemon Red..."
self.progress = 1
if self.onComplete then self.onComplete() end
end)
end
function RomImporter:startPath(path)
if not path then return end
local data, readError = readExternalPath(path)
if not data then
self:setError("Could not read the selected file: " .. tostring(readError))
return
end
self:startData(data, path:match("[^/\\]+$") or path)
end
function RomImporter:filedropped(file)
if self.state == "working" then return end
local data, readError = readDroppedFile(file)
if not data then
self:setError("Could not read the dropped file: " .. tostring(readError))
return
end
self:startData(data, file:getFilename())
end
function RomImporter:choose()
if self.state == "working" then return end
local path = chooseRom()
if path then
self:startPath(path)
elseif love.system.getOS() ~= "OS X"
and love.system.getOS() ~= "Windows"
and love.system.getOS() ~= "Linux" then
self:setError("File selection is unavailable here. Drop the .gb file onto the window.")
end
end
function RomImporter:update(dt)
self.pulse = self.pulse + dt
if self.state ~= "working" or not self.worker then return end
local started = love.timer.getTime()
repeat
local ok, workerError = coroutine.resume(self.worker)
if not ok then
print(debug.traceback(self.worker, tostring(workerError)))
self:setError(tostring(workerError))
return
end
if coroutine.status(self.worker) == "dead" then
self.worker = nil
return
end
until love.timer.getTime() - started >= 0.008
end
local function setColor255(r, g, b, a)
love.graphics.setColor(r / 255, g / 255, b / 255, (a or 255) / 255)
end
local function printCentered(text, y, font, width)
love.graphics.setFont(font)
love.graphics.printf(text, 0, y, width, "center")
end
function RomImporter:draw()
local width, height = love.graphics.getDimensions()
setColor255(241, 243, 232)
love.graphics.rectangle("fill", 0, 0, width, height)
setColor255(181, 35, 42)
love.graphics.rectangle("fill", 0, 0, width, math.max(8, height * 0.025))
local fontKey = ("%dx%d"):format(width, height)
if self.fontKey ~= fontKey then
self.fontKey = fontKey
self.bodyFont = love.graphics.newFont(
math.max(16, math.min(22, height * 0.038)))
self.smallFont = love.graphics.newFont(
math.max(13, math.min(17, height * 0.029)))
self.warningFont = love.graphics.newFont(
math.max(10, math.min(12, height * 0.022)))
end
local bodyFont, smallFont, warningFont =
self.bodyFont, self.smallFont, self.warningFont
local contentWidth = math.min(width - 40, 520)
local left = (width - contentWidth) / 2
local logoWidth, logoHeight = self.logo:getDimensions()
local logoScale = math.min(
math.min(width - 48, 420) / logoWidth,
height * 0.15 / logoHeight)
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
self.logo,
(width - logoWidth * logoScale) / 2,
height * 0.075,
0, logoScale, logoScale)
setColor255(74, 88, 72)
printCentered("FIRST RUN", height * 0.205, smallFont, width)
local zoneY, zoneH = height * 0.29, math.min(180, height * 0.31)
setColor255(215, 220, 202)
love.graphics.rectangle("fill", left, zoneY, contentWidth, zoneH)
setColor255(74, 88, 72)
love.graphics.setLineWidth(2)
love.graphics.rectangle("line", left, zoneY, contentWidth, zoneH)
setColor255(25, 31, 28)
printCentered(self.status, zoneY + zoneH * 0.25, bodyFont, width)
setColor255(74, 88, 72)
love.graphics.setFont(smallFont)
local _, wrapped = smallFont:getWrap(self.detail, contentWidth - 48)
local visible = {}
for index = 1, math.min(#wrapped, 3) do visible[index] = wrapped[index] end
love.graphics.printf(table.concat(visible, "\n"),
left + 24, zoneY + zoneH * 0.52, contentWidth - 48, "center")
if self.state == "working" or self.state == "complete" then
local barY = zoneY + zoneH - 24
setColor255(164, 172, 151)
love.graphics.rectangle("fill", left + 24, barY, contentWidth - 48, 8)
setColor255(181, 35, 42)
love.graphics.rectangle("fill", left + 24, barY,
(contentWidth - 48) * self.progress, 8)
else
local buttonWidth = math.min(260, contentWidth - 80)
local buttonHeight = math.max(46, math.min(56, height * 0.09))
local buttonX = (width - buttonWidth) / 2
local buttonY = math.min(height - buttonHeight - 34, zoneY + zoneH + 36)
self.button = {
x = buttonX, y = buttonY, width = buttonWidth, height = buttonHeight,
}
setColor255(25, 31, 28)
love.graphics.rectangle("fill", buttonX, buttonY, buttonWidth, buttonHeight)
setColor255(255, 255, 255)
love.graphics.setFont(bodyFont)
love.graphics.printf("Choose ROM", buttonX,
buttonY + (buttonHeight - bodyFont:getHeight()) / 2,
buttonWidth, "center")
setColor255(74, 88, 72)
love.graphics.setFont(smallFont)
love.graphics.printf("or drop the .gb file here",
0, buttonY + buttonHeight + 12, width, "center")
end
local bcgWidth, bcgHeight = self.bcg:getDimensions()
love.graphics.setFont(warningFont)
local warningWidth = math.min(width - 32, 600)
local _, warningLines = warningFont:getWrap(TRUST_WARNING, warningWidth)
local warningHeight = #warningLines * warningFont:getHeight()
local warningY = height - warningHeight - 8
local bcgScale = math.min(
math.min(width - 48, 220) / bcgWidth,
height * 0.08 / bcgHeight)
local bcgDrawWidth = bcgWidth * bcgScale
local bcgDrawHeight = bcgHeight * bcgScale
local bcgX = (width - bcgDrawWidth) / 2
local bcgY = warningY - bcgDrawHeight - 8
self.bcgButton = {
x = bcgX, y = bcgY,
width = bcgDrawWidth, height = bcgDrawHeight,
}
love.graphics.setColor(1, 1, 1, 1)
love.graphics.draw(
self.bcg,
bcgX, bcgY,
0, bcgScale, bcgScale)
setColor255(74, 88, 72)
love.graphics.printf(
TRUST_WARNING,
(width - warningWidth) / 2, warningY,
warningWidth, "center")
love.graphics.setColor(1, 1, 1, 1)
end
function RomImporter:mousepressed(x, y, button)
if button ~= 1 then return end
local logo = self.bcgButton or {}
if x >= (logo.x or 0) and x <= (logo.x or 0) + (logo.width or 0)
and y >= (logo.y or 0) and y <= (logo.y or 0) + (logo.height or 0) then
love.system.openURL(COMMUNITY_URL)
return
end
if self.state == "working" then return end
local rect = self.button
if x >= (rect.x or 0) and x <= (rect.x or 0) + (rect.width or 0)
and y >= (rect.y or 0) and y <= (rect.y or 0) + (rect.height or 0) then
self:choose()
end
end
function RomImporter:keypressed(key)
if (key == "return" or key == "space") and self.state ~= "working" then
self:choose()
end
end
return RomImporter