forked from NinjaCheetah/NUSGet
Compare commits
6 Commits
v1.2.0
...
147e72c8c9
| Author | SHA1 | Date | |
|---|---|---|---|
|
147e72c8c9
|
|||
|
|
08c2bd27f5 | ||
|
dadcc02701
|
|||
|
c716291c2d
|
|||
|
|
7c35e2090d | ||
| 1ae712918e |
64
.github/workflows/python-build.yml
vendored
64
.github/workflows/python-build.yml
vendored
@@ -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
33
Build.ps1
Normal 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
|
||||
}
|
||||
}
|
||||
7
Makefile
7
Makefile
@@ -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
|
||||
|
||||
47
NUSGet.py
47
NUSGet.py
@@ -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(),
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
|
||||
62
README.md
62
README.md
@@ -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.
|
||||
|
||||
[](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?
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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
53
modules/tkey.py
Normal 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 |
318
resources/translations/nusget_ko.ts
Normal file
318
resources/translations/nusget_ko.ts
Normal 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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
hr { height: 1px; border-width: 0; }
|
||||
li.unchecked::marker { content: "\2610"; }
|
||||
li.checked::marker { content: "\2612"; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style="-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:'Sans Serif'; font-size:9pt;"><br /></p></body></html></source>
|
||||
<translation><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
hr { height: 1px; border-width: 0; }
|
||||
li.unchecked::marker { content: "\2610"; }
|
||||
li.checked::marker { content: "\2612"; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style="-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:'Sans Serif'; font-size:9pt;"><br /></p></body></html></translation>
|
||||
</message>
|
||||
<message>
|
||||
<source><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;">
|
||||
<p style="-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:'Sans Serif'; font-size:9pt;"><br /></p></body></html></source>
|
||||
<translation type="vanished"><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Sans Serif'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html></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 "Use local files, if they exist", try disabling that option before trying the download again to fix potential issues with local data.</source>
|
||||
<translation>TMD 또는 티켓이 손상되었거나 복호화되는 콘텐츠와 일치하지 않을 수 있습니다. "로컬 파일이 있으면 사용"을 체크한 경우, 로컬 데이터와 관련된 잠재적인 문제를 해결하기 위해 다시 다운로드를 시도하기 전에 해당 옵션을 비활성화해 보세요.</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 "Pack installable archive" or "Create decrypted contents". These options are not available for titles without a ticket. Only encrypted contents have been saved.</source>
|
||||
<translation>요청한 타이틀에 대한 티켓을 다운로드할 수 없지만 "설치 가능한 아카이브 팩" 또는 "암호 해독된 콘텐츠 생성"을 선택했습니다. 이러한 옵션은 티켓이 없는 타이틀에는 사용할 수 없습니다. 암호화된 콘텐츠만 저장되었습니다.</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 "NUSGet" inside your downloads folder.</source>
|
||||
<translation>NUSGet v{nusget_version}
|
||||
개발자 : NinjaCheetah
|
||||
libWiiPy {libwiipy_version}에서 제공
|
||||
DSi 지원 : libTWLPy {libtwlpy_version}에서 제공
|
||||
|
||||
왼쪽 목록에서 타이틀을 선택하거나 타이틀 ID를 입력하여 시작하세요.
|
||||
|
||||
체크 표시가 있는 타이틀은 무료이며 티켓을 사용할 수 있으며, WAD 또는 TAD에 복호화 및/또는 패킹할 수 있습니다. X가 있는 타이틀은 티켓이 없으며 암호화된 콘텐츠만 저장할 수 있습니다.
|
||||
|
||||
타이틀은 다운로드 폴더 내의 "NUSBet"이라는 폴더에 다운로드됩니다.</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'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's a newer version of NUSGet available!</source>
|
||||
<translation>
|
||||
|
||||
NUSBet의 새로운 버전이 나왔습니다!</translation>
|
||||
</message>
|
||||
<message>
|
||||
<location filename="../../modules/core.py" line="36"/>
|
||||
<source>
|
||||
|
||||
You're running the latest release of NUSGet.</source>
|
||||
<translation>
|
||||
|
||||
NUSBet의 최신 릴리스를 실행하고 있습니다.</translation>
|
||||
</message>
|
||||
</context>
|
||||
</TS>
|
||||
Reference in New Issue
Block a user