mirror of
https://github.com/shijimasoft/cia-unix.git
synced 2025-06-18 14:25:34 -04:00
replaced the decrypt.py
script with the native ctrdecrypt
module
This commit is contained in:
parent
5690616cc0
commit
8920b5c15d
27
README.md
27
README.md
@ -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
|
||||
You’ll 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)
|
||||
|
20
cia-unix.cr
20
cia-unix.cr
@ -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)
|
||||
|
461
decrypt.py
461
decrypt.py
@ -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')
|
19
dltools.sh
19
dltools.sh
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user