Merge pull request #17 from jroweboy/patreon

Early access release
This commit is contained in:
bunnei 2019-11-14 17:26:37 -05:00 committed by GitHub
commit 9cf5e745d4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 1173 additions and 130 deletions

86
Cargo.lock generated
View file

@ -585,6 +585,20 @@ name = "itoa"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "jsonwebtoken"
version = "6.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "kernel32-sys"
version = "0.2.2"
@ -623,6 +637,7 @@ dependencies = [
name = "liftinstall"
version = "0.1.0"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"chrono 0.4.7 (registry+https://github.com/rust-lang/crates.io-index)",
"clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)",
@ -630,11 +645,12 @@ dependencies = [
"fern 0.5.8 (registry+https://github.com/rust-lang/crates.io-index)",
"futures 0.1.28 (registry+https://github.com/rust-lang/crates.io-index)",
"hyper 0.11.27 (registry+https://github.com/rust-lang/crates.io-index)",
"jsonwebtoken 6.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 1.8.7 (registry+https://github.com/rust-lang/crates.io-index)",
"number_prefix 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"regex 1.1.7 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)",
"reqwest 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)",
"semver 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_derive 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
@ -646,6 +662,7 @@ dependencies = [
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"walkdir 2.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
"web-view 0.4.1 (git+https://github.com/j-selby/web-view.git?rev=752106e4637356cbdb39a0bf1113ea3ae8a14243)",
"webbrowser 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
@ -681,7 +698,7 @@ dependencies = [
[[package]]
name = "lzma-sys"
version = "0.1.14"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
@ -733,13 +750,11 @@ dependencies = [
[[package]]
name = "mime_guess"
version = "2.0.0-alpha.6"
version = "2.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
"phf 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
"phf_codegen 0.7.24 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 1.4.2 (registry+https://github.com/rust-lang/crates.io-index)",
"unicase 2.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1212,7 +1227,7 @@ dependencies = [
[[package]]
name = "reqwest"
version = "0.9.18"
version = "0.9.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"base64 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1227,7 +1242,7 @@ dependencies = [
"hyper-tls 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)",
"mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)",
"native-tls 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
"serde 1.0.93 (registry+https://github.com/rust-lang/crates.io-index)",
"serde_json 1.0.39 (registry+https://github.com/rust-lang/crates.io-index)",
@ -1240,6 +1255,20 @@ dependencies = [
"tokio-timer 0.2.11 (registry+https://github.com/rust-lang/crates.io-index)",
"url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)",
"uuid 0.7.4 (registry+https://github.com/rust-lang/crates.io-index)",
"winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "ring"
version = "0.14.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"cc 1.0.37 (registry+https://github.com/rust-lang/crates.io-index)",
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
"libc 0.2.58 (registry+https://github.com/rust-lang/crates.io-index)",
"spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)",
"untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -1402,6 +1431,11 @@ name = "smallvec"
version = "0.6.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "stable_deref_trait"
version = "1.1.1"
@ -1823,6 +1857,11 @@ name = "unicode-xid"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "untrusted"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "url"
version = "1.7.2"
@ -1906,6 +1945,15 @@ dependencies = [
"webview-sys 0.1.2 (git+https://github.com/j-selby/web-view.git?rev=752106e4637356cbdb39a0bf1113ea3ae8a14243)",
]
[[package]]
name = "webbrowser"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "webview-sys"
version = "0.1.2"
@ -1967,6 +2015,14 @@ name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
[[package]]
name = "winreg"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
name = "winres"
version = "0.1.11"
@ -1997,7 +2053,7 @@ name = "xz2"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
dependencies = [
"lzma-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)",
"lzma-sys 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)",
]
[[package]]
@ -2080,6 +2136,7 @@ dependencies = [
"checksum indexmap 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7e81a7c05f79578dbc15793d8b619db9ba32b4577003ef3af1a91c416798c58d"
"checksum iovec 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dbe6e417e7d0975db6512b90796e8ce223145ac4e33c377e4a42882a0e88bb08"
"checksum itoa 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "501266b7edd0174f8530248f87f99c88fbe60ca4ef3dd486835b8d8d53136f7f"
"checksum jsonwebtoken 6.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "a81d1812d731546d2614737bee92aa071d37e9afa1409bc374da9e5e70e70b22"
"checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d"
"checksum language-tags 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a91d884b6667cd606bb5a69aa0c99ba811a115fc68915e7056ec08a46e93199a"
"checksum lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bc5729f27f159ddd61f4df6228e827e86643d4d3e7c32183cb30a1c08f604a14"
@ -2088,14 +2145,14 @@ dependencies = [
"checksum lock_api 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "62ebf1391f6acad60e5c8b43706dde4582df75c06698ab44511d15016bc2442c"
"checksum log 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b"
"checksum log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)" = "c84ec4b527950aa83a329754b01dbe3f58361d1c5efacd1f6d68c494d08a17c6"
"checksum lzma-sys 0.1.14 (registry+https://github.com/rust-lang/crates.io-index)" = "16b5c59c57cc4d39e7999f50431aa312ea78af7c93b23fbb0c3567bd672e7f35"
"checksum lzma-sys 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "53e48818fd597d46155132bbbb9505d6d1b3d360b4ee25cfa91c406f8a90fe91"
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
"checksum memchr 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2efc7bc57c883d4a4d6e3246905283d8dae951bb3bd32f49d6ef297f546e1c39"
"checksum memoffset 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "0f9dc261e2b62d7a622bf416ea3c5245cdd5d9a7fcc428c0d06804dfce1775b3"
"checksum mime 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0"
"checksum mime 0.3.13 (registry+https://github.com/rust-lang/crates.io-index)" = "3e27ca21f40a310bd06d9031785f4801710d566c184a6e15bad4f1d9b65f9425"
"checksum mime_guess 1.8.7 (registry+https://github.com/rust-lang/crates.io-index)" = "0d977de9ee851a0b16e932979515c0f3da82403183879811bc97d50bd9cc50f7"
"checksum mime_guess 2.0.0-alpha.6 (registry+https://github.com/rust-lang/crates.io-index)" = "30de2e4613efcba1ec63d8133f344076952090c122992a903359be5a4f99c3ed"
"checksum mime_guess 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1a0ed03949aef72dbdf3116a383d7b38b4768e6f960528cd6a6044aa9ed68599"
"checksum miniz_oxide 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "c468f2369f07d651a5d0bb2c9079f8488a66d5466efe42d0c5c6466edcb7f71e"
"checksum miniz_oxide_c_api 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b7fe927a42e3807ef71defb191dc87d4e24479b221e67015fe38ae2b7b447bab"
"checksum mio 0.6.19 (registry+https://github.com/rust-lang/crates.io-index)" = "83f51996a3ed004ef184e16818edc51fadffe8e7ca68be67f9dee67d84d0ff23"
@ -2147,7 +2204,8 @@ dependencies = [
"checksum regex-syntax 0.6.7 (registry+https://github.com/rust-lang/crates.io-index)" = "9d76410686f9e3a17f06128962e0ecc5755870bb890c34820c7af7f1db2e1d48"
"checksum relay 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "1576e382688d7e9deecea24417e350d3062d97e32e45d70b1cde65994ff1489a"
"checksum remove_dir_all 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "4a83fa3702a688b9359eccba92d153ac33fd2e8462f9e0e3fdf155239ea7792e"
"checksum reqwest 0.9.18 (registry+https://github.com/rust-lang/crates.io-index)" = "00eb63f212df0e358b427f0f40aa13aaea010b470be642ad422bcbca2feff2e4"
"checksum reqwest 0.9.21 (registry+https://github.com/rust-lang/crates.io-index)" = "02b7e953e14c6f3102b7e8d1f1ee3abf5ecee80b427f5565c9389835cecae95c"
"checksum ring 0.14.6 (registry+https://github.com/rust-lang/crates.io-index)" = "426bc186e3e95cac1e4a4be125a4aca7e84c2d616ffc02244eef36e2a60a093c"
"checksum rustc-demangle 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "a7f4dccf6f4891ebcc0c39f9b6eb1a83b9bf5d747cb439ec6fba4f3b977038af"
"checksum rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)" = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
"checksum ryu 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "b96a9549dc8d48f2c283938303c4b5a77aa29bfbc5b54b084fb1630408899a8f"
@ -2171,6 +2229,7 @@ dependencies = [
"checksum slug 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b3bc762e6a4b6c6fcaade73e77f9ebc6991b676f88bb2358bddb56560f073373"
"checksum smallvec 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4c8cbcd6df1e117c2210e13ab5109635ad68a929fcbb8964dc965b76cb5ee013"
"checksum smallvec 0.6.10 (registry+https://github.com/rust-lang/crates.io-index)" = "ab606a9c5e214920bb66c458cd7be8ef094f813f20fe77a54cc7dbfff220d4b7"
"checksum spin 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
"checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8"
"checksum string 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d0bbfb8937e38e34c3444ff00afb28b0811d9554f15c5ad64d12b0308d1d1995"
"checksum strsim 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
@ -2213,6 +2272,7 @@ dependencies = [
"checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426"
"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526"
"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc"
"checksum untrusted 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "55cd1f4b4e96b46aeb8d4855db4a7a9bd96eeeb5c6a1ab54593328761642ce2f"
"checksum url 1.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "dd4e7c0d531266369519a4aa4f399d748bd37043b00bde1e4ff1f60a120b355a"
"checksum urlencoding 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3df3561629a8bb4c57e5a2e4c43348d9e29c7c29d9b1c4c1f47166deca8f37ed"
"checksum utf8-ranges 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "9d50aa7650df78abf942826607c62468ce18d9019673d4a2ebe1865dbb96ffde"
@ -2224,6 +2284,7 @@ dependencies = [
"checksum want 0.0.4 (registry+https://github.com/rust-lang/crates.io-index)" = "a05d9d966753fa4b5c8db73fcab5eed4549cfe0e1e4e66911e5564a0085c35d1"
"checksum want 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b6395efa4784b027708f7451087e647ec73cc74f5d9bc2e418404248d679a230"
"checksum web-view 0.4.1 (git+https://github.com/j-selby/web-view.git?rev=752106e4637356cbdb39a0bf1113ea3ae8a14243)" = "<none>"
"checksum webbrowser 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "97d468a911faaaeb783693b004e1c62e0063e646b0afae5c146cd144e566e66d"
"checksum webview-sys 0.1.2 (git+https://github.com/j-selby/web-view.git?rev=752106e4637356cbdb39a0bf1113ea3ae8a14243)" = "<none>"
"checksum which 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b57acb10231b9493c8472b20cb57317d0679a49e0bdbee44b3b803a6473af164"
"checksum widestring 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "effc0e4ff8085673ea7b9b2e3c73f6bd4d118810c9009ed8f1e16bd96c331db6"
@ -2233,6 +2294,7 @@ dependencies = [
"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
"checksum winapi-util 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7168bab6e1daee33b4557efd0e95d5ca70a03706d39fa5f3fe7a236f584b03c9"
"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
"checksum winreg 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "b2986deb581c4fe11b621998a5e53361efe6b48a151178d0cd9eeffa4dc6acc9"
"checksum winres 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "ff4fb510bbfe5b8992ff15f77a2e6fe6cf062878f0eda00c0f44963a807ca5dc"
"checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
"checksum xattr 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "244c3741f4240ef46274860397c7c74e50eb23624996930e484c16679633a54c"

View file

@ -15,7 +15,7 @@ futures = "0.1.25"
mime_guess = "1.8.6"
url = "1.7.2"
reqwest = "0.9.12"
reqwest = "0.9.21"
number_prefix = "0.3.0"
serde = "1.0.89"
@ -38,6 +38,13 @@ chrono = "0.4.6"
clap = "2.32.0"
# used to open a link to the users default browser
webbrowser = "0.5.2"
# used in JWT based package authentication
jsonwebtoken = "6"
# used to decode the public key for verifying JWT tokens
base64 = "0.10.1"
[build-dependencies]
walkdir = "2.2.7"
serde = "1.0.89"

View file

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

View file

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

52
config.linux.v2.toml Normal file
View file

@ -0,0 +1,52 @@
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"
description = "Includes frequent updates to yuzu with all the latest reviewed and tested features."
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-windows-msvc/yuzu.exe"
description = "Launch yuzu"
[[packages]]
name = "yuzu Early Access"
description = "Bonus preview release for project supporters. Thanks 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"
requires_authorization = true
# puts a "new" ribbon the package select
is_new = true
[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-earlyaccess/yuzu.exe"
description = "Launch yuzu Early Access"

52
config.windows.v9.toml Normal file
View file

@ -0,0 +1,52 @@
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"
description = "Includes frequent updates to yuzu with all the latest reviewed and tested features."
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"
[[packages]]
name = "yuzu Early Access"
description = "Bonus preview release for project supporters. Thanks 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"
requires_authorization = true
# puts a "new" ribbon the package select
is_new = true
[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-patreon/yuzu.exe"
description = "Launch yuzu Early Access"

View file

@ -36,6 +36,34 @@ pub struct PackageDescription {
pub source: PackageSource,
#[serde(default)]
pub shortcuts: Vec<PackageShortcut>,
#[serde(default)]
pub requires_authorization: Option<bool>,
#[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>,
}
/// Configuration for validating the JWT token
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JWTValidation {
pub iss: Option<String>,
// This can technically be a Vec as well, but thats a pain to support atm
pub aud: Option<String>,
}
/// The configuration for this release.
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AuthenticationConfig {
pub pub_key_base64: String,
pub auth_url: String,
pub validation: Option<JWTValidation>,
}
/// Describes the application itself.
@ -66,6 +94,8 @@ pub struct Config {
pub packages: Vec<PackageDescription>,
#[serde(default)]
pub hide_advanced: bool,
#[serde(default)]
pub authentication: Option<AuthenticationConfig>,
}
impl Config {

View file

@ -0,0 +1,234 @@
use http::{build_client, build_async_client};
use hyper::header::{ContentLength, ContentType};
use reqwest::header::{USER_AGENT};
use futures::{Stream, Future};
use jwt::{decode, Validation, Algorithm};
use frontend::rest::services::{WebService, Request, Response, default_future};
use frontend::rest::services::Future as InternalFuture;
use logging::LoggingErrors;
use url::form_urlencoded;
use std::collections::HashMap;
use std::sync::Arc;
use config::JWTValidation;
#[derive(Debug, Serialize, Deserialize)]
struct Auth {
username: String,
token: String,
jwt_token: Option<JWTClaims>,
}
/// claims struct, it needs to derive `Serialize` and/or `Deserialize`
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct JWTClaims {
pub sub: String,
pub iss: String,
pub aud: String,
pub exp: usize,
#[serde(default)]
pub roles: Vec<String>,
#[serde(rename = "releaseChannels", default)]
pub channels: Vec<String>,
#[serde(rename = "isPatreonAccountLinked")]
pub is_linked: bool,
#[serde(rename = "isPatreonSubscriptionActive")]
pub is_subscribed: bool,
}
/// Calls the given server to obtain a JWT token and returns a Future<String> with the response
pub fn authenticate_async(url: String, username: String, token: String)
-> Box<dyn futures::Future<Item = String, Error = String>> {
// Build the HTTP client up
let client = match build_async_client() {
Ok(v) => v,
Err(_) => {
return Box::new(futures::future::err("Unable to build async web client".to_string()));
},
};
Box::new(client.post(&url)
.header(USER_AGENT, "liftinstall (j-selby)")
.header("X-USERNAME", username.clone())
.header("X-TOKEN", token.clone())
.send()
.map_err(|err| {
format!("stream error {:?}, client: {:?}, http: {:?}, redirect: {:?}, serialization: {:?}, timeout: {:?}, server: {:?}",
err, err.is_client_error(), err.is_http(), err.is_redirect(),
err.is_serialization(), err.is_timeout(), err.is_server_error())
})
.map(|mut response| {
match response.status() {
reqwest::StatusCode::OK =>
Ok(response.text()
.map_err(|e| {
format!("Error while converting the response to text {:?}", e)
})),
_ => {
Err(format!("Error wrong response code from server {:?}", response.status()))
}
}
})
.and_then(|x| x)
.flatten()
)
}
pub fn authenticate_sync(url: String, username: String, token: String)
-> Result<String, String> {
// Build the HTTP client up
let client = build_client()?;
let mut response = client.post(&url)
.header(USER_AGENT, "liftinstall (j-selby)")
.header("X-USERNAME", username.clone())
.header("X-TOKEN", token.clone())
.send()
.map_err(|err| {
format!("stream error {:?}, client: {:?}, http: {:?}, redirect: {:?}, serialization: {:?}, timeout: {:?}, server: {:?}",
err, err.is_client_error(), err.is_http(), err.is_redirect(),
err.is_serialization(), err.is_timeout(), err.is_server_error())
})?;
match response.status() {
reqwest::StatusCode::OK =>
Ok(response.text()
.map_err(|e| {
format!("Error while converting the response to text {:?}", e)
})?),
_ => {
Err(format!("Error wrong response code from server {:?}", response.status()))
}
}
}
pub fn validate_token(body: String, pub_key_base64: String, validation: Option<JWTValidation>) -> Result<JWTClaims, String> {
// Get the public key for this authentication url
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));
},
}
};
// Configure validation for audience and issuer if the configuration provides it
let validation = match validation {
Some(v) => {
let mut valid = Validation::new(Algorithm::RS256);
valid.iss = v.iss;
if v.aud.is_some() {
valid.set_audience(&v.aud.unwrap());
}
valid
}
None => Validation::default()
};
// Verify the JWT token
decode::<JWTClaims>(&body, pub_key.as_slice(), &validation)
.map(|tok| tok.claims)
.map_err(|err| format!("Error while decoding the JWT. error: {:?} jwt: {:?}", err, body))
}
pub fn handle(service: &WebService, _req: Request) -> InternalFuture {
let framework = service.framework.read().log_expect("InstallerFramework has been dirtied");
let credentials = framework.database.credentials.clone();
let config = framework.config.clone().unwrap();
// If authentication isn't configured, just return immediately
if config.authentication.is_none() {
return default_future(Response::new().with_status(hyper::Ok).with_body("{}"));
}
// Create moveable framework references so that the lambdas can write to them later
let write_cred_fw = Arc::clone(&service.framework);
Box::new(
_req.body().concat2().map(move |body| {
let req = form_urlencoded::parse(body.as_ref())
.into_owned()
.collect::<HashMap<String, String>>();
// Determine which credentials we should use
let (username, token) = {
let req_username = req.get("username").unwrap();
let req_token = req.get("token").unwrap();
// if the user didn't provide credentials, and theres nothing stored in the database, return an early error
let req_cred_valid = !req_username.is_empty() && !req_token.is_empty();
let stored_cred_valid = !credentials.username.is_empty() && !credentials.token.is_empty();
if !req_cred_valid && !stored_cred_valid {
info!("No passed in credential and no stored credentials to validate");
return default_future(Response::new().with_status(hyper::BadRequest));
}
if req_cred_valid {
(req.get("username").unwrap().clone(), req.get("token").unwrap().clone())
} else {
(credentials.username.clone(), credentials.token.clone())
}
};
// second copy of the credentials so we can move them into a different closure
let (username_clone, token_clone) = (username.clone(), token.clone());
let authentication = config.authentication.unwrap();
let auth_url = authentication.auth_url.clone();
let pub_key_base64 = authentication.pub_key_base64.clone();
let validation = authentication.validation.clone();
// call the authentication URL to see if we are authenticated
Box::new(authenticate_async(auth_url, username.clone(), token.clone())
.map(|body| {
validate_token(body, pub_key_base64, validation)
})
.and_then(|res| res)
.map(move |claims| {
let out = Auth {
username: username_clone,
token: token_clone,
jwt_token: Some(claims.clone()),
};
// Convert the json to a string and return the json token
match serde_json::to_string(&out) {
Ok(v) => Ok(v),
Err(e) => {
Err(format!("Error while converting the claims to JSON string: {:?}", e))
}
}
})
.and_then(|res| res)
.map(move |json| {
{
// Store the validated username and password into the installer database
let mut framework = write_cred_fw.write().log_expect("InstallerFramework has been dirtied");
framework.database.credentials.username = username;
framework.database.credentials.token = token;
}
// Finally return the JSON with the response
info!("successfully verified username and token");
Response::new()
.with_header(ContentLength(json.len() as u64))
.with_header(ContentType::json())
.with_status(hyper::StatusCode::Ok)
.with_body(json)
})
.map_err(|err| {
Response::new().with_status(hyper::StatusCode::InternalServerError)
})
.or_else(|err| {
// Convert the Err value into an Ok value since the error code from this HTTP request is an Ok(response)
Ok(err)
})
)
})
// Flatten the internal future into the output response future
.flatten()
)
}

View file

@ -0,0 +1,29 @@
use frontend::rest::services::{WebService, Request, Response};
use frontend::rest::services::Future as InternalFuture;
use futures::{Stream, Future};
use url::form_urlencoded;
use std::collections::HashMap;
use hyper::header::ContentType;
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").unwrap()).is_ok() {
Response::new()
.with_status(hyper::Ok)
.with_header(ContentType::json())
.with_body("{}")
} else {
Response::new()
.with_status(hyper::BadRequest)
.with_header(ContentType::json())
.with_body("{}")
}
}))
}

View file

@ -21,6 +21,8 @@ use futures::future::Future as _;
use futures::sink::Sink;
mod attributes;
pub mod authentication;
mod browser;
mod config;
mod default_path;
mod dark_mode;
@ -137,7 +139,9 @@ 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::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::Get, _) => static_files::handle(self, req),

View file

@ -17,7 +17,7 @@ enum CallbackType {
/// 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 = if is_launcher { (600, 300) } else { (1024, 500) };
let size = (1024, 550);
info!("Spawning web view instance");

View file

@ -36,16 +36,22 @@ pub fn build_async_client() -> Result<AsyncClient, String> {
}
/// Streams a file from a HTTP server.
pub fn stream_file<F>(url: &str, mut callback: F) -> Result<(), String>
pub fn stream_file<F>(url: &str, authorization: Option<String>, mut callback: F) -> Result<(), String>
where
F: FnMut(Vec<u8>, u64) -> (),
{
assert_ssl(url)?;
let mut client = build_client()?
.get(url)
let mut client = if authorization.is_some() {
build_client()?.get(url)
.header("Authorization", format!("Bearer {}", authorization.unwrap()))
.send()
.map_err(|x| format!("Failed to GET resource: {:?}", x))?;
.map_err(|x| format!("Failed to GET resource: {:?}", x))?
} else {
build_client()?.get(url)
.send()
.map_err(|x| format!("Failed to GET resource: {:?}", x))?
};
let size = match client.headers().get(CONTENT_LENGTH) {
Some(ref v) => v

View file

@ -50,14 +50,25 @@ pub enum InstallMessage {
Status(String, f64),
PackageInstalled,
Error(String),
AuthorizationRequired(String),
EOF,
}
#[derive(Serialize, Deserialize, Default, Clone)]
pub struct Credentials {
#[serde(default)]
pub username: String,
#[serde(default)]
pub token: String,
}
/// Metadata about the current installation itself.
#[derive(Serialize, Deserialize, Clone)]
pub struct InstallationDatabase {
pub packages: Vec<LocalInstallation>,
pub shortcuts: Vec<String>,
#[serde(default)]
pub credentials: Credentials,
}
impl InstallationDatabase {
@ -66,6 +77,7 @@ impl InstallationDatabase {
InstallationDatabase {
packages: Vec::new(),
shortcuts: Vec::new(),
credentials: Credentials{username: String::new(), token: String::new()},
}
}
}
@ -114,6 +126,11 @@ macro_rules! declare_messenger_callback {
error!("Failed to submit queue message: {:?}", v);
}
}
TaskMessage::AuthorizationRequired(msg) => {
if let Err(v) = $target.send(InstallMessage::AuthorizationRequired(msg.to_string())) {
error!("Failed to submit queue message: {:?}", v);
}
}
TaskMessage::PackageInstalled => {
if let Err(v) = $target.send(InstallMessage::PackageInstalled) {
error!("Failed to submit queue message: {:?}", v);
@ -252,7 +269,7 @@ impl InstallerFramework {
let mut downloaded = 0;
let mut data_storage: Vec<u8> = Vec::new();
http::stream_file(tool, |data, size| {
http::stream_file(tool, None, |data, size| {
{
data_storage.extend_from_slice(&data);
}

View file

@ -47,6 +47,10 @@ extern crate slug;
#[cfg(not(windows))]
extern crate sysinfo;
extern crate jsonwebtoken as jwt;
extern crate base64;
mod archives;
mod config;
mod frontend;

View file

@ -45,7 +45,7 @@ extern "C" int saveShortcut(
const wchar_t *args,
const wchar_t *workingDir)
{
char *errStr = NULL;
const char *errStr = NULL;
HRESULT h;
IShellLink *shellLink = NULL;
IPersistFile *persistFile = NULL;

View file

@ -107,6 +107,7 @@ impl ReleaseSource for GithubReleases {
files.push(File {
name: string.to_string(),
url: url.to_string(),
requires_authorization: false,
});
}

View file

@ -6,12 +6,15 @@ pub mod types;
pub mod github;
pub mod patreon;
use self::types::ReleaseSource;
/// Returns a ReleaseSource by a name, if possible
pub fn get_by_name(name: &str) -> Option<Box<dyn ReleaseSource>> {
match name {
"github" => Some(Box::new(github::GithubReleases::new())),
"patreon" => Some(Box::new(patreon::PatreonReleases::new())),
_ => None,
}
}

108
src/sources/patreon.rs Normal file
View file

@ -0,0 +1,108 @@
//! github/mod.rs
//!
//! Contains the Github API implementation of a release source.
use sources::types::*;
use http::build_client;
use reqwest::header::USER_AGENT;
use reqwest::StatusCode;
pub struct PatreonReleases {}
/// The configuration for this release.
#[derive(Serialize, Deserialize)]
struct PatreonConfig {
repo: String,
}
impl PatreonReleases {
pub fn new() -> Self {
PatreonReleases {}
}
}
impl ReleaseSource for PatreonReleases {
fn get_current_releases(&self, _config: &TomlValue) -> Result<Vec<Release>, String> {
let config: PatreonConfig = match _config.clone().try_into() {
Ok(v) => v,
Err(v) => return Err(format!("Failed to parse release config: {:?}", v)),
};
let mut results: Vec<Release> = Vec::new();
// Build the HTTP client up
let client = build_client()?;
let mut response = client
.get(&format!(
"https://api.yuzu-emu.org/downloads/{}/",
config.repo
))
.header(USER_AGENT, "liftinstall (j-selby)")
.send()
.map_err(|x| format!("Error while sending HTTP request: {:?}", x))?;
match response.status() {
StatusCode::OK => {}
StatusCode::FORBIDDEN => {
return Err(
"You are not eligible to download this release".to_string(),
);
}
_ => {
return Err(format!("Bad status code: {:?}.", response.status()));
}
}
let body = response
.text()
.map_err(|x| format!("Failed to decode HTTP response body: {:?}", x))?;
let result: serde_json::Value = serde_json::from_str(&body)
.map_err(|x| format!("Failed to parse response: {:?}", x))?;
// Parse JSON from server
let mut files = Vec::new();
let id: u64 = match result["version"].as_u64() {
Some(v) => v,
None => return Err("JSON payload missing information about ID".to_string()),
};
let downloads = match result["files"].as_array() {
Some(v) => v,
None => return Err("JSON payload not an array".to_string()),
};
for file in downloads.iter() {
let string = match file["name"].as_str() {
Some(v) => v,
None => {
return Err(
"JSON payload missing information about release name".to_string()
);
}
};
let url = match file["url"].as_str() {
Some(v) => v,
None => {
return Err(
"JSON payload missing information about release URL".to_string()
);
}
};
files.push(File {
name: string.to_string(),
url: url.to_string(),
requires_authorization: true,
});
}
results.push(Release {
version: Version::new_number(id),
files,
});
Ok(results)
}
}

View file

@ -66,6 +66,7 @@ impl Ord for Version {
pub struct File {
pub name: String,
pub url: String,
pub requires_authorization: bool,
}
impl File {}

View file

@ -0,0 +1,69 @@
use installer::InstallerFramework;
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
use logging::LoggingErrors;
use frontend::rest::services::authentication;
use futures::{Stream, Future};
use tasks::resolver::ResolvePackageTask;
pub struct CheckAuthorizationTask {
pub name: String,
}
impl Task for CheckAuthorizationTask {
fn execute(
&mut self,
mut input: Vec<TaskParamType>,
context: &mut InstallerFramework,
messenger: &dyn Fn(&TaskMessage),
) -> Result<TaskParamType, String> {
assert_eq!(input.len(), 1);
let params = input.pop().log_expect("Should have input from resolver!");
let (version, file) = match params {
TaskParamType::File(v, f) => { Ok((v, f)) },
_ => { Err("Unexpected TaskParamType in CheckAuthorization: {:?}") }
}?;
if !file.requires_authorization {
return Ok(TaskParamType::Authentication(version, file, None));
}
let username = context.database.credentials.username.clone();
let token = context.database.credentials.token.clone();
let authentication = context.config.clone().unwrap().authentication.unwrap();
let auth_url = authentication.auth_url.clone();
let pub_key_base64 = authentication.pub_key_base64.clone();
let validation = authentication.validation.clone();
// Authorizaion is required for this package so post the username and token and get a jwt_token response
let jwt_token = match authentication::authenticate_sync(auth_url, username, token) {
Ok(jwt) => jwt,
Err(_) => return Ok(TaskParamType::Authentication(version, file, None))
};
let claims = match authentication::validate_token(jwt_token.clone(), pub_key_base64, validation) {
Ok(c) => c,
Err(_) => return Ok(TaskParamType::Authentication(version, file, None))
};
// Validate that they are authorized
let authorized =
claims.roles.contains(&"vip".to_string()) || (claims.channels.contains(&"early-access".to_string()));
if !authorized {
return Ok(TaskParamType::Authentication(version, file, None));
}
Ok(TaskParamType::Authentication(version, file, Some(jwt_token)))
}
fn dependencies(&self) -> Vec<TaskDependency> {
vec![TaskDependency::build(
TaskOrdering::Pre,
Box::new(ResolvePackageTask {
name: self.name.clone(),
}),
)]
}
fn name(&self) -> String {
format!("CheckAuthorizationTask (for {:?})", self.name)
}
}

View file

@ -2,11 +2,8 @@
use installer::InstallerFramework;
use tasks::Task;
use tasks::TaskDependency;
use tasks::TaskMessage;
use tasks::TaskOrdering;
use tasks::TaskParamType;
use tasks::check_authorization::CheckAuthorizationTask;
use tasks::{Task, TaskDependency, TaskMessage, TaskOrdering, TaskParamType};
use tasks::resolver::ResolvePackageTask;
@ -30,11 +27,18 @@ impl Task for DownloadPackageTask {
assert_eq!(input.len(), 1);
let file = input.pop().log_expect("Should have input from resolver!");
let (version, file) = match file {
TaskParamType::File(v, f) => (v, f),
let (version, file, auth) = match file {
TaskParamType::Authentication(v, f, auth) => (v, f, auth),
_ => return Err("Unexpected param type to download package".to_string()),
};
// TODO: move this back below checking for latest version after testing is done
if file.requires_authorization && auth.is_none() {
info!("Authorization required to update this package!");
messenger(&TaskMessage::AuthorizationRequired("AuthorizationRequired"));
return Ok(TaskParamType::Break);
}
// Check to see if this is the newest file available already
for element in &context.database.packages {
if element.name == self.name {
@ -54,7 +58,7 @@ impl Task for DownloadPackageTask {
let mut downloaded = 0;
let mut data_storage: Vec<u8> = Vec::new();
stream_file(&file.url, |data, size| {
stream_file(&file.url, auth, |data, size| {
{
data_storage.extend_from_slice(&data);
}
@ -92,7 +96,7 @@ impl Task for DownloadPackageTask {
fn dependencies(&self) -> Vec<TaskDependency> {
vec![TaskDependency::build(
TaskOrdering::Pre,
Box::new(ResolvePackageTask {
Box::new(CheckAuthorizationTask {
name: self.name.clone(),
}),
)]

View file

@ -9,6 +9,7 @@ use installer::InstallerFramework;
use sources::types::File;
use sources::types::Version;
pub mod check_authorization;
pub mod download_pkg;
pub mod ensure_only_instance;
pub mod install;
@ -29,6 +30,8 @@ pub enum TaskParamType {
None,
/// Metadata about a file
File(Version, File),
/// Authentication token for a package
Authentication(Version, File, Option<String>),
/// Downloaded contents of a file
FileContents(Version, File, Vec<u8>),
/// List of shortcuts that have been generated
@ -62,6 +65,7 @@ impl TaskDependency {
/// A message from a task.
pub enum TaskMessage<'a> {
DisplayMessage(&'a str, f64),
AuthorizationRequired(&'a str),
PackageInstalled,
}

View file

@ -71,6 +71,7 @@ pre {
.clickable-box {
cursor: pointer;
position: relative;
}
.clickable-box label {
@ -126,4 +127,48 @@ pre {
body.has-background-black-ter .subtitle, body.has-background-black-ter .column > div {
color: hsl(0, 0%, 96%);
}
.ribbon {
position: absolute;
right: -5px; top: -5px;
z-index: 1;
overflow: hidden;
width: 75px; height: 75px;
text-align: right;
}
.ribbon span {
font-size: 10px;
font-weight: bold;
color: #FFF;
text-transform: uppercase;
text-align: center;
line-height: 20px;
transform: rotate(45deg);
-webkit-transform: rotate(45deg);
width: 100px;
display: block;
background: #79A70A;
background: linear-gradient(#FF3C28 0%, #FF3C28 100%);
box-shadow: 0 3px 10px -5px rgba(0, 0, 0, 1);
position: absolute;
top: 19px; right: -21px;
}
.ribbon span::before {
content: "";
position: absolute; left: 0px; top: 100%;
z-index: -1;
border-left: 3px solid #FF3C28;
border-right: 3px solid transparent;
border-bottom: 3px solid transparent;
border-top: 3px solid #FF3C28;
}
.ribbon span::after {
content: "";
position: absolute; right: 0px; top: 100%;
z-index: -1;
border-left: 3px solid transparent;
border-right: 3px solid #FF3C28;
border-bottom: 3px solid transparent;
border-top: 3px solid #FF3C28;
}
</style>

View file

@ -84,6 +84,13 @@ var app = new Vue({
attrs: base_attributes,
config: {},
install_location: '',
username: '',
token: '',
jwt_token: {},
is_authenticated: false,
is_linked: false,
is_subscribed: false,
has_reward_tier: false,
// If the option to pick an install location should be provided
show_install_location: true,
metadata: {
@ -111,6 +118,34 @@ var app = new Vue({
}
)
},
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
})
},
ajax: ajax,
stream_ajax: stream_ajax
}

View file

@ -7,6 +7,8 @@ import ErrorView from './views/ErrorView.vue'
import InstallPackages from './views/InstallPackages.vue'
import CompleteView from './views/CompleteView.vue'
import ModifyView from './views/ModifyView.vue'
import AuthenticationView from './views/AuthenticationView.vue'
import ReAuthenticationView from './views/ReAuthenticationView.vue'
Vue.use(Router)
@ -47,6 +49,16 @@ export default new Router({
name: 'modify',
component: ModifyView
},
{
path: '/authentication',
name: 'authentication',
component: AuthenticationView
},
{
path: '/reauthenticate',
name: 'reauthenticate',
component: ReAuthenticationView
},
{
path: '/',
redirect: '/config'

View file

@ -0,0 +1,153 @@
<template>
<div class="column has-padding">
<b-message type="is-info" :active.sync="browser_opened">
Page opened! Check your default browser for the page, and follow the instructions there to link your patreon account.
When you are done, enter the token below.
</b-message>
<b-message type="is-info" :active.sync="show_header">
The <strong>Early Access</strong> 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.
To be an Early Access member, you must be a Patreon Early Access Subscriber.
</b-message>
<div>
If you are a subscriber, <a v-on:click="launch_browser('https://profile.yuzu-emu.org/')">click here to link your yuzu-emu.org account</a>
<br>
If you are not already a subscriber, <a v-on:click="launch_browser('https://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">click here to become one</a>
</div>
<br>
<div class="control">
<label for="token">Token</label>
<input class="input" type="text" v-model="combined_token" placeholder="Token" id="token">
</div>
<br>
<b-message type="is-danger" :active.sync="invalid_token">
Login failed!
Double check that your token is correct and try again
</b-message>
<b-message type="is-danger" :active.sync="invalid_login">
Login failed!
Double check that your token is correct and try again
</b-message>
<b-message type="is-danger" :active.sync="unlinked_patreon">
Your credentials are valid, but you still need to link your patreon!
If this is an error, then <a v-on:click="launch_browser('https://profile.yuzu-emu.org/')">click here to link your yuzu-emu.org account</a>
</b-message>
<b-message type="is-danger" :active.sync="no_subscription">
Your patreon is linked, but you are not a current subscriber!
<a v-on:click="launch_browser('https://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">Log into your patreon account</a> and support the project!
</b-message>
<b-message type="is-danger" :active.sync="tier_not_selected">
Your patreon is linked, and you are supporting the project, but you must first join the Early Access reward tier!
<a v-on:click="launch_browser('https://www.patreon.com/join/yuzuteam/checkout?rid=2822069')">Log into your patreon account</a> and choose to back the Early Access reward tier.
</b-message>
<div class="is-left-floating is-bottom-floating">
<p class="control">
<a class="button is-medium" v-on:click="go_back">Back</a>
</p>
</div>
<div class="is-right-floating is-bottom-floating">
<p class="control">
<a class="button is-dark is-medium" v-on:click="verify_token">Verify Token</a>
</p>
</div>
</div>
</template>
<script>
export default {
name: 'AuthenticationView',
created: function() {
// If they are already authenticated when this page is loaded,
// then we can asssume they are "clicking here for more details" and should show the appropriate error message
if (this.$root.is_authenticated) {
this.verification_opened = true;
}
},
data: function() {
return {
browser_opened: false,
verification_opened: false,
invalid_token: false,
}
},
computed: {
show_header: function() {
return !this.browser_opened && !this.verification_opened && !this.invalid_token;
},
invalid_login: function() {
return this.verification_opened && !this.$root.is_authenticated;
},
unlinked_patreon: function() {
return this.verification_opened && this.$root.is_authenticated && !this.$root.is_linked;
},
no_subscription: function() {
return this.verification_opened && this.$root.is_linked && !this.$root.is_subscribed;
},
tier_not_selected: function() {
return this.verification_opened && this.$root.is_linked && this.$root.is_subscribed && !this.$root.has_reward_tier;
},
combined_token: {
// getter
get: function () {
if (this.$root.$data.username && this.$root.$data.token) {
return btoa(this.$root.$data.username + ":" + this.$root.$data.token)
}
return "";
},
// setter
set: function (newValue) {
try {
var split = atob(newValue).split(':')
this.$root.$data.username = split[0];
this.$root.$data.token = split[1];
this.invalid_token = false;
} catch (e) {
this.invalid_token = true;
}
}
}
},
methods: {
go_back: function () {
this.$router.go(-1)
},
launch_browser: function(url) {
const that = this;
let app = this.$root;
app.ajax('/api/open-browser', function (e) {
// only open the browser opened message if there isn't an error message currently
if (!that.verification_opened) {
that.browser_opened = true;
}
}, function (e) {}, {
"url": url,
});
},
verify_token: function() {
this.browser_opened = false;
this.$root.check_authentication(this.success, this.error);
},
success: function() {
// if they are eligible, go back to the select package page
if (this.$root.has_reward_tier) {
this.$router.go(-1);
return;
}
// They aren't currently eligible for the release, so display the error message
this.verification_opened = true;
},
error: function() {
this.verification_opened = true;
}
}
}
</script>

View file

@ -23,7 +23,7 @@
<h4 class="subtitle">Thanks for installing {{ $root.$data.attrs.name }}!</h4>
<p>You can find your installed applications in your start menu.</p>
<br>
<img src="../assets/how-to-open.png" alt="Where yuzu is installed"/>
</div>
<div v-else>

View file

@ -29,29 +29,27 @@ export default {
this.$root.ajax('/api/config', function (e) {
that.$root.config = e
that.choose_next_state()
// Update the updater if needed
if (that.$root.config.new_tool) {
this.$router.push('/install/updater')
return
}
that.$root.check_authentication(that.choose_next_state, that.choose_next_state)
}, function (e) {
console.error('Got error while downloading config: ' +
e)
console.error('Got error while downloading config: ' + e)
if (that.$root.metadata.is_launcher) {
// Just launch the target application
that.$root.exit()
} else {
that.$router.replace({ name: 'showerr',
params: { msg: 'Got error while downloading config: ' +
e } })
params: { msg: 'Got error while downloading config: ' + e } })
}
})
},
choose_next_state: function () {
var app = this.$root
// Update the updater if needed
if (app.config.new_tool) {
this.$router.push('/install/updater')
return
}
if (app.metadata.preexisting_install) {
app.install_location = app.metadata.install_path

View file

@ -25,6 +25,7 @@ export default {
is_updater_update: false,
is_update: false,
failed_with_error: false,
authorization_required: false,
packages_installed: 0
}
},
@ -41,10 +42,12 @@ export default {
var app = this.$root
var results = {}
var requires_authorization = false;
for (var package_index = 0; package_index < app.config.packages.length; package_index++) {
var current_package = app.config.packages[package_index]
if (current_package.default != null) {
requires_authorization |= current_package.requires_authorization;
results[current_package.name] = current_package.default
}
}
@ -71,6 +74,10 @@ export default {
that.packages_installed += 1
}
if (line.hasOwnProperty('AuthorizationRequired')) {
that.authorization_required = true
}
if (line.hasOwnProperty('Error')) {
that.failed_with_error = true
that.$router.replace({ name: 'showerr', params: { msg: line.Error } })
@ -90,7 +97,9 @@ export default {
}
}
} else {
if (app.metadata.is_launcher) {
if (that.authorization_required) {
that.$router.push('/reauthenticate')
} else if (app.metadata.is_launcher) {
app.exit()
} else if (!that.failed_with_error) {
if (that.is_uninstall) {

View file

@ -0,0 +1,47 @@
<template>
<div class="column has-padding">
<b-message type="is-info">
<h4 class="subtitle">Update Available!</h4>
There is a new Early Access update available, but you are no longer in the early access reward tier.
We'd love to have you check out this update, so <a v-on:click="go_authenticate">click here to refresh your access!</a>
</b-message>
<br>
Alternatively, you can install the regular version of yuzu by clicking <strong>Back</strong> below.
<br>
Click <strong>Launch Old Version</strong> to continue using the old version of yuzu Early Access.
<div class="is-left-floating is-bottom-floating">
<p class="control">
<a class="button is-medium" v-on:click="go_packages">Back</a>
</p>
</div>
<div class="is-right-floating is-bottom-floating">
<p class="control">
<a class="button is-dark is-medium" v-on:click="launch_old_version">Launch Old Version</a>
</p>
</div>
</div>
</template>
<script>
export default {
name: "ReAuthenticationView",
methods: {
go_authenticate: function() {
this.$router.replace('/authentication')
},
launch_old_version: function () {
this.$root.exit()
},
go_packages: function () {
this.$router.push('/packages')
}
}
}
</script>
<style scoped>
</style>

View file

@ -6,7 +6,8 @@
<div class="tile is-ancestor">
<div class="tile is-parent" v-for="Lpackage in $root.$data.config.packages" :key="Lpackage.name" :index="Lpackage.name">
<div class="tile is-child">
<div class="box clickable-box" v-on:click.capture.stop="Lpackage.default = !Lpackage.default">
<div class="box clickable-box" v-if="!Lpackage.requires_authorization || (Lpackage.requires_authorization && $root.$data.has_reward_tier)" v-on:click.capture.stop="Lpackage.default = !Lpackage.default">
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
<label class="checkbox">
<b-checkbox v-model="Lpackage.default">
{{ Lpackage.name }}
@ -17,6 +18,42 @@
{{ Lpackage.description }}
</p>
</div>
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_authenticated" v-on:click="show_authentication">
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
<b-checkbox>
{{ Lpackage.name }}
</b-checkbox>
<p>
{{Lpackage.need_authentication_description}}
</p>
</div>
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_linked" v-on:click="show_authorization">
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
<b-checkbox>
{{ Lpackage.name }}
</b-checkbox>
<p>
{{Lpackage.need_link_description}}
</p>
</div>
<div class="box clickable-box" v-else-if="Lpackage.requires_authorization && !$root.$data.is_subscribed" v-on:click="show_authorization">
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
<b-checkbox>
{{ Lpackage.name }}
</b-checkbox>
<p>
{{Lpackage.need_subscription_description}}
</p>
</div>
<div class="box clickable-box" v-else v-on:click="show_authorization">
<div class="ribbon" v-if="Lpackage.is_new"><span>New!</span></div>
<b-checkbox>
{{ Lpackage.name }}
</b-checkbox>
<p>
{{Lpackage.need_reward_tier_description}}
</p>
</div>
</div>
</div>
</div>
@ -63,6 +100,20 @@
<script>
export default {
name: 'SelectPackages',
created: function() {
if (this.$root.$data.has_reward_tier) {
for (let package_index = 0; package_index < this.$root.config.packages.length; package_index++) {
let current_package = this.$root.config.packages[package_index];
// If they are authorized, make the packages that require authorization default
if (current_package.requires_authorization) {
current_package.default = true;
} else {
// And unselect any other packages
current_package.default = false;
}
}
}
},
data: function () {
return {
advanced: false
@ -81,7 +132,13 @@ export default {
},
go_back: function () {
this.$router.go(-1)
}
},
show_authentication: function () {
this.$router.push('/authentication')
},
show_authorization: function () {
this.$router.push('/authentication')
},
}
}
</script>