initial
This commit is contained in:
@@ -0,0 +1,4 @@
|
|||||||
|
build_bins/
|
||||||
|
__pycache__/
|
||||||
|
*.egg-info
|
||||||
|
*.pyc
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
|
||||||
|
import DSRomEncryptor.blowfish_locator as blowfish_locator
|
||||||
|
import DSRomEncryptor.keytransform as keytransform
|
||||||
|
import DSRomEncryptor.blowfish as blowfish
|
||||||
|
import DSRomEncryptor.crc16 as crc16
|
||||||
|
import DSRomEncryptor.util as util
|
||||||
|
|
||||||
|
crc16 = crc16.crc16
|
||||||
|
|
||||||
|
|
||||||
@@ -0,0 +1,99 @@
|
|||||||
|
|
||||||
|
import struct
|
||||||
|
|
||||||
|
from DSRomEncryptor.util import *
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [ 'Blowfish' ]
|
||||||
|
|
||||||
|
|
||||||
|
P_TABLE_ENTRY_COUNT = 18
|
||||||
|
S_BOX_COUNT = 4
|
||||||
|
S_BOX_ENTRY_COUNT = 256
|
||||||
|
KEY_TABLE_P_TABLE_LENGTH = P_TABLE_ENTRY_COUNT * 4
|
||||||
|
KEY_TABLE_S_BOXES_LENGTH = S_BOX_COUNT * S_BOX_ENTRY_COUNT * 4
|
||||||
|
KEY_TABLE_LENGTH = KEY_TABLE_P_TABLE_LENGTH + KEY_TABLE_S_BOXES_LENGTH
|
||||||
|
BLOCK_LENGTH = 8
|
||||||
|
|
||||||
|
|
||||||
|
class Blowfish:
|
||||||
|
def __init__(self, keyTable: bytes):
|
||||||
|
if len(keyTable) < KEY_TABLE_LENGTH:
|
||||||
|
raise ValueError("Key table length %d is invalid" % len(keyTable))
|
||||||
|
|
||||||
|
#print("\tblowfish init")
|
||||||
|
self._pTable = bytes_read_u32le(keyTable, 0, P_TABLE_ENTRY_COUNT)
|
||||||
|
self._sBoxes = (
|
||||||
|
bytes_read_u32le(keyTable, 0x048, S_BOX_ENTRY_COUNT),
|
||||||
|
bytes_read_u32le(keyTable, 0x448, S_BOX_ENTRY_COUNT),
|
||||||
|
bytes_read_u32le(keyTable, 0x848, S_BOX_ENTRY_COUNT),
|
||||||
|
bytes_read_u32le(keyTable, 0xc48, S_BOX_ENTRY_COUNT),
|
||||||
|
)
|
||||||
|
#print("\tblowfish init end")
|
||||||
|
|
||||||
|
|
||||||
|
def enc_block(self, blk: int) -> int:
|
||||||
|
y = blk & 0xffff_ffff
|
||||||
|
x = (blk >> 32) & 0xffff_ffff
|
||||||
|
|
||||||
|
for i in range(16):
|
||||||
|
z = self._pTable[i] ^ x
|
||||||
|
a = self._sBoxes[0][(z >> 24) & 0xff]
|
||||||
|
b = self._sBoxes[1][(z >> 16) & 0xff]
|
||||||
|
c = self._sBoxes[2][(z >> 8) & 0xff]
|
||||||
|
d = self._sBoxes[3][(z ) & 0xff]
|
||||||
|
|
||||||
|
ab = (a + b) & 0xffff_ffff
|
||||||
|
dcab = (d + (c ^ ab)) & 0xffff_ffff
|
||||||
|
x = dcab ^ y
|
||||||
|
y = z
|
||||||
|
|
||||||
|
return (x ^ self._pTable[16]) | ((y ^ self._pTable[17]) << 32)
|
||||||
|
|
||||||
|
def dec_block(self, blk: int) -> int:
|
||||||
|
y = blk & ((1<<32)-1)
|
||||||
|
x = (blk >> 32) & ((1<<32)-1)
|
||||||
|
|
||||||
|
for i in range(17, 1, -1):
|
||||||
|
z = self._pTable[i] ^ x
|
||||||
|
a = self._sBoxes[0][(z >> 24) & 0xff]
|
||||||
|
b = self._sBoxes[1][(z >> 16) & 0xff]
|
||||||
|
c = self._sBoxes[2][(z >> 8) & 0xff]
|
||||||
|
d = self._sBoxes[3][(z >> 0) & 0xff]
|
||||||
|
|
||||||
|
a = (a + b) & ((1<<32)-1)
|
||||||
|
d = (d + (c ^ a)) & ((1<<32)-1)
|
||||||
|
x = d ^ y
|
||||||
|
y = z
|
||||||
|
|
||||||
|
return (x ^ self._pTable[1]) | ((y ^ self._pTable[0]) << 32)
|
||||||
|
|
||||||
|
|
||||||
|
def encrypt(self, data: bytes, mut: bool=False) -> bytes|None:
|
||||||
|
if len(data) & 7:
|
||||||
|
raise ValueError("Input length must be a multiple of 8, but is %d" % len(data))
|
||||||
|
|
||||||
|
mut = mut and isinstance(data, bytearray)
|
||||||
|
r = data if mut else bytearray(len(data))
|
||||||
|
|
||||||
|
for i in range(0, len(data), BLOCK_LENGTH):
|
||||||
|
#print("\tblowfish block")
|
||||||
|
v = self.enc_block(bytes_read_u64le(data, i))
|
||||||
|
bytearr_write_u64le(r, i, v)
|
||||||
|
|
||||||
|
assert len(r) == len(data)
|
||||||
|
return None if mut else bytes(r)
|
||||||
|
|
||||||
|
def decrypt(self, data: bytes, mut: bool=False) -> bytes|None:
|
||||||
|
if len(data) & 7:
|
||||||
|
raise ValueError("Input length must be a multiple of 8, but is %d" % len(data))
|
||||||
|
|
||||||
|
mut = mut and isinstance(data, bytearray)
|
||||||
|
r = data if mut else bytearray(len(data))
|
||||||
|
|
||||||
|
for i in range(0, len(data), BLOCK_LENGTH):
|
||||||
|
v = self.dec_block(bytes_read_u64le(data, i))
|
||||||
|
bytearr_write_u64le(r, i, v)
|
||||||
|
|
||||||
|
return None if mut else bytes(r)
|
||||||
|
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
|
||||||
|
|
||||||
|
import os, sys, traceback
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from DSRomEncryptor.blowfish import KEY_TABLE_LENGTH
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [ 'try_get_ntr_blowfish', 'try_get_twl_blowfish', 'try_get_twl_dev_blowfish' ]
|
||||||
|
|
||||||
|
|
||||||
|
NTR_BLOWFISH_NAME = "ntrBlowfish.bin"
|
||||||
|
TWL_BLOWFISH_NAME = "twlBlowfish.bin"
|
||||||
|
TWL_DEV_BLOWFISH_NAME = "twlDevBlowfish.bin"
|
||||||
|
BIOS_NDS_7_NAME = "biosnds7.rom"
|
||||||
|
BIOS_DSI_7_NAME = "biosdsi7.rom"
|
||||||
|
BIOS_NDS_7_LENGTH = 0x4000
|
||||||
|
BIOS_NDS_7_BLOWFISH_OFFSET = 0x30
|
||||||
|
BIOS_DSI_7_LENGTH = 0x10000
|
||||||
|
BIOS_DSI_7_BLOWFISH_OFFSET = 0xc6d0
|
||||||
|
|
||||||
|
|
||||||
|
def _try_load_from_cwd_dir(name: str) -> bytes|None:
|
||||||
|
appdir = Path(os.getcwd()).absolute()
|
||||||
|
|
||||||
|
try:
|
||||||
|
return (appdir / name).read_bytes()
|
||||||
|
except IOError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _try_load_from_app_dir(name: str) -> bytes|None:
|
||||||
|
appdir = Path(sys.argv[0]).absolute().parent
|
||||||
|
|
||||||
|
try:
|
||||||
|
return (appdir / name).read_bytes()
|
||||||
|
except IOError:
|
||||||
|
return _try_load_from_cwd_dir(name)
|
||||||
|
|
||||||
|
|
||||||
|
def try_get_ntr_blowfish() -> bytes|None:
|
||||||
|
r = _try_load_from_app_dir(NTR_BLOWFISH_NAME)
|
||||||
|
if r: return r
|
||||||
|
|
||||||
|
r = _try_load_from_app_dir(BIOS_NDS_7_NAME)
|
||||||
|
if not r or len(r) != BIOS_NDS_7_LENGTH:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return r[BIOS_NDS_7_BLOWFISH_OFFSET:][:KEY_TABLE_LENGTH]
|
||||||
|
|
||||||
|
def try_get_twl_blowfish() -> bytes|None:
|
||||||
|
r = _try_load_from_app_dir(TWL_BLOWFISH_NAME)
|
||||||
|
if r: return r
|
||||||
|
|
||||||
|
r = _try_load_from_app_dir(BIOS_DSI_7_NAME)
|
||||||
|
if not r or len(r) != BIOS_DSI_7_LENGTH:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return r[BIOS_DSI_7_BLOWFISH_OFFSET:][:KEY_TABLE_LENGTH]
|
||||||
|
|
||||||
|
def try_get_twl_dev_blowfish() -> bytes|None:
|
||||||
|
return _try_load_from_app_dir(TWL_DEV_BLOWFISH_NAME)
|
||||||
|
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
|
||||||
|
import argparse
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [ 'parse_args' ]
|
||||||
|
|
||||||
|
|
||||||
|
def parse_args():
|
||||||
|
parser = argparse.ArgumentParser(description="== DSRomEncryptor by Gericom (Python 3 port by PoroCYon) ==")
|
||||||
|
|
||||||
|
parser.add_argument('--dsidev', default=False, action='store_true', help="Use DSi dev blowfish")
|
||||||
|
parser.add_argument('input_path', type=argparse.FileType('rb'), help="The path of the input .nds file")
|
||||||
|
parser.add_argument('output_path', type=argparse.FileType('wb'), help="The path of the output .nds file")
|
||||||
|
|
||||||
|
return parser.parse_args()
|
||||||
|
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
|
||||||
|
|
||||||
|
__all__ = [ 'crc16' ]
|
||||||
|
|
||||||
|
|
||||||
|
_TABLE = [
|
||||||
|
0x0000, 0xC0C1, 0xC181, 0x0140, 0xC301, 0x03C0, 0x0280, 0xC241, 0xC601, 0x06C0, 0x0780, 0xC741, 0x0500,
|
||||||
|
0xC5C1, 0xC481, 0x0440, 0xCC01, 0x0CC0, 0x0D80, 0xCD41, 0x0F00, 0xCFC1, 0xCE81, 0x0E40, 0x0A00, 0xCAC1,
|
||||||
|
0xCB81, 0x0B40, 0xC901, 0x09C0, 0x0880, 0xC841, 0xD801, 0x18C0, 0x1980, 0xD941, 0x1B00, 0xDBC1, 0xDA81,
|
||||||
|
0x1A40, 0x1E00, 0xDEC1, 0xDF81, 0x1F40, 0xDD01, 0x1DC0, 0x1C80, 0xDC41, 0x1400, 0xD4C1, 0xD581, 0x1540,
|
||||||
|
0xD701, 0x17C0, 0x1680, 0xD641, 0xD201, 0x12C0, 0x1380, 0xD341, 0x1100, 0xD1C1, 0xD081, 0x1040, 0xF001,
|
||||||
|
0x30C0, 0x3180, 0xF141, 0x3300, 0xF3C1, 0xF281, 0x3240, 0x3600, 0xF6C1, 0xF781, 0x3740, 0xF501, 0x35C0,
|
||||||
|
0x3480, 0xF441, 0x3C00, 0xFCC1, 0xFD81, 0x3D40, 0xFF01, 0x3FC0, 0x3E80, 0xFE41, 0xFA01, 0x3AC0, 0x3B80,
|
||||||
|
0xFB41, 0x3900, 0xF9C1, 0xF881, 0x3840, 0x2800, 0xE8C1, 0xE981, 0x2940, 0xEB01, 0x2BC0, 0x2A80, 0xEA41,
|
||||||
|
0xEE01, 0x2EC0, 0x2F80, 0xEF41, 0x2D00, 0xEDC1, 0xEC81, 0x2C40, 0xE401, 0x24C0, 0x2580, 0xE541, 0x2700,
|
||||||
|
0xE7C1, 0xE681, 0x2640, 0x2200, 0xE2C1, 0xE381, 0x2340, 0xE101, 0x21C0, 0x2080, 0xE041, 0xA001, 0x60C0,
|
||||||
|
0x6180, 0xA141, 0x6300, 0xA3C1, 0xA281, 0x6240, 0x6600, 0xA6C1, 0xA781, 0x6740, 0xA501, 0x65C0, 0x6480,
|
||||||
|
0xA441, 0x6C00, 0xACC1, 0xAD81, 0x6D40, 0xAF01, 0x6FC0, 0x6E80, 0xAE41, 0xAA01, 0x6AC0, 0x6B80, 0xAB41,
|
||||||
|
0x6900, 0xA9C1, 0xA881, 0x6840, 0x7800, 0xB8C1, 0xB981, 0x7940, 0xBB01, 0x7BC0, 0x7A80, 0xBA41, 0xBE01,
|
||||||
|
0x7EC0, 0x7F80, 0xBF41, 0x7D00, 0xBDC1, 0xBC81, 0x7C40, 0xB401, 0x74C0, 0x7580, 0xB541, 0x7700, 0xB7C1,
|
||||||
|
0xB681, 0x7640, 0x7200, 0xB2C1, 0xB381, 0x7340, 0xB101, 0x71C0, 0x7080, 0xB041, 0x5000, 0x90C1, 0x9181,
|
||||||
|
0x5140, 0x9301, 0x53C0, 0x5280, 0x9241, 0x9601, 0x56C0, 0x5780, 0x9741, 0x5500, 0x95C1, 0x9481, 0x5440,
|
||||||
|
0x9C01, 0x5CC0, 0x5D80, 0x9D41, 0x5F00, 0x9FC1, 0x9E81, 0x5E40, 0x5A00, 0x9AC1, 0x9B81, 0x5B40, 0x9901,
|
||||||
|
0x59C0, 0x5880, 0x9841, 0x8801, 0x48C0, 0x4980, 0x8941, 0x4B00, 0x8BC1, 0x8A81, 0x4A40, 0x4E00, 0x8EC1,
|
||||||
|
0x8F81, 0x4F40, 0x8D01, 0x4DC0, 0x4C80, 0x8C41, 0x4400, 0x84C1, 0x8581, 0x4540, 0x8701, 0x47C0, 0x4680,
|
||||||
|
0x8641, 0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def crc16(data: bytes) -> int:
|
||||||
|
r = 0xffff
|
||||||
|
|
||||||
|
for d in data:
|
||||||
|
r = (r >> 8) ^ _TABLE[(r ^ d) & 0xff]
|
||||||
|
r = r & 0xffff
|
||||||
|
|
||||||
|
#print("crc16: len", len(data), hex(len(data)), "->", hex(r))
|
||||||
|
return r
|
||||||
|
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
|
||||||
|
import struct, codecs
|
||||||
|
|
||||||
|
from DSRomEncryptor.blowfish import Blowfish, KEY_TABLE_LENGTH, P_TABLE_ENTRY_COUNT, BLOCK_LENGTH
|
||||||
|
from DSRomEncryptor.util import *
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [ 'transform_table' ]
|
||||||
|
|
||||||
|
|
||||||
|
def apply_keycode(keycode: bytearray, modulo: int, keyTable: bytearray):
|
||||||
|
bf = Blowfish(keyTable)
|
||||||
|
|
||||||
|
#print(" keycode", codecs.encode(keycode, 'hex'))
|
||||||
|
keycode[4:12] = bf.encrypt(keycode[4:12])
|
||||||
|
#print(" keycode", codecs.encode(keycode, 'hex'))
|
||||||
|
keycode[0: 8] = bf.encrypt(keycode[0: 8])
|
||||||
|
#print(" keycode", codecs.encode(keycode, 'hex'))
|
||||||
|
|
||||||
|
assert len(keycode) == 12
|
||||||
|
|
||||||
|
#print(" keycode shuffle")
|
||||||
|
for i in range(P_TABLE_ENTRY_COUNT):
|
||||||
|
a = bytes_read_u32le(keyTable, i*4)
|
||||||
|
b = bytes_read_u32be(keycode , (i*4) % modulo)
|
||||||
|
bytearr_write_u32le(keyTable, i*4, a ^ b)
|
||||||
|
|
||||||
|
scratch = bytearray(8)
|
||||||
|
|
||||||
|
for i in range(0, KEY_TABLE_LENGTH, BLOCK_LENGTH):
|
||||||
|
#print(" keycode block", i)
|
||||||
|
bf = Blowfish(keyTable)
|
||||||
|
bf.encrypt(scratch, mut=True)
|
||||||
|
#print(" scratch", codecs.encode(scratch, 'hex'))
|
||||||
|
|
||||||
|
copy_bytes(scratch[4:8], keyTable, i )
|
||||||
|
copy_bytes(scratch[0:4], keyTable, i+4)
|
||||||
|
|
||||||
|
|
||||||
|
def transform_table(gamecode: int, level: int, modulo: int, keyTable: bytearray) -> bytes:
|
||||||
|
assert len(keyTable) == KEY_TABLE_LENGTH, len(keyTable)
|
||||||
|
newtab = bytearray(keyTable[:KEY_TABLE_LENGTH])
|
||||||
|
|
||||||
|
keycode = bytearray(12)
|
||||||
|
#print("keyxform init level", level, "gamecode", hex(gamecode))
|
||||||
|
bytearr_write_u32le(keycode, 0, gamecode )
|
||||||
|
bytearr_write_u32le(keycode, 4, gamecode >> 1)
|
||||||
|
bytearr_write_u32le(keycode, 8, (gamecode << 1) & ((1<<32)-1))
|
||||||
|
|
||||||
|
if level >= 1:
|
||||||
|
#print("keycode lv1 modulo", modulo)
|
||||||
|
apply_keycode(keycode, modulo, newtab)
|
||||||
|
if level >= 2:
|
||||||
|
#print("keycode lv2 modulo", modulo)
|
||||||
|
apply_keycode(keycode, modulo, newtab)
|
||||||
|
|
||||||
|
#print("keyxform second level", level, "gamecode", hex(gamecode))
|
||||||
|
bytearr_write_u32le(keycode, 4, (bytes_read_u32le(keycode, 4) << 1)&((1<<32)-1))
|
||||||
|
bytearr_write_u32le(keycode, 8, bytes_read_u32le(keycode, 8) >> 1)
|
||||||
|
|
||||||
|
if level >= 3:
|
||||||
|
#print("keycode lv3 modulo", modulo)
|
||||||
|
apply_keycode(keycode, modulo, newtab)
|
||||||
|
|
||||||
|
assert len(keycode) == 12
|
||||||
|
assert len(newtab) == len(keyTable[:KEY_TABLE_LENGTH])
|
||||||
|
|
||||||
|
return newtab
|
||||||
|
|
||||||
@@ -0,0 +1,142 @@
|
|||||||
|
|
||||||
|
import struct, sys, traceback
|
||||||
|
|
||||||
|
import DSRomEncryptor.blowfish_locator as blowfish_locator
|
||||||
|
import DSRomEncryptor.cmdline as cmdline
|
||||||
|
import DSRomEncryptor.keytransform as keytransform
|
||||||
|
from DSRomEncryptor.blowfish import Blowfish
|
||||||
|
from DSRomEncryptor.blowfish import KEY_TABLE_P_TABLE_LENGTH, KEY_TABLE_S_BOXES_LENGTH
|
||||||
|
from DSRomEncryptor.crc16 import crc16
|
||||||
|
from DSRomEncryptor.util import *
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [ 'main' ]
|
||||||
|
|
||||||
|
|
||||||
|
SECURE_AREA_ID = struct.unpack('<Q', b"encryObj")[0]
|
||||||
|
|
||||||
|
ROM_HEADER_GAME_CODE_OFFSET = 0xC
|
||||||
|
ROM_HEADER_UNIT_CODE_OFFSET = 0x12
|
||||||
|
ROM_HEADER_TWL_FLAGS_OFFSET = 0x1C
|
||||||
|
ROM_HEADER_ARM9_OFFSET_OFFSET = 0x20
|
||||||
|
ROM_HEADER_SECURE_AREA_CRC_OFFSET = 0x6C
|
||||||
|
ROM_HEADER_NTR_AREA_END_OFFSET = 0x90
|
||||||
|
ROM_HEADER_TWL_AREA_START_OFFSET = 0x92
|
||||||
|
ROM_HEADER_CRC_OFFSET = 0x15E
|
||||||
|
ROM_NTR_BLOWFISH_P_TABLE_OFFSET = 0x1600
|
||||||
|
ROM_NTR_BLOWFISH_S_BOXES_OFFSET = 0x1C00
|
||||||
|
ROM_SECURE_AREA_START_OFFSET = 0x4000
|
||||||
|
ROM_ENCRYPTED_SECURE_AREA_END_OFFSET = 0x4800
|
||||||
|
ROM_SECURE_AREA_END_OFFSET = 0x8000
|
||||||
|
ROM_TWL_BLOWFISH_P_TABLE_OFFSET = 0x600
|
||||||
|
ROM_TWL_BLOWFISH_S_BOXES_OFFSET = 0xC00
|
||||||
|
TWL_CHUNK_SHIFT = 19
|
||||||
|
TWL_CHUNK_SIZE = 0x80000
|
||||||
|
UNIT_CODE_TWL_FLAG = 2
|
||||||
|
TWL_FLAGS_HAS_TWL_EXCLUSIVE_AREA_FLAG = 1
|
||||||
|
|
||||||
|
|
||||||
|
TEST_PATTERN = b"\xff\x00\xff\x00\xaa\x55\xaa\x55"
|
||||||
|
|
||||||
|
|
||||||
|
def should_rom_have_twl_area(romData: bytes) -> bool:
|
||||||
|
return (romData[ROM_HEADER_UNIT_CODE_OFFSET] & UNIT_CODE_TWL_FLAG) != 0 \
|
||||||
|
and (romData[ROM_HEADER_TWL_FLAGS_OFFSET] & TWL_FLAGS_HAS_TWL_EXCLUSIVE_AREA_FLAG) != 0
|
||||||
|
|
||||||
|
|
||||||
|
def insert_test_patterns(romData: bytearray):
|
||||||
|
copy_bytes(TEST_PATTERN, romData, 0x3000)
|
||||||
|
|
||||||
|
for i in range(8, 0x200):
|
||||||
|
romData[0x3000 + i] = i & 0xff
|
||||||
|
for i in range(0, 0x200):
|
||||||
|
romData[0x3200 + i] = 0xff - (i & 0xff)
|
||||||
|
|
||||||
|
copy_bytes(b"\x00" * 0x200, romData, 0x3400)
|
||||||
|
copy_bytes(b"\xff" * 0x200, romData, 0x3600)
|
||||||
|
copy_bytes(b"\x0f" * 0x200, romData, 0x3800)
|
||||||
|
copy_bytes(b"\xf0" * 0x200, romData, 0x3a00)
|
||||||
|
copy_bytes(b"\x55" * 0x200, romData, 0x3c00)
|
||||||
|
copy_bytes(b"\xaa" * 0x1ff, romData, 0x3e00)
|
||||||
|
|
||||||
|
romData[0x3fff] = 0
|
||||||
|
|
||||||
|
|
||||||
|
def run_options(args) -> int|None:
|
||||||
|
assert args is not None
|
||||||
|
|
||||||
|
ntr_bf = blowfish_locator.try_get_ntr_blowfish()
|
||||||
|
if ntr_bf is None:
|
||||||
|
print("Error: Could not load NTR Blowfish key.", file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
|
||||||
|
romData = bytearray(args.input_path.read())
|
||||||
|
|
||||||
|
gameCode = bytes_read_u32le(romData, ROM_HEADER_GAME_CODE_OFFSET)
|
||||||
|
#print("gamecode", hex(gameCode), struct.pack('<I', gameCode))
|
||||||
|
ntrTable = keytransform.transform_table(gameCode, 2, 8, ntr_bf)
|
||||||
|
|
||||||
|
copy_bytes(ntrTable[:KEY_TABLE_P_TABLE_LENGTH],
|
||||||
|
romData, ROM_NTR_BLOWFISH_P_TABLE_OFFSET)
|
||||||
|
copy_bytes(ntrTable[KEY_TABLE_P_TABLE_LENGTH:][:KEY_TABLE_S_BOXES_LENGTH],
|
||||||
|
romData, ROM_NTR_BLOWFISH_S_BOXES_OFFSET)
|
||||||
|
|
||||||
|
insert_test_patterns(romData)
|
||||||
|
|
||||||
|
arm9off = bytes_read_u32le(romData, ROM_HEADER_ARM9_OFFSET_OFFSET)
|
||||||
|
if ROM_SECURE_AREA_END_OFFSET - arm9off > 0: #arm9off < ROM_SECURE_AREA_END_OFFSET:
|
||||||
|
sec_crc = crc16(romData[arm9off:ROM_SECURE_AREA_END_OFFSET])
|
||||||
|
if sec_crc != bytes_read_u16le(romData, ROM_HEADER_SECURE_AREA_CRC_OFFSET):
|
||||||
|
sectbl = keytransform.transform_table(gameCode, 3, 8, ntr_bf)
|
||||||
|
bf = Blowfish(sectbl)
|
||||||
|
|
||||||
|
enc_secareaID = bf.enc_block(SECURE_AREA_ID)
|
||||||
|
enc_secareaID = Blowfish(ntrTable).enc_block(enc_secareaID)
|
||||||
|
bytearr_write_u64le(romData, ROM_SECURE_AREA_START_OFFSET, enc_secareaID)
|
||||||
|
|
||||||
|
secarea_pt = romData[(ROM_SECURE_AREA_START_OFFSET + 8):ROM_ENCRYPTED_SECURE_AREA_END_OFFSET]
|
||||||
|
secarea_ct = bf.encrypt(secarea_pt)
|
||||||
|
romData[(ROM_SECURE_AREA_START_OFFSET + 8):ROM_ENCRYPTED_SECURE_AREA_END_OFFSET] = secarea_ct
|
||||||
|
|
||||||
|
sec_crc = crc16(romData[arm9off:ROM_SECURE_AREA_END_OFFSET])
|
||||||
|
bytearr_write_u16le(romData, ROM_HEADER_SECURE_AREA_CRC_OFFSET, sec_crc)
|
||||||
|
|
||||||
|
try: # twl
|
||||||
|
if should_rom_have_twl_area(romData):
|
||||||
|
twl_area_start = bytes_read_u16le(romData, ROM_HEADER_TWL_AREA_START_OFFSET) * TWL_CHUNK_SIZE
|
||||||
|
|
||||||
|
if twl_area_start == 0:
|
||||||
|
tas_value = (len(romData) + (TWL_CHUNK_SIZE - 1)) >> TWL_CHUNK_SHIFT
|
||||||
|
twl_area_start = tas_value * TWL_CHUNK_SIZE
|
||||||
|
|
||||||
|
bytearr_write_u16le(romData, ROM_HEADER_NTR_AREA_END_OFFSET , tas_value)
|
||||||
|
bytearr_write_u16le(romData, ROM_HEADER_TWL_AREA_START_OFFSET, tas_value)
|
||||||
|
|
||||||
|
romData.extend(b"\x00"*(twl_area_start - len(romData) + 0x8000))
|
||||||
|
|
||||||
|
twltab = blowfish_locator.try_get_twl_dev_blowfish() if args.dsidev \
|
||||||
|
else blowfish_locator.try_get_twl_blowfish()
|
||||||
|
if twltab is None:
|
||||||
|
print("Error: could not load TWL %sBlowfish key."%("dev " if args.dsidev else ""), file=sys.stderr)
|
||||||
|
return 1
|
||||||
|
if not args.dsidev:
|
||||||
|
twltab = keytransform.transform_table(gameCode, 1, 8, twltab)
|
||||||
|
|
||||||
|
copy_bytes(twltab[:KEY_TABLE_P_TABLE_LENGTH],
|
||||||
|
romData, twl_area_start + ROM_TWL_BLOWFISH_P_TABLE_OFFSET)
|
||||||
|
copy_bytes(twltab[KEY_TABLE_P_TABLE_LENGTH:][:KEY_TABLE_S_BOXES_LENGTH],
|
||||||
|
romData, twl_area_start + ROM_TWL_BLOWFISH_S_BOXES_OFFSET)
|
||||||
|
except:
|
||||||
|
print("Warning: couldn't insert TWL Blowfish.", file=sys.stderr)
|
||||||
|
traceback.print_exc()
|
||||||
|
|
||||||
|
# fix header CRC
|
||||||
|
bytearr_write_u16le(romData, ROM_HEADER_CRC_OFFSET, crc16(romData[:ROM_HEADER_CRC_OFFSET]))
|
||||||
|
|
||||||
|
args.output_path.write(romData)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
args = cmdline.parse_args()
|
||||||
|
exit(run_options(args) or 0)
|
||||||
|
|
||||||
@@ -0,0 +1,62 @@
|
|||||||
|
|
||||||
|
import struct
|
||||||
|
from typing import Sequence, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [ 'copy_bytes'
|
||||||
|
, 'bytes_read_u16le', 'bytes_read_u32le', 'bytes_read_u64le'
|
||||||
|
, 'bytes_read_u32be'
|
||||||
|
, 'bytearr_write_u16le', 'bytearr_write_u32le', 'bytearr_write_u64le'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def copy_bytes(src: bytes, dest: bytearray, destoff: int=0, srclen: int=None):
|
||||||
|
srclen = len(src) if srclen is None else srclen
|
||||||
|
assert srclen+destoff <= len(dest), (srclen, destoff, len(dest))
|
||||||
|
dest[destoff:(destoff+srclen)] = src
|
||||||
|
|
||||||
|
|
||||||
|
def bytes_read_u16le(bs: bytes, off: int, n: int = 1) -> int|Tuple[int]:
|
||||||
|
assert n >= 1, n
|
||||||
|
data = bs[off:(off+n*2)]
|
||||||
|
r = struct.unpack('<%dH'%n, data)
|
||||||
|
#print("\t\tbytes_read_u16le", hex(off), n, "->", "snip" if len(r) > 20 else [hex(x) for x in r])
|
||||||
|
return r[0] if n == 1 else r
|
||||||
|
|
||||||
|
def bytes_read_u32le(bs: bytes, off: int, n: int = 1) -> int|Tuple[int]:
|
||||||
|
assert n >= 1, n
|
||||||
|
data = bs[off:(off+n*4)]
|
||||||
|
r = struct.unpack('<%dI'%n, data)
|
||||||
|
#print("\t\tbytes_read_u32le", hex(off), n, "->", "snip" if len(r) > 20 else [hex(x) for x in r])
|
||||||
|
return r[0] if n == 1 else r
|
||||||
|
|
||||||
|
def bytes_read_u32be(bs: bytes, off: int, n: int = 1) -> int|Tuple[int]:
|
||||||
|
assert n >= 1, n
|
||||||
|
data = bs[off:(off+n*4)]
|
||||||
|
r = struct.unpack('>%dI'%n, data)
|
||||||
|
#print("\t\tbytes_read_u32be", hex(off), n, "->", "snip" if len(r) > 20 else [hex(x) for x in r])
|
||||||
|
return r[0] if n == 1 else r
|
||||||
|
|
||||||
|
def bytes_read_u64le(bs: bytes, off: int, n: int = 1) -> int|Tuple[int]:
|
||||||
|
assert n >= 1, n
|
||||||
|
data = bs[off:(off+n*8)]
|
||||||
|
r = struct.unpack('<%dQ'%n, data)
|
||||||
|
#print("\t\tbytes_read_u64le", hex(off), n, "->", "snip" if len(r) > 20 else [hex(x) for x in r])
|
||||||
|
return r[0] if n == 1 else r
|
||||||
|
|
||||||
|
|
||||||
|
def bytearr_write_u16le(ba: bytearray, off: int, v: int|Sequence[int]):
|
||||||
|
if isinstance(v, int): v = (v,)
|
||||||
|
#print("\t\tbytearr_write_u16le", hex(off), len(v), "snip" if len(v) > 20 else [hex(x) for x in v])
|
||||||
|
copy_bytes(struct.pack('<%dH'%len(v), *v), ba, off)
|
||||||
|
|
||||||
|
def bytearr_write_u32le(ba: bytearray, off: int, v: int|Sequence[int]):
|
||||||
|
if isinstance(v, int): v = (v,)
|
||||||
|
#print("\t\tbytearr_write_u32le", hex(off), len(v), "snip" if len(v) > 20 else [hex(x) for x in v])
|
||||||
|
copy_bytes(struct.pack('<%dI'%len(v), *v), ba, off)
|
||||||
|
|
||||||
|
def bytearr_write_u64le(ba: bytearray, off: int, v: int|Sequence[int]):
|
||||||
|
if isinstance(v, int): v = (v,)
|
||||||
|
#print("\t\tbytearr_write_u64le", hex(off), len(v), "snip" if len(v) > 20 else [hex(x) for x in v])
|
||||||
|
copy_bytes(struct.pack('<%dQ'%len(v), *v), ba, off)
|
||||||
|
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2025 Gericom
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
# DSRomEncryptor
|
||||||
|
Tool to prepare DS and DSi roms for use on a cartridge. The tool inserts the necessary NTR and TWL blowfish key blocks and encrypts the NTR secure area (0x4000-0x4800).
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
`DSRomEncryptor [--dsidev] input.nds output.nds`
|
||||||
|
|
||||||
|
### Arguments
|
||||||
|
- `--dsidev` - (optional) insert dev twl blowfish instead of retail, for use with twl dev units;
|
||||||
|
- `input.nds` - path to the input nds file;
|
||||||
|
- `output.nds` - path to the output nds file.
|
||||||
|
|
||||||
|
## Blowfish tables
|
||||||
|
The blowfish tables to be used by DSRomEncryptor are not included and need to be supplied by the user. The files described below go in the executable directory of DSRomEncryptor. Twl blowfish is only necessary when processing twl hybrid or exclusive roms, and the dev variant is only used when using the `--dsidev` flag.
|
||||||
|
|
||||||
|
- Ntr blowfish
|
||||||
|
- Either `ntrBlowfish.bin` containing the ntr blowfish table (SHA1: `84E467F2485078E401A17A5F231E3FE6E9686648`)
|
||||||
|
- Or a ds arm7 bios dump named `biosnds7.rom` (SHA1: `24F67BDEA115A2C847C8813A262502EE1607B7DF`)
|
||||||
|
- Twl blowfish (retail)
|
||||||
|
- Either `twlBlowfish.bin` containing the twl blowfish table (SHA1: `2DEA11191F28C6CC1956DADB8941AFFD4B2B5102`)
|
||||||
|
- Or a dsi arm7i bios dump named `biosdsi7.rom` (SHA1: `A3AA751EB6BDAAF8A827BA9E03576A6F1AB0F547` for an incomplete dump or `C7C7570BFE51C3C7C5DA3B01331B94E7E7CB4F53` for a complete dump)
|
||||||
|
- Twl blowfish (dev)
|
||||||
|
- `twlDevBlowfish.bin` containing the dev twl blowfish table (SHA1: `CFF62F24444F5494001F019D505F9C51D40FC8B3`)
|
||||||
|
|
||||||
|
## License
|
||||||
|
DSRomEncryptor is licensed under the MIT License, see [LICENSE](./LICENSE.txt) for more information.
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
[project]
|
||||||
|
name = "DSRomEncryptor"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Tool to prepare DS and DSi roms for use on a cartridge. The tool inserts the necessary NTR and TWL blowfish key blocks and encrypts the NTR secure area (0x4000-0x4800)."
|
||||||
|
readme = "README.md"
|
||||||
|
license = 'MIT'
|
||||||
|
keywords = []
|
||||||
|
|
||||||
|
authors = [
|
||||||
|
{name = "Gericom", email = "fnouwt2@gmail.com"},
|
||||||
|
{name = "PoroCYon", email = "p@pcy.be"},
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{name = "PoroCYon", email = "p@pcy.be"},
|
||||||
|
]
|
||||||
|
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Operating System :: Microsoft :: Windows",
|
||||||
|
"Environment :: Console",
|
||||||
|
"Typing :: Typed"
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
requires-python = ">= 3.9"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
DSRomEncryptor = "DSRomEncryptor.program:main"
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
|
||||||
|
import codecs, hashlib
|
||||||
|
|
||||||
|
from DSRomEncryptor import *
|
||||||
|
from DSRomEncryptor.util import *
|
||||||
|
|
||||||
|
|
||||||
|
def sha1(bs: bytes):
|
||||||
|
s1 = hashlib.sha1()
|
||||||
|
s1.update(bs)
|
||||||
|
return s1.digest()
|
||||||
|
|
||||||
|
|
||||||
|
def test():
|
||||||
|
a = bytearray(8)
|
||||||
|
bytearr_write_u32le(a, 4, 0x45434D41)
|
||||||
|
assert a == b"\0\0\0\0\x41\x4d\x43\x45"
|
||||||
|
assert bytes_read_u32le(a, 4) == 0x45434D41, a
|
||||||
|
assert bytes_read_u32le(a, 0, 2) == (0,0x45434D41), a
|
||||||
|
copy_bytes(a[4:], a, 0)
|
||||||
|
assert bytes_read_u32le(a, 0, 2) == (0x45434D41,0x45434D41), a
|
||||||
|
assert bytes_read_u32be(a, 0) == 0x414D4345, a
|
||||||
|
|
||||||
|
ntr_bf = blowfish_locator.try_get_ntr_blowfish()
|
||||||
|
assert ntr_bf is not None, "Could not find NTR Blowfish table."
|
||||||
|
assert sha1(ntr_bf) == codecs.decode(b"84E467F2485078E401A17A5F231E3FE6E9686648", 'hex')
|
||||||
|
|
||||||
|
twl_bf = blowfish_locator.try_get_twl_blowfish()
|
||||||
|
assert twl_bf is not None, "Could not find twl Blowfish table."
|
||||||
|
assert sha1(twl_bf) == codecs.decode(b"2DEA11191F28C6CC1956DADB8941AFFD4B2B5102", 'hex')
|
||||||
|
|
||||||
|
table = keytransform.transform_table(0x45434D41, 2, 8, ntr_bf)
|
||||||
|
|
||||||
|
digest = sha1(table)
|
||||||
|
SHA1_EXPECT = b"d7215dede5a9bf97fc75338b037a681f158c3bbc"
|
||||||
|
|
||||||
|
assert digest == codecs.decode(SHA1_EXPECT, 'hex'), (SHA1_EXPECT, codecs.encode(digest, 'hex'))
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
test()
|
||||||
|
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
|
||||||
|
.PHONY: loader9 loader7 clean
|
||||||
|
|
||||||
|
all: checklibtwl loader9 loader7 data/aplist.bin data/savelist.bin
|
||||||
|
|
||||||
|
PICO_PLATFORM ?= DSPICO
|
||||||
|
|
||||||
|
checklibtwl:
|
||||||
|
$(MAKE) -C libs/libtwl
|
||||||
|
|
||||||
|
loader9: checklibtwl
|
||||||
|
$(MAKE) -f Makefile.arm9 PLATFORM=$(PICO_PLATFORM)
|
||||||
|
|
||||||
|
loader7: checklibtwl
|
||||||
|
$(MAKE) -f Makefile.arm7
|
||||||
|
|
||||||
|
data/%.bin: data/%.csv
|
||||||
|
PicoLoaderConverter $* -i "$<" -o "$@"
|
||||||
|
|
||||||
|
clean:
|
||||||
|
$(MAKE) -f Makefile.arm7 clean
|
||||||
|
$(MAKE) -f Makefile.arm9 clean
|
||||||
|
$(RM) -rf build
|
||||||
|
|
||||||
@@ -0,0 +1,367 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Oo]ut/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
.idea/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# Wavefront obj resources
|
||||||
|
!*/Resources/Models/*.obj
|
||||||
@@ -0,0 +1,165 @@
|
|||||||
|
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import IntEnum
|
||||||
|
from math import log
|
||||||
|
import struct
|
||||||
|
|
||||||
|
|
||||||
|
__all__ = [ 'CardSaveType', 'DSProtectVersion', 'HexInt', 'BinInt'
|
||||||
|
, 'SaveListEntry', 'CsvSaveListEntry'
|
||||||
|
, 'ApListEntry', 'CsvApListEntry'
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
class CardSaveType_(IntEnum):
|
||||||
|
NONE = 0
|
||||||
|
EEPROM = 1
|
||||||
|
FLASH = 2
|
||||||
|
NAND = 3
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.name.lower()
|
||||||
|
|
||||||
|
def CardSaveType(s):
|
||||||
|
if isinstance(s, str):
|
||||||
|
return CardSaveType_(['none', 'eeprom', 'flash', 'nand'].index(s))
|
||||||
|
return CardSaveType_(s)
|
||||||
|
|
||||||
|
class DSProtectVersion_(IntEnum):
|
||||||
|
V1_06 = 0
|
||||||
|
V1_05 = 1
|
||||||
|
V1_08 = 2
|
||||||
|
V1_10 = 3
|
||||||
|
V1_20 = 4
|
||||||
|
V1_22 = 5
|
||||||
|
V1_23 = 6
|
||||||
|
V1_23Z = 7
|
||||||
|
V1_25 = 8
|
||||||
|
V1_26 = 9
|
||||||
|
V1_27 = 10
|
||||||
|
V1_28 = 11
|
||||||
|
V2_00 = 12
|
||||||
|
V2_01 = 13
|
||||||
|
V2_03 = 14
|
||||||
|
V2_05 = 15
|
||||||
|
V2_00s = 16
|
||||||
|
V2_01s = 17
|
||||||
|
V2_03s = 18
|
||||||
|
V2_05s = 19
|
||||||
|
|
||||||
|
def __str__(self) -> str:
|
||||||
|
return self.name[1:].replace('_', '.')
|
||||||
|
|
||||||
|
def DSProtectVersion(s):
|
||||||
|
if isinstance(s, str):
|
||||||
|
v = DSProtectVersion_.__members__.get("V" + s.replace('.', '_'))
|
||||||
|
if v is None:
|
||||||
|
raise ValueError("Can't convert '%s' to DSProtectVersion!" % s)
|
||||||
|
return v
|
||||||
|
return DSProtectVersion_(s)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def serialize_u24(v: int) -> bytes:
|
||||||
|
return bytes((v & 0xff, (v >> 8) & 0xff, (v >> 16) & 0x24))
|
||||||
|
|
||||||
|
def serialize_gamecode(gc: str) -> int:
|
||||||
|
enc = gc.encode()
|
||||||
|
if len(gc) != 4 or len(enc) != 4:
|
||||||
|
raise ValueError("gamecode '%s' must be an ASCII string of length 4"%gc)
|
||||||
|
return struct.unpack('<I', enc)[0]
|
||||||
|
|
||||||
|
|
||||||
|
def HexInt(x): return int(x, 0) # input CSV has 0x prefixes for hex numbers
|
||||||
|
def BinInt(x): return int(x, 2)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SaveListEntry: # little endian
|
||||||
|
gameCode: int # u32
|
||||||
|
saveType: CardSaveType # u8
|
||||||
|
saveSize: int # u8
|
||||||
|
# padding: u16
|
||||||
|
|
||||||
|
def __init__(self, gameCode: int, saveType: CardSaveType, saveSize: int):
|
||||||
|
super().__init__()
|
||||||
|
self.gameCode = gameCode & 0xffff_ffff
|
||||||
|
self.saveType = saveType
|
||||||
|
|
||||||
|
if saveSize & (saveSize - 1):
|
||||||
|
raise ValueError("saveSize %d not a power of two!" % saveSize)
|
||||||
|
|
||||||
|
self.saveSize = 0 if saveSize == 0 else (saveSize.bit_length() - 1)
|
||||||
|
|
||||||
|
def serialize(self) -> bytes:
|
||||||
|
r = struct.pack('<IBBH', self.gameCode, int(self.saveType), self.saveSize, 0)
|
||||||
|
assert len(r) == 8, r
|
||||||
|
return r
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CsvSaveListEntry:
|
||||||
|
gameCode: str
|
||||||
|
saveType: str
|
||||||
|
saveSize: HexInt
|
||||||
|
|
||||||
|
def tobinary(self) -> SaveListEntry:
|
||||||
|
return SaveListEntry(serialize_gamecode(self.gameCode),
|
||||||
|
CardSaveType(self.saveType), self.saveSize)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ApListEntry: # little endian
|
||||||
|
gameCode: int # u32
|
||||||
|
gameVersion: int # 5 bits
|
||||||
|
dsprotVersion: DSProtectVersion_ # 5 bits
|
||||||
|
dsprotFuncMask: int # 6 bits
|
||||||
|
regularOvlId: int # u16
|
||||||
|
sOvlId: int # u16
|
||||||
|
regularOffset: int # u24
|
||||||
|
sOffset: int # u24
|
||||||
|
|
||||||
|
OVERLAY_ID_STATIC_ARM9 = 0xfffe
|
||||||
|
OVERLAY_ID_INVALID = 0xffff
|
||||||
|
|
||||||
|
def __init__(self, gameCode: int, gameVersion: int, dsprotVersion: DSProtectVersion_,
|
||||||
|
dsprotFuncMask: int, regularOvlId: int, sOvlId: int,
|
||||||
|
regularOffset: int, sOffset: int):
|
||||||
|
super().__init__()
|
||||||
|
self.gameCode = gameCode & 0xffff_ffff
|
||||||
|
self.gameVersion = gameVersion & 0x1f
|
||||||
|
self.dsprotVersion = dsprotVersion
|
||||||
|
self.dsprotFuncMask = dsprotFuncMask & 0x3f
|
||||||
|
self.regularOvlId = regularOvlId & 0xffff
|
||||||
|
self.sOvlId = sOvlId & 0xffff
|
||||||
|
self.regularOffset = regularOffset & 0xff_ffff
|
||||||
|
self.sOffset = sOffset & 0xff_ffff
|
||||||
|
|
||||||
|
def serialize(self) -> bytes:
|
||||||
|
info = (self.gameVersion & 0x1f) \
|
||||||
|
| ((int(self.dsprotVersion) & 0x1f) << 5) \
|
||||||
|
| ((self.dsprotFuncMask & 0x3f) << 10)
|
||||||
|
|
||||||
|
r = struct.pack('<IHHH3B3B', self.gameCode, info,
|
||||||
|
self.regularOvlId, self.sOvlId,
|
||||||
|
*serialize_u24(self.regularOffset),
|
||||||
|
*serialize_u24(self.sOffset))
|
||||||
|
assert len(r) == 16, len(r)
|
||||||
|
return r
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class CsvApListEntry:
|
||||||
|
gameCode: str
|
||||||
|
gameVersion: int
|
||||||
|
dsprotVersion: str
|
||||||
|
dsprotFuncMask: BinInt
|
||||||
|
regularOvlId: int
|
||||||
|
regularOffset: HexInt
|
||||||
|
sOvlId: int
|
||||||
|
sOffset: HexInt
|
||||||
|
|
||||||
|
def tobinary(self) -> ApListEntry:
|
||||||
|
return ApListEntry(serialize_gamecode(self.gameCode), self.gameVersion,
|
||||||
|
DSProtectVersion(self.dsprotVersion),
|
||||||
|
self.dsprotFuncMask, self.regularOvlId, self.sOvlId,
|
||||||
|
self.regularOffset, self.sOffset)
|
||||||
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
|
||||||
|
import argparse, csv
|
||||||
|
|
||||||
|
from PicoLoaderConverter.plctypes import *
|
||||||
|
|
||||||
|
|
||||||
|
def scsv_parse_struct_table(f, typ):
|
||||||
|
annot = typ.__annotations__
|
||||||
|
header = f.readline().strip().split(';')
|
||||||
|
|
||||||
|
for x in header:
|
||||||
|
assert x in annot, (x, annot)
|
||||||
|
for k in annot.keys():
|
||||||
|
assert k in header, (k, header)
|
||||||
|
|
||||||
|
csvr = csv.DictReader(f, fieldnames=header, delimiter=';')
|
||||||
|
|
||||||
|
for row in csvr:
|
||||||
|
parsed = dict((k, annot[k](v)) for (k, v) in row.items())
|
||||||
|
yield typ(**parsed)
|
||||||
|
|
||||||
|
|
||||||
|
def run_aplist(args) -> int|None:
|
||||||
|
intable = list(scsv_parse_struct_table(args.input, CsvApListEntry))
|
||||||
|
intable.sort(key=lambda e: e.gameVersion)
|
||||||
|
intable.sort(key=lambda e: e.gameCode) # alphabetic sort, ie can't compare these as integers!
|
||||||
|
|
||||||
|
for e in intable:
|
||||||
|
args.output.write(e.tobinary().serialize())
|
||||||
|
|
||||||
|
|
||||||
|
def run_savelist(args) -> int|None:
|
||||||
|
intable = list(scsv_parse_struct_table(args.input, CsvSaveListEntry))
|
||||||
|
intable.sort(key=lambda e: e.gameCode) # alphabetic sort, ie can't compare these as integers!
|
||||||
|
|
||||||
|
for e in intable:
|
||||||
|
args.output.write(e.tobinary().serialize())
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int|None:
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
subparsers = parser.add_subparsers(dest="verb", help="subcommand")
|
||||||
|
|
||||||
|
aplist = subparsers.add_parser("aplist", help="")
|
||||||
|
|
||||||
|
aplist.add_argument('-i', "--input", type=argparse.FileType('r'), help="Input .csv file", required=True)
|
||||||
|
aplist.add_argument('-o', "--output", type=argparse.FileType('wb'), help="Output .bin file", required=True)
|
||||||
|
|
||||||
|
savelist = subparsers.add_parser("savelist", help="")
|
||||||
|
|
||||||
|
savelist.add_argument('-i', "--input", type=argparse.FileType('r'), help="Input .csv file", required=True)
|
||||||
|
savelist.add_argument('-o', "--output", type=argparse.FileType('wb'), help="Output .bin file", required=True)
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
verb = {
|
||||||
|
'aplist': run_aplist,
|
||||||
|
'savelist': run_savelist
|
||||||
|
}.get(args.verb)
|
||||||
|
|
||||||
|
if verb is None:
|
||||||
|
print("ERROR: invalid verb?!")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
return verb(args)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
exit(main() or 0)
|
||||||
|
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
[project]
|
||||||
|
name = "PicoLoaderConverter"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "Converts AP-list and savetype-list CSV files to binary format"
|
||||||
|
#readme = "README.md"
|
||||||
|
license = 'MIT'
|
||||||
|
keywords = []
|
||||||
|
|
||||||
|
authors = [
|
||||||
|
{name = "Gericom", email = "fnouwt2@gmail.com"},
|
||||||
|
{name = "PoroCYon", email = "p@pcy.be"},
|
||||||
|
]
|
||||||
|
maintainers = [
|
||||||
|
{name = "PoroCYon", email = "p@pcy.be"},
|
||||||
|
]
|
||||||
|
|
||||||
|
classifiers = [
|
||||||
|
"Development Status :: 4 - Beta",
|
||||||
|
"Programming Language :: Python :: 3",
|
||||||
|
"Programming Language :: Python :: 3.9",
|
||||||
|
"Programming Language :: Python :: 3.10",
|
||||||
|
"Programming Language :: Python :: 3.11",
|
||||||
|
"Programming Language :: Python :: 3.12",
|
||||||
|
"Programming Language :: Python :: 3.13",
|
||||||
|
"Operating System :: POSIX :: Linux",
|
||||||
|
"Operating System :: Microsoft :: Windows",
|
||||||
|
"Environment :: Console",
|
||||||
|
"Typing :: Typed"
|
||||||
|
]
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
]
|
||||||
|
|
||||||
|
requires-python = ">= 3.9"
|
||||||
|
|
||||||
|
[project.scripts]
|
||||||
|
PicoLoaderConverter = "PicoLoaderConverter.program:main"
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
# DSPico buildscripts
|
||||||
|
|
||||||
|
Scripts to automatically build the entire DSPico stack. Also, replaces the C#
|
||||||
|
components of the build with Python.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
```
|
||||||
|
$ ls
|
||||||
|
DSRomEncryptor buildscript dspico-bootloader dspico-dldi dspico-firmware dspico-wrfuxxed output pico-launcher pico-loader
|
||||||
|
|
||||||
|
$ ./buildscript/build.py --help
|
||||||
|
usage: build.py [-h] --ntr-blowfish NTR_BLOWFISH --twl-blowfish TWL_BLOWFISH
|
||||||
|
[--twl-dev-blowfish TWL_DEV_BLOWFISH] [--wrfu-tester WRFU_TESTER]
|
||||||
|
[--wonderful-env WONDERFUL_ENV]
|
||||||
|
srcdir outdir
|
||||||
|
|
||||||
|
positional arguments:
|
||||||
|
srcdir Directory containing all cloned repositories
|
||||||
|
outdir Directory containing all build artefacts
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--ntr-blowfish NTR_BLOWFISH
|
||||||
|
Source file for NTR Blowfish keys (table binary or BIOS image)
|
||||||
|
--twl-blowfish TWL_BLOWFISH
|
||||||
|
Source file for TWL Blowfish keys (table binary or BIOS/bootrom image)
|
||||||
|
--twl-dev-blowfish TWL_DEV_BLOWFISH
|
||||||
|
Source file for TWL development Blowfish keys (table binary or SRL) (optional)
|
||||||
|
--wrfu-tester WRFU_TESTER
|
||||||
|
Path to WRFU Tester SRL (for Wfruxxed, optional)
|
||||||
|
--wonderful-env WONDERFUL_ENV
|
||||||
|
Environment file to source for the Wonderful/BlocksDS toolchains
|
||||||
|
|
||||||
|
$ ./buildscript/build.py \
|
||||||
|
--ntr-blowfish path/to/biosnds7.rom \
|
||||||
|
--twl-blowfish path/to/biosdsi7.rom \
|
||||||
|
--wrfu-tester path/to/WRFUTester_v0.60_20080821.srl \
|
||||||
|
. output
|
||||||
|
```
|
||||||
@@ -0,0 +1,393 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
import argparse, codecs, hashlib, os, sys
|
||||||
|
import shutil, subprocess, traceback
|
||||||
|
import multiprocessing
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Tuple, Sequence, Dict
|
||||||
|
|
||||||
|
|
||||||
|
BLOWFISH_P_S_LENGTH = 0x1048
|
||||||
|
|
||||||
|
HASH_NTR_BLOWFISH_TABLE = codecs.decode(b"84E467F2485078E401A17A5F231E3FE6E9686648", 'hex')
|
||||||
|
HASH_NTR_BLOWFISH_BIOS = codecs.decode(b"24F67BDEA115A2C847C8813A262502EE1607B7DF", 'hex')
|
||||||
|
HASH_TWL_BLOWFISH_TABLE = codecs.decode(b"2DEA11191F28C6CC1956DADB8941AFFD4B2B5102", 'hex')
|
||||||
|
HASH_TWL_BLOWFISH_BIOS = codecs.decode(b"A3AA751EB6BDAAF8A827BA9E03576A6F1AB0F547", 'hex')
|
||||||
|
HASH_TWL_BLOWFISH_BROM = codecs.decode(b"C7C7570BFE51C3C7C5DA3B01331B94E7E7CB4F53", 'hex')
|
||||||
|
HASH_WFRU_TESTER_0_60 = codecs.decode(b"2d65fb7a0c62a4f08954b98c95f42b804fccfd26", 'hex')
|
||||||
|
HASH_TWL_DEV_BLOWFISH = codecs.decode(b"CFF62F24444F5494001F019D505F9C51D40FC8B3", 'hex')
|
||||||
|
TWL_DEV_BLOWFISH_BEGIN = b"\x69\x63\x52\x05"
|
||||||
|
|
||||||
|
|
||||||
|
NPROC = "-j%d" % multiprocessing.cpu_count()
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Tools:
|
||||||
|
sed: str
|
||||||
|
make: str
|
||||||
|
cmake: str
|
||||||
|
dlditool: str
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class InputBlobs:
|
||||||
|
ntr_bf: bytes
|
||||||
|
twl_bf: bytes
|
||||||
|
|
||||||
|
twl_dev_bf: bytes|None = None
|
||||||
|
wrfu_tester: bytes|None = None
|
||||||
|
|
||||||
|
|
||||||
|
HAS_ERRORS = False
|
||||||
|
|
||||||
|
|
||||||
|
def error(err, *args, fatal=False, **kwargs):
|
||||||
|
global HAS_ERRORS
|
||||||
|
|
||||||
|
#traceback.print_stack(file=sys.stderr)
|
||||||
|
#print(file=sys.stderr)
|
||||||
|
print(err, file=sys.stderr, *args, **kwargs)
|
||||||
|
|
||||||
|
if fatal: exit(1)
|
||||||
|
else: HAS_ERRORS = True
|
||||||
|
|
||||||
|
def check_errors():
|
||||||
|
if HAS_ERRORS:
|
||||||
|
print(file=sys.stderr)
|
||||||
|
print("Errors occurred. Exiting.", file=sys.stderr)
|
||||||
|
exit(1)
|
||||||
|
|
||||||
|
def subproc_run(args, **kwargs):
|
||||||
|
cp = subprocess.run(args, **kwargs, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||||
|
if cp.returncode != 0:
|
||||||
|
sys.stderr.buffer.write(cp.stdout)
|
||||||
|
error("Subcommand '%s' failed." % " ".join(str(x) for x in args), fatal=True)
|
||||||
|
|
||||||
|
|
||||||
|
class Check:
|
||||||
|
def repos(srcdir: Path):
|
||||||
|
for x in ('dspico-dldi', 'dspico-bootloader', 'dspico-firmware'):
|
||||||
|
if not (srcdir / x).is_dir():
|
||||||
|
error("'%s' repository not found in source directory '%s'" % (x, srcdir))
|
||||||
|
|
||||||
|
for x in ('dspico-bootloader', 'pico-loader', 'pico-launcher'):
|
||||||
|
if not (srcdir / x / 'libs' / 'libtwl' / 'Makefile').is_file():
|
||||||
|
error("'%s' repository: did you properly set up the submodules?" % x)
|
||||||
|
|
||||||
|
if not (srcdir / 'dspico-firmware' / 'pico-sdk' / 'CMakeLists.txt').is_file():
|
||||||
|
error("'dspico-firmware' repository: did you properly set up the submodules?")
|
||||||
|
if not (srcdir / 'dspico-firmware' / 'pico-sdk' / 'lib' / 'tinyusb' / 'LICENSE').is_file():
|
||||||
|
error("'dspico-firmware' repository: did you properly set up the Pico SDK submodules?")
|
||||||
|
|
||||||
|
|
||||||
|
def toolchain(wonderful_env: Path|None) -> Tools:
|
||||||
|
r = {}
|
||||||
|
for x in Tools.__annotations__.keys():
|
||||||
|
tool = os.getenv(x.upper()) or shutil.which(x)
|
||||||
|
if x == 'dlditool' and (tool is None or not Path(tool).is_file()):
|
||||||
|
bds = Path(os.getenv('BLOCKSDS') or '/opt/blocksds/core')
|
||||||
|
if bds.is_dir():
|
||||||
|
tool = bds / "tools" / "dlditool" / "dlditool"
|
||||||
|
if not tool.is_file() and wonderful_env is not None:
|
||||||
|
tool = subprocess.run(["/bin/sh", "-c", ". '%s' && which dlditool" % wonderful_env],
|
||||||
|
check=True, capture_output=True) \
|
||||||
|
.stdout.decode().strip()
|
||||||
|
|
||||||
|
if tool is None or not Path(tool).is_file():
|
||||||
|
error("No '%s' binary found. Please specify its location with the '%s' environment variable." % (x, x.upper()))
|
||||||
|
r[x] = tool
|
||||||
|
|
||||||
|
|
||||||
|
if wonderful_env is None:
|
||||||
|
blocksds = Path(os.getenv('BLOCKSDS') or '/opt/blocksds/core')
|
||||||
|
blocksdsext = Path(os.getenv('BLOCKSDSEXT') or '/opt/blocksds/ext')
|
||||||
|
wftoolcahin = Path(os.getenv('WONDERFUL_TOOLCHAIN') or '/opt/wonderful')
|
||||||
|
|
||||||
|
if not blocksds.is_dir():
|
||||||
|
error("Blocks DS not found. Please specify it via the 'BLOCKSDS' environment variable, or use --wonderful-env.")
|
||||||
|
if not blocksdsext.is_dir():
|
||||||
|
error("Blocks DS EXT not found. Please specify it via the 'BLOCKSDSEXT' environment variable, or use --wonderful-env.")
|
||||||
|
if not wftoolchain.is_dir():
|
||||||
|
error("Blocks DS not found. Please specify it via the 'BLOCKSDS' environment variable, or use --wonderful-env.")
|
||||||
|
|
||||||
|
return Tools(**r)
|
||||||
|
|
||||||
|
|
||||||
|
def prerequisites(blobs: InputBlobs, filesdir: Path):
|
||||||
|
def calc_sha1(b: bytes) -> bytes:
|
||||||
|
s = hashlib.sha1()
|
||||||
|
s.update(b)
|
||||||
|
return s.digest()
|
||||||
|
|
||||||
|
ntr_bf_sha1 = calc_sha1(blobs.ntr_bf)
|
||||||
|
if ntr_bf_sha1 == HASH_NTR_BLOWFISH_TABLE:
|
||||||
|
(filesdir / 'ntrBlowfish.bin').write_bytes(blobs.ntr_bf)
|
||||||
|
elif ntr_bf_sha1 == HASH_NTR_BLOWFISH_BIOS:
|
||||||
|
(filesdir / 'biosnds7.rom').write_bytes(blobs.ntr_bf)
|
||||||
|
else:
|
||||||
|
error("Invalid NTR blowfish file. Must be either a flat table binary, or the ARM7 BIOS image.")
|
||||||
|
|
||||||
|
twl_bf_sha1 = calc_sha1(blobs.twl_bf)
|
||||||
|
if twl_bf_sha1 == HASH_TWL_BLOWFISH_TABLE:
|
||||||
|
(filesdir / 'twlBlowfish.bin').write_bytes(blobs.twl_bf)
|
||||||
|
elif twl_bf_sha1 in (HASH_TWL_BLOWFISH_BIOS, HASH_TWL_BLOWFISH_BROM):
|
||||||
|
(filesdir / 'biosdsi7.rom').write_bytes(blobs.twl_bf)
|
||||||
|
else:
|
||||||
|
error("Invalid TWL blowfish file. Must be either a flat table binary, or the ARM7 BIOS or bootROM image.")
|
||||||
|
|
||||||
|
if blobs.twl_dev_bf is not None:
|
||||||
|
if calc_sha1(blobs.twl_dev_bf) == HASH_TWL_DEV_BLOWFISH:
|
||||||
|
(filesdir / 'twlDevBlowfish.bin').write_bytes(blobs.twl_dev_bf)
|
||||||
|
else:
|
||||||
|
off = 0
|
||||||
|
found = False
|
||||||
|
while not found:
|
||||||
|
off = blobs.twl_dev_bf.find(b"\x69\x63\x52\x05", off)
|
||||||
|
if off == -1:
|
||||||
|
break
|
||||||
|
|
||||||
|
ptable = blobs.twl_dev_bf[off:(off+0x48)]
|
||||||
|
sboxes = blobs.twl_dev_bf[(off+0x600):(off+0x1600)]
|
||||||
|
|
||||||
|
if len(ptable)+len(sboxes) == BLOWFISH_P_S_LENGTH and calc_sha1(ptable + sboxes) == HASH_TWL_DEV_BLOWFISH:
|
||||||
|
(filesdir / 'twlDevBlowfish.bin').write_bytes(ptable + sboxes)
|
||||||
|
found = True
|
||||||
|
break
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
error("Invalid TWL dev blowfish file. Must be either a flat table binary, or an SRL containing the key.")
|
||||||
|
|
||||||
|
if blobs.wrfu_tester is not None:
|
||||||
|
if calc_sha1(blobs.wrfu_tester) == HASH_WFRU_TESTER_0_60:
|
||||||
|
(filesdir / 'dsimode.nds').write_bytes(blobs.wrfu_tester)
|
||||||
|
else:
|
||||||
|
error("Invalid WRFU Tester binary. Must be the v0.60 SRL.")
|
||||||
|
|
||||||
|
|
||||||
|
def setup_venv(exedir: Path, filesdir: Path) -> Path:
|
||||||
|
venvdir = filesdir / 'dspico-build-venv'
|
||||||
|
|
||||||
|
if venvdir.is_dir():
|
||||||
|
shutil.rmtree(venvdir)
|
||||||
|
|
||||||
|
import venv
|
||||||
|
venv.create(str(venvdir), symlinks=True, with_pip=True,
|
||||||
|
prompt="dspico-build-venv")
|
||||||
|
|
||||||
|
subproc_run([str(venvdir / 'bin' / 'pip'), 'install', '-e',
|
||||||
|
str(exedir / 'DSRomEncryptor')])
|
||||||
|
subproc_run([str(venvdir / 'bin' / 'pip'), 'install', '-e',
|
||||||
|
str(exedir / 'PicoLoaderConverter')])
|
||||||
|
|
||||||
|
return venvdir
|
||||||
|
|
||||||
|
|
||||||
|
def do_args():
|
||||||
|
parser = argparse.ArgumentParser()
|
||||||
|
|
||||||
|
parser.add_argument('--ntr-blowfish', type=argparse.FileType('rb'), required=True,
|
||||||
|
help="Source file for NTR Blowfish keys (table binary or BIOS image)")
|
||||||
|
parser.add_argument('--twl-blowfish', type=argparse.FileType('rb'), required=True,
|
||||||
|
help="Source file for TWL Blowfish keys (table binary or BIOS/bootrom image)")
|
||||||
|
|
||||||
|
parser.add_argument('--twl-dev-blowfish', type=argparse.FileType('rb'),
|
||||||
|
help="Source file for TWL development Blowfish keys (table binary or SRL) (optional)")
|
||||||
|
parser.add_argument('--wrfu-tester', type=argparse.FileType('rb'),
|
||||||
|
help="Path to WRFU Tester SRL (for Wfruxxed, optional)")
|
||||||
|
|
||||||
|
parser.add_argument('--wonderful-env', type=Path,
|
||||||
|
help="Environment file to source for the Wonderful/BlocksDS toolchains")
|
||||||
|
|
||||||
|
parser.add_argument('srcdir', type=Path, help="Directory containing all cloned repositories")
|
||||||
|
parser.add_argument('outdir', type=Path, help="Directory containing all build artefacts")
|
||||||
|
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
if not args.srcdir.is_dir(): error("Source directory '%s' does not exist or is not a directory!"%args.srcdir, fatal=True)
|
||||||
|
if not args.outdir.is_dir(): error("Source directory '%s' does not exist or is not a directory!"%args.srcdir, fatal=True)
|
||||||
|
|
||||||
|
return (args, Path(sys.argv[0]).parent.absolute())
|
||||||
|
|
||||||
|
|
||||||
|
class Build:
|
||||||
|
def run_with_env(envfiles: Path|None|Sequence[Path], args, **kwargs):
|
||||||
|
if envfiles is not None:
|
||||||
|
sources = (". '%s'"%envfiles) if isinstance(envfiles, (str,Path)) \
|
||||||
|
else " && ".join(". '%s'"%s for s in envfiles)
|
||||||
|
args = ["/bin/sh", "-c", "%s && '%s'" % (sources, "' '".join(str(x) for x in args))]
|
||||||
|
subproc_run(args, **kwargs)
|
||||||
|
|
||||||
|
def dspico_dldi(envfile, tools: Tools, srcdir: Path, filesdir: Path) -> Path:
|
||||||
|
prjdir = srcdir / "dspico-dldi"
|
||||||
|
Build.run_with_env(envfile, [tools.make, '-C', str(prjdir), NPROC])
|
||||||
|
return Path(shutil.copy((prjdir / "DSpico.dldi"), filesdir))
|
||||||
|
|
||||||
|
def dspico_bootloader(envfile, tools: Tools, srcdir: Path, romenc: Path, filesdir: Path) -> Path:
|
||||||
|
prjdir = srcdir / "dspico-bootloader"
|
||||||
|
Build.run_with_env(envfile, [tools.make, '-C', str(prjdir), NPROC])
|
||||||
|
subproc_run([tools.dlditool, filesdir / "DSpico.dldi", prjdir / "BOOTLOADER.nds"])
|
||||||
|
subproc_run([romenc, prjdir / "BOOTLOADER.nds", filesdir / 'default.nds'], cwd=filesdir)
|
||||||
|
return filesdir / 'default.nds'
|
||||||
|
|
||||||
|
def dspico_wrfuxxed(envfile, tools: Tools, srcdir: Path, filesdir: Path) -> Path:
|
||||||
|
prjdir = srcdir / "dspico-wrfuxxed"
|
||||||
|
Build.run_with_env(envfile, [tools.make, '-C', str(prjdir), NPROC])
|
||||||
|
return Path(shutil.copy((prjdir / "uartBufv060.bin"), filesdir))
|
||||||
|
|
||||||
|
def dspico_firmware(tools: Tools, srcdir: Path, filesdir: Path, default_nds: Path, uartBufv060=None) -> (Path, Path):
|
||||||
|
prjdir = srcdir / "dspico-firmware"
|
||||||
|
buildname = "vanilla" if uartBufv060 is None else "wrfuxxed"
|
||||||
|
|
||||||
|
shutil.copy(default_nds, prjdir / 'roms')
|
||||||
|
if uartBufv060:
|
||||||
|
shutil.copy(filesdir / 'dsimode.nds', prjdir / 'roms')
|
||||||
|
shutil.copy(uartBufv060, prjdir / 'data')
|
||||||
|
subproc_run([tools.sed, '--posix', '-i',
|
||||||
|
's/^.*DSPICO_ENABLE_WRFUXXED.*$/ DSPICO_ENABLE_WRFUXXED/', prjdir / 'CMakeLists.txt'])
|
||||||
|
else:
|
||||||
|
subproc_run([tools.sed, '--posix', '-i',
|
||||||
|
's/^.*DSPICO_ENABLE_WRFUXXED.*$/ #DSPICO_ENABLE_WRFUXXED/', prjdir / 'CMakeLists.txt'])
|
||||||
|
|
||||||
|
|
||||||
|
cmake_build = prjdir / ('cmake-build-%s' % buildname)
|
||||||
|
if cmake_build.is_dir(): shutil.rmtree(cmake_build)
|
||||||
|
cmake_build.mkdir()
|
||||||
|
|
||||||
|
cmake_ver_str = subprocess.run([tools.cmake, "--version"], check=True, capture_output=True).stdout.decode().splitlines()[0]
|
||||||
|
if not cmake_ver_str.startswith("cmake version "):
|
||||||
|
error("Can't parse CMake version output: %s" % cmake_ver_str)
|
||||||
|
return None
|
||||||
|
cmake_ver = tuple(int(x) for x in cmake_ver_str[len("cmake version "):].split("."))
|
||||||
|
|
||||||
|
cmake_env = dict(os.environ)
|
||||||
|
cmake_env['PICO_SDK_PATH'] = "../pico-sdk"
|
||||||
|
cmake_args = ["-DCMAKE_BUILD_TYPE:STRING=RelWithDebInfo",
|
||||||
|
"-G", "Unix Makefiles", "-B", cmake_build, prjdir]
|
||||||
|
|
||||||
|
if cmake_ver >= (4,):
|
||||||
|
# we are running CMake version 4.x or newer, so we need to fix up some stuff
|
||||||
|
cmake_args.insert(0, "-DCMAKE_POLICY_VERSION_MINIMUM=3.5")
|
||||||
|
subproc_run([tools.sed, '--posix', '-i',
|
||||||
|
's/^cmake_minimum_required(VERSION 3\\..*)/cmake_minimum_required(VERSION 3.13)/',
|
||||||
|
prjdir / "pico-sdk" / "tools" / "pioasm" / "CMakeLists.txt"])
|
||||||
|
|
||||||
|
subproc_run([tools.cmake, *cmake_args], env=cmake_env)
|
||||||
|
subproc_run([tools.make, "-C", cmake_build, NPROC])
|
||||||
|
return shutil.copy(cmake_build / 'DSpico.uf2', filesdir / ("DSpico-%s.uf2" % buildname))
|
||||||
|
|
||||||
|
def dspico_loader(envfile, tools: Tools, srcdir: Path, filesdir: Path, exedir: Path, venvdir: Path) -> Tuple[Path, Path, Path, Path]:
|
||||||
|
prjdir = srcdir / "pico-loader"
|
||||||
|
|
||||||
|
Build.run_with_env((envfile, venvdir/'bin'/'activate'),
|
||||||
|
[tools.make, '-f', exedir / 'Makefile.loader',
|
||||||
|
'-C', prjdir, NPROC])
|
||||||
|
|
||||||
|
a = shutil.copy(prjdir / 'picoLoader7.bin', filesdir)
|
||||||
|
b = shutil.copy(prjdir / 'picoLoader9_DSPICO.bin', filesdir)
|
||||||
|
c = shutil.copy(prjdir / 'data' / 'aplist.bin', filesdir)
|
||||||
|
d = shutil.copy(prjdir / 'data' / 'savelist.bin', filesdir)
|
||||||
|
|
||||||
|
return (a, b, c, d)
|
||||||
|
|
||||||
|
def dspico_launcher(envfile, tools: Tools, srcdir: Path, filesdir: Path) -> Tuple[Path, Path]:
|
||||||
|
prjdir = srcdir / "pico-launcher"
|
||||||
|
|
||||||
|
Build.run_with_env(envfile, [tools.make, '-C', prjdir, NPROC])
|
||||||
|
|
||||||
|
return (shutil.copy(prjdir / 'LAUNCHER.nds', filesdir), (prjdir / '_pico'))
|
||||||
|
|
||||||
|
|
||||||
|
def install_to_destdir(filesdict: Dict[str, Path], outdir: Path):
|
||||||
|
if (outdir / '_pîco').is_dir(): shutil.rmtree(outdir / '_pico')
|
||||||
|
shutil.copytree(filesdict['launcher_dir'], outdir / '_pico')
|
||||||
|
shutil.copy(filesdict['launcher'], outdir / '_picoboot.nds')
|
||||||
|
shutil.copy(filesdict['loader_7'], outdir / 'picoLoader7.bin')
|
||||||
|
shutil.copy(filesdict['loader_9'], outdir / 'picoLoader9.bin')
|
||||||
|
shutil.copy(filesdict['aplist'], outdir / 'aplist.bin')
|
||||||
|
shutil.copy(filesdict['savelist'], outdir / 'savelist.bin')
|
||||||
|
|
||||||
|
|
||||||
|
def run(args, exedir: Path):
|
||||||
|
blobs = InputBlobs(args.ntr_blowfish.read(), args.twl_blowfish.read(),
|
||||||
|
None if args.twl_dev_blowfish is None else args.twl_dev_blowfish.read(),
|
||||||
|
None if args.wrfu_tester is None else args.wrfu_tester.read(),
|
||||||
|
)
|
||||||
|
|
||||||
|
srcdir = args.srcdir.absolute()
|
||||||
|
outdir = args.outdir.absolute()
|
||||||
|
|
||||||
|
filesdir = (exedir / 'build_bins').absolute()
|
||||||
|
filesdir.mkdir(exist_ok=True, parents=True)
|
||||||
|
|
||||||
|
print("[===] Checking inputs...")
|
||||||
|
Check.repos(args.srcdir)
|
||||||
|
tools = Check.toolchain(args.wonderful_env)
|
||||||
|
Check.prerequisites(blobs, filesdir)
|
||||||
|
check_errors()
|
||||||
|
|
||||||
|
print("[===] Setting up Python venv...")
|
||||||
|
venvdir = setup_venv(exedir, filesdir).absolute()
|
||||||
|
check_errors()
|
||||||
|
romenc = venvdir / "bin" / "DSRomEncryptor"
|
||||||
|
plconv = venvdir / "bin" / "PicoLoaderConverter"
|
||||||
|
print("[-->] ROM encryptor =", romenc, ", loader CSV conv =", plconv)
|
||||||
|
|
||||||
|
print("[===] Compiling DLDI driver...")
|
||||||
|
dspico_dldi = Build.dspico_dldi(args.wonderful_env, tools, srcdir, filesdir)
|
||||||
|
check_errors()
|
||||||
|
print("[-->] DLDI driver =", dspico_dldi)
|
||||||
|
|
||||||
|
print("[===] Compiling bootloader...")
|
||||||
|
default_nds = Build.dspico_bootloader(args.wonderful_env, tools, srcdir, romenc, filesdir)
|
||||||
|
check_errors()
|
||||||
|
print("[-->] bootloader =", default_nds)
|
||||||
|
|
||||||
|
do_wrfuxxed = (srcdir / 'dspico-wrfuxxed').is_dir() and blobs.wrfu_tester is not None
|
||||||
|
|
||||||
|
if do_wrfuxxed:
|
||||||
|
print("[===] Compiling Wrfuxxed exploit...")
|
||||||
|
uartBufv060 = Build.dspico_wrfuxxed(args.wonderful_env, tools, srcdir, filesdir)
|
||||||
|
check_errors()
|
||||||
|
print("[-->] Wrfuxxed =", uartBufv060)
|
||||||
|
|
||||||
|
print("[===] Compiling RP2040 firmware (vanilla)...")
|
||||||
|
fw_v = Build.dspico_firmware(tools, srcdir, filesdir, default_nds)
|
||||||
|
if do_wrfuxxed:
|
||||||
|
print("[===] Compiling RP2040 firmware (with Wrfuxxed)...")
|
||||||
|
fw_w = Build.dspico_firmware(tools, srcdir, filesdir, default_nds, uartBufv060)
|
||||||
|
check_errors()
|
||||||
|
print("[-->] firmware (vanilla) =", fw_v)
|
||||||
|
if do_wrfuxxed:
|
||||||
|
print("[-->] firmware (Wrfuxxed) =", fw_w)
|
||||||
|
|
||||||
|
print("[===] Compiling DSPico Loader...")
|
||||||
|
loader_bins = Build.dspico_loader(args.wonderful_env, tools, srcdir, filesdir, exedir, venvdir)
|
||||||
|
check_errors()
|
||||||
|
print("[-->] loader binaries =", loader_bins)
|
||||||
|
|
||||||
|
print("[===] Compiling DSPico Launcher...")
|
||||||
|
launcher_nds, launcher_dir = Build.dspico_launcher(args.wonderful_env, tools, srcdir, filesdir)
|
||||||
|
check_errors()
|
||||||
|
print("[-->] DSPico launcher =", launcher_nds, "launcher dir =", launcher_dir)
|
||||||
|
|
||||||
|
print("[===] Installing files to destdir...")
|
||||||
|
install_to_destdir({
|
||||||
|
'launcher_dir': launcher_dir,
|
||||||
|
'launcher': launcher_nds,
|
||||||
|
'loader_7': loader_bins[0],
|
||||||
|
'loader_9': loader_bins[1],
|
||||||
|
'aplist': loader_bins[2],
|
||||||
|
'savelist': loader_bins[3],
|
||||||
|
}, outdir)
|
||||||
|
check_errors()
|
||||||
|
|
||||||
|
print("[===] All done! Enjoy!")
|
||||||
|
print("[===] Don't forget to flash your firmware to the RP2040!\n\t(",
|
||||||
|
fw_v, "or", fw_w, ")")
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
args = do_args()
|
||||||
|
exit(run(*args) or 0)
|
||||||
|
|
||||||
Reference in New Issue
Block a user