Compare commits

..

159 commits
1.8 ... master

Author SHA1 Message Date
liushuyu f46fed17b7
Merge pull request #29 from liushuyu/yuzu
Installer Overhaul
2023-01-03 18:32:25 -07:00
liushuyu 31b3e7e3a6
CI: use Node.js 16 LTS 2022-12-24 18:51:41 -07:00
liushuyu fec5587b87
ui/authenticationview: fix token pasting ...
... on certain platforms where current context does not include
Clipboard API in the navigator namespace
2022-12-24 17:38:34 -07:00
liushuyu d28b19c25a
deps: remove babel-eslint ...
... also update dependencies to their latest LTS version
2022-12-24 17:27:14 -07:00
liushuyu 08ab3c369e
ui: fix authentication view indentation error 2022-11-27 17:20:12 -07:00
liushuyu d8df3b3114 Merge lat9nq's changes 2022-11-27 17:11:03 -07:00
liushuyu 61a7db2005
fix(auth): fix JWT verification 2022-11-27 17:02:05 -07:00
liushuyu 7b8bf579f2
ui: update dependencies 2022-11-27 16:49:44 -07:00
liushuyu 3ea6aa9852
deps: update dependencies 2022-11-27 16:49:13 -07:00
bunnei faeb4885bf
Merge pull request #30 from lat9nq/linux-mainline-fix
linux v3: Use AppImage for Mainline
2022-08-01 11:17:04 -07:00
lat9nq 85ed6275f8 linux v3: Use AppImage for Mainline
The regular yuzu executable is not anywhere near as guaranteed to run as
the AppImage is.
2022-07-31 19:47:29 -04:00
liushuyu f3d0d06a09
Merge pull request #28 from lat9nq/ea-linux
general: Fix Linux build
2022-07-28 17:13:26 -06:00
lat9nq 8c795396eb installer: Use an inline expression for is_windows
Co-authored-by: liushuyu <liushuyu011@gmail.com>
2022-07-28 16:57:36 -04:00
lat9nq e6600e3b17 general: Housekeeping
Keep up with updates to the compiler.
2022-07-28 16:39:35 -04:00
liushuyu 89be1c0d84
fix(tasks): fix shortcut manipulation logic 2022-04-02 22:03:53 -06:00
liushuyu 42e092f54d
feat(ui): add a "back" button ...
... on the complete view when entered in maintenance mode
2022-04-01 20:27:04 -06:00
liushuyu 278af40b37
feat(ui): add underline when hovering on a clickable link 2022-04-01 20:15:24 -06:00
liushuyu 8917ba88ca
fix(auth): fix panic issue when frontend returned an invalid payload 2022-04-01 20:13:04 -06:00
liushuyu b87dab83d8
feat(ui/views): replace primary selection buttons ...
... with accent colors
2022-04-01 18:59:13 -06:00
liushuyu 79b799f655
feat(ui/selectpackages): make the cursor a pointer shape ...
... when interacting with the selection box
2022-04-01 18:54:34 -06:00
liushuyu c61c068ed0
feat(ui): lint js code 2022-03-31 03:41:39 -06:00
liushuyu 3727e4185b
fix(tasks): fix multiple logic issues under Windows 2022-03-31 02:10:25 -06:00
liushuyu d6cb916a9c
ui: sync translations 2022-03-30 16:26:09 -06:00
liushuyu d269677b2c
tree-wide: format code 2022-03-30 01:47:24 -06:00
liushuyu 9a27b24f05
meta: affix an icon to the title bar 2022-03-30 01:38:46 -06:00
liushuyu 3fc8583646
tasks: uninstall the program as clean as possible 2022-03-30 00:30:25 -06:00
liushuyu a3f0d0f999
ui: adjust repair warning prompt layout 2022-03-29 23:20:37 -06:00
liushuyu d194ed5dd5
tasks: fix shortcut creation logic 2022-03-29 23:19:40 -06:00
liushuyu 0decda8232
ui: fix clickable box contrast issue under darkmode 2022-03-29 22:23:14 -06:00
liushuyu 2103a8ec15
ui: warn about file deletion before a repair 2022-03-29 22:22:20 -06:00
liushuyu a86bd209a8
locales: add German translations 2022-03-29 18:51:57 -06:00
liushuyu eff81e6d99
ui/app: make language dropdown scrollable 2022-03-28 21:48:34 -06:00
liushuyu d63473ec9c
locales: update translations 2022-03-28 21:20:15 -06:00
liushuyu 1fd97b6e42
ui: update dependencies (LTS versions) 2022-03-28 17:09:40 -06:00
liushuyu 0cfa44330d
deps: update dependencies 2022-03-28 17:02:46 -06:00
liushuyu 679312f101
fix(CI): fix Windows CI 2021-10-15 23:37:41 -06:00
liushuyu 2120abf299
fix(ci): fix CI 2021-10-15 19:10:08 -06:00
liushuyu a8db5ff8c4
fix(ui/views): fix EA auth UI handling 2021-10-15 19:09:09 -06:00
liushuyu faba49c025 feat(frontend/win): bundle webview2 installer 2021-10-15 19:09:02 -06:00
liushuyu 3196736d36 fix(frontend): fix early access authentication 2021-10-15 19:09:02 -06:00
lat9nq f809e6cb23
Update src/native/mod.rs
Co-authored-by: liushuyu <liushuyu011@gmail.com>
2021-10-15 19:19:00 -04:00
lat9nq 77a26c1496
Update src/tasks/install_desktop_shortcut.rs
Co-authored-by: liushuyu <liushuyu011@gmail.com>
2021-10-15 19:18:56 -04:00
liushuyu e990138200
fix(ui): fix fractional scaling 2021-10-15 14:37:18 -06:00
liushuyu 6e7d045794
feat(ui): migrate UI/Web framework to WRY 2021-10-15 04:35:47 -06:00
liushuyu 0d4022d348
feat(install): add recovery mode ...
... when metadata is corrupted, recovery mode will be activated
2021-10-15 01:42:47 -06:00
liushuyu 109322836b
feat(ui): add a mock to mock authentication ...
... also updates UI/JS dependencies
2021-10-15 00:28:45 -06:00
liushuyu fbf7640657
deps: update dependencies 2021-10-14 23:45:30 -06:00
liushuyu 2d42189e5e
feat(ui/i18n): mark most of the yuzu-specific strings as translatable 2021-10-14 23:03:21 -06:00
liushuyu dde96db57c
fix(tree-wide): re-apply yuzu specific changes 2021-10-14 21:13:31 -06:00
liushuyu a816cbe767 Merge remote-tracking branch 'fix-usability' into yuzu 2021-07-28 18:18:38 -06:00
lat9nq 95ee7a1739 native: Linux shortcut specific to maintenance tool 2021-07-25 16:18:27 -04:00
lat9nq 061944079b travis: Build using the linux-liftinstall Docker container
travis: Build in release mode

travis: Use yuzu user for building

travis: Fix permissions resetting
2021-07-24 19:02:34 -04:00
lat9nq e54199ad6f SelectPackages: Desktop shortcut is for Windows 2021-07-24 19:02:28 -04:00
lat9nq 4ed1ffb5c3 general: Get ready for Linux release
Creates a config for Linux based on config.windows.v10.toml, then points
to it. We also remove the icon stub.
2021-07-24 19:02:28 -04:00
lat9nq 2958c583af views: Stub things that don't work or don't apply to Linux
Icons don't work in Linux. Start menu is not a Linux thing.
Automatically scrolls to the verify token button in the Authentication
view.
2021-07-24 19:02:28 -04:00
lat9nq 825e9cc1c3 general: Fix Linux shortcuts
Makes them function even if it's missing the icon.
2021-07-24 19:02:28 -04:00
lat9nq 810ef5fb25 src: Fix Linux build
Syncs the parameters between the Windows and Linux create_shortcut
functions. Makes the install_desktop_shortcut only work on Windows where
it has create_desktop_shortcut implemented.
2021-07-24 19:02:15 -04:00
liushuyu 89e1b2f91f
ui: web-view: set debug mode on debug build 2021-06-08 18:05:38 -06:00
liushuyu f13b2fe93d
deps: update dependencies...
... and use tinyfiledialog instead of the web-view provided one
2021-06-08 17:42:43 -06:00
liushuyu df0414b26e
ui: fix eslint errors 2021-03-21 21:59:28 -06:00
liushuyu a9de893cca
ui: show error if no package is selected 2021-03-21 21:38:32 -06:00
liushuyu bdda585f12
deps: update dependencies 2021-03-21 21:38:10 -06:00
liushuyu 27aa9924f3 ui/ux: implement "View Local Files" function...
... for Windows
2020-11-06 17:14:34 -07:00
liushuyu 322f72609f ui/ux: implement "View Local Files" function...
... for Linux/macOS
2020-07-19 19:01:23 -06:00
liushuyu ca994e49d3 ui: bugfix: do not show the overwrite dialog...
... when doing a repair
2020-07-19 19:01:23 -06:00
liushuyu 41918c709c frontend/rest/assets: add missing webfonts for icons 2020-07-19 19:01:23 -06:00
liushuyu 48fa172169 meta: refine i18n and mark more strings 2020-07-19 19:01:12 -06:00
liushuyu 928661db77 ux: allow user to overwrite the directory...
... if they really want
2020-07-19 18:46:35 -06:00
liushuyu 45c562d723 meta: add repair functionality 2020-07-19 18:46:32 -06:00
liushuyu c7628c1474 i18n: merge-strings: fix issues with only one locale 2020-07-18 14:09:19 +10:00
liushuyu 34fd140a9e ui: main: more robust error handling in /app/exit 2020-07-18 14:09:19 +10:00
liushuyu cd7fb8de28 ui: update NPM dependencies 2020-07-18 14:09:19 +10:00
liushuyu 01419e5da4 ui: mock-server: can now simulate other modes...
... like maintenance mode and launcher mode
2020-07-18 14:09:19 +10:00
James 3ce4504a5b
Create SECURITY.md 2020-05-29 16:01:03 +10:00
liushuyu 5003edd43d ui: messages: fix end of file line-ending 2020-05-29 15:57:05 +10:00
liushuyu 87efd394a1 use actual buttons 2020-05-29 15:57:05 +10:00
liushuyu 8e212460d8 helper: clean up functions 2020-05-29 15:57:05 +10:00
liushuyu 4bb84d98b3 DownloadConfig: fix error message display 2020-05-29 15:57:05 +10:00
liushuyu 1dbf078728 i18n: automated locale detection and...
... manual selection box
2020-05-29 15:57:05 +10:00
liushuyu 8e8d729019 mock-server: can now emulate errors 2020-05-29 15:57:05 +10:00
liushuyu 9fcfe0c77b ui: update frontend dependencies 2020-05-29 15:57:05 +10:00
liushuyu 74cefc277e i18n: basic implementation and string interpolation 2020-05-29 15:57:05 +10:00
liushuyu adbd4a304d ci: add caching 2020-05-29 15:56:05 +10:00
liushuyu 351be36f05 ci: switch to GitHub Actions completely 2020-05-29 15:56:05 +10:00
liushuyu 9866a32c10 deps: update dependencies 2020-05-28 16:50:52 +10:00
liushuyu 3537b5823f build.rs: fix build on Windows with gcc 2020-05-28 16:50:52 +10:00
liushuyu 5ff1486f69 meta: update to Rust 2018 standard 2020-05-28 16:50:52 +10:00
liushuyu 7acefbc8cb deps: update dependencies 2020-05-28 16:50:52 +10:00
bunnei ef71b707cb
Merge pull request #26 from jroweboy/replace-old
Change updater to replace the existing installer.
2019-12-30 13:19:19 -05:00
James Rowe c68ebcb61e Change updater to replace the existing installer. 2019-12-19 23:05:33 -07:00
bunnei 93e24ea06a
Merge pull request #25 from jroweboy/fix
Fix self update code path
2019-12-08 22:27:32 -05:00
James Rowe d9d8b92cc4 Fix self update code path 2019-12-08 20:12:54 -07:00
bunnei 76a77d3caf
Merge pull request #24 from jroweboy/patch-1
Unpublish update 1.8
2019-12-08 22:04:10 -05:00
James Rowe ea8b631aa2
Unpublish update 1.8 2019-12-08 20:03:23 -07:00
bunnei f848e8fb53
Merge pull request #23 from jroweboy/merged
Installer v1.8
2019-12-08 21:52:21 -05:00
James Rowe d9e4e5ecc2 Prevent fresh install with no packages selected 2019-12-08 17:26:42 -07:00
James Rowe 6210a2668f Attempt to refresh shortcuts on create 2019-12-08 17:26:18 -07:00
liushuyu d2399d97e4 Fix build on Linux and macOS 2019-12-08 06:07:40 +00:00
liushuyu b9e825faa5 ui/frontend: adaptive changes for...
... Vue and Webpack behavioral changes
2019-12-07 22:35:37 +00:00
liushuyu 630f2231ab ui: use axios as possible 2019-12-07 22:35:37 +00:00
liushuyu 91fb88aa98 use ajax to fetch basic_attrs 2019-12-07 22:35:37 +00:00
liushuyu 713b85b59a lint: automated lint 2019-12-07 22:35:37 +00:00
liushuyu ca6ac320c2 deps: update dependencies 2019-12-07 22:35:37 +00:00
James Rowe 9999c52ea8 Add a little padding to select packages description 2019-12-06 22:45:42 -07:00
James Rowe 6cae746192 Launch existing maintenance tool if it exists in the default install folder 2019-12-06 22:31:37 -07:00
James Rowe 732e344605 Launch app on fresh install exit. Add desktop shortcuts 2019-12-06 22:04:44 -07:00
James Rowe 9b58c273d1 Merge branches 'icon-swap' and 'right-click' into merged 2019-12-06 02:12:22 -07:00
James Rowe b356f0057f Dark theme logo and package icons 2019-12-06 02:11:49 -07:00
James Rowe d339816695 Use the correct icon for the application shortcuts 2019-12-06 09:09:15 +00:00
James Rowe d2ad619d87 Use the correct icon for the application shortcuts 2019-12-06 00:27:44 -07:00
James Rowe 128c1b1f41 Add paste button 2019-12-06 00:10:47 -07:00
James Rowe 37d27a82ba Move to installer v1.8 2019-12-05 09:49:03 -07:00
James Rowe eb556c8cab Force disable exp and nbf validation.
Some clients had the wrong time information so it would fail to validate their token when installing. Remove these checks since they'll be checked on the server side anyway
2019-12-02 09:38:33 -07:00
James ca8defda7e
Remove Ubuntu/macOS targets in Github Actions 2019-08-27 05:23:33 +00:00
James 6853ade29c
Fix checkout version 2019-08-27 05:20:53 +00:00
James b6122349d6
Remove un-needed name calls 2019-08-27 05:19:12 +00:00
James 12081db009
Delete main.workflow 2019-08-27 05:18:18 +00:00
James 3abc0a1b11
Add test Rust build script 2019-08-27 05:17:01 +00:00
James 56cdaabbae
Add GitHub Actions CI 2019-08-27 05:03:29 +00:00
dependabot[bot] a02e8a1624 Bump js-yaml from 3.13.0 to 3.13.1 in /ui
Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.13.0 to 3.13.1.
- [Release notes](https://github.com/nodeca/js-yaml/releases)
- [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md)
- [Commits](https://github.com/nodeca/js-yaml/compare/3.13.0...3.13.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-19 10:08:56 +00:00
dependabot[bot] 4b158036da Bump webpack-bundle-analyzer from 3.1.0 to 3.3.2 in /ui
Bumps [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) from 3.1.0 to 3.3.2.
- [Release notes](https://github.com/webpack-contrib/webpack-bundle-analyzer/releases)
- [Changelog](https://github.com/webpack-contrib/webpack-bundle-analyzer/blob/master/CHANGELOG.md)
- [Commits](https://github.com/webpack-contrib/webpack-bundle-analyzer/compare/v3.1.0...v3.3.2)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-19 10:08:46 +00:00
dependabot[bot] eb6475bac6 Bump lodash from 4.17.11 to 4.17.15 in /ui
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.11 to 4.17.15.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.11...4.17.15)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-19 08:05:21 +00:00
dependabot[bot] c7cef0b49d Bump lodash.defaultsdeep from 4.6.0 to 4.6.1 in /ui
Bumps [lodash.defaultsdeep](https://github.com/lodash/lodash) from 4.6.0 to 4.6.1.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.6.0...4.6.1)

Signed-off-by: dependabot[bot] <support@github.com>
2019-07-19 07:32:50 +00:00
James 2ee02bbf46
Assert return code is valid when running yarn 2019-07-19 06:26:06 +00:00
James 6d443805fc Bump version to 0.2.0 for new changes 2019-07-05 09:19:13 +10:00
James c4139f7e37 Correctly handle exit on error 2019-07-03 15:10:34 +10:00
James 6272c294c8 Fix error display on launcher pages 2019-07-03 14:40:39 +10:00
James 5d31fd0129 Merge branch 'ui-tweaks' into ui-tweaks-test 2019-07-03 14:10:37 +10:00
James e69443c22e Make launcher mode behaviour more robust 2019-07-03 14:09:07 +10:00
liushuyu c8699b6e62
ui: makes error message selectable 2019-07-01 10:01:02 +08:00
liushuyu e83cf6cf4e
ui: use Buefy components to...
... beautify the UI
2019-06-30 10:12:22 +08:00
liushuyu 9a28807423 travis: add cache and fix issues 2019-06-29 16:17:58 +00:00
liushuyu b3b686ed53 native: further improve Unicode support on Windows 2019-06-29 15:14:28 +00:00
liushuyu f80db92188 native: deal with Unicode issues in native APIs 2019-06-29 15:14:28 +00:00
liushuyu 4578450bff native: fix uninstall self-destruction behavior...... by not showing the command prompt window and fork-spawning the cmd 2019-06-29 15:14:28 +00:00
James 5603981af1 Add instructions for node + yarn 2019-06-26 23:44:17 +10:00
James 341a6a6537 Polish Vue UI split 2019-06-26 23:43:24 +10:00
liushuyu 27d0a05ade build: integrate webpack building into build.rs 2019-06-25 15:44:41 +08:00
liushuyu 6c19b8b0d1
frontend: ui: include prebuilt assets...
... and update rust side stuff
2019-06-25 12:45:56 +08:00
liushuyu ff574c9d73 ui: split files and use Webpack 2019-06-25 11:06:28 +08:00
James 761ce91299 Revert window.close usage 2019-06-24 00:44:18 +10:00
James 44e0ebdab4 Implement basic dark mode 2019-06-24 00:18:59 +10:00
James 270a17cd86 Use patched web-view to fix dialogs, remove nfd 2019-06-23 22:28:41 +10:00
James f24d1112dd Migrate assets to server module 2019-06-23 21:55:16 +10:00
James 3109d48dce Migrate self updating functions to own module 2019-06-23 21:46:04 +10:00
James 5d53ef7a2e Add explicit 'dyn's as per Rust nightly requirements 2019-06-23 21:35:41 +10:00
James 4d50a0f8f8 Update packages, use async client for downloading config
While this has a hell of a lot more boilerplate, this is quite
a bit cleaner.
2019-06-23 21:24:13 +10:00
James a447ef25b6 Clean up codebase, fixing minor errors 2019-06-23 20:27:35 +10:00
James 9d1f4c2576 Break apart REST into separate services
This cleans up locking, ensures consistent futures for all endpoints
and enhances code re-use.
2019-06-23 20:19:43 +10:00
James 30bb49e1fb Update Cargo.lock for new version 2019-06-20 22:36:35 +10:00
James 8b6c2c1708
Merge pull request #11 from liushuyu/fix-linux
linux: implement platform-dependent functions
2019-04-08 22:43:27 +10:00
James 0d63e0ab21
Merge pull request #10 from liushuyu/master
CI enhancements
2019-04-08 22:39:52 +10:00
liushuyu 269b083ec8
travis: use official Rust Docker image 2019-04-03 00:58:49 -06:00
liushuyu ae63bc7dab
travis: add macos and windows CI 2019-04-03 00:57:27 -06:00
liushuyu d3fb463f20
linux: implement platform-dependent functions 2019-04-01 13:50:02 -06:00
James 137d2ec539
Merge pull request #9 from liushuyu/fix-linux
Fix compile on Linux
2019-04-01 16:38:14 +11:00
liushuyu fed2d28aa8
platform: fix regression...
... that prevents the build on Windows
2019-03-29 11:44:26 -06:00
liushuyu 66e2473a40
deps: replace xz-decom with xz2 and update deps 2019-03-28 11:49:14 -06:00
liushuyu d236eeec0c
platform: fix build on Linux and update web-view 2019-03-28 11:08:13 -06:00
112 changed files with 11423 additions and 8581 deletions

58
.github/workflows/test-build.yml vendored Normal file
View file

@ -0,0 +1,58 @@
name: Rust
on: [push, pull_request]
jobs:
build:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: hecrj/setup-rust-action@master
with:
rust-version: stable
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libwebkit2gtk-4.0-dev libssl-dev libappindicator3-dev
if: runner.os == 'Linux'
- name: Get yarn cache directory path
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- name: Get cargo cache directory path
id: cargo-cache-dir-path
run: echo "::set-output name=dir::$HOME/.cargo/"
- uses: actions/cache@v2
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-yarn-
- uses: actions/cache@v2
id: cargo-cache
with:
path: ${{ steps.cargo-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-cargo-${{ hashFiles('Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-
- uses: actions/setup-node@v1
with:
node-version: '16.x'
- run: npm install -g yarn
- uses: actions/checkout@v2
- name: Download Webview2
run: Invoke-WebRequest -Uri 'https://go.microsoft.com/fwlink/p/?LinkId=2124703' -OutFile 'MicrosoftEdgeWebview2Setup.exe'
if: runner.os == 'Windows'
shell: pwsh
- name: Build
run: cargo build --verbose

2
.gitignore vendored
View file

@ -5,3 +5,5 @@
**/*.rs.bk
*.log
*.exe

View file

@ -1,27 +0,0 @@
matrix:
include:
- os: linux
language: cpp
sudo: required
dist: trusty
services: docker
install: docker pull rust:1
cache:
directories:
- $HOME/.cargo
- $TRAVIS_BUILD_DIR/ui/node_modules
script: docker run -v $HOME/.cargo:/root/.cargo -v $(pwd):/liftinstall rust:1 /bin/bash -ex /liftinstall/.travis/build.sh
- os: osx
language: rust
cache: cargo
osx_image: xcode10
script: brew install yarn && cargo build
- os: windows
language: rust
cache: cargo
script:
- choco install nodejs yarn
- export PATH="$PROGRAMFILES/nodejs/:$PROGRAMFILES (x86)/Yarn/bin/:$PATH"
- cargo build

View file

@ -1,15 +1,6 @@
#!/usr/bin/env bash
cd /liftinstall || exit 1
# setup NodeJS
curl -sL https://deb.nodesource.com/setup_12.x | bash -
# setup Yarn
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
echo "deb https://dl.yarnpkg.com/debian/ stable main" > /etc/apt/sources.list.d/yarn.list
apt-get update
apt-get install -y libwebkit2gtk-4.0-dev libssl-dev nodejs yarn
yarn --cwd ui
cargo build
cargo build --release

4
.travis/exec.sh Normal file
View file

@ -0,0 +1,4 @@
#!/bin/bash -ex
# the UID for the container yuzu user is 1027
docker run -u root -v $(pwd):/liftinstall -t yuzuemu/build-environments:linux-liftinstall /bin/bash /liftinstall/.travis/build.sh

8
.tx/config Executable file
View file

@ -0,0 +1,8 @@
[main]
host = https://www.transifex.com
[o:yuzu-emulator:p:yuzu:r:installer]
file_filter = ui/translations/<lang>.po
source_file = ui/translations/en.po
source_lang = en
type = PO

4039
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,64 +1,71 @@
[package]
name = "liftinstall"
version = "0.1.0"
version = "0.2.0"
edition = "2018"
authors = ["James <jselby@jselby.net>"]
repository = "https://github.com/j-selby/liftinstall.git"
documentation = "https://liftinstall.jselby.net"
description = "An adaptable installer for your application."
build = "build.rs"
resolver = "2"
[dependencies]
web-view = {git = "https://github.com/j-selby/web-view.git", rev = "752106e4637356cbdb39a0bf1113ea3ae8a14243"}
anyhow = "^1"
wry = "0.12"
tinyfiledialogs = "3.8"
hyper = "0.11.27"
futures = "0.1.25"
mime_guess = "1.8.6"
url = "1.7.2"
futures = "0.1.29"
mime_guess = "2.0"
url = "2.2"
reqwest = "0.9.21"
number_prefix = "0.3.0"
reqwest = "0.9.22"
number_prefix = "0.4"
serde = "1.0.89"
serde_derive = "1.0.89"
serde_json = "1.0.39"
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
toml = "0.5.0"
toml = "0.5"
semver = {version = "0.9.0", features = ["serde"]}
regex = "1.1.5"
semver = {version = "1.0", features = ["serde"]}
regex = "1.4"
dirs = "1.0.5"
zip = "0.5.1"
xz2 = "0.1.6"
dirs = "^4"
zip = "0.6"
xz2 = "0.1"
tar = "0.4"
log = "0.4"
fern = "0.5"
chrono = "0.4.6"
fern = "0.6"
chrono = "0.4"
clap = "2.32.0"
clap = "2.33"
# used to open a link to the users default browser
webbrowser = "0.5.2"
webbrowser = "0.8"
# used in JWT based package authentication
jsonwebtoken = "6"
jsonwebtoken = "^8"
# used to decode the public key for verifying JWT tokens
base64 = "0.10.1"
base64 = "0.13"
[build-dependencies]
walkdir = "2.2.7"
serde = "1.0.89"
serde_derive = "1.0.89"
toml = "0.5.0"
which = "2.0.1"
walkdir = "2.3"
serde = "1.0"
serde_derive = "1.0"
toml = "0.5"
which = "4.0"
image = { version = "0.24", default-features = false, features = ["ico"] }
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3", features = ["psapi", "winbase", "winioctl", "winnt"] }
widestring = "0.4.0"
widestring = "^1"
webview2 = "0.1"
tempfile = "3"
[target.'cfg(not(windows))'.dependencies]
sysinfo = "0.8.2"
slug = "0.1.4"
sysinfo = "0.26"
slug = "0.1"
[target.'cfg(windows)'.build-dependencies]
winres = "0.1"

18
Justfile Normal file
View file

@ -0,0 +1,18 @@
ui-build:
yarn --cwd {{ justfile_directory() }}/ui/ install
yarn --cwd {{ justfile_directory() }}/ui/ build
ui-test:
cd {{ justfile_directory() }}/ui/ && node mock-server.js &
yarn --cwd {{ justfile_directory() }}/ui/ serve
update-i18n:
#!/bin/bash -e
[ -z "${TX_PULL}" ] || tx pull -a --minimum-perc 85
for i in {{ justfile_directory() }}/ui/translations/*.po; do
TARGET_FILE="$(basename $i)"
TARGET_LANG="${TARGET_FILE/.po/}"
OUTPUT="{{ justfile_directory() }}/ui/src/locales/${TARGET_LANG}.json"
i18next-conv -l en -s "$i" -t "$OUTPUT" -K
node {{ justfile_directory() }}/ui/unbreak-translations.js "$OUTPUT" "$OUTPUT"
done

13
SECURITY.md Normal file
View file

@ -0,0 +1,13 @@
# Security Policy
## Supported Versions
As `liftinstall` is a template for your project, no specific versioning is
provided at this time, though a rough version is given in the Cargo file.
Only the latest version from this file will be supported.
## Reporting a Vulnerability
For any specific security concerns/vulnerabilities, please email me directly
at security *at* jselby.net.

View file

@ -1,2 +1,2 @@
name = "yuzu"
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.linux.v2.toml"
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.linux.v3.toml"

View file

@ -1,2 +1,2 @@
name = "yuzu"
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.windows.v9.toml"
target_url = "https://raw.githubusercontent.com/yuzu-emu/liftinstall/master/config.windows.v10.toml"

View file

@ -12,6 +12,7 @@ extern crate toml;
extern crate which;
use std::env;
use std::io::Write;
use std::path::PathBuf;
use std::fs::copy;
@ -22,6 +23,8 @@ use std::process::Command;
use std::env::consts::OS;
use image::imageops::FilterType;
/// Describes the application itself.
#[derive(Debug, Deserialize)]
pub struct BaseAttributes {
@ -46,6 +49,8 @@ fn handle_binary(config: &BaseAttributes) {
cc::Build::new()
.cpp(true)
.define("_WIN32_WINNT", Some("0x0600"))
.define("WINVER", Some("0x0600"))
.file("src/native/interop.cpp")
.compile("interop");
}
@ -60,6 +65,13 @@ fn main() {
let os = OS.to_lowercase();
#[cfg(windows)]
{
if std::fs::metadata("MicrosoftEdgeWebview2Setup.exe").is_err() {
panic!("Please download MicrosoftEdgeWebview2Setup.exe from https://go.microsoft.com/fwlink/p/?LinkId=2124703 and put the file at the workspace root!");
}
}
// Find target config
let target_config = PathBuf::from(format!("bootstrap.{}.toml", os));
@ -90,6 +102,14 @@ fn main() {
let yarn_binary =
which::which("yarn").expect("Failed to find yarn - please go ahead and install it!");
// bundle the icon
let mut f = File::create(output_dir.join("icon-data.bin")).unwrap();
let icon_file = image::open("ui/public/favicon.ico").expect("Unable to read the icon file");
let icon_data = icon_file
.resize_exact(48, 48, FilterType::Triangle)
.to_rgba8();
f.write_all(&icon_data.into_vec()).unwrap();
// Build and deploy frontend files
Command::new(&yarn_binary)
.arg("--version")
@ -102,7 +122,7 @@ fn main() {
.unwrap()
.wait()
.expect("Unable to install Node.JS dependencies using Yarn");
Command::new(&yarn_binary)
let return_code = Command::new(&yarn_binary)
.args(&[
"--cwd",
ui_dir.to_str().expect("Unable to covert path"),
@ -114,8 +134,7 @@ fn main() {
.to_str()
.expect("Unable to convert path"),
])
.spawn()
.unwrap()
.wait()
.status()
.expect("Unable to build frontend assets using Webpack");
assert!(return_code.success());
}

58
config.linux.v3.toml Normal file
View file

@ -0,0 +1,58 @@
installing_message = "Reminder: yuzu is an <b>experimental</b> emulator. Stuff will break!"
hide_advanced = true
[authentication]
# Base64 encoded version of the public key for validating the JWT token. Must be in DER format
pub_key_base64 = "MIIBCgKCAQEAs5K6s49JVV9LBMzDrkORsoPSYsv1sCXDtxjp4pn8p0uPSvJAsbNNmdIgCjfSULzbHLM28MblnI4zYP8ZgKtkjdg+Ic5WQbS5iBAkf18zMafpOrotTArLsgZSmUfNYt0SOiN17D+sq/Ov/CKXRM9CttKkEbanBTVqkx7sxsHVbkI6tDvkboSaNeVPHzHlfAbvGrUo5cbAFCB/KnRsoxr+g7jLKTxU1w4xb/pIs91h80AXV/yZPXL6ItPM3/0noIRXjmoeYWf2sFQaFALNB2Kef0p6/hoHYUQP04ZSIL3Q+v13z5X2YJIlI4eLg+iD25QYm9V8oP3+Xro4vd47a0/maQIDAQAB"
# URL to authenticate against. This must return a JWT token with their permissions and a custom claim patreonInfo with the following structure
# "patreonInfo": { "linked": false, "activeSubscription": false }
# If successful, the frontend will use this JWT token as a Bearer Authentication when requesting the binaries to download
auth_url = "https://api.yuzu-emu.org/jwt/installer/"
[authentication.validation]
iss = "citra-core"
aud = "installer"
[[packages]]
name = "yuzu Early Access"
description = "Preview release with the newest features for the supporters."
icon = "thicc_logo_installer__ea_shadow.png"
requires_authorization = true
# puts a "new" ribbon the package select
is_new = true
[packages.extended_description]
no_action_description = "Thank you for your support!"
# Displayed when the package has no authentication for the user
need_authentication_description = "Click here to sign in with your yuzu account for Early Access"
# Displayed when the package has an authentication, but the user has not linked their account
need_link_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
# Displayed when the package has an authentication, but the user has not linked their account
need_subscription_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
# Displayed when the package has an authentication, but the user has not linked their account
need_reward_tier_description = "You are signed in, but are not backing an eligible reward tier! Click here for more details"
[packages.source]
name = "patreon"
match = "^yuzu-linux-[0-9]*-[0-9a-f]*.tar.xz$"
[packages.source.config]
repo = "earlyaccess"
[[packages.shortcuts]]
name = "yuzu Early Access"
relative_path = "yuzu-linux-early-access/yuzu-early-access.AppImage"
description = "Launch yuzu Early Access"
[[packages]]
name = "yuzu"
description = "Includes frequent updates to yuzu with all the latest reviewed and tested features."
icon = "thicc_logo_installer_shadow.png"
default = true
[packages.source]
name = "github"
match = "^yuzu-linux-[0-9]*-[0-9a-f]*.tar.xz$"
[packages.source.config]
repo = "yuzu-emu/yuzu-mainline"
[[packages.shortcuts]]
name = "yuzu"
relative_path = "yuzu-linux-mainline/yuzu-mainline.AppImage"
description = "Launch yuzu"

58
config.windows.v10.toml Normal file
View file

@ -0,0 +1,58 @@
installing_message = "Reminder: yuzu is an <b>experimental</b> emulator. Stuff will break!"
hide_advanced = true
[authentication]
# Base64 encoded version of the public key for validating the JWT token. Must be in DER format
pub_key_base64 = "MIIBCgKCAQEAs5K6s49JVV9LBMzDrkORsoPSYsv1sCXDtxjp4pn8p0uPSvJAsbNNmdIgCjfSULzbHLM28MblnI4zYP8ZgKtkjdg+Ic5WQbS5iBAkf18zMafpOrotTArLsgZSmUfNYt0SOiN17D+sq/Ov/CKXRM9CttKkEbanBTVqkx7sxsHVbkI6tDvkboSaNeVPHzHlfAbvGrUo5cbAFCB/KnRsoxr+g7jLKTxU1w4xb/pIs91h80AXV/yZPXL6ItPM3/0noIRXjmoeYWf2sFQaFALNB2Kef0p6/hoHYUQP04ZSIL3Q+v13z5X2YJIlI4eLg+iD25QYm9V8oP3+Xro4vd47a0/maQIDAQAB"
# URL to authenticate against. This must return a JWT token with their permissions and a custom claim patreonInfo with the following structure
# "patreonInfo": { "linked": false, "activeSubscription": false }
# If successful, the frontend will use this JWT token as a Bearer Authentication when requesting the binaries to download
auth_url = "https://api.yuzu-emu.org/jwt/installer/"
[authentication.validation]
iss = "citra-core"
aud = "installer"
[[packages]]
name = "yuzu Early Access"
description = "Preview release with the newest features for the supporters."
icon = "thicc_logo_installer__ea_shadow.png"
requires_authorization = true
# puts a "new" ribbon the package select
is_new = true
[packages.extended_description]
no_action_description = "Thank you for your support!"
# Displayed when the package has no authentication for the user
need_authentication_description = "Click here to sign in with your yuzu account for Early Access"
# Displayed when the package has an authentication, but the user has not linked their account
need_link_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
# Displayed when the package has an authentication, but the user has not linked their account
need_subscription_description = "You are signed in, but you need to link your Patreon account! Click here for more details"
# Displayed when the package has an authentication, but the user has not linked their account
need_reward_tier_description = "You are signed in, but are not backing an eligible reward tier! Click here for more details"
[packages.source]
name = "patreon"
match = "^yuzu-windows-msvc-[0-9]*-[0-9a-f]*.tar.xz$"
[packages.source.config]
repo = "earlyaccess"
[[packages.shortcuts]]
name = "yuzu Early Access"
relative_path = "yuzu-windows-msvc-early-access/yuzu.exe"
description = "Launch yuzu Early Access"
[[packages]]
name = "yuzu"
description = "Includes frequent updates to yuzu with all the latest reviewed and tested features."
icon = "thicc_logo_installer_shadow.png"
default = true
[packages.source]
name = "github"
match = "^yuzu-windows-msvc-[0-9]*-[0-9a-f]*.tar.xz$"
[packages.source.config]
repo = "yuzu-emu/yuzu-mainline"
[[packages.shortcuts]]
name = "yuzu"
relative_path = "yuzu-windows-msvc/yuzu.exe"
description = "Launch yuzu"

View file

@ -41,7 +41,7 @@ impl<'a> Archive<'a> for ZipArchive<'a> {
continue;
}
func(i, Some(max), archive.sanitized_name(), &mut archive)?;
func(i, Some(max), archive.mangled_name(), &mut archive)?;
}
Ok(())

View file

@ -7,8 +7,8 @@ use toml::de::Error as TomlError;
use serde_json::{self, Error as SerdeError};
use sources::get_by_name;
use sources::types::Release;
use crate::sources::get_by_name;
use crate::sources::types::Release;
/// Description of the source of a package.
#[derive(Debug, Deserialize, Serialize, Clone)]
@ -25,6 +25,23 @@ pub struct PackageShortcut {
pub name: String,
pub relative_path: String,
pub description: String,
#[serde(default)]
pub has_desktop_shortcut: bool,
}
/// Extra description for authentication and authorization state for a package
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct PackageExtendedDescription {
#[serde(default)]
pub no_action_description: Option<String>,
#[serde(default)]
pub need_authentication_description: Option<String>,
#[serde(default)]
pub need_link_description: Option<String>,
#[serde(default)]
pub need_subscription_description: Option<String>,
#[serde(default)]
pub need_reward_tier_description: Option<String>,
}
/// Describes a overview of a individual package.
@ -32,6 +49,8 @@ pub struct PackageShortcut {
pub struct PackageDescription {
pub name: String,
pub description: String,
#[serde(default)]
pub icon: Option<String>,
pub default: Option<bool>,
pub source: PackageSource,
#[serde(default)]
@ -41,13 +60,7 @@ pub struct PackageDescription {
#[serde(default)]
pub is_new: Option<bool>,
#[serde(default)]
pub need_authentication_description: Option<String>,
#[serde(default)]
pub need_link_description: Option<String>,
#[serde(default)]
pub need_subscription_description: Option<String>,
#[serde(default)]
pub need_reward_tier_description: Option<String>,
pub extended_description: Option<PackageExtendedDescription>,
}
/// Configuration for validating the JWT token
@ -71,6 +84,8 @@ pub struct AuthenticationConfig {
pub struct BaseAttributes {
pub name: String,
pub target_url: String,
#[serde(default)]
pub recovery: bool,
}
impl BaseAttributes {

View file

@ -4,8 +4,8 @@
use std::sync::{Arc, RwLock};
use installer::InstallerFramework;
use logging::LoggingErrors;
use crate::installer::InstallerFramework;
use crate::logging::LoggingErrors;
pub mod rest;
mod ui;
@ -16,7 +16,7 @@ pub fn launch(app_name: &str, is_launcher: bool, framework: InstallerFramework)
let (servers, address) = rest::server::spawn_servers(framework.clone());
ui::start_ui(app_name, &address, is_launcher);
ui::start_ui(app_name, &address, is_launcher).log_expect("Failed to start UI");
// Explicitly hint that we want the servers instance until here.
drop(servers);

View file

@ -2,7 +2,8 @@
extern crate mime_guess;
use self::mime_guess::{get_mime_type, octet_stream};
use self::mime_guess::from_ext;
use self::mime_guess::mime::APPLICATION_OCTET_STREAM;
macro_rules! include_files_as_assets {
( $target_match:expr, $( $file_name:expr ),* ) => {
@ -23,9 +24,9 @@ pub fn file_from_string(file_path: &str) -> Option<(String, &'static [u8])> {
Some(ext_ptr) => {
let ext = &file_path[ext_ptr + 1..];
get_mime_type(ext)
from_ext(ext).first_or_octet_stream()
}
None => octet_stream(),
None => APPLICATION_OCTET_STREAM,
};
let string_mime = guessed_mime.to_string();
@ -34,17 +35,21 @@ pub fn file_from_string(file_path: &str) -> Option<(String, &'static [u8])> {
file_path,
"/index.html",
"/favicon.ico",
"/img/logo.png",
"/img/light_mode_installer_logo.png",
"/img/dark_mode_installer_logo.png",
"/thicc_logo_installer__ea_shadow.png",
"/thicc_logo_installer_shadow.png",
"/img/how-to-open.png",
"/css/app.css",
"/css/chunk-vendors.css",
"/fonts/roboto-v18-latin-regular.eot",
"/fonts/roboto-v18-latin-regular.woff",
"/fonts/roboto-v18-latin-regular.woff2",
"/fonts/materialdesignicons-webfont.eot",
"/fonts/materialdesignicons-webfont.woff",
"/fonts/materialdesignicons-webfont.woff2",
"/js/chunk-vendors.js",
"/js/chunk-vendors.js.map",
"/js/app.js",
"/js/app.js.map"
"/js/app.js"
)?;
Some((string_mime, contents))

View file

@ -2,11 +2,11 @@
//!
//! Contains the over-arching server object + methods to manipulate it.
use frontend::rest::services::WebService;
use crate::frontend::rest::services::WebService;
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use hyper::server::Http;

View file

@ -2,27 +2,23 @@
//!
//! The /api/attr call returns an executable script containing session variables.
use frontend::rest::services::default_future;
use frontend::rest::services::encapsulate_json;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::Response;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::default_future;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use hyper::header::{ContentLength, ContentType};
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
pub fn handle(service: &WebService, _req: Request) -> Future {
let framework = service.get_framework_read();
let file = encapsulate_json(
"base_attributes",
&framework
.base_attributes
.to_json_str()
.log_expect("Failed to render JSON representation of config"),
);
let file = framework
.base_attributes
.to_json_str()
.log_expect("Failed to render JSON representation of config");
default_future(
Response::new()

View file

@ -2,27 +2,26 @@
//!
//! Provides mechanisms to authenticate users using JWT.
use std::collections::HashMap;
use std::collections::HashSet;
use std::sync::Arc;
use futures::{Future, Stream};
use hyper::header::{ContentLength, ContentType};
use jsonwebtoken::DecodingKey;
use jwt::{decode, Algorithm, Validation};
use reqwest::header::USER_AGENT;
use url::form_urlencoded;
use crate::frontend::rest::services::Future as InternalFuture;
use crate::frontend::rest::services::{default_future, Request, Response, WebService};
use frontend::rest::services::Future as InternalFuture;
use frontend::rest::services::{default_future, Request, Response, WebService};
use crate::http::{build_async_client, build_client};
use http::{build_async_client, build_client};
use crate::config::JWTValidation;
use config::JWTValidation;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
#[derive(Debug, Serialize, Deserialize)]
struct Auth {
@ -48,6 +47,12 @@ pub struct JWTClaims {
pub is_subscribed: bool,
}
#[derive(Debug, Serialize, Deserialize, Clone)]
struct AuthRequest {
username: String,
token: String,
}
/// Calls the given server to obtain a JWT token and returns a Future<String> with the response
pub fn authenticate_async(
url: String,
@ -126,32 +131,34 @@ pub fn validate_token(
let pub_key = if pub_key_base64.is_empty() {
vec![]
} else {
match base64::decode(&pub_key_base64) {
Ok(v) => v,
Err(err) => {
return Err(format!(
"Configured public key was not empty and did not decode as base64 {:?}",
err
));
}
}
base64::decode(&pub_key_base64).map_err(|e| {
format!(
"Configured public key was not empty and did not decode as base64 {:?}",
e
)
})?
};
// Configure validation for audience and issuer if the configuration provides it
let validation = match validation {
let mut validation = match validation {
Some(v) => {
let mut valid = Validation::new(Algorithm::RS256);
valid.iss = v.iss;
valid.iss = v.iss.map(|iss| {
let mut issuer = HashSet::new();
issuer.insert(iss);
issuer
});
if let &Some(ref v) = &v.aud {
valid.set_audience(v);
valid.set_audience(&[v]);
}
valid
}
None => Validation::default(),
};
validation.validate_exp = false;
validation.validate_nbf = false;
// Verify the JWT token
decode::<JWTClaims>(&body, pub_key.as_slice(), &validation)
decode::<JWTClaims>(&body, &DecodingKey::from_rsa_der(&pub_key), &validation)
.map(|tok| tok.claims)
.map_err(|err| {
format!(
@ -162,6 +169,7 @@ pub fn validate_token(
}
pub fn handle(service: &WebService, _req: Request) -> InternalFuture {
info!("Handling authentication");
let framework = service
.framework
.read()
@ -184,14 +192,19 @@ pub fn handle(service: &WebService, _req: Request) -> InternalFuture {
_req.body()
.concat2()
.map(move |body| {
let req = form_urlencoded::parse(body.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
let req = serde_json::from_slice::<AuthRequest>(&body);
if req.is_err() {
warn!("Failed to parse auth request from the frontend");
return default_future(
Response::new().with_status(hyper::StatusCode::BadRequest),
);
}
let req = req.unwrap();
// Determine which credentials we should use
let (username, token) = {
let req_username = req.get("username").log_expect("No username in request");
let req_token = req.get("token").log_expect("No token in request");
let req_username = req.username;
let req_token = req.token;
// if the user didn't provide credentials, and theres nothing stored in the
// database, return an early error

View file

@ -2,20 +2,21 @@
//!
//! Launches the user's web browser on request from the frontend.
use frontend::rest::services::Future as InternalFuture;
use frontend::rest::services::{Request, Response, WebService};
use crate::frontend::rest::services::Future as InternalFuture;
use crate::frontend::rest::services::{Request, Response, WebService};
use crate::logging::LoggingErrors;
use futures::{Future, Stream};
use hyper::header::ContentType;
use logging::LoggingErrors;
use std::collections::HashMap;
use url::form_urlencoded;
#[derive(Debug, Serialize, Deserialize, Clone)]
struct OpenRequest {
url: String,
}
pub fn handle(_service: &WebService, req: Request) -> InternalFuture {
Box::new(req.body().concat2().map(move |body| {
let req = form_urlencoded::parse(body.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
if webbrowser::open(req.get("url").log_expect("No URL to launch")).is_ok() {
let req: OpenRequest = serde_json::from_slice(&body).log_expect("Malformed request");
if webbrowser::open(&req.url).is_ok() {
Response::new()
.with_status(hyper::Ok)
.with_header(ContentType::json())

View file

@ -4,18 +4,18 @@
//!
//! This endpoint should be usable directly from a <script> tag during loading.
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::Response;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use hyper::header::{ContentLength, ContentType};
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use config::Config;
use crate::config::Config;
use http::build_async_client;
use crate::http::build_async_client;
use futures::stream::Stream;
use futures::Future as _;

View file

@ -2,17 +2,17 @@
//!
//! This call returns if dark mode is enabled on the system currently.
use frontend::rest::services::default_future;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::Response;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::default_future;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use hyper::header::{ContentLength, ContentType};
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use native::is_dark_mode_active;
use crate::native::is_dark_mode_active;
pub fn handle(_service: &WebService, _req: Request) -> Future {
let file = serde_json::to_string(&is_dark_mode_active())

View file

@ -2,15 +2,15 @@
//!
//! The /api/default-path returns the default path for the application to install into.
use frontend::rest::services::default_future;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::Response;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::default_future;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use hyper::header::{ContentLength, ContentType};
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
/// Struct used by serde to send a JSON payload to the client containing an optional value.
#[derive(Serialize)]

View file

@ -2,11 +2,11 @@
//!
//! The /api/exit closes down the application.
use frontend::rest::services::default_future;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::Response;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::default_future;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use hyper::header::ContentType;
use hyper::StatusCode;

View file

@ -2,14 +2,14 @@
//!
//! The /api/install call installs a set of packages dictated by a POST request.
use frontend::rest::services::stream_progress;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::stream_progress;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::WebService;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use installer::InstallMessage;
use crate::installer::InstallMessage;
use futures::future::Future as _;
use futures::stream::Stream;
@ -28,12 +28,23 @@ pub fn handle(service: &WebService, req: Request) -> Future {
let mut to_install = Vec::new();
let mut path: Option<String> = None;
let mut force_install = false;
let mut install_desktop_shortcut = false;
// Transform results into just an array of stuff to install
for (key, value) in &results {
if key == "path" {
path = Some(value.to_owned());
continue;
} else if key == "installDesktopShortcut" {
info!("Found installDesktopShortcut {:?}", value);
install_desktop_shortcut = value == "true";
continue;
}
if key == "mode" && value == "force" {
force_install = true;
continue;
}
if value == "true" {
@ -41,6 +52,19 @@ pub fn handle(service: &WebService, req: Request) -> Future {
}
}
if !install_desktop_shortcut {
let framework_ref = framework
.read()
.log_expect("InstallerFramework has been dirtied");
install_desktop_shortcut = framework_ref.preexisting_install
&& framework_ref
.database
.packages
.first()
.and_then(|x| Some(x.shortcuts.len() > 1))
.unwrap_or(false);
}
// The frontend always provides this
let path =
path.log_expect("No path specified by frontend when one should have already existed");
@ -55,7 +79,13 @@ pub fn handle(service: &WebService, req: Request) -> Future {
framework.set_install_dir(&path);
}
if let Err(v) = framework.install(to_install, &sender, new_install) {
if let Err(v) = framework.install(
to_install,
&sender,
new_install,
install_desktop_shortcut,
force_install,
) {
error!("Install error occurred: {:?}", v);
if let Err(v) = sender.send(InstallMessage::Error(v)) {
error!("Failed to send install error: {:?}", v);

View file

@ -5,15 +5,15 @@
//!
//! e.g. if the application is in maintenance mode
use frontend::rest::services::default_future;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::Response;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::default_future;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use hyper::header::{ContentLength, ContentType};
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
pub fn handle(service: &WebService, _req: Request) -> Future {
let framework = service.get_framework_read();

View file

@ -4,12 +4,12 @@
use std::sync::{Arc, RwLock, RwLockReadGuard, RwLockWriteGuard};
use installer::{InstallMessage, InstallerFramework};
use crate::installer::{InstallMessage, InstallerFramework};
use hyper::server::Service;
use hyper::{Method, StatusCode};
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use std::sync::mpsc::{channel, Sender};
@ -33,6 +33,8 @@ mod packages;
mod static_files;
mod uninstall;
mod update_updater;
mod verify_path;
mod view_folder;
/// Expected incoming Request format from Hyper.
pub type Request = hyper::server::Request;
@ -139,11 +141,13 @@ impl Service for WebService {
(Method::Get, "/api/exit") => exit::handle(self, req),
(Method::Get, "/api/packages") => packages::handle(self, req),
(Method::Get, "/api/installation-status") => installation_status::handle(self, req),
(Method::Get, "/api/view-local-folder") => view_folder::handle(self, req),
(Method::Post, "/api/check-auth") => authentication::handle(self, req),
(Method::Post, "/api/start-install") => install::handle(self, req),
(Method::Post, "/api/open-browser") => browser::handle(self, req),
(Method::Post, "/api/uninstall") => uninstall::handle(self, req),
(Method::Post, "/api/update-updater") => update_updater::handle(self, req),
(Method::Post, "/api/verify-path") => verify_path::handle(self, req),
(Method::Get, _) => static_files::handle(self, req),
e => {
info!("Returned 404 for {:?}", e);

View file

@ -2,16 +2,16 @@
//!
//! The /api/packages call returns all the currently installed packages.
use frontend::rest::services::default_future;
use frontend::rest::services::encapsulate_json;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::Response;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::default_future;
use crate::frontend::rest::services::encapsulate_json;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use hyper::header::{ContentLength, ContentType};
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
pub fn handle(service: &WebService, _req: Request) -> Future {
let framework = service.get_framework_read();

View file

@ -4,18 +4,18 @@
//!
//! e.g. index.html, main.js, ...
use frontend::rest::assets;
use crate::frontend::rest::assets;
use frontend::rest::services::default_future;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::Response;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::default_future;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use hyper::header::{ContentLength, ContentType};
use hyper::StatusCode;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
pub fn handle(_service: &WebService, req: Request) -> Future {
// At this point, we have a web browser client. Search for a index page

View file

@ -2,15 +2,15 @@
//!
//! The /api/uninstall call uninstalls all packages.
use frontend::rest::services::default_future;
use frontend::rest::services::stream_progress;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::default_future;
use crate::frontend::rest::services::stream_progress;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::WebService;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use installer::InstallMessage;
use crate::installer::InstallMessage;
pub fn handle(service: &WebService, _req: Request) -> Future {
let framework = service.framework.clone();

View file

@ -2,15 +2,15 @@
//!
//! The /api/update-updater call attempts to update the currently running updater.
use frontend::rest::services::default_future;
use frontend::rest::services::stream_progress;
use frontend::rest::services::Future;
use frontend::rest::services::Request;
use frontend::rest::services::WebService;
use crate::frontend::rest::services::default_future;
use crate::frontend::rest::services::stream_progress;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::WebService;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use installer::InstallMessage;
use crate::installer::InstallMessage;
pub fn handle(service: &WebService, _req: Request) -> Future {
let framework = service.framework.clone();

View file

@ -0,0 +1,48 @@
//! frontend/rest/services/verify_path.rs
//!
//! The /api/verify-path returns whether the path exists or not.
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use url::form_urlencoded;
use hyper::header::{ContentLength, ContentType};
use futures::future::Future as _;
use futures::stream::Stream;
use crate::logging::LoggingErrors;
use std::collections::HashMap;
use std::path::PathBuf;
/// Struct used by serde to send a JSON payload to the client containing an optional value.
#[derive(Serialize)]
struct VerifyResponse {
exists: bool,
}
pub fn handle(_service: &WebService, req: Request) -> Future {
Box::new(req.body().concat2().map(move |b| {
let results = form_urlencoded::parse(b.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
let mut exists = false;
if let Some(path) = results.get("path") {
let path = PathBuf::from(path);
exists = path.is_dir();
}
let response = VerifyResponse { exists };
let file = serde_json::to_string(&response)
.log_expect("Failed to render JSON payload of default path object");
Response::new()
.with_header(ContentLength(file.len() as u64))
.with_header(ContentType::json())
.with_body(file)
}))
}

View file

@ -0,0 +1,35 @@
//! frontend/rest/services/view_folder.rs
//!
//! The /api/view-local-folder returns whether the path exists or not.
//! Side-effect: will open the folder in the default file manager if it exists.
use super::default_future;
use crate::frontend::rest::services::Future;
use crate::frontend::rest::services::Request;
use crate::frontend::rest::services::Response;
use crate::frontend::rest::services::WebService;
use hyper::header::{ContentLength, ContentType};
use crate::logging::LoggingErrors;
use crate::native::open_in_shell;
pub fn handle(service: &WebService, _: Request) -> Future {
let framework = service.get_framework_read();
let mut response = false;
let path = framework.install_path.clone();
if let Some(path) = path {
response = true;
open_in_shell(path.as_path());
}
let file = serde_json::to_string(&response)
.log_expect("Failed to render JSON payload of installation status object");
default_future(
Response::new()
.with_header(ContentLength(file.len() as u64))
.with_header(ContentType::json())
.with_body(file),
)
}

View file

@ -2,70 +2,85 @@
//!
//! Provides a web-view UI.
use web_view::Content;
use logging::LoggingErrors;
use anyhow::Result;
use wry::{
application::{
dpi::LogicalSize,
event::{Event, StartCause, WindowEvent},
event_loop::{ControlFlow, EventLoop},
window::{Icon, WindowBuilder},
},
webview::{RpcResponse, WebViewBuilder},
};
use log::Level;
#[derive(Deserialize, Debug)]
enum CallbackType {
SelectInstallDir { callback_name: String },
Log { msg: String, kind: String },
Test {},
}
use crate::logging::LoggingErrors;
const ICON_DATA: &[u8] = include_bytes!(concat!(env!("OUT_DIR"), "/icon-data.bin"));
/// Starts the main web UI. Will return when UI is closed.
pub fn start_ui(app_name: &str, http_address: &str, _is_launcher: bool) {
let size = (1024, 550);
pub fn start_ui(app_name: &str, http_address: &str, is_launcher: bool) -> Result<()> {
#[cfg(windows)]
{
crate::native::prepare_install_webview2(app_name).log_expect("Unable to install webview2");
}
let size = if is_launcher {
(600.0, 300.0)
} else {
(1024.0, 600.0)
};
info!("Spawning web view instance");
web_view::builder()
.title(&format!("{} Installer", app_name))
.content(Content::Url(http_address))
.size(size.0, size.1)
.resizable(false)
.debug(false)
.user_data(())
.invoke_handler(|wv, msg| {
let mut cb_result = Ok(());
let command: CallbackType =
serde_json::from_str(msg).log_expect(&format!("Unable to parse string: {:?}", msg));
debug!("Incoming payload: {:?}", command);
match command {
CallbackType::SelectInstallDir { callback_name } => {
let result = wv
.dialog()
.choose_directory("Select a install directory...", "");
if let Ok(Some(new_path)) = result {
if new_path.to_string_lossy().len() > 0 {
let result = serde_json::to_string(&new_path)
.log_expect("Unable to serialize response");
let command = format!("{}({});", callback_name, result);
debug!("Injecting response: {}", command);
cb_result = wv.eval(&command);
let window_icon =
Icon::from_rgba(ICON_DATA.to_vec(), 48, 48).log_expect("Unable to construct window icon");
let event_loop = EventLoop::new();
let window = WindowBuilder::new()
.with_title(format!("{} Installer", app_name))
.with_window_icon(Some(window_icon))
.with_inner_size(LogicalSize::new(size.0, size.1))
.with_resizable(false)
.build(&event_loop)?;
let _webview = WebViewBuilder::new(window)?
.with_url(http_address)?
.with_rpc_handler(|_, mut event| {
debug!("Incoming payload: {:?}", event);
match event.method.as_str() {
"Test" => (),
"Log" => {
if let Some(msg) = event.params.take() {
if let Ok(msg) = serde_json::from_value::<(String, String)>(msg) {
let kind = match msg.0.as_str() {
"info" | "log" => Level::Info,
"warn" => Level::Warn,
_ => Level::Error,
};
log!(target: "liftinstall::frontend::js", kind, "{}", msg.1);
}
}
}
CallbackType::Log { msg, kind } => {
let kind = match kind.as_ref() {
"info" | "log" => Level::Info,
"warn" => Level::Warn,
"error" => Level::Error,
_ => Level::Error,
};
log!(target: "liftinstall::frontend::js", kind, "{}", msg);
"SelectInstallDir" => {
let result =
tinyfiledialogs::select_folder_dialog("Select a install directory...", "")
.and_then(|v| serde_json::to_value(v).ok());
return Some(RpcResponse::new_result(event.id, result));
}
CallbackType::Test {} => {}
_ => warn!("Unknown RPC method: {}", event.method),
}
cb_result
None
})
.run()
.log_expect("Unable to launch Web UI!");
.build()?;
event_loop.run(move |event, _, control_flow| {
*control_flow = ControlFlow::Wait;
match event {
Event::NewEvents(StartCause::Init) => info!("Webview started"),
Event::WindowEvent {
event: WindowEvent::CloseRequested,
..
} => *control_flow = ControlFlow::Exit,
_ => (),
}
});
}

View file

@ -7,7 +7,7 @@ use reqwest::header::CONTENT_LENGTH;
use std::io::Read;
use std::time::Duration;
use reqwest::async::Client as AsyncClient;
use reqwest::r#async::Client as AsyncClient;
use reqwest::Client;
use reqwest::StatusCode;

View file

@ -21,28 +21,29 @@ use std::io::Cursor;
use std::process::Command;
use std::process::{exit, Stdio};
use config::BaseAttributes;
use config::Config;
use crate::config::BaseAttributes;
use crate::config::Config;
use sources::types::Version;
use crate::sources::types::Version;
use tasks::install::InstallTask;
use tasks::uninstall::UninstallTask;
use tasks::uninstall_global_shortcut::UninstallGlobalShortcutsTask;
use tasks::DependencyTree;
use tasks::TaskMessage;
use crate::tasks::install::InstallTask;
use crate::tasks::uninstall::UninstallTask;
use crate::tasks::uninstall_global_shortcut::UninstallGlobalShortcutsTask;
use crate::tasks::DependencyTree;
use crate::tasks::TaskMessage;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use dirs::home_dir;
use std::collections::HashSet;
use std::fs::remove_file;
use http;
use crate::http;
use number_prefix::{NumberPrefix, Prefixed, Standalone};
use number_prefix::NumberPrefix::{self, Prefixed, Standalone};
use native;
use crate::native;
/// A message thrown during the installation of packages.
#[derive(Serialize)]
@ -97,6 +98,7 @@ pub struct InstallerFramework {
// If we just completed an uninstall, and we should clean up after ourselves.
pub burn_after_exit: bool,
pub launcher_path: Option<String>,
pub is_windows: bool,
}
/// Contains basic properties on the status of the session. Subset of InstallationFramework.
@ -117,7 +119,7 @@ pub struct LocalInstallation {
/// Relative paths to generated files
pub files: Vec<String>,
/// Absolute paths to generated shortcut files
pub shortcuts: Vec<String>,
pub shortcuts: HashSet<String>,
}
macro_rules! declare_messenger_callback {
@ -174,11 +176,14 @@ impl InstallerFramework {
/// items: Array of named packages to be installed/kept
/// messages: Channel used to send progress messages
/// fresh_install: If the install directory must be empty
/// force_install: If the install directory should be erased first
pub fn install(
&mut self,
items: Vec<String>,
messages: &Sender<InstallMessage>,
fresh_install: bool,
create_desktop_shortcuts: bool,
force_install: bool,
) -> Result<(), String> {
info!(
"Framework: Installing {:?} to {:?}",
@ -207,6 +212,8 @@ impl InstallerFramework {
items,
uninstall_items,
fresh_install,
create_desktop_shortcuts,
force_install,
});
let mut tree = DependencyTree::build(task);
@ -451,6 +458,25 @@ impl InstallerFramework {
is_launcher: false,
burn_after_exit: false,
launcher_path: None,
is_windows: cfg!(windows),
}
}
/// The special recovery mode for the Installer Framework.
pub fn new_recovery_mode(attrs: BaseAttributes, install_path: &Path) -> Self {
InstallerFramework {
base_attributes: BaseAttributes {
recovery: true,
..attrs
},
config: None,
database: InstallationDatabase::new(),
install_path: Some(install_path.to_path_buf()),
preexisting_install: true,
is_launcher: false,
burn_after_exit: false,
launcher_path: None,
is_windows: cfg!(windows),
}
}
@ -478,6 +504,7 @@ impl InstallerFramework {
is_launcher: false,
burn_after_exit: false,
launcher_path: None,
is_windows: cfg!(windows),
})
}
}

View file

@ -7,7 +7,7 @@
#![deny(unsafe_code)]
#![deny(missing_docs)]
extern crate web_view;
extern crate wry;
extern crate futures;
extern crate hyper;
@ -65,13 +65,16 @@ mod tasks;
use installer::InstallerFramework;
use logging::LoggingErrors;
use std::path::PathBuf;
use clap::App;
use clap::Arg;
use config::BaseAttributes;
use std::fs;
use std::process::{exit, Command, Stdio};
static RAW_CONFIG: &'static str = include_str!(concat!(env!("OUT_DIR"), "/bootstrap.toml"));
const RAW_CONFIG: &str = include_str!(concat!(env!("OUT_DIR"), "/bootstrap.toml"));
fn main() {
let config = BaseAttributes::from_toml_str(RAW_CONFIG).expect("Config file could not be read");
@ -112,6 +115,7 @@ fn main() {
.parent()
.log_expect("Parent directory of executable could not be found");
// Handle self-updating if needed
self_update::perform_swap(&current_exe, matches.value_of("swap"));
if let Some(new_matches) = self_update::check_args(reinterpret_app, current_path) {
matches = new_matches;
@ -119,15 +123,29 @@ fn main() {
self_update::cleanup(current_path);
// Load in metadata + setup the installer framework
let mut fresh_install = false;
let metadata_file = current_path.join("metadata.json");
let mut framework = if metadata_file.exists() {
info!("Using pre-existing metadata file: {:?}", metadata_file);
InstallerFramework::new_with_db(config, current_path).log_expect("Unable to parse metadata")
InstallerFramework::new_with_db(config.clone(), current_path).unwrap_or_else(|e| {
error!("Failed to load metadata: {:?}", e);
warn!("Entering recovery mode");
InstallerFramework::new_recovery_mode(config, current_path)
})
} else {
info!("Starting fresh install");
fresh_install = true;
InstallerFramework::new(config)
};
// check for existing installs if we are running as a fresh install
let installed_path = PathBuf::from(framework.get_default_path().unwrap());
if fresh_install && installed_path.join("metadata.json").exists() {
info!("Existing install detected! Copying Trying to launch this install instead");
// Ignore the return value from this since it should exit the application if its successful
let _ = replace_existing_install(&current_exe, &installed_path);
}
let is_launcher = if let Some(string) = matches.value_of("launcher") {
framework.is_launcher = true;
framework.launcher_path = Some(string.to_string());
@ -139,3 +157,50 @@ fn main() {
// Start up the UI
frontend::launch(&app_name, is_launcher, framework);
}
fn replace_existing_install(current_exe: &PathBuf, installed_path: &PathBuf) -> Result<(), String> {
// Generate installer path
let platform_extension = if cfg!(windows) {
"maintenancetool.exe"
} else {
"maintenancetool"
};
let new_tool = if cfg!(windows) {
"maintenancetool_new.exe"
} else {
"maintenancetool_new"
};
if let Err(v) = fs::copy(current_exe, installed_path.join(new_tool)) {
return Err(format!("Unable to copy installer binary: {:?}", v));
}
let existing = installed_path
.join(platform_extension)
.into_os_string()
.into_string();
let new = installed_path.join(new_tool).into_os_string().into_string();
if existing.is_ok() && new.is_ok() {
// Remove NTFS alternate stream which tells the operating system that the updater was downloaded from the internet
if cfg!(windows) {
let _ = fs::remove_file(
installed_path.join("maintenancetool_new.exe:Zone.Identifier:$DATA"),
);
}
info!("Launching {:?}", existing);
let success = Command::new(new.unwrap())
.arg("--swap")
.arg(existing.unwrap())
.stdout(Stdio::null())
.stderr(Stdio::null())
.spawn();
if success.is_ok() {
exit(0);
} else {
error!("Unable to start existing yuzu maintenance tool. Launching old one instead");
}
}
Ok(())
}

View file

@ -43,9 +43,10 @@ extern "C" int saveShortcut(
const wchar_t *description,
const wchar_t *path,
const wchar_t *args,
const wchar_t *workingDir)
const wchar_t *workingDir,
const wchar_t *exePath)
{
const char *errStr = NULL;
char *errStr = NULL;
HRESULT h;
IShellLink *shellLink = NULL;
IPersistFile *persistFile = NULL;
@ -82,6 +83,9 @@ extern "C" int saveShortcut(
shellLink->SetDescription(description);
if (path != NULL)
shellLink->SetPath(path);
// default to using the first icon in the exe (usually correct)
if (exePath != NULL)
shellLink->SetIconLocation(exePath, 0);
if (args != NULL)
shellLink->SetArguments(args);
if (workingDir != NULL)
@ -95,6 +99,10 @@ extern "C" int saveShortcut(
goto err;
}
// Notify that a new shortcut was created using the shell api
SHChangeNotify(SHCNE_CREATE, SHCNF_PATH, shortcutPath, NULL);
SHChangeNotify(SHCNE_UPDATEITEM, SHCNF_PATH, shortcutPath, NULL);
persistFile->Release();
shellLink->Release();
CoUninitialize();
@ -160,3 +168,15 @@ extern "C" HRESULT getSystemFolder(wchar_t *out_path)
}
return result;
}
extern "C" HRESULT getDesktopFolder(wchar_t *out_path)
{
PWSTR path = NULL;
HRESULT result = SHGetKnownFolderPath(FOLDERID_Desktop, 0, NULL, &path);
if (result == S_OK)
{
wcscpy_s(out_path, MAX_PATH + 1, path);
CoTaskMemFree(path);
}
return result;
}

View file

@ -14,10 +14,14 @@ mod natives {
#![allow(non_snake_case)]
const PROCESS_LEN: usize = 10192;
const WV2_INSTALLER_DATA: &[u8] = include_bytes!("../../MicrosoftEdgeWebview2Setup.exe");
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use std::env;
use std::io::Write;
use std::os::windows::ffi::OsStrExt;
use std::path::Path;
use winapi::shared::minwindef::{DWORD, FALSE, MAX_PATH};
@ -26,10 +30,16 @@ mod natives {
use winapi::um::psapi::{
EnumProcessModulesEx, GetModuleFileNameExW, K32EnumProcesses, LIST_MODULES_ALL,
};
use winapi::um::shellapi::ShellExecuteW;
use winapi::um::winnt::{
HANDLE, PROCESS_QUERY_INFORMATION, PROCESS_TERMINATE, PROCESS_VM_READ,
};
use winapi::um::winuser::SW_SHOWDEFAULT;
use std::process::Command;
use tempfile::Builder;
use tinyfiledialogs::{message_box_yes_no, MessageBoxIcon, YesNo};
use webview2::EnvironmentBuilder;
use widestring::U16CString;
extern "C" {
@ -39,6 +49,7 @@ mod natives {
path: *const winapi::ctypes::wchar_t,
args: *const winapi::ctypes::wchar_t,
workingDir: *const winapi::ctypes::wchar_t,
exePath: *const winapi::ctypes::wchar_t,
) -> ::std::os::raw::c_int;
pub fn isDarkThemeActive() -> ::std::os::raw::c_uint;
@ -49,6 +60,36 @@ mod natives {
) -> ::std::os::raw::c_int;
pub fn getSystemFolder(out_path: *mut ::std::os::raw::c_ushort) -> HRESULT;
pub fn getDesktopFolder(out_path: *mut ::std::os::raw::c_ushort) -> HRESULT;
}
pub fn prepare_install_webview2(name: &str) -> Result<(), String> {
if EnvironmentBuilder::default()
.get_available_browser_version_string()
.is_ok()
{
return Ok(());
}
if message_box_yes_no(&format!("{} installer", name), &format!("{} installer now requires Webview2 runtime to function properly.\nDo you wish to install it now?", name), MessageBoxIcon::Question, YesNo::Yes) == YesNo::No {
std::process::exit(1);
}
let mut installer_file = Builder::new()
.suffix(".exe")
.tempfile()
.log_expect("Unable to open the webview2 installer file");
installer_file
.write_all(&WV2_INSTALLER_DATA)
.log_expect("Unable to write the webview2 installer file");
let path = installer_file.path().to_owned();
installer_file.keep().log_unwrap();
Command::new(&path)
.arg("/install")
.spawn()
.log_expect("Unable to run the webview2 installer")
.wait()
.log_unwrap();
Ok(())
}
// Needed here for Windows interop
@ -59,13 +100,63 @@ mod natives {
target: &str,
args: &str,
working_dir: &str,
exe_path: &str,
) -> Result<String, String> {
let source_file = format!(
"{}\\Microsoft\\Windows\\Start Menu\\Programs\\{}.lnk",
env::var("APPDATA").log_expect("APPDATA is bad, apparently"),
name
);
create_shortcut_inner(
source_file,
name,
description,
target,
args,
working_dir,
exe_path,
)
}
// Needed here for Windows interop
#[allow(unsafe_code)]
pub fn create_desktop_shortcut(
name: &str,
description: &str,
target: &str,
args: &str,
working_dir: &str,
exe_path: &str,
) -> Result<String, String> {
let mut cmd_path = [0u16; MAX_PATH + 1];
let _result = unsafe { getDesktopFolder(cmd_path.as_mut_ptr()) };
let source_path = format!(
"{}\\{}.lnk",
String::from_utf16_lossy(&cmd_path[..count_u16(&cmd_path)]).as_str(),
name
);
create_shortcut_inner(
source_path,
name,
description,
target,
args,
working_dir,
exe_path,
)
}
// Needed here for Windows interop
#[allow(unsafe_code)]
fn create_shortcut_inner(
source_file: String,
_name: &str,
description: &str,
target: &str,
args: &str,
working_dir: &str,
exe_path: &str,
) -> Result<String, String> {
info!("Generating shortcut @ {:?}", source_file);
let native_target_dir = U16CString::from_str(source_file.clone())
@ -78,6 +169,8 @@ mod natives {
U16CString::from_str(args).log_expect("Error while converting to wchar_t");
let native_working_dir =
U16CString::from_str(working_dir).log_expect("Error while converting to wchar_t");
let native_exe_path =
U16CString::from_str(exe_path).log_expect("Error while converting to wchar_t");
let shortcutResult = unsafe {
saveShortcut(
@ -86,6 +179,7 @@ mod natives {
native_target.as_ptr(),
native_args.as_ptr(),
native_working_dir.as_ptr(),
native_exe_path.as_ptr(),
)
};
@ -98,6 +192,37 @@ mod natives {
}
}
// Needed to call unsafe function `ShellExecuteW` from `winapi` crate
#[allow(unsafe_code)]
pub fn open_in_shell(path: &Path) {
let native_verb = U16CString::from_str("open").unwrap();
// https://doc.rust-lang.org/std/os/windows/ffi/trait.OsStrExt.html#tymethod.encode_wide
let mut native_path: Vec<u16> = path.as_os_str().encode_wide().collect();
native_path.push(0); // NULL terminator
unsafe {
ShellExecuteW(
std::ptr::null_mut(),
native_verb.as_ptr(),
native_path.as_ptr(),
std::ptr::null_mut(),
std::ptr::null_mut(),
SW_SHOWDEFAULT,
);
}
}
#[inline]
fn count_u16(u16str: &[u16]) -> usize {
let mut pos = 0;
for x in u16str.iter() {
if *x == 0 {
break;
}
pos += 1;
}
pos
}
/// Cleans up the installer
pub fn burn_on_exit(app_name: &str) {
let current_exe = env::current_exe().log_expect("Current executable could not be found");
@ -111,6 +236,7 @@ mod natives {
.to_str()
.log_expect("Unable to convert tool path to string")
.replace(" ", "\\ ");
let tool_wv = format!("{}.WebView2", tool);
let log = path.join(format!("{}_installer.log", app_name));
let log = log
@ -118,7 +244,15 @@ mod natives {
.log_expect("Unable to convert log path to string")
.replace(" ", "\\ ");
let target_arguments = format!("/C choice /C Y /N /D Y /T 2 & del {} {}", tool, log);
let install_path = path
.to_str()
.log_expect("Unable to convert path to string")
.replace(" ", "\\ ");
let target_arguments = format!(
"/C choice /C Y /N /D Y /T 2 & del {} {} & rmdir /Q /S {} & rmdir {}",
tool, log, tool_wv, install_path
);
info!("Launching cmd with {:?}", target_arguments);
@ -248,19 +382,21 @@ mod natives {
#[cfg(not(windows))]
mod natives {
use std::fs::remove_file;
use std::fs::{remove_dir, remove_file};
use std::env;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use sysinfo::{ProcessExt, SystemExt};
use sysinfo::{PidExt, ProcessExt, SystemExt};
use dirs;
use slug::slugify;
use std::fs::{create_dir_all, File};
use std::io::Write;
use std::path::Path;
use std::process::Command;
#[cfg(target_os = "linux")]
pub fn create_shortcut(
@ -269,6 +405,7 @@ mod natives {
target: &str,
args: &str,
working_dir: &str,
exe_path: &str,
) -> Result<String, String> {
// FIXME: no icon will be shown since no icon is provided
let data_local_dir = dirs::data_local_dir();
@ -277,7 +414,7 @@ mod natives {
let mut path = x;
path.push("applications");
match create_dir_all(path.to_path_buf()) {
Ok(_) => (()),
Ok(_) => (),
Err(e) => {
return Err(format!(
"Local data directory does not exist and cannot be created: {}",
@ -285,17 +422,17 @@ mod natives {
));
}
};
path.push(format!("{}.desktop", slugify(name))); // file name
path.push(format!("yuzu-maintenance-tool_{}.desktop", slugify(name))); // file name
let desktop_file = format!(
"[Desktop Entry]\nName={}\nExec=\"{}\" {}\nComment={}\nPath={}\n",
name, target, args, description, working_dir
"[Desktop Entry]\nType=Application\nName={}\nExec=\"{}\" {}\nComment={}\nPath={}\nIcon=yuzu\n",
name, target, args, description, working_dir
);
let desktop_f = File::create(path);
let mut desktop_f = match desktop_f {
Ok(file) => file,
Err(e) => return Err(format!("Unable to create desktop file: {}", e)),
};
let mut desktop_f = desktop_f.write_all(desktop_file.as_bytes());
let desktop_f = desktop_f.write_all(desktop_file.as_bytes());
match desktop_f {
Ok(_) => Ok("".to_string()),
Err(e) => Err(format!("Unable to write desktop file: {}", e)),
@ -313,27 +450,44 @@ mod natives {
target: &str,
args: &str,
working_dir: &str,
_exe_path: &str,
) -> Result<String, String> {
warn!("STUB! Creating shortcut is not implemented on macOS");
Ok("".to_string())
}
pub fn open_in_shell(path: &Path) {
let shell: &str;
if cfg!(target_os = "linux") {
shell = "xdg-open";
} else if cfg!(target_os = "macos") {
shell = "open";
} else {
warn!("Unsupported platform");
return;
}
Command::new(shell).arg(path).spawn().ok();
}
/// Cleans up the installer
pub fn burn_on_exit(app_name: &str) {
let current_exe = env::current_exe().log_expect("Current executable could not be found");
let exe_dir = current_exe
.parent()
.log_expect("Current executable directory cannot be found");
if let Err(e) = remove_file(exe_dir.join(format!("{}_installer.log", app_name))) {
// No regular logging now.
eprintln!("Failed to delete maintenance log: {:?}", e);
};
// Thank god for *nix platforms
if let Err(e) = remove_file(&current_exe) {
// No regular logging now.
eprintln!("Failed to delete maintenancetool: {:?}", e);
};
let current_dir = env::current_dir().log_expect("Current directory cannot be found");
if let Err(e) = remove_file(current_dir.join(format!("{}_installer.log", app_name))) {
// No regular logging now.
eprintln!("Failed to delete installer log: {:?}", e);
};
// delete the directory if not empty and ignore errors (since we can't handle errors anymore)
remove_dir(exe_dir).ok();
}
/// Returns a list of running processes
@ -342,9 +496,9 @@ mod natives {
let mut processes: Vec<super::Process> = Vec::new();
let mut system = sysinfo::System::new();
system.refresh_all();
for (pid, procs) in system.get_process_list() {
for (pid, procs) in system.processes() {
processes.push(super::Process {
pid: *pid as usize,
pid: pid.as_u32() as usize,
name: procs.name().to_string(),
});
}

View file

@ -9,7 +9,7 @@ use std::{thread, time};
use clap::{App, ArgMatches};
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
/// Swaps around the main executable if needed.
pub fn perform_swap(current_exe: &PathBuf, to_path: Option<&str>) {

View file

@ -7,9 +7,9 @@ use reqwest::StatusCode;
use serde_json;
use sources::types::*;
use crate::sources::types::*;
use http::build_client;
use crate::http::build_client;
pub struct GithubReleases {}

View file

@ -2,10 +2,10 @@
//!
//! Contains the yuzu-emu core API implementation of a release source.
use http::build_client;
use crate::http::build_client;
use crate::sources::types::*;
use reqwest::header::USER_AGENT;
use reqwest::StatusCode;
use sources::types::*;
pub struct PatreonReleases {}

View file

@ -23,9 +23,7 @@ impl Version {
fn coarse_into_semver(&self) -> SemverVersion {
match *self {
Version::Semver(ref version) => version.to_owned(),
Version::Integer(ref version) => {
SemverVersion::from((version.to_owned(), 0 as u64, 0 as u64))
}
Version::Integer(ref version) => SemverVersion::new(version.to_owned(), 0u64, 0u64),
}
}

View file

@ -1,13 +1,13 @@
//! Validates that users have correct authorization to download packages.
use frontend::rest::services::authentication;
use crate::frontend::rest::services::authentication;
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use tasks::resolver::ResolvePackageTask;
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
use crate::tasks::resolver::ResolvePackageTask;
use crate::tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
pub struct CheckAuthorizationTask {
pub name: String,
@ -22,7 +22,9 @@ impl Task for CheckAuthorizationTask {
) -> Result<TaskParamType, String> {
assert_eq!(input.len(), 1);
let params = input.pop().log_expect("Should have input from resolver!");
let params = input
.pop()
.log_expect("Check Authorization Task should have input from resolver!");
let (version, file) = match params {
TaskParamType::File(v, f) => Ok((v, f)),
_ => Err("Unexpected TaskParamType in CheckAuthorization: {:?}"),

View file

@ -1,15 +1,19 @@
//! Downloads a package into memory.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::check_authorization::CheckAuthorizationTask;
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
use crate::tasks::check_authorization::CheckAuthorizationTask;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskOrdering;
use crate::tasks::TaskParamType;
use http::stream_file;
use crate::http::stream_file;
use number_prefix::{NumberPrefix, Prefixed, Standalone};
use number_prefix::NumberPrefix::{self, Prefixed, Standalone};
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
pub struct DownloadPackageTask {
pub name: String,
@ -24,7 +28,9 @@ impl Task for DownloadPackageTask {
) -> Result<TaskParamType, String> {
assert_eq!(input.len(), 1);
let file = input.pop().log_expect("Should have input from resolver!");
let file = input
.pop()
.log_expect("Download Package Task should have input from resolver!");
let (version, file, auth) = match file {
TaskParamType::Authentication(v, f, auth) => (v, f, auth),
_ => return Err("Unexpected param type to download package".to_string()),

View file

@ -1,14 +1,14 @@
//! Verifies that this is the only running instance of the installer, and that no application is running.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use native::get_process_names;
use native::Process;
use crate::native::get_process_names;
use crate::native::Process;
use std::process;

View file

@ -1,24 +1,29 @@
//! Overall hierarchy for installing a installation of the application.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::ensure_only_instance::EnsureOnlyInstanceTask;
use tasks::install_dir::VerifyInstallDirTask;
use tasks::install_global_shortcut::InstallGlobalShortcutsTask;
use tasks::install_pkg::InstallPackageTask;
use tasks::save_executable::SaveExecutableTask;
use tasks::uninstall_pkg::UninstallPackageTask;
use crate::tasks::ensure_only_instance::EnsureOnlyInstanceTask;
use crate::tasks::install_dir::VerifyInstallDirTask;
use crate::tasks::install_global_shortcut::InstallGlobalShortcutsTask;
use crate::tasks::install_pkg::InstallPackageTask;
use crate::tasks::launch_installed_on_exit::LaunchOnExitTask;
use crate::tasks::remove_target_dir::RemoveTargetDirTask;
use crate::tasks::save_executable::SaveExecutableTask;
use crate::tasks::uninstall_pkg::UninstallPackageTask;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskOrdering;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskOrdering;
use crate::tasks::TaskParamType;
pub struct InstallTask {
pub items: Vec<String>,
pub uninstall_items: Vec<String>,
pub fresh_install: bool,
pub create_desktop_shortcuts: bool,
// force_install: remove the target directory before installing
pub force_install: bool,
}
impl Task for InstallTask {
@ -40,6 +45,13 @@ impl Task for InstallTask {
Box::new(EnsureOnlyInstanceTask {}),
));
if self.force_install {
elements.push(TaskDependency::build(
TaskOrdering::Pre,
Box::new(RemoveTargetDirTask {}),
));
}
elements.push(TaskDependency::build(
TaskOrdering::Pre,
Box::new(VerifyInstallDirTask {
@ -60,7 +72,10 @@ impl Task for InstallTask {
for item in &self.items {
elements.push(TaskDependency::build(
TaskOrdering::Pre,
Box::new(InstallPackageTask { name: item.clone() }),
Box::new(InstallPackageTask {
name: item.clone(),
create_desktop_shortcuts: self.create_desktop_shortcuts,
}),
));
}
@ -74,6 +89,11 @@ impl Task for InstallTask {
TaskOrdering::Pre,
Box::new(InstallGlobalShortcutsTask {}),
));
elements.push(TaskDependency::build(
TaskOrdering::Post,
Box::new(LaunchOnExitTask {}),
))
}
elements

View file

@ -0,0 +1,133 @@
//! Generates shortcuts for a specified file.
use crate::installer::InstallerFramework;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use crate::config::PackageDescription;
use crate::logging::LoggingErrors;
#[cfg(windows)]
use crate::native::create_desktop_shortcut;
#[cfg(target_os = "linux")]
use crate::native::create_shortcut;
pub struct InstallDesktopShortcutTask {
pub name: String,
pub should_run: bool,
}
impl Task for InstallDesktopShortcutTask {
fn execute(
&mut self,
_: Vec<TaskParamType>,
context: &mut InstallerFramework,
messenger: &dyn Fn(&TaskMessage),
) -> Result<TaskParamType, String> {
if !self.should_run {
return Ok(TaskParamType::GeneratedShortcuts(Vec::new()));
}
messenger(&TaskMessage::DisplayMessage(
&format!(
"Generating desktop shortcuts for package {:?}...",
self.name
),
0.0,
));
let path = context
.install_path
.as_ref()
.log_expect("No install path specified");
let starting_dir = path
.to_str()
.log_expect("Unable to build shortcut metadata (startingdir)");
let mut installed_files = Vec::new();
let mut metadata: Option<PackageDescription> = None;
for description in &context
.config
.as_ref()
.log_expect("Should have packages by now")
.packages
{
if self.name == description.name {
metadata = Some(description.clone());
break;
}
}
let package = match metadata {
Some(v) => v,
None => return Err(format!("Package {:?} could not be found.", self.name)),
};
// Generate installer path
let platform_extension = if cfg!(windows) {
"maintenancetool.exe"
} else {
"maintenancetool"
};
for shortcut in package.shortcuts {
let tool_path = path.join(platform_extension);
let tool_path = tool_path
.to_str()
.log_expect("Unable to build shortcut metadata (tool)");
let exe_path = path.join(shortcut.relative_path);
let exe_path = exe_path
.to_str()
.log_expect("Unable to build shortcut metadata (exe)");
#[cfg(windows)]
installed_files.push(create_desktop_shortcut(
&shortcut.name,
&shortcut.description,
tool_path,
// TODO: Send by list
&format!("--launcher \"{}\"", exe_path),
&starting_dir,
exe_path,
)?);
#[cfg(target_os = "linux")]
installed_files.push(create_shortcut(
&shortcut.name,
&shortcut.description,
tool_path,
&format!("--launcher \"{}\"", exe_path),
&starting_dir,
exe_path,
)?);
}
// Update the installed packages shortcuts information in the database
let packages = &mut context.database.packages;
for pack in packages {
if pack.name == self.name {
pack.shortcuts.extend(installed_files.clone());
}
}
Ok(TaskParamType::GeneratedShortcuts(installed_files))
}
fn dependencies(&self) -> Vec<TaskDependency> {
vec![]
}
fn name(&self) -> String {
format!(
"InstallDesktopShortcutTask (for {:?}, should_run = {:?})",
self.name, self.should_run
)
}
}

View file

@ -1,16 +1,16 @@
//! Verifies properties about the installation directory.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use std::fs::create_dir_all;
use std::fs::read_dir;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
pub struct VerifyInstallDirTask {
pub clean_install: bool,

View file

@ -1,17 +1,17 @@
//! Generates the global shortcut for this application.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use native::create_shortcut;
use tasks::save_database::SaveDatabaseTask;
use tasks::TaskOrdering;
use crate::native::create_shortcut;
use crate::tasks::save_database::SaveDatabaseTask;
use crate::tasks::TaskOrdering;
pub struct InstallGlobalShortcutsTask {}
@ -58,9 +58,10 @@ impl Task for InstallGlobalShortcutsTask {
// TODO: Send by list
"",
&starting_dir,
"",
)?;
if !shortcut_file.is_empty() {
if !shortcut_file.is_empty() && !context.database.shortcuts.contains(&shortcut_file) {
context.database.shortcuts.push(shortcut_file);
}

View file

@ -1,32 +1,35 @@
//! Installs a specific package.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::download_pkg::DownloadPackageTask;
use tasks::install_shortcuts::InstallShortcutsTask;
use tasks::save_database::SaveDatabaseTask;
use tasks::uninstall_pkg::UninstallPackageTask;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskOrdering;
use tasks::TaskParamType;
use crate::tasks::download_pkg::DownloadPackageTask;
use crate::tasks::install_shortcuts::InstallShortcutsTask;
use crate::tasks::save_database::SaveDatabaseTask;
use crate::tasks::uninstall_pkg::UninstallPackageTask;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskOrdering;
use crate::tasks::TaskParamType;
use config::PackageDescription;
use installer::LocalInstallation;
use crate::config::PackageDescription;
use crate::installer::LocalInstallation;
use std::fs::create_dir_all;
use std::io::copy;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use archives;
use crate::archives;
use crate::tasks::install_desktop_shortcut::InstallDesktopShortcutTask;
use std::collections::HashSet;
use std::fs::OpenOptions;
use std::path::Path;
pub struct InstallPackageTask {
pub name: String,
pub create_desktop_shortcuts: bool,
}
impl Task for InstallPackageTask {
@ -66,20 +69,20 @@ impl Task for InstallPackageTask {
None => return Err(format!("Package {:?} could not be found.", self.name)),
};
// Grab data from the shortcut generator
let shortcuts = input.pop().log_expect("Should have input from resolver!");
let shortcuts = match shortcuts {
TaskParamType::GeneratedShortcuts(files) => files,
// If the resolver returned early, we need to unwind
// Ignore input from the uninstaller - no useful information passed
// If a previous task Breaks, then just early exit
match input
.pop()
.log_expect("Install Package Task should have guaranteed output!")
{
TaskParamType::Break => return Ok(TaskParamType::None),
_ => return Err("Unexpected shortcuts param type to install package".to_string()),
_ => (),
};
// Ignore input from the uninstaller - no useful information passed
input.pop();
// Grab data from the resolver
let data = input.pop().log_expect("Should have input from resolver!");
let data = input
.pop()
.log_expect("Install Package Task should have input from resolver!");
let (version, file, data) = match data {
TaskParamType::FileContents(version, file, data) => (version, file, data),
_ => return Err("Unexpected file contents param type to install package".to_string()),
@ -139,7 +142,7 @@ impl Task for InstallPackageTask {
info!("Creating file: {:?}", string_name);
if !installed_files.contains(&string_name) {
installed_files.push(string_name.to_string());
installed_files.push(string_name);
}
let mut file_metadata = OpenOptions::new();
@ -168,9 +171,9 @@ impl Task for InstallPackageTask {
// Save metadata about this package
context.database.packages.push(LocalInstallation {
name: package.name.to_owned(),
name: package.name,
version,
shortcuts,
shortcuts: HashSet::new(),
files: installed_files,
});
@ -195,11 +198,18 @@ impl Task for InstallPackageTask {
}),
),
TaskDependency::build(
TaskOrdering::Pre,
TaskOrdering::Post,
Box::new(InstallShortcutsTask {
name: self.name.clone(),
}),
),
TaskDependency::build(
TaskOrdering::Post,
Box::new(InstallDesktopShortcutTask {
name: self.name.clone(),
should_run: self.create_desktop_shortcuts,
}),
),
TaskDependency::build(TaskOrdering::Post, Box::new(SaveDatabaseTask {})),
]
}

View file

@ -1,17 +1,17 @@
//! Generates shortcuts for a specified file.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use config::PackageDescription;
use crate::config::PackageDescription;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
use native::create_shortcut;
use crate::native::create_shortcut;
pub struct InstallShortcutsTask {
pub name: String,
@ -83,9 +83,18 @@ impl Task for InstallShortcutsTask {
// TODO: Send by list
&format!("--launcher \"{}\"", exe_path),
&starting_dir,
exe_path,
)?);
}
// Update the installed packages shortcuts information in the database
let packages = &mut context.database.packages;
for pack in packages {
if pack.name == self.name {
pack.shortcuts.extend(installed_files.clone());
}
}
Ok(TaskParamType::GeneratedShortcuts(installed_files))
}

View file

@ -0,0 +1,76 @@
//! Configures lift to launch the new package on fresh install after its closed
//! If theres multiple launchable packages, then choose the first listed in config
//! If there are multiple shortcuts for the first package, then launch the first.
use crate::installer::InstallerFramework;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use crate::config::PackageDescription;
use crate::logging::LoggingErrors;
pub struct LaunchOnExitTask {}
impl Task for LaunchOnExitTask {
fn execute(
&mut self,
_: Vec<TaskParamType>,
context: &mut InstallerFramework,
_: &dyn Fn(&TaskMessage),
) -> Result<TaskParamType, String> {
let pkg = &context.database.packages.first();
if pkg.is_none() {
return Ok(TaskParamType::None);
}
let pkg = pkg.unwrap();
// look up the first shortcut for the first listed package in the database
let path = context
.install_path
.as_ref()
.log_expect("No install path specified");
let mut metadata: Option<PackageDescription> = None;
for description in &context
.config
.as_ref()
.log_expect("Should have packages by now")
.packages
{
if pkg.name == description.name {
metadata = Some(description.clone());
break;
}
}
let package_desc = match metadata {
Some(v) => v,
// Package metadata is missing. Dunno what went wrong but we can skip this then
None => return Ok(TaskParamType::None),
};
let shortcut = package_desc.shortcuts.first();
// copy the path to the actual exe into launcher_path so it'll load it on exit
context.launcher_path = shortcut.map(|s| {
path.join(s.relative_path.clone())
.to_str()
.map(|t| t.to_string())
.unwrap()
});
Ok(TaskParamType::None)
}
fn dependencies(&self) -> Vec<TaskDependency> {
vec![]
}
fn name(&self) -> String {
"LaunchOnExitTask".to_string()
}
}

View file

@ -4,19 +4,22 @@
use std::fmt;
use std::fmt::Display;
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use sources::types::File;
use sources::types::Version;
use crate::sources::types::File;
use crate::sources::types::Version;
pub mod check_authorization;
pub mod download_pkg;
pub mod ensure_only_instance;
pub mod install;
pub mod install_desktop_shortcut;
pub mod install_dir;
pub mod install_global_shortcut;
pub mod install_pkg;
pub mod install_shortcuts;
pub mod launch_installed_on_exit;
pub mod remove_target_dir;
pub mod resolver;
pub mod save_database;
pub mod save_executable;

View file

@ -0,0 +1,64 @@
//! remove the whole target directory from the existence
use crate::installer::InstallerFramework;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
pub struct RemoveTargetDirTask {}
impl Task for RemoveTargetDirTask {
fn execute(
&mut self,
_: Vec<TaskParamType>,
context: &mut InstallerFramework,
messenger: &dyn Fn(&TaskMessage),
) -> Result<TaskParamType, String> {
messenger(&TaskMessage::DisplayMessage(
"Removing previous install...",
0.1,
));
// erase the database as well
context.database.packages = Vec::new();
if let Some(path) = context.install_path.as_ref() {
let entries = std::fs::read_dir(path)
.map_err(|e| format!("Error reading {}: {}", path.to_string_lossy(), e))?;
// remove everything under the path
if !context.preexisting_install {
std::fs::remove_dir_all(path)
.map_err(|e| format!("Error removing {}: {}", path.to_string_lossy(), e))?;
return Ok(TaskParamType::None);
}
// remove everything except the maintenancetool if repairing
for entry in entries {
let path = entry
.map_err(|e| format!("Error reading file: {}", e))?
.path();
if let Some(filename) = path.file_name() {
if filename.to_string_lossy().starts_with("maintenancetool") {
continue;
}
}
if path.is_dir() {
std::fs::remove_dir_all(&path)
.map_err(|e| format!("Error removing {}: {}", path.to_string_lossy(), e))?;
} else {
std::fs::remove_file(&path)
.map_err(|e| format!("Error removing {}: {}", path.to_string_lossy(), e))?;
}
}
}
Ok(TaskParamType::None)
}
fn dependencies(&self) -> Vec<TaskDependency> {
vec![]
}
fn name(&self) -> String {
"RemoveTargetDirTask".to_string()
}
}

View file

@ -2,18 +2,18 @@
use std::env::consts::OS;
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use config::PackageDescription;
use crate::config::PackageDescription;
use regex::Regex;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
pub struct ResolvePackageTask {
pub name: String,

View file

@ -1,11 +1,11 @@
//! Saves the main database into the installation directory.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
pub struct SaveDatabaseTask {}

View file

@ -1,11 +1,11 @@
//! Saves the installer executable into the install directory.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use std::fs::File;
use std::fs::OpenOptions;
@ -14,7 +14,7 @@ use std::io::copy;
use std::env::current_exe;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
pub struct SaveExecutableTask {}

View file

@ -1,14 +1,14 @@
//! Uninstalls a set of packages.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskParamType;
use tasks::uninstall_pkg::UninstallPackageTask;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskOrdering;
use crate::tasks::uninstall_pkg::UninstallPackageTask;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskOrdering;
pub struct UninstallTask {
pub items: Vec<String>,

View file

@ -1,15 +1,15 @@
//! Uninstalls a specific package.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use crate::tasks::save_database::SaveDatabaseTask;
use crate::tasks::TaskOrdering;
use std::fs::remove_file;
use tasks::save_database::SaveDatabaseTask;
use tasks::TaskOrdering;
pub struct UninstallGlobalShortcutsTask {}

View file

@ -1,21 +1,21 @@
//! Uninstalls a specific package.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::save_database::SaveDatabaseTask;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskOrdering;
use tasks::TaskParamType;
use crate::tasks::save_database::SaveDatabaseTask;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskOrdering;
use crate::tasks::TaskParamType;
use installer::LocalInstallation;
use crate::installer::LocalInstallation;
use std::fs::remove_dir;
use std::fs::remove_file;
use logging::LoggingErrors;
use tasks::uninstall_shortcuts::UninstallShortcutsTask;
use crate::logging::LoggingErrors;
use crate::tasks::uninstall_shortcuts::UninstallShortcutsTask;
pub struct UninstallPackageTask {
pub name: String,
@ -44,7 +44,7 @@ impl Task for UninstallPackageTask {
}
}
let mut package = match metadata {
let package = match metadata {
Some(v) => v,
None => {
if self.optional {
@ -63,8 +63,7 @@ impl Task for UninstallPackageTask {
0.0,
));
// Reverse, as to delete directories last
package.files.reverse();
let mut directories = Vec::new();
let max = package.files.len();
for (i, file) in package.files.iter().enumerate() {
@ -78,7 +77,9 @@ impl Task for UninstallPackageTask {
));
let result = if file.is_dir() {
remove_dir(file)
// we don't delete directory just yet
directories.push(file);
Ok(())
} else {
remove_file(file)
};
@ -88,6 +89,17 @@ impl Task for UninstallPackageTask {
}
}
// sort directories by reverse depth order
directories.sort_by(|a, b| {
let depth_a = a.components().fold(0usize, |acc, _| acc + 1);
let depth_b = b.components().fold(0usize, |acc, _| acc + 1);
depth_b.cmp(&depth_a)
});
for i in directories.iter() {
info!("Deleting directory: {:?}", i);
remove_dir(i).ok();
}
Ok(TaskParamType::None)
}

View file

@ -1,18 +1,18 @@
//! Uninstalls a specific package.
use installer::InstallerFramework;
use crate::installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskParamType;
use crate::tasks::Task;
use crate::tasks::TaskDependency;
use crate::tasks::TaskMessage;
use crate::tasks::TaskParamType;
use installer::LocalInstallation;
use crate::installer::LocalInstallation;
use std::fs::remove_dir;
use std::fs::remove_file;
use logging::LoggingErrors;
use crate::logging::LoggingErrors;
pub struct UninstallShortcutsTask {
pub name: String,

View file

@ -1,3 +1 @@
> 1%
last 2 versions
not ie <= 11

View file

@ -3,15 +3,14 @@ module.exports = {
env: {
node: true
},
'extends': [
'plugin:vue/essential',
extends: [
'plugin:vue/recommended',
'@vue/standard'
],
rules: {
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
},
parserOptions: {
parser: 'babel-eslint'
'no-console': 'off',
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
'no-redeclare': 'off',
camelcase: 'off'
}
}

20
ui/merge-strings.js Executable file
View file

@ -0,0 +1,20 @@
#!/bin/env node
const fs = require('fs')
const merge = require('deepmerge')
const glob = require('glob')
glob('src/locales/!(messages).json', {}, (e, files) => {
let messages = []
for (const file of files) {
console.log(`Loading ${file}...`)
const locale_messages = require(`./${file}`)
messages.push(locale_messages)
}
console.log('Merging messages...')
if (messages && messages.length > 1) {
messages = merge.all(messages)
} else {
messages = messages[0] // single locale mode
}
fs.writeFileSync('src/locales/messages.json', JSON.stringify(messages), {})
})

View file

@ -4,20 +4,38 @@ const express = require('express')
const app = express()
const port = 3000
let showError = false
let showConfigError = false
let maintenance = false
let launcher = false
let fileExists = false
let darkMode = false
let recoveryMode = false
function progressSimulation (res) {
var progress = 0.0
var timer = setInterval(() => {
var resp = JSON.stringify({ Status: ['Processing...', progress] }) + '\n'
if (showError) {
const resp = JSON.stringify({ Error: 'Simulated error.' }) + '\n'
res.write(resp)
res.status(200).end()
return
}
let progress = 0.0
const timer = setInterval(() => {
const resp = JSON.stringify({ Status: ['Processing...', progress] }) + '\n'
progress += 0.1
res.write(resp)
if (progress >= 1) {
res.status(200).end()
clearInterval(timer)
}
}, 1500)
}, 500)
}
function returnConfig (res) {
if (showConfigError) {
res.status(500).json({})
return
}
res.json({
installing_message:
'Test Banner <strong>Bold</strong>&nbsp;<pre>Code block</pre>&nbsp;<i>Italic</i>&nbsp;<del>Strike</del>',
@ -52,21 +70,22 @@ function returnConfig (res) {
}
app.get('/api/attrs', (req, res) => {
console.log('-- Get attrs')
res.send(
`var base_attributes = {"name":"yuzu","target_url":"https://raw.githubusercontent.com/j-selby/test-installer/master/config.linux.v2.toml"};`
{ name: 'yuzu', recovery: recoveryMode, target_url: 'https://raw.githubusercontent.com/j-selby/test-installer/master/config.linux.v2.toml' }
)
})
app.get('/api/dark-mode', (req, res) => {
res.json(false)
res.json(darkMode)
})
app.get('/api/installation-status', (req, res) => {
res.json({
database: { packages: [], shortcuts: [] },
install_path: null,
preexisting_install: false,
is_launcher: false,
preexisting_install: maintenance,
is_launcher: launcher,
launcher_path: null
})
})
@ -82,13 +101,72 @@ app.get('/api/config', (req, res) => {
})
app.post('/api/start-install', (req, res) => {
console.log(`-- Install: ${req.body}`)
console.log('-- Install:')
console.log(req.body)
progressSimulation(res)
})
app.get('/api/exit', (req, res) => {
console.log('-- Exit')
res.status(204)
if (showError) {
res.status(500).send('Simulated error: Nothing to see here.')
return
}
res.status(204).send()
})
app.post('/api/verify-path', (req, res) => {
console.log('-- Verify Path')
res.send({
exists: fileExists
})
})
app.post('/api/check-auth', (req, res) => {
console.log('-- Check Authorization')
res.send({
username: 'test1',
token: 'token',
jwt_token: {
isPatreonAccountLinked: true,
isPatreonSubscriptionActive: true,
releaseChannels: ['early-access']
}
})
})
process.argv.forEach((val, index) => {
switch (val) {
case 'maintenance':
maintenance = true
console.log('Simulating maintenance mode')
break
case 'launcher':
maintenance = true
launcher = true
console.log('Simulating launcher mode')
break
case 'exists':
fileExists = true
console.log('Simulating file exists situation')
break
case 'dark':
darkMode = true
console.log('Simulating dark mode')
break
case 'config-error':
showConfigError = true
console.log('Simulating configuration errors')
break
case 'error':
showError = true
console.log('Simulating errors')
break
case 'recovery':
recoveryMode = true
console.log('Simulating recovery mode')
break
}
})
console.log(`Listening on ${port}...`)

View file

@ -5,23 +5,32 @@
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
"lint": "vue-cli-service lint",
"postinstall": "node merge-strings.js"
},
"dependencies": {
"buefy": "^0.7.7",
"vue": "^2.6.6",
"vue-router": "^3.0.1"
"@mdi/font": "^7.1.96",
"axios": "^1.2.1",
"buefy": "^0.9.22",
"canvas-confetti": "^1.6.0",
"vue": "^2.6.14",
"vue-axios": "^3.5.2",
"vue-i18n": "^8.26.5",
"vue-router": "^3.5.2"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.5.0",
"@vue/cli-plugin-eslint": "^3.5.0",
"@vue/cli-service": "^3.5.0",
"@vue/eslint-config-standard": "^4.0.0",
"babel-eslint": "^10.0.1",
"eslint": "^5.8.0",
"eslint-plugin-vue": "^5.0.0",
"express": "^4.17.1",
"http-proxy-middleware": "^0.19.1",
"vue-template-compiler": "^2.5.21"
"@vue/cli-plugin-babel": "^5.0.8",
"@vue/cli-plugin-eslint": "^5.0.8",
"@vue/cli-service": "^5.0.8",
"@vue/eslint-config-standard": "^6.1.0",
"eslint": "^8.30.0",
"eslint-plugin-import": "^2.26.0",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^6.1.1",
"eslint-plugin-standard": "^4.1.0",
"eslint-plugin-vue": "^9.8.0",
"express": "^4.18.2",
"http-proxy-middleware": "^2.0.6",
"vue-template-compiler": "^2.7.14"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 105 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -4,7 +4,6 @@
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=11">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<script src="/api/attrs" type="text/javascript"></script>
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title id="window-title">... Installer</title>
</head>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.5 KiB

View file

@ -4,20 +4,28 @@
<div class="container is-max-height">
<div class="columns is-max-height">
<div class="column is-one-third has-padding" v-if="!$root.$data.metadata.is_launcher">
<img src="./assets/logo.png" width="60%" alt="Application icon" />
<img src="./assets/light_mode_installer_logo.png" id="applicationIcon" alt="Application icon" />
<br />
<br />
<h2 class="subtitle" v-if="!$root.$data.metadata.preexisting_install">
Welcome to the {{ $root.$data.attrs.name }} installer!
{{ $t('app.installer_title', {'name': $root.$data.attrs.name}) }}
</h2>
<h2 class="subtitle" v-if="!$root.$data.metadata.preexisting_install">
We will have you up and running in just a few moments.
{{ $t('app.installer_subtitle') }}
</h2>
<h2 class="subtitle" v-if="$root.$data.metadata.preexisting_install">
Welcome to the {{ $root.$data.attrs.name }} Maintenance Tool.
{{ $t('app.maintenance_title', {'name': $root.$data.attrs.name}) }}
</h2>
<b-dropdown hoverable @change="selectLocale" aria-role="list" scrollable>
<button class="button" slot="trigger">
<span>{{ $t('locale') }}</span>
<b-icon icon="menu-down"></b-icon>
</button>
<b-dropdown-item v-for="(locale, index) in this.$i18n.messages" v-bind:key="index" :value="index" aria-role="listitem">{{locale.locale}}</b-dropdown-item>
</b-dropdown>
</div>
<router-view />
@ -27,6 +35,33 @@
</div>
</template>
<script>
export default {
mounted: function () {
// detect languages
const languages = window.navigator.languages
if (languages) {
// standard-compliant browsers
for (let index = 0; index < languages.length; index++) {
const lang = languages[index]
// Find the most preferred language that we support
if (Object.prototype.hasOwnProperty.call(this.$i18n.messages, lang)) {
this.$i18n.locale = lang
return
}
}
}
// IE9+ support
this.$i18n.locale = window.navigator.browserLanguage
},
methods: {
selectLocale: function (locale) {
this.$i18n.locale = locale
}
}
}
</script>
<style>
/* roboto-regular - latin */
@font-face {
@ -52,6 +87,30 @@ body, div, span, h1, h2, h3, h4, h5, h6 {
cursor: default;
}
#applicationIcon {
width:0px; height: 0px;
padding: 50px 60% 0px 0px;
background: url("./assets/light_mode_installer_logo.png") left top no-repeat;
background-size: contain;
}
body.has-background-black-ter #applicationIcon {
background: url("./assets/dark_mode_installer_logo.png") left top no-repeat;
background-size: contain;
}
.package-icon {
width: 3rem;
height: 3rem;
float: left;
padding-right: 10px;
padding-top: 10px;
}
.package-description {
overflow: hidden;
}
pre {
-webkit-user-select: text;
-moz-user-select: text;
@ -123,6 +182,10 @@ pre {
background: #fff;
}
.tile.box.clickable-box {
color: #4a4a4a;
}
/* Dark mode */
body.has-background-black-ter .subtitle, body.has-background-black-ter .column > div {
color: hsl(0, 0%, 96%);
@ -171,4 +234,7 @@ body.has-background-black-ter .subtitle, body.has-background-black-ter .column >
border-bottom: 3px solid transparent;
border-top: 3px solid #FF3C28;
}
a:hover {
text-decoration: underline;
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View file

@ -4,60 +4,6 @@
* Additional state-less helper methods.
*/
var request_id = 0
/**
* Makes a AJAX request.
*
* @param path The path to connect to.
* @param successCallback A callback with a JSON payload.
* @param failCallback A fail callback. Optional.
* @param data POST data. Optional.
*/
export function ajax (path, successCallback, failCallback, data) {
if (failCallback === undefined) {
failCallback = defaultFailHandler
}
console.log('Making HTTP request to ' + path)
var req = new XMLHttpRequest()
req.addEventListener('load', function () {
// The server can sometimes return a string error. Make sure we handle this.
if (this.status === 200 && this.getResponseHeader('Content-Type').indexOf('application/json') !== -1) {
successCallback(JSON.parse(this.responseText))
} else {
failCallback(this.responseText)
}
})
req.addEventListener('error', failCallback)
req.open(data == null ? 'GET' : 'POST', path + '?nocache=' + request_id++, true)
// Rocket only currently supports URL encoded forms.
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
if (data != null) {
var form = ''
for (var key in data) {
if (!data.hasOwnProperty(key)) {
continue
}
if (form !== '') {
form += '&'
}
form += encodeURIComponent(key) + '=' + encodeURIComponent(data[key])
}
req.send(form)
} else {
req.send()
}
}
/**
* Makes a AJAX request, streaming each line as it arrives. Type should be text/plain,
* each line will be interpreted as JSON separately.
@ -69,7 +15,7 @@ export function ajax (path, successCallback, failCallback, data) {
* @param data POST data. Optional.
*/
export function stream_ajax (path, callback, successCallback, failCallback, data) {
var req = new XMLHttpRequest()
const req = new XMLHttpRequest()
console.log('Making streaming HTTP request to ' + path)
@ -82,23 +28,23 @@ export function stream_ajax (path, callback, successCallback, failCallback, data
}
})
var buffer = ''
var seenBytes = 0
let buffer = ''
let seenBytes = 0
req.onreadystatechange = function () {
if (req.readyState > 2) {
buffer += req.responseText.substr(seenBytes)
var pointer
let pointer
while ((pointer = buffer.indexOf('\n')) >= 0) {
var line = buffer.substring(0, pointer).trim()
const line = buffer.substring(0, pointer).trim()
buffer = buffer.substring(pointer + 1)
if (line.length === 0) {
continue
}
var contents = JSON.parse(line)
const contents = JSON.parse(line)
callback(contents)
}
@ -108,15 +54,15 @@ export function stream_ajax (path, callback, successCallback, failCallback, data
req.addEventListener('error', failCallback)
req.open(data == null ? 'GET' : 'POST', path + '?nocache=' + request_id++, true)
req.open(data == null ? 'GET' : 'POST', path + '?nocache=' + Date.now(), true)
// Rocket only currently supports URL encoded forms.
req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
if (data != null) {
var form = ''
let form = ''
for (var key in data) {
if (!data.hasOwnProperty(key)) {
for (const key in data) {
if (!data[key]) {
continue
}
@ -132,13 +78,3 @@ export function stream_ajax (path, callback, successCallback, failCallback, data
req.send()
}
}
/**
* The default handler if a AJAX request fails. Not to be used directly.
*
* @param e The XMLHttpRequest that failed.
*/
function defaultFailHandler (e) {
console.error('A AJAX request failed, and was not caught:')
console.error(e)
}

1
ui/src/locales/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
messages.json

78
ui/src/locales/ca.json Normal file
View file

@ -0,0 +1,78 @@
{
"ca": {
"locale": "Català",
"error": {
"title": "S'ha produït un error",
"exit_error": "{msg}\n\nSi us plau, carregui l'arxiu de registre (a {path}) a l'equip de {name}",
"location_unknown": "la ubicació on està aquest instal·lador"
},
"complete": {
"thanks": "Gràcies per instal·lar {name}!",
"updated": "{name} s'ha actualitzat.",
"uninstalled": "{name} s'ha desinstal·lat.",
"up_to_date": "{name} ja està actualitzat!",
"where_to_find": "Pot trobar les aplicacions instal·lades al seu menú d'inici. ",
"migration_where_to_find": "Pot trobar les seves aplicacions instal·lades al seu menú d'inici; si es troba al mig d'alguna cosa, torni-ho a provar.",
"migration_finished": "L'hem traslladat a la nova i única versió de {name}."
},
"modify": {
"title": "Esculli una opció:",
"update": "Actualitzar",
"uninstall": "Desinstal·lar",
"prompt": "Està segur de que vol desinstal·lar {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Veure arxius locals",
"prompt_recover": "Les dades de l'instal·lador per a {name} estan malmeses. <br>Cal una reparació per restaurar la instal·lació.",
"prompt_confirm": "Desinstal·lar {name}",
"modify": "Modificar",
"repair": "Reparar"
},
"auth": {
"paste": "Enganxar",
"token": "Token",
"verify": "Verificar token",
"page_header": "El canal de llançament d'accés anticipat li permet provar les últimes funcions experimentals i correccions abans que es fusionin al yuzu. Aquest canal inclou totes les actualitzacions diàries de yuzu, a més d'aquestes funcions exclusives.\n\nPer ser membre d'accés anticipat, ha de ser subscriptor d'accés anticipat a Patreon.",
"page_opened": "Pàgina oberta! Comprovi el seu navegador predeterminat per a la pàgina i segueixi les instruccions que hi ha per enllaçar el seu compte de Patreon.\nQuan hagi acabat, introdueixi el token a continuació.",
"login_failed": "Error a l'iniciar sessió!\n\nComprovi que el teu token sigui correcte i torni-ho a provar"
},
"back": "Enrere",
"exit": "Sortir",
"yes": "Sí",
"no": "No",
"continue": "Continuar",
"cancel": "Cancel·lar",
"app": {
"installer_title": "Benvingut a l'instal·lador de {name}!",
"maintenance_title": "Benvingut a l'eina de manteniment de {name}.",
"window_title": "Instal·lador de {name}",
"installer_subtitle": "Estarem llestos en uns instants."
},
"select_packages": {
"title": "Seleccioni quins paquets vol instal·lar:",
"installed": "(Instal·lat)",
"advanced": "Avançat...",
"install": "Instal·lar",
"location": "Ubicació d'instal·lació",
"select": "Seleccionar",
"overwriting": "Sobreescrivint",
"options": "Opcions d'instal·lació",
"title_repair": "Seleccioni quins paquets vol reparar:",
"location_placeholder": "Introdueixi una ruta d'instal·lació aquí",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Si us plau, seleccioni almenys un paquet a instal·lar!",
"nothing_picked": "Res seleccionat",
"option_shortcut": "Crear un accés directe al escriptori"
},
"install_packages": {
"uninstall": "Desinstal·lant...",
"install": "Instal·lant...",
"check_for_update": "Comprovant actualitzacions...",
"self_update": "Descarregant una auto-actualització...",
"please_wait": "Si us plau, esperi..."
},
"download_packages": {
"download_config": "Descarregant configuració...",
"error_download_config": "S'ha produït un error mentre descarregàvem la configuració: {msg}"
}
}
}

78
ui/src/locales/cs.json Normal file
View file

@ -0,0 +1,78 @@
{
"cs": {
"locale": "Česky",
"error": {
"title": "Objevil se error",
"exit_error": "{msg}\n\nProsím nahrajte záznamoví soubor (do {path}) pro {name} tým",
"location_unknown": "místo kde se instalátor nachází"
},
"complete": {
"thanks": "Děkujeme za instalování {name}!",
"updated": "{name} byl aktualizován.",
"uninstalled": "{name} byl odinstalován.",
"up_to_date": "{name} už je aktuální!",
"where_to_find": "Nainstalované aplikace můžete najít ve vašem start menu.",
"migration_where_to_find": "Nainstalované aplikace můžete najít ve vašem start menu - jestli jste byly uprostřed něčeho, prosím zkuste to znovu.",
"migration_finished": "Byly jste přesunuti do nové, jediné verze {name}."
},
"modify": {
"title": "Vyberte možnost:",
"update": "Aktualizovat",
"uninstall": "Odinstalovat",
"prompt": "Jste si jistý že chcete odinstalovat {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Zobrazit místní soubory",
"prompt_recover": "Data instalátoru pro {name} jsou poškozený.<br>Oprava je potřeba pro obnovní instalace.",
"prompt_confirm": "Odinstalovat {name}",
"modify": "Modifikovat",
"repair": "Opravit"
},
"auth": {
"paste": "Vložit",
"token": "Token",
"verify": "Ověřit Token",
"page_header": "Early Access kanál pro zveřejňovaní vám umožní si vyzkoušet nejnovější experimentální funkce and opravy, před tím než spojeny s yuzu. Tento kanál obsahuje všechny pravidelné denní yuzu aktualizace, k tomu ještě tyto exkluzivní funkce.\n\nAby jste se stal Early Access členem, musíte se jako první stát Patreon Early Access odběratelem.",
"page_opened": "Stránka otevřena! Zkontrolujte váš výchozí prohlížeč pro stránku s instrukcemi a postupujte podle pokynů pro propojení patreon účtu.\nAž budete hotoví, vložte token níže.",
"login_failed": "Přihlášení selhalo!\n\nUjistěte se, že jste zadali token správně a zkuste to znovu."
},
"back": "Zpět",
"exit": "Opustit",
"yes": "Ano",
"no": "Ne",
"continue": "Pokračovat",
"cancel": "Zrušit",
"app": {
"installer_title": "Vítejte v {name} instalátoru!",
"maintenance_title": "Vítejte v {name} Nástroji pro Údržbu.",
"window_title": "{name} Instalátor",
"installer_subtitle": "Za chvilku vás uvedeme zpátky do provozu."
},
"select_packages": {
"title": "Vyberte balíček, který si chcete nainstalovat:",
"installed": "(nainstalováno)",
"advanced": "Pokročilé...",
"install": "Nainstalovat",
"location": "Místo Instalace",
"select": "Vyberte",
"overwriting": "Přepisování",
"options": "Možnosti Instalace",
"title_repair": "Vyberte balíček, který chcete opravit:",
"location_placeholder": "Sem zadejte místo instalace",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Prosím vyberte alespoň jeden balíček na instalování!",
"nothing_picked": "Nic nevybráno",
"option_shortcut": "Vytvořit Zástupce na Plochu"
},
"install_packages": {
"uninstall": "Odinstalovávám...",
"install": "Instaluji...",
"check_for_update": "Hledám nové aktualizace...",
"self_update": "Stahuji samo-aktualizaci...",
"please_wait": "Prosím čekejte..."
},
"download_packages": {
"download_config": "Stahuji konfigurace...",
"error_download_config": "Error se objevil při stahování konfigurace: {msg}"
}
}
}

78
ui/src/locales/de.json Normal file
View file

@ -0,0 +1,78 @@
{
"de": {
"locale": "Deutsch",
"error": {
"title": "Ein Fehler ist aufgetreten",
"exit_error": "{msg}\n\nBitte laden sie die Log-Datei (in {path}) für das {name} Team hoch.",
"location_unknown": "der Ort an dem sich der Installer befindet"
},
"complete": {
"thanks": "Danke fürs installieren {name}!",
"updated": "{name} wurde aktualisiert.",
"uninstalled": "{name} wurde deinstalliert.",
"up_to_date": "{name} ist bereits auf dem neusten Stand",
"where_to_find": "Sie finden Ihre installierten Anwendungen im Startmenü.",
"migration_where_to_find": "Sie finden Ihre installierten Anwendungen in Ihrem Startmenü - wenn Sie gerade mit etwas beschäftigt waren, versuchen Sie es einfach noch einmal.",
"migration_finished": "Sie wurden in die neue, einfache Version von {Name} migriert."
},
"modify": {
"title": "Wähle eine Option:",
"update": "Aktualisieren",
"uninstall": "Deinstallieren",
"prompt": "Bist du sicher dass du {name} deinstallieren möchtest?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Lokale Dateien anzeigen",
"prompt_recover": "Die Installationsdaten für {name} sind beschädigt.<br>Eine Reperatur wird benötigt um die Installation wiederherzustellen.",
"prompt_confirm": "{name} deinstallieren",
"modify": "Modifizieren",
"repair": "Reparieren"
},
"auth": {
"paste": "Einfügen",
"token": "Token",
"verify": "Token verifizieren",
"page_header": "Im Early-Access-Channel können Sie die neuesten experimentellen Funktionen und Korrekturen ausprobieren, bevor sie in yuzu integriert werden. Dieser Kanal enthält alle regulären täglichen Updates von yuzu sowie diese exklusiven Funktionen.\n\nUm ein Early Access-Mitglied zu sein, müssen Sie ein Patreon Early Access-Abonnent sein.",
"page_opened": "Seite geöffnet! Folgen Sie den Anweisungen in ihrem Browser, um Ihr Patreon-Konto zu verknüpfen.\nDanach geben Sie den Token unten ein.",
"login_failed": "Login fehlgeschlagen!\n\nÜberprüfen Sie, ob Ihr Token korrekt ist und versuchen Sie es erneut."
},
"back": "Zurück",
"exit": "Verlassen",
"yes": "Ja",
"no": "Nein",
"continue": "Fortsetzen",
"cancel": "Abbrechen",
"app": {
"installer_title": "Willkommen zum {name}-Installer!",
"maintenance_title": "Willkommen zum {name}-Wartungstool!",
"window_title": "{name} Installer",
"installer_subtitle": "In nur wenigen Augenblicken sind wir startklar."
},
"select_packages": {
"title": "Wähle die Pakete aus die du installieren möchtest:",
"installed": "(Installieren)",
"advanced": "Erweitert...",
"install": "Installieren",
"location": "Installationsort",
"select": "Auswählen",
"overwriting": "Überschreiben",
"options": "Installationsoptionen",
"title_repair": "Wählen Sie die Pakete aus, die Sie reparieren möchten:",
"location_placeholder": "Gib hier den Installationspfad an",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Bitte wähle mindestens ein Paket zum installieren aus!",
"nothing_picked": "Nichts ausgewählt",
"option_shortcut": "Desktopverknüpfung erstellen"
},
"install_packages": {
"uninstall": "Deinstallieren...",
"install": "Installieren...",
"check_for_update": "Suche nach Updates...",
"self_update": "Selbst-Update wird heruntergeladen...",
"please_wait": "Bitte warten..."
},
"download_packages": {
"download_config": "Konfiguration wird heruntergeladen...",
"error_download_config": "Fehler beim Herunterladen der Konfiguration: {msg}"
}
}
}

80
ui/src/locales/en.json Normal file
View file

@ -0,0 +1,80 @@
{
"en":{
"locale":"English",
"app":{
"installer_title":"Welcome to the {name} installer!",
"installer_subtitle":"We will have you up and running in just a few moments.",
"maintenance_title":"Welcome to the {name} Maintenance Tool.",
"window_title":"{name} Installer"
},
"download_config":{
"download_config":"Downloading config...",
"error_download_config":"Got error while downloading config: {msg}"
},
"select_packages":{
"title":"Select which packages you want to install:",
"title_repair":"Select which packages you want to repair:",
"installed":"(installed)",
"advanced":"Advanced...",
"install":"Install",
"modify":"Modify",
"repair": "Repair",
"location":"Install Location",
"location_placeholder":"Enter a install path here",
"select":"Select",
"overwriting": "Overwriting",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked": "Nothing selected",
"nothing_picked_warning": "Please select at least one package to install!",
"options": "Install Options",
"option_shortcut": "Create Desktop Shortcut"
},
"install_packages":{
"check_for_update":"Checking for updates...",
"uninstall":"Uninstalling...",
"self_update":"Downloading self-update...",
"install":"Installing...",
"please_wait":"Please wait..."
},
"error":{
"title":"An error occurred",
"exit_error":"{msg}\n\nPlease upload the log file (in {path}) to the {name} team",
"location_unknown":"the location where this installer is"
},
"complete":{
"thanks":"Thanks for installing {name}!",
"up_to_date":"{name} is already up to date!",
"updated":"{name} has been updated.",
"uninstalled":"{name} has been uninstalled.",
"where_to_find":"You can find your installed applications in your start menu.",
"migration_where_to_find": "You can find your installed applications in your start menu - if you were in the middle of something, just reattempt.",
"migration_finished": "You have been moved to the new, single version of {name}."
},
"modify":{
"title":"Choose an option:",
"update":"Update",
"modify":"Modify",
"repair": "Repair",
"uninstall":"Uninstall",
"view_local_files": "View local files",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to continue?",
"prompt_recover": "Installer data for {name} is corrupted.<br>A repair is required to restore the installation.",
"prompt":"Are you sure you want to uninstall {name}?",
"prompt_confirm":"Uninstall {name}"
},
"auth":{
"paste": "Paste",
"token": "Token",
"verify": "Verify Token",
"page_header": "The Early Access release channel lets you try out the latest experimental features and fixes, before they are merged into yuzu. This channel includes all regular yuzu daily updates, plus these exclusive features.\n\nTo be an Early Access member, you must be a Patreon Early Access Subscriber.",
"page_opened": "Page opened! Check your default browser for the page, and follow the instructions there to link your patreon account.\nWhen you are done, enter the token below.",
"login_failed": "Login failed!\n\nDouble check that your token is correct and try again"
},
"back":"Back",
"exit":"Exit",
"yes":"Yes",
"no":"No",
"continue": "Continue",
"cancel":"Cancel"
}
}

78
ui/src/locales/es.json Normal file
View file

@ -0,0 +1,78 @@
{
"es": {
"locale": "Español",
"error": {
"title": "Ha ocurrido un error",
"exit_error": "{msg}\n\nPor favor, suba el archivo del registro (en {path}) al equipo de {name}",
"location_unknown": "la ubicación donde se encuentra el instalador"
},
"complete": {
"thanks": "¡Gracias por instalar {name}!",
"updated": "{name} ha sido actualizado.",
"uninstalled": "{name} ha sido desinstalado.",
"up_to_date": "¡{name} ya está actualizado!",
"where_to_find": "Puedes encontrar las aplicaciones instaladas en el menú de inicio.",
"migration_where_to_find": "Puedes encontrar tus aplicaciones instaladas en el menú de inicio - si estabas en mitad de algo, reinténtalo.",
"migration_finished": "Se ha actualizado a la nueva y única versión de {name}."
},
"modify": {
"title": "Elige una opción:",
"update": "Actualizar",
"uninstall": "Desinstalar",
"prompt": "¿Estás seguro que deseas desinstalar {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Ver archivos locales",
"prompt_recover": "Los datos de la instalación de {name} están corruptos.<br>Se necesita una reparación para restaurar la instalación.",
"prompt_confirm": "Desinstalar {name}",
"modify": "Modificar",
"repair": "Reparar"
},
"auth": {
"paste": "Pegar",
"token": "Token",
"verify": "Verificar Token",
"page_header": "El canal de acceso Early Access te permitirá probar las últimas características experimentales y arreglos antes de que lleguen a yuzu. Este canal incluye todas las actualizaciones diarias de yuzu, además de las características exclusivas.\n\nPara unirte al Early Access, debes ser un Suscriptor del Early Access en Patreon.",
"page_opened": "¡Página abierta! Comprueba la página en tu explorador por defecto, y sigue las instrucciones para vincular tu cuenta de Patreon.\nCuando termines, introduzca el token abajo.",
"login_failed": "¡Acceso fallido!\n\nComprueba que tu token sea el correcto e inténtelo de nuevo"
},
"back": "Atrás",
"exit": "Salir",
"yes": "Sí",
"no": "No",
"continue": "Continuar",
"cancel": "Cancelar",
"app": {
"installer_title": "¡Te damos la bienvenida al instalador de {name}!",
"maintenance_title": "Te damos la bienvenida a la Herramienta de Mantenimiento de {name}.",
"window_title": "Instalador de {name}",
"installer_subtitle": "Tendremos todo listo y funcional en unos minutos."
},
"select_packages": {
"title": "Seleccione los paquetes que desea instalar:",
"installed": "(instalado)",
"advanced": "Avanzado...",
"install": "Instalar",
"location": "Ubicación de la instalación",
"select": "Elegir",
"overwriting": "Sobreescribir",
"options": "Opciones de instalación",
"title_repair": "Seleccione los paquetes que desea reparar:",
"location_placeholder": "Introduce una ruta de instalación aquí",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "¡Por favor, seleccione al menos un paquete para la instalación!",
"nothing_picked": "Nada seleccionado",
"option_shortcut": "Crear acceso directo en el escritorio"
},
"install_packages": {
"uninstall": "Desinstalando...",
"install": "Instalando...",
"check_for_update": "Comprobando actualizaciones...",
"self_update": "Descargando actualización automática...",
"please_wait": "Por favor, espere..."
},
"download_packages": {
"download_config": "Descargando config...",
"error_download_config": "Se ha producido un error durante la descarga del config: {msg}"
}
}
}

78
ui/src/locales/fr.json Normal file
View file

@ -0,0 +1,78 @@
{
"fr": {
"locale": "Français",
"error": {
"title": "Une erreur s'est produite",
"exit_error": "{msg}\n\nVeuillez téléverser votre fichier journal (dans {path}) à l'équipe {name}",
"location_unknown": "l'emplacement où se trouve ce programme d'installation"
},
"complete": {
"thanks": "Merci d'avoir installé {name}!",
"updated": "{name} a été mis à jour.",
"uninstalled": "{name} a été désinstallé.",
"up_to_date": "{name} est déjà à jour!",
"where_to_find": "Vous pouvez trouver vos applications installées dans votre menu démarrer.",
"migration_where_to_find": "Vous pouvez trouver vos applications installées dans votre menu démarrer - si vous étiez au milieu de quelque chose, réessayez.",
"migration_finished": "Vous avez été déplacé vers la nouvelle version unique de {name}."
},
"modify": {
"title": "Choisissez une option:",
"update": "Mettre à jour",
"uninstall": "Désinstaller",
"prompt": "Êtes-vous certain de vouloir désinstaller {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Afficher les fichiers locaux",
"prompt_recover": "Les données du programme d'installation pour {name} sont corrompues.<br> Une réparation est requise pour restaurer l'installation.",
"prompt_confirm": "Désinstaller {name}",
"modify": "Modifier",
"repair": "Réparer"
},
"auth": {
"paste": "Coller",
"token": "Jeton",
"verify": "Vérifier le jeton",
"page_header": "Le canal de publication Early Access vous permet d'essayer les dernières fonctionnalités expérimentales et correctifs, avant qu'elles ne soient fusionnées dans yuzu. Cette chaîne comprend toutes les mises à jour quotidiennes régulières de yuzu, ainsi que des fonctionnalités exclusives.\n\nPour être un membre Early Access, vous devez être membre Patreon Early Access.",
"page_opened": "Page ouverte! Vérifiez votre navigateur par défaut pour la page et suivez les instructions pour lier votre compte Patreon.\nLorsque vous avez terminé, entrez le jeton ci-dessous.",
"login_failed": "Échec de la connexion!\n\nVérifiez que votre jeton est valide et réessayez"
},
"back": "Retour",
"exit": "Quitter",
"yes": "Oui",
"no": "Non",
"continue": "Continuer",
"cancel": "Annuler",
"app": {
"installer_title": "Bienvenue dans le programme d'installation de {name}!",
"maintenance_title": "Bienvenue dans l'outil de maintenance de {name}.",
"window_title": "Programme d'installation de {name}",
"installer_subtitle": "Vous serez prêt dans quelques instants."
},
"select_packages": {
"title": "Sélectionner les paquets que vous désirez installer:",
"installed": "(installé)",
"advanced": "Avancé...",
"install": "Installer",
"location": "Emplacement d'installation",
"select": "Sélectionner",
"overwriting": "Écrasement",
"options": "Options d'installation",
"title_repair": "Sélectionnez les paquets que vous désirez réparer:",
"location_placeholder": "Entrez un chemin d'installation ici",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Veuillez sélectionner au moins un paquet à installer!",
"nothing_picked": "Aucune sélection",
"option_shortcut": "Créer un raccourci sur le bureau"
},
"install_packages": {
"uninstall": "Désinstallation...",
"install": "Installation...",
"check_for_update": "Vérification des mises à jour...",
"self_update": "Téléchargement de la mise à jour automatique...",
"please_wait": "Veuillez patienter..."
},
"download_packages": {
"download_config": "Téléchargement de la configuration...",
"error_download_config": "Une erreur s'est produite lors du téléchargement de la configuration: {msg}"
}
}
}

78
ui/src/locales/id.json Normal file
View file

@ -0,0 +1,78 @@
{
"id": {
"locale": "Bahasa Indonesia",
"error": {
"title": "Terjadi kesalahan",
"exit_error": "{msg}\n\nHarap unggah file log (di {path}) kepada tim {name}",
"location_unknown": "lokasi dimana penginstal ini berada"
},
"complete": {
"thanks": "Terima kasih telah menginstal {name}!",
"updated": "{name} telah diperbaharui.",
"uninstalled": "{name} telah di hapus.",
"up_to_date": "{name} sudah up-to-date!",
"where_to_find": "Anda dapat menemukan aplikasi yang terinstal di start menu anda.",
"migration_where_to_find": "Anda dapat menemukan aplikasi yang terinstal di start menu anda - jika anda sedang mengerjakan sesuatu yang lain, coba lagi.",
"migration_finished": "Anda telah dipindahkan ke versi tunggal yang baru dari {name}."
},
"modify": {
"title": "Pilih opsi:",
"update": "Perbaharui",
"uninstall": "Copot Instalasi",
"prompt": "Apakah anda yakin untuk menghapus {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Lihat file lokal",
"prompt_recover": "Data instalatur untuk {name} rusak.<br>Diperlukan perbaikan untuk memulihkan instalasi.",
"prompt_confirm": "Hapus {name}",
"modify": "Modifikasi",
"repair": "Perbaiki"
},
"auth": {
"paste": "Tempel",
"token": "Token",
"verify": "Verifikasi Token",
"page_header": "Saluran rilis Early Access memperbolehkan anda untuk mencoba fitur eksperimental dan perbaikan terbaru sebelum mereka disatukan menjadi yuzu. Saluran ini mencakup semua pembaruan harian yuzu reguler, ditambah fitur-fitur ekslusif ini.\n\nUntuk menjadi anggota Early Access, anda harus menjadi pelanggan Early Access Patreon.",
"page_opened": "Halaman dibuka! Periksa peramban anda untuk halamannya, dan ikuti instruksi yang ada untuk menghubungkan akun patreon anda.\nJika anda sudah selesai, masukkan token di bawah.",
"login_failed": "Login gagal!\n\nPeriksa kembali token anda dan coba lagi"
},
"back": "Kembali",
"exit": "Keluar",
"yes": "Ya",
"no": "Tidak",
"continue": "Lanjut",
"cancel": "Batalkan",
"app": {
"installer_title": "Selamat datang di instalatur {name}!",
"maintenance_title": "Selamat datang di Maintenance Tool {name}.",
"window_title": "{name} Instalatur",
"installer_subtitle": "Kami akan membuat anda siap dan berjalan hanya dalam beberapa saat."
},
"select_packages": {
"title": "Pilih paket mana yang ingin Anda install:",
"installed": "(terinstal)",
"advanced": "Lanjutan...",
"install": "Instal",
"location": "Lokasi Instal",
"select": "Pilih",
"overwriting": "Menimpa",
"options": "Opsi Instal",
"title_repair": "Pilih paket mana yang ingin anda perbaiki:",
"location_placeholder": "Masukkan lokasi instal di sini",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Silakan pilih setidaknya satu paket untuk diinstal!",
"nothing_picked": "Tidak ada yang dipilih",
"option_shortcut": "Buat Pintasan Desktop"
},
"install_packages": {
"uninstall": "Mencopot Instalan...",
"install": "Menginstal...",
"check_for_update": "Memeriksa pembaruan...",
"self_update": "Mengunduh pembaruan diri...",
"please_wait": "Mohon tunggu..."
},
"download_packages": {
"download_config": "Mengunduh konfigurasi...",
"error_download_config": "Mendapatkan kesalahan saat mengunduh konfigurasi: {msg}"
}
}
}

78
ui/src/locales/it.json Normal file
View file

@ -0,0 +1,78 @@
{
"it": {
"locale": "Italiano",
"error": {
"title": "Si è verificato un errore",
"exit_error": "{msg}\n\nPer favore carica il file di registro (in {path}) nel {name}",
"location_unknown": "la posizione in cui si trova il programma di installazione è"
},
"complete": {
"thanks": "Grazie per aver installato {name}!",
"updated": "{name} è stato aggiornato.",
"uninstalled": "{name} è stato disinstallato.",
"up_to_date": "{name} è già aggiornato!",
"where_to_find": "Puoi trovare le applicazioni installate nel tuo menu di avvio.",
"migration_where_to_find": "Puoi trovare le applicazioni installate nel tuo menu di avvio",
"migration_finished": "Sei stato spostato alla nuova versione di {name}."
},
"modify": {
"title": "Scegli un'opzione:",
"update": "Aggiorna",
"uninstall": "Disinstalla",
"prompt": "Sei sicuro di voler disinstallare {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Visualizza i file locali",
"prompt_recover": "I dati di installazione del programma {name} sono danneggiati. <br>È necessaria la riparazione per ripristinare l'installazione.",
"prompt_confirm": "Disinstalla {name}",
"modify": "Modifica",
"repair": "Ripara"
},
"auth": {
"paste": "Incolla",
"token": "Token",
"verify": "Verifica token",
"page_header": "Il canale di rilascio dell'accesso anticipato ti consente di provare le ultime funzionalità e correzioni sperimentali, prima che vengano rilasciate in yuzu. Questo canale include tutti gli aggiornamenti quotidiani regolari di yuzu, oltre ad altre funzionalità esclusive.\nPer essere un membro Early Access, devi essere abbonato a Patreon Early Access.",
"page_opened": "Pagina aperta! Controlla il tuo browser e segui le istruzioni per collegare il tuo account patreon.\nQuando hai finito, inserisci il token qui sotto.",
"login_failed": "Accesso fallito!\n\nRicontrolla che il tuo token sia corretto e riprova"
},
"back": "Indietro",
"exit": "Esci",
"yes": "Si",
"no": "No",
"continue": "Continua",
"cancel": "Annulla",
"app": {
"installer_title": "Benvenuto nell'installer di {name}!",
"maintenance_title": "Benvenuto nello strumento di manutenzione di {name}.",
"window_title": "Installer di {name}",
"installer_subtitle": "Sarà pronto in pochi istanti."
},
"select_packages": {
"title": "Seleziona i pacchetti che vuoi installare:",
"installed": "(installato)",
"advanced": "Avanzate",
"install": "Installa",
"location": "Percorso di installazione",
"select": "Seleziona",
"overwriting": "Sovrascrivendo",
"options": "Opzioni di installazione",
"title_repair": "Seleziona i pacchetti da riparare:",
"location_placeholder": "Inserisci un percorso di installazione",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Seleziona almeno un pacchetto da installare!",
"nothing_picked": "Niente di selezionato",
"option_shortcut": "Crea una scorciatoia sul desktop"
},
"install_packages": {
"uninstall": "Sto disinstallando...",
"install": "Installazione in corso...",
"check_for_update": "Ricerca di aggiornamenti in corso...",
"self_update": "Download dell'aggiornamento in corso...",
"please_wait": "Attendere prego..."
},
"download_packages": {
"download_config": "Scaricamento della configurazione....",
"error_download_config": "Si è verificato un errore durante il download della configurazione: {msg}"
}
}
}

78
ui/src/locales/ko_KR.json Normal file
View file

@ -0,0 +1,78 @@
{
"ko-KR": {
"locale": "한국어",
"error": {
"title": "오류가 발생했습니다",
"exit_error": "{msg}\n\n{path}에 있는 로그 파일을 {name} 에 업로드하세요",
"location_unknown": "설치 프로그램이 있는 위치"
},
"complete": {
"thanks": "{name} 을 설치해 주셔서 감사합니다!",
"updated": "{name} 이 업데이트 되었습니다",
"uninstalled": "{name} 이 제거 되었습니다",
"up_to_date": "{name} 은(는) 이미 최신 버전입니다!",
"where_to_find": "시작 메뉴에서 설치한 프로그램을 찾을 수 있습니다",
"migration_where_to_find": "시작 메뉴에서 설치된 응용 프로그램을 찾을 수 있습니다. 설치중이었다면 다시 시도하십시오.",
"migration_finished": "{name}의 새로운 단일 버전으로 이동되었습니다."
},
"modify": {
"title": "옵션 선택:",
"update": "업데이트",
"uninstall": "제거",
"prompt": "정말 {name} 을 제거하시겠습니까?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "로컬 파일 보기",
"prompt_recover": "{name}의 설치 프로그램 데이터가 손상되었습니다.<br>설치를 복원하려면 복구가 필요합니다.",
"prompt_confirm": "{name} 삭제",
"modify": "수정",
"repair": "복구"
},
"auth": {
"paste": "붙여넣기",
"token": "토큰",
"verify": "검증 토큰",
"page_header": "얼리 엑세스 릴리스 채널을 통해 yuzu에 병합되기 전에 최신 실험 기능 및 수정 사항을 시험해 볼 수 있습니다. 이 채널에는 모든 정기 yuzu 일일 업데이트와 이러한 독점 기능이 포함됩니다.\n\nEarly Access 회원이 되려면 Patreon Early Access 가입자여야 합니다.",
"page_opened": "페이지가 열렸습니다! 페이지의 기본 브라우저를 확인하고 해당 페이지의 지침에 따라 Patreon 계정을 연결하세요.\n완료되면 아래 토큰을 입력하십시오.",
"login_failed": "로그인 실패!\n\n토큰이 올바른지 다시 확인하고 다시 시도하세요"
},
"back": "뒤로",
"exit": "종료",
"yes": "예",
"no": "아니요",
"continue": "계속",
"cancel": "취소",
"app": {
"installer_title": "{name} 설치 프로그램에 오신 것을 환영합니다!",
"maintenance_title": "{name} 관리 도구에 오신 것을 환영합니다!",
"window_title": "{name} 설치 프로그램",
"installer_subtitle": "몇 분 안에 준비하여 실행할 수 있습니다"
},
"select_packages": {
"title": "설치할 패키지를 선택하세요:",
"installed": "(설치됨)",
"advanced": "고급...",
"install": "설치",
"location": "설치 위치",
"select": "선택",
"overwriting": "덮어쓰기",
"options": "설치 옵션",
"title_repair": "복구할 패키지 선택:",
"location_placeholder": "여기에 설치 경로를 입력하세요",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "설치할 패키지를 하나 이상 선택하세요!",
"nothing_picked": "아무것도 선택되지 않았습니다",
"option_shortcut": "바탕화면 바로가기 만들기"
},
"install_packages": {
"uninstall": "제거중...",
"install": "설치중...",
"check_for_update": "업데이트 확인 중...",
"self_update": "수동 업데이트 다운로드중...",
"please_wait": "잠시 기다려 주세요..."
},
"download_packages": {
"download_config": "설정 파일 다운로드중...",
"error_download_config": "설정 파일 다운로드 중 오류 발생: {msg}"
}
}
}

78
ui/src/locales/nb.json Normal file
View file

@ -0,0 +1,78 @@
{
"nb": {
"locale": "Bokmål",
"error": {
"title": "En feil oppsto",
"exit_error": "{msg}\n\nVennlist last opp loggfilen (i {path} til {name}-teamet",
"location_unknown": "plasseringen til installasjonsprogrammet"
},
"complete": {
"thanks": "Takk for at du installerte {name}!",
"updated": "{name} har blitt oppdatert.",
"uninstalled": "{name} har blitt avinstallert.",
"up_to_date": "{name} er allerede oppdatert!",
"where_to_find": "Du kan finne de installerte programmene i startmenyen.",
"migration_where_to_find": "Du kan finne de installerte programmene i startmenyen hvis du var midt oppi noe, bare prøv på nytt.",
"migration_finished": "Du har blitt flyttet til den nye, forente versjonen av {name}."
},
"modify": {
"title": "Velg en handling:",
"update": "Oppdatering",
"uninstall": "Avinstaller",
"prompt": "Er du sikker på at du vil avinstallere {navn}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Vis lokale filer",
"prompt_recover": "Installasjonsdata for {name} er korrupt.<br>Reparasjon er nødvendig for å gjenopprette installasjonen.",
"prompt_confirm": "Avinstaller {name}",
"modify": "Modifiser",
"repair": "Reparer"
},
"auth": {
"paste": "Lim inn",
"token": "Token",
"verify": "Verifiser token",
"page_header": "Early Accesskanalen lar deg prøve den siste eksperimentelle funksjonaliteten og fiksene, før de kommer inn i yuzu. Denne kanalen inkluderer alle de vanlige daglige yuzu-oppdateringene, i tillegg til denne eksklusive funksjonaliteten.\n\nFor å bli et Early Accessmedlem må du være en Patreon Early Accessabonnent.",
"page_opened": "Side åpnet! Se etter siden i standardnettleseren din, og følg instruksjonene der for å koble til Patreon-kontoen din.\nNår du er ferdig, skriv inn token-en under.",
"login_failed": "Innlogging feilet!\n\nDobbeltsjekk at token-en din er korrekt og prøv på nytt"
},
"back": "Tilbake",
"exit": "Avslutt",
"yes": "Ja",
"no": "Nei",
"continue": "Fortsett",
"cancel": "Avbryt",
"app": {
"installer_title": "Velkommen til installasjonsprogrammet for {navn}!",
"maintenance_title": "Velkommen til vedlikeholdsverktøyet for {navn}.",
"window_title": "Installasjonsprogram for {navn}",
"installer_subtitle": "Vi er klar til å kjøre om noen få øyeblikk."
},
"select_packages": {
"title": "Velg hvilke pakker du ønsker å installere:",
"installed": "(installert)",
"advanced": "Avansert...",
"install": "Installer",
"location": "Installasjonsdestinasjon",
"select": "Velg",
"overwriting": "Overskriver",
"options": "Installasjonsinnstillinger",
"title_repair": "Velg hvilke pakker du ønsker å reparere:",
"location_placeholder": "Skriv inn en installasjonssti her",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Vennligst velg minst én pakke å installere!",
"nothing_picked": "Ingen ting valgt",
"option_shortcut": "Lag skrivebordssnarvei"
},
"install_packages": {
"uninstall": "Avinstallerer...",
"install": "Installerer...",
"check_for_update": "Ser etter oppdateringer...",
"self_update": "Laster ned selvoppdatering...",
"please_wait": "Vennligst vent..."
},
"download_packages": {
"download_config": "Laster ned konfigurasjon...",
"error_download_config": "Mottok feilmelding under nedlasting av konfigurasjon: {msg}"
}
}
}

78
ui/src/locales/pl.json Normal file
View file

@ -0,0 +1,78 @@
{
"pl": {
"locale": "Polski",
"error": {
"title": "Wystąpił błąd",
"exit_error": "{msg}\n\nPrześlij plik dziennika (w {path}) do zespołu {name}",
"location_unknown": "lokalizacja, w której znajduje się ten instalator"
},
"complete": {
"thanks": "Dziękujemy za zainstalowanie {name}!",
"updated": "{name} zostało zaktualizowane.",
"uninstalled": "{name} zostało odinstalowane.",
"up_to_date": "{name} jest już aktualne!",
"where_to_find": "Zainstalowane aplikacje można znaleźć w menu Start.",
"migration_where_to_find": "Zainstalowane aplikacje możesz znaleźć w menu Start - jeśli byłeś w trakcie czegoś, po prostu spróbuj ponownie.",
"migration_finished": "Zostałeś przeniesiony do nowej, pojedynczej wersji {name}."
},
"modify": {
"title": "Wybierz opcję:",
"update": "Zaktualizuj",
"uninstall": "Odinstaluj",
"prompt": "Czy na pewno chcesz odinstalować {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Wyświetl lokalne pliki",
"prompt_recover": "Dane instalatora dla {name} są uszkodzone. <br>Do przywrócenia instalacji wymagana jest naprawa.",
"prompt_confirm": "Odinstaluj {name}",
"modify": "Modyfikuj",
"repair": "Napraw"
},
"auth": {
"paste": "Wklej",
"token": "Token",
"verify": "Zweryfikuj Token",
"page_header": "Kanał wersji Early Access pozwala wypróbować najnowsze eksperymentalne funkcje i poprawki, zanim zostaną one połączone z yuzu. Ten kanał zawiera wszystkie regularne codzienne aktualizacje yuzu oraz te ekskluzywne funkcje.\n\nAby zostać członkiem Early Access, musisz być subskrybentem Patreon Early Access.",
"page_opened": "Strona otwarta! Sprawdź domyślną przeglądarkę dla strony i postępuj zgodnie z instrukcjami, aby połączyć swoje konto patreon.\nGdy skończysz, wprowadź poniższy token.",
"login_failed": "Logowanie nie powiodło się!\n\nSprawdź dokładnie, czy Twój Token jest poprawny i spróbuj ponownie."
},
"back": "Cofnij",
"exit": "Wyjście",
"yes": "Tak",
"no": "Nie",
"continue": "Kontynuuj",
"cancel": "Anuluj",
"app": {
"installer_title": "Witaj w instalatorze {name}!",
"maintenance_title": "Witaj w narzędziu do konserwacji {name}!",
"window_title": "Instalator {name}",
"installer_subtitle": "Już za chwilę będziesz mógł pracować."
},
"select_packages": {
"title": "Wybierz które pakiety chcesz zainstalować:",
"installed": "(zainstalowano)",
"advanced": "Zaawansowane...",
"install": "Zainstaluj",
"location": "Lokacja instalacji",
"select": "Wybierz",
"overwriting": "Nadpisywanie",
"options": "Opcje instalacji",
"title_repair": "Wybierz które pakiety chcesz naprawić:",
"location_placeholder": "Wybierz miejsce instalacji",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Wybierz co najmniej jeden pakiet do zainstalowania!",
"nothing_picked": "Nic nie wybrano",
"option_shortcut": "Utwórz skrót na pulpicie"
},
"install_packages": {
"uninstall": "Odinstalowywanie...",
"install": "Instalowanie...",
"check_for_update": "Sprawdzanie aktualizacji...",
"self_update": "Pobieranie samoaktualizacji...",
"please_wait": "Proszę czekać..."
},
"download_packages": {
"download_config": "Pobieranie konfiguracji...",
"error_download_config": "Wystąpił błąd podczas pobierania konfiguracji: {msg}"
}
}
}

78
ui/src/locales/pt_BR.json Normal file
View file

@ -0,0 +1,78 @@
{
"pt-BR": {
"locale": "Português do Brasil",
"error": {
"title": "Ocorreu um erro",
"exit_error": "{msg}\n\nPor favor, carregue o arquivo de registro (em {path}) para a equipe do {name}.",
"location_unknown": "o local onde se encontra este instalador"
},
"complete": {
"thanks": "Obrigado por instalar o {name}!",
"updated": "O {name} foi atualizado.",
"uninstalled": "O {name} foi desinstalado.",
"up_to_date": "O {name} já está atualizado!",
"where_to_find": "Você pode encontrar suas aplicações instaladas no seu menu inicial.",
"migration_where_to_find": "Você pode encontrar suas aplicações instaladas no seu menu inicial - se estiver no meio de alguma coisa, é só tentar novamente.",
"migration_finished": "Você foi movido para a nova versão única do {name}."
},
"modify": {
"title": "Escolha uma opção:",
"update": "Atualizar",
"uninstall": "Desinstalar",
"prompt": "Você tem certeza de que deseja desinstalar o {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Ver arquivos locais",
"prompt_recover": "Os dados do instalador do {name} estão corrompidos.<br>Um reparo é necessário para restaurar a instalação.",
"prompt_confirm": "Desinstalar o {name}",
"modify": "Modificar",
"repair": "Reparar"
},
"auth": {
"paste": "Colar",
"token": "Token",
"verify": "Verificar o Token",
"page_header": "O canal da versão de Acesso Antecipado permite que você experimente as últimas características experimentais e correções, antes que elas sejam fundidas no yuzu. Este canal inclui todas as atualizações diárias regulares do yuzu, além destas características exclusivas.\n\nPara ser um membro do Acesso Antecipado, você deve ser um assinante do Patreon de Acesso Antecipado.",
"page_opened": "Página aberta! Verifique seu navegador padrão com a página, e siga as instruções lá para vincular sua conta do patreon.\nQuando terminar, digite o token abaixo.",
"login_failed": "Falha no login!\n\nVerifique duas vezes se seu token está correto e tente novamente"
},
"back": "Voltar",
"exit": "Sair",
"yes": "Sim",
"no": "Não",
"continue": "Continuar",
"cancel": "Cancelar",
"app": {
"installer_title": "Bem-vindo ao instalador do {name}!",
"maintenance_title": "Bem-vindo à Ferramenta de Manutenção do {name}.",
"window_title": "Instalador do {name}",
"installer_subtitle": "Em poucos instantes, estaremos prontos para começar."
},
"select_packages": {
"title": "Selecione quais pacotes você deseja instalar:",
"installed": "(instalado)",
"advanced": "Avançado...",
"install": "Instalar",
"location": "Local de instalação",
"select": "Selecionar",
"overwriting": "Sobrescrever",
"options": "Opções de instalação",
"title_repair": "Selecione quais pacotes você deseja reparar:",
"location_placeholder": "Insira aqui um caminho de instalação",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Por favor, selecione pelo menos um pacote para instalar!",
"nothing_picked": "Nada selecionado",
"option_shortcut": "Criar atalho na Área de Trabalho"
},
"install_packages": {
"uninstall": "Desinstalando...",
"install": "Instalando...",
"check_for_update": "Verificando se há atualizações...",
"self_update": "Baixando a auto-atualização...",
"please_wait": "Por favor, aguarde..."
},
"download_packages": {
"download_config": "Baixando configuração...",
"error_download_config": "Houve um erro durante o download da configuração: {msg}"
}
}
}

78
ui/src/locales/pt_PT.json Normal file
View file

@ -0,0 +1,78 @@
{
"pt-PT": {
"locale": "Português",
"error": {
"title": "Ocorreu um erro",
"exit_error": "{msg}\n\nPor favor, carregue o arquivo de registro (em {path}) para a equipe do {name}.",
"location_unknown": "o local onde se encontra este instalador"
},
"complete": {
"thanks": "Obrigado por instalar o {name}!",
"updated": "O {name} foi atualizado.",
"uninstalled": "O {name} foi desinstalado.",
"up_to_date": "O {name} já está atualizado!",
"where_to_find": "Você pode encontrar suas aplicações instaladas no seu menu inicial.",
"migration_where_to_find": "Você pode encontrar suas aplicações instaladas no seu menu inicial - se estiver no meio de alguma coisa, é só tentar novamente.",
"migration_finished": "Você foi movido para a nova versão única do {name}."
},
"modify": {
"title": "Escolha uma opção:",
"update": "Actualização",
"uninstall": "Desinstalar",
"prompt": "Você tem certeza de que deseja desinstalar o {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Ver arquivos locais",
"prompt_recover": "Os dados do instalador do {name} estão corrompidos.<br>Um reparo é necessário para restaurar a instalação.",
"prompt_confirm": "Desinstalar o {name}",
"modify": "Modificar",
"repair": "Reparar"
},
"auth": {
"paste": "Colar",
"token": "Token",
"verify": "Verificar o Token",
"page_header": "O canal da versão de Acesso Antecipado permite que você experimente as últimas características experimentais e correções, antes que elas sejam fundidas no yuzu. Este canal inclui todas as atualizações diárias regulares do yuzu, além destas características exclusivas.\n\nPara ser um membro do Acesso Antecipado, você deve ser um assinante do Patreon de Acesso Antecipado.",
"page_opened": "Página aberta! Verifique seu navegador padrão com a página, e siga as instruções lá para vincular sua conta do patreon.\nQuando terminar, digite o token abaixo.",
"login_failed": "Falha no login!\n\nVerifique duas vezes se seu token está correto e tente novamente"
},
"back": "Voltar",
"exit": "Sair",
"yes": "Sim",
"no": "Não",
"continue": "Continuar",
"cancel": "Cancelar",
"app": {
"installer_title": "Bem-vindo ao instalador do {name}!",
"maintenance_title": "Bem-vindo à Ferramenta de Manutenção do {name}.",
"window_title": "Instalador do {name}",
"installer_subtitle": "Em poucos instantes, estaremos prontos para começar."
},
"select_packages": {
"title": "Selecione quais pacotes você deseja instalar:",
"installed": "(instalado)",
"advanced": "Avançado...",
"install": "Instalar",
"location": "Local de instalação",
"select": "Selecionar",
"overwriting": "Sobrescrever",
"options": "Opções de instalação",
"title_repair": "Selecione quais pacotes você deseja reparar:",
"location_placeholder": "Insira aqui um caminho de instalação",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Por favor, selecione pelo menos um pacote para instalar!",
"nothing_picked": "Nada selecionado",
"option_shortcut": "Criar atalho na Área de Trabalho"
},
"install_packages": {
"uninstall": "Desinstalando...",
"install": "Instalando...",
"check_for_update": "Verificando se há atualizações...",
"self_update": "Baixando a auto-atualização...",
"please_wait": "Por favor, aguarde..."
},
"download_packages": {
"download_config": "Baixando configuração...",
"error_download_config": "Houve um erro durante o download da configuração: {msg}"
}
}
}

78
ui/src/locales/ru_RU.json Normal file
View file

@ -0,0 +1,78 @@
{
"ru-RU": {
"locale": "Русский",
"error": {
"title": "Произошла ошибка",
"exit_error": "{msg}\n\nПожалуйста, загрузите файл журнала (в {path}) и отправьте его команде {name}",
"location_unknown": "расположение, в котором находится установщик"
},
"complete": {
"thanks": "Спасибо за установку {name}!",
"updated": "{name} был обновлён.",
"uninstalled": "{name} был удалён.",
"up_to_date": "{name} уже обновлён!",
"where_to_find": "Установленные приложения можно найти в меню \"Пуск\".",
"migration_where_to_find": "Установленные приложения можно найти в меню \"Пуск\" - если вы были заняты чем-то, просто повторите попытку.",
"migration_finished": "Вы были переведены на новую, единую версию {name}."
},
"modify": {
"title": "Выберите вариант:",
"update": "Обновить",
"uninstall": "Удалить",
"prompt": "Вы уверены, что хотите удалить {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Просмотреть локальные файлы",
"prompt_recover": "Файлы установщика {name} повреждены.<br>Для восстановления установки требуется починка.",
"prompt_confirm": "Удалить {name}",
"modify": "Изменить",
"repair": "Исправить"
},
"auth": {
"paste": "Вставить",
"token": "Токен",
"verify": "Проверить токен",
"page_header": "Канал выпуска Early Access позволяет вам опробовать последние экспериментальные функции и исправления, прежде чем они будут внедрены в yuzu. Этот канал включает все обычные ежедневные обновления yuzu, а также эти эксклюзивные возможности.\n\nЧтобы стать участником Early Access, вы должны быть подписчиком Patreon Early Access.",
"page_opened": "Страница открыта! Проверьте страницу на вашем браузере по умолчанию, и следуйте инструкциям, чтобы связать свой аккаунт Patreon.\nКогда вы закончите, введите токен ниже.",
"login_failed": "Вход не удался!\n\nПроверьте правильность вашего токена и повторите попытку"
},
"back": "Назад",
"exit": "Выход",
"yes": "Да",
"no": "Нет",
"continue": "Продолжить",
"cancel": "Отмена",
"app": {
"installer_title": "Добро пожаловать в установщик {name}!",
"maintenance_title": "Добро пожаловать в Инструмент Обслуживания {name}.",
"window_title": "Установщик {name}",
"installer_subtitle": "Мы сейчас всё для вас подготовим."
},
"select_packages": {
"title": "Выберите пакеты для установки:",
"installed": "(установлено)",
"advanced": "Дополнительно...",
"install": "Установить",
"location": "Расположение установки",
"select": "Выбрать",
"overwriting": "Перезапись",
"options": "Параметры установки",
"title_repair": "Выберите пакеты для починки:",
"location_placeholder": "Введите путь установки здесь",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Пожалуйста, выберите хотя-бы один пакет для установки!",
"nothing_picked": "Ничего не выбрано",
"option_shortcut": "Создать ярлык на Рабочем Столе"
},
"install_packages": {
"uninstall": "Удаление...",
"install": "Установка...",
"check_for_update": "Проверка обновлений...",
"self_update": "Загрузка самообновления...",
"please_wait": "Пожалуйста, подождите..."
},
"download_packages": {
"download_config": "Загрузка конфигурации...",
"error_download_config": "Произошла ошибка при загрузке конфигурации: {msg}"
}
}
}

78
ui/src/locales/tr_TR.json Normal file
View file

@ -0,0 +1,78 @@
{
"tr-TR": {
"locale": "Türkçe",
"error": {
"title": "Bir hata oluştu",
"exit_error": "{msg}\n\nLütfen ({path}'deki) log dosyasını {name} takımına gönderin",
"location_unknown": "bu yükleyicinin bulunduğu konum"
},
"complete": {
"thanks": "Yüklediğiniz için teşekkür ederiz, {name}!",
"updated": "{name} güncellendi.",
"uninstalled": "{name} kaldırıldı",
"up_to_date": "{name} zaten güncel!",
"where_to_find": "Yüklü uygulamalarınızı start menüsünde bulabilirsiniz.",
"migration_where_to_find": "Yüklü uygulamalarınızı start menüsünde bulabilirsiniz - eğer bir şeyle uğraşıyorduysanız sadece yeniden deneyin.",
"migration_finished": "{name}'in en yeni versiyonuna taşındınız."
},
"modify": {
"title": "Birini seçin:",
"update": "Güncelle",
"uninstall": "Kaldır",
"prompt": "{name}'i kaldırmak istediğinizden emin misiniz?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Yerel dosyaları görüntüle",
"prompt_recover": "{name} yükleyici dosyaları bozuk.<br>Yüklemeyi düzeltmek için onarım gerekli.",
"prompt_confirm": "{name}'i kaldır",
"modify": "Düzenle",
"repair": "Onar"
},
"auth": {
"paste": "Yapıştır",
"token": "Token",
"verify": "Token'i Doğrula",
"page_header": "Erken Erişim Kanalı, en son deneysel özellik ve düzeltmeleri yuzuya eklenmeden denemenizi sağlar. Bu kanal tüm günlük yuzu güncellemelerini ve üstüne bütün özel özellikleri içerir.\n\nBir Erken Erişim üyesi olmak için Patreon Erken Erişim üyesi olmalısınız.",
"page_opened": "Sayfa açıldı! Patreon hesabınızı bağlamak için tarayıcınızda açılan sayfadaki adımları takip edin.\nİşiniz bittiğinde tokeninizi aşağıya girin.",
"login_failed": "Giriş başarısız oldu!\n\nToken'inizin doğruluğunu kontrol edip yeniden deneyin"
},
"back": "Geri",
"exit": ık",
"yes": "Evet",
"no": "Hayır",
"continue": "Devam Et",
"cancel": "İptal",
"app": {
"installer_title": "{name} yükleyicisine hoş geldiniz!",
"maintenance_title": "{name} Bakım Aracına Hoş Geldiniz.",
"window_title": "{name} Yükleyicisi",
"installer_subtitle": "Birazdan hepsi hallolacak."
},
"select_packages": {
"title": "Yüklemek istediğiniz paketleri seçin:",
"installed": "(Yüklendi)",
"advanced": "Gelişmiş...",
"install": "Yükle",
"location": "Yükleme Konumu",
"select": "Seç",
"overwriting": "Üstüne yazma",
"options": "Yükleme Seçenekleri",
"title_repair": "Onarmak istediğiniz paketleri seçin:",
"location_placeholder": "Bir yükleme konumu belirleyin",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Lütfen yüklemek için en az bir paket seçin!",
"nothing_picked": "Hiçbir şey seçilmedi",
"option_shortcut": "Masaüstü Kısayolu Oluştur"
},
"install_packages": {
"uninstall": "Kaldırılıyor...",
"install": "Yükleniyor...",
"check_for_update": "Güncellemeler kontrol ediliyor...",
"self_update": "Güncelleme indiriliyor...",
"please_wait": "Lütfen bekleyin..."
},
"download_packages": {
"download_config": "Yapılandırma indiriliyor...",
"error_download_config": "Yapılandırma indirilirken bir hata oluştu: {msg}"
}
}
}

78
ui/src/locales/vi.json Normal file
View file

@ -0,0 +1,78 @@
{
"vi": {
"locale": "Tiếng Việt",
"error": {
"title": "Có lỗi xảy ra",
"exit_error": "{msg}\n\nVui lòng tải lên bản báo cáo (in {path}) cho đội {name}",
"location_unknown": "nơi trình cài đặt được đặt"
},
"complete": {
"thanks": "Cảm ơn bạn đã cài {name}!",
"updated": "{name} đã được cập nhật.",
"uninstalled": "{name} đã được gỡ cài đặt.",
"up_to_date": "{name} đã ở phiên bản mới nhất!",
"where_to_find": "Bạn có thể tìm thấy các ứng dụng đã cài trong menu Start",
"migration_where_to_find": "Bạn có thể tìm thấy các ứng dụng đã cài trong menu Start - nếu đang làm dở việc gì thì cứ thử lại.",
"migration_finished": "Bạn đã được chuyển tới bản mới của {name}"
},
"modify": {
"title": "Chọn một sự lựa chọn:",
"update": "Cập nhật",
"uninstall": "Gỡ cài đặt",
"prompt": "Bạn có chắc bạn muốn gỡ cài đặt {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Xem các tập tin trên máy",
"prompt_recover": "Dữ liệu cài đặt cho {name} đã bị lỗi.<br>Bạn cần thực hiện vá lỗi để khôi phục lại bản cài đặt.",
"prompt_confirm": "Gỡ cài đặt {name}",
"modify": "Chỉnh sửa",
"repair": "Sửa chữa"
},
"auth": {
"paste": "Dán",
"token": "Token",
"verify": "Kiểm tra Token",
"page_header": "Kênh Truy Cập Sớm cho phép bạn thử một số chức năng và vá lỗi trong giai đoạn thử nghiệm trước khi nó được đưa vào yuzu chính thức. Kênh này bao gồm tất cả bản cập nhật yuzu hằng ngày bình thường và một số lợi ích đặc biệt trên.\n\nĐể trở thành một thành viên của kênh Truy Cập Sớm thì bạn phải là một người người ủng hộ và đăng ký kênh Truy Cập Sớm trên Patreon.",
"page_opened": "Trang đã được mở! Kiểm tra trình duyệt mặc định cho trang này, và làm theo hướng dẫn ở đó để liên kết tài khoản Patreon của bạn.\nKhi bạn hoàn thành, hãy nhập token vào bên dưới.",
"login_failed": "Đăng nhập thất bại!\n\nHãy kiểm tra lại token của bạn đã chính xác chưa và thử lại"
},
"back": "Trở lại",
"exit": "Thoát",
"yes": "Có",
"no": "Không",
"continue": "Tiếp tục",
"cancel": "Hủy bỏ",
"app": {
"installer_title": "Chào mừng tới trình cài đặt {name}!",
"maintenance_title": "Chào mừng tới trình bảo trì {name}.",
"window_title": "Trình cài đặt {name}",
"installer_subtitle": "Chúng ta sẽ hoàn thành trong một vài giây lát."
},
"select_packages": {
"title": "Chọn các gói mà bạn muốn cài:",
"installed": "(đã cài đặt)",
"advanced": "Nâng cao...",
"install": "Cài đặt",
"location": "Nơi cài đặt",
"select": "Lựa chọn",
"overwriting": "Đang ghi đè",
"options": "Các thiết lập cài đặt",
"title_repair": "Chọn các gói mà bạn muốn vá lỗir:",
"location_placeholder": "Nhập đường dẫn tới nơi bạn muốn cài vào đây",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Vui lòng chọn ít nhất một gói để cài!",
"nothing_picked": "Chưa chọn gì",
"option_shortcut": "Tạo đường dẫn trên màn hình chính"
},
"install_packages": {
"uninstall": "Đang gỡ cài đặt...",
"install": "Đang cài đặt...",
"check_for_update": "Đang tìm bản cập nhật...",
"self_update": "Đang tải chương trình tự cập nhật...",
"please_wait": "Vui lòng đợi..."
},
"download_packages": {
"download_config": "Đang tải cấu hình",
"error_download_config": "Có lỗi trong khi tải về cấu hình: {msg}"
}
}
}

78
ui/src/locales/vi_VN.json Normal file
View file

@ -0,0 +1,78 @@
{
"vi-VN": {
"locale": "Tiếng Anh",
"error": {
"title": "Có lỗi xảy ra",
"exit_error": "{msg}\n\nVui lòng tải lên bản báo cáo (in {path}) cho đội {name}",
"location_unknown": "nơi trình cài đặt được đặt"
},
"complete": {
"thanks": "Cảm ơn bạn đã cài {name}!",
"updated": "{name} đã được cập nhật.",
"uninstalled": "{name} đã được gỡ cài đặt.",
"up_to_date": "{name} đã ở phiên bản mới nhất!",
"where_to_find": "Bạn có thể tìm thấy các ứng dụng đã cài trong menu Start",
"migration_where_to_find": "Bạn có thể tìm thấy các ứng dụng đã cài trong menu Start - nếu đang làm dở việc gì thì cứ thử lại.",
"migration_finished": "Bạn đã được chuyển tới bản mới của {name}"
},
"modify": {
"title": "Chọn một sự lựa chọn:",
"update": "Cập nhật",
"uninstall": "Gỡ cài đặt",
"prompt": "Bạn có chắc bạn muốn gỡ cài đặt {name}?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "Xem các tập tin trên máy",
"prompt_recover": "Dữ liệu cài đặt cho {name} đã bị lỗi.<br>Bạn cần thực hiện vá lỗi để khôi phục lại bản cài đặt.",
"prompt_confirm": "Gỡ cài đặt {name}",
"modify": "Chỉnh sửa",
"repair": "Sửa chữa"
},
"auth": {
"paste": "Dán",
"token": "Token",
"verify": "Kiểm tra Token",
"page_header": "Kênh Truy Cập Sớm cho phép bạn thử một số chức năng và vá lỗi trong giai đoạn thử nghiệm trước khi nó được đưa vào yuzu chính thức. Kênh này bao gồm tất cả bản cập nhật yuzu hằng ngày bình thường và một số lợi ích đặc biệt trên.\n\nĐể trở thành một thành viên của kênh Truy Cập Sớm thì bạn phải là một người người ủng hộ và đăng ký kênh Truy Cập Sớm trên Patreon.",
"page_opened": "Trang đã được mở! Kiểm tra trình duyệt mặc định cho trang này, và làm theo hướng dẫn ở đó để liên kết tài khoản Patreon của bạn.\nKhi bạn hoàn thành, hãy nhập token vào bên dưới.",
"login_failed": "Đăng nhập thất bại!\n\nHãy kiểm tra lại token của bạn đã chính xác chưa và thử lại"
},
"back": "Trở lại",
"exit": "Thoát",
"yes": "Có",
"no": "Không",
"continue": "Tiếp tục",
"cancel": "Huỷ",
"app": {
"installer_title": "Chào mừng tới trình cài đặt {name}!",
"maintenance_title": "Chào mừng tới trình bảo trì {name}.",
"window_title": "Trình cài đặt {name}",
"installer_subtitle": "Chúng ta sẽ hoàn thành trong một vài giây lát."
},
"select_packages": {
"title": "Chọn các gói mà bạn muốn cài:",
"installed": "(đã cài đặt)",
"advanced": "Nâng cao...",
"install": "Cài đặt",
"location": "Nơi cài đặt",
"select": "Lựa chọn",
"overwriting": "Đang ghi đè",
"options": "Các thiết lập cài đặt",
"title_repair": "Chọn các gói mà bạn muốn vá lỗir:",
"location_placeholder": "Nhập đường dẫn tới nơi bạn muốn cài vào đây",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "Vui lòng chọn ít nhất một gói để cài!",
"nothing_picked": "Chưa chọn gì",
"option_shortcut": "Tạo đường dẫn trên màn hình chính"
},
"install_packages": {
"uninstall": "Đang gỡ cài đặt...",
"install": "Đang cài đặt...",
"check_for_update": "Đang tìm bản cập nhật...",
"self_update": "Đang tải chương trình tự cập nhật...",
"please_wait": "Vui lòng đợi..."
},
"download_packages": {
"download_config": "Đang tải cấu hình",
"error_download_config": "Có lỗi trong khi tải về cấu hình: {msg}"
}
}
}

78
ui/src/locales/zh_CN.json Normal file
View file

@ -0,0 +1,78 @@
{
"zh-CN": {
"locale": "简体中文",
"error": {
"title": "发生错误",
"exit_error": "{msg}\n\n请上传日志文件(位于 {path})至 {name} 开发团队。",
"location_unknown": "此安装程序位于"
},
"complete": {
"thanks": "感谢您安装 {name}",
"updated": "{name} 已更新。",
"uninstalled": "{name} 已卸载。",
"up_to_date": "{name} 已是最新版本!",
"where_to_find": "您可以在“开始”菜单中找到已安装的应用程序。",
"migration_where_to_find": "稍后您可以在“开始”菜单中找到已安装的应用程序。",
"migration_finished": "您已迁移到另一版本的 {name}。"
},
"modify": {
"title": "选择一个选项:",
"update": "更新",
"uninstall": "卸载",
"prompt": "您确定要卸载 {name} 吗?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "浏览本地文件",
"prompt_recover": " {name} 的安装数据已损坏。<br>需要修复才能继续安装。",
"prompt_confirm": "卸载 {name}",
"modify": "更改",
"repair": "修复"
},
"auth": {
"paste": "粘贴",
"token": "令牌",
"verify": "验证令牌",
"page_header": "Early Access 渠道允许您在最新的实验性功能和修复程序合并到主线版 yuzu 之前试用它们。该渠道包括 yuzu 每日更新,以及这些独家功能。\n\n要想成为 Early Access 用户,您需要成为 Early Access 的捐献者(通过 Patreon 平台)。",
"page_opened": "相关网页已打开。请检查打开的网页,并按照提示关联您的 patreon 帐户。\n完成后请在下方输入您的令牌。",
"login_failed": "验证失败!\n\n请确保您的令牌无误后再试。"
},
"back": "上一步",
"exit": "退出",
"yes": "确定",
"no": "取消",
"continue": "继续",
"cancel": "取消",
"app": {
"installer_title": "欢迎使用 {name} 安装程序!",
"maintenance_title": "欢迎使用 {name} 维护工具。",
"window_title": "{name} 安装程序",
"installer_subtitle": "只需要几分钟,我们将很快完成安装。"
},
"select_packages": {
"title": "选择要安装的软件包:",
"installed": "(已安装)",
"advanced": "高级...",
"install": "安装",
"location": "安装位置",
"select": "选择",
"overwriting": "正在覆盖",
"options": "安装选项",
"title_repair": "选择要修复的软件包:",
"location_placeholder": "输入安装路径",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "请至少选择一个软件包进行安装。",
"nothing_picked": "未选择任何项目",
"option_shortcut": "创建桌面快捷方式"
},
"install_packages": {
"uninstall": "正在卸载...",
"install": "安装中...",
"check_for_update": "正在检查更新...",
"self_update": "正在下载更新...",
"please_wait": "请稍等..."
},
"download_packages": {
"download_config": "正在下载配置...",
"error_download_config": "下载配置时出错: {msg}"
}
}
}

78
ui/src/locales/zh_TW.json Normal file
View file

@ -0,0 +1,78 @@
{
"zh-TW": {
"locale": "繁體中文",
"error": {
"title": "發生錯誤",
"exit_error": "{msg}\n\n請上傳紀錄檔(位於 {path})至 {name} 開發團隊。",
"location_unknown": "此安裝程式位於"
},
"complete": {
"thanks": "感謝您安裝 {name}",
"updated": "{name} 已更新。",
"uninstalled": "{name} 已解除安裝。",
"up_to_date": "{name} 已是最新版本!",
"where_to_find": "您可以在「開始」選單中找到已安裝的應用程式。",
"migration_where_to_find": "您可以在「開始」選單中找到已安裝的應用程式。",
"migration_finished": "您已轉換到另一版本的 {name}。"
},
"modify": {
"title": "選擇一個選項:",
"update": "更新",
"uninstall": "解除安裝",
"prompt": "您確定要解除安裝 {name} 嗎?",
"prompt_repair": "<b>WARNING:</b> All the files under <code>{path}</code> will be <b>deleted</b> as part of the repair process!<br>Do you wish to proceed?",
"view_local_files": "瀏覽本機檔案",
"prompt_recover": " {name} 的安裝資料已損毀。<br>需要修復才能繼續安裝。",
"prompt_confirm": "解除安裝 {name}",
"modify": "修改",
"repair": "修復"
},
"auth": {
"paste": "貼上",
"token": "Token",
"verify": "驗證 Token",
"page_header": "Early Access 通道允許您在最新的實驗性功能和修復檔合併到 yuzu 主要通道之前先行體驗。此通道包含 yuzu 每日更新及以上獨家功能。\n\n欲成為 Early Access 成員您需要至Patreon 平台贊助 Early Access。",
"page_opened": "已開啟網頁。請檢視網頁並依照指示連結您的 patreon 帳號。\n完成後請在下方輸入您的 token。",
"login_failed": "驗證失敗!\n\n請確定您的 token 正確後再重試。"
},
"back": "上一步",
"exit": "退出",
"yes": "確定",
"no": "取消",
"continue": "繼續",
"cancel": "取消",
"app": {
"installer_title": "歡迎使用 {name} 安裝程式!",
"maintenance_title": "歡迎使用 {name} 維護工具!",
"window_title": "{name} 安裝程式",
"installer_subtitle": "安裝過程將在幾分鐘內完成。"
},
"select_packages": {
"title": "選擇要安裝的軟體包:",
"installed": "(已安裝)",
"advanced": "進階...",
"install": "安裝",
"location": "安裝位置",
"select": "選擇",
"overwriting": "正在覆寫檔案",
"options": "安裝選項",
"title_repair": "選擇要修復的軟體包:",
"location_placeholder": "輸入安裝路徑",
"overwriting_warning": "Directory <code>{path}</code> already exists.<br>Are you sure you want to <b>overwrite</b> the contents inside?",
"nothing_picked_warning": "請至少選擇一個軟體包進行安裝。",
"nothing_picked": "未選擇任何選項",
"option_shortcut": "新增捷徑到桌面"
},
"install_packages": {
"uninstall": "正在解除安裝...",
"install": "正在安裝...",
"check_for_update": "正在檢查更新...",
"self_update": "正在下載更新...",
"please_wait": "請稍後..."
},
"download_packages": {
"download_config": "正在下載設定...",
"error_download_config": "下載設定時出錯: {msg}"
}
}
}

View file

@ -1,60 +1,64 @@
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import { ajax, stream_ajax } from './helpers'
import axios from 'axios'
import VueAxios from 'vue-axios'
import VueI18n from 'vue-i18n'
import { stream_ajax as streamAjax } from './helpers'
import Buefy from 'buefy'
import messages from './locales/messages.json'
import 'buefy/dist/buefy.css'
import '@mdi/font/css/materialdesignicons.min.css'
// HACK: disables context menu
document.addEventListener('contextmenu', event => event.preventDefault())
Vue.config.productionTip = false
Vue.use(Buefy)
Vue.use(VueI18n)
Vue.use(VueAxios, axios)
export const i18n = new VueI18n({
locale: 'en', // set locale
fallbackLocale: 'en',
messages // set locale messages
})
// Borrowed from http://tobyho.com/2012/07/27/taking-over-console-log/
function intercept (method) {
console[method] = function () {
var message = Array.prototype.slice.apply(arguments).join(' ')
window.external.invoke(
JSON.stringify({
Log: {
kind: method,
msg: message
}
})
const message = Array.prototype.slice.apply(arguments).join(' ')
window.rpc.notify(
'Log', method, message
)
}
}
// See if we have access to the JSON interface
var has_external_interface = false;
let hasExternalInterface = false
try {
window.external.invoke(JSON.stringify({
Test: {}
}))
has_external_interface = true;
window.rpc.notify('Test')
hasExternalInterface = true
} catch (e) {
console.warn("Running without JSON interface - unexpected behaviour may occur!")
console.warn('Running without JSON interface - unexpected behaviour may occur!')
}
// Overwrite loggers with the logging backend
if (has_external_interface) {
if (hasExternalInterface) {
window.onerror = function (msg, url, line) {
window.external.invoke(
JSON.stringify({
Log: {
kind: 'error',
msg: msg + ' @ ' + url + ':' + line
}
})
window.rpc.notify(
'Log', 'error', msg + ' @ ' + url + ':' + line
)
}
var methods = ['log', 'warn', 'error']
for (var i = 0; i < methods.length; i++) {
const methods = ['log', 'warn', 'error']
for (let i = 0; i < methods.length; i++) {
intercept(methods[i])
}
}
// Disable F5
function disable_shortcuts (e) {
function disableShortcuts (e) {
switch (e.keyCode) {
case 116: // F5
e.preventDefault()
@ -63,25 +67,26 @@ function disable_shortcuts (e) {
}
// Check to see if we need to enable dark mode
ajax('/api/dark-mode', function (enable) {
if (enable) {
axios.get('/api/dark-mode').then(function (resp) {
if (resp.data === true) {
document.body.classList.add('has-background-black-ter')
}
})
window.addEventListener('keydown', disable_shortcuts)
window.addEventListener('keydown', disableShortcuts)
document.getElementById('window-title').innerText =
base_attributes.name + ' Installer'
axios.get('/api/attrs').then(function (resp) {
document.getElementById('window-title').innerText =
i18n.t('app.window_title', { name: resp.data.name })
}).catch(function (err) {
console.error(err)
})
function selectFileCallback (name) {
app.install_location = name
}
var app = new Vue({
const app = new Vue({
i18n: i18n,
router: router,
data: {
attrs: base_attributes,
attrs: {},
config: {},
install_location: '',
username: '',
@ -102,53 +107,61 @@ var app = new Vue({
render: function (caller) {
return caller(App)
},
mounted: function () {
axios.get('/api/attrs').then(function (resp) {
app.attrs = resp.data
}).catch(function (err) {
console.error(err)
})
},
methods: {
exit: function () {
ajax(
'/api/exit',
function () {},
function (msg) {
var search_location = app.metadata.install_path.length > 0 ? app.metadata.install_path :
"the location where this installer is";
axios.get('/api/exit').catch(function (msg) {
const searchLocation = (app.metadata.install_path && app.metadata.install_path.length > 0)
? app.metadata.install_path
: i18n.t('error.location_unknown')
app.$router.replace({ name: 'showerr', params: { msg: msg +
'\n\nPlease upload the log file (in ' + search_location + ') to ' +
'the ' + app.attrs.name + ' team'
}});
}
)
},
check_authentication: function (success, error) {
var that = this;
var app = this.$root;
app.ajax('/api/check-auth', function (auth) {
app.$data.username = auth.username;
app.$data.token = auth.token;
that.jwt_token = auth.jwt_token;
that.is_authenticated = Object.keys(that.jwt_token).length !== 0 && that.jwt_token.constructor === Object;
if (that.is_authenticated) {
// Give all permissions to vip roles
that.is_linked = that.jwt_token.isPatreonAccountLinked;
that.is_subscribed = that.jwt_token.isPatreonSubscriptionActive;
that.has_reward_tier = that.jwt_token.releaseChannels.indexOf("early-access") > -1;
}
if (success) {
success();
}
}, function (e) {
if (error) {
error();
}
}, {
"username": app.$data.username,
"token": app.$data.token
app.$router.replace({
name: 'showerr',
params: {
msg: i18n.t('error.exit_error', {
name: app.attrs.name,
path: searchLocation,
msg: msg
})
}
})
})
},
check_authentication: function (success, error) {
const that = this
const app = this.$root
ajax: ajax,
stream_ajax: stream_ajax
axios.post('/api/check-auth', {
username: app.$data.username,
token: app.$data.token
}).then(function (resp) {
app.$data.username = resp.data.username
app.$data.token = resp.data.token
that.jwt_token = resp.data.jwt_token
that.is_authenticated = Object.keys(that.jwt_token).length !== 0 && that.jwt_token.constructor === Object
if (that.is_authenticated) {
// Give all permissions to vip roles
that.is_linked = that.jwt_token.isPatreonAccountLinked
that.is_subscribed = that.jwt_token.isPatreonSubscriptionActive
that.has_reward_tier = that.jwt_token.releaseChannels.indexOf('early-access') > -1
}
if (success) {
success()
}
}).catch(function () {
if (error) {
error()
}
})
},
stream_ajax: streamAjax
}
}).$mount('#app')
console.log("Vue started")
console.log('Vue started')

Some files were not shown because too many files have changed in this diff Show more