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.
Files changed (39) hide show
  1. nstools-1.1.0/PKG-INFO +11 -0
  2. nstools-1.1.0/nstools/Fs/BaseFs.py +178 -0
  3. nstools-1.1.0/nstools/Fs/Bktr.py +268 -0
  4. nstools-1.1.0/nstools/Fs/Cnmt.py +80 -0
  5. nstools-1.1.0/nstools/Fs/File.py +500 -0
  6. nstools-1.1.0/nstools/Fs/Hfs0.py +170 -0
  7. nstools-1.1.0/nstools/Fs/Ivfc.py +49 -0
  8. nstools-1.1.0/nstools/Fs/Nacp.py +613 -0
  9. nstools-1.1.0/nstools/Fs/Nca.py +305 -0
  10. nstools-1.1.0/nstools/Fs/Nsp.py +453 -0
  11. nstools-1.1.0/nstools/Fs/Pfs0.py +280 -0
  12. nstools-1.1.0/nstools/Fs/Rom.py +57 -0
  13. nstools-1.1.0/nstools/Fs/Ticket.py +226 -0
  14. nstools-1.1.0/nstools/Fs/Type.py +30 -0
  15. nstools-1.1.0/nstools/Fs/Xci.py +324 -0
  16. nstools-1.1.0/nstools/Fs/__init__.py +27 -0
  17. nstools-1.1.0/nstools/lib/BlockDecompressorReader.py +65 -0
  18. nstools-1.1.0/nstools/lib/FsCert.py +90 -0
  19. nstools-1.1.0/nstools/lib/FsNcaMod.py +255 -0
  20. nstools-1.1.0/nstools/lib/FsTools.py +151 -0
  21. nstools-1.1.0/nstools/lib/Header.py +27 -0
  22. nstools-1.1.0/nstools/lib/Hex.py +39 -0
  23. nstools-1.1.0/nstools/lib/NcaKeys.py +47 -0
  24. nstools-1.1.0/nstools/lib/PathTools.py +50 -0
  25. nstools-1.1.0/nstools/lib/Verify.py +804 -0
  26. nstools-1.1.0/nstools/lib/VerifyTools.py +380 -0
  27. nstools-1.1.0/nstools/nut/Hex.py +40 -0
  28. nstools-1.1.0/nstools/nut/Keys.py +198 -0
  29. nstools-1.1.0/nstools/nut/Print.py +34 -0
  30. nstools-1.1.0/nstools/nut/Titles.py +78 -0
  31. nstools-1.1.0/nstools/nut/aes128.py +428 -0
  32. nstools-1.1.0/nstools.egg-info/PKG-INFO +11 -0
  33. nstools-1.1.0/nstools.egg-info/SOURCES.txt +37 -0
  34. nstools-1.1.0/nstools.egg-info/dependency_links.txt +1 -0
  35. nstools-1.1.0/nstools.egg-info/not-zip-safe +1 -0
  36. nstools-1.1.0/nstools.egg-info/requires.txt +4 -0
  37. nstools-1.1.0/nstools.egg-info/top_level.txt +1 -0
  38. nstools-1.1.0/setup.cfg +4 -0
  39. 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
+