Skip to content

Instantly share code, notes, and snippets.

@Sergey004
Created April 12, 2026 13:20
Show Gist options
  • Select an option

  • Save Sergey004/941e99c1735bc2d49750c674d3f1839b to your computer and use it in GitHub Desktop.

Select an option

Save Sergey004/941e99c1735bc2d49750c674d3f1839b to your computer and use it in GitHub Desktop.
DSRomEncryptor port to Python
#!/usr/bin/env python3
"""
DSRomEncryptor - Python port of Gericom's DSRomEncryptor
Encrypts Nintendo DS ROM secure areas using Blowfish encryption.
Original: https://github.com/Gericom/DSRomEncryptor
"""
import argparse
import struct
import sys
import os
from pathlib import Path
# ---------------------------------------------------------------------------
# Constants
# ---------------------------------------------------------------------------
HEADING_TEXT = "== DSRomEncryptor by Gericom (Python port) =="
SECURE_AREA_ID = 0x6A624F7972636E65 # "encryObj"
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
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
# ---------------------------------------------------------------------------
# CRC-16
# ---------------------------------------------------------------------------
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 calculate_crc16(data: bytes) -> int:
"""Calculate CRC-16 of the given data."""
result = 0xFFFF
for b in data:
result = (result >> 8) ^ CRC16_TABLE[(result ^ b) & 0xFF]
return result & 0xFFFF
# ---------------------------------------------------------------------------
# Blowfish
# ---------------------------------------------------------------------------
class Blowfish:
"""Blowfish cipher implementation for NDS ROM encryption."""
P_TABLE_ENTRY_COUNT = 18
S_BOX_COUNT = 4
S_BOX_ENTRY_COUNT = 256
BLOCK_LENGTH = 8
KEY_TABLE_P_TABLE_LENGTH = P_TABLE_ENTRY_COUNT * 4 # 72 bytes
KEY_TABLE_S_BOXES_LENGTH = S_BOX_COUNT * S_BOX_ENTRY_COUNT * 4 # 4096 bytes
KEY_TABLE_LENGTH = KEY_TABLE_P_TABLE_LENGTH + KEY_TABLE_S_BOXES_LENGTH # 4168 bytes
def __init__(self, key_table: bytes | bytearray | memoryview):
if len(key_table) < self.KEY_TABLE_LENGTH:
raise ValueError("Key table length is invalid.")
data = bytes(key_table)
# Read P-table: 18 uint32 LE values starting at offset 0
self._p_table = list(struct.unpack_from('<18I', data, 0))
# Read S-boxes: 4 x 256 uint32 LE values
self._s_boxes = []
self._s_boxes.append(list(struct.unpack_from('<256I', data, 0x48)))
self._s_boxes.append(list(struct.unpack_from('<256I', data, 0x448)))
self._s_boxes.append(list(struct.unpack_from('<256I', data, 0x848)))
self._s_boxes.append(list(struct.unpack_from('<256I', data, 0xC48)))
def _encrypt_value(self, val: int) -> int:
"""Encrypt a single 64-bit value."""
y = val & 0xFFFFFFFF
x = (val >> 32) & 0xFFFFFFFF
for i in range(16):
z = self._p_table[i] ^ x
a = self._s_boxes[0][(z >> 24) & 0xFF]
b = self._s_boxes[1][(z >> 16) & 0xFF]
c = self._s_boxes[2][(z >> 8) & 0xFF]
d = self._s_boxes[3][z & 0xFF]
x = (d + ((c ^ b) + a)) ^ y
x &= 0xFFFFFFFF
y = z
result_x = (x ^ self._p_table[16]) & 0xFFFFFFFF
result_y = (y ^ self._p_table[17]) & 0xFFFFFFFF
return result_x | (result_y << 32)
def encrypt_value(self, val: int) -> int:
"""Encrypt a single 64-bit value."""
return self._encrypt_value(val)
def _decrypt_value(self, val: int) -> int:
"""Decrypt a single 64-bit value."""
y = val & 0xFFFFFFFF
x = (val >> 32) & 0xFFFFFFFF
for i in range(17, 1, -1):
z = self._p_table[i] ^ x
a = self._s_boxes[0][(z >> 24) & 0xFF]
b = self._s_boxes[1][(z >> 16) & 0xFF]
c = self._s_boxes[2][(z >> 8) & 0xFF]
d = self._s_boxes[3][z & 0xFF]
x = (d + ((c ^ b) + a)) ^ y
x &= 0xFFFFFFFF
y = z
result_x = (x ^ self._p_table[1]) & 0xFFFFFFFF
result_y = (y ^ self._p_table[0]) & 0xFFFFFFFF
return result_x | (result_y << 32)
def decrypt_value(self, val: int) -> int:
"""Decrypt a single 64-bit value."""
return self._decrypt_value(val)
def encrypt(self, data: bytearray) -> None:
"""Encrypt data in place. Length must be a multiple of 8."""
if len(data) % 8 != 0:
raise ValueError("Data length must be a multiple of 8.")
for i in range(0, len(data), self.BLOCK_LENGTH):
val = struct.unpack_from('<Q', data, i)[0]
enc = self._encrypt_value(val)
struct.pack_into('<Q', data, i, enc)
def decrypt(self, data: bytearray) -> None:
"""Decrypt data in place. Length must be a multiple of 8."""
if len(data) % 8 != 0:
raise ValueError("Data length must be a multiple of 8.")
for i in range(0, len(data), self.BLOCK_LENGTH):
val = struct.unpack_from('<Q', data, i)[0]
dec = self._decrypt_value(val)
struct.pack_into('<Q', data, i, dec)
# ---------------------------------------------------------------------------
# Key Transform
# ---------------------------------------------------------------------------
def transform_table(game_code: int, level: int, modulo: int, key_table: bytes) -> bytearray:
"""
Transform the given blowfish key table based on game code, level and modulo.
Returns a new transformed table as bytearray.
"""
new_table = bytearray(key_table[:Blowfish.KEY_TABLE_LENGTH])
# Build key code: 12 bytes (3 x uint32 LE)
key_code = bytearray(12)
struct.pack_into('<I', key_code, 0, game_code & 0xFFFFFFFF)
struct.pack_into('<I', key_code, 4, (game_code >> 1) & 0xFFFFFFFF)
struct.pack_into('<I', key_code, 8, (game_code << 1) & 0xFFFFFFFF)
if level >= 1:
_apply_key_code(key_code, modulo, new_table)
if level >= 2:
_apply_key_code(key_code, modulo, new_table)
# Modify key code bytes 4..8 and 8..12
val4 = struct.unpack_from('<I', key_code, 4)[0]
val8 = struct.unpack_from('<I', key_code, 8)[0]
struct.pack_into('<I', key_code, 4, (val4 << 1) & 0xFFFFFFFF)
struct.pack_into('<I', key_code, 8, (val8 >> 1) & 0xFFFFFFFF)
if level >= 3:
_apply_key_code(key_code, modulo, new_table)
return new_table
def _apply_key_code(key_code: bytearray, modulo: int, key_table: bytearray) -> None:
"""Apply key code transformation to the key table in place."""
bf = Blowfish(key_table)
# Encrypt key_code[4:12] in place (8 bytes)
chunk = bytearray(key_code[4:12])
bf.encrypt(chunk)
key_code[4:12] = chunk
# Encrypt key_code[0:8] in place (8 bytes)
chunk = bytearray(key_code[0:8])
bf.encrypt(chunk)
key_code[0:8] = chunk
# XOR P-table entries with key code (big-endian read from key code)
for i in range(Blowfish.P_TABLE_ENTRY_COUNT):
offset_in_keycode = (i * 4) % modulo
# Read 4 bytes from key_code as big-endian uint32
if offset_in_keycode + 4 <= len(key_code):
kc_val = struct.unpack_from('>I', key_code, offset_in_keycode)[0]
else:
# Handle wrap-around case
kc_val = 0
for j in range(4):
idx = (offset_in_keycode + j) % len(key_code)
kc_val = (kc_val << 8) | key_code[idx]
table_offset = i * 4
existing = struct.unpack_from('<I', key_table, table_offset)[0]
struct.pack_into('<I', key_table, table_offset, existing ^ kc_val)
# Update table using scratch buffer
scratch = bytearray(8)
for i in range(0, Blowfish.KEY_TABLE_LENGTH, Blowfish.BLOCK_LENGTH):
bf = Blowfish(key_table)
bf.encrypt(scratch)
key_table[i:i + 4] = scratch[4:8]
key_table[i + 4:i + 8] = scratch[0:4]
# ---------------------------------------------------------------------------
# Blowfish Locator
# ---------------------------------------------------------------------------
def _find_script_dir() -> Path:
"""Return the directory where this script is located."""
return Path(__file__).resolve().parent
def try_get_ntr_blowfish(script_dir: Path | None = None) -> bytes | None:
"""Try to load the NTR (DS) blowfish key."""
if script_dir is None:
script_dir = _find_script_dir()
# Try direct file
path = script_dir / NTR_BLOWFISH_NAME
if path.is_file():
data = path.read_bytes()
if len(data) == Blowfish.KEY_TABLE_LENGTH:
return data
# Try from DS arm7 BIOS dump
path = script_dir / BIOS_NDS_7_NAME
if path.is_file():
data = path.read_bytes()
if len(data) == BIOS_NDS_7_LENGTH:
return data[BIOS_NDS_7_BLOWFISH_OFFSET:BIOS_NDS_7_BLOWFISH_OFFSET + Blowfish.KEY_TABLE_LENGTH]
return None
def try_get_twl_blowfish(script_dir: Path | None = None) -> bytes | None:
"""Try to load the TWL (DSi) blowfish key."""
if script_dir is None:
script_dir = _find_script_dir()
# Try direct file
path = script_dir / TWL_BLOWFISH_NAME
if path.is_file():
data = path.read_bytes()
if len(data) == Blowfish.KEY_TABLE_LENGTH:
return data
# Try from DSi arm7 BIOS dump
path = script_dir / BIOS_DSI_7_NAME
if path.is_file():
data = path.read_bytes()
if len(data) == BIOS_DSI_7_LENGTH:
return data[BIOS_DSI_7_BLOWFISH_OFFSET:BIOS_DSI_7_BLOWFISH_OFFSET + Blowfish.KEY_TABLE_LENGTH]
return None
def try_get_twl_dev_blowfish(script_dir: Path | None = None) -> bytes | None:
"""Try to load the TWL dev (DSi development) blowfish key."""
if script_dir is None:
script_dir = _find_script_dir()
path = script_dir / TWL_DEV_BLOWFISH_NAME
if path.is_file():
return path.read_bytes()
return None
# ---------------------------------------------------------------------------
# Helper functions
# ---------------------------------------------------------------------------
def _insert_test_patterns(rom_data: bytearray) -> None:
"""Insert standard test patterns into the ROM data."""
# Pattern at 0x3000
rom_data[0x3000:0x3008] = bytes([0xFF, 0x00, 0xFF, 0x00, 0xAA, 0x55, 0xAA, 0x55])
# Ascending pattern at 0x3008
for i in range(8, 0x200):
rom_data[0x3000 + i] = i & 0xFF
# Descending pattern at 0x3200
for i in range(0x200):
rom_data[0x3200 + i] = 0xFF - (i & 0xFF)
# Fill patterns
rom_data[0x3400:0x3600] = bytes(0x200) # 0x00
rom_data[0x3600:0x3800] = bytes([0xFF] * 0x200)
rom_data[0x3800:0x3A00] = bytes([0x0F] * 0x200)
rom_data[0x3A00:0x3C00] = bytes([0xF0] * 0x200)
rom_data[0x3C00:0x3E00] = bytes([0x55] * 0x200)
rom_data[0x3E00:0x3FFF] = bytes([0xAA] * 0x1FF)
rom_data[0x3FFF] = 0
def _should_rom_have_twl_area(rom_data: bytearray) -> bool:
"""Check if a ROM should have a TWL (DSi) area."""
return (
(rom_data[ROM_HEADER_UNIT_CODE_OFFSET] & UNIT_CODE_TWL_FLAG) != 0
and (rom_data[ROM_HEADER_TWL_FLAGS_OFFSET] & TWL_FLAGS_HAS_TWL_EXCLUSIVE_AREA_FLAG) != 0
)
# ---------------------------------------------------------------------------
# Main logic
# ---------------------------------------------------------------------------
def encrypt_rom(input_path: str, output_path: str, use_dsi_dev_blowfish: bool = False) -> None:
"""
Encrypt a Nintendo DS ROM.
Args:
input_path: Path to the input .nds file.
output_path: Path for the output .nds file.
use_dsi_dev_blowfish: Whether to use the DSi development blowfish key.
"""
# Determine search directory for key files (same dir as the input ROM or script dir)
search_dirs = [Path(input_path).resolve().parent, _find_script_dir()]
# Try to load NTR blowfish key
ntr_blowfish = None
for d in search_dirs:
ntr_blowfish = try_get_ntr_blowfish(d)
if ntr_blowfish is not None:
break
if ntr_blowfish is None:
print("Error: Could not load ntr blowfish key.")
print(f" Place '{NTR_BLOWFISH_NAME}' or '{BIOS_NDS_7_NAME}' in the same directory as the ROM or this script.")
sys.exit(1)
# Read ROM
rom_data = bytearray(Path(input_path).read_bytes())
# Read game code
game_code = struct.unpack_from('<I', rom_data, ROM_HEADER_GAME_CODE_OFFSET)[0]
# Transform and write NTR blowfish tables
ntr_table = transform_table(game_code, 2, 8, ntr_blowfish)
rom_data[ROM_NTR_BLOWFISH_P_TABLE_OFFSET:ROM_NTR_BLOWFISH_P_TABLE_OFFSET + Blowfish.KEY_TABLE_P_TABLE_LENGTH] = \
ntr_table[:Blowfish.KEY_TABLE_P_TABLE_LENGTH]
rom_data[ROM_NTR_BLOWFISH_S_BOXES_OFFSET:ROM_NTR_BLOWFISH_S_BOXES_OFFSET + Blowfish.KEY_TABLE_S_BOXES_LENGTH] = \
ntr_table[Blowfish.KEY_TABLE_P_TABLE_LENGTH:Blowfish.KEY_TABLE_P_TABLE_LENGTH + Blowfish.KEY_TABLE_S_BOXES_LENGTH]
# Insert test patterns
_insert_test_patterns(rom_data)
# Encrypt secure area
arm9_offset = struct.unpack_from('<I', rom_data, ROM_HEADER_ARM9_OFFSET_OFFSET)[0]
if ROM_SECURE_AREA_END_OFFSET - arm9_offset > 0:
secure_crc = calculate_crc16(rom_data[arm9_offset:ROM_SECURE_AREA_END_OFFSET])
existing_crc = struct.unpack_from('<H', rom_data, ROM_HEADER_SECURE_AREA_CRC_OFFSET)[0]
if existing_crc != secure_crc:
secure_table = transform_table(game_code, 3, 8, ntr_blowfish)
secure_bf = Blowfish(secure_table)
ntr_bf = Blowfish(ntr_table)
# Encrypt secure area ID
encrypted_id = secure_bf.encrypt_value(SECURE_AREA_ID)
encrypted_id = ntr_bf.encrypt_value(encrypted_id)
struct.pack_into('<Q', rom_data, ROM_SECURE_AREA_START_OFFSET, encrypted_id)
# Encrypt secure area data
secure_area = rom_data[ROM_SECURE_AREA_START_OFFSET + 8:ROM_ENCRYPTED_SECURE_AREA_END_OFFSET]
secure_bf.encrypt(secure_area)
# Update secure area CRC
secure_crc = calculate_crc16(rom_data[arm9_offset:ROM_SECURE_AREA_END_OFFSET])
struct.pack_into('<H', rom_data, ROM_HEADER_SECURE_AREA_CRC_OFFSET, secure_crc)
# TWL (DSi) area
try:
if _should_rom_have_twl_area(rom_data):
twl_area_start = struct.unpack_from('<H', rom_data, ROM_HEADER_TWL_AREA_START_OFFSET)[0] * TWL_CHUNK_SIZE
if twl_area_start == 0:
# Add TWL area to ROM
twl_area_start_value = (len(rom_data) + (TWL_CHUNK_SIZE - 1)) >> TWL_CHUNK_SHIFT
twl_area_start = twl_area_start_value * TWL_CHUNK_SIZE
struct.pack_into('<H', rom_data, ROM_HEADER_NTR_AREA_END_OFFSET, twl_area_start_value)
struct.pack_into('<H', rom_data, ROM_HEADER_TWL_AREA_START_OFFSET, twl_area_start_value)
rom_data.extend(b'\x00' * (twl_area_start + 0x8000 - len(rom_data)))
twl_table = None
if use_dsi_dev_blowfish:
twl_table_bytes = None
for d in search_dirs:
twl_table_bytes = try_get_twl_dev_blowfish(d)
if twl_table_bytes is not None:
break
if twl_table_bytes is None:
print("Error: Could not load twl dev blowfish key.")
sys.exit(1)
twl_table = twl_table_bytes
else:
twl_blowfish_bytes = None
for d in search_dirs:
twl_blowfish_bytes = try_get_twl_blowfish(d)
if twl_blowfish_bytes is not None:
break
if twl_blowfish_bytes is None:
print("Error: Could not load twl blowfish key.")
sys.exit(1)
twl_table = transform_table(game_code, 1, 8, twl_blowfish_bytes)
twl_offset_p = twl_area_start + ROM_TWL_BLOWFISH_P_TABLE_OFFSET
twl_offset_s = twl_area_start + ROM_TWL_BLOWFISH_S_BOXES_OFFSET
rom_data[twl_offset_p:twl_offset_p + Blowfish.KEY_TABLE_P_TABLE_LENGTH] = \
twl_table[:Blowfish.KEY_TABLE_P_TABLE_LENGTH]
rom_data[twl_offset_s:twl_offset_s + Blowfish.KEY_TABLE_S_BOXES_LENGTH] = \
twl_table[Blowfish.KEY_TABLE_P_TABLE_LENGTH:Blowfish.KEY_TABLE_P_TABLE_LENGTH + Blowfish.KEY_TABLE_S_BOXES_LENGTH]
except Exception:
print("Warning: Couldn't insert twl blowfish.")
# Fix header CRC
header_crc = calculate_crc16(rom_data[:ROM_HEADER_CRC_OFFSET])
struct.pack_into('<H', rom_data, ROM_HEADER_CRC_OFFSET, header_crc)
# Write output
Path(output_path).write_bytes(rom_data)
print(f"Done! Encrypted ROM written to: {output_path}")
# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description=HEADING_TEXT,
formatter_class=argparse.RawDescriptionHelpFormatter,
)
parser.add_argument('input_path', help='The path of the input nds file.')
parser.add_argument('output_path', help='The path of the output nds file.')
parser.add_argument('--dsidev', action='store_true', default=False,
help='Use DSi dev blowfish.')
args = parser.parse_args()
if not os.path.isfile(args.input_path):
print(f"Error: Input file not found: {args.input_path}")
sys.exit(1)
encrypt_rom(args.input_path, args.output_path, args.dsidev)
if __name__ == '__main__':
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment