6 Commits

Author SHA1 Message Date
147e72c8c9 Added Title Key generation code 2024-12-13 16:56:15 -05:00
Aep
08c2bd27f5 Add Public System Menu Versions (#16)
* Add System Menu Versions

* add ()

* fix comments

* Fix version getting messed up by public version

---------

Co-authored-by: Aep <itisinfactmark@gmail.com>
2024-11-27 11:50:34 -05:00
dadcc02701 Fix --onefile accidentally being passed for macOS builds instead of only --standalone 2024-11-16 23:00:37 -05:00
c716291c2d Updated build process, workflow, and build directions in README 2024-11-16 22:50:36 -05:00
DDinghoya
7c35e2090d Add Korean translations (#15)
* Create nusget_ko.ts

* Update NUSGet.pyproject
2024-11-16 22:02:52 -05:00
1ae712918e Run extra build job for macOS x86_64 2024-11-16 01:13:29 -05:00
12 changed files with 601 additions and 73 deletions

View File

@@ -1,5 +1,4 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python
# This workflow will install Python dependencies and then build NUSGet for all platforms
name: Python application
@@ -21,17 +20,16 @@ jobs:
- uses: actions/checkout@v4
- name: Install ccache for Nuitka
run: sudo apt update && sudo apt install -y ccache libicu70
- name: Set up Python 3.11
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.12"
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Build Package
run: |
make linux
run: make all
- name: Prepare Package for Upload
run: |
mv NUSGet ~/NUSGet
@@ -41,26 +39,24 @@ jobs:
uses: actions/upload-artifact@v4
with:
path: ~/NUSGet.tar
name: NUSGet-linux-bin
name: NUSGet-Linux-bin
build-macos:
build-macos-x86:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.11
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.12"
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Build Package
run: |
python build_translations.py
python -m nuitka --show-progress --include-data-dir=data=data --include-data-dir=resources=resources --assume-yes-for-downloads --standalone --plugin-enable=pyside6 NUSGet.py --macos-create-app-bundle --macos-app-icon=resources/icon.png
run: ARCH_FLAGS=--macos-target-arch=x86_64 make all
- name: Prepare Package for Upload
run: |
mv NUSGet.app ~/NUSGet.app
@@ -70,7 +66,34 @@ jobs:
uses: actions/upload-artifact@v4
with:
path: ~/NUSGet.tar
name: NUSGet-macos-bin
name: NUSGet-macOS-x86_64-bin
build-macos-arm64:
runs-on: macos-latest
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Build Package
run: ARCH_FLAGS=--macos-target-arch=arm64 make all
- name: Prepare Package for Upload
run: |
mv NUSGet.app ~/NUSGet.app
cd ~
tar cvf NUSGet.tar NUSGet.app
- name: Upload Package
uses: actions/upload-artifact@v4
with:
path: ~/NUSGet.tar
name: NUSGet-macOS-arm64-bin
build-windows:
@@ -80,26 +103,23 @@ jobs:
- uses: actions/checkout@v4
- name: Enable Developer Command Prompt
uses: ilammy/msvc-dev-cmd@v1.13.0
- name: Set up Python 3.11
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.11"
python-version: "3.12"
- name: Install Dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt
- name: Build Package
run: |
python build_translations.py
python -m nuitka --show-progress --include-data-dir=data=data --include-data-dir=resources=resources --assume-yes-for-downloads --onefile --windows-icon-from-ico=resources/icon.png --plugin-enable=pyside6 NUSGet.py --windows-console-mode=disable
run: .\Build.ps1
- name: Upload Package
uses: actions/upload-artifact@v4
with:
path: D:\a\NUSGet\NUSGet\NUSGet.dist
name: NUSGet-windows-bin
name: NUSGet-Windows-bin
- name: Upload Onefile Package
uses: actions/upload-artifact@v4
with:
path: D:\a\NUSGet\NUSGet\NUSGet.exe
name: NUSGet-windows-onefile-bin
name: NUSGet-Windows-onefile-bin

33
Build.ps1 Normal file
View File

@@ -0,0 +1,33 @@
# Build.ps1 for NUSGet
# Default option is to run build, like a Makefile
param(
[string]$Task = "build"
)
$buildNUSGet = {
Write-Host "Building NUSGet..."
python build_translations.py
python -m nuitka --show-progress --assume-yes-for-downloads NUSGet.py
}
$cleanNUSGet = {
Write-Host "Cleaning..."
Remove-Item -Recurse -Force NUSGet.exe, ./NUSGet.build/, ./NUSGet.dist/, ./NUSGet.onefile-build/
}
switch ($Task.ToLower()) {
"build" {
& $buildNUSGet
break
}
"clean" {
& $cleanNUSGet
break
}
default {
Write-Host "Unknown task: $Task" -ForegroundColor Red
Write-Host "Available tasks: build, clean"
break
}
}

View File

@@ -1,10 +1,11 @@
CC=python -m nuitka
ARCH_FLAGS?=
linux:
all:
python build_translations.py
$(CC) --show-progress --include-data-dir=data=data --include-data-dir=resources=resources --assume-yes-for-downloads --onefile --plugin-enable=pyside6 NUSGet.py -o NUSGet
$(CC) --show-progress --assume-yes-for-downloads NUSGet.py $(ARCH_FLAGS) -o NUSGet
linux-install:
install:
install -d /opt/NUSGet
install NUSGet /opt/NUSGet/
install ./packaging/icon.png /opt/NUSGet/NUSGet.png

View File

@@ -1,6 +1,23 @@
# "NUSGet.py", licensed under the MIT license
# Copyright 2024 NinjaCheetah
# Nuitka options. These determine compilation settings based on the current OS.
# nuitka-project-if: {OS} == "Darwin":
# nuitka-project: --standalone
# nuitka-project: --macos-create-app-bundle
# nuitka-project: --macos-app-icon={MAIN_DIRECTORY}/resources/icon.png
# nuitka-project-if: {OS} == "Windows":
# nuitka-project: --onefile
# nuitka-project: --windows-icon-from-ico={MAIN_DIRECTORY}/resources/icon.png
# nuitka-project: --windows-console-mode=disable
# nuitka-project-if: {OS} in ("Linux", "FreeBSD", "OpenBSD"):
# nuitka-project: --onefile
# These are standard options that are needed on all platforms.
# nuitka-project: --plugin-enable=pyside6
# nuitka-project: --include-data-dir={MAIN_DIRECTORY}/data=data
# nuitka-project: --include-data-dir={MAIN_DIRECTORY}/resources=resources
import os
import sys
import json
@@ -21,6 +38,7 @@ from modules.download_wii import run_nus_download_wii, run_nus_download_wii_batc
from modules.download_dsi import run_nus_download_dsi, run_nus_download_dsi_batch
nusget_version = "1.2.0"
current_selected_version = ""
regions = {"World": ["41"], "USA/NTSC": ["45"], "Europe/PAL": ["50"], "Japan": ["4A"], "Korea": ["4B"], "China": ["43"],
"Australia/NZ": ["55"]}
@@ -112,7 +130,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
new_region.setText(0, region)
for title_version in title["Versions"][region]:
new_version = QTreeWidgetItem()
new_version.setText(0, "v" + str(title_version))
# Added public display versions (4.3U, 4.3J, etc)
# messy code can absolutely be cleaned up!!
strVersion = str(title_version)
public_versions = title.get("Public Versions", {})
if strVersion in public_versions:
public_version = " (" + public_versions[strVersion] + ")" # add ()
else:
public_version = ""
# changed to strVersion here
#current_selected_version = strVersion
new_version.setText(0, "v" + strVersion + public_version)
new_region.addChild(new_version)
new_title.addChild(new_region)
# Set an indicator icon to show if a ticket is offered for this title or not.
@@ -156,7 +184,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
selected_title = SelectedTitle(title["TID"], title["Name"], title["Archive Name"], item.text(0)[1:],
title["Ticket"], item.parent().text(0), category,
self.ui.console_select_dropdown.currentText(), danger_text)
self.load_title_data(selected_title)
self.load_title_data(selected_title, selected_title.version.split(" ", 1)[0])
def tid_updated(self):
tid = self.ui.tid_entry.text()
@@ -191,10 +219,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
if ret == QMessageBox.StandardButton.Yes:
webbrowser.open("https://github.com/NinjaCheetah/NUSGet/releases/latest")
def load_title_data(self, selected_title: SelectedTitle):
def load_title_data(self, selected_title: SelectedTitle, real_version):
# Use the information passed from the double click callback to prepare a title for downloading.
# If the last two characters are "XX", then this title has multiple regions, and each region uses its own
# two-digit code. Use the region info passed to load the correct code.
global current_selected_version
current_selected_version = real_version
if selected_title.tid[-2:] == "XX":
global regions
region_code = regions[selected_title.region][0]
@@ -203,14 +233,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
tid = selected_title.tid
# Load the TID and version into the entry boxes.
self.ui.tid_entry.setText(tid)
self.ui.version_entry.setText(selected_title.version)
self.ui.version_entry.setText(real_version)
# Load the WAD name, assuming it exists. This shouldn't ever be able to fail as the database has a WAD name
# for every single title, regardless of whether it can be packed or not.
try:
archive_name = f"{selected_title.archive_name}"
if selected_title.category != "System" and selected_title.category != "IOS":
archive_name += f"-{str(bytes.fromhex(tid).decode())[-4:]}"
archive_name += f"-v{selected_title.version}"
archive_name += f"-v{real_version}"
if selected_title.region != "World":
archive_name += f"-{selected_title.region.split('/')[0]}"
if self.ui.console_select_dropdown.currentText() == "DSi":
@@ -232,7 +262,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
danger_text = danger_text + ("Note: This Title does not have a Ticket available, so it cannot be decrypted"
" or packed into a WAD/TAD.")
# Print log info about the selected title and version.
self.log_text = f"{tid} - {selected_title.name}\nVersion: {selected_title.version}\n\n{danger_text}\n"
self.log_text = f"{tid} - {selected_title.name}\nVersion: {real_version}\n\n{danger_text}\n"
self.ui.log_text_browser.setText(self.log_text)
def lock_ui_for_download(self):
@@ -273,12 +303,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
# Create a new worker object to handle the download in a new thread.
if self.ui.console_select_dropdown.currentText() == "DSi":
worker = Worker(run_nus_download_dsi, out_folder, self.ui.tid_entry.text(),
self.ui.version_entry.text(), self.ui.pack_archive_chkbox.isChecked(),
current_selected_version, self.ui.pack_archive_chkbox.isChecked(),
self.ui.keep_enc_chkbox.isChecked(), self.ui.create_dec_chkbox.isChecked(),
self.ui.use_local_chkbox.isChecked(), self.ui.archive_file_entry.text())
else:
worker = Worker(run_nus_download_wii, out_folder, self.ui.tid_entry.text(),
self.ui.version_entry.text(), self.ui.pack_archive_chkbox.isChecked(),
# don't use self.ui.version_entry.text(), use current_selected_version
current_selected_version, self.ui.pack_archive_chkbox.isChecked(),
self.ui.keep_enc_chkbox.isChecked(), self.ui.create_dec_chkbox.isChecked(),
self.ui.use_wiiu_nus_chkbox.isChecked(), self.ui.use_local_chkbox.isChecked(),
self.ui.pack_vwii_mode_chkbox.isChecked(), self.ui.patch_ios_chkbox.isChecked(),

View File

@@ -9,6 +9,7 @@
"./resources/translations/nusget_fr.ts",
"./resources/translations/nusget_it.ts",
"./resources/translations/nusget_de.ts",
"./resources/translations/nusget_ro.ts"
"./resources/translations/nusget_ro.ts",
"./resources/translations/nusget_ko.ts"
]
}

View File

@@ -1,5 +1,5 @@
# NUSGet
A modern and supercharged NUS downloader built with Python and Qt6. Powered by libWiiPy and libTWLPy.
# NUSGet After Dark
A modern and supercharged NUS downloader built with Python and Qt6. Powered by libWiiPy and libTWLPy. Fork with features not acceptable for prod.
[![Python application](https://github.com/NinjaCheetah/NUSGet/actions/workflows/python-build.yml/badge.svg)](https://github.com/NinjaCheetah/NUSGet/actions/workflows/python-build.yml)
@@ -12,10 +12,11 @@ NUSGet also offers the ability to create vWii WADs that can be installed from wi
The following features are available for all supported consoles:
- Downloading encrypted contents (files like `00000000`, `00000001`, etc.) directly from the update servers for any title.
- Creating decrypted contents (*.app files) from the encrypted contents on the servers. Only supported for free titles.
- Creating decrypted contents (*.app files) from the encrypted contents on the servers.
**For Wii and vWii titles only:**
- "Pack installable archive (WAD/TAD)": Pack the encrypted contents, TMD, and Ticket into a WAD file that can be installed on a Wii or in Dolphin Emulator. Only supported for free titles.
- "Pack installable archive (WAD/TAD)": Pack the encrypted contents, TMD, and Ticket into a WAD file that can be installed on a Wii or in Dolphin Emulator.
- Forging Tickets for titles without a common Ticket available on the NUS by using the Title Key algorithm to derive the key needed to decrypt the title.
**For vWii titles only:**
- "Re-encrypt title using the Wii Common Key": Re-encrypt the Title Key in a vWii title's Ticket before packing the WAD, so that the WAD can be installed via a normal WAD manager on the vWii, and can be extracted with legacy tools. **This also creates WADs that can be installed directly in Dolphin, allowing for running the vWii System Menu in Dolphin without a vWii NAND dump!**
@@ -33,48 +34,43 @@ For basic usage on all platforms, you can download the latest release for your o
## Building
### System Requirements
- **Windows:** Python 3.11 (Requires Windows 8.1 or later)
- **Linux:** Python 3.11
- **macOS:** Python 3.11 (Requires macOS 10.9 or later, however macOS 11.0 or later may be required for library support)
- **Windows:** Python 3.12 (Requires Windows 8.1 or later)
- **Linux:** Python 3.12
- **macOS:** Python 3.12 (Requires macOS 10.9 or later, however macOS 11.0 or later may be required for library support)
**Python 3.12 may be supported now, however it is not tested at this time.**
First, install the required dependencies:
```
pip install -r requirements.txt
```
Then, use the command for your platform to build an executable with Nuitka:
**Windows**
First, make sure you're inside a venv, and then install the required dependencies:
```shell
python -m nuitka --show-progress --include-data-dir=data=data --include-data-dir=resources=resources --assume-yes-for-downloads --onefile --windows-icon-from-ico=resources/icon.png --plugin-enable=pyside6 NUSGet.py --windows-console-mode=disable
pip install --upgrade -r requirements.txt
```
After that, follow the directions for your platform.
**Linux**
### Linux and macOS
A Makefile is available to build NUSGet on Linux and macOS. **On Linux**, this will give you an executable named `NUSGet` in the root of the project directory. **On macOS**, you'll instead get an application bundle named `NUSGet.app`.
```shell
python -m nuitka --show-progress --include-data-dir=data=data --include-data-dir=resources=resources --assume-yes-for-downloads --onefile --plugin-enable=pyside6 NUSGet.py -o NUSGet
make all
```
**macOS**
Optionally, **on Linux**, you can install NUSGet so that it's available system-wide. This will install it into `/opt/NUSGet/`.
```shell
python -m nuitka --show-progress --include-data-dir=data=data --include-data-dir=resources=resources --assume-yes-for-downloads --onefile --plugin-enable=pyside6 NUSGet.py --macos-create-app-bundle --macos-app-icon=resources/icon.png
sudo make install
```
On macOS, you can instead put the application bundle in `/Applications/` just like any other program.
The result will be a single binary named `NUSGet` that contains everything required to run NUSGet. No dependencies are needed on the target system. On Windows and macOS, this will also embed the icon in the program.
### For Linux Users:
A Makefile has been included to both build and install NUSGet on Linux. This will automatically set up NUSGet under `/opt/` and install a .desktop file to `/usr/share/applications/` so you can use it like any other application.
First, use make to build NUSGet (this automates the step above):
### Windows
On Windows, you can use the PowerShell script `Build.ps1` in place of the Makefile. This will give you an executable named `NUSGet.exe` in the root of the project directory.
```shell
make linux
.\Build.ps1
```
Then, run the install command with `sudo` (or your favorite alternative):
```shell
sudo make linux-install
```
## Translations
A huge thanks to all the wonderful translators who've helped make NUSGet available to more people!
- **German:** [@yeah-its-gloria](https://github.com/yeah-its-gloria)
- **Italian:** [@LNLenost](https://github.com/LNLenost)
- **Korean:** [@DDinghoya](https://github.com/DDinghoya)
- **Norwegian:** [@Rolfie](https://github.com/rolfiee)
- **Romanian:** [@NotImplementedLife](https://github.com/NotImplementedLife)
If your language isn't present or is out of date, and you'd like to contribute, you can check out [TRANSLATING.md](https://github.com/NinjaCheetah/NUSGet/blob/main/TRANSLATING.md) for directions on how to translate NUSGet.
## Why this and not NUSD?

View File

@@ -39,6 +39,46 @@
"Japan": [128, 192, 224, 256, 288, 352, 384, 416, 448, 480, 512],
"Korea": [390, 454, 486, 518]
},
"Public Versions": {
"97": "2.0U",
"128": "2.0J",
"130": "2.0E",
"162": "2.1E",
"192": "2.2J",
"193": "2.2U",
"194": "2.2E",
"224": "3.0J",
"225": "3.0U",
"226": "3.0E",
"256": "3.1J",
"257": "3.1U",
"258": "3.1E",
"288": "3.2J",
"289": "3.2U",
"290": "3.2E",
"352": "3.3J",
"353": "3.3U",
"354": "3.3E",
"384": "3.4J",
"385": "3.4U",
"386": "3.4E",
"390": "3.5K",
"416": "4.0J",
"417": "4.0U",
"418": "4.0E",
"448": "4.1J",
"449": "4.1U",
"450": "4.1E",
"454": "4.1K",
"480": "4.2J",
"481": "4.2U",
"482": "4.2E",
"486": "4.2K",
"512": "4.3J",
"513": "4.3U",
"514": "4.3E",
"518": "4.3K"
},
"Ticket": true,
"Archive Name": "System-Menu",
"Danger": "The System Menu is a critical part of the Wii's operation, and should not be modified without proper brick prevention in place. You should have BootMii installed as boot2 if possible, and if not, Priiloader installed before making changes to the System Menu."

View File

@@ -4,13 +4,15 @@
import os
import pathlib
from typing import List, Tuple
from .tkey import find_tkey
import libWiiPy
from libWiiPy.title.ticket import _TitleLimit
def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_wad_chkbox: bool, keep_enc_chkbox: bool,
decrypt_contents_chkbox: bool, wiiu_nus_chkbox: bool, use_local_chkbox: bool,
repack_vwii_chkbox: bool, patch_ios: bool, wad_file_name: str, progress_callback=None):
#print(version)
# Actual NUS download function that runs in a separate thread.
# Immediately knock out any invalidly formatted Title IDs.
if len(tid) != 16:
@@ -57,6 +59,7 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
tmd_out.write(title.tmd.dump())
tmd_out.close()
# Use a local ticket, if one exists and "use local files" is enabled.
forge_ticket = False
if use_local_chkbox is True and os.path.exists(os.path.join(version_dir, "tik")):
progress_callback.emit(" - Parsing local copy of Ticket...")
local_ticket = open(os.path.join(version_dir, "tik"), "rb")
@@ -69,11 +72,10 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
ticket_out.write(title.ticket.dump())
ticket_out.close()
except ValueError:
# If libWiiPy returns an error, then no ticket is available. Log this, and disable options requiring a
# ticket so that they aren't attempted later.
progress_callback.emit(" - No Ticket is available!")
pack_wad_enabled = False
decrypt_contents_enabled = False
# If libWiiPy returns an error, then no ticket is available. Try to forge a ticket after we download the
# content.
progress_callback.emit(" - No Ticket is available! Will try forging a Ticket.")
forge_ticket = True
# Load the content records from the TMD, and begin iterating over the records.
title.load_content_records()
content_list = []
@@ -104,6 +106,39 @@ def run_nus_download_wii(out_folder: pathlib.Path, tid: str, version: str, pack_
enc_content_out.write(content_list[content])
enc_content_out.close()
title.content.content_list = content_list
# Try to forge a Ticket, if a common one wasn't available.
if forge_ticket is True:
progress_callback.emit(" - Attempting to forge Ticket...")
try:
title_key = find_tkey(tid, title.content.content_list[0], title.tmd.content_records[0])
title_key_enc = libWiiPy.title.encrypt_title_key(title_key, 0, tid)
ticket = libWiiPy.title.Ticket()
ticket.common_key_index = 0
ticket.console_id = 0
ticket.content_access_permissions = b'\xff' * 64
ticket.ecdh_data = b'\x00' * 60
ticket.permit_mask = b'\x00' * 4
ticket.permitted_titles = b'\x00' * 4
ticket.signature = b'\x00' * 256
ticket.signature_issuer = "Root-CA00000001-XS00000003" + ("\x00" * 38)
ticket.signature_type = b'\x00\x01' * 2
ticket.ticket_id = b'\x00' * 8
ticket.ticket_version = 0
ticket.title_export_allowed = 0
ticket.title_id = tid.encode()
ticket.title_key_enc = title_key_enc
ticket.title_limits_list = [_TitleLimit(0, 0) for _ in range(0, 8)]
ticket.title_version = 0
ticket.unknown1 = b'\xff' * 2
ticket.unknown2 = b'\x00' * 48
ticket.fakesign()
title.ticket = ticket
open(os.path.join(version_dir, "tik"), "wb").write(title.ticket.dump())
progress_callback.emit(" - Successfully forged Ticket!")
except Exception:
progress_callback.emit(" - Ticket could not be forged!")
pack_wad_enabled = False
decrypt_contents_enabled = False
# If decrypt local contents is still true, decrypt each content and write out the decrypted file.
if decrypt_contents_enabled is True:
try:

53
modules/tkey.py Normal file
View File

@@ -0,0 +1,53 @@
# "tkey-gen.py", licensed under the MIT license
# Copyright 2024 NinjaCheetah
import binascii
import hashlib
import libWiiPy
from libWiiPy.types import _ContentRecord
def _secret(start, length):
ret = b''
add = start + length
for _ in range(length):
unsigned_start = start & 0xFF # Compensates for how Python handles negative values vs PHP.
ret += bytes.fromhex(f"{unsigned_start:02x}"[-2:])
nxt = start + add
add = start
start = nxt
return ret
def _mungetid(tid):
# Remove leading zeroes from the TID.
while tid.startswith("00"):
tid = tid[2:]
if tid == "":
tid = "00"
# In PHP, the last character just gets dropped if you make a hex string from an odd-length input, so this
# replicates that functionality.
if len(tid) % 2 != 0:
tid = tid[:-1]
return bytes.fromhex(tid)
def _derive_key(tid, passwd):
key_secret = _secret(-3, 10)
salt = hashlib.md5(key_secret + _mungetid(tid)).digest()
# Had to reduce the length here from 32 to 16 when converting to get the same length keys.
return hashlib.pbkdf2_hmac("sha1", passwd.encode(), salt, 20, 16).hex()
def find_tkey(tid: str, banner_enc: bytes, content_record: _ContentRecord) -> bytes:
# Find a working Title Key by generating a key with a password, then decrypting content 0 and comparing it to the
# expected hash. If the hash matches, then we generated the correct key.
passwds = ["nintendo", "mypass"]
for passwd in passwds:
key = binascii.unhexlify(_derive_key(tid, passwd).encode())
banner_dec = libWiiPy.title.decrypt_content(banner_enc, key, content_record.index, content_record.content_size)
banner_dec_hash = hashlib.sha1(banner_dec).hexdigest()
content_record_hash = content_record.content_hash.decode()
if banner_dec_hash == content_record_hash:
return key
raise Exception("Valid Title Key could not be generated")

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 40 KiB

After

Width:  |  Height:  |  Size: 41 KiB

View File

@@ -0,0 +1,318 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE TS>
<TS version="2.1" language="ko_KR">
<context>
<name>MainWindow</name>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="26"/>
<source>MainWindow</source>
<translation></translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="43"/>
<source>Available Titles</source>
<translation> </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="72"/>
<source>Wii</source>
<translation>Wii</translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="103"/>
<source>vWii</source>
<translation>vWii</translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="134"/>
<source>DSi</source>
<translation>DSi</translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="174"/>
<source>Title ID</source>
<translation> ID</translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="181"/>
<source>v</source>
<translation>v</translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="194"/>
<source>Version</source>
<translation></translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="201"/>
<source>Console:</source>
<translation>:</translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="237"/>
<source>Start Download</source>
<translation> </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="250"/>
<source>Run Script</source>
<translation> </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="277"/>
<source>General Settings</source>
<translation> </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="308"/>
<source>Pack installable archive (WAD/TAD)</source>
<translation> (WAD/TAD) </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="323"/>
<source>File Name</source>
<translation> </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="357"/>
<source>Keep encrypted contents</source>
<translation> </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="393"/>
<source>Create decrypted contents (*.app)</source>
<translation> (*.app) </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="432"/>
<source>Use local files, if they exist</source>
<translation> </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="477"/>
<source>Use the Wii U NUS (faster, only effects Wii/vWii)</source>
<translation>Wii U NUS ( Wii/vWii에만 )</translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="575"/>
<source>vWii Title Settings</source>
<translation>vWii </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="609"/>
<source>Re-encrypt title using the Wii Common Key</source>
<translation>Wii </translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="666"/>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
hr { height: 1px; border-width: 0; }
li.unchecked::marker { content: &quot;\2610&quot;; }
li.checked::marker { content: &quot;\2612&quot;; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;Sans Serif&apos;; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
hr { height: 1px; border-width: 0; }
li.unchecked::marker { content: &quot;\2610&quot;; }
li.checked::marker { content: &quot;\2612&quot;; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;Sans Serif&apos;; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<source>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Noto Sans&apos;; font-size:10pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:&apos;Sans Serif&apos;; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</source>
<translation type="vanished">&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:&apos;Sans Serif&apos;; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="264"/>
<source>No Output Selected</source>
<translation> </translation>
</message>
<message>
<location filename="../../NUSGet.py" line="265"/>
<source>You have not selected any format to output the data in!</source>
<translation> !</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="267"/>
<source>Please select at least one option for how you would like the download to be saved.</source>
<translation> .</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="298"/>
<source>Invalid Title ID</source>
<translation> ID</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="299"/>
<source>The Title ID you have entered is not in a valid format!</source>
<translation> ID의 !</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="301"/>
<source>Title IDs must be 16 digit strings of numbers and letters. Please enter a correctly formatted Title ID, or select one from the menu on the left.</source>
<translation> ID는 16 . ID를 .</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="303"/>
<source>Title ID/Version Not Found</source>
<translation> ID/ </translation>
</message>
<message>
<location filename="../../NUSGet.py" line="304"/>
<source>No title with the provided Title ID or version could be found!</source>
<translation> ID !</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="306"/>
<source>Please make sure that you have entered a valid Title ID, or selected one from the title database, and that the provided version exists for the title you are attempting to download.</source>
<translation> ID를 , .</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="308"/>
<source>Content Decryption Failed</source>
<translation> </translation>
</message>
<message>
<location filename="../../NUSGet.py" line="309"/>
<source>Content decryption was not successful! Decrypted contents could not be created.</source>
<translation> ! .</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="312"/>
<source>Your TMD or Ticket may be damaged, or they may not correspond with the content being decrypted. If you have checked &quot;Use local files, if they exist&quot;, try disabling that option before trying the download again to fix potential issues with local data.</source>
<translation>TMD . &quot; &quot; , .</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="315"/>
<source>Ticket Not Available</source>
<translation> </translation>
</message>
<message>
<location filename="../../NUSGet.py" line="316"/>
<source>No Ticket is Available for the Requested Title!</source>
<translation> !</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="319"/>
<source>A ticket could not be downloaded for the requested title, but you have selected &quot;Pack installable archive&quot; or &quot;Create decrypted contents&quot;. These options are not available for titles without a ticket. Only encrypted contents have been saved.</source>
<translation> &quot; &quot; &quot; &quot; . . .</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="321"/>
<source>Unknown Error</source>
<translation> </translation>
</message>
<message>
<location filename="../../NUSGet.py" line="322"/>
<source>An Unknown Error has Occurred!</source>
<translation> !</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="324"/>
<source>Please try again. If this issue persists, please open a new issue on GitHub detailing what you were trying to do when this error occurred.</source>
<translation> . GitHub에서 .</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="364"/>
<source>Open NUS script</source>
<translation>NUS </translation>
</message>
<message>
<location filename="../../NUSGet.py" line="364"/>
<source>NUS Scripts (*.nus *.txt)</source>
<translation>NUS (*.nus *.txt)</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="371"/>
<location filename="../../NUSGet.py" line="386"/>
<location filename="../../NUSGet.py" line="389"/>
<location filename="../../NUSGet.py" line="397"/>
<location filename="../../NUSGet.py" line="415"/>
<location filename="../../NUSGet.py" line="422"/>
<source>Script Failure</source>
<translation> </translation>
</message>
<message>
<location filename="../../NUSGet.py" line="371"/>
<source>Failed to open the script.</source>
<translation> .</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="85"/>
<source>NUSGet v{nusget_version}
Developed by NinjaCheetah
Powered by libWiiPy {libwiipy_version}
DSi support provided by libTWLPy {libtwlpy_version}
Select a title from the list on the left, or enter a Title ID to begin.
Titles marked with a checkmark are free and have a ticket available, and can be decrypted and/or packed into a WAD or TAD. Titles with an X do not have a ticket, and only their encrypted contents can be saved.
Titles will be downloaded to a folder named &quot;NUSGet&quot; inside your downloads folder.</source>
<translation>NUSGet v{nusget_version}
개발자 : NinjaCheetah
libWiiPy {libwiipy_version}
DSi 지원 : libTWLPy {libtwlpy_version}
ID를 .
, WAD TAD에 / . X가 .
&quot;NUSBet&quot; .</translation>
</message>
<message>
<location filename="../../qt/ui/MainMenu.ui" line="519"/>
<source>Apply patches to IOS (Applies to WADs only)</source>
<translation>IOS에 (WAD에만 )</translation>
</message>
<message>
<location filename="../../NUSGet.py" line="184"/>
<source>NUSGet Update Available</source>
<translation>NUSGet </translation>
</message>
<message>
<location filename="../../NUSGet.py" line="185"/>
<source>There&apos;s a newer version of NUSGet available!</source>
<translation>NUSBet의 !</translation>
</message>
<message>
<location filename="../../modules/core.py" line="26"/>
<source>
Could not check for updates.</source>
<translation>
.</translation>
</message>
<message>
<location filename="../../modules/core.py" line="34"/>
<source>
There&apos;s a newer version of NUSGet available!</source>
<translation>
NUSBet의 !</translation>
</message>
<message>
<location filename="../../modules/core.py" line="36"/>
<source>
You&apos;re running the latest release of NUSGet.</source>
<translation>
NUSBet의 .</translation>
</message>
</context>
</TS>