diff --git a/bun.lock b/bun.lock index 99a11cd..a71eb9b 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,7 @@ "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slider": "^1.3.6", @@ -15,7 +16,6 @@ "@tailwindcss/vite": "^4.1.17", "@tanstack/react-form": "^1.25.0", "@tauri-apps/api": "^2", - "@tauri-apps/plugin-notification": "~2", "@tauri-apps/plugin-opener": "^2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -24,6 +24,7 @@ "react-dom": "^19.1.0", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.17", + "ts-pattern": "^5.9.0", "zod": "^4.1.12", }, "devDependencies": { @@ -186,6 +187,8 @@ "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], + "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.8", "", { "dependencies": { "@radix-ui/react-context": "1.1.3", "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA=="], + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.3", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.11", "@radix-ui/react-focus-guards": "1.1.3", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.8", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ=="], "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g=="], @@ -330,8 +333,6 @@ "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.9.4", "", { "os": "win32", "cpu": "x64" }, "sha512-EdYd4c9wGvtPB95kqtEyY+bUR+k4kRw3IA30mAQ1jPH6z57AftT8q84qwv0RDp6kkEqOBKxeInKfqi4BESYuqg=="], - "@tauri-apps/plugin-notification": ["@tauri-apps/plugin-notification@2.3.3", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-Zw+ZH18RJb41G4NrfHgIuofJiymusqN+q8fGUIIV7vyCH+5sSn5coqRv/MWB9qETsUs97vmU045q7OyseCV3Qg=="], - "@tauri-apps/plugin-opener": ["@tauri-apps/plugin-opener@2.5.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-ei/yRRoCklWHImwpCcDK3VhNXx+QXM9793aQ64YxpqVF0BDuuIlXhZgiAkc15wnPVav+IbkYhmDJIv5R326Mew=="], "@types/babel__core": ["@types/babel__core@7.20.5", "", { "dependencies": { "@babel/parser": "^7.20.7", "@babel/types": "^7.20.7", "@types/babel__generator": "*", "@types/babel__template": "*", "@types/babel__traverse": "*" } }, "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA=="], @@ -470,6 +471,8 @@ "tinyglobby": ["tinyglobby@0.2.15", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.3" } }, "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ=="], + "ts-pattern": ["ts-pattern@5.9.0", "", {}, "sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg=="], + "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "tw-animate-css": ["tw-animate-css@1.4.0", "", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], @@ -496,6 +499,10 @@ "@radix-ui/react-primitive/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], + "@radix-ui/react-progress/@radix-ui/react-context": ["@radix-ui/react-context@1.1.3", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw=="], + + "@radix-ui/react-progress/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], + "@radix-ui/react-select/@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], "@radix-ui/react-separator/@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.4", "", { "dependencies": { "@radix-ui/react-slot": "1.2.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg=="], diff --git a/config.html b/config.html new file mode 100644 index 0000000..05769e3 --- /dev/null +++ b/config.html @@ -0,0 +1,13 @@ + + + + + + + Config + + +
+ + + diff --git a/index.html b/index.html index fba1582..f7593a8 100644 --- a/index.html +++ b/index.html @@ -4,9 +4,8 @@ - Tauri + React + Typescript + Patcher -
diff --git a/package.json b/package.json index 5dc60d8..d67d974 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@radix-ui/react-accordion": "^1.2.12", "@radix-ui/react-checkbox": "^1.3.3", "@radix-ui/react-label": "^2.1.8", + "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-select": "^2.2.6", "@radix-ui/react-separator": "^1.1.8", "@radix-ui/react-slider": "^1.3.6", @@ -21,7 +22,6 @@ "@tailwindcss/vite": "^4.1.17", "@tanstack/react-form": "^1.25.0", "@tauri-apps/api": "^2", - "@tauri-apps/plugin-notification": "~2", "@tauri-apps/plugin-opener": "^2", "class-variance-authority": "^0.7.1", "clsx": "^2.1.1", @@ -30,6 +30,7 @@ "react-dom": "^19.1.0", "tailwind-merge": "^3.4.0", "tailwindcss": "^4.1.17", + "ts-pattern": "^5.9.0", "zod": "^4.1.12" }, "devDependencies": { diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index bd35481..84cd40f 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -7,13 +7,19 @@ name = "Config" version = "0.1.0" dependencies = [ "display-info", + "futures-util", + "memmap2", + "rayon", + "reqwest", "serde", "serde_json", "tauri", "tauri-build", - "tauri-plugin-notification", "tauri-plugin-opener", + "tokio", + "walkdir", "windows 0.62.2", + "xxhash-rust", ] [[package]] @@ -505,6 +511,16 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "core-foundation" version = "0.10.1" @@ -528,9 +544,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa95a34622365fa5bbf40b20b75dba8dfa8c94c734aea8ac9a5ca38af14316f1" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.10.1", "core-graphics-types", - "foreign-types", + "foreign-types 0.5.0", "libc", ] @@ -541,7 +557,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d44a101f213f6c4cdc1853d4b78aef6db6bdfa3468798cc1d9912f4735013eb" dependencies = [ "bitflags 2.10.0", - "core-foundation", + "core-foundation 0.10.1", "libc", ] @@ -572,6 +588,25 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "crossbeam-deque" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51" +dependencies = [ + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" +dependencies = [ + "crossbeam-utils", +] + [[package]] name = "crossbeam-utils" version = "0.8.21" @@ -833,6 +868,12 @@ version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" +[[package]] +name = "either" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" + [[package]] name = "embed-resource" version = "3.0.6" @@ -853,6 +894,15 @@ version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4ef6b89e5b37196644d8796de5268852ff179b44e96276cf4290264843743bb7" +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + [[package]] name = "endi" version = "1.1.0" @@ -975,6 +1025,15 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "foreign-types" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" +dependencies = [ + "foreign-types-shared 0.1.1", +] + [[package]] name = "foreign-types" version = "0.5.0" @@ -982,7 +1041,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d737d9aa519fb7b749cbc3b962edcf310a8dd1f4b67c91c4f83975dbdd17d965" dependencies = [ "foreign-types-macros", - "foreign-types-shared", + "foreign-types-shared 0.3.1", ] [[package]] @@ -996,6 +1055,12 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "foreign-types-shared" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" + [[package]] name = "foreign-types-shared" version = "0.3.1" @@ -1028,6 +1093,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" dependencies = [ "futures-core", + "futures-sink", ] [[package]] @@ -1406,6 +1472,25 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "h2" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3c0b69cfcb4e1b9f1bf2f53f95f766e4661169728ec61cd3fe5a0166f2d1386" +dependencies = [ + "atomic-waker", + "bytes", + "fnv", + "futures-core", + "futures-sink", + "http", + "indexmap 2.12.0", + "slab", + "tokio", + "tokio-util", + "tracing", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1504,6 +1589,7 @@ dependencies = [ "bytes", "futures-channel", "futures-core", + "h2", "http", "http-body", "httparse", @@ -1515,6 +1601,38 @@ dependencies = [ "want", ] +[[package]] +name = "hyper-rustls" +version = "0.27.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3c93eb611681b207e1fe55d5a71ecf91572ec8a6705cdb6857f7d8d5242cf58" +dependencies = [ + "http", + "hyper", + "hyper-util", + "rustls", + "rustls-pki-types", + "tokio", + "tokio-rustls", + "tower-service", +] + +[[package]] +name = "hyper-tls" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" +dependencies = [ + "bytes", + "http-body-util", + "hyper", + "hyper-util", + "native-tls", + "tokio", + "tokio-native-tls", + "tower-service", +] + [[package]] name = "hyper-util" version = "0.1.18" @@ -1534,9 +1652,11 @@ dependencies = [ "percent-encoding", "pin-project-lite", "socket2", + "system-configuration", "tokio", "tower-service", "tracing", + "windows-registry", ] [[package]] @@ -1943,18 +2063,6 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" -[[package]] -name = "mac-notification-sys" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ee70bb2bba058d58e252d2944582d634fc884fc9c489a966d428dedcf653e97" -dependencies = [ - "cc", - "objc2 0.6.3", - "objc2-foundation 0.3.2", - "time", -] - [[package]] name = "markup5ever" version = "0.14.1" @@ -2058,6 +2166,23 @@ dependencies = [ "windows-sys 0.60.2", ] +[[package]] +name = "native-tls" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87de3442987e9dbec73158d5c715e7ad9072fda936bb03d19d7fa10e00520f0e" +dependencies = [ + "libc", + "log", + "openssl", + "openssl-probe", + "openssl-sys", + "schannel", + "security-framework", + "security-framework-sys", + "tempfile", +] + [[package]] name = "ndk" version = "0.9.0" @@ -2113,20 +2238,6 @@ version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" -[[package]] -name = "notify-rust" -version = "4.11.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6442248665a5aa2514e794af3b39661a8e73033b1cc5e59899e1276117ee4400" -dependencies = [ - "futures-lite", - "log", - "mac-notification-sys", - "serde", - "tauri-winrt-notification", - "zbus", -] - [[package]] name = "num-conv" version = "0.1.0" @@ -2462,6 +2573,50 @@ dependencies = [ "pathdiff", ] +[[package]] +name = "openssl" +version = "0.10.75" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08838db121398ad17ab8531ce9de97b244589089e290a384c900cb9ff7434328" +dependencies = [ + "bitflags 2.10.0", + "cfg-if", + "foreign-types 0.3.2", + "libc", + "once_cell", + "openssl-macros", + "openssl-sys", +] + +[[package]] +name = "openssl-macros" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "openssl-probe" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d05e27ee213611ffe7d6348b942e8f942b37114c00cc03cec254295a4a17852e" + +[[package]] +name = "openssl-sys" +version = "0.9.111" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82cab2d520aa75e3c58898289429321eb788c3106963d0dc886ec7a5f4adc321" +dependencies = [ + "cc", + "libc", + "pkg-config", + "vcpkg", +] + [[package]] name = "option-ext" version = "0.2.0" @@ -2912,16 +3067,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand" -version = "0.9.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6db2770f06117d490610c7488547d543617b21bfa07796d7a12f6f1bd53850d1" -dependencies = [ - "rand_chacha 0.9.0", - "rand_core 0.9.3", -] - [[package]] name = "rand_chacha" version = "0.2.2" @@ -2942,16 +3087,6 @@ dependencies = [ "rand_core 0.6.4", ] -[[package]] -name = "rand_chacha" -version = "0.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" -dependencies = [ - "ppv-lite86", - "rand_core 0.9.3", -] - [[package]] name = "rand_core" version = "0.5.1" @@ -2970,15 +3105,6 @@ dependencies = [ "getrandom 0.2.16", ] -[[package]] -name = "rand_core" -version = "0.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" -dependencies = [ - "getrandom 0.3.4", -] - [[package]] name = "rand_hc" version = "0.2.0" @@ -3003,6 +3129,26 @@ version = "0.6.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" +[[package]] +name = "rayon" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "368f01d005bf8fd9b1206fb6fa653e6c4a81ceb1466406b81792d87c5677a58f" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22e18b0f0062d30d4230b2e85ff77fdfe4326feb054b9783a3460d8435c8ab91" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + [[package]] name = "redox_syscall" version = "0.5.18" @@ -3080,22 +3226,31 @@ checksum = "9d0946410b9f7b082a427e4ef5c8ff541a88b357bc6c637c40db3a68ac70a36f" dependencies = [ "base64 0.22.1", "bytes", + "encoding_rs", + "futures-channel", "futures-core", "futures-util", + "h2", "http", "http-body", "http-body-util", "hyper", + "hyper-rustls", + "hyper-tls", "hyper-util", "js-sys", "log", + "mime", + "native-tls", "percent-encoding", "pin-project-lite", + "rustls-pki-types", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "tokio", + "tokio-native-tls", "tokio-util", "tower", "tower-http", @@ -3107,6 +3262,20 @@ dependencies = [ "web-sys", ] +[[package]] +name = "ring" +version = "0.17.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a4689e6c2294d81e88dc6261c768b63bc4fcdb852be6d1352498b114f61383b7" +dependencies = [ + "cc", + "cfg-if", + "getrandom 0.2.16", + "libc", + "untrusted", + "windows-sys 0.52.0", +] + [[package]] name = "rustc_version" version = "0.4.1" @@ -3129,6 +3298,39 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "rustls" +version = "0.23.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "533f54bc6a7d4f647e46ad909549eda97bf5afc1585190ef692b4286b198bd8f" +dependencies = [ + "once_cell", + "rustls-pki-types", + "rustls-webpki", + "subtle", + "zeroize", +] + +[[package]] +name = "rustls-pki-types" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94182ad936a0c91c324cd46c6511b9510ed16af436d7b5bab34beab0afd55f7a" +dependencies = [ + "zeroize", +] + +[[package]] +name = "rustls-webpki" +version = "0.103.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +dependencies = [ + "ring", + "rustls-pki-types", + "untrusted", +] + [[package]] name = "rustversion" version = "1.0.22" @@ -3150,6 +3352,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "schannel" +version = "0.1.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "891d81b926048e76efe18581bf793546b4c0eaf8448d72be8de2bbee5fd166e1" +dependencies = [ + "windows-sys 0.61.2", +] + [[package]] name = "schemars" version = "0.8.22" @@ -3207,6 +3418,29 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "security-framework" +version = "2.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "core-foundation-sys", + "libc", + "security-framework-sys", +] + +[[package]] +name = "security-framework-sys" +version = "2.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1f0cbffaac4852523ce30d8bd3c5cdc873501d96ff467ca09b6767bb8cd5c0" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "selectors" version = "0.24.0" @@ -3505,7 +3739,7 @@ dependencies = [ "bytemuck", "cfg_aliases", "core-graphics", - "foreign-types", + "foreign-types 0.5.0", "js-sys", "log", "objc2 0.5.2", @@ -3587,6 +3821,12 @@ version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" +[[package]] +name = "subtle" +version = "2.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" + [[package]] name = "swift-rs" version = "1.0.7" @@ -3640,6 +3880,27 @@ dependencies = [ "syn 2.0.110", ] +[[package]] +name = "system-configuration" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c879d448e9d986b661742763247d3693ed13609438cf3d006f51f5368a5ba6b" +dependencies = [ + "bitflags 2.10.0", + "core-foundation 0.9.4", + "system-configuration-sys", +] + +[[package]] +name = "system-configuration-sys" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e1d1b10ced5ca923a1fcb8d03e96b8d3268065d724548c0211415ff6ac6bac4" +dependencies = [ + "core-foundation-sys", + "libc", +] + [[package]] name = "system-deps" version = "6.2.2" @@ -3661,7 +3922,7 @@ checksum = "f3a753bdc39c07b192151523a3f77cd0394aa75413802c883a0f6f6a0e5ee2e7" dependencies = [ "bitflags 2.10.0", "block2 0.6.2", - "core-foundation", + "core-foundation 0.10.1", "core-graphics", "crossbeam-channel", "dispatch", @@ -3841,25 +4102,6 @@ dependencies = [ "walkdir", ] -[[package]] -name = "tauri-plugin-notification" -version = "2.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01fc2c5ff41105bd1f7242d8201fdf3efd70749b82fa013a17f2126357d194cc" -dependencies = [ - "log", - "notify-rust", - "rand 0.9.2", - "serde", - "serde_json", - "serde_repr", - "tauri", - "tauri-plugin", - "thiserror 2.0.17", - "time", - "url", -] - [[package]] name = "tauri-plugin-opener" version = "2.5.2" @@ -3983,18 +4225,6 @@ dependencies = [ "toml 0.9.8", ] -[[package]] -name = "tauri-winrt-notification" -version = "0.7.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b1e66e07de489fe43a46678dd0b8df65e0c973909df1b60ba33874e297ba9b9" -dependencies = [ - "quick-xml 0.37.5", - "thiserror 2.0.17", - "windows 0.61.3", - "windows-version", -] - [[package]] name = "tempfile" version = "3.23.0" @@ -4109,11 +4339,45 @@ dependencies = [ "bytes", "libc", "mio", + "parking_lot", "pin-project-lite", + "signal-hook-registry", "socket2", + "tokio-macros", "windows-sys 0.61.2", ] +[[package]] +name = "tokio-macros" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af407857209536a95c8e56f8231ef2c2e2aff839b22e07a1ffcbc617e9db9fa5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.110", +] + +[[package]] +name = "tokio-native-tls" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" +dependencies = [ + "native-tls", + "tokio", +] + +[[package]] +name = "tokio-rustls" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1729aa945f29d91ba541258c8df89027d5792d85a8841fb65e8bf0f4ede4ef61" +dependencies = [ + "rustls", + "tokio", +] + [[package]] name = "tokio-util" version = "0.7.17" @@ -4403,6 +4667,12 @@ version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" +[[package]] +name = "untrusted" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" + [[package]] name = "url" version = "2.5.7" @@ -4451,6 +4721,12 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + [[package]] name = "version-compare" version = "0.2.1" @@ -4999,6 +5275,17 @@ dependencies = [ "windows-link 0.2.1", ] +[[package]] +name = "windows-registry" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02752bf7fbdcce7f2a27a742f798510f3e5ad88dbe84871e5168e2120c3d5720" +dependencies = [ + "windows-link 0.2.1", + "windows-result 0.4.1", + "windows-strings 0.5.1", +] + [[package]] name = "windows-result" version = "0.3.4" @@ -5044,6 +5331,15 @@ dependencies = [ "windows-targets 0.42.2", ] +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets 0.52.6", +] + [[package]] name = "windows-sys" version = "0.59.0" @@ -5413,6 +5709,12 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" +[[package]] +name = "xxhash-rust" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdd20c5420375476fbd4394763288da7eb0cc0b8c11deed431a91562af7335d3" + [[package]] name = "yoke" version = "0.8.1" @@ -5538,6 +5840,12 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zeroize" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b97154e67e32c85465826e8bcc1c59429aaaf107c1e4a9e53c8d8ccd5eff88d0" + [[package]] name = "zerotrie" version = "0.2.3" diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 04b8830..4f477b1 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -23,7 +23,13 @@ tauri-plugin-opener = "2" serde = { version = "1", features = ["derive"] } serde_json = "1" display-info = "0.5.7" -tauri-plugin-notification = "2" +xxhash-rust = { version = "0.8.15", features = ["xxh3"] } +reqwest = { version = "0.12", features = ["json", "stream", "blocking"] } +tokio = { version = "1", features = ["full"] } +rayon = "1.10" +memmap2 = "0.9" +futures-util = "0.3" +walkdir = "2.3" [target.'cfg(windows)'.dependencies] windows = { version = "0.62", features = ["Win32_Graphics_Direct3D9"] } diff --git a/src-tauri/capabilities/default.json b/src-tauri/capabilities/default.json index 2985263..6ff9e23 100644 --- a/src-tauri/capabilities/default.json +++ b/src-tauri/capabilities/default.json @@ -2,14 +2,11 @@ "$schema": "../gen/schemas/desktop-schema.json", "identifier": "default", "description": "Capability for the main window", - "windows": [ - "main" - ], + "windows": ["main", "config"], "permissions": [ "core:default", "opener:default", "core:window:default", - "core:window:allow-start-dragging", - "notification:default" + "core:window:allow-start-dragging" ] -} \ No newline at end of file +} diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rs index 48ca4a6..1dcbc6c 100644 --- a/src-tauri/src/lib.rs +++ b/src-tauri/src/lib.rs @@ -1,8 +1,29 @@ use serde::{Deserialize, Serialize}; -use std::fs; +use std::{fs, fs::File, sync::Mutex}; use std::path::PathBuf; -use tauri::Manager; -use tauri_plugin_notification::NotificationExt; +use std::collections::HashMap; +use tokio::io::AsyncWriteExt; +use futures_util::StreamExt; +use tauri::{AppHandle, Emitter, Manager}; +use xxhash_rust::xxh3::xxh3_64; +use rayon::prelude::*; +use memmap2::Mmap; +use walkdir::WalkDir; + +pub const DIR: &str = if cfg!(debug_assertions) { + if cfg!(target_os = "windows") { + "C:\\Users\\Ego\\Desktop\\test\\" + } else { + "/home/ego/Documents/metin2/client/dist/" + } +} else { + "./" +}; + +// TODO HANDLE ERRORS PROPERLY +// TODO SUPPORT REGEX +// TODO SUPPORT ALL FILE MOVES +pub const SERVER: &str = "http://192.168.31.60:8080"; #[derive(Debug, Serialize, Deserialize)] pub struct Config { @@ -22,19 +43,8 @@ pub struct Config { pub fog_mode_on: bool, pub camera_mode: CameraMode, pub private_shop: PrivateShop, - // pub object_culling: bool, - // pub visibility: i32, - // pub software_cursor: bool, - // pub is_save_id: i32, - // pub save_id: i32, - // pub preloading_delay_time: i32, - // pub decompressed_texture: bool, - // pub use_default_ime: bool, - // pub software_tiling: i32, } -// ------------------ Enums ------------------ - #[derive(Debug, Serialize, Deserialize)] #[serde(rename_all = "lowercase")] pub enum Locale { @@ -137,7 +147,7 @@ impl Default for Config { } } -fn get_config_path(_app: &tauri::AppHandle) -> PathBuf { +fn get_config_path(_app: &AppHandle) -> PathBuf { std::env::current_exe() .expect("Failed to get executable path") .parent() @@ -147,7 +157,7 @@ fn get_config_path(_app: &tauri::AppHandle) -> PathBuf { } #[tauri::command] -fn get_config(app: tauri::AppHandle) -> Config { +fn get_config(app: AppHandle) -> Config { let config_path = get_config_path(&app); match fs::read_to_string(&config_path) { @@ -160,39 +170,39 @@ fn get_config(app: tauri::AppHandle) -> Config { } #[tauri::command] -fn set_config(config: Config, app: tauri::AppHandle) -> bool { +fn set_config(config: Config, app: AppHandle) -> bool { let config_path = get_config_path(&app); match serde_json::to_string_pretty(&config) { Ok(json) => match fs::write(&config_path, json) { Ok(_) => { - app.notification() - .builder() - .title("Configuration") - .body("Saved Metin2 client settings") - .show() - .unwrap(); + // app.notification() + // .builder() + // .title("Configuration") + // .body("Saved Metin2 client settings") + // .show() + // .unwrap(); true } - Err(e) => { - app.notification() - .builder() - .title("Configuration") - .body(&format!("Could not save config: {}", e)) - .show() - .unwrap(); + Err(_e) => { + // app.notification() + // .builder() + // .title("Configuration") + // .body(&format!("Could not save config: {}", e)) + // .show() + // .unwrap(); false } }, - Err(e) => { - app.notification() - .builder() - .title("Configuration") - .body(&format!("Could not serialize config: {}", e)) - .show() - .unwrap(); + Err(_e) => { + // app.notification() + // .builder() + // .title("Configuration") + // .body(&format!("Could not serialize config: {}", e)) + // .show() + // .unwrap(); false } @@ -289,20 +299,297 @@ fn get_resolutions() -> Vec { } #[tauri::command] -fn close(app: tauri::AppHandle) { +fn close(app: AppHandle) { app.exit(0); } +pub fn pack_hash(app: AppHandle) -> std::io::Result> { + let entries: Vec = WalkDir::new(DIR) + .into_iter() + .filter_map(|e| e.ok()) + .filter(|e| e.path().is_file()) + .map(|e| e.path().to_path_buf()) + .collect(); + + let results = Mutex::new(Vec::with_capacity(entries.len())); + + entries.par_iter().try_for_each(|path| -> std::io::Result<()> { + // Relative path to DIR + let rel_path = path.strip_prefix(DIR) + .unwrap() + .to_string_lossy() + .replace("\\", "/"); // normalize + + app.emit("file", &rel_path) + .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; + + let file = File::open(path)?; + let mmap = unsafe { Mmap::map(&file)? }; + let hash = xxh3_64(&mmap); + + results.lock().unwrap().push((rel_path, hash)); + + Ok(()) + })?; + + Ok(results.into_inner().unwrap()) +} + +#[derive(Debug, Deserialize)] +struct ChecksumResponse { + checksums: HashMap, +} + +async fn fetch_server_checksums() -> Result, String> { + let response = reqwest::get(format!("{}/checksum", SERVER)) + .await + .map_err(|e| format!("Failed to fetch checksums: {}", e))?; + + let parsed: ChecksumResponse = response + .json() + .await + .map_err(|e| format!("Failed to parse checksums: {}", e))?; + + let mut out = HashMap::new(); + + for (name, hash_str) in parsed.checksums { + let hash = hash_str + .parse::() + .map_err(|_| format!("Invalid checksum number for file: {}", name))?; + + // Normalize separators + let normalized_name = name.replace("\\", "/"); + out.insert(normalized_name, hash); + } + + Ok(out) +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +#[serde(into = "u8", try_from = "u8")] +pub enum Status { + Awaiting = 0, + Fetching = 1, + Checksumming = 2, + Verifying = 3, + Updating = 4, + Ready = 5, +} + +impl From for u8 { + fn from(status: Status) -> Self { + status as u8 + } +} + +impl TryFrom for Status { + type Error = String; + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Status::Awaiting), + 1 => Ok(Status::Fetching), + 2 => Ok(Status::Checksumming), + 3 => Ok(Status::Verifying), + 4 => Ok(Status::Updating), + 5 => Ok(Status::Ready), + _ => Err(format!("Invalid status value: {}", value)), + } + } +} + +async fn download_file(app_handle: &AppHandle, url: &str, dest_path: &str, file_name: &str) -> Result<(), String> { + if tokio::fs::metadata(dest_path).await.is_ok() { + let _ = tokio::fs::remove_file(dest_path).await; + } + + let client = reqwest::Client::new(); + let response = client.get(url) + .send() + .await + .map_err(|e| format!("Failed to download {}: {}", file_name, e))?; + + let total_size = response + .content_length() + .ok_or_else(|| format!("Failed to get content length for {}", file_name))?; + + let mut stream = response.bytes_stream(); + let mut file = tokio::fs::File::create(dest_path) + .await + .map_err(|e| format!("Failed to create file {}: {}", dest_path, e))?; + + let mut downloaded: u64 = 0; + + while let Some(chunk) = stream.next().await { + let chunk = chunk + .map_err(|e| format!("Error while downloading {}: {}", file_name, e))?; + + file.write_all(&chunk) + .await + .map_err(|e| format!("Failed to write {}: {}", dest_path, e))?; + + downloaded += chunk.len() as u64; + + // Emit progress safely + if let Err(e) = app_handle.emit("download", serde_json::json!({ + "file": file_name, + "downloaded": downloaded, + "total": total_size, + "percent": (downloaded as f64 / total_size as f64 * 100.0) as u8 + })) { + eprintln!("Failed to emit download progress: {}", e); + } + } + + Ok(()) +} + + +pub struct WindowState { + config_open: Mutex, +} + +impl Default for WindowState { + fn default() -> Self { + Self { + config_open: Mutex::new(false), + } + } +} + +#[tauri::command] +fn open_config(app: AppHandle) -> Result<(), String> { + let state = app.state::(); + let mut is_open = state.config_open.lock().unwrap(); + + if *is_open { + if let Some(window) = app.get_webview_window("config") { + let _ = window.close(); + return Ok(()); + } else { + *is_open = false; + } + } + + *is_open = true; + + let window = tauri::WebviewWindowBuilder::new( + &app, + "config", + tauri::WebviewUrl::App("config.html".into()) + ) + .title("Configuration") + .inner_size(800.0, 600.0) + .resizable(false) + .decorations(false) + .visible(true) + .build() + .map_err(|e| format!("Failed to create config window: {}", e))?; + + let app_handle = app.clone(); + window.on_window_event(move |event| { + if let tauri::WindowEvent::Destroyed = event { + let state = app_handle.state::(); + *state.config_open.lock().unwrap() = false; + } + }); + + Ok(()) +} + pub fn run() { tauri::Builder::default() - .plugin(tauri_plugin_notification::init()) .plugin(tauri_plugin_opener::init()) + .manage(WindowState::default()) .invoke_handler(tauri::generate_handler![ + open_config, get_resolutions, get_config, set_config, close, ]) + .setup(|app| { + let app_handle = app.handle().clone(); + + let _ = fs::create_dir_all(DIR); + + tauri::async_runtime::spawn(async move { + let _ = app_handle.emit("status", Status::Fetching).unwrap(); + + let server_hashes = match fetch_server_checksums().await { + Ok(hashes) => hashes, + Err(e) => { + let _ = app_handle.emit("error", e.to_string()).unwrap(); + return; + } + }; + + let _ = app_handle.emit("status", Status::Checksumming).unwrap(); + + let local_hashes = match tokio::task::spawn_blocking({ + let app_handle = app_handle.clone(); + move || pack_hash(app_handle) + }) + .await + { + Ok(Ok(hashes)) => hashes, + Ok(Err(e)) => { + let _ = app_handle.emit("error", e.to_string()).unwrap(); + return; + } + Err(e) => { + let _ = app_handle.emit("error", e.to_string()).unwrap(); + return; + } + }; + let local_map: HashMap = local_hashes.into_iter().collect(); + let _ = app_handle.emit("status", Status::Verifying).unwrap(); + + let mut updatable: HashMap = HashMap::new(); + + for (filename, server_hash) in &server_hashes { + match local_map.get(filename) { + Some(local_hash) => { + if local_hash != server_hash { + updatable.insert(filename.clone(), *server_hash); + } + } + None => { + updatable.insert(filename.clone(), *server_hash); + } + } + } + + if !updatable.is_empty() { + let _ = app_handle.emit("status", Status::Updating).unwrap(); + + for (file, _hash) in &updatable { + let url = format!("{}/{}", SERVER, file); + + let mut dest_path = PathBuf::from(DIR); + dest_path.push(file); + + if let Some(parent) = dest_path.parent() { + if let Err(e) = fs::create_dir_all(parent) { + let _ = app_handle.emit("error", format!("Failed to create dir: {}", e.to_string())); + continue; + } + } + + match download_file(&app_handle, &url, dest_path.to_str().unwrap(), file).await { + Ok(_) => {}, + Err(e) => { + let _ = app_handle.emit("error", e.to_string()); + } + } + } + } + + let _ = app_handle.emit("status", Status::Ready).unwrap(); + }); + + Ok(()) + }) .run(tauri::generate_context!()) .expect("error while running tauri application"); } diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 48c355b..03c37b2 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -14,9 +14,9 @@ "app": { "windows": [ { - "title": "Configuration", - "width": 800, - "height": 600, + "title": "Patcher", + "width": 600, + "height": 300, "resizable": false, "decorations": false } diff --git a/src/assets/background.jpg b/src/assets/background.jpg new file mode 100644 index 0000000..927d1fb Binary files /dev/null and b/src/assets/background.jpg differ diff --git a/src/components/drag-bar.tsx b/src/components/drag-bar.tsx new file mode 100644 index 0000000..b0e983d --- /dev/null +++ b/src/components/drag-bar.tsx @@ -0,0 +1,40 @@ +import { invoke } from "@tauri-apps/api/core"; +import { CogIcon, XIcon } from "lucide-react"; +import { useCallback } from "react"; + +export default function DragBar({ isMain }: { isMain?: boolean }) { + const close = useCallback(async () => { + if (isMain) { + await invoke("close"); + return; + } + + await invoke("open_config"); + }, [isMain]); + + const config = useCallback(async () => { + await invoke("open_config"); + }, []); + + return ( +
+
+ {isMain && ( + + )} + +
+ ); +} diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx new file mode 100644 index 0000000..10af7e6 --- /dev/null +++ b/src/components/ui/progress.tsx @@ -0,0 +1,29 @@ +import * as React from "react" +import * as ProgressPrimitive from "@radix-ui/react-progress" + +import { cn } from "@/lib/utils" + +function Progress({ + className, + value, + ...props +}: React.ComponentProps) { + return ( + + + + ) +} + +export { Progress } diff --git a/src/config.tsx b/src/config.tsx new file mode 100644 index 0000000..87f6c06 --- /dev/null +++ b/src/config.tsx @@ -0,0 +1,10 @@ +import React from "react"; +import ReactDOM from "react-dom/client"; +import App from "./pages/config"; +import "./index.css"; + +ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( + + + +); diff --git a/src/index.css b/src/index.css index f4c1e9b..91ebb33 100644 --- a/src/index.css +++ b/src/index.css @@ -3,6 +3,102 @@ @custom-variant dark (&:is(.dark *)); +:root { + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --destructive-foreground: oklch(1 0 0); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(87% 0 0); + --chart-2: oklch(70.8% 0 0); + --chart-3: oklch(55.6% 0 0); + --chart-4: oklch(43.9% 0 0); + --chart-5: oklch(37.1% 0 0); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); + --header: oklch(1 0 0); + --header-foreground: oklch(0.145 0 0); + --footer: oklch(1 0 0); + --footer-foreground: oklch(0.145 0 0); + --code: oklch(1 0 0); + --code-foreground: oklch(0.708 0 0); + --code-highlight: oklch(0.27 0 0); + --code-number: oklch(0.72 0 0); + --code-selection: oklch(0.922 0 0); + --code-border: oklch(0.922 0 0); + --radius: 0rem; +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.269 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.722 0.09 222.3); + --primary-foreground: oklch(0.1134 0 0); + --secondary: oklch(0.722 0.09 0); + --secondary-foreground: oklch(0.985 0 0); + --muted: oklch(0.269 0 0); + --muted-foreground: oklch(0.708 0 0); + --accent: oklch(0.371 0 0); + --accent-foreground: oklch(0.985 0 0); + --destructive: oklch(0.704 0.191 22.216); + --destructive-foreground: oklch(0.985 0 0); + --border: oklch(1 0 0 / 10%); + --input: oklch(1 0 0 / 15%); + --ring: oklch(0.556 0 0); + --chart-1: oklch(87% 0 0); + --chart-2: oklch(70.8% 0 0); + --chart-3: oklch(55.6% 0 0); + --chart-4: oklch(43.9% 0 0); + --chart-5: oklch(37.1% 0 0); + --sidebar: oklch(0.205 0 0); + --sidebar-foreground: oklch(0.985 0 0); + --sidebar-primary: oklch(0.488 0.243 264.376); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.269 0 0); + --sidebar-accent-foreground: oklch(0.985 0 0); + --sidebar-border: oklch(1 0 0 / 10%); + --sidebar-ring: oklch(0.439 0 0); + --header: oklch(0.145 0 0); + --header-foreground: oklch(0.985 0 0); + --footer: oklch(0.145 0 0); + --footer-foreground: oklch(0.985 0 0); + --code: oklch(0.2 0 0); + --code-foreground: oklch(0.708 0 0); + --code-highlight: oklch(0.27 0 0); + --code-number: oklch(0.72 0 0); + --code-selection: oklch(0.922 0 0); + --code-border: oklch(1 0 0 / 10%); + --radius: 0rem; +} + +.container { + @apply mx-auto; +} + @theme inline { --radius-sm: calc(var(--radius) - 4px); --radius-md: calc(var(--radius) - 2px); @@ -41,75 +137,6 @@ --color-sidebar-ring: var(--sidebar-ring); } -:root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); - --secondary: oklch(0.97 0 0); - --secondary-foreground: oklch(0.205 0 0); - --muted: oklch(0.97 0 0); - --muted-foreground: oklch(0.556 0 0); - --accent: oklch(0.97 0 0); - --accent-foreground: oklch(0.205 0 0); - --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); - --chart-1: oklch(0.646 0.222 41.116); - --chart-2: oklch(0.6 0.118 184.704); - --chart-3: oklch(0.398 0.07 227.392); - --chart-4: oklch(0.828 0.189 84.429); - --chart-5: oklch(0.769 0.188 70.08); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); -} - -.dark { - --background: oklch(0.145 0 0); - --foreground: oklch(0.985 0 0); - --card: oklch(0.205 0 0); - --card-foreground: oklch(0.985 0 0); - --popover: oklch(0.205 0 0); - --popover-foreground: oklch(0.985 0 0); - --primary: oklch(0.922 0 0); - --primary-foreground: oklch(0.205 0 0); - --secondary: oklch(0.269 0 0); - --secondary-foreground: oklch(0.985 0 0); - --muted: oklch(0.269 0 0); - --muted-foreground: oklch(0.708 0 0); - --accent: oklch(0.269 0 0); - --accent-foreground: oklch(0.985 0 0); - --destructive: oklch(0.704 0.191 22.216); - --border: oklch(1 0 0 / 10%); - --input: oklch(1 0 0 / 15%); - --ring: oklch(0.556 0 0); - --chart-1: oklch(0.488 0.243 264.376); - --chart-2: oklch(0.696 0.17 162.48); - --chart-3: oklch(0.769 0.188 70.08); - --chart-4: oklch(0.627 0.265 303.9); - --chart-5: oklch(0.645 0.246 16.439); - --sidebar: oklch(0.205 0 0); - --sidebar-foreground: oklch(0.985 0 0); - --sidebar-primary: oklch(0.488 0.243 264.376); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.269 0 0); - --sidebar-accent-foreground: oklch(0.985 0 0); - --sidebar-border: oklch(1 0 0 / 10%); - --sidebar-ring: oklch(0.556 0 0); -} - @layer base { * { @apply border-border outline-ring/50; diff --git a/src/main.tsx b/src/main.tsx index 02055fd..9d47849 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,6 @@ import React from "react"; import ReactDOM from "react-dom/client"; -import App from "./App"; +import App from "./pages/patcher"; import "./index.css"; ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render( diff --git a/src/App.tsx b/src/pages/config.tsx similarity index 95% rename from src/App.tsx rename to src/pages/config.tsx index 86355c3..e1e731d 100644 --- a/src/App.tsx +++ b/src/pages/config.tsx @@ -2,7 +2,7 @@ import { z } from "zod"; import { PropsWithChildren, useCallback, useEffect, useState } from "react"; import { useForm } from "@tanstack/react-form"; import { invoke } from "@tauri-apps/api/core"; -import { CornerUpLeftIcon, Loader2Icon, SaveIcon, XIcon } from "lucide-react"; +import { CornerUpLeftIcon, Loader2Icon, SaveIcon } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Field, @@ -32,6 +32,7 @@ import { AccordionItem, AccordionTrigger, } from "@/components/ui/accordion"; +import DragBar from "@/components/drag-bar"; const config = z.object({ $schema: z.string(), @@ -73,16 +74,6 @@ const config = z.object({ view_distance: z.number().min(0).max(100), show_sales_text: z.boolean(), }), - - // object_culling: z.boolean(), // Should be 0 - // visibility: z.number().int(), // Should be 3 - // software_cursor: z.boolean(), // Always false - // is_save_id: z.number().int(), - // save_id: z.number().int(), - // preloading_delay_time: z.number().int(), // Default seems to be 20 - // decompressed_texture: z.boolean(), // Should always be true - // use_default_ime: z.boolean(), // Should be false - // software_tiling: z.number().int(), // Should always be 0 for auto tiling }); type Config = z.infer; @@ -98,7 +89,7 @@ const Category = ({ return (
@@ -112,6 +103,7 @@ const Category = ({ ); }; + const Settings = ({ resolutions, initialConfig, @@ -156,7 +148,12 @@ const Settings = ({ }} className="flex flex-col h-screen" > - + -
- Sound Volume settings @@ -511,7 +506,6 @@ const Settings = ({ }} /> - { - await invoke("close"); - }, []); - useEffect(() => { loadResolutions(); }, []); return (
-
-
- -
+ {config ? ( ) : ( diff --git a/src/pages/patcher.tsx b/src/pages/patcher.tsx new file mode 100644 index 0000000..b06744c --- /dev/null +++ b/src/pages/patcher.tsx @@ -0,0 +1,176 @@ +import { P, match } from "ts-pattern"; +import { + createContext, + PropsWithChildren, + useContext, + useEffect, + useState, +} from "react"; +import DragBar from "@/components/drag-bar"; +import { Progress } from "@/components/ui/progress"; +import { listen } from "@tauri-apps/api/event"; +import Background from "@/assets/background.jpg"; + +const prettyBytes = (num: number, precision = 3, addSpace = true) => { + const UNITS = ["B", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]; + if (Math.abs(num) < 1) return num + (addSpace ? " " : "") + UNITS[0]; + const exponent = Math.min( + Math.floor(Math.log10(num < 0 ? -num : num) / 3), + UNITS.length - 1 + ); + const n = Number( + ((num < 0 ? -num : num) / 1000 ** exponent).toPrecision(precision) + ); + return (num < 0 ? "-" : "") + n + (addSpace ? " " : "") + UNITS[exponent]; +}; + +const PatcherProgress = () => { + const state = useStatus(); + + return ( +
+ {match(state) + .with({ error: P.not(P.nullish) }, ({ error }) => ( +
{JSON.stringify(error)}
+ )) + .with({ status: Status.Awaiting }, () => ( +
Starting patcher...
+ )) + .with({ status: Status.Fetching }, () => ( +
Fetching checksums...
+ )) + .with({ status: Status.Checksumming }, () => ( +
Checking local files...
+ )) + .with({ status: Status.Verifying }, () => ( +
Verifying files...
+ )) + .with({ status: Status.Updating }, ({ download }) => ( +
+
Updating files...
+ {download && ( +
+ + + {download.file}: {prettyBytes(download.downloaded)}/ + {prettyBytes(download.total)} + +
+ )} +
+ )) + .otherwise(() => ( +
Ready!
+ ))} +
+ ); +}; + +enum Status { + Awaiting, + Fetching, + Checksumming, + Verifying, + Updating, + Ready, +} + +type Download = { + file: string; + downloaded: number; + total: number; + percent: number; +}; + +const stateContext = createContext<{ + status: Status; + error: string | null; + download: Download | null; +}>({ + status: Status.Awaiting, + error: null, + download: null, +}); + +const StateProvider = ({ children }: PropsWithChildren) => { + const [status, setStatus] = useState(Status.Awaiting); + const [error, setError] = useState(null); + const [download, setDownload] = useState(null); + + useEffect(() => { + const setupListeners = async () => { + const statusUnlisten = await listen("status", (event) => { + setStatus(event.payload); + }); + const errorUnlisten = await listen("error", (event) => { + setError(event.payload); + }); + const downloadUnlisten = await listen("download", (event) => { + setDownload(event.payload); + }); + + return () => { + statusUnlisten(); + errorUnlisten(); + downloadUnlisten(); + }; + }; + + let cleanup: () => void; + setupListeners().then((cleanupFn) => { + cleanup = cleanupFn; + }); + + return () => { + if (cleanup) cleanup(); + }; + }, []); + + return ( + + {children} + + ); +}; + +const useStatus = () => useContext(stateContext); + +const Patcher = () => { + const { status, error } = useStatus(); + + return ( + <> + Background +
+ + +
+ + ); +}; + +function App() { + return ( + +
+ + +
+
+ ); +} + +export default App;