nstools 1.2.2__tar.gz → 2.0.0b2__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 (48) hide show
  1. {nstools-1.2.2 → nstools-2.0.0b2}/PKG-INFO +3 -3
  2. {nstools-1.2.2 → nstools-2.0.0b2}/README.md +2 -2
  3. nstools-2.0.0b2/ns_verify_folder.py +110 -0
  4. {nstools-1.2.2/nstools/lib → nstools-2.0.0b2/nstools}/FsCert.py +2 -2
  5. nstools-2.0.0b2/nstools/FsNcz.py +74 -0
  6. {nstools-1.2.2/nstools/lib → nstools-2.0.0b2/nstools}/FsTools.py +3 -3
  7. {nstools-1.2.2/nstools/lib → nstools-2.0.0b2/nstools}/PathTools.py +0 -1
  8. {nstools-1.2.2/nstools/lib → nstools-2.0.0b2/nstools}/Verify.py +19 -23
  9. {nstools-1.2.2/nstools/lib → nstools-2.0.0b2/nstools}/VerifyTools.py +10 -14
  10. {nstools-1.2.2 → nstools-2.0.0b2}/nstools.egg-info/PKG-INFO +3 -3
  11. nstools-2.0.0b2/nstools.egg-info/SOURCES.txt +21 -0
  12. {nstools-1.2.2 → nstools-2.0.0b2}/setup.py +3 -4
  13. nstools-1.2.2/ns_verify_folder.py +0 -156
  14. nstools-1.2.2/nstools/Fs/BaseFs.py +0 -178
  15. nstools-1.2.2/nstools/Fs/Bktr.py +0 -268
  16. nstools-1.2.2/nstools/Fs/Cnmt.py +0 -80
  17. nstools-1.2.2/nstools/Fs/File.py +0 -500
  18. nstools-1.2.2/nstools/Fs/Hfs0.py +0 -181
  19. nstools-1.2.2/nstools/Fs/Ivfc.py +0 -49
  20. nstools-1.2.2/nstools/Fs/Nacp.py +0 -613
  21. nstools-1.2.2/nstools/Fs/Nca.py +0 -305
  22. nstools-1.2.2/nstools/Fs/Nsp.py +0 -452
  23. nstools-1.2.2/nstools/Fs/Pfs0.py +0 -305
  24. nstools-1.2.2/nstools/Fs/Rom.py +0 -57
  25. nstools-1.2.2/nstools/Fs/Ticket.py +0 -226
  26. nstools-1.2.2/nstools/Fs/Type.py +0 -30
  27. nstools-1.2.2/nstools/Fs/Xci.py +0 -355
  28. nstools-1.2.2/nstools/Fs/__init__.py +0 -27
  29. nstools-1.2.2/nstools/lib/BlockDecompressorReader.py +0 -65
  30. nstools-1.2.2/nstools/lib/FsNcaMod.py +0 -255
  31. nstools-1.2.2/nstools/lib/Header.py +0 -27
  32. nstools-1.2.2/nstools/nut/Hex.py +0 -40
  33. nstools-1.2.2/nstools/nut/Keys.py +0 -202
  34. nstools-1.2.2/nstools/nut/Print.py +0 -45
  35. nstools-1.2.2/nstools/nut/Titles.py +0 -78
  36. nstools-1.2.2/nstools/nut/aes128.py +0 -428
  37. nstools-1.2.2/nstools.egg-info/SOURCES.txt +0 -43
  38. {nstools-1.2.2 → nstools-2.0.0b2}/LICENSE.md +0 -0
  39. {nstools-1.2.2 → nstools-2.0.0b2}/bin/ns-verify-folder +0 -0
  40. {nstools-1.2.2 → nstools-2.0.0b2}/bin/ns-verify-folder-log +0 -0
  41. {nstools-1.2.2 → nstools-2.0.0b2}/bin/ns-verify-folder-log.bat +0 -0
  42. {nstools-1.2.2 → nstools-2.0.0b2}/bin/ns-verify-folder.bat +0 -0
  43. {nstools-1.2.2/nstools/lib → nstools-2.0.0b2/nstools}/NcaKeys.py +0 -0
  44. {nstools-1.2.2 → nstools-2.0.0b2}/nstools.egg-info/dependency_links.txt +0 -0
  45. {nstools-1.2.2 → nstools-2.0.0b2}/nstools.egg-info/requires.txt +0 -0
  46. {nstools-1.2.2 → nstools-2.0.0b2}/nstools.egg-info/top_level.txt +0 -0
  47. {nstools-1.2.2 → nstools-2.0.0b2}/nstools.egg-info/zip-safe +0 -0
  48. {nstools-1.2.2 → nstools-2.0.0b2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nstools
3
- Version: 1.2.2
3
+ Version: 2.0.0b2
4
4
  Home-page: https://github.com/seiya-dev/NSTools
5
5
  License: MIT
6
6
  Requires-Python: >=3.10
@@ -20,11 +20,11 @@ Dynamic: requires-python
20
20
 
21
21
  # Nintendo Switch Tools
22
22
 
23
- Tools for XCI, XCZ, NSP and NSZ
23
+ Tools for XCI, XCZ, NSP and NSZ
24
24
 
25
25
  Based on nut, NSC_B and nsz
26
26
 
27
27
  # pypi.org
28
28
 
29
- for using nstools.Fs, nstools.lib and nstools.nut:
29
+ for using nstools, nsz.Fs and nsz.nut:
30
30
  https://pypi.org/project/nstools/
@@ -1,10 +1,10 @@
1
1
  # Nintendo Switch Tools
2
2
 
3
- Tools for XCI, XCZ, NSP and NSZ
3
+ Tools for XCI, XCZ, NSP and NSZ
4
4
 
5
5
  Based on nut, NSC_B and nsz
6
6
 
7
7
  # pypi.org
8
8
 
9
- for using nstools.Fs, nstools.lib and nstools.nut:
9
+ for using nstools, nsz.Fs and nsz.nut:
10
10
  https://pypi.org/project/nstools/
@@ -0,0 +1,110 @@
1
+ #! /usr/bin/python3
2
+
3
+ from pathlib import Path
4
+ import argparse
5
+ import requests
6
+ import json
7
+ import sys
8
+
9
+ from nsz.nut import Keys
10
+ from nstools import Verify
11
+
12
+ # set app path
13
+ appPath = Path(sys.argv[0])
14
+ while not appPath.is_dir():
15
+ appPath = appPath.parents[0]
16
+ appPath = Path(appPath).resolve().as_posix()
17
+ print(f'[:INFO:] App Path: {appPath}')
18
+
19
+ # set args
20
+ parser = argparse.ArgumentParser(formatter_class = argparse.ArgumentDefaultsHelpFormatter)
21
+ parser.add_argument('-i', '--input', help = 'input folder')
22
+ parser.add_argument('-w', '--webhook-url', help = 'discord webhook url', required = False)
23
+ parser.add_argument('--save-log', help = 'save verify log', required = False, action='store_true')
24
+ args = parser.parse_args()
25
+
26
+ INCP_PATH = args.input
27
+ WHOOK_URL = args.webhook_url
28
+ SAVE_VLOG = bool(args.save_log)
29
+
30
+ Keys.load_default()
31
+ if not Keys.keys_loaded:
32
+ input('Press Enter to exit...')
33
+ sys.exit(1)
34
+
35
+ def send_hook(message_content: str = '', PadPrint: bool = False):
36
+ if message_content == '':
37
+ return
38
+ try:
39
+ print_msg = message_content
40
+ if PadPrint == True:
41
+ print_msg = f'\n{message_content}'
42
+ print(print_msg)
43
+ payload = {
44
+ 'username': 'Contributions',
45
+ 'content': message_content.strip()
46
+ }
47
+ headers = {"Content-type": "application/json"}
48
+ response = requests.post(WHOOK_URL, data=json.dumps(payload), headers=headers)
49
+ response.raise_for_status()
50
+ except:
51
+ pass
52
+
53
+ def scan_folder():
54
+ ipath = Path(INCP_PATH).resolve().as_posix()
55
+
56
+ if not Path(ipath).exists():
57
+ print(f'[:WARN:] Please put your files in "{ipath}" and run this script again.')
58
+ return
59
+
60
+ files = list()
61
+
62
+ for item in sorted(list(Path(ipath).iterdir())):
63
+ item_path = Path(item)
64
+ if not item_path.is_file() or item_path.is_symlink():
65
+ continue
66
+ if not item_path.name.lower().endswith(('.xci', '.xcz', '.nsp', '.nsz')):
67
+ continue
68
+ files.append(item.as_posix())
69
+
70
+ findex = 0
71
+ for item in sorted(files):
72
+ item_path = Path(item)
73
+
74
+ findex += 1
75
+ send_hook(f'[:INFO:] File found ({findex} of {len(files)}): {item_path.name}', True)
76
+ send_hook(f'[:INFO:] Checking filename...')
77
+
78
+ data = Verify.parse_name(item_path.name)
79
+
80
+ if data is None:
81
+ send_hook(f'{item_path.name}: BAD NAME')
82
+
83
+ rootpath = item_path.parent.as_posix()
84
+ basename = item_path.name
85
+ basename = f'{basename[:-4]}-{basename[-3:]}-verify'
86
+ log_name = Path(rootpath).joinpath(basename).as_posix()
87
+
88
+ try:
89
+ send_hook(f'[:INFO:] Verifying...')
90
+ nspTest, nspLog = Verify.verify(item_path.as_posix())
91
+ if nspTest != True:
92
+ send_hook(f'{item_path}: BAD', True)
93
+ else:
94
+ send_hook(f'{item_path}: OK', True)
95
+ if SAVE_VLOG == True:
96
+ if nspTest != True:
97
+ with open(f'{log_name}-bad.log', 'w') as f:
98
+ f.write(f'{nspLog}')
99
+ else:
100
+ with open(f'{log_name}-ok.log', 'w') as f:
101
+ f.write(f'{nspLog}')
102
+ except Exception as e:
103
+ send_hook(f'[:WARN:] An error occurred:\n{item_path}: {str(e)}')
104
+
105
+ if __name__ == "__main__":
106
+ if INCP_PATH:
107
+ scan_folder()
108
+ else:
109
+ parser.print_help()
110
+ print()
@@ -1,4 +1,4 @@
1
- import binascii
1
+ from binascii import a2b_base64
2
2
 
3
3
  class PublicCert:
4
4
  def getPublic(ctype = None):
@@ -86,5 +86,5 @@ class PublicCert:
86
86
  }
87
87
  if ctype not in certlist:
88
88
  ctype = 'Tinfoil'
89
- chain = bytearray(binascii.a2b_base64(certlist[ctype]))
89
+ chain = bytearray(a2b_base64(certlist[ctype]))
90
90
  return chain
@@ -0,0 +1,74 @@
1
+ from binascii import hexlify as hx, unhexlify as uhx
2
+
3
+ from nsz.nut import Keys
4
+ from nsz.nut import Print
5
+
6
+ from nsz.Fs import Type
7
+ from nsz.Fs.File import File
8
+ from nsz.Fs.Nca import NcaHeader as NczHeader
9
+
10
+ class Ncz(File):
11
+ def __init__(self, path = None, mode = 'rb', cryptoType = -1, cryptoKey = -1, cryptoCounter = -1):
12
+ self.header = None
13
+ self.sectionFilesystems = []
14
+ self.sections = []
15
+ super(Ncz, self).__init__(path, mode, cryptoType, cryptoKey, cryptoCounter)
16
+
17
+ def __iter__(self):
18
+ return self.sectionFilesystems.__iter__()
19
+
20
+ def __getitem__(self, key):
21
+ return self.sectionFilesystems[key]
22
+
23
+ def open(self, file = None, mode = 'rb', cryptoType = -1, cryptoKey = -1, cryptoCounter = -1, meta_only=False):
24
+ super(Ncz, self).open(file, mode, cryptoType, cryptoKey, cryptoCounter, meta_only)
25
+
26
+ self.header = NczHeader()
27
+ self.partition(0x0, 0xC00, self.header, Type.Crypto.XTS, uhx(Keys.get('header_key')))
28
+ self.header.seek(0x400)
29
+
30
+ def masterKey(self):
31
+ return max(self.header.cryptoType, self.header.cryptoType2)
32
+
33
+ def buildId(self):
34
+ if self.header.contentType != Type.Content.PROGRAM:
35
+ return None
36
+
37
+ try:
38
+ f = self[0]['main']
39
+ f.seek(0x40)
40
+ return hx(f.read(0x20)).decode('utf8').upper()
41
+ except IOError as e:
42
+ pass
43
+ except:
44
+ raise
45
+ return None
46
+
47
+ def printInfo(self, maxDepth = 3, indent = 0):
48
+ tabs = '\t' * indent
49
+ Print.info('\n%sNCA Archive\n' % (tabs))
50
+ super(Ncz, self).printInfo(maxDepth, indent)
51
+
52
+ Print.info(tabs + 'magic = ' + str(self.header.magic))
53
+ Print.info(tabs + 'titleId = ' + str(self.header.titleId))
54
+ Print.info(tabs + 'rightsId = ' + str(self.header.rightsId))
55
+ Print.info(tabs + 'isGameCard = ' + hex(self.header.isGameCard))
56
+ Print.info(tabs + 'contentType = ' + str(self.header.contentType))
57
+ Print.info(tabs + 'cryptoType = ' + str(self.cryptoType))
58
+ Print.info(tabs + 'Size: ' + str(self.header.size))
59
+ Print.info(tabs + 'crypto master key: ' + str(self.header.cryptoType))
60
+ Print.info(tabs + 'crypto master key2: ' + str(self.header.cryptoType2))
61
+ Print.info(tabs + 'key Index: ' + str(self.header.keyIndex))
62
+ #Print.info(tabs + 'key Block: ' + str(self.header.getKeyBlock()))
63
+ for key in self.header.keys:
64
+ if key:
65
+ Print.info(tabs + 'key Block: ' + str(hx(key)))
66
+
67
+ if(indent+1 < maxDepth):
68
+ Print.info('\n%sPartitions:' % (tabs))
69
+
70
+ for s in self:
71
+ s.printInfo(maxDepth, indent+1)
72
+
73
+ if self.header.contentType == Type.Content.PROGRAM:
74
+ Print.info(tabs + 'build Id: ' + str(self.buildId()))
@@ -1,12 +1,12 @@
1
1
  from binascii import hexlify as hx, unhexlify as uhx
2
2
  from hashlib import sha256, sha1
3
-
4
3
  from copy import copy
5
- from . import FsNcaMod
4
+
5
+ from .FsNcz import Ncz
6
6
 
7
7
  def get_ncz_data(src_nca):
8
8
  nca = copy(src_nca)
9
- nca = FsNcaMod.Nca(nca)
9
+ nca = Ncz(nca)
10
10
  return nca
11
11
 
12
12
  def get_data_from_cnmt(nca):
@@ -13,7 +13,6 @@ def expandFiles(path):
13
13
  f = path.joinpath(f)
14
14
  files.append(f)
15
15
  return files
16
-
17
16
 
18
17
  def isGame(filePath):
19
18
  return filePath.suffix == '.nsp' or filePath.suffix == '.xci' or filePath.suffix == '.nsz' or filePath.suffix == '.xcz'
@@ -1,29 +1,25 @@
1
+ from os.path import basename as fsBasename, abspath as fsAbsPath
1
2
  from binascii import hexlify as hx, unhexlify as uhx
2
- from hashlib import sha256, sha1
3
+ from re import search as re_search
4
+ from hashlib import sha256
5
+ from pathlib import Path
3
6
 
4
- import os
5
- import sys
6
- import re
7
+ from zstandard import ZstdDecompressor
8
+ from enlighten import Counter as pb_Counter
7
9
 
8
- from pathlib import Path
10
+ from nsz import Header, BlockDecompressorReader
11
+ from nsz.Fs import factory
12
+ from nsz.Fs import Xci, Nsp
13
+ from nsz.Fs import Nca, Ticket
14
+ from nsz.Fs import Type
9
15
 
10
16
  from . import FsTools
11
17
  from . import VerifyTools
12
- from . import Header, BlockDecompressorReader
13
18
  from .FsCert import PublicCert
14
19
 
15
- import zstandard
16
- import enlighten
17
-
18
- from nstools.Fs import factory
19
- from nstools.Fs import Xci, Nsp
20
- from nstools.Fs import Nca, Ticket
21
- from nstools.Fs import Type
22
-
23
-
24
20
  def parse_name(file: str):
25
- res_id = re.search(r'(?P<title_id>\[0100[A-F0-9]{12}\])', file)
26
- res_ver = re.search(r'(?P<version>\[v\d+\])', file)
21
+ res_id = re_search(r'(?P<title_id>\[0100[A-F0-9]{12}\])', file)
22
+ res_ver = re_search(r'(?P<version>\[v\d+\])', file)
27
23
 
28
24
  if res_id is None or res_ver is None:
29
25
  return None
@@ -68,7 +64,7 @@ def verify(file: str, vlevel: int = 3):
68
64
  vlevel = 3
69
65
 
70
66
  try:
71
- filename = os.path.abspath(file)
67
+ filename = fsAbsPath(file)
72
68
 
73
69
  check = True
74
70
  vmsg = list()
@@ -100,7 +96,7 @@ def verify(file: str, vlevel: int = 3):
100
96
  f.flush()
101
97
  f.close()
102
98
 
103
- outlog = os.path.basename(file) + '\n'
99
+ outlog = fsBasename(file) + '\n'
104
100
  outlog += '\n'.join(vmsg) + '\n'
105
101
 
106
102
  return check, outlog
@@ -192,7 +188,7 @@ def verify_decrypt(nspx, vmsg = None):
192
188
  vmsg.append(tvmsg)
193
189
  if f.header.contentType != Type.Content.PROGRAM:
194
190
  correct = VerifyTools.verify_enforcer(f)
195
- if correct == True and f.header.contentType == Type.Content.PUBLIC_DATA and f.header.getRightsId() == 0:
191
+ if correct == True and f.header.contentType == Type.Content.PUBLICDATA and f.header.getRightsId() == 0:
196
192
  correct = VerifyTools.pr_noenc_check_dlc(f)
197
193
  if correct == False:
198
194
  bad_dec = True
@@ -571,7 +567,7 @@ def verify_hash(nspx, headerlist, vmsg = None):
571
567
  counter = 0
572
568
  mbDiv = 1048576
573
569
  BAR_FMT = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}| {count:{len_total}d}/{total:d} {unit} [{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]'
574
- bar = enlighten.Counter(total = nca_size//mbDiv, desc='Hashing', unit='MiB', color='red', bar_format=BAR_FMT)
570
+ bar = pb_Counter(total = nca_size//mbDiv, desc='Hashing', unit='MiB', color='red', bar_format=BAR_FMT)
575
571
 
576
572
  i = 0
577
573
  f.rewind();
@@ -651,7 +647,7 @@ def verify_hash(nspx, headerlist, vmsg = None):
651
647
  counter = 0
652
648
  mbDiv = 1048576
653
649
  BAR_FMT = u'{desc}{desc_pad}{percentage:3.0f}%|{bar}| {count:{len_total}d}/{total:d} {unit} [{elapsed}<{eta}, {rate:.2f}{unit_pad}{unit}/s]'
654
- bar = enlighten.Counter(total = nca_size//mbDiv, desc='Hashing', unit='MiB', color='red', bar_format=BAR_FMT)
650
+ bar = pb_Counter(total = nca_size//mbDiv, desc='Hashing', unit='MiB', color='red', bar_format=BAR_FMT)
655
651
 
656
652
  i = 0
657
653
  f.rewind();
@@ -704,7 +700,7 @@ def verify_hash(nspx, headerlist, vmsg = None):
704
700
  pos = f.tell()
705
701
 
706
702
  if not useBlockCompression:
707
- decompressor = zstandard.ZstdDecompressor().stream_reader(f)
703
+ decompressor = ZstdDecompressor().stream_reader(f)
708
704
 
709
705
  spsize = 0
710
706
 
@@ -7,24 +7,20 @@ from Crypto.Util import Counter
7
7
  from Crypto.PublicKey import RSA
8
8
  from Crypto.Signature import PKCS1_v1_5, PKCS1_PSS
9
9
 
10
- import io
10
+ from zstandard import ZstdDecompressor
11
11
 
12
- from nstools.nut import Hex
13
- from nstools.nut import Keys
14
- from nstools.nut import aes128
12
+ from nsz import Header, BlockDecompressorReader
13
+ from nsz.nut import Hex
14
+ from nsz.nut import Keys
15
+ from nsz.nut import aes128
16
+ from nsz.Fs import Type
17
+ from nsz.Fs import File
18
+ from nsz.Fs import Nca
19
+ from nsz.Fs import Ticket
15
20
 
16
21
  from . import FsTools
17
- from . import Header, BlockDecompressorReader
18
22
  from .NcaKeys import getNcaModulusKey
19
23
 
20
- import zstandard
21
-
22
- from nstools.Fs import Type
23
- from nstools.Fs import File
24
- from nstools.Fs import Nca
25
- from nstools.Fs import Ticket
26
-
27
-
28
24
  RSA_PUBLIC_EXPONENT = 0x10001
29
25
  FS_HEADER_LENGTH = 0x200
30
26
  UNCOMPRESSABLE_HEADER_SIZE = 0x4000
@@ -136,7 +132,7 @@ def verify_ncz(self, target):
136
132
  pos = f.tell()
137
133
 
138
134
  if not useBlockCompression:
139
- decompressor = zstandard.ZstdDecompressor().stream_reader(f)
135
+ decompressor = ZstdDecompressor().stream_reader(f)
140
136
 
141
137
  count = 0
142
138
  checkstarter = 0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nstools
3
- Version: 1.2.2
3
+ Version: 2.0.0b2
4
4
  Home-page: https://github.com/seiya-dev/NSTools
5
5
  License: MIT
6
6
  Requires-Python: >=3.10
@@ -20,11 +20,11 @@ Dynamic: requires-python
20
20
 
21
21
  # Nintendo Switch Tools
22
22
 
23
- Tools for XCI, XCZ, NSP and NSZ
23
+ Tools for XCI, XCZ, NSP and NSZ
24
24
 
25
25
  Based on nut, NSC_B and nsz
26
26
 
27
27
  # pypi.org
28
28
 
29
- for using nstools.Fs, nstools.lib and nstools.nut:
29
+ for using nstools, nsz.Fs and nsz.nut:
30
30
  https://pypi.org/project/nstools/
@@ -0,0 +1,21 @@
1
+ LICENSE.md
2
+ README.md
3
+ ns_verify_folder.py
4
+ setup.py
5
+ bin/ns-verify-folder
6
+ bin/ns-verify-folder-log
7
+ bin/ns-verify-folder-log.bat
8
+ bin/ns-verify-folder.bat
9
+ nstools/FsCert.py
10
+ nstools/FsNcz.py
11
+ nstools/FsTools.py
12
+ nstools/NcaKeys.py
13
+ nstools/PathTools.py
14
+ nstools/Verify.py
15
+ nstools/VerifyTools.py
16
+ nstools.egg-info/PKG-INFO
17
+ nstools.egg-info/SOURCES.txt
18
+ nstools.egg-info/dependency_links.txt
19
+ nstools.egg-info/requires.txt
20
+ nstools.egg-info/top_level.txt
21
+ nstools.egg-info/zip-safe
@@ -16,7 +16,7 @@ if readmePath.is_file():
16
16
 
17
17
  setuptools.setup(
18
18
  name = 'nstools',
19
- version = '1.2.2',
19
+ version = '2.0.0b2',
20
20
  url = 'https://github.com/seiya-dev/NSTools',
21
21
  long_description = long_description,
22
22
  long_description_content_type = 'text/markdown',
@@ -31,11 +31,10 @@ setuptools.setup(
31
31
  ],
32
32
 
33
33
  packages = [
34
- 'nstools.Fs',
35
- 'nstools.nut',
36
- 'nstools.lib',
34
+ 'nstools',
37
35
  ],
38
36
  install_requires = [
37
+ # 'nsz @ git+https://github.com/nicoboss/nsz.git@...', # use requirements.txt
39
38
  'zstandard',
40
39
  'enlighten',
41
40
  'requests',
@@ -1,156 +0,0 @@
1
- #! /usr/bin/python3
2
-
3
- import os
4
- import sys
5
- import json
6
- import requests
7
- import re
8
-
9
- from pathlib import Path
10
-
11
- from nstools.nut import Keys
12
-
13
- from nstools.lib import Verify
14
-
15
- # set app path
16
- appPath = Path(sys.argv[0])
17
- while not appPath.is_dir():
18
- appPath = appPath.parents[0]
19
- appPath = os.path.abspath(appPath)
20
- print(f'[:INFO:] App Path: {appPath}')
21
-
22
- # set logs path
23
- # logs_dir = os.path.abspath(os.path.join(appPath, '..', 'logs'))
24
- # print(f'[:INFO:] Logs Path: {logs_dir}')
25
-
26
- import argparse
27
- parser = argparse.ArgumentParser(formatter_class = argparse.ArgumentDefaultsHelpFormatter)
28
- parser.add_argument('-i', '--input', help = 'input folder')
29
- parser.add_argument('-w', '--webhook-url', help = 'discord webhook url', required = False)
30
- parser.add_argument('--save-log', help = 'save verify log', required = False, action='store_true')
31
- args = parser.parse_args()
32
-
33
- INCP_PATH = args.input
34
- WHOOK_URL = args.webhook_url
35
- SAVE_VLOG = bool(args.save_log)
36
-
37
- Keys.load_default()
38
- if not Keys.keys_loaded:
39
- input('Press Enter to exit...')
40
- sys.exit(1)
41
-
42
- def send_hook(message_content: str = '', PadPrint: bool = False):
43
- if message_content == '':
44
- return
45
- try:
46
- print_msg = message_content
47
- if PadPrint == True:
48
- print_msg = f'\n{message_content}'
49
- print(print_msg)
50
- payload = {
51
- 'username': 'Contributions',
52
- 'content': message_content.strip()
53
- }
54
- headers = {"Content-type": "application/json"}
55
- response = requests.post(WHOOK_URL, data=json.dumps(payload), headers=headers)
56
- response.raise_for_status()
57
- except:
58
- pass
59
-
60
- def scan_folder():
61
- ipath = os.path.abspath(INCP_PATH)
62
- fname = os.path.basename(ipath).upper()
63
-
64
- # lpath_badfolder = os.path.join(logs_dir, 'bad-folder.log')
65
- # lpath_badname = os.path.join(logs_dir, 'bad-names.log')
66
- # lpath_badfile = os.path.join(logs_dir, 'bad-file.log')
67
-
68
- # if not os.path.exists(logs_dir):
69
- # os.makedirs(logs_dir)
70
-
71
- # if os.path.exists(lpath_badfolder):
72
- # os.remove(lpath_badfolder)
73
- # if os.path.exists(lpath_badname):
74
- # os.remove(lpath_badname)
75
- # if os.path.exists(lpath_badfile):
76
- # os.remove(lpath_badfile)
77
-
78
- if not os.path.exists(ipath):
79
- print(f'[:WARN:] Please put your files in "{ipath}" and run this script again.')
80
- return
81
-
82
- files = list()
83
- for item in sorted(os.listdir(ipath)):
84
- item_path = os.path.join(ipath, item)
85
- if not os.path.isfile(item_path):
86
- continue
87
- if not item.lower().endswith(('.xci', '.xcz', '.nsp', '.nsz')):
88
- continue
89
- files.append(item)
90
-
91
- findex = 0
92
- for item in sorted(files):
93
- item_path = os.path.join(ipath, item)
94
-
95
- findex += 1
96
- send_hook(f'[:INFO:] File found ({findex} of {len(files)}): {item_path}', True)
97
- send_hook(f'[:INFO:] Checking filename...')
98
-
99
- data = Verify.parse_name(item)
100
-
101
- if data is None:
102
- send_hook(f'{item_path}: BAD NAME')
103
- # with open(lpath_badname, 'a') as f:
104
- # f.write(f'{item_path}\n')
105
-
106
- # if data is not None and re.match(r'^BASE|UPD(ATE)?|DLC|XCI$', fname) is not None:
107
- # if item.lower().endswith(('.xci', '.xcz')):
108
- # iscart = True
109
- # else:
110
- # iscart = False
111
- # if fname == 'UPDATE':
112
- # fname = 'UPD'
113
- # if fname == 'BASE' and data['title_type'] != 'BASE' or fname == 'BASE' and iscart == True:
114
- # with open(lpath_badfolder, 'a') as f:
115
- # f.write(f'{item_path}\n')
116
- # if fname == 'UPD' and data['title_type'] != 'UPD' or fname == 'UPD' and iscart == True:
117
- # with open(lpath_badfolder, 'a') as f:
118
- # f.write(f'{item_path}\n')
119
- # if fname == 'DLC' and data['title_type'] != 'DLC' or fname == 'DLC' and iscart == True:
120
- # with open(lpath_badfolder, 'a') as f:
121
- # f.write(f'{item_path}\n')
122
- # if fname == 'XCI' and iscart == False:
123
- # with open(lpath_badfolder, 'a') as f:
124
- # f.write(f'{item_path}\n')
125
-
126
- rootpath = os.path.dirname(item_path)
127
- basename = os.path.basename(item_path)
128
- basename = f'{basename[:-4]}-{basename[-3:]}-verify'
129
- log_name = os.path.join(rootpath, basename)
130
-
131
- try:
132
- send_hook(f'[:INFO:] Verifying...')
133
- nspTest, nspLog = Verify.verify(item_path)
134
- if nspTest != True:
135
- send_hook(f'{item_path}: BAD', True)
136
- # with open(lpath_badfile, 'a') as f:
137
- # f.write(f'{item_path}\n')
138
- else:
139
- send_hook(f'{item_path}: OK', True)
140
- if SAVE_VLOG == True:
141
- if nspTest != True:
142
- with open(f'{log_name}-bad.log', 'w') as f:
143
- f.write(f'{nspLog}')
144
- else:
145
- with open(f'{log_name}-ok.log', 'w') as f:
146
- f.write(f'{nspLog}')
147
- except Exception as e:
148
- send_hook(f'[:WARN:] An error occurred:\n{item_path}: {str(e)}')
149
-
150
-
151
- if __name__ == "__main__":
152
- if INCP_PATH:
153
- scan_folder()
154
- else:
155
- parser.print_help()
156
- print()