Skip to content

Commit

Permalink
Build modchip images
Browse files Browse the repository at this point in the history
  • Loading branch information
9ary committed Dec 3, 2023
1 parent 1622750 commit a6cf478
Show file tree
Hide file tree
Showing 8 changed files with 123 additions and 43 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/continuous-integration-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:
fetch-depth: 0

- name: Fetch Qoob prebuilt
run: curl -Lo ipl.rom https://github.com/redolution/iplboot/releases/download/r5.2/iplboot.gcb
run: curl -Lo res/ipl.rom https://github.com/redolution/iplboot/releases/download/r5.2/iplboot.gcb

- name: Build iplboot
run: |
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,5 @@ ipl.rom
*.gci

/subprojects/libogc2

!res/qoob_sx_13c_upgrade.elf
71 changes: 45 additions & 26 deletions buildtools/dol2ipl.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,22 +107,17 @@ def pack_uf2(data, base_address):
return ret

def main():
if len(sys.argv) != 4:
print(f"Usage: {sys.argv[0]} <original IPL> <executable> <output>")
if len(sys.argv) not in range(3, 4 + 1):
print(f"Usage: {sys.argv[0]} <output> <executable> [<IPL ROM>|<SX updater>]")
return 1

with open(sys.argv[1], "rb") as f:
ipl = bytearray(f.read())
output = sys.argv[1]
executable = sys.argv[2]

header = ipl[:256]
bs1 = scramble(ipl[256:2 * 1024])

if header:
print(header.decode("ASCII"))

with open(sys.argv[2], "rb") as f:
with open(executable, "rb") as f:
exe = bytearray(f.read())

if sys.argv[2].endswith(".dol"):
if executable.endswith(".dol"):
entry, load, img = flatten_dol(exe)
entry &= 0x017FFFFF
entry |= 0x80000000
Expand All @@ -132,15 +127,24 @@ def main():
print(f"Entry point: 0x{entry:0{8}X}")
print(f"Load address: 0x{load:0{8}X}")
print(f"Image size: {size} bytes ({size // 1024}K)")
elif sys.argv[2].endswith(".elf"):
elif executable.endswith(".elf"):
pass
else:
print("Unknown input format")
return -1

header = bytearray(b"(C) iplboot".ljust(256, b"\x00"))
qoob_header = bytearray(b"(C) iplboot".ljust(256, b"\x00"))

if output.endswith(".gcb"):
if len(sys.argv) < 4:
print("Missing required IPL ROM!")
return 1

rom = sys.argv[3]
with open(rom, "rb") as f:
ipl = bytearray(f.read())
bs1 = scramble(ipl[256:2 * 1024])

if sys.argv[3].endswith(".gcb"):
bs1[0x51C + 2:0x51C + 4] = struct.pack(">H", load >> 16)
bs1[0x520 + 2:0x520 + 4] = struct.pack(">H", load & 0xFFFF)
bs1[0x5D4 + 2:0x5D4 + 4] = struct.pack(">H", entry >> 16)
Expand All @@ -149,25 +153,25 @@ def main():
bs1[0x528 + 2:0x528 + 4] = struct.pack(">H", size & 0xFFFF)

# Qoob specific
npages = math.ceil((len(header) + len(bs1) + size) / 0x10000)
header[0xFD] = npages
npages = math.ceil((len(qoob_header) + len(bs1) + size) / 0x10000)
qoob_header[0xFD] = npages
print(f"Qoob blocks: {npages}")

# Put it all together
out = header + scramble(bs1 + img)
out = qoob_header + scramble(bs1 + img)

# Pad to a multiple of 64KB
out += bytearray(npages * 0x10000 - len(out))

elif sys.argv[3].endswith(".vgc"):
elif output.endswith(".vgc"):
if entry != 0x81300000 or load != 0x01300000:
print("Invalid entry point and base address (must be 0x81300000)")
return -1

header = b"VIPR\x00\x02".ljust(16, b"\x00") + b"iplboot".ljust(16, b"\x00")
out = header + scramble(bytearray(0x720) + img)[0x720:]

elif sys.argv[3].endswith(".uf2"):
elif output.endswith(".uf2"):
if entry != 0x81300000 or load != 0x01300000:
print("Invalid entry point and base address (must be 0x81300000)")
return -1
Expand All @@ -193,24 +197,39 @@ def main():

out = pack_uf2(header + img, 0x10080000)

elif sys.argv[3].endswith(".qbsx"):
elif output.startswith("qoob_sx_"):
if len(sys.argv) < 4:
print("Missing required updater!")
return 1

updater = sys.argv[3]
with open(updater, "rb") as f:
out = bytearray(f.read())

# SX BIOSes are always one page long
header[0xFD] = 1
qoob_header[0xFD] = 1

out = header + scramble(exe, qoobsx=True)
img = qoob_header + scramble(exe, qoobsx=True)

if len(out) > 62800:
if len(img) > 62800:
print("Warning: SX BIOS image too big to fit in flasher")
return -1
if len(out) > 1 << 16:
if len(img) > 1 << 16:
print("SX BIOS image too big")
return -1

msg = b"QOOB SX iplboot install\0"
msg_offset = 7240
img_offset = 7404

out[msg_offset:msg_offset + len(msg)] = msg
out[img_offset:img_offset + len(img)] = img

else:
print("Unknown output format")
return -1

with open(sys.argv[3], "wb") as f:
with open(output, "wb") as f:
f.write(out)

if __name__ == "__main__":
Expand Down
2 changes: 2 additions & 0 deletions buildtools/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
python = find_program('python3')
dol2ipl = [python, files('dol2ipl.py')]
77 changes: 66 additions & 11 deletions meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ libogc_proj = subproject(
)
libogc_deps = libogc_proj.get_variable('deps')

subdir('buildtools')
subdir('res')
objcopy = find_program('objcopy')
elf2dol = find_program('elf2dol')
dols = []
compressed_exes = []
dols = {}
compressed_exes = {}

linker_script = meson.current_source_dir() / 'ogc.ld'

Expand All @@ -37,21 +40,73 @@ iplboot = executable(
name_suffix: 'elf',
)
compressed_exes += {
'exe': iplboot,
'link_args': [
'-Wl,--section-start,.init=0x01300000'
],
'dol': true,
'iplboot': {
'exe': iplboot,
'link_args': [
'-Wl,--section-start,.init=0x01300000'
],
'dol': true,
},
'iplboot_sx': {
'exe': iplboot,
'link_args': [
'-Wl,--section-start,.init=0x01500000'
],
},
}

subdir('packer')

foreach exe: dols
custom_target(
exe.name() + '_dol',
foreach name, exe: dols
dol = custom_target(
name + '_dol',
input: exe,
output: exe.name() + '.dol',
output: name + '.dol',
command: [elf2dol, '@INPUT@', '@OUTPUT@'],
build_by_default: true,
)
dols += {name: dol}
endforeach

qoob_pro = custom_target(
'qoob_pro',
input: [dols['iplboot'], ipl_rom],
output: 'iplboot_qoob_pro.gcb',
command: [dol2ipl, '@OUTPUT@', '@INPUT@'],
build_by_default: true,
)
alias_target('qoob_pro', qoob_pro)

sx_exe = compressed_exes['iplboot_sx']['compressed']
qoob_sx_stripped = custom_target(
sx_exe.name() + '_stripped',
input: sx_exe,
output: sx_exe.name() + '_stripped.elf',
command: [objcopy, '-S', '@INPUT@', '@OUTPUT@'],
)
qoob_sx = custom_target(
'qoob_sx',
input: [qoob_sx_stripped, qoob_sx_updater],
output: 'qoob_sx_iplboot_upgrade.elf',
command: [dol2ipl, '@OUTPUT@', '@INPUT@'],
build_by_default: true,
)
alias_target('qoob_sx', qoob_sx)

viper = custom_target(
'viper',
input: dols['iplboot'],
output: 'iplboot_viper.vgc',
command: [dol2ipl, '@OUTPUT@', '@INPUT@'],
build_by_default: true,
)
alias_target('viper', viper)

pico = custom_target(
'pico',
input: dols['iplboot'],
output: 'iplboot_pico.uf2',
command: [dol2ipl, '@OUTPUT@', '@INPUT@'],
build_by_default: true,
)
alias_target('pico', pico)
10 changes: 5 additions & 5 deletions packer/meson.build
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,10 @@ packer_stub = static_library(

packer_linker_script = meson.current_source_dir() / 'link.ld'

objcopy = find_program('objcopy')
stat = find_program('stat')
seven_zip = find_program('7z')

foreach exe: compressed_exes
name = exe['exe'].name()

foreach name, exe: compressed_exes
bin = custom_target(
name + '_bin',
input: exe['exe'],
Expand Down Expand Up @@ -88,7 +85,10 @@ foreach exe: compressed_exes
name_suffix: 'elf',
)

compressed_exes += {
name: exe + {'compressed': out},
}
if exe.get('dol', false)
dols += out
dols += {name: out}
endif
endforeach
2 changes: 2 additions & 0 deletions res/meson.build
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
ipl_rom = files('ipl.rom')
qoob_sx_updater = files('qoob_sx_13c_upgrade.elf')
Binary file added res/qoob_sx_13c_upgrade.elf
Binary file not shown.

0 comments on commit a6cf478

Please sign in to comment.