Skip to content

Instantly share code, notes, and snippets.

@unresolvedsymbol
Last active May 18, 2022 18:36
Show Gist options
  • Save unresolvedsymbol/8037533a8b46ea7db11ed881c099c7f0 to your computer and use it in GitHub Desktop.
Save unresolvedsymbol/8037533a8b46ea7db11ed881c099c7f0 to your computer and use it in GitHub Desktop.
#!/usr/bin/env python3
import os
import sys
import struct
import binascii
import argparse
MB = 0x100000
parser = argparse.ArgumentParser(description='Edit CCI card type for 3DS flashcarts')
parser.add_argument('--trim', action='store_true', help='trim to fit (will break pokemon games)')
parser.add_argument('--type', type=int, choices=[1, 2], required=True, help='card type to set')
parser.add_argument('file', type=argparse.FileType('rb+'), nargs='+', help='CCIs (.3ds/3dz) to edit')
args = parser.parse_args()
print('-=- -=-')
print('CCI card type converter by Void')
print(' Original code by snailface')
print(' Pkm-breaking trim by Jdbye')
print('-=- -=-')
print()
# Creates matching unmodified checksum when converting back to original type
# (Unless you have a bad dump that had not properly cleared the untrimmed area)
def num2string(n, size):
ret = b''
for i in range(size):
ret += bytes([n & 0xFF])
n >>= 8
return ret
def byteSwap(n):
return n[::-1]
def hex2value(n):
return int(binascii.hexlify(n), 16)
def grabData(off, size):
f.seek(off)
offset = f.read(size)
return offset
def isFlag(f, off):
f.seek(off)
byte = ord(f.read(1))
return byte and byte < 3
def process(f, type, trim):
print("[-] Flags", ord(grabData(0x18B, 1)), ord(grabData(0x18D, 1)), ord(grabData(0x18F, 1)), hex2value(byteSwap(grabData(0x300, 4))), grabData(0x200, 4), '\n')
if ord(grabData(0x18D, 1)) == type:
print('[!] Skipping "%s" as already type %d' % (f.name, type))
return
print('[!] Converting "%s"' % f.name)
if isFlag(f, 0x18B):
f.seek(0x18B)
f.write(bytes([type]))
f.seek(0x18D)
f.write(bytes([type]))
if isFlag(f, 0x18F):
f.seek(0x18F)
f.write(bytes([type]))
dataSize = hex2value(byteSwap(grabData(0x300, 4)))
saveOffset = int(dataSize / MB) * MB + MB
mediaUnitOffset = int(saveOffset / 0x200)
f.seek(0x200)
f.write(num2string(mediaUnitOffset, 4) if type == 2 else b'\xff\xff\xff\xff')
# Format type 2 save
f.seek(0, 2)
clearSize = f.tell() - saveOffset
f.seek(saveOffset)
f.write(b'\xff' * clearSize)
if trim:
print('[*] Trimming')
# TODO: Are all type 2 saves 16M or is this just the max?
f.truncate(saveOffset + (MB * 16))
for f in args.file:
process(f, args.type, args.trim)
print()
print('[~] Done processing %d files' % len(args.file))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment