replaced the decrypt.py script with the native ctrdecrypt module

This commit is contained in:
shijimasoft 2024-06-10 15:23:55 +02:00
parent 5690616cc0
commit 8920b5c15d
4 changed files with 31 additions and 496 deletions

View File

@ -1,43 +1,38 @@
# cia-unix
*Decrypt CIA roms in UNIX environments* 🪐
#### Requirements
* [Python 2.7](https://www.python.org/downloads/release/python-2718/)
* [PyCrypto](https://pypi.org/project/pycrypto/)
* `pip install pycrypto` or `easy_install pycrypto`
> **Note**
> A new Python 3 (unstable) version has been released, you can find the experimental code [here](https://github.com/shijimasoft/cia-unix/tree/experimental).
*Decrypt CIA and 3DS roms in UNIX environments (Linux and macOS)*
```
cia-unix/
├─ cia-unix
├─ decrypt.py
├─ ctrdecrypt
├─ ctrtool
├─ makerom
└─ Encrypted Game.cia
```
**ctrtool**, **makerom** and **decrypt.py** can be downloaded with `dltools.sh`
## 📋 Roadmap
**ctrtool**, **makerom** and [**ctrdecrypt**](https://github.com/shijimasoft/ctrdecrypt) can be downloaded with `dltools.sh`
## ✅ Roadmap
- [x] Decrypt .cia
- [x] Games
- [x] Patch and DLCs
- [x] Decrypt .3ds
- [ ] Rust `decrypt.py` rewrite (*[in progress](https://github.com/shijimasoft/ctrdecrypt)*)
- [x] Rust [`decrypt.py`](https://github.com/shijimasoft/cia-unix/blob/old-python3/decrypt.py) rewrite (ctrdecrypt)
> **Warning**
> [!WARNING]
> Decryption with cia-unix may fail, when it happens it is suggested to decrypt roms directly on the 3DS.
The old _python 3_ version can be found [here](https://github.com/shijimasoft/cia-unix/tree/old-python3).
## ⚡️ Build from source
Youll need the [Crystal compiler](https://crystal-lang.org/install/)
```
```bash
crystal build cia-unix.cr --release --no-debug
```
Dependencies can be compiled with [makerom](https://github.com/3DSGuy/Project_CTR/tree/master/makerom) and [ctrtool](https://github.com/3DSGuy/Project_CTR/tree/master/ctrtool) make files
Dependencies can be compiled with [makerom](https://github.com/3DSGuy/Project_CTR/tree/master/makerom) and [ctrtool](https://github.com/3DSGuy/Project_CTR/tree/master/ctrtool) make files.
## Contributors
ctrtool and makerom are from [3DSGuy repository](https://github.com/3DSGuy/Project_CTR)

View File

@ -4,22 +4,16 @@ log : File = File.new "cia-unix.log", "w"
log.puts Time.utc.to_s
# dependencies check
tools = ["python2.7", "./ctrtool", "./makerom", "decrypt.py", "seeddb.bin"]
tools = ["./ctrtool", "./ctrdecrypt", "./makerom", "seeddb.bin"]
tools.each do |tool|
case tool
when "python2.7"
if !File.exists? %x[which #{tool}].chomp
log.delete if File.exists? "cia-unix.log"
puts "#{"Python 2.7".colorize.mode(:bold)} not found. Install it before continue"
abort "https://www.python.org/download/releases/2.7/"
end
when "./ctrtool", "./makerom"
when "./ctrtool", "./ctrdecrypt", "./makerom"
if !File.exists? %x[which #{tool}].chomp
log.delete if File.exists? "cia-unix.log"
download_dep
abort "#{tool.lchop("./").colorize.mode(:bold)} not found. Make sure it's located in the #{"same directory".colorize.mode(:underline)}" if !File.exists? tool
end
when "decrypt.py", "seeddb.bin"
when "seeddb.bin"
if !File.exists? tool
log.delete if File.exists? "cia-unix.log"
download_dep
@ -77,7 +71,7 @@ Dir["*.3ds"].each do |ds|
dsn : String = ds.chomp ".3ds"
puts "Decrypting: #{ds.colorize.mode(:bold)}..."
log.puts %x[python2.7 decrypt.py '#{ds}']
log.puts %x[./ctrdecrypt '#{ds}']
Dir["#{dsn}.*.ncch"].each do |ncch|
case ncch
@ -118,7 +112,7 @@ Dir["*.cia"].each do |cia|
# game
if content.match /T.*d.*00040000/
puts "CIA Type: Game"
log.puts %x[python2.7 decrypt.py '#{cia}']
log.puts %x[./ctrdecrypt '#{cia}']
i : UInt8 = 0
Dir["*.ncch"].sort.each do |ncch|
@ -129,7 +123,7 @@ Dir["*.cia"].each do |cia|
# patch
elsif content.match /T.*d.*0004000(e|E)/
puts "CIA Type: #{"Patch".colorize.mode(:bold)}"
log.puts %x[python2.7 decrypt.py '#{cia}']
log.puts %x[./ctrdecrypt '#{cia}']
patch_parts : Int32 = Dir["#{cutn}.*.ncch"].size
args = gen_args(cutn, patch_parts)
@ -139,7 +133,7 @@ Dir["*.cia"].each do |cia|
# dlc
elsif content.match /T.*d.*0004008(c|C)/
puts "CIA Type: #{"DLC".colorize.mode(:bold)}"
log.puts %x[python2.7 decrypt.py '#{cia}']
log.puts %x[./ctrdecrypt '#{cia}']
dlc_parts : Int32 = Dir["#{cutn}.*.ncch"].size
args = gen_args(cutn, dlc_parts)

View File

@ -1,461 +0,0 @@
import os, sys, glob, struct
from Crypto.Cipher import AES
from Crypto.Util import Counter
from hashlib import sha256
from ctypes import *
from binascii import hexlify, unhexlify
import ssl
context = ssl._create_unverified_context()
import urllib
devkeys = 0
if devkeys == 0:
cmnkeys = [
133950820312113818052254735185268169624, 99246800502542016961820831682009877448,
334554929844945050845898870533261453695, 49958241579738975625263968236998589894,
163298441886091868932353113880783679166, 219349161507556771313991622940048346626]
key0x2C = 246647523836745093481291640204864831571
key0x25 = 275024782269591852539264289417494026995
key0x18 = 174013536497093865167571429864564540276
key0x1B = 92615092018138441822550407327763030402
else:
cmnkeys = [
113835763157985768603715566790521001275, 90662311705905729160277180104286604325,
176960587729458970190709525955428573895, 16565743050834760937649323226713341516,
297981520825143936698022683828169936983, 5413407133145539762491762794674946987]
key0x2C = 107678000672959294808833481810464881181
key0x25 = 172220582634352810158581394293866026779
key0x18 = 64197259709433409621779576860674900598
key0x1B = 144279189824071929460111192617823391670
fixedzeros = 0
fixedsys = 109645209274529458878270608689136408907
keys = [[key0x2C, key0x25, key0x18, key0x1B], [fixedzeros, fixedsys]]
mediaUnitSize = 512
ncsdPartitions = [
'Main', 'Manual', 'DownloadPlay', 'Partition4', 'Partition5', 'Partition6', 'N3DSUpdateData', 'UpdateData']
tab = ' '
class ncchHdr(Structure):
_fields_ = [
(
'signature', c_uint8 * 256),
(
'magic', c_char * 4),
(
'ncchSize', c_uint32),
(
'titleId', c_uint8 * 8),
(
'makerCode', c_uint16),
(
'formatVersion', c_uint8),
(
'formatVersion2', c_uint8),
(
'seedcheck', c_char * 4),
(
'programId', c_uint8 * 8),
(
'padding1', c_uint8 * 16),
(
'logoHash', c_uint8 * 32),
(
'productCode', c_uint8 * 16),
(
'exhdrHash', c_uint8 * 32),
(
'exhdrSize', c_uint32),
(
'padding2', c_uint32),
(
'flags', c_uint8 * 8),
(
'plainRegionOffset', c_uint32),
(
'plainRegionSize', c_uint32),
(
'logoOffset', c_uint32),
(
'logoSize', c_uint32),
(
'exefsOffset', c_uint32),
(
'exefsSize', c_uint32),
(
'exefsHashSize', c_uint32),
(
'padding4', c_uint32),
(
'romfsOffset', c_uint32),
(
'romfsSize', c_uint32),
(
'romfsHashSize', c_uint32),
(
'padding5', c_uint32),
(
'exefsHash', c_uint8 * 32),
(
'romfsHash', c_uint8 * 32)]
def __new__(cls, buf):
return cls.from_buffer_copy(buf)
def __init__(self, data):
pass
class ncchSection:
exheader = 1
exefs = 2
romfs = 3
class ncch_offsetsize(Structure):
_fields_ = [
(
'offset', c_uint32),
(
'size', c_uint32)]
class ncsdHdr(Structure):
_fields_ = [
(
'signature', c_uint8 * 256),
(
'magic', c_char * 4),
(
'mediaSize', c_uint32),
(
'titleId', c_uint8 * 8),
(
'padding0', c_uint8 * 16),
(
'offset_sizeTable', ncch_offsetsize * 8),
(
'padding1', c_uint8 * 40),
(
'flags', c_uint8 * 8),
(
'ncchIdTable', c_uint8 * 64),
(
'padding2', c_uint8 * 48)]
class SeedError(Exception):
pass
class ciaReader:
def __init__(self, fhandle, encrypted, titkey, cIdx, contentOff):
self.fhandle = fhandle
self.encrypted = encrypted
self.name = fhandle.name
self.cIdx = cIdx
self.contentOff = contentOff
self.cipher = AES.new(titkey, AES.MODE_CBC, to_bytes(cIdx, 2, 'big') + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
def seek(self, offs):
if offs == 0:
self.fhandle.seek(self.contentOff)
self.cipher.IV = to_bytes(self.cIdx, 2, 'big') + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
else:
self.fhandle.seek(self.contentOff + offs - 16)
self.cipher.IV = self.fhandle.read(16)
def read(self, bytes):
if bytes == 0:
return ''
data = self.fhandle.read(bytes)
if self.encrypted:
data = self.cipher.decrypt(data)
return data
def from_bytes(data, endianess='big'):
if isinstance(data, str):
data = bytearray(data)
if endianess == 'big':
data = reversed(data)
num = 0
for offset, byte in enumerate(data):
num += byte << offset * 8
return num
def to_bytes(n, length, endianess='big'):
h = '%x' % n
s = ('0' * (len(h) % 2) + h).zfill(length * 2).decode('hex')
if endianess == 'big':
return s
return s[::-1]
def scramblekey(keyX, keyY):
rol = lambda val, r_bits, max_bits: val << r_bits % max_bits & 2 ** max_bits - 1 | (val & 2 ** max_bits - 1) >> max_bits - r_bits % max_bits
return rol((rol(keyX, 2, 128) ^ keyY) + 42503689118608475533858958821215598218 & 340282366920938463463374607431768211455, 87, 128)
def reverseCtypeArray(ctypeArray):
return ('').join('%02X' % x for x in ctypeArray[::-1])
def getNcchAesCounter(header, type):
counter = bytearray('\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00')
if header.formatVersion == 2 or header.formatVersion == 0:
counter[:8] = bytearray(header.titleId[::-1])
counter[8:9] = chr(type)
elif header.formatVersion == 1:
x = 0
if type == ncchSection.exheader:
x = 512
if type == ncchSection.exefs:
x = header.exefsOffset * mediaUnitSize
if type == ncchSection.romfs:
x = header.romfsOffset * mediaUnitSize
counter[:8] = bytearray(header.titleId)
for i in range(4):
counter[12 + i] = chr(x >> (3 - i) * 8 & 255)
return bytes(counter)
def getNewkeyY(keyY, header, titleId):
seeds = {}
seedif = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), 'seeddb.bin')
if os.path.exists(seedif):
with open(seedif, 'rb') as (seeddb):
seedcount = struct.unpack('<I', seeddb.read(4))[0]
seeddb.read(12)
for i in range(seedcount):
key = hexlify(seeddb.read(8)[::-1])
seeds[key] = bytearray(seeddb.read(16))
seeddb.read(8)
if titleId not in seeds:
print (tab + '********************************')
print (tab + "Couldn't find seed in seeddb, checking online...")
print (tab + '********************************')
for country in ['JP', 'US', 'GB', 'KR', 'TW', 'AU', 'NZ']:
r = urllib.urlopen('https://kagiya-ctr.cdn.nintendo.net/title/0x%s/ext_key?country=%s' % (titleId, country), context=context)
if r.getcode() == 200:
seeds[titleId] = r.read()
break
if titleId in seeds:
seedcheck = struct.unpack('>I', header.seedcheck)[0]
if int(sha256(seeds[titleId] + unhexlify(titleId)[::-1]).hexdigest()[:8], 16) == seedcheck:
keystr = sha256(to_bytes(keyY, 16, 'big') + seeds[titleId]).hexdigest()[:32]
newkeyY = unhexlify(keystr)
return from_bytes(newkeyY, 'big')
raise SeedError('Seed check fail, wrong seed?')
raise SeedError('Something Happened :/')
def align(x, y):
mask = ~(y - 1)
return x + (y - 1) & mask
def parseCIA(fh):
print ('Parsing CIA in file "%s":' % os.path.basename(fh.name))
fh.seek(0)
headerSize, type, version, cachainSize, tikSize, tmdSize, metaSize, contentSize = struct.unpack('<IHHIIIIQ', fh.read(32))
cachainOff = align(headerSize, 64)
tikOff = align(cachainOff + cachainSize, 64)
tmdOff = align(tikOff + tikSize, 64)
contentOffs = align(tmdOff + tmdSize, 64)
metaOff = align(contentOffs + contentSize, 64)
fh.seek(tikOff + 127 + 320)
enckey = fh.read(16)
fh.seek(tikOff + 156 + 320)
tid = fh.read(8)
if hexlify(tid)[:5] == '00048':
print ('Unsupported CIA file')
return
fh.seek(tikOff + 177 + 320)
cmnkeyidx = struct.unpack('B', fh.read(1))[0]
titkey = AES.new(to_bytes(cmnkeys[cmnkeyidx], 16, 'big'), AES.MODE_CBC, tid + '\x00\x00\x00\x00\x00\x00\x00\x00').decrypt(enckey)
fh.seek(tmdOff + 518)
contentCount = struct.unpack('>H', fh.read(2))[0]
nextContentOffs = 0
for i in range(contentCount):
fh.seek(tmdOff + 2820 + 48 * i)
cId, cIdx, cType, cSize = struct.unpack('>IHHQ', fh.read(16))
cEnc = 1
if cType & 1 == 0:
cEnc = 0
fh.seek(contentOffs + nextContentOffs)
if cEnc:
test = AES.new(titkey, AES.MODE_CBC, to_bytes(cIdx, 2, 'big') + '\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00').decrypt(fh.read(512))
else:
test = fh.read(512)
if not test[256:260] == 'NCCH':
print (' Problem parsing CIA content, skipping. Sorry about that :/\n')
continue
fh.seek(contentOffs + nextContentOffs)
ciaHandle = ciaReader(fh, cEnc, titkey, cIdx, contentOffs + nextContentOffs)
nextContentOffs = nextContentOffs + align(cSize, 64)
parseNCCH(ciaHandle, cSize, 0, cIdx, tid, 0, 0)
def parseNCSD(fh):
print ('Parsing NCSD in file "%s":' % os.path.basename(fh.name))
fh.seek(0)
header = ncsdHdr()
fh.readinto(header)
for i in range(len(header.offset_sizeTable)):
if header.offset_sizeTable[i].offset:
parseNCCH(fh, header.offset_sizeTable[i].size * mediaUnitSize, header.offset_sizeTable[i].offset * mediaUnitSize, i, reverseCtypeArray(header.titleId), 0, 1)
def parseNCCH(fh, fsize, offs=0, idx=0, titleId='', standAlone=1, fromNcsd=0):
tab = ' ' if not standAlone else ' '
if not standAlone and fromNcsd:
print (' Parsing %s NCCH' % ncsdPartitions[idx])
elif not standAlone:
print (' Parsing NCCH %d' % idx)
else:
print ('Parsing NCCH in file "%s":' % os.path.basename(fh.name))
entries = 0
data = ''
fh.seek(offs)
tmp = fh.read(512)
header = ncchHdr(tmp)
if titleId == '':
titleId = reverseCtypeArray(header.programId)
ncchKeyY = from_bytes(header.signature[:16], 'big')
print (tab + 'Product code: ' + str(bytearray(header.productCode)).rstrip('\x00'))
print (tab + 'KeyY: %032X' % ncchKeyY)
print (tab + 'Title ID: %s' % reverseCtypeArray(header.titleId))
print (tab + 'Format version: %d' % header.formatVersion)
usesExtraCrypto = bytearray(header.flags)[3]
if usesExtraCrypto:
print (tab + 'Uses Extra NCCH crypto, keyslot 0x%X' % {1: 37, 10: 24, 11: 27}[usesExtraCrypto])
fixedCrypto = 0
encrypted = 1
if header.flags[7] & 1:
fixedCrypto = 2 if header.titleId[3] & 16 else 1
print (tab + 'Uses fixed-key crypto')
if header.flags[7] & 4:
encrypted = 0
print (tab + 'Not Encrypted')
useSeedCrypto = header.flags[7] & 32 != 0
keyY = ncchKeyY
if useSeedCrypto:
keyY = getNewkeyY(ncchKeyY, header, hexlify(titleId))
print (tab + 'Uses 9.6 NCCH Seed crypto with KeyY: %032X' % keyY)
print ('')
base = os.path.splitext(os.path.basename(fh.name))[0]
base += '.%s.ncch' % (idx if fromNcsd == 0 else ncsdPartitions[idx])
base = os.path.join(os.path.dirname(os.path.realpath(sys.argv[0])), base)
with open(base, 'wb') as (f):
tmp = tmp[:399] + chr(ord(tmp[399]) & 2 | 4) + tmp[400:]
f.write(tmp)
if header.exhdrSize != 0:
counter = getNcchAesCounter(header, ncchSection.exheader)
dumpSection(f, fh, 512, header.exhdrSize * 2, ncchSection.exheader, counter, usesExtraCrypto, fixedCrypto, encrypted, [ncchKeyY, keyY])
if header.exefsSize != 0:
counter = getNcchAesCounter(header, ncchSection.exefs)
dumpSection(f, fh, header.exefsOffset * mediaUnitSize, header.exefsSize * mediaUnitSize, ncchSection.exefs, counter, usesExtraCrypto, fixedCrypto, encrypted, [ncchKeyY, keyY])
if header.romfsSize != 0:
counter = getNcchAesCounter(header, ncchSection.romfs)
dumpSection(f, fh, header.romfsOffset * mediaUnitSize, header.romfsSize * mediaUnitSize, ncchSection.romfs, counter, usesExtraCrypto, fixedCrypto, encrypted, [ncchKeyY, keyY])
print ('')
def dumpSection(f, fh, offset, size, type, ctr, usesExtraCrypto, fixedCrypto, encrypted, keyYs):
cryptoKeys = {0: 0, 1: 1, 10: 2, 11: 3}
sections = [
'ExHeader', 'ExeFS', 'RomFS']
print (tab + '%s offset: %08X' % (sections[(type - 1)], offset))
print (tab + '%s counter: %s' % (sections[(type - 1)], hexlify(ctr)))
print (tab + '%s size: %d bytes' % (sections[(type - 1)], size))
tmp = offset - f.tell()
if tmp > 0:
f.write(fh.read(tmp))
if not encrypted:
sizeleft = size
while sizeleft > 4194304:
f.write(fh.read(4194304))
sizeleft -= 4194304
if sizeleft > 0:
f.write(fh.read(sizeleft))
return
key0x2C = to_bytes(scramblekey(keys[0][0], keyYs[0]), 16, 'big')
if type == ncchSection.exheader:
key = key0x2C
if fixedCrypto:
key = to_bytes(keys[1][(fixedCrypto - 1)], 16, 'big')
cipher = AES.new(key, AES.MODE_CTR, counter=Counter.new(128, initial_value=from_bytes(ctr, 'big')))
f.write(cipher.decrypt(fh.read(size)))
if type == ncchSection.exefs:
key = key0x2C
if fixedCrypto:
key = to_bytes(keys[1][(fixedCrypto - 1)], 16, 'big')
cipher = AES.new(key, AES.MODE_CTR, counter=Counter.new(128, initial_value=from_bytes(ctr, 'big')))
exedata = fh.read(size)
exetmp = cipher.decrypt(exedata)
if usesExtraCrypto:
extraCipher = AES.new(to_bytes(scramblekey(keys[0][cryptoKeys[usesExtraCrypto]], keyYs[1]), 16, 'big'), AES.MODE_CTR, counter=Counter.new(128, initial_value=from_bytes(ctr, 'big')))
exetmp2 = extraCipher.decrypt(exedata)
for i in range(10):
fname, off, size = struct.unpack('<8sII', exetmp[i * 16:(i + 1) * 16])
off += 512
if fname.strip('\x00') not in ('icon', 'banner'):
exetmp = exetmp[:off] + exetmp2[off:off + size] + exetmp[off + size:]
f.write(exetmp)
if type == ncchSection.romfs:
key = to_bytes(scramblekey(keys[0][cryptoKeys[usesExtraCrypto]], keyYs[1]), 16, 'big')
if fixedCrypto:
key = to_bytes(keys[1][(fixedCrypto - 1)], 16, 'big')
cipher = AES.new(key, AES.MODE_CTR, counter=Counter.new(128, initial_value=from_bytes(ctr, 'big')))
sizeleft = size
while sizeleft > 4194304:
f.write(cipher.decrypt(fh.read(4194304)))
sizeleft -= 4194304
if sizeleft > 0:
f.write(cipher.decrypt(fh.read(sizeleft)))
if len(sys.argv) < 2:
print ('usage: decrypt.py *file*')
sys.exit()
inpFiles = []
existFiles = []
for i in range(len(sys.argv) - 1):
inpFiles = inpFiles + glob.glob(sys.argv[(i + 1)].replace('[', '[[]'))
for i in range(len(inpFiles)):
if os.path.isfile(inpFiles[i]):
existFiles.append(inpFiles[i])
if existFiles == []:
print ("Input files don't exist")
sys.exit()
print ('')
for file in existFiles:
with open(file, 'rb') as (fh):
fh.seek(256)
magic = fh.read(4)
if magic == 'NCSD':
result = parseNCSD(fh)
print ('')
elif magic == 'NCCH':
fh.seek(0, 2)
result = parseNCCH(fh, fh.tell())
print ('')
elif fh.name.split('.')[(-1)].lower() == 'cia':
fh.seek(0)
if fh.read(4) == ' \x00\x00':
parseCIA(fh)
print ('')
print ('Partitions extracted')

View File

@ -4,12 +4,18 @@ SCRIPT_DIR=$(dirname "${BASH_SOURCE[0]}")
BOLD=$(tput bold)
NORMAL=$(tput sgr0)
CTRDECRYPT_VER=1.1.0
CTRTOOL_VER=1.2.0
MAKEROM_VER=0.18.4
# Darwin
if [[ "$OSTYPE" == "darwin"* ]]; then
# Apple Silicon
echo " * Downloading ${BOLD}ctrdecrypt${NORMAL}"
wget "https://github.com/shijimasoft/ctrdecrypt/releases/download/v${CTRDECRYPT_VER}/ctrdecrypt-macos-universal.zip" -q
echo " * Extracting ${BOLD}ctrdecrypt${NORMAL}"
unzip -qq "ctrdecrypt-macos-universal.zip" -d "ctrdecrypt-macos-universal"
mv "ctrdecrypt-macos-universal/ctrdecrypt" "${SCRIPT_DIR}/ctrdecrypt"
if [[ $(uname -m) == 'arm64' ]]; then
echo " * Downloading ${BOLD}ctrtool${NORMAL}"
wget "https://github.com/3DSGuy/Project_CTR/releases/download/ctrtool-v${CTRTOOL_VER}/ctrtool-v${CTRTOOL_VER}-macos_arm64.zip" -q
@ -37,6 +43,11 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
# Linux (x86_64)
elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
echo " * Downloading ${BOLD}ctrdecrypt${NORMAL}"
wget "https://github.com/shijimasoft/ctrdecrypt/releases/download/v${CTRDECRYPT_VER}/ctrdecrypt-linux-x86_64.zip" -q
echo " * Extracting ${BOLD}ctrdecrypt${NORMAL}"
unzip -qq "ctrdecrypt-linux-x86_64.zip" -d "ctrdecrypt-linux-x86_64"
mv "ctrdecrypt-linux-x86_64/ctrdecrypt" "${SCRIPT_DIR}/ctrdecrypt"
echo " * Downloading ${BOLD}ctrtool${NORMAL}"
wget "https://github.com/3DSGuy/Project_CTR/releases/download/ctrtool-v${CTRTOOL_VER}/ctrtool-v${CTRTOOL_VER}-ubuntu_x86_64.zip" -q
echo " * Extracting ${BOLD}ctrtool${NORMAL}"
@ -49,20 +60,16 @@ elif [[ "$OSTYPE" == "linux-gnu"* ]]; then
mv "makerom-v${MAKEROM_VER}-ubuntu_x86_64/makerom" "${SCRIPT_DIR}/makerom"
fi
if [[ ! -f "decrypt.py" ]]; then
echo " * Downloading ${BOLD}decrypt.py${NORMAL}"
wget "https://raw.githubusercontent.com/shijimasoft/cia-unix/main/decrypt.py" -q
fi
if [[ ! -f "seeddb.bin" ]]; then
echo " * Downloading ${BOLD}seeddb.bin${NORMAL}"
wget "https://github.com/ihaveamac/3DS-rom-tools/raw/master/seeddb/seeddb.bin" -q
fi
echo " * Cleaning up"
rm -rf "ctrdecrypt-"*
rm -rf "ctrtool-v${CTRTOOL_VER}-"*
rm -rf "makerom-v${MAKEROM_VER}-"*
chmod +x ctrtool makerom
chmod +x ctrdecrypt ctrtool makerom
echo