diff --git a/bun.lock b/bun.lock index eefc9945..bf77b4ec 100644 --- a/bun.lock +++ b/bun.lock @@ -1,6 +1,6 @@ { "lockfileVersion": 1, - "configVersion": 0, + "configVersion": 1, "workspaces": { "": { "name": "liquidlauncher", @@ -79,85 +79,85 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.31", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", "@jridgewell/sourcemap-codec": "^1.4.14" } }, "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw=="], - "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.55.1", "", { "os": "android", "cpu": "arm" }, "sha512-9R0DM/ykwfGIlNu6+2U09ga0WXeZ9MRC2Ter8jnz8415VbuIykVuc6bhdrbORFZANDmTDvq26mJrEVTl8TdnDg=="], + "@rollup/rollup-android-arm-eabi": ["@rollup/rollup-android-arm-eabi@4.57.1", "", { "os": "android", "cpu": "arm" }, "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg=="], - "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.55.1", "", { "os": "android", "cpu": "arm64" }, "sha512-eFZCb1YUqhTysgW3sj/55du5cG57S7UTNtdMjCW7LwVcj3dTTcowCsC8p7uBdzKsZYa8J7IDE8lhMI+HX1vQvg=="], + "@rollup/rollup-android-arm64": ["@rollup/rollup-android-arm64@4.57.1", "", { "os": "android", "cpu": "arm64" }, "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w=="], - "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.55.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-p3grE2PHcQm2e8PSGZdzIhCKbMCw/xi9XvMPErPhwO17vxtvCN5FEA2mSLgmKlCjHGMQTP6phuQTYWUnKewwGg=="], + "@rollup/rollup-darwin-arm64": ["@rollup/rollup-darwin-arm64@4.57.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg=="], - "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.55.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-rDUjG25C9qoTm+e02Esi+aqTKSBYwVTaoS1wxcN47/Luqef57Vgp96xNANwt5npq9GDxsH7kXxNkJVEsWEOEaQ=="], + "@rollup/rollup-darwin-x64": ["@rollup/rollup-darwin-x64@4.57.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w=="], - "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.55.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-+JiU7Jbp5cdxekIgdte0jfcu5oqw4GCKr6i3PJTlXTCU5H5Fvtkpbs4XJHRmWNXF+hKmn4v7ogI5OQPaupJgOg=="], + "@rollup/rollup-freebsd-arm64": ["@rollup/rollup-freebsd-arm64@4.57.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug=="], - "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.55.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-V5xC1tOVWtLLmr3YUk2f6EJK4qksksOYiz/TCsFHu/R+woubcLWdC9nZQmwjOAbmExBIVKsm1/wKmEy4z4u4Bw=="], + "@rollup/rollup-freebsd-x64": ["@rollup/rollup-freebsd-x64@4.57.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q=="], - "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-Rn3n+FUk2J5VWx+ywrG/HGPTD9jXNbicRtTM11e/uorplArnXZYsVifnPPqNNP5BsO3roI4n8332ukpY/zN7rQ=="], + "@rollup/rollup-linux-arm-gnueabihf": ["@rollup/rollup-linux-arm-gnueabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw=="], - "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.55.1", "", { "os": "linux", "cpu": "arm" }, "sha512-grPNWydeKtc1aEdrJDWk4opD7nFtQbMmV7769hiAaYyUKCT1faPRm2av8CX1YJsZ4TLAZcg9gTR1KvEzoLjXkg=="], + "@rollup/rollup-linux-arm-musleabihf": ["@rollup/rollup-linux-arm-musleabihf@4.57.1", "", { "os": "linux", "cpu": "arm" }, "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw=="], - "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-a59mwd1k6x8tXKcUxSyISiquLwB5pX+fJW9TkWU46lCqD/GRDe9uDN31jrMmVP3feI3mhAdvcCClhV8V5MhJFQ=="], + "@rollup/rollup-linux-arm64-gnu": ["@rollup/rollup-linux-arm64-gnu@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g=="], - "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.55.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-puS1MEgWX5GsHSoiAsF0TYrpomdvkaXm0CofIMG5uVkP6IBV+ZO9xhC5YEN49nsgYo1DuuMquF9+7EDBVYu4uA=="], + "@rollup/rollup-linux-arm64-musl": ["@rollup/rollup-linux-arm64-musl@4.57.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q=="], - "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-r3Wv40in+lTsULSb6nnoudVbARdOwb2u5fpeoOAZjFLznp6tDU8kd+GTHmJoqZ9lt6/Sys33KdIHUaQihFcu7g=="], + "@rollup/rollup-linux-loong64-gnu": ["@rollup/rollup-linux-loong64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA=="], - "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-MR8c0+UxAlB22Fq4R+aQSPBayvYa3+9DrwG/i1TKQXFYEaoW3B5b/rkSRIypcZDdWjWnpcvxbNaAJDcSbJU3Lw=="], + "@rollup/rollup-linux-loong64-musl": ["@rollup/rollup-linux-loong64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw=="], - "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-3KhoECe1BRlSYpMTeVrD4sh2Pw2xgt4jzNSZIIPLFEsnQn9gAnZagW9+VqDqAHgm1Xc77LzJOo2LdigS5qZ+gw=="], + "@rollup/rollup-linux-ppc64-gnu": ["@rollup/rollup-linux-ppc64-gnu@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w=="], - "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.55.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-ziR1OuZx0vdYZZ30vueNZTg73alF59DicYrPViG0NEgDVN8/Jl87zkAPu4u6VjZST2llgEUjaiNl9JM6HH1Vdw=="], + "@rollup/rollup-linux-ppc64-musl": ["@rollup/rollup-linux-ppc64-musl@4.57.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw=="], - "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-uW0Y12ih2XJRERZ4jAfKamTyIHVMPQnTZcQjme2HMVDAHY4amf5u414OqNYC+x+LzRdRcnIG1YodLrrtA8xsxw=="], + "@rollup/rollup-linux-riscv64-gnu": ["@rollup/rollup-linux-riscv64-gnu@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A=="], - "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.55.1", "", { "os": "linux", "cpu": "none" }, "sha512-u9yZ0jUkOED1BFrqu3BwMQoixvGHGZ+JhJNkNKY/hyoEgOwlqKb62qu+7UjbPSHYjiVy8kKJHvXKv5coH4wDeg=="], + "@rollup/rollup-linux-riscv64-musl": ["@rollup/rollup-linux-riscv64-musl@4.57.1", "", { "os": "linux", "cpu": "none" }, "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw=="], - "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.55.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/0PenBCmqM4ZUd0190j7J0UsQ/1nsi735iPRakO8iPciE7BQ495Y6msPzaOmvx0/pn+eJVVlZrNrSh4WSYLxNg=="], + "@rollup/rollup-linux-s390x-gnu": ["@rollup/rollup-linux-s390x-gnu@4.57.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg=="], - "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-a8G4wiQxQG2BAvo+gU6XrReRRqj+pLS2NGXKm8io19goR+K8lw269eTrPkSdDTALwMmJp4th2Uh0D8J9bEV1vg=="], + "@rollup/rollup-linux-x64-gnu": ["@rollup/rollup-linux-x64-gnu@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg=="], - "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.55.1", "", { "os": "linux", "cpu": "x64" }, "sha512-bD+zjpFrMpP/hqkfEcnjXWHMw5BIghGisOKPj+2NaNDuVT+8Ds4mPf3XcPHuat1tz89WRL+1wbcxKY3WSbiT7w=="], + "@rollup/rollup-linux-x64-musl": ["@rollup/rollup-linux-x64-musl@4.57.1", "", { "os": "linux", "cpu": "x64" }, "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw=="], - "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.55.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-eLXw0dOiqE4QmvikfQ6yjgkg/xDM+MdU9YJuP4ySTibXU0oAvnEWXt7UDJmD4UkYialMfOGFPJnIHSe/kdzPxg=="], + "@rollup/rollup-openbsd-x64": ["@rollup/rollup-openbsd-x64@4.57.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw=="], - "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.55.1", "", { "os": "none", "cpu": "arm64" }, "sha512-xzm44KgEP11te3S2HCSyYf5zIzWmx3n8HDCc7EE59+lTcswEWNpvMLfd9uJvVX8LCg9QWG67Xt75AuHn4vgsXw=="], + "@rollup/rollup-openharmony-arm64": ["@rollup/rollup-openharmony-arm64@4.57.1", "", { "os": "none", "cpu": "arm64" }, "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ=="], - "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.55.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-yR6Bl3tMC/gBok5cz/Qi0xYnVbIxGx5Fcf/ca0eB6/6JwOY+SRUcJfI0OpeTpPls7f194as62thCt/2BjxYN8g=="], + "@rollup/rollup-win32-arm64-msvc": ["@rollup/rollup-win32-arm64-msvc@4.57.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ=="], - "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.55.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-3fZBidchE0eY0oFZBnekYCfg+5wAB0mbpCBuofh5mZuzIU/4jIVkbESmd2dOsFNS78b53CYv3OAtwqkZZmU5nA=="], + "@rollup/rollup-win32-ia32-msvc": ["@rollup/rollup-win32-ia32-msvc@4.57.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew=="], - "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-xGGY5pXj69IxKb4yv/POoocPy/qmEGhimy/FoTpTSVju3FYXUQQMFCaZZXJVidsmGxRioZAwpThl/4zX41gRKg=="], + "@rollup/rollup-win32-x64-gnu": ["@rollup/rollup-win32-x64-gnu@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ=="], - "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.55.1", "", { "os": "win32", "cpu": "x64" }, "sha512-SPEpaL6DX4rmcXtnhdrQYgzQ5W2uW3SCJch88lB2zImhJRhIIK44fkUrgIV/Q8yUNfw5oyZ5vkeQsZLhCb06lw=="], + "@rollup/rollup-win32-x64-msvc": ["@rollup/rollup-win32-x64-msvc@4.57.1", "", { "os": "win32", "cpu": "x64" }, "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA=="], "@sveltejs/vite-plugin-svelte": ["@sveltejs/vite-plugin-svelte@3.1.2", "", { "dependencies": { "@sveltejs/vite-plugin-svelte-inspector": "^2.1.0", "debug": "^4.3.4", "deepmerge": "^4.3.1", "kleur": "^4.1.5", "magic-string": "^0.30.10", "svelte-hmr": "^0.16.0", "vitefu": "^0.2.5" }, "peerDependencies": { "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.0" } }, "sha512-Txsm1tJvtiYeLUVRNqxZGKR/mI+CzuIQuc2gn+YCs9rMTowpNZ2Nqt53JdL8KF9bLhAf2ruR/dr9eZCwdTriRA=="], "@sveltejs/vite-plugin-svelte-inspector": ["@sveltejs/vite-plugin-svelte-inspector@2.1.0", "", { "dependencies": { "debug": "^4.3.4" }, "peerDependencies": { "@sveltejs/vite-plugin-svelte": "^3.0.0", "svelte": "^4.0.0 || ^5.0.0-next.0", "vite": "^5.0.0" } }, "sha512-9QX28IymvBlSCqsCll5t0kQVxipsfhFFL+L2t3nTWfXnddYwxBuAEtTtlaVQpRz9c37BhJjltSeY4AJSC03SSg=="], - "@tauri-apps/api": ["@tauri-apps/api@2.9.1", "", {}, "sha512-IGlhP6EivjXHepbBic618GOmiWe4URJiIeZFlB7x3czM0yDHHYviH1Xvoiv4FefdkQtn6v7TuwWCRfOGdnVUGw=="], + "@tauri-apps/api": ["@tauri-apps/api@2.10.1", "", {}, "sha512-hKL/jWf293UDSUN09rR69hrToyIXBb8CjGaWC7gfinvnQrBVvnLr08FeFi38gxtugAVyVcTa5/FD/Xnkb1siBw=="], - "@tauri-apps/cli": ["@tauri-apps/cli@2.9.6", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.9.6", "@tauri-apps/cli-darwin-x64": "2.9.6", "@tauri-apps/cli-linux-arm-gnueabihf": "2.9.6", "@tauri-apps/cli-linux-arm64-gnu": "2.9.6", "@tauri-apps/cli-linux-arm64-musl": "2.9.6", "@tauri-apps/cli-linux-riscv64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-gnu": "2.9.6", "@tauri-apps/cli-linux-x64-musl": "2.9.6", "@tauri-apps/cli-win32-arm64-msvc": "2.9.6", "@tauri-apps/cli-win32-ia32-msvc": "2.9.6", "@tauri-apps/cli-win32-x64-msvc": "2.9.6" }, "bin": { "tauri": "tauri.js" } }, "sha512-3xDdXL5omQ3sPfBfdC8fCtDKcnyV7OqyzQgfyT5P3+zY6lcPqIYKQBvUasNvppi21RSdfhy44ttvJmftb0PCDw=="], + "@tauri-apps/cli": ["@tauri-apps/cli@2.10.0", "", { "optionalDependencies": { "@tauri-apps/cli-darwin-arm64": "2.10.0", "@tauri-apps/cli-darwin-x64": "2.10.0", "@tauri-apps/cli-linux-arm-gnueabihf": "2.10.0", "@tauri-apps/cli-linux-arm64-gnu": "2.10.0", "@tauri-apps/cli-linux-arm64-musl": "2.10.0", "@tauri-apps/cli-linux-riscv64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-gnu": "2.10.0", "@tauri-apps/cli-linux-x64-musl": "2.10.0", "@tauri-apps/cli-win32-arm64-msvc": "2.10.0", "@tauri-apps/cli-win32-ia32-msvc": "2.10.0", "@tauri-apps/cli-win32-x64-msvc": "2.10.0" }, "bin": { "tauri": "tauri.js" } }, "sha512-ZwT0T+7bw4+DPCSWzmviwq5XbXlM0cNoleDKOYPFYqcZqeKY31KlpoMW/MOON/tOFBPgi31a2v3w9gliqwL2+Q=="], - "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.9.6", "", { "os": "darwin", "cpu": "arm64" }, "sha512-gf5no6N9FCk1qMrti4lfwP77JHP5haASZgVbBgpZG7BUepB3fhiLCXGUK8LvuOjP36HivXewjg72LTnPDScnQQ=="], + "@tauri-apps/cli-darwin-arm64": ["@tauri-apps/cli-darwin-arm64@2.10.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-avqHD4HRjrMamE/7R/kzJPcAJnZs0IIS+1nkDP5b+TNBn3py7N2aIo9LIpy+VQq0AkN8G5dDpZtOOBkmWt/zjA=="], - "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.9.6", "", { "os": "darwin", "cpu": "x64" }, "sha512-oWh74WmqbERwwrwcueJyY6HYhgCksUc6NT7WKeXyrlY/FPmNgdyQAgcLuTSkhRFuQ6zh4Np1HZpOqCTpeZBDcw=="], + "@tauri-apps/cli-darwin-x64": ["@tauri-apps/cli-darwin-x64@2.10.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-keDmlvJRStzVFjZTd0xYkBONLtgBC9eMTpmXnBXzsHuawV2q9PvDo2x6D5mhuoMVrJ9QWjgaPKBBCFks4dK71Q=="], - "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.9.6", "", { "os": "linux", "cpu": "arm" }, "sha512-/zde3bFroFsNXOHN204DC2qUxAcAanUjVXXSdEGmhwMUZeAQalNj5cz2Qli2elsRjKN/hVbZOJj0gQ5zaYUjSg=="], + "@tauri-apps/cli-linux-arm-gnueabihf": ["@tauri-apps/cli-linux-arm-gnueabihf@2.10.0", "", { "os": "linux", "cpu": "arm" }, "sha512-e5u0VfLZsMAC9iHaOEANumgl6lfnJx0Dtjkd8IJpysZ8jp0tJ6wrIkto2OzQgzcYyRCKgX72aKE0PFgZputA8g=="], - "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-pvbljdhp9VOo4RnID5ywSxgBs7qiylTPlK56cTk7InR3kYSTJKYMqv/4Q/4rGo/mG8cVppesKIeBMH42fw6wjg=="], + "@tauri-apps/cli-linux-arm64-gnu": ["@tauri-apps/cli-linux-arm64-gnu@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-YrYYk2dfmBs5m+OIMCrb+JH/oo+4FtlpcrTCgiFYc7vcs6m3QDd1TTyWu0u01ewsCtK2kOdluhr/zKku+KP7HA=="], - "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.9.6", "", { "os": "linux", "cpu": "arm64" }, "sha512-02TKUndpodXBCR0oP//6dZWGYcc22Upf2eP27NvC6z0DIqvkBBFziQUcvi2n6SrwTRL0yGgQjkm9K5NIn8s6jw=="], + "@tauri-apps/cli-linux-arm64-musl": ["@tauri-apps/cli-linux-arm64-musl@2.10.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-GUoPdVJmrJRIXFfW3Rkt+eGK9ygOdyISACZfC/bCSfOnGt8kNdQIQr5WRH9QUaTVFIwxMlQyV3m+yXYP+xhSVA=="], - "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.9.6", "", { "os": "linux", "cpu": "none" }, "sha512-fmp1hnulbqzl1GkXl4aTX9fV+ubHw2LqlLH1PE3BxZ11EQk+l/TmiEongjnxF0ie4kV8DQfDNJ1KGiIdWe1GvQ=="], + "@tauri-apps/cli-linux-riscv64-gnu": ["@tauri-apps/cli-linux-riscv64-gnu@2.10.0", "", { "os": "linux", "cpu": "none" }, "sha512-JO7s3TlSxshwsoKNCDkyvsx5gw2QAs/Y2GbR5UE2d5kkU138ATKoPOtxn8G1fFT1aDW4LH0rYAAfBpGkDyJJnw=="], - "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-vY0le8ad2KaV1PJr+jCd8fUF9VOjwwQP/uBuTJvhvKTloEwxYA/kAjKK9OpIslGA9m/zcnSo74czI6bBrm2sYA=="], + "@tauri-apps/cli-linux-x64-gnu": ["@tauri-apps/cli-linux-x64-gnu@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-Uvh4SUUp4A6DVRSMWjelww0GnZI3PlVy7VS+DRF5napKuIehVjGl9XD0uKoCoxwAQBLctvipyEK+pDXpJeoHng=="], - "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.9.6", "", { "os": "linux", "cpu": "x64" }, "sha512-TOEuB8YCFZTWVDzsO2yW0+zGcoMiPPwcUgdnW1ODnmgfwccpnihDRoks+ABT1e3fHb1ol8QQWsHSCovb3o2ENQ=="], + "@tauri-apps/cli-linux-x64-musl": ["@tauri-apps/cli-linux-x64-musl@2.10.0", "", { "os": "linux", "cpu": "x64" }, "sha512-AP0KRK6bJuTpQ8kMNWvhIpKUkQJfcPFeba7QshOQZjJ8wOS6emwTN4K5g/d3AbCMo0RRdnZWwu67MlmtJyxC1Q=="], - "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.9.6", "", { "os": "win32", "cpu": "arm64" }, "sha512-ujmDGMRc4qRLAnj8nNG26Rlz9klJ0I0jmZs2BPpmNNf0gM/rcVHhqbEkAaHPTBVIrtUdf7bGvQAD2pyIiUrBHQ=="], + "@tauri-apps/cli-win32-arm64-msvc": ["@tauri-apps/cli-win32-arm64-msvc@2.10.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-97DXVU3dJystrq7W41IX+82JEorLNY+3+ECYxvXWqkq7DBN6FsA08x/EFGE8N/b0LTOui9X2dvpGGoeZKKV08g=="], - "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.9.6", "", { "os": "win32", "cpu": "ia32" }, "sha512-S4pT0yAJgFX8QRCyKA1iKjZ9Q/oPjCZf66A/VlG5Yw54Nnr88J1uBpmenINbXxzyhduWrIXBaUbEY1K80ZbpMg=="], + "@tauri-apps/cli-win32-ia32-msvc": ["@tauri-apps/cli-win32-ia32-msvc@2.10.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-EHyQ1iwrWy1CwMalEm9z2a6L5isQ121pe7FcA2xe4VWMJp+GHSDDGvbTv/OPdkt2Lyr7DAZBpZHM6nvlHXEc4A=="], - "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.9.6", "", { "os": "win32", "cpu": "x64" }, "sha512-ldWuWSSkWbKOPjQMJoYVj9wLHcOniv7diyI5UAJ4XsBdtaFB0pKHQsqw/ItUma0VXGC7vB4E9fZjivmxur60aw=="], + "@tauri-apps/cli-win32-x64-msvc": ["@tauri-apps/cli-win32-x64-msvc@2.10.0", "", { "os": "win32", "cpu": "x64" }, "sha512-NTpyQxkpzGmU6ceWBTY2xRIEaS0ZLbVx1HE1zTA3TY/pV3+cPoPPOs+7YScr4IMzXMtOw7tLw5LEXo5oIG3qaQ=="], "@tauri-apps/plugin-clipboard-manager": ["@tauri-apps/plugin-clipboard-manager@2.3.2", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-CUlb5Hqi2oZbcZf4VUyUH53XWPPdtpw43EUpCza5HWZJwxEoDowFzNUDt1tRUXA8Uq+XPn17Ysfptip33sG4eQ=="], @@ -167,7 +167,7 @@ "@tauri-apps/plugin-process": ["@tauri-apps/plugin-process@2.3.1", "", { "dependencies": { "@tauri-apps/api": "^2.8.0" } }, "sha512-nCa4fGVaDL/B9ai03VyPOjfAHRHSBz5v6F/ObsB73r/dA3MHHhZtldaDMIc0V/pnUw9ehzr2iEG+XkSEyC0JJA=="], - "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.9.0", "", { "dependencies": { "@tauri-apps/api": "^2.6.0" } }, "sha512-j++sgY8XpeDvzImTrzWA08OqqGqgkNyxczLD7FjNJJx/uXxMZFz5nDcfkyoI/rCjYuj2101Tci/r/HFmOmoxCg=="], + "@tauri-apps/plugin-updater": ["@tauri-apps/plugin-updater@2.10.0", "", { "dependencies": { "@tauri-apps/api": "^2.10.1" } }, "sha512-ljN8jPlnT0aSn8ecYhuBib84alxfMx6Hc8vJSKMJyzGbTPFZAC44T2I1QNFZssgWKrAlofvJqCC6Rr472JWfkQ=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -213,7 +213,7 @@ "postcss": ["postcss@8.5.6", "", { "dependencies": { "nanoid": "^3.3.11", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" } }, "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg=="], - "rollup": ["rollup@4.55.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.55.1", "@rollup/rollup-android-arm64": "4.55.1", "@rollup/rollup-darwin-arm64": "4.55.1", "@rollup/rollup-darwin-x64": "4.55.1", "@rollup/rollup-freebsd-arm64": "4.55.1", "@rollup/rollup-freebsd-x64": "4.55.1", "@rollup/rollup-linux-arm-gnueabihf": "4.55.1", "@rollup/rollup-linux-arm-musleabihf": "4.55.1", "@rollup/rollup-linux-arm64-gnu": "4.55.1", "@rollup/rollup-linux-arm64-musl": "4.55.1", "@rollup/rollup-linux-loong64-gnu": "4.55.1", "@rollup/rollup-linux-loong64-musl": "4.55.1", "@rollup/rollup-linux-ppc64-gnu": "4.55.1", "@rollup/rollup-linux-ppc64-musl": "4.55.1", "@rollup/rollup-linux-riscv64-gnu": "4.55.1", "@rollup/rollup-linux-riscv64-musl": "4.55.1", "@rollup/rollup-linux-s390x-gnu": "4.55.1", "@rollup/rollup-linux-x64-gnu": "4.55.1", "@rollup/rollup-linux-x64-musl": "4.55.1", "@rollup/rollup-openbsd-x64": "4.55.1", "@rollup/rollup-openharmony-arm64": "4.55.1", "@rollup/rollup-win32-arm64-msvc": "4.55.1", "@rollup/rollup-win32-ia32-msvc": "4.55.1", "@rollup/rollup-win32-x64-gnu": "4.55.1", "@rollup/rollup-win32-x64-msvc": "4.55.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-wDv/Ht1BNHB4upNbK74s9usvl7hObDnvVzknxqY/E/O3X6rW1U1rV1aENEfJ54eFZDTNo7zv1f5N4edCluH7+A=="], + "rollup": ["rollup@4.57.1", "", { "dependencies": { "@types/estree": "1.0.8" }, "optionalDependencies": { "@rollup/rollup-android-arm-eabi": "4.57.1", "@rollup/rollup-android-arm64": "4.57.1", "@rollup/rollup-darwin-arm64": "4.57.1", "@rollup/rollup-darwin-x64": "4.57.1", "@rollup/rollup-freebsd-arm64": "4.57.1", "@rollup/rollup-freebsd-x64": "4.57.1", "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", "@rollup/rollup-linux-arm-musleabihf": "4.57.1", "@rollup/rollup-linux-arm64-gnu": "4.57.1", "@rollup/rollup-linux-arm64-musl": "4.57.1", "@rollup/rollup-linux-loong64-gnu": "4.57.1", "@rollup/rollup-linux-loong64-musl": "4.57.1", "@rollup/rollup-linux-ppc64-gnu": "4.57.1", "@rollup/rollup-linux-ppc64-musl": "4.57.1", "@rollup/rollup-linux-riscv64-gnu": "4.57.1", "@rollup/rollup-linux-riscv64-musl": "4.57.1", "@rollup/rollup-linux-s390x-gnu": "4.57.1", "@rollup/rollup-linux-x64-gnu": "4.57.1", "@rollup/rollup-linux-x64-musl": "4.57.1", "@rollup/rollup-openbsd-x64": "4.57.1", "@rollup/rollup-openharmony-arm64": "4.57.1", "@rollup/rollup-win32-arm64-msvc": "4.57.1", "@rollup/rollup-win32-ia32-msvc": "4.57.1", "@rollup/rollup-win32-x64-gnu": "4.57.1", "@rollup/rollup-win32-x64-msvc": "4.57.1", "fsevents": "~2.3.2" }, "bin": { "rollup": "dist/bin/rollup" } }, "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A=="], "source-map-js": ["source-map-js@1.2.1", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], diff --git a/gh_assets/auto-update-setting.png b/gh_assets/auto-update-setting.png new file mode 100644 index 00000000..b67fe579 Binary files /dev/null and b/gh_assets/auto-update-setting.png differ diff --git a/gh_assets/modrinth-search.png b/gh_assets/modrinth-search.png new file mode 100644 index 00000000..da21ba60 Binary files /dev/null and b/gh_assets/modrinth-search.png differ diff --git a/gh_assets/modrinth-sync.png b/gh_assets/modrinth-sync.png new file mode 100644 index 00000000..c4644771 Binary files /dev/null and b/gh_assets/modrinth-sync.png differ diff --git a/gh_assets/modrinth-updates.png b/gh_assets/modrinth-updates.png new file mode 100644 index 00000000..6b4b9883 Binary files /dev/null and b/gh_assets/modrinth-updates.png differ diff --git a/public/img/icon/icon-download.svg b/public/img/icon/icon-download.svg new file mode 100644 index 00000000..e69e2440 --- /dev/null +++ b/public/img/icon/icon-download.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/public/img/icon/icon-refresh.svg b/public/img/icon/icon-refresh.svg new file mode 100644 index 00000000..accba17b --- /dev/null +++ b/public/img/icon/icon-refresh.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/public/img/icon/icon-trash.svg b/public/img/icon/icon-trash.svg new file mode 100644 index 00000000..cd635304 --- /dev/null +++ b/public/img/icon/icon-trash.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 666ee568..9da867dc 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -337,7 +337,7 @@ dependencies = [ "serde", "serde_json", "signature", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "uuid", @@ -352,7 +352,7 @@ dependencies = [ "byteorder", "indexmap 2.13.0", "simdnbt", - "thiserror 2.0.17", + "thiserror 2.0.18", "tracing", "uuid", ] @@ -608,7 +608,7 @@ dependencies = [ "semver", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -623,9 +623,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.52" +version = "1.2.53" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd4932aefd12402b36c60956a4fe0035421f544799057659ff86f923657aada3" +checksum = "755d2fce177175ffca841e9a06afdb2c4ab0f593d53b4dee48147dfaade85932" dependencies = [ "find-msvc-tools", "jobserver", @@ -736,9 +736,9 @@ dependencies = [ [[package]] name = "cmov" -version = "0.4.4" +version = "0.5.0-pre.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "360a5d5b750cd7fb97d5ead6e6e0ef0b288d3c2464a189f04f38670e268842ed" +checksum = "5417da527aa9bf6a1e10a781231effd1edd3ee82f27d5f8529ac9b279babce96" [[package]] name = "combine" @@ -931,9 +931,9 @@ dependencies = [ [[package]] name = "crypto-common" -version = "0.2.0-rc.9" +version = "0.2.0-rc.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b8986f836d4aeb30ccf4c9d3bd562fd716074cfd7fc4a2948359fbd21ed809" +checksum = "0f4fc0003068acd7e9cb6659fd956dc4d671f102a06cc115990b9e7bb5745c25" dependencies = [ "hybrid-array", ] @@ -988,9 +988,9 @@ dependencies = [ [[package]] name = "ctutils" -version = "0.3.1" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7c67c81499f542d1dd38c6a2a2fe825f4dd4bca5162965dd2eea0c8119873d3c" +checksum = "758e5ed90be3c8abff7f9a6f37ab7f6d8c59c2210d448b81f3f508134aec84e4" dependencies = [ "cmov", ] @@ -1087,13 +1087,13 @@ dependencies = [ [[package]] name = "digest" -version = "0.11.0-rc.5" +version = "0.11.0-rc.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebf9423bafb058e4142194330c52273c343f8a5beb7176d052f0e73b17dd35b9" +checksum = "2778ee7344f47967d6701053913962accf9bfdb0caa4b6d921b7c4a615f658d0" dependencies = [ "block-buffer 0.11.0", "const-oid", - "crypto-common 0.2.0-rc.9", + "crypto-common 0.2.0-rc.10", ] [[package]] @@ -1395,21 +1395,20 @@ dependencies = [ [[package]] name = "filetime" -version = "0.2.26" +version = "0.2.27" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bc0505cd1b6fa6580283f6bdf70a73fcf4aba1184038c90902b92b3dd0df63ed" +checksum = "f98844151eee8917efc50bd9e8318cb963ae8b297431495d3f758616ea5c57db" dependencies = [ "cfg-if", "libc", "libredox", - "windows-sys 0.60.2", ] [[package]] name = "find-msvc-tools" -version = "0.1.7" +version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f449e6c6c08c865631d4890cfacf252b3d396c9bcc83adb6623cdb02a8336c41" +checksum = "8591b0bcc8a98a64310a2fae1bb3e9b8564dd10e381e6e28010fde8e8e8568db" [[package]] name = "fixedbitset" @@ -2562,6 +2561,7 @@ dependencies = [ "serde", "serde_json", "sha1 0.10.6", + "sha2 0.10.9", "sysinfo", "tauri", "tauri-build", @@ -2570,12 +2570,13 @@ dependencies = [ "tauri-plugin-opener", "tauri-plugin-process", "tauri-plugin-updater", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tokio-tar", "tracing", "tracing-appender", "tracing-subscriber", + "urlencoding", "uuid", "void", ] @@ -2755,7 +2756,7 @@ dependencies = [ "once_cell", "png 0.17.16", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -2946,7 +2947,7 @@ name = "oauth2" version = "5.1.0" source = "git+https://github.com/martijnberger/oauth2-rs.git#8851eb63bbeeb25a64a0fbf842a7f9160a7d9be6" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "chrono", "getrandom 0.3.4", "http", @@ -2956,7 +2957,7 @@ dependencies = [ "serde_json", "serde_path_to_error", "sha2 0.10.9", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", ] @@ -3290,7 +3291,7 @@ dependencies = [ "objc2-osa-kit", "serde", "serde_json", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -3586,9 +3587,9 @@ dependencies = [ [[package]] name = "pkcs8" -version = "0.11.0-rc.8" +version = "0.11.0-rc.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77089aec8290d0b7bb01b671b091095cf1937670725af4fd73d47249f03b12c0" +checksum = "80f8fa6196ede5a9f9ee95b44ca134bddc9b70e8913f9297bd58c909f5889a09" dependencies = [ "der", "spki", @@ -3789,7 +3790,7 @@ dependencies = [ "rustc-hash", "rustls", "socket2", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tracing", "web-time", @@ -3811,7 +3812,7 @@ dependencies = [ "rustls", "rustls-pki-types", "slab", - "thiserror 2.0.17", + "thiserror 2.0.18", "tinyvec", "tracing", "web-time", @@ -4045,7 +4046,7 @@ checksum = "a4e608c6638b9c18977b00b475ac1f28d14e84b27d8d42f70e0bf1e3dec127ac" dependencies = [ "getrandom 0.2.17", "libredox", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -4226,7 +4227,7 @@ dependencies = [ "const-oid", "crypto-bigint", "crypto-primes", - "digest 0.11.0-rc.5", + "digest 0.11.0-rc.6", "pkcs1", "pkcs8", "rand_core 0.10.0-rc-3", @@ -4304,9 +4305,9 @@ dependencies = [ [[package]] name = "rustls-pki-types" -version = "1.13.2" +version = "1.14.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21e6f2ab2928ca4291b86736a8bd920a277a399bba1589409d72154ff87c1282" +checksum = "be040f8b0a225e40375822a563fa9524378b9d63112f53e19ffff34df5d33fdd" dependencies = [ "web-time", "zeroize", @@ -4341,9 +4342,9 @@ checksum = "f87165f0995f63a9fbeea62b64d10b4d9d8e78ec6d7d51fb2125fda7bb36788f" [[package]] name = "rustls-webpki" -version = "0.103.8" +version = "0.103.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffdfa2f5286e2247234e03f680868ac2815974dc39e00ea15adc445d0aafe52" +checksum = "d7df23109aa6c1567d1c575b9952556388da57401e4ace1d15f79eedad0d8f53" dependencies = [ "aws-lc-rs", "ring", @@ -4708,7 +4709,7 @@ checksum = "aa1ae819b9870cadc959a052363de870944a1646932d274a4e270f64bf79e5ef" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-rc.5", + "digest 0.11.0-rc.6", ] [[package]] @@ -4730,7 +4731,7 @@ checksum = "19d43dc0354d88b791216bb5c1bfbb60c0814460cc653ae0ebd71f286d0bd927" dependencies = [ "cfg-if", "cpufeatures", - "digest 0.11.0-rc.5", + "digest 0.11.0-rc.6", ] [[package]] @@ -4764,7 +4765,7 @@ version = "3.0.0-rc.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a0251c9d6468f4ba853b6352b190fb7c1e405087779917c238445eb03993826" dependencies = [ - "digest 0.11.0-rc.5", + "digest 0.11.0-rc.6", "rand_core 0.10.0-rc-3", ] @@ -4787,20 +4788,20 @@ dependencies = [ [[package]] name = "simdnbt" version = "0.9.0" -source = "git+https://github.com/azalea-rs/simdnbt#3bdc57cd04f291fbd557d9f6f3481bea75894c53" +source = "git+https://github.com/azalea-rs/simdnbt#30ef78fa8c03cb13d7d131af5cbd13cae974a466" dependencies = [ "byteorder", "flate2", "serde", "simd_cesu8", "simdnbt-derive", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] name = "simdnbt-derive" version = "0.9.0" -source = "git+https://github.com/azalea-rs/simdnbt#3bdc57cd04f291fbd557d9f6f3481bea75894c53" +source = "git+https://github.com/azalea-rs/simdnbt#30ef78fa8c03cb13d7d131af5cbd13cae974a466" dependencies = [ "proc-macro2", "quote", @@ -5171,7 +5172,7 @@ dependencies = [ "tauri-runtime", "tauri-runtime-wry", "tauri-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", "tokio", "tray-icon", "url", @@ -5223,7 +5224,7 @@ dependencies = [ "sha2 0.10.9", "syn 2.0.114", "tauri-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "url", "uuid", @@ -5273,7 +5274,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.17", + "thiserror 2.0.18", ] [[package]] @@ -5290,7 +5291,7 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-plugin-fs", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", ] @@ -5311,7 +5312,7 @@ dependencies = [ "tauri", "tauri-plugin", "tauri-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", "toml 0.9.11+spec-1.1.0", "url", ] @@ -5332,7 +5333,7 @@ dependencies = [ "serde_json", "tauri", "tauri-plugin", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "windows", "zbus", @@ -5372,7 +5373,7 @@ dependencies = [ "tauri", "tauri-plugin", "tempfile", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tokio", "url", @@ -5398,7 +5399,7 @@ dependencies = [ "serde", "serde_json", "tauri-utils", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "webkit2gtk", "webview2-com", @@ -5462,7 +5463,7 @@ dependencies = [ "serde_json", "serde_with", "swift-rs", - "thiserror 2.0.17", + "thiserror 2.0.18", "toml 0.9.11+spec-1.1.0", "url", "urlpattern", @@ -5516,11 +5517,11 @@ dependencies = [ [[package]] name = "thiserror" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f63587ca0f12b72a0600bcba1d40081f830876000bb46dd2337a3051618f4fc8" +checksum = "4288b5bcbc7920c07a1149a35cf9590a2aa808e0bc1eafaade0b80947865fbc4" dependencies = [ - "thiserror-impl 2.0.17", + "thiserror-impl 2.0.18", ] [[package]] @@ -5536,9 +5537,9 @@ dependencies = [ [[package]] name = "thiserror-impl" -version = "2.0.17" +version = "2.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3ff15c8ecd7de3849db632e14d18d2571fa09dfc5ed93479bc4485c7a517c913" +checksum = "ebc4ee7f67670e9b64d05fa4253e753e016c6c95ff35b89b7941d6b856dec1d5" dependencies = [ "proc-macro2", "quote", @@ -5865,7 +5866,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "786d480bce6247ab75f005b14ae1624ad978d3029d9113f0a22fa1ac773faeaf" dependencies = [ "crossbeam-channel", - "thiserror 2.0.17", + "thiserror 2.0.18", "time", "tracing-subscriber", ] @@ -5938,7 +5939,7 @@ dependencies = [ "once_cell", "png 0.17.16", "serde", - "thiserror 2.0.17", + "thiserror 2.0.18", "windows-sys 0.60.2", ] @@ -6054,6 +6055,12 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "urlencoding" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daf8dba3b7eb870caf1ddeed7bc9d2a049f3cfdfae7cb521b087cc33ae4c49da" + [[package]] name = "urlpattern" version = "0.3.0" @@ -6168,9 +6175,9 @@ checksum = "ccf3ec651a847eb01de73ccad15eb7d99f80485de043efb2f370cd654f4ea44b" [[package]] name = "wasip2" -version = "1.0.1+wasi-0.2.4" +version = "1.0.2+wasi-0.2.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0562428422c63773dad2c345a1882263bbf4d65cf3f42e90921f787ef5ad58e7" +checksum = "9517f9239f02c069db75e65f174b3da828fe5f5b945c4dd26bd25d89c03ebcf5" dependencies = [ "wit-bindgen", ] @@ -6430,7 +6437,7 @@ version = "0.38.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "381336cfffd772377d291702245447a5251a2ffa5bad679c99e61bc48bacbf9c" dependencies = [ - "thiserror 2.0.17", + "thiserror 2.0.18", "windows", "windows-core 0.61.2", ] @@ -6982,9 +6989,9 @@ dependencies = [ [[package]] name = "wit-bindgen" -version = "0.46.0" +version = "0.51.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" +checksum = "d7249219f66ced02969388cf2bb044a09756a083d0fab1e566056b04d9fbcaa5" [[package]] name = "wl-clipboard-rs" @@ -6996,7 +7003,7 @@ dependencies = [ "log", "os_pipe", "rustix", - "thiserror 2.0.17", + "thiserror 2.0.18", "tree_magic_mini", "wayland-backend", "wayland-client", @@ -7044,7 +7051,7 @@ dependencies = [ "sha2 0.10.9", "soup3", "tao-macros", - "thiserror 2.0.17", + "thiserror 2.0.18", "url", "webkit2gtk", "webkit2gtk-sys", @@ -7137,9 +7144,9 @@ dependencies = [ [[package]] name = "zbus" -version = "5.13.1" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17f79257df967b6779afa536788657777a0001f5b42524fcaf5038d4344df40b" +checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" dependencies = [ "async-broadcast", "async-executor", @@ -7172,9 +7179,9 @@ dependencies = [ [[package]] name = "zbus_macros" -version = "5.13.1" +version = "5.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aad23e2d2f91cae771c7af7a630a49e755f1eb74f8a46e9f6d5f7a146edf5a37" +checksum = "0bbd5a90dbe8feee5b13def448427ae314ccd26a49cac47905cafefb9ff846f1" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", @@ -7290,9 +7297,9 @@ dependencies = [ [[package]] name = "zmij" -version = "1.0.14" +version = "1.0.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd8f3f50b848df28f887acb68e41201b5aea6bc8a8dacc00fb40635ff9a72fea" +checksum = "dfcd145825aace48cff44a8844de64bf75feec3080e0aa5cdbde72961ae51a65" [[package]] name = "zstd" @@ -7340,9 +7347,9 @@ dependencies = [ [[package]] name = "zvariant" -version = "5.9.1" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "326aaed414f04fe839777b4c443d4e94c74e7b3621093bd9c5e649ac8aa96543" +checksum = "68b64ef4f40c7951337ddc7023dd03528a57a3ce3408ee9da5e948bd29b232c4" dependencies = [ "endi", "enumflags2", @@ -7354,9 +7361,9 @@ dependencies = [ [[package]] name = "zvariant_derive" -version = "5.9.1" +version = "5.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba44e1f8f4da9e6e2d25d2a60b116ef8b9d0be174a7685e55bb12a99866279a7" +checksum = "484d5d975eb7afb52cc6b929c13d3719a20ad650fea4120e6310de3fc55e415c" dependencies = [ "proc-macro-crate 3.4.0", "proc-macro2", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index a2a307b1..e1860535 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -50,6 +50,7 @@ chrono = { version = "0.4", features = ["serde"] } rand = "0.9" md5 = "0.7.0" sha1 = "0.10" +sha2 = "0.10" base16ct = {version = "0.2.0", features = ["alloc"] } # UI library @@ -69,6 +70,7 @@ azalea-auth = { git = "https://github.com/CCBlueX/azalea.git", branch = "custom_ # Data serialization libs serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" +urlencoding = "2.1" [features] # by default Tauri runs in production mode diff --git a/src-tauri/src/app/gui/commands/client.rs b/src-tauri/src/app/gui/commands/client.rs index b67f0973..23ac8e77 100644 --- a/src-tauri/src/app/gui/commands/client.rs +++ b/src-tauri/src/app/gui/commands/client.rs @@ -19,6 +19,7 @@ use anyhow::anyhow; use backon::{ExponentialBuilder, Retryable}; +use std::collections::HashMap; use std::path::PathBuf; use std::{ sync::{Arc, Mutex}, @@ -28,6 +29,7 @@ use tauri::{Emitter, Window}; use tokio::fs; use tracing::{error, info, warn}; use uuid::Uuid; +use serde_json; use crate::app::client_api::{BlogPost, Branches, Build, Changelog, Client, PaginatedResponse}; use crate::app::client_api::{LoaderMod, ModSource}; @@ -195,11 +197,62 @@ pub(crate) async fn delete_custom_mod( let mod_path = mod_cache_path.join(mod_name); if mod_path.exists() { - fs::remove_file(mod_path) + fs::remove_file(&mod_path) .await .map_err(|e| format!("unable to delete custom mod: {:?}", e))?; } + // Remove Modrinth metadata entry for this mod + let metadata_path = mod_cache_path.join(".modrinth_meta.json"); + if metadata_path.exists() { + match fs::read_to_string(&metadata_path).await { + Ok(content) => { + match serde_json::from_str::>(&content) { + Ok(mut metadata) => { + let filename_without_ext = mod_name.replace(".jar", ""); + let mut found = false; + metadata.retain(|_key, value| { + if let Some(file) = value.get("filename").and_then(|f| f.as_str()) { + if file == mod_name || file.replace(".jar", "") == filename_without_ext { + found = true; + return false; + } + } + true + }); + + if found { + if metadata.is_empty() { + // Remove metadata file if empty + if let Err(e) = fs::remove_file(&metadata_path).await { + warn!("Failed to remove empty Modrinth metadata file: {:?}", e); + } + } else { + // Save updated metadata + match serde_json::to_string(&metadata) { + Ok(json) => { + if let Err(e) = fs::write(&metadata_path, json).await { + warn!("Failed to update Modrinth metadata file: {:?}", e); + } + } + Err(e) => { + warn!("Failed to serialize Modrinth metadata: {:?}", e); + } + } + } + } + } + Err(e) => { + warn!("Failed to parse Modrinth metadata file: {:?}", e); + } + } + } + Err(e) => { + warn!("Failed to read Modrinth metadata file: {:?}", e); + } + } + } + Ok(()) } diff --git a/src-tauri/src/app/gui/commands/mod.rs b/src-tauri/src/app/gui/commands/mod.rs index 4c3783ae..4fed2f73 100644 --- a/src-tauri/src/app/gui/commands/mod.rs +++ b/src-tauri/src/app/gui/commands/mod.rs @@ -21,8 +21,10 @@ pub(crate) mod auth; pub(crate) mod client; pub(crate) mod data; pub(crate) mod system; +pub(crate) mod modrinth; pub(crate) use auth::*; pub(crate) use client::*; pub(crate) use data::*; pub(crate) use system::*; +pub(crate) use modrinth::*; diff --git a/src-tauri/src/app/gui/commands/modrinth.rs b/src-tauri/src/app/gui/commands/modrinth.rs new file mode 100644 index 00000000..e3315bbc --- /dev/null +++ b/src-tauri/src/app/gui/commands/modrinth.rs @@ -0,0 +1,332 @@ +/* + * This file is part of LiquidLauncher (https://github.com/CCBlueX/LiquidLauncher) + * + * Copyright (c) 2015 - 2024 CCBlueX + * + * LiquidLauncher is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidLauncher is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidLauncher. If not, see . + */ + +use crate::app::modrinth::{self, ModrinthProject, ModrinthVersion}; +use crate::LAUNCHER_DIRECTORY; +use sha2::{Sha512, Digest}; +use std::collections::HashMap; +use std::path::PathBuf; +use tokio::fs; +use serde::{Serialize, Deserialize}; +use tracing::info; + +#[derive(Serialize, Deserialize, Clone)] +pub struct InstalledModInfo { + pub project_id: String, + pub version_id: String, + pub filename: String, + pub title: String, +} + +#[derive(Serialize)] +pub struct ModWithUpdate { + pub info: InstalledModInfo, + pub has_update: bool, + pub new_version: Option, +} + +async fn get_metadata_path(branch: &str, mc_version: &str) -> PathBuf { + let data = LAUNCHER_DIRECTORY.data_dir(); + data.join("custom_mods") + .join(format!("{}-{}", branch, mc_version)) + .join(".modrinth_meta.json") +} + +async fn load_metadata(branch: &str, mc_version: &str) -> HashMap { + let path = get_metadata_path(branch, mc_version).await; + if let Ok(content) = fs::read_to_string(&path).await { + serde_json::from_str(&content).unwrap_or_default() + } else { + HashMap::new() + } +} + +async fn save_metadata(branch: &str, mc_version: &str, metadata: &HashMap) { + let path = get_metadata_path(branch, mc_version).await; + if let Ok(json) = serde_json::to_string(metadata) { + let _ = fs::write(&path, json).await; + } +} + +#[tauri::command] +pub(crate) async fn modrinth_search( + query: String, + mc_version: String, + loader: String, +) -> Result, String> { + modrinth::search_mods(&query, &mc_version, &loader) + .await + .map_err(|e| format!("Search failed: {:?}", e)) +} + +#[tauri::command] +pub(crate) async fn modrinth_get_version( + project_id: String, + mc_version: String, + loader: String, +) -> Result, String> { + modrinth::get_compatible_version(&project_id, &mc_version, &loader) + .await + .map_err(|e| format!("Failed to get version: {:?}", e)) +} + +#[tauri::command] +pub(crate) async fn modrinth_install( + project_id: String, + mc_version: String, + loader: String, + branch: String, + title: String, +) -> Result { + let version = modrinth::get_compatible_version(&project_id, &mc_version, &loader) + .await + .map_err(|e| format!("Failed to get version: {:?}", e))? + .ok_or("No compatible version found")?; + + let file = version.files.iter() + .find(|f| f.primary) + .or(version.files.first()) + .ok_or("No files available")?; + + let data = LAUNCHER_DIRECTORY.data_dir(); + let mod_path = data + .join("custom_mods") + .join(format!("{}-{}", branch, mc_version)); + + if !mod_path.exists() { + fs::create_dir_all(&mod_path).await + .map_err(|e| format!("Failed to create directory: {:?}", e))?; + } + + let dest = mod_path.join(&file.filename); + + modrinth::download_mod(file, &dest) + .await + .map_err(|e| format!("Download failed: {:?}", e))?; + + // Save metadata + let mut metadata = load_metadata(&branch, &mc_version).await; + metadata.insert(project_id.clone(), InstalledModInfo { + project_id, + version_id: version.id, + filename: file.filename.clone(), + title, + }); + save_metadata(&branch, &mc_version, &metadata).await; + + Ok(file.filename.clone()) +} + +#[tauri::command] +pub(crate) async fn modrinth_get_installed( + branch: String, + mc_version: String, +) -> Result, String> { + let metadata = load_metadata(&branch, &mc_version).await; + Ok(metadata.into_values().collect()) +} + +#[tauri::command] +pub(crate) async fn modrinth_check_updates( + branch: String, + mc_version: String, + loader: String, +) -> Result, String> { + let metadata = load_metadata(&branch, &mc_version).await; + let mut results = Vec::new(); + + for info in metadata.values() { + let latest = modrinth::get_compatible_version(&info.project_id, &mc_version, &loader) + .await + .ok() + .flatten(); + + let (has_update, new_version) = match latest { + Some(v) if v.id != info.version_id => (true, Some(v.version_number)), + _ => (false, None), + }; + + results.push(ModWithUpdate { + info: info.clone(), + has_update, + new_version, + }); + } + + Ok(results) +} + +#[tauri::command] +pub(crate) async fn modrinth_update_mod( + project_id: String, + mc_version: String, + loader: String, + branch: String, +) -> Result { + let mut metadata = load_metadata(&branch, &mc_version).await; + let old_info = metadata.get(&project_id).cloned() + .ok_or("Mod not found in metadata")?; + + let version = modrinth::get_compatible_version(&project_id, &mc_version, &loader) + .await + .map_err(|e| format!("Failed to get version: {:?}", e))? + .ok_or("No compatible version found")?; + + let file = version.files.iter() + .find(|f| f.primary) + .or(version.files.first()) + .ok_or("No files available")?; + + let data = LAUNCHER_DIRECTORY.data_dir(); + let mod_path = data + .join("custom_mods") + .join(format!("{}-{}", branch, mc_version)); + + let dest = mod_path.join(&file.filename); + + // Download new file FIRST (uses temp file internally for safety) + modrinth::download_mod(file, &dest) + .await + .map_err(|e| format!("Download failed: {:?}", e))?; + + // Only delete old file AFTER successful download + if old_info.filename != file.filename { + let old_path = mod_path.join(&old_info.filename); + if old_path.exists() { + if let Err(e) = fs::remove_file(&old_path).await { + tracing::warn!("Failed to remove old mod file: {:?}", e); + } + } + } + + // Update metadata + metadata.insert(project_id.clone(), InstalledModInfo { + project_id, + version_id: version.id, + filename: file.filename.clone(), + title: old_info.title, + }); + save_metadata(&branch, &mc_version, &metadata).await; + + Ok(file.filename.clone()) +} + +#[tauri::command] +pub(crate) async fn modrinth_uninstall( + project_id: String, + branch: String, + mc_version: String, +) -> Result<(), String> { + let mut metadata = load_metadata(&branch, &mc_version).await; + let info = metadata + .get(&project_id) + .cloned() + .ok_or("Mod not found in metadata")?; + + let data = LAUNCHER_DIRECTORY.data_dir(); + let mod_path = data + .join("custom_mods") + .join(format!("{}-{}", branch, mc_version)); + + let file_path = mod_path.join(&info.filename); + if file_path.exists() { + fs::remove_file(&file_path) + .await + .map_err(|e| format!("Failed to remove mod file: {:?}", e))?; + } + + metadata.remove(&project_id); + save_metadata(&branch, &mc_version, &metadata).await; + + Ok(()) +} + + +/// Scans existing mods and identifies which ones are from Modrinth. +/// Adds them to metadata for update tracking. +#[tauri::command] +pub(crate) async fn modrinth_sync_existing( + branch: String, + mc_version: String, +) -> Result { + let data = LAUNCHER_DIRECTORY.data_dir(); + let mod_path = data + .join("custom_mods") + .join(format!("{}-{}", branch, mc_version)); + + if !mod_path.exists() { + return Ok(0); + } + + let mut metadata = load_metadata(&branch, &mc_version).await; + let mut synced = 0u32; + + let mut entries = fs::read_dir(&mod_path).await + .map_err(|e| format!("Failed to read mods directory: {:?}", e))?; + + while let Some(entry) = entries.next_entry().await + .map_err(|e| format!("Failed to read entry: {:?}", e))? + { + let path = entry.path(); + + // Skip non-jar files and metadata + if path.extension().map(|e| e != "jar").unwrap_or(true) { + continue; + } + + let filename = path.file_name() + .and_then(|n| n.to_str()) + .unwrap_or_default() + .to_string(); + + // Skip if already tracked + if metadata.values().any(|m| m.filename == filename) { + continue; + } + + // Calculate SHA-512 hash + let bytes = match fs::read(&path).await { + Ok(b) => b, + Err(_) => continue, + }; + + let hash = format!("{:x}", Sha512::digest(&bytes)); + + // Look up on Modrinth + if let Ok(Some(version)) = modrinth::get_version_from_hash(&hash).await { + if let Ok(Some(project)) = modrinth::get_project(&version.project_id).await { + info!("Synced existing mod: {} -> {}", filename, project.title); + + metadata.insert(version.project_id.clone(), InstalledModInfo { + project_id: version.project_id, + version_id: version.id, + filename, + title: project.title, + }); + synced += 1; + } + } + } + + if synced > 0 { + save_metadata(&branch, &mc_version, &metadata).await; + } + + Ok(synced) +} diff --git a/src-tauri/src/app/gui/mod.rs b/src-tauri/src/app/gui/mod.rs index 8e06dd85..b54540f7 100644 --- a/src-tauri/src/app/gui/mod.rs +++ b/src-tauri/src/app/gui/mod.rs @@ -75,7 +75,15 @@ pub fn gui_main() { get_launcher_version, get_custom_mods, install_custom_mod, - delete_custom_mod + delete_custom_mod, + modrinth_search, + modrinth_get_version, + modrinth_install, + modrinth_update_mod, + modrinth_uninstall, + modrinth_check_updates, + modrinth_get_installed, + modrinth_sync_existing ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); diff --git a/src-tauri/src/app/mod.rs b/src-tauri/src/app/mod.rs index 9384714d..376b6913 100644 --- a/src-tauri/src/app/mod.rs +++ b/src-tauri/src/app/mod.rs @@ -22,3 +22,4 @@ pub mod gui; pub mod options; pub mod webview; pub mod client_api; +pub mod modrinth; diff --git a/src-tauri/src/app/modrinth.rs b/src-tauri/src/app/modrinth.rs new file mode 100644 index 00000000..ab21ef9e --- /dev/null +++ b/src-tauri/src/app/modrinth.rs @@ -0,0 +1,207 @@ +/* + * This file is part of LiquidLauncher (https://github.com/CCBlueX/LiquidLauncher) + * + * Copyright (c) 2015 - 2024 CCBlueX + * + * LiquidLauncher is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * LiquidLauncher is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with LiquidLauncher. If not, see . + */ + +use anyhow::Result; +use serde::{Deserialize, Serialize}; +use crate::HTTP_CLIENT; + +const MODRINTH_API: &str = "https://api.modrinth.com/v2"; + +#[derive(Serialize, Deserialize, Clone)] +pub struct ModrinthProject { + pub slug: String, + pub title: String, + pub description: String, + pub icon_url: Option, + pub project_id: String, + pub author: String, + pub downloads: u64, +} + +#[derive(Deserialize)] +pub struct SearchResponse { + pub hits: Vec, + pub total_hits: u32, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ModrinthVersion { + pub id: String, + pub project_id: String, + pub name: String, + pub version_number: String, + pub files: Vec, + pub game_versions: Vec, + pub loaders: Vec, +} + +#[derive(Serialize, Deserialize, Clone)] +pub struct ModrinthFile { + pub url: String, + pub filename: String, + pub primary: bool, + pub size: u64, +} + +pub async fn search_mods(query: &str, mc_version: &str, loader: &str) -> Result> { + let facets = format!( + "[[\"versions:{}\"],[\"categories:{}\"],[\"project_type:mod\"]]", + mc_version, loader + ); + + let url = format!( + "{}/search?query={}&facets={}&limit=20", + MODRINTH_API, + urlencoding::encode(query), + urlencoding::encode(&facets) + ); + + let response: SearchResponse = HTTP_CLIENT + .get(&url) + .send() + .await? + .json() + .await?; + + Ok(response.hits) +} + +pub async fn get_compatible_version(project_id: &str, mc_version: &str, loader: &str) -> Result> { + let url = format!( + "{}/project/{}/version?loaders=[\"{}\"]&game_versions=[\"{}\"]", + MODRINTH_API, project_id, loader, mc_version + ); + + let versions: Vec = HTTP_CLIENT + .get(&url) + .send() + .await? + .json() + .await?; + + Ok(versions.into_iter().next()) +} + +/// Downloads a mod file with atomic write to prevent corruption on network failure. +/// Uses a temporary file and only moves to final destination after successful download. +pub async fn download_mod(file: &ModrinthFile, dest_path: &std::path::Path) -> Result<()> { + use anyhow::Context; + + let temp_path = dest_path.with_extension("tmp"); + + let response = HTTP_CLIENT + .get(&file.url) + .timeout(std::time::Duration::from_secs(300)) + .send() + .await + .context("Network request failed - check your internet connection")?; + + if !response.status().is_success() { + anyhow::bail!("Download failed with status: {}", response.status()); + } + + let bytes = response.bytes() + .await + .context("Download interrupted - connection lost")?; + + // Verify we got the expected size + if bytes.len() as u64 != file.size { + anyhow::bail!( + "Download incomplete: got {} bytes, expected {}", + bytes.len(), + file.size + ); + } + + // Write to temp file first + tokio::fs::write(&temp_path, &bytes) + .await + .context("Failed to write temporary file")?; + + // Remove destination if exists (required for Windows) + if dest_path.exists() { + let _ = tokio::fs::remove_file(dest_path).await; + } + + // Move to final destination + if let Err(e) = tokio::fs::rename(&temp_path, dest_path).await { + let _ = tokio::fs::remove_file(&temp_path).await; + return Err(e).context("Failed to save mod file"); + } + + Ok(()) +} + +pub async fn get_project_from_hash(hash: &str) -> Result> { + let url = format!("{}/version_file/{}", MODRINTH_API, hash); + + let response = HTTP_CLIENT + .get(&url) + .send() + .await; + + match response { + Ok(resp) if resp.status().is_success() => { + let version: ModrinthVersion = resp.json().await?; + Ok(Some(version.project_id)) + } + _ => Ok(None) + } +} + +/// Get full version info from file hash (SHA-512) +pub async fn get_version_from_hash(hash: &str) -> Result> { + let url = format!("{}/version_file/{}", MODRINTH_API, hash); + + let response = HTTP_CLIENT + .get(&url) + .send() + .await; + + match response { + Ok(resp) if resp.status().is_success() => { + Ok(Some(resp.json().await?)) + } + _ => Ok(None) + } +} + +/// Get project details by ID +pub async fn get_project(project_id: &str) -> Result> { + let url = format!("{}/project/{}", MODRINTH_API, project_id); + + let response = HTTP_CLIENT + .get(&url) + .send() + .await; + + match response { + Ok(resp) if resp.status().is_success() => { + Ok(Some(resp.json().await?)) + } + _ => Ok(None) + } +} + +#[derive(Deserialize)] +pub struct ModrinthProjectDetails { + pub id: String, + pub slug: String, + pub title: String, +} diff --git a/src-tauri/src/app/options.rs b/src-tauri/src/app/options.rs index 98898740..5dee23f5 100644 --- a/src-tauri/src/app/options.rs +++ b/src-tauri/src/app/options.rs @@ -75,6 +75,8 @@ pub(crate) struct LauncherOptions { pub keep_launcher_open: bool, #[serde(rename = "sessionToken", default = "random_token")] pub session_token: String, + #[serde(rename = "autoUpdateMods", default)] + pub auto_update_mods: bool, } #[derive(Serialize, Deserialize)] @@ -129,7 +131,8 @@ impl Options { keep_launcher_open: legacy.keep_launcher_open, show_nightly_builds: legacy.show_nightly_builds, concurrent_downloads: legacy.concurrent_downloads as u32, - session_token: random_token() + session_token: random_token(), + auto_update_mods: false, }, premium_options: PremiumOptions { account: legacy.client_account, @@ -174,7 +177,8 @@ impl Default for LauncherOptions { show_nightly_builds: false, keep_launcher_open: false, concurrent_downloads: 10, - session_token: random_token() + session_token: random_token(), + auto_update_mods: false, } } } diff --git a/src/lib/main/MainScreen.svelte b/src/lib/main/MainScreen.svelte index cb60aca0..62d2b874 100644 --- a/src/lib/main/MainScreen.svelte +++ b/src/lib/main/MainScreen.svelte @@ -172,6 +172,7 @@ await authenticate(); await checkMemory(); + await autoUpdateMods(); await launchClient(); } catch (error) { console.error("Failed to start client:", error); @@ -181,6 +182,37 @@ } } + async function autoUpdateMods() { + if (!options.launcher.autoUpdateMods) return; + + progressState.text = "Checking for mod updates..."; + try { + const updates = await invoke("modrinth_check_updates", { + branch: versionState.currentBuild.branch, + mcVersion: versionState.currentBuild.mcVersion, + loader: versionState.currentBuild.subsystem || "fabric" + }); + + const modsToUpdate = updates.filter(m => m.has_update); + if (modsToUpdate.length === 0) return; + + for (const mod of modsToUpdate) { + progressState.text = `Updating ${mod.info.title}...`; + await invoke("modrinth_update_mod", { + projectId: mod.info.project_id, + mcVersion: versionState.currentBuild.mcVersion, + loader: versionState.currentBuild.subsystem || "fabric", + branch: versionState.currentBuild.branch + }); + } + + await updateMods(); + } catch (e) { + console.error("Auto-update failed:", e); + log = [...log, `Auto-update warning: ${e}`]; + } + } + async function authenticate() { if (options.premium.account) { try { diff --git a/src/lib/main/ModrinthSearch.svelte b/src/lib/main/ModrinthSearch.svelte new file mode 100644 index 00000000..9bf19e58 --- /dev/null +++ b/src/lib/main/ModrinthSearch.svelte @@ -0,0 +1,406 @@ + + + + + diff --git a/src/lib/main/ModrinthUpdates.svelte b/src/lib/main/ModrinthUpdates.svelte new file mode 100644 index 00000000..0311b37f --- /dev/null +++ b/src/lib/main/ModrinthUpdates.svelte @@ -0,0 +1,390 @@ + + +{#if mods.length > 0} +
+
+ + Modrinth Mods + {#if hasUpdates} + {updateCount} + {/if} + +
+ {#if hasUpdates} + + {/if} + +
+
+ +
+ {#each mods as mod (mod.info.project_id)} +
+ {mod.info.title} +
+ {#if mod.has_update} + + → {mod.new_version} + + + {:else} + + {/if} + +
+
+ {/each} +
+
+{/if} + + diff --git a/src/lib/main/VersionSelect.svelte b/src/lib/main/VersionSelect.svelte index 5e0634ef..dfb4d167 100644 --- a/src/lib/main/VersionSelect.svelte +++ b/src/lib/main/VersionSelect.svelte @@ -6,6 +6,10 @@ import SettingWrapper from "../settings/SettingWrapper.svelte"; import CustomModSetting from "../settings/CustomModSetting.svelte"; import IconButtonSetting from "../settings/IconButtonSetting.svelte"; + import ModrinthSearch from "./ModrinthSearch.svelte"; + import ModrinthUpdates from "./ModrinthUpdates.svelte"; + import Tabs from "../settings/tab/Tabs.svelte"; + import {installedMods} from "../stores/modsStore.js"; import {invoke} from "@tauri-apps/api/core"; import {open as dialogOpen} from "@tauri-apps/plugin-dialog"; @@ -18,8 +22,13 @@ currentBuild: null }; + let activeTab = "Version"; + let modrinthUpdatesRef; const dispatch = createEventDispatcher(); + // Sync store with versionState.customMods whenever it changes + $: installedMods.setMods(versionState.customMods); + async function deleteMod(event) { try { await invoke("delete_custom_mod", { @@ -27,6 +36,8 @@ mcVersion: versionState.currentBuild.mcVersion, modName: `${event.detail.name}.jar` }); + // Update store immediately for instant UI feedback + installedMods.removeMod(event.detail.name); dispatch('updateMods'); } catch (error) { console.error("Failed to delete mod:", error); @@ -59,64 +70,94 @@ alert(`Failed to install mod: ${error}`); } } + + function handleModrinthInstalled() { + dispatch('updateMods'); + if (modrinthUpdatesRef?.checkUpdates) { + modrinthUpdatesRef.checkUpdates(); + } + } dispatch('hide')} > - ({ - value: e, - text: `${e.charAt(0).toUpperCase()}${e.slice(1)} ${e === "legacy" ? "(unsupported)" : ""}` - }))} - bind:value={options.version.branchName} - on:change={() => dispatch('updateData')} + - ({ - value: e.buildId, - text: `${e.lbVersion} git-${e.commitId.substring(0, 7)} - ${e.date}` - })) - ]} - bind:value={options.version.buildId} - on:change={() => dispatch('updateData')} - /> - dispatch('updateData')} - /> - - {#each versionState.recommendedMods as mod} - dispatch('updateModStates')} - /> - {/each} - - -
- -
- {#each versionState.customMods as mod} - dispatch('updateModStates')} - on:delete={deleteMod} - /> - {/each} -
-
\ No newline at end of file + + {#if activeTab === "Version"} + ({ + value: e, + text: `${e.charAt(0).toUpperCase()}${e.slice(1)} ${e === "legacy" ? "(unsupported)" : ""}` + }))} + bind:value={options.version.branchName} + on:change={() => dispatch('updateData')} + /> + ({ + value: e.buildId, + text: `${e.lbVersion} git-${e.commitId.substring(0, 7)} - ${e.date}` + })) + ]} + bind:value={options.version.buildId} + on:change={() => dispatch('updateData')} + /> + dispatch('updateData')} + /> + + {#each versionState.recommendedMods as mod} + dispatch('updateModStates')} + /> + {/each} + + {:else if activeTab === "Mods"} + +
+ +
+ {#each versionState.customMods as mod} + dispatch('updateModStates')} + on:delete={deleteMod} + /> + {/each} +
+ + dispatch('updateMods')} + on:removed={() => dispatch('updateMods')} + /> + {/if} + diff --git a/src/lib/main/settings/GeneralSettings.svelte b/src/lib/main/settings/GeneralSettings.svelte index 0e343731..deb13dd8 100644 --- a/src/lib/main/settings/GeneralSettings.svelte +++ b/src/lib/main/settings/GeneralSettings.svelte @@ -121,6 +121,12 @@ bind:value={options.launcher.keepLauncherOpen} /> + + set(mods), + addMod: (mod) => update(mods => [...mods, mod]), + removeMod: (modName) => update(mods => mods.filter(m => m.name !== modName)), + clear: () => set([]) +};