mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2025-12-13 07:34:36 +01:00
Compare commits
102 Commits
v1.0.5-alp
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6db219e32e | ||
|
|
5bd4f3ff6e | ||
|
|
6206b6762a | ||
|
|
258c656815 | ||
|
|
68404f5e39 | ||
|
|
8c75541939 | ||
|
|
aa32782179 | ||
|
|
2cda891f02 | ||
|
|
9c8ed852d1 | ||
|
|
e75f65ab56 | ||
|
|
bdeda36043 | ||
|
|
151e7c3919 | ||
|
|
e6365b5592 | ||
|
|
ed76a086b6 | ||
|
|
c4526e2ff6 | ||
|
|
80c28669e1 | ||
|
|
5e48a4772e | ||
|
|
10234bed5a | ||
|
|
22d8a23386 | ||
|
|
6ef3e86820 | ||
|
|
0087801f83 | ||
|
|
55be3ebc2b | ||
|
|
7cf1b198ff | ||
|
|
fb231acaa6 | ||
|
|
827b8942c9 | ||
|
|
0fd4376e0d | ||
|
|
d6c5463619 | ||
|
|
9ade56dc6f | ||
|
|
484cd36565 | ||
|
|
1c27ae172e | ||
|
|
541352c3fd | ||
|
|
bfbf3e77a2 | ||
|
|
404e946e7c | ||
|
|
8077285e79 | ||
|
|
94c1bfd104 | ||
|
|
ed473ad659 | ||
|
|
b624ef693e | ||
|
|
c10a5cd82f | ||
|
|
25c6a70dc0 | ||
|
|
86c595abda | ||
|
|
35e6144733 | ||
|
|
565f229dac | ||
|
|
9bd4b9ccaf | ||
|
|
6750787bf9 | ||
|
|
c0770606ae | ||
|
|
4e03fec2a0 | ||
|
|
e7809be97c | ||
|
|
782e350ae5 | ||
|
|
6eab66032b | ||
|
|
c1842722b4 | ||
|
|
31aef02d5f | ||
|
|
bcdbb2ba06 | ||
|
|
ff8f35c52a | ||
|
|
a7188828fa | ||
|
|
db6917df5d | ||
|
|
8268c127a9 | ||
|
|
8fd5b895af | ||
|
|
10b9141815 | ||
|
|
d7783c5936 | ||
|
|
27122cd399 | ||
|
|
49740470ac | ||
|
|
7b693d7758 | ||
|
|
03439c3757 | ||
|
|
e83ae64714 | ||
|
|
3ecd13d1c3 | ||
|
|
28701ba007 | ||
|
|
3a84825cf9 | ||
|
|
4de8a43c3a | ||
|
|
d28e0a1bc8 | ||
|
|
bf8de40caa | ||
|
|
8e7d6ee420 | ||
|
|
d6e9bb100b | ||
|
|
374c6abbfc | ||
|
|
a728e8ba2c | ||
|
|
4371804429 | ||
|
|
fa38a22117 | ||
|
|
a7914406b4 | ||
|
|
57c1a7800c | ||
|
|
9426937a62 | ||
|
|
96d69d9ff6 | ||
|
|
defc1b43bd | ||
|
|
01fce477ef | ||
|
|
2f610ce0af | ||
|
|
d9772d9ffb | ||
|
|
569c465a50 | ||
|
|
8a5e66be48 | ||
|
|
e633f7dad7 | ||
|
|
bffd089192 | ||
|
|
bf3d892af5 | ||
|
|
424dd4ff1c | ||
|
|
93097db5cc | ||
|
|
c06aa9f885 | ||
|
|
f7bcad0a90 | ||
|
|
7120f846f7 | ||
|
|
f6ed37d2e4 | ||
|
|
99b210b3be | ||
|
|
7d52766c07 | ||
|
|
212d974ac6 | ||
|
|
a6e06e3005 | ||
|
|
56c1f2dcc2 | ||
|
|
a843888cb8 | ||
|
|
281bfb5cea |
4
.gitattributes
vendored
4
.gitattributes
vendored
@@ -1 +1,3 @@
|
||||
* text=auto eol=lf
|
||||
* text=auto eol=lf
|
||||
|
||||
src-tauri/keys/* binary
|
||||
|
||||
85
.github/workflows/build.yml
vendored
Normal file
85
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push:
|
||||
paths:
|
||||
- '.github/workflows/build.yml'
|
||||
- 'src-tauri/**/*'
|
||||
- 'src/**/*'
|
||||
pull_request:
|
||||
paths:
|
||||
- '.github/workflows/build.yml'
|
||||
- 'src-tauri/**/*'
|
||||
- 'src/**/*'
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}-${{ github.workflow }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build-win:
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install deps and build
|
||||
run: yarn && yarn build --debug
|
||||
|
||||
- name: Compress build
|
||||
uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: src-tauri/target/debug/lang/ src-tauri/target/debug/keys/ src-tauri/target/debug/Cultivation.exe src-tauri/target/debug/bundle/msi/
|
||||
recursive: true
|
||||
dest: Cultivation.zip
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: CultivationWin
|
||||
path: Cultivation.zip
|
||||
|
||||
build-ubuntu:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: setup node
|
||||
uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: 16
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
toolchain: stable
|
||||
|
||||
- name: Install libraries
|
||||
run: sudo apt install libwebkit2gtk-4.0-dev build-essential curl wget libssl-dev libgtk-3-dev libayatana-appindicator3-dev librsvg2-dev
|
||||
|
||||
- name: Install deps and build
|
||||
run: yarn && yarn build --debug
|
||||
|
||||
- name: Compress build
|
||||
uses: vimtor/action-zip@v1
|
||||
with:
|
||||
files: src-tauri/target/debug/lang/ src-tauri/target/debug/keys/ src-tauri/target/debug/cultivation
|
||||
recursive: true
|
||||
dest: Cultivation.zip
|
||||
|
||||
- name: Upload build
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
name: CultivationLinux
|
||||
path: Cultivation.zip
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,6 +10,7 @@
|
||||
|
||||
# production
|
||||
/build
|
||||
.idea
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env sh
|
||||
. "$(dirname -- "$0")/_/husky.sh"
|
||||
|
||||
yarn lint-staged
|
||||
yarn lint-staged --allow-empty
|
||||
|
||||
8
.idea/.gitignore
generated
vendored
8
.idea/.gitignore
generated
vendored
@@ -1,8 +0,0 @@
|
||||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
14
.idea/cultivation.iml
generated
14
.idea/cultivation.iml
generated
@@ -1,14 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/src-tauri/src" isTestSource="false" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/temp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/.tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/tmp" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/src-tauri/target" />
|
||||
</content>
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
7
.idea/discord.xml
generated
7
.idea/discord.xml
generated
@@ -1,7 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
<option name="description" value="" />
|
||||
</component>
|
||||
</project>
|
||||
40
.idea/inspectionProfiles/Project_Default.xml
generated
40
.idea/inspectionProfiles/Project_Default.xml
generated
@@ -1,40 +0,0 @@
|
||||
<component name="InspectionProjectProfileManager">
|
||||
<profile version="1.0">
|
||||
<option name="myName" value="Project Default" />
|
||||
<inspection_tool class="Eslint" enabled="true" level="WARNING" enabled_by_default="true" />
|
||||
<inspection_tool class="HtmlUnknownBooleanAttribute" enabled="false" level="WARNING" enabled_by_default="false" />
|
||||
<inspection_tool class="HttpUrlsUsage" enabled="true" level="WEAK WARNING" enabled_by_default="true">
|
||||
<option name="ignoredUrls">
|
||||
<list>
|
||||
<option value="http://localhost" />
|
||||
<option value="http://127.0.0.1" />
|
||||
<option value="http://0.0.0.0" />
|
||||
<option value="http://www.w3.org/" />
|
||||
<option value="http://json-schema.org/draft" />
|
||||
<option value="http://java.sun.com/" />
|
||||
<option value="http://xmlns.jcp.org/" />
|
||||
<option value="http://javafx.com/javafx/" />
|
||||
<option value="http://javafx.com/fxml" />
|
||||
<option value="http://maven.apache.org/xsd/" />
|
||||
<option value="http://maven.apache.org/POM/" />
|
||||
<option value="http://www.springframework.org/schema/" />
|
||||
<option value="http://www.springframework.org/tags" />
|
||||
<option value="http://www.springframework.org/security/tags" />
|
||||
<option value="http://www.thymeleaf.org" />
|
||||
<option value="http://www.jboss.org/j2ee/schema/" />
|
||||
<option value="http://www.jboss.com/xml/ns/" />
|
||||
<option value="http://www.ibm.com/webservices/xsd" />
|
||||
<option value="http://activemq.apache.org/schema/" />
|
||||
<option value="http://schema.cloudfoundry.org/spring/" />
|
||||
<option value="http://schemas.xmlsoap.org/" />
|
||||
<option value="http://cxf.apache.org/schemas/" />
|
||||
<option value="http://primefaces.org/ui" />
|
||||
<option value="http://tiles.apache.org/" />
|
||||
<option value="http://api.grasscutter.io" />
|
||||
<option value="http://api.grasscutters.xyz" />
|
||||
</list>
|
||||
</option>
|
||||
</inspection_tool>
|
||||
<inspection_tool class="JSIgnoredPromiseFromCall" enabled="false" level="WEAK WARNING" enabled_by_default="false" />
|
||||
</profile>
|
||||
</component>
|
||||
8
.idea/modules.xml
generated
8
.idea/modules.xml
generated
@@ -1,8 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/cultivation.iml" filepath="$PROJECT_DIR$/.idea/cultivation.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
6
.idea/vcs.xml
generated
6
.idea/vcs.xml
generated
@@ -1,6 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
||||
45
README.md
45
README.md
@@ -1,17 +1,14 @@
|
||||
# Client Patching Notice
|
||||
|
||||
For game versions 2.8 and above, Cultivation automatically makes a small patch to your game client when launching using Grasscutter, and restores it upon closing the game. In theory, you should still be totally safe, however it would be dishonest to not explicitly state that **modifying the game client could, theoretically, lead to a ban if you connect to official servers with it**. It is extremely unlikely AND there are no instances known of it happening, but the possibility exists.
|
||||
EN | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) |
|
||||
|
||||
# Cultivation
|
||||
|
||||
A game launcher designed to easily proxy traffic from anime game to private servers.
|
||||
|
||||
While the Cultivation repository is **open**. This does **not** mean it has released.
|
||||
Please do **NOT install, download, or use pre-compiled versions of Cultivation found elsewhere**. Only use releases from this GitHub repository.
|
||||
|
||||
# Table Of Contents
|
||||
|
||||
- [Client Patching Notice](#client-patching-notice)
|
||||
- [Download](#download)
|
||||
- [Setup](#setup)
|
||||
- [Developer Quick-start](#developer-quickstart)
|
||||
- [Setup](#setup)
|
||||
- [Building](#building)
|
||||
@@ -21,11 +18,45 @@ Please do **NOT install, download, or use pre-compiled versions of Cultivation f
|
||||
- [Screenshots](#screenshots)
|
||||
- [Credits](#credits)
|
||||
|
||||
# Client Patching Notice
|
||||
|
||||
For game versions 2.8 and above, Cultivation automatically makes a small patch to your game client when launching using Grasscutter, and restores it upon closing the game. In theory, you should still be totally safe, however it would be dishonest to not explicitly state that **modifying the game client could, theoretically, lead to a ban if you connect to official servers with it**. It is extremely unlikely AND there are no instances known of it happening, but the possibility exists.
|
||||
|
||||
# Download
|
||||
|
||||
[Find release builds here!](https://github.com/Grasscutters/Cultivation/releases)
|
||||
|
||||
Once downloaded, extract somewhere and open as administrator.
|
||||
Download and open the MSI, and once installed, run Cultivation as administrator. Refer below for more [detailed setup instructions](#setup).
|
||||
|
||||
**Windows 7 Users:** You will need to download [WebView](https://developer.microsoft.com/en-us/microsoft-edge/webview2/#download-section) manually, and download the `.zip` instead of the `.msi`.
|
||||
|
||||
# Setup
|
||||
|
||||
5-minute video for those who don't like to/cannot read: https://youtu.be/e0irOYbQe7I
|
||||
|
||||
- Download Cultivation
|
||||
- If you are on Windows 10 or 11, use the MSI
|
||||
- If you are on Windows 7, or the MSI doesn't work, use the zip and download [WebView](https://developer.microsoft.com/en-us/microsoft-edge/webview2/)
|
||||
- If you are on Linux or MacOS, [help us port Windows-specific system calls to Linux/MacOS!](https://github.com/Grasscutters/Cultivation/issues/7)
|
||||
- Install or extract Cultivation
|
||||
- Open Cultivation **_as administrator_**
|
||||
- Before clicking randomly on stuff, in options (top right cog icon), set your Game Install Path.
|
||||
- If you are using an existing server installation from somewhere else, you can set the `.jar` file in settings as well. All downloads made through Culti will automatically use that path, no additional config needed.
|
||||
- If you use multiple Java versions, you can set the Java path to your Java 17 installation (only required if you are running your own server)
|
||||
- Decide if you want to download your own server, or just join a public one
|
||||
- If joining a public one, you're done. Just click "Connect with Grasscutter" and input the address and port. You do not have to continue these instructions.
|
||||
- If you are getting System Error, or 4214, ask the [Discord support channels](https://discord.gg/grasscutter)
|
||||
- Open the "Downloads" menu (top right)
|
||||
- Download "latest grasscutter" (second from the top)
|
||||
- Download "resources" (very bottom)
|
||||
- Once all of that is done, click the icon next to "Launch"
|
||||
- To play on your new server:
|
||||
- Click "Connect with Grasscutter"
|
||||
- Input `localhost` as the address, and `443` as the port
|
||||
- Ensure HTTPS is disabled
|
||||
- Any generic "I am getting XYZ error!" should go in the [Discord support channels](https://discord.gg/grasscutter)
|
||||
- Any specific Cultivation issues should go in [the issues section](/issues)
|
||||
- Any Grasscutter server related issues should go in [the Grasscutter issues section](https://github.com/Grasscutters/Grasscutter)
|
||||
|
||||
# Developer Quickstart
|
||||
|
||||
|
||||
88
README_zh-CN.md
Normal file
88
README_zh-CN.md
Normal file
@@ -0,0 +1,88 @@
|
||||
[EN](README.md) | 简中 | [繁中](README_zh-TW.md)
|
||||
|
||||
# 客户端修补通知
|
||||
|
||||
对于游戏版本为 2.8 及以上时,使用 Grasscutter 启动时,Cultivation 会自动为您的游戏客户端制作一个小补丁,并在关闭游戏时恢复它。 从理论上讲,你应该是完全安全的,但是不明确**如果您使用它连接到官方服务器,修改游戏客户端可能会导致封号**,但可能性是非常小的,并且从未接到发生过此类情况的问题,但存在这种可能性!
|
||||
|
||||
# Cultivation
|
||||
|
||||
一个游戏启动器,旨在轻松将某动漫游戏的流量代理到私人服务器。
|
||||
|
||||
虽然此存储库是**开放的**。 但这**并不**意味着它已经发布。
|
||||
请不要**安装、下载或使用在其他地方找到的预编译版本的 Cultivation**。 仅使用此 GitHub 存储库中的版本。
|
||||
|
||||
# 目录
|
||||
|
||||
- [下载](#下载)
|
||||
- [开发人员快速入门](#开发人员快速入门)
|
||||
- [安装](#安装)
|
||||
- [编译](#编译)
|
||||
- [代码格式化与纠错](#代码格式化与纠错)
|
||||
- [生成更新项目](#生成更新项目)
|
||||
- [启动器主题](#启动器主题)
|
||||
- [画面](#画面)
|
||||
- [成员](#成员)
|
||||
|
||||
# 下载
|
||||
|
||||
[在此处查找发布版本!](https://github.com/Grasscutters/Cultivation/releases)
|
||||
|
||||
下载后,从某个位置解压缩并以管理员身份打开。
|
||||
|
||||
# 开发人员快速入门
|
||||
|
||||
### 安装
|
||||
|
||||
- 安装 [NodeJS >12](https://nodejs.org/en/)
|
||||
- 安装 [yarn](https://classic.yarnpkg.com/lang/en/docs/install)
|
||||
- 安装 [Rust](https://www.rust-lang.org/tools/install)
|
||||
- `yarn install`
|
||||
- `yarn start:dev`
|
||||
|
||||
### 编译
|
||||
|
||||
发布版本,
|
||||
|
||||
- `yarn build`
|
||||
|
||||
调试版本,
|
||||
|
||||
- `yarn build --debug`
|
||||
|
||||
### 代码格式化与纠错
|
||||
|
||||
格式化:
|
||||
|
||||
- `yarn format`
|
||||
|
||||
纠错, 修复(一些)错误:
|
||||
|
||||
- `yarn lint`, `yarn lint:fix`
|
||||
|
||||
### 生成更新项目
|
||||
|
||||
- 将 `TAURI_PRIVATE_KEY` 添加到环境变量,其中包含私钥的路径。
|
||||
- 将 `TAURI_KEY_PASSWORD` 添加到环境变量,其中包含私钥的密码。
|
||||
- `yarn build`
|
||||
|
||||
更新将生成在 `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
|
||||
|
||||
# 启动器主题
|
||||
|
||||
完整的主题参考可以[在这里找到!](/THEMES.md)
|
||||
|
||||
# 画面
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 成员
|
||||
|
||||
- [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
|
||||
- [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
|
||||
- [Benj](https://github.com/4Benj): For assistance in client patching.
|
||||
- [lilmayofuksu](https://github.com/lilmayofuksu): For assistance in client patching.
|
||||
- [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
|
||||
88
README_zh-TW.md
Normal file
88
README_zh-TW.md
Normal file
@@ -0,0 +1,88 @@
|
||||
[EN](README.md) | [简中](README_zh-CN.md) | 繁中
|
||||
|
||||
# 客戶端修補通知
|
||||
|
||||
對於遊戲版本為 2.8 及以上時,使用 Grasscutter 啟動時,Cultivation 會自動為您的遊戲客戶端製作一個小修補,並在關閉遊戲時恢復它。 從理論上講,你應該是完全安全的,但是不明確**如果您使用它連接到官方伺服器,修改遊戲客戶端可能會導致封號**,但可能性是非常小的,並且從未接到發生過此類情況的問題,但存在這種可能性!
|
||||
|
||||
# Cultivation
|
||||
|
||||
一個遊戲啟動器,旨在輕松將某動漫遊戲的流量代理到私人伺服器。
|
||||
|
||||
雖然此存儲庫是**開放的**。 但這**並不**意味著它已經發布。
|
||||
請不要**安裝、下載或使用在其他地方找到的預編譯版本的 Cultivation**。 僅使用此 GitHub 存儲庫中的版本。
|
||||
|
||||
# 目錄
|
||||
|
||||
- [下載](#下載)
|
||||
- [開發人員快速入門](#開發人員快速入門)
|
||||
- [安裝](#安裝)
|
||||
- [編譯](#編譯)
|
||||
- [代碼格式化與糾錯](#代碼格式化與糾錯)
|
||||
- [生成更新項目](#生成更新項目)
|
||||
- [啟動器主題](#啟動器主題)
|
||||
- [畫面](#畫面)
|
||||
- [成員](#成員)
|
||||
|
||||
# 下載
|
||||
|
||||
[在此處查找發布版本!](https://github.com/Grasscutters/Cultivation/releases)
|
||||
|
||||
下載後,從某個位置解壓縮並以管理員身份打開。
|
||||
|
||||
# 開發人員快速入門
|
||||
|
||||
### 安裝
|
||||
|
||||
- 安裝 [NodeJS >12](https://nodejs.org/en/)
|
||||
- 安裝 [yarn](https://classic.yarnpkg.com/lang/en/docs/install) (`npm`愛好者去哭吧!(滑稽))
|
||||
- 安裝 [Rust](https://www.rust-lang.org/tools/install)
|
||||
- `yarn install`
|
||||
- `yarn start:dev`
|
||||
|
||||
### 編譯
|
||||
|
||||
發布版本,
|
||||
|
||||
- `yarn build`
|
||||
|
||||
調試版本,
|
||||
|
||||
- `yarn build --debug`
|
||||
|
||||
### 代碼格式化與糾錯
|
||||
|
||||
格式化:
|
||||
|
||||
- `yarn format`
|
||||
|
||||
糾錯, 修復(一些)錯誤:
|
||||
|
||||
- `yarn lint`, `yarn lint:fix`
|
||||
|
||||
### 生成更新項目
|
||||
|
||||
- 將 `TAURI_PRIVATE_KEY` 添加到環境變數,其中包含私鑰的路徑。
|
||||
- 將 `TAURI_KEY_PASSWORD` 添加到環境變數,其中包含私鑰的密碼。
|
||||
- `yarn build`
|
||||
|
||||
更新將生成在 `src-tauri/target/(release|debug)/msi/Cultivation_X.X.X_x64_xx-XX.msi.zip`
|
||||
|
||||
# 啟動器主題
|
||||
|
||||
完整的主題參考可以[在這裏找到!](/THEMES.md)
|
||||
|
||||
# 畫面
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## 成員
|
||||
|
||||
- [SpikeHD](https://github.com/SpikeHD): For originally creating **GrassClipper** and creating the amazing UI of Cultivation.
|
||||
- [KingRainbow44](https://github.com/KingRainbow44): For building a proxy daemon from scratch and integrating it with Cultivation.
|
||||
- [Benj](https://github.com/4Benj): For assistance in client patching.
|
||||
- [lilmayofuksu](https://github.com/lilmayofuksu): For assistance in client patching.
|
||||
- [Tauri](https://tauri.app): For providing an amazing, efficient, and simple desktop application framework/library.
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cultivation",
|
||||
"version": "1.0.5",
|
||||
"version": "1.0.10",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.0.0-rc.5",
|
||||
@@ -20,14 +20,14 @@
|
||||
"scripts": {
|
||||
"start": "cross-env BROWSER=none react-scripts start",
|
||||
"postbuild:windows": "xcopy /E /H /C /I /Y \".\\src-tauri\\lang\" \".\\src-tauri\\target\\release\\lang\"",
|
||||
"postbuild:linux": "cp -r \".\\src-tauri\\lang\" \".\\lang\"",
|
||||
"postbuild:linux": "cp -r \"./src-tauri/lang\" \"./lang\"",
|
||||
"build:windows": "yarn tauri build",
|
||||
"build:linux": "yarn tauri build",
|
||||
"build": "react-scripts build && run-script-os",
|
||||
"test": "react-scripts test",
|
||||
"eject": "react-scripts eject",
|
||||
"tauri": "tauri",
|
||||
"start:dev": "tauri dev",
|
||||
"start:dev": "tauri dev -- -- --no-admin",
|
||||
"format": "cargo fmt --manifest-path ./src-tauri/Cargo.toml --all && yarn prettier --write --cache --loglevel warn .",
|
||||
"lint": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features && yarn tsc --noEmit && yarn eslint src",
|
||||
"lint:fix": "cargo clippy --manifest-path ./src-tauri/Cargo.toml --no-default-features --fix --allow-dirty && yarn tsc --noEmit && yarn eslint --fix src",
|
||||
|
||||
1450
src-tauri/Cargo.lock
generated
1450
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -12,33 +12,33 @@ rust-version = "1.57"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "1.0.0-rc.8", features = [] }
|
||||
tauri-build = { version = "1.2.0", features = [] }
|
||||
cc = "1.0"
|
||||
|
||||
[target.'cfg(windows)'.dependencies]
|
||||
is_elevated = "0.1.2"
|
||||
registry = "1.2.1"
|
||||
registry = "1.2.2"
|
||||
|
||||
[target.'cfg(unix)'.dependencies]
|
||||
sudo = "0.6.0"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
tauri = { version = "1.0.0-rc.9", features = ["api-all"] }
|
||||
tauri = { version = "1.2.0", features = ["api-all"] }
|
||||
|
||||
# Access system process info.
|
||||
sysinfo = "0.24.6"
|
||||
sysinfo = "0.26.7"
|
||||
|
||||
# ZIP-archive library.
|
||||
zip-extract = "0.1.1"
|
||||
unrar = "0.4.4"
|
||||
zip = "0.6.2"
|
||||
zip = "0.6.3"
|
||||
|
||||
# For creating a "global" downloads list.
|
||||
once_cell = "1.13.0"
|
||||
once_cell = "1.16.0"
|
||||
|
||||
# Program opener.
|
||||
open = "3.0.2"
|
||||
open = "3.0.3"
|
||||
duct = "0.13.5"
|
||||
|
||||
# Serialization.
|
||||
@@ -46,21 +46,23 @@ serde_json = "1"
|
||||
|
||||
# Dependencies for the HTTP(S) proxy.
|
||||
http = "0.2"
|
||||
hudsucker = "0.17.2"
|
||||
tracing = "0.1.21"
|
||||
tokio-rustls = "0.23.0"
|
||||
tokio-tungstenite = "0.17.0"
|
||||
tokio = { version = "1.18.2", features = ["signal"] }
|
||||
rustls-pemfile = "1.0.0"
|
||||
reqwest = { version = "0.11.3", features = ["stream"] }
|
||||
futures-util = "0.3.14"
|
||||
rcgen = { version = "0.9", features = ["x509-parser"] }
|
||||
hudsucker = "0.18.0"
|
||||
tracing = "0.1.37"
|
||||
tokio-rustls = "0.23.4"
|
||||
tokio-tungstenite = "0.17.2"
|
||||
tokio = { version = "1.21.2", features = ["signal"] }
|
||||
rustls-pemfile = "1.0.1"
|
||||
reqwest = { version = "0.11.13", features = ["stream"] }
|
||||
futures-util = "0.3.25"
|
||||
rcgen = { version = "0.10", features = ["x509-parser"] }
|
||||
|
||||
# metadata stuff
|
||||
regex = "1"
|
||||
|
||||
# other
|
||||
file_diff = "1.0.0"
|
||||
rust-ini = "0.18.0"
|
||||
ctrlc = "3.2.3"
|
||||
|
||||
[features]
|
||||
# by default Tauri runs in production mode
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<RSAKeyValue>
|
||||
<Exponent>AQAB</Exponent>
|
||||
<Modulus>yytg/H9lz7Lm0XcA8LMqIyXPVNApYTcSepT4VDLB4qqqFC3s
|
||||
/Huv8vN7zA/P4uoREIu8KMenADFk7uwrZSxoMWwJgn6A7sbAt1cqAaUXB
|
||||
9J4NzhL0x3AFTiHEQbw86hRvm2VGkbA5sWnr0NZw8SGBBY+EODwNIt51G
|
||||
dBA7eoUQU=</Modulus>
|
||||
<RSAKeyValue>
|
||||
<Exponent>AQAB</Exponent>
|
||||
<Modulus>yytg/H9lz7Lm0XcA8LMqIyXPVNApYTcSepT4VDLB4qqqFC3s
|
||||
/Huv8vN7zA/P4uoREIu8KMenADFk7uwrZSxoMWwJgn6A7sbAt1cqAaUXB
|
||||
9J4NzhL0x3AFTiHEQbw86hRvm2VGkbA5sWnr0NZw8SGBBY+EODwNIt51G
|
||||
dBA7eoUQU=</Modulus>
|
||||
</RSAKeyValue>
|
||||
@@ -11,13 +11,21 @@
|
||||
"files_extracting": "文件解压中:"
|
||||
},
|
||||
"options": {
|
||||
"enabled": "已启用",
|
||||
"disabled": "已禁用",
|
||||
"game_path": "选择游戏安装路径",
|
||||
"game_executable": "选择游戏可执行文件",
|
||||
"recover_metadata": "紧急情况下恢复元数据文件",
|
||||
"grasscutter_jar": "选择 Grasscutter JAR 文件",
|
||||
"java_path": "设置自定义 Java 路径",
|
||||
"toggle_encryption": "启用加密",
|
||||
"install_certificate": "安装代理证书",
|
||||
"java_path": "选择自定义 Java 路径",
|
||||
"grasscutter_with_game": "随游戏自动启动 Grasscutter",
|
||||
"language": "语言",
|
||||
"language": "选择语言",
|
||||
"background": "设置自定义背景(链接或文件)",
|
||||
"theme": "设置主题"
|
||||
"theme": "设置主题",
|
||||
"patch_metadata": "自动修改元数据",
|
||||
"use_proxy": "使用内置代理"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "下载 Grasscutter 稳定版数据",
|
||||
@@ -28,7 +36,8 @@
|
||||
"grasscutter_latest": "下载 Grasscutter 开发版",
|
||||
"grasscutter_stable_update": "更新 Grasscutter 稳定版",
|
||||
"grasscutter_latest_update": "更新 Grasscutter 开发版",
|
||||
"resources": "下载 Grasscutter 资源"
|
||||
"resources": "下载 Grasscutter 资源",
|
||||
"game": "下载游戏"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "下载中",
|
||||
@@ -40,10 +49,11 @@
|
||||
"components": {
|
||||
"select_file": "选择文件或文件夹...",
|
||||
"select_folder": "选择文件夹...",
|
||||
"download": "下载"
|
||||
"download": "下载",
|
||||
"install": "安装"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "最近的PR",
|
||||
"latest_commits": "最近提交",
|
||||
"latest_version": "最新版本"
|
||||
},
|
||||
"help": {
|
||||
@@ -53,6 +63,17 @@
|
||||
"gc_dev_jar": "下载最新的 Grasscutter 开发版,包括 JAR 文件和数据。",
|
||||
"gc_stable_data": "下载当前的 Grasscutter 稳定版数据,不包括 JAR 文件。此选项在更新时有帮助。",
|
||||
"gc_dev_data": "下载最新的 Grasscutter 开发版数据,不包括 JAR 文件。此选项在更新时有帮助。",
|
||||
"resources": "资源文件在运行 Grasscutter 服务器时是必要的。此选项在已经存在资源文件时不可选。"
|
||||
"resources": "资源文件在运行 Grasscutter 服务器时是必要的。此选项在已经存在资源文件时不可选。",
|
||||
"emergency_metadata": "在出现意外情况时,自动将元数据恢复至原始版本",
|
||||
"use_proxy": "使用 Cultivation 的内置代理。除非你使用 Fiddler 等软件,否则应启用此项。",
|
||||
"patch_metadata": "自动修改和恢复游戏元数据。除非要游玩旧版本/非官方版本,抑或你已经手动修改了元数据,否则应启用此项。"
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "选择 Akebi 可执行文件",
|
||||
"migoto": "选择 3DMigoto 可执行文件",
|
||||
"reshade": "选择 Reshade 注入器"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,19 @@
|
||||
"options": {
|
||||
"enabled": "已啟用",
|
||||
"disabled": "未啟用",
|
||||
"game_path": "選擇遊戲安裝路徑",
|
||||
"game_executable": "選擇遊戲執行檔",
|
||||
"recover_metadata": "緊急恢復Metadata",
|
||||
"grasscutter_jar": "選擇伺服器JAR檔案",
|
||||
"toggle_encryption": "設定加密",
|
||||
"java_path": "設定自定義Java路徑",
|
||||
"install_certificate": "安裝代理憑證",
|
||||
"java_path": "選擇自定義Java路徑",
|
||||
"grasscutter_with_game": "伴隨遊戲一起啟動Grasscutter",
|
||||
"language": "語言",
|
||||
"background": "設定自定義背景(網址或檔案)",
|
||||
"theme": "設定主題"
|
||||
"background": "選擇自定義背景(網址或檔案)",
|
||||
"theme": "選擇主題",
|
||||
"patch_metadata": "自動修補Metadata",
|
||||
"use_proxy": "使用內建代理伺服器"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "下載Grasscutter穩定版數據(Data)",
|
||||
@@ -31,7 +36,8 @@
|
||||
"grasscutter_latest": "下載Grasscutter開發板",
|
||||
"grasscutter_stable_update": "更新Grasscutter穩定版",
|
||||
"grasscutter_latest_update": "更新Grasscutter開發板",
|
||||
"resources": "下載Grasscutter資源(Resources)"
|
||||
"resources": "下載Grasscutter資源(Resources)",
|
||||
"game": "下載遊戲"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "下載中",
|
||||
@@ -43,7 +49,8 @@
|
||||
"components": {
|
||||
"select_file": "選擇檔案或資料夾...",
|
||||
"select_folder": "選擇資料夾...",
|
||||
"download": "下載"
|
||||
"download": "下載",
|
||||
"install": "安裝"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "最近的PR",
|
||||
@@ -51,11 +58,23 @@
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "確保這是Dispatch伺服器端口,不是遊戲伺服器端口。 大部分伺服器的端口都是443。",
|
||||
"game_help_text": "你不需要另外一個遊戲備份來使用Grasscutter。這是給想要降級到2.6或者還沒安裝遊戲的人使用的。",
|
||||
"game_help_text": "您不需要另外一個遊戲備份來使用Grasscutter。這是給想要降級到2.6或者還沒安裝遊戲的人使用的。",
|
||||
"gc_stable_jar": "下載當前的Grasscutter穩定版本,包括JAR答案還有資料文件。",
|
||||
"gc_dev_jar": "下載當前的Grasscutter穩定版本資料文件,其中不會附帶JAR文件。這個選項在更新時很有用。",
|
||||
"gc_stable_data": "下載當前最新的Grasscutter開發版本資料文件,其中不會附帶JAR文件。這個選項在更新時很有用。",
|
||||
"gc_dev_data": "下載當前最新的Grasscutter開發版本的資料文件,其中不會附帶JAR文件。這個選項在更新時很有用。",
|
||||
"resources": "資源文件在架設一個Grasscutter伺服器時是必要的。 這個選項會在你已經有裡面有檔案的資源資料夾時不可選。"
|
||||
"encryption": "在正常情況下,此選項應該被關閉。",
|
||||
"resources": "資源文件在架設一個Grasscutter伺服器時是必要的。 這個選項會在您已經有裡面有檔案的資源資料夾時不可選。",
|
||||
"emergency_metadata": "一旦有東西出了問題,此選項可以把您的Metadata恢復成官方版本。",
|
||||
"use_proxy": "使用Cultivation內建的代理伺服器。此選項應該被啟用,除非你使用其他的代理伺服器。",
|
||||
"patch_metadata": "自動修補和恢復Metadata。除非您的遊戲版本是舊的或者是非官方的,此選項應該被啟用。"
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "選擇Akebi執行檔",
|
||||
"migoto": "選擇3DMigoto執行檔",
|
||||
"reshade": "選擇Reshade注入器"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,14 +13,17 @@
|
||||
"options": {
|
||||
"enabled": "Aktiviert",
|
||||
"disabled": "Deaktiviert",
|
||||
"game_path": "Spielpfad",
|
||||
"game_executable": "Spiel Datei auswählen",
|
||||
"recover_metadata": "Notfall Wiederherstellung der Metadaten",
|
||||
"grasscutter_jar": "Grasscuter JAR auswählen",
|
||||
"toggle_encryption": "Verschlüsselung umschalten",
|
||||
"java_path": "Benutzerdefinierten Java Pfad setzen",
|
||||
"grasscutter_with_game": "Grasscutter automatisch mit dem Spiel starten",
|
||||
"language": "Sprache auswählen",
|
||||
"background": "Benutzerdefinierten Hintergrund festlegen (link oder bild)",
|
||||
"theme": "Theme auswählen"
|
||||
"theme": "Theme auswählen",
|
||||
"patch_metadata": "Metadaten automatisch patchen"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Stabile Grasscutter Daten herunterladen",
|
||||
@@ -31,7 +34,8 @@
|
||||
"grasscutter_latest": "Aktuellste Grasscutter Version herunterladen",
|
||||
"grasscutter_stable_update": "Stabile Grasscutter Version aktualisieren",
|
||||
"grasscutter_latest_update": "Aktuellste Grasscutter Version aktualisieren",
|
||||
"resources": "Grasscutter Ressourcen herunterladen"
|
||||
"resources": "Grasscutter Ressourcen herunterladen",
|
||||
"game": "Spiel herunterladen"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Lädt herunter",
|
||||
@@ -43,7 +47,8 @@
|
||||
"components": {
|
||||
"select_file": "Datei oder Ordner auswählen...",
|
||||
"select_folder": "Ordner auswählen...",
|
||||
"download": "Herunterladen"
|
||||
"download": "Herunterladen",
|
||||
"install": "Installieren"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Letzte Commits",
|
||||
@@ -56,6 +61,17 @@
|
||||
"gc_dev_jar": "Laden Sie die neueste Grasscutter-Entwicklungsversion herunter, welche eine Jar-Datei und Datendateien enthält.",
|
||||
"gc_stable_data": "Laden Sie die stabilen Grasscutter Daten herunter, welche keine Jar-Datei enthalten. Dies ist nützlich zum Aktualisieren.",
|
||||
"gc_dev_data": "Laden Sie die neuesten Grasscutter-Entwicklungsdateien herunter, welche keine Jar-Datei enthält. Dies ist nützlich zum Aktualisieren.",
|
||||
"resources": "Diese werden auch benötigt, um einen Grasscutter-Server auszuführen. Diese Schaltfläche ist grau, wenn Sie einen bestehenden Ressourcenordner mit Inhalten haben"
|
||||
"resources": "Diese werden auch benötigt, um einen Grasscutter-Server auszuführen. Diese Schaltfläche ist grau, wenn Sie einen bestehenden Ressourcenordner mit Inhalten haben",
|
||||
"emergency_metadata": "Im Fall, dass etwas schief laufen sollte, kannst du deine Metadaten auf die letzte offizielle Version zurücksetzen",
|
||||
"use_proxy": "Nutze den internen Proxy von Cultivation. Du solltest dies aktivieren, es sei denn du nutzt Programme wie Fiddler",
|
||||
"patch_metadata": "Patche und aktualisiere deine Metadaten automatisch. Solange du nicht mit einer alten/nicht offiziellen Version spielst oder deine Metadaten manuell gepatcht hast, sollte dies aktiviert sein."
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Akebi.exe festlegen",
|
||||
"migoto": "Migoto.exe festlegen",
|
||||
"reshade": "Reshade injector festlegen"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"game_path": "Set Game Install Path",
|
||||
"game_command": "Game Launch Command",
|
||||
"game_executable": "Set Game Executable",
|
||||
"recover_metadata": "Emergency Metadata Recovery",
|
||||
"grasscutter_jar": "Set Grasscutter JAR",
|
||||
@@ -25,7 +26,9 @@
|
||||
"background": "Set Custom Background (link or image file)",
|
||||
"theme": "Set Theme",
|
||||
"patch_metadata": "Automatically Patch Metadata",
|
||||
"use_proxy": "Use Internal Proxy"
|
||||
"use_proxy": "Use Internal Proxy",
|
||||
"wipe_login": "Wipe Login Cache",
|
||||
"horny_mode": "Horny Mode"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Download Grasscutter Stable Data",
|
||||
|
||||
80
src-tauri/lang/nl.json
Normal file
80
src-tauri/lang/nl.json
Normal file
@@ -0,0 +1,80 @@
|
||||
{
|
||||
"lang_name": "Nederlands",
|
||||
"main": {
|
||||
"title": "Cultivation",
|
||||
"launch_button": "Start",
|
||||
"gc_enable": "Verbind Met Grasscutter",
|
||||
"https_enable": "Gebruik HTTPS",
|
||||
"ip_placeholder": "Server Address...",
|
||||
"port_placeholder": "Poort...",
|
||||
"files_downloading": "Bestanden Aan Downloaden: ",
|
||||
"files_extracting": "Bestanden Uitpakken: "
|
||||
},
|
||||
"options": {
|
||||
"enabled": "Ingeschakeld",
|
||||
"disabled": "Uitgeschakeld",
|
||||
"game_path": "Spel Installatie Pad Instellen",
|
||||
"game_executable": "Stel De Exe Van Het Spel In",
|
||||
"recover_metadata": "Noodherstel Van De Metadata",
|
||||
"grasscutter_jar": "Stel De Grasscutter JAR In",
|
||||
"toggle_encryption": "Versleuteling Inschakelen",
|
||||
"install_certificate": "Proxy-certificaat installeren",
|
||||
"java_path": "Aangepast Java-pad Instellen",
|
||||
"grasscutter_with_game": "Start Automatisch Grasscutter Met Spel",
|
||||
"language": "Selecteer Taal",
|
||||
"background": "Aangepaste Achtergrond Instellen (link of afbeeldingsbestand)",
|
||||
"theme": "Thema instellen",
|
||||
"patch_metadata": "Metadata Automatisch Bijwerken",
|
||||
"use_proxy": "Gebruik Interne Proxy"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Download Stabiele Gegevens Van Grasscutter",
|
||||
"grasscutter_latest_data": "Download De Nieuwste Gegevens Van Grasscutter",
|
||||
"grasscutter_stable_data_update": "Stabiele gegevens Van Grasscutter bijwerken",
|
||||
"grasscutter_latest_data_update": "Nieuwste gegevens Van Grasscutter bijwerken",
|
||||
"grasscutter_stable": "Download Stabiele Versie Van Grasscutter",
|
||||
"grasscutter_latest": "Download De Nieuwste Versie Van Grasscutter",
|
||||
"grasscutter_stable_update": "Update Grasscutter Naar De Stabiele Versie",
|
||||
"grasscutter_latest_update": "Update Grasscutter Naar De Nieuwste Versie",
|
||||
"resources": "Download Grasscutter bronnen",
|
||||
"game": "Download Spel"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Aan Het Downloading",
|
||||
"extracting": "Uitpakken",
|
||||
"error": "Fout",
|
||||
"finished": "Voltooid",
|
||||
"stopped": "Gestopt"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Select file or folder...",
|
||||
"select_folder": "Select folder...",
|
||||
"download": "Download",
|
||||
"install": "Install"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Recente Opdrachten",
|
||||
"latest_version": "Nieuwste Versie"
|
||||
},
|
||||
"help": {
|
||||
"port_help_text": "Zorg ervoor dat dit de Dispatch server poort is, niet de Game server poort. Dit is bijna altijd '443'.",
|
||||
"game_help_text": "U hoeft geen aparte kopie te gebruiken om met Grasscutter te spelen. Dit is voor downgraden naar 2.6 of als u het spel niet geinstalleerd heeft.",
|
||||
"gc_stable_jar": "Download de huidige stabiele Grasscutter build, die jar file en data bestanden bevat.",
|
||||
"gc_dev_jar": "Download de laatste ontwikkeling Grasscutter build, die jar file en data bestanden bevat.",
|
||||
"gc_stable_data": "Download de huidige stabiele versie van de Grasscutter data bestanden, die niet met een jar file komen. Dit is handig voor het bijwerken.",
|
||||
"gc_dev_data": "Download de nieuwste versie van de Grasscutter data bestanden, die niet met een jar file komen. Dit is handig voor het bijwerken.",
|
||||
"encryption": "Dit wordt meestal uitgeschakeld.",
|
||||
"resources": "Deze zijn ook nodig om een Grasscutter server te draaien. Deze knop zal grijs zijn als u een bestaande resources map heeft met inhoud erin",
|
||||
"emergency_metadata": "Voor het geval er iets fout is gegaan, herstel uw metadata naar de laatste offici<63>le versies metadata.",
|
||||
"use_proxy": "Gebruik de Cultivation interne proxy. U zou dit ingeschakeld moeten hebben, tenzij u iets als Fiddler gebruikt",
|
||||
"patch_metadata": "Patch en unpatch je spel metadata automatisch. Tenzij je met oude/niet-offici<63>le versies speelt, of je hebt je metadata handmatig gepatcht, zou dit ingeschakeld moeten zijn."
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Stel Akebi Exe Bestand In",
|
||||
"migoto": "Stel 3DMigoto Exe Bestand In",
|
||||
"reshade": "Stel Reshade Injector In"
|
||||
}
|
||||
}
|
||||
@@ -13,14 +13,19 @@
|
||||
"options": {
|
||||
"enabled": "Включено",
|
||||
"disabled": "Выключено",
|
||||
"game_path": "Установить путь к файлам игры",
|
||||
"game_executable": "Установить исполняемый файл игры",
|
||||
"recover_metadata": "Принудительное восстановление Метаданных",
|
||||
"grasscutter_jar": "Установить Grasscutter JAR",
|
||||
"toggle_encryption": "Переключить шифрование",
|
||||
"install_certificate": "Установить сертификат для работы Прокси",
|
||||
"java_path": "Установить пользовательский путь Java",
|
||||
"grasscutter_with_game": "Автоматически запускать Grasscutter вместе с игрой",
|
||||
"language": "Установить язык",
|
||||
"background": "Установить свой фон (ссылка или файл)",
|
||||
"theme": "Установить тему"
|
||||
"theme": "Установить тему",
|
||||
"patch_metadata": "Автоматический патч Метаданных при запуске",
|
||||
"use_proxy": "Использовать встроенный Прокси"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
|
||||
@@ -31,7 +36,8 @@
|
||||
"grasscutter_latest": "Скачать последнюю версию Grasscutter",
|
||||
"grasscutter_stable_update": "Обновить стабильную версию Grasscutter",
|
||||
"grasscutter_latest_update": "Обновить последнюю версию Grasscutter",
|
||||
"resources": "Скачать ресурсы Grasscutter"
|
||||
"resources": "Скачать ресурсы Grasscutter",
|
||||
"game": "Скачать Игру"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Скачивание",
|
||||
@@ -43,7 +49,8 @@
|
||||
"components": {
|
||||
"select_file": "Выберите файл или папку...",
|
||||
"select_folder": "Выберите папку...",
|
||||
"download": "Скачать"
|
||||
"download": "Скачать",
|
||||
"install": "Установить"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Последние коммиты",
|
||||
@@ -56,6 +63,18 @@
|
||||
"gc_dev_jar": "Скачать последнюю версию для разработки Grasscutter, которая содержит jar файл и данные.",
|
||||
"gc_stable_data": "Скачать стабильные данные Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
||||
"gc_dev_data": "Скачать последнюю версию для разработки Grasscutter, в которой нету jar файла. Это полезно для обновления.",
|
||||
"resources": "Это необходимо для запуска сервера Grasscutter. Эта кнопка будет серой, если у Вас уже есть не пустая папка с ресурсами."
|
||||
"encryption": "Обычно это должно быть выключено.",
|
||||
"resources": "Это необходимо для запуска сервера Grasscutter. Эта кнопка будет серой, если у Вас уже есть не пустая папка с ресурсами.",
|
||||
"emergency_metadata": "Если что-то пошло не так, восстановит Метаданные до последней официальной версии.",
|
||||
"use_proxy": "Использовать встроенный Прокси. Отключите если используете Fiddler или подобную программу",
|
||||
"patch_metadata": "Патчит и восстанавливает Метаданные автоматически. Если вы не играете на старых/модифицированых версиях, или сами в ручную патчите Метаданные, эта опция должна быть включена."
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Путь к исполняемому файлу Akebi",
|
||||
"migoto": "Путь к исполняемому файлу 3DMigoto ",
|
||||
"reshade": "Путь к инжектору Reshade"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,18 +14,21 @@
|
||||
"enabled": "Bật",
|
||||
"disabled": "Tắt",
|
||||
"game_path": "Đường dẫn cài game",
|
||||
"game_command": "Lệnh khởi chạy game",
|
||||
"game_executable": "Tập tin thực thi game",
|
||||
"recover_metadata": "Khôi phục Metadata khẩn cấp",
|
||||
"recover_metadata": "Khôi phục metadata khẩn cấp",
|
||||
"grasscutter_jar": "Tập tin JAR Grasscutter",
|
||||
"toggle_encryption": "Bật/tắt mã hóa",
|
||||
"install_certificate": "Cài chứng chỉ proxy",
|
||||
"java_path": "Đường dẫn Java tùy chỉnh",
|
||||
"grasscutter_with_game": "Tự động chạy Grasscutter cùng với game",
|
||||
"language": "Chọn ngôn ngữ",
|
||||
"language": "Ngôn ngữ",
|
||||
"background": "Hình nền tùy chỉnh (liên kết hoặc tập tin hình ảnh)",
|
||||
"theme": "Giao diện",
|
||||
"patch_metadata": "Tự động sửa Metadata",
|
||||
"use_proxy": "Sử dụng proxy nội bộ"
|
||||
"patch_metadata": "Tự động vá metadata",
|
||||
"use_proxy": "Sử dụng proxy nội bộ",
|
||||
"wipe_login": "Tẩy sạch cache đăng nhập",
|
||||
"horny_mode": "Chế độ hứng tình"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_stable_data": "Tải dữ liệu Grasscutter bản ổn định",
|
||||
@@ -40,17 +43,17 @@
|
||||
"game": "Tải game"
|
||||
},
|
||||
"download_status": {
|
||||
"downloading": "Đang tải xuống",
|
||||
"downloading": "Đang tải",
|
||||
"extracting": "Đang giải nén",
|
||||
"error": "Lỗi",
|
||||
"finished": "Hoàn thành",
|
||||
"finished": "Đã hoàn thành",
|
||||
"stopped": "Đã dừng"
|
||||
},
|
||||
"components": {
|
||||
"select_file": "Chọn tập tin hoặc thư mục...",
|
||||
"select_folder": "Chọn thư mục...",
|
||||
"download": "Tải xuống",
|
||||
"install": "Cài đặt"
|
||||
"download": "Tải",
|
||||
"install": "Cài"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Thay Đổi Gần Đây",
|
||||
@@ -62,11 +65,19 @@
|
||||
"gc_stable_jar": "Tải xuống phiên bản ổn định của Grasscutter, bao gồm tập tin jar và các tệp dữ liệu.",
|
||||
"gc_dev_jar": "Tải xuống phiên bản phát triển mới nhất của Grasscutter, bao gồm tập tin jar và các tệp dữ liệu.",
|
||||
"gc_stable_data": "Tải xuống tệp dữ liệu phiên bản ổn định hiện hành của Grasscutter. Bản này không đi kèm với tập tin jar, hữu dụng khi muốn cập nhật.",
|
||||
"gc_dev_data": "Tải xuống tệp dữ liệu phiên bản mới nhất của Grasscutter. Bản này không đi kèm với tập tin jar, hữu dụng khi muốn cập nhật.",
|
||||
"resources": "Chúng được yêu cầu để chạy máy chủ Grasscutter. Nút này sẽ có màu xám nếu bạn đã có sẵn một thư mục tài nguyên có nội dung bên trong"
|
||||
"gc_dev_data": "Tải xuống tệp dữ liệu phiên bản mới nhất của Grasscutter. Bản này không đi kèm với tập tin jar, hữu dụng khi muốn cập nhật.",
|
||||
"encryption": "Mục này nên được tắt.",
|
||||
"resources": "Tài nguyên được yêu cầu để chạy máy chủ Grasscutter. Nút này sẽ có màu xám nếu bạn đã có sẵn một thư mục tài nguyên (resources) có nội dung bên trong",
|
||||
"emergency_metadata": "Trong trường hợp gặp vấn đề, khôi phục lại metadata về phiên bản chính thức mới nhất.",
|
||||
"use_proxy": "Sử dụng proxy nội bộ của Cultivation. Nên bật tùy chọn này trừ khi bạn đang sử dụng một ứng dụng khác, như Fiddler",
|
||||
"patch_metadata": "Tự động vá và sửa lại metadata của game. Tùy chọn này nên được bật trừ khi bạn đang sử dụng phiên bản cũ, phiên bản không chính thức hoặc bạn đã tự vá metadata rồi."
|
||||
},
|
||||
"swag": {
|
||||
"akebi_name": "Akebi",
|
||||
"migoto_name": "Migoto",
|
||||
"reshade_name": "Reshade",
|
||||
"akebi": "Tập tin thực thi Akebi",
|
||||
"migoto": "Tập tin thực thi 3dMigoto"
|
||||
"migoto": "Tập tin thực thi 3dMigoto",
|
||||
"reshade": "Tập tin inject Reshade"
|
||||
}
|
||||
}
|
||||
|
||||
20
src-tauri/src/admin.rs
Normal file
20
src-tauri/src/admin.rs
Normal file
@@ -0,0 +1,20 @@
|
||||
#[cfg(windows)]
|
||||
pub fn reopen_as_admin() {
|
||||
let install = std::env::current_exe().unwrap();
|
||||
|
||||
println!("Opening as admin: {}", install.to_str().unwrap());
|
||||
|
||||
Command::new("powershell.exe")
|
||||
.arg("powershell")
|
||||
.arg(format!(
|
||||
"-command \"&{{Start-Process -filepath '{}' -verb runas}}\"",
|
||||
install.to_str().unwrap()
|
||||
))
|
||||
.spawn()
|
||||
.expect("Error starting exec as admin");
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn reopen_as_admin() {}
|
||||
@@ -1,6 +1,7 @@
|
||||
use file_diff::diff;
|
||||
use std::fs;
|
||||
use std::io::{Read, Write};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[tauri::command]
|
||||
pub fn rename(path: String, new_name: String) {
|
||||
@@ -16,9 +17,16 @@ pub fn rename(path: String, new_name: String) {
|
||||
new_path = path.replace('\\', "/");
|
||||
}
|
||||
|
||||
let path_replaced = &path.replace(&new_path.split('/').last().unwrap(), &new_name);
|
||||
let path_replaced = &path.replace(new_path.split('/').last().unwrap(), &new_name);
|
||||
|
||||
fs::rename(path, &path_replaced).unwrap();
|
||||
match fs::rename(&path, path_replaced) {
|
||||
Ok(_) => {
|
||||
println!("Renamed {} to {}", &path, path_replaced);
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error: {}", e);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -28,19 +36,19 @@ pub fn dir_create(path: String) {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_exists(path: &str) -> bool {
|
||||
let path_buf = std::path::PathBuf::from(path);
|
||||
let path_buf = PathBuf::from(path);
|
||||
fs::metadata(path_buf).is_ok()
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_is_empty(path: &str) -> bool {
|
||||
let path_buf = std::path::PathBuf::from(path);
|
||||
let path_buf = PathBuf::from(path);
|
||||
fs::read_dir(path_buf).unwrap().count() == 0
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn dir_delete(path: &str) {
|
||||
let path_buf = std::path::PathBuf::from(path);
|
||||
let path_buf = PathBuf::from(path);
|
||||
fs::remove_dir_all(path_buf).unwrap();
|
||||
}
|
||||
|
||||
@@ -52,16 +60,15 @@ pub fn are_files_identical(path1: &str, path2: &str) -> bool {
|
||||
#[tauri::command]
|
||||
pub fn copy_file(path: String, new_path: String) -> bool {
|
||||
let filename = &path.split('/').last().unwrap();
|
||||
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
||||
let path_buf = std::path::PathBuf::from(&path);
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
// If the new path doesn't exist, create it.
|
||||
if !dir_exists(new_path_buf.pop().to_string().as_str()) {
|
||||
if !dir_exists(PathBuf::from(&new_path).pop().to_string().as_str()) {
|
||||
std::fs::create_dir_all(&new_path).unwrap();
|
||||
}
|
||||
|
||||
// Copy old to new
|
||||
match std::fs::copy(&path_buf, format!("{}/{}", new_path, filename)) {
|
||||
match std::fs::copy(path_buf, format!("{}/{}", new_path, filename)) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
println!("Failed to copy file: {}", e);
|
||||
@@ -74,11 +81,11 @@ pub fn copy_file(path: String, new_path: String) -> bool {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn copy_file_with_new_name(path: String, new_path: String, new_name: String) -> bool {
|
||||
let mut new_path_buf = std::path::PathBuf::from(&new_path);
|
||||
let path_buf = std::path::PathBuf::from(&path);
|
||||
let mut new_path_buf = PathBuf::from(&new_path);
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
// If the new path doesn't exist, create it.
|
||||
if !dir_exists(new_path_buf.pop().to_string().as_str()) {
|
||||
if !dir_exists(PathBuf::from(&new_path).pop().to_string().as_str()) {
|
||||
match std::fs::create_dir_all(&new_path) {
|
||||
Ok(_) => {}
|
||||
Err(e) => {
|
||||
@@ -88,8 +95,10 @@ pub fn copy_file_with_new_name(path: String, new_path: String, new_name: String)
|
||||
};
|
||||
}
|
||||
|
||||
new_path_buf.push(new_name);
|
||||
|
||||
// Copy old to new
|
||||
match std::fs::copy(&path_buf, format!("{}/{}", new_path, new_name)) {
|
||||
match std::fs::copy(path_buf, &new_path_buf) {
|
||||
Ok(_) => true,
|
||||
Err(e) => {
|
||||
println!("Failed to copy file: {}", e);
|
||||
@@ -102,7 +111,7 @@ pub fn copy_file_with_new_name(path: String, new_path: String, new_name: String)
|
||||
|
||||
#[tauri::command]
|
||||
pub fn delete_file(path: String) -> bool {
|
||||
let path_buf = std::path::PathBuf::from(&path);
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
match std::fs::remove_file(path_buf) {
|
||||
Ok(_) => true,
|
||||
@@ -117,7 +126,7 @@ pub fn delete_file(path: String) -> bool {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn read_file(path: String) -> String {
|
||||
let path_buf = std::path::PathBuf::from(&path);
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
let mut file = match fs::File::open(path_buf) {
|
||||
Ok(file) => file,
|
||||
@@ -135,10 +144,10 @@ pub fn read_file(path: String) -> String {
|
||||
|
||||
#[tauri::command]
|
||||
pub fn write_file(path: String, contents: String) {
|
||||
let path_buf = std::path::PathBuf::from(&path);
|
||||
let path_buf = PathBuf::from(&path);
|
||||
|
||||
// Create file if it exists, otherwise just open and rewrite
|
||||
let mut file = match fs::File::create(&path_buf) {
|
||||
let mut file = match fs::File::create(path_buf) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open file: {}", e);
|
||||
|
||||
@@ -9,23 +9,19 @@ static SITE_URL: &str = "https://gamebanana.com";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_download_links(mod_id: String) -> String {
|
||||
let res = web::query(format!("{}/apiv9/Mod/{}/DownloadPage", SITE_URL, mod_id).as_str()).await;
|
||||
|
||||
res
|
||||
web::query(format!("{}/apiv9/Mod/{}/DownloadPage", SITE_URL, mod_id).as_str()).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn list_submissions(mode: String) -> String {
|
||||
let res = web::query(
|
||||
pub async fn list_submissions(mode: String, page: String) -> String {
|
||||
web::query(
|
||||
format!(
|
||||
"{}/apiv9/Util/Game/Submissions?_idGameRow=8552&_nPage=1&_nPerpage=50&_sMode={}",
|
||||
SITE_URL, mode
|
||||
"{}/apiv9/Util/Game/Submissions?_idGameRow=8552&_nPage={}&_nPerpage=50&_sMode={}",
|
||||
SITE_URL, page, mode
|
||||
)
|
||||
.as_str(),
|
||||
)
|
||||
.await;
|
||||
|
||||
res
|
||||
.await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
|
||||
@@ -9,7 +9,7 @@ pub async fn get_lang(window: tauri::Window, lang: String) -> String {
|
||||
let lang_path: PathBuf = [&install_location(), "lang", &format!("{}.json", lang)]
|
||||
.iter()
|
||||
.collect();
|
||||
match std::fs::read_to_string(&lang_path) {
|
||||
match std::fs::read_to_string(lang_path) {
|
||||
Ok(x) => x,
|
||||
Err(e) => {
|
||||
emit_lang_err(window, format!("Failed to read language file: {}", e));
|
||||
|
||||
@@ -2,71 +2,139 @@
|
||||
all(not(debug_assertions), target_os = "windows"),
|
||||
windows_subsystem = "windows"
|
||||
)]
|
||||
#![deny(clippy::all, unused)]
|
||||
|
||||
use file_helpers::dir_exists;
|
||||
use once_cell::sync::Lazy;
|
||||
use std::fs;
|
||||
use std::io::Write;
|
||||
use std::{collections::HashMap, sync::Mutex};
|
||||
use system_helpers::is_elevated;
|
||||
use tauri::api::path::data_dir;
|
||||
use tauri::async_runtime::block_on;
|
||||
|
||||
use std::thread;
|
||||
use structs::APIQuery;
|
||||
use sysinfo::{System, SystemExt};
|
||||
|
||||
#[cfg(windows)]
|
||||
use crate::admin::reopen_as_admin;
|
||||
|
||||
mod admin;
|
||||
mod downloader;
|
||||
mod file_helpers;
|
||||
mod gamebanana;
|
||||
mod lang;
|
||||
mod metadata_patcher;
|
||||
mod proxy;
|
||||
mod structs;
|
||||
mod system_helpers;
|
||||
mod unzip;
|
||||
mod web;
|
||||
|
||||
static WATCH_GAME_PROCESS: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new(String::new()));
|
||||
|
||||
fn try_flush() {
|
||||
std::io::stdout().flush().unwrap_or(())
|
||||
}
|
||||
|
||||
fn has_arg(args: &[String], arg: &str) -> bool {
|
||||
args.contains(&arg.to_string())
|
||||
}
|
||||
|
||||
async fn arg_handler(args: &[String]) {
|
||||
if has_arg(args, "--proxy") {
|
||||
let mut pathbuf = data_dir().unwrap();
|
||||
pathbuf.push("cultivation");
|
||||
pathbuf.push("ca");
|
||||
|
||||
connect(8035, pathbuf.to_str().unwrap().to_string()).await;
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
enable_process_watcher,
|
||||
connect,
|
||||
disconnect,
|
||||
req_get,
|
||||
get_bg_file,
|
||||
is_game_running,
|
||||
get_theme_list,
|
||||
system_helpers::run_command,
|
||||
system_helpers::run_program,
|
||||
system_helpers::run_program_relative,
|
||||
system_helpers::run_jar,
|
||||
system_helpers::open_in_browser,
|
||||
system_helpers::install_location,
|
||||
system_helpers::is_elevated,
|
||||
proxy::set_proxy_addr,
|
||||
proxy::generate_ca_files,
|
||||
unzip::unzip,
|
||||
file_helpers::rename,
|
||||
file_helpers::dir_create,
|
||||
file_helpers::dir_exists,
|
||||
file_helpers::dir_is_empty,
|
||||
file_helpers::dir_delete,
|
||||
file_helpers::copy_file,
|
||||
file_helpers::copy_file_with_new_name,
|
||||
file_helpers::delete_file,
|
||||
file_helpers::are_files_identical,
|
||||
file_helpers::read_file,
|
||||
file_helpers::write_file,
|
||||
downloader::download_file,
|
||||
downloader::stop_download,
|
||||
lang::get_lang,
|
||||
lang::get_languages,
|
||||
web::valid_url,
|
||||
web::web_get,
|
||||
gamebanana::get_download_links,
|
||||
gamebanana::list_submissions,
|
||||
gamebanana::list_mods,
|
||||
metadata_patcher::patch_metadata
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
let args: Vec<String> = std::env::args().collect();
|
||||
|
||||
if !is_elevated() && !has_arg(&args, "--no-admin") {
|
||||
println!("===============================================================================");
|
||||
println!("You running as a non-elevated user. Some stuff will almost definitely not work.");
|
||||
println!("===============================================================================");
|
||||
|
||||
#[cfg(windows)]
|
||||
reopen_as_admin();
|
||||
}
|
||||
|
||||
// Setup datadir/cultivation just in case something went funky and it wasn't made
|
||||
if !dir_exists(data_dir().unwrap().join("cultivation").to_str().unwrap()) {
|
||||
fs::create_dir_all(data_dir().unwrap().join("cultivation")).unwrap();
|
||||
}
|
||||
|
||||
// Always set CWD to the location of the executable.
|
||||
let mut exe_path = std::env::current_exe().unwrap();
|
||||
exe_path.pop();
|
||||
std::env::set_current_dir(&exe_path).unwrap();
|
||||
|
||||
block_on(arg_handler(&args));
|
||||
|
||||
// For disabled GUI
|
||||
ctrlc::set_handler(|| {
|
||||
disconnect();
|
||||
std::process::exit(0);
|
||||
})
|
||||
.unwrap_or(());
|
||||
|
||||
if !has_arg(&args, "--no-gui") {
|
||||
tauri::Builder::default()
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
enable_process_watcher,
|
||||
connect,
|
||||
disconnect,
|
||||
req_get,
|
||||
is_game_running,
|
||||
get_theme_list,
|
||||
system_helpers::run_command,
|
||||
system_helpers::run_program,
|
||||
system_helpers::run_program_relative,
|
||||
system_helpers::run_jar,
|
||||
system_helpers::open_in_browser,
|
||||
system_helpers::install_location,
|
||||
system_helpers::is_elevated,
|
||||
system_helpers::set_migoto_target,
|
||||
system_helpers::wipe_registry,
|
||||
system_helpers::get_platform,
|
||||
proxy::set_proxy_addr,
|
||||
proxy::generate_ca_files,
|
||||
unzip::unzip,
|
||||
file_helpers::rename,
|
||||
file_helpers::dir_create,
|
||||
file_helpers::dir_exists,
|
||||
file_helpers::dir_is_empty,
|
||||
file_helpers::dir_delete,
|
||||
file_helpers::copy_file,
|
||||
file_helpers::copy_file_with_new_name,
|
||||
file_helpers::delete_file,
|
||||
file_helpers::are_files_identical,
|
||||
file_helpers::read_file,
|
||||
file_helpers::write_file,
|
||||
downloader::download_file,
|
||||
downloader::stop_download,
|
||||
lang::get_lang,
|
||||
lang::get_languages,
|
||||
web::valid_url,
|
||||
web::web_get,
|
||||
gamebanana::get_download_links,
|
||||
gamebanana::list_submissions,
|
||||
gamebanana::list_mods,
|
||||
metadata_patcher::patch_metadata
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
} else {
|
||||
try_flush();
|
||||
println!("Press enter or CTRL-C twice to quit...");
|
||||
std::io::stdin().read_line(&mut String::new()).unwrap();
|
||||
}
|
||||
|
||||
// Always disconnect upon closing the program
|
||||
disconnect();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -88,6 +156,9 @@ fn enable_process_watcher(window: tauri::Window, process: String) {
|
||||
println!("Starting process watcher...");
|
||||
|
||||
thread::spawn(move || {
|
||||
// Initial sleep for 8 seconds, since running 20 different injectors or whatever can take a while
|
||||
std::thread::sleep(std::time::Duration::from_secs(10));
|
||||
|
||||
let mut system = System::new_all();
|
||||
|
||||
loop {
|
||||
@@ -180,53 +251,3 @@ async fn get_theme_list(data_dir: String) -> Vec<HashMap<String, String>> {
|
||||
|
||||
themes
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
// TODO: Replace with downloading the background file & saving it.
|
||||
async fn get_bg_file(bg_path: String, appdata: String) -> String {
|
||||
let copy_loc = appdata;
|
||||
let query = web::query("https://api.grasscutter.io/cultivation/query").await;
|
||||
let response_data: APIQuery = match serde_json::from_str(&query) {
|
||||
Ok(data) => data,
|
||||
Err(e) => {
|
||||
println!("Failed to parse response: {}", e);
|
||||
return "".to_string();
|
||||
}
|
||||
};
|
||||
|
||||
let file_name = response_data.bg_file.to_string();
|
||||
|
||||
// First we see if the file already exists in our local bg folder.
|
||||
if file_helpers::dir_exists(format!("{}\\bg\\{}", copy_loc, file_name).as_str()) {
|
||||
return format!("{}\\{}", copy_loc, response_data.bg_file.as_str());
|
||||
}
|
||||
|
||||
// Now we check if the bg folder, which is one directory above the game_path, exists.
|
||||
let bg_img_path = format!("{}\\{}", &bg_path, &file_name);
|
||||
|
||||
// If it doesn't, then we do not have backgrounds to grab.
|
||||
if !file_helpers::dir_exists(&bg_path) {
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
// BG folder does exist, lets see if the image exists.
|
||||
if !file_helpers::dir_exists(&bg_img_path) {
|
||||
// Image doesn't exist
|
||||
return "".to_string();
|
||||
}
|
||||
|
||||
// The image exists, lets copy it to our local '\bg' folder.
|
||||
let bg_img_path_local = format!("{}\\bg\\{}", copy_loc, file_name.as_str());
|
||||
|
||||
match std::fs::copy(bg_img_path, bg_img_path_local) {
|
||||
Ok(_) => {
|
||||
// Copy was successful, lets return true.
|
||||
format!("{}\\{}", copy_loc, response_data.bg_file)
|
||||
}
|
||||
Err(e) => {
|
||||
// Copy failed, lets return false
|
||||
println!("Failed to copy background image: {}", e);
|
||||
"".to_string()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
use regex::Regex;
|
||||
use std::fs::File;
|
||||
use std::fs::OpenOptions;
|
||||
use std::io::Read;
|
||||
use std::io::Write;
|
||||
use std::{fs, fs::File, fs::OpenOptions, io::Read, io::Write, path::Path};
|
||||
|
||||
// For these two functions, a non-zero return value indicates failure.
|
||||
extern "C" {
|
||||
@@ -13,6 +10,12 @@ extern "C" {
|
||||
#[tauri::command]
|
||||
pub fn patch_metadata(metadata_folder: &str) -> bool {
|
||||
let metadata_file = &(metadata_folder.to_owned() + "\\global-metadata-unpatched.dat");
|
||||
// check if metadata_file exists
|
||||
if !Path::new(metadata_file).exists() {
|
||||
println!("Metadata file not found");
|
||||
return false;
|
||||
}
|
||||
|
||||
println!("Patching metadata file: {}", metadata_file);
|
||||
let decrypted = decrypt_metadata(metadata_file);
|
||||
if do_vecs_match(&decrypted, &Vec::new()) {
|
||||
@@ -36,7 +39,7 @@ pub fn patch_metadata(metadata_folder: &str) -> bool {
|
||||
let mut file = match OpenOptions::new()
|
||||
.create(true)
|
||||
.write(true)
|
||||
.open(&(metadata_folder.to_owned() + "\\global-metadata-patched.dat"))
|
||||
.open(metadata_folder.to_owned() + "\\global-metadata-patched.dat")
|
||||
{
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
@@ -111,15 +114,20 @@ fn replace_keys(data: &[u8]) -> Vec<u8> {
|
||||
fn replace_rsa_key(old_data: &str, to_replace: &str, file_name: &str) -> String {
|
||||
// Read dispatch key file
|
||||
unsafe {
|
||||
let mut new_key_file = match File::open(&("keys/".to_owned() + file_name)) {
|
||||
// Get key path from current directory
|
||||
let key_file_path = std::env::current_dir()
|
||||
.unwrap()
|
||||
.join("keys")
|
||||
.join(file_name);
|
||||
|
||||
let key_data = match fs::read(&key_file_path) {
|
||||
Ok(file) => file,
|
||||
Err(e) => {
|
||||
println!("Failed to open keys/{}: {}", file_name, e);
|
||||
println!("Failed to open {}: {}", key_file_path.to_str().unwrap(), e);
|
||||
return String::new();
|
||||
}
|
||||
};
|
||||
let mut key_data = Vec::new();
|
||||
new_key_file.read_to_end(&mut key_data).unwrap();
|
||||
|
||||
let new_key = String::from_utf8_unchecked(key_data.to_vec());
|
||||
|
||||
// Replace old key with new key
|
||||
@@ -129,6 +137,7 @@ fn replace_rsa_key(old_data: &str, to_replace: &str, file_name: &str) -> String
|
||||
|
||||
fn encrypt_metadata(old_data: &[u8]) -> Vec<u8> {
|
||||
let mut data = old_data.to_vec();
|
||||
|
||||
let success = unsafe { encrypt_global_metadata(data.as_mut_ptr(), data.len()) } == 0;
|
||||
if success {
|
||||
println!("Successfully encrypted global-metadata");
|
||||
@@ -140,6 +149,5 @@ fn encrypt_metadata(old_data: &[u8]) -> Vec<u8> {
|
||||
}
|
||||
|
||||
fn do_vecs_match<T: PartialEq>(a: &Vec<T>, b: &Vec<T>) -> bool {
|
||||
let matching = a.iter().zip(b.iter()).filter(|&(a, b)| a == b).count();
|
||||
matching == a.len() && matching == b.len()
|
||||
a == b
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@
|
||||
* https://github.com/omjadas/hudsucker/blob/main/examples/log.rs
|
||||
*/
|
||||
|
||||
#[cfg(target_os = "linux")]
|
||||
use crate::system_helpers::run_command;
|
||||
|
||||
use once_cell::sync::Lazy;
|
||||
use std::{str::FromStr, sync::Mutex};
|
||||
use std::{path::PathBuf, str::FromStr, sync::Mutex};
|
||||
|
||||
use hudsucker::{
|
||||
async_trait::async_trait,
|
||||
@@ -19,7 +22,7 @@ use std::net::SocketAddr;
|
||||
use std::path::Path;
|
||||
|
||||
use rustls_pemfile as pemfile;
|
||||
use tauri::http::Uri;
|
||||
use tauri::{api::path::data_dir, http::Uri};
|
||||
|
||||
#[cfg(windows)]
|
||||
use registry::{Data, Hive, Security};
|
||||
@@ -49,12 +52,13 @@ impl HttpHandler for ProxyHandler {
|
||||
mut request: Request<Body>,
|
||||
) -> RequestOrResponse {
|
||||
let uri = request.uri().to_string();
|
||||
let uri_path = request.uri().path();
|
||||
let uri_path_and_query = request.uri().path_and_query().unwrap().as_str();
|
||||
|
||||
if uri.contains("hoyoverse.com") || uri.contains("mihoyo.com") || uri.contains("yuanshen.com") {
|
||||
// Create new URI.
|
||||
let new_uri =
|
||||
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path).as_str()).unwrap();
|
||||
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path_and_query).as_str())
|
||||
.unwrap();
|
||||
// Set request URI to the new one.
|
||||
*request.uri_mut() = new_uri;
|
||||
}
|
||||
@@ -75,11 +79,32 @@ impl HttpHandler for ProxyHandler {
|
||||
* Starts an HTTP(S) proxy server.
|
||||
*/
|
||||
pub async fn create_proxy(proxy_port: u16, certificate_path: String) {
|
||||
let cert_path = PathBuf::from(certificate_path);
|
||||
let pk_path = cert_path.join("private.key");
|
||||
let ca_path = cert_path.join("cert.crt");
|
||||
|
||||
// Get the certificate and private key.
|
||||
let mut private_key_bytes: &[u8] =
|
||||
&fs::read(format!("{}\\private.key", certificate_path)).expect("Could not read private key");
|
||||
let mut ca_cert_bytes: &[u8] =
|
||||
&fs::read(format!("{}\\cert.crt", certificate_path)).expect("Could not read certificate");
|
||||
let mut private_key_bytes: &[u8] = &match fs::read(&pk_path) {
|
||||
// Try regenerating the CA stuff and read it again. If that doesn't work, quit.
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
println!("Encountered {}. Regenerating CA cert and retrying...", e);
|
||||
generate_ca_files(&data_dir().unwrap().join("cultivation"));
|
||||
|
||||
fs::read(&pk_path).expect("Could not read private key")
|
||||
}
|
||||
};
|
||||
|
||||
let mut ca_cert_bytes: &[u8] = &match fs::read(&ca_path) {
|
||||
// Try regenerating the CA stuff and read it again. If that doesn't work, quit.
|
||||
Ok(b) => b,
|
||||
Err(e) => {
|
||||
println!("Encountered {}. Regenerating CA cert and retrying...", e);
|
||||
generate_ca_files(&data_dir().unwrap().join("cultivation"));
|
||||
|
||||
fs::read(&ca_path).expect("Could not read certificate")
|
||||
}
|
||||
};
|
||||
|
||||
// Parse the private key and certificate.
|
||||
let private_key = rustls::PrivateKey(
|
||||
@@ -138,9 +163,29 @@ pub fn connect_to_proxy(proxy_port: u16) {
|
||||
println!("Connected to the proxy.");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(unix)]
|
||||
pub fn connect_to_proxy(proxy_port: u16) {
|
||||
// Edit /etc/environment to set $http_proxy and $https_proxy
|
||||
let mut env_file = match fs::read_to_string("/etc/environment") {
|
||||
Ok(f) => f,
|
||||
Err(e) => {
|
||||
println!("Error opening /etc/environment: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Append the proxy configuration.
|
||||
// We will not remove the current proxy config if it exists, so we can just remove these last lines when we disconnect
|
||||
env_file += format!("\nhttps_proxy=127.0.0.1:{}", proxy_port).as_str();
|
||||
env_file += format!("\nhttp_proxy=127.0.0.1:{}", proxy_port).as_str();
|
||||
|
||||
// Save
|
||||
fs::write("/etc/environment", env_file).unwrap();
|
||||
}
|
||||
|
||||
#[cfg(target_od = "macos")]
|
||||
pub fn connect_to_proxy(_proxy_port: u16) {
|
||||
println!("Connecting to the proxy is not implemented on this platform.");
|
||||
println!("No Mac support yet. Someone mail me a Macbook and I will do it B)")
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -162,7 +207,26 @@ pub fn disconnect_from_proxy() {
|
||||
println!("Disconnected from proxy.");
|
||||
}
|
||||
|
||||
#[cfg(not(windows))]
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn disconnect_from_proxy() {
|
||||
println!("Re-writing environment variables");
|
||||
|
||||
let regexp = regex::Regex::new(
|
||||
// This has to be specific as possible or we risk fuckin up their environment LOL
|
||||
r"(https|http)_proxy=.*127.0.0.1:.*",
|
||||
)
|
||||
.unwrap();
|
||||
let environment = &fs::read_to_string("/etc/environment").expect("Failed to open environment");
|
||||
|
||||
let new_environment = regexp.replace_all(environment, "").to_string();
|
||||
|
||||
// Write new environment
|
||||
fs::write("/etc/environment", new_environment.trim_end()).expect(
|
||||
"Could not write environment, remove proxy declarations manually if they are still set",
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(target_os = "macos")]
|
||||
pub fn disconnect_from_proxy() {}
|
||||
|
||||
/*
|
||||
@@ -260,11 +324,27 @@ pub fn install_ca_files(cert_path: &Path) {
|
||||
"/Library/Keychains/System.keychain",
|
||||
cert_path.to_str().unwrap(),
|
||||
],
|
||||
None,
|
||||
);
|
||||
println!("Installed certificate.");
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, target_os = "macos")))]
|
||||
// If this is borked on non-debian platforms, so be it
|
||||
#[cfg(target_os = "linux")]
|
||||
pub fn install_ca_files(cert_path: &Path) {
|
||||
let usr_certs = PathBuf::from("/usr/local/share/ca-certificates");
|
||||
let usr_cert_path = usr_certs.join("cultivation.crt");
|
||||
|
||||
// Create dir if it doesn't exist
|
||||
fs::create_dir_all(&usr_certs).expect("Unable to create local certificate directory");
|
||||
|
||||
fs::copy(cert_path, &usr_cert_path).expect("Unable to copy cert to local certificate directory");
|
||||
run_command("update-ca-certificates", vec![], None);
|
||||
|
||||
println!("Installed certificate.");
|
||||
}
|
||||
|
||||
#[cfg(not(any(windows, target_os = "macos", target_os = "linux")))]
|
||||
pub fn install_ca_files(_cert_path: &Path) {
|
||||
println!("Certificate installation is not supported on this platform.");
|
||||
}
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub(crate) struct APIQuery {
|
||||
pub bg_file: String,
|
||||
}
|
||||
@@ -1,9 +1,14 @@
|
||||
use duct::cmd;
|
||||
use ini::Ini;
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[cfg(windows)]
|
||||
use registry::{Data, Hive, Security};
|
||||
|
||||
#[tauri::command]
|
||||
pub fn run_program(path: String, args: Option<String>) {
|
||||
// Without unwrap_or, this can crash when UAC prompt is denied
|
||||
open::that(format!("{} {}", &path, &args.unwrap_or("".into()))).unwrap_or(());
|
||||
open::that(format!("{} {}", &path, &args.unwrap_or_else(|| "".into()))).unwrap_or(());
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -19,10 +24,10 @@ pub fn run_program_relative(path: String, args: Option<String>) {
|
||||
std::env::set_current_dir(&path_buf).unwrap();
|
||||
|
||||
// Without unwrap_or, this can crash when UAC prompt is denied
|
||||
open::that(format!("{} {}", &path, args.unwrap_or("".into()))).unwrap_or(());
|
||||
open::that(format!("{} {}", &path, args.unwrap_or_else(|| "".into()))).unwrap_or(());
|
||||
|
||||
// Restore the original working directory
|
||||
std::env::set_current_dir(&cwd).unwrap();
|
||||
std::env::set_current_dir(cwd).unwrap();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
@@ -47,7 +52,7 @@ pub fn run_command(program: &str, args: Vec<&str>, relative: Option<bool>) {
|
||||
cmd(prog, args).run().unwrap();
|
||||
|
||||
// Restore the original working directory
|
||||
std::env::set_current_dir(&cwd).unwrap();
|
||||
std::env::set_current_dir(cwd).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -88,6 +93,70 @@ pub fn install_location() -> String {
|
||||
return exe_path.to_str().unwrap().to_string();
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn set_migoto_target(path: String, migoto_path: String) -> bool {
|
||||
let pathbuf = PathBuf::from(path);
|
||||
let mut migoto_pathbuf = PathBuf::from(migoto_path);
|
||||
|
||||
migoto_pathbuf.pop();
|
||||
migoto_pathbuf.push("d3dx.ini");
|
||||
|
||||
let mut conf = match Ini::load_from_file(&migoto_pathbuf) {
|
||||
Ok(c) => {
|
||||
println!("Loaded migoto ini");
|
||||
c
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error loading migoto config: {}", e);
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
// Set options
|
||||
conf
|
||||
.with_section(Some("Loader"))
|
||||
.set("target", pathbuf.file_name().unwrap().to_str().unwrap());
|
||||
|
||||
// Write file
|
||||
match conf.write_to_file(&migoto_pathbuf) {
|
||||
Ok(_) => {
|
||||
println!("Wrote config!");
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Error writing config: {}", e);
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tauri::command]
|
||||
pub fn wipe_registry(exec_name: String) {
|
||||
// Fetch the 'Internet Settings' registry key.
|
||||
let settings =
|
||||
match Hive::CurrentUser.open(format!("Software\\miHoYo\\{}", exec_name), Security::Write) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
println!("Error getting registry setting: {}", e);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
// Wipe login cache
|
||||
match settings.set_value(
|
||||
"MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810",
|
||||
&Data::String("".parse().unwrap()),
|
||||
) {
|
||||
Ok(_) => (),
|
||||
Err(e) => println!("Error wiping registry: {}", e),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[tauri::command]
|
||||
pub fn wipe_registry(_exec_name: String) {}
|
||||
|
||||
#[cfg(windows)]
|
||||
#[tauri::command]
|
||||
pub fn is_elevated() -> bool {
|
||||
@@ -99,3 +168,8 @@ pub fn is_elevated() -> bool {
|
||||
pub fn is_elevated() -> bool {
|
||||
sudo::check() == sudo::RunningAs::Root
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn get_platform() -> &'static str {
|
||||
std::env::consts::OS
|
||||
}
|
||||
|
||||
@@ -58,38 +58,35 @@ pub fn unzip(
|
||||
}
|
||||
};
|
||||
|
||||
full_path = new_path.clone();
|
||||
full_path = new_path;
|
||||
}
|
||||
|
||||
println!("Is rar file? {}", zipfile.ends_with(".rar"));
|
||||
|
||||
let mut name = "".into();
|
||||
let name;
|
||||
let success;
|
||||
|
||||
// If file ends in zip, OR is unknown, extract as zip, otherwise extract as rar
|
||||
if zipfile.ends_with(".rar") {
|
||||
extract_rar(
|
||||
&window,
|
||||
&zipfile,
|
||||
&f,
|
||||
&full_path,
|
||||
top_level.unwrap_or(false),
|
||||
);
|
||||
success = extract_rar(&zipfile, &f, &full_path, top_level.unwrap_or(true));
|
||||
|
||||
let archive = Archive::new(zipfile.clone());
|
||||
name = archive.list().unwrap().next().unwrap().unwrap().filename;
|
||||
} else {
|
||||
extract_zip(
|
||||
&window,
|
||||
&zipfile,
|
||||
&f,
|
||||
&full_path,
|
||||
top_level.unwrap_or(false),
|
||||
);
|
||||
success = extract_zip(&zipfile, &f, &full_path, top_level.unwrap_or(true));
|
||||
|
||||
// Get the name of the inenr file in the zip file
|
||||
let mut zip = zip::ZipArchive::new(&f).unwrap();
|
||||
let file = zip.by_index(0).unwrap();
|
||||
name = file.name().to_string().clone();
|
||||
name = file.name().to_string();
|
||||
}
|
||||
|
||||
if !success {
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
|
||||
res_hash.insert("path".to_string(), zipfile.to_string());
|
||||
|
||||
window.emit("download_error", &res_hash).unwrap();
|
||||
}
|
||||
|
||||
// If the contents is a jar file, emit that we have extracted a new jar file
|
||||
@@ -114,29 +111,21 @@ pub fn unzip(
|
||||
for entry in read_dir(&write_path).unwrap() {
|
||||
let entry = entry.unwrap();
|
||||
let entry_path = entry.path();
|
||||
if entry_path.is_dir() {
|
||||
if !dirs.contains(&entry_path) {
|
||||
new_dir = entry_path.to_str().unwrap().to_string();
|
||||
}
|
||||
if entry_path.is_dir() && !dirs.contains(&entry_path) {
|
||||
new_dir = entry_path.to_str().unwrap().to_string();
|
||||
}
|
||||
}
|
||||
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
res_hash.insert("file", zipfile.to_string());
|
||||
res_hash.insert("new_folder", new_dir.to_string());
|
||||
res_hash.insert("new_folder", new_dir);
|
||||
|
||||
window.emit("extract_end", &res_hash).unwrap();
|
||||
});
|
||||
}
|
||||
|
||||
fn extract_rar(
|
||||
window: &tauri::Window,
|
||||
rarfile: &String,
|
||||
_f: &File,
|
||||
full_path: &path::PathBuf,
|
||||
_top_level: bool,
|
||||
) {
|
||||
let archive = Archive::new(rarfile.clone());
|
||||
fn extract_rar(rarfile: &str, _f: &File, full_path: &path::Path, _top_level: bool) -> bool {
|
||||
let archive = Archive::new(rarfile.to_string());
|
||||
|
||||
let mut open_archive = archive
|
||||
.extract_to(full_path.to_str().unwrap().to_string())
|
||||
@@ -148,41 +137,29 @@ fn extract_rar(
|
||||
"Extracted rar file to: {}",
|
||||
full_path.to_str().unwrap_or("Error")
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to extract rar file: {}", e);
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
|
||||
res_hash.insert("error".to_string(), e.to_string());
|
||||
res_hash.insert("path".to_string(), rarfile.to_string());
|
||||
|
||||
window.emit("download_error", &res_hash).unwrap();
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn extract_zip(
|
||||
window: &tauri::Window,
|
||||
zipfile: &String,
|
||||
f: &File,
|
||||
full_path: &path::PathBuf,
|
||||
top_level: bool,
|
||||
) {
|
||||
fn extract_zip(_zipfile: &str, f: &File, full_path: &path::Path, top_level: bool) -> bool {
|
||||
match zip_extract::extract(f, full_path, top_level) {
|
||||
Ok(_) => {
|
||||
println!(
|
||||
"Extracted zip file to: {}",
|
||||
full_path.to_str().unwrap_or("Error")
|
||||
);
|
||||
|
||||
true
|
||||
}
|
||||
Err(e) => {
|
||||
println!("Failed to extract zip file: {}", e);
|
||||
let mut res_hash = std::collections::HashMap::new();
|
||||
|
||||
res_hash.insert("error".to_string(), e.to_string());
|
||||
res_hash.insert("path".to_string(), zipfile.to_string());
|
||||
|
||||
window.emit("download_error", &res_hash).unwrap();
|
||||
false
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"package": {
|
||||
"productName": "Cultivation",
|
||||
"version": "1.0.5"
|
||||
"version": "1.0.10"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
||||
@@ -5,7 +5,6 @@ import DownloadHandler from '../utils/download'
|
||||
import { getConfigOption } from '../utils/configuration'
|
||||
import { getTheme, loadTheme } from '../utils/themes'
|
||||
import { convertFileSrc, invoke } from '@tauri-apps/api/tauri'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { Main } from './Main'
|
||||
import { Mods } from './Mods'
|
||||
|
||||
@@ -28,10 +27,6 @@ class App extends React.Component<Readonly<unknown>, IState> {
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const game_exe = await getConfigOption('game_install_path')
|
||||
const game_path = game_exe?.substring(0, game_exe.replace(/\\/g, '/').lastIndexOf('/')) || ''
|
||||
const root_path = game_path?.substring(0, game_path.replace(/\\/g, '/').lastIndexOf('/')) || ''
|
||||
|
||||
// Load a theme if it exists
|
||||
const theme = await getConfigOption('theme')
|
||||
if (theme && theme !== 'default') {
|
||||
@@ -42,23 +37,7 @@ class App extends React.Component<Readonly<unknown>, IState> {
|
||||
// Get custom bg AFTER theme is loaded !! important !!
|
||||
const custom_bg = await getConfigOption('customBackground')
|
||||
|
||||
if (!custom_bg || !/png|jpg|jpeg$/.test(custom_bg)) {
|
||||
if (game_path) {
|
||||
// Get the bg by invoking, then set the background to that bg.
|
||||
const bgLoc: string = await invoke('get_bg_file', {
|
||||
bgPath: root_path,
|
||||
appdata: await dataDir(),
|
||||
})
|
||||
|
||||
bgLoc &&
|
||||
this.setState(
|
||||
{
|
||||
bgFile: bgLoc,
|
||||
},
|
||||
this.forceUpdate
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (custom_bg) {
|
||||
const isUrl = /^http(s)?:\/\//gm.test(custom_bg)
|
||||
|
||||
if (!isUrl) {
|
||||
|
||||
@@ -12,7 +12,7 @@ import Plus from '../../resources/icons/plus.svg'
|
||||
|
||||
import './ServerLaunchSection.css'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import { getGameExecutable } from '../../utils/game'
|
||||
import { getGameExecutable, getGameVersion } from '../../utils/game'
|
||||
import { patchGame, unpatchGame } from '../../utils/metadata'
|
||||
|
||||
interface IProps {
|
||||
@@ -110,6 +110,30 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
// Connect to proxy
|
||||
if (config.toggle_grasscutter) {
|
||||
if (config.patch_metadata) {
|
||||
const gameVersion = await getGameVersion()
|
||||
console.log(gameVersion)
|
||||
|
||||
if (gameVersion == null) {
|
||||
alert(
|
||||
'Game version could not be determined. Please make sure you have the game correctly selected and try again.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (gameVersion?.major == 2 && gameVersion?.minor < 8) {
|
||||
alert(
|
||||
'Game version is too old for metadata patching. Please disable metadata patching in the settings and try again.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
if (gameVersion?.major == 3 && gameVersion?.minor >= 1) {
|
||||
alert(
|
||||
'Game version is too new for metadata patching. Please disable metadata patching in the settings to launch the game.\nNOTE: You will require a UA patch to play the game.'
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const patched = await patchGame()
|
||||
|
||||
if (!patched) {
|
||||
@@ -161,6 +185,14 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
}
|
||||
}
|
||||
|
||||
if (config.wipe_login) {
|
||||
// First wipe registry if we have to
|
||||
await invoke('wipe_registry', {
|
||||
// The exe is always PascalCase so we can get the dir using regex
|
||||
execName: (await getGameExecutable())?.split('.exe')[0].replace(/([a-z\d])([A-Z])/g, '$1 $2'),
|
||||
})
|
||||
}
|
||||
|
||||
// Launch the program
|
||||
const gameExists = await invoke('dir_exists', {
|
||||
path: exe || config.game_install_path,
|
||||
|
||||
@@ -17,7 +17,7 @@ const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archiv
|
||||
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
|
||||
const STABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/stable/Grasscutter.zip'
|
||||
const DEV_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip'
|
||||
const RESOURCES_DOWNLOAD = 'https://gitlab.com/yukiz/GrasscutterResources/-/archive/2.8/GrasscutterResources-2.8.zip'
|
||||
const RESOURCES_DOWNLOAD = 'https://github.com/tamilpp25/Grasscutter_Resources/archive/refs/heads/3.0.zip'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void
|
||||
|
||||
@@ -4,7 +4,7 @@ import { dataDir } from '@tauri-apps/api/path'
|
||||
import DirInput from '../common/DirInput'
|
||||
import Menu from './Menu'
|
||||
import Tr, { getLanguages, translate } from '../../../utils/language'
|
||||
import { setConfigOption, getConfig, getConfigOption } from '../../../utils/configuration'
|
||||
import { setConfigOption, getConfig, getConfigOption, Configuration } from '../../../utils/configuration'
|
||||
import Checkbox from '../common/Checkbox'
|
||||
import Divider from './Divider'
|
||||
import { getThemeList } from '../../../utils/themes'
|
||||
@@ -15,6 +15,7 @@ import BigButton from '../common/BigButton'
|
||||
import DownloadHandler from '../../../utils/download'
|
||||
import * as meta from '../../../utils/metadata'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
import TextInput from '../common/TextInput'
|
||||
|
||||
interface IProps {
|
||||
closeFn: () => void
|
||||
@@ -34,7 +35,10 @@ interface IState {
|
||||
encryption: boolean
|
||||
patch_metadata: boolean
|
||||
use_internal_proxy: boolean
|
||||
wipe_login: boolean
|
||||
horny_mode: boolean
|
||||
swag: boolean
|
||||
platform: string
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: string
|
||||
@@ -59,7 +63,10 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
encryption: false,
|
||||
patch_metadata: false,
|
||||
use_internal_proxy: false,
|
||||
wipe_login: false,
|
||||
horny_mode: false,
|
||||
swag: false,
|
||||
platform: '',
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: '',
|
||||
@@ -76,19 +83,20 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
this.setCustomBackground = this.setCustomBackground.bind(this)
|
||||
this.toggleEncryption = this.toggleEncryption.bind(this)
|
||||
this.restoreMetadata = this.restoreMetadata.bind(this)
|
||||
this.toggleMetadata = this.toggleMetadata.bind(this)
|
||||
this.toggleProxy = this.toggleProxy.bind(this)
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
const config = await getConfig()
|
||||
const languages = await getLanguages()
|
||||
const platform: string = await invoke('get_platform')
|
||||
|
||||
// Remove jar from path
|
||||
const path = config.grasscutter_path.replace(/\\/g, '/')
|
||||
const folderPath = path.substring(0, path.lastIndexOf('/'))
|
||||
const encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
|
||||
|
||||
console.log(platform)
|
||||
|
||||
this.setState({
|
||||
game_install_path: config.game_install_path || '',
|
||||
grasscutter_path: config.grasscutter_path || '',
|
||||
@@ -102,7 +110,10 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
encryption: await translate(encEnabled ? 'options.enabled' : 'options.disabled'),
|
||||
patch_metadata: config.patch_metadata || false,
|
||||
use_internal_proxy: config.use_internal_proxy || false,
|
||||
wipe_login: config.wipe_login || false,
|
||||
horny_mode: config.horny_mode || false,
|
||||
swag: config.swag_mode || false,
|
||||
platform,
|
||||
|
||||
// Swag stuff
|
||||
akebi_path: config.akebi_path || '',
|
||||
@@ -116,6 +127,17 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
setGameExecutable(value: string) {
|
||||
setConfigOption('game_install_path', value)
|
||||
|
||||
// I hope this stops people setting launcher.exe because oml it's annoying
|
||||
if (value.endsWith('launcher.exe')) {
|
||||
const pathArr = value.replace(/\\/g, '/').split('/')
|
||||
pathArr.pop()
|
||||
const path = pathArr.join('/') + '/Genshin Impact Game/'
|
||||
|
||||
alert(
|
||||
`You have set your game execuatable to "launcher.exe". You should not do this. Your game executable is located in:\n\n${path}`
|
||||
)
|
||||
}
|
||||
|
||||
this.setState({
|
||||
game_install_path: value,
|
||||
})
|
||||
@@ -151,6 +173,12 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
this.setState({
|
||||
migoto_path: value,
|
||||
})
|
||||
|
||||
// Set game exe in Migoto ini
|
||||
invoke('set_migoto_target', {
|
||||
path: this.state.game_install_path,
|
||||
migotoPath: value,
|
||||
})
|
||||
}
|
||||
|
||||
setReshade(value: string) {
|
||||
@@ -236,37 +264,39 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
})
|
||||
}
|
||||
|
||||
async toggleMetadata() {
|
||||
const changedVal = !(await getConfigOption('patch_metadata'))
|
||||
async toggleOption(opt: keyof Configuration) {
|
||||
const changedVal = !(await getConfigOption(opt))
|
||||
|
||||
await setConfigOption('patch_metadata', changedVal)
|
||||
await setConfigOption(opt, changedVal)
|
||||
|
||||
// @ts-expect-error shut up bitch
|
||||
this.setState({
|
||||
patch_metadata: changedVal,
|
||||
})
|
||||
}
|
||||
|
||||
async toggleProxy() {
|
||||
const changedVal = !(await getConfigOption('use_internal_proxy'))
|
||||
|
||||
await setConfigOption('use_internal_proxy', changedVal)
|
||||
|
||||
this.setState({
|
||||
use_internal_proxy: changedVal,
|
||||
[opt]: changedVal,
|
||||
})
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<Menu closeFn={this.props.closeFn} className="Options" heading="Options">
|
||||
<div className="OptionSection" id="menuOptionsContainerGamePath">
|
||||
<div className="OptionLabel" id="menuOptionsLabelGamePath">
|
||||
<Tr text="options.game_path" />
|
||||
{!this.state.platform || this.state.platform === 'windows' ? (
|
||||
<div className="OptionSection" id="menuOptionsContainerGamePath">
|
||||
<div className="OptionLabel" id="menuOptionsLabelGamePath">
|
||||
<Tr text="options.game_path" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsDirGamePath">
|
||||
<DirInput onChange={this.setGameExecutable} value={this.state?.game_install_path} extensions={['exe']} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsDirGamePath">
|
||||
<DirInput onChange={this.setGameExecutable} value={this.state?.game_install_path} extensions={['exe']} />
|
||||
) : (
|
||||
<div className="OptionSection" id="menuOptionsContainerGameCommand">
|
||||
<div className="OptionLabel" id="menuOptionsLabelGameCommand">
|
||||
<Tr text="options.game_command" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsGameCommand">
|
||||
<TextInput />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="OptionSection" id="menuOptionsContainermetaDownload">
|
||||
<div className="OptionLabel" id="menuOptionsLabelmetaDownload">
|
||||
<Tr text="options.recover_metadata" />
|
||||
@@ -284,7 +314,11 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
<HelpButton contents="help.patch_metadata" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxPatchMeta">
|
||||
<Checkbox onChange={this.toggleMetadata} checked={this.state?.patch_metadata} id="patchMeta" />
|
||||
<Checkbox
|
||||
onChange={() => this.toggleOption('patch_metadata')}
|
||||
checked={this.state?.patch_metadata}
|
||||
id="patchMeta"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="OptionSection" id="menuOptionsContainerUseProxy">
|
||||
@@ -293,7 +327,23 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
<HelpButton contents="help.use_proxy" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxUseProxy">
|
||||
<Checkbox onChange={this.toggleProxy} checked={this.state?.use_internal_proxy} id="useProxy" />
|
||||
<Checkbox
|
||||
onChange={() => this.toggleOption('use_internal_proxy')}
|
||||
checked={this.state?.use_internal_proxy}
|
||||
id="useProxy"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="OptionSection" id="menuOptionsContainerWipeLogin">
|
||||
<div className="OptionLabel" id="menuOptionsLabelWipeLogin">
|
||||
<Tr text="options.wipe_login" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxWipeLogin">
|
||||
<Checkbox
|
||||
onChange={() => this.toggleOption('wipe_login')}
|
||||
checked={this.state?.wipe_login}
|
||||
id="wipeLogin"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -366,12 +416,26 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxGCWGame">
|
||||
<Checkbox
|
||||
onChange={this.toggleGrasscutterWithGame}
|
||||
onChange={() => this.toggleOption('grasscutter_with_game')}
|
||||
checked={this.state?.grasscutter_with_game}
|
||||
id="gcWithGame"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{this.state.swag ? (
|
||||
<div className="OptionSection" id="menuOptionsContainerHorny">
|
||||
<div className="OptionLabel" id="menuOptionsLabelHorny">
|
||||
<Tr text="options.horny_mode" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxHorny">
|
||||
<Checkbox
|
||||
onChange={() => this.toggleOption('horny_mode')}
|
||||
checked={this.state?.horny_mode}
|
||||
id="hornyMode"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<Divider />
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import React from 'react'
|
||||
import { getConfigOption } from '../../../utils/configuration'
|
||||
import { getInstalledMods, getMods, ModData, PartialModData } from '../../../utils/gamebanana'
|
||||
import { LoadingCircle } from './LoadingCircle'
|
||||
|
||||
@@ -11,6 +12,7 @@ interface IProps {
|
||||
}
|
||||
|
||||
interface IState {
|
||||
horny: boolean
|
||||
modList: ModData[] | null
|
||||
installedList:
|
||||
| {
|
||||
@@ -25,6 +27,7 @@ export class ModList extends React.Component<IProps, IState> {
|
||||
super(props)
|
||||
|
||||
this.state = {
|
||||
horny: false,
|
||||
modList: null,
|
||||
installedList: null,
|
||||
}
|
||||
@@ -60,8 +63,10 @@ export class ModList extends React.Component<IProps, IState> {
|
||||
}
|
||||
|
||||
const mods = await getMods(this.props.mode)
|
||||
const horny = await getConfigOption('horny_mode')
|
||||
|
||||
this.setState({
|
||||
horny,
|
||||
modList: mods,
|
||||
})
|
||||
}
|
||||
@@ -78,10 +83,16 @@ export class ModList extends React.Component<IProps, IState> {
|
||||
<div className="ModListInner">
|
||||
{this.props.mode === 'installed'
|
||||
? this.state.installedList?.map((mod) => (
|
||||
<ModTile path={mod.path} mod={mod.info} key={mod.info.name} onClick={this.downloadMod} />
|
||||
<ModTile
|
||||
horny={this.state.horny}
|
||||
path={mod.path}
|
||||
mod={mod.info}
|
||||
key={mod.info.name}
|
||||
onClick={this.downloadMod}
|
||||
/>
|
||||
))
|
||||
: this.state.modList?.map((mod: ModData) => (
|
||||
<ModTile mod={mod} key={mod.id} onClick={this.downloadMod} />
|
||||
<ModTile horny={this.state.horny} mod={mod} key={mod.id} onClick={this.downloadMod} />
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
|
||||
@@ -12,6 +12,7 @@ import { disableMod, enableMod, modIsEnabled } from '../../../utils/mods'
|
||||
|
||||
interface IProps {
|
||||
mod: ModData | PartialModData
|
||||
horny?: boolean
|
||||
path?: string
|
||||
onClick: (mod: ModData) => void
|
||||
}
|
||||
@@ -107,7 +108,9 @@ export class ModTile extends React.Component<IProps, IState> {
|
||||
))}
|
||||
<img
|
||||
src={mod.images[0]}
|
||||
className={`ModImageInner ${'id' in mod && mod.nsfw ? 'nsfw' : ''} ${this.state.hover ? 'blur' : ''}`}
|
||||
className={`ModImageInner ${'id' in mod && !this.props.horny && mod.nsfw ? 'nsfw' : ''} ${
|
||||
this.state.hover ? 'blur' : ''
|
||||
}`}
|
||||
/>
|
||||
</div>
|
||||
<div className="ModInner">
|
||||
|
||||
@@ -22,6 +22,8 @@ let defaultConfig: Configuration
|
||||
debug_enabled: false,
|
||||
patch_metadata: true,
|
||||
use_internal_proxy: true,
|
||||
wipe_login: false,
|
||||
horny_mode: false,
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -46,6 +48,8 @@ export interface Configuration {
|
||||
debug_enabled: boolean
|
||||
patch_metadata: boolean
|
||||
use_internal_proxy: boolean
|
||||
wipe_login: boolean
|
||||
horny_mode: boolean
|
||||
swag_mode?: boolean
|
||||
|
||||
// Swag stuff
|
||||
|
||||
@@ -77,14 +77,20 @@ export default class DownloadHandler {
|
||||
|
||||
// Extraction events
|
||||
listen('extract_start', ({ payload }) => {
|
||||
// Find the download that is no extracting and set it's status as such
|
||||
// Find the download that is extracting and set it's status as such
|
||||
const index = this.downloads.findIndex((download) => download.path === payload)
|
||||
this.downloads[index].status = 'extracting'
|
||||
})
|
||||
|
||||
listen('extract_end', ({ payload }) => {
|
||||
// Find the download that is no extracting and set it's status as such
|
||||
const index = this.downloads.findIndex((download) => download.path === payload)
|
||||
// @ts-expect-error shut up typescript
|
||||
const obj: {
|
||||
file: string
|
||||
new_folder: string
|
||||
} = payload
|
||||
|
||||
// Find the download that is not extracting and set it's status as such
|
||||
const index = this.downloads.findIndex((download) => download.path === obj.file || obj.new_folder)
|
||||
this.downloads[index].status = 'finished'
|
||||
})
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { getConfig } from './configuration'
|
||||
|
||||
export async function getGameExecutable() {
|
||||
@@ -25,3 +26,36 @@ export async function getGameFolder() {
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
export async function getGameDataFolder() {
|
||||
const gameExec = await getGameExecutable()
|
||||
|
||||
if (!gameExec) {
|
||||
return null
|
||||
}
|
||||
|
||||
return (await getGameFolder()) + '\\' + gameExec.replace('.exe', '_Data')
|
||||
}
|
||||
|
||||
export async function getGameVersion() {
|
||||
const GameData = await getGameDataFolder()
|
||||
|
||||
if (!GameData) {
|
||||
return null
|
||||
}
|
||||
|
||||
const settings = JSON.parse(
|
||||
await invoke('read_file', {
|
||||
path: GameData + '\\StreamingAssets\\asb_settings.json',
|
||||
})
|
||||
)
|
||||
|
||||
const versionRaw = settings.variance.split('.')
|
||||
const version = {
|
||||
major: parseInt(versionRaw[0]),
|
||||
minor: parseInt(versionRaw[1].split('_')[0]),
|
||||
release: parseInt(versionRaw[1].split('_')[1]),
|
||||
}
|
||||
|
||||
return version
|
||||
}
|
||||
|
||||
@@ -118,13 +118,30 @@ interface ModDownload {
|
||||
}
|
||||
|
||||
export async function getMods(mode: string) {
|
||||
const resp = JSON.parse(
|
||||
await invoke('list_submissions', {
|
||||
mode,
|
||||
})
|
||||
)
|
||||
let modList: GamebananaResponse[] = []
|
||||
let hadMods = true
|
||||
let page = 1
|
||||
|
||||
return formatGamebananaData(resp)
|
||||
while (hadMods) {
|
||||
const resp = JSON.parse(
|
||||
await invoke('list_submissions', {
|
||||
mode,
|
||||
page: '' + page,
|
||||
})
|
||||
)
|
||||
|
||||
if (resp.length === 0) hadMods = false
|
||||
|
||||
modList = [...modList, ...resp]
|
||||
page++
|
||||
|
||||
console.log(page)
|
||||
console.log(resp)
|
||||
}
|
||||
|
||||
console.log(modList)
|
||||
|
||||
return formatGamebananaData(modList)
|
||||
}
|
||||
|
||||
export async function formatGamebananaData(obj: GamebananaResponse[]) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { invoke } from '@tauri-apps/api'
|
||||
import { dataDir } from '@tauri-apps/api/path'
|
||||
import DownloadHandler from './download'
|
||||
import { getGameExecutable, getGameFolder } from './game'
|
||||
import { getGameDataFolder } from './game'
|
||||
|
||||
export async function patchMetadata() {
|
||||
const metadataExists = await invoke('dir_exists', {
|
||||
@@ -35,6 +35,11 @@ export async function patchMetadata() {
|
||||
})
|
||||
|
||||
if (!patchedMeta) {
|
||||
// Remove metadata backup, in case it invalid or something
|
||||
await invoke('delete_file', {
|
||||
path: (await getBackupMetadataPath()) + '\\global-metadata-unpatched.dat',
|
||||
})
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -166,16 +171,13 @@ export async function unpatchGame() {
|
||||
}
|
||||
|
||||
export async function getGameMetadataPath() {
|
||||
const gameExec = await getGameExecutable()
|
||||
const gameData = await getGameDataFolder()
|
||||
|
||||
if (!gameExec) {
|
||||
if (!gameData) {
|
||||
return null
|
||||
}
|
||||
|
||||
return ((await getGameFolder()) + '\\' + gameExec.replace('.exe', '_Data') + '\\Managed\\Metadata').replace(
|
||||
/\\/g,
|
||||
'/'
|
||||
)
|
||||
return (gameData + '\\Managed\\Metadata').replace(/\\/g, '/')
|
||||
}
|
||||
|
||||
export async function getBackupMetadataPath() {
|
||||
@@ -183,6 +185,7 @@ export async function getBackupMetadataPath() {
|
||||
}
|
||||
|
||||
export async function globalMetadataLink() {
|
||||
// TODO: Get metadata based on current game version.
|
||||
const versionAPIUrl =
|
||||
'https://sdk-os-static.mihoyo.com/hk4e_global/mdk/launcher/api/resource?channel_id=1&key=gcStgarh&launcher_id=10&sub_channel_id=0'
|
||||
|
||||
|
||||
@@ -21,6 +21,9 @@ export function unzip(
|
||||
})
|
||||
|
||||
listen('extract_end', ({ payload }) => {
|
||||
console.log(payload)
|
||||
console.log(file)
|
||||
|
||||
// @ts-expect-error Payload is an object
|
||||
if (payload?.file === file) {
|
||||
resolve(payload as UnzipPayload)
|
||||
|
||||
Reference in New Issue
Block a user