Compare commits
159 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f2d0e72f2 | ||
|
|
c9552f9785 | ||
|
|
444ff3e2d1 | ||
|
|
7d56c8a381 | ||
|
|
da051f8d8f | ||
|
|
81e6bc5833 | ||
|
|
ca5ea827b2 | ||
|
|
b700fff9ac | ||
|
|
81741364a7 | ||
|
|
faebc42f0d | ||
|
|
63e11ca377 | ||
|
|
54a2215e04 | ||
|
|
5edc3869cd | ||
|
|
a772d660f3 | ||
|
|
3c504cc85d | ||
|
|
0a2c16db0c | ||
|
|
2bd84c8d5a | ||
|
|
7cd668efb7 | ||
|
|
a6265c3089 | ||
|
|
a2300c1a96 | ||
|
|
3dae3f9173 | ||
|
|
63c420d5d8 | ||
|
|
a94c6bb581 | ||
|
|
9fe0044a65 | ||
|
|
c05ce5eff4 | ||
|
|
a019103ed5 | ||
|
|
50e55f4fca | ||
|
|
0706683690 | ||
|
|
9cdb77bafa | ||
|
|
b476c54825 | ||
|
|
8b2e541b1d | ||
|
|
931531e799 | ||
|
|
1695d69aa3 | ||
|
|
217bd3bed3 | ||
|
|
384e8794bf | ||
|
|
61b398a89a | ||
|
|
ba78fd0dc5 | ||
|
|
43969a773e | ||
|
|
6e1eabbe0f | ||
|
|
b99d1e5dea | ||
|
|
6ce566aea5 | ||
|
|
a4209961e2 | ||
|
|
181ff3f2bf | ||
|
|
b85b522643 | ||
|
|
5158e264c0 | ||
|
|
fd67da0527 | ||
|
|
7bdec8457f | ||
|
|
bc75c9a89f | ||
|
|
c2e8734e85 | ||
|
|
22e965521a | ||
|
|
b6b1af5959 | ||
|
|
876be3b7b6 | ||
|
|
62f48353ba | ||
|
|
9c65e5a12d | ||
|
|
235e947186 | ||
|
|
0a8bc01870 | ||
|
|
f0bdc01156 | ||
|
|
cd6fed6aae | ||
|
|
7835ebc346 | ||
|
|
3c33581a08 | ||
|
|
3e9a8c9249 | ||
|
|
d6c8f120c6 | ||
|
|
cb2fa1abfc | ||
|
|
25f2cfbff2 | ||
|
|
3404d4cece | ||
|
|
44e1584461 | ||
|
|
8a16188996 | ||
|
|
70518762ae | ||
|
|
a0370912da | ||
|
|
3fee702ee2 | ||
|
|
54d73a6d3b | ||
|
|
9fe9c9d491 | ||
|
|
4300c9ee1b | ||
|
|
a3780bdcea | ||
|
|
6554b68efa | ||
|
|
1a00db9d55 | ||
|
|
fb3ad260da | ||
|
|
ed02b0f260 | ||
|
|
620334439c | ||
|
|
ab5c54b47a | ||
|
|
40e4616520 | ||
|
|
3ebb3bd055 | ||
|
|
25e19b22f7 | ||
|
|
c67266fe1a | ||
|
|
92d747a0f5 | ||
|
|
79b52ed13e | ||
|
|
bd3ad8782a | ||
|
|
6b57619871 | ||
|
|
c8644c80cd | ||
|
|
430ee2280a | ||
|
|
f71c10619c | ||
|
|
b6497c03f6 | ||
|
|
ecb470b938 | ||
|
|
a9d3734117 | ||
|
|
faf77d8212 | ||
|
|
3e6d7af720 | ||
|
|
f69cf8130d | ||
|
|
796208722e | ||
|
|
85dbc54641 | ||
|
|
8381446a69 | ||
|
|
97dc39620c | ||
|
|
7fb973c28d | ||
|
|
4421ac1ceb | ||
|
|
159abfa246 | ||
|
|
4b06700187 | ||
|
|
ee68ca54b8 | ||
|
|
0bfd336796 | ||
|
|
9b967a9af0 | ||
|
|
299aaa5359 | ||
|
|
4e3927bbd0 | ||
|
|
38f19ec778 | ||
|
|
168f4b0303 | ||
|
|
6f8300fb32 | ||
|
|
2ff2923d39 | ||
|
|
aa724e12ba | ||
|
|
e039309a77 | ||
|
|
a4f0a97088 | ||
|
|
9d4c431eef | ||
|
|
1f7179e941 | ||
|
|
4d27bf5492 | ||
|
|
6b85d2cef1 | ||
|
|
aae9930f5e | ||
|
|
eca19aa4bf | ||
|
|
8e02538405 | ||
|
|
928da0cbda | ||
|
|
267693c6ab | ||
|
|
3f99afaa38 | ||
|
|
8e67e5f0fc | ||
|
|
cb1508e6d5 | ||
|
|
070be1ff94 | ||
|
|
7730eacea8 | ||
|
|
c5e3373fe1 | ||
|
|
d7ec620173 | ||
|
|
1c72350d4a | ||
|
|
4ef15f8b81 | ||
|
|
8fc7b614a0 | ||
|
|
0789a69975 | ||
|
|
b405a816c9 | ||
|
|
99c1db3655 | ||
|
|
6b099de63c | ||
|
|
275707fe27 | ||
|
|
c535b96b12 | ||
|
|
6b77cbb0c0 | ||
|
|
a33d8e1061 | ||
|
|
aaf11211dc | ||
|
|
83b2aca942 | ||
|
|
fbae286dff | ||
|
|
ba9b6b54bf | ||
|
|
1677514355 | ||
|
|
ec1042efa3 | ||
|
|
b03ad4ade3 | ||
|
|
04f6e5d2a8 | ||
|
|
16c074db1a | ||
|
|
8d958a2d1d | ||
|
|
74c1cd3be0 | ||
|
|
0fd5f348e2 | ||
|
|
0c9433d0d3 | ||
|
|
8fb34d42dc | ||
|
|
be831eb04a |
21
.github/workflows/build_presets.yml
vendored
21
.github/workflows/build_presets.yml
vendored
@@ -1,6 +1,12 @@
|
||||
name: build
|
||||
|
||||
on: [push, pull_request]
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- dev
|
||||
pull_request:
|
||||
branches-ignore:
|
||||
- dev
|
||||
|
||||
jobs:
|
||||
build:
|
||||
@@ -8,7 +14,7 @@ jobs:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest]
|
||||
preset: [MinSizeRel]
|
||||
preset: [Release]
|
||||
runs-on: ${{ matrix.os }}
|
||||
container: devkitpro/devkita64:latest
|
||||
|
||||
@@ -16,16 +22,17 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
# fetch latest cmake
|
||||
- uses: lukka/get-cmake@latest
|
||||
- uses: lukka/get-cmake@v3.31.6
|
||||
|
||||
- name: Configure CMake
|
||||
run: |
|
||||
cmake --preset ${{ matrix.preset }} -DUSE_VFS_GC=0
|
||||
run: cmake --preset ${{ matrix.preset }}
|
||||
|
||||
- name: Build
|
||||
run: cmake --build --preset ${{ matrix.preset }} --parallel 4
|
||||
run: cmake --build --preset ${{ matrix.preset }} --parallel $(nproc)
|
||||
|
||||
- uses: actions/upload-artifact@master
|
||||
- name: Deploy
|
||||
if: ${{ github.event_name != 'pull_request' && github.event.action != 'unassigned' }}
|
||||
uses: actions/upload-artifact@master
|
||||
with:
|
||||
name: sphaira-${{ matrix.preset }}
|
||||
path: build/${{ matrix.preset }}/sphaira.nro
|
||||
|
||||
33
.github/workflows/python-usb-export.yml
vendored
Normal file
33
.github/workflows/python-usb-export.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: USB Export Python Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: &python_usb_export_paths
|
||||
- 'tools/tests/test_usb_export.py'
|
||||
- 'tools/usb_export.py'
|
||||
- 'tools/usb_common.py'
|
||||
- 'tools/requirements.txt'
|
||||
- '.github/workflows/python-usb-export.yml'
|
||||
pull_request:
|
||||
paths: *python_usb_export_paths
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r tools/requirements.txt
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
python3 tools/tests/test_usb_export.py
|
||||
33
.github/workflows/python-usb-install.yml
vendored
Normal file
33
.github/workflows/python-usb-install.yml
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
name: USB Install Python Tests
|
||||
|
||||
on:
|
||||
push:
|
||||
paths: &python_usb_install_paths
|
||||
- 'tools/tests/test_usb_install.py'
|
||||
- 'tools/usb_install.py'
|
||||
- 'tools/usb_common.py'
|
||||
- 'tools/requirements.txt'
|
||||
- '.github/workflows/python-usb-install.yml'
|
||||
pull_request:
|
||||
paths: *python_usb_install_paths
|
||||
|
||||
jobs:
|
||||
test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Python 3.11
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.11'
|
||||
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -r tools/requirements.txt
|
||||
|
||||
- name: Run tests
|
||||
run: |
|
||||
python3 tools/tests/test_usb_install.py
|
||||
55
.github/workflows/webusb-build.yml
vendored
Normal file
55
.github/workflows/webusb-build.yml
vendored
Normal file
@@ -0,0 +1,55 @@
|
||||
name: Build and Deploy WebUSB Site
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- 'tools/webusb/**'
|
||||
|
||||
jobs:
|
||||
build-and-deploy:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout source
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 20
|
||||
|
||||
- name: Install minifiers
|
||||
run: |
|
||||
npm install -g html-minifier-terser terser csso-cli
|
||||
|
||||
- name: Minify HTML
|
||||
run: |
|
||||
html-minifier-terser --collapse-whitespace --remove-comments --minify-css true --minify-js true -o tools/webusb/index.html.min tools/webusb/index.html
|
||||
|
||||
- name: Minify JS
|
||||
run: |
|
||||
terser tools/webusb/index.js -c -m -o tools/webusb/index.js.min
|
||||
|
||||
- name: Minify CSS
|
||||
run: |
|
||||
csso tools/webusb/index.css --output tools/webusb/index.css.min
|
||||
|
||||
- name: Prepare deploy branch
|
||||
run: |
|
||||
rm -rf webusb
|
||||
mkdir webusb
|
||||
cp tools/webusb/index.html.min webusb/index.html
|
||||
cp tools/webusb/index.js.min webusb/index.js
|
||||
cp tools/webusb/index.css.min webusb/index.css
|
||||
cp -r tools/webusb/assets webusb/assets
|
||||
|
||||
- name: Commit and force-push to webusb branch
|
||||
run: |
|
||||
cd webusb
|
||||
git init
|
||||
git config user.name "github-actions[bot]"
|
||||
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||
git add .
|
||||
git commit -m "Deploy minified webusb build"
|
||||
git branch -M webusb
|
||||
git remote add origin "https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git"
|
||||
git push --force origin webusb
|
||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -25,3 +25,8 @@ compile_commands.json
|
||||
out
|
||||
|
||||
usb_test/
|
||||
__pycache__
|
||||
usb_*.spec
|
||||
|
||||
CMakeUserPresets.json
|
||||
build_patreon.sh
|
||||
|
||||
@@ -21,19 +21,21 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
|
||||
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
|
||||
|
||||
# enable LTO (only in release builds)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
|
||||
if (ipo_supported)
|
||||
message(STATUS "IPO / LTO enabled for ALL targets")
|
||||
cmake_policy(SET CMP0069 NEW)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
if (LTO)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Release" OR CMAKE_BUILD_TYPE STREQUAL "MinSizeRel")
|
||||
include(CheckIPOSupported)
|
||||
check_ipo_supported(RESULT ipo_supported OUTPUT ipo_error)
|
||||
if (ipo_supported)
|
||||
message(STATUS "IPO / LTO enabled for ALL targets")
|
||||
cmake_policy(SET CMP0069 NEW)
|
||||
set(CMAKE_POLICY_DEFAULT_CMP0069 NEW)
|
||||
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
|
||||
else()
|
||||
message(STATUS "IPO / LTO not supported: <${ipo_error}>")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "IPO / LTO not supported: <${ipo_error}>")
|
||||
message(STATUS "IPO / LTO not enabled in debug build")
|
||||
endif()
|
||||
else()
|
||||
message(STATUS "IPO / LTO not enabled in debug build")
|
||||
endif()
|
||||
|
||||
function(dkp_fatal_if_not_found var package)
|
||||
|
||||
@@ -16,25 +16,49 @@
|
||||
"name": "Release",
|
||||
"displayName": "Release",
|
||||
"inherits":["core"],
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE": "Release" }
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||
"LTO": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "RelWithDebInfo",
|
||||
"displayName": "RelWithDebInfo",
|
||||
"name": "Lite",
|
||||
"displayName": "Lite",
|
||||
"inherits":["core"],
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE":"RelWithDebInfo" }
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||
"LTO": true,
|
||||
|
||||
"ENABLE_NVJPG": false,
|
||||
"ENABLE_NSZ": false,
|
||||
|
||||
"ENABLE_LIBUSBHSFS": false,
|
||||
"ENABLE_LIBUSBDVD": false,
|
||||
"ENABLE_FTPSRV": false,
|
||||
"ENABLE_LIBHAZE": false,
|
||||
|
||||
"ENABLE_AUDIO_MP3": false,
|
||||
"ENABLE_AUDIO_OGG": false,
|
||||
"ENABLE_AUDIO_WAV": false,
|
||||
"ENABLE_AUDIO_FLAC": false,
|
||||
|
||||
"ENABLE_DEVOPTAB_HTTP": false,
|
||||
"ENABLE_DEVOPTAB_NFS": false,
|
||||
"ENABLE_DEVOPTAB_SMB2": false,
|
||||
"ENABLE_DEVOPTAB_FTP": false,
|
||||
"ENABLE_DEVOPTAB_SFTP": false,
|
||||
"ENABLE_DEVOPTAB_WEBDAV": false
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "MinSizeRel",
|
||||
"displayName": "MinSizeRel",
|
||||
"name": "Dev",
|
||||
"displayName": "Dev",
|
||||
"inherits":["core"],
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE":"MinSizeRel" }
|
||||
},
|
||||
{
|
||||
"name": "Debug",
|
||||
"displayName": "Debug",
|
||||
"inherits":["core"],
|
||||
"cacheVariables": { "CMAKE_BUILD_TYPE":"Debug" }
|
||||
"cacheVariables": {
|
||||
"CMAKE_BUILD_TYPE": "MinSizeRel",
|
||||
"LTO": false,
|
||||
"DEV_BUILD": true
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
@@ -44,18 +68,13 @@
|
||||
"jobs": 16
|
||||
},
|
||||
{
|
||||
"name": "RelWithDebInfo",
|
||||
"configurePreset": "RelWithDebInfo",
|
||||
"name": "Lite",
|
||||
"configurePreset": "Lite",
|
||||
"jobs": 16
|
||||
},
|
||||
{
|
||||
"name": "MinSizeRel",
|
||||
"configurePreset": "MinSizeRel",
|
||||
"jobs": 16
|
||||
},
|
||||
{
|
||||
"name": "Debug",
|
||||
"configurePreset": "Debug",
|
||||
"name": "Dev",
|
||||
"configurePreset": "Dev",
|
||||
"jobs": 16
|
||||
}
|
||||
]
|
||||
|
||||
@@ -98,6 +98,7 @@ The output will be found in `build/MinSizeRel/sphaira.nro`
|
||||
- [hb-appstore](https://github.com/fortheusers/hb-appstore)
|
||||
- [haze](https://github.com/Atmosphere-NX/Atmosphere/tree/master/troposphere/haze)
|
||||
- [nxdumptool](https://github.com/DarkMatterCore/nxdumptool) (for gamecard bin dumping and rsa verify code)
|
||||
- [Liam0](https://github.com/ThatNerdyPikachu/switch-010editor-templates) (for ticket / cert structs)
|
||||
- [libusbhsfs](https://github.com/DarkMatterCore/libusbhsfs)
|
||||
- [libnxtc](https://github.com/DarkMatterCore/libnxtc)
|
||||
- [oss-nvjpg](https://github.com/averne/oss-nvjpg)
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"url": "https://github.com/ITotalJustice/ftpsrv",
|
||||
"assets": [
|
||||
{
|
||||
"name": "switch"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,3 +0,0 @@
|
||||
{
|
||||
"url": "https://github.com/ITotalJustice/untitled"
|
||||
}
|
||||
@@ -1,24 +1,24 @@
|
||||
{
|
||||
"[Applet Mode]": " | Applet Modus |",
|
||||
"[Applet Mode]": "[Applet Modus]",
|
||||
"No Internet": "Kein Internet",
|
||||
"Switch-Handheld!": "Handheld!",
|
||||
"Switch-Docked!": "Angedockt!",
|
||||
"Warning! Logs are enabled, Sphaira will run slowly!": "",
|
||||
"Switch-Handheld!": "Handheld-Modus!",
|
||||
"Switch-Docked!": "TV-Modus!",
|
||||
"Warning! Logs are enabled, Sphaira will run slowly!": "Warnung! Protokollierung ist aktiviert, Sphaira läuft langsam!",
|
||||
"Audio disabled due to suspended game": "Audio deaktivert wegen Spielabbruch",
|
||||
"Are you sure you wish to cancel?": "Bist du sicher dass du abbrechen willst?",
|
||||
"An error occurred": "",
|
||||
"An error occurred": "Ein Fehler ist aufgetreten.",
|
||||
"If this message appears repeatedly, please open an issue.": "Bei wiederholtem Auftreten bitte Issue erstellen.",
|
||||
|
||||
"Menu Options": " Menü | Optionen",
|
||||
"Menu Options": "Menü | Optionen",
|
||||
"Menu": "Menü",
|
||||
"Theme": "Themes",
|
||||
"Theme Options": " Themes | Optionen",
|
||||
"Select Theme": "Theme wählen",
|
||||
"Theme": "Designs",
|
||||
"Theme Options": "Designs | Optionen",
|
||||
"Select Theme": "Design wählen",
|
||||
"Music": "Musik",
|
||||
"12 Hour Time": "12-Std Zeitformat",
|
||||
"Download Default Music": "",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
"Download Default Music": "Standardmusik herunterladen",
|
||||
"Failed to download default_music.bfstm, please try again": "Fehler beim Herunterladen von default_music.bfstm. Bitte versuchen Sie es erneut.",
|
||||
"Overwrite current default music?": "Aktuelle Standardmusik überschreiben?",
|
||||
|
||||
"Network": "Konnektivität",
|
||||
"Network Options": "Konnektivität | Optionen",
|
||||
@@ -28,8 +28,8 @@
|
||||
"Nxlink Connected": "NXLink | Verbunden",
|
||||
"Nxlink Upload": "NXLink | wird hochgeladen...",
|
||||
"Nxlink Finished": "NXLink | Hochladen beendet",
|
||||
"Hdd": "",
|
||||
"Hdd write protect": "",
|
||||
"Hdd": "HDD",
|
||||
"Hdd write protect": "HDD Schreibgeschützt",
|
||||
|
||||
"Language": "Sprache",
|
||||
"Auto": "Systemsprache",
|
||||
@@ -49,11 +49,11 @@
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Extras",
|
||||
"Misc Options": " Extras | Optionen",
|
||||
"Misc Options": "Extras | Optionen",
|
||||
"Games": "Spiele",
|
||||
"Game Options": "",
|
||||
"Hide forwarders": "",
|
||||
"Launch random game": "",
|
||||
"Game Options": "Spiel | Optionen",
|
||||
"Hide forwarders": "Forwarders ausblenden",
|
||||
"Launch random game": "Zufälliges Spiel starten",
|
||||
"List meta records": "",
|
||||
"Entries": "",
|
||||
"Failed to list application meta entries": "",
|
||||
@@ -102,11 +102,10 @@
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Lade JSON-File",
|
||||
"Select asset to download for ": "Wähle Asset für den Download von ",
|
||||
"Failed to download json": "",
|
||||
"Failed to download app!": "",
|
||||
"Failed to download json": "Download von JSON-Datei fehlgeschlagen",
|
||||
"Failed to download app!": "Download der App fehlgeschlagen",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
@@ -116,19 +115,19 @@
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"Failed to install, press B to exit...": "",
|
||||
"Install success!": "",
|
||||
"Install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Connected, waiting for file list...": "Verbunden, warte auf die Dateiliste...",
|
||||
"Connected, starting transfer...": "Verbunden, Übertragung startet...",
|
||||
"Failed to init usb, press B to exit...": "",
|
||||
"Waiting for connection...": "",
|
||||
"Transferring data...": "",
|
||||
"Waiting for connection...": "Warten auf Verbindung...",
|
||||
"Transferring data...": "Datenübertragung...",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"waiting for usb connection...": "Warten auf USB Verbindung...",
|
||||
"Disable MTP for usb install": "",
|
||||
"Re-enabled MTP": "",
|
||||
"Installed via usb": "",
|
||||
@@ -142,7 +141,7 @@
|
||||
"microSD card %.1f GB": "",
|
||||
"Exit": "",
|
||||
"Install disabled...\nPlease enable installing via the install options.": "",
|
||||
"No GameCard inserted": "",
|
||||
"No GameCard inserted": "Keine Spielkarte eingelegt",
|
||||
"GameCard is already trimmed!": "",
|
||||
"WARNING: GameCard is already trimmed!": "",
|
||||
"Continue": "",
|
||||
@@ -189,7 +188,7 @@
|
||||
"Enter custom URL": "",
|
||||
"Enter URL": "",
|
||||
|
||||
"Advanced": "Erweitert...",
|
||||
"Advanced": "Erweitert",
|
||||
"Advanced Options": " Erweitert | Optionen",
|
||||
"Logging": "Protokollieren",
|
||||
"Replace hbmenu on exit": "hbmenu durch sphaira ersetzen",
|
||||
@@ -244,7 +243,7 @@
|
||||
"Creating Control": "Erstelle Control",
|
||||
"Creating Meta": "Erstelle Meta",
|
||||
"Writing Nca": "Schreibe NCA",
|
||||
"Updating ncm databse": "Aktualisiere NCM-Datenbank",
|
||||
"Updating ncm database": "Aktualisiere NCM-Datenbank",
|
||||
"Pushing application record": "Übertrage Anwendungsdaten",
|
||||
"Failed to install forwarder": "Fehler beim installieren des Forwarders",
|
||||
"Unstar": "Kein Favorit",
|
||||
@@ -254,7 +253,7 @@
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
|
||||
"AppStore": "hb-AppStore",
|
||||
"Appstore": "",
|
||||
"Appstore": "hb-AppStore",
|
||||
"Store": "hb-Store",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Rubrik: %s | Sort.nach.: %s | Ordnung: %s",
|
||||
"AppStore Options": " hb-AppStore | Optionen",
|
||||
@@ -262,7 +261,7 @@
|
||||
"Changelog": "Neuerungen",
|
||||
"Details": "Details",
|
||||
"version: %s": "Version: %s",
|
||||
"updated: %s": "Letztes Update am: %s",
|
||||
"updated: %s": "Letztes Update: %s",
|
||||
"category: %s": "Rubrik: %s",
|
||||
"extracted: %.2f MiB": "Größe: %.2f MiB",
|
||||
"app_dls: %s": "Anzahl Downloads: %s",
|
||||
@@ -331,10 +330,10 @@
|
||||
"Launch ": "Starte ",
|
||||
"Launch option for: ": "Start Option für: ",
|
||||
"Select launcher for: ": "Wähle Launcher für: ",
|
||||
"Close FileBrowser?": "",
|
||||
"Close FileBrowser?": "Datei-Manager schließen?",
|
||||
|
||||
"Sort By": "Sortierung",
|
||||
"Sort Options": " Sortierung | Optionen",
|
||||
"Sort Options": "Sortierung | Optionen",
|
||||
"Filter": "Rubrik",
|
||||
"All": "Alles anzeigen",
|
||||
"Emulators": "Emulatoren",
|
||||
@@ -370,20 +369,20 @@
|
||||
"Back": "Zurück",
|
||||
"Select": "Auswählen",
|
||||
"Open": "Öffne",
|
||||
"Close": "",
|
||||
"Close": "Schließe",
|
||||
"Launch": "Starte",
|
||||
"Restart": "Neustart",
|
||||
"Next": "",
|
||||
"Prev": "",
|
||||
"Yes": "Ja",
|
||||
"No": "Nein",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
"On": "An",
|
||||
"Off": "Aus",
|
||||
|
||||
"Install": "Installieren",
|
||||
"Install Selected files?": "",
|
||||
"Installing ": "Installiert wird: ",
|
||||
"Installed ": "",
|
||||
"Installed ": "Installiert ",
|
||||
"Installed!": "Installiert!",
|
||||
"Trying to load ": "Versucht zu laden wird: ",
|
||||
"Checking MD5": "Checke MD5 Prüfsumme",
|
||||
@@ -411,13 +410,13 @@
|
||||
"Updated to ": "Aktualisiert auf: ",
|
||||
"Failed to download update": "Herunterladen des Updates fehlgeschlagen!",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
"%zu hours %zu minutes remaining": "%zu Stunden und %zu Minuten verbleibend",
|
||||
"%zu minutes %zu seconds remaining": "%zu Minuten und %zu Sekunde verbleibend",
|
||||
"%zu seconds remaining": "%zu Sekunde verbleibend",
|
||||
|
||||
"Loading...": "Wird geladen...",
|
||||
"Loading": "Wird geladen",
|
||||
"Empty!": "Keine Daten!",
|
||||
"Not Ready...": "Nicht bereit...",
|
||||
"Error loading page!": "Ladefehler!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
"Failed to download app!": "Failed to download app!",
|
||||
|
||||
"FTP Install": "FTP Install",
|
||||
"FTP Install (EXPERIMENTAL)": "FTP Install (EXPERIMENTAL)",
|
||||
"Connection Type: WiFi | Strength: ": "Connection Type: WiFi | Strength: ",
|
||||
"Connection Type: Ethernet": "Connection Type: Ethernet",
|
||||
"Connection Type: None": "Connection Type: None",
|
||||
@@ -116,9 +115,9 @@
|
||||
"Password:": "Password:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "Passphrase",
|
||||
"Failed to install via FTP, press B to exit...": "Failed to install via FTP, press to exit...",
|
||||
"Ftp install success!": "Ftp install success!",
|
||||
"Ftp install failed!": "Ftp install failed!",
|
||||
"Failed to install, press B to exit...": "Failed to install, press to exit...",
|
||||
"Install success!": "Install success!",
|
||||
"Install failed!": "Install failed!",
|
||||
"USB Install": "USB Install",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "Connected, waiting for file list...",
|
||||
@@ -244,7 +243,7 @@
|
||||
"Creating Control": "Creating Control",
|
||||
"Creating Meta": "Creating Meta",
|
||||
"Writing Nca": "Writing Nca",
|
||||
"Updating ncm databse": "Updating ncm databse",
|
||||
"Updating ncm database": "Updating ncm database",
|
||||
"Pushing application record": "Pushing application record",
|
||||
"Failed to install forwarder": "Failed to install forwarder",
|
||||
"Unstar": "Unstar",
|
||||
@@ -420,4 +419,4 @@
|
||||
"Empty!": "Empty!",
|
||||
"Not Ready...": "Not Ready...",
|
||||
"Error loading page!": "Error loading page!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
"No Internet": "Sin Internet",
|
||||
"Switch-Handheld!": "¡Switch-Modo-Portátil!",
|
||||
"Switch-Docked!": "¡Switch-Modo-TV!",
|
||||
"Warning! Logs are enabled, Sphaira will run slowly!": "",
|
||||
"Audio disabled due to suspended game": "",
|
||||
"Are you sure you wish to cancel?": "¿Estás seguro que deseas cancelar?",
|
||||
"An error occurred": "",
|
||||
"If this message appears repeatedly, please open an issue.": "Si este mensaje aparece repetidamente, por favor abrir un 'issue'.",
|
||||
"Warning! Logs are enabled, Sphaira will run slowly!": "ADVERTENCIA: ¡el registro de eventos está activado, Sphaira correrá más lento!",
|
||||
"Audio disabled due to suspended game": "Audio deshabilitado debido al juego suspendido",
|
||||
"Are you sure you wish to cancel?": "¿Realmente deseas cancelar?",
|
||||
"An error occurred": "Ha ocurrido un error",
|
||||
"If this message appears repeatedly, please open an issue.": "Si este mensaje aparece repetidamente, por favor abra un 'issue'.",
|
||||
|
||||
"Menu Options": "Opciones de menú",
|
||||
"Menu": "Menú",
|
||||
@@ -15,10 +15,10 @@
|
||||
"Theme Options": "Opciones de tema",
|
||||
"Select Theme": "Seleccionar tema",
|
||||
"Music": "Música",
|
||||
"12 Hour Time": "",
|
||||
"Download Default Music": "",
|
||||
"Failed to download default_music.bfstm, please try again": "",
|
||||
"Overwrite current default music?": "",
|
||||
"12 Hour Time": "Reloj de 12 horas",
|
||||
"Download Default Music": "Descargar música por defecto",
|
||||
"Failed to download default_music.bfstm, please try again": "Falló la descarga de default_music.bfstm. Inténtelo de nuevo.",
|
||||
"Overwrite current default music?": "¿Sobreescribir la música por defecto actual?",
|
||||
|
||||
"Network": "Red",
|
||||
"Network Options": "Opciones de red",
|
||||
@@ -28,8 +28,8 @@
|
||||
"Nxlink Connected": "NXlink conectado",
|
||||
"Nxlink Upload": "NXlink subida",
|
||||
"Nxlink Finished": "NXlink finalizado",
|
||||
"Hdd": "",
|
||||
"Hdd write protect": "",
|
||||
"Hdd": "Disco Duro",
|
||||
"Hdd write protect": "Disco duro de solo lectura",
|
||||
|
||||
"Language": "Idioma",
|
||||
"Auto": "Automático",
|
||||
@@ -48,122 +48,121 @@
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Varios",
|
||||
"Misc Options": "Opciones varias",
|
||||
"Misc": "Misceláneas",
|
||||
"Misc Options": "Opciones misceláneas",
|
||||
"Games": "Juegos",
|
||||
"Game Options": "",
|
||||
"Hide forwarders": "",
|
||||
"Launch random game": "",
|
||||
"List meta records": "",
|
||||
"Entries": "",
|
||||
"Failed to list application meta entries": "",
|
||||
"No meta entries found...\n": "",
|
||||
"Updating application record list": "",
|
||||
"Dump": "",
|
||||
"Dump options": "",
|
||||
"Dump Options": "",
|
||||
"Select content to dump": "",
|
||||
"Dump All": "",
|
||||
"Dump Application": "",
|
||||
"Dump Patch": "",
|
||||
"Dump AddOnContent": "",
|
||||
"Dump DataPatch": "",
|
||||
"Created nested folder": "",
|
||||
"Append folder with .xci": "",
|
||||
"Trim XCI": "",
|
||||
"Label trimmed XCI": "",
|
||||
"Multi-threaded USB transfer": "",
|
||||
"Dump All Bins": "",
|
||||
"Dump XCI": "",
|
||||
"Dump Card ID Set": "",
|
||||
"Dump Card UID": "",
|
||||
"Dump Certificate": "",
|
||||
"Dump Initial Data": "",
|
||||
"Select dump location": "",
|
||||
"microSD card (/dumps/)": "",
|
||||
"USB transfer (Switch 2 Switch)": "",
|
||||
"/dev/null (Speed Test)": "",
|
||||
"Dumping": "",
|
||||
"Dump successfull!": "",
|
||||
"Dump failed!": "",
|
||||
"Delete successfull!": "",
|
||||
"Delete failed!": "",
|
||||
"Success": "",
|
||||
"Game Options": "Opciones de juegos",
|
||||
"Hide forwarders": "Ocultar forwarders",
|
||||
"Launch random game": "Ejecutar un juego aleatorio",
|
||||
"List meta records": "Listar registros de metadatos",
|
||||
"Entries": "Entradas",
|
||||
"Failed to list application meta entries": "Error al listar las entradas de metadatos de las aplicaciones",
|
||||
"No meta entries found...\n": "No se encontraron entradas de metadatos…\n",
|
||||
"Updating application record list": "Actualizando la lista de registros de aplicaciones",
|
||||
"Dump": "Volcar",
|
||||
"Dump options": "Opciones de volcado",
|
||||
"Dump Options": "Opciones de volcado",
|
||||
"Select content to dump": "Seleccione el contenido a volcar",
|
||||
"Dump All": "Volcar todo",
|
||||
"Dump Application": "Volcar aplicación",
|
||||
"Dump Patch": "Volcar parche",
|
||||
"Dump AddOnContent": "Volcar contenido adicional",
|
||||
"Dump DataPatch": "Volcar datos de parche",
|
||||
"Created nested folder": "Se creó una carpeta anidadada",
|
||||
"Append folder with .xci": "Adjuntar carpeta con archivo .XCI",
|
||||
"Trim XCI": "Archivo XCI recortado",
|
||||
"Label trimmed XCI": "Archivo XCI con etiqueta removida",
|
||||
"Multi-threaded USB transfer": "Transferencia USB multi-hilos",
|
||||
"Dump All Bins": "Volcar todos los binarios",
|
||||
"Dump XCI": "Volcar archivo XCI",
|
||||
"Dump Card ID Set": "Volcar conjunto de ID de tarjeta",
|
||||
"Dump Card UID": "Volcar UID de tarjeta",
|
||||
"Dump Certificate": "Volcar Certificado",
|
||||
"Dump Initial Data": "Volcar datos iniciales",
|
||||
"Select dump location": "Escoja la ubicación para el volcado",
|
||||
"microSD card (/dumps/)": "Tarjeta microSD (/dumps/)",
|
||||
"USB transfer (Switch 2 Switch)": "Transferencia USB (de Switch a Switch)",
|
||||
"/dev/null (Speed Test)": "Medir la velocidad usando dispositivo /dev/null",
|
||||
"Dumping": "Volcando",
|
||||
"Dump successfull!": "¡Volcado exitoso!",
|
||||
"Dump failed!": "¡Error en el volcado!",
|
||||
"Delete successfull!": "¡Borrado exitoso!",
|
||||
"Delete failed!": "¡Error al borrar!",
|
||||
"Success": "Proceso exitoso",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Opciones de Themezer",
|
||||
"Nsfw": "NSFW",
|
||||
"Page": "Página",
|
||||
"Page %zu / %zu": "Pág. %zu / %zu",
|
||||
"Enter Page Number": "Ingresar Número de Página",
|
||||
"Bad Page": "Página Errónea",
|
||||
"Download theme?": "¿Descargar Tema?",
|
||||
"Enter Page Number": "Ingrese número de página",
|
||||
"Bad Page": "Página errónea",
|
||||
"Download theme?": "¿Descargar tema?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Descargando json",
|
||||
"Downloading json": "Descargando JSON",
|
||||
"Select asset to download for ": "Seleccionar recurso a descargar para ",
|
||||
"Failed to download json": "",
|
||||
"Failed to download app!": "",
|
||||
"Failed to download json": "Error al descargar JSON",
|
||||
"Failed to download app!": "¡Error al descargar aplicación!",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
"Host:": "",
|
||||
"Port:": "",
|
||||
"Username:": "",
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
"Connected, starting transfer...": "",
|
||||
"Failed to init usb, press B to exit...": "",
|
||||
"Waiting for connection...": "",
|
||||
"Transferring data...": "",
|
||||
"USB connected, sending file list": "",
|
||||
"Sent file list, waiting for command...": "",
|
||||
"waiting for usb connection...": "",
|
||||
"Disable MTP for usb install": "",
|
||||
"Re-enabled MTP": "",
|
||||
"Installed via usb": "",
|
||||
"Usb install success!": "",
|
||||
"Usb install failed!": "",
|
||||
"Press B to exit...": "",
|
||||
"GameCard Install": "",
|
||||
"GameCard": "",
|
||||
"GC": "",
|
||||
"System memory %.1f GB": "",
|
||||
"microSD card %.1f GB": "",
|
||||
"Exit": "",
|
||||
"Install disabled...\nPlease enable installing via the install options.": "",
|
||||
"No GameCard inserted": "",
|
||||
"GameCard is already trimmed!": "",
|
||||
"WARNING: GameCard is already trimmed!": "",
|
||||
"Continue": "",
|
||||
"Gc install success!": "",
|
||||
"Gc install failed!": "",
|
||||
"FTP Install": "Instalación vía FTP",
|
||||
"Connection Type: WiFi | Strength: ": "Tipo de conexión: WiFi | Fuerza de la señal",
|
||||
"Connection Type: Ethernet": "Tipo de conexión: Ethernet",
|
||||
"Connection Type: None": "Tipo de conexión: ninguna",
|
||||
"Host:": "Host",
|
||||
"Port:": "Puerto",
|
||||
"Username:": "Nombre de usuario",
|
||||
"Password:": "Contraseña",
|
||||
"SSID:": "SSID",
|
||||
"Passphrase:": "Parafrase",
|
||||
"Failed to install, press B to exit...": "Error al instalar vía FTP. Presione B para salir…",
|
||||
"Install success!": "¡Instalación vía FTP satisfactoria!",
|
||||
"Install failed!": "¡Error en la instalación vía FTP!",
|
||||
"USB Install": "Instalación vía USB",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "Conectado, esperando lista de archivos…",
|
||||
"Connected, starting transfer...": "Conectado, iniciando la transferencia…",
|
||||
"Failed to init usb, press B to exit...": "Error al iniciar el USB. Presione B para salir…",
|
||||
"Waiting for connection...": "Esperando conexión…",
|
||||
"Transferring data...": "Tranfiriendo datos…",
|
||||
"USB connected, sending file list": "USB conectado, enviando listado de archivos",
|
||||
"Sent file list, waiting for command...": "Lista de archivos enviada, esperando comando…",
|
||||
"waiting for usb connection...": "Esperando por conexión USB…",
|
||||
"Disable MTP for usb install": "Instalación vía MTP deshabilitada",
|
||||
"Re-enabled MTP": "Instalación vía MTP rehabilitada",
|
||||
"Installed via usb": "Instalado vía USB",
|
||||
"Usb install success!": "Instalación USB satisfactoria",
|
||||
"Usb install failed!": "Error en instalación USB",
|
||||
"Press B to exit...": "Presione B para salir…",
|
||||
"GameCard Install": "Instalación de tarjeta de juego",
|
||||
"GameCard": "Tarjeta de juego",
|
||||
"GC": "TJ",
|
||||
"System memory %.1f GB": "Memoria del sistema %.1f GB",
|
||||
"microSD card %.1f GB": "Tarjeta microSD %.1f GB",
|
||||
"Exit": "Salir",
|
||||
"Install disabled...\nPlease enable installing via the install options.": "Instalación deshabilitada…\nPor favor habilite instalar aplicaciones en las opciones de instalación",
|
||||
"No GameCard inserted": "No hay una tarjeta de juego insertada",
|
||||
"GameCard is already trimmed!": "¡La tarjeta de juego ya se encuentra recortada!",
|
||||
"WARNING: GameCard is already trimmed!": "ADVERTENCIA: ¡la tarjeta de juego ya se encuentra recortada!",
|
||||
"Continue": "Continuar",
|
||||
"Gc install success!": "Instalación de TJ satisfactoria",
|
||||
"Gc install failed!": "Fallo al instalar TJ",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "",
|
||||
"IRS": "",
|
||||
"Irs": "IRS",
|
||||
"Ambient Noise Level: ": "Nivel de Ruido Ambiente",
|
||||
"IRS (Infrared Joycon Camera)": "IRS (Cámara infraroja del JoyCon)",
|
||||
"IRS": "IRS",
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "Nivel de ruido ambiente",
|
||||
"Controller": "Control",
|
||||
"Pad ": "GamePad ",
|
||||
"Pad ": "Pad ",
|
||||
"HandHeld": "Portátil",
|
||||
" (Available)": " (Disponible)",
|
||||
" (Unsupported)": "(No Compatible)",
|
||||
" (Unsupported)": "(No compatible)",
|
||||
" (Unconnected)": " (Desconectado)",
|
||||
"Rotation": "Rotación",
|
||||
"0 (Sideways)": "0° (De lado)",
|
||||
"90 (Flat)": "90° (Plano)",
|
||||
"180 (-Sideways)": "180° (De lado)",
|
||||
"270 (Upside down)": "270° (Al revés)",
|
||||
"0 (Sideways)": "0° (de lado)",
|
||||
"90 (Flat)": "90° (plano)",
|
||||
"180 (-Sideways)": "180° (-de lado)",
|
||||
"270 (Upside down)": "270° (al revés)",
|
||||
"Colour": "Color",
|
||||
"Grey": "Gris",
|
||||
"Ironbow": "Paleta térmica",
|
||||
@@ -184,10 +183,10 @@
|
||||
"External Light Filter": "Filtro de luz externa",
|
||||
"Load Default": "Cargar predeterminado",
|
||||
|
||||
"Web": "",
|
||||
"Select URL": "",
|
||||
"Enter custom URL": "",
|
||||
"Enter URL": "",
|
||||
"Web": "Web",
|
||||
"Select URL": "Seleccione la URL",
|
||||
"Enter custom URL": "Ingrese una URL personalizada",
|
||||
"Enter URL": "Escriba la URL",
|
||||
|
||||
"Advanced": "Avanzado",
|
||||
"Advanced Options": "Opciones avanzadas",
|
||||
@@ -195,79 +194,79 @@
|
||||
"Replace hbmenu on exit": "Reemplazar hbmenu",
|
||||
"Restore hbmenu?": "¿Restaurar hbmenu?",
|
||||
"Restore": "Restaurar",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Fallo al encontrar /switch/hbmenu.nro\nUsar la Tienda para reinstalar hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Fallo al restaurar hbmenu, por favor volver a descargar hbmenu",
|
||||
"Failed to find /switch/hbmenu.nro\nUse the Appstore to re-install hbmenu": "Fallo al encontrar /switch/hbmenu.nro\nUse la tienda para reinstalar hbmenu",
|
||||
"Failed to restore hbmenu, please re-download hbmenu": "Fallo al restaurar hbmenu. Por favor vuelva a descargar hbmenu",
|
||||
"Failed to restore hbmenu, using sphaira instead": "Fallo al restaurar hbmenu, se usará sphaira",
|
||||
"Restored hbmenu, closing sphaira": "hbmenu restaurado, cerrando sphaira",
|
||||
"Restored hbmenu": "hbmenu restaurado",
|
||||
"Restart Sphaira?": "¿Reiniciar sphaira?",
|
||||
"Press OK to restart Sphaira": "Presiona OK para reiniciar sphaira",
|
||||
"Boost CPU during transfer": "",
|
||||
"Boost CPU during transfer": "Tranferir con overclock",
|
||||
"Text scroll speed": "Velocidad de scroll",
|
||||
"Slow": "Lento",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rápido",
|
||||
"Set left-side menu": "",
|
||||
"Set right-side menu": "",
|
||||
"Install options": "",
|
||||
"Install Options": "",
|
||||
"Enable sysmmc": "",
|
||||
"Enable emummc": "",
|
||||
"Show install warning": "Precaución de instalación",
|
||||
"Set left-side menu": "Menú izquierdo",
|
||||
"Set right-side menu": "Menú derecho",
|
||||
"Install options": "Opciones de instalación",
|
||||
"Install Options": "Opciones de instalación",
|
||||
"Enable sysmmc": "Habilitar SysMMC",
|
||||
"Enable emummc": "Habilitar EmuMMC",
|
||||
"Show install warning": "Mostrar precaución al instalar",
|
||||
"Install location": "Dispositivo de instalación",
|
||||
"System memory": "Memoria de sistema",
|
||||
"microSD card": "microSD",
|
||||
"Allow downgrade": "",
|
||||
"Skip if already installed": "",
|
||||
"Ticket only": "",
|
||||
"Skip base": "",
|
||||
"Skip patch": "",
|
||||
"Skip dlc": "",
|
||||
"Skip data patch": "",
|
||||
"Skip ticket": "",
|
||||
"Skip NCA hash verify": "",
|
||||
"Skip RSA header verify": "",
|
||||
"Skip RSA NPDM verify": "",
|
||||
"Ignore distribution bit": "",
|
||||
"Convert to standard crypto": "",
|
||||
"Lower master key": "",
|
||||
"Lower system version": "",
|
||||
"Allow downgrade": "Permitir instalar versiones anteriores",
|
||||
"Skip if already installed": "Saltar si ya está instalado",
|
||||
"Ticket only": "Únicamente tickets",
|
||||
"Skip base": "Saltar base",
|
||||
"Skip patch": "Saltar parche",
|
||||
"Skip dlc": "Saltar DLC",
|
||||
"Skip data patch": "Saltar datos de parche",
|
||||
"Skip ticket": "Saltar ticket",
|
||||
"Skip NCA hash verify": "Saltar verificación de hash NCA",
|
||||
"Skip RSA header verify": "Saltar verificación de encabezado RSA",
|
||||
"Skip RSA NPDM verify": "Saltar verificación de NPDM RSA",
|
||||
"Ignore distribution bit": "Ignorar bit de distribución",
|
||||
"Convert to standard crypto": "Convertir a crypto estándar",
|
||||
"Lower master key": "Bajar versión de llave maestra",
|
||||
"Lower system version": "Bajar versión de sistema",
|
||||
|
||||
"Homebrew": "Homebrew",
|
||||
"Apps": "Apps",
|
||||
"Homebrew Options": "Opciones de Homebrew",
|
||||
"Homebrew Options": "Opciones de homebrew",
|
||||
"Hide Sphaira": "Ocultar Sphaira",
|
||||
"Install Forwarder": "Instalar Forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ADVERTENCIA: ¡La instalación de fordwarders podría producir un baneo de la consola!",
|
||||
"Installing Forwarder": "Instalando Forwarder",
|
||||
"Creating Program": "Creando Program",
|
||||
"Creating Control": "Creando Control",
|
||||
"Creating Meta": "Creando Meta",
|
||||
"Install Forwarder": "Instalar forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ADVERTENCIA: ¡la instalación de fordwarders podría terminar en un baneo!",
|
||||
"Installing Forwarder": "Instalando forwarder",
|
||||
"Creating Program": "Creando program",
|
||||
"Creating Control": "Creando control",
|
||||
"Creating Meta": "Creando meta",
|
||||
"Writing Nca": "Creando NCA",
|
||||
"Updating ncm databse": "Actualizando base de datos ncm",
|
||||
"Updating ncm database": "Actualizando base de datos ncm",
|
||||
"Pushing application record": "Registro de aplicación",
|
||||
"Failed to install forwarder": "Fallo al instalar forwarder",
|
||||
"Unstar": "Quitar favorito",
|
||||
"Star": "Favorito",
|
||||
"Unstarred ": "Quitar Favorito",
|
||||
"Unstar": "Quitar de favoritos",
|
||||
"Star": "Añadir a favoritos",
|
||||
"Unstarred ": "No favorito",
|
||||
"Starred ": "Favorito",
|
||||
"Failed to remove old forwarder, please manually remove it!": "",
|
||||
"Failed to remove old forwarder, please manually remove it!": "Error al remover forwarder anterior, por favor remuévalo manualmente",
|
||||
|
||||
"AppStore": "Tienda",
|
||||
"Appstore": "",
|
||||
"Appstore": "Tienda",
|
||||
"Store": "Tienda",
|
||||
"Filter: %s | Sort: %s | Order: %s": "Filtrar: %s | Clasificar: %s | Orden: %s",
|
||||
"AppStore Options": "Opciones de la Tienda",
|
||||
"AppStore Options": "Opciones de la tienda",
|
||||
"Info": "Información",
|
||||
"Changelog": "Log de cambios",
|
||||
"Changelog": "Registro de cambios",
|
||||
"Details": "Detalles",
|
||||
"version: %s": "version: %s",
|
||||
"updated: %s": "actualizado: %s",
|
||||
"category: %s": "categoría: %s",
|
||||
"extracted: %.2f MiB": "extraído: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "Mostrar mas del Autor",
|
||||
"Leave Feedback": "Dejar Mensaje",
|
||||
"More by Author": "Mostrar mas del autor",
|
||||
"Leave Feedback": "Dejar mensaje",
|
||||
|
||||
"FileBrowser": "Explorador de archivos",
|
||||
"Files": "Archivos",
|
||||
@@ -281,75 +280,75 @@
|
||||
"Copy": "Copiar",
|
||||
"Copying ": "Copiando ",
|
||||
"Paste": "Pegar",
|
||||
"Paste file(s)?": "",
|
||||
"Paste file(s)?": "Pegar archivo(s)",
|
||||
"Pasting ": "Pegando ",
|
||||
"Pasting": "Pegando",
|
||||
"Rename": "Renombrar",
|
||||
"Set New File Name": "Establecer nuevo nombre de archivo",
|
||||
"Failed to delete directory": "",
|
||||
"Failed to delete file": "",
|
||||
"Extract zip": "",
|
||||
"Extract Options": "",
|
||||
"Extract here": "",
|
||||
"Extract to root": "",
|
||||
"Are you sure you want to extract to root?": "",
|
||||
"Extract to...": "",
|
||||
"Enter the path to the folder to extract into": "",
|
||||
"Extracting ": "",
|
||||
"Extract success!": "",
|
||||
"Extract failed!": "",
|
||||
"Compress to zip": "",
|
||||
"Compress Options": "",
|
||||
"Compress": "",
|
||||
"Compress to...": "",
|
||||
"Compressing ": "",
|
||||
"Compress success!": "",
|
||||
"Compress failed!": "",
|
||||
"Failed to delete directory": "Error al borrar carpeta",
|
||||
"Failed to delete file": "Error al borrar archivo",
|
||||
"Extract zip": "Extraer carpeta comprimida ZIP",
|
||||
"Extract Options": "Opciones de extracción",
|
||||
"Extract here": "Extraer aquí",
|
||||
"Extract to root": "Extraer en la raíz",
|
||||
"Are you sure you want to extract to root?": "¿Realmente desea extraerlo en la carpeta raíz?",
|
||||
"Extract to...": "Extraer en…",
|
||||
"Enter the path to the folder to extract into": "Ingrese la ruta en la cual extraer",
|
||||
"Extracting ": "Extrayendo",
|
||||
"Extract success!": "¡Extracción satisfactoria!",
|
||||
"Extract failed!": "¡Extracción fallida!",
|
||||
"Compress to zip": "Comprimir en archivo ZIP",
|
||||
"Compress Options": "Opciones de compresión",
|
||||
"Compress": "Comprimir",
|
||||
"Compress to...": "Comprimir en…",
|
||||
"Compressing ": "Comprimiendo",
|
||||
"Compress success!": "¡Compresión satisfactoria!",
|
||||
"Compress failed!": "¡Error en la descompresión!",
|
||||
"Create File": "Crear archivo",
|
||||
"Set File Name": "Establecer nombre de archivo",
|
||||
"Create Folder": "Crear carpeta",
|
||||
"Set Folder Name": "Establecer nombre de carpeta",
|
||||
"Creating ": "Creando ",
|
||||
"View as text (unfinished)": "Ver como texto (sin terminar)",
|
||||
"Upload": "",
|
||||
"Select upload location": "",
|
||||
"No upload locations set!": "",
|
||||
"Uploading": "",
|
||||
"Upload successfull!": "",
|
||||
"Upload failed!": "",
|
||||
"Hash": "",
|
||||
"Hash Options": "",
|
||||
"Hashing": "",
|
||||
"Failed to hash file...": "",
|
||||
"Upload": "Subir",
|
||||
"Select upload location": "Seleccione la ubicación a la cual subir",
|
||||
"No upload locations set!": "No se ha definido la ubicación para subir",
|
||||
"Uploading": "Subiendo",
|
||||
"Upload successfull!": "¡Subida satisfactoria!",
|
||||
"Upload failed!": "¡Error en la subida!",
|
||||
"Hash": "Hash",
|
||||
"Hash Options": "Opciones de hash",
|
||||
"Hashing": "Creando hash",
|
||||
"Failed to hash file...": "Error al crear hash del archivo…",
|
||||
"Ignore read only": "Ignorar sólo lectura",
|
||||
"Mount": "Montar",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Imagen memoria interna",
|
||||
"Image microSD card": "Imagen tarjeta microSD",
|
||||
"Empty...": "Vacío...",
|
||||
"Empty...": "Vacío…",
|
||||
"Open with DayBreak?": "¿Abrir con DayBreak?",
|
||||
"Launch ": "Abrir ",
|
||||
"Launch option for: ": "Opción de abrir con: ",
|
||||
"Select launcher for: ": "Seleccionar abrir con: ",
|
||||
"Close FileBrowser?": "",
|
||||
"Close FileBrowser?": "¿Cerrar explorador de archivos?",
|
||||
|
||||
"Sort By": "Ordenar por",
|
||||
"Sort Options": "Opciones de clasificación",
|
||||
"Filter": "Filtrar",
|
||||
"Filter": "Filtro",
|
||||
"All": "Todo",
|
||||
"Emulators": "Emuladores",
|
||||
"Tools": "Herramientas",
|
||||
"Themes": "Temas",
|
||||
"Legacy": "Legado",
|
||||
"Sort": "Clasificar",
|
||||
"Sort": "Ordenar",
|
||||
"Size": "Tamaño",
|
||||
"Size (Star)": "Tamaño (favorito)",
|
||||
"Size (Star)": "Tamaño (favoritos)",
|
||||
"Alphabetical": "Alfabético",
|
||||
"Alphabetical (Star)": "Alfabético (favorito)",
|
||||
"Alphabetical (Star)": "Alfabético (favoritos)",
|
||||
"Updated": "Actualizado",
|
||||
"Updated (Star)": "Actualizado (favorito)",
|
||||
"Updated (Star)": "Actualizado (favoritos)",
|
||||
"Downloads": "Descargas",
|
||||
"Likes": "Me Gusta",
|
||||
"Likes": "Me gusta",
|
||||
"ID": "ID",
|
||||
"Order": "Orden",
|
||||
"Descending": "Descendente",
|
||||
@@ -358,39 +357,39 @@
|
||||
"Ascending": "Ascendente",
|
||||
"Ascending (Up)": "Ascendente (arriba)",
|
||||
"Asc": "Ascendente",
|
||||
"Layout": "",
|
||||
"List": "",
|
||||
"Icon": "",
|
||||
"Grid": "",
|
||||
"Layout": "Diseño",
|
||||
"List": "Lista",
|
||||
"Icon": "Íconos",
|
||||
"Grid": "Malla",
|
||||
"Search": "Buscar",
|
||||
|
||||
"Options": "Opciones",
|
||||
"Split": "",
|
||||
"Split": "Dividir",
|
||||
"OK": "OK",
|
||||
"Back": "Atrás",
|
||||
"Select": "Seleccionar",
|
||||
"Open": "Abrir",
|
||||
"Close": "",
|
||||
"Close": "Cerrar",
|
||||
"Launch": "Ejecutar",
|
||||
"Restart": "Reiniciar",
|
||||
"Next": "",
|
||||
"Prev": "",
|
||||
"Next": "Siguiente",
|
||||
"Prev": "Anterior",
|
||||
"Yes": "Sí",
|
||||
"No": "No",
|
||||
"On": "",
|
||||
"Off": "",
|
||||
"On": "Activo",
|
||||
"Off": "Apagado",
|
||||
|
||||
"Install": "Instalar",
|
||||
"Install Selected files?": "",
|
||||
"Install Selected files?": "¿Instalar los archivos seleccionados?",
|
||||
"Installing ": "Instalando ",
|
||||
"Installed ": "",
|
||||
"Installed ": "Instalado",
|
||||
"Installed!": "¡Instalado!",
|
||||
"Trying to load ": "Intentando cargar ",
|
||||
"Checking MD5": "Chequeando MD5",
|
||||
"Checking MD5": "Revisando MD5",
|
||||
|
||||
"Delete": "Borrar",
|
||||
"Delete Selected files?": "¿Eliminar archivos seleccionados?",
|
||||
"Are you sure you want to delete ": "¿Estás seguro que quieres eliminar? ",
|
||||
"Are you sure you want to delete ": "¿Realmente desea eliminar? ",
|
||||
"Scanning ": "Escaneando ",
|
||||
"Deleting ": "Borrando ",
|
||||
"Deleting": "Borrando",
|
||||
@@ -403,7 +402,7 @@
|
||||
"Download": "Descargar",
|
||||
"Downloading ": "Descargando ",
|
||||
"Downloaded ": "Descargado ",
|
||||
"Download via the Network options!": "",
|
||||
"Download via the Network options!": "¡Descargar vía las opciones de red!",
|
||||
|
||||
"Update": "Actualizar",
|
||||
"Update avaliable: ": "Actualización disponible: ",
|
||||
@@ -411,13 +410,13 @@
|
||||
"Updated to ": "Actualizado a ",
|
||||
"Failed to download update": "Fallo al descargar actualización",
|
||||
|
||||
"%zu hours %zu minutes remaining": "",
|
||||
"%zu minutes %zu seconds remaining": "",
|
||||
"%zu seconds remaining": "",
|
||||
"%zu hours %zu minutes remaining": "Faltan %zu horas %zu minutos",
|
||||
"%zu minutes %zu seconds remaining": "Faltan %zu minutos %zu segundos",
|
||||
"%zu seconds remaining": "Faltan %zu segundos",
|
||||
|
||||
"Loading...": "Cargando...",
|
||||
"Loading...": "Cargando…",
|
||||
"Loading": "Cargando",
|
||||
"Empty!": "¡Vacío!",
|
||||
"Not Ready...": "No listo aún...",
|
||||
"Not Ready...": "Todavía no está listo…",
|
||||
"Error loading page!": "¡Error cargando la página!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"An error occurred": "Une erreur s'est produite",
|
||||
"If this message appears repeatedly, please open an issue.": "Si ce message apparaît en boucle, veuillez ouvrir une issue sur :",
|
||||
|
||||
"Menu Options": "Paramètres",
|
||||
"Menu Options": "Paramètres du menu",
|
||||
"Menu": "Paramètres",
|
||||
"Theme": "Thèmes",
|
||||
"Theme Options": "Paramètres des Thèmes",
|
||||
@@ -26,7 +26,7 @@
|
||||
"Mtp": "MTP",
|
||||
"Nxlink": "Nxlink",
|
||||
"Nxlink Connected": "Nxlink Connecté",
|
||||
"Nxlink Upload": "Téléchargement Nxlink",
|
||||
"Nxlink Upload": "Téléversement Nxlink",
|
||||
"Nxlink Finished": "Nxlink terminé",
|
||||
"Hdd": "Disque Dur",
|
||||
"Hdd write protect": "Protection en écriture du disque dur",
|
||||
@@ -73,12 +73,12 @@
|
||||
"Trim XCI": "Trimmer le XCI",
|
||||
"Label trimmed XCI": "Étiquette sur le XCI trimmer",
|
||||
"Multi-threaded USB transfer": "Transfert USB multithread",
|
||||
"Dump All Bins": "",
|
||||
"Dump XCI": "",
|
||||
"Dump Card ID Set": "",
|
||||
"Dump Card UID": "",
|
||||
"Dump Certificate": "",
|
||||
"Dump Initial Data": "",
|
||||
"Dump All Bins": "Dumper tous les éléments",
|
||||
"Dump XCI": "Dumper le XCI",
|
||||
"Dump Card ID Set": "Dumper l'ID de la cartouche",
|
||||
"Dump Card UID": "Dumper l'UID de la cartouche",
|
||||
"Dump Certificate": "Dumper le certificat",
|
||||
"Dump Initial Data": "Dumper l'Initial Data",
|
||||
"Select dump location": "Sélectionner l'emplacement du dump :",
|
||||
"microSD card (/dumps/)": "carte microSD (/dumps/)",
|
||||
"USB transfer (Switch 2 Switch)": "Transfert USB (Switch à Switch",
|
||||
@@ -100,13 +100,12 @@
|
||||
"Download theme?": "Voulez-vous télécharger ce thème ?",
|
||||
|
||||
"GitHub": "Github",
|
||||
"Downloading json": "Téléchargement du .json",
|
||||
"Downloading json": "Téléchargement du json",
|
||||
"Select asset to download for ": "Sélectionnez l'actif à télécharger pour :",
|
||||
"Failed to download json": "Échec du téléchargement de .json",
|
||||
"Failed to download json": "Échec du téléchargement du json",
|
||||
"Failed to download app!": "Échec du téléchargement de l'application",
|
||||
|
||||
"FTP Install": "Installer contenu via FTP",
|
||||
"FTP Install (EXPERIMENTAL)": "Installation via FTP (EXPERIMENTAL)",
|
||||
"Connection Type: WiFi | Strength: ": "Type de connexion : WiFi / Force du signal :",
|
||||
"Connection Type: Ethernet": "Type de connexion : Ethernet",
|
||||
"Connection Type: None": "Type de connexion : Aucune",
|
||||
@@ -116,9 +115,9 @@
|
||||
"Password:": "Mot de passe :",
|
||||
"SSID:": "SSID :",
|
||||
"Passphrase:": "Mot de passe :",
|
||||
"Failed to install via FTP, press B to exit...": "Installation via FTP échouée, appuyer sur B pour quitter...",
|
||||
"Ftp install success!": "Installation via FTP réussie !",
|
||||
"Ftp install failed!": "Installation via FTP échouée !",
|
||||
"Failed to install, press B to exit...": "Installation échouée, appuyer sur B pour quitter...",
|
||||
"Install success!": "Installation réussie !",
|
||||
"Install failed!": "Installation échouée !",
|
||||
"USB Install": "Installer contenu via USB",
|
||||
"USB": "Installation via USB",
|
||||
"Connected, waiting for file list...": "Connecté, en attente de la liste des fichiers...",
|
||||
@@ -136,7 +135,7 @@
|
||||
"Usb install failed!": "Installation via USB échouée !",
|
||||
"Press B to exit...": "Appuyer sur B pour quitter...",
|
||||
"GameCard Install": "Installation de la cartouche du jeu",
|
||||
"GameCard": "Carte du jeu",
|
||||
"GameCard": "Cartouche du jeu",
|
||||
"GC": "GC",
|
||||
"System memory %.1f GB": "Mémoire système %.1f GB",
|
||||
"microSD card %.1f GB": "Carte microSD %.1f GB",
|
||||
@@ -150,7 +149,7 @@
|
||||
"Gc install failed!": "Installation de la cartouche échouée !",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "IRS (infrarouge de la cam du Joycon",
|
||||
"IRS": "Infrarouge Camera",
|
||||
"IRS": "Caméra Infrarouge",
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "Niveau de bruit ambiant: ",
|
||||
"Controller": "Contrôleur",
|
||||
@@ -187,7 +186,7 @@
|
||||
"Web": "Web",
|
||||
"Select URL": "Sélectionnez une adresse",
|
||||
"Enter custom URL": "Entrez une adresse personnalisée",
|
||||
"Enter URL": "Enter l'adresse :",
|
||||
"Enter URL": "Entrer l'adresse :",
|
||||
|
||||
"Advanced": "Menu avancé",
|
||||
"Advanced Options": "Menu avancé",
|
||||
@@ -211,8 +210,8 @@
|
||||
"Set right-side menu": "Menu de droite par défaut",
|
||||
"Install options": "Options d'installation des jeux",
|
||||
"Install Options": "Options d'Installation",
|
||||
"Enable sysmmc": "Afficher la SYSmmc",
|
||||
"Enable emummc": "Afficher sur l'EMUmmc",
|
||||
"Enable sysmmc": "Afficher la SysNAND",
|
||||
"Enable emummc": "Afficher l'emuNAND",
|
||||
"Show install warning": "Avertissement lors d'installation",
|
||||
"Install location": "Emplacement d'installation",
|
||||
"System memory": "Mémoire système",
|
||||
@@ -229,6 +228,7 @@
|
||||
"Skip RSA header verify": "Vérification de l'entête RSA",
|
||||
"Skip RSA NPDM verify": "Vérification RSA NPDM",
|
||||
"Ignore distribution bit": "Ignorer le bit de distribution",
|
||||
"Convert to common ticket": "Convertir en ticket commun",
|
||||
"Convert to standard crypto": "Convertir en crypto standard",
|
||||
"Lower master key": "Abaisser la master key",
|
||||
"Lower system version": "Abaisser la version du système",
|
||||
@@ -237,14 +237,14 @@
|
||||
"Apps": "Applications",
|
||||
"Homebrew Options": "Options Homebrew",
|
||||
"Hide Sphaira": "Masquer Sphaira",
|
||||
"Install Forwarder": "Installer le Forwarder",
|
||||
"Install Forwarder": "Créer un Forwarder (icône au menu)",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ATTENTION : L'installation de forwarders entraînera un ban !",
|
||||
"Installing Forwarder": "Installation du Forwarder...",
|
||||
"Creating Program": "Création de Program",
|
||||
"Creating Control": "Création de Control",
|
||||
"Creating Meta": "Création de Meta",
|
||||
"Writing Nca": "Écriture NCA",
|
||||
"Updating ncm databse": "Mise à jour de la base de données ncm",
|
||||
"Updating ncm database": "Mise à jour de la base de données ncm",
|
||||
"Pushing application record": "Ajout de l'enregistrement de l'application",
|
||||
"Failed to install forwarder": "Échec de l'installation du forwarder",
|
||||
"Unstar": "Retirer des favories",
|
||||
@@ -257,7 +257,7 @@
|
||||
"Appstore": "Boutique d'applications",
|
||||
"Store": "AppStore",
|
||||
"Filter: %s | Sort : %s | Order: %s" : "Filtre : %s | Tri : %s | Ordre : %s",
|
||||
"AppStore Options": "Options du AppStore",
|
||||
"AppStore Options": "Options de l'AppStore",
|
||||
"Info": "Info.",
|
||||
"Changelog": "Journal des modifications",
|
||||
"Details": "Détails",
|
||||
@@ -281,13 +281,13 @@
|
||||
"Copy": "Copier",
|
||||
"Copying ": "Copie en cours...",
|
||||
"Paste": "Coller",
|
||||
"Paste file(s)?": "Copier le colis ?",
|
||||
"Paste file(s)?": "Copier le(s) fichier(s) ?",
|
||||
"Pasting": "Collage en cours...",
|
||||
"Pasting": "Collage en cours...",
|
||||
"Rename": "Renommer",
|
||||
"Set New File Name": "Nouveau nom du fichier",
|
||||
"Failed to delete directory": "Échec de la suppression du répertoire",
|
||||
"Failed to delete file": "Échec de la suppression du répertoire",
|
||||
"Failed to delete file": "Échec de la suppression du fichier",
|
||||
"Extract zip": "Extraire le zip",
|
||||
"Extract Options": "Options d'extraction",
|
||||
"Extract here": "Extraire ici",
|
||||
@@ -308,15 +308,15 @@
|
||||
"Create File": "Créer un fichier",
|
||||
"Set File Name": "Nommer Le fichier",
|
||||
"Create Folder": "Créer un dossier",
|
||||
"Set Folder Name": "Nommer la dossier",
|
||||
"Set Folder Name": "Nommer le dossier",
|
||||
"Creating ": "Création ",
|
||||
"View as text (unfinished)": "Afficher sous forme de texte (inachevé)",
|
||||
"Upload": "Télécharger",
|
||||
"Select upload location": "Emplacement du téléchargement :",
|
||||
"No upload locations set!": "Aucun emplacement configuré !",
|
||||
"Uploading": "Téléchargement en cours...",
|
||||
"Upload successfull!": "Téléchargement réussi !",
|
||||
"Upload failed!": "Téléchargement échoué !",
|
||||
"Upload": "Téléverser",
|
||||
"Select upload location": "Emplacement du téléversement :",
|
||||
"No upload locations set!": "Aucun emplacement de téléversement configuré !",
|
||||
"Uploading": "Téléversement en cours...",
|
||||
"Upload successfull!": "Téléversement réussi !",
|
||||
"Upload failed!": "Téléversement échoué !",
|
||||
"Hash": "Hash",
|
||||
"Hash Options": "Options du Hash",
|
||||
"Hashing": "Hachage",
|
||||
@@ -333,8 +333,8 @@
|
||||
"Select launcher for: ": "Sélectionner le lanceur pour : ",
|
||||
"Close FileBrowser?": "Fermer le navigateur de fichiers ?",
|
||||
|
||||
"Sort By": "Tri Par",
|
||||
"Sort Options": "Options de Tri",
|
||||
"Sort By": "Trier par",
|
||||
"Sort Options": "Options de tri",
|
||||
"Filter": "Filtre",
|
||||
"All": "Tous",
|
||||
"Emulators": "Émulateurs",
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
"Failed to download app!": "",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
@@ -116,9 +115,9 @@
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"Failed to install, press B to exit...": "",
|
||||
"Install success!": "",
|
||||
"Install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
@@ -244,7 +243,7 @@
|
||||
"Creating Control": "",
|
||||
"Creating Meta": "",
|
||||
"Writing Nca": "",
|
||||
"Updating ncm databse": "",
|
||||
"Updating ncm database": "",
|
||||
"Pushing application record": "",
|
||||
"Failed to install forwarder": "",
|
||||
"Unstar": "Rimuovi dai preferiti",
|
||||
@@ -420,4 +419,4 @@
|
||||
"Empty!": "Vuoto!",
|
||||
"Not Ready...": "Non pronto...",
|
||||
"Error loading page!": "Errore nel caricare la pagina!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
"Failed to download app!": "アプリのダウンロードに失敗しました!",
|
||||
|
||||
"FTP Install": "FTPでインストール",
|
||||
"FTP Install (EXPERIMENTAL)": "FTPでインストール(実験機能)",
|
||||
"Connection Type: WiFi | Strength: ": "接続: WiFi | 強度: ",
|
||||
"Connection Type: Ethernet": "接続: イーサネット",
|
||||
"Connection Type: None": "接続: なし",
|
||||
@@ -116,9 +115,9 @@
|
||||
"Password:": "暗証番号:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "WiFi暗証番号:",
|
||||
"Failed to install via FTP, press B to exit...": "FTP経由でインストールできませんでした、を押して終了します",
|
||||
"Ftp install success!": "FTPインストール完了!",
|
||||
"Ftp install failed!": "FTPインストール失敗!",
|
||||
"Failed to install, press B to exit...": "FTP経由でインストールできませんでした、を押して終了します",
|
||||
"Install success!": "FTPインストール完了!",
|
||||
"Install failed!": "FTPインストール失敗!",
|
||||
"USB Install": "USBインストール",
|
||||
"USB": "USBインストール",
|
||||
"Connected, waiting for file list...": "接続されました、ファイルリストを待っています",
|
||||
@@ -244,7 +243,7 @@
|
||||
"Creating Control": "コントロール作成中",
|
||||
"Creating Meta": "メター作成中",
|
||||
"Writing Nca": "Nca書き取り中",
|
||||
"Updating ncm databse": "ncmのDBをアップデート中",
|
||||
"Updating ncm database": "ncmのDBをアップデート中",
|
||||
"Pushing application record": "アプリの記録をプッシュ中",
|
||||
"Failed to install forwarder": "Forwarderのインストール失敗",
|
||||
"Unstar": "お気に入り解除",
|
||||
@@ -420,4 +419,4 @@
|
||||
"Empty!": "何も見つかりません",
|
||||
"Not Ready...": "準備ができていません",
|
||||
"Error loading page!": "ページのロードエラー"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
"Failed to download app!": "앱 다운로드 실패!",
|
||||
|
||||
"FTP Install": "FTP 설치",
|
||||
"FTP Install (EXPERIMENTAL)": "FTP 설치 (실험실 기능)",
|
||||
"Connection Type: WiFi | Strength: ": "상태: WiFi | 신호 세기: ",
|
||||
"Connection Type: Ethernet": "상태: 이더넷",
|
||||
"Connection Type: None": "상태: 연결 없음",
|
||||
@@ -116,9 +115,9 @@
|
||||
"Password:": "비밀번호:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "WiFi 암호:",
|
||||
"Failed to install via FTP, press B to exit...": "FTP 설치 실패함, 종료하려면 버튼을 입력하세요...",
|
||||
"Ftp install success!": "FTP 설치 완료!",
|
||||
"Ftp install failed!": "FTP 설치 실패!",
|
||||
"Failed to install, press B to exit...": "FTP 설치 실패함, 종료하려면 버튼을 입력하세요...",
|
||||
"Install success!": "FTP 설치 완료!",
|
||||
"Install failed!": "FTP 설치 실패!",
|
||||
"USB Install": "USB 설치",
|
||||
"USB": "USB 설치",
|
||||
"Connected, waiting for file list...": "연결됨, 파일 목록 대기 중...",
|
||||
@@ -244,7 +243,7 @@
|
||||
"Creating Control": "컨트롤 생성",
|
||||
"Creating Meta": "메타 생성",
|
||||
"Writing Nca": "Nca 쓰기",
|
||||
"Updating ncm databse": "Ncm 데이터베이스 업데이트",
|
||||
"Updating ncm database": "Ncm 데이터베이스 업데이트",
|
||||
"Pushing application record": "응용 프로그램 기록 푸싱",
|
||||
"Failed to install forwarder": "바로가기 설치 실패함",
|
||||
"Unstar": "즐겨찾기 해제",
|
||||
@@ -420,4 +419,4 @@
|
||||
"Empty!": "찾을 수 없습니다!",
|
||||
"Not Ready...": "준비되지 않음...",
|
||||
"Error loading page!": "페이지 로딩 오류!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
"Failed to download app!": "",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
@@ -116,9 +115,9 @@
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"Failed to install, press B to exit...": "",
|
||||
"Install success!": "",
|
||||
"Install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
@@ -244,7 +243,7 @@
|
||||
"Creating Control": "",
|
||||
"Creating Meta": "",
|
||||
"Writing Nca": "",
|
||||
"Updating ncm databse": "",
|
||||
"Updating ncm database": "",
|
||||
"Pushing application record": "",
|
||||
"Failed to install forwarder": "",
|
||||
"Unstar": "",
|
||||
@@ -420,4 +419,4 @@
|
||||
"Empty!": "",
|
||||
"Not Ready...": "",
|
||||
"Error loading page!": ""
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"No Internet": "Sem internet",
|
||||
"Switch-Handheld!": "Switch-Portátil",
|
||||
"Switch-Docked!": "Switch-Docado",
|
||||
"Warning! Logs are enabled, Sphaira will run slowly!": "",
|
||||
"Warning! Logs are enabled, Sphaira will run slowly!": "AVISO: O registro de depuração está habilitado, o sphaira ficará lento!",
|
||||
"Audio disabled due to suspended game": "Áudio desativado devido ao software suspenso.",
|
||||
"Are you sure you wish to cancel?": "Tem certeza de que quer cancelar?",
|
||||
"An error occurred": "Ocorreu um erro.",
|
||||
@@ -50,19 +50,20 @@
|
||||
|
||||
"Misc": "Diversos",
|
||||
"Misc Options": "Diversos",
|
||||
|
||||
"Games": "Softwares",
|
||||
"Game Options": "Opções do software",
|
||||
"Hide forwarders": "Ocultar atalhos forwarder",
|
||||
"Launch random game": "Iniciar um software aleatório",
|
||||
"List meta records": "Registro de dados",
|
||||
"List meta records": "Lista de registros",
|
||||
"Entries": "Entradas",
|
||||
"Failed to list application meta entries": "Falha ao listar as meta entradas do software.",
|
||||
"No meta entries found...\n": "Nenhuma entrada de registros encontrada...\n",
|
||||
"Updating application record list": "Atualizando a lista de registros do software...",
|
||||
"Dump": "Exportar dados",
|
||||
"Dump": "Exportar software",
|
||||
"Dump options": "Opções de exportação",
|
||||
"Dump Options": "Opções de exportação",
|
||||
"Select content to dump": "Exportação de dados",
|
||||
"Select content to dump": "Exportação de software",
|
||||
"Dump All": "Exportar tudo",
|
||||
"Dump Application": "Exportar software base",
|
||||
"Dump Patch": "Exportar atualização",
|
||||
@@ -72,7 +73,7 @@
|
||||
"Append folder with .xci": "Acrescentar .xci à pasta",
|
||||
"Trim XCI": "Aparar arquivos .xci",
|
||||
"Label trimmed XCI": "Rotular .xci aparados",
|
||||
"Multi-threaded USB transfer": "Trasfêrencia em multithread",
|
||||
"Multi-threaded USB transfer": "Transferências em multithread",
|
||||
"Dump All Bins": "Exportar todos os binários",
|
||||
"Dump XCI": "Exportar .xci",
|
||||
"Dump Card ID Set": "Exportar conjunto de IDs",
|
||||
@@ -89,24 +90,54 @@
|
||||
"Delete successfull!": "Remoção concluída.",
|
||||
"Delete failed!": "Remoção falhou.",
|
||||
"Success": "Concluído.",
|
||||
"Refresh": "Recarregar",
|
||||
"Create contents folder": "Criar pasta de conteúdo",
|
||||
"Create save": "Criar dados salvo",
|
||||
"Title cache": "Usar cache de títulos",
|
||||
"Delete title cache": "Apagar cache de títulos",
|
||||
|
||||
"Saves": "Dados salvos",
|
||||
"Save Options": "Opções de dados salvos",
|
||||
"Account": "Usuário",
|
||||
"Data Type": "Tipo de dados",
|
||||
"System": "Sistema",
|
||||
"BCAT": "BCAT",
|
||||
"Device": "Dispositivo",
|
||||
"Temporary": "Temporário",
|
||||
"Cache": "Cache",
|
||||
"System BCAT": "BCAT (Sistema)",
|
||||
"Backup": "Backup",
|
||||
"Auto backup": "Backup automático",
|
||||
"Auto backup on restore": "Fazer backup ao restaurar",
|
||||
"Compress backup": "Comprimir backups",
|
||||
"Are you sure you want to backup save(s)?": "Tem certeza de que quer fazer backup dos dados salvo?",
|
||||
"No saves found in ": "Nenhum dado salvo encontrado em ",
|
||||
"Backed up to ": "Backup salvo em ",
|
||||
"Backup successfull!": "Backup concluído.",
|
||||
"Backup failed!": "Backup falhou.",
|
||||
"Select backup location": "Selecione o local de backup",
|
||||
"Select restore location": "Selecione o local de restauração",
|
||||
"Restore save for: ": "Restaurar backup de dados salvo para: ",
|
||||
"Are you sure you want to restore ": "Tem certeza de que quer restaurar os dados salvo de ",
|
||||
"Restore successfull!": "Restauração concluída.",
|
||||
"Restore failed!": "Restauração falhou.",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Opções do Themezer",
|
||||
"Nsfw": "Temas 18+ (NSFW)",
|
||||
"Page": "Ir para uma página específica",
|
||||
"Page": "Página",
|
||||
"Page %zu / %zu": "Página %zu / %zu",
|
||||
"Enter Page Number": "Número da página",
|
||||
"Enter Page Number": "Digite o № da página.",
|
||||
"Bad Page": "Página inválida.",
|
||||
"Download theme?": "Baixar tema?",
|
||||
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Baixando JSON",
|
||||
"Select asset to download for ": "Selecione o recurso para baixar de ",
|
||||
"Failed to download json": "",
|
||||
"Failed to download app!": "",
|
||||
"Failed to download json": "Falha ao baixar json.",
|
||||
"Failed to download app!": "falha ao baixar aplicativo.",
|
||||
|
||||
"FTP Install": "Instalação via FTP",
|
||||
"FTP Install (EXPERIMENTAL)": "Instalação via FTP (experimental)",
|
||||
"Connection Type: WiFi | Strength: ": "Conexão por rede Wi-Fi | Intensidade do sinal: ",
|
||||
"Connection Type: Ethernet": "Conexão por cabo Ethernet",
|
||||
"Connection Type: None": "Sem conexão",
|
||||
@@ -116,40 +147,55 @@
|
||||
"Password:": "Senha:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "Senha:",
|
||||
"Failed to install via FTP, press B to exit...": "Falha ao instalar via FTP, aperte B para sair.",
|
||||
"Ftp install success!": "Instalação via FTP concluída.",
|
||||
"Ftp install failed!": "Instalação via FTP falhou.",
|
||||
"Failed to install, press B to exit...": "Falha ao instalar, aperte para sair.",
|
||||
"Install success!": "Instalação concluída.",
|
||||
"Install failed!": "Instalação falhou.",
|
||||
"MTP Install": "Instalação via MTP",
|
||||
"State: %s | Speed: %s": "Estado: %s | Velocidade: %s",
|
||||
"Detached": "Desconectado",
|
||||
"Attached": "Conectado",
|
||||
"Powered": "Energia",
|
||||
"Default": "Padrão",
|
||||
"Address": "Endereço",
|
||||
"Configured": "Configurado",
|
||||
"Suspended": "Suspenso",
|
||||
"USB 1.0 Low Speed": "USB 1.0",
|
||||
"USB 1.1 Full Speed": "USB 1.1 (Full Speed)",
|
||||
"USB 2.0 High Speed": "USB 2.0 (High Speed)",
|
||||
"USB 3.0 Super Speed": "USB 3.0 (SuperSpeed)",
|
||||
"Drag'n'Drop (NSP, XCI, NSZ, XCZ) to the install folder": "Arraste o(s) arquivo(s) .nsp/.xci/.nsz/.xcz para a pasta \"Install\".",
|
||||
"Failed to install via MTP, press B to exit...": "Falha ao instalar via MTP, aperte para sair.",
|
||||
"MTP install success!": "Instalação via MTP concluída.",
|
||||
"MTP install failed!": "Instalação via MTP falhou.",
|
||||
"USB Install": "Instalação via USB",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "Conectado, aguardando lista de arquivos...",
|
||||
"Connected, starting transfer...": "Conectado, iniciando transferência...",
|
||||
"Failed to init usb, press B to exit...": "Falha ao instalar via USB,\naperte B para sair.",
|
||||
"Failed to init usb, press B to exit...": "Falha ao inicializar USB,\naperte para sair.",
|
||||
"Waiting for connection...": "Aguardando conexão...",
|
||||
"Transferring data...": "Transferindo dados...",
|
||||
"USB connected, sending file list": "USB conectado, enviando lista de arquivos",
|
||||
"Sent file list, waiting for command...": "Lista de arquivos enviada, aguardando comando...",
|
||||
"waiting for usb connection...": "Aguardando conexão USB...",
|
||||
"Disable MTP for usb install": "Escuta MTP desabilitada temporáriamente.",
|
||||
"Disable MTP for usb install": "Escuta MTP desabilitada temporariamente.",
|
||||
"Re-enabled MTP": "Escuta MTP reabilitada.",
|
||||
"Installed via usb": "Instalado via USB",
|
||||
"Usb install success!": "Instalação via USB concluída.",
|
||||
"Usb install failed!": "Instalação via USB falhou.",
|
||||
"Press B to exit...": "Aperte B para sair.",
|
||||
"Press B to exit...": "Aperte para sair.",
|
||||
"GameCard Install": "Instalação de cartão de jogo",
|
||||
"GameCard": "Cartão de jogo",
|
||||
"GC": "Cartão de jogo",
|
||||
"System memory %.1f GB": "Memória do console %.1f GB",
|
||||
"microSD card %.1f GB": "Cartão microSD %.1f GB",
|
||||
"Exit": "Sair",
|
||||
"Install disabled...\nPlease enable installing via the install options.": "",
|
||||
"No GameCard inserted": "",
|
||||
"GameCard is already trimmed!": "",
|
||||
"WARNING: GameCard is already trimmed!": "",
|
||||
"Continue": "",
|
||||
"Install disabled...\nPlease enable installing via the install options.": "Instalação desabilitada.\nHabilite a função através das opções de instalação.",
|
||||
"No GameCard inserted": "Nenhum cartão de jogo inserido.",
|
||||
"GameCard is already trimmed!": "O cartão de jogo já está aparado.",
|
||||
"WARNING: GameCard is already trimmed!": "AVISO: O cartão de jogo já está aparado.",
|
||||
"Continue": "Continuar",
|
||||
"Gc install success!": "Instalação de cartão de jogo concluída.",
|
||||
"Gc install failed!": "Instalação de cartão de jogo falhou.",
|
||||
"Gc install success!": "Instalação do cartão de jogo concluída.",
|
||||
"Gc install failed!": "Instalação do cartão de jogo falhou.",
|
||||
|
||||
"IRS (Infrared Joycon Camera)": "Câmera de movimento IR",
|
||||
"IRS": "Câmera de movimento IR",
|
||||
@@ -185,19 +231,16 @@
|
||||
"Trimming Format": "Formato do recorte",
|
||||
"External Light Filter": "Filtro de luz externa",
|
||||
"Load Default": "Restaurar padrão",
|
||||
|
||||
|
||||
"Web": "Navegador de internet",
|
||||
"Select URL": "Selecione uma URL",
|
||||
"Enter custom URL": "Digitar URL",
|
||||
"Enter URL": "Digitar URL",
|
||||
|
||||
"Web": "",
|
||||
"Select URL": "",
|
||||
"Enter custom URL": "",
|
||||
"Enter URL": "",
|
||||
|
||||
"Advanced": "Opções avançadas",
|
||||
"Advanced": "Avançado",
|
||||
"Advanced Options": "Opções avançadas",
|
||||
"Logging": "Registro de depuração",
|
||||
"Advanced options": "Opções avançadas",
|
||||
"Logging": "Registros de depuração",
|
||||
"Replace hbmenu on exit": "Substituir hbmenu ao sair",
|
||||
"Restore hbmenu?": "Restaurar hbmenu?",
|
||||
"Restore": "Restaurar",
|
||||
@@ -217,8 +260,8 @@
|
||||
"Set right-side menu": "Menu do botão R",
|
||||
"Install options": "Opções de instalação",
|
||||
"Install Options": "Opções de instalação",
|
||||
"Enable sysmmc": "Habilitar sysMMC",
|
||||
"Enable emummc": "Habilitar emuMMC",
|
||||
"Enable sysmmc": "Habilitar em sysMMC",
|
||||
"Enable emummc": "Habilitar em emuMMC",
|
||||
"Show install warning": "Mostrar aviso de instalação",
|
||||
"Install location": "Local de instalação",
|
||||
"System memory": "Memória do console",
|
||||
@@ -235,16 +278,10 @@
|
||||
"Skip RSA header verify": "Pular checagens de header RSA",
|
||||
"Skip RSA NPDM verify": "Pular checagens de NPDM RSA",
|
||||
"Ignore distribution bit": "Ignorar bit de distribuição",
|
||||
"Convert to common ticket": "Converter para ticket comum",
|
||||
"Convert to standard crypto": "Converter para crypto padrão",
|
||||
"Lower master key": "Reduzir master keys",
|
||||
"Lower system version": "Reduzir versão do sistema",
|
||||
"Dump options": "Opções de exportação",
|
||||
"Dump Options": "Opções de exportação",
|
||||
"Created nested folder": "Criar pastas aninhadas",
|
||||
"Append folder with .xci": "Acrescentar .xci à pasta",
|
||||
"Trim XCI": "Aparar arquivos .xci",
|
||||
"Label trimmed XCI": "Rotular .xci aparados",
|
||||
"Multi-threaded USB transfer": "Transferências em multithread",
|
||||
|
||||
"Homebrew": "Homebrews",
|
||||
"Apps": "Homebrews",
|
||||
@@ -253,11 +290,11 @@
|
||||
"Install Forwarder": "Instalar atalho forwarder",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "AVISO: Instalar atalhos forwarder pode resultar em um banimento!",
|
||||
"Installing Forwarder": "Instalando atalho forwarder...",
|
||||
"Creating Program": "Criando 'Program'",
|
||||
"Creating Control": "Criando 'Control'",
|
||||
"Creating Meta": "Criando 'Meta'",
|
||||
"Creating Program": "Criando \"Program\"",
|
||||
"Creating Control": "Criando \"Control\"",
|
||||
"Creating Meta": "Criando \"Meta\"",
|
||||
"Writing Nca": "Gravando NCA",
|
||||
"Updating ncm databse": "Atualizando base de dados NCM",
|
||||
"Updating ncm database": "Atualizando base de dados NCM",
|
||||
"Pushing application record": "Aplicando registro do software",
|
||||
"Failed to install forwarder": "Falha ao instalar atalho forwarder.",
|
||||
"Unstar": "Desfavoritar",
|
||||
@@ -274,11 +311,11 @@
|
||||
"Info": "Informações",
|
||||
"Changelog": "Alterações",
|
||||
"Details": "Detalhes",
|
||||
"version: %s": "versão: %s",
|
||||
"updated: %s": "atualizado: %s",
|
||||
"category: %s": "categoria: %s",
|
||||
"extracted: %.2f MiB": "tam. extraído: %.2f MiB",
|
||||
"app_dls: %s": "downloads: %s",
|
||||
"version: %s": "Versão: %s",
|
||||
"updated: %s": "Atualizado: %s",
|
||||
"category: %s": "Categoria: %s",
|
||||
"extracted: %.2f MiB": "Tamanho: %.2f MiB",
|
||||
"app_dls: %s": "№ de downloads: %s",
|
||||
"More by Author": "Mais deste autor",
|
||||
"Leave Feedback": "Deixar um feedback",
|
||||
|
||||
@@ -291,18 +328,17 @@
|
||||
"Folders First": "Ordenar pastas primeiro",
|
||||
"Hidden Last": "Ordenar ocultos por último",
|
||||
"Split": "Dividir",
|
||||
"Close": "Fechar",
|
||||
"Cut": "Recortar",
|
||||
"Copy": "Copiar",
|
||||
"Copying ": "Copiando ",
|
||||
"Paste": "Colar",
|
||||
"Paste file(s)?": "",
|
||||
"Paste file(s)?": "Colar arquivo(s)?",
|
||||
"Pasting ": "Colando ",
|
||||
"Pasting": "Colando ",
|
||||
"Rename": "Renomear",
|
||||
"Set New File Name": "Defina o nome do novo arquivo",
|
||||
"Failed to delete directory": "",
|
||||
"Failed to delete file": "",
|
||||
"Set New File Name": "Defina o nome do novo arquivo.",
|
||||
"Failed to delete directory": "Falha ao apagar diretório.",
|
||||
"Failed to delete file": "Falha ao apagar arquivo.",
|
||||
"Extract zip": "Extrair .zip",
|
||||
"Extract Options": "Opções de extração",
|
||||
"Extract here": "Extrair aqui",
|
||||
@@ -321,9 +357,9 @@
|
||||
"Compress success!": "Compressão concluída.",
|
||||
"Compress failed!": "Compressão falhou.",
|
||||
"Create File": "Criar arquivo",
|
||||
"Set File Name": "Defina o nome do arquivo",
|
||||
"Set File Name": "Defina o nome do arquivo.",
|
||||
"Create Folder": "Criar pasta",
|
||||
"Set Folder Name": "Defina o nome da pasta",
|
||||
"Set Folder Name": "Defina o nome da pasta.",
|
||||
"Creating ": "Criando ",
|
||||
"View as text (unfinished)": "Ver como texto (inacabado)",
|
||||
"Upload": "Enviar",
|
||||
@@ -334,10 +370,9 @@
|
||||
"Upload failed!": "Envio falhou.",
|
||||
"Hash": "Calcular hash",
|
||||
"Hash Options": "Calcular hash",
|
||||
"Hashing": "",
|
||||
"Failed to hash file...": "",
|
||||
"View as text (unfinished)": "Ver como texto (inacabado)",
|
||||
"Ignore read only": "Ignorar modo somente leitura",
|
||||
"Hashing": "Calculando hash...",
|
||||
"Failed to hash file...": "Falha ao calcular hash do arquivo.",
|
||||
"Ignore read only": "Ignorar \"somente leitura\"",
|
||||
"Mount": "Montar",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Imagem (memória do console)",
|
||||
@@ -364,8 +399,8 @@
|
||||
"Alphabetical (Star)": "Alfabética (favoritos)",
|
||||
"Updated": "Mais recentes",
|
||||
"Updated (Star)": "Mais recentes (favoritos)",
|
||||
"Downloads": "Nº de downloads",
|
||||
"Likes": "Nº de curtidas",
|
||||
"Downloads": "№ de downloads",
|
||||
"Likes": "№ de curtidas",
|
||||
"ID": "ID",
|
||||
"Order": "Ordem",
|
||||
"Descending": "Descendente",
|
||||
@@ -419,7 +454,7 @@
|
||||
"Download": "Baixar",
|
||||
"Downloading ": "Baixando ",
|
||||
"Downloaded ": "Baixado ",
|
||||
"Download via the Network options!": "",
|
||||
"Download via the Network options!": "Baixe através das opções de rede.",
|
||||
|
||||
"Update": "Atualizar",
|
||||
"Update avaliable: ": "Atualização disponível: ",
|
||||
@@ -436,4 +471,4 @@
|
||||
"Empty!": "Vazio.",
|
||||
"Not Ready...": "Não está pronto...",
|
||||
"Error loading page!": "Erro ao carregar página."
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"No Internet": "Нет интернета",
|
||||
"Switch-Handheld!": "Режим Портатива",
|
||||
"Switch-Docked!": "Режим Дока",
|
||||
"Warning! Logs are enabled, Sphaira will run slowly!": "",
|
||||
"Warning! Logs are enabled, Sphaira will run slowly!": "Внимание! Включены логи, Sphaira будет работать медленно!",
|
||||
"Audio disabled due to suspended game": "Звук отключён из-за приостановки игры",
|
||||
"Are you sure you wish to cancel?": "Вы уверены, что хотите отменить?",
|
||||
"An error occurred": "Произошла ошибка",
|
||||
@@ -73,14 +73,14 @@
|
||||
"Trim XCI": "Обрезать .xci",
|
||||
"Label trimmed XCI": "Пометить обрезанный .xci",
|
||||
"Multi-threaded USB transfer": "Многоядерная USB передача",
|
||||
"Dump All Bins": "",
|
||||
"Dump XCI": "",
|
||||
"Dump Card ID Set": "",
|
||||
"Dump Card UID": "",
|
||||
"Dump Certificate": "",
|
||||
"Dump Initial Data": "",
|
||||
"Dump All Bins": "Дамп всех BIN-файлов",
|
||||
"Dump XCI": "Дамп XCI",
|
||||
"Dump Card ID Set": "Дамп набора ID карты",
|
||||
"Dump Card UID": "Дамп UID карты",
|
||||
"Dump Certificate": "Дамп сертификата",
|
||||
"Dump Initial Data": "Дамп начальных данных",
|
||||
"Select dump location": "Выберите место для дампа",
|
||||
"microSD card (/dumps/)": "microSD карта (/dumps/)",
|
||||
"microSD card (/dumps/)": "microSD (/dumps/)",
|
||||
"USB transfer (Switch 2 Switch)": "Передача по USB (Switch 2 Switch)",
|
||||
"/dev/null (Speed Test)": "/dev/null (тест скорости)",
|
||||
"Dumping": "Снятие дампа",
|
||||
@@ -89,6 +89,33 @@
|
||||
"Delete successfull!": "Удаление успешно!",
|
||||
"Delete failed!": "Ошибка удаления!",
|
||||
"Success": "Успех",
|
||||
"Title cache": "Кэш заголовков",
|
||||
|
||||
"Saves": "Сохранения",
|
||||
"Save Options": "Опции сохранений",
|
||||
"Account": "Пользователь",
|
||||
"Data Type": "Тип данных",
|
||||
"System": "Система",
|
||||
"BCAT": "BCAT",
|
||||
"Device": "Устройство",
|
||||
"Temporary": "Временные",
|
||||
"Cache": "Кэш",
|
||||
"System BCAT": "BCAT (Система)",
|
||||
"Backup": "Сделать бэкап",
|
||||
"Auto backup": "Автоматический бэкап",
|
||||
"Auto backup on restore": "Бэкап при восстановлении",
|
||||
"Compress backup": "Сжимать бэкапы",
|
||||
"Are you sure you want to backup save(s)?": "Вы уверены, что хотите сделать бэкап(ы)?",
|
||||
"No saves found in ": "Сохранения не найдены в ",
|
||||
"Backed up to ": "Бэкап сохранен в ",
|
||||
"Backup successfull!": "Бэкап успешно создан!",
|
||||
"Backup failed!": "Ошибка бэкапа!",
|
||||
"Select backup location": "Выберите место для бэкапа",
|
||||
"Select restore location": "Выберите место для восстановления",
|
||||
"Restore save for: ": "Восстановить сохранение для: ",
|
||||
"Are you sure you want to restore ": "Вы уверены, что хотите восстановить ",
|
||||
"Restore successfull!": "Восстановление успешно!",
|
||||
"Restore failed!": "Ошибка восстановления!",
|
||||
|
||||
"Themezer": "Themezer",
|
||||
"Themezer Options": "Опции Themezer",
|
||||
@@ -102,11 +129,10 @@
|
||||
"GitHub": "GitHub",
|
||||
"Downloading json": "Загрузка json",
|
||||
"Select asset to download for ": "Выберите ресурс для загрузки: ",
|
||||
"Failed to download json": "",
|
||||
"Failed to download app!": "",
|
||||
"Failed to download json": "Не удалось загрузить json",
|
||||
"Failed to download app!": "Не удалось загрузить приложение!",
|
||||
|
||||
"FTP Install": "Установка по FTP",
|
||||
"FTP Install (EXPERIMENTAL)": "Установка по FTP (ЭКСПЕРИМЕНТАЛЬНО)",
|
||||
"Connection Type: WiFi | Strength: ": "Тип подключения: WiFi | Сигнал: ",
|
||||
"Connection Type: Ethernet": "Тип подключения: Ethernet",
|
||||
"Connection Type: None": "Нет подключения",
|
||||
@@ -116,9 +142,26 @@
|
||||
"Password:": "Пароль:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "Пароль:",
|
||||
"Failed to install via FTP, press B to exit...": "Не удалось установить по FTP, нажмите B для выхода...",
|
||||
"Ftp install success!": "Установка по FTP прошла успешно!",
|
||||
"Ftp install failed!": "Сбой установки по FTP!",
|
||||
"Failed to install, press B to exit...": "Не удалось установить по FTP, нажмите B для выхода...",
|
||||
"Install success!": "Установка по FTP прошла успешно!",
|
||||
"Install failed!": "Сбой установки по FTP!",
|
||||
"MTP Install": "Установка по MTP",
|
||||
"State: %s | Speed: %s": "Состояние: %s | Скорость: %s",
|
||||
"Detached": "Отключено",
|
||||
"Attached": "Подключено",
|
||||
"Powered": "Заряжается",
|
||||
"Default": "По умолчанию",
|
||||
"Address": "Адрес",
|
||||
"Configured": "Настроено",
|
||||
"Suspended": "Приостановлено",
|
||||
"USB 1.0 Low Speed": "USB 1.0",
|
||||
"USB 1.1 Full Speed": "USB 1.1",
|
||||
"USB 2.0 High Speed": "USB 2.0",
|
||||
"USB 3.0 Super Speed": "USB 3.0",
|
||||
"Drag'n'Drop (NSP, XCI, NSZ, XCZ) to the install folder": "Перетащите (NSP, XCI, NSZ, XCZ) в папку установки",
|
||||
"Failed to install via MTP, press B to exit...": "Не удалось установить по MTP, нажмите B для выхода...",
|
||||
"MTP install success!": "Установка по MTP успешна!",
|
||||
"MTP install failed!": "Сбой установки по MTP!",
|
||||
"USB Install": "Установка по USB",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "Подключено, ожидание списка файлов...",
|
||||
@@ -138,14 +181,14 @@
|
||||
"GameCard Install": "Установка с картриджа",
|
||||
"GameCard": "Картридж",
|
||||
"GC": "GC",
|
||||
"System memory %.1f GB": "Память системы %.1f ГБ",
|
||||
"microSD card %.1f GB": "Карта microSD %.1f ГБ",
|
||||
"System memory %.1f GB": "NAND %.1f ГБ",
|
||||
"microSD card %.1f GB": "microSD %.1f ГБ",
|
||||
"Exit": "Выход",
|
||||
"Install disabled...\nPlease enable installing via the install options.": "",
|
||||
"No GameCard inserted": "",
|
||||
"GameCard is already trimmed!": "",
|
||||
"WARNING: GameCard is already trimmed!": "",
|
||||
"Continue": "",
|
||||
"Install disabled...\nPlease enable installing via the install options.": "Установка отключена...\nПожалуйста, включите установку в опциях установки.",
|
||||
"No GameCard inserted": "Картридж не вставлен",
|
||||
"GameCard is already trimmed!": "Картридж уже обрезан!",
|
||||
"WARNING: GameCard is already trimmed!": "ВНИМАНИЕ: Картридж уже обрезан!",
|
||||
"Continue": "Продолжить",
|
||||
"Gc install success!": "Установка с картриджа успешна!",
|
||||
"Gc install failed!": "Сбой установки с картриджа!",
|
||||
|
||||
@@ -184,14 +227,14 @@
|
||||
"External Light Filter": "Внешний светофильтр",
|
||||
"Load Default": "По умолчанию",
|
||||
|
||||
"Web": "",
|
||||
"Select URL": "",
|
||||
"Enter custom URL": "",
|
||||
"Enter URL": "",
|
||||
"Web": "Браузер",
|
||||
"Select URL": "Выберите URL",
|
||||
"Enter custom URL": "Введите свой URL",
|
||||
"Enter URL": "Введите URL",
|
||||
|
||||
"Advanced": "Продвинутые",
|
||||
"Advanced Options": "Расширенные опции",
|
||||
"Logging": "Журналирование",
|
||||
"Logging": "Логи",
|
||||
"Replace hbmenu on exit": "Замена hbmenu при выходе",
|
||||
"Restore hbmenu?": "Восстановить hbmenu?",
|
||||
"Restore": "Восстановить",
|
||||
@@ -228,7 +271,8 @@
|
||||
"Skip NCA hash verify": "Не проверять NCA hash",
|
||||
"Skip RSA header verify": "Не проверять RSA header",
|
||||
"Skip RSA NPDM verify": "Не проверять RSA NPDM",
|
||||
"Ignore distribution bit": "Игнор. бит распределения",
|
||||
"Ignore distribution bit": "Игнор. битa распределения",
|
||||
"Convert to common ticket": "Конверт. в общий тикет",
|
||||
"Convert to standard crypto": "Конверт. в стандарт. крипт.",
|
||||
"Lower master key": "Снизить мастер-ключ",
|
||||
"Lower system version": "Снизить версию системы",
|
||||
@@ -239,12 +283,12 @@
|
||||
"Hide Sphaira": "Скрыть Sphaira",
|
||||
"Install Forwarder": "Установить форвардер",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "ВНИМАНИЕ: \nУстановка в сиснанд приведет к бану!",
|
||||
"Installing Forwarder": "Установить форвардер",
|
||||
"Installing Forwarder": "Установка форвардера",
|
||||
"Creating Program": "Создание программы",
|
||||
"Creating Control": "Создание элемента управления",
|
||||
"Creating Meta": "Создание меты",
|
||||
"Writing Nca": "Запись NCA",
|
||||
"Updating ncm databse": "Обновление базы данных NCM",
|
||||
"Updating ncm database": "Обновление базы данных NCM",
|
||||
"Pushing application record": "Добавление записи приложения",
|
||||
"Failed to install forwarder": "Не удалось установить форвардер",
|
||||
"Unstar": "Убрать из избранного",
|
||||
@@ -266,8 +310,9 @@
|
||||
"category: %s": "категория: %s",
|
||||
"extracted: %.2f MiB": "извлечено: %.2f MiB",
|
||||
"app_dls: %s": "app_dls: %s",
|
||||
"More by Author": "Другие приложения автора",
|
||||
"More by Author": "Другое от автора",
|
||||
"Leave Feedback": "Оставить отзыв",
|
||||
"Visit Website": "Посетить сайт",
|
||||
|
||||
"FileBrowser": "Файлы",
|
||||
"Files": "Файлы",
|
||||
@@ -277,17 +322,18 @@
|
||||
"Show Hidden": "Показать скрытые",
|
||||
"Folders First": "Папки в начале",
|
||||
"Hidden Last": "Скрытые в конце",
|
||||
"Split": "Разделить",
|
||||
"Cut": "Вырезать",
|
||||
"Copy": "Копировать",
|
||||
"Copying ": "Копирование ",
|
||||
"Paste": "Вставить",
|
||||
"Paste file(s)?": "",
|
||||
"Paste file(s)?": "Вставить файл(ы)?",
|
||||
"Pasting ": "Вставка ",
|
||||
"Pasting": "Вставка",
|
||||
"Rename": "Переименовать",
|
||||
"Set New File Name": "Задайте новое имя файла",
|
||||
"Failed to delete directory": "",
|
||||
"Failed to delete file": "",
|
||||
"Failed to delete directory": "Не удалось удалить папку",
|
||||
"Failed to delete file": "Не удалось удалить файл",
|
||||
"Extract zip": "Распаковать .zip",
|
||||
"Extract Options": "Опции распаковки",
|
||||
"Extract here": "Распаковать сюда",
|
||||
@@ -319,10 +365,10 @@
|
||||
"Upload failed!": "Сбой Отправки!",
|
||||
"Hash": "Хэш",
|
||||
"Hash Options": "Опции хэша",
|
||||
"Hashing": "",
|
||||
"Failed to hash file...": "",
|
||||
"Hashing": "Вычисление хэша",
|
||||
"Failed to hash file...": "Не удалось вычислить хэш файла...",
|
||||
"Ignore read only": "Игнор. только для чтения",
|
||||
"Mount": "Монтирован",
|
||||
"Mount": "Монтировать",
|
||||
"Sd": "Micro SD",
|
||||
"Image System memory": "Альбом Сиснанда",
|
||||
"Image microSD card": "Альбом Эмунанда",
|
||||
@@ -340,7 +386,7 @@
|
||||
"Emulators": "Эмуляторы",
|
||||
"Tools": "Инструменты",
|
||||
"Themes": "Темы",
|
||||
"Legacy": "Старые",
|
||||
"Legacy": "Легаси",
|
||||
"Sort": "Сортировать",
|
||||
"Size": "По размеру",
|
||||
"Size (Star)": "По размеру (избр.)",
|
||||
@@ -403,7 +449,7 @@
|
||||
"Download": "Скачать",
|
||||
"Downloading ": "Загрузка ",
|
||||
"Downloaded ": "Загружено ",
|
||||
"Download via the Network options!": "",
|
||||
"Download via the Network options!": "Скачайте через: Меню - Сеть",
|
||||
|
||||
"Update": "Обновить",
|
||||
"Update avaliable: ": "Доступно обновление: ",
|
||||
@@ -420,4 +466,4 @@
|
||||
"Empty!": "Пусто!",
|
||||
"Not Ready...": "Не готово...",
|
||||
"Error loading page!": "Ошибка загрузки страницы!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
"Failed to download app!": "",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
@@ -116,9 +115,9 @@
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"Failed to install, press B to exit...": "",
|
||||
"Install success!": "",
|
||||
"Install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
@@ -244,7 +243,7 @@
|
||||
"Creating Control": "Skapar kontroll",
|
||||
"Creating Meta": "Skapar metadata",
|
||||
"Writing Nca": "Skriver Nca",
|
||||
"Updating ncm databse": "Uppdaterar ncm-databas",
|
||||
"Updating ncm database": "Uppdaterar ncm-databas",
|
||||
"Pushing application record": "Skickar programpost",
|
||||
"Failed to install forwarder": "Misslyckades att installera genväg",
|
||||
"Unstar": "Avmarkera stjärna",
|
||||
@@ -420,4 +419,4 @@
|
||||
"Empty!": "Tomt!",
|
||||
"Not Ready...": "Inte redo...",
|
||||
"Error loading page!": "Fel vid laddning av sida!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,7 +72,7 @@
|
||||
"Append folder with .xci": "Додати до теки .xci",
|
||||
"Trim XCI": "Обрізати XCI",
|
||||
"Label trimmed XCI": "Позначити обрізаний XCI",
|
||||
"Multi-threaded USB transfer": "Многопоточ. передача USB",
|
||||
"Multi-threaded USB transfer": "Багатопотокова передача USB",
|
||||
"Dump All Bins": "Дамп всіх BIN-файлів",
|
||||
"Dump XCI": "Дамп XCI",
|
||||
"Dump Card ID Set": "Дамп набору ID картриджа",
|
||||
@@ -106,7 +106,6 @@
|
||||
"Failed to download app!": "Не вдалося завантажити програму!",
|
||||
|
||||
"FTP Install": "Встановлення через FTP",
|
||||
"FTP Install (EXPERIMENTAL)": "Встановлення через FTP (ЕКСПЕРИМЕНТАЛЬНО)",
|
||||
"Connection Type: WiFi | Strength: ": "Тип підключення: WiFi | Сила сигналу: ",
|
||||
"Connection Type: Ethernet": "Тип підключення: Ethernet",
|
||||
"Connection Type: None": "Тип підключення: Немає",
|
||||
@@ -116,9 +115,9 @@
|
||||
"Password:": "Пароль:",
|
||||
"SSID:": "SSID:",
|
||||
"Passphrase:": "Кодова фраза:",
|
||||
"Failed to install via FTP, press B to exit...": "Не вдалося встановити через FTP, натисніть B для виходу...",
|
||||
"Ftp install success!": "Встановлення через FTP успішно завершено.",
|
||||
"Ftp install failed!": "Встановлення через FTP не вдалося.",
|
||||
"Failed to install, press B to exit...": "Не вдалося встановити через FTP, натисніть B для виходу...",
|
||||
"Install success!": "Успішно встановлено",
|
||||
"Install failed!": "Встановлення не вдалося.",
|
||||
"USB Install": "Встановлення через USB",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "Підключено, очікування списку файлів...",
|
||||
@@ -207,8 +206,8 @@
|
||||
"Slow": "Повільно",
|
||||
"Normal": "Середнє",
|
||||
"Fast": "Швидко",
|
||||
"Set left-side menu": "Ліве меню",
|
||||
"Set right-side menu": "Праве меню",
|
||||
"Set left-side menu": "Ліве меню (L)",
|
||||
"Set right-side menu": "Праве меню (R)",
|
||||
"Install options": "Опції встановлення",
|
||||
"Install Options": "Опції встановлення",
|
||||
"Enable sysmmc": "Дозволити в sysMMC",
|
||||
@@ -244,7 +243,7 @@
|
||||
"Creating Control": "Створення контролера",
|
||||
"Creating Meta": "Створення метаданих",
|
||||
"Writing Nca": "Запис NCA",
|
||||
"Updating ncm databse": "Оновлення бази даних NCM",
|
||||
"Updating ncm database": "Оновлення бази даних NCM",
|
||||
"Pushing application record": "Запис даних програми",
|
||||
"Failed to install forwarder": "Не вдалося встановити форвардер",
|
||||
"Unstar": "Прибрати з обраного",
|
||||
@@ -420,4 +419,4 @@
|
||||
"Empty!": "Пусто!",
|
||||
"Not Ready...": "Не готово...",
|
||||
"Error loading page!": "Помилка завантаження сторінки!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,7 +106,6 @@
|
||||
"Failed to download app!": "",
|
||||
|
||||
"FTP Install": "",
|
||||
"FTP Install (EXPERIMENTAL)": "",
|
||||
"Connection Type: WiFi | Strength: ": "",
|
||||
"Connection Type: Ethernet": "",
|
||||
"Connection Type: None": "",
|
||||
@@ -116,9 +115,9 @@
|
||||
"Password:": "",
|
||||
"SSID:": "",
|
||||
"Passphrase:": "",
|
||||
"Failed to install via FTP, press B to exit...": "",
|
||||
"Ftp install success!": "",
|
||||
"Ftp install failed!": "",
|
||||
"Failed to install, press B to exit...": "",
|
||||
"Install success!": "",
|
||||
"Install failed!": "",
|
||||
"USB Install": "",
|
||||
"USB": "",
|
||||
"Connected, waiting for file list...": "",
|
||||
@@ -244,7 +243,7 @@
|
||||
"Creating Control": "Tạo điều khiển",
|
||||
"Creating Meta": "Tạo Meta",
|
||||
"Writing Nca": "Ghi Nca",
|
||||
"Updating ncm databse": "Cập nhật ncm databse",
|
||||
"Updating ncm database": "Cập nhật ncm database",
|
||||
"Pushing application record": "Đẩy ứng dụng",
|
||||
"Failed to install forwarder": "Cài đặt ra ngoài màn hình thất bại",
|
||||
"Unstar": "Xoá yêu thích",
|
||||
@@ -420,4 +419,4 @@
|
||||
"Empty!": "Trống!",
|
||||
"Not Ready...": "Chưa sẵn sàng...",
|
||||
"Error loading page!": "Lỗi tải trang!"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,10 +24,11 @@
|
||||
"Network Options": "网络选项",
|
||||
"Ftp": "FTP",
|
||||
"Mtp": "MTP",
|
||||
"Nxlink": "Nxlink插件提交",
|
||||
"Nxlink Connected": "Nxlink 已连接",
|
||||
"Nxlink Upload": "Nxlink 上传中",
|
||||
"Nxlink Finished": "Nxlink 已结束",
|
||||
"MTP Install": "MTP安装",
|
||||
"Nxlink": "Nxlink上传",
|
||||
"Nxlink Connected": "Nxlink已连接",
|
||||
"Nxlink Upload": "Nxlink上传中",
|
||||
"Nxlink Finished": "Nxlink已结束",
|
||||
"Hdd": "HDD",
|
||||
"Hdd write protect": "HDD写保护",
|
||||
|
||||
@@ -49,11 +50,11 @@
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "拓展",
|
||||
"Misc Options": "拓展设置",
|
||||
"Misc Options": "拓展选项",
|
||||
"Games": "游戏",
|
||||
"Game Options": "游戏选项",
|
||||
"Hide forwarders": "隐藏前端启动",
|
||||
"Launch random game": "开启随机游戏",
|
||||
"Launch random game": "启动随机游戏",
|
||||
"List meta records": "列出元数据记录",
|
||||
"Entries": "条目",
|
||||
"Failed to list application meta entries": "未能列出应用程序元条目",
|
||||
@@ -63,12 +64,18 @@
|
||||
"Select content to dump": "选择要转储的内容",
|
||||
"Dump All": "转储全部内容",
|
||||
"Dump Application": "转储应用程序(本体)",
|
||||
"Dump Patch": "转储补丁(DLC)",
|
||||
"Dump AddOnContent": "转储追加内容(UPD)",
|
||||
"Dump DataPatch": "转储游戏数据",
|
||||
"Dump Patch": "转储更新补丁(UPD)",
|
||||
"Dump AddOnContent": "转储追加内容(DLC)",
|
||||
"Dump DataPatch": "转储数据补丁",
|
||||
"Dump All Bins": "转储全部文件",
|
||||
"Dump XCI": "转储.xci",
|
||||
"Dump Card ID Set": "转储游戏卡ID",
|
||||
"Dump Card UID": "转储游戏卡UID",
|
||||
"Dump Certificate": "转储游戏证书",
|
||||
"Dump Initial Data": "转储初始数据",
|
||||
"Select dump location": "选择转储位置",
|
||||
"microSD card (/dumps/NSP/)": "microSD卡(/dumps/NSP/)",
|
||||
"USB transfer (Switch 2 Switch)": "USB传输(Switch 2 Switch)",
|
||||
"USB transfer (Switch 2 Switch)": "USB传输(Switch对Switch)",
|
||||
"/dev/null (Speed Test)": "/dev/null(速度测试)",
|
||||
"Dumping": "正在转储",
|
||||
"Dump successfull!": "转储成功!",
|
||||
@@ -76,6 +83,33 @@
|
||||
"Success": "成功",
|
||||
"Delete successfull!": "删除成功!",
|
||||
"Delete failed!": "删除失败!",
|
||||
"Title cache": "标题缓存",
|
||||
|
||||
"Saves": "存档",
|
||||
"Save Options": "存档选项",
|
||||
"Account": "账户",
|
||||
"Backup": "备份",
|
||||
"Auto backup": "自动备份",
|
||||
"Auto backup on restore": "还原时自动备份",
|
||||
"Compress backup": "压缩备份",
|
||||
"Are you sure you want to backup save(s)?": "您确定要备份存档吗?",
|
||||
"No saves found in ": "未找到存档 ",
|
||||
"Backed up to ": "已备份到 ",
|
||||
"Backup successfull!": "已备份成功!",
|
||||
"Backup failed!": "备份失败!",
|
||||
"Restore save for: ": "还原备份文件: ",
|
||||
"Are you sure you want to restore ": "您确定要还原已备份文件吗 ",
|
||||
"Restore successfull!": "已还原成功!",
|
||||
"Restore failed!": "还原失败!",
|
||||
"Data Type": "数据类型",
|
||||
"System": "系统",
|
||||
"BCAT": "BCAT数据",
|
||||
"Device": "设备",
|
||||
"Temporary": "临时",
|
||||
"Cache": "缓存",
|
||||
"System BCAT": "系统备份",
|
||||
"Select backup location": "选择备份位置",
|
||||
"Select restore location": "选择还原位置",
|
||||
|
||||
"Themezer": "在线主题",
|
||||
"Themezer Options": "在线主题选项",
|
||||
@@ -93,7 +127,6 @@
|
||||
"Failed to download app!": "下载应用程序失败!",
|
||||
|
||||
"FTP Install": "通过 FTP 安装",
|
||||
"FTP Install (EXPERIMENTAL)": "通过 FTP 安装(实验性)",
|
||||
"Connection Type: WiFi | Strength: ": "连接类型:WiFi |强度:",
|
||||
"Connection Type: Ethernet": "连接类型:以太网",
|
||||
"Connection Type: None": "连接类型:无",
|
||||
@@ -103,9 +136,9 @@
|
||||
"Password:": "密码:",
|
||||
"SSID:": "网络名称:",
|
||||
"Passphrase:": "密码:",
|
||||
"Failed to install via FTP, press B to exit...": "通过 FTP 安装失败,按 B 键退出...",
|
||||
"Ftp install success!": "通过 FTP 安装成功。",
|
||||
"Ftp install failed!": "通过 FTP 安装失败。",
|
||||
"Failed to install, press B to exit...": "通过 FTP 安装失败,按 B 键退出...",
|
||||
"Install success!": "通过 FTP 安装成功。",
|
||||
"Install failed!": "通过 FTP 安装失败。",
|
||||
"USB Install": "通过 USB 安装",
|
||||
"USB": "USB",
|
||||
"Connected, waiting for file list...": "已连接,正在等待文件列表...",
|
||||
@@ -119,14 +152,30 @@
|
||||
"Disable MTP for usb install": "暂时禁用 USB 安装的 MTP 功能",
|
||||
"Re-enabled MTP": "重新启用 MTP",
|
||||
"Installed via usb": "通过 USB 安装",
|
||||
"Usb install success!": "通过 USB 安装成功。",
|
||||
"Usb install failed!": "通过 USB 安装失败。",
|
||||
"Usb install success!": "USB安装成功!",
|
||||
"Usb install failed!": "USB安装失败!",
|
||||
"State: %s | Speed: %s": "状态: %s | 速度: %s",
|
||||
"Detached": "已断开",
|
||||
"Attached": "已连接",
|
||||
"Powered": "已供电",
|
||||
"Default": "默认",
|
||||
"Address": "地址",
|
||||
"Configured": "已配置",
|
||||
"Suspended": "已挂起",
|
||||
"USB 1.0 Low Speed": "USB 1.0 低速",
|
||||
"USB 1.1 Full Speed": "USB 1.1 全速",
|
||||
"USB 2.0 High Speed": "USB 2.0 高速",
|
||||
"USB 3.0 Super Speed": "USB 3.0 超高速",
|
||||
"Drag'n'Drop (NSP, XCI, NSZ, XCZ) to the install folder": "拖放文件(NSP, XCI, NSZ, XCZ)至安装文件夹",
|
||||
"Failed to install via MTP, press B to exit...": "MTP安装失败,请按B键退出...",
|
||||
"MTP install success!": "MTP安装成功!",
|
||||
"MTP install failed!": "MTP安装失败!",
|
||||
"Press B to exit...": "按 B 键退出...",
|
||||
"GameCard Install": "卡带安装",
|
||||
"GameCard": "卡带",
|
||||
"GameCard Install": "游戏卡安装",
|
||||
"GameCard": "游戏卡",
|
||||
"GC": "GC",
|
||||
"System memory %.1f GB": "系统内存 %.1f GB",
|
||||
"microSD card %.1f GB": "SD卡内存 %.1f GB",
|
||||
"System memory %.1f GB": "主机内存 %.1f GB",
|
||||
"microSD card %.1f GB": "microSD卡 %.1f GB",
|
||||
"Exit": "退出",
|
||||
"Install disabled...\nPlease enable installing via the install options.": "安装已禁用...\n请通过安装选项启用安装。",
|
||||
"No GameCard inserted": "未插入游戏卡",
|
||||
@@ -143,8 +192,8 @@
|
||||
"Controller": "控制器",
|
||||
"Pad ": "手柄 ",
|
||||
"HandHeld": "掌机模式",
|
||||
" (Available)": " (可用的)",
|
||||
" (Unsupported)": " (不支持的)",
|
||||
" (Available)": " (可用)",
|
||||
" (Unsupported)": " (不支持)",
|
||||
" (Unconnected)": " (未连接)",
|
||||
"Rotation": "旋转",
|
||||
"0 (Sideways)": "0度",
|
||||
@@ -194,7 +243,7 @@
|
||||
"Restored hbmenu": "已恢复 hbmenu",
|
||||
"Restart Sphaira?": "重启 Sphaira?",
|
||||
"Press OK to restart Sphaira": "按OK键以重启shphaira菜单",
|
||||
"Boost CPU during transfer": "在传输过程中提升CPU",
|
||||
"Boost CPU during transfer": "传输时提升CPU频率",
|
||||
"Text scroll speed": "文本滚动速度",
|
||||
"Slow": "慢",
|
||||
"Normal": "正常",
|
||||
@@ -214,11 +263,11 @@
|
||||
"Skip if already installed": "若已安装则跳过",
|
||||
"Ticket only": "仅安装票据",
|
||||
"Skip base": "跳过基础部分",
|
||||
"Skip patch": "跳过补丁",
|
||||
"Skip dlc": "跳过 DLC(可下载内容)",
|
||||
"Skip patch": "跳过更新补丁(UPD)",
|
||||
"Skip dlc": "跳过追加内容(DLC)",
|
||||
"Skip data patch": "跳过数据补丁",
|
||||
"Skip ticket": "跳过票据",
|
||||
"Skip NCA hash verify": "跳过NCA哈希验证",
|
||||
"Skip NCA hash verify": "跳过 NCA 哈希验证",
|
||||
"Skip RSA header verify": "跳过 RSA 头部验证",
|
||||
"Skip RSA NPDM verify": "跳过 RSA NPDM 验证",
|
||||
"Ignore distribution bit": "忽略分布位",
|
||||
@@ -229,13 +278,13 @@
|
||||
"Dump Options": "转储选项",
|
||||
"Created nested folder": "已创建嵌套文件夹",
|
||||
"Append folder with .xci": "用.xci附加文件夹",
|
||||
"Trim XCI": "修剪 XCI",
|
||||
"Label trimmed XCI": "标记已修剪的XCI",
|
||||
"Trim XCI": "裁剪 XCI",
|
||||
"Label trimmed XCI": "标记已裁剪的XCI",
|
||||
"Multi-threaded USB transfer": "多线程USB传输",
|
||||
|
||||
"Homebrew": "应用列表",
|
||||
"Homebrew": "自制软件",
|
||||
"Apps": "应用",
|
||||
"Homebrew Options": "应用选项",
|
||||
"Homebrew Options": "自制软件选项",
|
||||
"Hide Sphaira": "在应用列表中隐藏Sphaira",
|
||||
"Install Forwarder": "安装前端应用",
|
||||
"WARNING: Installing forwarders will lead to a ban!": "警告:安装前端应用可能导致ban机!",
|
||||
@@ -244,7 +293,7 @@
|
||||
"Creating Control": "正在创建控制器",
|
||||
"Creating Meta": "正在创建元数据",
|
||||
"Writing Nca": "正在写入Nca",
|
||||
"Updating ncm databse": "正在更新ncm数据库",
|
||||
"Updating ncm database": "正在更新ncm数据库",
|
||||
"Pushing application record": "正在推送应用记录",
|
||||
"Failed to install forwarder": "前端应用安装失败",
|
||||
"Unstar": "取消星标",
|
||||
@@ -269,7 +318,7 @@
|
||||
"More by Author": "作者更多作品",
|
||||
"Leave Feedback": "留言反馈",
|
||||
|
||||
"FileBrowser": "文件浏览",
|
||||
"FileBrowser": "文件浏览器",
|
||||
"Files": "文件",
|
||||
"%zd files": "%zd 个文件",
|
||||
"%zd dirs": "%zd 个文件夹",
|
||||
@@ -319,8 +368,8 @@
|
||||
"Upload failed!": "上传失败!",
|
||||
"Hash": "哈希",
|
||||
"Hash Options": "哈希选项",
|
||||
"Hashing": "正在哈希文件",
|
||||
"Failed to hash file...": "哈希文件失败...",
|
||||
"Hashing": "正在计算文件哈希",
|
||||
"Failed to hash file...": "计算文件哈希失败...",
|
||||
"Ignore read only": "忽略只读",
|
||||
"Mount": "挂载",
|
||||
"Sd": "SD卡",
|
||||
@@ -340,7 +389,7 @@
|
||||
"Emulators": "模拟器",
|
||||
"Tools": "工具",
|
||||
"Themes": "主题",
|
||||
"Legacy": "可更新",
|
||||
"Legacy": "旧版",
|
||||
"Sort": "排序",
|
||||
"Size": "按大小",
|
||||
"Size (Star)": "按大小(星标优先)",
|
||||
@@ -377,8 +426,8 @@
|
||||
"Prev": "上一项",
|
||||
"Yes": "是",
|
||||
"No": "否",
|
||||
"On": "开",
|
||||
"Off": "关",
|
||||
"On": "开启",
|
||||
"Off": "关闭",
|
||||
|
||||
"Install": "安装",
|
||||
"Install Selected files?": "安装所选文件?",
|
||||
@@ -417,7 +466,29 @@
|
||||
|
||||
"Loading...": "加载中...",
|
||||
"Loading": "加载中",
|
||||
"Empty!": "空空如野!",
|
||||
"Empty!": "空空如也!",
|
||||
"Not Ready...": "尚未准备好...",
|
||||
"Error loading page!": "页面加载失败!"
|
||||
"Error loading page!": "页面加载失败!",
|
||||
"USB Speed Status": "USB速度状态",
|
||||
"Data Type": "数据类型",
|
||||
"Save Location Selection": "存档位置选择",
|
||||
"MTP Transfer": "MTP传输",
|
||||
"USB Installation": "USB安装",
|
||||
"Archive Data": "存档数据",
|
||||
"Game Card Data": "游戏卡数据",
|
||||
"Storage Path": "存储路径",
|
||||
"MTP Connection": "MTP连接",
|
||||
"USB Device": "USB设备",
|
||||
"Data Category": "数据类别",
|
||||
"Save Management": "存档管理",
|
||||
"Transfer Mode": "传输模式",
|
||||
"Installation Method": "安装方式",
|
||||
"File Type": "文件类型",
|
||||
"Storage Device": "存储设备",
|
||||
"Data Transfer": "数据传输",
|
||||
"Save Data Type": "存档数据类型",
|
||||
"USB Connection": "USB连接",
|
||||
"MTP Settings": "MTP设置",
|
||||
"USB Configuration": "USB配置",
|
||||
"Data Storage": "数据存储"
|
||||
}
|
||||
|
||||
@@ -8,13 +8,13 @@ build_preset() {
|
||||
cmake --build --preset $1
|
||||
}
|
||||
|
||||
build_preset MinSizeRel
|
||||
build_preset Release
|
||||
|
||||
rm -rf out
|
||||
|
||||
# --- SWITCH --- #
|
||||
mkdir -p out/switch/sphaira/
|
||||
cp -r build/MinSizeRel/*.nro out/switch/sphaira/sphaira.nro
|
||||
cp -r build/Release/*.nro out/switch/sphaira/sphaira.nro
|
||||
pushd out
|
||||
zip -r9 sphaira.zip switch
|
||||
popd
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
#include <switch.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#define EXIT_DETECTION_STR "if this isn't replaced i will exit :)"
|
||||
|
||||
// this uses 3-4KiB more than nx-hbloader.
|
||||
// this uses 16KiB less than nx-hbloader.
|
||||
static char g_argv[2048] = {0};
|
||||
// this can and will be modified by libnx if launched nro calls envSetNextLoad().
|
||||
static char g_nextArgv[2048] = {0};
|
||||
@@ -47,7 +46,7 @@ static void fix_nro_path(char* path) {
|
||||
|
||||
// Credit to behemoth
|
||||
// SOURCE: https://github.com/HookedBehemoth/nx-hbloader/commit/7f8000a41bc5e8a6ad96a097ef56634cfd2fabcb
|
||||
static NX_NORETURN void selfExit(void) {
|
||||
static void NX_NORETURN selfExit(void) {
|
||||
Result rc = smInitialize();
|
||||
if (R_FAILED(rc))
|
||||
goto fail0;
|
||||
@@ -162,7 +161,8 @@ static void getOwnProcessHandle(void) {
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 12));
|
||||
|
||||
Thread t;
|
||||
rc = threadCreate(&t, &procHandleReceiveThread, (void*)(uintptr_t)server_handle, NULL, 0x1000, 0x20, 0);
|
||||
u8* stack = g_heapAddr;
|
||||
rc = threadCreate(&t, &procHandleReceiveThread, (void*)(uintptr_t)server_handle, stack, 0x1000, 0x20, 0);
|
||||
if (R_FAILED(rc))
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 10));
|
||||
|
||||
@@ -290,10 +290,10 @@ void NX_NORETURN loadNro(void) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
u8* romfs_dirs = malloc(romfs_header.dirTableSize); // should be 1 entry ("/")
|
||||
u8* romfs_files = malloc(romfs_header.fileTableSize); // should be 2 entries (argv and nro)
|
||||
u8 romfs_dirs[1024 * 2]; // should be 1 entry ("/")
|
||||
u8 romfs_files[1024 * 4]; // should be 2 entries (argv and nro)
|
||||
|
||||
if (!romfs_dirs || !romfs_files) {
|
||||
if (romfs_header.dirTableSize > sizeof(romfs_dirs) || romfs_header.fileTableSize > sizeof(romfs_files)) {
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, LibnxError_OutOfMemory));
|
||||
}
|
||||
|
||||
@@ -317,8 +317,6 @@ void NX_NORETURN loadNro(void) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
free(romfs_dirs);
|
||||
free(romfs_files);
|
||||
fsStorageClose(&s);
|
||||
|
||||
strcpy(g_defaultNroPath, g_nextNroPath);
|
||||
@@ -337,40 +335,24 @@ void NX_NORETURN loadNro(void) {
|
||||
}
|
||||
|
||||
uint8_t *nrobuf = (uint8_t*) g_heapAddr;
|
||||
|
||||
NroStart* start = (NroStart*) (nrobuf + 0);
|
||||
header = (NroHeader*) (nrobuf + sizeof(NroStart));
|
||||
uint8_t* rest = (uint8_t*) (nrobuf + sizeof(NroStart) + sizeof(NroHeader));
|
||||
|
||||
FsFileSystem fs;
|
||||
FsFile f;
|
||||
u64 bytes_read = 0;
|
||||
s64 offset = 0;
|
||||
|
||||
if (R_FAILED(rc = fsOpenSdCardFileSystem(&fs))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
// don't fatal if we don't find the nro, exit to menu
|
||||
FsFile f;
|
||||
if (R_FAILED(rc = fsFsOpenFile(&fs, fixedNextNroPath, FsOpenMode_Read, &f))) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
if (R_FAILED(rc = fsFileRead(&f, offset, start, sizeof(*start), FsReadOption_None, &bytes_read)) ||
|
||||
bytes_read != sizeof(*start)) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
offset += sizeof(*start);
|
||||
|
||||
if (R_FAILED(rc = fsFileRead(&f, offset, header, sizeof(*header), FsReadOption_None, &bytes_read)) ||
|
||||
bytes_read != sizeof(*header) || header->magic != NROHEADER_MAGIC) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
offset += sizeof(*header);
|
||||
|
||||
const size_t rest_size = header->size - (sizeof(NroStart) + sizeof(NroHeader));
|
||||
if (R_FAILED(rc = fsFileRead(&f, offset, rest, rest_size, FsReadOption_None, &bytes_read)) ||
|
||||
bytes_read != rest_size) {
|
||||
u64 bytes_read;
|
||||
if (R_FAILED(rc = fsFileRead(&f, 0, start, g_heapSize, FsReadOption_None, &bytes_read)) ||
|
||||
header->magic != NROHEADER_MAGIC ||
|
||||
bytes_read < sizeof(*start) + sizeof(*header) + header->size) {
|
||||
diagAbortWithResult(rc);
|
||||
}
|
||||
|
||||
@@ -513,13 +495,11 @@ bool __nx_fsdev_support_cwd = false;
|
||||
// u32 __nx_applet_exit_mode = 0;
|
||||
|
||||
void __libnx_initheap(void) {
|
||||
static char g_innerheap[0x4000];
|
||||
|
||||
extern char* fake_heap_start;
|
||||
extern char* fake_heap_end;
|
||||
|
||||
fake_heap_start = &g_innerheap[0];
|
||||
fake_heap_end = &g_innerheap[sizeof(g_innerheap)];
|
||||
fake_heap_start = NULL;
|
||||
fake_heap_end = NULL;
|
||||
}
|
||||
|
||||
void __appInit(void) {
|
||||
@@ -557,7 +537,20 @@ void __appExit(void) {
|
||||
|
||||
}
|
||||
|
||||
// exit() effectively never gets called, so let's stub it out.
|
||||
void __wrap_exit(void) {
|
||||
// exit() effectively never gets called, so let's stub it out.
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 39));
|
||||
}
|
||||
|
||||
// stub alloc calls as they're not used (saves 4KiB).
|
||||
void* __libnx_alloc(size_t size) {
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 40));
|
||||
}
|
||||
|
||||
void* __libnx_aligned_alloc(size_t alignment, size_t size) {
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 41));
|
||||
}
|
||||
|
||||
void __libnx_free(void* p) {
|
||||
diagAbortWithResult(MAKERESULT(Module_HomebrewLoader, 43));
|
||||
}
|
||||
|
||||
@@ -1,6 +1,34 @@
|
||||
cmake_minimum_required(VERSION 3.13)
|
||||
|
||||
set(sphaira_VERSION 0.12.0)
|
||||
# generic options.
|
||||
option(ENABLE_NVJPG "" OFF)
|
||||
option(ENABLE_NSZ "enables exporting to nsz" ON)
|
||||
|
||||
# lib options.
|
||||
option(ENABLE_LIBUSBHSFS "enables FAT/exFAT hdd mounting" ON)
|
||||
option(ENABLE_LIBUSBDVD "enables cd/dvd/iso/cue mounting" ON)
|
||||
option(ENABLE_FTPSRV "enables MTP server support" ON)
|
||||
option(ENABLE_LIBHAZE "enables MTP server support" ON)
|
||||
|
||||
# audio options.
|
||||
option(ENABLE_AUDIO_MP3 "" ON)
|
||||
option(ENABLE_AUDIO_OGG "" ON)
|
||||
option(ENABLE_AUDIO_WAV "" ON)
|
||||
option(ENABLE_AUDIO_FLAC "" ON)
|
||||
|
||||
# devoptab options.
|
||||
option(ENABLE_DEVOPTAB_HTTP "" ON)
|
||||
option(ENABLE_DEVOPTAB_NFS "" ON)
|
||||
option(ENABLE_DEVOPTAB_SMB2 "" ON)
|
||||
option(ENABLE_DEVOPTAB_FTP "" ON)
|
||||
option(ENABLE_DEVOPTAB_WEBDAV "" ON)
|
||||
# disable by default because we are CPU bound for upload/download.
|
||||
# max speed is 8MiB/s, which is fine for wifi, but awful for ethernet.
|
||||
# other clients get 36-40MiB/s.
|
||||
# it also adds 230k to binary size, and i don't think anyone will use it.
|
||||
option(ENABLE_DEVOPTAB_SFTP "" OFF)
|
||||
|
||||
set(sphaira_VERSION 1.0.0)
|
||||
|
||||
project(sphaira
|
||||
VERSION ${sphaira_VERSION}
|
||||
@@ -30,12 +58,18 @@ execute_process(
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
|
||||
set(sphaira_VERSION_HASH "${sphaira_VERSION} [${GIT_COMMIT}]")
|
||||
if (DEFINED sphaira_VERSION_OVERRIDE)
|
||||
set(sphaira_DISPLAY_VERSION "${sphaira_VERSION_OVERRIDE} [${GIT_COMMIT}]")
|
||||
else()
|
||||
set(sphaira_DISPLAY_VERSION "${sphaira_VERSION} [${GIT_COMMIT}]")
|
||||
endif()
|
||||
|
||||
add_executable(sphaira
|
||||
source/ui/menus/appstore.cpp
|
||||
source/ui/menus/file_viewer.cpp
|
||||
source/ui/menus/image_viewer.cpp
|
||||
source/ui/menus/filebrowser.cpp
|
||||
source/ui/menus/file_picker.cpp
|
||||
source/ui/menus/homebrew.cpp
|
||||
source/ui/menus/irs_menu.cpp
|
||||
source/ui/menus/main_menu.cpp
|
||||
@@ -44,10 +78,12 @@ add_executable(sphaira
|
||||
source/ui/menus/themezer.cpp
|
||||
source/ui/menus/ghdl.cpp
|
||||
source/ui/menus/usb_menu.cpp
|
||||
source/ui/menus/ftp_menu.cpp
|
||||
source/ui/menus/gc_menu.cpp
|
||||
source/ui/menus/game_menu.cpp
|
||||
source/ui/menus/game_meta_menu.cpp
|
||||
source/ui/menus/game_nca_menu.cpp
|
||||
source/ui/menus/grid_menu_base.cpp
|
||||
source/ui/menus/install_stream_menu_base.cpp
|
||||
|
||||
source/ui/error_box.cpp
|
||||
source/ui/notification.cpp
|
||||
@@ -60,6 +96,7 @@ add_executable(sphaira
|
||||
source/ui/widget.cpp
|
||||
source/ui/list.cpp
|
||||
source/ui/scrolling_text.cpp
|
||||
source/ui/music_player.cpp
|
||||
|
||||
source/app.cpp
|
||||
source/download.cpp
|
||||
@@ -78,35 +115,58 @@ add_executable(sphaira
|
||||
source/web.cpp
|
||||
source/hasher.cpp
|
||||
source/i18n.cpp
|
||||
source/ftpsrv_helper.cpp
|
||||
source/threaded_file_transfer.cpp
|
||||
source/title_info.cpp
|
||||
source/minizip_helper.cpp
|
||||
|
||||
source/utils/utils.cpp
|
||||
source/utils/audio.cpp
|
||||
source/utils/devoptab_common.cpp
|
||||
source/utils/devoptab_romfs.cpp
|
||||
source/utils/devoptab_save.cpp
|
||||
source/utils/devoptab_nro.cpp
|
||||
source/utils/devoptab_nca.cpp
|
||||
source/utils/devoptab_nsp.cpp
|
||||
source/utils/devoptab_xci.cpp
|
||||
source/utils/devoptab_zip.cpp
|
||||
source/utils/devoptab_bfsar.cpp
|
||||
source/utils/devoptab_vfs.cpp
|
||||
source/utils/devoptab_fatfs.cpp
|
||||
source/utils/devoptab_game.cpp
|
||||
source/utils/devoptab_mounts.cpp
|
||||
source/utils/devoptab.cpp
|
||||
|
||||
source/usb/base.cpp
|
||||
source/usb/usbds.cpp
|
||||
source/usb/usbhs.cpp
|
||||
source/usb/usb_uploader.cpp
|
||||
source/usb/usb_installer.cpp
|
||||
source/usb/usb_dumper.cpp
|
||||
|
||||
source/yati/yati.cpp
|
||||
source/yati/container/nsp.cpp
|
||||
source/yati/container/xci.cpp
|
||||
source/yati/source/file.cpp
|
||||
source/yati/source/usb.cpp
|
||||
source/yati/source/stream.cpp
|
||||
source/yati/source/stream_file.cpp
|
||||
|
||||
source/yati/nx/es.cpp
|
||||
source/yati/nx/keys.cpp
|
||||
source/yati/nx/nca.cpp
|
||||
source/yati/nx/ncz.cpp
|
||||
source/yati/nx/ncm.cpp
|
||||
source/yati/nx/ns.cpp
|
||||
|
||||
source/yati/nx/nxdumptool_rsa.c
|
||||
source/yati/nx/nxdumptool/save.c
|
||||
)
|
||||
|
||||
target_compile_definitions(sphaira PRIVATE
|
||||
-DAPP_VERSION="${sphaira_VERSION}"
|
||||
-DAPP_VERSION_HASH="${sphaira_VERSION_HASH}"
|
||||
-DAPP_DISPLAY_VERSION="${sphaira_DISPLAY_VERSION}"
|
||||
-DCURL_NO_OLDIES=1
|
||||
-DDEV_BUILD=$<BOOL:${DEV_BUILD}>
|
||||
-DZSTD_STATIC_LINKING_ONLY=1
|
||||
)
|
||||
|
||||
target_compile_options(sphaira PRIVATE
|
||||
@@ -128,6 +188,8 @@ target_compile_options(sphaira PRIVATE
|
||||
# disabled as it warns for strcat 2 paths together, but it will never
|
||||
# overflow due to fs enforcing a max path len anyway.
|
||||
-Wno-format-truncation
|
||||
# many false positives when LTO is not enabled.
|
||||
-Wno-suggest-final-types
|
||||
|
||||
# the below are taken from my gba emulator, they've served me well ;)
|
||||
-Wformat-overflow=2
|
||||
@@ -144,28 +206,15 @@ target_compile_options(sphaira PRIVATE
|
||||
-Wcast-qual
|
||||
-Wcast-align
|
||||
-Wimplicit-fallthrough=5
|
||||
-Wsuggest-final-types
|
||||
-Wuninitialized
|
||||
)
|
||||
|
||||
include(FetchContent)
|
||||
set(FETCHCONTENT_QUIET FALSE)
|
||||
|
||||
FetchContent_Declare(ftpsrv
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
|
||||
# GIT_TAG 1.2.2
|
||||
GIT_TAG 8c18431
|
||||
SOURCE_SUBDIR NONE
|
||||
)
|
||||
|
||||
FetchContent_Declare(libhaze
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
|
||||
GIT_TAG 8e16df2
|
||||
)
|
||||
|
||||
FetchContent_Declare(libpulsar
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git
|
||||
GIT_TAG de656e4
|
||||
GIT_TAG ac7bc97
|
||||
)
|
||||
|
||||
FetchContent_Declare(nanovg
|
||||
@@ -180,12 +229,12 @@ FetchContent_Declare(stb
|
||||
|
||||
FetchContent_Declare(yyjson
|
||||
GIT_REPOSITORY https://github.com/ibireme/yyjson.git
|
||||
GIT_TAG 0.11.1
|
||||
GIT_TAG 0.12.0
|
||||
)
|
||||
|
||||
FetchContent_Declare(minIni
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/minIni-nx.git
|
||||
GIT_TAG 11cac8b
|
||||
GIT_TAG 6e952b6
|
||||
)
|
||||
|
||||
FetchContent_Declare(zstd
|
||||
@@ -194,46 +243,301 @@ FetchContent_Declare(zstd
|
||||
SOURCE_SUBDIR build/cmake
|
||||
)
|
||||
|
||||
FetchContent_Declare(libusbhsfs
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git
|
||||
GIT_TAG db2bf2a
|
||||
)
|
||||
|
||||
FetchContent_Declare(libnxtc
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libnxtc.git
|
||||
GIT_TAG 0d369b8
|
||||
GIT_TAG 88ce3d8
|
||||
)
|
||||
|
||||
FetchContent_Declare(nvjpg
|
||||
GIT_REPOSITORY https://github.com/averne/oss-nvjpg.git
|
||||
GIT_TAG fdcaba8
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/oss-nvjpg.git
|
||||
GIT_TAG 45680e7
|
||||
)
|
||||
|
||||
set(USE_NEW_ZSTD ON)
|
||||
# has issues with some homebrew and game icons (oxenfree, overwatch2).
|
||||
set(USE_NVJPG ON)
|
||||
FetchContent_Declare(dr_libs
|
||||
GIT_REPOSITORY https://github.com/mackron/dr_libs.git
|
||||
GIT_TAG b962384
|
||||
SOURCE_SUBDIR NONE
|
||||
)
|
||||
|
||||
if (ENABLE_NVJPG)
|
||||
FetchContent_Declare(nvjpg
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/oss-nvjpg.git
|
||||
GIT_TAG 45680e7
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(nvjpg)
|
||||
|
||||
add_library(nvjpg
|
||||
${nvjpg_SOURCE_DIR}/lib/decoder.cpp
|
||||
${nvjpg_SOURCE_DIR}/lib/image.cpp
|
||||
${nvjpg_SOURCE_DIR}/lib/surface.cpp
|
||||
)
|
||||
|
||||
target_include_directories(nvjpg PUBLIC ${nvjpg_SOURCE_DIR}/include)
|
||||
set_target_properties(nvjpg PROPERTIES CXX_STANDARD 26)
|
||||
|
||||
target_link_libraries(nvjpg PRIVATE nvjpg)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_NVJPG)
|
||||
endif()
|
||||
|
||||
if (ENABLE_NSZ)
|
||||
target_sources(sphaira PRIVATE source/utils/nsz_dumper.cpp)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_NSZ)
|
||||
endif()
|
||||
|
||||
if (ENABLE_LIBUSBHSFS)
|
||||
# enable this if you want ntfs and ext4 support, at the cost of a huge final binary size.
|
||||
set(USBHSFS_GPL OFF)
|
||||
set(USBHSFS_SXOS_DISABLE ON)
|
||||
|
||||
FetchContent_Declare(libusbhsfs
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git
|
||||
GIT_TAG 625269b
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(libusbhsfs)
|
||||
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_LIBUSBHSFS)
|
||||
target_link_libraries(sphaira PRIVATE libusbhsfs)
|
||||
else()
|
||||
target_sources(sphaira PRIVATE source/ff16/ffunicode.c)
|
||||
endif()
|
||||
|
||||
if (ENABLE_LIBUSBDVD)
|
||||
FetchContent_Declare(libusbdvd
|
||||
GIT_REPOSITORY https://github.com/proconsule/libusbdvd.git
|
||||
GIT_TAG 3cb0613
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(libusbdvd)
|
||||
|
||||
add_library(libusbdvd
|
||||
${libusbdvd_SOURCE_DIR}/source/usbdvd.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/usbdvd_scsi.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/usbdvd_utils.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/fs/usbdvd_datadisc.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs/audiocdfs.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs/cdaudio_devoptab.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/fs/iso9660/usbdvd_iso9660.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/fs/iso9660/iso9660_devoptab.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/fs/udf/usbdvd_udf.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/fs/udf/udf_devoptab.cpp
|
||||
${libusbdvd_SOURCE_DIR}/source/os/switch/switch_usb.cpp
|
||||
|
||||
)
|
||||
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/)
|
||||
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/os/switch)
|
||||
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs)
|
||||
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/audiocdfs)
|
||||
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/iso9660)
|
||||
target_include_directories(libusbdvd PRIVATE ${libusbdvd_SOURCE_DIR}/source/fs/udf)
|
||||
target_include_directories(libusbdvd PUBLIC ${libusbdvd_SOURCE_DIR}/include)
|
||||
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_LIBUSBDVD)
|
||||
target_link_libraries(sphaira PRIVATE libusbdvd)
|
||||
target_sources(sphaira PRIVATE source/usbdvd.cpp)
|
||||
endif()
|
||||
|
||||
if (ENABLE_FTPSRV)
|
||||
FetchContent_Declare(ftpsrv
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/ftpsrv.git
|
||||
GIT_TAG 7c82402
|
||||
SOURCE_SUBDIR NONE
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(ftpsrv)
|
||||
|
||||
set(FTPSRV_LIB_BUILD TRUE)
|
||||
set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
|
||||
set(FTPSRV_LIB_PATH_SIZE 0x301)
|
||||
set(FTPSRV_LIB_SESSIONS 16)
|
||||
set(FTPSRV_LIB_BUF_SIZE 1024*64)
|
||||
|
||||
set(FTPSRV_LIB_CUSTOM_DEFINES
|
||||
USE_VFS_SAVE=$<BOOL:FALSE>
|
||||
USE_VFS_STORAGE=$<BOOL:TRUE>
|
||||
# disabled as it may conflict with the gamecard menu.
|
||||
USE_VFS_GC=$<BOOL:FALSE>
|
||||
USE_VFS_USBHSFS=$<BOOL:FALSE>
|
||||
VFS_NX_BUFFER_IO=$<BOOL:TRUE>
|
||||
# let sphaira handle init / closing of the hdd.
|
||||
USE_VFS_USBHSFS_INIT=$<BOOL:FALSE>
|
||||
# disable romfs mounting as otherwise we cannot write / modify sphaira.nro
|
||||
USE_VFS_ROMFS=$<BOOL:FALSE>
|
||||
FTP_SOCKET_HEADER="${ftpsrv_SOURCE_DIR}/src/platform/nx/socket_nx.h"
|
||||
)
|
||||
|
||||
add_subdirectory(${ftpsrv_SOURCE_DIR} binary_dir)
|
||||
|
||||
add_library(ftpsrv_helper
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/utils.c
|
||||
)
|
||||
|
||||
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv)
|
||||
target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platform)
|
||||
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_FTPSRV)
|
||||
target_link_libraries(sphaira PRIVATE ftpsrv_helper)
|
||||
|
||||
target_sources(sphaira PRIVATE
|
||||
source/ftpsrv_helper.cpp
|
||||
source/ui/menus/ftp_menu.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ENABLE_LIBHAZE)
|
||||
FetchContent_Declare(libhaze
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
|
||||
GIT_TAG 81154c1
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(libhaze)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_LIBHAZE)
|
||||
target_link_libraries(sphaira PRIVATE libhaze)
|
||||
|
||||
target_sources(sphaira PRIVATE
|
||||
source/haze_helper.cpp
|
||||
source/ui/menus/mtp_menu.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ENABLE_DEVOPTAB_HTTP)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_HTTP)
|
||||
target_sources(sphaira PRIVATE source/utils/devoptab_http.cpp)
|
||||
endif()
|
||||
|
||||
if (ENABLE_DEVOPTAB_NFS)
|
||||
FetchContent_Declare(libnfs
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libnfs.git
|
||||
GIT_TAG 65f3e11
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(libnfs)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_NFS)
|
||||
target_link_libraries(sphaira PRIVATE nfs)
|
||||
target_sources(sphaira PRIVATE source/utils/devoptab_nfs.cpp)
|
||||
|
||||
# todo: fix this upstream as nfs should export these folders.
|
||||
target_include_directories(sphaira PRIVATE
|
||||
${libnfs_SOURCE_DIR}/include
|
||||
${libnfs_SOURCE_DIR}/include/nfsc
|
||||
${libnfs_SOURCE_DIR}/nfs
|
||||
)
|
||||
endif()
|
||||
|
||||
if (ENABLE_DEVOPTAB_SMB2)
|
||||
FetchContent_Declare(libsmb2
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libsmb2.git
|
||||
GIT_TAG 867beea
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(libsmb2)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_SMB2)
|
||||
target_link_libraries(sphaira PRIVATE smb2)
|
||||
target_sources(sphaira PRIVATE source/utils/devoptab_smb2.cpp)
|
||||
endif()
|
||||
|
||||
if (ENABLE_DEVOPTAB_FTP)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_FTP)
|
||||
target_sources(sphaira PRIVATE source/utils/devoptab_ftp.cpp)
|
||||
endif()
|
||||
|
||||
if (ENABLE_DEVOPTAB_SFTP)
|
||||
# set to build from source, otherwise it will link against the older dkp libssh2.
|
||||
if (1)
|
||||
set(CRYPTO_BACKEND mbedTLS)
|
||||
set(ENABLE_ZLIB_COMPRESSION ON)
|
||||
set(ENABLE_DEBUG_LOGGING OFF)
|
||||
set(BUILD_EXAMPLES OFF)
|
||||
set(BUILD_TESTING OFF)
|
||||
set(LINT OFF)
|
||||
|
||||
FetchContent_Declare(libssh2
|
||||
GIT_REPOSITORY https://github.com/libssh2/libssh2.git
|
||||
# GIT_TAG a0dafb3 # latest commit, works fine, but i'll stick to main release.
|
||||
GIT_TAG libssh2-1.11.1
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(libssh2)
|
||||
target_link_libraries(sphaira PRIVATE libssh2::libssh2)
|
||||
else()
|
||||
include(FindPkgConfig)
|
||||
pkg_check_modules(LIBSSH2 libssh2 REQUIRED)
|
||||
target_include_directories(sphaira PRIVATE ${LIBSSH2_INCLUDE_DIRS})
|
||||
target_link_libraries(sphaira PRIVATE ${LIBSSH2_LIBRARIES})
|
||||
endif()
|
||||
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_SFTP)
|
||||
target_sources(sphaira PRIVATE source/utils/devoptab_sftp.cpp)
|
||||
endif()
|
||||
|
||||
if (ENABLE_DEVOPTAB_WEBDAV)
|
||||
set(PUGIXML_NO_EXCEPTIONS ON)
|
||||
set(PUGIXML_WCHAR_MODE OFF)
|
||||
|
||||
FetchContent_Declare(pugixml
|
||||
GIT_REPOSITORY https://github.com/zeux/pugixml.git
|
||||
GIT_TAG v1.15
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(pugixml)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_DEVOPTAB_WEBDAV)
|
||||
target_link_libraries(sphaira PRIVATE pugixml)
|
||||
target_sources(sphaira PRIVATE source/utils/devoptab_webdav.cpp)
|
||||
endif()
|
||||
|
||||
if (ENABLE_AUDIO_MP3)
|
||||
FetchContent_Declare(id3v2lib
|
||||
GIT_REPOSITORY https://github.com/larsbs/id3v2lib.git
|
||||
GIT_TAG 141ffb8
|
||||
)
|
||||
|
||||
FetchContent_MakeAvailable(id3v2lib)
|
||||
target_link_libraries(sphaira PRIVATE id3v2lib)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_MP3)
|
||||
endif()
|
||||
|
||||
if (ENABLE_AUDIO_OGG)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_OGG)
|
||||
endif()
|
||||
|
||||
if (ENABLE_AUDIO_WAV)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_WAV)
|
||||
endif()
|
||||
|
||||
if (ENABLE_AUDIO_FLAC)
|
||||
target_compile_definitions(sphaira PRIVATE ENABLE_AUDIO_FLAC)
|
||||
endif()
|
||||
|
||||
# ztsd
|
||||
set(ZSTD_BUILD_STATIC ON)
|
||||
set(ZSTD_BUILD_SHARED OFF)
|
||||
set(ZSTD_BUILD_COMPRESSION OFF)
|
||||
set(ZSTD_BUILD_COMPRESSION ${ENABLE_NSZ})
|
||||
set(ZSTD_MULTITHREAD_SUPPORT ${ENABLE_NSZ})
|
||||
set(ZSTD_BUILD_DECOMPRESSION ON)
|
||||
set(ZSTD_BUILD_DICTBUILDER OFF)
|
||||
set(ZSTD_LEGACY_SUPPORT OFF)
|
||||
set(ZSTD_MULTITHREAD_SUPPORT OFF)
|
||||
set(ZSTD_BUILD_PROGRAMS OFF)
|
||||
set(ZSTD_BUILD_TESTS OFF)
|
||||
|
||||
# minini
|
||||
set(MININI_LIB_NAME minIni)
|
||||
set(MININI_USE_STDIO ON)
|
||||
set(MININI_USE_NX OFF)
|
||||
set(MININI_USE_FLOAT OFF)
|
||||
set(MININI_USE_FLOAT ON)
|
||||
|
||||
# nanovg
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug" OR CMAKE_BUILD_TYPE STREQUAL "RelWithDebInfo")
|
||||
set(NANOVG_DEBUG ON)
|
||||
endif()
|
||||
set(NANOVG_NO_JPEG OFF)
|
||||
set(NANOVG_NO_PNG OFF)
|
||||
set(NANOVG_NO_BMP ON)
|
||||
set(NANOVG_NO_BMP OFF)
|
||||
set(NANOVG_NO_PSD ON)
|
||||
set(NANOVG_NO_TGA ON)
|
||||
set(NANOVG_NO_GIF ON)
|
||||
@@ -241,6 +545,8 @@ set(NANOVG_NO_HDR ON)
|
||||
set(NANOVG_NO_PIC ON)
|
||||
set(NANOVG_NO_PNM ON)
|
||||
|
||||
# yyjson
|
||||
set(YYJSON_INSTALL OFF)
|
||||
set(YYJSON_DISABLE_READER OFF)
|
||||
set(YYJSON_DISABLE_WRITER OFF)
|
||||
set(YYJSON_DISABLE_UTILS ON)
|
||||
@@ -249,63 +555,23 @@ set(YYJSON_DISABLE_NON_STANDARD ON)
|
||||
set(YYJSON_DISABLE_UTF8_VALIDATION ON)
|
||||
set(YYJSON_DISABLE_UNALIGNED_MEMORY_ACCESS OFF)
|
||||
|
||||
# enable this if you want ntfs and ext4 support, at the cost of a huge final binary size.
|
||||
set(USBHSFS_GPL OFF)
|
||||
set(USBHSFS_SXOS_DISABLE ON)
|
||||
|
||||
FetchContent_MakeAvailable(
|
||||
ftpsrv
|
||||
libhaze
|
||||
libpulsar
|
||||
nanovg
|
||||
stb
|
||||
minIni
|
||||
yyjson
|
||||
zstd
|
||||
libusbhsfs
|
||||
libnxtc
|
||||
nvjpg
|
||||
dr_libs
|
||||
)
|
||||
|
||||
set(FTPSRV_LIB_BUILD TRUE)
|
||||
set(FTPSRV_LIB_SOCK_UNISTD TRUE)
|
||||
set(FTPSRV_LIB_VFS_CUSTOM ${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.h)
|
||||
set(FTPSRV_LIB_PATH_SIZE 0x301)
|
||||
set(FTPSRV_LIB_SESSIONS 16)
|
||||
set(FTPSRV_LIB_BUF_SIZE 1024*64)
|
||||
|
||||
set(FTPSRV_LIB_CUSTOM_DEFINES
|
||||
USE_VFS_SAVE=$<BOOL:FALSE>
|
||||
USE_VFS_STORAGE=$<BOOL:TRUE>
|
||||
# disabled as it may conflict with the gamecard menu.
|
||||
USE_VFS_GC=$<BOOL:FALSE>
|
||||
USE_VFS_USBHSFS=$<BOOL:TRUE>
|
||||
VFS_NX_BUFFER_IO=$<BOOL:TRUE>
|
||||
# let sphaira handle init / closing of the hdd.
|
||||
USE_VFS_USBHSFS_INIT=$<BOOL:FALSE>
|
||||
# disable romfs mounting as otherwise we cannot write / modify sphaira.nro
|
||||
USE_VFS_ROMFS=$<BOOL:FALSE>
|
||||
)
|
||||
|
||||
add_subdirectory(${ftpsrv_SOURCE_DIR} binary_dir)
|
||||
|
||||
add_library(ftpsrv_helper
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs_nx.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_none.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_root.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_fs.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_storage.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_stdio.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/vfs/vfs_nx_hdd.c
|
||||
${ftpsrv_SOURCE_DIR}/src/platform/nx/utils.c
|
||||
)
|
||||
|
||||
target_link_libraries(ftpsrv_helper PUBLIC ftpsrv libusbhsfs)
|
||||
target_include_directories(ftpsrv_helper PUBLIC ${ftpsrv_SOURCE_DIR}/src/platform)
|
||||
|
||||
add_library(stb INTERFACE)
|
||||
target_include_directories(stb INTERFACE ${stb_SOURCE_DIR})
|
||||
|
||||
add_library(dr_libs INTERFACE)
|
||||
target_include_directories(dr_libs INTERFACE ${dr_libs_SOURCE_DIR})
|
||||
|
||||
add_library(libnxtc
|
||||
${libnxtc_SOURCE_DIR}/source/nxtc.c
|
||||
${libnxtc_SOURCE_DIR}/source/nxtc_log.c
|
||||
@@ -313,16 +579,6 @@ add_library(libnxtc
|
||||
)
|
||||
target_include_directories(libnxtc PUBLIC ${libnxtc_SOURCE_DIR}/include)
|
||||
|
||||
if (USE_NVJPG)
|
||||
add_library(nvjpg
|
||||
${nvjpg_SOURCE_DIR}/lib/decoder.cpp
|
||||
${nvjpg_SOURCE_DIR}/lib/image.cpp
|
||||
${nvjpg_SOURCE_DIR}/lib/surface.cpp
|
||||
)
|
||||
target_include_directories(nvjpg PUBLIC ${nvjpg_SOURCE_DIR}/include)
|
||||
set_target_properties(nvjpg PROPERTIES CXX_STANDARD 26)
|
||||
endif()
|
||||
|
||||
find_package(ZLIB REQUIRED)
|
||||
find_library(minizip_lib minizip REQUIRED)
|
||||
find_path(minizip_inc minizip REQUIRED)
|
||||
@@ -331,10 +587,12 @@ find_package(CURL REQUIRED)
|
||||
find_path(mbedtls_inc mbedtls REQUIRED)
|
||||
find_library(mbedcrypto_lib mbedcrypto REQUIRED)
|
||||
|
||||
if (NOT USE_NEW_ZSTD)
|
||||
find_path(zstd_inc zstd.h REQUIRED)
|
||||
find_library(zstd_lib zstd REQUIRED)
|
||||
endif()
|
||||
add_library(fatfs
|
||||
source/ff16/diskio.c
|
||||
source/ff16/ff.c
|
||||
)
|
||||
|
||||
target_include_directories(fatfs PUBLIC source/ff16)
|
||||
|
||||
set_target_properties(sphaira PROPERTIES
|
||||
C_STANDARD 23
|
||||
@@ -344,15 +602,15 @@ set_target_properties(sphaira PROPERTIES
|
||||
)
|
||||
|
||||
target_link_libraries(sphaira PRIVATE
|
||||
ftpsrv_helper
|
||||
libhaze
|
||||
libpulsar
|
||||
minIni
|
||||
nanovg
|
||||
stb
|
||||
yyjson
|
||||
# libusbhsfs
|
||||
libnxtc
|
||||
fatfs
|
||||
dr_libs
|
||||
libzstd_static
|
||||
|
||||
${minizip_lib}
|
||||
ZLIB::ZLIB
|
||||
@@ -360,24 +618,11 @@ target_link_libraries(sphaira PRIVATE
|
||||
${mbedcrypto_lib}
|
||||
)
|
||||
|
||||
if (USE_NEW_ZSTD)
|
||||
message(STATUS "USING UPSTREAM ZSTD")
|
||||
target_link_libraries(sphaira PRIVATE libzstd_static)
|
||||
else()
|
||||
message(STATUS "USING LOCAL ZSTD")
|
||||
target_link_libraries(sphaira PRIVATE ${zstd_lib})
|
||||
target_include_directories(sphaira PRIVATE ${zstd_inc})
|
||||
endif()
|
||||
|
||||
if (USE_NVJPG)
|
||||
target_link_libraries(sphaira PRIVATE nvjpg)
|
||||
target_compile_definitions(sphaira PRIVATE USE_NVJPG)
|
||||
endif()
|
||||
|
||||
target_include_directories(sphaira PRIVATE
|
||||
include
|
||||
${minizip_inc}
|
||||
${mbedtls_inc}
|
||||
include/yati/nx/nxdumptool
|
||||
)
|
||||
|
||||
# copy the romfs
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
|
||||
#include "nanovg.h"
|
||||
#include "nanovg/dk_renderer.hpp"
|
||||
#include "pulsar.h"
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/notification.hpp"
|
||||
#include "owo.hpp"
|
||||
#include "option.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "log.hpp"
|
||||
#include "utils/audio.hpp"
|
||||
|
||||
#ifdef USE_NVJPG
|
||||
#include <nvjpg.hpp>
|
||||
@@ -18,19 +18,11 @@
|
||||
#include <string>
|
||||
#include <span>
|
||||
#include <optional>
|
||||
#include <utility>
|
||||
|
||||
namespace sphaira {
|
||||
|
||||
enum SoundEffect {
|
||||
SoundEffect_Music,
|
||||
SoundEffect_Focus,
|
||||
SoundEffect_Scroll,
|
||||
SoundEffect_Limit,
|
||||
SoundEffect_Startup,
|
||||
SoundEffect_Install,
|
||||
SoundEffect_Error,
|
||||
SoundEffect_MAX,
|
||||
};
|
||||
using SoundEffect = audio::SoundEffect;
|
||||
|
||||
enum class LaunchType {
|
||||
Normal,
|
||||
@@ -58,12 +50,19 @@ public:
|
||||
static void Exit();
|
||||
static void ExitRestart();
|
||||
static auto GetVg() -> NVGcontext*;
|
||||
static void Push(std::shared_ptr<ui::Widget>);
|
||||
|
||||
static void Push(std::unique_ptr<ui::Widget>&&);
|
||||
|
||||
template<ui::DerivedFromWidget T, typename... Args>
|
||||
static void Push(Args&&... args) {
|
||||
Push(std::make_unique<T>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// pops all widgets above a menu
|
||||
static void PopToMenu();
|
||||
|
||||
// this is thread safe
|
||||
static void Notify(std::string text, ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
|
||||
static void Notify(const std::string& text, ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
|
||||
static void Notify(ui::NotifEntry entry);
|
||||
static void NotifyPop(ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
|
||||
static void NotifyClear(ui::NotifEntry::Side side = ui::NotifEntry::Side::RIGHT);
|
||||
@@ -95,12 +94,14 @@ public:
|
||||
static auto GetInstallSysmmcEnable() -> bool;
|
||||
static auto GetInstallEmummcEnable() -> bool;
|
||||
static auto GetInstallSdEnable() -> bool;
|
||||
static auto GetInstallPrompt() -> bool;
|
||||
static auto GetThemeMusicEnable() -> bool;
|
||||
static auto Get12HourTimeEnable() -> bool;
|
||||
static auto GetLanguage() -> long;
|
||||
static auto GetTextScrollSpeed() -> long;
|
||||
|
||||
static auto GetNszCompressLevel() -> u8;
|
||||
static auto GetNszThreadCount() -> u8;
|
||||
static auto GetNszBlockExponent() -> u8;
|
||||
|
||||
static void SetMtpEnable(bool enable);
|
||||
static void SetFtpEnable(bool enable);
|
||||
static void SetNxlinkEnable(bool enable);
|
||||
@@ -125,10 +126,18 @@ public:
|
||||
static void DisplayThemeOptions(bool left_side = true);
|
||||
// todo:
|
||||
static void DisplayNetworkOptions(bool left_side = true);
|
||||
static void DisplayMiscOptions(bool left_side = true);
|
||||
static void DisplayMenuOptions(bool left_side = true);
|
||||
static void DisplayAdvancedOptions(bool left_side = true);
|
||||
static void DisplayInstallOptions(bool left_side = true);
|
||||
static void DisplayDumpOptions(bool left_side = true);
|
||||
static void DisplayFtpOptions(bool left_side = true);
|
||||
static void DisplayMtpOptions(bool left_side = true);
|
||||
static void DisplayHddOptions(bool left_side = true);
|
||||
|
||||
// helper for sidebar options to toggle install on/off
|
||||
static void ShowEnableInstallPromptOption(option::OptionBool& option, bool& enable);
|
||||
// displays an option box to enable installing, shows warning.
|
||||
static void ShowEnableInstallPrompt();
|
||||
|
||||
void Draw();
|
||||
void Update();
|
||||
@@ -141,8 +150,15 @@ public:
|
||||
|
||||
void LoadTheme(const ThemeMeta& meta);
|
||||
void CloseTheme();
|
||||
void CloseThemeBackgroundMusic();
|
||||
void ScanThemes(const std::string& path);
|
||||
void ScanThemeEntries();
|
||||
void LoadAndPlayThemeMusic();
|
||||
static Result SetDefaultBackgroundMusic(fs::Fs* fs, const fs::FsPath& path);
|
||||
static void SetBackgroundMusicPause(bool pause);
|
||||
|
||||
static Result GetSdSize(s64* free, s64* total);
|
||||
static Result GetEmmcSize(s64* free, s64* total);
|
||||
|
||||
// helper that converts 1.2.3 to a u32 used for comparisons.
|
||||
static auto GetVersionFromString(const char* str) -> u32;
|
||||
@@ -192,6 +208,54 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
static void SetBoostMode(bool enable, bool force = false) {
|
||||
static Mutex mutex{};
|
||||
static int ref_count{};
|
||||
|
||||
mutexLock(&mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&mutex));
|
||||
|
||||
if (enable) {
|
||||
ref_count++;
|
||||
appletSetCpuBoostMode(ApmCpuBoostMode_FastLoad);
|
||||
} else {
|
||||
if (ref_count) {
|
||||
ref_count--;
|
||||
}
|
||||
}
|
||||
|
||||
if (!ref_count || force) {
|
||||
ref_count = 0;
|
||||
appletSetCpuBoostMode(ApmCpuBoostMode_Normal);
|
||||
}
|
||||
}
|
||||
|
||||
static auto GetAccountList() -> std::vector<AccountProfileBase> {
|
||||
std::vector<AccountProfileBase> out;
|
||||
|
||||
AccountUid uids[ACC_USER_LIST_SIZE];
|
||||
s32 account_count;
|
||||
if (R_SUCCEEDED(accountListAllUsers(uids, std::size(uids), &account_count))) {
|
||||
for (s32 i = 0; i < account_count; i++) {
|
||||
AccountProfile profile;
|
||||
if (R_SUCCEEDED(accountGetProfile(&profile, uids[i]))) {
|
||||
ON_SCOPE_EXIT(accountProfileClose(&profile));
|
||||
|
||||
AccountProfileBase base;
|
||||
if (R_SUCCEEDED(accountProfileGet(&profile, nullptr, &base))) {
|
||||
// sometimes the uid for the acc can differ to the base.
|
||||
base.uid = uids[i];
|
||||
log_write("[ACC] found uid: 0x%016lX%016lX\n", uids[i].uid[0], uids[i].uid[1]);
|
||||
log_write("[ACC] base uid: 0x%016lX%016lX\n", base.uid.uid[0], base.uid.uid[1]);
|
||||
out.emplace_back(base);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
// private:
|
||||
static constexpr inline auto CONFIG_PATH = "/config/sphaira/config.ini";
|
||||
static constexpr inline auto PLAYLOG_PATH = "/config/sphaira/playlog.ini";
|
||||
@@ -208,11 +272,12 @@ public:
|
||||
PadState m_pad{};
|
||||
TouchInfo m_touch_info{};
|
||||
Controller m_controller{};
|
||||
KeyboardState m_keyboard{};
|
||||
std::vector<ThemeMeta> m_theme_meta_entries;
|
||||
|
||||
Vec2 m_scale{1, 1};
|
||||
|
||||
std::vector<std::shared_ptr<ui::Widget>> m_widgets;
|
||||
std::vector<std::unique_ptr<ui::Widget>> m_widgets;
|
||||
u32 m_pop_count{};
|
||||
ui::NotifMananger m_notif_manager{};
|
||||
|
||||
@@ -234,10 +299,12 @@ public:
|
||||
|
||||
option::OptionBool m_log_enabled{INI_SECTION, "log_enabled", false};
|
||||
option::OptionBool m_replace_hbmenu{INI_SECTION, "replace_hbmenu", false};
|
||||
option::OptionString m_default_music{INI_SECTION, "default_music", "/config/sphaira/themes/default_music.bfstm"};
|
||||
option::OptionString m_theme_path{INI_SECTION, "theme", DEFAULT_THEME_PATH};
|
||||
option::OptionBool m_theme_music{INI_SECTION, "theme_music", true};
|
||||
option::OptionBool m_12hour_time{INI_SECTION, "12hour_time", false};
|
||||
option::OptionBool m_show_ip_addr{INI_SECTION, "show_ip_addr", true};
|
||||
option::OptionLong m_language{INI_SECTION, "language", 0}; // auto
|
||||
option::OptionString m_center_menu{INI_SECTION, "center_side_menu", "Homebrew"};
|
||||
option::OptionString m_left_menu{INI_SECTION, "left_side_menu", "FileBrowser"};
|
||||
option::OptionString m_right_menu{INI_SECTION, "right_side_menu", "Appstore"};
|
||||
option::OptionBool m_progress_boost_mode{INI_SECTION, "progress_boost_mode", true};
|
||||
@@ -246,7 +313,6 @@ public:
|
||||
option::OptionBool m_install_sysmmc{INI_SECTION, "install_sysmmc", false};
|
||||
option::OptionBool m_install_emummc{INI_SECTION, "install_emummc", false};
|
||||
option::OptionBool m_install_sd{INI_SECTION, "install_sd", true};
|
||||
option::OptionLong m_install_prompt{INI_SECTION, "install_prompt", true};
|
||||
option::OptionBool m_allow_downgrade{INI_SECTION, "allow_downgrade", false};
|
||||
option::OptionBool m_skip_if_already_installed{INI_SECTION, "skip_if_already_installed", true};
|
||||
option::OptionBool m_ticket_only{INI_SECTION, "ticket_only", false};
|
||||
@@ -255,30 +321,76 @@ public:
|
||||
option::OptionBool m_skip_addon{INI_SECTION, "skip_addon", false};
|
||||
option::OptionBool m_skip_data_patch{INI_SECTION, "skip_data_patch", false};
|
||||
option::OptionBool m_skip_ticket{INI_SECTION, "skip_ticket", false};
|
||||
option::OptionBool m_skip_nca_hash_verify{INI_SECTION, "skip_nca_hash_verify", false};
|
||||
option::OptionBool m_skip_rsa_header_fixed_key_verify{INI_SECTION, "skip_rsa_header_fixed_key_verify", false};
|
||||
option::OptionBool m_skip_rsa_npdm_fixed_key_verify{INI_SECTION, "skip_rsa_npdm_fixed_key_verify", false};
|
||||
option::OptionBool m_skip_nca_hash_verify{INI_SECTION, "skip_nca_hash_verify", true};
|
||||
option::OptionBool m_skip_rsa_header_fixed_key_verify{INI_SECTION, "skip_rsa_header_fixed_key_verify", true};
|
||||
option::OptionBool m_skip_rsa_npdm_fixed_key_verify{INI_SECTION, "skip_rsa_npdm_fixed_key_verify", true};
|
||||
option::OptionBool m_ignore_distribution_bit{INI_SECTION, "ignore_distribution_bit", false};
|
||||
option::OptionBool m_convert_to_common_ticket{INI_SECTION, "convert_to_common_ticket", true};
|
||||
option::OptionBool m_convert_to_standard_crypto{INI_SECTION, "convert_to_standard_crypto", false};
|
||||
option::OptionBool m_lower_master_key{INI_SECTION, "lower_master_key", false};
|
||||
option::OptionBool m_lower_system_version{INI_SECTION, "lower_system_version", false};
|
||||
option::OptionBool m_lower_system_version{INI_SECTION, "lower_system_version", true};
|
||||
|
||||
// dump options
|
||||
option::OptionBool m_dump_app_folder{"dump", "app_folder", true};
|
||||
option::OptionBool m_dump_append_folder_with_xci{"dump", "append_folder_with_xci", true};
|
||||
option::OptionBool m_dump_trim_xci{"dump", "trim_xci", false};
|
||||
option::OptionBool m_dump_label_trim_xci{"dump", "label_trim_xci", false};
|
||||
option::OptionBool m_dump_usb_transfer_stream{"dump", "usb_transfer_stream", true};
|
||||
option::OptionBool m_dump_convert_to_common_ticket{"dump", "convert_to_common_ticket", true};
|
||||
option::OptionLong m_nsz_compress_level{"dump", "nsz_compress_level", 3};
|
||||
option::OptionLong m_nsz_compress_threads{"dump", "nsz_compress_threads", 3};
|
||||
option::OptionBool m_nsz_compress_ldm{"dump", "nsz_compress_ldm", true};
|
||||
option::OptionBool m_nsz_compress_block{"dump", "nsz_compress_block", false};
|
||||
option::OptionLong m_nsz_compress_block_exponent{"dump", "nsz_compress_block_exponent", 6};
|
||||
|
||||
// todo: move this into it's own menu
|
||||
option::OptionLong m_text_scroll_speed{"accessibility", "text_scroll_speed", 1}; // normal
|
||||
|
||||
PLSR_PlayerSoundId m_sound_ids[SoundEffect_MAX]{};
|
||||
// ftp options.
|
||||
option::OptionLong m_ftp_port{"ftp", "port", 5000};
|
||||
option::OptionBool m_ftp_anon{"ftp", "anon", true};
|
||||
option::OptionString m_ftp_user{"ftp", "user", ""};
|
||||
option::OptionString m_ftp_pass{"ftp", "pass", ""};
|
||||
option::OptionBool m_ftp_show_album{"ftp", "show_album", true};
|
||||
option::OptionBool m_ftp_show_ams_contents{"ftp", "show_ams_contents", false};
|
||||
option::OptionBool m_ftp_show_bis_storage{"ftp", "show_bis_storage", false};
|
||||
option::OptionBool m_ftp_show_bis_fs{"ftp", "show_bis_fs", false};
|
||||
option::OptionBool m_ftp_show_content_system{"ftp", "show_content_system", false};
|
||||
option::OptionBool m_ftp_show_content_user{"ftp", "show_content_user", false};
|
||||
option::OptionBool m_ftp_show_content_sd{"ftp", "show_content_sd", false};
|
||||
// option::OptionBool m_ftp_show_content_sd0{"ftp", "show_content_sd0", false};
|
||||
// option::OptionBool m_ftp_show_custom_system{"ftp", "show_custom_system", false};
|
||||
// option::OptionBool m_ftp_show_custom_sd{"ftp", "show_custom_sd", false};
|
||||
option::OptionBool m_ftp_show_games{"ftp", "show_games", true};
|
||||
option::OptionBool m_ftp_show_install{"ftp", "show_install", true};
|
||||
option::OptionBool m_ftp_show_mounts{"ftp", "show_mounts", false};
|
||||
option::OptionBool m_ftp_show_switch{"ftp", "show_switch", false};
|
||||
|
||||
// mtp options.
|
||||
option::OptionLong m_mtp_vid{"mtp", "vid", 0x057e}; // nintendo (hidden from ui)
|
||||
option::OptionLong m_mtp_pid{"mtp", "pid", 0x201d}; // switch (hidden from ui)
|
||||
option::OptionBool m_mtp_allocate_file{"mtp", "allocate_file", true};
|
||||
option::OptionBool m_mtp_show_album{"mtp", "show_album", true};
|
||||
option::OptionBool m_mtp_show_content_sd{"mtp", "show_content_sd", false};
|
||||
option::OptionBool m_mtp_show_content_system{"mtp", "show_content_system", false};
|
||||
option::OptionBool m_mtp_show_content_user{"mtp", "show_content_user", false};
|
||||
option::OptionBool m_mtp_show_games{"mtp", "show_games", true};
|
||||
option::OptionBool m_mtp_show_install{"mtp", "show_install", true};
|
||||
option::OptionBool m_mtp_show_mounts{"mtp", "show_mounts", false};
|
||||
option::OptionBool m_mtp_show_speedtest{"mtp", "show_speedtest", false};
|
||||
|
||||
std::shared_ptr<fs::FsNativeSd> m_fs{};
|
||||
audio::SongID m_background_music{};
|
||||
|
||||
#ifdef USE_NVJPG
|
||||
nj::Decoder m_decoder;
|
||||
#endif
|
||||
|
||||
double m_delta_time{};
|
||||
|
||||
static constexpr const char* INSTALL_DEPENDS_STR =
|
||||
"Installing is disabled.\n\n"
|
||||
"Enable in the options by selecting Menu (Y) -> Advanced -> Install options -> Enable.";
|
||||
|
||||
private: // from nanovg decko3d example by adubbz
|
||||
static constexpr unsigned NumFramebuffers = 2;
|
||||
static constexpr unsigned StaticCmdSize = 0x1000;
|
||||
|
||||
@@ -174,6 +174,7 @@ enum {
|
||||
Module_Npln = 321,
|
||||
Module_Tspm = 499,
|
||||
Module_Devmenu = 500,
|
||||
Module_Sphaira = 505,
|
||||
};
|
||||
|
||||
enum SvcError {
|
||||
@@ -494,7 +495,337 @@ enum NcmError {
|
||||
NcmError_WriteToReadOnlyContentStorage = 0x17C05,
|
||||
};
|
||||
|
||||
#define R_SUCCEED() return 0
|
||||
enum class SphairaResult : Result {
|
||||
TransferCancelled,
|
||||
StreamBadSeek,
|
||||
|
||||
FsTooManyEntries,
|
||||
FsNewPathTooLarge,
|
||||
FsInvalidType,
|
||||
FsEmpty,
|
||||
FsAlreadyRoot,
|
||||
FsNoCurrentPath,
|
||||
FsBrokenCurrentPath,
|
||||
FsIndexOutOfBounds,
|
||||
FsFsNotActive,
|
||||
FsNewPathEmpty,
|
||||
FsLoadingCancelled,
|
||||
FsBrokenRoot,
|
||||
|
||||
FsUnknownStdioError,
|
||||
FsStdioFailedToSeek,
|
||||
FsStdioFailedToRead,
|
||||
FsStdioFailedToWrite,
|
||||
FsStdioFailedToOpenFile,
|
||||
FsStdioFailedToCreate,
|
||||
FsStdioFailedToTruncate,
|
||||
FsStdioFailedToFlush,
|
||||
FsStdioFailedToCreateDirectory,
|
||||
FsStdioFailedToDeleteFile,
|
||||
FsStdioFailedToDeleteDirectory,
|
||||
FsStdioFailedToOpenDirectory,
|
||||
FsStdioFailedToRename,
|
||||
FsStdioFailedToStat,
|
||||
|
||||
FsReadOnly,
|
||||
FsNotActive,
|
||||
FsFailedStdioStat,
|
||||
FsFailedStdioOpendir,
|
||||
|
||||
NroBadMagic,
|
||||
NroBadSize,
|
||||
|
||||
AppFailedMusicDownload,
|
||||
CurlFailedEasyInit,
|
||||
DumpFailedNetworkUpload,
|
||||
|
||||
UnzOpen2_64,
|
||||
UnzGetGlobalInfo64,
|
||||
UnzLocateFile,
|
||||
UnzGoToFirstFile,
|
||||
UnzGoToNextFile,
|
||||
UnzOpenCurrentFile,
|
||||
UnzGetCurrentFileInfo64,
|
||||
UnzReadCurrentFile,
|
||||
|
||||
ZipOpen2_64,
|
||||
ZipOpenNewFileInZip,
|
||||
ZipWriteInFileInZip,
|
||||
|
||||
MmzBadLocalHeaderSig,
|
||||
MmzBadLocalHeaderRead,
|
||||
|
||||
FileBrowserFailedUpload,
|
||||
FileBrowserDirNotDaybreak,
|
||||
|
||||
AppstoreFailedZipDownload,
|
||||
AppstoreFailedMd5,
|
||||
AppstoreFailedParseManifest,
|
||||
|
||||
GameBadReadForDump,
|
||||
GameEmptyMetaEntries,
|
||||
GameMultipleKeysFound,
|
||||
GameNoNspEntriesBuilt,
|
||||
|
||||
KeyMissingNcaKeyArea,
|
||||
KeyMissingTitleKek,
|
||||
KeyMissingMasterKey,
|
||||
KeyFailedDecyptETicketDeviceKey,
|
||||
|
||||
NcaFailedNcaHeaderHashVerify,
|
||||
NcaBadSigKeyGen,
|
||||
|
||||
GcBadReadForDump,
|
||||
GcEmptyGamecard,
|
||||
GcBadXciMagic,
|
||||
GcBadXciRomSize,
|
||||
GcFailedToGetSecurityInfo,
|
||||
|
||||
GhdlEmptyAsset,
|
||||
GhdlFailedToDownloadAsset,
|
||||
GhdlFailedToDownloadAssetJson,
|
||||
|
||||
ThemezerFailedToDownloadThemeMeta,
|
||||
ThemezerFailedToDownloadTheme,
|
||||
|
||||
MainFailedToDownloadUpdate,
|
||||
|
||||
UsbDsBadDeviceSpeed,
|
||||
|
||||
NcaBadMagic,
|
||||
NspBadMagic,
|
||||
XciBadMagic,
|
||||
XciSecurePartitionNotFound,
|
||||
|
||||
EsBadTitleKeyType,
|
||||
EsPersonalisedTicketDeviceIdMissmatch,
|
||||
EsFailedDecryptPersonalisedTicket,
|
||||
EsBadDecryptedPersonalisedTicketSize,
|
||||
EsBadTicketSize,
|
||||
// found ticket has missmatching rights_id from it's name.
|
||||
EsInvalidTicketBadRightsId,
|
||||
EsInvalidTicketFromatVersion,
|
||||
EsInvalidTicketKeyType,
|
||||
EsInvalidTicketKeyRevision,
|
||||
|
||||
OwoBadArgs,
|
||||
|
||||
UsbCancelled,
|
||||
UsbBadMagic,
|
||||
UsbBadVersion,
|
||||
UsbBadCount,
|
||||
UsbBadBufferAlign,
|
||||
UsbBadTransferSize,
|
||||
UsbEmptyTransferSize,
|
||||
UsbOverflowTransferSize,
|
||||
UsbBadTotalSize,
|
||||
|
||||
UsbUploadBadMagic,
|
||||
UsbUploadExit,
|
||||
UsbUploadBadCount,
|
||||
UsbUploadBadTransferSize,
|
||||
UsbUploadBadTotalSize,
|
||||
UsbUploadBadCommand,
|
||||
|
||||
// unkown container for the source provided.
|
||||
YatiContainerNotFound,
|
||||
// nca required by the cnmt but not found in collection.
|
||||
YatiNcaNotFound,
|
||||
YatiInvalidNcaReadSize,
|
||||
YatiInvalidNcaSigKeyGen,
|
||||
YatiInvalidNcaMagic,
|
||||
YatiInvalidNcaSignature0,
|
||||
YatiInvalidNcaSignature1,
|
||||
// invalid sha256 over the entire nca.
|
||||
YatiInvalidNcaSha256,
|
||||
// section could not be found.
|
||||
YatiNczSectionNotFound,
|
||||
// section count == 0.
|
||||
YatiInvalidNczSectionCount,
|
||||
// block could not be found.
|
||||
YatiNczBlockNotFound,
|
||||
// block version != 2.
|
||||
YatiInvalidNczBlockVersion,
|
||||
// block type != 1.
|
||||
YatiInvalidNczBlockType,
|
||||
// block count == 0.
|
||||
YatiInvalidNczBlockTotal,
|
||||
// block size exponent < 14 || > 32.
|
||||
YatiInvalidNczBlockSizeExponent,
|
||||
// zstd error while decompressing ncz.
|
||||
YatiInvalidNczZstdError,
|
||||
// nca has rights_id but matching ticket wasn't found.
|
||||
YatiTicketNotFound,
|
||||
// found ticket has missmatching rights_id from it's name.
|
||||
YatiInvalidTicketBadRightsId,
|
||||
// cert not found for the ticket.
|
||||
YatiCertNotFound,
|
||||
// unable to fetch header from ncm database.
|
||||
YatiNcmDbCorruptHeader,
|
||||
// unable to total infos from ncm database.
|
||||
YatiNcmDbCorruptInfos,
|
||||
|
||||
NszFailedCreateCctx,
|
||||
NszFailedSetCompressionLevel,
|
||||
NszFailedSetThreadCount,
|
||||
NszFailedSetLongDistanceMode,
|
||||
NszFailedResetCctx,
|
||||
NszFailedCompress2,
|
||||
NszFailedCompressStream2,
|
||||
NszTooManyBlocks,
|
||||
// set when nca finished but not all blocks were handled.
|
||||
NszMissingBlocks,
|
||||
};
|
||||
|
||||
#define MAKE_SPHAIRA_RESULT_ENUM(x) Result_##x = MAKERESULT(Module_Sphaira, (Result)SphairaResult::x)
|
||||
|
||||
enum : Result {
|
||||
MAKE_SPHAIRA_RESULT_ENUM(TransferCancelled),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(StreamBadSeek),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsTooManyEntries),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsNewPathTooLarge),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsInvalidType),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsEmpty),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsAlreadyRoot),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsNoCurrentPath),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsBrokenCurrentPath),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsIndexOutOfBounds),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsFsNotActive),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsNewPathEmpty),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsLoadingCancelled),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsBrokenRoot),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsUnknownStdioError),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToSeek),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToRead),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToWrite),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToOpenFile),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToCreate),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToTruncate),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToFlush),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToCreateDirectory),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToDeleteFile),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToDeleteDirectory),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToOpenDirectory),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToRename),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsStdioFailedToStat),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsReadOnly),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsNotActive),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsFailedStdioStat),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FsFailedStdioOpendir),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NroBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NroBadSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(AppFailedMusicDownload),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(CurlFailedEasyInit),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(DumpFailedNetworkUpload),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UnzOpen2_64),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UnzGetGlobalInfo64),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UnzLocateFile),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UnzGoToFirstFile),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UnzGoToNextFile),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UnzOpenCurrentFile),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UnzGetCurrentFileInfo64),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UnzReadCurrentFile),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(ZipOpen2_64),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(ZipOpenNewFileInZip),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(ZipWriteInFileInZip),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(MmzBadLocalHeaderSig),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(MmzBadLocalHeaderRead),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FileBrowserFailedUpload),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(FileBrowserDirNotDaybreak),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(AppstoreFailedZipDownload),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(AppstoreFailedMd5),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(AppstoreFailedParseManifest),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GameBadReadForDump),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GameEmptyMetaEntries),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GameMultipleKeysFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GameNoNspEntriesBuilt),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(KeyMissingNcaKeyArea),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(KeyMissingTitleKek),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(KeyMissingMasterKey),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(KeyFailedDecyptETicketDeviceKey),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NcaFailedNcaHeaderHashVerify),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NcaBadSigKeyGen),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GcBadReadForDump),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GcEmptyGamecard),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GcBadXciMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GcBadXciRomSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GcFailedToGetSecurityInfo),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GhdlEmptyAsset),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GhdlFailedToDownloadAsset),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(GhdlFailedToDownloadAssetJson),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(ThemezerFailedToDownloadThemeMeta),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(ThemezerFailedToDownloadTheme),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(MainFailedToDownloadUpdate),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbDsBadDeviceSpeed),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NspBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(XciBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NcaBadMagic),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(XciSecurePartitionNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsBadTitleKeyType),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsPersonalisedTicketDeviceIdMissmatch),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsFailedDecryptPersonalisedTicket),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsBadDecryptedPersonalisedTicketSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsBadTicketSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketBadRightsId),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketFromatVersion),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketKeyType),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(EsInvalidTicketKeyRevision),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(OwoBadArgs),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbCancelled),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbBadVersion),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbBadCount),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbBadBufferAlign),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbBadTransferSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbEmptyTransferSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbOverflowTransferSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadExit),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadCount),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadTransferSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadTotalSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(UsbUploadBadCommand),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiContainerNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiNcaNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaReadSize),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaSigKeyGen),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaMagic),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaSignature0),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaSignature1),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNcaSha256),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiNczSectionNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczSectionCount),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiNczBlockNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczBlockVersion),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczBlockType),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczBlockTotal),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczBlockSizeExponent),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidNczZstdError),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiTicketNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiInvalidTicketBadRightsId),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiCertNotFound),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiNcmDbCorruptHeader),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(YatiNcmDbCorruptInfos),
|
||||
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedCreateCctx),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedSetCompressionLevel),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedSetThreadCount),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedSetLongDistanceMode),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedResetCctx),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedCompress2),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszFailedCompressStream2),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszTooManyBlocks),
|
||||
MAKE_SPHAIRA_RESULT_ENUM(NszMissingBlocks),
|
||||
};
|
||||
|
||||
#undef MAKE_SPHAIRA_RESULT_ENUM
|
||||
|
||||
#define R_SUCCEED() return (Result)0
|
||||
|
||||
#define R_THROW(_rc) return _rc
|
||||
|
||||
@@ -520,19 +851,83 @@ enum NcmError {
|
||||
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
|
||||
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__)
|
||||
|
||||
#define ON_SCOPE_EXIT(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }};
|
||||
#define ON_SCOPE_FAIL(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_FAILED(rc)) { _f; } }};
|
||||
#define ON_SCOPE_SUCCESS(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_SUCCEEDED(rc)) { _f; } }};
|
||||
template<typename Function>
|
||||
struct ScopeGuard {
|
||||
ScopeGuard(Function&& function) : m_function(std::forward<Function>(function)) {
|
||||
|
||||
// threading helpers.
|
||||
#define PRIO_PREEMPTIVE 0x3B
|
||||
}
|
||||
~ScopeGuard() {
|
||||
m_function();
|
||||
}
|
||||
|
||||
// threading affinity, use with svcSetThreadCoreMask().
|
||||
#define THREAD_AFFINITY_CORE0 BIT(0)
|
||||
#define THREAD_AFFINITY_CORE1 BIT(1)
|
||||
#define THREAD_AFFINITY_CORE2 BIT(2)
|
||||
#define THREAD_AFFINITY_DEFAULT(core) (BIT(core)|THREAD_AFFINITY_CORE1|THREAD_AFFINITY_CORE2)
|
||||
#define THREAD_AFFINITY_ALL (THREAD_AFFINITY_CORE0|THREAD_AFFINITY_CORE1|THREAD_AFFINITY_CORE2)
|
||||
ScopeGuard(const ScopeGuard&) = delete;
|
||||
void operator=(const ScopeGuard&) = delete;
|
||||
|
||||
// mutex helpers.
|
||||
#define SCOPED_MUTEX(mutex) mutexLock(mutex); ON_SCOPE_EXIT(mutexUnlock(mutex))
|
||||
private:
|
||||
const Function m_function;
|
||||
};
|
||||
|
||||
struct ScopedMutex {
|
||||
ScopedMutex(Mutex* mutex) : m_mutex{mutex} {
|
||||
mutexLock(m_mutex);
|
||||
}
|
||||
~ScopedMutex() {
|
||||
mutexUnlock(m_mutex);
|
||||
}
|
||||
|
||||
ScopedMutex(const ScopedMutex&) = delete;
|
||||
void operator=(const ScopedMutex&) = delete;
|
||||
|
||||
private:
|
||||
Mutex* const m_mutex;
|
||||
};
|
||||
|
||||
struct ScopedRMutex {
|
||||
ScopedRMutex(RMutex* _mutex) : mutex{_mutex} {
|
||||
rmutexLock(mutex);
|
||||
}
|
||||
|
||||
~ScopedRMutex() {
|
||||
rmutexUnlock(mutex);
|
||||
}
|
||||
|
||||
ScopedRMutex(const ScopedRMutex&) = delete;
|
||||
void operator=(const ScopedRMutex&) = delete;
|
||||
|
||||
private:
|
||||
RMutex* const mutex;
|
||||
};
|
||||
|
||||
struct ScopedRwLock {
|
||||
ScopedRwLock(RwLock* _lock, bool _write) : lock{_lock}, write{_write} {
|
||||
if (write) {
|
||||
rwlockWriteLock(lock);
|
||||
} else {
|
||||
rwlockReadLock(lock);
|
||||
}
|
||||
}
|
||||
|
||||
~ScopedRwLock() {
|
||||
if (write) {
|
||||
rwlockWriteUnlock(lock);
|
||||
} else {
|
||||
rwlockReadUnlock(lock);
|
||||
}
|
||||
}
|
||||
|
||||
ScopedRwLock(const ScopedRwLock&) = delete;
|
||||
void operator=(const ScopedRwLock&) = delete;
|
||||
|
||||
private:
|
||||
RwLock* const lock;
|
||||
bool const write;
|
||||
};
|
||||
|
||||
// #define ON_SCOPE_EXIT(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }};
|
||||
#define ON_SCOPE_EXIT(_f) ScopeGuard ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { _f; }};
|
||||
#define SCOPED_MUTEX(_m) ScopedMutex ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m}
|
||||
#define SCOPED_RMUTEX(_m) ScopedRMutex ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m}
|
||||
#define SCOPED_RWLOCK(_m, _write) ScopedRwLock ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){_m, _write}
|
||||
|
||||
// #define ON_SCOPE_FAIL(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_FAILED(rc)) { _f; } }};
|
||||
// #define ON_SCOPE_SUCCESS(_f) std::experimental::scope_exit ANONYMOUS_VARIABLE(SCOPE_EXIT_STATE_){[&] { if (R_SUCCEEDED(rc)) { _f; } }};
|
||||
|
||||
@@ -52,7 +52,7 @@ struct Fields {
|
||||
|
||||
struct Header {
|
||||
Header() = default;
|
||||
Header(std::initializer_list<std::pair<const std::string, std::string>> p) : m_map{p} {}
|
||||
Header(std::initializer_list<std::pair<const std::string, std::string>>&& p) : m_map{std::forward<decltype(p)>(p)} {}
|
||||
std::unordered_map<std::string, std::string> m_map;
|
||||
|
||||
auto Find(const std::string& key) const {
|
||||
@@ -91,7 +91,7 @@ struct UserPass {
|
||||
struct UploadInfo {
|
||||
UploadInfo() = default;
|
||||
UploadInfo(const std::string& name) : m_name{name} {}
|
||||
UploadInfo(const std::string& name, s64 size, OnUploadCallback cb) : m_name{name}, m_size{size}, m_callback{cb} {}
|
||||
UploadInfo(const std::string& name, s64 size, const OnUploadCallback& cb) : m_name{name}, m_size{size}, m_callback{cb} {}
|
||||
UploadInfo(const std::string& name, const std::vector<u8>& data) : m_name{name}, m_data{data} {}
|
||||
std::string m_name{};
|
||||
std::vector<u8> m_data{};
|
||||
@@ -142,6 +142,7 @@ struct DownloadEventData {
|
||||
|
||||
auto Init() -> bool;
|
||||
void Exit();
|
||||
void ExitSignal();
|
||||
|
||||
// sync functions
|
||||
auto ToMemory(const Api& e) -> ApiResult;
|
||||
|
||||
@@ -1,33 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "location.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <minizip/ioapi.h>
|
||||
|
||||
namespace sphaira::dump {
|
||||
|
||||
enum DumpLocationType {
|
||||
// dump using native fs.
|
||||
DumpLocationType_SdCard,
|
||||
// dump to usb pc.
|
||||
DumpLocationType_Usb,
|
||||
// dump to usb using tinfoil protocol.
|
||||
DumpLocationType_UsbS2S,
|
||||
// speed test, only reads the data, doesn't write anything.
|
||||
DumpLocationType_DevNull,
|
||||
// dump to stdio, ideal for custom mount points using devoptab, such as hdd.
|
||||
DumpLocationType_Stdio,
|
||||
// dump to custom locations found in locations.ini.
|
||||
DumpLocationType_Network,
|
||||
};
|
||||
|
||||
enum DumpLocationFlag {
|
||||
DumpLocationFlag_SdCard = 1 << DumpLocationType_SdCard,
|
||||
DumpLocationFlag_Usb = 1 << DumpLocationType_Usb,
|
||||
DumpLocationFlag_UsbS2S = 1 << DumpLocationType_UsbS2S,
|
||||
DumpLocationFlag_DevNull = 1 << DumpLocationType_DevNull,
|
||||
DumpLocationFlag_Stdio = 1 << DumpLocationType_Stdio,
|
||||
DumpLocationFlag_Network = 1 << DumpLocationType_Network,
|
||||
DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio | DumpLocationFlag_Network,
|
||||
DumpLocationFlag_All = DumpLocationFlag_SdCard | DumpLocationFlag_Usb | DumpLocationFlag_UsbS2S | DumpLocationFlag_DevNull | DumpLocationFlag_Stdio,
|
||||
};
|
||||
|
||||
struct DumpEntry {
|
||||
DumpLocationType type;
|
||||
s32 index;
|
||||
};
|
||||
|
||||
struct DumpLocation {
|
||||
DumpEntry entry{};
|
||||
location::StdioEntries stdio{};
|
||||
};
|
||||
|
||||
struct BaseSource {
|
||||
@@ -36,11 +50,36 @@ struct BaseSource {
|
||||
virtual auto GetName(const std::string& path) const -> std::string = 0;
|
||||
virtual auto GetSize(const std::string& path) const -> s64 = 0;
|
||||
virtual auto GetIcon(const std::string& path) const -> int { return 0; }
|
||||
|
||||
Result Read(const std::string& path, void* buf, s64 off, s64 size) {
|
||||
u64 bytes_read;
|
||||
return Read(path, buf, off, size, &bytes_read);
|
||||
}
|
||||
};
|
||||
|
||||
struct WriteSource {
|
||||
virtual ~WriteSource() = default;
|
||||
virtual Result Write(const void* buf, s64 off, s64 size) = 0;
|
||||
virtual Result SetSize(s64 size) = 0;
|
||||
};
|
||||
|
||||
// called after dump has finished.
|
||||
using OnExit = std::function<void(Result rc)>;
|
||||
using OnLocation = std::function<void(const DumpLocation& loc)>;
|
||||
|
||||
void Dump(std::shared_ptr<BaseSource> source, const std::vector<fs::FsPath>& paths, OnExit on_exit = [](Result){}, u32 location_flags = DumpLocationFlag_All);
|
||||
using CustomTransfer = std::function<Result(ui::ProgressBox* pbox, BaseSource* source, WriteSource* writer, const fs::FsPath& path)>;
|
||||
|
||||
// prompts the user to select dump location, calls on_loc on success with the selected location.
|
||||
void DumpGetLocation(const std::string& title, u32 location_flags, const OnLocation& on_loc, const CustomTransfer& custom_transfer = nullptr);
|
||||
|
||||
Result Dump(ui::ProgressBox* pbox, const std::shared_ptr<BaseSource>& source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, const CustomTransfer& custom_transfer = nullptr);
|
||||
|
||||
// dumps to a fetched location using DumpGetLocation().
|
||||
void Dump(const std::shared_ptr<BaseSource>& source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, const OnExit& on_exit, const CustomTransfer& custom_transfer = nullptr);
|
||||
// DumpGetLocation() + Dump() all in one.
|
||||
void Dump(const std::shared_ptr<BaseSource>& source, const std::vector<fs::FsPath>& paths, const OnExit& on_exit = nullptr, u32 location_flags = DumpLocationFlag_All);
|
||||
void Dump(const std::shared_ptr<BaseSource>& source, const std::vector<fs::FsPath>& paths, const CustomTransfer& custom_transfer, const OnExit& on_exit = nullptr, u32 location_flags = DumpLocationFlag_All);
|
||||
|
||||
void FileFuncWriter(WriteSource* writer, zlib_filefunc64_def* funcs);
|
||||
|
||||
} // namespace sphaira::dump
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <string>
|
||||
#include <switch.h>
|
||||
#include <nxlink.h>
|
||||
#include <haze.h>
|
||||
#include "download.hpp"
|
||||
|
||||
namespace sphaira::evman {
|
||||
@@ -24,7 +23,6 @@ struct ExitEventData {
|
||||
using EventData = std::variant<
|
||||
LaunchNroEventData,
|
||||
ExitEventData,
|
||||
HazeCallbackData,
|
||||
NxlinkCallbackData,
|
||||
curl::DownloadEventData
|
||||
>;
|
||||
|
||||
@@ -4,18 +4,36 @@
|
||||
#include <dirent.h>
|
||||
#include <cstring>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <sys/syslimits.h>
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace fs {
|
||||
|
||||
enum OpenMode : u32 {
|
||||
OpenMode_Read = FsOpenMode_Read,
|
||||
OpenMode_Write = FsOpenMode_Write,
|
||||
OpenMode_Append = FsOpenMode_Append,
|
||||
|
||||
// enables buffering for stdio based files.
|
||||
OpenMode_EnableBuffer = 1 << 16,
|
||||
OpenMode_ReadBuffered = OpenMode_Read | OpenMode_EnableBuffer,
|
||||
OpenMode_WriteBuffered = OpenMode_Write | OpenMode_EnableBuffer,
|
||||
OpenMode_AppendBuffered = OpenMode_Append | OpenMode_EnableBuffer,
|
||||
};
|
||||
|
||||
struct FsPath {
|
||||
FsPath() = default;
|
||||
constexpr FsPath(const auto& str) { From(str); }
|
||||
|
||||
constexpr FsPath(const FsPath& p) { From(p); }
|
||||
constexpr FsPath(const char* str) { From(str); }
|
||||
constexpr FsPath(const std::string& str) { From(str); }
|
||||
constexpr FsPath(const std::string_view& str) { From(str); }
|
||||
|
||||
constexpr void From(const FsPath& p) {
|
||||
*this = p;
|
||||
From(p.s);
|
||||
}
|
||||
|
||||
constexpr void From(const char* str) {
|
||||
@@ -56,6 +74,19 @@ struct FsPath {
|
||||
s[0] = '\0';
|
||||
}
|
||||
|
||||
constexpr auto starts_with(std::string_view str) const -> bool {
|
||||
return !strncasecmp(s, str.data(), str.length());
|
||||
}
|
||||
|
||||
constexpr auto ends_with(std::string_view str) const -> bool {
|
||||
const auto len = length();
|
||||
if (len < str.length()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !strncasecmp(s + len - str.length(), str.data(), str.length());
|
||||
}
|
||||
|
||||
constexpr operator const char*() const { return s; }
|
||||
constexpr operator char*() { return s; }
|
||||
constexpr operator std::string() { return s; }
|
||||
@@ -65,6 +96,11 @@ struct FsPath {
|
||||
constexpr char& operator[](std::size_t idx) { return s[idx]; }
|
||||
constexpr const char& operator[](std::size_t idx) const { return s[idx]; }
|
||||
|
||||
constexpr FsPath& operator=(const FsPath& p) noexcept {
|
||||
From(p.s);
|
||||
return *this;
|
||||
}
|
||||
|
||||
constexpr FsPath operator+(const FsPath& v) const noexcept {
|
||||
FsPath r{*this};
|
||||
return r += v;
|
||||
@@ -116,20 +152,24 @@ struct FsPath {
|
||||
return *this;
|
||||
}
|
||||
|
||||
static constexpr bool path_equal(std::string_view a, std::string_view b) {
|
||||
return a.length() == b.length() && !strncasecmp(a.data(), b.data(), a.length());
|
||||
}
|
||||
|
||||
constexpr bool operator==(const FsPath& v) const noexcept {
|
||||
return !strcasecmp(*this, v);
|
||||
return path_equal(*this, v);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const char* v) const noexcept {
|
||||
return !strcasecmp(*this, v);
|
||||
return path_equal(*this, v);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const std::string& v) const noexcept {
|
||||
return !strncasecmp(*this, v.data(), v.length());
|
||||
return path_equal(*this, v);
|
||||
}
|
||||
|
||||
constexpr bool operator==(const std::string_view v) const noexcept {
|
||||
return !strncasecmp(*this, v.data(), v.length());
|
||||
return path_equal(*this, v);
|
||||
}
|
||||
|
||||
static consteval bool Test(const auto& str) {
|
||||
@@ -142,7 +182,7 @@ struct FsPath {
|
||||
return path[0] == str[0];
|
||||
}
|
||||
|
||||
char s[FS_MAX_PATH]{};
|
||||
char s[PATH_MAX]{};
|
||||
};
|
||||
|
||||
inline FsPath operator+(const char* v, const FsPath& fp) {
|
||||
@@ -160,15 +200,41 @@ inline FsPath operator+(const std::string_view& v, const FsPath& fp) {
|
||||
return r += fp;
|
||||
}
|
||||
|
||||
static_assert(FsPath::Test("abc"));
|
||||
static_assert(FsPath::Test(std::string_view{"abc"}));
|
||||
static_assert(FsPath::Test(std::string{"abc"}));
|
||||
static_assert(FsPath::Test(FsPath{"abc"}));
|
||||
// Fs seems to be limted to file paths of 255 characters.
|
||||
// i've disabled this as network mounts will often have very long paths
|
||||
// that do not have this limit.
|
||||
// a proper fix would be to return an error if the path is too long and the path
|
||||
// is native.
|
||||
struct FsPathReal {
|
||||
static constexpr inline size_t FS_REAL_MAX_LENGTH = PATH_MAX;
|
||||
|
||||
static_assert(FsPath::TestFrom("abc"));
|
||||
static_assert(FsPath::TestFrom(std::string_view{"abc"}));
|
||||
static_assert(FsPath::TestFrom(std::string{"abc"}));
|
||||
static_assert(FsPath::TestFrom(FsPath{"abc"}));
|
||||
constexpr FsPathReal(const FsPath& str) : FsPathReal{str.s} { }
|
||||
explicit constexpr FsPathReal(const char* str) {
|
||||
size_t real = 0;
|
||||
for (size_t i = 0; str[i]; i++) {
|
||||
// skip multiple slashes.
|
||||
if (i && str[i] == '/' && str[i - 1] == '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
// save single char.
|
||||
s[real++] = str[i];
|
||||
|
||||
// check if we have exceeded the path.
|
||||
if (real >= FS_REAL_MAX_LENGTH) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// null the end.
|
||||
s[real] = '\0';
|
||||
}
|
||||
|
||||
constexpr operator const char*() const { return s; }
|
||||
constexpr operator std::string_view() const { return s; }
|
||||
|
||||
char s[PATH_MAX];
|
||||
};
|
||||
|
||||
// fwd
|
||||
struct Fs;
|
||||
@@ -185,16 +251,14 @@ struct File {
|
||||
fs::Fs* m_fs{};
|
||||
FsFile m_native{};
|
||||
std::FILE* m_stdio{};
|
||||
s64 m_stdio_off{};
|
||||
// sadly, fatfs doesn't support fstat, so we have to manually
|
||||
// stat the file to get it's size.
|
||||
FsPath m_path{};
|
||||
u32 m_mode{};
|
||||
};
|
||||
|
||||
struct Dir {
|
||||
~Dir();
|
||||
|
||||
Result GetEntryCount(s64* out);
|
||||
Result Read(s64 *total_entries, size_t max_entries, FsDirectoryEntry *buf);
|
||||
Result ReadAll(std::vector<FsDirectoryEntry>& buf);
|
||||
void Close();
|
||||
|
||||
@@ -206,44 +270,38 @@ struct Dir {
|
||||
|
||||
FsPath AppendPath(const fs::FsPath& root_path, const fs::FsPath& file_path);
|
||||
|
||||
Result CreateFile(FsFileSystem* fs, const FsPath& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true);
|
||||
Result CreateDirectory(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
|
||||
Result CreateFile(FsFileSystem* fs, const FsPathReal& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true);
|
||||
Result CreateDirectory(FsFileSystem* fs, const FsPathReal& path, bool ignore_read_only = true);
|
||||
Result CreateDirectoryRecursively(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
|
||||
Result CreateDirectoryRecursivelyWithPath(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
|
||||
Result DeleteFile(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
|
||||
Result DeleteDirectory(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
|
||||
Result DeleteDirectoryRecursively(FsFileSystem* fs, const FsPath& path, bool ignore_read_only = true);
|
||||
Result RenameFile(FsFileSystem* fs, const FsPath& src, const FsPath& dst, bool ignore_read_only = true);
|
||||
Result RenameDirectory(FsFileSystem* fs, const FsPath& src, const FsPath& dst, bool ignore_read_only = true);
|
||||
Result GetEntryType(FsFileSystem* fs, const FsPath& path, FsDirEntryType* out);
|
||||
Result GetFileTimeStampRaw(FsFileSystem* fs, const FsPath& path, FsTimeStampRaw *out);
|
||||
Result SetTimestamp(FsFileSystem* fs, const FsPath& path, const FsTimeStampRaw* ts);
|
||||
Result DeleteFile(FsFileSystem* fs, const FsPathReal& path, bool ignore_read_only = true);
|
||||
Result DeleteDirectory(FsFileSystem* fs, const FsPathReal& path, bool ignore_read_only = true);
|
||||
Result DeleteDirectoryRecursively(FsFileSystem* fs, const FsPathReal& path, bool ignore_read_only = true);
|
||||
Result RenameFile(FsFileSystem* fs, const FsPathReal& src, const FsPathReal& dst, bool ignore_read_only = true);
|
||||
Result RenameDirectory(FsFileSystem* fs, const FsPathReal& src, const FsPathReal& dst, bool ignore_read_only = true);
|
||||
Result GetEntryType(FsFileSystem* fs, const FsPathReal& path, FsDirEntryType* out);
|
||||
Result GetFileTimeStampRaw(FsFileSystem* fs, const FsPathReal& path, FsTimeStampRaw *out);
|
||||
Result SetTimestamp(FsFileSystem* fs, const FsPathReal& path, const FsTimeStampRaw* ts);
|
||||
bool FileExists(FsFileSystem* fs, const FsPath& path);
|
||||
bool DirExists(FsFileSystem* fs, const FsPath& path);
|
||||
Result read_entire_file(FsFileSystem* fs, const FsPath& path, std::vector<u8>& out);
|
||||
Result write_entire_file(FsFileSystem* fs, const FsPath& path, const std::vector<u8>& in, bool ignore_read_only = true);
|
||||
Result copy_entire_file(FsFileSystem* fs, const FsPath& dst, const FsPath& src, bool ignore_read_only = true);
|
||||
|
||||
Result CreateFile(const FsPath& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true);
|
||||
Result CreateDirectory(const FsPath& path, bool ignore_read_only = true);
|
||||
Result CreateFile(const FsPathReal& path, u64 size = 0, u32 option = 0, bool ignore_read_only = true);
|
||||
Result CreateDirectory(const FsPathReal& path, bool ignore_read_only = true);
|
||||
Result CreateDirectoryRecursively(const FsPath& path, bool ignore_read_only = true);
|
||||
Result CreateDirectoryRecursivelyWithPath(const FsPath& path, bool ignore_read_only = true);
|
||||
Result DeleteFile(const FsPath& path, bool ignore_read_only = true);
|
||||
Result DeleteDirectory(const FsPath& path, bool ignore_read_only = true);
|
||||
Result DeleteFile(const FsPathReal& path, bool ignore_read_only = true);
|
||||
Result DeleteDirectory(const FsPathReal& path, bool ignore_read_only = true);
|
||||
Result DeleteDirectoryRecursively(const FsPath& path, bool ignore_read_only = true);
|
||||
Result RenameFile(const FsPath& src, const FsPath& dst, bool ignore_read_only = true);
|
||||
Result RenameDirectory(const FsPath& src, const FsPath& dst, bool ignore_read_only = true);
|
||||
Result GetEntryType(const FsPath& path, FsDirEntryType* out);
|
||||
Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out);
|
||||
Result SetTimestamp(const FsPath& path, const FsTimeStampRaw* ts);
|
||||
Result RenameFile(const FsPathReal& src, const FsPathReal& dst, bool ignore_read_only = true);
|
||||
Result RenameDirectory(const FsPathReal& src, const FsPathReal& dst, bool ignore_read_only = true);
|
||||
Result GetEntryType(const FsPathReal& path, FsDirEntryType* out);
|
||||
Result GetFileTimeStampRaw(const FsPathReal& path, FsTimeStampRaw *out);
|
||||
Result SetTimestamp(const FsPathReal& path, const FsTimeStampRaw* ts);
|
||||
bool FileExists(const FsPath& path);
|
||||
bool DirExists(const FsPath& path);
|
||||
Result read_entire_file(const FsPath& path, std::vector<u8>& out);
|
||||
Result write_entire_file(const FsPath& path, const std::vector<u8>& in, bool ignore_read_only = true);
|
||||
Result copy_entire_file(const FsPath& dst, const FsPath& src, bool ignore_read_only = true);
|
||||
|
||||
Result OpenFile(fs::Fs* fs, const fs::FsPath& path, u32 mode, File* f);
|
||||
Result OpenDirectory(fs::Fs* fs, const fs::FsPath& path, u32 mode, Dir* d);
|
||||
Result OpenFile(fs::Fs* fs, const FsPathReal& path, u32 mode, File* f);
|
||||
Result OpenDirectory(fs::Fs* fs, const FsPathReal& path, u32 mode, Dir* d);
|
||||
|
||||
// opens dir, fetches count for all entries.
|
||||
// NOTE: this function will be slow on non-native fs, due to multiple
|
||||
@@ -260,23 +318,12 @@ Result DirGetEntryCount(fs::Fs* fs, const fs::FsPath& path, s64* file_count, s64
|
||||
Result FileGetSizeAndTimestamp(fs::Fs* fs, const FsPath& path, FsTimeStampRaw* ts, s64* size);
|
||||
Result IsDirEmpty(fs::Fs* m_fs, const fs::FsPath& path, bool* out);
|
||||
|
||||
struct Fs {
|
||||
static constexpr inline u32 FsModule = 505;
|
||||
static constexpr inline Result ResultTooManyEntries = MAKERESULT(FsModule, 1);
|
||||
static constexpr inline Result ResultNewPathTooLarge = MAKERESULT(FsModule, 2);
|
||||
static constexpr inline Result ResultInvalidType = MAKERESULT(FsModule, 3);
|
||||
static constexpr inline Result ResultEmpty = MAKERESULT(FsModule, 4);
|
||||
static constexpr inline Result ResultAlreadyRoot = MAKERESULT(FsModule, 5);
|
||||
static constexpr inline Result ResultNoCurrentPath = MAKERESULT(FsModule, 6);
|
||||
static constexpr inline Result ResultBrokenCurrentPath = MAKERESULT(FsModule, 7);
|
||||
static constexpr inline Result ResultIndexOutOfBounds = MAKERESULT(FsModule, 8);
|
||||
static constexpr inline Result ResultFsNotActive = MAKERESULT(FsModule, 9);
|
||||
static constexpr inline Result ResultNewPathEmpty = MAKERESULT(FsModule, 10);
|
||||
static constexpr inline Result ResultLoadingCancelled = MAKERESULT(FsModule, 11);
|
||||
static constexpr inline Result ResultBrokenRoot = MAKERESULT(FsModule, 12);
|
||||
static constexpr inline Result ResultUnknownStdioError = MAKERESULT(FsModule, 13);
|
||||
static constexpr inline Result ResultReadOnly = MAKERESULT(FsModule, 14);
|
||||
// helpers.
|
||||
Result read_entire_file(Fs* fs, const FsPath& path, std::vector<u8>& out);
|
||||
Result write_entire_file(Fs* fs, const FsPath& path, std::span<const u8> in, bool ignore_read_only = true);
|
||||
Result copy_entire_file(Fs* fs, const FsPath& dst, const FsPath& src, bool ignore_read_only = true);
|
||||
|
||||
struct Fs {
|
||||
Fs(bool ignore_read_only = true) : m_ignore_read_only{ignore_read_only} {}
|
||||
virtual ~Fs() = default;
|
||||
|
||||
@@ -292,13 +339,12 @@ struct Fs {
|
||||
virtual Result GetEntryType(const FsPath& path, FsDirEntryType* out) = 0;
|
||||
virtual Result GetFileTimeStampRaw(const FsPath& path, FsTimeStampRaw *out) = 0;
|
||||
virtual Result SetTimestamp(const FsPath& path, const FsTimeStampRaw* ts) = 0;
|
||||
virtual Result Commit() = 0;
|
||||
virtual bool FileExists(const FsPath& path) = 0;
|
||||
virtual bool DirExists(const FsPath& path) = 0;
|
||||
virtual bool IsNative() const = 0;
|
||||
virtual bool IsSd() const { return false; }
|
||||
virtual FsPath Root() const { return "/"; }
|
||||
virtual Result read_entire_file(const FsPath& path, std::vector<u8>& out) = 0;
|
||||
virtual Result write_entire_file(const FsPath& path, const std::vector<u8>& in) = 0;
|
||||
virtual Result copy_entire_file(const FsPath& dst, const FsPath& src) = 0;
|
||||
|
||||
Result OpenFile(const fs::FsPath& path, u32 mode, File* f) {
|
||||
return fs::OpenFile(this, path, mode, f);
|
||||
@@ -318,6 +364,15 @@ struct Fs {
|
||||
Result IsDirEmpty(const fs::FsPath& path, bool* out) {
|
||||
return fs::IsDirEmpty(this, path, out);
|
||||
}
|
||||
Result read_entire_file(const FsPath& path, std::vector<u8>& out) {
|
||||
return fs::read_entire_file(this, path, out);
|
||||
}
|
||||
Result write_entire_file(const FsPath& path, std::span<const u8> in) {
|
||||
return fs::write_entire_file(this, path, in, m_ignore_read_only);
|
||||
}
|
||||
Result copy_entire_file(const FsPath& dst, const FsPath& src) {
|
||||
return fs::copy_entire_file(this, dst, src, m_ignore_read_only);
|
||||
}
|
||||
|
||||
void SetIgnoreReadOnly(bool enable) {
|
||||
m_ignore_read_only = enable;
|
||||
@@ -367,6 +422,9 @@ struct FsStdio : Fs {
|
||||
Result SetTimestamp(const FsPath& path, const FsTimeStampRaw *ts) override {
|
||||
return fs::SetTimestamp(path, ts);
|
||||
}
|
||||
Result Commit() override {
|
||||
R_SUCCEED();
|
||||
}
|
||||
bool FileExists(const FsPath& path) override {
|
||||
return fs::FileExists(path);
|
||||
}
|
||||
@@ -379,15 +437,6 @@ struct FsStdio : Fs {
|
||||
FsPath Root() const override {
|
||||
return m_root;
|
||||
}
|
||||
Result read_entire_file(const FsPath& path, std::vector<u8>& out) override {
|
||||
return fs::read_entire_file(path, out);
|
||||
}
|
||||
Result write_entire_file(const FsPath& path, const std::vector<u8>& in) override {
|
||||
return fs::write_entire_file(path, in, m_ignore_read_only);
|
||||
}
|
||||
Result copy_entire_file(const FsPath& dst, const FsPath& src) override {
|
||||
return fs::copy_entire_file(dst, src, m_ignore_read_only);
|
||||
}
|
||||
|
||||
const FsPath m_root;
|
||||
};
|
||||
@@ -402,10 +451,6 @@ struct FsNative : Fs {
|
||||
}
|
||||
}
|
||||
|
||||
Result Commit() {
|
||||
return fsFsCommit(&m_fs);
|
||||
}
|
||||
|
||||
Result GetFreeSpace(const FsPath& path, s64* out) {
|
||||
return fsFsGetFreeSpace(&m_fs, path, out);
|
||||
}
|
||||
@@ -414,36 +459,6 @@ struct FsNative : Fs {
|
||||
return fsFsGetTotalSpace(&m_fs, path, out);
|
||||
}
|
||||
|
||||
// Result OpenDirectory(const FsPath& path, u32 mode, FsDir *out) {
|
||||
// return fsFsOpenDirectory(&m_fs, path, mode, out);
|
||||
// }
|
||||
|
||||
// void DirClose(FsDir *d) {
|
||||
// fsDirClose(d);
|
||||
// }
|
||||
|
||||
// Result DirGetEntryCount(FsDir *d, s64* out) {
|
||||
// return fsDirGetEntryCount(d, out);
|
||||
// }
|
||||
|
||||
// Result DirGetEntryCount(const FsPath& path, u32 mode, s64* out) {
|
||||
// FsDir d;
|
||||
// R_TRY(OpenDirectory(path, mode, &d));
|
||||
// ON_SCOPE_EXIT(DirClose(&d));
|
||||
// return DirGetEntryCount(&d, out);
|
||||
// }
|
||||
|
||||
// Result DirRead(FsDir *d, s64 *total_entries, size_t max_entries, FsDirectoryEntry *buf) {
|
||||
// return fsDirRead(d, total_entries, max_entries, buf);
|
||||
// }
|
||||
|
||||
// Result DirRead(const FsPath& path, u32 mode, s64 *total_entries, size_t max_entries, FsDirectoryEntry *buf) {
|
||||
// FsDir d;
|
||||
// R_TRY(OpenDirectory(path, mode, &d));
|
||||
// ON_SCOPE_EXIT(DirClose(&d));
|
||||
// return DirRead(&d, total_entries, max_entries, buf);
|
||||
// }
|
||||
|
||||
virtual bool IsFsActive() {
|
||||
return serviceIsActive(&m_fs.s);
|
||||
}
|
||||
@@ -488,6 +503,9 @@ struct FsNative : Fs {
|
||||
Result SetTimestamp(const FsPath& path, const FsTimeStampRaw *ts) override {
|
||||
return fs::SetTimestamp(&m_fs, path, ts);
|
||||
}
|
||||
Result Commit() override {
|
||||
return fsFsCommit(&m_fs);
|
||||
}
|
||||
bool FileExists(const FsPath& path) override {
|
||||
return fs::FileExists(&m_fs, path);
|
||||
}
|
||||
@@ -497,19 +515,10 @@ struct FsNative : Fs {
|
||||
bool IsNative() const override {
|
||||
return true;
|
||||
}
|
||||
Result read_entire_file(const FsPath& path, std::vector<u8>& out) override {
|
||||
return fs::read_entire_file(&m_fs, path, out);
|
||||
}
|
||||
Result write_entire_file(const FsPath& path, const std::vector<u8>& in) override {
|
||||
return fs::write_entire_file(&m_fs, path, in, m_ignore_read_only);
|
||||
}
|
||||
Result copy_entire_file(const FsPath& dst, const FsPath& src) override {
|
||||
return fs::copy_entire_file(&m_fs, dst, src, m_ignore_read_only);
|
||||
}
|
||||
|
||||
FsFileSystem m_fs{};
|
||||
Result m_open_result{};
|
||||
bool m_own{true};
|
||||
const bool m_own{true};
|
||||
};
|
||||
|
||||
#if 0
|
||||
@@ -523,6 +532,8 @@ struct FsNativeSd final : FsNative {
|
||||
FsNativeSd(bool ignore_read_only = true) : FsNative{fsdevGetDeviceFileSystem("sdmc:"), false, ignore_read_only} {
|
||||
m_open_result = 0;
|
||||
}
|
||||
|
||||
bool IsSd() const override { return true; }
|
||||
};
|
||||
#endif
|
||||
|
||||
@@ -551,13 +562,23 @@ struct FsNativeGameCard final : FsNative {
|
||||
};
|
||||
|
||||
struct FsNativeSave final : FsNative {
|
||||
FsNativeSave(FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr, bool read_only) {
|
||||
if (read_only) {
|
||||
m_open_result = fsOpenReadOnlySaveDataFileSystem(&m_fs, save_data_space_id, attr);
|
||||
FsNativeSave(FsSaveDataType data_type, FsSaveDataSpaceId save_data_space_id, const FsSaveDataAttribute *attr, bool read_only) {
|
||||
if (data_type == FsSaveDataType_System || data_type == FsSaveDataType_SystemBcat) {
|
||||
m_open_result = fsOpenSaveDataFileSystemBySystemSaveDataId(&m_fs, FsSaveDataSpaceId_System, attr);
|
||||
} else {
|
||||
m_open_result = fsOpenSaveDataFileSystem(&m_fs, save_data_space_id, attr);
|
||||
if (read_only) {
|
||||
m_open_result = fsOpenReadOnlySaveDataFileSystem(&m_fs, save_data_space_id, attr);
|
||||
} else {
|
||||
m_open_result = fsOpenSaveDataFileSystem(&m_fs, save_data_space_id, attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
struct FsNativeId final : FsNative {
|
||||
FsNativeId(u64 program_id, FsFileSystemType type, const FsPath& path, FsContentAttributes attr = FsContentAttributes_All) {
|
||||
m_open_result = fsOpenFileSystemWithId(&m_fs, program_id, type, path, attr);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace fs
|
||||
|
||||
@@ -6,12 +6,13 @@ namespace sphaira::ftpsrv {
|
||||
|
||||
bool Init();
|
||||
void Exit();
|
||||
void ExitSignal();
|
||||
|
||||
using OnInstallStart = std::function<bool(void* user, const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(void* user, const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void(void* user)>;
|
||||
using OnInstallStart = std::function<bool(const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void()>;
|
||||
|
||||
void InitInstallMode(void* user, OnInstallStart on_start, OnInstallWrite on_write, OnInstallClose on_close);
|
||||
void InitInstallMode(const OnInstallStart& on_start, const OnInstallWrite& on_write, const OnInstallClose& on_close);
|
||||
void DisableInstallMode();
|
||||
|
||||
unsigned GetPort();
|
||||
|
||||
@@ -14,6 +14,7 @@ enum class Type {
|
||||
Md5,
|
||||
Sha1,
|
||||
Sha256,
|
||||
Null,
|
||||
};
|
||||
|
||||
struct BaseSource {
|
||||
@@ -25,7 +26,7 @@ struct BaseSource {
|
||||
auto GetTypeStr(Type type) -> const char*;
|
||||
|
||||
// returns the hash string.
|
||||
Result Hash(ui::ProgressBox* pbox, Type type, std::shared_ptr<BaseSource> source, std::string& out);
|
||||
Result Hash(ui::ProgressBox* pbox, Type type, BaseSource* source, std::string& out);
|
||||
Result Hash(ui::ProgressBox* pbox, Type type, fs::Fs* fs, const fs::FsPath& path, std::string& out);
|
||||
Result Hash(ui::ProgressBox* pbox, Type type, std::span<const u8> data, std::string& out);
|
||||
|
||||
|
||||
18
sphaira/include/haze_helper.hpp
Normal file
18
sphaira/include/haze_helper.hpp
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace sphaira::libhaze {
|
||||
|
||||
bool Init();
|
||||
bool IsInit();
|
||||
void Exit();
|
||||
|
||||
using OnInstallStart = std::function<bool(const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void()>;
|
||||
|
||||
void InitInstallMode(const OnInstallStart& on_start, const OnInstallWrite& on_write, const OnInstallClose& on_close);
|
||||
void DisableInstallMode();
|
||||
|
||||
} // namespace sphaira::libhaze
|
||||
@@ -3,23 +3,13 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <switch.h>
|
||||
// to import FsEntryFlags.
|
||||
// todo: this should be part of a smaller header, such as filesystem_types.hpp
|
||||
#include "ui/menus/filebrowser.hpp"
|
||||
|
||||
namespace sphaira::location {
|
||||
|
||||
struct Entry {
|
||||
std::string name{};
|
||||
std::string url{};
|
||||
std::string user{};
|
||||
std::string pass{};
|
||||
std::string bearer{};
|
||||
std::string pub_key{};
|
||||
std::string priv_key{};
|
||||
u16 port{};
|
||||
};
|
||||
using Entries = std::vector<Entry>;
|
||||
|
||||
auto Load() -> Entries;
|
||||
void Add(const Entry& e);
|
||||
using FsEntryFlag = ui::menu::filebrowser::FsEntryFlag;
|
||||
|
||||
// helper for hdd devices.
|
||||
// this doesn't really belong in this header, however
|
||||
@@ -29,8 +19,14 @@ struct StdioEntry {
|
||||
std::string mount{};
|
||||
// ums0: (USB Flash Disk)
|
||||
std::string name{};
|
||||
// set if read-only.
|
||||
bool write_protect;
|
||||
// FsEntryFlag
|
||||
u32 flags{};
|
||||
// optional dump path inside the mount point.
|
||||
std::string dump_path{};
|
||||
// set to hide for filebrowser.
|
||||
bool fs_hidden{};
|
||||
// set to hide in dump list.
|
||||
bool dump_hidden{};
|
||||
};
|
||||
|
||||
using StdioEntries = std::vector<StdioEntry>;
|
||||
|
||||
@@ -12,6 +12,8 @@ extern "C" {
|
||||
bool log_file_init();
|
||||
bool log_nxlink_init();
|
||||
void log_file_exit();
|
||||
bool log_is_init();
|
||||
|
||||
void log_nxlink_exit();
|
||||
void log_write(const char* s, ...) __attribute__ ((format (printf, 1, 2)));
|
||||
void log_write_arg(const char* s, va_list* v);
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <vector>
|
||||
#include <span>
|
||||
#include <switch.h>
|
||||
#include "fs.hpp"
|
||||
|
||||
namespace sphaira::mz {
|
||||
|
||||
@@ -20,5 +21,12 @@ struct MzSpan {
|
||||
void FileFuncMem(MzMem* mem, zlib_filefunc64_def* funcs);
|
||||
void FileFuncSpan(MzSpan* span, zlib_filefunc64_def* funcs);
|
||||
void FileFuncStdio(zlib_filefunc64_def* funcs);
|
||||
void FileFuncNative(zlib_filefunc64_def* funcs);
|
||||
|
||||
// minizip takes 18ms to open a zip and 4ms to parse the first file entry.
|
||||
// this results in a dropped frame.
|
||||
// this version simply reads the local header + file name in 2 reads,
|
||||
// which takes 1-2ms.
|
||||
Result PeekFirstFileName(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& name);
|
||||
|
||||
} // namespace sphaira::mz
|
||||
|
||||
@@ -9,8 +9,14 @@
|
||||
|
||||
namespace sphaira {
|
||||
|
||||
struct NroData {
|
||||
NroStart start;
|
||||
NroHeader header;
|
||||
};
|
||||
|
||||
struct Hbini {
|
||||
u64 timestamp{}; // timestamp of last launch
|
||||
bool hidden{};
|
||||
};
|
||||
|
||||
struct MiniNacp {
|
||||
@@ -61,7 +67,7 @@ auto nro_parse(const fs::FsPath& path, NroEntry& entry) -> Result;
|
||||
* nro found.
|
||||
* this does nothing if nested=false.
|
||||
*/
|
||||
auto nro_scan(const fs::FsPath& path, std::vector<NroEntry>& nros, bool hide_spahira, bool nested = true, bool scan_all_dir = true) -> Result;
|
||||
auto nro_scan(const fs::FsPath& path, std::vector<NroEntry>& nros, bool nested = true, bool scan_all_dir = true) -> Result;
|
||||
|
||||
auto nro_get_icon(const fs::FsPath& path, u64 size, u64 offset) -> std::vector<u8>;
|
||||
auto nro_get_icon(const fs::FsPath& path) -> std::vector<u8>;
|
||||
|
||||
@@ -44,6 +44,9 @@ bool nxlinkInitialize(NxlinkCallback callback);
|
||||
// signal for the event to close and then join the thread.
|
||||
void nxlinkExit();
|
||||
|
||||
// async the exit, call this first and then call exit later to avoid blocking.
|
||||
void nxlinkSignalExit();
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -7,10 +7,11 @@ namespace sphaira::option {
|
||||
|
||||
template<typename T>
|
||||
struct OptionBase {
|
||||
OptionBase(const std::string& section, const std::string& name, T default_value)
|
||||
OptionBase(const std::string& section, const std::string& name, T default_value, bool file = true)
|
||||
: m_section{section}
|
||||
, m_name{name}
|
||||
, m_default_value{default_value}
|
||||
, m_file{file}
|
||||
{}
|
||||
|
||||
auto Get() -> T;
|
||||
@@ -29,11 +30,13 @@ private:
|
||||
const std::string m_section;
|
||||
const std::string m_name;
|
||||
const T m_default_value;
|
||||
const bool m_file;
|
||||
std::optional<T> m_value;
|
||||
};
|
||||
|
||||
using OptionBool = OptionBase<bool>;
|
||||
using OptionLong = OptionBase<long>;
|
||||
using OptionFloat = OptionBase<float>;
|
||||
using OptionString = OptionBase<std::string>;
|
||||
|
||||
} // namespace sphaira::option
|
||||
|
||||
@@ -20,17 +20,6 @@ struct OwoConfig {
|
||||
std::vector<u8> program_nca{};
|
||||
};
|
||||
|
||||
enum {
|
||||
Module_Owo = 424,
|
||||
};
|
||||
|
||||
enum OwoError {
|
||||
OwoError_BadArgs = MAKERESULT(Module_Owo, 1),
|
||||
};
|
||||
|
||||
// fwd
|
||||
// struct ui::ProgressBox;
|
||||
|
||||
auto install_forwarder(OwoConfig& config, NcmStorageId storage_id) -> Result;
|
||||
auto install_forwarder(ui::ProgressBox* pbox, OwoConfig& config, NcmStorageId storage_id) -> Result;
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@
|
||||
|
||||
#include <switch.h>
|
||||
#include <string>
|
||||
#include <sys/syslimits.h>
|
||||
|
||||
namespace sphaira::swkbd {
|
||||
|
||||
Result ShowText(std::string& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = FS_MAX_PATH);
|
||||
Result ShowNumPad(s64& out, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = FS_MAX_PATH);
|
||||
Result ShowText(std::string& out, const char* header = nullptr, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = PATH_MAX);
|
||||
Result ShowNumPad(s64& out, const char* header = nullptr, const char* guide = nullptr, const char* initial = nullptr, s64 len_min = -1, s64 len_max = PATH_MAX);
|
||||
|
||||
} // namespace sphaira::swkbd
|
||||
|
||||
@@ -15,7 +15,10 @@ enum class Mode {
|
||||
SingleThreadedIfSmaller,
|
||||
};
|
||||
|
||||
using DecompressWriteCallback = std::function<Result(const void* data, s64 size)>;
|
||||
|
||||
using ReadCallback = std::function<Result(void* data, s64 off, s64 size, u64* bytes_read)>;
|
||||
using DecompressCallback = std::function<Result(void* data, s64 off, s64 size, const DecompressWriteCallback& callback)>;
|
||||
using WriteCallback = std::function<Result(const void* data, s64 off, s64 size)>;
|
||||
|
||||
// used for pull api
|
||||
@@ -32,25 +35,26 @@ using StartCallback = std::function<Result(PullCallback pull)>;
|
||||
using StartCallback2 = std::function<Result(StartThreadCallback start, PullCallback pull)>;
|
||||
|
||||
// reads data from rfunc into wfunc.
|
||||
Result Transfer(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, WriteCallback wfunc, Mode mode = Mode::MultiThreaded);
|
||||
Result Transfer(ui::ProgressBox* pbox, s64 size, const ReadCallback& rfunc, const WriteCallback& wfunc, Mode mode = Mode::MultiThreaded);
|
||||
Result Transfer(ui::ProgressBox* pbox, s64 size, const ReadCallback& rfunc, const DecompressCallback& dfunc, const WriteCallback& wfunc, Mode mode = Mode::MultiThreaded);
|
||||
|
||||
// reads data from rfunc, pull data from provided pull() callback.
|
||||
Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback sfunc, Mode mode = Mode::MultiThreaded);
|
||||
Result TransferPull(ui::ProgressBox* pbox, s64 size, ReadCallback rfunc, StartCallback2 sfunc, Mode mode = Mode::MultiThreaded);
|
||||
Result TransferPull(ui::ProgressBox* pbox, s64 size, const ReadCallback& rfunc, const StartCallback& sfunc, Mode mode = Mode::MultiThreaded);
|
||||
Result TransferPull(ui::ProgressBox* pbox, s64 size, const ReadCallback& rfunc, const StartCallback2& sfunc, Mode mode = Mode::MultiThreaded);
|
||||
|
||||
// helper for extract zips.
|
||||
// this will multi-thread unzip if size >= 512KiB, otherwise it'll single pass.
|
||||
Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, s64 size, u32 crc32 = 0);
|
||||
Result TransferUnzip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, s64 size, u32 crc32 = 0, Mode mode = Mode::SingleThreadedIfSmaller);
|
||||
|
||||
// same as above but for zipping files.
|
||||
Result TransferZip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, u32* crc32 = nullptr);
|
||||
Result TransferZip(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& path, u32* crc32 = nullptr, Mode mode = Mode::SingleThreadedIfSmaller);
|
||||
|
||||
// passes the name inside the zip an final output path.
|
||||
using UnzipAllFilter = std::function<bool(const fs::FsPath& name, fs::FsPath& path)>;
|
||||
|
||||
// helper all-in-one unzip function that unzips a zip (either open or path provided).
|
||||
// the filter function can be used to modify the path and filter out unwanted files.
|
||||
Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter = nullptr);
|
||||
Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter = nullptr);
|
||||
Result TransferUnzipAll(ui::ProgressBox* pbox, void* zfile, fs::Fs* fs, const fs::FsPath& base_path, const UnzipAllFilter& filter = nullptr, Mode mode = Mode::SingleThreadedIfSmaller);
|
||||
Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs* fs, const fs::FsPath& base_path, const UnzipAllFilter& filter = nullptr, Mode mode = Mode::SingleThreadedIfSmaller);
|
||||
|
||||
} // namespace sphaira::thread
|
||||
|
||||
83
sphaira/include/title_info.hpp
Normal file
83
sphaira/include/title_info.hpp
Normal file
@@ -0,0 +1,83 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::title {
|
||||
|
||||
constexpr u32 ContentMetaTypeToContentFlag(u8 meta_type) {
|
||||
if (meta_type & 0x80) {
|
||||
return 1 << (meta_type - 0x80);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
enum ContentFlag {
|
||||
ContentFlag_Application = ContentMetaTypeToContentFlag(NcmContentMetaType_Application),
|
||||
ContentFlag_Patch = ContentMetaTypeToContentFlag(NcmContentMetaType_Patch),
|
||||
ContentFlag_AddOnContent = ContentMetaTypeToContentFlag(NcmContentMetaType_AddOnContent),
|
||||
ContentFlag_DataPatch = ContentMetaTypeToContentFlag(NcmContentMetaType_DataPatch),
|
||||
|
||||
// nca locations where a control.nacp can exist.
|
||||
ContentFlag_Nacp = ContentFlag_Application | ContentFlag_Patch,
|
||||
// all of the above.
|
||||
ContentFlag_All = ContentFlag_Application | ContentFlag_Patch | ContentFlag_AddOnContent | ContentFlag_DataPatch,
|
||||
};
|
||||
|
||||
enum class NacpLoadStatus {
|
||||
// not yet attempted to be loaded.
|
||||
None,
|
||||
// started loading.
|
||||
Progress,
|
||||
// loaded, ready to parse.
|
||||
Loaded,
|
||||
// failed to load, do not attempt to load again!
|
||||
Error,
|
||||
};
|
||||
|
||||
struct ThreadResultData {
|
||||
u64 id{};
|
||||
std::vector<u8> icon;
|
||||
NacpLanguageEntry lang{};
|
||||
NacpLoadStatus status{NacpLoadStatus::None};
|
||||
};
|
||||
|
||||
using MetaEntries = std::vector<NsApplicationContentMetaStatus>;
|
||||
|
||||
// starts background thread (ref counted).
|
||||
Result Init();
|
||||
// closes the background thread.
|
||||
void Exit();
|
||||
// clears cache and empties the result array.
|
||||
void Clear();
|
||||
|
||||
// adds new entry to queue.
|
||||
void PushAsync(u64 app_id);
|
||||
void PushAsync(const std::span<const NsApplicationRecord> app_ids);
|
||||
// gets entry without removing it from the queue.
|
||||
auto GetAsync(u64 app_id) -> ThreadResultData*;
|
||||
// single threaded title info fetch.
|
||||
auto Get(u64 app_id, bool* cached = nullptr) -> ThreadResultData*;
|
||||
|
||||
auto GetNcmCs(u8 storage_id) -> NcmContentStorage&;
|
||||
auto GetNcmDb(u8 storage_id) -> NcmContentMetaDatabase&;
|
||||
|
||||
// gets all meta entries for an id.
|
||||
Result GetMetaEntries(u64 id, MetaEntries& out, u32 flags = ContentFlag_All);
|
||||
|
||||
// returns the nca path of a control nca.
|
||||
Result GetControlPathFromStatus(const NsApplicationContentMetaStatus& status, u64* out_program_id, fs::FsPath* out_path);
|
||||
|
||||
// taken from nxdumptool.
|
||||
void utilsReplaceIllegalCharacters(char *str, bool ascii_only);
|
||||
|
||||
// /atmosphere/contents/xxx
|
||||
auto GetContentsPath(u64 app_id) -> fs::FsPath;
|
||||
|
||||
} // namespace sphaira::title
|
||||
@@ -16,6 +16,8 @@ public:
|
||||
private:
|
||||
std::optional<Result> m_code{};
|
||||
std::string m_message{};
|
||||
std::string m_code_message{};
|
||||
std::string m_code_module{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -10,14 +10,14 @@ struct List final : Object {
|
||||
GRID,
|
||||
};
|
||||
|
||||
using Callback = std::function<void(NVGcontext* vg, Theme* theme, Vec4 v, s64 index)>;
|
||||
using Callback = std::function<void(NVGcontext* vg, Theme* theme, const Vec4& v, s64 index)>;
|
||||
using TouchCallback = std::function<void(bool touch, s64 index)>;
|
||||
|
||||
List(s64 row, s64 page, const Vec4& pos, const Vec4& v, const Vec2& pad = {});
|
||||
|
||||
void OnUpdate(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
|
||||
void OnUpdate(Controller* controller, TouchInfo* touch, s64 index, s64 count, const TouchCallback& callback);
|
||||
|
||||
void Draw(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
|
||||
void Draw(NVGcontext* vg, Theme* theme, s64 count, const Callback& callback) const;
|
||||
|
||||
auto SetScrollBarPos(float x, float y, float h) {
|
||||
m_scrollbar.x = x;
|
||||
@@ -73,10 +73,10 @@ private:
|
||||
auto ClampX(float x, s64 count) const -> float;
|
||||
auto ClampY(float y, s64 count) const -> float;
|
||||
|
||||
void OnUpdateHome(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
|
||||
void OnUpdateGrid(Controller* controller, TouchInfo* touch, s64 index, s64 count, TouchCallback callback);
|
||||
void DrawHome(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
|
||||
void DrawGrid(NVGcontext* vg, Theme* theme, s64 count, Callback callback) const;
|
||||
void OnUpdateHome(Controller* controller, TouchInfo* touch, s64 index, s64 count, const TouchCallback& callback);
|
||||
void OnUpdateGrid(Controller* controller, TouchInfo* touch, s64 index, s64 count, const TouchCallback& callback);
|
||||
void DrawHome(NVGcontext* vg, Theme* theme, s64 count, const Callback& callback) const;
|
||||
void DrawGrid(NVGcontext* vg, Theme* theme, s64 count, const Callback& callback) const;
|
||||
|
||||
private:
|
||||
const s64 m_row;
|
||||
|
||||
@@ -86,14 +86,16 @@ struct EntryMenu final : MenuBase {
|
||||
|
||||
private:
|
||||
struct Option {
|
||||
Option(const std::string& dt, const std::string& ct, std::function<void(void)> f)
|
||||
using Callback = std::function<void(void)>;
|
||||
|
||||
Option(const std::string& dt, const std::string& ct, const Callback& f)
|
||||
: display_text{dt}, confirm_text{ct}, func{f} {}
|
||||
Option(const std::string& dt, std::function<void(void)> f)
|
||||
Option(const std::string& dt, const Callback& f)
|
||||
: display_text{dt}, func{f} {}
|
||||
|
||||
std::string display_text{};
|
||||
std::string confirm_text{};
|
||||
std::function<void(void)> func{};
|
||||
const std::string display_text;
|
||||
const std::string confirm_text;
|
||||
const Callback func{};
|
||||
};
|
||||
|
||||
Entry& m_entry;
|
||||
@@ -105,9 +107,9 @@ private:
|
||||
LazyImage m_banner{};
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
std::shared_ptr<ScrollableText> m_details{};
|
||||
std::shared_ptr<ScrollableText> m_changelog{};
|
||||
std::shared_ptr<ScrollableText> m_detail_changelog{};
|
||||
std::unique_ptr<ScrollableText> m_details{};
|
||||
std::unique_ptr<ScrollableText> m_changelog{};
|
||||
ScrollableText* m_detail_changelog{};
|
||||
std::unique_ptr<ScrollableText> m_manifest_list{};
|
||||
|
||||
bool m_show_changlog{};
|
||||
|
||||
19
sphaira/include/ui/menus/file_picker.hpp
Normal file
19
sphaira/include/ui/menus/file_picker.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/filebrowser.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::filebrowser::picker {
|
||||
|
||||
using Callback = std::function<bool(const fs::FsPath& path)>;
|
||||
|
||||
struct Menu final : Base {
|
||||
explicit Menu(const Callback& cb, const std::vector<std::string>& filter = {}, const fs::FsPath& path = {});
|
||||
|
||||
private:
|
||||
void OnClick(FsView* view, const FsEntry& fs_entry, const FileEntry& entry, const fs::FsPath& path) override;
|
||||
|
||||
private:
|
||||
const Callback m_callback;
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::filebrowser::picker
|
||||
@@ -7,7 +7,7 @@
|
||||
namespace sphaira::ui::menu::fileview {
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(const fs::FsPath& path);
|
||||
Menu(fs::Fs* fs, const fs::FsPath& path);
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "File"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
@@ -15,8 +15,8 @@ struct Menu final : MenuBase {
|
||||
void OnFocusGained() override;
|
||||
|
||||
private:
|
||||
fs::Fs* const m_fs;
|
||||
const fs::FsPath m_path;
|
||||
fs::FsNativeSd m_fs{};
|
||||
fs::File m_file{};
|
||||
s64 m_file_size{};
|
||||
s64 m_file_offset{};
|
||||
|
||||
@@ -2,21 +2,48 @@
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "ui/progress_box.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
#include "hasher.hpp"
|
||||
// #include <optional>
|
||||
#include "nro.hpp"
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::filebrowser {
|
||||
|
||||
enum FsOption : u32 {
|
||||
FsOption_NONE,
|
||||
|
||||
// can split screen.
|
||||
FsOption_CanSplit = BIT(0),
|
||||
// can selected multiple files.
|
||||
FsOption_CanSelect = BIT(1),
|
||||
// shows the option to install.
|
||||
FsOption_CanInstall = BIT(2),
|
||||
// loads file assoc.
|
||||
FsOption_LoadAssoc = BIT(3),
|
||||
// do not prompt on exit even if not tabbed.
|
||||
FsOption_DoNotPrompt = BIT(4),
|
||||
|
||||
FsOption_Normal = FsOption_LoadAssoc | FsOption_CanInstall | FsOption_CanSplit | FsOption_CanSelect,
|
||||
FsOption_All = FsOption_DoNotPrompt | FsOption_Normal,
|
||||
FsOption_Picker = FsOption_NONE,
|
||||
};
|
||||
|
||||
enum FsEntryFlag {
|
||||
FsEntryFlag_None,
|
||||
// write protected.
|
||||
FsEntryFlag_ReadOnly = 1 << 0,
|
||||
// supports file assoc.
|
||||
FsEntryFlag_Assoc = 1 << 1,
|
||||
// this is an sd card, files can be launched from here.
|
||||
FsEntryFlag_IsSd = 1 << 2, // todo: remove this.
|
||||
// do not stat files in this entry (faster for network mount).
|
||||
FsEntryFlag_NoStatFile = 1 << 3,
|
||||
FsEntryFlag_NoStatDir = 1 << 4,
|
||||
FsEntryFlag_NoRandomReads = 1 << 5,
|
||||
FsEntryFlag_NoRandomWrites = 1 << 6,
|
||||
};
|
||||
|
||||
enum class FsType {
|
||||
@@ -24,6 +51,7 @@ enum class FsType {
|
||||
ImageNand,
|
||||
ImageSd,
|
||||
Stdio,
|
||||
Custom,
|
||||
};
|
||||
|
||||
enum class SelectedType {
|
||||
@@ -62,13 +90,33 @@ struct FsEntry {
|
||||
return flags & FsEntryFlag_Assoc;
|
||||
}
|
||||
|
||||
auto IsSd() const -> bool {
|
||||
return flags & FsEntryFlag_IsSd;
|
||||
}
|
||||
|
||||
auto IsNoStatFile() const -> bool {
|
||||
return flags & FsEntryFlag_NoStatFile;
|
||||
}
|
||||
|
||||
auto IsNoStatDir() const -> bool {
|
||||
return flags & FsEntryFlag_NoStatDir;
|
||||
}
|
||||
|
||||
auto IsNoRandomReads() const -> bool {
|
||||
return flags & FsEntryFlag_NoRandomReads;
|
||||
}
|
||||
|
||||
auto IsNoRandomWrites() const -> bool {
|
||||
return flags & FsEntryFlag_NoRandomWrites;
|
||||
}
|
||||
|
||||
auto IsSame(const FsEntry& e) const {
|
||||
return root == e.root && type == e.type;
|
||||
}
|
||||
};
|
||||
|
||||
// roughly 1kib in size per entry
|
||||
struct FileEntry : FsDirectoryEntry {
|
||||
struct FileEntry final : FsDirectoryEntry {
|
||||
std::string extension{}; // if any
|
||||
std::string internal_name{}; // if any
|
||||
std::string internal_extension{}; // if any
|
||||
@@ -78,6 +126,7 @@ struct FileEntry : FsDirectoryEntry {
|
||||
bool checked_extension{}; // did we already search for an ext?
|
||||
bool checked_internal_extension{}; // did we already search for an ext?
|
||||
bool selected{}; // is this file selected?
|
||||
bool done_stat{}; // have we checked file_size / count.
|
||||
|
||||
auto IsFile() const -> bool {
|
||||
return type == FsDirEntryType_File;
|
||||
@@ -96,6 +145,11 @@ struct FileEntry : FsDirectoryEntry {
|
||||
}
|
||||
|
||||
auto GetExtension() const -> std::string {
|
||||
if (!checked_extension) {
|
||||
if (auto ext = std::strrchr(name, '.')) {
|
||||
return ext+1;
|
||||
}
|
||||
}
|
||||
return extension;
|
||||
}
|
||||
|
||||
@@ -154,13 +208,16 @@ struct FsDirCollection {
|
||||
|
||||
using FsDirCollections = std::vector<FsDirCollection>;
|
||||
|
||||
struct Menu;
|
||||
void SignalChange();
|
||||
|
||||
struct Base;
|
||||
|
||||
struct FsView final : Widget {
|
||||
friend class Menu;
|
||||
friend class Base;
|
||||
|
||||
FsView(Menu* menu, ViewSide side);
|
||||
FsView(Menu* menu, const fs::FsPath& path, const FsEntry& entry, ViewSide side);
|
||||
FsView(FsView* view, ViewSide side);
|
||||
FsView(Base* menu, ViewSide side);
|
||||
FsView(Base* menu, const std::shared_ptr<fs::Fs>& fs, const fs::FsPath& path, const FsEntry& entry, ViewSide side);
|
||||
~FsView();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
@@ -181,10 +238,13 @@ struct FsView final : Widget {
|
||||
|
||||
void SetSide(ViewSide side);
|
||||
|
||||
static Result DeleteAllCollections(ProgressBox* pbox, fs::Fs* fs, const FsDirCollections& collections, u32 mode = FsDirOpenMode_ReadDirs|FsDirOpenMode_ReadFiles);
|
||||
static auto get_collection(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
|
||||
static auto get_collections(fs::Fs* fs, const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
|
||||
|
||||
private:
|
||||
// private:
|
||||
void OnClick();
|
||||
|
||||
void SetIndex(s64 index);
|
||||
void InstallForwarder();
|
||||
|
||||
@@ -193,7 +253,7 @@ private:
|
||||
void ZipFiles(fs::FsPath zip_path);
|
||||
void UploadFiles();
|
||||
|
||||
auto Scan(const fs::FsPath& new_path, bool is_walk_up = false) -> Result;
|
||||
auto Scan(fs::FsPath new_path, bool is_walk_up = false) -> Result;
|
||||
|
||||
auto GetNewPath(const FileEntry& entry) const -> fs::FsPath {
|
||||
return GetNewPath(m_path, entry.name);
|
||||
@@ -240,11 +300,11 @@ private:
|
||||
}
|
||||
|
||||
auto IsSd() const -> bool {
|
||||
return m_fs_entry.type == FsType::Sd;
|
||||
return m_fs_entry.IsSd();
|
||||
}
|
||||
|
||||
void Sort();
|
||||
void SortAndFindLastFile();
|
||||
void SortAndFindLastFile(bool scan = false);
|
||||
void SetIndexFromLastFile(const LastFile& last_file);
|
||||
|
||||
void OnDeleteCallback();
|
||||
@@ -255,7 +315,7 @@ private:
|
||||
auto get_collection(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollection& out, bool inc_file, bool inc_dir, bool inc_size) -> Result;
|
||||
auto get_collections(const fs::FsPath& path, const fs::FsPath& parent_name, FsDirCollections& out, bool inc_size = false) -> Result;
|
||||
|
||||
void SetFs(const fs::FsPath& new_path, const FsEntry& new_entry);
|
||||
void SetFs(const std::shared_ptr<fs::Fs>& fs, const fs::FsPath& new_path, const FsEntry& new_entry);
|
||||
|
||||
auto GetNative() -> fs::FsNative* {
|
||||
return (fs::FsNative*)m_fs.get();
|
||||
@@ -266,11 +326,17 @@ private:
|
||||
void DisplayOptions();
|
||||
void DisplayAdvancedOptions();
|
||||
|
||||
private:
|
||||
Menu* m_menu{};
|
||||
using MountFsFunc = Result(*)(fs::Fs *fs, const fs::FsPath &path, fs::FsPath &out_path);
|
||||
// using MountFsFunc = std::function<Result(fs::Fs *fs, const fs::FsPath &path, fs::FsPath &out_path)>;
|
||||
using UmountFsFunc = std::function<void(const fs::FsPath &mount)>;
|
||||
|
||||
void MountFileFs(const MountFsFunc& mount_func, const UmountFsFunc& umount_func);
|
||||
|
||||
// private:
|
||||
Base* m_menu{};
|
||||
ViewSide m_side{};
|
||||
|
||||
std::unique_ptr<fs::Fs> m_fs{};
|
||||
std::shared_ptr<fs::Fs> m_fs{};
|
||||
FsEntry m_fs_entry{};
|
||||
fs::FsPath m_path{};
|
||||
std::vector<FileEntry> m_entries{};
|
||||
@@ -296,7 +362,7 @@ private:
|
||||
|
||||
// contains all selected files for a command, such as copy, delete, cut etc.
|
||||
struct SelectedStash {
|
||||
void Add(std::shared_ptr<FsView> view, SelectedType type, const std::vector<FileEntry>& files, const fs::FsPath& path) {
|
||||
void Add(FsView* view, SelectedType type, const std::vector<FileEntry>& files, const fs::FsPath& path) {
|
||||
if (files.empty()) {
|
||||
Reset();
|
||||
} else {
|
||||
@@ -331,28 +397,34 @@ struct SelectedStash {
|
||||
}
|
||||
|
||||
// private:
|
||||
std::shared_ptr<FsView> m_view{};
|
||||
FsView* m_view{};
|
||||
std::vector<FileEntry> m_files{};
|
||||
fs::FsPath m_path{};
|
||||
SelectedType m_type{SelectedType::None};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
struct Base : MenuBase {
|
||||
friend class FsView;
|
||||
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
Base(u32 flags, u32 options);
|
||||
Base(const std::shared_ptr<fs::Fs>& fs, const FsEntry& fs_entry, const fs::FsPath& path, bool is_custom, u32 flags, u32 options);
|
||||
|
||||
void SetFilter(const std::vector<std::string>& filter) {
|
||||
m_filter = filter;
|
||||
}
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Files"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
virtual void OnFocusGained() override;
|
||||
|
||||
static auto GetNewPath(const fs::FsPath& root_path, const fs::FsPath& file_path) -> fs::FsPath {
|
||||
return fs::AppendPath(root_path, file_path);
|
||||
}
|
||||
|
||||
private:
|
||||
virtual void OnClick(FsView* view, const FsEntry& fs_entry, const FileEntry& entry, const fs::FsPath& path);
|
||||
|
||||
protected:
|
||||
auto IsSplitScreen() const {
|
||||
return m_split_screen;
|
||||
}
|
||||
@@ -382,23 +454,40 @@ private:
|
||||
|
||||
void PromptIfShouldExit();
|
||||
|
||||
auto CanInstall() const {
|
||||
return m_options & FsOption_CanInstall;
|
||||
}
|
||||
|
||||
auto CreateFs(const FsEntry& fs_entry) -> std::shared_ptr<fs::Fs>;
|
||||
|
||||
private:
|
||||
void Init(const std::shared_ptr<fs::Fs>& fs, const FsEntry& fs_entry, const fs::FsPath& path, bool is_custom);
|
||||
|
||||
protected:
|
||||
static constexpr inline const char* INI_SECTION = "filebrowser";
|
||||
|
||||
std::shared_ptr<FsView> view{};
|
||||
std::shared_ptr<FsView> view_left{};
|
||||
std::shared_ptr<FsView> view_right{};
|
||||
const u32 m_options;
|
||||
|
||||
std::shared_ptr<fs::Fs> m_custom_fs{};
|
||||
FsEntry m_custom_fs_entry{};
|
||||
|
||||
FsView* view{};
|
||||
std::unique_ptr<FsView> view_left{};
|
||||
std::unique_ptr<FsView> view_right{};
|
||||
|
||||
std::vector<FileAssocEntry> m_assoc_entries{};
|
||||
SelectedStash m_selected{};
|
||||
|
||||
// this keeps track of the highlighted file before opening a folder
|
||||
// if the user presses B to go back to the previous dir
|
||||
// this vector is popped, then, that entry is checked if it still exists
|
||||
// if it does, the index becomes that file.
|
||||
std::vector<LastFile> m_previous_highlighted_file{};
|
||||
s64 m_index{};
|
||||
s64 m_selected_count{};
|
||||
std::vector<std::string> m_filter{};
|
||||
|
||||
// local copy of nro entries that is loaded in LoadAssocEntriesPath()
|
||||
// if homebrew::GetNroEntries() returns nothing, usually due to
|
||||
// the menu not being loaded.
|
||||
// this is a bit of a hack to support replacing the homebrew menu tab,
|
||||
// sphaira wasn't really designed for this.
|
||||
// however this will work for now, until i add support for additional
|
||||
// nro scan mounts, at which point this won't scale.
|
||||
std::vector<NroEntry> m_nro_entries{};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Alphabetical};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||
@@ -408,8 +497,39 @@ private:
|
||||
option::OptionBool m_ignore_read_only{INI_SECTION, "ignore_read_only", false};
|
||||
|
||||
bool m_loaded_assoc_entries{};
|
||||
bool m_is_update_folder{};
|
||||
bool m_split_screen{};
|
||||
};
|
||||
|
||||
struct Menu final : Base {
|
||||
Menu(u32 flags, u32 options = FsOption_All) : Base{flags, options} {
|
||||
}
|
||||
|
||||
Menu(const std::shared_ptr<fs::Fs>& fs, const FsEntry& fs_entry, const fs::FsPath& path, u32 options = FsOption_All)
|
||||
: Base{fs, fs_entry, path, true, MenuFlag_None, options} {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
// case insensitive check
|
||||
auto IsSamePath(std::string_view a, std::string_view b) -> bool;
|
||||
auto IsExtension(std::string_view ext1, std::string_view ext2) -> bool;
|
||||
auto IsExtension(std::string_view ext, std::span<const std::string_view> list) -> bool;
|
||||
|
||||
struct FsStdioWrapper final : fs::FsStdio {
|
||||
using OnExit = std::function<void(void)>;
|
||||
FsStdioWrapper(const fs::FsPath& root, const OnExit& on_exit) : fs::FsStdio{true, root}, m_on_exit{on_exit} {
|
||||
|
||||
}
|
||||
|
||||
~FsStdioWrapper() {
|
||||
if (m_on_exit) {
|
||||
m_on_exit();
|
||||
}
|
||||
}
|
||||
|
||||
const OnExit m_on_exit;
|
||||
};
|
||||
|
||||
void MountFsHelper(const std::shared_ptr<fs::Fs>& fs, const fs::FsPath& name);
|
||||
|
||||
} // namespace sphaira::ui::menu::filebrowser
|
||||
|
||||
@@ -1,56 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "yati/source/stream.hpp"
|
||||
#include "ui/menus/install_stream_menu_base.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::ftp {
|
||||
|
||||
enum class State {
|
||||
// not connected.
|
||||
None,
|
||||
// just connected, starts the transfer.
|
||||
Connected,
|
||||
// set whilst transfer is in progress.
|
||||
Progress,
|
||||
// set when the transfer is finished.
|
||||
Done,
|
||||
// failed to connect.
|
||||
Failed,
|
||||
};
|
||||
|
||||
struct StreamFtp final : yati::source::Stream {
|
||||
StreamFtp(const fs::FsPath& path, std::stop_token token);
|
||||
|
||||
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
|
||||
bool Push(const void* buf, s64 size);
|
||||
void Disable();
|
||||
|
||||
// private:
|
||||
fs::FsPath m_path{};
|
||||
std::stop_token m_token{};
|
||||
std::vector<u8> m_buffer{};
|
||||
Mutex m_mutex{};
|
||||
bool m_active{};
|
||||
// bool m_push_exit{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
struct Menu final : stream::Menu {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "FTP"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
// this should be private
|
||||
// private:
|
||||
std::shared_ptr<StreamFtp> m_source{};
|
||||
Thread m_thread{};
|
||||
Mutex m_mutex{};
|
||||
// the below are shared across threads, lock with the above mutex!
|
||||
State m_state{State::None};
|
||||
void OnDisableInstallMode() override;
|
||||
|
||||
private:
|
||||
const char* m_user{};
|
||||
const char* m_pass{};
|
||||
unsigned m_port{};
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
|
||||
#include "ui/menus/grid_menu_base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
|
||||
#include "yati/container/base.hpp"
|
||||
#include "yati/nx/keys.hpp"
|
||||
|
||||
#include "title_info.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
#include <memory>
|
||||
@@ -10,26 +15,13 @@
|
||||
|
||||
namespace sphaira::ui::menu::game {
|
||||
|
||||
enum class NacpLoadStatus {
|
||||
// not yet attempted to be loaded.
|
||||
None,
|
||||
// started loading.
|
||||
Progress,
|
||||
// loaded, ready to parse.
|
||||
Loaded,
|
||||
// failed to load, do not attempt to load again!
|
||||
Error,
|
||||
};
|
||||
|
||||
struct Entry {
|
||||
u64 app_id{};
|
||||
u8 last_event{};
|
||||
NacpLanguageEntry lang{};
|
||||
int image{};
|
||||
bool selected{};
|
||||
|
||||
std::shared_ptr<NsApplicationControlData> control{};
|
||||
u64 jpeg_size{};
|
||||
NacpLoadStatus status{NacpLoadStatus::None};
|
||||
title::NacpLoadStatus status{title::NacpLoadStatus::None};
|
||||
|
||||
auto GetName() const -> const char* {
|
||||
return lang.name;
|
||||
@@ -40,45 +32,6 @@ struct Entry {
|
||||
}
|
||||
};
|
||||
|
||||
struct ThreadResultData {
|
||||
u64 id{};
|
||||
std::shared_ptr<NsApplicationControlData> control{};
|
||||
u64 jpeg_size{};
|
||||
NacpLanguageEntry lang{};
|
||||
NacpLoadStatus status{NacpLoadStatus::None};
|
||||
};
|
||||
|
||||
struct ThreadData {
|
||||
ThreadData(bool title_cache);
|
||||
|
||||
void Run();
|
||||
void Close();
|
||||
void Push(u64 id);
|
||||
void Push(std::span<const Entry> entries);
|
||||
void Pop(std::vector<ThreadResultData>& out);
|
||||
|
||||
auto IsRunning() const -> bool {
|
||||
return m_running;
|
||||
}
|
||||
|
||||
auto IsTitleCacheEnabled() const {
|
||||
return m_title_cache;
|
||||
}
|
||||
|
||||
private:
|
||||
UEvent m_uevent{};
|
||||
Mutex m_mutex_id{};
|
||||
Mutex m_mutex_result{};
|
||||
bool m_title_cache{};
|
||||
|
||||
// app_ids pushed to the queue, signal uevent when pushed.
|
||||
std::vector<u64> m_ids{};
|
||||
// control data pushed to the queue.
|
||||
std::vector<ThreadResultData> m_result{};
|
||||
|
||||
std::atomic_bool m_running{};
|
||||
};
|
||||
|
||||
enum SortType {
|
||||
SortType_Updated,
|
||||
};
|
||||
@@ -90,6 +43,8 @@ enum OrderType {
|
||||
|
||||
using LayoutType = grid::LayoutType;
|
||||
|
||||
void SignalChange();
|
||||
|
||||
struct Menu final : grid::Menu {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
@@ -131,7 +86,9 @@ private:
|
||||
}
|
||||
|
||||
void DeleteGames();
|
||||
void DumpGames(u32 flags);
|
||||
void ExportOptions(bool to_nsz);
|
||||
void DumpGames(u32 flags, bool to_nsz);
|
||||
void CreateSaves(AccountUid uid);
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "games";
|
||||
@@ -144,14 +101,79 @@ private:
|
||||
bool m_is_reversed{};
|
||||
bool m_dirty{};
|
||||
|
||||
std::unique_ptr<ThreadData> m_thread_data{};
|
||||
Thread m_thread{};
|
||||
// use for detection game card removal to force a refresh.
|
||||
Event m_gc_event{};
|
||||
FsEventNotifier m_gc_event_notifier{};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_Grid};
|
||||
option::OptionBool m_hide_forwarders{INI_SECTION, "hide_forwarders", false};
|
||||
option::OptionBool m_title_cache{INI_SECTION, "title_cache", true};
|
||||
};
|
||||
|
||||
struct NcmMetaData {
|
||||
// points to global service, do not close manually!
|
||||
NcmContentStorage* cs{};
|
||||
NcmContentMetaDatabase* db{};
|
||||
u64 app_id{};
|
||||
NcmContentMetaKey key{};
|
||||
};
|
||||
|
||||
Result GetMetaEntries(const Entry& e, title::MetaEntries& out, u32 flags = title::ContentFlag_All);
|
||||
|
||||
Result GetNcmMetaFromMetaStatus(const NsApplicationContentMetaStatus& status, NcmMetaData& out);
|
||||
void DeleteMetaEntries(u64 app_id, int image, const std::string& name, const title::MetaEntries& entries);
|
||||
|
||||
struct TikEntry {
|
||||
FsRightsId id{};
|
||||
u8 key_gen{};
|
||||
std::vector<u8> tik_data{};
|
||||
std::vector<u8> cert_data{};
|
||||
};
|
||||
|
||||
struct NspEntry {
|
||||
// application name.
|
||||
std::string application_name{};
|
||||
// name of the nsp (name [id][v0][BASE].nsp).
|
||||
fs::FsPath path{};
|
||||
// tickets and cert data, will be empty if title key crypto isn't used.
|
||||
std::vector<TikEntry> tickets{};
|
||||
// all the collections for this nsp, such as nca's and tickets.
|
||||
std::vector<yati::container::CollectionEntry> collections{};
|
||||
// raw nsp data (header, file table and string table).
|
||||
std::vector<u8> nsp_data{};
|
||||
// size of the entier nsp.
|
||||
s64 nsp_size{};
|
||||
// copy of ncm cs, it is not closed.
|
||||
NcmContentStorage cs{};
|
||||
// copy of the icon, if invalid, it will use the default icon.
|
||||
int icon{};
|
||||
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read);
|
||||
|
||||
private:
|
||||
static auto InRange(s64 off, s64 offset, s64 size) -> bool {
|
||||
return off < offset + size && off >= offset;
|
||||
}
|
||||
|
||||
static auto ClipSize(s64 off, s64 size, s64 file_size) -> s64 {
|
||||
return std::min(size, file_size - off);
|
||||
}
|
||||
};
|
||||
|
||||
struct ContentInfoEntry {
|
||||
NsApplicationContentMetaStatus status{};
|
||||
std::vector<NcmContentInfo> content_infos{};
|
||||
std::vector<NcmRightsId> ncm_rights_id{};
|
||||
};
|
||||
|
||||
auto BuildNspPath(const Entry& e, const NsApplicationContentMetaStatus& status, bool to_nsz = false) -> fs::FsPath;
|
||||
Result BuildContentEntry(const NsApplicationContentMetaStatus& status, ContentInfoEntry& out, bool to_nsz = false);
|
||||
Result BuildNspEntry(const Entry& e, const ContentInfoEntry& info, const keys::Keys& keys, NspEntry& out, bool to_nsz = false);
|
||||
Result BuildNspEntries(Entry& e, const title::MetaEntries& meta_entries, std::vector<NspEntry>& out, bool to_nsz = false);
|
||||
Result BuildNspEntries(Entry& e, u32 flags, std::vector<NspEntry>& out, bool to_nsz = false);
|
||||
|
||||
// dumps the array of nsp entries.
|
||||
void DumpNsp(const std::vector<NspEntry>& entries, bool to_nsz);
|
||||
|
||||
} // namespace sphaira::ui::menu::game
|
||||
|
||||
111
sphaira/include/ui/menus/game_meta_menu.hpp
Normal file
111
sphaira/include/ui/menus/game_meta_menu.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/menus/game_menu.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "yati/nx/ncm.hpp"
|
||||
#include <span>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::menu::game::meta {
|
||||
|
||||
enum TicketType : u8 {
|
||||
TicketType_None,
|
||||
TicketType_Common,
|
||||
TicketType_Personalised,
|
||||
TicketType_Missing,
|
||||
};
|
||||
|
||||
struct MiniNacp {
|
||||
char display_version[0x10];
|
||||
};
|
||||
|
||||
struct MetaEntry {
|
||||
NsApplicationContentMetaStatus status{};
|
||||
ncm::ContentMeta content_meta{};
|
||||
// small version of nacp to speed up loading.
|
||||
MiniNacp nacp{};
|
||||
// total size of all ncas.
|
||||
s64 size{};
|
||||
// set to the key gen (if possible), only if title key encrypted.
|
||||
u8 key_gen{};
|
||||
// set to the ticket type.
|
||||
u8 ticket_type{TicketType_None};
|
||||
// set if it has missing ncas.
|
||||
u8 missing_count{};
|
||||
// set if selected.
|
||||
bool selected{};
|
||||
// set if we have checked the above meta data.
|
||||
bool checked{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(Entry& entry);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Meta"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
void Scan();
|
||||
void UpdateSubheading();
|
||||
|
||||
auto GetSelectedEntries() const {
|
||||
title::MetaEntries out;
|
||||
for (auto& e : m_entries) {
|
||||
if (e.selected) {
|
||||
out.emplace_back(e.status);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_entries.empty() && out.empty()) {
|
||||
out.emplace_back(m_entries[m_index].status);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void ClearSelection() {
|
||||
for (auto& e : m_entries) {
|
||||
e.selected = false;
|
||||
}
|
||||
|
||||
m_selected_count = 0;
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) -> MetaEntry& {
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) const -> const MetaEntry& {
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
auto GetEntry() -> MetaEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
auto GetEntry() const -> const MetaEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
void DumpGames(bool to_nsz);
|
||||
void DeleteGames();
|
||||
Result ResetRequiredSystemVersion(MetaEntry& entry) const;
|
||||
Result GetNcmSizeOfMetaStatus(MetaEntry& entry) const;
|
||||
|
||||
private:
|
||||
Entry& m_entry;
|
||||
std::vector<MetaEntry> m_entries{};
|
||||
s64 m_index{};
|
||||
s64 m_selected_count{};
|
||||
std::unique_ptr<List> m_list{};
|
||||
bool m_dirty{};
|
||||
|
||||
std::vector<FsRightsId> m_common_tickets{};
|
||||
std::vector<FsRightsId> m_personalised_tickets{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::game::meta
|
||||
92
sphaira/include/ui/menus/game_nca_menu.hpp
Normal file
92
sphaira/include/ui/menus/game_nca_menu.hpp
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "ui/menus/game_meta_menu.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "yati/nx/nca.hpp"
|
||||
#include "yati/nx/ncm.hpp"
|
||||
#include <span>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::menu::game::meta_nca {
|
||||
|
||||
struct NcaEntry {
|
||||
NcmContentId content_id{};
|
||||
u64 size{};
|
||||
u8 content_type{};
|
||||
// decrypted nca header.
|
||||
nca::Header header{};
|
||||
// set if missing.
|
||||
bool missing{};
|
||||
// set if selected.
|
||||
bool selected{};
|
||||
// set if we have checked the above meta data.
|
||||
bool checked{};
|
||||
};
|
||||
|
||||
struct Menu final : MenuBase {
|
||||
Menu(Entry& entry, const meta::MetaEntry& meta_entry);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Nca"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
void Scan();
|
||||
void UpdateSubheading();
|
||||
|
||||
auto GetSelectedEntries() const {
|
||||
std::vector<NcaEntry> out;
|
||||
for (auto& e : m_entries) {
|
||||
if (e.selected && !e.missing) {
|
||||
out.emplace_back(e);
|
||||
}
|
||||
}
|
||||
|
||||
if (!m_entries.empty() && out.empty()) {
|
||||
out.emplace_back(m_entries[m_index]);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void ClearSelection() {
|
||||
for (auto& e : m_entries) {
|
||||
e.selected = false;
|
||||
}
|
||||
|
||||
m_selected_count = 0;
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) -> NcaEntry& {
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
auto GetEntry(u32 index) const -> const NcaEntry& {
|
||||
return m_entries[index];
|
||||
}
|
||||
|
||||
auto GetEntry() -> NcaEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
auto GetEntry() const -> const NcaEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
void DumpNcas();
|
||||
Result MountNcaFs();
|
||||
|
||||
private:
|
||||
Entry& m_entry;
|
||||
const meta::MetaEntry& m_meta_entry;
|
||||
NcmMetaData m_meta{};
|
||||
std::vector<NcaEntry> m_entries{};
|
||||
s64 m_index{};
|
||||
s64 m_selected_count{};
|
||||
std::unique_ptr<List> m_list{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::game::meta_nca
|
||||
@@ -7,7 +7,8 @@
|
||||
#include <span>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::menu::gc {
|
||||
// todo: pr to libnx
|
||||
extern "C" {
|
||||
|
||||
typedef enum {
|
||||
FsGameCardPartitionRaw_None = -1,
|
||||
@@ -15,6 +16,13 @@ typedef enum {
|
||||
FsGameCardPartitionRaw_Secure = 1,
|
||||
} FsGameCardPartitionRaw;
|
||||
|
||||
Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, FsGameCardPartitionRaw partition);
|
||||
Result fsOpenGameCardDetectionEventNotifier(FsEventNotifier* out);
|
||||
|
||||
}
|
||||
|
||||
namespace sphaira::ui::menu::gc {
|
||||
|
||||
////////////////////////////////////////////////
|
||||
// The below structs are taken from nxdumptool./
|
||||
////////////////////////////////////////////////
|
||||
@@ -88,6 +96,33 @@ typedef struct {
|
||||
|
||||
static_assert(sizeof(GameCardInitialData) == 0x200);
|
||||
|
||||
/// Encrypted using AES-128-CTR with the key and IV/counter from the `GameCardTitleKeyAreaEncryption` section. Assumed to be all zeroes in retail gamecards.
|
||||
typedef struct {
|
||||
u8 titlekey[0x10]; ///< Decrypted titlekey from the `GameCardInitialData` section.
|
||||
u8 reserved[0xCF0];
|
||||
} GameCardTitleKeyArea;
|
||||
|
||||
static_assert(sizeof(GameCardTitleKeyArea) == 0xD00);
|
||||
|
||||
/// Encrypted using RSA-2048-OAEP and a private OAEP key from AuthoringTool. Assumed to be all zeroes in retail gamecards.
|
||||
typedef struct {
|
||||
u8 titlekey_encryption_key[0x10]; ///< Used as the AES-128-CTR key for the `GameCardTitleKeyArea` section. Randomly generated during XCI creation by AuthoringTool.
|
||||
u8 titlekey_encryption_iv[0x10]; ///< Used as the AES-128-CTR IV/counter for the `GameCardTitleKeyArea` section. Randomly generated during XCI creation by AuthoringTool.
|
||||
u8 reserved[0xE0];
|
||||
} GameCardTitleKeyAreaEncryption;
|
||||
|
||||
static_assert(sizeof(GameCardTitleKeyAreaEncryption) == 0x100);
|
||||
|
||||
/// Used to secure communications between the Lotus and the inserted gamecard.
|
||||
/// Supposedly precedes the gamecard header.
|
||||
typedef struct {
|
||||
GameCardInitialData initial_data;
|
||||
GameCardTitleKeyArea titlekey_area;
|
||||
GameCardTitleKeyAreaEncryption titlekey_area_encryption;
|
||||
} GameCardKeyArea;
|
||||
|
||||
static_assert(sizeof(GameCardKeyArea) == 0x1000);
|
||||
|
||||
typedef struct {
|
||||
u8 maker_code; ///< GameCardUidMakerCode.
|
||||
u8 version; ///< TODO: determine whether this matches GameCardVersion or not.
|
||||
@@ -151,8 +186,7 @@ struct ApplicationEntry {
|
||||
u64 app_id{};
|
||||
u32 version{};
|
||||
u8 key_gen{};
|
||||
std::unique_ptr<NsApplicationControlData> control{};
|
||||
u64 control_size{};
|
||||
std::vector<u8> icon;
|
||||
NacpLanguageEntry lang_entry{};
|
||||
|
||||
std::vector<GcCollections> application{};
|
||||
@@ -199,6 +233,9 @@ private:
|
||||
void FreeImage();
|
||||
void OnChangeIndex(s64 new_index);
|
||||
Result DumpGames(u32 flags);
|
||||
Result DumpXcz(u32 flags);
|
||||
|
||||
Result MountGcFs();
|
||||
|
||||
private:
|
||||
FsDeviceOperator m_dev_op{};
|
||||
@@ -221,12 +258,12 @@ private:
|
||||
|
||||
FsStorage m_storage{};
|
||||
// size of normal partition.
|
||||
s64 m_parition_normal_size{};
|
||||
s64 m_partition_normal_size{};
|
||||
// size of secure partition.
|
||||
s64 m_parition_secure_size{};
|
||||
s64 m_partition_secure_size{};
|
||||
// used size reported in the xci header.
|
||||
s64 m_storage_trimmed_size{};
|
||||
// total size of m_parition_normal_size + m_parition_secure_size.
|
||||
// total size of m_partition_normal_size + m_partition_secure_size.
|
||||
s64 m_storage_total_size{};
|
||||
// reported size via rom_size in the xci header.
|
||||
s64 m_storage_full_size{};
|
||||
|
||||
@@ -32,12 +32,15 @@ struct GhApiAsset {
|
||||
std::string content_type{};
|
||||
u64 size{};
|
||||
u64 download_count{};
|
||||
std::string updated_at{};
|
||||
std::string browser_download_url{};
|
||||
};
|
||||
|
||||
struct GhApiEntry {
|
||||
std::string tag_name{};
|
||||
std::string name{};
|
||||
std::string published_at{};
|
||||
bool prerelease{};
|
||||
std::vector<GhApiAsset> assets{};
|
||||
};
|
||||
|
||||
@@ -72,4 +75,18 @@ private:
|
||||
std::unique_ptr<List> m_list{};
|
||||
};
|
||||
|
||||
// creates a popup box on another thread.
|
||||
void DownloadEntries(const Entry& entry);
|
||||
|
||||
// parses the params into entry struct and calls DonwloadEntries
|
||||
bool Download(const std::string& url, const std::vector<AssetEntry>& assets = {}, const std::string& tag = {}, const std::string& pre_install_message = {}, const std::string& post_install_message = {});
|
||||
|
||||
// calls the above function by pushing the asset to an array.
|
||||
inline bool Download(const std::string& url, const AssetEntry& asset, const std::string& tag = {}, const std::string& pre_install_message = {}, const std::string& post_install_message = {}) {
|
||||
std::vector<AssetEntry> assets;
|
||||
assets.emplace_back(asset);
|
||||
|
||||
return Download(url, assets, tag, pre_install_message, post_install_message);
|
||||
}
|
||||
|
||||
} // namespace sphaira::ui::menu::gh
|
||||
|
||||
@@ -8,6 +8,12 @@
|
||||
|
||||
namespace sphaira::ui::menu::homebrew {
|
||||
|
||||
enum Filter {
|
||||
Filter_All,
|
||||
Filter_HideHidden,
|
||||
Filter_MAX,
|
||||
};
|
||||
|
||||
enum SortType {
|
||||
SortType_Updated,
|
||||
SortType_Alphabetical,
|
||||
@@ -25,9 +31,10 @@ enum OrderType {
|
||||
using LayoutType = grid::LayoutType;
|
||||
|
||||
auto GetNroEntries() -> std::span<const NroEntry>;
|
||||
void SignalChange();
|
||||
|
||||
struct Menu final : grid::Menu {
|
||||
Menu();
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "Apps"; };
|
||||
@@ -42,30 +49,45 @@ struct Menu final : grid::Menu {
|
||||
static Result InstallHomebrew(const fs::FsPath& path, const std::vector<u8>& icon);
|
||||
static Result InstallHomebrewFromPath(const fs::FsPath& path);
|
||||
|
||||
auto GetEntry(s64 i) -> NroEntry& {
|
||||
return m_entries[m_entries_current[i]];
|
||||
}
|
||||
|
||||
auto GetEntry() -> NroEntry& {
|
||||
return GetEntry(m_index);
|
||||
}
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
void InstallHomebrew();
|
||||
void ScanHomebrew();
|
||||
void Sort();
|
||||
void SortAndFindLastFile();
|
||||
void SortAndFindLastFile(bool scan = false);
|
||||
void FreeEntries();
|
||||
void OnLayoutChange();
|
||||
void DisplayOptions();
|
||||
|
||||
auto IsStarEnabled() -> bool {
|
||||
return m_sort.Get() >= SortType_UpdatedStar;
|
||||
}
|
||||
|
||||
Result MountNroFs();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "homebrew";
|
||||
|
||||
std::vector<NroEntry> m_entries{};
|
||||
std::vector<u32> m_entries_index[Filter_MAX]{};
|
||||
std::span<u32> m_entries_current{};
|
||||
|
||||
s64 m_index{}; // where i am in the array
|
||||
std::unique_ptr<List> m_list{};
|
||||
bool m_dirty{};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_AlphabeticalStar};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||
option::OptionLong m_layout{INI_SECTION, "layout", LayoutType::LayoutType_GridDetail};
|
||||
option::OptionBool m_hide_sphaira{INI_SECTION, "hide_sphaira", false};
|
||||
option::OptionBool m_show_hidden{INI_SECTION, "show_hidden", false};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::homebrew
|
||||
|
||||
36
sphaira/include/ui/menus/image_viewer.hpp
Normal file
36
sphaira/include/ui/menus/image_viewer.hpp
Normal file
@@ -0,0 +1,36 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "fs.hpp"
|
||||
#include <vector>
|
||||
|
||||
namespace sphaira::ui::menu::imageview {
|
||||
|
||||
struct Menu final : Widget {
|
||||
Menu(fs::Fs* fs, const fs::FsPath& path);
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
auto IsMenu() const -> bool override {
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateSize();
|
||||
|
||||
private:
|
||||
const fs::FsPath m_path;
|
||||
int m_image{};
|
||||
float m_image_width{};
|
||||
float m_image_height{};
|
||||
|
||||
// for zoom, 0.1 - 1.0
|
||||
float m_zoom{1};
|
||||
|
||||
// for pan.
|
||||
float m_xoff{};
|
||||
float m_yoff{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::imageview
|
||||
65
sphaira/include/ui/menus/install_stream_menu_base.hpp
Normal file
65
sphaira/include/ui/menus/install_stream_menu_base.hpp
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
#include "yati/source/stream.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::stream {
|
||||
|
||||
enum class State {
|
||||
// not connected.
|
||||
None,
|
||||
// just connected, starts the transfer.
|
||||
Connected,
|
||||
// set whilst transfer is in progress.
|
||||
Progress,
|
||||
// set when the transfer is finished.
|
||||
Done,
|
||||
// failed to connect.
|
||||
Failed,
|
||||
};
|
||||
|
||||
using OnInstallStart = std::function<bool(const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void()>;
|
||||
|
||||
struct Stream final : yati::source::Stream {
|
||||
Stream(const fs::FsPath& path, std::stop_token token);
|
||||
|
||||
Result ReadChunk(void* buf, s64 size, u64* bytes_read) override;
|
||||
bool Push(const void* buf, s64 size);
|
||||
void Disable();
|
||||
auto& GetPath() const { return m_path; }
|
||||
|
||||
private:
|
||||
fs::FsPath m_path{};
|
||||
std::stop_token m_token{};
|
||||
std::vector<u8> m_buffer{};
|
||||
CondVar m_can_read{};
|
||||
CondVar m_can_write{};
|
||||
|
||||
public:
|
||||
Mutex m_mutex{};
|
||||
std::atomic_bool m_active{};
|
||||
};
|
||||
|
||||
struct Menu : MenuBase {
|
||||
Menu(const std::string& title, u32 flags);
|
||||
virtual ~Menu();
|
||||
|
||||
virtual void Update(Controller* controller, TouchInfo* touch);
|
||||
virtual void Draw(NVGcontext* vg, Theme* theme);
|
||||
virtual void OnDisableInstallMode() = 0;
|
||||
|
||||
protected:
|
||||
bool OnInstallStart(const char* path);
|
||||
bool OnInstallWrite(const void* buf, size_t size);
|
||||
void OnInstallClose();
|
||||
|
||||
private:
|
||||
std::unique_ptr<Stream> m_source{};
|
||||
Thread m_thread{};
|
||||
Mutex m_mutex{};
|
||||
State m_state{State::None};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::stream
|
||||
@@ -17,7 +17,7 @@ enum class UpdateState {
|
||||
Error,
|
||||
};
|
||||
|
||||
using MiscMenuFunction = std::function<std::shared_ptr<ui::menu::MenuBase>(u32 flags)>;
|
||||
using MiscMenuFunction = std::function<std::unique_ptr<MenuBase>(u32 flags)>;
|
||||
|
||||
enum MiscMenuFlag : u8 {
|
||||
// can be set as the rightside menu.
|
||||
@@ -31,6 +31,7 @@ struct MiscMenuEntry {
|
||||
const char* title;
|
||||
MiscMenuFunction func;
|
||||
u8 flag;
|
||||
const char* info;
|
||||
|
||||
auto IsShortcut() const -> bool {
|
||||
return flag & MiscMenuFlag_Shortcut;
|
||||
@@ -41,7 +42,7 @@ struct MiscMenuEntry {
|
||||
}
|
||||
};
|
||||
|
||||
auto GetMiscMenuEntries() -> std::span<const MiscMenuEntry>;
|
||||
auto GetMenuMenuEntries() -> std::span<const MiscMenuEntry>;
|
||||
|
||||
// this holds 2 menus and allows for switching between them
|
||||
struct MainMenu final : Widget {
|
||||
@@ -58,14 +59,14 @@ struct MainMenu final : Widget {
|
||||
}
|
||||
|
||||
private:
|
||||
void OnLRPress(std::shared_ptr<MenuBase> menu, Button b);
|
||||
void OnLRPress(MenuBase* menu, Button b);
|
||||
void AddOnLRPress();
|
||||
|
||||
private:
|
||||
std::shared_ptr<MenuBase> m_centre_menu{};
|
||||
std::shared_ptr<MenuBase> m_left_menu{};
|
||||
std::shared_ptr<MenuBase> m_right_menu{};
|
||||
std::shared_ptr<MenuBase> m_current_menu{};
|
||||
std::unique_ptr<MenuBase> m_centre_menu{};
|
||||
std::unique_ptr<MenuBase> m_left_menu{};
|
||||
std::unique_ptr<MenuBase> m_right_menu{};
|
||||
MenuBase* m_current_menu{};
|
||||
|
||||
std::string m_update_url{};
|
||||
std::string m_update_version{};
|
||||
|
||||
@@ -13,12 +13,14 @@ enum MenuFlag {
|
||||
|
||||
struct PolledData {
|
||||
struct tm tm{};
|
||||
u32 battery_percetange{};
|
||||
PsmChargerType charger_type{};
|
||||
NifmInternetConnectionType type{};
|
||||
NifmInternetConnectionStatus status{};
|
||||
u32 strength{};
|
||||
u32 ip{};
|
||||
s64 sd_free{1};
|
||||
s64 sd_total{1};
|
||||
s64 emmc_free{1};
|
||||
s64 emmc_total{1};
|
||||
};
|
||||
|
||||
struct MenuBase : Widget {
|
||||
@@ -33,9 +35,17 @@ struct MenuBase : Widget {
|
||||
return true;
|
||||
}
|
||||
|
||||
void SetTitle(std::string title);
|
||||
void SetTitleSubHeading(std::string sub_heading);
|
||||
void SetSubHeading(std::string sub_heading);
|
||||
void SetTitle(const std::string& title) {
|
||||
m_title = title;
|
||||
}
|
||||
|
||||
void SetTitleSubHeading(const std::string& sub_heading) {
|
||||
m_title_sub_heading = sub_heading;
|
||||
}
|
||||
|
||||
void SetSubHeading(const std::string& sub_heading) {
|
||||
m_sub_heading = sub_heading;
|
||||
}
|
||||
|
||||
auto GetTitle() const {
|
||||
return m_title;
|
||||
|
||||
19
sphaira/include/ui/menus/mtp_menu.hpp
Normal file
19
sphaira/include/ui/menus/mtp_menu.hpp
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/install_stream_menu_base.hpp"
|
||||
|
||||
namespace sphaira::ui::menu::mtp {
|
||||
|
||||
struct Menu final : stream::Menu {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
|
||||
auto GetShortTitle() const -> const char* override { return "MTP"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void OnDisableInstallMode() override;
|
||||
|
||||
private:
|
||||
bool m_was_mtp_enabled{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::mtp
|
||||
@@ -2,33 +2,29 @@
|
||||
|
||||
#include "ui/menus/grid_menu_base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "title_info.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
#include "dumper.hpp"
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::ui::menu::save {
|
||||
|
||||
enum class NacpLoadStatus {
|
||||
// not yet attempted to be loaded.
|
||||
None,
|
||||
// started loading.
|
||||
Progress,
|
||||
// loaded, ready to parse.
|
||||
Loaded,
|
||||
// failed to load, do not attempt to load again!
|
||||
Error,
|
||||
enum BackupFlag {
|
||||
BackupFlag_None = 0,
|
||||
// option to allow the user to set the save file name.
|
||||
BackupFlag_SetName = 1 << 0,
|
||||
// set if this is a auto backup (on restore).
|
||||
BackupFlag_IsAuto = 1 << 1,
|
||||
};
|
||||
|
||||
struct Entry final : FsSaveDataInfo {
|
||||
NacpLanguageEntry lang{};
|
||||
int image{};
|
||||
bool selected{};
|
||||
|
||||
std::shared_ptr<NsApplicationControlData> control{};
|
||||
u64 jpeg_size{};
|
||||
NacpLoadStatus status{NacpLoadStatus::None};
|
||||
title::NacpLoadStatus status{title::NacpLoadStatus::None};
|
||||
|
||||
auto GetName() const -> const char* {
|
||||
return lang.name;
|
||||
@@ -39,43 +35,6 @@ struct Entry final : FsSaveDataInfo {
|
||||
}
|
||||
};
|
||||
|
||||
struct AccountEntry {
|
||||
AccountUid uid;
|
||||
AccountProfile profile;
|
||||
AccountProfileBase base;
|
||||
};
|
||||
|
||||
struct ThreadResultData {
|
||||
u64 id{};
|
||||
std::shared_ptr<NsApplicationControlData> control{};
|
||||
u64 jpeg_size{};
|
||||
NacpLanguageEntry lang{};
|
||||
NacpLoadStatus status{NacpLoadStatus::None};
|
||||
};
|
||||
|
||||
struct ThreadData {
|
||||
ThreadData();
|
||||
|
||||
auto IsRunning() const -> bool;
|
||||
void Run();
|
||||
void Close();
|
||||
void Push(u64 id);
|
||||
void Push(std::span<const Entry> entries);
|
||||
void Pop(std::vector<ThreadResultData>& out);
|
||||
|
||||
private:
|
||||
UEvent m_uevent{};
|
||||
Mutex m_mutex_id{};
|
||||
Mutex m_mutex_result{};
|
||||
|
||||
// app_ids pushed to the queue, signal uevent when pushed.
|
||||
std::vector<u64> m_ids{};
|
||||
// control data pushed to the queue.
|
||||
std::vector<ThreadResultData> m_result{};
|
||||
|
||||
std::atomic_bool m_running{};
|
||||
};
|
||||
|
||||
enum SortType {
|
||||
SortType_Updated,
|
||||
};
|
||||
@@ -87,6 +46,8 @@ enum OrderType {
|
||||
|
||||
using LayoutType = grid::LayoutType;
|
||||
|
||||
void SignalChange();
|
||||
|
||||
struct Menu final : grid::Menu {
|
||||
Menu(u32 flags);
|
||||
~Menu();
|
||||
@@ -127,9 +88,18 @@ private:
|
||||
m_selected_count = 0;
|
||||
}
|
||||
|
||||
void BackupSaves(std::vector<std::reference_wrapper<Entry>>& entries);
|
||||
void DisplayOptions();
|
||||
|
||||
void BackupSaves(std::vector<std::reference_wrapper<Entry>>& entries, u32 flags);
|
||||
void RestoreSave();
|
||||
|
||||
auto BuildSavePath(const Entry& e, u32 flags) const -> fs::FsPath;
|
||||
Result RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::FsPath& path);
|
||||
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, Entry& e, u32 flags);
|
||||
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, std::span<const std::reference_wrapper<Entry>> entries, u32 flags);
|
||||
|
||||
Result MountSaveFs();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "saves";
|
||||
|
||||
@@ -140,11 +110,9 @@ private:
|
||||
bool m_is_reversed{};
|
||||
bool m_dirty{};
|
||||
|
||||
std::vector<AccountEntry> m_accounts{};
|
||||
std::vector<AccountProfileBase> m_accounts{};
|
||||
s64 m_account_index{};
|
||||
|
||||
ThreadData m_thread_data{};
|
||||
Thread m_thread{};
|
||||
u8 m_data_type{FsSaveDataType_Account};
|
||||
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", SortType::SortType_Updated};
|
||||
option::OptionLong m_order{INI_SECTION, "order", OrderType::OrderType_Descending};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
#if 0
|
||||
#pragma once
|
||||
|
||||
#include "ui/menus/menu_base.hpp"
|
||||
@@ -104,7 +105,7 @@ struct Config {
|
||||
u32 limit{18};
|
||||
bool nsfw{false};
|
||||
|
||||
void SetQuery(std::string new_query) {
|
||||
void SetQuery(const std::string& new_query) {
|
||||
query = new_query;
|
||||
}
|
||||
|
||||
@@ -112,7 +113,7 @@ struct Config {
|
||||
query.clear();
|
||||
}
|
||||
|
||||
void SetCreator(Creator new_creator) {
|
||||
void SetCreator(const Creator& new_creator) {
|
||||
creator = new_creator.id;
|
||||
}
|
||||
|
||||
@@ -138,6 +139,7 @@ struct Menu final : MenuBase {
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index) {
|
||||
m_index = index;
|
||||
if (!m_index) {
|
||||
@@ -147,7 +149,7 @@ struct Menu final : MenuBase {
|
||||
|
||||
void InvalidateAllPages();
|
||||
void PackListDownload();
|
||||
void OnPackListDownload();
|
||||
void DisplayOptions();
|
||||
|
||||
private:
|
||||
static constexpr inline const char* INI_SECTION = "themezer";
|
||||
@@ -169,6 +171,9 @@ private:
|
||||
option::OptionLong m_sort{INI_SECTION, "sort", 0};
|
||||
option::OptionLong m_order{INI_SECTION, "order", 0};
|
||||
option::OptionBool m_nsfw{INI_SECTION, "nsfw", false};
|
||||
|
||||
bool m_checked_for_nro{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::themezer
|
||||
#endif
|
||||
|
||||
@@ -27,19 +27,16 @@ struct Menu final : MenuBase {
|
||||
auto GetShortTitle() const -> const char* override { return "USB"; };
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
void OnFocusGained() override;
|
||||
|
||||
// this should be private
|
||||
// private:
|
||||
std::shared_ptr<yati::source::Usb> m_usb_source{};
|
||||
void ThreadFunction();
|
||||
|
||||
private:
|
||||
std::unique_ptr<yati::source::Usb> m_usb_source{};
|
||||
bool m_was_mtp_enabled{};
|
||||
|
||||
Thread m_thread{};
|
||||
Mutex m_mutex{};
|
||||
// the below are shared across threads, lock with the above mutex!
|
||||
State m_state{State::None};
|
||||
std::atomic<State> m_state{State::None};
|
||||
std::vector<std::string> m_names{};
|
||||
bool m_usb_has_connection{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::menu::usb
|
||||
|
||||
45
sphaira/include/ui/music_player.hpp
Normal file
45
sphaira/include/ui/music_player.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "utils/audio.hpp"
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::ui::music {
|
||||
|
||||
struct Menu final : Widget {
|
||||
Menu(fs::Fs* fs, const fs::FsPath& path);
|
||||
~Menu();
|
||||
|
||||
void Update(Controller* controller, TouchInfo* touch) override;
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
void PauseToggle();
|
||||
void SeekForward();
|
||||
void SeekBack();
|
||||
|
||||
void IncreaseVolume();
|
||||
void DecreaseVolume();
|
||||
|
||||
private:
|
||||
audio::SongID m_song{};
|
||||
audio::Info m_info{};
|
||||
audio::Meta m_meta{};
|
||||
// only set if metadata was loaded.
|
||||
int m_icon{};
|
||||
|
||||
ScrollingText m_scroll_title{};
|
||||
ScrollingText m_scroll_artist{};
|
||||
ScrollingText m_scroll_album{};
|
||||
|
||||
// from movienx
|
||||
static constexpr Vec4 osd_progress_bar{400.f, 550, 1280.f - (400.f * 2.f), 10.f};
|
||||
// static constexpr Vec4 osd_progress_bar{300.f, SCREEN_HEIGHT / 2 - 15 / 2, 1280.f - (300.f * 2.f), 10.f};
|
||||
static constexpr Vec2 osd_time_text_left{osd_progress_bar.x - 12.f, osd_progress_bar.y - 2.f};
|
||||
static constexpr Vec2 osd_time_text_right{osd_progress_bar.x + osd_progress_bar.w + 12.f, osd_progress_bar.y - 2.f};
|
||||
static constexpr Vec4 osd_bar_outline{osd_time_text_left.x - 80, osd_progress_bar.y - 30, osd_progress_bar.w + 80 * 2 + 30, osd_progress_bar.h + 30 + 30};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui::music
|
||||
@@ -11,8 +11,7 @@ public:
|
||||
enum class Side { LEFT, RIGHT };
|
||||
|
||||
public:
|
||||
NotifEntry(std::string text, Side side);
|
||||
~NotifEntry() = default;
|
||||
NotifEntry(const std::string& text, Side side);
|
||||
|
||||
auto Draw(NVGcontext* vg, Theme* theme, float y) -> bool;
|
||||
auto GetSide() const noexcept { return m_side; }
|
||||
@@ -22,17 +21,14 @@ private:
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
private:
|
||||
std::string m_text{};
|
||||
std::string m_text;
|
||||
Side m_side;
|
||||
std::size_t m_count{180}; // count down to zero
|
||||
Side m_side{};
|
||||
bool m_bounds_measured{};
|
||||
};
|
||||
|
||||
class NotifMananger final : public Object {
|
||||
public:
|
||||
NotifMananger() = default;
|
||||
~NotifMananger() = default;
|
||||
|
||||
void Draw(NVGcontext* vg, Theme* theme) override;
|
||||
|
||||
void Push(const NotifEntry& entry);
|
||||
@@ -45,6 +41,7 @@ private:
|
||||
|
||||
private:
|
||||
void Draw(NVGcontext* vg, Theme* theme, Entries& entries);
|
||||
auto GetEntries(NotifEntry::Side side) -> Entries&;
|
||||
|
||||
private:
|
||||
Entries m_entries_left{};
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
|
||||
namespace sphaira::ui::gfx {
|
||||
|
||||
void drawImage(NVGcontext*, float x, float y, float w, float h, int texture, float rounded = 0.F);
|
||||
void drawImage(NVGcontext*, const Vec4& v, int texture, float rounded = 0.F);
|
||||
void drawImage(NVGcontext*, float x, float y, float w, float h, int texture, float rounded = 0.F, float alpha = 1.0F);
|
||||
void drawImage(NVGcontext*, const Vec4& v, int texture, float rounded = 0.F, float alpha = 1.0F);
|
||||
|
||||
void dimBackground(NVGcontext*);
|
||||
|
||||
@@ -30,7 +30,8 @@ void drawTextArgs(NVGcontext*, float x, float y, float size, int align, const NV
|
||||
|
||||
void drawTextBox(NVGcontext*, float x, float y, float size, float bound, const NVGcolor& c, const char* str, int align = NVG_ALIGN_LEFT | NVG_ALIGN_TOP, const char* end = nullptr);
|
||||
|
||||
void textBounds(NVGcontext*, float x, float y, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6)));
|
||||
void textBounds(NVGcontext*, float x, float y, float *bounds, const char* str);
|
||||
void textBoundsArgs(NVGcontext*, float x, float y, float *bounds, const char* str, ...) __attribute__ ((format (printf, 5, 6)));
|
||||
|
||||
auto getButton(Button button) -> const char*;
|
||||
void drawScrollbar(NVGcontext*, const Theme*, u32 index_off, u32 count, u32 max_per_page);
|
||||
@@ -41,6 +42,8 @@ void drawScrollbar2(NVGcontext*, const Theme*, s64 index_off, s64 count, s64 row
|
||||
|
||||
void drawAppLable(NVGcontext* vg, const Theme*, ScrollingText& st, float x, float y, float w, const char* name);
|
||||
|
||||
void drawSpinner(NVGcontext* vg, const Theme*, float cx, float cy, float r, float t);
|
||||
|
||||
void updateHighlightAnimation();
|
||||
void getHighlightAnimation(float* gradientX, float* gradientY, float* color);
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public:
|
||||
m_pos = { x, y, w, h };
|
||||
}
|
||||
|
||||
auto SetPos(Vec4 v) noexcept -> void {
|
||||
auto SetPos(const Vec4& v) noexcept -> void {
|
||||
m_pos = v;
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ class OptionBoxEntry final : public Widget {
|
||||
public:
|
||||
|
||||
public:
|
||||
OptionBoxEntry(const std::string& text, Vec4 pos);
|
||||
OptionBoxEntry(const std::string& text, const Vec4& pos);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override {}
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
@@ -32,9 +32,10 @@ public:
|
||||
using Options = std::vector<Option>;
|
||||
|
||||
public:
|
||||
OptionBox(const std::string& message, const Option& a, Callback cb = [](auto){}, int image = 0); // confirm
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, Callback cb, int image = 0); // yesno
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, Callback cb, int image = 0); // yesno
|
||||
OptionBox(const std::string& message, const Option& a, const Callback& cb = [](auto){}, int image = 0, bool own_image = false); // confirm
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, const Callback& cb, int image = 0, bool own_image = false); // yesno
|
||||
OptionBox(const std::string& message, const Option& a, const Option& b, s64 index, const Callback& cb, int image = 0, bool own_image = false); // yesno
|
||||
~OptionBox();
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
@@ -46,9 +47,10 @@ private:
|
||||
void SetIndex(s64 index);
|
||||
|
||||
private:
|
||||
std::string m_message{};
|
||||
Callback m_callback{};
|
||||
int m_image{};
|
||||
const std::string m_message;
|
||||
const Callback m_callback;
|
||||
const int m_image;
|
||||
const bool m_own_image;
|
||||
|
||||
Vec4 m_spacer_line{};
|
||||
|
||||
|
||||
@@ -13,11 +13,11 @@ public:
|
||||
using Callback = std::function<void(std::optional<s64>)>;
|
||||
|
||||
public:
|
||||
explicit PopupList(std::string title, Items items, Callback cb, s64 index = 0);
|
||||
PopupList(std::string title, Items items, Callback cb, std::string index);
|
||||
PopupList(std::string title, Items items, std::string& index_str_ref, s64& index);
|
||||
PopupList(std::string title, Items items, std::string& index_ref);
|
||||
PopupList(std::string title, Items items, s64& index_ref);
|
||||
explicit PopupList(const std::string& title, const Items& items, const Callback& cb, s64 index = 0);
|
||||
PopupList(const std::string& title, const Items& items, const Callback& cb, const std::string& index);
|
||||
PopupList(const std::string& title, const Items& items, std::string& index_str_ref, s64& index);
|
||||
PopupList(const std::string& title, const Items& items, std::string& index_ref);
|
||||
PopupList(const std::string& title, const Items& items, s64& index_ref);
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
@@ -29,12 +29,12 @@ private:
|
||||
|
||||
private:
|
||||
static constexpr Vec2 m_title_pos{70.f, 28.f};
|
||||
static constexpr Vec4 m_block{280.f, 110.f, 720.f, 60.f};
|
||||
static constexpr Vec4 m_block{280.f, 110.f, SCREEN_HEIGHT, 60.f};
|
||||
static constexpr float m_text_xoffset{15.f};
|
||||
static constexpr float m_line_width{1220.f};
|
||||
|
||||
std::string m_title{};
|
||||
Items m_items{};
|
||||
const std::string m_title;
|
||||
const Items m_items;
|
||||
Callback m_callback{};
|
||||
s64 m_index{}; // index in list array
|
||||
s64 m_starting_index{};
|
||||
|
||||
@@ -11,14 +11,14 @@ namespace sphaira::ui {
|
||||
struct ProgressBox;
|
||||
using ProgressBoxCallback = std::function<Result(ProgressBox*)>;
|
||||
using ProgressBoxDoneCallback = std::function<void(Result rc)>;
|
||||
// using CancelCallback = std::function<void()>;
|
||||
|
||||
struct ProgressBox final : Widget {
|
||||
ProgressBox(
|
||||
int image,
|
||||
const std::string& action,
|
||||
const std::string& title,
|
||||
ProgressBoxCallback callback, ProgressBoxDoneCallback done = [](Result rc){},
|
||||
int cpuid = 1, int prio = PRIO_PREEMPTIVE, int stack_size = 1024*128
|
||||
const ProgressBoxCallback& callback, const ProgressBoxDoneCallback& done = nullptr
|
||||
);
|
||||
~ProgressBox();
|
||||
|
||||
@@ -28,6 +28,8 @@ struct ProgressBox final : Widget {
|
||||
auto SetActionName(const std::string& action) -> ProgressBox&;
|
||||
auto SetTitle(const std::string& title) -> ProgressBox&;
|
||||
auto NewTransfer(const std::string& transfer) -> ProgressBox&;
|
||||
// zeros the saved offset.
|
||||
auto ResetTranfser() -> ProgressBox&;
|
||||
auto UpdateTransfer(s64 offset, s64 size) -> ProgressBox&;
|
||||
// not const in order to avoid copy by using std::swap
|
||||
auto SetImage(int image) -> ProgressBox&;
|
||||
@@ -38,15 +40,14 @@ struct ProgressBox final : Widget {
|
||||
auto ShouldExit() -> bool;
|
||||
auto ShouldExitResult() -> Result;
|
||||
|
||||
// helper functions
|
||||
auto CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||
auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst) -> Result;
|
||||
void Yield();
|
||||
void AddCancelEvent(UEvent* event);
|
||||
void RemoveCancelEvent(const UEvent* event);
|
||||
|
||||
auto GetCpuId() const {
|
||||
return m_cpuid;
|
||||
}
|
||||
// helper functions
|
||||
auto CopyFile(fs::Fs* fs_src, fs::Fs* fs_dst, const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result;
|
||||
auto CopyFile(fs::Fs* fs, const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result;
|
||||
auto CopyFile(const fs::FsPath& src, const fs::FsPath& dst, bool single_threaded = false) -> Result;
|
||||
void Yield();
|
||||
|
||||
auto OnDownloadProgressCallback() {
|
||||
return [this](s64 dltotal, s64 dlnow, s64 ultotal, s64 ulnow){
|
||||
@@ -64,6 +65,11 @@ struct ProgressBox final : Widget {
|
||||
};
|
||||
}
|
||||
|
||||
// auto-clear = false
|
||||
auto GetCancelEvent() {
|
||||
return &m_uevent;
|
||||
}
|
||||
|
||||
private:
|
||||
void FreeImage();
|
||||
|
||||
@@ -75,10 +81,12 @@ public:
|
||||
};
|
||||
|
||||
private:
|
||||
UEvent m_uevent{};
|
||||
Mutex m_mutex{};
|
||||
Thread m_thread{};
|
||||
ThreadData m_thread_data{};
|
||||
ProgressBoxDoneCallback m_done{};
|
||||
std::vector<UEvent*> m_cancel_events{};
|
||||
|
||||
// shared data start.
|
||||
std::string m_action{};
|
||||
@@ -97,7 +105,6 @@ private:
|
||||
ScrollingText m_scroll_title{};
|
||||
ScrollingText m_scroll_transfer{};
|
||||
|
||||
int m_cpuid{};
|
||||
int m_image{};
|
||||
bool m_own_image{};
|
||||
};
|
||||
|
||||
@@ -2,47 +2,136 @@
|
||||
|
||||
#include "ui/widget.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "ui/scrolling_text.hpp"
|
||||
#include "option.hpp"
|
||||
#include <memory>
|
||||
#include <concepts>
|
||||
#include <utility>
|
||||
#include <sys/syslimits.h>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
class SidebarEntryBase : public Widget {
|
||||
public:
|
||||
SidebarEntryBase(std::string&& title);
|
||||
virtual auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
using DependsCallback = std::function<bool(void)>;
|
||||
using DependsClickCallback = std::function<void(void)>;
|
||||
|
||||
public:
|
||||
explicit SidebarEntryBase(const std::string& title, const std::string& info);
|
||||
|
||||
using Widget::Draw;
|
||||
virtual void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left);
|
||||
auto OnFocusGained() noexcept -> void override;
|
||||
auto OnFocusLost() noexcept -> void override;
|
||||
|
||||
void DrawEntry(NVGcontext* vg, Theme* theme, const std::string& left, const std::string& right, bool use_selected);
|
||||
|
||||
void Depends(const DependsCallback& callback, const std::string& depends_info, const DependsClickCallback& depends_click = {}) {
|
||||
m_depends_callback = callback;
|
||||
m_depends_info = depends_info;
|
||||
m_depends_click = depends_click;
|
||||
}
|
||||
|
||||
void Depends(bool& value, const std::string& depends_info, const DependsClickCallback& depends_click = {}) {
|
||||
m_depends_callback = [&value](){ return value; };
|
||||
m_depends_info = depends_info;
|
||||
m_depends_click = depends_click;
|
||||
}
|
||||
|
||||
void Depends(option::OptionBool& value, const std::string& depends_info, const DependsClickCallback& depends_click = {}) {
|
||||
m_depends_callback = [&value](){ return value.Get(); };
|
||||
m_depends_info = depends_info;
|
||||
m_depends_click = depends_click;
|
||||
}
|
||||
|
||||
void SetDirty(bool dirty = true) {
|
||||
m_dirty = dirty;
|
||||
}
|
||||
|
||||
auto IsDirty() const -> bool {
|
||||
return m_dirty;
|
||||
}
|
||||
|
||||
protected:
|
||||
std::string m_title;
|
||||
auto IsEnabled() const -> bool {
|
||||
if (m_depends_callback) {
|
||||
return m_depends_callback();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void DependsClick() const {
|
||||
if (m_depends_click) {
|
||||
m_depends_click();
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
const std::string m_title;
|
||||
|
||||
private:
|
||||
const std::string m_info;
|
||||
std::string m_depends_info{};
|
||||
DependsCallback m_depends_callback{};
|
||||
DependsClickCallback m_depends_click{};
|
||||
ScrollingText m_scolling_title{};
|
||||
ScrollingText m_scolling_value{};
|
||||
bool m_dirty{};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept DerivedFromSidebarBase = std::is_base_of_v<SidebarEntryBase, T>;
|
||||
|
||||
class SidebarEntryBool final : public SidebarEntryBase {
|
||||
public:
|
||||
using Callback = std::function<void(bool&)>;
|
||||
|
||||
public:
|
||||
SidebarEntryBool(std::string title, bool option, Callback cb, std::string true_str = "On", std::string false_str = "Off");
|
||||
SidebarEntryBool(std::string title, bool& option, std::string true_str = "On", std::string false_str = "Off");
|
||||
explicit SidebarEntryBool(const std::string& title, bool option, const Callback& cb, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
|
||||
explicit SidebarEntryBool(const std::string& title, bool& option, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
|
||||
explicit SidebarEntryBool(const std::string& title, option::OptionBool& option, const Callback& cb, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
|
||||
explicit SidebarEntryBool(const std::string& title, option::OptionBool& option, const std::string& info = "", const std::string& true_str = "On", const std::string& false_str = "Off");
|
||||
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
|
||||
|
||||
private:
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
bool m_option;
|
||||
Callback m_callback;
|
||||
std::string m_true_str;
|
||||
std::string m_false_str;
|
||||
};
|
||||
|
||||
class SidebarEntrySlider final : public SidebarEntryBase {
|
||||
public:
|
||||
using Callback = std::function<void(float&)>;
|
||||
|
||||
public:
|
||||
explicit SidebarEntrySlider(const std::string& title, float value, float min, float max, int steps, const Callback& cb, const std::string& info = "");
|
||||
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
|
||||
|
||||
private:
|
||||
float m_value;
|
||||
float m_min;
|
||||
float m_max;
|
||||
int m_steps;
|
||||
Callback m_callback;
|
||||
|
||||
float m_duration;
|
||||
float m_inc;
|
||||
};
|
||||
|
||||
class SidebarEntryCallback final : public SidebarEntryBase {
|
||||
public:
|
||||
using Callback = std::function<void()>;
|
||||
|
||||
public:
|
||||
SidebarEntryCallback(std::string title, Callback cb, bool pop_on_click = false);
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
explicit SidebarEntryCallback(const std::string& title, const Callback& cb, const std::string& info);
|
||||
explicit SidebarEntryCallback(const std::string& title, const Callback& cb, bool pop_on_click = false, const std::string& info = "");
|
||||
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
|
||||
|
||||
private:
|
||||
Callback m_callback;
|
||||
bool m_pop_on_click;
|
||||
const Callback m_callback;
|
||||
const bool m_pop_on_click;
|
||||
};
|
||||
|
||||
class SidebarEntryArray final : public SidebarEntryBase {
|
||||
@@ -52,85 +141,144 @@ public:
|
||||
using Callback = std::function<void(s64& index)>;
|
||||
|
||||
public:
|
||||
explicit SidebarEntryArray(std::string title, Items items, Callback cb, s64 index = 0);
|
||||
SidebarEntryArray(std::string title, Items items, Callback cb, std::string index);
|
||||
SidebarEntryArray(std::string title, Items items, std::string& index);
|
||||
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
auto OnFocusGained() noexcept -> void override;
|
||||
auto OnFocusLost() noexcept -> void override;
|
||||
explicit SidebarEntryArray(const std::string& title, const Items& items, const Callback& cb, s64 index = 0, const std::string& info = "");
|
||||
explicit SidebarEntryArray(const std::string& title, const Items& items, const Callback& cb, const std::string& index, const std::string& info = "");
|
||||
explicit SidebarEntryArray(const std::string& title, const Items& items, std::string& index, const std::string& info = "");
|
||||
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
|
||||
|
||||
private:
|
||||
Items m_items;
|
||||
ListCallback m_list_callback;
|
||||
Callback m_callback;
|
||||
const Items m_items;
|
||||
const Callback m_callback;
|
||||
s64 m_index;
|
||||
s64 m_tick{};
|
||||
float m_text_yoff{};
|
||||
ListCallback m_list_callback{};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
class SidebarEntrySlider final : public SidebarEntryBase {
|
||||
// single text entry.
|
||||
// the callback is called when the entry is clicked.
|
||||
// usually, the within the callback the text will be changed, use SetText().
|
||||
class SidebarEntryTextBase : public SidebarEntryBase {
|
||||
public:
|
||||
SidebarEntrySlider(std::string title, T& value, T min, T max)
|
||||
: SidebarEntryBase{title}
|
||||
, m_value{value}
|
||||
, m_min{min}
|
||||
, m_max{max} {
|
||||
using Callback = std::function<void(void)>;
|
||||
|
||||
public:
|
||||
explicit SidebarEntryTextBase(const std::string& title, const std::string& value, const Callback& cb, const std::string& info = "");
|
||||
|
||||
void Draw(NVGcontext* vg, Theme* theme, const Vec4& root_pos, bool left) override;
|
||||
|
||||
void SetCallback(const Callback& cb) {
|
||||
m_callback = cb;
|
||||
}
|
||||
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto GetValue() const -> const std::string& {
|
||||
return m_value;
|
||||
}
|
||||
|
||||
void SetValue(const std::string& value) {
|
||||
m_value = value;
|
||||
}
|
||||
|
||||
private:
|
||||
T& m_value;
|
||||
T m_min;
|
||||
T m_max;
|
||||
T m_step{};
|
||||
Vec4 m_bar{};
|
||||
Vec4 m_bar_fill{};
|
||||
std::string m_value;
|
||||
Callback m_callback;
|
||||
};
|
||||
|
||||
class Sidebar final : public Widget {
|
||||
class SidebarEntryTextInput final : public SidebarEntryTextBase {
|
||||
public:
|
||||
enum class Side { LEFT, RIGHT };
|
||||
using Items = std::vector<std::shared_ptr<SidebarEntryBase>>;
|
||||
using Callback = std::function<void(SidebarEntryTextInput* input)>;
|
||||
|
||||
public:
|
||||
Sidebar(std::string title, Side side, Items&& items);
|
||||
Sidebar(std::string title, Side side);
|
||||
Sidebar(std::string title, std::string sub, Side side, Items&& items);
|
||||
Sidebar(std::string title, std::string sub, Side side);
|
||||
// uses normal keyboard.
|
||||
explicit SidebarEntryTextInput(const std::string& title, const std::string& value, const std::string& header = {}, const std::string& guide = {}, s64 len_min = -1, s64 len_max = PATH_MAX, const std::string& info = "", const Callback& callback = nullptr);
|
||||
// uses numpad.
|
||||
explicit SidebarEntryTextInput(const std::string& title, s64 value, const std::string& header = {}, const std::string& guide = {}, s64 len_min = -1, s64 len_max = PATH_MAX, const std::string& info = "", const Callback& callback = nullptr);
|
||||
|
||||
auto GetNumValue() const -> s64 {
|
||||
return std::stoul(GetValue());
|
||||
}
|
||||
|
||||
void SetNumValue(s64 value) {
|
||||
SetValue(std::to_string(value));
|
||||
}
|
||||
private:
|
||||
const std::string m_header;
|
||||
const std::string m_guide;
|
||||
const s64 m_len_min;
|
||||
const s64 m_len_max;
|
||||
const Callback m_callback;
|
||||
};
|
||||
|
||||
class SidebarEntryFilePicker final : public SidebarEntryTextBase {
|
||||
public:
|
||||
explicit SidebarEntryFilePicker(const std::string& title, const std::string& value, const std::vector<std::string>& filter, const std::string& info = "");
|
||||
|
||||
// extension filter.
|
||||
void SetFilter(const std::vector<std::string>& filter) {
|
||||
m_filter = filter;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> m_filter{};
|
||||
};
|
||||
|
||||
class Sidebar : public Widget {
|
||||
public:
|
||||
enum class Side { LEFT, RIGHT };
|
||||
using Items = std::vector<std::unique_ptr<SidebarEntryBase>>;
|
||||
using OnExitWhenChangedCallback = std::function<void()>;
|
||||
|
||||
public:
|
||||
explicit Sidebar(const std::string& title, Side side, float width = 450.f);
|
||||
explicit Sidebar(const std::string& title, const std::string& sub, Side side, float width = 450.f);
|
||||
~Sidebar();
|
||||
|
||||
auto Update(Controller* controller, TouchInfo* touch) -> void override;
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
auto OnFocusGained() noexcept -> void override;
|
||||
auto OnFocusLost() noexcept -> void override;
|
||||
|
||||
void Add(std::shared_ptr<SidebarEntryBase> entry);
|
||||
auto Add(std::unique_ptr<SidebarEntryBase>&& entry) -> SidebarEntryBase*;
|
||||
|
||||
template<DerivedFromSidebarBase T, typename... Args>
|
||||
auto Add(Args&&... args) -> T* {
|
||||
return (T*)Add(std::make_unique<T>(std::forward<Args>(args)...));
|
||||
}
|
||||
|
||||
// sets a callback that is called on exit when the any options were changed.
|
||||
// the change detection isn't perfect, it just checks if the A button was pressed...
|
||||
void SetOnExitWhenChanged(const OnExitWhenChangedCallback& cb) {
|
||||
m_on_exit_when_changed = cb;
|
||||
}
|
||||
|
||||
private:
|
||||
void SetIndex(s64 index);
|
||||
void SetupButtons();
|
||||
|
||||
private:
|
||||
std::string m_title;
|
||||
std::string m_sub;
|
||||
Side m_side;
|
||||
Items m_items;
|
||||
const std::string m_title;
|
||||
const std::string m_sub;
|
||||
const Side m_side;
|
||||
Items m_items{};
|
||||
s64 m_index{};
|
||||
|
||||
std::unique_ptr<List> m_list;
|
||||
std::unique_ptr<List> m_list{};
|
||||
|
||||
Vec4 m_top_bar{};
|
||||
Vec4 m_bottom_bar{};
|
||||
Vec2 m_title_pos{};
|
||||
Vec4 m_base_pos{};
|
||||
|
||||
OnExitWhenChangedCallback m_on_exit_when_changed{};
|
||||
|
||||
static constexpr float m_title_size{28.f};
|
||||
// static constexpr Vec2 box_size{380.f, 70.f};
|
||||
static constexpr Vec2 m_box_size{400.f, 70.f};
|
||||
};
|
||||
|
||||
class FormSidebar : public Sidebar {
|
||||
public:
|
||||
explicit FormSidebar(const std::string& title) : Sidebar{title, Side::LEFT, 540.f} {
|
||||
// explicit FormSidebar(const std::string& title) : Sidebar{title, Side::LEFT} {
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include "nanovg.h"
|
||||
#include "pulsar.h"
|
||||
#include "fs.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
@@ -56,8 +55,8 @@ struct Vec2 {
|
||||
struct Vec4 {
|
||||
constexpr Vec4() = default;
|
||||
constexpr Vec4(float _x, float _y, float _w, float _h) : x{_x}, y{_y}, w{_w}, h{_h} {}
|
||||
constexpr Vec4(Vec2 vec0, Vec2 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.x}, h{vec1.y} {}
|
||||
constexpr Vec4(Vec4 vec0, Vec4 vec1) : x{vec0.x}, y{vec0.y}, w{vec1.w}, h{vec1.h} {}
|
||||
constexpr Vec4(const Vec2& vec0, const Vec2& vec1) : x{vec0.x}, y{vec0.y}, w{vec1.x}, h{vec1.y} {}
|
||||
constexpr Vec4(const Vec4& vec0, const Vec4& vec1) : x{vec0.x}, y{vec0.y}, w{vec1.w}, h{vec1.h} {}
|
||||
|
||||
float& operator[](std::size_t idx) {
|
||||
switch (idx) {
|
||||
@@ -229,6 +228,7 @@ struct ThemeMeta {
|
||||
struct Theme {
|
||||
ThemeMeta meta;
|
||||
ElementEntry elements[ThemeEntryID_MAX];
|
||||
fs::FsPath music_path;
|
||||
|
||||
auto GetColour(ThemeEntryID id) const {
|
||||
return elements[id].colour;
|
||||
@@ -274,6 +274,13 @@ enum class Button : u64 {
|
||||
START = static_cast<u64>(HidNpadButton_Plus),
|
||||
SELECT = static_cast<u64>(HidNpadButton_Minus),
|
||||
|
||||
SL_LEFT = static_cast<u64>(HidNpadButton_LeftSL),
|
||||
SR_LEFT = static_cast<u64>(HidNpadButton_LeftSR),
|
||||
SL_RIGHT = static_cast<u64>(HidNpadButton_RightSL),
|
||||
SR_RIGHT = static_cast<u64>(HidNpadButton_RightSR),
|
||||
SL_ANY = SL_LEFT | SL_RIGHT,
|
||||
SR_ANY = SR_LEFT | SR_RIGHT,
|
||||
|
||||
// todo:
|
||||
DPAD_LEFT = static_cast<u64>(HidNpadButton_Left),
|
||||
DPAD_RIGHT = static_cast<u64>(HidNpadButton_Right),
|
||||
@@ -284,11 +291,13 @@ enum class Button : u64 {
|
||||
LS_RIGHT = static_cast<u64>(HidNpadButton_StickLRight),
|
||||
LS_UP = static_cast<u64>(HidNpadButton_StickLUp),
|
||||
LS_DOWN = static_cast<u64>(HidNpadButton_StickLDown),
|
||||
LS_ANY = LS_LEFT | LS_RIGHT | LS_UP | LS_DOWN,
|
||||
|
||||
RS_LEFT = static_cast<u64>(HidNpadButton_StickRLeft),
|
||||
RS_RIGHT = static_cast<u64>(HidNpadButton_StickRRight),
|
||||
RS_UP = static_cast<u64>(HidNpadButton_StickRUp),
|
||||
RS_DOWN = static_cast<u64>(HidNpadButton_StickRDown),
|
||||
RS_ANY = RS_LEFT | RS_RIGHT | RS_UP | RS_DOWN,
|
||||
|
||||
ANY_LEFT = static_cast<u64>(HidNpadButton_AnyLeft),
|
||||
ANY_RIGHT = static_cast<u64>(HidNpadButton_AnyRight),
|
||||
@@ -332,10 +341,10 @@ struct Action final {
|
||||
CallbackWithBool
|
||||
>;
|
||||
|
||||
Action(Callback cb) : Action{ActionType::DOWN, "", cb} {}
|
||||
Action(std::string hint, Callback cb) : Action{ActionType::DOWN, hint, cb} {}
|
||||
Action(u8 type, Callback cb) : Action{type, "", cb} {}
|
||||
Action(u8 type, std::string hint, Callback cb) : m_type{type}, m_callback{cb}, m_hint{hint} {}
|
||||
explicit Action(const Callback& cb) : Action{ActionType::DOWN, "", cb} {}
|
||||
explicit Action(const std::string& hint, const Callback& cb) : Action{ActionType::DOWN, hint, cb} {}
|
||||
explicit Action(u8 type, const Callback& cb) : Action{type, "", cb} {}
|
||||
explicit Action(u8 type, const std::string& hint, const Callback& cb) : m_type{type}, m_callback{cb}, m_hint{hint} {}
|
||||
|
||||
auto IsHidden() const noexcept { return m_hint.empty(); }
|
||||
|
||||
@@ -357,6 +366,81 @@ struct Action final {
|
||||
std::string m_hint{};
|
||||
};
|
||||
|
||||
struct GenericHidState {
|
||||
GenericHidState() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
buttons_cur = 0;
|
||||
buttons_old = 0;
|
||||
}
|
||||
|
||||
u64 GetButtons() const {
|
||||
return buttons_cur;
|
||||
}
|
||||
|
||||
u64 GetButtonsDown() const {
|
||||
return buttons_cur & ~buttons_old;
|
||||
}
|
||||
|
||||
u64 GetButtonsUp() const {
|
||||
return ~buttons_cur & buttons_old;
|
||||
}
|
||||
|
||||
virtual void Update() = 0;
|
||||
|
||||
protected:
|
||||
u64 buttons_cur;
|
||||
u64 buttons_old;
|
||||
};
|
||||
|
||||
struct KeyboardState final : GenericHidState {
|
||||
struct MapEntry {
|
||||
HidKeyboardKey key;
|
||||
u64 button;
|
||||
};
|
||||
using Map = std::span<const MapEntry>;
|
||||
|
||||
void Init(Map map) {
|
||||
m_map = map;
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Update() override {
|
||||
buttons_old = buttons_cur;
|
||||
buttons_cur = 0;
|
||||
|
||||
if (!hidGetKeyboardStates(&m_state, 1)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const auto ctrl = m_state.modifiers & HidKeyboardModifier_Control;
|
||||
const auto shift = m_state.modifiers & HidKeyboardModifier_Shift;
|
||||
|
||||
for (const auto& map : m_map) {
|
||||
if (hidKeyboardStateGetKey(&m_state, map.key)) {
|
||||
if (shift && map.button == static_cast<u64>(Button::L)) {
|
||||
buttons_cur |= static_cast<u64>(Button::L2);
|
||||
} else if (shift && map.button == static_cast<u64>(Button::R)) {
|
||||
buttons_cur |= static_cast<u64>(Button::R2);
|
||||
} else if (ctrl && map.button == static_cast<u64>(Button::L)) {
|
||||
buttons_cur |= static_cast<u64>(Button::L3);
|
||||
} else if (ctrl && map.button == static_cast<u64>(Button::R)) {
|
||||
buttons_cur |= static_cast<u64>(Button::R3);
|
||||
} else {
|
||||
buttons_cur |= map.button;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Map m_map{};
|
||||
HidKeyboardState m_state{};
|
||||
};
|
||||
|
||||
struct Controller {
|
||||
u64 m_kdown{};
|
||||
u64 m_kheld{};
|
||||
@@ -388,26 +472,44 @@ struct Controller {
|
||||
m_kup = 0;
|
||||
}
|
||||
|
||||
void UpdateButtonHeld(u64 buttons) {
|
||||
void UpdateButtonHeld(u64 buttons, double delta) {
|
||||
if (m_kdown & buttons) {
|
||||
m_step = 50;
|
||||
m_step_max = m_MAX_STEP;
|
||||
m_step = m_INC_STEP;
|
||||
m_counter = 0;
|
||||
m_step_max_counter = 0;
|
||||
} else if (m_kheld & buttons) {
|
||||
m_counter += m_step;
|
||||
m_counter += m_step * delta;
|
||||
|
||||
// if we are at the max, ignore the delta and go as fast as the frame rate.
|
||||
if (m_step_max == m_MAX) {
|
||||
m_counter = m_MAX;
|
||||
}
|
||||
|
||||
if (m_counter >= m_MAX) {
|
||||
m_kdown |= m_kheld & buttons;
|
||||
m_counter = 0;
|
||||
m_step = std::min(m_step + 50, m_MAX_STEP);
|
||||
m_step = std::min(m_step + m_INC_STEP, m_step_max);
|
||||
|
||||
// slowly speed up until we reach 1 button down per frame.
|
||||
m_step_max_counter++;
|
||||
if (m_step_max_counter >= 5) {
|
||||
m_step_max_counter = 0;
|
||||
m_step_max = std::min(m_step_max + m_INC_STEP, m_MAX);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr int m_MAX = 1000;
|
||||
static constexpr int m_MAX_STEP = 250;
|
||||
int m_step = 50;
|
||||
int m_counter = 0;
|
||||
static constexpr double m_MAX = 1000;
|
||||
static constexpr double m_MAX_STEP = 250;
|
||||
static constexpr double m_INC_STEP = 50;
|
||||
|
||||
double m_step_max = m_MAX_STEP;
|
||||
double m_step = m_INC_STEP;
|
||||
double m_counter = 0;
|
||||
int m_step_max_counter = 0;
|
||||
};
|
||||
|
||||
} // namespace sphaira
|
||||
|
||||
@@ -5,15 +5,19 @@
|
||||
#include <memory>
|
||||
#include <map>
|
||||
#include <unordered_map>
|
||||
#include <concepts>
|
||||
|
||||
namespace sphaira::ui {
|
||||
|
||||
struct uiButton final : Object {
|
||||
uiButton(Button button, Action action) : m_button{button}, m_action{action} {}
|
||||
uiButton(Button button, const std::string& button_str, const std::string& action_str);
|
||||
uiButton(Button button, const std::string& action_str);
|
||||
|
||||
auto Draw(NVGcontext* vg, Theme* theme) -> void override;
|
||||
|
||||
Button m_button;
|
||||
Action m_action;
|
||||
std::string m_button_str;
|
||||
std::string m_action_str;
|
||||
Vec4 m_button_pos{};
|
||||
Vec4 m_hint_pos{};
|
||||
};
|
||||
@@ -44,8 +48,8 @@ struct Widget : public Object {
|
||||
}
|
||||
|
||||
auto HasAction(Button button) const -> bool;
|
||||
void SetAction(Button button, Action action);
|
||||
void SetActions(std::same_as<std::pair<Button, Action>> auto ...args) {
|
||||
void SetAction(Button button, const Action& action);
|
||||
void SetActions(std::same_as<std::pair<Button, Action>> auto&& ...args) {
|
||||
const std::array list = {args...};
|
||||
for (const auto& [button, action] : list) {
|
||||
SetAction(button, action);
|
||||
@@ -62,6 +66,12 @@ struct Widget : public Object {
|
||||
m_actions.clear();
|
||||
}
|
||||
|
||||
void RemoveActions(const Actions& actions) {
|
||||
for (auto& e : actions) {
|
||||
RemoveAction(e.first);
|
||||
}
|
||||
}
|
||||
|
||||
auto FireAction(Button button, u8 type = ActionType::DOWN) -> bool;
|
||||
|
||||
void SetPop(bool pop = true) {
|
||||
@@ -77,6 +87,8 @@ struct Widget : public Object {
|
||||
}
|
||||
|
||||
auto GetUiButtons() const -> uiButtons;
|
||||
static void SetupUiButtons(uiButtons& buttons, const Vec2& button_pos = {1220, 675});
|
||||
static auto GetUiButtons(const Actions& actions, const Vec2& button_pos = {1220, 675}) -> uiButtons;
|
||||
|
||||
Actions m_actions{};
|
||||
Vec2 m_button_pos{1220, 675};
|
||||
@@ -84,4 +96,7 @@ struct Widget : public Object {
|
||||
bool m_pop{false};
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
concept DerivedFromWidget = std::is_base_of_v<Widget, T>;
|
||||
|
||||
} // namespace sphaira::ui
|
||||
|
||||
@@ -2,18 +2,12 @@
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <new>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb {
|
||||
|
||||
struct Base {
|
||||
enum { USBModule = 523 };
|
||||
|
||||
enum : Result {
|
||||
Result_Cancelled = MAKERESULT(USBModule, 100),
|
||||
};
|
||||
|
||||
Base(u64 transfer_timeout);
|
||||
virtual ~Base();
|
||||
|
||||
@@ -45,43 +39,10 @@ struct Base {
|
||||
ueventSignal(GetCancelEvent());
|
||||
}
|
||||
|
||||
auto& GetTransferBuffer() {
|
||||
return m_aligned;
|
||||
}
|
||||
|
||||
auto GetTransferTimeout() const {
|
||||
return m_transfer_timeout;
|
||||
}
|
||||
|
||||
public:
|
||||
// custom allocator for std::vector that respects alignment.
|
||||
// https://en.cppreference.com/w/cpp/named_req/Allocator
|
||||
template <typename T, std::size_t Align>
|
||||
struct CustomVectorAllocator {
|
||||
public:
|
||||
// https://en.cppreference.com/w/cpp/memory/new/operator_new
|
||||
auto allocate(std::size_t n) -> T* {
|
||||
n = (n + (Align - 1)) &~ (Align - 1);
|
||||
return new(align) T[n];
|
||||
}
|
||||
|
||||
// https://en.cppreference.com/w/cpp/memory/new/operator_delete
|
||||
auto deallocate(T* p, std::size_t n) noexcept -> void {
|
||||
// ::operator delete[] (p, n, align);
|
||||
::operator delete[] (p, align);
|
||||
}
|
||||
|
||||
private:
|
||||
static constexpr inline std::align_val_t align{Align};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct PageAllocator : CustomVectorAllocator<T, 0x1000> {
|
||||
using value_type = T; // used by std::vector
|
||||
};
|
||||
|
||||
using PageAlignedVector = std::vector<u8, PageAllocator<u8>>;
|
||||
|
||||
protected:
|
||||
enum UsbSessionEndpoint {
|
||||
UsbSessionEndpoint_In = 0,
|
||||
@@ -96,7 +57,7 @@ protected:
|
||||
private:
|
||||
u64 m_transfer_timeout{};
|
||||
UEvent m_uevent{};
|
||||
PageAlignedVector m_aligned{};
|
||||
std::unique_ptr<u8*> m_aligned{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb::tinfoil {
|
||||
|
||||
enum Magic : u32 {
|
||||
Magic_List0 = 0x304C5554, // TUL0 (Tinfoil Usb List 0)
|
||||
Magic_Command0 = 0x30435554, // TUC0 (Tinfoil USB Command 0)
|
||||
};
|
||||
|
||||
enum USBCmdType : u8 {
|
||||
REQUEST = 0,
|
||||
RESPONSE = 1
|
||||
};
|
||||
|
||||
enum USBCmdId : u32 {
|
||||
EXIT = 0,
|
||||
FILE_RANGE = 1
|
||||
};
|
||||
|
||||
// extension flags for sphaira.
|
||||
enum USBFlag : u8 {
|
||||
USBFlag_NONE = 0,
|
||||
// stream install, does not allow for random access.
|
||||
// allows the upload to be multi threaded., do not modify!
|
||||
// the order of the file list must be kept as-is.
|
||||
USBFlag_STREAM = 1 << 0,
|
||||
};
|
||||
|
||||
struct TUSHeader {
|
||||
u32 magic; // TUL0 (Tinfoil Usb List 0)
|
||||
u32 nspListSize;
|
||||
u8 flags;
|
||||
u8 padding[0x7];
|
||||
};
|
||||
|
||||
struct NX_PACKED USBCmdHeader {
|
||||
u32 magic; // TUC0 (Tinfoil USB Command 0)
|
||||
USBCmdType type;
|
||||
u8 padding[0x3];
|
||||
u32 cmdId;
|
||||
u64 dataSize;
|
||||
u8 reserved[0xC];
|
||||
};
|
||||
|
||||
struct FileRangeCmdHeader {
|
||||
u64 size;
|
||||
u64 offset;
|
||||
u64 nspNameLen;
|
||||
u64 padding;
|
||||
};
|
||||
|
||||
static_assert(sizeof(TUSHeader) == 0x10, "TUSHeader must be 0x10!");
|
||||
static_assert(sizeof(USBCmdHeader) == 0x20, "USBCmdHeader must be 0x20!");
|
||||
|
||||
} // namespace sphaira::usb::tinfoil
|
||||
112
sphaira/include/usb/usb_api.hpp
Normal file
112
sphaira/include/usb/usb_api.hpp
Normal file
@@ -0,0 +1,112 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "defines.hpp"
|
||||
|
||||
namespace sphaira::usb::api {
|
||||
|
||||
enum : u32 {
|
||||
MAGIC = 0x53504830,
|
||||
PACKET_SIZE = 24,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
CMD_QUIT = 0,
|
||||
CMD_OPEN = 1,
|
||||
CMD_EXPORT = 1,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
RESULT_OK = 0,
|
||||
RESULT_ERROR = 1,
|
||||
};
|
||||
|
||||
enum : u32 {
|
||||
FLAG_NONE = 0,
|
||||
FLAG_STREAM = 1 << 0,
|
||||
};
|
||||
|
||||
struct UsbPacket {
|
||||
u32 magic{};
|
||||
u32 arg2{};
|
||||
u32 arg3{};
|
||||
u32 arg4{};
|
||||
u32 arg5{};
|
||||
u32 crc32c{}; // crc32 over the above 16 bytes.
|
||||
|
||||
protected:
|
||||
u32 CalculateCrc32c() const {
|
||||
return crc32cCalculate(this, 20);
|
||||
}
|
||||
|
||||
void GenerateCrc32c() {
|
||||
crc32c = CalculateCrc32c();
|
||||
}
|
||||
|
||||
Result Verify() const {
|
||||
R_UNLESS(crc32c == CalculateCrc32c(), 1); // todo: add error code.
|
||||
R_UNLESS(magic == MAGIC, Result_UsbBadMagic);
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
struct SendPacket : UsbPacket {
|
||||
static SendPacket Build(u32 cmd, u32 arg3 = 0, u32 arg4 = 0) {
|
||||
SendPacket packet{MAGIC, cmd, arg3, arg4};
|
||||
packet.GenerateCrc32c();
|
||||
return packet;
|
||||
}
|
||||
|
||||
Result Verify() const {
|
||||
return UsbPacket::Verify();
|
||||
}
|
||||
|
||||
u32 GetCmd() const {
|
||||
return arg2;
|
||||
}
|
||||
};
|
||||
|
||||
struct ResultPacket : UsbPacket {
|
||||
static ResultPacket Build(u32 result, u32 arg3 = 0, u32 arg4 = 0) {
|
||||
ResultPacket packet{MAGIC, result, arg3, arg4};
|
||||
packet.GenerateCrc32c();
|
||||
return packet;
|
||||
}
|
||||
|
||||
Result Verify() const {
|
||||
R_TRY(UsbPacket::Verify());
|
||||
R_UNLESS(arg2 == RESULT_OK, 1); // todo: create error code.
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
struct SendDataPacket : UsbPacket {
|
||||
static SendDataPacket Build(u64 off, u32 size, u32 crc32c) {
|
||||
SendDataPacket packet{MAGIC, u32(off >> 32), u32(off), size, crc32c};
|
||||
packet.GenerateCrc32c();
|
||||
return packet;
|
||||
}
|
||||
|
||||
Result Verify() const {
|
||||
return UsbPacket::Verify();
|
||||
}
|
||||
|
||||
u64 GetOffset() const {
|
||||
return (u64(arg2) << 32) | arg3;
|
||||
}
|
||||
|
||||
u32 GetSize() const {
|
||||
return arg4;
|
||||
}
|
||||
|
||||
u32 GetCrc32c() const {
|
||||
return arg5;
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(UsbPacket) == PACKET_SIZE);
|
||||
static_assert(sizeof(SendPacket) == PACKET_SIZE);
|
||||
static_assert(sizeof(ResultPacket) == PACKET_SIZE);
|
||||
static_assert(sizeof(SendDataPacket) == PACKET_SIZE);
|
||||
|
||||
} // namespace sphaira::usb::api
|
||||
45
sphaira/include/usb/usb_dumper.hpp
Normal file
45
sphaira/include/usb/usb_dumper.hpp
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
#include "usb/usbds.hpp"
|
||||
#include "usb/usb_api.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb::dump {
|
||||
|
||||
struct Usb {
|
||||
Usb(u64 transfer_timeout);
|
||||
~Usb();
|
||||
|
||||
Result Write(const void* buf, u64 off, u32 size);
|
||||
void SignalCancel();
|
||||
|
||||
// waits for connection and then sends file list.
|
||||
Result IsUsbConnected(u64 timeout);
|
||||
Result WaitForConnection(std::string_view path, u64 timeout);
|
||||
|
||||
// Result OpenFile(u32 index, s64& file_size);
|
||||
Result CloseFile();
|
||||
|
||||
auto GetOpenResult() const {
|
||||
return m_open_result;
|
||||
}
|
||||
|
||||
auto GetCancelEvent() {
|
||||
return m_usb->GetCancelEvent();
|
||||
}
|
||||
|
||||
private:
|
||||
Result SendAndVerify(const void* data, u32 size, u64 timeout, api::ResultPacket* out = nullptr);
|
||||
Result SendAndVerify(const void* data, u32 size, api::ResultPacket* out = nullptr);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbDs> m_usb{};
|
||||
Result m_open_result{};
|
||||
bool m_was_connected{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb::dumpl
|
||||
47
sphaira/include/usb/usb_installer.hpp
Normal file
47
sphaira/include/usb/usb_installer.hpp
Normal file
@@ -0,0 +1,47 @@
|
||||
#pragma once
|
||||
|
||||
#include "usb/usbds.hpp"
|
||||
#include "usb/usb_api.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb::install {
|
||||
|
||||
struct Usb {
|
||||
Usb(u64 transfer_timeout);
|
||||
~Usb();
|
||||
|
||||
Result Read(void* buf, u64 off, u32 size, u64* bytes_read);
|
||||
u32 GetFlags() const;
|
||||
void SignalCancel();
|
||||
|
||||
// waits for connection and then sends file list.
|
||||
Result IsUsbConnected(u64 timeout);
|
||||
Result WaitForConnection(u64 timeout, std::vector<std::string>& names);
|
||||
|
||||
Result OpenFile(u32 index, s64& file_size);
|
||||
Result CloseFile();
|
||||
|
||||
auto GetOpenResult() const {
|
||||
return m_open_result;
|
||||
}
|
||||
|
||||
auto GetCancelEvent() {
|
||||
return m_usb->GetCancelEvent();
|
||||
}
|
||||
|
||||
private:
|
||||
Result SendAndVerify(const void* data, u32 size, u64 timeout, api::ResultPacket* out = nullptr);
|
||||
Result SendAndVerify(const void* data, u32 size, api::ResultPacket* out = nullptr);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbDs> m_usb{};
|
||||
Result m_open_result{};
|
||||
bool m_was_connected{};
|
||||
u32 m_flags{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb::install
|
||||
@@ -10,38 +10,41 @@
|
||||
namespace sphaira::usb::upload {
|
||||
|
||||
struct Usb {
|
||||
enum { USBModule = 523 };
|
||||
|
||||
enum : Result {
|
||||
Result_BadMagic = MAKERESULT(USBModule, 0),
|
||||
Result_Exit = MAKERESULT(USBModule, 1),
|
||||
Result_BadCount = MAKERESULT(USBModule, 2),
|
||||
Result_BadTransferSize = MAKERESULT(USBModule, 3),
|
||||
Result_BadTotalSize = MAKERESULT(USBModule, 4),
|
||||
Result_BadCommand = MAKERESULT(USBModule, 4),
|
||||
};
|
||||
|
||||
Usb(u64 transfer_timeout);
|
||||
virtual ~Usb();
|
||||
|
||||
virtual Result Read(const std::string& path, void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
virtual Result Read(void* buf, u64 off, u32 size, u64* bytes_read) = 0;
|
||||
virtual Result Open(u32 index, s64& out_size, u16& out_flags) = 0;
|
||||
|
||||
Result IsUsbConnected(u64 timeout) {
|
||||
return m_usb->IsUsbConnected(timeout);
|
||||
}
|
||||
|
||||
// waits for connection and then sends file list.
|
||||
Result WaitForConnection(u64 timeout, u8 flags, std::span<const std::string> names);
|
||||
Result WaitForConnection(u64 timeout, std::span<const std::string> names);
|
||||
|
||||
// polls for command, executes transfer if possible.
|
||||
// will return Result_Exit if exit command is recieved.
|
||||
Result PollCommands();
|
||||
|
||||
private:
|
||||
Result FileRangeCmd(u64 data_size);
|
||||
Result file_transfer_loop();
|
||||
|
||||
auto GetOpenResult() const {
|
||||
return m_open_result;
|
||||
}
|
||||
|
||||
auto GetCancelEvent() {
|
||||
return m_usb->GetCancelEvent();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbHs> m_usb;
|
||||
Result SendResult(u32 result, u32 arg3 = 0, u32 arg4 = 0);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbHs> m_usb{};
|
||||
std::vector<u8> m_buf{};
|
||||
Result m_open_result{};
|
||||
bool m_was_connected{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::usb::upload
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
|
||||
#include "base.hpp"
|
||||
|
||||
auto GetUsbDsStateStr(UsbState state) -> const char*;
|
||||
auto GetUsbDsSpeedStr(UsbDeviceSpeed speed) -> const char*;
|
||||
|
||||
namespace sphaira::usb {
|
||||
|
||||
// Device Host
|
||||
|
||||
13
sphaira/include/usbdvd.hpp
Normal file
13
sphaira/include/usbdvd.hpp
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "location.hpp"
|
||||
|
||||
namespace sphaira::usbdvd {
|
||||
|
||||
Result MountAll();
|
||||
void UnmountAll();
|
||||
|
||||
bool GetMountPoint(location::StdioEntry& out);
|
||||
|
||||
} // namespace sphaira::usbdvd
|
||||
81
sphaira/include/utils/audio.hpp
Normal file
81
sphaira/include/utils/audio.hpp
Normal file
@@ -0,0 +1,81 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "image.hpp"
|
||||
#include <string>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::audio {
|
||||
|
||||
enum class State {
|
||||
Free, // private use.
|
||||
Playing, // song is playing.
|
||||
Paused, // song has paused.
|
||||
Finished, // song has finished.
|
||||
Error, // error in playback.
|
||||
};
|
||||
|
||||
struct Progress {
|
||||
u64 played;
|
||||
};
|
||||
|
||||
struct Info {
|
||||
u64 sample_count;
|
||||
u32 sample_rate;
|
||||
u32 channels;
|
||||
u32 loop_start;
|
||||
bool looping;
|
||||
};
|
||||
|
||||
struct Meta {
|
||||
std::string title{};
|
||||
std::string album{};
|
||||
std::string artist{};
|
||||
std::vector<u8> image{};
|
||||
};
|
||||
|
||||
enum class SoundEffect {
|
||||
Focus,
|
||||
Scroll,
|
||||
Limit,
|
||||
Startup,
|
||||
Install,
|
||||
Error,
|
||||
MAX,
|
||||
};
|
||||
|
||||
enum Flag {
|
||||
Flag_None = 0,
|
||||
// plays the song for ever.
|
||||
Flag_Loop = 1 << 0,
|
||||
};
|
||||
|
||||
using SongID = void*;
|
||||
|
||||
Result Init();
|
||||
void ExitSignal();
|
||||
void Exit();
|
||||
|
||||
Result PlaySoundEffect(SoundEffect effect);
|
||||
|
||||
Result OpenSong(fs::Fs* fs, const fs::FsPath& path, u32 flags, SongID* id);
|
||||
Result CloseSong(SongID* id);
|
||||
|
||||
Result PlaySong(SongID id);
|
||||
Result PauseSong(SongID id);
|
||||
Result SeekSong(SongID id, u64 target);
|
||||
|
||||
// todo:
|
||||
// 0.0 -> 2.0.
|
||||
Result GetVolumeSong(SongID id, float* out);
|
||||
Result SetVolumeSong(SongID id, float in);
|
||||
|
||||
// todo:
|
||||
Result GetPitchSong(SongID id, float* out);
|
||||
Result SetPitchSong(SongID id, float in);
|
||||
|
||||
Result GetInfo(SongID id, Info* out);
|
||||
Result GetMeta(SongID id, Meta* out);
|
||||
Result GetProgress(SongID id, Progress* out_progress, State* out_state);
|
||||
|
||||
} // namespace sphaira::audio
|
||||
43
sphaira/include/utils/devoptab.hpp
Normal file
43
sphaira/include/utils/devoptab.hpp
Normal file
@@ -0,0 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "yati/source/base.hpp"
|
||||
#include "location.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::devoptab {
|
||||
|
||||
Result MountSaveSystem(u64 id, fs::FsPath& out_path);
|
||||
Result MountZip(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
Result MountNsp(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
Result MountXci(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
Result MountXciSource(const std::shared_ptr<sphaira::yati::source::Base>& source, s64 size, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
Result MountNca(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
Result MountNcaNcm(NcmContentStorage* cs, const NcmContentId* id, fs::FsPath& out_path);
|
||||
Result MountBfsar(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
Result MountNro(fs::Fs* fs, const fs::FsPath& path, fs::FsPath& out_path);
|
||||
|
||||
Result MountVfsAll();
|
||||
Result MountWebdavAll();
|
||||
Result MountHttpAll();
|
||||
Result MountFtpAll();
|
||||
Result MountSftpAll();
|
||||
Result MountNfsAll();
|
||||
Result MountSmb2All();
|
||||
Result MountFatfsAll();
|
||||
Result MountGameAll();
|
||||
Result MountInternalMounts();
|
||||
|
||||
Result GetNetworkDevices(location::StdioEntries& out);
|
||||
void UmountAllNeworkDevices();
|
||||
void UmountNeworkDevice(const fs::FsPath& mount);
|
||||
|
||||
// manually set the array so that we can avoid nullptr access.
|
||||
// SEE: https://github.com/devkitPro/newlib/issues/35
|
||||
void FixDkpBug();
|
||||
|
||||
void DisplayDevoptabSideBar();
|
||||
|
||||
} // namespace sphaira::devoptab
|
||||
227
sphaira/include/utils/devoptab_common.hpp
Normal file
227
sphaira/include/utils/devoptab_common.hpp
Normal file
@@ -0,0 +1,227 @@
|
||||
#pragma once
|
||||
|
||||
#include "yati/source/file.hpp"
|
||||
#include "utils/lru.hpp"
|
||||
#include "location.hpp"
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <span>
|
||||
#include <functional>
|
||||
#include <unordered_map>
|
||||
#include <curl/curl.h>
|
||||
|
||||
namespace sphaira::devoptab::common {
|
||||
|
||||
// max entries per devoptab, should be enough.
|
||||
enum { MAX_ENTRIES = 4 };
|
||||
|
||||
struct BufferedDataBase : yati::source::Base {
|
||||
BufferedDataBase(const std::shared_ptr<yati::source::Base>& _source, u64 _size)
|
||||
: source{_source}
|
||||
, capacity{_size} {
|
||||
|
||||
}
|
||||
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||
return source->Read(buf, off, size, bytes_read);
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<yati::source::Base> source;
|
||||
const u64 capacity;
|
||||
};
|
||||
|
||||
// buffers data in 512k chunks to maximise throughput.
|
||||
// not suitable if random access >= 512k is common.
|
||||
// if that is needed, see the LRU cache varient used for fatfs.
|
||||
struct BufferedData : BufferedDataBase {
|
||||
BufferedData(const std::shared_ptr<yati::source::Base>& _source, u64 _size, u64 _alloc = 1024 * 512)
|
||||
: BufferedDataBase{_source, _size} {
|
||||
m_data.resize(_alloc);
|
||||
}
|
||||
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
|
||||
private:
|
||||
u64 m_off{};
|
||||
u64 m_size{};
|
||||
std::vector<u8> m_data{};
|
||||
};
|
||||
|
||||
struct BufferedFileData {
|
||||
u8* data{};
|
||||
u64 off{};
|
||||
u64 size{};
|
||||
|
||||
~BufferedFileData() {
|
||||
if (data) {
|
||||
free(data);
|
||||
}
|
||||
}
|
||||
|
||||
void Allocate(u64 new_size) {
|
||||
data = (u8*)realloc(data, new_size * sizeof(*data));
|
||||
off = 0;
|
||||
size = 0;
|
||||
}
|
||||
};
|
||||
|
||||
constexpr u64 CACHE_LARGE_ALLOC_SIZE = 1024 * 512;
|
||||
constexpr u64 CACHE_LARGE_SIZE = 1024 * 16;
|
||||
|
||||
struct LruBufferedData : BufferedDataBase {
|
||||
LruBufferedData(const std::shared_ptr<yati::source::Base>& _source, u64 _size, u32 small = 1024, u32 large = 2)
|
||||
: BufferedDataBase{_source, _size} {
|
||||
buffered_small.resize(small);
|
||||
buffered_large.resize(large);
|
||||
lru_cache[0].Init(buffered_small);
|
||||
lru_cache[1].Init(buffered_large);
|
||||
}
|
||||
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
|
||||
private:
|
||||
utils::Lru<BufferedFileData> lru_cache[2]{};
|
||||
std::vector<BufferedFileData> buffered_small{}; // 1MiB (usually).
|
||||
std::vector<BufferedFileData> buffered_large{}; // 1MiB
|
||||
};
|
||||
|
||||
bool fix_path(const char* str, char* out, bool strip_leading_slash = false);
|
||||
|
||||
void update_devoptab_for_read_only(devoptab_t* devoptab, bool read_only);
|
||||
|
||||
struct PushPullThreadData {
|
||||
static constexpr size_t MAX_BUFFER_SIZE = 1024 * 64; // 64KB max buffer
|
||||
|
||||
explicit PushPullThreadData(CURL* _curl);
|
||||
virtual ~PushPullThreadData();
|
||||
|
||||
Result CreateAndStart();
|
||||
void Cancel();
|
||||
bool IsRunning();
|
||||
|
||||
// only set curl=true if called from a curl callback.
|
||||
size_t PullData(char* data, size_t total_size, bool curl = false);
|
||||
size_t PushData(const char* data, size_t total_size, bool curl = false);
|
||||
|
||||
static size_t progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow);
|
||||
|
||||
private:
|
||||
static void thread_func(void* arg);
|
||||
|
||||
public:
|
||||
CURL* const curl{};
|
||||
std::vector<char> buffer{};
|
||||
Mutex mutex{};
|
||||
CondVar can_push{};
|
||||
CondVar can_pull{};
|
||||
|
||||
long code{};
|
||||
bool error{};
|
||||
bool finished{};
|
||||
bool started{};
|
||||
|
||||
private:
|
||||
Thread thread{};
|
||||
};
|
||||
|
||||
struct MountConfig {
|
||||
std::string name{};
|
||||
std::string url{};
|
||||
std::string user{};
|
||||
std::string pass{};
|
||||
std::string dump_path{};
|
||||
long port{};
|
||||
long timeout{};
|
||||
bool read_only{};
|
||||
bool no_stat_file{true};
|
||||
bool no_stat_dir{true};
|
||||
bool fs_hidden{};
|
||||
bool dump_hidden{};
|
||||
|
||||
std::unordered_map<std::string, std::string> extra{};
|
||||
};
|
||||
using MountConfigs = std::vector<MountConfig>;
|
||||
|
||||
struct PullThreadData final : PushPullThreadData {
|
||||
using PushPullThreadData::PushPullThreadData;
|
||||
static size_t pull_thread_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
};
|
||||
|
||||
struct PushThreadData final : PushPullThreadData {
|
||||
using PushPullThreadData::PushPullThreadData;
|
||||
static size_t push_thread_callback(const char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
};
|
||||
|
||||
struct MountDevice {
|
||||
MountDevice(const MountConfig& _config) : config{_config} {}
|
||||
virtual ~MountDevice() = default;
|
||||
|
||||
virtual bool fix_path(const char* str, char* out, bool strip_leading_slash = false) {
|
||||
return common::fix_path(str, out, strip_leading_slash);
|
||||
}
|
||||
|
||||
virtual bool Mount() = 0;
|
||||
virtual int devoptab_open(void *fileStruct, const char *path, int flags, int mode) { return -EIO; }
|
||||
virtual int devoptab_close(void *fd) { return -EIO; }
|
||||
virtual ssize_t devoptab_read(void *fd, char *ptr, size_t len) { return -EIO; }
|
||||
virtual ssize_t devoptab_write(void *fd, const char *ptr, size_t len) { return -EIO; }
|
||||
virtual ssize_t devoptab_seek(void *fd, off_t pos, int dir) { return 0; }
|
||||
virtual int devoptab_fstat(void *fd, struct stat *st) { return -EIO; }
|
||||
virtual int devoptab_unlink(const char *path) { return -EIO; }
|
||||
virtual int devoptab_rename(const char *oldName, const char *newName) { return -EIO; }
|
||||
virtual int devoptab_mkdir(const char *path, int mode) { return -EIO; }
|
||||
virtual int devoptab_rmdir(const char *path) { return -EIO; }
|
||||
virtual int devoptab_diropen(void* fd, const char *path) { return -EIO; }
|
||||
virtual int devoptab_dirreset(void* fd) { return -EIO; }
|
||||
virtual int devoptab_dirnext(void* fd, char *filename, struct stat *filestat) { return -EIO; }
|
||||
virtual int devoptab_dirclose(void* fd) { return -EIO; }
|
||||
virtual int devoptab_lstat(const char *path, struct stat *st) { return -EIO; }
|
||||
virtual int devoptab_ftruncate(void *fd, off_t len) { return -EIO; }
|
||||
virtual int devoptab_statvfs(const char *_path, struct statvfs *buf) { return -EIO; }
|
||||
virtual int devoptab_fsync(void *fd) { return -EIO; }
|
||||
virtual int devoptab_utimes(const char *_path, const struct timeval times[2]) { return -EIO; }
|
||||
|
||||
const MountConfig config;
|
||||
};
|
||||
|
||||
struct MountCurlDevice : MountDevice {
|
||||
using MountDevice::MountDevice;
|
||||
virtual ~MountCurlDevice();
|
||||
|
||||
PushThreadData* CreatePushData(CURL* curl, const std::string& url, size_t offset);
|
||||
PullThreadData* CreatePullData(CURL* curl, const std::string& url, bool append = false);
|
||||
|
||||
virtual bool Mount();
|
||||
virtual void curl_set_common_options(CURL* curl, const std::string& url);
|
||||
static size_t write_memory_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
static size_t write_data_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
static size_t read_data_callback(char *ptr, size_t size, size_t nmemb, void *userdata);
|
||||
static std::string html_decode(const std::string_view& str);
|
||||
static std::string url_decode(const std::string& str);
|
||||
std::string build_url(const std::string& path, bool is_dir);
|
||||
|
||||
protected:
|
||||
CURL* curl{};
|
||||
CURL* transfer_curl{};
|
||||
|
||||
private:
|
||||
// path extracted from the url.
|
||||
std::string m_url_path{};
|
||||
CURLU* curlu{};
|
||||
CURLSH* m_curl_share{};
|
||||
RwLock m_rwlocks[CURL_LOCK_DATA_LAST]{};
|
||||
bool m_mounted{};
|
||||
};
|
||||
|
||||
void LoadConfigsFromIni(const fs::FsPath& path, MountConfigs& out_configs);
|
||||
|
||||
using CreateDeviceCallback = std::function<std::unique_ptr<MountDevice>(const MountConfig& config)>;
|
||||
Result MountNetworkDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name, bool force_read_only = false);
|
||||
|
||||
// same as above but takes in the device and expects the mount name to be set.
|
||||
bool MountNetworkDevice2(std::unique_ptr<MountDevice>&& device, const MountConfig& config, size_t file_size, size_t dir_size, const char* name, const char* mount_name);
|
||||
|
||||
bool MountReadOnlyIndexDevice(const CreateDeviceCallback& create_device, size_t file_size, size_t dir_size, const char* name, fs::FsPath& out_path);
|
||||
|
||||
} // namespace sphaira::devoptab::common
|
||||
41
sphaira/include/utils/devoptab_romfs.hpp
Normal file
41
sphaira/include/utils/devoptab_romfs.hpp
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
|
||||
#include "yati/source/base.hpp"
|
||||
#include <memory>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include <string_view>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace sphaira::devoptab::romfs {
|
||||
|
||||
struct RomfsCollection {
|
||||
romfs_header header;
|
||||
std::vector<u8> dir_table;
|
||||
std::vector<u8> file_table;
|
||||
u64 offset;
|
||||
};
|
||||
|
||||
struct FileEntry {
|
||||
const romfs_file* romfs;
|
||||
u64 offset;
|
||||
u64 size;
|
||||
};
|
||||
|
||||
struct DirEntry {
|
||||
const RomfsCollection* romfs_collection;
|
||||
const romfs_dir* romfs_root; // start of the dir.
|
||||
u32 romfs_childDir;
|
||||
u32 romfs_childFile;
|
||||
};
|
||||
|
||||
bool find_file(const RomfsCollection& romfs, std::string_view path, FileEntry& out);
|
||||
bool find_dir(const RomfsCollection& romfs, std::string_view path, DirEntry& out);
|
||||
|
||||
// helper
|
||||
void dirreset(DirEntry& entry);
|
||||
bool dirnext(DirEntry& entry, char* filename, struct stat *filestat);
|
||||
|
||||
Result LoadRomfsCollection(yati::source::Base* source, u64 offset, RomfsCollection& out);
|
||||
|
||||
} // namespace sphaira::devoptab::romfs
|
||||
76
sphaira/include/utils/lru.hpp
Normal file
76
sphaira/include/utils/lru.hpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
#include <span>
|
||||
|
||||
namespace sphaira::utils {
|
||||
|
||||
template<typename T>
|
||||
struct LinkedList {
|
||||
T* data;
|
||||
LinkedList* next;
|
||||
LinkedList* prev;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
struct Lru {
|
||||
using ListEntry = LinkedList<T>;
|
||||
|
||||
// pass span of the data.
|
||||
void Init(std::span<T> data) {
|
||||
list_flat_array.clear();
|
||||
list_flat_array.resize(data.size());
|
||||
|
||||
auto list_entry = list_head = list_flat_array.data();
|
||||
|
||||
for (size_t i = 0; i < data.size(); i++) {
|
||||
list_entry = list_flat_array.data() + i;
|
||||
list_entry->data = data.data() + i;
|
||||
|
||||
if (i + 1 < data.size()) {
|
||||
list_entry->next = &list_flat_array[i + 1];
|
||||
}
|
||||
if (i) {
|
||||
list_entry->prev = &list_flat_array[i - 1];
|
||||
}
|
||||
}
|
||||
|
||||
list_tail = list_entry->prev->next;
|
||||
}
|
||||
|
||||
// moves entry to the front of the list.
|
||||
void Update(ListEntry* entry) {
|
||||
// only update position if we are not the head.
|
||||
if (list_head != entry) {
|
||||
entry->prev->next = entry->next;
|
||||
if (entry->next) {
|
||||
entry->next->prev = entry->prev;
|
||||
} else {
|
||||
list_tail = entry->prev;
|
||||
}
|
||||
|
||||
// update head.
|
||||
auto head_temp = list_head;
|
||||
list_head = entry;
|
||||
list_head->prev = nullptr;
|
||||
list_head->next = head_temp;
|
||||
head_temp->prev = list_head;
|
||||
}
|
||||
}
|
||||
|
||||
// moves last entry (tail) to the front of the list.
|
||||
auto GetNextFree() {
|
||||
Update(list_tail);
|
||||
return list_head->data;
|
||||
}
|
||||
|
||||
auto begin() const { return list_head; }
|
||||
auto end() const { return list_tail; }
|
||||
|
||||
private:
|
||||
ListEntry* list_head{};
|
||||
ListEntry* list_tail{};
|
||||
std::vector<ListEntry> list_flat_array{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::utils
|
||||
23
sphaira/include/utils/nsz_dumper.hpp
Normal file
23
sphaira/include/utils/nsz_dumper.hpp
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "defines.hpp"
|
||||
#include "dumper.hpp"
|
||||
|
||||
#include "ui/progress_box.hpp"
|
||||
#include "yati/nx/keys.hpp"
|
||||
#include "yati/nx/nca.hpp"
|
||||
#include "yati/container/base.hpp"
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace sphaira::utils::nsz {
|
||||
|
||||
using Collection = yati::container::CollectionEntry;
|
||||
using Collections = yati::container::Collections;
|
||||
|
||||
using NcaReaderCreator = std::function<std::unique_ptr<nca::NcaReader>(const nca::Header& header, const keys::KeyEntry& title_key, const Collection& collection)>;
|
||||
|
||||
Result NszExport(ui::ProgressBox* pbox, const NcaReaderCreator& nca_creator, s64& read_offset, s64& write_offset, Collections& collections, const keys::Keys& keys, dump::BaseSource* source, dump::WriteSource* writer, const fs::FsPath& path);
|
||||
|
||||
} // namespace sphaira::utils::nsz
|
||||
28
sphaira/include/utils/profile.hpp
Normal file
28
sphaira/include/utils/profile.hpp
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/types.hpp"
|
||||
#include "log.hpp"
|
||||
|
||||
namespace sphaira::utils {
|
||||
|
||||
struct ScopedTimestampProfile final {
|
||||
ScopedTimestampProfile(const std::string& name) : m_name{name} {
|
||||
|
||||
}
|
||||
|
||||
~ScopedTimestampProfile() {
|
||||
Log();
|
||||
}
|
||||
|
||||
void Log() {
|
||||
log_write("\t[%s] time taken: %.2fs %.2fms\n", m_name.c_str(), m_ts.GetSecondsD(), m_ts.GetMsD());
|
||||
}
|
||||
|
||||
private:
|
||||
const std::string m_name;
|
||||
TimeStamp m_ts{};
|
||||
};
|
||||
|
||||
#define SCOPED_TIMESTAMP(name) sphaira::utils::ScopedTimestampProfile ANONYMOUS_VARIABLE(SCOPE_PROFILE_STATE_){name};
|
||||
|
||||
} // namespace sphaira::utils
|
||||
58
sphaira/include/utils/thread.hpp
Normal file
58
sphaira/include/utils/thread.hpp
Normal file
@@ -0,0 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "defines.hpp"
|
||||
#include <functional>
|
||||
#include <atomic>
|
||||
|
||||
namespace sphaira::utils {
|
||||
|
||||
static inline Result CreateThread(Thread *t, ThreadFunc entry, void *arg, size_t stack_sz = 1024*128, int prio = 0x3B) {
|
||||
u64 core_mask = 0;
|
||||
R_TRY(svcGetInfo(&core_mask, InfoType_CoreMask, CUR_PROCESS_HANDLE, 0));
|
||||
R_TRY(threadCreate(t, entry, arg, nullptr, stack_sz, prio, -2));
|
||||
R_TRY(svcSetThreadCoreMask(t->handle, -1, core_mask));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
struct Async final {
|
||||
using Callback = std::function<void(void)>;
|
||||
|
||||
// core0=main, core1=audio, core2=servers (ftp,mtp,nxlink)
|
||||
Async(Callback&& callback) : m_callback{std::forward<Callback>(callback)} {
|
||||
m_running = true;
|
||||
|
||||
if (R_FAILED(CreateThread(&m_thread, thread_func, &m_callback))) {
|
||||
m_running = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (R_FAILED(threadStart(&m_thread))) {
|
||||
threadClose(&m_thread);
|
||||
m_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
~Async() {
|
||||
WaitForExit();
|
||||
}
|
||||
|
||||
void WaitForExit() {
|
||||
if (m_running) {
|
||||
threadWaitForExit(&m_thread);
|
||||
threadClose(&m_thread);
|
||||
m_running = false;
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
static void thread_func(void* arg) {
|
||||
(*static_cast<Callback*>(arg))();
|
||||
}
|
||||
|
||||
private:
|
||||
Callback m_callback;
|
||||
Thread m_thread{};
|
||||
std::atomic_bool m_running{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::utils
|
||||
32
sphaira/include/utils/utils.hpp
Normal file
32
sphaira/include/utils/utils.hpp
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "ui/types.hpp"
|
||||
|
||||
namespace sphaira::utils {
|
||||
|
||||
struct HashStr {
|
||||
char str[0x21];
|
||||
};
|
||||
|
||||
HashStr hexIdToStr(FsRightsId id);
|
||||
HashStr hexIdToStr(NcmRightsId id);
|
||||
HashStr hexIdToStr(NcmContentId id);
|
||||
|
||||
template<typename T>
|
||||
constexpr inline T AlignUp(T value, T align) {
|
||||
return (value + (align - 1)) &~ (align - 1);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
constexpr inline T AlignDown(T value, T align) {
|
||||
return value &~ (align - 1);
|
||||
}
|
||||
|
||||
// formats size to 1.23 MB in 1024 base.
|
||||
// only uses 32 bytes so its SSO optimised, not need to cache.
|
||||
std::string formatSizeStorage(u64 size);
|
||||
|
||||
// formats size to 1.23 MB in 1000 base (used for progress bars).
|
||||
std::string formatSizeNetwork(u64 size);
|
||||
|
||||
} // namespace sphaira::utils
|
||||
@@ -29,7 +29,7 @@ using Collections = std::vector<CollectionEntry>;
|
||||
struct Base {
|
||||
using Source = source::Base;
|
||||
|
||||
Base(std::shared_ptr<Source> source) : m_source{source} { }
|
||||
Base(Source* source) : m_source{source} { }
|
||||
virtual ~Base() = default;
|
||||
virtual Result GetCollections(Collections& out) = 0;
|
||||
auto GetSource() const {
|
||||
@@ -37,7 +37,7 @@ struct Base {
|
||||
}
|
||||
|
||||
protected:
|
||||
std::shared_ptr<Source> m_source;
|
||||
Source* m_source;
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
|
||||
@@ -9,9 +9,10 @@ namespace sphaira::yati::container {
|
||||
struct Nsp final : Base {
|
||||
using Base::Base;
|
||||
Result GetCollections(Collections& out) override;
|
||||
Result GetCollections(Collections& out, s64 off);
|
||||
|
||||
// builds nsp meta data and the size of the entier nsp.
|
||||
static auto Build(std::span<CollectionEntry> collections, s64& size) -> std::vector<u8>;
|
||||
static auto Build(std::span<const CollectionEntry> collections, s64& size) -> std::vector<u8>;
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
|
||||
@@ -8,8 +8,63 @@
|
||||
namespace sphaira::yati::container {
|
||||
|
||||
struct Xci final : Base {
|
||||
|
||||
struct Hfs0Header {
|
||||
u32 magic;
|
||||
u32 total_files;
|
||||
u32 string_table_size;
|
||||
u32 padding;
|
||||
};
|
||||
|
||||
struct Hfs0FileTableEntry {
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
u32 name_offset;
|
||||
u32 hash_size;
|
||||
u64 padding;
|
||||
u8 hash[0x20];
|
||||
};
|
||||
|
||||
struct Hfs0 {
|
||||
Hfs0Header header{};
|
||||
std::vector<Hfs0FileTableEntry> file_table{};
|
||||
std::vector<std::string> string_table{};
|
||||
s64 data_offset{};
|
||||
|
||||
auto GetHfs0Size() const {
|
||||
return sizeof(header) + file_table.size() * sizeof(Hfs0FileTableEntry) + header.string_table_size;
|
||||
}
|
||||
|
||||
auto GetHfs0Data() const -> std::vector<u8>;
|
||||
};
|
||||
|
||||
struct Partition {
|
||||
// name of the partition.
|
||||
std::string name;
|
||||
// offset of this hfs0.
|
||||
s64 hfs0_offset;
|
||||
s64 hfs0_size;
|
||||
Hfs0 hfs0;
|
||||
// all the collections for this partition, may be empty.
|
||||
Collections collections;
|
||||
};
|
||||
|
||||
struct Root {
|
||||
// offset of this hfs0.
|
||||
s64 hfs0_offset;
|
||||
Hfs0 hfs0;
|
||||
std::vector<Partition> partitions;
|
||||
};
|
||||
|
||||
using Partitions = std::vector<Partition>;
|
||||
|
||||
using Base::Base;
|
||||
Result GetCollections(Collections& out) override;
|
||||
Result GetRoot(Root& out);
|
||||
|
||||
private:
|
||||
Result Hfs0GetPartition(source::Base* source, s64 off, Hfs0& out);
|
||||
Result ReadPartitionFromHfs0(source::Base* source, const Hfs0& root, u32 index, Partition& out);
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::container
|
||||
|
||||
@@ -54,4 +54,15 @@ static inline void cryptoAes128Xts(const void* in, void* out, const u8* key, u64
|
||||
Aes128Xts(key, is_encryptor).Run(out, in, sector, sector_size, data_size);
|
||||
}
|
||||
|
||||
static inline void UpdateCtr(u8* counter, u64 offset) {
|
||||
const u64 swp = __bswap64(offset >> 4);
|
||||
std::memcpy(&counter[0x8], &swp, 0x8);
|
||||
}
|
||||
|
||||
static inline void SetCtr(u8* counter, u64 ctr, u64 offset = 0) {
|
||||
const u64 swp = __bswap64(ctr);
|
||||
std::memcpy(&counter[0x0], &swp, 0x8);
|
||||
UpdateCtr(counter, offset);
|
||||
}
|
||||
|
||||
} // namespace sphaira::crypto
|
||||
|
||||
@@ -2,49 +2,132 @@
|
||||
|
||||
#include <switch.h>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
#include "ncm.hpp"
|
||||
#include "keys.hpp"
|
||||
|
||||
namespace sphaira::es {
|
||||
|
||||
enum { TicketModule = 507 };
|
||||
|
||||
enum : Result {
|
||||
// found ticket has missmatching rights_id from it's name.
|
||||
Result_InvalidTicketBadRightsId = MAKERESULT(TicketModule, 71),
|
||||
Result_InvalidTicketVersion = MAKERESULT(TicketModule, 72),
|
||||
Result_InvalidTicketKeyType = MAKERESULT(TicketModule, 73),
|
||||
Result_InvalidTicketKeyRevision = MAKERESULT(TicketModule, 74),
|
||||
enum TitleKeyType : u8 {
|
||||
TitleKeyType_Common = 0,
|
||||
TitleKeyType_Personalized = 1,
|
||||
};
|
||||
|
||||
enum TicketSigantureType {
|
||||
TicketSigantureType_RSA_4096_SHA1 = 0x010000,
|
||||
TicketSigantureType_RSA_2048_SHA1 = 0x010001,
|
||||
TicketSigantureType_ECDSA_SHA1 = 0x010002,
|
||||
TicketSigantureType_RSA_4096_SHA256 = 0x010003,
|
||||
TicketSigantureType_RSA_2048_SHA256 = 0x010004,
|
||||
TicketSigantureType_ECDSA_SHA256 = 0x010005,
|
||||
TicketSigantureType_HMAC_SHA1_160 = 0x010006,
|
||||
enum SigType : u32 {
|
||||
SigType_Rsa4096Sha1 = 65536,
|
||||
SigType_Rsa2048Sha1 = 65537,
|
||||
SigType_Ecc480Sha1 = 65538,
|
||||
SigType_Rsa4096Sha256 = 65539,
|
||||
SigType_Rsa2048Sha256 = 65540,
|
||||
SigType_Ecc480Sha256 = 65541,
|
||||
SigType_Hmac160Sha1 = 65542
|
||||
};
|
||||
|
||||
enum TicketTitleKeyType {
|
||||
TicketTitleKeyType_Common = 0,
|
||||
TicketTitleKeyType_Personalized = 1,
|
||||
enum PubKeyType : u32 {
|
||||
PubKeyType_Rsa4096 = 0,
|
||||
PubKeyType_Rsa2048 = 1,
|
||||
PubKeyType_Ecc480 = 2
|
||||
};
|
||||
|
||||
enum TicketPropertiesBitfield {
|
||||
TicketPropertiesBitfield_None = 0,
|
||||
// temporary ticket, removed on restart
|
||||
TicketPropertiesBitfield_Temporary = 1 << 4,
|
||||
struct SignatureBlockRsa4096 {
|
||||
SigType sig_type;
|
||||
u8 sign[0x200];
|
||||
u8 reserved_1[0x3C];
|
||||
};
|
||||
static_assert(sizeof(SignatureBlockRsa4096) == 0x240);
|
||||
|
||||
struct SignatureBlockRsa2048 {
|
||||
SigType sig_type;
|
||||
u8 sign[0x100];
|
||||
u8 reserved_1[0x3C];
|
||||
};
|
||||
static_assert(sizeof(SignatureBlockRsa2048) == 0x140);
|
||||
|
||||
struct SignatureBlockEcc480 {
|
||||
SigType sig_type;
|
||||
u8 sign[0x3C];
|
||||
u8 reserved_1[0x40];
|
||||
};
|
||||
static_assert(sizeof(SignatureBlockEcc480) == 0x80);
|
||||
|
||||
struct SignatureBlockHmac160 {
|
||||
SigType sig_type;
|
||||
u8 sign[0x14];
|
||||
u8 reserved_1[0x28];
|
||||
};
|
||||
static_assert(sizeof(SignatureBlockHmac160) == 0x40);
|
||||
|
||||
struct CertHeader {
|
||||
char issuer[0x40];
|
||||
PubKeyType pub_key_type;
|
||||
char subject[0x40]; /* ServerId, DeviceId */
|
||||
u32 date;
|
||||
};
|
||||
static_assert(sizeof(CertHeader) == 0x88);
|
||||
|
||||
struct PublicKeyBlockRsa4096 {
|
||||
u8 public_key[0x200];
|
||||
u32 public_exponent;
|
||||
u8 reserved_1[0x34];
|
||||
};
|
||||
static_assert(sizeof(PublicKeyBlockRsa4096) == 0x238);
|
||||
|
||||
struct PublicKeyBlockRsa2048 {
|
||||
u8 public_key[0x100];
|
||||
u32 public_exponent;
|
||||
u8 reserved_1[0x34];
|
||||
};
|
||||
static_assert(sizeof(PublicKeyBlockRsa2048) == 0x138);
|
||||
|
||||
struct PublicKeyBlockEcc480 {
|
||||
u8 public_key[0x3C];
|
||||
u8 reserved_1[0x3C];
|
||||
};
|
||||
static_assert(sizeof(PublicKeyBlockEcc480) == 0x78);
|
||||
|
||||
template<typename Sig, typename Pub>
|
||||
struct Cert {
|
||||
Sig signature_block;
|
||||
CertHeader cert_header;
|
||||
Pub public_key_block;
|
||||
};
|
||||
|
||||
using CertRsa4096PubRsa4096 = Cert<SignatureBlockRsa4096, PublicKeyBlockRsa4096>;
|
||||
using CertRsa4096PubRsa2048 = Cert<SignatureBlockRsa4096, PublicKeyBlockRsa2048>;
|
||||
using CertRsa4096PubEcc480 = Cert<SignatureBlockRsa4096, PublicKeyBlockEcc480>;
|
||||
|
||||
using CertRsa2048PubRsa4096 = Cert<SignatureBlockRsa2048, PublicKeyBlockRsa4096>;
|
||||
using CertRsa2048PubRsa2048 = Cert<SignatureBlockRsa2048, PublicKeyBlockRsa2048>;
|
||||
using CertRsa2048PubEcc480 = Cert<SignatureBlockRsa2048, PublicKeyBlockEcc480>;
|
||||
|
||||
using CertEcc480PubRsa4096 = Cert<SignatureBlockEcc480, PublicKeyBlockRsa4096>;
|
||||
using CertEcc480PubRsa2048 = Cert<SignatureBlockEcc480, PublicKeyBlockRsa2048>;
|
||||
using CertEcc480PubEcc480 = Cert<SignatureBlockEcc480, PublicKeyBlockEcc480>;
|
||||
|
||||
using CertHmac160PubRsa4096 = Cert<SignatureBlockHmac160, PublicKeyBlockRsa4096>;
|
||||
using CertHmac160PubRsa2048 = Cert<SignatureBlockHmac160, PublicKeyBlockRsa2048>;
|
||||
using CertHmac160PubEcc480 = Cert<SignatureBlockHmac160, PublicKeyBlockEcc480>;
|
||||
|
||||
static_assert(sizeof(CertRsa4096PubRsa4096) == 0x500);
|
||||
static_assert(sizeof(CertRsa4096PubRsa2048) == 0x400);
|
||||
static_assert(sizeof(CertRsa4096PubEcc480) == 0x340);
|
||||
static_assert(sizeof(CertRsa2048PubRsa4096) == 0x400);
|
||||
static_assert(sizeof(CertRsa2048PubRsa2048) == 0x300);
|
||||
static_assert(sizeof(CertRsa2048PubEcc480) == 0x240);
|
||||
static_assert(sizeof(CertEcc480PubRsa4096) == 0x340);
|
||||
static_assert(sizeof(CertEcc480PubRsa2048) == 0x240);
|
||||
static_assert(sizeof(CertEcc480PubEcc480) == 0x180);
|
||||
static_assert(sizeof(CertHmac160PubRsa4096) == 0x300);
|
||||
static_assert(sizeof(CertHmac160PubRsa2048) == 0x200);
|
||||
static_assert(sizeof(CertHmac160PubEcc480) == 0x140);
|
||||
|
||||
struct TicketData {
|
||||
u8 issuer[0x40];
|
||||
char issuer[0x40];
|
||||
u8 title_key_block[0x100];
|
||||
u8 ticket_version1;
|
||||
u8 format_version;
|
||||
u8 title_key_type;
|
||||
u16 ticket_version2;
|
||||
u8 license_type;
|
||||
u16 version;
|
||||
TitleKeyType license_type;
|
||||
u8 master_key_revision;
|
||||
u16 properties_bitfield;
|
||||
u8 _0x148[0x8];
|
||||
@@ -52,10 +135,28 @@ struct TicketData {
|
||||
u64 device_id;
|
||||
FsRightsId rights_id;
|
||||
u32 account_id;
|
||||
u8 _0x174[0xC];
|
||||
u8 _0x180[0x140];
|
||||
u32 sect_total_size;
|
||||
u32 sect_hdr_offset;
|
||||
u16 sect_hdr_count;
|
||||
u16 sect_hdr_entry_size;
|
||||
};
|
||||
static_assert(sizeof(TicketData) == 0x2C0);
|
||||
static_assert(sizeof(TicketData) == 0x180);
|
||||
|
||||
template<typename Sig>
|
||||
struct Ticket {
|
||||
Sig signature_block;
|
||||
TicketData data;
|
||||
};
|
||||
|
||||
using TicketRsa4096 = Ticket<SignatureBlockRsa4096>;
|
||||
using TicketRsa2048 = Ticket<SignatureBlockRsa2048>;
|
||||
using TicketEcc480 = Ticket<SignatureBlockEcc480>;
|
||||
using TicketHmac160 = Ticket<SignatureBlockHmac160>;
|
||||
|
||||
static_assert(sizeof(TicketRsa4096) == 0x3C0);
|
||||
static_assert(sizeof(TicketRsa2048) == 0x2C0);
|
||||
static_assert(sizeof(TicketEcc480) == 0x200);
|
||||
static_assert(sizeof(TicketHmac160) == 0x1C0);
|
||||
|
||||
struct EticketRsaDeviceKey {
|
||||
u8 ctr[AES_128_KEY_SIZE];
|
||||
@@ -90,12 +191,39 @@ Result GetCommonTicketAndCertificateSize(u64 *tik_size_out, u64 *cert_size_out,
|
||||
Result GetCommonTicketAndCertificateData(u64 *tik_size_out, u64 *cert_size_out, void* tik_buf, u64 tik_size, void* cert_buf, u64 cert_size, const FsRightsId* rightsId); // [4.0.0+]
|
||||
|
||||
// ticket functions.
|
||||
Result GetTicketDataOffset(std::span<const u8> ticket, u64& out);
|
||||
Result GetTicketDataOffset(std::span<const u8> ticket, u64& out, bool is_cert = false);
|
||||
Result GetTicketData(std::span<const u8> ticket, es::TicketData* out);
|
||||
Result SetTicketData(std::span<u8> ticket, const es::TicketData* in);
|
||||
|
||||
// gets the title key and performs RSA-2048-OAEP if needed.
|
||||
Result GetTitleKey(keys::KeyEntry& out, const TicketData& data, const keys::Keys& keys);
|
||||
Result DecryptTitleKey(keys::KeyEntry& out, u8 key_gen, const keys::Keys& keys);
|
||||
Result PatchTicket(std::span<u8> ticket, const keys::Keys& keys);
|
||||
Result EncryptTitleKey(keys::KeyEntry& out, u8 key_gen, const keys::Keys& keys);
|
||||
|
||||
Result ShouldPatchTicket(const TicketData& data, std::span<const u8> ticket, std::span<const u8> cert_chain, bool patch_personalised, bool& should_patch);
|
||||
Result ShouldPatchTicket(std::span<const u8> ticket, std::span<const u8> cert_chain, bool patch_personalised, bool& should_patch);
|
||||
// cert chain may be modified if the ticket is converted to common ticket.
|
||||
Result PatchTicket(std::vector<u8>& ticket, std::vector<u8>& cert_chain, u8 key_gen, const keys::Keys& keys, bool patch_personalised);
|
||||
|
||||
// fills out with the list of common / personalised rights ids.
|
||||
Result GetCommonTickets(std::vector<FsRightsId>& out);
|
||||
Result GetPersonalisedTickets(std::vector<FsRightsId>& out);
|
||||
|
||||
// checks if the rights id is found in common / personalised.
|
||||
Result IsRightsIdCommon(const FsRightsId& id, bool* out);
|
||||
Result IsRightsIdPersonalised(const FsRightsId& id, bool* out);
|
||||
|
||||
// helper for the above if the db has already been parsed.
|
||||
bool IsRightsIdValid(const FsRightsId& id);
|
||||
bool IsRightsIdFound(const FsRightsId& id, std::span<const FsRightsId> ids);
|
||||
|
||||
// wrapper around ipc.
|
||||
Result GetCommonTicketAndCertificate(const FsRightsId& rights_id, std::vector<u8>& tik_out, std::vector<u8>& cert_out);
|
||||
// fetches data from system es save.
|
||||
Result GetPersonalisedTicketData(u64 *size_out, void *tik_data, u64 tik_size, const FsRightsId* rightsId);
|
||||
Result GetPersonalisedTicketAndCertificate(const FsRightsId& rights_id, std::vector<u8>& tik_out, std::vector<u8>& cert_out);
|
||||
|
||||
// fills out with the decrypted title key.
|
||||
Result GetTitleKeyDecrypted(const FsRightsId& rights_id, u8 key_gen, const keys::Keys& keys, keys::KeyEntry& out);
|
||||
Result GetTitleKeyDecrypted(std::span<const u8> ticket, const FsRightsId& rights_id, u8 key_gen, const keys::Keys& keys, keys::KeyEntry& out);
|
||||
|
||||
} // namespace sphaira::es
|
||||
|
||||
@@ -46,19 +46,19 @@ struct Keys {
|
||||
}
|
||||
|
||||
auto GetNcaKeyArea(KeyEntry* out, u8 key, u8 index) const -> Result {
|
||||
R_UNLESS(HasNcaKeyArea(key, index), 0x1);
|
||||
R_UNLESS(HasNcaKeyArea(key, index), Result_KeyMissingNcaKeyArea);
|
||||
*out = key_area_key[index][FixKey(key)];
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
auto GetTitleKek(KeyEntry* out, u8 key) const -> Result {
|
||||
R_UNLESS(HasTitleKek(key), 0x1);
|
||||
R_UNLESS(HasTitleKek(key), Result_KeyMissingTitleKek);
|
||||
*out = titlekek[FixKey(key)];
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
auto GetMasterKey(KeyEntry* out, u8 key) const -> Result {
|
||||
R_UNLESS(HasMasterKey(key), 0x1);
|
||||
R_UNLESS(HasMasterKey(key), Result_KeyMissingMasterKey);
|
||||
*out = master_key[FixKey(key)];
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user