Compare commits

...

71 Commits

Author SHA1 Message Date
NotThorny
f61f4eed51 Bump version 2025-11-14 15:24:14 -07:00
NotThorny
99b45ddf52 Fix reading profile path 2025-11-14 15:24:14 -07:00
NotThorny
fafec01fe3 Fix not loading old configs from correct dir 2025-11-14 02:45:24 -07:00
NotThorny
0c910b7317 Update langs 2025-11-14 02:38:22 -07:00
NotThorny
6f2be3c5a5 Add profiles 2025-11-14 02:37:37 -07:00
NotThorny
d2b8124877 Update AIO to 6.1 2025-10-31 21:17:31 -06:00
NotThorny
247150c62a Update patch 2025-10-25 16:18:44 -06:00
NotThorny
1599c37100 Update AIO 6.0 2025-10-17 00:48:37 -06:00
NotThorny
ecb7936a8f Add 6.0 patch from @pmagixc
Bump version
2025-10-15 21:36:14 -06:00
Thoronium
a40080cca2 Merge pull request #255 from GID0317/main
Update launch_args, Fix login timeout and transparency support
2025-07-01 14:32:09 -06:00
GID
364d138779 Update launch_args and fix login timeout to use the options OptionSection for better UX and easier for theming 2025-07-01 19:35:14 +07:00
GID
f03cc0a09f Update to make the transparency enabled to make easier to support rounded corners windows 2025-07-01 19:31:39 +07:00
Thoronium
7750266a3d Bump version 2025-03-27 01:19:00 -06:00
Thoronium
028ee380f2 Replace 5.x+ individual patch files with universal 2025-03-27 01:17:24 -06:00
Thoronium
fcd08cace5 Bump version 2025-02-26 13:05:21 -07:00
Thoronium
0258aa006a Remove unused patch 2025-02-26 13:04:25 -07:00
Thoronium
8c10c00b53 Update file 2025-02-15 14:28:50 -07:00
Thoronium
fe094c952b Fix typo 2025-02-14 23:49:11 -07:00
Thoronium
63a883cf1d Bump version 2025-02-14 15:25:56 -07:00
Thoronium
8207260968 Add 5.4 AIO 2025-02-14 15:19:18 -07:00
Thoronium
4e72101fda Bump version 2025-02-03 17:11:19 -07:00
Thoronium
14206e87a0 Update menu version 2025-02-03 17:09:56 -07:00
Thoronium
13d129f175 Add 5.3 AIO 2025-02-03 17:03:44 -07:00
Thoronium
14173e5b9f Merge branch 'main' of https://github.com/Grasscutters/Cultivation 2025-01-16 20:45:13 -07:00
Thoronium
3669bb334b Bump version 2025-01-16 20:45:07 -07:00
Thoronium
03fed7a69a Support XXMI launching with args 2025-01-16 20:41:17 -07:00
Thoronium
55a90ea531 Add 5.3 patch
Bump version
2025-01-01 13:47:37 -07:00
Thoronium
b28c3881e6 Bump version 2024-12-23 02:36:28 -07:00
Thoronium
4d98cd9468 Fix comparison 2024-10-31 02:11:46 -06:00
Thoronium
2ed97f9787 Merge pull request #210 from Grasscutters/dependabot/npm_and_yarn/babel/traverse-7.23.2
Bump @babel/traverse from 7.18.9 to 7.23.2
2024-10-31 02:04:24 -06:00
Thoronium
1a82ab0012 Clippy ubuntu 2024-10-30 16:47:59 -06:00
Thoronium
017c116d81 Merge branch 'main' of https://github.com/Grasscutters/Cultivation 2024-10-30 16:31:15 -06:00
Thoronium
422ce59f96 Clippy 2024-10-30 16:31:09 -06:00
Thoronium
02a304d830 Remove clippy for macos
Macos not supported, can add back if it ever is.
2024-10-30 16:06:57 -06:00
Thoronium
ae8c03debe Update README.md
Fix link
2024-10-30 15:39:21 -06:00
Thoronium
dcc2b1707b Update THEMES.md
Update link
2024-10-30 14:36:59 -06:00
Thoronium
8ba916d1ea Add ExampleTheme.zip
To replace expired discord link for theming reference.
2024-10-30 14:36:10 -06:00
Thoronium
4b6d1a11fb Merge pull request #235 from NotThorny/Merge-Compat
Merge Thorny Edition changes
2024-10-30 14:25:19 -06:00
Thoronium
2b95034ddb Update README.md
Fix discord invite
Clarify AIO download is one or the other
2024-10-30 13:41:44 -06:00
Thoronium
989487b381 Lint & Prettier 2024-10-30 13:32:09 -06:00
Thoronium
f6f5eae31c Changes from 1.2.1 - 1.5.1
Contains slightly modified commits from between 1.2.1 and 1.5.1.
2024-10-30 13:18:07 -06:00
SpikeHD
31c60755af Merge pull request #215 from daydreamer-json/patch-1
Add Japanese language support
2023-12-23 19:42:29 -08:00
SpikeHD
22a2fff644 Merge pull request #217 from NotThorny/Fixes
Bugfixes
2023-12-23 19:42:06 -08:00
Thoronium
d11e432e76 Remove offline mode check
Forgot offline mode wasn't merged
2023-11-27 15:45:21 -07:00
Thoronium
572006ff95 Fix failing to launch on missing arg 2023-11-27 15:36:34 -07:00
Thoronium
27148eac8e Fix wrench loading
Fix game missing notification
2023-11-27 15:36:26 -07:00
Thoronium
7dce15f553 Only detect game path on windows 2023-11-27 15:30:35 -07:00
daydreamer-json
1204f967d7 Fix README_ja-JP.md
Corrected translation errors
2023-11-20 06:44:42 +09:00
daydreamer-json
51b938a5da Fix README.md 2023-11-20 05:04:48 +09:00
daydreamer-json
0def90223f README Japanese translation 2023-11-20 05:02:54 +09:00
daydreamer-json
f6de90ca50 Create ja.json 2023-11-20 02:47:19 +09:00
dependabot[bot]
e1ba27203a Bump @babel/traverse from 7.18.9 to 7.23.2
Bumps [@babel/traverse](https://github.com/babel/babel/tree/HEAD/packages/babel-traverse) from 7.18.9 to 7.23.2.
- [Release notes](https://github.com/babel/babel/releases)
- [Changelog](https://github.com/babel/babel/blob/main/CHANGELOG.md)
- [Commits](https://github.com/babel/babel/commits/v7.23.2/packages/babel-traverse)

---
updated-dependencies:
- dependency-name: "@babel/traverse"
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-18 06:23:42 +00:00
SpikeHD
6a161f4539 Merge pull request #205 from Grasscutters/dependabot/cargo/src-tauri/webpki-0.22.2
Bump webpki from 0.22.0 to 0.22.2 in /src-tauri
2023-10-03 15:35:19 -07:00
dependabot[bot]
5b6f4e94db Bump webpki from 0.22.0 to 0.22.2 in /src-tauri
Bumps [webpki](https://github.com/briansmith/webpki) from 0.22.0 to 0.22.2.
- [Commits](https://github.com/briansmith/webpki/commits)

---
updated-dependencies:
- dependency-name: webpki
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
2023-10-02 21:36:39 +00:00
SpikeHD
a32d7186df Merge pull request #204 from NotThorny/Fix-SettingsArgs
Fix args in settings
2023-10-01 01:58:48 -07:00
Thoronium
680c6b04a4 Remove unnecessary passing of args 2023-09-28 13:17:48 -06:00
Thoronium
5db28756d2 Fix launch args setting 2023-09-28 13:13:34 -06:00
SpikeHD
6d53f344ac Merge pull request #202 from NotThorny/Settings-LaunchArgs
Settings & QoL Alerts
2023-09-18 13:25:15 -07:00
Thoronium
d144b2dc94 Remove forgotten debug 2023-09-16 21:31:21 -06:00
Thoronium
7264331436 Update langs 2023-09-16 21:26:46 -06:00
Thoronium
016d377f30 Fix display input 2023-09-16 21:26:46 -06:00
Thoronium
0e2b4a4a54 Small fixes 2023-09-16 21:26:46 -06:00
Thoronium
a1571ad800 Launch args from settings 2023-09-16 21:26:46 -06:00
Thoronium
d849ea32c9 Add encryption alert 2023-09-16 21:26:46 -06:00
Thoronium
1c7293578c Update folder check 2023-09-16 21:26:46 -06:00
Thoronium
73a9e18712 Add webcache clearing 2023-09-16 21:26:46 -06:00
Thoronium
96bf4ef3fe Checks for localhost
Linting
2023-09-16 21:26:46 -06:00
SpikeHD
4c5b79513a Merge pull request #201 from NotThorny/Exit-Proceedures
Fix Closure Stuff
2023-09-10 00:32:18 -07:00
Thoronium
b222601cf1 Close handling 2023-09-09 22:33:16 -06:00
SpikeHD
f8d2e62a06 Merge pull request #199 from RealShuru/patch-1
Update de.json
2023-09-09 15:57:02 -07:00
RealShuru
33547cd34e Update de.json 2023-09-09 00:26:32 +02:00
63 changed files with 3561 additions and 1780 deletions

View File

@@ -32,7 +32,7 @@ jobs:
strategy:
fail-fast: false
matrix:
platform: [windows-latest, ubuntu-latest, macos-latest]
platform: [windows-latest, ubuntu-latest]
steps:
- uses: actions/checkout@v3

1
.gitignore vendored
View File

@@ -10,6 +10,7 @@
# production
/build
/target
# misc
.DS_Store

View File

@@ -1,4 +1,4 @@
EN | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) |
EN | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | [日本語](README_ja-JP.md)
# Cultivation
@@ -19,7 +19,7 @@ A game launcher designed to easily proxy traffic from anime game to private serv
- [Screenshots](#screenshots)
- [Credits](#credits)
# Client Patching Notice - RSA
# Client Patching Notice
For game versions 3.1 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.
@@ -46,15 +46,15 @@ Download and open the MSI, and once installed, run Cultivation as administrator.
- 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)
- If you are getting System Error, or 4214, ask the [Discord support channels](https://discord.gg/T5vZU6UyeG)
- Open the "Downloads" menu (top right)
- Download "Grasscutter All-in-One" (top of the list)
- Download "Grasscutter All-in-One" (select **one** of the AIOs that matches the version you want)
- Once 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 generic "I am getting XYZ error!" should go in the [Discord support channels](https://discord.gg/T5vZU6UyeG)
- 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)

124
README_ja-JP.md Normal file
View File

@@ -0,0 +1,124 @@
[EN](README.md) | [简中](README_zh-CN.md) | [繁中](README_zh-TW.md) | 日本語
# Cultivation
某アニメゲームからプライベートサーバーへのトラフィックを簡単にプロキシできるように設計されたゲームランチャー。
# 目次
- [クライアントのパッチに関するお知らせ](#クライアントのパッチに関するお知らせ)
- [ダウンロード](#ダウンロード)
- [セットアップ](#セットアップ)
- [トラブルシューティング](#トラブルシューティング)
- [開発者向けクイックスタート](#開発者向けクイックスタート)
- [セットアップ](#セットアップ)
- [ビルド](#ビルド)
- [コードフォーマット・lint](#コードフォーマットlint)
- [artifact を生成](#artifactを生成)
- [テーマについて](#テーマについて)
- [スクリーンショット](#スクリーンショット)
- [クレジット](#クレジット)
# クライアントのパッチに関するお知らせ
ゲームバージョン 3.1 以降の場合、Cultivation は Grasscutter を使用して起動するときにゲームクライアントに自動的に小さなパッチ(RSA パッチ)を適用し、ゲームを閉じると自動的に解除します。理論上は安全ですが、<strong>ゲームクライアント自体に変更を加えるため、公式サーバーに接続すると BAN につながる可能性があります。</strong>これによる BAN についての既知の事例はありませんが、可能性は存在します。
# ダウンロード
[**リリースビルドはこちら**](https://github.com/Grasscutters/Cultivation/releases)
MSI インストーラーをダウンロードして開き、インストールしたら、管理者として Cultivation を実行します。[より詳細なセットアップ手順](#セットアップ)については、以下を参照してください。
**Windows 7 をお使いの場合:** [WebView2](https://developer.microsoft.com/ja-jp/microsoft-edge/webview2/#download-section)を手動でダウンロードしてインストールする必要があります。また、Cultivation のインストールには`.msi`の代わりに`.zip`を使用してください。
# セットアップ
5 分間の解説動画(英語): https://youtu.be/e0irOYbQe7I
- Cultivation をダウンロードします。
- Windows 10/11 をお使いの場合は、MSI インストーラーを使用してください。
- Windows 7 をお使いの場合または MSI インストーラーが動作しない場合、ZIP を使用してください。また、[WebView2](https://developer.microsoft.com/ja-jp/microsoft-edge/webview2/)をインストールしてください。
- GNU/Linux または macOS をお使いの場合は、[Linux・macOS での動作をサポートするのを手伝っていただけると嬉しいです!](https://github.com/Grasscutters/Cultivation/issues/7)
- Cultivation をインストールまたは展開します。
- Cultivation を<strong><u>管理者権限で</u></strong>開きます。
- Options(右上の歯車アイコン)内で、ゲームのインストールパスを設定します。
- 他の場所に既存の Grasscutter サーバーがインストールされている場合は、`.jar`ファイルのパスを設定できます。Cultivation を介して行われるすべてのダウンロードは、そのパスを自動的に使用します。追加の構成は必要ありません。
- 複数の Java バージョンを使用している場合、Java 17 のパスを Cultivation に設定できます(自分で Grasscutter サーバーを実行している場合にのみ必要です)。
- 自分でサーバーをダウンロードするか、公開サーバーに参加するかどうかを決定します。
- 公開サーバーに参加する場合は、[Grasscutter に接続]をクリックして、アドレスとポートを入力してください。
- システムエラー、または 4214 エラーが表示されている場合は、[Discord サポートチャンネル](https://discord.gg/grasscutter)で問い合わせてください。
- 自分でサーバーをダウンロードする場合は、"Downloads"メニューを開きます。(右上の下矢印アイコン)
- "Grasscutter All-in-One をダウンロード"します。(一番上)
- それが完了したら、「起動」の横にあるサーバーアイコンをクリックします。
- 自分のサーバーでプレイするには:
- [Grasscutter に接続]をクリックします。
- アドレスに`localhost`、ポート番号に`443`を指定します。
- HTTPS 接続を無効にします。
- 何らかのエラーが発生した場合は、[Discord サポートチャンネル](https://discord.gg/grasscutter)で問い合わせてください。
- 何らかの Cultivation に関する問題は[Issues ページ](/issues)へお願いします。
- 何らかの Grasscutter サーバーに関する問題は[Grasscutter の Issues ページ](https://github.com/Grasscutters/Grasscutter/issues)へお願いします。
# トラブルシューティング
### ホワイトスクリーン、インスタントクラッシュなどの問題
- まず、[Windows 8 互換モード](https://www.lifewire.com/run-older-programs-with-windows-10-compatibility-mode-4587064)で実行してみてください。
- 解決しない場合は、[WebView2](https://developer.microsoft.com/ja-jp/microsoft-edge/webview2/#download-section)を完全にアンインストールしてから再インストールしてみてください。
- アンインストール時に問題が発生する場合は、`HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}`レジストリを削除して再度試してください。
- [コマンドプロンプトからアンインストール](https://superuser.com/a/1743626)する方法を試すこともできます。
### Cultivation を使用した後にインターネットに接続できない問題
ゲームを終了すると、Cultivation ウィンドウに戻り再びポップアップすることを確認してください。これは、ゲームが終了されたこと、そしてプロキシ設定が正常に戻されたことを示しています。ウィンドウに戻る前に Cultivation を閉じた場合、またはインターネットの他の問題が発生した場合は、[Windows のプロキシ設定](https://is.gd/tZHkvl)を開き、"手動プロキシセットアップ"をオフにしてください。これでインターネット接続は元に戻ります。
# 開発者向けクイックスタート
### セットアップ
- [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 tauri dev`
### ビルド
リリースビルド:
- `yarn build`
デバッグビルド:
- `yarn build --debug`
### コードフォーマット・lint
- `yarn format`
- `yarn lint`, `yarn lint:fix`
### artifact を生成
- 秘密鍵へのパスを持つ環境変数として`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)
# スクリーンショット
![image](https://user-images.githubusercontent.com/107363768/221495236-ca1e2f2e-0f85-4765-a5f3-8bdcea299612.png)
![image](https://user-images.githubusercontent.com/107363768/221495246-ea309640-f866-4f50-bda8-f9d916380f92.png)
![image](https://user-images.githubusercontent.com/107363768/221495249-5a1aac39-9e8a-4244-9642-72c2e7be8a69.png)
![image](https://user-images.githubusercontent.com/107363768/221495254-ffbfc24e-ef5d-4e72-9068-a02132381dcc.png)
## クレジット
- [SpikeHD](https://github.com/SpikeHD): オリジナルである **GrassClipper** を製作し、Cultivation の素晴らしい UI を作成
- [KingRainbow44](https://github.com/KingRainbow44): ゼロからプロキシデーモンを作成し、Cultivation へ統合
- [Benj](https://github.com/4Benj): クライアントのパッチに関するアシスタント
- [lilmayofuksu](https://github.com/lilmayofuksu): クライアントのパッチに関するアシスタント
- [Tauri](https://tauri.app): 素晴らしく軽量でシンプルなデスクトップアプリケーションフレームワーク・ライブラリを提供

View File

@@ -1,4 +1,4 @@
[EN](README.md) | 简中 | [繁中](README_zh-TW.md)
[EN](README.md) | 简中 | [繁中](README_zh-TW.md) | [日本語](README_ja-JP.md)
# Cultivation

View File

@@ -1,4 +1,4 @@
[EN](README.md) | [简中](README_zh-CN.md) | 繁中
[EN](README.md) | [简中](README_zh-CN.md) | 繁中 | [日本語](README_ja-JP.md)
# 客戶端修補通知

View File

@@ -8,7 +8,7 @@
Themes support entirely custom JS and CSS, enabling you to potentially change every single thing about Cultivation with relative ease.
You can refer to the example theme [found here.](https://cdn.discordapp.com/attachments/992943872479084614/992993575652565002/Example.zip)
You can refer to the example theme [found here.](https://github.com/Grasscutters/Cultivation/blob/main/docs/ExampleTheme.zip)
You will need CSS and JS experience if you want to do anything cool.

BIN
docs/ExampleTheme.zip Normal file

Binary file not shown.

View File

@@ -1,6 +1,6 @@
{
"name": "cultivation",
"version": "1.0.26",
"version": "1.7.1",
"private": true,
"dependencies": {
"@tauri-apps/api": "^1.0.0-rc.5",

3026
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
[package]
name = "cultivation"
version = "1.2.0"
version = "1.6.3"
description = "A custom launcher for anime game."
authors = ["KingRainbow44", "SpikeHD"]
license = ""

View File

@@ -1,7 +1,7 @@
{
"lang_name": "简体中文",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "启动",
"gc_enable": "以 Grasscutter 模式连接",
"https_enable": "使用 HTTPS",
@@ -33,11 +33,17 @@
"horny_mode": "Horny 模式",
"auto_mongodb": "自动启动 MongoDB",
"un_elevated": "非提升运行游戏(无管理员)",
"redirect_more": "还可以重定向其他MHY游戏"
"redirect_more": "还可以重定向其他MHY游戏",
"web_cache": "删除 webCaches 文件夹",
"launch_args": "启动参数",
"offline_mode": "离线模式",
"fix_res": "修复登录超时",
"show_version": "在按钮上显示游戏版本",
"save_profile": "保存配置配置文件"
},
"downloads": {
"grasscutter_fullbuild": "下载 Grasscutter 一体化",
"grasscutter_fullquest": "下载 Quest 一体化",
"grasscutter_fullquest": "下载 6.1 一体化",
"grasscutter_stable_data": "下载 Grasscutter 稳定版数据",
"grasscutter_latest_data": "下载 Grasscutter 开发版数据",
"grasscutter_stable_data_update": "更新 Grasscutter 稳定版数据",
@@ -65,7 +71,8 @@
"select_folder": "选择文件夹...",
"download": "下载",
"delete": "删除",
"install": "安装"
"install": "安装",
"fix": "Fix"
},
"news": {
"latest_commits": "最近提交",

View File

@@ -1,7 +1,7 @@
{
"lang_name": "繁體中文",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "啟動",
"gc_enable": "以Grasscutter模式連接",
"https_enable": "使用 HTTPS",
@@ -33,11 +33,17 @@
"horny_mode": "Horny模式",
"auto_mongodb": "自動啟動 MongoDB",
"un_elevated": "在不升高的情况下运行游戏(没有管理员)。",
"redirect_more": "同時重定向其他 MHY 遊戲"
"redirect_more": "同時重定向其他 MHY 遊戲",
"web_cache": "刪除 webCaches 文件夾",
"launch_args": "啟動參數",
"offline_mode": "離線模式",
"fix_res": "修復登入逾時",
"show_version": "在按鈕上顯示遊戲版本",
"save_profile": "儲存配置設定檔"
},
"downloads": {
"grasscutter_fullbuild": "下載Grasscutter多合一下載",
"grasscutter_fullquest": "下载 Quest 一体化",
"grasscutter_fullquest": "下载 6.1 一体化",
"grasscutter_stable_data": "下載Grasscutter穩定版數據Data",
"grasscutter_latest_data": "下載Grasscutter開發板數據Data",
"grasscutter_stable_data_update": "更新Grasscutter穩定版數據Data",
@@ -65,7 +71,8 @@
"select_folder": "選擇資料夾...",
"download": "下載",
"delete": "刪除",
"install": "安裝"
"install": "安裝",
"fix": "Fix"
},
"news": {
"latest_commits": "最近的PR",

View File

@@ -1,62 +1,71 @@
{
"lang_name": "Deutsch",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "Starten",
"gc_enable": "Über Grasscutter verbinden",
"https_enable": "HTTPS nutzen",
"ip_placeholder": "Server Adresse...",
"port_placeholder": "Port...",
"files_downloading": "Herunterladen von Dateien: ",
"files_extracting": "Extrahieren von Dateien: ",
"game_path_notify": "Spielpfad nicht gefunden, denken Sie daran, ihn in den Einstellungen festzulegen!"
"gc_enable": "Mit Grasscutter verbinden",
"https_enable": "HTTPS verwenden",
"ip_placeholder": "Server-Adresse",
"port_placeholder": "Port",
"files_downloading": "Dateien herunterladen: ",
"files_extracting": "Dateien extrahieren: ",
"game_path_notify": "Spielverzeichnis nicht gefunden, bitte lege es in den Einstellungen fest!"
},
"options": {
"enabled": "Aktiviert",
"disabled": "Deaktiviert",
"game_path": "Spielpfad",
"game_executable": "Spiel Datei auswählen",
"recover_rsa": "Notfall Wiederherstellung der RSA",
"grasscutter_jar": "Grasscuter JAR auswählen",
"game_path": "Spielinstallationspfad festlegen",
"game_command": "Spielstartbefehl",
"game_executable": "Spiele Datei festlegen",
"recover_rsa": "Notfall-RSA löschen",
"grasscutter_jar": "Grasscutter-JAR festlegen",
"toggle_encryption": "Verschlüsselung umschalten",
"install_certificate": "Installeer proxy certificaat",
"java_path": "Benutzerdefinierten Java Pfad setzen",
"install_certificate": "Proxy-Zertifikat installieren",
"java_path": "Benutzerdefinierten Java-Pfad festlegen",
"grasscutter_with_game": "Grasscutter automatisch mit dem Spiel starten",
"language": "Sprache auswählen",
"background": "Benutzerdefinierten Hintergrund festlegen (link oder bild)",
"use_theme_background": "Verwenden Sie den vom ausgewählten Thema bereitgestellten Hintergrund",
"theme": "Theme auswählen",
"background": "Benutzerdefinierten Hintergrund festlegen (Link oder Bild-Datei)",
"use_theme_background": "Hintergrund des ausgewählten Themes verwenden",
"theme": "Theme festlegen",
"patch_rsa": "RSA automatisch patchen",
"use_proxy": "Gebruik interne proxy",
"wipe_login": "Wis de inlogcache",
"horny_mode": "Geile modus",
"auto_mongodb": "Start automatisch MongoDB",
"un_elevated": "Führen Sie das Spiel nicht erhöht aus (kein Admin)",
"redirect_more": "Leiten Sie auch andere MHY-Spiele um"
"use_proxy": "Internen Proxy verwenden",
"wipe_login": "Login-Cache löschen",
"horny_mode": "Horny Modus",
"auto_mongodb": "MongoDB automatisch starten",
"un_elevated": "Spiel ohne Administratorrechte ausführen",
"redirect_more": "Auch andere miHoYo-Spiele umleiten",
"check_aagl": "Für weitere Optionen, schaue weiter",
"grasscutter_elevation": "Methode zur Ausführung von GC auf eingeschränkten Ports",
"web_cache": "WebCaches-Ordner löschen",
"launch_args": "Start-Argumente",
"offline_mode": "Offline-Modus",
"fix_res": "Login-Zeitüberschreitung beheben",
"show_version": "Spielversion in Schaltflächen anzeigen",
"save_profile": "Profil speichern"
},
"downloads": {
"grasscutter_fullbuild": "Alles in Einem Grasscutter Daten herunterladen",
"grasscutter_fullquest": "Alles in Einem Questing Daten herunterladen",
"grasscutter_stable_data": "Stabile Grasscutter Daten herunterladen",
"grasscutter_latest_data": "Aktuellste Grasscutter Daten herunterladen",
"grasscutter_stable_data_update": "Stabile Grasscutter Daten aktualisieren",
"grasscutter_latest_data_update": "Aktuellste Grasscutter Daten aktualisieren",
"grasscutter_unstable": "Stabile Grasscutter Version herunterladen",
"grasscutter_latest": "Aktuellste Grasscutter Version herunterladen",
"grasscutter_unstable_update": "Stabile Grasscutter Version aktualisieren",
"grasscutter_latest_update": "Aktuellste Grasscutter Version aktualisieren",
"resources": "Grasscutter Ressourcen herunterladen",
"grasscutter_fullbuild": "Grasscutter All-in-One herunterladen",
"grasscutter_fullquest": "6.1 All-in-One herunterladen",
"grasscutter_stable_data": "Stabile Grasscutter-Daten herunterladen",
"grasscutter_latest_data": "Neueste Grasscutter-Daten herunterladen",
"grasscutter_stable_data_update": "Stabile Grasscutter-Daten aktualisieren",
"grasscutter_latest_data_update": "Neueste Grasscutter-Daten aktualisieren",
"grasscutter_unstable": "Grasscutter Questing herunterladen",
"grasscutter_latest": "Neueste Grasscutter-Version herunterladen",
"grasscutter_unstable_update": "Grasscutter Questing aktualisieren",
"grasscutter_latest_update": "Neueste Grasscutter-Version aktualisieren",
"resources": "Grasscutter-Ressourcen herunterladen",
"game": "Spiel herunterladen",
"aio_header": "Alles in Einem herunterladen",
"individual_header": "Einzelne Teile herunterladen:",
"aio_header": "Alles-in-Einem Downloads:",
"individual_header": "Einzelne Downloads:",
"mods_header": "Mods:",
"migoto": "GIMI 3dmigoto herunterladen"
},
"download_status": {
"downloading": "Lädt herunter",
"extracting": "Extrahiert",
"downloading": "Herunterladen",
"extracting": "Extrahieren",
"error": "Fehler",
"finished": "Fertig",
"finished": "Abgeschlossen",
"stopped": "Gestoppt"
},
"components": {
@@ -64,33 +73,36 @@
"select_folder": "Ordner auswählen...",
"download": "Herunterladen",
"delete": "Löschen",
"install": "Installieren"
"install": "Installieren",
"fix": "Fix"
},
"news": {
"latest_commits": "Letzte Commits",
"latest_version": "Letzte Version"
"latest_commits": "Neueste Commits",
"latest_version": "Neueste Version"
},
"help": {
"port_help_text": "Vergewissern Sie sich, dass es sich um den Port des Dispatch-Servers handelt, nicht um den Port des Spiel-Servers. Dieser ist fast immer '443'.",
"game_help_text": "Sie müssen keine separate Kopie verwenden, um mit Grasscutter zu spielen. Dies ist entweder für ein Downgrade auf die Version 2.6 oder wenn Sie das Spiel nicht installiert haben.",
"gc_stable_jar": "Laden Sie den aktuellen stabilen Grasscutter-Build herunter, der eine Jar-Datei und Datendateien enthält.",
"gc_fullbuild": "Download een volledige Grasscutter-build, inclusief repo, jar en bronnen. Is volledig ingesteld en vereist geen andere downloads uit dit menu",
"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",
"emergency_rsa": "Im Fall, dass etwas schief laufen sollte, kannst du deine RSA 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_rsa": "Patche und aktualisiere deine RSA automatisch. Solange du nicht mit einer alten/nicht offiziellen Version spielst oder deine RSA manuell gepatcht hast, sollte dies aktiviert sein.",
"add_delay": "Verzögerung im 3dmigoto-Lader einstellen! \nDies sollte die Ladeprobleme beheben, führt aber zu einer kleinen Verzögerung, wenn 3dmigoto beim Start des Spiels geladen wird. \nSie können nun wieder mit 3dmigoto starten.",
"migoto": "Zum Importieren von Modellen von GameBanana"
"port_help_text": "Stelle sicher, dass dies der Port des Dispatch-Servers ist, nicht der des Spielservers. Dies ist fast immer '443'.",
"game_help_text": "Du musst keine separate Kopie verwenden, um mit Grasscutter zu spielen. Dies ist entweder für ein Downgrade auf Version 2.6 oder wenn du das Spiel nicht installiert hast.",
"gc_stable_jar": "Lade die aktuelle stabile Grasscutter-Version herunter, die die JAR-Datei und Daten enthält.",
"gc_fullbuild": "Lade eine vollständige Grasscutter-Version herunter, inklusive Repository, JAR-Datei und Ressourcen. Sie ist vollständig eingerichtet und erfordert keine weiteren Downloads aus diesem Menü.",
"gc_dev_jar": "Lade die neueste Entwicklungsversion von Grasscutter herunter, die die JAR-Datei und Daten enthält.",
"gc_stable_data": "Lade die aktuellen stabilen Grasscutter-Daten herunter, die keine JAR-Datei enthalten. Dies ist nützlich für Updates.",
"gc_dev_data": "Lade die neuesten Entwicklungsdetails von Grasscutter herunter, die keine JAR-Datei enthalten. Dies ist nützlich für Updates.",
"encryption": "Dies sollte normalerweise deaktiviert sein.",
"resources": "Diese werden auch benötigt, um einen Grasscutter-Server auszuführen. Diese Schaltfläche ist grau, wenn bereits ein Ressourcenordner mit Inhalten vorhanden ist.",
"emergency_rsa": "Für den Fall, dass etwas schiefgegangen ist, erzwingt das Löschen des RSA-Patches.",
"use_proxy": "Verwende den internen Cultivation-Proxy. Dies sollte aktiviert sein, es sei denn, du verwendest etwas wie Fiddler.",
"patch_rsa": "Patche und entpatche dein Spiel-RSA automatisch. Dies sollte aktiviert sein, es sei denn, du spielst mit alten/nicht offiziellen Versionen (Versionen 3.0 und älter).",
"add_delay": "Setze eine Verzögerung im 3dmigoto-Loader! Dies sollte Ladeprobleme beheben, fügt jedoch eine geringfügige Verzögerung hinzu, wenn 3dmigoto beim Starten des Spiels geladen wird. Du kannst jetzt wieder mit 3dmigoto starten.",
"migoto": "Zum Importieren von Modellen von GameBanana",
"grasscutter_elevation_help_text": "Die Methode, die verwendet wird, um Grasscutter zu ermöglichen, Port 443 zu binden (was für normale Benutzer unter Linux nicht erlaubt ist). Verfügbare Methoden:\n Capability - gewähre der Java Virtual Machine die Fähigkeit, Ports unter 1024 zu binden. Dies ermöglicht auch allen anderen Programmen, die auf dieser JVM ausgeführt werden, diese Ports zu binden.\n Root - führe GC als Root aus. Dies ermöglicht auch dem GC-Server, seinen Plugins und der JVM so ziemlich alles, einschließlich das Senden deiner Bilder an die NSA, CIA und die Alphabet-Boys.\n None - für keine Methode. Dies erfordert, dass du den GC-Dispatch-Port änderst."
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "Akebi.exe festlegen",
"migoto": "Migoto.exe festlegen",
"reshade": "Reshade injector festlegen"
"akebi": "Akebi/Acrepi Ausführbare Datei festlegen",
"migoto": "3DMigoto Ausführbare Datei festlegen",
"reshade": "Reshade Injector festlegen"
}
}

View File

@@ -1,7 +1,7 @@
{
"lang_name": "English",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "Launch",
"gc_enable": "Connect to Grasscutter",
"https_enable": "Use HTTPS",
@@ -35,11 +35,17 @@
"un_elevated": "Run the game non-elevated (no admin)",
"redirect_more": "Also redirect other MHY games",
"check_aagl": "For more options, check the other launcher",
"grasscutter_elevation": "Method of running GC on restricted ports"
"grasscutter_elevation": "Method of running GC on restricted ports",
"web_cache": "Delete webCaches folder",
"launch_args": "Launch Args",
"offline_mode": "Offline Mode",
"fix_res": "Fix Login Timeout",
"show_version": "Show game version in buttons",
"save_profile": "Save profile"
},
"downloads": {
"grasscutter_fullbuild": "Download Grasscutter All-in-One",
"grasscutter_fullquest": "Download Questing All-in-One",
"grasscutter_fullbuild": "Download Grasscutter 4.0 All-in-One",
"grasscutter_fullquest": "Download 6.1 All-in-One",
"grasscutter_stable_data": "Download Grasscutter Stable Data",
"grasscutter_latest_data": "Download Grasscutter Latest Data",
"grasscutter_stable_data_update": "Update Grasscutter Stable Data",
@@ -67,7 +73,8 @@
"select_folder": "Select folder...",
"download": "Download",
"delete": "Delete",
"install": "Install"
"install": "Install",
"fix": "Fix"
},
"news": {
"latest_commits": "Recent Commits",
@@ -94,7 +101,7 @@
"akebi_name": "Akebi",
"migoto_name": "Migoto",
"reshade_name": "Reshade",
"akebi": "Set Akebi/Acrepi Executable",
"akebi": "Set Akebi/Other Cheat Executable",
"migoto": "Set 3DMigoto Executable",
"reshade": "Set Reshade Injector"
}

View File

@@ -1,7 +1,7 @@
{
"lang_name": "Español",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "Launch",
"gc_enable": "Conectar Via Grasscutter",
"https_enable": "Usar HTTPS",
@@ -33,11 +33,17 @@
"horny_mode": "Modo cachondo",
"auto_mongodb": "Iniciar automáticamente MongoDB",
"un_elevated": "Ejecutar el juego sin permisos de administrador",
"redirect_more": "También redirigir otros juegos MHY"
"redirect_more": "También redirigir otros juegos MHY",
"web_cache": "Eliminar la carpeta webCaches",
"launch_args": "Args de lanzamiento",
"offline_mode": "Modo sin conexión",
"fix_res": "Reparar el tiempo de espera",
"show_version": "Mostrar versión del juego en botones",
"save_profile": "Guardar perfil"
},
"downloads": {
"grasscutter_fullbuild": "Descargar datos todo en uno de Grasscutter",
"grasscutter_fullquest": "Descargar datos todo en uno de Questing",
"grasscutter_fullquest": "Descargar datos todo en uno de 6.1",
"grasscutter_stable_data": "Descargar datos Estables de Grasscutter",
"grasscutter_latest_data": "Descargar datos más Recientes de Grasscutter",
"grasscutter_stable_data_update": "Actualizar datos estables de Grasscutter",
@@ -65,7 +71,8 @@
"select_folder": "Seleccionar la carpeta",
"download": "Descargar",
"delete": "Borrar",
"install": "Instalar"
"install": "Instalar",
"fix": "Fix"
},
"news": {
"latest_commits": "Commits recientes",

View File

@@ -1,7 +1,7 @@
{
"lang_name": "Francais",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "Lancer",
"gc_enable": "Se connecter avec Grasscutter",
"https_enable": "Utiliser HTTPS",
@@ -33,11 +33,17 @@
"horny_mode": "Mode horny",
"auto_mongodb": "Démarrer automatiquement MongoDB",
"un_elevated": "Exécuter le jeu sans élévation (pas d'administrateur)",
"redirect_more": "Réorienter également les autres jeux MHY"
"redirect_more": "Réorienter également les autres jeux MHY",
"web_cache": "Supprimer le dossier webCaches",
"launch_args": "Arguments de lancement",
"offline_mode": "Mode hors ligne",
"fix_res": "Réparation du login",
"show_version": "Afficher la version du jeu sur les boutons",
"save_profile": "Enregistrer le profil"
},
"downloads": {
"grasscutter_fullbuild": "Telecharger Grasscutter tout-en-un",
"grasscutter_fullquest": "Télécharger les Quêtes tout-en-un",
"grasscutter_fullquest": "Télécharger les 6.1 tout-en-un",
"grasscutter_stable_data": "Télécharger les donnees de Grasscutter (version stable)",
"grasscutter_latest_data": "Télécharger les donnees de Grasscutter (derniere version)",
"grasscutter_stable_data_update": "Mettre à jour les données de Grasscutter (version stable)",
@@ -64,7 +70,8 @@
"select_folder": "Choisir un dossier...",
"download": "Télécharger",
"delete": "Supprimer",
"install": "Installer"
"install": "Installer",
"fix": "Fix"
},
"news": {
"latest_commits": "Commits récents",

View File

@@ -1,7 +1,7 @@
{
"lang_name": "Indonesia",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "Luncurkan",
"gc_enable": "Terhubung Melalui GrassCutter",
"ip_placeholder": "Alamat Server...",
@@ -32,11 +32,17 @@
"horny_mode": "Mode Terangsang",
"auto_mongodb": "Mulai MongoDB secara otomatis",
"un_elevated": "Jalankan game yang tidak ditinggikan (tanpa admin)",
"redirect_more": "Juga mengarahkan ulang game MHY lainnya"
"redirect_more": "Juga mengarahkan ulang game MHY lainnya",
"web_cache": "Hapus folder webCaches",
"launch_args": "Luncurkan Args",
"offline_mode": "Mode Offline",
"fix_res": "Perbaiki batas waktu login",
"show_version": "Tampilkan versi game pada tombol",
"save_profile": "Simpan profil"
},
"downloads": {
"grasscutter_fullbuild": "Sedang Mendownload Grasscutter Semua Dalam Satu",
"grasscutter_fullquest": "Unduh pencarian semua dalam satu",
"grasscutter_fullquest": "Unduh 6.1 semua dalam satu",
"grasscutter_stable_data": "Sedang Mendownload Grasscutter Versi Stabil",
"grasscutter_latest_data": "Sedang Mendownload Grasscutter Data Terbaru",
"grasscutter_stable_data_update": "Memperbaharui Grasscutter Data Stabil",
@@ -62,7 +68,8 @@
"select_file": "Pilih File Atau Folder...",
"select_folder": "Pilih Folder...",
"download": "download",
"delete": "Menghapus"
"delete": "Menghapus",
"fix": "Fix"
},
"news": {
"latest_commits": "Commit Terbaru",

View File

@@ -33,11 +33,17 @@
"horny_mode": "Modalità Horny",
"auto_mongodb": "Avvia Automaticamente MongoDB",
"un_elevated": "Avvia il gioco non-elevato (non admin)",
"redirect_more": "Reindirizza anche altri giochi MHY"
"redirect_more": "Reindirizza anche altri giochi MHY",
"web_cache": "Elimina la cartella webCaches",
"launch_args": "Argomenti di lancio",
"offline_mode": "Modalità Offline",
"fix_res": "Correggere il timeout dell'accesso",
"show_version": "Mostra la versione del gioco sui pulsanti",
"save_profile": "Salva il profilo"
},
"downloads": {
"grasscutter_fullbuild": "Scarica Grasscutter Tutto-in-Uno",
"grasscutter_fullquest": "Scarica Questing Tutto-in-Uno",
"grasscutter_fullquest": "Scarica 6.1 Tutto-in-Uno",
"grasscutter_stable_data": "Scarica i dati di Grasscutter Stabili",
"grasscutter_latest_data": "Scarica i dati di Grasscutter Più Recenti",
"grasscutter_stable_data_update": "Aggiorna i dati di Grasscutter Stabili",
@@ -65,7 +71,8 @@
"select_folder": "Seleziona cartella...",
"download": "Scarica",
"delete": "Cancella",
"install": "Installa"
"install": "Installa",
"fix": "Fix"
},
"news": {
"latest_commits": "Commit Recenti",

105
src-tauri/lang/ja.json Normal file
View File

@@ -0,0 +1,105 @@
{
"lang_name": "日本語",
"main": {
"title": "Cultivation: Thorny Edition",
"launch_button": "起動",
"gc_enable": "Grasscutterに接続",
"https_enable": "HTTPS接続を使用",
"ip_placeholder": "サーバーアドレス...",
"port_placeholder": "ポート...",
"files_downloading": "ファイルをダウンロード中: ",
"files_extracting": "ファイルを展開中: ",
"game_path_notify": "ゲームのパスが見つかりません!"
},
"options": {
"enabled": "有効",
"disabled": "無効",
"game_path": "ゲームのインストールパスを設定",
"game_command": "ゲームの実行コマンド",
"game_executable": "ゲームの実行ファイルパスを設定",
"recover_rsa": "RSAを強制削除",
"grasscutter_jar": "Grasscutter jarファイルを設定",
"toggle_encryption": "暗号化の有無",
"install_certificate": "プロキシの証明書をインストール",
"java_path": "カスタムJavaパスを設定",
"grasscutter_with_game": "ゲーム起動時にGrasscutterを自動起動",
"language": "言語を設定",
"background": "カスタムの背景を設定 (画像ファイルまたはリンク)",
"use_theme_background": "選択したテーマが提供する背景を使用",
"theme": "テーマを設定",
"patch_rsa": "自動的にRSAにパッチを適用",
"use_proxy": "内部プロキシを使用",
"wipe_login": "ログインキャッシュを削除",
"horny_mode": "Hornyモード",
"auto_mongodb": "MongoDBを自動起動",
"un_elevated": "昇格せずにゲームを実行 (非管理者権限)",
"redirect_more": "他のmhyゲームもリダイレクト",
"check_aagl": "その他のオプションは、他のランチャーをチェックしてください",
"grasscutter_elevation": "制限されたポートでのGCの実行方法",
"web_cache": "webCachesフォルダを削除",
"launch_args": "Launch Args",
"show_version": "ボタンにゲームバージョンを表示",
"save_profile": "プロフィールを保存"
},
"downloads": {
"grasscutter_fullbuild": "Grasscutter All-in-Oneをダウンロード",
"grasscutter_fullquest": "6.1 All-in-Oneをダウンロード",
"grasscutter_stable_data": "Grasscutter安定版データファイルをダウンロード",
"grasscutter_latest_data": "Grasscutter最新版データファイルをダウンロード",
"grasscutter_stable_data_update": "Grasscutter安定版データファイルをアップデート",
"grasscutter_latest_data_update": "Grasscutter最新版データファイルをアップデート",
"grasscutter_unstable": "Grasscutter Questingをダウンロード",
"grasscutter_latest": "Grasscutter最新版をダウンロード",
"grasscutter_unstable_update": "Grasscutter Questingをアップデート",
"grasscutter_latest_update": "Grasscutter最新版をアップデート",
"resources": "Grasscutter Resourcesをダウンロード",
"game": "ゲームをダウンロード",
"aio_header": "All-in-Oneダウンロード:",
"individual_header": "個別ダウンロード:",
"mods_header": "Mod:",
"migoto": "GIMI 3Dmigotoをダウンロード"
},
"download_status": {
"downloading": "ダウンロード中",
"extracting": "展開中",
"error": "エラー",
"finished": "完了しました",
"stopped": "停止しました"
},
"components": {
"select_file": "ファイルまたはフォルダーを選択...",
"select_folder": "フォルダーを選択...",
"download": "ダウンロード",
"delete": "削除",
"install": "インストール"
},
"news": {
"latest_commits": "最新のコミット",
"latest_version": "最新のバージョン"
},
"help": {
"port_help_text": "ゲームサーバーのポートではなく、Dispatchサーバーのポートです。これは大抵'443'です。",
"game_help_text": "Grasscutterでプレイするために別のコピーを使用する必要はありません。これは、ゲームがインストールされていない場合か、ver 2.6にダウングレードするためにあります。",
"gc_stable_jar": "Grasscutterの現時点での安定版 (jarファイルとデータファイルを含む) をダウンロードします。",
"gc_fullbuild": "repo、jar、resourcesを含む完全なGrasscutterビルドをダウンロードします。これは完全にセットアップされており、他のダウンロードを行う必要はありません。",
"gc_dev_jar": "Grasscutterの現時点での最新版 (jarファイルとデータファイルを含む) をダウンロードします。",
"gc_stable_data": "Grasscutterの現時点での安定版データファイルをダウンロードします。jarファイルは含まれません。アップデートのために使用します。",
"gc_dev_data": "Grasscutterの現時点での最新版データファイルをダウンロードします。jarファイルは含まれません。アップデートのために使用します。",
"encryption": "これは通常は無効にするべきです。",
"resources": "これはGrasscutterサーバーを実行するために必要です。既存のresourcesフォルダ内にファイルがある場合、このボタンはグレーアウトします。",
"emergency_rsa": "何か問題が起きた場合に、RSAパッチを強制的に削除します。",
"use_proxy": "Cultivationの内部プロキシを使用します。Fiddlerのような外部のプロキシを使わない限り、これを有効にしておく必要があります。",
"patch_rsa": "ゲームのRSAに自動的にパッチを適用/解除します。古い(ver 3.0以前)又は非公式のバージョンでプレイしない限り、これは有効にしておくべきです。",
"add_delay": "3Dmigoto Loaderに遅延を設定しました!\nこれはロード時の問題を解決しますが、ゲーム起動時に3Dmigotoがロードされる際に遅延が発生します。\nもう一度3Dmigotoを使用して起動できます。",
"migoto": "GameBananaからモデルをインポートするために使用します。",
"grasscutter_elevation_help_text": "Grasscutterがポート443をバインド(Linuxでは一般ユーザーは許可されていません)できるようにするための方法を指定します。\n利用可能な方法:\n「Capability」1024以下のポートをバインドする権限をJava仮想マシンに与えます。これは、そのJVM上で実行されている他のすべてのプログラムがこれらのポートをバインドできるようになることを意味します。\n「Root」Grasscutterをrootとして実行します。これは、GCサーバー、そのプラグイン、およびJVMが制限なくほとんど何でもできるようになることを意味します。\n「None」なし。この場合、GrasscutterのDispatchポートを変更する必要があります。"
},
"swag": {
"akebi_name": "Akebi",
"migoto_name": "3Dmigoto",
"reshade_name": "Reshade",
"akebi": "Akebi実行ファイルを設定",
"migoto": "3Dmigoto実行ファイルを設定",
"reshade": "Reshadeインジェクターを設定"
}
}

View File

@@ -1,7 +1,7 @@
{
"lang_name": "한국어",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "게임 시작",
"gc_enable": "Grasscutter 연결",
"https_enable": "HTTPS 사용",
@@ -33,11 +33,17 @@
"horny_mode": "Horny 모드",
"auto_mongodb": "MongoDB 자동 시작",
"un_elevated": "게임 비상승 실행(관리자 없음)",
"redirect_more": "다른 MHY 게임도 리디렉션"
"redirect_more": "다른 MHY 게임도 리디렉션",
"web_cache": "webCaches 폴더 삭제",
"launch_args": "실행 인수",
"offline_mode": "오프라인 모드",
"fix_res": "로그인 시간 초과 수정",
"show_version": "버튼에 게임 버전 표시",
"save_profile": "프로필 저장"
},
"downloads": {
"grasscutter_fullbuild": "올인원 Grasscutter 다운로드",
"grasscutter_fullquest": "퀘스트 올인원 다운로드",
"grasscutter_fullquest": "6.1 올인원 다운로드",
"grasscutter_stable_data": "안정적인 데이터 다운로드",
"grasscutter_latest_data": "최신 데이터 다운로드",
"grasscutter_stable_data_update": "안정적 데이터 업데이트",
@@ -65,7 +71,8 @@
"select_folder": "폴더 선택...",
"download": "다운로드",
"delete": "삭제",
"install": "설치"
"install": "설치",
"fix": "Fix"
},
"news": {
"latest_commits": "공지 사항",

View File

@@ -1,7 +1,7 @@
{
"lang_name": "Latviešu",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "Palaist",
"gc_enable": "Savienot ar Grasscutter",
"https_enable": "Izm. HTTPS",
@@ -31,11 +31,17 @@
"horny_mode": "Uzbudināts režīms",
"auto_mongodb": "Automātiski startējiet MongoDB",
"un_elevated": "Palaist spēli bez paaugstinājuma (bez administratora)",
"redirect_more": "Arī novirzīt citas MHY spēles"
"redirect_more": "Arī novirzīt citas MHY spēles",
"web_cache": "Dzēsiet mapi WebCaches",
"launch_args": "Palaišanas args",
"offline_mode": "Bezsaistes režīms",
"fix_res": "Fiksēt pieteikšanās laika",
"show_version": "Pogās redzamā spēles versija",
"save_profile": "Saglabāt profilu"
},
"downloads": {
"grasscutter_fullbuild": "Lejupielādējiet Grasscutter viss vienā",
"grasscutter_fullquest": "Lejupielādēt questing viss vienā",
"grasscutter_fullquest": "Lejupielādēt 6.1 viss vienā",
"grasscutter_stable_data": "Lejupielādējiet Grasscutter stabilos datus",
"grasscutter_latest_data": "Lejupielādējiet Grasscutter jaunākos datus",
"grasscutter_stable_data_update": "Atjauniniet Grasscutter stabilos datus",
@@ -61,7 +67,8 @@
"select_file": "Izvēlēties failu vai mapu...",
"select_folder": "Izvēlēties mapu...",
"download": "Lejupielādēt",
"delete": "Dzēst"
"delete": "Dzēst",
"fix": "Fix"
},
"news": {
"latest_commits": "Nesen kommitus",

View File

@@ -1,7 +1,7 @@
{
"lang_name": "Nederlands",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "Start",
"gc_enable": "Verbind Met Grasscutter",
"https_enable": "Gebruik HTTPS",
@@ -32,11 +32,17 @@
"horny_mode": "Geile modus",
"auto_mongodb": "Start automatisch MongoDB",
"un_elevated": "Voer het spel uit zonder hoogtevrees (geen admin)",
"redirect_more": "Richt ook andere MHY-spellen"
"redirect_more": "Richt ook andere MHY-spellen",
"web_cache": "Verwijder de webCaches-map",
"launch_args": "Args starten",
"offline_mode": "Offline Modus",
"fix_res": "Time-out inloggen verhelpen",
"show_version": "Spelversie weergegeven op knoppen",
"save_profile": "Profiel opslaan"
},
"downloads": {
"grasscutter_fullbuild": "Grasscutter Alles-in-één Downloaden",
"grasscutter_fullquest": "Alles-in-één zoeken downloaden",
"grasscutter_fullquest": "Alles-in-één 6.1 downloaden",
"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",
@@ -64,7 +70,8 @@
"select_folder": "Select folder...",
"download": "Download",
"delete": "Verwijder",
"install": "Install"
"install": "Install",
"fix": "Fix"
},
"news": {
"latest_commits": "Recente Opdrachten",

View File

@@ -35,11 +35,17 @@
"un_elevated": "Uruchamiaj grę bez uprawnień administratora/roota",
"redirect_more": "Przekieruj też inne gry MHY",
"check_aagl": "Więcej opcji znajdziesz w drugim launcherze",
"grasscutter_elevation": "Sposób uruchomienia GC na ograniczonym porcie"
"grasscutter_elevation": "Sposób uruchomienia GC na ograniczonym porcie",
"web_cache": "Usuń folder webCaches",
"launch_args": "Argumenty uruchamiania",
"offline_mode": "Tryb offline",
"fix_res": "Napraw limit czasu logowania",
"show_version": "Wersja gry wyświetlana na przyciskach",
"save_profile": "Zapisz profil"
},
"downloads": {
"grasscutter_fullbuild": "Pobierz Grasscutter (wszystko w jednym)",
"grasscutter_fullquest": "Pobierz Questing (wszystko w jednym)",
"grasscutter_fullquest": "Pobierz 6.1 (wszystko w jednym)",
"grasscutter_stable_data": "Pobierz stabilne dane Grasscuttera",
"grasscutter_latest_data": "Pobierz najnowsze dane Grasscuttera",
"grasscutter_stable_data_update": "Zaaktualizuj stabilne dane Grasscuttera",
@@ -67,7 +73,8 @@
"select_folder": "Wybierz folder...",
"download": "Pobierz",
"delete": "Usuń",
"install": "Zainstaluj"
"install": "Zainstaluj",
"fix": "Fix"
},
"news": {
"latest_commits": "Ostatnie Commity",

View File

@@ -33,11 +33,17 @@
"horny_mode": "Modo com tesão",
"auto_mongodb": "Iniciar MongoDB Automaticamente",
"un_elevated": "Executar o jogo não-elevated (sem admin)",
"redirect_more": "Também redirecionar outros jogos MHY"
"redirect_more": "Também redirecionar outros jogos MHY",
"web_cache": "Excluir pasta webCaches",
"launch_args": "Argumentos de lançamento",
"offline_mode": "Modo offline",
"fix_res": "Corrigir o tempo limite de login",
"show_version": "Versão do jogo exibida nos botões",
"save_profile": "Salvar perfil"
},
"downloads": {
"grasscutter_fullbuild": "Baixar o Grasscutter Tudo-em-Um",
"grasscutter_fullquest": "Baixar de missões em um só lugar",
"grasscutter_fullquest": "Baixar de 6.1 em um só lugar",
"grasscutter_stable_data": "Baixar os Dados do Grasscutter Estável",
"grasscutter_latest_data": "Baixar os Dados do Grasscutter Mais Recente",
"grasscutter_stable_data_update": "Atualizar os Dados do Grasscutter Estável",
@@ -65,7 +71,8 @@
"select_folder": "Selecione a pasta...",
"download": "Baixar",
"delete": "Deletar",
"install": "Instalar"
"install": "Instalar",
"fix": "Fix"
},
"news": {
"latest_commits": "Commits Recentes",

View File

@@ -1,7 +1,7 @@
{
"lang_name": "Русский",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "Запустить",
"gc_enable": "Подключиться с Grasscutter",
"https_enable": "Исп. HTTPS",
@@ -32,11 +32,17 @@
"horny_mode": "роговой режим",
"auto_mongodb": "Автоматически запускать MongoDB",
"un_elevated": "Запустите игру в неэлегантном режиме (без администратора)",
"redirect_more": "Также перенаправьте другие игры MHY"
"redirect_more": "Также перенаправьте другие игры MHY",
"web_cache": "Удалить папку webCaches",
"launch_args": "Параметры запуска",
"offline_mode": "Автономный режим",
"fix_res": "Исправить таймаут входа в систему",
"show_version": "Версия игры отображается на кнопках",
"save_profile": "Сохранить профиль"
},
"downloads": {
"grasscutter_fullbuild": "Скачать все в одном Grasscutter",
"grasscutter_fullquest": "Скачать квесты все в одном",
"grasscutter_fullquest": "Скачать 6.1 все в одном",
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
"grasscutter_latest_data": "Скачать последние данные Grasscutter",
"grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter",
@@ -64,7 +70,8 @@
"select_folder": "Выберите папку...",
"download": "Скачать",
"delete": "Удалить",
"install": "Установить"
"install": "Установить",
"fix": "Fix"
},
"news": {
"latest_commits": "Последние коммиты",

View File

@@ -1,7 +1,7 @@
{
"lang_name": "Tiếng Việt",
"main": {
"title": "Cultivation",
"title": "Cultivation: Thorny Edition",
"launch_button": "Khởi Chạy",
"gc_enable": "Kết nối qua Grasscutter",
"https_enable": "Dùng HTTPS",
@@ -33,11 +33,17 @@
"horny_mode": "Chế độ hứng tình",
"auto_mongodb": "Tự động khởi động MongoDB",
"un_elevated": "Chạy trò chơi không nâng cao (không có quản trị viên)",
"redirect_more": "Đồng thời chuyển hướng các trò chơi MHY khác"
"redirect_more": "Đồng thời chuyển hướng các trò chơi MHY khác",
"web_cache": "Xóa thư mục webCaches",
"launch_args": "Khởi chạy đối số",
"offline_mode": "Chế độ ngoại tuyến",
"fix_res": "Sửa lỗi hết thời gian đăng nhập",
"show_version": "Hiển thị phiên bản trò chơi trên các nút",
"save_profile": "Lưu hồ sơ"
},
"downloads": {
"grasscutter_fullbuild": "Tải Grasscutter tất cả trong một",
"grasscutter_fullquest": "Tải xuống truy vấn tất cả trong một",
"grasscutter_fullquest": "Tải 6.1 tất cả trong một",
"grasscutter_stable_data": "Tải dữ liệu Grasscutter bản ổn định",
"grasscutter_latest_data": "Tải dữ liệu Grasscutter bản mới nhất",
"grasscutter_stable_data_update": "Cập nhật dữ liệu Grasscutter bản ổn định",
@@ -65,7 +71,8 @@
"select_folder": "Chọn thư mục...",
"download": "Tải",
"delete": "Xóa bỏ",
"install": "Cài"
"install": "Cài",
"fix": "Fix"
},
"news": {
"latest_commits": "Thay Đổi Gần Đây",

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -28,20 +28,35 @@ pub struct Configuration {
pub auto_mongodb: Option<bool>,
pub un_elevated: Option<bool>,
pub redirect_more: Option<bool>,
pub launch_args: Option<String>,
pub offline_mode: Option<bool>,
pub show_version: Option<bool>,
pub profile: Option<String>,
}
pub fn config_path() -> PathBuf {
pub fn config_path(profile: String) -> PathBuf {
let mut path = tauri::api::path::data_dir().unwrap();
path.push("cultivation");
path.push("configuration.json");
if profile.as_str() == "default" {
path.push("configuration.json");
} else {
path.push("profiles");
path.push(profile + ".json");
}
path
}
pub fn get_config() -> Configuration {
let path = config_path();
pub fn get_config(profile_name: String) -> Configuration {
let path = config_path(profile_name.clone());
let config = std::fs::read_to_string(path).unwrap_or("{}".to_string());
let config: Configuration = serde_json::from_str(&config).unwrap_or_default();
//let default = String::from("default");
let prof = config.profile.clone().unwrap_or_default();
if prof != String::from("default") && prof != profile_name.clone() {
return get_config(prof.clone());
}
config
}

View File

@@ -1,6 +1,6 @@
use file_diff::diff;
use std::fs;
use std::io::{Read, Write};
use std::io::{Read, Seek, SeekFrom, Write};
use std::path::PathBuf;
#[tauri::command]
@@ -57,6 +57,11 @@ pub fn are_files_identical(path1: &str, path2: &str) -> bool {
diff(path1, path2)
}
#[tauri::command]
pub fn does_file_exist(path1: &str) -> bool {
fs::metadata(path1).is_ok()
}
#[tauri::command]
pub fn copy_file(path: String, new_path: String) -> bool {
let filename = &path.split('/').last().unwrap();
@@ -127,21 +132,46 @@ pub fn delete_file(path: String) -> bool {
#[tauri::command]
pub fn read_file(path: String) -> String {
let path_buf = PathBuf::from(&path);
let mut file = match fs::File::open(path_buf) {
Ok(file) => file,
Err(e) => {
println!("Failed to open file {}: {}", &path, e);
if path.contains("config") {
// Server.ts won't print the error so handle the message here for the user
println!("Server config not found or invalid. Be sure to run the server at least once to generate it before making edits.");
}
return String::new(); // Send back error for handling by the caller
}
};
println!("Debug: Reading file of path {}", path.clone(),);
let mut contents = String::new();
file.read_to_string(&mut contents).unwrap();
// Version data is 3 bytes long, 3 bytes in
let ext = path_buf.extension().unwrap();
if ext.eq("bytes") {
let offset_bytes = 3;
let num_bytes = 3;
let mut byte_file = match std::fs::File::open(path_buf) {
Ok(byte_file) => byte_file,
Err(e) => {
println!("{}", e);
return String::new();
}
};
byte_file
.seek(SeekFrom::Start(offset_bytes))
.unwrap_or_default();
let mut buf = vec![0; num_bytes];
byte_file.read_exact(&mut buf).unwrap_or_default();
contents = String::from_utf8_lossy(&buf).to_string();
} else {
let mut file = match fs::File::open(path_buf) {
Ok(file) => file,
Err(e) => {
if path.contains("config") {
// Server.ts won't print the error so handle the message here for the user
println!("Server config not found or invalid. Be sure to run the server at least once to generate it before making edits.");
} else {
println!("Failed to open file: {}", e);
}
return String::new(); // Send back error for handling by the caller
}
};
file.read_to_string(&mut contents).unwrap();
}
contents
}

View File

@@ -28,12 +28,7 @@ pub async fn get_languages() -> std::collections::HashMap<String, String> {
for entry in lang_files {
let entry = entry.unwrap();
let path = entry.path();
let filename = path
.file_name()
.unwrap_or_else(|| panic!("Failed to get filename from path: {:?}", path))
.to_str()
.unwrap_or_else(|| panic!("Failed to convert filename to string: {:?}", path))
.to_string();
let filename = path.file_name().unwrap().to_str().unwrap();
let content = match std::fs::read_to_string(&path) {
Ok(x) => x,

View File

@@ -101,22 +101,22 @@ async fn parse_args(inp: &Vec<String>) -> Result<Args, ArgsError> {
args.parse(inp).unwrap();
let config = config::get_config();
let config = config::get_config(String::from("default"));
if args.value_of("help")? {
println!("{}", args.full_usage());
std::process::exit(0);
}
// Patch if needed
if args.value_of("patch")? {
patch::patch_game(false, 0.to_string()).await;
}
if args.value_of("launch-game")? {
let game_path = config.game_install_path;
let game_args: String = args.value_of("game-args").unwrap_or_default();
// Patch if needed
if args.value_of("patch")? {
patch::patch_game().await;
}
if game_path.is_some() {
if args.value_of("non-elevated-game")? {
system_helpers::run_un_elevated(game_path.unwrap(), Some(game_args))
@@ -154,8 +154,8 @@ async fn parse_args(inp: &Vec<String>) -> Result<Args, ArgsError> {
pathbuf.push("cultivation");
pathbuf.push("ca");
if args.value_of("other_redirects")? {
proxy::set_redirect_more();
if args.value_of("other-redirects")? {
// proxy::set_redirect_more(); // Unused
}
connect(8035, pathbuf.to_str().unwrap().to_string()).await;
@@ -207,8 +207,10 @@ fn main() -> Result<(), ArgsError> {
is_grasscutter_running,
restart_grasscutter,
get_theme_list,
get_profile_list,
system_helpers::run_command,
system_helpers::run_program,
system_helpers::run_program_args,
system_helpers::run_program_relative,
system_helpers::start_service,
system_helpers::service_status,
@@ -218,7 +220,6 @@ fn main() -> Result<(), ArgsError> {
system_helpers::open_in_browser,
system_helpers::install_location,
system_helpers::is_elevated,
system_helpers::set_migoto_target,
system_helpers::set_migoto_delay,
system_helpers::wipe_registry,
system_helpers::get_platform,
@@ -229,7 +230,6 @@ fn main() -> Result<(), ArgsError> {
patch::unpatch_game,
proxy::set_proxy_addr,
proxy::generate_ca_files,
proxy::set_redirect_more,
release::get_latest_release,
unzip::unzip,
file_helpers::rename,
@@ -243,6 +243,7 @@ fn main() -> Result<(), ArgsError> {
file_helpers::are_files_identical,
file_helpers::read_file,
file_helpers::write_file,
file_helpers::does_file_exist,
downloader::download_file,
downloader::stop_download,
lang::get_lang,
@@ -297,7 +298,7 @@ fn enable_process_watcher(window: tauri::Window, process: String) {
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));
std::thread::sleep(std::time::Duration::from_secs(60));
let mut system = System::new_all();
@@ -336,7 +337,7 @@ fn enable_process_watcher(window: tauri::Window, process: String) {
fn enable_process_watcher(window: tauri::Window, process: String) {
drop(process);
thread::spawn(move || {
let end_time = Instant::now() + Duration::from_secs(60);
let end_time = Instant::now() + Duration::from_secs(90);
let game_thread = loop {
let mut lock = AAGL_THREAD.lock().unwrap();
if lock.is_some() {
@@ -536,3 +537,20 @@ async fn get_theme_list(data_dir: String) -> Vec<HashMap<String, String>> {
themes
}
#[tauri::command]
async fn get_profile_list(data_dir: String) -> Vec<String> {
let profile_loc = format!("{}/profiles", data_dir);
// Ensure folder exists
if !std::path::Path::new(&profile_loc).exists() {
std::fs::create_dir_all(&profile_loc).unwrap();
}
let mut p_list = Vec::new();
for entry in std::fs::read_dir(&profile_loc).unwrap() {
p_list.push(entry.unwrap().file_name().into_string().unwrap());
}
p_list
}

View File

@@ -50,8 +50,91 @@ struct WhatToUnpach {
#[cfg(windows)]
#[tauri::command]
pub async fn patch_game() -> bool {
let patch_path = PathBuf::from(system_helpers::install_location()).join("patch/version.dll");
pub async fn patch_game(_newer_game: bool, version: String) -> bool {
let mut patch_path;
// Altpatch first - Now using as hoyonet switch
// People keep using this when they shouldn't, 99.8% of people will never need it. Just remove for now.
// if newer_game {
// let alt_patch_path = PathBuf::from(system_helpers::install_location()).join("altpatch");
// // Should handle overwriting backup with new version backup later
// let backup_path = PathBuf::from(system_helpers::install_location())
// .join("altpatch/original-mihoyonet.dll")
// .to_str()
// .unwrap()
// .to_string();
// let backup_exists = file_helpers::does_file_exist(&backup_path);
// if !backup_exists {
// let backup = file_helpers::copy_file_with_new_name(
// get_game_rsa_path().await.unwrap()
// + &String::from("/GenshinImpact_Data/Plugins/mihoyonet.dll"),
// alt_patch_path.clone().to_str().unwrap().to_string(),
// String::from("original-mihoyonet.dll"),
// );
// if !backup {
// println!("Unable to backup file!");
// }
// }
// patch_path = PathBuf::from(system_helpers::install_location()).join("altpatch/mihoyonet.dll");
// // Copy the other part of patch to game files
// let alt_replaced = file_helpers::copy_file_with_new_name(
// patch_path.clone().to_str().unwrap().to_string(),
// get_game_rsa_path().await.unwrap() + &String::from("/GenshinImpact_Data/Plugins"),
// String::from("mihoyonet.dll"),
// );
// if !alt_replaced {
// return false;
// }
/*** For replacing old backup file with new one, for example when version changes
* Currently replaces when it shouldn't. Will figure it out when it matters
* ***/
// else {
// // Check if game file matches backup
// let matching_alt_backup = file_helpers::are_files_identical(
// &backup_path.clone(),
// PathBuf::from(get_game_rsa_path().await.unwrap())
// .join("/GenshinImpact_Data/Plugins/mihoyonet.dll")
// .to_str()
// .unwrap(),
// );
// let is_alt_patched = file_helpers::are_files_identical(
// PathBuf::from(system_helpers::install_location()).join("altpatch/mihoyonet.dll").to_str().unwrap(),
// PathBuf::from(get_game_rsa_path().await.unwrap())
// .join("/GenshinImpact_Data/Plugins/mihoyonet.dll")
// .to_str()
// .unwrap(),
// );
// // Check if already alt patched
// if !matching_alt_backup {
// // Copy new backup if it is not patched
// if !is_alt_patched {
// file_helpers::copy_file_with_new_name(
// get_game_rsa_path().await.unwrap() + &String::from("/GenshinImpact_Data/Plugins/mihoyonet.dll"),
// alt_patch_path.clone().to_str().unwrap().to_string(),
// String::from("original-mihoyonet.dll"),
// );
// }
// }
// }
// }
// Standard patch
patch_path = PathBuf::from(system_helpers::install_location()).join("patch/version.dll");
let i_ver = version.parse::<i32>().unwrap();
// For newer than 4.0, use specific patch files
if i_ver > 40 {
let patch_version = format!("patch/{version}version.dll");
patch_path = PathBuf::from(system_helpers::install_location()).join(patch_version);
}
// Are we already patched with mhypbase? If so, that's fine, just continue as normal
let game_is_patched = file_helpers::are_files_identical(
@@ -70,6 +153,23 @@ pub async fn patch_game() -> bool {
return true;
}
// For 5.0 and up use universal
if i_ver > 49 {
if i_ver < 59 {
patch_path = PathBuf::from(system_helpers::install_location()).join("patch/5version.dll");
} else {
// 6.0 patch not checked/tested if works for old vers, so separate
patch_path = PathBuf::from(system_helpers::install_location()).join("patch/6version.dll");
}
let replaced50 = file_helpers::copy_file_with_new_name(
patch_path.clone().to_str().unwrap().to_string(),
get_game_rsa_path().await.unwrap(),
String::from("Astrolabe.dll"),
);
return replaced50;
}
// Copy the patch to game files
let replaced = file_helpers::copy_file_with_new_name(
patch_path.clone().to_str().unwrap().to_string(),
@@ -86,7 +186,7 @@ pub async fn patch_game() -> bool {
#[cfg(target_os = "linux")]
#[tauri::command]
pub async fn patch_game() -> bool {
pub async fn patch_game(_newer_game: bool, _version: String) -> bool {
let mut patch_state_mutex = PATCH_STATE.lock().await;
if patch_state_mutex.is_some() {
println!("Game already patched!");
@@ -170,6 +270,36 @@ pub async fn unpatch_game() -> bool {
.to_string(),
);
file_helpers::delete_file(
PathBuf::from(get_game_rsa_path().await.unwrap())
.join("Astrolabe.dll")
.to_str()
.unwrap()
.to_string(),
);
let core_patch_path = PathBuf::from(system_helpers::install_location());
let patch_path = core_patch_path.clone().join("altpatch/mihoyonet.dll");
let backup_path = core_patch_path
.clone()
.join("altpatch/original-mihoyonet.dll");
let is_alt_patched = file_helpers::are_files_identical(
patch_path.clone().to_str().unwrap(),
PathBuf::from(get_game_rsa_path().await.unwrap())
.join("GenshinImpact_Data/Plugins/mihoyonet.dll")
.to_str()
.unwrap(),
);
if is_alt_patched {
file_helpers::copy_file_with_new_name(
backup_path.clone().to_str().unwrap().to_string(),
get_game_rsa_path().await.unwrap() + &String::from("/GenshinImpact_Data/Plugins"),
String::from("mihoyonet.dll"),
);
}
deleted
}
@@ -219,7 +349,7 @@ pub async fn unpatch_game() -> bool {
}
pub async fn get_game_rsa_path() -> Option<String> {
let config = config::get_config();
let config = config::get_config(String::from("default"));
config.game_install_path.as_ref()?;

View File

@@ -3,8 +3,6 @@
* https://github.com/omjadas/hudsucker/blob/main/examples/log.rs
*/
use crate::config::get_config;
use once_cell::sync::Lazy;
use std::{path::PathBuf, str::FromStr, sync::Mutex};
@@ -41,8 +39,6 @@ async fn shutdown_signal() {
// Global ver for getting server address.
static SERVER: Lazy<Mutex<String>> = Lazy::new(|| Mutex::new("http://localhost:443".to_string()));
static REDIRECT_MORE: Lazy<Mutex<bool>> = Lazy::new(|| Mutex::new(false));
#[derive(Clone)]
struct ProxyHandler;
@@ -58,11 +54,6 @@ pub fn set_proxy_addr(addr: String) {
println!("Set server to {}", SERVER.lock().unwrap());
}
#[tauri::command]
pub fn set_redirect_more() {
*REDIRECT_MORE.lock().unwrap() = true;
}
#[async_trait]
impl HttpHandler for ProxyHandler {
async fn handle_request(
@@ -72,93 +63,33 @@ impl HttpHandler for ProxyHandler {
) -> RequestOrResponse {
let uri = req.uri().to_string();
let mut more = get_config().redirect_more;
if uri.contains("hoyoverse.com")
|| uri.contains("mihoyo.com")
|| uri.contains("yuanshen.com")
|| uri.ends_with(".yuanshen.com:12401")
|| uri.contains("starrails.com")
|| uri.contains("bhsr.com")
|| uri.contains("bh3.com")
|| uri.contains("honkaiimpact3.com")
|| uri.contains("zenlesszonezero.com")
{
// Handle CONNECTs
if req.method().as_str() == "CONNECT" {
let builder = Response::builder()
.header("DecryptEndpoint", "Created")
.status(StatusCode::OK);
let res = builder.body(()).unwrap();
if *REDIRECT_MORE.lock().unwrap() {
more = Some(true);
}
match more {
Some(true) => {
if uri.contains("hoyoverse.com")
|| uri.contains("mihoyo.com")
|| uri.contains("yuanshen.com")
|| uri.contains("starrails.com")
|| uri.contains("bhsr.com")
|| uri.contains("bh3.com")
|| uri.contains("honkaiimpact3.com")
|| uri.contains("zenlesszonezero.com")
{
// Handle CONNECTs
if req.method().as_str() == "CONNECT" {
let builder = Response::builder()
.header("DecryptEndpoint", "Created")
.status(StatusCode::OK);
let res = builder.body(()).unwrap();
// Respond to CONNECT
*res.body()
} else {
let uri_path_and_query = req.uri().path_and_query().unwrap().as_str();
// Create new URI.
let new_uri =
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path_and_query).as_str())
.unwrap();
// Set request URI to the new one.
*req.uri_mut() = new_uri;
}
}
}
Some(false) => {
if uri.contains("hoyoverse.com")
|| uri.contains("mihoyo.com")
|| uri.contains("yuanshen.com")
{
// Handle CONNECTs
if req.method().as_str() == "CONNECT" {
let builder = Response::builder()
.header("DecryptEndpoint", "Created")
.status(StatusCode::OK);
let res = builder.body(()).unwrap();
// Respond to CONNECT
*res.body()
} else {
let uri_path_and_query = req.uri().path_and_query().unwrap().as_str();
// Create new URI.
let new_uri =
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path_and_query).as_str())
.unwrap();
// Set request URI to the new one.
*req.uri_mut() = new_uri;
}
}
}
// Use default as fallback
None => {
if uri.contains("hoyoverse.com")
|| uri.contains("mihoyo.com")
|| uri.contains("yuanshen.com")
{
// Handle CONNECTs
if req.method().as_str() == "CONNECT" {
let builder = Response::builder()
.header("DecryptEndpoint", "Created")
.status(StatusCode::OK);
let res = builder.body(()).unwrap();
// Respond to CONNECT
*res.body()
} else {
let uri_path_and_query = req.uri().path_and_query().unwrap().as_str();
// Create new URI.
let new_uri =
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path_and_query).as_str())
.unwrap();
// Set request URI to the new one.
*req.uri_mut() = new_uri;
}
}
// Respond to CONNECT
*res.body()
} else {
let uri_path_and_query = req.uri().path_and_query().unwrap().as_str();
// Create new URI.
let new_uri =
Uri::from_str(format!("{}{}", SERVER.lock().unwrap(), uri_path_and_query).as_str())
.unwrap();
// Set request URI to the new one.
*req.uri_mut() = new_uri;
}
}
@@ -176,26 +107,14 @@ impl HttpHandler for ProxyHandler {
async fn should_intercept(&mut self, _ctx: &HttpContext, _req: &Request<Body>) -> bool {
let uri = _req.uri().to_string();
let more = get_config().redirect_more;
match more {
Some(true) => {
uri.contains("hoyoverse.com")
|| uri.contains("mihoyo.com")
|| uri.contains("yuanshen.com")
|| uri.contains("starrails.com")
|| uri.contains("bhsr.com")
|| uri.contains("bh3.com")
|| uri.contains("honkaiimpact3.com")
|| uri.contains("zenlesszonezero.com")
}
Some(false) => {
uri.contains("hoyoverse.com") || uri.contains("mihoyo.com") || uri.contains("yuanshen.com")
}
None => {
uri.contains("hoyoverse.com") || uri.contains("mihoyo.com") || uri.contains("yuanshen.com")
}
}
uri.contains("hoyoverse.com")
|| uri.contains("mihoyo.com")
|| uri.contains("yuanshen.com")
|| uri.contains("starrails.com")
|| uri.contains("bhsr.com")
|| uri.contains("bh3.com")
|| uri.contains("honkaiimpact3.com")
|| uri.contains("zenlesszonezero.com")
}
}
@@ -307,7 +226,7 @@ pub fn connect_to_proxy(proxy_port: u16) {
Config::update(config);
}
#[cfg(target_od = "macos")]
#[cfg(target_os = "macos")]
pub fn connect_to_proxy(_proxy_port: u16) {
println!("No Mac support yet. Someone mail me a Macbook and I will do it B)")
}

View File

@@ -16,7 +16,6 @@ pub async fn get_latest_release() -> Release {
.unwrap();
let text = response.text().await.unwrap();
// This includes ip when github rate limits you, so avoid it for now to avoid leaks through screenshots
//println!("Response: {}", text);
// Parse "tag_name" from JSON

View File

@@ -44,6 +44,7 @@ fn strcmd(cmd: &Command) -> String {
}
#[cfg(target_os = "linux")]
#[allow(dead_code)]
pub trait AsRoot {
fn as_root(&self) -> Self;
fn as_root_gui(&self) -> Self;
@@ -64,6 +65,7 @@ impl AsRoot for Command {
}
#[cfg(target_os = "linux")]
#[allow(dead_code)]
trait InTerminalEmulator {
fn in_terminal(&self) -> Self;
fn in_terminal_noclose(&self) -> Self;
@@ -106,6 +108,18 @@ impl SpawnItsFineReally for Command {
}
}
#[tauri::command]
pub fn run_program_args(path: String, args: Option<String>) {
match open::with(
format!("{}", args.unwrap_or_else(|| "".into())),
path.clone(),
) {
Ok(_) => (),
Err(e) => println!("Failed to open program ({}): {}", &path, e),
};
return;
}
#[tauri::command]
pub fn run_program(path: String, args: Option<String>) {
// Without unwrap_or, this can crash when UAC prompt is denied
@@ -214,7 +228,7 @@ pub fn run_jar(path: String, execute_in: String, java_path: String) {
#[cfg(not(target_os = "linux"))]
#[tauri::command]
pub fn run_jar_root(path: String, execute_in: String, java_path: String) {
pub fn run_jar_root(_path: String, _execute_in: String, _java_path: String) {
panic!("Not implemented");
}
@@ -390,44 +404,6 @@ pub fn install_location() -> String {
}
}
#[tauri::command]
pub fn set_migoto_target(window: tauri::Window, migoto_path: String) -> bool {
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;
}
};
window.emit("migoto_set", &()).unwrap();
// Set options
conf
.with_section(Some("Loader"))
.set("target", "GenshinImpact.exe");
// Write file
match conf.write_to_file(&migoto_pathbuf) {
Ok(_) => {
println!("Wrote config!");
true
}
Err(e) => {
println!("Error writing config: {}", e);
false
}
}
}
#[tauri::command]
pub fn set_migoto_delay(migoto_path: String) -> bool {
let mut migoto_pathbuf = PathBuf::from(migoto_path);
@@ -448,9 +424,18 @@ pub fn set_migoto_delay(migoto_path: String) -> bool {
// Set options
conf.with_section(Some("Loader")).set("delay", "20");
conf
.with_section(Some("Include"))
.set("include", "ShaderFixes\\help.ini");
// Write file
match conf.write_to_file(&migoto_pathbuf) {
match conf.write_to_file_opt(
&migoto_pathbuf,
ini::WriteOption {
escape_policy: (ini::EscapePolicy::Nothing),
line_separator: (ini::LineSeparator::SystemDefault),
},
) {
Ok(_) => {
println!("Wrote delay!");
true
@@ -483,6 +468,33 @@ pub fn wipe_registry(exec_name: String) {
Ok(_) => (),
Err(e) => println!("Error wiping registry: {}", e),
}
match settings.set_value(
"MIHOYOSDK_ADL_PROD_CN_h3123967166",
&Data::String("".parse().unwrap()),
) {
Ok(_) => (),
Err(e) => println!("Error wiping registry: {}", e),
}
let hsr_settings = match Hive::CurrentUser.open(
"Software\\Cognosphere\\Star Rail".to_string(),
Security::Write,
) {
Ok(s) => s,
Err(e) => {
println!("Error getting registry setting: {}", e);
return;
}
};
match hsr_settings.set_value(
"MIHOYOSDK_ADL_PROD_OVERSEA_h1158948810",
&Data::String("".parse().unwrap()),
) {
Ok(_) => (),
Err(e) => println!("Error wiping registry: {}", e),
}
}
#[cfg(windows)]
@@ -500,7 +512,7 @@ pub fn service_status(service: String) -> bool {
}
};
let status_result = my_service.query_status();
if let Ok(..) = status_result {
if status_result.is_ok() {
let status = status_result.unwrap();
println!("{} service status: {:?}", service, status.current_state);
if status.current_state == Stopped {

View File

@@ -107,7 +107,8 @@ pub fn unzip(
.unwrap();
}
if zipfile.contains("GrasscutterQuests") {
// Alternate builds
if zipfile.contains("GrasscutterQuests") || zipfile.contains("Grasscutter50") {
window
.emit("jar_extracted", destpath.to_string() + "grasscutter.jar")
.unwrap();

View File

@@ -36,8 +36,8 @@ pub(crate) async fn valid_url(url: String) -> bool {
.await
.ok();
if response.is_some() {
return response.unwrap().status().as_str() == "200";
if let Some(thing) = response {
return thing.status().as_str() == "200";
} else {
false
}

View File

@@ -1,5 +1,5 @@
{
"$schema": "../node_modules/@tauri-apps/cli/schema.json",
"$schema": "..\\node_modules/@tauri-apps/cli\\schema.json",
"build": {
"beforeDevCommand": "yarn start",
"devPath": "http://localhost:3000",
@@ -7,7 +7,7 @@
},
"package": {
"productName": "Cultivation",
"version": "1.2.0"
"version": "1.7.1"
},
"tauri": {
"allowlist": {
@@ -67,11 +67,13 @@
"windows": [
{
"fullscreen": false,
"transparent": true,
"height": 730,
"resizable": true,
"title": "Cultivation",
"width": 1280,
"decorations": false
"decorations": false,
"center": true
}
]
}

View File

@@ -17,7 +17,7 @@ interface IState {
}
const downloadHandler = new DownloadHandler()
const DEFAULT_BG = 'https://api.grasscutter.io/cultivation/bgfile'
const WEB_BG = 'https://api.grasscutter.io/cultivation/bgfile'
class App extends React.Component<Readonly<unknown>, IState> {
constructor(props: Readonly<unknown>) {
@@ -25,7 +25,7 @@ class App extends React.Component<Readonly<unknown>, IState> {
this.state = {
page: 'main',
bgFile: DEFAULT_BG,
bgFile: FALLBACK_BG,
}
}
@@ -39,8 +39,9 @@ class App extends React.Component<Readonly<unknown>, IState> {
// Get custom bg AFTER theme is loaded !! important !!
const custom_bg = await getConfigOption('custom_background')
const offline_mode = await getConfigOption('offline_mode')
if (custom_bg) {
if (custom_bg || offline_mode) {
const isUrl = /^http(s)?:\/\//gm.test(custom_bg)
if (!isUrl) {
@@ -50,7 +51,7 @@ class App extends React.Component<Readonly<unknown>, IState> {
this.setState(
{
bgFile: isValid ? convertFileSrc(custom_bg) : DEFAULT_BG,
bgFile: isValid ? convertFileSrc(custom_bg) : FALLBACK_BG,
},
this.forceUpdate
)
@@ -62,7 +63,7 @@ class App extends React.Component<Readonly<unknown>, IState> {
this.setState(
{
bgFile: isValid ? custom_bg : DEFAULT_BG,
bgFile: isValid ? custom_bg : FALLBACK_BG,
},
this.forceUpdate
)
@@ -70,12 +71,12 @@ class App extends React.Component<Readonly<unknown>, IState> {
} else {
// Check if api bg is accessible
const isDefaultValid = await invoke('valid_url', {
url: DEFAULT_BG,
url: WEB_BG,
})
this.setState(
{
bgFile: isDefaultValid ? DEFAULT_BG : FALLBACK_BG,
bgFile: isDefaultValid ? WEB_BG : FALLBACK_BG,
},
this.forceUpdate
)
@@ -86,6 +87,7 @@ class App extends React.Component<Readonly<unknown>, IState> {
// @ts-expect-error - TS doesn't like our custom event
page: e.detail,
})
this.forceUpdate
})
}

View File

@@ -15,7 +15,7 @@ async function setProxyAddress(address: string) {
}
async function startProxy() {
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + 'cultivation/ca' })
await invoke('connect', { port: 2222, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
await invoke('open_in_browser', { url: 'https://hoyoverse.com' })
}
@@ -24,12 +24,12 @@ async function stopProxy() {
}
async function generateCertificates() {
await invoke('generate_ca_files', { path: (await dataDir()) + 'cultivation' })
await invoke('generate_ca_files', { path: (await dataDir()) + '\\cultivation' })
}
async function generateInfo() {
console.log({
certificatePath: (await dataDir()) + 'cultivation/ca',
certificatePath: (await dataDir()) + '\\cultivation\\ca',
isAdmin: await invoke('is_elevated'),
connectingTo: proxyAddress,
})

View File

@@ -45,6 +45,7 @@ interface IState {
notification: React.ReactElement | null
isGamePathSet: boolean
game_install_path: string
platform: string
}
export class Main extends React.Component<IProps, IState> {
@@ -64,6 +65,7 @@ export class Main extends React.Component<IProps, IState> {
notification: null,
isGamePathSet: true,
game_install_path: '',
platform: '',
}
listen('lang_error', (payload) => {
@@ -74,8 +76,10 @@ export class Main extends React.Component<IProps, IState> {
setConfigOption('grasscutter_path', payload)
})
listen('migoto_extracted', ({ payload }: { payload: string }) => {
setConfigOption('migoto_path', payload)
listen('migoto_extracted', async ({ payload }: { payload: string }) => {
await setConfigOption('migoto_path', payload)
this.setState({ migotoSet: true })
window.location.reload()
})
// Emitted for rsa replacing-purposes
@@ -91,14 +95,6 @@ export class Main extends React.Component<IProps, IState> {
}
})
listen('migoto_set', async () => {
this.setState({
migotoSet: !!(await getConfigOption('migoto_path')),
})
window.location.reload()
})
// Emitted for automatic processes
listen('grasscutter_closed', async () => {
const autoService = await getConfigOption('auto_mongodb')
@@ -152,10 +148,18 @@ export class Main extends React.Component<IProps, IState> {
game_install_path: game_path,
})
if (this.state.game_install_path === '') {
this.setState({ isGamePathSet: false })
}
this.setState({
migotoSet: !!(await getConfigOption('migoto_path')),
})
this.setState({
platform: await invoke('get_platform'),
})
if (!cert_generated) {
// Generate the certificate
await invoke('generate_ca_files', {
@@ -166,36 +170,43 @@ export class Main extends React.Component<IProps, IState> {
}
// Ensure old configs are updated to use RSA
const updatedProfile = await getConfigOption('profile')
await setConfigOption('profile', updatedProfile)
const updatedConfig = await getConfigOption('patch_rsa')
await setConfigOption('patch_rsa', updatedConfig)
// Get latest version and compare to this version
const latestVersion: {
tag_name: string
link: string
} = await invoke('get_latest_release')
const tagName = latestVersion?.tag_name.replace(/[^\d.]/g, '')
// Update launch args to allow launching when updating from old versions
await setConfigOption('launch_args', await getConfigOption('launch_args'))
// Check if tagName is different than current version
if (tagName && tagName !== (await getVersion())) {
// Display notification of new release
this.setState({
notification: (
<>
Cultivation{' '}
<a href="#" onClick={() => invoke('open_in_browser', { url: latestVersion.link })}>
{latestVersion?.tag_name}
</a>{' '}
is now available!
</>
),
})
if (!(await getConfigOption('offline_mode'))) {
// Get latest version and compare to this version
const latestVersion: {
tag_name: string
link: string
} = await invoke('get_latest_release')
const tagName = latestVersion?.tag_name.replace(/[^\d.]/g, '')
setTimeout(() => {
// Check if tagName is different than current version
if (tagName && tagName !== (await getVersion())) {
// Display notification of new release
this.setState({
notification: null,
notification: (
<>
Cultivation{' '}
<a href="#" onClick={() => invoke('open_in_browser', { url: latestVersion.link })}>
{latestVersion?.tag_name}
</a>{' '}
is now available!
</>
),
})
}, 6000)
setTimeout(() => {
this.setState({
notification: null,
})
}, 6000)
}
}
// Period check to only show progress bar when downloading files
@@ -222,7 +233,7 @@ export class Main extends React.Component<IProps, IState> {
})) as boolean
// Set no game path so the user understands it doesn't exist there
if (!game_exists) {
if (!game_exists && this.state.platform === 'windows') {
setConfigOption('game_install_path', '')
}
@@ -323,7 +334,10 @@ export class Main extends React.Component<IProps, IState> {
this.state.optionsOpen ? (
<Options
downloadManager={this.props.downloadHandler}
closeFn={() => this.setState({ optionsOpen: !this.state.optionsOpen })}
closeFn={async () => {
this.setState({ optionsOpen: !this.state.optionsOpen })
this.setState({ migotoSet: !!(await getConfigOption('migoto_path')) })
}}
/>
) : null
}
@@ -339,7 +353,7 @@ export class Main extends React.Component<IProps, IState> {
}
<div className="BottomSection" id="bottomSectionContainer">
<ServerLaunchSection openExtras={this.openExtrasMenu} />
<ServerLaunchSection openExtras={this.openExtrasMenu} downloadHandler={this.props.downloadHandler} />
<div
id="DownloadProgress"

View File

@@ -151,7 +151,7 @@ export class Mods extends React.Component<IProps, IState> {
},
this.forceUpdate
)
}, 500)
}, 300)
}
render() {
@@ -211,7 +211,7 @@ export class Mods extends React.Component<IProps, IState> {
<TextInput
id="search"
key="search"
placeholder={this.state.page.toString()}
placeholder={'Search Mods - Page ' + this.state.page.toString()}
onChange={(text: string) => {
this.setSearch(text)
}}

View File

@@ -14,7 +14,7 @@
}
#playButton > div {
margin-bottom: 6px;
margin-bottom: 2px;
}
#playButton .BigButton {
@@ -26,10 +26,27 @@
#serverControls {
color: white;
display: flex;
justify-content: space-between;
flex-direction: row;
text-shadow: 1px 1px 8px black;
}
#menuOptionsContainerProfiles {
justify-self: right;
align-self: right;
padding-left: 8px;
width: min-content;
}
#serverControls select {
width: 150px;
height: 30px;
border: none;
border-bottom: 2px solid #cecece;
border-radius: 6px;
}
.BottomSection .CheckboxDisplay {
border-color: #c5c5c5;
background: #fff;

View File

@@ -3,7 +3,7 @@ import Checkbox from './common/Checkbox'
import BigButton from './common/BigButton'
import TextInput from './common/TextInput'
import HelpButton from './common/HelpButton'
import { getConfig, saveConfig, setConfigOption } from '../../utils/configuration'
import { getConfig, saveConfig, setConfigOption, setProfileOption } from '../../utils/configuration'
import { translate } from '../../utils/language'
import { invoke } from '@tauri-apps/api/tauri'
@@ -16,9 +16,12 @@ import { GrasscutterElevation } from './menu/Options'
import { getGameExecutable, getGameVersion, getGrasscutterJar } from '../../utils/game'
import { patchGame, unpatchGame } from '../../utils/rsa'
import { listen } from '@tauri-apps/api/event'
import { confirm } from '@tauri-apps/api/dialog'
import DownloadHandler from '../../utils/download'
interface IProps {
openExtras: (playGame: () => void) => void
downloadHandler: DownloadHandler
}
interface IState {
@@ -40,6 +43,8 @@ interface IState {
migotoSet: boolean
unElevated: boolean
profile: string
profiles: string[]
}
export default class ServerLaunchSection extends React.Component<IProps, IState> {
@@ -63,6 +68,8 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
akebiSet: false,
migotoSet: false,
unElevated: false,
profile: 'default',
profiles: ['default'],
}
this.toggleGrasscutter = this.toggleGrasscutter.bind(this)
@@ -71,10 +78,16 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
this.setPort = this.setPort.bind(this)
this.toggleHttps = this.toggleHttps.bind(this)
this.launchServer = this.launchServer.bind(this)
this.setButtonLabel = this.setButtonLabel.bind(this)
this.setProfile = this.setProfile.bind(this)
listen('start_grasscutter', async () => {
this.launchServer()
})
listen('set_game', async () => {
this.setButtonLabel()
})
}
async componentDidMount() {
@@ -94,7 +107,11 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
akebiSet: config.akebi_path !== '',
migotoSet: config.migoto_path !== '',
unElevated: config.un_elevated || false,
profile: config.profile || 'default',
profiles: (await this.getProfileList()).map((t) => t),
})
this.setButtonLabel()
}
async toggleGrasscutter() {
@@ -118,11 +135,30 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
return
}
// Check for HTTPS on local
if (this.state.httpsEnabled) {
if (this.state.ip == 'localhost') {
if (
await confirm(
"Oops! HTTPS is enabled but you're connecting to localhost! \nHTTPS MUST be disabled for localhost. \n\nWould you like to disable HTTPS and continue?",
{ title: 'WARNING!!', type: 'warning' }
)
) {
this.toggleHttps()
} else {
if (!(await confirm('You have chosen to keep HTTPS enabled! \n\nYOU WILL ERROR ON LOGIN.'))) {
return
}
}
}
}
// Connect to proxy
if (config.toggle_grasscutter) {
const game_exe = await getGameExecutable()
const newerGame = false
const patchable = game_exe?.toLowerCase().includes('genshin' || 'yuanshen')
const patchable = game_exe?.toLowerCase().includes('yuanshen') || game_exe?.toLowerCase().includes('genshin')
if (config.patch_rsa && patchable) {
const gameVersion = await getGameVersion()
@@ -145,10 +181,59 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
return
}
const patched = await patchGame()
if (gameVersion?.major == 4 && gameVersion?.minor == 5) {
await confirm(
'Please use Cultivation version 1.4.0 for game version 4.5. You can find that here: https://github.com/NotThorny/Cultivation/releases/tag/1.4.0'
)
return
}
const versionString = gameVersion?.major.toString() + gameVersion?.minor.toString()
// Keeps being misused, remove for now.
// if ((gameVersion?.major == 4 && gameVersion?.minor > 5) || config.newer_game) {
// newerGame = true
// const path = (await invoke('install_location')) as string
// const patchstring = '\\altpatch\\'
// const altPatch = path + patchstring
// const ALT_PATCH =
// 'https://autopatchhk.yuanshen.com/client_app/download/pc_zip/20231030132335_iOEfPMcbrXpiA8Ca/ScatteredFiles/GenshinImpact_Data/Plugins/mihoyonet.dll'
// const pExists = (await invoke('dir_exists', {
// path: altPatch,
// })) as boolean
// if (!pExists) {
// await invoke('dir_create', {
// path: altPatch,
// })
// this.props.downloadHandler.addDownload(ALT_PATCH, path + '/altpatch/mihoyonet.dll')
// await confirm('Please wait for the download in the bottom left to disappear, then click yes')
// }
// /* For custom address patch only, used in 4.5 */
// // let httpString = 'http://'
// // if (this.state.httpsEnabled) {
// // httpString = 'https://'
// // }
// // config.launch_args = '-server=' + httpString + this.state.ip + ':' + this.state.port
// }
const patched = await patchGame(newerGame, versionString)
if (!patched) {
alert('Could not patch! Try launching again, or patching manually.')
alert(
"Could not patch! You're trying to launch a version that you don't have a patch for!" +
"\nEnsure you're using a valid game version, and have the patch for this version in your Cultivation install folder." +
'\n\nIf this means nothing to you, YOU HAVE THE WRONG GAME VERSION.' +
// Add game version due to overwhelming number of people saying they are using a version they are not using
'\n\nYOUR GAME VERSION: ' +
gameVersion?.major +
'.' +
gameVersion?.minor
)
return
}
}
@@ -167,7 +252,7 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
addr: (this.state.httpsEnabled ? 'https' : 'http') + '://' + this.state.ip + ':' + this.state.port,
})
// Connect to proxy
await invoke('connect', { port: 8365, certificatePath: (await dataDir()) + 'cultivation/ca' })
await invoke('connect', { port: 8365, certificatePath: (await dataDir()) + '\\cultivation\\ca' })
}
// Open server as well if the options are set
@@ -195,9 +280,26 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
if (config.un_elevated) {
await invoke('run_un_elevated', {
path: config.game_install_path,
args: config.launch_args,
})
} else {
await invoke('run_program_relative', { path: exe || config.game_install_path })
if (config.launch_args.length < 1) {
// Run relative when there are no args
await invoke('run_program_relative', { path: exe || config.game_install_path })
}
// Handle XXMI
else if (proc_name?.toLowerCase().includes('xxmi')) {
await invoke('run_program_args', {
path: exe,
args: config.launch_args,
})
} else {
// Run directly when there are args
await invoke('run_program', {
path: exe || config.game_install_path,
args: config.launch_args,
})
}
}
else alert('Game not found! At: ' + (exe || config.game_install_path))
}
@@ -285,6 +387,38 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
await saveConfig(config)
}
async setButtonLabel() {
const ver = await getGameVersion()
if (ver != null) {
this.setState({
buttonLabel: (await translate('main.launch_button')) + ' ' + ver?.major + '.' + ver?.minor,
})
} else {
this.setState({
buttonLabel: await translate('main.launch_button'),
})
}
}
async setProfile(value: string) {
this.setState({ profile: value })
await setProfileOption('profile', value)
window.location.reload()
}
async getProfileList() {
const profiles: string[] = await invoke('get_profile_list', {
dataDir: `${await dataDir()}/cultivation`,
})
const list = ['default']
profiles.forEach((t) => {
list.push(t.split('.json')[0])
})
return list
}
render() {
return (
<div id="playButton">
@@ -295,6 +429,23 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
onChange={this.toggleGrasscutter}
checked={this.state.grasscutterEnabled}
/>
<div className="OptionSection" id="menuOptionsContainerProfiles">
<div className="OptionValue" id="menuOptionsSelectProfiles">
<select
value={this.state.profile}
id="menuOptionsSelectMenuProfiles"
onChange={(event) => {
this.setProfile(event.target.value)
}}
>
{this.state.profiles.map((t) => (
<option key={t} value={t}>
{t}
</option>
))}
</select>
</div>
</div>
</div>
{this.state.grasscutterEnabled && (

View File

@@ -1,12 +1,14 @@
import React from 'react'
import { app } from '@tauri-apps/api'
import { app, invoke } from '@tauri-apps/api'
import { appWindow } from '@tauri-apps/api/window'
import { getConfig, setConfigOption } from '../../utils/configuration'
import Tr from '../../utils/language'
import { confirm } from '@tauri-apps/api/dialog'
import './TopBar.css'
import closeIcon from '../../resources/icons/close.svg'
import minIcon from '../../resources/icons/min.svg'
import { unpatchGame } from '../../utils/rsa'
interface IProps {
children?: React.ReactNode | React.ReactNode[]
@@ -36,7 +38,19 @@ export default class TopBar extends React.Component<IProps, IState> {
this.setState({ version })
}
handleClose() {
async handleClose() {
if (await invoke('is_game_running')) {
const confirmed = await confirm(
'Game is running. You WILL NOT be unpatched. Would you like to exit?',
'WARNING!!'
)
if (!confirmed) {
return
}
}
await invoke('disconnect')
unpatchGame()
appWindow.close()
}

View File

@@ -8,19 +8,23 @@ import { dataDir } from '@tauri-apps/api/path'
import './Downloads.css'
import Divider from './Divider'
import { getConfigOption } from '../../../utils/configuration'
import { getConfigOption, setConfigOption } from '../../../utils/configuration'
import { invoke } from '@tauri-apps/api'
import { listen } from '@tauri-apps/api/event'
import HelpButton from '../common/HelpButton'
import { ask } from '@tauri-apps/api/dialog'
const FULL_BUILD_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterCulti.zip'
const FULL_BUILD_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterCulti.zip' // Change to link that can be updated without modifying here
const FULL_QUEST_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterQuests.zip'
const FULL_50_DOWNLOAD = 'https://github.com/NotThorny/Grasscutter/releases/download/culti-aio/GrasscutterLunaGC61.zip' // https://github.com/pmagixc/LunaGC_6.0.0
const STABLE_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/stable.zip'
const DEV_REPO_DOWNLOAD = 'https://github.com/Grasscutters/Grasscutter/archive/refs/heads/development.zip'
const UNSTABLE_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/unstable/Grasscutter.zip'
const DEV_DOWNLOAD = 'https://nightly.link/Grasscutters/Grasscutter/workflows/build/development/Grasscutter.zip'
const RESOURCES_DOWNLOAD = 'https://gitlab.com/api/v4/projects/35984297/repository/archive.zip' // Use Yuuki res as grasscutter crepe res are broken
const MIGOTO_DOWNLOAD =
'https://github.com/SilentNightSound/GI-Model-Importer/releases/download/V7.0/3dmigoto-GIMI-for-playing-mods.zip'
'https://github.com/SilentNightSound/GI-Model-Importer/releases/download/v7.0/3dmigoto-GIMI-for-playing-mods.zip'
const MIGOTO_FALLBACK = 'https://cdn.discordapp.com/attachments/615655311960965130/1177724469847003268/GIMI7.zip' // Since main dl fails for a few too many users
interface IProps {
closeFn: () => void
@@ -56,8 +60,10 @@ export default class Downloads extends React.Component<IProps, IState> {
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
this.downloadGrasscutterFullBuild = this.downloadGrasscutterFullBuild.bind(this)
this.downloadGrasscutterFullQuest = this.downloadGrasscutterFullQuest.bind(this)
this.downloadGrasscutterFull50 = this.downloadGrasscutterFull50.bind(this)
this.downloadGrasscutterStableRepo = this.downloadGrasscutterStableRepo.bind(this)
this.downloadGrasscutterDevRepo = this.downloadGrasscutterDevRepo.bind(this)
this.downloadGrasscutterUnstable = this.downloadGrasscutterUnstable.bind(this)
this.downloadGrasscutterLatest = this.downloadGrasscutterLatest.bind(this)
this.downloadResources = this.downloadResources.bind(this)
this.downloadMigoto = this.downloadMigoto.bind(this)
@@ -76,6 +82,19 @@ export default class Downloads extends React.Component<IProps, IState> {
this.setState({ grasscutter_set: true }, this.forceUpdate)
})
// Listen for GIMI failure to initiate fallback
listen('download_error', ({ payload }) => {
// @ts-expect-error shut up typescript
const errorData: {
path: string
error: string
} = payload
if (errorData.path.includes('GIMI.zip')) {
this.downloadMigotoFallback()
}
})
if (!gc_path || gc_path === '') {
this.setState({
grasscutter_set: false,
@@ -85,15 +104,15 @@ export default class Downloads extends React.Component<IProps, IState> {
return
}
const path = gc_path.substring(0, gc_path.lastIndexOf('/'))
const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
if (gc_path) {
const resources_exist: boolean =
((await invoke('dir_exists', {
path: path + '/resources',
path: path + '\\resources',
})) as boolean) &&
(!(await invoke('dir_is_empty', {
path: path + '/resources',
path: path + '\\resources',
})) as boolean)
this.setState({
@@ -110,7 +129,7 @@ export default class Downloads extends React.Component<IProps, IState> {
// Set to default if not set
if (!path || path === '') {
const appdata = await dataDir()
folderPath = appdata + 'cultivation/grasscutter'
folderPath = appdata + 'cultivation\\grasscutter'
// Early return since its formatted properly
return folderPath
@@ -133,8 +152,8 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterFullBuild() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '/GrasscutterCulti.zip', async () => {
await unzip(folder + '/GrasscutterCulti.zip', folder + '/', true)
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '\\GrasscutterCulti.zip', async () => {
await unzip(folder + '\\GrasscutterCulti.zip', folder + '\\', true)
this.toggleButtons()
})
@@ -143,8 +162,18 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterFullQuest() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '/GrasscutterQuests.zip', async () => {
await unzip(folder + '/GrasscutterQuests.zip', folder + '/', true)
this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '\\GrasscutterQuests.zip', async () => {
await unzip(folder + '\\GrasscutterQuests.zip', folder + '\\', true)
this.toggleButtons()
})
this.toggleButtons()
}
async downloadGrasscutterFull50() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(FULL_50_DOWNLOAD, folder + '\\Grasscutter50.zip', async () => {
await unzip(folder + '\\Grasscutter50.zip', folder + '\\', true)
this.toggleButtons()
})
@@ -153,8 +182,8 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterStableRepo() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '/grasscutter_repo.zip', async () => {
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true)
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
this.toggleButtons()
})
@@ -163,18 +192,28 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterDevRepo() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '/grasscutter_repo.zip', async () => {
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true)
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
this.toggleButtons()
})
this.toggleButtons()
}
async downloadGrasscutterUnstable() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(UNSTABLE_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
this.toggleButtons
})
this.toggleButtons()
}
async downloadGrasscutterLatest() {
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '/grasscutter.zip', async () => {
await unzip(folder + '/grasscutter.zip', folder + '/', true)
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
this.toggleButtons()
})
@@ -185,27 +224,38 @@ export default class Downloads extends React.Component<IProps, IState> {
}
async downloadResources() {
// Tell the user this is not needed in most cases
if (
!(await ask(
'These are not needed if you have already downloaded the All-in-One!! \nAre you sure you want to continue this download?'
))
) {
// If refusing confirmation
return
}
// Tell the user this takes some time
alert(
'Extracting resources can take time! If your resources appear to be "stuck" extracting for less than 15-20 mins, they likely still are extracting.'
)
const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '/resources.zip', async () => {
this.props.downloadManager.addDownload(RESOURCES_DOWNLOAD, folder + '\\resources.zip', async () => {
// Delete the existing folder if it exists
if (
await invoke('dir_exists', {
path: folder + '/resources',
path: folder + '\\resources',
})
) {
await invoke('dir_delete', {
path: folder + '/resources',
path: folder + '\\resources',
})
}
await unzip(folder + '/resources.zip', folder + '/', true)
await unzip(folder + '\\resources.zip', folder + '\\', true)
// Rename folder to resources
invoke('rename', {
path: folder + '/Resources',
path: folder + '\\Resources',
newName: 'resources',
})
@@ -216,13 +266,27 @@ export default class Downloads extends React.Component<IProps, IState> {
}
async downloadMigoto() {
const folder = (await this.getCultivationFolder()) + '/3dmigoto'
await invoke('dir_create', {
path: folder,
if (!this.state.swag) {
await setConfigOption('swag_mode', true)
this.setState({ swag: true })
await setConfigOption('last_extras', { migoto: true, akebi: false, reshade: false })
}
const folder = await this.getCultivationFolder()
this.props.downloadManager.addDownload(MIGOTO_DOWNLOAD, folder + '\\GIMI.zip', async () => {
await unzip(folder + '\\GIMI.zip', folder + '\\', true, true)
this.toggleButtons()
})
this.props.downloadManager.addDownload(MIGOTO_DOWNLOAD, folder + '/GIMI-3dmigoto.zip', async () => {
await unzip(folder + '/GIMI-3dmigoto.zip', folder + '/', true)
this.toggleButtons()
}
async downloadMigotoFallback() {
const folder = await this.getCultivationFolder()
this.props.downloadManager.addDownload(MIGOTO_FALLBACK, folder + '\\GIMI7.zip', async () => {
await unzip(folder + '\\GIMI7.zip', folder + '\\', true, true)
this.toggleButtons()
})
@@ -256,7 +320,6 @@ export default class Downloads extends React.Component<IProps, IState> {
<Tr text={'downloads.grasscutter_fullbuild'} />
<HelpButton contents="help.gc_fullbuild" />
</div>
<div className="DownloadValue" id="downloadMenuButtonGCFullBuild">
<BigButton
disabled={this.state.grasscutter_downloading}
@@ -267,7 +330,6 @@ export default class Downloads extends React.Component<IProps, IState> {
</BigButton>
</div>
</div>
<div className="DownloadMenuSection" id="downloadMenuContainerGCFullQuest">
<div className="DownloadLabel" id="downloadMenuLabelGCFullQuest">
<Tr text={'downloads.grasscutter_fullquest'} />
@@ -276,18 +338,38 @@ export default class Downloads extends React.Component<IProps, IState> {
<div className="DownloadValue" id="downloadMenuButtonGCFullQuest">
<BigButton
disabled={this.state.grasscutter_downloading}
onClick={this.downloadGrasscutterFullQuest}
onClick={this.downloadGrasscutterFull50}
id="grasscutterFullBuildBtn"
>
<Tr text="components.download" />
</BigButton>
</div>
</div>
<Divider />
<div className="HeaderText" id="downloadMenuIndividualHeader">
<Tr text="downloads.individual_header" />
</div>
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCUnstable">
<div className="DownloadLabel" id="downloadMenuLabelGCUnstable">
<Tr
text={
this.state.grasscutter_set ? 'downloads.grasscutter_unstable' : 'downloads.grasscutter_unstable_update'
}
/>
<HelpButton contents="help.gc_unstable_jar" />
</div>
<div className="DownloadValue" id="downloadMenuButtonGCUnstable">
<BigButton
disabled={this.state.grasscutter_downloading}
onClick={this.downloadGrasscutterUnstable}
id="grasscutterUnstableBtn"
>
<Tr text="components.download" />
</BigButton>
</div>
</div> */}
<div className="DownloadMenuSection" id="downloadMenuContainerGCDev">
<div className="DownloadLabel" id="downloadMenuLabelGCDev">
<Tr
@@ -306,7 +388,28 @@ export default class Downloads extends React.Component<IProps, IState> {
</div>
</div>
<div className="DownloadMenuSection" id="downloadMenuContainerGCDevData">
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCStableData">
<div className="DownloadLabel" id="downloadMenuLabelGCStableData">
<Tr
text={
this.state.grasscutter_set
? 'downloads.grasscutter_stable_data'
: 'downloads.grasscutter_stable_data_update'
}
/>
<HelpButton contents="help.gc_stable_data" />
</div>
<div className="DownloadValue" id="downloadMenuButtonGCStableData">
<BigButton
disabled={this.state.repo_downloading}
onClick={this.downloadGrasscutterStableRepo}
id="grasscutterStableRepo"
>
<Tr text="components.download" />
</BigButton>
</div>
</div> */}
{/* <div className="DownloadMenuSection" id="downloadMenuContainerGCDevData">
<div className="DownloadLabel" id="downloadMenuLabelGCDevData">
<Tr
text={
@@ -326,7 +429,7 @@ export default class Downloads extends React.Component<IProps, IState> {
<Tr text="components.download" />
</BigButton>
</div>
</div>
</div> */}
<div className="DownloadMenuSection" id="downloadMenuContainerResources">
<div className="DownloadLabel" id="downloadMenuLabelResources">
@@ -344,25 +447,23 @@ export default class Downloads extends React.Component<IProps, IState> {
</div>
</div>
{this.state.swag && (
<>
<Divider />
<div className="HeaderText" id="downloadMenuModsHeader">
<Tr text="downloads.mods_header" />
<>
<Divider />
<div className="HeaderText" id="downloadMenuModsHeader">
<Tr text="downloads.mods_header" />
</div>
<div className="DownloadMenuSection" id="downloadMenuContainerMigoto">
<div className="DownloadLabel" id="downloadMenuLabelMigoto">
<Tr text={'downloads.migoto'} />
<HelpButton contents="help.migoto" />
</div>
<div className="DownloadMenuSection" id="downloadMenuContainerMigoto">
<div className="DownloadLabel" id="downloadMenuLabelMigoto">
<Tr text={'downloads.migoto'} />
<HelpButton contents="help.migoto" />
</div>
<div className="DownloadValue" id="downloadMenuButtonMigoto">
<BigButton disabled={this.state.migoto_downloading} onClick={this.downloadMigoto} id="migotoBtn">
<Tr text="components.download" />
</BigButton>
</div>
<div className="DownloadValue" id="downloadMenuButtonMigoto">
<BigButton disabled={this.state.migoto_downloading} onClick={this.downloadMigoto} id="migotoBtn">
<Tr text="components.download" />
</BigButton>
</div>
</>
)}
</div>
</>
</Menu>
)
}

View File

@@ -69,7 +69,10 @@ export class ExtrasMenu extends React.Component<IProps, IState> {
// This injects independent of the game
if (this.state.launch_migoto) {
await this.launchMigoto()
if (await this.launchMigoto()) {
// Already launched the game (ie. via XXMI)
return
}
}
// This injects independent of the game
@@ -104,7 +107,17 @@ export class ExtrasMenu extends React.Component<IProps, IState> {
if (!config.migoto_path) return alert('Migoto not installed or set!')
if (config.migoto_path?.toLowerCase().includes('xxmi')) {
// Get game exe from game path, so we can watch it
const pathArr = config.migoto_path.replace(/\\/g, '/').split('/')
const gameExec = pathArr[pathArr.length - 1]
this.props.playGame(config.migoto_path, gameExec)
return true
}
await invoke('run_program_relative', { path: config.migoto_path })
return false
}
async launchReshade() {

View File

@@ -45,8 +45,8 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGame() {
const folder = this.state.gameDownloadFolder
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '/game.zip', async () => {
await unzip(folder + '/game.zip', folder + '/', true)
this.props.downloadManager.addDownload(GAME_DOWNLOAD, folder + '\\game.zip', async () => {
await unzip(folder + '\\game.zip', folder + '\\', true)
this.setState({
gameDownloading: false,
})

View File

@@ -20,3 +20,8 @@
.OptionSection .HelpButton img {
filter: invert(0%) sepia(91%) saturate(7464%) hue-rotate(101deg) brightness(0%) contrast(107%);
}
input#profile_name {
height: 25px;
border-radius: 6px;
}

View File

@@ -4,7 +4,14 @@ import { dataDir } from '@tauri-apps/api/path'
import DirInput from '../common/DirInput'
import Menu from './Menu'
import Tr, { getLanguages } from '../../../utils/language'
import { setConfigOption, getConfig, getConfigOption, Configuration } from '../../../utils/configuration'
import {
setConfigOption,
getConfig,
getConfigOption,
Configuration,
saveNewProfileConfig,
setProfileOption,
} from '../../../utils/configuration'
import Checkbox from '../common/Checkbox'
import Divider from './Divider'
import { getThemeList } from '../../../utils/themes'
@@ -16,6 +23,11 @@ import DownloadHandler from '../../../utils/download'
import * as meta from '../../../utils/rsa'
import HelpButton from '../common/HelpButton'
import SmallButton from '../common/SmallButton'
import { ask, confirm } from '@tauri-apps/api/dialog'
import TextInput from '../common/TextInput'
import { unzip } from '../../../utils/zipUtils'
import { getGameExecutable } from '../../../utils/game'
import { emit } from '@tauri-apps/api/event'
export enum GrasscutterElevation {
None = 'None',
@@ -49,6 +61,11 @@ interface IState {
platform: string
un_elevated: boolean
redirect_more: boolean
launch_args: string
offline_mode: boolean
newer_game: boolean
show_version: boolean
profile_name: string
// Linux stuff
grasscutter_elevation: string
@@ -84,6 +101,11 @@ export default class Options extends React.Component<IProps, IState> {
platform: '',
un_elevated: false,
redirect_more: false,
launch_args: '',
offline_mode: false,
newer_game: false,
show_version: true,
profile_name: '',
// Linux stuff
grasscutter_elevation: GrasscutterElevation.None,
@@ -103,8 +125,13 @@ export default class Options extends React.Component<IProps, IState> {
this.setCustomBackground = this.setCustomBackground.bind(this)
this.toggleEncryption = this.toggleEncryption.bind(this)
this.removeRSA = this.removeRSA.bind(this)
this.deleteWebCache = this.deleteWebCache.bind(this)
this.addMigotoDelay = this.addMigotoDelay.bind(this)
this.toggleUnElevatedGame = this.toggleUnElevatedGame.bind(this)
this.setLaunchArgs = this.setLaunchArgs.bind(this)
this.toggleShowVersion = this.toggleShowVersion.bind(this)
this.setProfileName = this.setProfileName.bind(this)
this.saveProfile = this.saveProfile.bind(this)
}
async componentDidMount() {
@@ -112,13 +139,10 @@ export default class Options extends React.Component<IProps, IState> {
const languages = await getLanguages()
const platform: string = await invoke('get_platform')
let encEnabled
if (config.grasscutter_path) {
// Remove jar from path
const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/'))
encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
}
// 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)
@@ -143,6 +167,10 @@ export default class Options extends React.Component<IProps, IState> {
platform,
un_elevated: config.un_elevated || false,
redirect_more: config.redirect_more || false,
launch_args: config.launch_args,
offline_mode: config.offline_mode || false,
newer_game: config.newer_game || false,
show_version: config.show_version || false,
// Linux stuff
grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None,
@@ -156,22 +184,28 @@ export default class Options extends React.Component<IProps, IState> {
this.forceUpdate()
}
setGameExecutable(value: string) {
setConfigOption('game_install_path', value)
async setGameExecutable(value: string) {
await setConfigOption('game_install_path', value)
// I hope this stops people setting launcher.exe because oml it's annoying
if (value.endsWith('launcher.exe')) {
if (value.endsWith('launcher.exe') || value.endsWith('.lnk')) {
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}`
)
if (value.endsWith('.lnk')) {
alert(
'You have set your game executable to a shortcut. You should not do this. Your patching will not work, and your proxy may shut off unexpectedly.'
)
} else {
alert(
`You have set your game execuatable to "launcher.exe". You should not do this. Your game executable is located in:\n\n${path}`
)
}
}
// If setting any other game, automatically set to redirect more
if (!value.toLowerCase().includes('genshin' || 'yuanshen')) {
if (!value.toLowerCase().includes('genshin') || !value.toLowerCase().includes('yuanshen')) {
if (!this.state.redirect_more) {
this.toggleOption('redirect_more')
}
@@ -180,26 +214,24 @@ export default class Options extends React.Component<IProps, IState> {
this.setState({
game_install_path: value,
})
emit('set_game', { game_path: value })
}
async setGrasscutterJar(value: string) {
setConfigOption('grasscutter_path', value)
this.setState({
grasscutter_path: value,
})
const config = await getConfig()
const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/'))
const encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
// Update encryption button when setting new jar
this.setState({
grasscutter_path: value,
encryption: encEnabled,
})
window.location.reload()
// Encryption refuses to re-render w/o reload unless updated twice
this.forceUpdateEncyption()
}
setJavaPath(value: string) {
@@ -260,7 +292,7 @@ export default class Options extends React.Component<IProps, IState> {
if (!isUrl) {
const filename = value.replace(/\\/g, '/').split('/').pop()
const localBgPath = (await dataDir()).replace(/\\/g, '/')
const localBgPath = ((await dataDir()) as string).replace(/\\/g, '/')
await setConfigOption('custom_background', `${localBgPath}/cultivation/bg/${filename}`)
@@ -277,6 +309,16 @@ export default class Options extends React.Component<IProps, IState> {
}
}
async forceUpdateEncyption() {
const config = await getConfig()
const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/'))
this.setState({
encryption: await server.encryptionEnabled(folderPath + '/config.json'),
})
}
async toggleEncryption() {
const config = await getConfig()
@@ -290,6 +332,17 @@ export default class Options extends React.Component<IProps, IState> {
const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/'))
if (!(await server.encryptionEnabled(folderPath + '/config.json'))) {
if (
!(await confirm(
'Cultivation requires encryption DISABLED to connect and play locally. \n\n Are you sure you want to enable encryption?',
'Warning!'
))
) {
return
}
}
await server.toggleEncryption(folderPath + '/config.json')
this.setState({
@@ -312,6 +365,17 @@ export default class Options extends React.Component<IProps, IState> {
})
}
async toggleShowVersion() {
const changedVal = !(await getConfigOption('show_version'))
await setConfigOption('show_version', changedVal)
this.setState({
show_version: changedVal,
})
emit('set_config', { show_version: changedVal })
}
async setGCElevation(value: string) {
setConfigOption('grasscutter_elevation', value)
@@ -325,6 +389,15 @@ export default class Options extends React.Component<IProps, IState> {
}
async addMigotoDelay() {
if (
!(await ask(
'Set delay for 3dmigoto loader? This is specifically made for GIMI v6 and earlier. Using it on latest GIMI or SRMI will cause issues!!! \n\nWould you like to continue?',
{ title: 'GIMI Delay', type: 'warning' }
))
) {
return
}
invoke('set_migoto_delay', {
migotoPath: this.state.migoto_path,
})
@@ -336,6 +409,91 @@ export default class Options extends React.Component<IProps, IState> {
})
}
async deleteWebCache() {
if (await ask('Would you like to clear login cache? Yes to clear login cache. No to clear web cache.')) {
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'),
})
alert('Cleared login cache!')
return
}
alert('Cultivation may freeze for a moment while this occurs!')
// Get webCaches folder path
const pathArr = this.state.game_install_path.replace(/\\/g, '/').split('/')
pathArr.pop()
const path = pathArr.join('/') + '/GenshinImpact_Data/webCaches'
const path2 = pathArr.join('/') + '/Yuanshen_Data/webCaches'
// Delete the folder
if (await invoke('dir_exists', { path: path })) {
await invoke('dir_delete', { path: path })
}
if (await invoke('dir_exists', { path: path2 })) {
await invoke('dir_delete', { path: path2 })
}
}
async fixRes() {
const config = await getConfig()
const path = config.grasscutter_path.replace(/\\/g, '/')
let folderPath = path.substring(0, path.lastIndexOf('/'))
// Set to default if not set
if (!path || path === '') {
const appdata = await dataDir()
folderPath = appdata + 'cultivation\\grasscutter'
}
if (path.includes('/')) {
folderPath = path.substring(0, path.lastIndexOf('/'))
} else {
folderPath = path.substring(0, path.lastIndexOf('\\'))
}
// Check if Grasscutter path exists
if (folderPath.length < 1) {
alert('Grasscutter not installed or not set! This option can only work when it is installed.')
return
}
// Check if resources zip exists
if (
!(await invoke('dir_exists', {
path: folderPath + '\\GC-Resources-4.0.zip',
}))
) {
alert('Resources are already unzipped or do not exist! Ensure your resources zip is named "GC-Resources-4.0.zip"')
return
}
alert(
'This may fix white screen issues on login! Please be patient while extraction occurs, it may take some time (5-10 minutes). \n\n !! You will be alerted when it is done !!'
)
// Unzip resources
await unzip(folderPath + '\\GC-Resources-4.0.zip', folderPath + '\\', true)
// Rename folder to resources
invoke('rename', {
path: folderPath + '\\Resources',
newName: 'resources',
})
// Update config.json to read from folder
await server.changeResourcePath(folderPath + '/config.json')
// Check if Grasscutter is running, and restart if so to apply changes
if (await invoke('is_grasscutter_running')) {
alert('Automatically restarting Grasscutter for changes to apply!')
await invoke('restart_grasscutter')
}
alert('Resource fixing finished! Please launch the server again and try playing.')
}
async toggleOption(opt: keyof Configuration) {
const changedVal = !(await getConfigOption(opt))
@@ -347,6 +505,36 @@ export default class Options extends React.Component<IProps, IState> {
})
}
async setLaunchArgs(value: string) {
await setConfigOption('launch_args', value)
this.setState({
launch_args: value,
})
}
setProfileName(text: string) {
this.setState({
profile_name: text,
})
}
async saveProfile() {
if (this.state.profile_name == '') {
alert('No name set')
return
}
const config = await getConfig()
await saveNewProfileConfig(config, this.state.profile_name)
await setProfileOption('profile', this.state.profile_name)
this.setState({
profile_name: '',
})
window.location.reload()
}
render() {
return (
<Menu closeFn={this.props.closeFn} className="Options" heading="Options">
@@ -502,7 +690,7 @@ export default class Options extends React.Component<IProps, IState> {
<Tr text="swag.migoto" />
</div>
<div className="OptionValue" id="menuOptionsDirMigoto">
<SmallButton onClick={this.addMigotoDelay} id="migotoDelay" contents="help.add_delay"></SmallButton>
<SmallButton onClick={this.addMigotoDelay} id="migotoDelay"></SmallButton>
<DirInput onChange={this.setMigoto} value={this.state?.migoto_path} extensions={['exe']} />
</div>
</div>
@@ -519,6 +707,24 @@ export default class Options extends React.Component<IProps, IState> {
<Divider />
<div className="OptionSection" id="profileConfigContainer">
<div className="OptionLabel" id="menuOptionsLabelProfile">
<Tr text="options.save_profile" />
</div>
<TextInput
id="profile_name"
key="profile_name"
placeholder={'Profile name...'}
onChange={this.setProfileName}
initalValue={''}
/>
<BigButton onClick={this.saveProfile} id="saveProfile">
{'Save'}
</BigButton>
</div>
<Divider />
<div className="OptionSection" id="menuOptionsContainerGCWGame">
<div className="OptionLabel" id="menuOptionsLabelGCWDame">
<Tr text="options.grasscutter_with_game" />
@@ -559,6 +765,26 @@ export default class Options extends React.Component<IProps, IState> {
</div>
</div>
) : null}
<div className="OptionSection" id="menuOptionsContainerOffline">
<div className="OptionLabel" id="menuOptionsLabelOffline">
<Tr text="options.offline_mode" />
</div>
<div className="OptionValue" id="menuOptionsCheckboxOffline">
<Checkbox
onChange={() => this.toggleOption('offline_mode')}
checked={this.state?.offline_mode}
id="offlineMode"
/>
</div>
</div>
<div className="OptionSection" id="menuOptionsContainerShowVer">
<div className="OptionLabel" id="menuOptionsLabelShowVer">
<Tr text="options.show_version" />
</div>
<div className="OptionValue" id="menuOptionsButtonShowVer">
<Checkbox onChange={() => this.toggleShowVersion()} checked={this.state.show_version} id="showVer" />
</div>
</div>
<Divider />
@@ -646,6 +872,45 @@ export default class Options extends React.Component<IProps, IState> {
</select>
</div>
</div>
<Divider />
<div className="OptionSection" id="menuOptionsContainerAdvanced">
<div className="OptionLabel" id="menuOptionsLabelWebCache">
<Tr text="options.web_cache" />
</div>
<div className="OptionValue" id="menuOptionsButtondeleteWebcache">
<BigButton onClick={this.deleteWebCache} id="deleteWebcache">
<Tr text="components.delete" />
</BigButton>
</div>
</div>
<div className="OptionSection" id="menuOptionsContainerLaunchArgs">
<div className="OptionLabel" id="menuOptionsLaunchArgs">
<Tr text="options.launch_args" />
</div>
<div className="OptionValue" id="menuOptionsValueLaunchArgs">
<TextInput
id="launch_args"
key="launch_args"
placeholder={'-arg=value'}
onChange={this.setLaunchArgs}
value={this.state.launch_args}
/>
</div>
</div>
<div className="OptionSection" id="menuOptionsContainerFixRes">
<div className="OptionLabel" id="menuOptionsLabelFixRes">
<Tr text="options.fix_res" />
</div>
<div className="OptionValue" id="menuOptionsButtonfixRes">
<BigButton onClick={this.fixRes} id="fixRes">
<Tr text="components.fix" />
</BigButton>
</div>
</div>
</Menu>
)
}

View File

@@ -2,6 +2,7 @@
import { invoke } from '@tauri-apps/api/tauri'
import React from 'react'
import Tr from '../../../utils/language'
import { getConfig, getConfigOption } from '../../../utils/configuration'
import './NewsSection.css'
@@ -10,6 +11,7 @@ interface IProps {
}
interface IState {
offline: boolean
selected: string
news?: JSX.Element
commitList?: JSX.Element[]
@@ -40,6 +42,7 @@ export default class NewsSection extends React.Component<IProps, IState> {
super(props)
this.state = {
offline: false,
selected: props.selected || 'commits',
}
@@ -47,7 +50,16 @@ export default class NewsSection extends React.Component<IProps, IState> {
this.showNews = this.showNews.bind(this)
}
componentDidMount() {
async componentDidMount() {
const config = await getConfig()
this.setState({
offline: config.offline_mode || false,
})
// If offline, don't call news
if (this.state.offline) {
return
}
// Call showNews off the bat
this.showNews()
this.setSelected('commits')
@@ -110,6 +122,11 @@ export default class NewsSection extends React.Component<IProps, IState> {
}
async showNews() {
const offline_mode = await getConfigOption('offline_mode')
if (offline_mode) {
return
}
let news: JSX.Element | JSX.Element[] = <tr></tr>
switch (this.state.selected) {
@@ -124,7 +141,10 @@ export default class NewsSection extends React.Component<IProps, IState> {
case 'latest_version':
news = (
<tr>
<td>Latest version: Grasscutter 1.7.0 - Cultivation 1.2.0</td>
<td>
Work in progress area! These numbers may be outdated, so please do not use them as reference. Latest
version: Grasscutter 1.7.4 (4.0) / Forks (6.1) - Cultivation 1.7.1
</td>
</tr>
)
break
@@ -144,6 +164,10 @@ export default class NewsSection extends React.Component<IProps, IState> {
}
render() {
if (this.state.offline) {
return null
}
return (
<div className="NewsSection" id="newsContainer">
<div className="NewsTabs" id="newsTabsContainer">

View File

@@ -28,6 +28,11 @@ let defaultConfig: Configuration
auto_mongodb: false,
un_elevated: false,
redirect_more: false,
launch_args: '',
offline_mode: false,
newer_game: false,
show_version: true,
profile: 'default',
// Linux stuff
grasscutter_elevation: 'None',
@@ -62,6 +67,11 @@ export interface Configuration {
auto_mongodb: boolean
un_elevated: boolean
redirect_more: boolean
launch_args: string
offline_mode: boolean
newer_game: boolean
show_version: boolean
profile: string
// Linux stuff
grasscutter_elevation: string
@@ -84,6 +94,15 @@ export async function setConfigOption<K extends keyof Configuration>(key: K, val
await saveConfig(<Configuration>config)
}
export async function setProfileOption<K extends keyof Configuration>(key: K, value: Configuration[K]): Promise<void> {
const config = await getConfig()
config[key] = value
const defaultConfig = await getDefaultConfig()
defaultConfig[key] = value
await saveProfileConfig(<Configuration>defaultConfig)
}
export async function getConfigOption<K extends keyof Configuration>(key: K): Promise<Configuration[K]> {
const config = await getConfig()
const defaults = defaultConfig
@@ -107,16 +126,69 @@ export async function getConfig() {
return parsed
}
export async function getDefaultConfig() {
const raw = await readDefaultConfigFile()
let parsed: Configuration = defaultConfig
try {
parsed = <Configuration>JSON.parse(raw)
} catch (e) {
// We could not open the file
console.log(e)
}
return parsed
}
export async function saveConfig(obj: Configuration) {
const raw = JSON.stringify(obj)
await writeConfigFile(raw)
}
export async function saveProfileConfig(obj: Configuration) {
const local = await dataDir()
const raw = JSON.stringify(obj)
const prevPath = configFilePath
configFilePath = local + 'cultivation/configuration.json'
await writeConfigFile(raw)
configFilePath = prevPath
}
export async function saveNewProfileConfig(obj: Configuration, prof: string) {
obj['profile'] = prof
const local = await dataDir()
const raw = JSON.stringify(obj)
configFilePath = local + 'cultivation/profiles/' + obj['profile'] + '.json'
const file: fs.FsTextFileOption = {
path: configFilePath,
contents: raw,
}
await fs.writeFile(file)
}
async function readConfigFile() {
const local = await dataDir()
if (!configFilePath) configFilePath = local + 'cultivation/configuration.json'
if (!configFilePath) {
configFilePath = local + 'cultivation/configuration.json'
}
// Read existing config to get profile name
const raw = await fs.readTextFile(configFilePath)
const cfg = <Configuration>JSON.parse(raw)
// Switch file to config-specified profile
let pf = cfg['profile']
if (pf && pf != 'default') {
const pff = pf
pf = 'profiles/' + pff + '.json'
} else {
pf = 'configuration.json'
}
configFilePath = local + 'cultivation/' + pf
// Ensure Cultivation dir exists
const dirs = await fs.readDir(local)
@@ -126,7 +198,7 @@ async function readConfigFile() {
await fs.createDir(local + 'cultivation').catch((e) => console.log(e))
}
const innerDirs = await fs.readDir(local + 'cultivation')
const innerDirs = await fs.readDir(local + '/cultivation')
// Create grasscutter dir for potential installation
if (!innerDirs.find((fileOrDir) => fileOrDir?.name === 'grasscutter')) {
@@ -151,6 +223,12 @@ async function readConfigFile() {
return await fs.readTextFile(configFilePath)
}
async function readDefaultConfigFile() {
const local = await dataDir()
configFilePath = local + 'cultivation/configuration.json'
return await fs.readTextFile(configFilePath)
}
async function writeConfigFile(raw: string) {
// All external config functions call readConfigFile, which ensure files exists
await fs.writeFile({

View File

@@ -73,6 +73,11 @@ export default class DownloadHandler {
const index = this.downloads.findIndex((download) => download.path === errorData.path)
this.downloads[index].status = 'error'
this.downloads[index].error = errorData.error
// Remove GIMI from list as fallback will replace it
if (errorData.path.includes('GIMI.zip')) {
this.downloads.splice(index, 1)
}
})
// Extraction events
@@ -92,6 +97,9 @@ export default class DownloadHandler {
// Find the download that is not extracting and set it's status as such
const index = this.downloads.findIndex((download) => download.path === obj.file)
this.downloads[index].status = 'finished'
// Remove completed extraction from list
this.downloads.splice(index, 1)
})
}
@@ -101,25 +109,35 @@ export default class DownloadHandler {
downloadingJar() {
// Kinda hacky but it works
return this.downloads.some((d) => d.path.includes('grasscutter.zip') && d.status != ('finished' || 'error'))
return this.downloads.some(
(d) => d.path.includes('grasscutter.zip') && !(d.status.includes('finished') || d.status.includes('error'))
)
}
downloadingFullBuild() {
// Kinda hacky but it works
return this.downloads.some((d) => d.path.includes('GrasscutterCulti') && d.status != ('finished' || 'error'))
return this.downloads.some(
(d) => d.path.includes('GrasscutterCulti') && !(d.status.includes('finished') || d.status.includes('error'))
)
}
downloadingResources() {
// Kinda hacky but it works
return this.downloads.some((d) => d.path.includes('resources') && d.status != ('finished' || 'error'))
return this.downloads.some(
(d) => d.path.includes('resources') && !(d.status.includes('finished') || d.status.includes('error'))
)
}
downloadingRepo() {
return this.downloads.some((d) => d.path.includes('grasscutter_repo.zip') && d.status != ('finished' || 'error'))
return this.downloads.some(
(d) => d.path.includes('grasscutter_repo.zip') && !(d.status.includes('finished') || d.status.includes('error'))
)
}
downloadingMigoto() {
return this.downloads.some((d) => d.path.includes('3dmigoto') && d.status != ('finished' || 'error'))
return this.downloads.some(
(d) => d.path.includes('3dmigoto') && !(d.status.includes('finished') || d.status.includes('error'))
)
}
addDownload(url: string, path: string, onFinish?: () => void) {

View File

@@ -45,19 +45,57 @@ export async function getGameDataFolder() {
return null
}
return (await getGameFolder()) + '/' + gameExec.replace('.exe', '_Data')
return (await getGameFolder()) + '\\' + gameExec.replace('.exe', '_Data')
}
export async function getGameVersion() {
const GameData = await getGameDataFolder()
const platform = await invoke('get_platform')
if (!GameData) {
return null
}
let hasAsb = await invoke('dir_exists', {
path: GameData + '\\StreamingAssets\\asb_settings.json',
})
if (platform != 'windows') {
hasAsb = await invoke('dir_exists', {
path: GameData + '/StreamingAssets/asb_settings.json',
})
}
if (!hasAsb) {
// For games that cannot determine game version
let otherGameVer: string = await invoke('read_file', {
path: GameData + '\\StreamingAssets\\BinaryVersion.bytes',
})
if (platform != 'windows') {
otherGameVer = await invoke('read_file', {
path: GameData + '/StreamingAssets/BinaryVersion.bytes',
})
}
const versionRaw = otherGameVer.split('.')
const version = {
major: parseInt(versionRaw[0]),
minor: parseInt(versionRaw[1]),
// This will probably never matter, just use major/minor. If needed, full version values are near EOF
release: 0,
}
if (otherGameVer == null || otherGameVer.length < 1) {
return null
}
return version
}
const settings = JSON.parse(
await invoke('read_file', {
path: GameData + '/StreamingAssets/asb_settings.json',
path: GameData + '\\StreamingAssets\\asb_settings.json',
})
)

View File

@@ -65,7 +65,7 @@ export default class Tr extends React.Component<IProps, IState> {
})
} else {
this.setState({
translated_text: translation_obj[text] || '',
translated_text: translation_obj[text] || text,
})
}
}

View File

@@ -1,8 +1,8 @@
import { invoke } from '@tauri-apps/api'
// Patch file from: https://github.com/34736384/RSAPatch/
export async function patchGame() {
return invoke('patch_game')
export async function patchGame(newerGame: boolean, version: string) {
return invoke('patch_game', { newerGame, version })
}
export async function unpatchGame() {

View File

@@ -40,5 +40,32 @@ export async function encryptionEnabled(path: string) {
return false
}
return serverConf.server.http.encryption.useEncryption
if ('server' in serverConf) {
return serverConf.server.http.encryption.useEncryption
}
return false
}
export async function changeResourcePath(path: string) {
let serverConf
try {
serverConf = JSON.parse(
await invoke('read_file', {
path,
})
)
} catch (e) {
console.log(`Server config at ${path} not found or invalid. Be sure to run the server at least once to generate it`)
return
}
serverConf.folderStructure.resources = './resources/'
// Write file
await invoke('write_file', {
path,
contents: JSON.stringify(serverConf, null, 2),
})
}

View File

@@ -40,7 +40,7 @@ const defaultTheme = {
export async function getThemeList() {
// Do some invoke to backend to get the theme list
const themes = (await invoke('get_theme_list', {
dataDir: `${await dataDir()}cultivation`,
dataDir: `${await dataDir()}/cultivation`,
})) as BackendThemeList[]
const list: ThemeList[] = [
// ALWAYS include default theme

131
yarn.lock
View File

@@ -26,6 +26,14 @@
dependencies:
"@babel/highlight" "^7.18.6"
"@babel/code-frame@^7.22.13":
version "7.22.13"
resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.13.tgz#e3c1c099402598483b7a8c46a721d1038803755e"
integrity sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==
dependencies:
"@babel/highlight" "^7.22.13"
chalk "^2.4.2"
"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.18.8":
version "7.18.8"
resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d"
@@ -70,6 +78,16 @@
"@jridgewell/gen-mapping" "^0.3.2"
jsesc "^2.5.1"
"@babel/generator@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.0.tgz#df5c386e2218be505b34837acbcb874d7a983420"
integrity sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==
dependencies:
"@babel/types" "^7.23.0"
"@jridgewell/gen-mapping" "^0.3.2"
"@jridgewell/trace-mapping" "^0.3.17"
jsesc "^2.5.1"
"@babel/helper-annotate-as-pure@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb"
@@ -135,6 +153,11 @@
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz#0c0cee9b35d2ca190478756865bb3528422f51be"
integrity sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==
"@babel/helper-environment-visitor@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167"
integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==
"@babel/helper-explode-assignable-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.18.6.tgz#41f8228ef0a6f1a036b8dfdfec7ce94f9a6bc096"
@@ -150,6 +173,14 @@
"@babel/template" "^7.18.6"
"@babel/types" "^7.18.9"
"@babel/helper-function-name@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759"
integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==
dependencies:
"@babel/template" "^7.22.15"
"@babel/types" "^7.23.0"
"@babel/helper-hoist-variables@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz#d4d2c8fb4baeaa5c68b99cc8245c56554f926678"
@@ -157,6 +188,13 @@
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-hoist-variables@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-member-expression-to-functions@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.9.tgz#1531661e8375af843ad37ac692c132841e2fd815"
@@ -239,11 +277,28 @@
dependencies:
"@babel/types" "^7.18.6"
"@babel/helper-split-export-declaration@^7.22.6":
version "7.22.6"
resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
dependencies:
"@babel/types" "^7.22.5"
"@babel/helper-string-parser@^7.22.5":
version "7.22.5"
resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
"@babel/helper-validator-identifier@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076"
integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g==
"@babel/helper-validator-identifier@^7.22.20":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0"
integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==
"@babel/helper-validator-option@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz#bf0d2b5a509b1f336099e4ff36e1a63aa5db4db8"
@@ -277,11 +332,25 @@
chalk "^2.0.0"
js-tokens "^4.0.0"
"@babel/highlight@^7.22.13":
version "7.22.20"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.20.tgz#4ca92b71d80554b01427815e06f2df965b9c1f54"
integrity sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==
dependencies:
"@babel/helper-validator-identifier" "^7.22.20"
chalk "^2.4.2"
js-tokens "^4.0.0"
"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.18.6", "@babel/parser@^7.18.9":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.9.tgz#f2dde0c682ccc264a9a8595efd030a5cc8fd2539"
integrity sha512-9uJveS9eY9DJ0t64YbIBZICtJy8a5QrDEVdiLCG97fVLpDTpGX7t8mMSb6OWw6Lrnjqj4O8zwjELX3dhoMgiBg==
"@babel/parser@^7.22.15", "@babel/parser@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719"
integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==
"@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6":
version "7.18.6"
resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2"
@@ -1043,19 +1112,28 @@
"@babel/parser" "^7.18.6"
"@babel/types" "^7.18.6"
"@babel/traverse@^7.13.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2":
version "7.18.9"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.18.9.tgz#deeff3e8f1bad9786874cb2feda7a2d77a904f98"
integrity sha512-LcPAnujXGwBgv3/WHv01pHtb2tihcyW1XuL9wd7jqh1Z8AQkTd+QVjMrMijrln0T7ED3UXLIy36P9Ao7W75rYg==
"@babel/template@^7.22.15":
version "7.22.15"
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38"
integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==
dependencies:
"@babel/code-frame" "^7.18.6"
"@babel/generator" "^7.18.9"
"@babel/helper-environment-visitor" "^7.18.9"
"@babel/helper-function-name" "^7.18.9"
"@babel/helper-hoist-variables" "^7.18.6"
"@babel/helper-split-export-declaration" "^7.18.6"
"@babel/parser" "^7.18.9"
"@babel/types" "^7.18.9"
"@babel/code-frame" "^7.22.13"
"@babel/parser" "^7.22.15"
"@babel/types" "^7.22.15"
"@babel/traverse@^7.13.0", "@babel/traverse@^7.18.9", "@babel/traverse@^7.7.2":
version "7.23.2"
resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8"
integrity sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==
dependencies:
"@babel/code-frame" "^7.22.13"
"@babel/generator" "^7.23.0"
"@babel/helper-environment-visitor" "^7.22.20"
"@babel/helper-function-name" "^7.23.0"
"@babel/helper-hoist-variables" "^7.22.5"
"@babel/helper-split-export-declaration" "^7.22.6"
"@babel/parser" "^7.23.0"
"@babel/types" "^7.23.0"
debug "^4.1.0"
globals "^11.1.0"
@@ -1067,6 +1145,15 @@
"@babel/helper-validator-identifier" "^7.18.6"
to-fast-properties "^2.0.0"
"@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0":
version "7.23.0"
resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb"
integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==
dependencies:
"@babel/helper-string-parser" "^7.22.5"
"@babel/helper-validator-identifier" "^7.22.20"
to-fast-properties "^2.0.0"
"@bcoe/v8-coverage@^0.2.3":
version "0.2.3"
resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
@@ -1446,6 +1533,11 @@
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
"@jridgewell/resolve-uri@^3.1.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz#c08679063f279615a3326583ba3a90d1d82cc721"
integrity sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==
"@jridgewell/set-array@^1.0.0", "@jridgewell/set-array@^1.0.1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
@@ -1464,6 +1556,19 @@
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
"@jridgewell/sourcemap-codec@^1.4.14":
version "1.4.15"
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
"@jridgewell/trace-mapping@^0.3.17":
version "0.3.20"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz#72e45707cf240fa6b081d0366f8265b0cd10197f"
integrity sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==
dependencies:
"@jridgewell/resolve-uri" "^3.1.0"
"@jridgewell/sourcemap-codec" "^1.4.14"
"@jridgewell/trace-mapping@^0.3.7", "@jridgewell/trace-mapping@^0.3.9":
version "0.3.14"
resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.14.tgz#b231a081d8f66796e475ad588a1ef473112701ed"
@@ -2986,7 +3091,7 @@ case-sensitive-paths-webpack-plugin@^2.4.0:
resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.4.0.tgz#db64066c6422eed2e08cc14b986ca43796dbc6d4"
integrity sha512-roIFONhcxog0JSSWbvVAh3OocukmSgpqOH6YpMkCvav/ySIV3JKg4Dc8vYtQjYi/UxpNE36r/9v+VqTQqgkYmw==
chalk@^2.0.0, chalk@^2.4.1:
chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==