nstools 1.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- nstools-1.1.0/PKG-INFO +11 -0
- nstools-1.1.0/nstools/Fs/BaseFs.py +178 -0
- nstools-1.1.0/nstools/Fs/Bktr.py +268 -0
- nstools-1.1.0/nstools/Fs/Cnmt.py +80 -0
- nstools-1.1.0/nstools/Fs/File.py +500 -0
- nstools-1.1.0/nstools/Fs/Hfs0.py +170 -0
- nstools-1.1.0/nstools/Fs/Ivfc.py +49 -0
- nstools-1.1.0/nstools/Fs/Nacp.py +613 -0
- nstools-1.1.0/nstools/Fs/Nca.py +305 -0
- nstools-1.1.0/nstools/Fs/Nsp.py +453 -0
- nstools-1.1.0/nstools/Fs/Pfs0.py +280 -0
- nstools-1.1.0/nstools/Fs/Rom.py +57 -0
- nstools-1.1.0/nstools/Fs/Ticket.py +226 -0
- nstools-1.1.0/nstools/Fs/Type.py +30 -0
- nstools-1.1.0/nstools/Fs/Xci.py +324 -0
- nstools-1.1.0/nstools/Fs/__init__.py +27 -0
- nstools-1.1.0/nstools/lib/BlockDecompressorReader.py +65 -0
- nstools-1.1.0/nstools/lib/FsCert.py +90 -0
- nstools-1.1.0/nstools/lib/FsNcaMod.py +255 -0
- nstools-1.1.0/nstools/lib/FsTools.py +151 -0
- nstools-1.1.0/nstools/lib/Header.py +27 -0
- nstools-1.1.0/nstools/lib/Hex.py +39 -0
- nstools-1.1.0/nstools/lib/NcaKeys.py +47 -0
- nstools-1.1.0/nstools/lib/PathTools.py +50 -0
- nstools-1.1.0/nstools/lib/Verify.py +804 -0
- nstools-1.1.0/nstools/lib/VerifyTools.py +380 -0
- nstools-1.1.0/nstools/nut/Hex.py +40 -0
- nstools-1.1.0/nstools/nut/Keys.py +198 -0
- nstools-1.1.0/nstools/nut/Print.py +34 -0
- nstools-1.1.0/nstools/nut/Titles.py +78 -0
- nstools-1.1.0/nstools/nut/aes128.py +428 -0
- nstools-1.1.0/nstools.egg-info/PKG-INFO +11 -0
- nstools-1.1.0/nstools.egg-info/SOURCES.txt +37 -0
- nstools-1.1.0/nstools.egg-info/dependency_links.txt +1 -0
- nstools-1.1.0/nstools.egg-info/not-zip-safe +1 -0
- nstools-1.1.0/nstools.egg-info/requires.txt +4 -0
- nstools-1.1.0/nstools.egg-info/top_level.txt +1 -0
- nstools-1.1.0/setup.cfg +4 -0
- nstools-1.1.0/setup.py +18 -0
nstools-1.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: nstools
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Home-page: https://github.com/seiya-dev/NSTools
|
|
5
|
+
Requires-Python: >=3.10
|
|
6
|
+
Requires-Dist: zstandard
|
|
7
|
+
Requires-Dist: enlighten
|
|
8
|
+
Requires-Dist: requests
|
|
9
|
+
Requires-Dist: pycryptodome
|
|
10
|
+
|
|
11
|
+
tools for xci/xcz/nsp/nsz
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
from binascii import hexlify as hx, unhexlify as uhx
|
|
2
|
+
|
|
3
|
+
from nstools.nut import Print
|
|
4
|
+
|
|
5
|
+
from . import Bktr
|
|
6
|
+
from . import Type
|
|
7
|
+
|
|
8
|
+
from .File import File
|
|
9
|
+
from .File import MemoryFile
|
|
10
|
+
from .Cnmt import Cnmt
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EncryptedSection:
|
|
14
|
+
def __init__(self, offset, size, cryotoType, cryptoKey, cryptoCounter):
|
|
15
|
+
self.offset = offset
|
|
16
|
+
self.size = size
|
|
17
|
+
self.cryptoType = cryotoType
|
|
18
|
+
self.cryptoKey = cryptoKey
|
|
19
|
+
self.cryptoCounter = cryptoCounter
|
|
20
|
+
|
|
21
|
+
class BaseFs(File):
|
|
22
|
+
def __init__(self, buffer, path = None, mode = None, cryptoType = -1, cryptoKey = -1, cryptoCounter = -1):
|
|
23
|
+
self.buffer = buffer
|
|
24
|
+
self.sectionStart = 0
|
|
25
|
+
self.fsType = None
|
|
26
|
+
self.cryptoType = None
|
|
27
|
+
self.size = 0
|
|
28
|
+
self.cryptoCounter = None
|
|
29
|
+
self.magic = None
|
|
30
|
+
self._headerSize = None
|
|
31
|
+
self.bktrRelocation = None
|
|
32
|
+
self.bktrSubsection = None
|
|
33
|
+
|
|
34
|
+
self.files = []
|
|
35
|
+
|
|
36
|
+
if buffer:
|
|
37
|
+
self.buffer = buffer
|
|
38
|
+
try:
|
|
39
|
+
self.fsType = Type.Fs(buffer[0x3])
|
|
40
|
+
except:
|
|
41
|
+
self.fsType = buffer[0x3]
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
self.cryptoType = Type.Crypto(buffer[0x4])
|
|
45
|
+
except:
|
|
46
|
+
self.cryptoType = buffer[0x4]
|
|
47
|
+
|
|
48
|
+
self.cryptoCounter = bytearray((b"\x00"*8) + buffer[0x140:0x148])
|
|
49
|
+
self.cryptoCounter = self.cryptoCounter[::-1]
|
|
50
|
+
|
|
51
|
+
cryptoType = self.cryptoType
|
|
52
|
+
cryptoCounter = self.cryptoCounter
|
|
53
|
+
|
|
54
|
+
self.bktr1Buffer = buffer[0x100:0x120]
|
|
55
|
+
self.bktr2Buffer = buffer[0x120:0x140]
|
|
56
|
+
else:
|
|
57
|
+
self.bktr1Buffer = None
|
|
58
|
+
self.bktr2Buffer = None
|
|
59
|
+
|
|
60
|
+
super(BaseFs, self).__init__(path, mode, cryptoType, cryptoKey, cryptoCounter)
|
|
61
|
+
|
|
62
|
+
def __getitem__(self, key):
|
|
63
|
+
if isinstance(key, str):
|
|
64
|
+
for f in self.files:
|
|
65
|
+
if (hasattr(f, 'name') and f.name == key) or (hasattr(f, '_path') and f._path == key):
|
|
66
|
+
return f
|
|
67
|
+
elif isinstance(key, int):
|
|
68
|
+
return self.files[key]
|
|
69
|
+
|
|
70
|
+
raise IOError('FS File Not Found')
|
|
71
|
+
|
|
72
|
+
def getEncryptionSections(self):
|
|
73
|
+
sections = []
|
|
74
|
+
|
|
75
|
+
if self.hasBktr():
|
|
76
|
+
sectionOffset = self.realOffset()
|
|
77
|
+
|
|
78
|
+
for entry in self.bktrSubsection.getAllEntries():
|
|
79
|
+
ctr = self.setBktrCounter(entry.ctr, 0)
|
|
80
|
+
sections.append(EncryptedSection(self.realOffset() + entry.virtualOffset, entry.size, self.cryptoType, self.cryptoKey, ctr))
|
|
81
|
+
|
|
82
|
+
if len(sections) == 0:
|
|
83
|
+
sections.append(EncryptedSection(sectionOffset, self.size, self.cryptoType, self.cryptoKey, self.cryptoCounter))
|
|
84
|
+
else:
|
|
85
|
+
offset = sections[-1].offset + sections[-1].size
|
|
86
|
+
sections.append(EncryptedSection(offset, (sectionOffset + self.size) - offset, self.cryptoType, self.cryptoKey, self.cryptoCounter))
|
|
87
|
+
|
|
88
|
+
else:
|
|
89
|
+
sections.append(EncryptedSection(self.realOffset(), self.size, self.cryptoType, self.cryptoKey, self.cryptoCounter))
|
|
90
|
+
return sections
|
|
91
|
+
|
|
92
|
+
def realOffset(self):
|
|
93
|
+
return self.offset - self.sectionStart
|
|
94
|
+
|
|
95
|
+
def hasBktr(self):
|
|
96
|
+
return (False if self.bktrSubsection is None else True) and self.bktrSubsection.isValid()
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def open(self, path = None, mode = 'rb', cryptoType = -1, cryptoKey = -1, cryptoCounter = -1):
|
|
100
|
+
r = super(BaseFs, self).open(path, mode, cryptoType, cryptoKey, cryptoCounter)
|
|
101
|
+
|
|
102
|
+
if self.bktr1Buffer:
|
|
103
|
+
try:
|
|
104
|
+
self.bktrRelocation = Bktr.Bktr1(MemoryFile(self.bktr1Buffer), 'rb', nca = self)
|
|
105
|
+
except BaseException as e:
|
|
106
|
+
Print.info('bktr reloc exception: ' + str(e))
|
|
107
|
+
|
|
108
|
+
if self.bktr2Buffer:
|
|
109
|
+
try:
|
|
110
|
+
self.bktrSubsection = Bktr.Bktr2(MemoryFile(self.bktr2Buffer), 'rb', nca = self)
|
|
111
|
+
except BaseException as e:
|
|
112
|
+
Print.info('bktr subsection exception: ' + str(e))
|
|
113
|
+
|
|
114
|
+
def bktrRead(self, size = None, direct = False):
|
|
115
|
+
self.cryptoOffset = 0
|
|
116
|
+
self.ctr_val = 0
|
|
117
|
+
'''
|
|
118
|
+
if self.bktrRelocation:
|
|
119
|
+
entry = self.bktrRelocation.getRelocationEntry(self.tell())
|
|
120
|
+
|
|
121
|
+
if entry:
|
|
122
|
+
self.ctr_val = entry.ctr
|
|
123
|
+
#self.cryptoOffset = entry.virtualOffset + entry.physicalOffset
|
|
124
|
+
'''
|
|
125
|
+
if self.bktrSubsection is not None:
|
|
126
|
+
entries = self.bktrSubsection.getEntries(self.tell(), size)
|
|
127
|
+
#print('offset = %x' % self.tell())
|
|
128
|
+
for entry in entries:
|
|
129
|
+
#print('offset = %x' % self.tell())
|
|
130
|
+
entry.printInfo()
|
|
131
|
+
#sys.exit(0)
|
|
132
|
+
#else:
|
|
133
|
+
# print('unknown offset = %x' % self.tell())
|
|
134
|
+
|
|
135
|
+
return super(BaseFs, self).read(size, direct)
|
|
136
|
+
|
|
137
|
+
def read(self, size = None, direct = False):
|
|
138
|
+
'''
|
|
139
|
+
if self.cryptoType == Type.Crypto.BKTR or self.bktrSubsection is not None:
|
|
140
|
+
return self.bktrRead(size, True)
|
|
141
|
+
else:
|
|
142
|
+
return super(BaseFs, self).read(size, direct)
|
|
143
|
+
'''
|
|
144
|
+
return super(BaseFs, self).read(size, direct)
|
|
145
|
+
|
|
146
|
+
def getCnmt(self):
|
|
147
|
+
for f in self:
|
|
148
|
+
if isinstance(f, Cnmt):
|
|
149
|
+
return f
|
|
150
|
+
raise("No Cnmt found!")
|
|
151
|
+
|
|
152
|
+
def printInfo(self, maxDepth = 3, indent = 0):
|
|
153
|
+
tabs = '\t' * indent
|
|
154
|
+
Print.info(tabs + 'magic = ' + str(self.magic))
|
|
155
|
+
Print.info(tabs + 'fsType = ' + str(self.fsType))
|
|
156
|
+
Print.info(tabs + 'cryptoType = ' + str(self.cryptoType))
|
|
157
|
+
Print.info(tabs + 'size = ' + str(self.size))
|
|
158
|
+
Print.info(tabs + 'headerSize = %s' % (str(self._headerSize)))
|
|
159
|
+
Print.info(tabs + 'offset = %s - (%s)' % (str(self.offset), str(self.sectionStart)))
|
|
160
|
+
if self.cryptoCounter:
|
|
161
|
+
Print.info(tabs + 'cryptoCounter = ' + str(hx(self.cryptoCounter)))
|
|
162
|
+
|
|
163
|
+
if self.cryptoKey:
|
|
164
|
+
Print.info(tabs + 'cryptoKey = ' + str(hx(self.cryptoKey)))
|
|
165
|
+
|
|
166
|
+
Print.info('\n%s\t%s\n' % (tabs, '*' * 64))
|
|
167
|
+
Print.info('\n%s\tFiles:\n' % (tabs))
|
|
168
|
+
|
|
169
|
+
if(indent+1 < maxDepth):
|
|
170
|
+
for f in self:
|
|
171
|
+
f.printInfo(maxDepth, indent+1)
|
|
172
|
+
Print.info('\n%s\t%s\n' % (tabs, '*' * 64))
|
|
173
|
+
|
|
174
|
+
if self.bktrRelocation:
|
|
175
|
+
self.bktrRelocation.printInfo(maxDepth, indent+1)
|
|
176
|
+
|
|
177
|
+
if self.bktrSubsection:
|
|
178
|
+
self.bktrSubsection.printInfo(maxDepth, indent+1)
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
from binascii import hexlify as hx, unhexlify as uhx
|
|
2
|
+
from struct import pack as pk, unpack as upk
|
|
3
|
+
from hashlib import sha256
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import re
|
|
7
|
+
import pathlib
|
|
8
|
+
|
|
9
|
+
from nstools.nut import aes128
|
|
10
|
+
from nstools.nut import Hex
|
|
11
|
+
from nstools.nut import Keys, Print
|
|
12
|
+
|
|
13
|
+
from .File import File, MemoryFile
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
MEDIA_SIZE = 0x200
|
|
17
|
+
|
|
18
|
+
class Header(File):
|
|
19
|
+
def __init__(self, path = None, mode = None, cryptoType = -1, cryptoKey = -1, cryptoCounter = -1, nca = None):
|
|
20
|
+
self.size = 0
|
|
21
|
+
self.offset = 0
|
|
22
|
+
self.nca = nca
|
|
23
|
+
self.bktr_offset = 0
|
|
24
|
+
self.bktr_size = 0
|
|
25
|
+
self.magic = None
|
|
26
|
+
self.version = None
|
|
27
|
+
self.enctryCount = 0
|
|
28
|
+
self.reserved = None
|
|
29
|
+
self.buffer = None
|
|
30
|
+
super(Header, self).__init__(path, mode, cryptoType, cryptoKey, cryptoCounter)
|
|
31
|
+
|
|
32
|
+
def open(self, file = None, mode = 'rb', cryptoType = -1, cryptoKey = -1, cryptoCounter = -1):
|
|
33
|
+
super(Header, self).open(file, mode, cryptoType, cryptoKey, cryptoCounter)
|
|
34
|
+
self.rewind()
|
|
35
|
+
|
|
36
|
+
self.bktr_offset = self.readInt64()
|
|
37
|
+
self.bktr_size = self.readInt64()
|
|
38
|
+
self.magic = self.read(0x4)
|
|
39
|
+
self.version = self.readInt32()
|
|
40
|
+
self.enctryCount = self.readInt32()
|
|
41
|
+
self.reserved = self.readInt32()
|
|
42
|
+
|
|
43
|
+
def printInfo(self, maxDepth = 3, indent = 0):
|
|
44
|
+
if not self.bktr_size:
|
|
45
|
+
return
|
|
46
|
+
|
|
47
|
+
tabs = '\t' * indent
|
|
48
|
+
Print.info('\n%sBKTR' % (tabs))
|
|
49
|
+
Print.info('%soffset = %d' % (tabs, self.bktr_offset))
|
|
50
|
+
Print.info('%ssize = %d' % (tabs, self.bktr_size))
|
|
51
|
+
Print.info('%sentry count = %d' % (tabs, self.enctryCount))
|
|
52
|
+
|
|
53
|
+
Print.info('\n')
|
|
54
|
+
|
|
55
|
+
class BktrRelocationEntry:
|
|
56
|
+
def __init__(self, f):
|
|
57
|
+
self.virtualOffset = f.readInt64()
|
|
58
|
+
self.physicalOffset = f.readInt64()
|
|
59
|
+
self.isPatch = f.readInt32()
|
|
60
|
+
|
|
61
|
+
def printInfo(self, maxDepth = 3, indent = 0):
|
|
62
|
+
tabs = '\t' * indent
|
|
63
|
+
Print.info('%sRelocation Entry %s %x = %x' % (tabs, 'Patch' if self.isPatch else 'Base', self.physicalOffset, self.virtualOffset))
|
|
64
|
+
|
|
65
|
+
class BktrSubsectionEntry:
|
|
66
|
+
def __init__(self, f):
|
|
67
|
+
self.virtualOffset = f.readInt64()
|
|
68
|
+
self.size = 0
|
|
69
|
+
self.padding = f.readInt32()
|
|
70
|
+
self.ctr = f.readInt32()
|
|
71
|
+
|
|
72
|
+
def printInfo(self, maxDepth = 3, indent = 0):
|
|
73
|
+
tabs = '\t' * indent
|
|
74
|
+
Print.info('%sSubsection Entry %d, CTR = %x' % (tabs, self.virtualOffset, self.ctr))
|
|
75
|
+
|
|
76
|
+
class BktrBucket:
|
|
77
|
+
def __init__(self, f):
|
|
78
|
+
self.padding = f.readInt32()
|
|
79
|
+
self.entryCount = f.readInt32()
|
|
80
|
+
self.endOffset = f.readInt64()
|
|
81
|
+
self.entries = []
|
|
82
|
+
|
|
83
|
+
def getEntry(self, offset):
|
|
84
|
+
index = 0
|
|
85
|
+
last = self.entries[index]
|
|
86
|
+
for entry in self.entries:
|
|
87
|
+
if entry.virtualOffset > offset:
|
|
88
|
+
break
|
|
89
|
+
|
|
90
|
+
last = self.entries[index]
|
|
91
|
+
index += 1
|
|
92
|
+
|
|
93
|
+
return last
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def printInfo(self, maxDepth = 3, indent = 0):
|
|
97
|
+
tabs = '\t' * indent
|
|
98
|
+
Print.info('\n%sBKTR Bucket' % tabs)
|
|
99
|
+
Print.info('%sentries: %d' % (tabs, self.entryCount))
|
|
100
|
+
Print.info('%send offset: %d' % (tabs, self.endOffset))
|
|
101
|
+
|
|
102
|
+
for entry in self.entries:
|
|
103
|
+
entry.printInfo(maxDepth, indent + 1)
|
|
104
|
+
|
|
105
|
+
class BktrSubsectionBucket(BktrBucket):
|
|
106
|
+
def __init__(self, f):
|
|
107
|
+
super(BktrSubsectionBucket, self).__init__(f)
|
|
108
|
+
|
|
109
|
+
for i in range(self.entryCount):
|
|
110
|
+
self.entries.append(BktrSubsectionEntry(f))
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
class BktrRelocationBucket(BktrBucket):
|
|
114
|
+
def __init__(self, f):
|
|
115
|
+
super(BktrRelocationBucket, self).__init__(f)
|
|
116
|
+
|
|
117
|
+
if self.entryCount > 0xFFFF:
|
|
118
|
+
raise IOError('Too many entries')
|
|
119
|
+
|
|
120
|
+
for i in range(self.entryCount):
|
|
121
|
+
self.entries.append(BktrRelocationEntry(f))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
class Bktr(Header):
|
|
125
|
+
def __init__(self, path = None, mode = None, cryptoType = -1, cryptoKey = -1, cryptoCounter = -1, nca = None):
|
|
126
|
+
self.basePhysicalOffsets = []
|
|
127
|
+
super(Bktr, self).__init__(path, mode, cryptoType, cryptoKey, cryptoCounter, nca)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def open(self, file = None, mode = 'rb', cryptoType = -1, cryptoKey = -1, cryptoCounter = -1):
|
|
131
|
+
super(Bktr, self).open(file, mode, cryptoType, cryptoKey, cryptoCounter)
|
|
132
|
+
|
|
133
|
+
if self.bktr_size:
|
|
134
|
+
self.nca.seek(self.bktr_offset)
|
|
135
|
+
self.nca.readInt32() # padding
|
|
136
|
+
self.bucketCount = self.nca.readInt32()
|
|
137
|
+
self.totalPatchImageSize = self.nca.readInt64()
|
|
138
|
+
self.basePhysicalOffsets = []
|
|
139
|
+
for i in range(int(0x3FF0 / 8)):
|
|
140
|
+
self.basePhysicalOffsets.append(self.nca.readInt64())
|
|
141
|
+
|
|
142
|
+
def isValid(self):
|
|
143
|
+
return True if self.bktr_size > 0 else False
|
|
144
|
+
|
|
145
|
+
def getBucket(self, offset):
|
|
146
|
+
if len(self.buckets) == 0:
|
|
147
|
+
return None
|
|
148
|
+
|
|
149
|
+
index = 0
|
|
150
|
+
last = self.buckets[0]
|
|
151
|
+
|
|
152
|
+
for virtualOffset in self.basePhysicalOffsets:
|
|
153
|
+
if index >= len(self.buckets):
|
|
154
|
+
break
|
|
155
|
+
|
|
156
|
+
if offset > virtualOffset:
|
|
157
|
+
break
|
|
158
|
+
|
|
159
|
+
last = self.buckets[index]
|
|
160
|
+
index += 1
|
|
161
|
+
|
|
162
|
+
return last
|
|
163
|
+
|
|
164
|
+
def printInfo(self, maxDepth = 3, indent = 0):
|
|
165
|
+
super(Bktr, self).printInfo(maxDepth, indent)
|
|
166
|
+
tabs = '\t' * indent
|
|
167
|
+
Print.info('%sOffsets' % (tabs))
|
|
168
|
+
|
|
169
|
+
i = 0
|
|
170
|
+
for off in self.basePhysicalOffsets:
|
|
171
|
+
i += 1
|
|
172
|
+
if off == 0 and i != 1:
|
|
173
|
+
break
|
|
174
|
+
Print.info('%s %x' % (tabs, off))
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
class Bktr1(Bktr):
|
|
179
|
+
def __init__(self, path = None, mode = None, cryptoType = -1, cryptoKey = -1, cryptoCounter = -1, nca = None):
|
|
180
|
+
self.buckets = []
|
|
181
|
+
super(Bktr1, self).__init__(path, mode, cryptoType, cryptoKey, cryptoCounter, nca)
|
|
182
|
+
|
|
183
|
+
def open(self, file = None, mode = 'rb', cryptoType = -1, cryptoKey = -1, cryptoCounter = -1):
|
|
184
|
+
super(Bktr1, self).open(file, mode, cryptoType, cryptoKey, cryptoCounter)
|
|
185
|
+
|
|
186
|
+
self.buckets = []
|
|
187
|
+
|
|
188
|
+
#if self.bktr_size:
|
|
189
|
+
# for i in range(self.bucketCount):
|
|
190
|
+
# self.buckets.append(BktrRelocationBucket(self.nca))
|
|
191
|
+
|
|
192
|
+
def getRelocationEntry(self, offset):
|
|
193
|
+
if len(self.buckets) == 0:
|
|
194
|
+
return None
|
|
195
|
+
|
|
196
|
+
bucket = self.buckets[0]
|
|
197
|
+
|
|
198
|
+
index = 0
|
|
199
|
+
for virtualOffset in self.basePhysicalOffsets:
|
|
200
|
+
|
|
201
|
+
if virtualOffset > offset or index >= len(self.buckets):
|
|
202
|
+
break
|
|
203
|
+
|
|
204
|
+
bucket = self.buckets[index]
|
|
205
|
+
index += 1
|
|
206
|
+
|
|
207
|
+
result = bucket.entries[0]
|
|
208
|
+
for entry in bucket.entries:
|
|
209
|
+
if offset > entry.virtualOffset:
|
|
210
|
+
break
|
|
211
|
+
result = entry
|
|
212
|
+
|
|
213
|
+
return entry
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
def printInfo(self, maxDepth = 3, indent = 0):
|
|
217
|
+
super(Bktr1, self).printInfo(maxDepth, indent)
|
|
218
|
+
tabs = '\t' * indent
|
|
219
|
+
|
|
220
|
+
for bucket in self.buckets:
|
|
221
|
+
bucket.printInfo(maxDepth, indent+1)
|
|
222
|
+
|
|
223
|
+
class Bktr2(Bktr):
|
|
224
|
+
def __init__(self, path = None, mode = None, cryptoType = -1, cryptoKey = -1, cryptoCounter = -1, nca = None):
|
|
225
|
+
self.buckets = []
|
|
226
|
+
super(Bktr2, self).__init__(path, mode, cryptoType, cryptoKey, cryptoCounter, nca)
|
|
227
|
+
|
|
228
|
+
def open(self, file = None, mode = 'rb', cryptoType = -1, cryptoKey = -1, cryptoCounter = -1):
|
|
229
|
+
super(Bktr2, self).open(file, mode, cryptoType, cryptoKey, cryptoCounter)
|
|
230
|
+
|
|
231
|
+
self.buckets = []
|
|
232
|
+
|
|
233
|
+
if self.bktr_size:
|
|
234
|
+
for i in range(self.bucketCount):
|
|
235
|
+
self.buckets.append(BktrSubsectionBucket(self.nca))
|
|
236
|
+
|
|
237
|
+
def getEntries(self, offset, size):
|
|
238
|
+
entries = []
|
|
239
|
+
|
|
240
|
+
bucket = self.getBucket(offset)
|
|
241
|
+
if bucket is not None:
|
|
242
|
+
entries.append(bucket.getEntry(offset))
|
|
243
|
+
|
|
244
|
+
return entries
|
|
245
|
+
|
|
246
|
+
def getAllEntries(self):
|
|
247
|
+
entries = []
|
|
248
|
+
|
|
249
|
+
for bucket in self.buckets:
|
|
250
|
+
last = None
|
|
251
|
+
for entry in bucket.entries:
|
|
252
|
+
if last is not None:
|
|
253
|
+
last.size = entry.virtualOffset - last.virtualOffset
|
|
254
|
+
last = entry
|
|
255
|
+
entries.append(entry)
|
|
256
|
+
|
|
257
|
+
if len(entries) != 0:
|
|
258
|
+
entries[-1].size = bucket.endOffset - entries[-1].virtualOffset
|
|
259
|
+
|
|
260
|
+
return entries
|
|
261
|
+
|
|
262
|
+
def printInfo(self, maxDepth = 3, indent = 0):
|
|
263
|
+
super(Bktr2, self).printInfo(maxDepth, indent)
|
|
264
|
+
|
|
265
|
+
for bucket in self.buckets:
|
|
266
|
+
bucket.printInfo(maxDepth, indent+1)
|
|
267
|
+
|
|
268
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from binascii import hexlify as hx, unhexlify as uhx
|
|
2
|
+
|
|
3
|
+
from nstools.nut import Print, Keys
|
|
4
|
+
|
|
5
|
+
from .File import File
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class MetaEntry:
|
|
9
|
+
def __init__(self, f):
|
|
10
|
+
self.titleId = hx(f.read(8)[::-1]).decode()
|
|
11
|
+
self.version = f.readInt32()
|
|
12
|
+
self.type = f.readInt8()
|
|
13
|
+
self.install = f.readInt8()
|
|
14
|
+
|
|
15
|
+
f.readInt16() # junk
|
|
16
|
+
|
|
17
|
+
class ContentEntry:
|
|
18
|
+
def __init__(self, f):
|
|
19
|
+
self.hash = f.read(32)
|
|
20
|
+
self.ncaId = hx(f.read(16)).decode()
|
|
21
|
+
self.size = f.readInt48()
|
|
22
|
+
self.type = f.readInt8()
|
|
23
|
+
|
|
24
|
+
f.readInt8() # junk
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Cnmt(File):
|
|
28
|
+
def __init__(self, path = None, mode = None, cryptoType = -1, cryptoKey = -1, cryptoCounter = -1):
|
|
29
|
+
super(Cnmt, self).__init__(path, mode, cryptoType, cryptoKey, cryptoCounter)
|
|
30
|
+
|
|
31
|
+
self.titleId = None
|
|
32
|
+
self.version = None
|
|
33
|
+
self.titleType = None
|
|
34
|
+
self.headerOffset = None
|
|
35
|
+
self.contentEntryCount = None
|
|
36
|
+
self.metaEntryCount = None
|
|
37
|
+
self.contentEntries = []
|
|
38
|
+
self.metaEntries = []
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def open(self, file = None, mode = 'rb', cryptoType = -1, cryptoKey = -1, cryptoCounter = -1):
|
|
42
|
+
super(Cnmt, self).open(file, mode, cryptoType, cryptoKey, cryptoCounter)
|
|
43
|
+
self.rewind()
|
|
44
|
+
|
|
45
|
+
self.titleId = hx(self.read(8)[::-1]).decode()
|
|
46
|
+
self.version = self.readInt32()
|
|
47
|
+
self.titleType = self.readInt8()
|
|
48
|
+
|
|
49
|
+
self.readInt8() # junk
|
|
50
|
+
|
|
51
|
+
self.headerOffset = self.readInt16()
|
|
52
|
+
self.contentEntryCount = self.readInt16()
|
|
53
|
+
self.metaEntryCount = self.readInt16()
|
|
54
|
+
|
|
55
|
+
self.contentEntries = []
|
|
56
|
+
self.metaEntries = []
|
|
57
|
+
|
|
58
|
+
self.seek(0x20 + self.headerOffset)
|
|
59
|
+
for i in range(self.contentEntryCount):
|
|
60
|
+
self.contentEntries.append(ContentEntry(self))
|
|
61
|
+
|
|
62
|
+
for i in range(self.metaEntryCount):
|
|
63
|
+
self.metaEntries.append(MetaEntry(self))
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def printInfo(self, maxDepth = 3, indent = 0):
|
|
70
|
+
tabs = '\t' * indent
|
|
71
|
+
Print.info('\n%sCnmt\n' % (tabs))
|
|
72
|
+
Print.info('%stitleId = %s' % (tabs, self.titleId))
|
|
73
|
+
Print.info('%sversion = %x' % (tabs, self.version))
|
|
74
|
+
Print.info('%stitleType = %x' % (tabs, self.titleType))
|
|
75
|
+
|
|
76
|
+
for i in self.contentEntries:
|
|
77
|
+
Print.info('%s\tncaId: %s type = %x' % (tabs, i.ncaId, i.type))
|
|
78
|
+
super(Cnmt, self).printInfo(maxDepth, indent)
|
|
79
|
+
|
|
80
|
+
|