Compare commits
119 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 |
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,7 +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](switch-010editor-templates) (for ticket / cert structs)
|
||||
- [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"
|
||||
}
|
||||
@@ -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!": "ADVERTENCIA, ¡El registro de eventos está activado, Sphaira correrá más lento!",
|
||||
"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?": "¿Estás seguro que deseas cancelar?",
|
||||
"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 abrir un 'issue'.",
|
||||
"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ú",
|
||||
@@ -17,7 +17,7 @@
|
||||
"Music": "Música",
|
||||
"12 Hour Time": "Reloj de 12 horas",
|
||||
"Download Default Music": "Descargar música por defecto",
|
||||
"Failed to download default_music.bfstm, please try again": "Fallo al descargar archivo default_music.bfstm, intentar nuevamente",
|
||||
"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",
|
||||
@@ -29,7 +29,7 @@
|
||||
"Nxlink Upload": "NXlink subida",
|
||||
"Nxlink Finished": "NXlink finalizado",
|
||||
"Hdd": "Disco Duro",
|
||||
"Hdd write protect": "Disco duro solo lectura",
|
||||
"Hdd write protect": "Disco duro de solo lectura",
|
||||
|
||||
"Language": "Idioma",
|
||||
"Auto": "Automático",
|
||||
@@ -48,26 +48,26 @@
|
||||
"Vietnamese": "Vietnamese",
|
||||
"Ukrainian": "Українська",
|
||||
|
||||
"Misc": "Varios",
|
||||
"Misc Options": "Opciones varias",
|
||||
"Misc": "Misceláneas",
|
||||
"Misc Options": "Opciones misceláneas",
|
||||
"Games": "Juegos",
|
||||
"Game Options": "Opciones de Juegos",
|
||||
"Game Options": "Opciones de juegos",
|
||||
"Hide forwarders": "Ocultar forwarders",
|
||||
"Launch random game": "Ejecutar un juego aleatorio",
|
||||
"List meta records": "Listar registros meta",
|
||||
"List meta records": "Listar registros de metadatos",
|
||||
"Entries": "Entradas",
|
||||
"Failed to list application meta entries": "Error al listar las entradas meta del aplicativo",
|
||||
"No meta entries found...\n": "No se encontró entradas meta...\n",
|
||||
"Updating application record list": "Actualizando la lista de registros del aplicativo",
|
||||
"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 contenido a volcar",
|
||||
"Select content to dump": "Seleccione el contenido a volcar",
|
||||
"Dump All": "Volcar todo",
|
||||
"Dump Application": "Volcar aplicativo",
|
||||
"Dump Application": "Volcar aplicación",
|
||||
"Dump Patch": "Volcar parche",
|
||||
"Dump AddOnContent": "Volcar Contenido adicional",
|
||||
"Dump DataPatch": "Volcar Datos de 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",
|
||||
@@ -84,66 +84,66 @@
|
||||
"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 satisfactorio",
|
||||
"Dump failed!": "Error en el volcado",
|
||||
"Delete successfull!": "Borrado satisfactorio",
|
||||
"Delete failed!": "Error al borrar",
|
||||
"Success": "Proceso Exitoso",
|
||||
"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": "Error al descargar JSON",
|
||||
"Failed to download app!": "¡Error al descargar aplicativo!",
|
||||
"Failed to download app!": "¡Error al descargar aplicación!",
|
||||
|
||||
"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",
|
||||
"Connection Type: Ethernet": "Tipo de conexión: Ethernet",
|
||||
"Connection Type: None": "Tipo de conexión: ninguna",
|
||||
"Host:": "Host",
|
||||
"Port:": "Puerto",
|
||||
"Username:": "Nombre de Usuario",
|
||||
"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…",
|
||||
"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 instalación vía FTP!",
|
||||
"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, inicando la transferencia…",
|
||||
"Failed to init usb, press B to exit...": "Error al iniciar USB, presione B para salir…",
|
||||
"Waiting for connection...": "Esperando por conexión…",
|
||||
"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...": "Listado de archivos enviado, esprendo comando…",
|
||||
"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 rehabilitado",
|
||||
"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",
|
||||
"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 aplicativos 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!",
|
||||
"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",
|
||||
@@ -151,18 +151,18 @@
|
||||
"IRS (Infrared Joycon Camera)": "IRS (Cámara infraroja del JoyCon)",
|
||||
"IRS": "IRS",
|
||||
"Irs": "Irs",
|
||||
"Ambient Noise Level: ": "Nivel de Ruido Ambiente",
|
||||
"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,47 +184,47 @@
|
||||
"Load Default": "Cargar predeterminado",
|
||||
|
||||
"Web": "Web",
|
||||
"Select URL": "Seleccione el URL",
|
||||
"Enter custom URL": "Ingrese un URL personalizado",
|
||||
"Enter URL": "Escriba el URL",
|
||||
"Select URL": "Seleccione la URL",
|
||||
"Enter custom URL": "Ingrese una URL personalizada",
|
||||
"Enter URL": "Escriba la URL",
|
||||
|
||||
"Advanced": "Avanzado",
|
||||
"Advanced Options": "Opciones avanzadas",
|
||||
"Logging": "Bitácora",
|
||||
"Logging": "Registro",
|
||||
"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": "Tranferir con Overclock",
|
||||
"Boost CPU during transfer": "Tranferir con overclock",
|
||||
"Text scroll speed": "Velocidad de scroll",
|
||||
"Slow": "Lento",
|
||||
"Normal": "Normal",
|
||||
"Fast": "Rápido",
|
||||
"Set left-side menu": "Menú izquierdo",
|
||||
"Set right-side menu": "Menú Drecho",
|
||||
"Install options": "Opciones de Instalación",
|
||||
"Install Options": "Opciones de Instalación",
|
||||
"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": "Precaución de instalación",
|
||||
"Show install warning": "Mostrar precaución al instalar",
|
||||
"Install location": "Dispositivo de instalación",
|
||||
"System memory": "Memoria de sistema",
|
||||
"microSD card": "microSD",
|
||||
"Allow downgrade": "Permitir instalar versiones anteriores",
|
||||
"Skip if already installed": "Saltar si ya está instalado",
|
||||
"Ticket only": "Únicamente tiquetes",
|
||||
"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 Tiquete",
|
||||
"Skip NCA hash verify": "Saltar verificación de Hash NCA",
|
||||
"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",
|
||||
@@ -234,21 +234,21 @@
|
||||
|
||||
"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 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!": "Error al remover forwarder anterior, por favor remuévalo manualmente",
|
||||
|
||||
@@ -256,17 +256,17 @@
|
||||
"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",
|
||||
@@ -291,7 +291,7 @@
|
||||
"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?": "¿Seguro que desea extraerlo en la carpeta 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",
|
||||
@@ -317,9 +317,9 @@
|
||||
"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…",
|
||||
"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",
|
||||
@@ -334,21 +334,21 @@
|
||||
|
||||
"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",
|
||||
@@ -380,16 +380,16 @@
|
||||
"Off": "Apagado",
|
||||
|
||||
"Install": "Instalar",
|
||||
"Install Selected files?": "¡Instalar los archivos seleccionados?",
|
||||
"Install Selected files?": "¿Instalar los archivos seleccionados?",
|
||||
"Installing ": "Instalando ",
|
||||
"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",
|
||||
@@ -417,6 +417,6 @@
|
||||
"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!"
|
||||
}
|
||||
|
||||
@@ -210,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 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",
|
||||
@@ -228,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",
|
||||
@@ -236,7 +237,7 @@
|
||||
"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",
|
||||
@@ -332,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",
|
||||
|
||||
@@ -90,7 +90,11 @@
|
||||
"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",
|
||||
@@ -121,9 +125,9 @@
|
||||
"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?",
|
||||
|
||||
@@ -233,8 +237,9 @@
|
||||
"Enter custom URL": "Digitar URL",
|
||||
"Enter URL": "Digitar URL",
|
||||
|
||||
"Advanced": "Opções avançadas",
|
||||
"Advanced": "Avançado",
|
||||
"Advanced Options": "Opções avançadas",
|
||||
"Advanced options": "Opções avançadas",
|
||||
"Logging": "Registros de depuração",
|
||||
"Replace hbmenu on exit": "Substituir hbmenu ao sair",
|
||||
"Restore hbmenu?": "Restaurar hbmenu?",
|
||||
@@ -273,6 +278,7 @@
|
||||
"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",
|
||||
@@ -284,9 +290,9 @@
|
||||
"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 database": "Atualizando base de dados NCM",
|
||||
"Pushing application record": "Aplicando registro do software",
|
||||
@@ -309,7 +315,7 @@
|
||||
"updated: %s": "Atualizado: %s",
|
||||
"category: %s": "Categoria: %s",
|
||||
"extracted: %.2f MiB": "Tamanho: %.2f MiB",
|
||||
"app_dls: %s": "Nº de downloads: %s",
|
||||
"app_dls: %s": "№ de downloads: %s",
|
||||
"More by Author": "Mais deste autor",
|
||||
"Leave Feedback": "Deixar um feedback",
|
||||
|
||||
@@ -330,7 +336,7 @@
|
||||
"Pasting ": "Colando ",
|
||||
"Pasting": "Colando ",
|
||||
"Rename": "Renomear",
|
||||
"Set New File Name": "Defina o nome do novo arquivo",
|
||||
"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",
|
||||
@@ -351,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",
|
||||
@@ -366,7 +372,7 @@
|
||||
"Hash Options": "Calcular hash",
|
||||
"Hashing": "Calculando hash...",
|
||||
"Failed to hash file...": "Falha ao calcular hash do arquivo.",
|
||||
"Ignore read only": "Ignorar modo somente leitura",
|
||||
"Ignore read only": "Ignorar \"somente leitura\"",
|
||||
"Mount": "Montar",
|
||||
"Sd": "SD",
|
||||
"Image System memory": "Imagem (memória do console)",
|
||||
@@ -393,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",
|
||||
|
||||
@@ -80,7 +80,7 @@
|
||||
"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": "Снятие дампа",
|
||||
@@ -94,6 +94,13 @@
|
||||
"Saves": "Сохранения",
|
||||
"Save Options": "Опции сохранений",
|
||||
"Account": "Пользователь",
|
||||
"Data Type": "Тип данных",
|
||||
"System": "Система",
|
||||
"BCAT": "BCAT",
|
||||
"Device": "Устройство",
|
||||
"Temporary": "Временные",
|
||||
"Cache": "Кэш",
|
||||
"System BCAT": "BCAT (Система)",
|
||||
"Backup": "Сделать бэкап",
|
||||
"Auto backup": "Автоматический бэкап",
|
||||
"Auto backup on restore": "Бэкап при восстановлении",
|
||||
@@ -103,6 +110,8 @@
|
||||
"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!": "Восстановление успешно!",
|
||||
@@ -136,6 +145,23 @@
|
||||
"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...": "Подключено, ожидание списка файлов...",
|
||||
@@ -155,8 +181,8 @@
|
||||
"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.": "Установка отключена...\nПожалуйста, включите установку в опциях установки.",
|
||||
"No GameCard inserted": "Картридж не вставлен",
|
||||
@@ -245,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": "Снизить версию системы",
|
||||
@@ -295,6 +322,7 @@
|
||||
"Show Hidden": "Показать скрытые",
|
||||
"Folders First": "Папки в начале",
|
||||
"Hidden Last": "Скрытые в конце",
|
||||
"Split": "Разделить",
|
||||
"Cut": "Вырезать",
|
||||
"Copy": "Копировать",
|
||||
"Copying ": "Копирование ",
|
||||
|
||||
@@ -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写保护",
|
||||
|
||||
@@ -100,6 +101,15 @@
|
||||
"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": "在线主题选项",
|
||||
@@ -142,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": "游戏卡",
|
||||
"GC": "GC",
|
||||
"System memory %.1f GB": "主机内存 %.1f GB",
|
||||
"microSD card %.1f GB": "SD卡 %.1f GB",
|
||||
"microSD card %.1f GB": "microSD卡 %.1f GB",
|
||||
"Exit": "退出",
|
||||
"Install disabled...\nPlease enable installing via the install options.": "安装已禁用...\n请通过安装选项启用安装。",
|
||||
"No GameCard inserted": "未插入游戏卡",
|
||||
@@ -252,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机!",
|
||||
@@ -292,7 +318,7 @@
|
||||
"More by Author": "作者更多作品",
|
||||
"Leave Feedback": "留言反馈",
|
||||
|
||||
"FileBrowser": "文件浏览",
|
||||
"FileBrowser": "文件浏览器",
|
||||
"Files": "文件",
|
||||
"%zd files": "%zd 个文件",
|
||||
"%zd dirs": "%zd 个文件夹",
|
||||
@@ -363,7 +389,7 @@
|
||||
"Emulators": "模拟器",
|
||||
"Tools": "工具",
|
||||
"Themes": "主题",
|
||||
"Legacy": "可更新",
|
||||
"Legacy": "旧版",
|
||||
"Sort": "排序",
|
||||
"Size": "按大小",
|
||||
"Size (Star)": "按大小(星标优先)",
|
||||
@@ -442,5 +468,27 @@
|
||||
"Loading": "加载中",
|
||||
"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.13.3)
|
||||
# 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,10 @@ 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/mtp_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
|
||||
|
||||
@@ -62,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
|
||||
@@ -80,36 +115,58 @@ add_executable(sphaira
|
||||
source/web.cpp
|
||||
source/hasher.cpp
|
||||
source/i18n.cpp
|
||||
source/ftpsrv_helper.cpp
|
||||
source/haze_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
|
||||
@@ -131,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
|
||||
@@ -147,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 8782f6b
|
||||
SOURCE_SUBDIR NONE
|
||||
)
|
||||
|
||||
FetchContent_Declare(libhaze
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libhaze.git
|
||||
GIT_TAG af69c0a
|
||||
)
|
||||
|
||||
FetchContent_Declare(libpulsar
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/switch-libpulsar.git
|
||||
GIT_TAG de656e4
|
||||
GIT_TAG ac7bc97
|
||||
)
|
||||
|
||||
FetchContent_Declare(nanovg
|
||||
@@ -183,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
|
||||
@@ -197,14 +243,9 @@ FetchContent_Declare(zstd
|
||||
SOURCE_SUBDIR build/cmake
|
||||
)
|
||||
|
||||
FetchContent_Declare(libusbhsfs
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libusbhsfs.git
|
||||
GIT_TAG d0a973e
|
||||
)
|
||||
|
||||
FetchContent_Declare(libnxtc
|
||||
GIT_REPOSITORY https://github.com/ITotalJustice/libnxtc.git
|
||||
GIT_TAG 0d369b8
|
||||
GIT_TAG 88ce3d8
|
||||
)
|
||||
|
||||
FetchContent_Declare(nvjpg
|
||||
@@ -212,31 +253,291 @@ FetchContent_Declare(nvjpg
|
||||
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)
|
||||
@@ -244,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)
|
||||
@@ -252,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_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>
|
||||
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/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
|
||||
@@ -316,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)
|
||||
@@ -334,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
|
||||
@@ -347,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
|
||||
@@ -363,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;
|
||||
@@ -256,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{};
|
||||
|
||||
@@ -282,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};
|
||||
@@ -294,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};
|
||||
@@ -303,32 +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;
|
||||
|
||||
@@ -511,7 +511,22 @@ enum class SphairaResult : Result {
|
||||
FsNewPathEmpty,
|
||||
FsLoadingCancelled,
|
||||
FsBrokenRoot,
|
||||
|
||||
FsUnknownStdioError,
|
||||
FsStdioFailedToSeek,
|
||||
FsStdioFailedToRead,
|
||||
FsStdioFailedToWrite,
|
||||
FsStdioFailedToOpenFile,
|
||||
FsStdioFailedToCreate,
|
||||
FsStdioFailedToTruncate,
|
||||
FsStdioFailedToFlush,
|
||||
FsStdioFailedToCreateDirectory,
|
||||
FsStdioFailedToDeleteFile,
|
||||
FsStdioFailedToDeleteDirectory,
|
||||
FsStdioFailedToOpenDirectory,
|
||||
FsStdioFailedToRename,
|
||||
FsStdioFailedToStat,
|
||||
|
||||
FsReadOnly,
|
||||
FsNotActive,
|
||||
FsFailedStdioStat,
|
||||
@@ -537,6 +552,9 @@ enum class SphairaResult : Result {
|
||||
ZipOpenNewFileInZip,
|
||||
ZipWriteInFileInZip,
|
||||
|
||||
MmzBadLocalHeaderSig,
|
||||
MmzBadLocalHeaderRead,
|
||||
|
||||
FileBrowserFailedUpload,
|
||||
FileBrowserDirNotDaybreak,
|
||||
|
||||
@@ -574,8 +592,10 @@ enum class SphairaResult : Result {
|
||||
|
||||
UsbDsBadDeviceSpeed,
|
||||
|
||||
NcaBadMagic,
|
||||
NspBadMagic,
|
||||
XciBadMagic,
|
||||
XciSecurePartitionNotFound,
|
||||
|
||||
EsBadTitleKeyType,
|
||||
EsPersonalisedTicketDeviceIdMissmatch,
|
||||
@@ -594,7 +614,10 @@ enum class SphairaResult : Result {
|
||||
UsbBadMagic,
|
||||
UsbBadVersion,
|
||||
UsbBadCount,
|
||||
UsbBadBufferAlign,
|
||||
UsbBadTransferSize,
|
||||
UsbEmptyTransferSize,
|
||||
UsbOverflowTransferSize,
|
||||
UsbBadTotalSize,
|
||||
|
||||
UsbUploadBadMagic,
|
||||
@@ -641,6 +664,17 @@ enum class SphairaResult : Result {
|
||||
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)
|
||||
@@ -661,6 +695,19 @@ enum : Result {
|
||||
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),
|
||||
@@ -681,6 +728,8 @@ enum : Result {
|
||||
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),
|
||||
@@ -708,8 +757,12 @@ enum : Result {
|
||||
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),
|
||||
@@ -719,19 +772,24 @@ enum : Result {
|
||||
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(UsbBadTotalSize),
|
||||
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),
|
||||
@@ -753,6 +811,16 @@ enum : Result {
|
||||
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
|
||||
@@ -783,21 +851,83 @@ enum : Result {
|
||||
#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; }};
|
||||
template<typename Function>
|
||||
struct ScopeGuard {
|
||||
ScopeGuard(Function&& function) : m_function(std::forward<Function>(function)) {
|
||||
|
||||
}
|
||||
~ScopeGuard() {
|
||||
m_function();
|
||||
}
|
||||
|
||||
ScopeGuard(const ScopeGuard&) = delete;
|
||||
void operator=(const ScopeGuard&) = delete;
|
||||
|
||||
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; } }};
|
||||
|
||||
// threading helpers.
|
||||
#define PRIO_PREEMPTIVE 0x3B
|
||||
|
||||
// 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)
|
||||
|
||||
// mutex helpers.
|
||||
#define SCOPED_MUTEX(mutex) \
|
||||
mutexLock(mutex); \
|
||||
ON_SCOPE_EXIT(mutexUnlock(mutex))
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,33 +2,36 @@
|
||||
|
||||
#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 {
|
||||
@@ -38,7 +41,6 @@ struct DumpEntry {
|
||||
|
||||
struct DumpLocation {
|
||||
DumpEntry entry{};
|
||||
location::Entries network{};
|
||||
location::StdioEntries stdio{};
|
||||
};
|
||||
|
||||
@@ -48,17 +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)>;
|
||||
|
||||
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, OnLocation on_loc);
|
||||
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(std::shared_ptr<BaseSource> source, const DumpLocation& location, const std::vector<fs::FsPath>& paths, OnExit on_exit);
|
||||
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(std::shared_ptr<BaseSource> source, const std::vector<fs::FsPath>& paths, OnExit on_exit = [](Result){}, u32 location_flags = DumpLocationFlag_All);
|
||||
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
|
||||
|
||||
@@ -4,12 +4,26 @@
|
||||
#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;
|
||||
|
||||
@@ -44,10 +58,6 @@ struct FsPath {
|
||||
return s;
|
||||
}
|
||||
|
||||
constexpr auto starts_with(std::string_view str) const -> bool {
|
||||
return !strncasecmp(s, str.data(), str.length());
|
||||
}
|
||||
|
||||
constexpr auto empty() const {
|
||||
return s[0] == '\0';
|
||||
}
|
||||
@@ -64,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; }
|
||||
@@ -129,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) {
|
||||
@@ -155,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) {
|
||||
@@ -173,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;
|
||||
@@ -198,7 +251,6 @@ struct File {
|
||||
fs::Fs* m_fs{};
|
||||
FsFile m_native{};
|
||||
std::FILE* m_stdio{};
|
||||
s64 m_stdio_off{};
|
||||
u32 m_mode{};
|
||||
};
|
||||
|
||||
@@ -218,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
|
||||
@@ -272,6 +318,11 @@ 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);
|
||||
|
||||
// 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,10 +343,8 @@ struct Fs {
|
||||
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);
|
||||
@@ -315,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;
|
||||
@@ -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;
|
||||
};
|
||||
@@ -466,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
|
||||
@@ -492,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
|
||||
|
||||
@@ -533,4 +575,10 @@ struct FsNativeSave final : FsNative {
|
||||
}
|
||||
};
|
||||
|
||||
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(const char* path)>;
|
||||
using OnInstallWrite = std::function<bool(const void* buf, size_t size)>;
|
||||
using OnInstallClose = std::function<void()>;
|
||||
|
||||
void InitInstallMode(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);
|
||||
|
||||
|
||||
@@ -2,16 +2,17 @@
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace sphaira::haze {
|
||||
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(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();
|
||||
|
||||
} // namespace sphaira::haze
|
||||
} // 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>;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,11 +35,12 @@ 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.
|
||||
@@ -50,7 +54,7 @@ using UnzipAllFilter = std::function<bool(const fs::FsPath& name, fs::FsPath& pa
|
||||
|
||||
// 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, Mode mode = Mode::SingleThreadedIfSmaller);
|
||||
Result TransferUnzipAll(ui::ProgressBox* pbox, const fs::FsPath& zip_out, fs::Fs* fs, const fs::FsPath& base_path, UnzipAllFilter filter = nullptr, Mode mode = Mode::SingleThreadedIfSmaller);
|
||||
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
|
||||
@@ -17,6 +17,7 @@ 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{};
|
||||
|
||||
@@ -7,17 +7,43 @@
|
||||
#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 {
|
||||
@@ -25,6 +51,7 @@ enum class FsType {
|
||||
ImageNand,
|
||||
ImageSd,
|
||||
Stdio,
|
||||
Custom,
|
||||
};
|
||||
|
||||
enum class SelectedType {
|
||||
@@ -63,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
|
||||
@@ -79,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;
|
||||
@@ -97,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;
|
||||
}
|
||||
|
||||
@@ -157,13 +210,14 @@ using FsDirCollections = std::vector<FsDirCollection>;
|
||||
|
||||
void SignalChange();
|
||||
|
||||
struct Menu;
|
||||
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;
|
||||
@@ -188,7 +242,9 @@ struct FsView final : Widget {
|
||||
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();
|
||||
|
||||
@@ -197,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);
|
||||
@@ -244,7 +300,7 @@ private:
|
||||
}
|
||||
|
||||
auto IsSd() const -> bool {
|
||||
return m_fs_entry.type == FsType::Sd;
|
||||
return m_fs_entry.IsSd();
|
||||
}
|
||||
|
||||
void Sort();
|
||||
@@ -259,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();
|
||||
@@ -270,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{};
|
||||
@@ -300,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 {
|
||||
@@ -335,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;
|
||||
}
|
||||
@@ -386,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};
|
||||
@@ -412,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
|
||||
|
||||
@@ -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,
|
||||
@@ -28,7 +34,7 @@ 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"; };
|
||||
@@ -43,6 +49,14 @@ 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();
|
||||
@@ -51,15 +65,21 @@ private:
|
||||
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{};
|
||||
@@ -67,7 +87,7 @@ private:
|
||||
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
|
||||
@@ -35,10 +35,11 @@ private:
|
||||
std::stop_token m_token{};
|
||||
std::vector<u8> m_buffer{};
|
||||
CondVar m_can_read{};
|
||||
CondVar m_can_write{};
|
||||
|
||||
public:
|
||||
Mutex m_mutex{};
|
||||
bool m_active{};
|
||||
std::atomic_bool m_active{};
|
||||
};
|
||||
|
||||
struct Menu : MenuBase {
|
||||
@@ -55,7 +56,7 @@ protected:
|
||||
void OnInstallClose();
|
||||
|
||||
private:
|
||||
std::shared_ptr<Stream> m_source{};
|
||||
std::unique_ptr<Stream> m_source{};
|
||||
Thread m_thread{};
|
||||
Mutex m_mutex{};
|
||||
State m_state{State::None};
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include "ui/menus/grid_menu_base.hpp"
|
||||
#include "ui/list.hpp"
|
||||
#include "title_info.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "option.hpp"
|
||||
#include "dumper.hpp"
|
||||
@@ -11,25 +12,19 @@
|
||||
|
||||
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;
|
||||
@@ -40,37 +35,6 @@ struct Entry final : FsSaveDataInfo {
|
||||
}
|
||||
};
|
||||
|
||||
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,
|
||||
};
|
||||
@@ -124,12 +88,17 @@ 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, bool is_auto) const -> fs::FsPath;
|
||||
Result RestoreSaveInternal(ProgressBox* pbox, const Entry& e, const fs::FsPath& path) const;
|
||||
Result BackupSaveInternal(ProgressBox* pbox, const dump::DumpLocation& location, const Entry& e, bool compressed, bool is_auto = false) const;
|
||||
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";
|
||||
@@ -145,9 +114,6 @@ private:
|
||||
s64 m_account_index{};
|
||||
u8 m_data_type{FsSaveDataType_Account};
|
||||
|
||||
ThreadData m_thread_data{};
|
||||
Thread m_thread{};
|
||||
|
||||
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};
|
||||
|
||||
@@ -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*);
|
||||
|
||||
@@ -42,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,16 +40,15 @@ struct ProgressBox final : Widget {
|
||||
auto ShouldExit() -> bool;
|
||||
auto ShouldExitResult() -> Result;
|
||||
|
||||
void AddCancelEvent(UEvent* event);
|
||||
void RemoveCancelEvent(const UEvent* event);
|
||||
|
||||
// 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 GetCpuId() const {
|
||||
return m_cpuid;
|
||||
}
|
||||
|
||||
auto OnDownloadProgressCallback() {
|
||||
return [this](s64 dltotal, s64 dlnow, s64 ultotal, s64 ulnow){
|
||||
if (this->ShouldExit()) {
|
||||
@@ -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);
|
||||
@@ -83,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};
|
||||
@@ -90,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,7 +2,7 @@
|
||||
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <new>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::usb {
|
||||
@@ -39,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,
|
||||
@@ -90,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
|
||||
@@ -13,24 +13,38 @@ struct Usb {
|
||||
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,10 +2,8 @@
|
||||
|
||||
#include "base.hpp"
|
||||
|
||||
// TODO: remove these when libnx pr is merged.
|
||||
enum { UsbDeviceSpeed_None = 0x0 };
|
||||
enum { UsbDeviceSpeed_Low = 0x1 };
|
||||
Result usbDsGetSpeed(UsbDeviceSpeed *out);
|
||||
auto GetUsbDsStateStr(UsbState state) -> const char*;
|
||||
auto GetUsbDsSpeedStr(UsbDeviceSpeed speed) -> const char*;
|
||||
|
||||
namespace sphaira::usb {
|
||||
|
||||
|
||||
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
|
||||
|
||||
@@ -201,6 +201,29 @@ 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);
|
||||
Result PatchTicket(std::vector<u8>& ticket, std::span<const u8> cert_chain, u8 key_gen, const keys::Keys& keys, bool patch_personalised);
|
||||
// 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
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
#include "fs.hpp"
|
||||
#include "keys.hpp"
|
||||
#include "ncm.hpp"
|
||||
#include "yati/source/base.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
namespace sphaira::nca {
|
||||
|
||||
@@ -15,7 +18,7 @@ namespace sphaira::nca {
|
||||
#define NCA_SECTOR_SIZE 0x200
|
||||
#define NCA_XTS_SECTION_SIZE 0xC00
|
||||
#define NCA_SECTION_TOTAL 0x4
|
||||
#define NCA_MEDIA_REAL(x)((x * 0x200))
|
||||
#define NCA_MEDIA_REAL(x)((u64(x) * 0x200))
|
||||
|
||||
#define NCA_PROGRAM_LOGO_OFFSET 0x8000
|
||||
#define NCA_META_CNMT_OFFSET 0xC20
|
||||
@@ -94,6 +97,22 @@ struct SectionTableEntry {
|
||||
u32 media_end_offset; // divided by 0x200.
|
||||
u8 _0x8[0x4]; // unknown.
|
||||
u8 _0xC[0x4]; // unknown.
|
||||
|
||||
auto IsValid() const -> bool {
|
||||
return media_start_offset && media_end_offset;
|
||||
}
|
||||
|
||||
auto GetOffset() const -> u64 {
|
||||
return NCA_MEDIA_REAL(media_start_offset);
|
||||
}
|
||||
|
||||
auto GetOffsetEnd() const -> u64 {
|
||||
return NCA_MEDIA_REAL(media_end_offset);
|
||||
}
|
||||
|
||||
auto GetSize() const -> u64 {
|
||||
return GetOffsetEnd() - GetOffset();
|
||||
}
|
||||
};
|
||||
|
||||
struct LayerRegion {
|
||||
@@ -139,6 +158,55 @@ static_assert(sizeof(HierarchicalSha256Data) == 0xF8);
|
||||
static_assert(sizeof(IntegrityMetaInfo) == 0xF8);
|
||||
static_assert(sizeof(HierarchicalSha256Data) == sizeof(IntegrityMetaInfo));
|
||||
|
||||
struct BucketTreeHeader {
|
||||
u32 magic; // BKTR
|
||||
u32 version;
|
||||
u32 count;
|
||||
u8 _0xC[0x4];
|
||||
};
|
||||
|
||||
struct PatchInfo {
|
||||
u64 indirect_offset;
|
||||
u64 indirect_size;
|
||||
BucketTreeHeader indirect_header;
|
||||
u64 aes_ctr_offset;
|
||||
u64 aes_ctr_size;
|
||||
BucketTreeHeader aes_ctr_header;
|
||||
};
|
||||
static_assert(sizeof(PatchInfo) == 0x40);
|
||||
|
||||
struct CompressionInfo {
|
||||
u64 table_offset;
|
||||
u64 table_size;
|
||||
BucketTreeHeader table_header;
|
||||
u8 _0x20[0x8];
|
||||
};
|
||||
static_assert(sizeof(CompressionInfo) == 0x28);
|
||||
|
||||
struct BktrEntry {
|
||||
u8 _0x0[0x4];
|
||||
u32 count;
|
||||
u64 size;
|
||||
u64 offsets[0x3FF0 / sizeof(u64)];
|
||||
};
|
||||
static_assert(sizeof(BktrEntry) == 0x4000);
|
||||
|
||||
struct NX_PACKED BktrRelocationEntry {
|
||||
u64 patched_addr;
|
||||
u64 source_addr;
|
||||
u32 flag;
|
||||
};
|
||||
static_assert(sizeof(BktrRelocationEntry) == 0x14);
|
||||
|
||||
struct BktrRelocationBucket {
|
||||
u8 _0x0[0x4];
|
||||
u32 count;
|
||||
u64 end_offset;
|
||||
BktrRelocationEntry entries[0x3FF0 / sizeof(BktrRelocationEntry)];
|
||||
u8 _[0x3FF0 % sizeof(BktrRelocationEntry)];
|
||||
};
|
||||
static_assert(sizeof(BktrRelocationBucket) == 0x4000);
|
||||
|
||||
struct FsHeader {
|
||||
u16 version; // always 2.
|
||||
u8 fs_type; // see FileSystemType.
|
||||
@@ -152,12 +220,16 @@ struct FsHeader {
|
||||
IntegrityMetaInfo integrity_meta_info; // used for romfs
|
||||
} hash_data;
|
||||
|
||||
u8 patch_info[0x40];
|
||||
PatchInfo patch_info;
|
||||
u64 section_ctr;
|
||||
u8 spares_info[0x30];
|
||||
u8 compression_info[0x28];
|
||||
CompressionInfo compression_info;
|
||||
u8 meta_data_hash_data_info[0x30];
|
||||
u8 reserved[0x30];
|
||||
|
||||
auto IsValid() const -> bool {
|
||||
return version == 2;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(FsHeader) == 0x200);
|
||||
static_assert(sizeof(FsHeader::hash_data) == 0xF8);
|
||||
@@ -181,7 +253,15 @@ struct Header {
|
||||
u64 size;
|
||||
u64 program_id;
|
||||
u32 context_id;
|
||||
u32 sdk_version;
|
||||
union {
|
||||
u32 sdk_version;
|
||||
struct {
|
||||
u8 sdk_revision;
|
||||
u8 sdk_micro;
|
||||
u8 sdk_minor;
|
||||
u8 sdk_major;
|
||||
};
|
||||
};
|
||||
u8 key_gen; // see KeyGeneration.
|
||||
u8 sig_key_gen;
|
||||
u8 _0x222[0xE]; // empty.
|
||||
@@ -195,6 +275,10 @@ struct Header {
|
||||
|
||||
FsHeader fs_header[NCA_SECTION_TOTAL];
|
||||
|
||||
auto IsValid() const -> bool {
|
||||
return magic == NCA3_MAGIC;
|
||||
}
|
||||
|
||||
auto GetKeyGeneration() const -> u8 {
|
||||
if (old_key_gen < key_gen) {
|
||||
return key_gen;
|
||||
@@ -212,9 +296,25 @@ struct Header {
|
||||
key_gen = key_generation;
|
||||
}
|
||||
}
|
||||
|
||||
auto GetSectionCount() const -> u8 {
|
||||
u8 count = 0;
|
||||
for (u32 i = 0; i < NCA_SECTION_TOTAL; i++) {
|
||||
if (!fs_header[i].IsValid() || !fs_table[i].IsValid()) {
|
||||
break;
|
||||
}
|
||||
count++;
|
||||
}
|
||||
return count;
|
||||
}
|
||||
};
|
||||
static_assert(sizeof(Header) == 0xC00);
|
||||
|
||||
auto GetContentTypeStr(u8 content_type) -> const char*;
|
||||
auto GetDistributionTypeStr(u8 distribution_type) -> const char*;
|
||||
|
||||
Result DecryptHeader(const void* in, const keys::Keys& keys, Header& out);
|
||||
|
||||
Result DecryptKeak(const keys::Keys& keys, Header& header);
|
||||
Result EncryptKeak(const keys::Keys& keys, Header& header, u8 key_generation);
|
||||
Result VerifyFixedKey(const Header& header);
|
||||
@@ -225,4 +325,52 @@ Result ParseControl(const fs::FsPath& path, u64 program_id, void* nacp_out = nul
|
||||
|
||||
auto GetKeyGenStr(u8 key_gen) -> const char*;
|
||||
|
||||
// finds and decrypts the title key, also decrypts header key area if needed.
|
||||
Result GetDecryptedTitleKey(Header& header, const keys::Keys& keys, keys::KeyEntry& out);
|
||||
// same as above but also checks the path for ticket.
|
||||
Result GetDecryptedTitleKey(fs::Fs* fs, const fs::FsPath& path, Header& header, const keys::Keys& keys, keys::KeyEntry& out);
|
||||
|
||||
// helpers.
|
||||
struct DecyptedData : yati::source::Base {
|
||||
DecyptedData(u64 align, const std::shared_ptr<yati::source::Base>& source);
|
||||
Result Read(void *_buf, s64 _off, s64 _size, u64* _bytes_read) override;
|
||||
virtual Result SetCtr(u64 ctr) = 0;
|
||||
|
||||
private:
|
||||
virtual Result Decrypt(void* buf, s64 off, s64 size) = 0;
|
||||
|
||||
private:
|
||||
std::shared_ptr<yati::source::Base> m_source;
|
||||
const u64 m_align;
|
||||
};
|
||||
|
||||
// todo: add support for xts sections.
|
||||
struct DecyptedDataCtr final : DecyptedData {
|
||||
DecyptedDataCtr(const void* key, u64 ctr, const std::shared_ptr<yati::source::Base>& source);
|
||||
Result SetCtr(u64 ctr) override;
|
||||
|
||||
private:
|
||||
Result Decrypt(void* buf, s64 off, s64 size) override;
|
||||
|
||||
private:
|
||||
Aes128CtrContext m_ctx{};
|
||||
u8 m_ctr[AES_BLOCK_SIZE]{};
|
||||
};
|
||||
|
||||
struct NcaReader final : yati::source::Base {
|
||||
NcaReader(const nca::Header& decrypted_header, const void* key, u64 size, const std::shared_ptr<yati::source::Base>& source);
|
||||
Result Read(void *_buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
Result ReadEncrypted(void *_buf, s64 off, s64 size, u64* bytes_read);
|
||||
|
||||
private:
|
||||
Result ReadInternal(void *_buf, s64 off, s64 size, u64* bytes_read, bool decrypt);
|
||||
|
||||
private:
|
||||
const nca::Header m_header;
|
||||
const u64 m_capacity;
|
||||
std::shared_ptr<yati::source::Base> m_source;
|
||||
std::unique_ptr<DecyptedData> m_decryptor{};
|
||||
u8 m_key[0x10]{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::nca
|
||||
|
||||
@@ -1,6 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
#include "fs.hpp"
|
||||
#include "yati/source/base.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
|
||||
namespace sphaira::ncm {
|
||||
|
||||
@@ -31,10 +35,19 @@ union ExtendedHeader {
|
||||
NcmDataPatchMetaExtendedHeader data_patch;
|
||||
};
|
||||
|
||||
struct ContentMeta {
|
||||
NcmContentMetaHeader header;
|
||||
ExtendedHeader extened;
|
||||
};
|
||||
|
||||
auto GetMetaTypeStr(u8 meta_type) -> const char*;
|
||||
auto GetContentTypeStr(u8 content_type) -> const char*;
|
||||
auto GetStorageIdStr(u8 storage_id) -> const char*;
|
||||
auto GetMetaTypeShortStr(u8 meta_type) -> const char*;
|
||||
|
||||
auto GetReadableMetaTypeStr(u8 meta_type) -> const char*;
|
||||
auto GetReadableStorageIdStr(u8 storage_id) -> const char*;
|
||||
|
||||
auto GetAppId(u8 meta_type, u64 id) -> u64;
|
||||
auto GetAppId(const NcmContentMetaKey& key) -> u64;
|
||||
auto GetAppId(const PackagedContentMeta& meta) -> u64;
|
||||
@@ -44,4 +57,42 @@ auto GetContentIdFromStr(const char* str) -> NcmContentId;
|
||||
Result Delete(NcmContentStorage* cs, const NcmContentId *content_id);
|
||||
Result Register(NcmContentStorage* cs, const NcmContentId *content_id, const NcmPlaceHolderId *placeholder_id);
|
||||
|
||||
// fills out with the content header, which includes the normal and extended header.
|
||||
Result GetContentMeta(NcmContentMetaDatabase *db, const NcmContentMetaKey *key, ContentMeta& out);
|
||||
|
||||
// fills out will a list of all content infos tied to the key.
|
||||
Result GetContentInfos(NcmContentMetaDatabase *db, const NcmContentMetaKey *key, std::vector<NcmContentInfo>& out);
|
||||
// same as above but accepts the ncm header rather than fetching it.
|
||||
Result GetContentInfos(NcmContentMetaDatabase *db, const NcmContentMetaKey *key, const NcmContentMetaHeader& header, std::vector<NcmContentInfo>& out);
|
||||
|
||||
// removes key from ncm, including ncas and setting the db.
|
||||
Result DeleteKey(NcmContentStorage* cs, NcmContentMetaDatabase *db, const NcmContentMetaKey *key);
|
||||
|
||||
// sets the required system version.
|
||||
Result SetRequiredSystemVersion(NcmContentMetaDatabase *db, const NcmContentMetaKey *key, u32 version);
|
||||
|
||||
// returns true if type is application or update.
|
||||
static constexpr inline bool HasRequiredSystemVersion(u8 meta_type) {
|
||||
return meta_type == NcmContentMetaType_Application || meta_type == NcmContentMetaType_Patch;
|
||||
}
|
||||
|
||||
static constexpr inline bool HasRequiredSystemVersion(const NcmContentMetaKey *key) {
|
||||
return HasRequiredSystemVersion(key->type);
|
||||
}
|
||||
|
||||
// fills program id and out path of the control nca.
|
||||
Result GetFsPathFromContentId(NcmContentStorage* cs, const NcmContentMetaKey& key, const NcmContentId& id, u64* out_program_id, fs::FsPath* out_path);
|
||||
|
||||
// helper for reading nca from ncm.
|
||||
struct NcmSource final : yati::source::Base {
|
||||
NcmSource(NcmContentStorage* cs, const NcmContentId* id);
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
Result GetSize(s64* size);
|
||||
|
||||
private:
|
||||
NcmContentStorage m_cs;
|
||||
NcmContentId m_id;
|
||||
s64 m_size{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ncm
|
||||
|
||||
@@ -1,6 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include "yati/source/base.hpp"
|
||||
#include "utils/lru.hpp"
|
||||
#include "defines.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <zstd.h>
|
||||
|
||||
namespace sphaira::ncz {
|
||||
|
||||
@@ -8,7 +15,11 @@ namespace sphaira::ncz {
|
||||
// todo: byteswap this
|
||||
#define NCZ_BLOCK_MAGIC std::byteswap(0x4E435A424C4F434BUL)
|
||||
|
||||
#define NCZ_SECTION_OFFSET (0x4000 + sizeof(ncz::Header))
|
||||
#define NCZ_BLOCK_VERSION (2)
|
||||
#define NCZ_BLOCK_TYPE (1)
|
||||
|
||||
#define NCZ_NORMAL_SIZE (0x4000)
|
||||
#define NCZ_SECTION_OFFSET (NCZ_NORMAL_SIZE + sizeof(ncz::Header))
|
||||
|
||||
struct Header {
|
||||
u64 magic; // NCZ_SECTION_MAGIC
|
||||
@@ -23,11 +34,21 @@ struct BlockHeader {
|
||||
u8 block_size_exponent;
|
||||
u32 total_blocks;
|
||||
u64 decompressed_size;
|
||||
|
||||
Result IsValid() const {
|
||||
R_UNLESS(magic == NCZ_BLOCK_MAGIC, 9);
|
||||
R_UNLESS(version == NCZ_BLOCK_VERSION, Result_YatiInvalidNczBlockVersion);
|
||||
R_UNLESS(type == NCZ_BLOCK_TYPE, Result_YatiInvalidNczBlockType);
|
||||
R_UNLESS(total_blocks, Result_YatiInvalidNczBlockTotal);
|
||||
R_UNLESS(block_size_exponent >= 14 && block_size_exponent <= 32, Result_YatiInvalidNczBlockSizeExponent);
|
||||
R_SUCCEED();
|
||||
}
|
||||
};
|
||||
|
||||
struct Block {
|
||||
u32 size;
|
||||
};
|
||||
using Blocks = std::vector<Block>;
|
||||
|
||||
struct BlockInfo {
|
||||
u64 offset; // compressed offset.
|
||||
@@ -50,5 +71,39 @@ struct Section {
|
||||
return off < offset + size && off >= offset;
|
||||
}
|
||||
};
|
||||
using Sections = std::vector<Section>;
|
||||
|
||||
struct NczBlockReader final : yati::source::Base {
|
||||
explicit NczBlockReader(const Header& header, const Sections& sections, const BlockHeader& block_header, const Blocks& blocks, u64 offset, const std::shared_ptr<yati::source::Base>& source);
|
||||
Result Read(void *_buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
|
||||
private:
|
||||
struct LruData {
|
||||
s64 offset{};
|
||||
std::vector<u8> data{};
|
||||
|
||||
auto InRange(u64 off) const -> bool {
|
||||
return off < offset + data.size() && off >= offset;
|
||||
}
|
||||
};
|
||||
|
||||
private:
|
||||
Result ReadInternal(void *_buf, s64 off, s64 size, u64* bytes_read, bool decrypt);
|
||||
|
||||
private:
|
||||
const Header m_header;
|
||||
const Sections m_sections;
|
||||
const BlockHeader m_block_header;
|
||||
const Blocks m_blocks;
|
||||
const u64 m_block_offset;
|
||||
std::shared_ptr<yati::source::Base> m_source;
|
||||
|
||||
u32 m_block_size{};
|
||||
std::vector<BlockInfo> m_block_infos{};
|
||||
|
||||
// lru cache of blocks
|
||||
std::vector<LruData> m_lru_data{};
|
||||
utils::Lru<LruData> m_lru{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::ncz
|
||||
|
||||
@@ -1,22 +1,44 @@
|
||||
#pragma once
|
||||
|
||||
#include <switch.h>
|
||||
#include "ncm.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <span>
|
||||
#include <vector>
|
||||
|
||||
namespace sphaira::ns {
|
||||
|
||||
enum ApplicationRecordType {
|
||||
// installed
|
||||
ApplicationRecordType_Running = 0x0,
|
||||
ApplicationRecordType_Installed = 0x3,
|
||||
ApplicationRecordType_Downloading = 0x4,
|
||||
// application is gamecard, but gamecard isn't insterted
|
||||
ApplicationRecordType_GamecardMissing = 0x5,
|
||||
// archived
|
||||
ApplicationRecordType_Downloaded = 0x6,
|
||||
ApplicationRecordType_Updated = 0xA,
|
||||
ApplicationRecordType_Archived = 0xB,
|
||||
};
|
||||
|
||||
Result PushApplicationRecord(Service* srv, u64 tid, const ncm::ContentStorageRecord* records, u32 count);
|
||||
Result ListApplicationRecordContentMeta(Service* srv, u64 offset, u64 tid, ncm::ContentStorageRecord* out_records, u32 count, s32* entries_read);
|
||||
Result DeleteApplicationRecord(Service* srv, u64 tid);
|
||||
Result InvalidateApplicationControlCache(Service* srv, u64 tid);
|
||||
Result Initialize();
|
||||
void Exit();
|
||||
|
||||
Result PushApplicationRecord(u64 tid, const ncm::ContentStorageRecord* records, u32 count);
|
||||
Result ListApplicationRecordContentMeta(u64 offset, u64 tid, ncm::ContentStorageRecord* out_records, u32 count, s32* entries_read);
|
||||
Result DeleteApplicationRecord(u64 tid);
|
||||
Result InvalidateApplicationControlCache(u64 tid);
|
||||
|
||||
// helpers
|
||||
|
||||
// fills out with the number or records available
|
||||
Result GetApplicationRecords(u64 id, std::vector<ncm::ContentStorageRecord>& out);
|
||||
|
||||
// sets the lowest launch version based on the current record list.
|
||||
Result SetLowestLaunchVersion(u64 id);
|
||||
// same as above, but uses the provided record list.
|
||||
Result SetLowestLaunchVersion(u64 id, std::span<const ncm::ContentStorageRecord> records);
|
||||
|
||||
static inline bool IsNsControlFetchSlow() {
|
||||
return hosversionAtLeast(20,0,0);
|
||||
}
|
||||
|
||||
} // namespace sphaira::ns
|
||||
|
||||
63
sphaira/include/yati/nx/nxdumptool/core/nxdt_includes.h
Normal file
63
sphaira/include/yati/nx/nxdumptool/core/nxdt_includes.h
Normal file
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* nxdt_includes.h
|
||||
*
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __NXDT_INCLUDES_H__
|
||||
#define __NXDT_INCLUDES_H__
|
||||
|
||||
/* C headers. */
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <stddef.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <malloc.h>
|
||||
#include <errno.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <time.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/param.h>
|
||||
#include <fcntl.h>
|
||||
#include <dirent.h>
|
||||
#include <assert.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#ifndef __cplusplus
|
||||
#include <stdatomic.h>
|
||||
#else
|
||||
#include <atomic>
|
||||
#define _Atomic(X) std::atomic< X >
|
||||
#endif
|
||||
|
||||
/* libnx header. */
|
||||
#include <switch.h>
|
||||
|
||||
/* Global defines. */
|
||||
#include "../defines.h"
|
||||
|
||||
/* File/socket based logger. */
|
||||
#include "nxdt_log.h"
|
||||
|
||||
#endif /* __NXDT_INCLUDES_H__ */
|
||||
160
sphaira/include/yati/nx/nxdumptool/core/nxdt_log.h
Normal file
160
sphaira/include/yati/nx/nxdumptool/core/nxdt_log.h
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* nxdt_log.h
|
||||
*
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __NXDT_LOG_H__
|
||||
#define __NXDT_LOG_H__
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/// Used to control logfile verbosity.
|
||||
#define LOG_LEVEL_DEBUG 0
|
||||
#define LOG_LEVEL_INFO 1
|
||||
#define LOG_LEVEL_WARNING 2
|
||||
#define LOG_LEVEL_ERROR 3
|
||||
#define LOG_LEVEL_NONE 4
|
||||
|
||||
/// Defines the log level used throughout the application.
|
||||
/// Log messages with a log value lower than this one won't be compiled into the binary.
|
||||
/// If a value lower than LOG_LEVEL_DEBUG or equal to/greater than LOG_LEVEL_NONE is used, logfile output will be entirely disabled.
|
||||
#define LOG_LEVEL LOG_LEVEL_NONE /* TODO: change before release (warning?). */
|
||||
|
||||
#if (LOG_LEVEL >= LOG_LEVEL_DEBUG) && (LOG_LEVEL < LOG_LEVEL_NONE)
|
||||
|
||||
/// Helper macros.
|
||||
|
||||
#define LOG_MSG_GENERIC(level, fmt, ...) logWriteFormattedStringToLogFile(level, __FILE__, __LINE__, __PRETTY_FUNCTION__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_MSG_BUF_GENERIC(dst, dst_size, level, fmt, ...) logWriteFormattedStringToBuffer(dst, dst_size, level, __FILE__, __LINE__, __PRETTY_FUNCTION__, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DATA_GENERIC(data, data_size, level, fmt, ...) logWriteBinaryDataToLogFile(data, data_size, level, __FILE__, __LINE__, __PRETTY_FUNCTION__, fmt, ##__VA_ARGS__)
|
||||
|
||||
#if LOG_LEVEL == LOG_LEVEL_DEBUG
|
||||
#define LOG_MSG_DEBUG(fmt, ...) LOG_MSG_GENERIC(LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
|
||||
#define LOG_MSG_BUF_DEBUG(dst, dst_size, fmt, ...) LOG_MSG_BUF_GENERIC(dst, dst_size, LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DATA_DEBUG(data, data_size, fmt, ...) LOG_DATA_GENERIC(data, data_size, LOG_LEVEL_DEBUG, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define LOG_MSG_DEBUG(fmt, ...) do {} while(0)
|
||||
#define LOG_MSG_BUF_DEBUG(dst, dst_size, fmt, ...) do {} while(0)
|
||||
#define LOG_DATA_DEBUG(data, data_size, fmt, ...) do {} while(0)
|
||||
#endif /* LOG_LEVEL == LOG_LEVEL_DEBUG */
|
||||
|
||||
#if LOG_LEVEL <= LOG_LEVEL_INFO
|
||||
#define LOG_MSG_INFO(fmt, ...) LOG_MSG_GENERIC(LOG_LEVEL_INFO, fmt, ##__VA_ARGS__)
|
||||
#define LOG_MSG_BUF_INFO(dst, dst_size, fmt, ...) LOG_MSG_BUF_GENERIC(dst, dst_size, LOG_LEVEL_INFO, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DATA_INFO(data, data_size, fmt, ...) LOG_DATA_GENERIC(data, data_size, LOG_LEVEL_INFO, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define LOG_MSG_INFO(fmt, ...) do {} while(0)
|
||||
#define LOG_MSG_BUF_INFO(dst, dst_size, fmt, ...) do {} while(0)
|
||||
#define LOG_DATA_INFO(data, data_size, fmt, ...) do {} while(0)
|
||||
#endif /* LOG_LEVEL <= LOG_LEVEL_INFO */
|
||||
|
||||
#if LOG_LEVEL <= LOG_LEVEL_WARNING
|
||||
#define LOG_MSG_WARNING(fmt, ...) LOG_MSG_GENERIC(LOG_LEVEL_WARNING, fmt, ##__VA_ARGS__)
|
||||
#define LOG_MSG_BUF_WARNING(dst, dst_size, fmt, ...) LOG_MSG_BUF_GENERIC(dst, dst_size, LOG_LEVEL_WARNING, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DATA_WARNING(data, data_size, fmt, ...) LOG_DATA_GENERIC(data, data_size, LOG_LEVEL_WARNING, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define LOG_MSG_WARNING(fmt, ...) do {} while(0)
|
||||
#define LOG_MSG_BUF_WARNING(dst, dst_size, fmt, ...) do {} while(0)
|
||||
#define LOG_DATA_WARNING(data, data_size, fmt, ...) do {} while(0)
|
||||
#endif /* LOG_LEVEL <= LOG_LEVEL_WARNING */
|
||||
|
||||
#if LOG_LEVEL <= LOG_LEVEL_ERROR
|
||||
#define LOG_MSG_ERROR(fmt, ...) LOG_MSG_GENERIC(LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)
|
||||
#define LOG_MSG_BUF_ERROR(dst, dst_size, fmt, ...) LOG_MSG_BUF_GENERIC(dst, dst_size, LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)
|
||||
#define LOG_DATA_ERROR(data, data_size, fmt, ...) LOG_DATA_GENERIC(data, data_size, LOG_LEVEL_ERROR, fmt, ##__VA_ARGS__)
|
||||
#else
|
||||
#define LOG_MSG_ERROR(fmt, ...) do {} while(0)
|
||||
#define LOG_MSG_BUF_ERROR(dst, dst_size, fmt, ...) do {} while(0)
|
||||
#define LOG_DATA_ERROR(data, data_size, fmt, ...) do {} while(0)
|
||||
#endif /* LOG_LEVEL <= LOG_LEVEL_ERROR */
|
||||
|
||||
/// Writes the provided string to the logfile.
|
||||
/// If the logfile hasn't been created and/or opened, this function takes care of it.
|
||||
void logWriteStringToLogFile(const char *src);
|
||||
|
||||
/// Writes a formatted log string to the logfile.
|
||||
/// If the logfile hasn't been created and/or opened, this function takes care of it.
|
||||
__attribute__((format(printf, 5, 6))) void logWriteFormattedStringToLogFile(u8 level, const char *file_name, int line, const char *func_name, const char *fmt, ...);
|
||||
|
||||
/// Writes a formatted log string to the provided buffer.
|
||||
/// If the buffer isn't big enough to hold both its current contents and the new formatted string, it will be resized.
|
||||
__attribute__((format(printf, 7, 8))) void logWriteFormattedStringToBuffer(char **dst, size_t *dst_size, u8 level, const char *file_name, int line, const char *func_name, const char *fmt, ...);
|
||||
|
||||
/// Writes a formatted log string + a hex string representation of the provided binary data to the logfile.
|
||||
/// If the logfile hasn't been created and/or opened, this function takes care of it.
|
||||
__attribute__((format(printf, 7, 8))) void logWriteBinaryDataToLogFile(const void *data, size_t data_size, u8 level, const char *file_name, int line, const char *func_name, const char *fmt, ...);
|
||||
|
||||
/// Forces a flush operation on the logfile.
|
||||
void logFlushLogFile(void);
|
||||
|
||||
/// Write any pending data to the logfile, flushes it and then closes it.
|
||||
void logCloseLogFile(void);
|
||||
|
||||
/// Returns a pointer to a dynamically allocated buffer that holds the last error message string, or NULL if there's none.
|
||||
/// The allocated buffer must be freed by the caller using free().
|
||||
char *logGetLastMessage(void);
|
||||
|
||||
/// (Un)locks the log mutex. Can be used to block other threads and prevent them from writing data to the logfile.
|
||||
/// Use with caution.
|
||||
void logControlMutex(bool lock);
|
||||
|
||||
#else /* (LOG_LEVEL >= LOG_LEVEL_DEBUG) && (LOG_LEVEL < LOG_LEVEL_NONE) */
|
||||
|
||||
/// Helper macros.
|
||||
|
||||
#define LOG_MSG_GENERIC(level, fmt, ...) do {} while(0)
|
||||
#define LOG_MSG_BUF_GENERIC(dst, dst_size, level, fmt, ...) do {} while(0)
|
||||
#define LOG_DATA_GENERIC(data, data_size, level, fmt, ...) do {} while(0)
|
||||
|
||||
#define LOG_MSG_DEBUG(fmt, ...) do {} while(0)
|
||||
#define LOG_MSG_BUF_DEBUG(dst, dst_size, fmt, ...) do {} while(0)
|
||||
#define LOG_DATA_DEBUG(data, data_size, fmt, ...) do {} while(0)
|
||||
|
||||
#define LOG_MSG_INFO(fmt, ...) do {} while(0)
|
||||
#define LOG_MSG_BUF_INFO(dst, dst_size, fmt, ...) do {} while(0)
|
||||
#define LOG_DATA_INFO(data, data_size, fmt, ...) do {} while(0)
|
||||
|
||||
#define LOG_MSG_WARNING(fmt, ...) do {} while(0)
|
||||
#define LOG_MSG_BUF_WARNING(dst, dst_size, fmt, ...) do {} while(0)
|
||||
#define LOG_DATA_WARNING(data, data_size, fmt, ...) do {} while(0)
|
||||
|
||||
#define LOG_MSG_ERROR(fmt, ...) do {} while(0)
|
||||
#define LOG_MSG_BUF_ERROR(dst, dst_size, fmt, ...) do {} while(0)
|
||||
#define LOG_DATA_ERROR(data, data_size, fmt, ...) do {} while(0)
|
||||
|
||||
#define logWriteStringToLogFile(...) do {} while(0)
|
||||
#define logWriteFormattedStringToLogFile(...) do {} while(0)
|
||||
#define logWriteFormattedStringToBuffer(...) do {} while(0)
|
||||
#define logWriteBinaryDataToLogFile(...) do {} while(0)
|
||||
#define logFlushLogFile(...) do {} while(0)
|
||||
#define logCloseLogFile(...) do {} while(0)
|
||||
#define logGetLastMessage(...) NULL
|
||||
#define logControlMutex(...) do {} while(0)
|
||||
|
||||
#endif /* (LOG_LEVEL >= LOG_LEVEL_DEBUG) && (LOG_LEVEL < LOG_LEVEL_NONE) */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __NXDT_LOG_H__ */
|
||||
560
sphaira/include/yati/nx/nxdumptool/core/save.h
Normal file
560
sphaira/include/yati/nx/nxdumptool/core/save.h
Normal file
@@ -0,0 +1,560 @@
|
||||
/*
|
||||
* save.h
|
||||
*
|
||||
* Copyright (c) 2019-2020, shchmue.
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __SAVE_H__
|
||||
#define __SAVE_H__
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define IVFC_MAX_LEVEL 6
|
||||
|
||||
#define SAVE_HEADER_SIZE 0x4000
|
||||
#define SAVE_FAT_ENTRY_SIZE 8
|
||||
#define SAVE_FS_LIST_MAX_NAME_LENGTH 0x40
|
||||
#define SAVE_FS_LIST_ENTRY_SIZE 0x60
|
||||
|
||||
#define MAGIC_DISF 0x46534944
|
||||
#define MAGIC_DPFS 0x53465044
|
||||
#define MAGIC_JNGL 0x4C474E4A
|
||||
#define MAGIC_SAVE 0x45564153
|
||||
#define MAGIC_RMAP 0x50414D52
|
||||
#define MAGIC_IVFC 0x43465649
|
||||
|
||||
#define ACTION_VERIFY (1 << 2)
|
||||
|
||||
typedef enum {
|
||||
VALIDITY_UNCHECKED = 0,
|
||||
VALIDITY_INVALID,
|
||||
VALIDITY_VALID
|
||||
} validity_t;
|
||||
|
||||
typedef struct save_ctx_t save_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
u32 magic; /* "DISF". */
|
||||
u32 version;
|
||||
u8 hash[0x20];
|
||||
u64 file_map_entry_offset;
|
||||
u64 file_map_entry_size;
|
||||
u64 meta_map_entry_offset;
|
||||
u64 meta_map_entry_size;
|
||||
u64 file_map_data_offset;
|
||||
u64 file_map_data_size;
|
||||
u64 duplex_l1_offset_a;
|
||||
u64 duplex_l1_offset_b;
|
||||
u64 duplex_l1_size;
|
||||
u64 duplex_data_offset_a;
|
||||
u64 duplex_data_offset_b;
|
||||
u64 duplex_data_size;
|
||||
u64 journal_data_offset;
|
||||
u64 journal_data_size_a;
|
||||
u64 journal_data_size_b;
|
||||
u64 journal_size;
|
||||
u64 duplex_master_offset_a;
|
||||
u64 duplex_master_offset_b;
|
||||
u64 duplex_master_size;
|
||||
u64 ivfc_master_hash_offset_a;
|
||||
u64 ivfc_master_hash_offset_b;
|
||||
u64 ivfc_master_hash_size;
|
||||
u64 journal_map_table_offset;
|
||||
u64 journal_map_table_size;
|
||||
u64 journal_physical_bitmap_offset;
|
||||
u64 journal_physical_bitmap_size;
|
||||
u64 journal_virtual_bitmap_offset;
|
||||
u64 journal_virtual_bitmap_size;
|
||||
u64 journal_free_bitmap_offset;
|
||||
u64 journal_free_bitmap_size;
|
||||
u64 ivfc_l1_offset;
|
||||
u64 ivfc_l1_size;
|
||||
u64 ivfc_l2_offset;
|
||||
u64 ivfc_l2_size;
|
||||
u64 ivfc_l3_offset;
|
||||
u64 ivfc_l3_size;
|
||||
u64 fat_offset;
|
||||
u64 fat_size;
|
||||
u64 duplex_index;
|
||||
u64 fat_ivfc_master_hash_a;
|
||||
u64 fat_ivfc_master_hash_b;
|
||||
u64 fat_ivfc_l1_offset;
|
||||
u64 fat_ivfc_l1_size;
|
||||
u64 fat_ivfc_l2_offset;
|
||||
u64 fat_ivfc_l2_size;
|
||||
u8 _0x190[0x70];
|
||||
} fs_layout_t;
|
||||
|
||||
NXDT_ASSERT(fs_layout_t, 0x200);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u64 offset;
|
||||
u64 length;
|
||||
u32 block_size_power;
|
||||
} duplex_info_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
NXDT_ASSERT(duplex_info_t, 0x14);
|
||||
|
||||
typedef struct {
|
||||
u32 magic; /* "DPFS". */
|
||||
u32 version;
|
||||
duplex_info_t layers[3];
|
||||
} duplex_header_t;
|
||||
|
||||
NXDT_ASSERT(duplex_header_t, 0x44);
|
||||
|
||||
typedef struct {
|
||||
u32 version;
|
||||
u32 main_data_block_count;
|
||||
u32 journal_block_count;
|
||||
u32 _0x0C;
|
||||
} journal_map_header_t;
|
||||
|
||||
NXDT_ASSERT(journal_map_header_t, 0x10);
|
||||
|
||||
typedef struct {
|
||||
u32 magic; /* "JNGL". */
|
||||
u32 version;
|
||||
u64 total_size;
|
||||
u64 journal_size;
|
||||
u64 block_size;
|
||||
} journal_header_t;
|
||||
|
||||
NXDT_ASSERT(journal_header_t, 0x20);
|
||||
|
||||
typedef struct {
|
||||
u32 magic; /* "SAVE". */
|
||||
u32 version;
|
||||
u64 block_count;
|
||||
u64 block_size;
|
||||
} save_fs_header_t;
|
||||
|
||||
NXDT_ASSERT(save_fs_header_t, 0x18);
|
||||
|
||||
typedef struct {
|
||||
u64 block_size;
|
||||
u64 allocation_table_offset;
|
||||
u32 allocation_table_block_count;
|
||||
u32 _0x14;
|
||||
u64 data_offset;
|
||||
u32 data_block_count;
|
||||
u32 _0x24;
|
||||
u32 directory_table_block;
|
||||
u32 file_table_block;
|
||||
} fat_header_t;
|
||||
|
||||
NXDT_ASSERT(fat_header_t, 0x30);
|
||||
|
||||
typedef struct {
|
||||
u32 magic; /* "RMAP". */
|
||||
u32 version;
|
||||
u32 map_entry_count;
|
||||
u32 map_segment_count;
|
||||
u32 segment_bits;
|
||||
u8 _0x14[0x2C];
|
||||
} remap_header_t;
|
||||
|
||||
NXDT_ASSERT(remap_header_t, 0x40);
|
||||
|
||||
typedef struct remap_segment_ctx_t remap_segment_ctx_t;
|
||||
typedef struct remap_entry_ctx_t remap_entry_ctx_t;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct remap_entry_ctx_t {
|
||||
u64 virtual_offset;
|
||||
u64 physical_offset;
|
||||
u64 size;
|
||||
u32 alignment;
|
||||
u32 _0x1C;
|
||||
u64 virtual_offset_end;
|
||||
u64 physical_offset_end;
|
||||
remap_segment_ctx_t *segment;
|
||||
remap_entry_ctx_t *next;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
struct remap_segment_ctx_t{
|
||||
u64 offset;
|
||||
u64 length;
|
||||
remap_entry_ctx_t **entries;
|
||||
u64 entry_count;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
u8 *data;
|
||||
u8 *bitmap;
|
||||
} duplex_bitmap_t;
|
||||
|
||||
typedef struct {
|
||||
u32 block_size;
|
||||
u8 *bitmap_storage;
|
||||
u8 *data_a;
|
||||
u8 *data_b;
|
||||
duplex_bitmap_t bitmap;
|
||||
u64 _length;
|
||||
} duplex_storage_ctx_t;
|
||||
|
||||
enum base_storage_type {
|
||||
STORAGE_BYTES = 0,
|
||||
STORAGE_DUPLEX = 1,
|
||||
STORAGE_REMAP = 2,
|
||||
STORAGE_JOURNAL = 3
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
remap_header_t *header;
|
||||
remap_entry_ctx_t *map_entries;
|
||||
remap_segment_ctx_t *segments;
|
||||
enum base_storage_type type;
|
||||
u64 base_storage_offset;
|
||||
duplex_storage_ctx_t *duplex;
|
||||
FILE *file;
|
||||
} remap_storage_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
u64 title_id;
|
||||
u8 user_id[0x10];
|
||||
u64 save_id;
|
||||
u8 save_data_type;
|
||||
u8 _0x21[0x1F];
|
||||
u64 save_owner_id;
|
||||
u64 timestamp;
|
||||
u64 _0x50;
|
||||
u64 data_size;
|
||||
u64 journal_size;
|
||||
u64 commit_id;
|
||||
} extra_data_t;
|
||||
|
||||
NXDT_ASSERT(extra_data_t, 0x70);
|
||||
|
||||
typedef struct {
|
||||
u64 logical_offset;
|
||||
u64 hash_data_size;
|
||||
u32 block_size;
|
||||
u32 reserved;
|
||||
} ivfc_level_hdr_t;
|
||||
|
||||
NXDT_ASSERT(ivfc_level_hdr_t, 0x18);
|
||||
|
||||
typedef struct {
|
||||
u32 magic;
|
||||
u32 id;
|
||||
u32 master_hash_size;
|
||||
u32 num_levels;
|
||||
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
|
||||
u8 salt_source[0x20];
|
||||
} ivfc_save_hdr_t;
|
||||
|
||||
NXDT_ASSERT(ivfc_save_hdr_t, 0xC0);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u8 cmac[0x10];
|
||||
u8 _0x10[0xF0];
|
||||
fs_layout_t layout;
|
||||
duplex_header_t duplex_header;
|
||||
ivfc_save_hdr_t data_ivfc_header;
|
||||
u32 _0x404;
|
||||
journal_header_t journal_header;
|
||||
journal_map_header_t map_header;
|
||||
u8 _0x438[0x1D0];
|
||||
save_fs_header_t save_header;
|
||||
fat_header_t fat_header;
|
||||
remap_header_t main_remap_header, meta_remap_header;
|
||||
u64 _0x6D0;
|
||||
extra_data_t extra_data;
|
||||
u8 _0x748[0x390];
|
||||
ivfc_save_hdr_t fat_ivfc_header;
|
||||
u8 _0xB98[0x3468];
|
||||
} save_header_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
NXDT_ASSERT(save_header_t, 0x4000);
|
||||
|
||||
typedef struct {
|
||||
duplex_storage_ctx_t layers[2];
|
||||
duplex_storage_ctx_t data_layer;
|
||||
u64 _length;
|
||||
} hierarchical_duplex_storage_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
u8 *data_a;
|
||||
u8 *data_b;
|
||||
duplex_info_t info;
|
||||
} duplex_fs_layer_info_t;
|
||||
|
||||
typedef struct {
|
||||
u8 *map_storage;
|
||||
u8 *physical_block_bitmap;
|
||||
u8 *virtual_block_bitmap;
|
||||
u8 *free_block_bitmap;
|
||||
} journal_map_params_t;
|
||||
|
||||
typedef struct {
|
||||
u32 physical_index;
|
||||
u32 virtual_index;
|
||||
} journal_map_entry_t;
|
||||
|
||||
NXDT_ASSERT(journal_map_entry_t, 0x8);
|
||||
|
||||
typedef struct {
|
||||
journal_map_header_t *header;
|
||||
journal_map_entry_t *entries;
|
||||
u8 *map_storage;
|
||||
} journal_map_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
journal_map_ctx_t map;
|
||||
journal_header_t *header;
|
||||
u32 block_size;
|
||||
u64 journal_data_offset;
|
||||
u64 _length;
|
||||
FILE *file;
|
||||
} journal_storage_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
u64 data_offset;
|
||||
u64 data_size;
|
||||
u64 hash_offset;
|
||||
u32 hash_block_size;
|
||||
validity_t hash_validity;
|
||||
enum base_storage_type type;
|
||||
save_ctx_t *save_ctx;
|
||||
} ivfc_level_save_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
ivfc_level_save_ctx_t *data;
|
||||
u32 block_size;
|
||||
u8 salt[0x20];
|
||||
} integrity_verification_info_ctx_t;
|
||||
|
||||
typedef struct integrity_verification_storage_ctx_t integrity_verification_storage_ctx_t;
|
||||
|
||||
struct integrity_verification_storage_ctx_t {
|
||||
ivfc_level_save_ctx_t *hash_storage;
|
||||
ivfc_level_save_ctx_t *base_storage;
|
||||
validity_t *block_validities;
|
||||
u8 salt[0x20];
|
||||
u32 sector_size;
|
||||
u32 sector_count;
|
||||
u64 _length;
|
||||
integrity_verification_storage_ctx_t *next_level;
|
||||
};
|
||||
|
||||
typedef struct {
|
||||
ivfc_level_save_ctx_t levels[5];
|
||||
ivfc_level_save_ctx_t *data_level;
|
||||
validity_t **level_validities;
|
||||
u64 _length;
|
||||
integrity_verification_storage_ctx_t integrity_storages[4];
|
||||
} hierarchical_integrity_verification_storage_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
u32 prev;
|
||||
u32 next;
|
||||
} allocation_table_entry_t;
|
||||
|
||||
typedef struct {
|
||||
u32 free_list_entry_index;
|
||||
void *base_storage;
|
||||
fat_header_t *header;
|
||||
} allocation_table_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
hierarchical_integrity_verification_storage_ctx_t *base_storage;
|
||||
u32 block_size;
|
||||
u32 initial_block;
|
||||
allocation_table_ctx_t *fat;
|
||||
u64 _length;
|
||||
} allocation_table_storage_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
allocation_table_ctx_t *fat;
|
||||
u32 virtual_block;
|
||||
u32 physical_block;
|
||||
u32 current_segment_size;
|
||||
u32 next_block;
|
||||
u32 prev_block;
|
||||
} allocation_table_iterator_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
|
||||
u32 parent;
|
||||
} save_entry_key_t;
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u32 start_block;
|
||||
u64 length;
|
||||
u32 _0xC[2];
|
||||
} save_file_info_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
NXDT_ASSERT(save_file_info_t, 0x14);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u32 next_directory;
|
||||
u32 next_file;
|
||||
u32 _0x8[3];
|
||||
} save_find_position_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
NXDT_ASSERT(save_find_position_t, 0x14);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u32 next_sibling;
|
||||
union { /* Save table entry type. Size = 0x14. */
|
||||
save_file_info_t save_file_info;
|
||||
save_find_position_t save_find_position;
|
||||
};
|
||||
} save_table_entry_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
NXDT_ASSERT(save_table_entry_t, 0x18);
|
||||
|
||||
#pragma pack(push, 1)
|
||||
typedef struct {
|
||||
u32 parent;
|
||||
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
|
||||
save_table_entry_t value;
|
||||
u32 next;
|
||||
} save_fs_list_entry_t;
|
||||
#pragma pack(pop)
|
||||
|
||||
NXDT_ASSERT(save_fs_list_entry_t, 0x60);
|
||||
|
||||
typedef struct {
|
||||
u32 free_list_head_index;
|
||||
u32 used_list_head_index;
|
||||
allocation_table_storage_ctx_t storage;
|
||||
u32 capacity;
|
||||
} save_filesystem_list_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
save_filesystem_list_ctx_t file_table;
|
||||
save_filesystem_list_ctx_t directory_table;
|
||||
} hierarchical_save_file_table_ctx_t;
|
||||
|
||||
typedef struct {
|
||||
hierarchical_integrity_verification_storage_ctx_t *base_storage;
|
||||
allocation_table_ctx_t allocation_table;
|
||||
save_fs_header_t *header;
|
||||
hierarchical_save_file_table_ctx_t file_table;
|
||||
} save_filesystem_ctx_t;
|
||||
|
||||
struct save_ctx_t {
|
||||
save_header_t header;
|
||||
FILE *file;
|
||||
struct {
|
||||
FILE *file;
|
||||
u32 action;
|
||||
} tool_ctx;
|
||||
validity_t header_cmac_validity;
|
||||
validity_t header_hash_validity;
|
||||
u8 *data_ivfc_master;
|
||||
u8 *fat_ivfc_master;
|
||||
remap_storage_ctx_t data_remap_storage;
|
||||
remap_storage_ctx_t meta_remap_storage;
|
||||
duplex_fs_layer_info_t duplex_layers[3];
|
||||
hierarchical_duplex_storage_ctx_t duplex_storage;
|
||||
journal_storage_ctx_t journal_storage;
|
||||
journal_map_params_t journal_map_info;
|
||||
hierarchical_integrity_verification_storage_ctx_t core_data_ivfc_storage;
|
||||
hierarchical_integrity_verification_storage_ctx_t fat_ivfc_storage;
|
||||
u8 *fat_storage;
|
||||
save_filesystem_ctx_t save_filesystem_core;
|
||||
u8 save_mac_key[0x10];
|
||||
};
|
||||
|
||||
static inline u32 allocation_table_entry_index_to_block(u32 entry_index)
|
||||
{
|
||||
return (entry_index - 1);
|
||||
}
|
||||
|
||||
static inline u32 allocation_table_block_to_entry_index(u32 block_index)
|
||||
{
|
||||
return (block_index + 1);
|
||||
}
|
||||
|
||||
static inline int allocation_table_is_list_end(allocation_table_entry_t *entry)
|
||||
{
|
||||
return ((entry->next & 0x7FFFFFFF) == 0);
|
||||
}
|
||||
|
||||
static inline int allocation_table_is_list_start(allocation_table_entry_t *entry)
|
||||
{
|
||||
return (entry->prev == 0x80000000);
|
||||
}
|
||||
|
||||
static inline int allocation_table_get_next(allocation_table_entry_t *entry)
|
||||
{
|
||||
return (entry->next & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
static inline int allocation_table_get_prev(allocation_table_entry_t *entry)
|
||||
{
|
||||
return (entry->prev & 0x7FFFFFFF);
|
||||
}
|
||||
|
||||
static inline allocation_table_entry_t *save_allocation_table_read_entry(allocation_table_ctx_t *ctx, u32 entry_index)
|
||||
{
|
||||
return ((allocation_table_entry_t*)((u8*)ctx->base_storage + (entry_index * SAVE_FAT_ENTRY_SIZE)));
|
||||
}
|
||||
|
||||
static inline u32 save_allocation_table_get_free_list_entry_index(allocation_table_ctx_t *ctx)
|
||||
{
|
||||
return allocation_table_get_next(save_allocation_table_read_entry(ctx, ctx->free_list_entry_index));
|
||||
}
|
||||
|
||||
static inline u32 save_allocation_table_get_free_list_block_index(allocation_table_ctx_t *ctx)
|
||||
{
|
||||
return allocation_table_entry_index_to_block(save_allocation_table_get_free_list_entry_index(ctx));
|
||||
}
|
||||
|
||||
bool save_process(save_ctx_t *ctx);
|
||||
bool save_process_header(save_ctx_t *ctx);
|
||||
void save_free_contexts(save_ctx_t *ctx);
|
||||
|
||||
bool save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ctx_t *storage_ctx, u32 block_index);
|
||||
u32 save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, u64 offset, size_t count);
|
||||
bool save_fs_list_get_value(save_filesystem_list_ctx_t *ctx, u32 index, save_fs_list_entry_t *value);
|
||||
u32 save_fs_list_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *key, u32 *prev_index);
|
||||
bool save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_table_ctx_t *ctx, save_entry_key_t *key, const char *path);
|
||||
bool save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_fs_list_entry_t *entry);
|
||||
bool save_hierarchical_directory_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_fs_list_entry_t *entry);
|
||||
|
||||
save_ctx_t *save_open_savefile(const char *path, u32 action);
|
||||
void save_close_savefile(save_ctx_t **ctx);
|
||||
bool save_get_fat_storage_from_file_entry_by_path(save_ctx_t *ctx, const char *path, allocation_table_storage_ctx_t *out_fat_storage, u64 *out_file_entry_size);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* __SAVE_H__ */
|
||||
61
sphaira/include/yati/nx/nxdumptool/defines.h
Normal file
61
sphaira/include/yati/nx/nxdumptool/defines.h
Normal file
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* defines.h
|
||||
*
|
||||
* Copyright (c) 2020-2024, DarkMatterCore <pabloacurielz@gmail.com>.
|
||||
*
|
||||
* This file is part of nxdumptool (https://github.com/DarkMatterCore/nxdumptool).
|
||||
*
|
||||
* nxdumptool is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* nxdumptool is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#ifndef __DEFINES_H__
|
||||
#define __DEFINES_H__
|
||||
|
||||
/* Broadly useful language defines. */
|
||||
|
||||
#define MEMBER_SIZE(type, member) sizeof(((type*)NULL)->member)
|
||||
|
||||
#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0])))
|
||||
|
||||
#define ALIGN_UP(x, y) (((x) + ((y) - 1)) & ~((y) - 1))
|
||||
#define ALIGN_DOWN(x, y) ((x) & ~((y) - 1))
|
||||
#define IS_ALIGNED(x, y) (((x) & ((y) - 1)) == 0)
|
||||
|
||||
#define IS_POWER_OF_TWO(x) ((x) > 0 && ((x) & ((x) - 1)) == 0)
|
||||
|
||||
#define DIVIDE_UP(x, y) (((x) + ((y) - 1)) / (y))
|
||||
|
||||
#define CONCATENATE_IMPL(s1, s2) s1##s2
|
||||
#define CONCATENATE(s1, s2) CONCATENATE_IMPL(s1, s2)
|
||||
|
||||
#define ANONYMOUS_VARIABLE(pref) CONCATENATE(pref, __COUNTER__)
|
||||
|
||||
#define NON_COPYABLE(cls) \
|
||||
cls(const cls&) = delete; \
|
||||
cls& operator=(const cls&) = delete
|
||||
|
||||
#define NON_MOVEABLE(cls) \
|
||||
cls(cls&&) = delete; \
|
||||
cls& operator=(cls&&) = delete
|
||||
|
||||
#define ALWAYS_INLINE inline __attribute__((always_inline))
|
||||
#define ALWAYS_INLINE_LAMBDA __attribute__((always_inline))
|
||||
|
||||
#define CLEANUP(func) __attribute__((__cleanup__(func)))
|
||||
|
||||
#define NXDT_ASSERT(name, size) static_assert(sizeof(name) == (size), "Bad size for " #name "! Expected " #size ".")
|
||||
|
||||
#endif /* __DEFINES_H__ */
|
||||
@@ -10,6 +10,11 @@ struct Base {
|
||||
// virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
virtual Result Read(void* buf, s64 off, s64 size, u64* bytes_read) = 0;
|
||||
|
||||
Result Read2(void* buf, s64 off, s64 size) {
|
||||
u64 bytes_read;
|
||||
return Read(buf, off, size, &bytes_read);
|
||||
}
|
||||
|
||||
virtual bool IsStream() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace sphaira::yati::source {
|
||||
struct File final : Base {
|
||||
File(fs::Fs* fs, const fs::FsPath& path);
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
Result GetSize(s64* out);
|
||||
|
||||
private:
|
||||
fs::Fs* m_fs{};
|
||||
|
||||
@@ -1,42 +1,58 @@
|
||||
#pragma once
|
||||
|
||||
#include "base.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "usb/usbds.hpp"
|
||||
#include "usb/usb_installer.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
#include <switch.h>
|
||||
|
||||
namespace sphaira::yati::source {
|
||||
|
||||
struct Usb final : Base {
|
||||
Usb(u64 transfer_timeout);
|
||||
~Usb();
|
||||
Usb(u64 transfer_timeout) {
|
||||
m_usb = std::make_unique<usb::install::Usb>(transfer_timeout);
|
||||
}
|
||||
|
||||
bool IsStream() const override;
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override;
|
||||
Result Finished(u64 timeout);
|
||||
void SignalCancel() override {
|
||||
m_usb->SignalCancel();
|
||||
}
|
||||
|
||||
bool IsStream() const override {
|
||||
return m_usb->GetFlags() & usb::api::FLAG_STREAM;
|
||||
}
|
||||
|
||||
Result Read(void* buf, s64 off, s64 size, u64* bytes_read) override {
|
||||
return m_usb->Read(buf, off, size, bytes_read);
|
||||
}
|
||||
|
||||
Result IsUsbConnected(u64 timeout) {
|
||||
return m_usb->IsUsbConnected(timeout);
|
||||
}
|
||||
|
||||
Result WaitForConnection(u64 timeout, std::vector<std::string>& out_names);
|
||||
void SetFileNameForTranfser(const std::string& name);
|
||||
Result WaitForConnection(u64 timeout, std::vector<std::string>& names) {
|
||||
return m_usb->WaitForConnection(timeout, names);
|
||||
}
|
||||
|
||||
void SignalCancel() override {
|
||||
m_usb->Cancel();
|
||||
Result OpenFile(u32 index, s64& file_size) {
|
||||
return m_usb->OpenFile(index, file_size);
|
||||
}
|
||||
|
||||
Result CloseFile() {
|
||||
return m_usb->CloseFile();
|
||||
}
|
||||
|
||||
auto GetOpenResult() const {
|
||||
return m_usb->GetOpenResult();
|
||||
}
|
||||
|
||||
auto GetCancelEvent() {
|
||||
return m_usb->GetCancelEvent();
|
||||
}
|
||||
|
||||
private:
|
||||
Result SendCmdHeader(u32 cmdId, size_t dataSize, u64 timeout);
|
||||
Result SendFileRangeCmd(u64 offset, u64 size, u64 timeout);
|
||||
|
||||
private:
|
||||
std::unique_ptr<usb::UsbDs> m_usb;
|
||||
std::string m_transfer_file_name{};
|
||||
u8 m_flags{};
|
||||
std::unique_ptr<usb::install::Usb> m_usb{};
|
||||
};
|
||||
|
||||
} // namespace sphaira::yati::source
|
||||
|
||||
@@ -79,8 +79,8 @@ struct ConfigOverride {
|
||||
};
|
||||
|
||||
Result InstallFromFile(ui::ProgressBox* pbox, fs::Fs* fs, const fs::FsPath& path, const ConfigOverride& override = {});
|
||||
Result InstallFromSource(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const fs::FsPath& path, const ConfigOverride& override = {});
|
||||
Result InstallFromContainer(ui::ProgressBox* pbox, std::shared_ptr<container::Base> container, const ConfigOverride& override = {});
|
||||
Result InstallFromCollections(ui::ProgressBox* pbox, std::shared_ptr<source::Base> source, const container::Collections& collections, const ConfigOverride& override = {});
|
||||
Result InstallFromSource(ui::ProgressBox* pbox, source::Base* source, const fs::FsPath& path, const ConfigOverride& override = {});
|
||||
Result InstallFromContainer(ui::ProgressBox* pbox, container::Base* container, const ConfigOverride& override = {});
|
||||
Result InstallFromCollections(ui::ProgressBox* pbox, source::Base* source, const container::Collections& collections, const ConfigOverride& override = {});
|
||||
|
||||
} // namespace sphaira::yati
|
||||
|
||||
@@ -142,3 +142,18 @@ constexpr auto cexprHash(const char *str, std::size_t v = 0) noexcept -> std::si
|
||||
__VA_ARGS__ \
|
||||
} \
|
||||
}
|
||||
|
||||
#define JSON_ARR_ITR(member) \
|
||||
if (!yyjson_is_arr(json)) { \
|
||||
return; \
|
||||
} \
|
||||
const auto arr_size = yyjson_arr_size(json); \
|
||||
if (!arr_size) { \
|
||||
return; \
|
||||
} \
|
||||
member.resize(arr_size); \
|
||||
size_t idx, max; \
|
||||
yyjson_val *hit; \
|
||||
yyjson_arr_foreach(json, idx, max, hit) { \
|
||||
from_json(hit, member[idx]); \
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -4,6 +4,7 @@
|
||||
#include "evman.hpp"
|
||||
#include "fs.hpp"
|
||||
#include "app.hpp"
|
||||
#include "utils/thread.hpp"
|
||||
|
||||
#include <switch.h>
|
||||
#include <cstring>
|
||||
@@ -32,8 +33,6 @@ namespace {
|
||||
constexpr auto API_AGENT = "TotalJustice";
|
||||
constexpr u64 CHUNK_SIZE = 1024*1024;
|
||||
constexpr auto MAX_THREADS = 4;
|
||||
constexpr int THREAD_PRIO = PRIO_PREEMPTIVE;
|
||||
constexpr int THREAD_CORE = 1;
|
||||
|
||||
std::atomic_bool g_running{};
|
||||
CURLSH* g_curl_share{};
|
||||
@@ -61,11 +60,6 @@ struct SeekCustomData {
|
||||
s64 size{};
|
||||
};
|
||||
|
||||
// helper for creating webdav folders as libcurl does not have built-in
|
||||
// support for it.
|
||||
// only creates the folders if they don't exist.
|
||||
auto WebdavCreateFolder(CURL* curl, const Api& e) -> bool;
|
||||
|
||||
auto generate_key_from_path(const fs::FsPath& path) -> std::string {
|
||||
const auto key = crc32Calculate(path.s, path.size());
|
||||
return std::to_string(key);
|
||||
@@ -79,47 +73,58 @@ struct Cache {
|
||||
using Value = std::pair<std::string, std::string>;
|
||||
|
||||
bool init() {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
SCOPED_MUTEX(&m_mutex);
|
||||
|
||||
if (m_json) {
|
||||
return true;
|
||||
if (!m_json) {
|
||||
auto json_in = yyjson_read_file(JSON_PATH, YYJSON_READ_NOFLAG, nullptr, nullptr);
|
||||
if (json_in) {
|
||||
log_write("loading old json doc\n");
|
||||
m_json = yyjson_doc_mut_copy(json_in, nullptr);
|
||||
yyjson_doc_free(json_in);
|
||||
m_root = yyjson_mut_doc_get_root(m_json);
|
||||
} else {
|
||||
log_write("creating new json doc\n");
|
||||
m_json = yyjson_mut_doc_new(nullptr);
|
||||
m_root = yyjson_mut_obj(m_json);
|
||||
yyjson_mut_doc_set_root(m_json, m_root);
|
||||
}
|
||||
}
|
||||
|
||||
auto json_in = yyjson_read_file(JSON_PATH, YYJSON_READ_NOFLAG, nullptr, nullptr);
|
||||
if (json_in) {
|
||||
log_write("loading old json doc\n");
|
||||
m_json = yyjson_doc_mut_copy(json_in, nullptr);
|
||||
yyjson_doc_free(json_in);
|
||||
m_root = yyjson_mut_doc_get_root(m_json);
|
||||
} else {
|
||||
log_write("creating new json doc\n");
|
||||
m_json = yyjson_mut_doc_new(nullptr);
|
||||
m_root = yyjson_mut_obj(m_json);
|
||||
yyjson_mut_doc_set_root(m_json, m_root);
|
||||
if (!m_json) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return m_json && m_root;
|
||||
m_init_ref_count++;
|
||||
log_write("[ETAG] init: %u\n", m_init_ref_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
void exit() {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
SCOPED_MUTEX(&m_mutex);
|
||||
|
||||
if (!m_json) {
|
||||
return;
|
||||
}
|
||||
|
||||
m_init_ref_count--;
|
||||
if (m_init_ref_count) {
|
||||
return;
|
||||
}
|
||||
|
||||
// note: this takes 20ms
|
||||
if (!yyjson_mut_write_file(JSON_PATH, m_json, YYJSON_WRITE_NOFLAG, nullptr, nullptr)) {
|
||||
log_write("failed to write etag json: %s\n", JSON_PATH.s);
|
||||
log_write("[ETAG] failed to write etag json: %s\n", JSON_PATH.s);
|
||||
}
|
||||
|
||||
yyjson_mut_doc_free(m_json);
|
||||
m_json = nullptr;
|
||||
m_root = nullptr;
|
||||
log_write("[ETAG] exit\n");
|
||||
}
|
||||
|
||||
void get(const fs::FsPath& path, curl::Header& header) {
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
const auto [etag, last_modified] = get_internal(path);
|
||||
if (!etag.empty()) {
|
||||
header.m_map.emplace("if-none-match", etag);
|
||||
@@ -131,7 +136,6 @@ struct Cache {
|
||||
}
|
||||
|
||||
void set(const fs::FsPath& path, const curl::Header& value) {
|
||||
mutexLock(&m_mutex);
|
||||
ON_SCOPE_EXIT(mutexUnlock(&m_mutex));
|
||||
|
||||
std::string etag_str;
|
||||
@@ -246,7 +250,7 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
static constexpr inline fs::FsPath JSON_PATH{"/switch/sphaira/cache/cache.json"};
|
||||
static constexpr inline fs::FsPath JSON_PATH{"/switch/sphaira/cache/etag_v2.json"};
|
||||
static constexpr inline const char* ETAG_STR{"etag"};
|
||||
static constexpr inline const char* LAST_MODIFIED_STR{"last-modified"};
|
||||
|
||||
@@ -254,6 +258,7 @@ private:
|
||||
yyjson_mut_doc* m_json{};
|
||||
yyjson_mut_val* m_root{};
|
||||
std::unordered_map<std::string, Value> m_cache{};
|
||||
u32 m_init_ref_count{};
|
||||
};
|
||||
|
||||
struct ThreadEntry {
|
||||
@@ -262,14 +267,17 @@ struct ThreadEntry {
|
||||
R_UNLESS(m_curl != nullptr, Result_CurlFailedEasyInit);
|
||||
|
||||
ueventCreate(&m_uevent, true);
|
||||
R_TRY(threadCreate(&m_thread, ThreadFunc, this, nullptr, 1024*32, THREAD_PRIO, THREAD_CORE));
|
||||
R_TRY(svcSetThreadCoreMask(m_thread.handle, THREAD_CORE, THREAD_AFFINITY_DEFAULT(THREAD_CORE)));
|
||||
R_TRY(utils::CreateThread(&m_thread, ThreadFunc, this, 1024*32));
|
||||
R_TRY(threadStart(&m_thread));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Close() {
|
||||
void SignalClose() {
|
||||
ueventSignal(&m_uevent);
|
||||
}
|
||||
|
||||
void Close() {
|
||||
SignalClose();
|
||||
threadWaitForExit(&m_thread);
|
||||
threadClose(&m_thread);
|
||||
if (m_curl) {
|
||||
@@ -313,20 +321,24 @@ struct ThreadQueueEntry {
|
||||
};
|
||||
|
||||
struct ThreadQueue {
|
||||
std::deque<ThreadQueueEntry> m_entries;
|
||||
Thread m_thread;
|
||||
std::deque<ThreadQueueEntry> m_entries{};
|
||||
Thread m_thread{};
|
||||
Mutex m_mutex{};
|
||||
UEvent m_uevent{};
|
||||
|
||||
auto Create() -> Result {
|
||||
ueventCreate(&m_uevent, true);
|
||||
R_TRY(threadCreate(&m_thread, ThreadFunc, this, nullptr, 1024*32, THREAD_PRIO, THREAD_CORE));
|
||||
R_TRY(utils::CreateThread(&m_thread, ThreadFunc, this, 1024*32));
|
||||
R_TRY(threadStart(&m_thread));
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
void Close() {
|
||||
void SignalClose() {
|
||||
ueventSignal(&m_uevent);
|
||||
}
|
||||
|
||||
void Close() {
|
||||
SignalClose();
|
||||
threadWaitForExit(&m_thread);
|
||||
threadClose(&m_thread);
|
||||
}
|
||||
@@ -576,22 +588,16 @@ auto EscapeString(CURL* curl, const std::string& str) -> std::string {
|
||||
return result;
|
||||
}
|
||||
|
||||
auto EncodeUrl(std::string url) -> std::string {
|
||||
auto EncodeUrl(const std::string& url) -> std::string {
|
||||
log_write("[CURL] encoding url\n");
|
||||
|
||||
if (url.starts_with("webdav://")) {
|
||||
log_write("[CURL] updating host\n");
|
||||
url.replace(0, std::strlen("webdav"), "https");
|
||||
log_write("[CURL] updated host: %s\n", url.c_str());
|
||||
}
|
||||
|
||||
auto clu = curl_url();
|
||||
R_UNLESS(clu, url);
|
||||
ON_SCOPE_EXIT(curl_url_cleanup(clu));
|
||||
|
||||
log_write("[CURL] setting url\n");
|
||||
CURLUcode clu_code;
|
||||
clu_code = curl_url_set(clu, CURLUPART_URL, url.c_str(), CURLU_URLENCODE);
|
||||
clu_code = curl_url_set(clu, CURLUPART_URL, url.c_str(), CURLU_DEFAULT_SCHEME | CURLU_URLENCODE);
|
||||
R_UNLESS(clu_code == CURLUE_OK, url);
|
||||
log_write("[CURL] set url success\n");
|
||||
|
||||
@@ -815,13 +821,6 @@ auto UploadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (e.GetUrl().starts_with("webdav://")) {
|
||||
if (!WebdavCreateFolder(curl, e)) {
|
||||
log_write("[CURL] failed to create webdav folder, aborting\n");
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
const auto& info = e.GetUploadInfo();
|
||||
const auto url = e.GetUrl() + "/" + info.m_name;
|
||||
const auto encoded_url = EncodeUrl(url);
|
||||
@@ -941,76 +940,6 @@ auto UploadInternal(CURL* curl, const Api& e) -> ApiResult {
|
||||
return {success, http_code, header_out, chunk_out.data};
|
||||
}
|
||||
|
||||
auto WebdavCreateFolder(CURL* curl, const Api& e) -> bool {
|
||||
// if using webdav, extract the file path and create the directories.
|
||||
// https://github.com/WebDAVDevs/webdav-request-samples/blob/master/webdav_curl.md
|
||||
if (e.GetUrl().starts_with("webdav://")) {
|
||||
log_write("[CURL] found webdav url\n");
|
||||
|
||||
const auto info = e.GetUploadInfo();
|
||||
if (info.m_name.empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto& file_path = info.m_name;
|
||||
log_write("got file path: %s\n", file_path.c_str());
|
||||
|
||||
const auto file_loc = file_path.find_last_of('/');
|
||||
if (file_loc == file_path.npos) {
|
||||
log_write("failed to find last slash\n");
|
||||
return true;
|
||||
}
|
||||
|
||||
const auto path_view = file_path.substr(0, file_loc);
|
||||
log_write("got folder path: %s\n", path_view.c_str());
|
||||
|
||||
auto e2 = e;
|
||||
e2.SetOption(Path{});
|
||||
e2.SetOption(Url{e.GetUrl() + "/" + path_view});
|
||||
e2.SetOption(Flags{e.GetFlags() | Flag_NoBody});
|
||||
e2.SetOption(CustomRequest{"PROPFIND"});
|
||||
e2.SetOption(Header{
|
||||
{ "Depth", "0" },
|
||||
});
|
||||
|
||||
// test to see if the directory exists first.
|
||||
const auto exist_result = DownloadInternal(curl, e2);
|
||||
if (exist_result.success) {
|
||||
log_write("[CURL] folder already exist: %s\n", path_view.c_str());
|
||||
return true;
|
||||
} else {
|
||||
log_write("[CURL] folder does NOT exist, manually creating: %s\n", path_view.c_str());
|
||||
}
|
||||
|
||||
// make the request to create the folder.
|
||||
std::string folder;
|
||||
for (const auto dir : std::views::split(path_view, '/')) {
|
||||
if (dir.empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
folder += "/" + std::string{dir.data(), dir.size()};
|
||||
e2.SetOption(Url{e.GetUrl() + folder});
|
||||
e2.SetOption(Header{});
|
||||
e2.SetOption(CustomRequest{"MKCOL"});
|
||||
|
||||
const auto result = DownloadInternal(curl, e2);
|
||||
if (result.code == 201) {
|
||||
log_write("[CURL] created webdav directory\n");
|
||||
} else if (result.code == 405) {
|
||||
log_write("[CURL] webdav directory already exists: %ld\n", result.code);
|
||||
} else {
|
||||
log_write("[CURL] failed to create webdav directory: %ld\n", result.code);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log_write("[CURL] not a webdav url: %s\n", e.GetUrl().c_str());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void my_lock(CURL *handle, curl_lock_data data, curl_lock_access laccess, void *useptr) {
|
||||
mutexLock(&g_mutex_share[data]);
|
||||
}
|
||||
@@ -1021,6 +950,12 @@ void my_unlock(CURL *handle, curl_lock_data data, void *useptr) {
|
||||
|
||||
void ThreadEntry::ThreadFunc(void* p) {
|
||||
auto data = static_cast<ThreadEntry*>(p);
|
||||
|
||||
if (!g_cache.init()) {
|
||||
log_write("failed to init json cache\n");
|
||||
}
|
||||
ON_SCOPE_EXIT(g_cache.exit());
|
||||
|
||||
while (g_running) {
|
||||
auto rc = waitSingle(waiterForUEvent(&data->m_uevent), UINT64_MAX);
|
||||
// log_write("woke up\n");
|
||||
@@ -1049,6 +984,7 @@ void ThreadEntry::ThreadFunc(void* p) {
|
||||
|
||||
void ThreadQueue::ThreadFunc(void* p) {
|
||||
auto data = static_cast<ThreadQueue*>(p);
|
||||
|
||||
while (g_running) {
|
||||
auto rc = waitSingle(waiterForUEvent(&data->m_uevent), UINT64_MAX);
|
||||
log_write("[thread queue] woke up\n");
|
||||
@@ -1066,7 +1002,6 @@ void ThreadQueue::ThreadFunc(void* p) {
|
||||
}
|
||||
|
||||
// find the next avaliable thread
|
||||
u32 pop_count{};
|
||||
for (auto& entry : data->m_entries) {
|
||||
if (!g_running) {
|
||||
return;
|
||||
@@ -1080,13 +1015,14 @@ void ThreadQueue::ThreadFunc(void* p) {
|
||||
}
|
||||
|
||||
if (!thread.InProgress()) {
|
||||
thread.Setup(entry.api);
|
||||
// log_write("[dl queue] starting download\n");
|
||||
// mark entry for deletion
|
||||
entry.m_delete = true;
|
||||
pop_count++;
|
||||
keep_going = true;
|
||||
break;
|
||||
if (thread.Setup(entry.api)) {
|
||||
// log_write("[dl queue] starting download\n");
|
||||
// mark entry for deletion
|
||||
entry.m_delete = true;
|
||||
// pop_count++;
|
||||
keep_going = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1096,9 +1032,9 @@ void ThreadQueue::ThreadFunc(void* p) {
|
||||
}
|
||||
|
||||
// delete all entries marked for deletion
|
||||
for (u32 i = 0; i < pop_count; i++) {
|
||||
data->m_entries.pop_front();
|
||||
}
|
||||
std::erase_if(data->m_entries, [](auto& e){
|
||||
return e.m_delete;
|
||||
});
|
||||
}
|
||||
|
||||
log_write("exited download thread queue\n");
|
||||
@@ -1141,16 +1077,22 @@ auto Init() -> bool {
|
||||
|
||||
log_write("finished creating threads\n");
|
||||
|
||||
if (!g_cache.init()) {
|
||||
log_write("failed to init json cache\n");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Exit() {
|
||||
void ExitSignal() {
|
||||
g_running = false;
|
||||
|
||||
g_thread_queue.SignalClose();
|
||||
|
||||
for (auto& entry : g_threads) {
|
||||
entry.SignalClose();
|
||||
}
|
||||
}
|
||||
|
||||
void Exit() {
|
||||
ExitSignal();
|
||||
|
||||
g_thread_queue.Close();
|
||||
|
||||
if (g_curl_single) {
|
||||
@@ -1168,7 +1110,6 @@ void Exit() {
|
||||
}
|
||||
|
||||
curl_global_cleanup();
|
||||
g_cache.exit();
|
||||
}
|
||||
|
||||
auto ToMemory(const Api& e) -> ApiResult {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user