mirror of
https://github.com/Grasscutters/Cultivation.git
synced 2024-11-26 21:30:47 +00:00
Merge pull request #235 from NotThorny/Merge-Compat
Merge Thorny Edition changes
This commit is contained in:
commit
4b6d1a11fb
@ -25,7 +25,7 @@ For game versions 3.1 and above, Cultivation automatically makes a small patch t
|
||||
|
||||
# Download
|
||||
|
||||
[Find release builds here!](https://github.com/Grasscutters/Cultivation/releases)
|
||||
[Find release builds here!](/releases)
|
||||
|
||||
Download and open the MSI, and once installed, run Cultivation as administrator. Refer below for more [detailed setup instructions](#setup).
|
||||
|
||||
@ -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)
|
||||
|
||||
|
@ -14,49 +14,49 @@
|
||||
- [セットアップ](#セットアップ)
|
||||
- [ビルド](#ビルド)
|
||||
- [コードフォーマット・lint](#コードフォーマットlint)
|
||||
- [artifactを生成](#artifactを生成)
|
||||
- [artifact を生成](#artifactを生成)
|
||||
- [テーマについて](#テーマについて)
|
||||
- [スクリーンショット](#スクリーンショット)
|
||||
- [クレジット](#クレジット)
|
||||
|
||||
# クライアントのパッチに関するお知らせ
|
||||
|
||||
ゲームバージョン3.1以降の場合、CultivationはGrasscutterを使用して起動するときにゲームクライアントに自動的に小さなパッチ(RSAパッチ)を適用し、ゲームを閉じると自動的に解除します。理論上は安全ですが、<strong>ゲームクライアント自体に変更を加えるため、公式サーバーに接続するとBANにつながる可能性があります。</strong>これによるBANについての既知の事例はありませんが、可能性は存在します。
|
||||
ゲームバージョン 3.1 以降の場合、Cultivation は Grasscutter を使用して起動するときにゲームクライアントに自動的に小さなパッチ(RSA パッチ)を適用し、ゲームを閉じると自動的に解除します。理論上は安全ですが、<strong>ゲームクライアント自体に変更を加えるため、公式サーバーに接続すると BAN につながる可能性があります。</strong>これによる BAN についての既知の事例はありませんが、可能性は存在します。
|
||||
|
||||
# ダウンロード
|
||||
|
||||
[**リリースビルドはこちら**](https://github.com/Grasscutters/Cultivation/releases)
|
||||
|
||||
MSIインストーラーをダウンロードして開き、インストールしたら、管理者としてCultivationを実行します。[より詳細なセットアップ手順](#セットアップ)については、以下を参照してください。
|
||||
MSI インストーラーをダウンロードして開き、インストールしたら、管理者として Cultivation を実行します。[より詳細なセットアップ手順](#セットアップ)については、以下を参照してください。
|
||||
|
||||
**Windows 7をお使いの場合:** [WebView2](https://developer.microsoft.com/ja-jp/microsoft-edge/webview2/#download-section)を手動でダウンロードしてインストールする必要があります。また、Cultivationのインストールには`.msi`の代わりに`.zip`を使用してください。
|
||||
**Windows 7 をお使いの場合:** [WebView2](https://developer.microsoft.com/ja-jp/microsoft-edge/webview2/#download-section)を手動でダウンロードしてインストールする必要があります。また、Cultivation のインストールには`.msi`の代わりに`.zip`を使用してください。
|
||||
|
||||
# セットアップ
|
||||
|
||||
5分間の解説動画(英語): https://youtu.be/e0irOYbQe7I
|
||||
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>開きます。
|
||||
- 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 サーバーがインストールされている場合は、`.jar`ファイルのパスを設定できます。Cultivation を介して行われるすべてのダウンロードは、そのパスを自動的に使用します。追加の構成は必要ありません。
|
||||
- 複数の Java バージョンを使用している場合、Java 17 のパスを Cultivation に設定できます(自分で Grasscutter サーバーを実行している場合にのみ必要です)。
|
||||
- 自分でサーバーをダウンロードするか、公開サーバーに参加するかどうかを決定します。
|
||||
- 公開サーバーに参加する場合は、[Grasscutterに接続]をクリックして、アドレスとポートを入力してください。
|
||||
- システムエラー、または4214エラーが表示されている場合は、[Discordサポートチャンネル](https://discord.gg/grasscutter)で問い合わせてください。
|
||||
- 公開サーバーに参加する場合は、[Grasscutter に接続]をクリックして、アドレスとポートを入力してください。
|
||||
- システムエラー、または 4214 エラーが表示されている場合は、[Discord サポートチャンネル](https://discord.gg/grasscutter)で問い合わせてください。
|
||||
- 自分でサーバーをダウンロードする場合は、"Downloads"メニューを開きます。(右上の下矢印アイコン)
|
||||
- "Grasscutter All-in-Oneをダウンロード"します。(一番上)
|
||||
- "Grasscutter All-in-One をダウンロード"します。(一番上)
|
||||
- それが完了したら、「起動」の横にあるサーバーアイコンをクリックします。
|
||||
- 自分のサーバーでプレイするには:
|
||||
- [Grasscutterに接続]をクリックします。
|
||||
- [Grasscutter に接続]をクリックします。
|
||||
- アドレスに`localhost`、ポート番号に`443`を指定します。
|
||||
- HTTPS接続を無効にします。
|
||||
- 何らかのエラーが発生した場合は、[Discordサポートチャンネル](https://discord.gg/grasscutter)で問い合わせてください。
|
||||
- 何らかのCultivationに関する問題は[Issuesページ](/issues)へお願いします。
|
||||
- 何らかのGrasscutterサーバーに関する問題は[GrasscutterのIssuesページ](https://github.com/Grasscutters/Grasscutter/issues)へお願いします。
|
||||
- HTTPS 接続を無効にします。
|
||||
- 何らかのエラーが発生した場合は、[Discord サポートチャンネル](https://discord.gg/grasscutter)で問い合わせてください。
|
||||
- 何らかの Cultivation に関する問題は[Issues ページ](/issues)へお願いします。
|
||||
- 何らかの Grasscutter サーバーに関する問題は[Grasscutter の Issues ページ](https://github.com/Grasscutters/Grasscutter/issues)へお願いします。
|
||||
|
||||
# トラブルシューティング
|
||||
|
||||
@ -67,9 +67,9 @@ MSIインストーラーをダウンロードして開き、インストール
|
||||
- アンインストール時に問題が発生する場合は、`HKEY_LOCAL_MACHINE\SOFTWARE\WOW6432Node\Microsoft\EdgeUpdate\Clients\{F3017226-FE2A-4295-8BDF-00C3A9A7E4C5}`レジストリを削除して再度試してください。
|
||||
- [コマンドプロンプトからアンインストール](https://superuser.com/a/1743626)する方法を試すこともできます。
|
||||
|
||||
### Cultivationを使用した後にインターネットに接続できない問題
|
||||
### Cultivation を使用した後にインターネットに接続できない問題
|
||||
|
||||
ゲームを終了すると、Cultivationウィンドウに戻り再びポップアップすることを確認してください。これは、ゲームが終了されたこと、そしてプロキシ設定が正常に戻されたことを示しています。ウィンドウに戻る前にCultivationを閉じた場合、またはインターネットの他の問題が発生した場合は、[Windowsのプロキシ設定](https://is.gd/tZHkvl)を開き、"手動プロキシセットアップ"をオフにしてください。これでインターネット接続は元に戻ります。
|
||||
ゲームを終了すると、Cultivation ウィンドウに戻り再びポップアップすることを確認してください。これは、ゲームが終了されたこと、そしてプロキシ設定が正常に戻されたことを示しています。ウィンドウに戻る前に Cultivation を閉じた場合、またはインターネットの他の問題が発生した場合は、[Windows のプロキシ設定](https://is.gd/tZHkvl)を開き、"手動プロキシセットアップ"をオフにしてください。これでインターネット接続は元に戻ります。
|
||||
|
||||
# 開発者向けクイックスタート
|
||||
|
||||
@ -96,7 +96,7 @@ MSIインストーラーをダウンロードして開き、インストール
|
||||
- `yarn format`
|
||||
- `yarn lint`, `yarn lint:fix`
|
||||
|
||||
### artifactを生成
|
||||
### artifact を生成
|
||||
|
||||
- 秘密鍵へのパスを持つ環境変数として`TAURI_PRIVATE_KEY`を追加
|
||||
- 秘密鍵のパスワードを持つ環境変数として`TAURI_KEY_PASSWORD`を追加
|
||||
@ -117,8 +117,8 @@ MSIインストーラーをダウンロードして開き、インストール
|
||||
|
||||
## クレジット
|
||||
|
||||
- [SpikeHD](https://github.com/SpikeHD): オリジナルである **GrassClipper** を製作し、Cultivationの素晴らしいUIを作成
|
||||
- [KingRainbow44](https://github.com/KingRainbow44): ゼロからプロキシデーモンを作成し、Cultivationへ統合
|
||||
- [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): 素晴らしく軽量でシンプルなデスクトップアプリケーションフレームワーク・ライブラリを提供
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "cultivation",
|
||||
"version": "1.0.26",
|
||||
"version": "1.5.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^1.0.0-rc.5",
|
||||
|
3026
src-tauri/Cargo.lock
generated
3026
src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "cultivation"
|
||||
version = "1.2.0"
|
||||
version = "1.5.1"
|
||||
description = "A custom launcher for anime game."
|
||||
authors = ["KingRainbow44", "SpikeHD"]
|
||||
license = ""
|
||||
|
@ -35,11 +35,13 @@
|
||||
"un_elevated": "非提升运行游戏(无管理员)",
|
||||
"redirect_more": "还可以重定向其他MHY游戏",
|
||||
"web_cache": "删除 webCaches 文件夹",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "启动参数",
|
||||
"offline_mode": "离线模式",
|
||||
"fix_res": "修复登录超时"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "下载 Grasscutter 一体化",
|
||||
"grasscutter_fullquest": "下载 Quest 一体化",
|
||||
"grasscutter_fullquest": "下载 5.0 一体化",
|
||||
"grasscutter_stable_data": "下载 Grasscutter 稳定版数据",
|
||||
"grasscutter_latest_data": "下载 Grasscutter 开发版数据",
|
||||
"grasscutter_stable_data_update": "更新 Grasscutter 稳定版数据",
|
||||
@ -67,7 +69,8 @@
|
||||
"select_folder": "选择文件夹...",
|
||||
"download": "下载",
|
||||
"delete": "删除",
|
||||
"install": "安装"
|
||||
"install": "安装",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "最近提交",
|
||||
|
@ -35,11 +35,13 @@
|
||||
"un_elevated": "在不升高的情况下运行游戏(没有管理员)。",
|
||||
"redirect_more": "同時重定向其他 MHY 遊戲",
|
||||
"web_cache": "刪除 webCaches 文件夾",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "啟動參數",
|
||||
"offline_mode": "離線模式",
|
||||
"fix_res": "修復登入逾時"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "下載Grasscutter多合一下載",
|
||||
"grasscutter_fullquest": "下载 Quest 一体化",
|
||||
"grasscutter_fullquest": "下载 5.0 一体化",
|
||||
"grasscutter_stable_data": "下載Grasscutter穩定版數據(Data)",
|
||||
"grasscutter_latest_data": "下載Grasscutter開發板數據(Data)",
|
||||
"grasscutter_stable_data_update": "更新Grasscutter穩定版數據(Data)",
|
||||
@ -67,7 +69,8 @@
|
||||
"select_folder": "選擇資料夾...",
|
||||
"download": "下載",
|
||||
"delete": "刪除",
|
||||
"install": "安裝"
|
||||
"install": "安裝",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "最近的PR",
|
||||
|
@ -37,11 +37,13 @@
|
||||
"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": "Launch Args"
|
||||
"launch_args": "Start-Argumente",
|
||||
"offline_mode": "Offline-Modus",
|
||||
"fix_res": "Login-Zeitüberschreitung beheben"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Grasscutter All-in-One herunterladen",
|
||||
"grasscutter_fullquest": "Questing All-in-One herunterladen",
|
||||
"grasscutter_fullquest": "5.0 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",
|
||||
@ -69,7 +71,8 @@
|
||||
"select_folder": "Ordner auswählen...",
|
||||
"download": "Herunterladen",
|
||||
"delete": "Löschen",
|
||||
"install": "Installieren"
|
||||
"install": "Installieren",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Neueste Commits",
|
||||
|
@ -37,11 +37,13 @@
|
||||
"check_aagl": "For more options, check the other launcher",
|
||||
"grasscutter_elevation": "Method of running GC on restricted ports",
|
||||
"web_cache": "Delete webCaches folder",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Launch Args",
|
||||
"offline_mode": "Offline Mode",
|
||||
"fix_res": "Fix Login Timeout"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Download Grasscutter All-in-One",
|
||||
"grasscutter_fullquest": "Download Questing All-in-One",
|
||||
"grasscutter_fullquest": "Download 5.0 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",
|
||||
@ -69,7 +71,8 @@
|
||||
"select_folder": "Select folder...",
|
||||
"download": "Download",
|
||||
"delete": "Delete",
|
||||
"install": "Install"
|
||||
"install": "Install",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Recent Commits",
|
||||
@ -96,7 +99,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"
|
||||
}
|
||||
|
@ -35,11 +35,13 @@
|
||||
"un_elevated": "Ejecutar el juego sin permisos de administrador",
|
||||
"redirect_more": "También redirigir otros juegos MHY",
|
||||
"web_cache": "Eliminar la carpeta webCaches",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Args de lanzamiento",
|
||||
"offline_mode": "Modo sin conexión",
|
||||
"fix_res": "Reparar el tiempo de espera"
|
||||
},
|
||||
"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 5.0",
|
||||
"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",
|
||||
@ -67,7 +69,8 @@
|
||||
"select_folder": "Seleccionar la carpeta",
|
||||
"download": "Descargar",
|
||||
"delete": "Borrar",
|
||||
"install": "Instalar"
|
||||
"install": "Instalar",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Commits recientes",
|
||||
|
@ -35,11 +35,13 @@
|
||||
"un_elevated": "Exécuter le jeu sans élévation (pas d'administrateur)",
|
||||
"redirect_more": "Réorienter également les autres jeux MHY",
|
||||
"web_cache": "Supprimer le dossier webCaches",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Arguments de lancement",
|
||||
"offline_mode": "Mode hors ligne",
|
||||
"fix_res": "Réparation du login"
|
||||
},
|
||||
"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 5.0 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)",
|
||||
@ -66,7 +68,8 @@
|
||||
"select_folder": "Choisir un dossier...",
|
||||
"download": "Télécharger",
|
||||
"delete": "Supprimer",
|
||||
"install": "Installer"
|
||||
"install": "Installer",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Commits récents",
|
||||
|
@ -34,11 +34,13 @@
|
||||
"un_elevated": "Jalankan game yang tidak ditinggikan (tanpa admin)",
|
||||
"redirect_more": "Juga mengarahkan ulang game MHY lainnya",
|
||||
"web_cache": "Hapus folder webCaches",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Luncurkan Args",
|
||||
"offline_mode": "Mode Offline",
|
||||
"fix_res": "Perbaiki batas waktu login"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Sedang Mendownload Grasscutter Semua Dalam Satu",
|
||||
"grasscutter_fullquest": "Unduh pencarian semua dalam satu",
|
||||
"grasscutter_fullquest": "Unduh 5.0 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",
|
||||
@ -64,7 +66,8 @@
|
||||
"select_file": "Pilih File Atau Folder...",
|
||||
"select_folder": "Pilih Folder...",
|
||||
"download": "download",
|
||||
"delete": "Menghapus"
|
||||
"delete": "Menghapus",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Commit Terbaru",
|
||||
|
@ -35,11 +35,13 @@
|
||||
"un_elevated": "Avvia il gioco non-elevato (non admin)",
|
||||
"redirect_more": "Reindirizza anche altri giochi MHY",
|
||||
"web_cache": "Elimina la cartella webCaches",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Argomenti di lancio",
|
||||
"offline_mode": "Modalità Offline",
|
||||
"fix_res": "Correggere il timeout dell'accesso"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Scarica Grasscutter Tutto-in-Uno",
|
||||
"grasscutter_fullquest": "Scarica Questing Tutto-in-Uno",
|
||||
"grasscutter_fullquest": "Scarica 5.0 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",
|
||||
@ -67,7 +69,8 @@
|
||||
"select_folder": "Seleziona cartella...",
|
||||
"download": "Scarica",
|
||||
"delete": "Cancella",
|
||||
"install": "Installa"
|
||||
"install": "Installa",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Commit Recenti",
|
||||
|
@ -35,11 +35,13 @@
|
||||
"un_elevated": "게임 비상승 실행(관리자 없음)",
|
||||
"redirect_more": "다른 MHY 게임도 리디렉션",
|
||||
"web_cache": "webCaches 폴더 삭제",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "실행 인수",
|
||||
"offline_mode": "오프라인 모드",
|
||||
"fix_res": "로그인 시간 초과 수정"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "올인원 Grasscutter 다운로드",
|
||||
"grasscutter_fullquest": "퀘스트 올인원 다운로드",
|
||||
"grasscutter_fullquest": "5.0 올인원 다운로드",
|
||||
"grasscutter_stable_data": "안정적인 데이터 다운로드",
|
||||
"grasscutter_latest_data": "최신 데이터 다운로드",
|
||||
"grasscutter_stable_data_update": "안정적 데이터 업데이트",
|
||||
@ -67,7 +69,8 @@
|
||||
"select_folder": "폴더 선택...",
|
||||
"download": "다운로드",
|
||||
"delete": "삭제",
|
||||
"install": "설치"
|
||||
"install": "설치",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "공지 사항",
|
||||
|
@ -33,11 +33,13 @@
|
||||
"un_elevated": "Palaist spēli bez paaugstinājuma (bez administratora)",
|
||||
"redirect_more": "Arī novirzīt citas MHY spēles",
|
||||
"web_cache": "Dzēsiet mapi WebCaches",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Palaišanas args",
|
||||
"offline_mode": "Bezsaistes režīms",
|
||||
"fix_res": "Fiksēt pieteikšanās laika"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Lejupielādējiet Grasscutter viss vienā",
|
||||
"grasscutter_fullquest": "Lejupielādēt questing viss vienā",
|
||||
"grasscutter_fullquest": "Lejupielādēt 5.0 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",
|
||||
@ -63,7 +65,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",
|
||||
|
@ -34,11 +34,13 @@
|
||||
"un_elevated": "Voer het spel uit zonder hoogtevrees (geen admin)",
|
||||
"redirect_more": "Richt ook andere MHY-spellen",
|
||||
"web_cache": "Verwijder de webCaches-map",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Args starten",
|
||||
"offline_mode": "Offline Modus",
|
||||
"fix_res": "Time-out inloggen verhelpen"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Grasscutter Alles-in-één Downloaden",
|
||||
"grasscutter_fullquest": "Alles-in-één zoeken downloaden",
|
||||
"grasscutter_fullquest": "Alles-in-één 5.0 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",
|
||||
@ -66,7 +68,8 @@
|
||||
"select_folder": "Select folder...",
|
||||
"download": "Download",
|
||||
"delete": "Verwijder",
|
||||
"install": "Install"
|
||||
"install": "Install",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Recente Opdrachten",
|
||||
|
@ -37,11 +37,13 @@
|
||||
"check_aagl": "Więcej opcji znajdziesz w drugim launcherze",
|
||||
"grasscutter_elevation": "Sposób uruchomienia GC na ograniczonym porcie",
|
||||
"web_cache": "Usuń folder webCaches",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Argumenty uruchamiania",
|
||||
"offline_mode": "Tryb offline",
|
||||
"fix_res": "Napraw limit czasu logowania"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Pobierz Grasscutter (wszystko w jednym)",
|
||||
"grasscutter_fullquest": "Pobierz Questing (wszystko w jednym)",
|
||||
"grasscutter_fullquest": "Pobierz 5.0 (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",
|
||||
@ -69,7 +71,8 @@
|
||||
"select_folder": "Wybierz folder...",
|
||||
"download": "Pobierz",
|
||||
"delete": "Usuń",
|
||||
"install": "Zainstaluj"
|
||||
"install": "Zainstaluj",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Ostatnie Commity",
|
||||
|
@ -35,11 +35,13 @@
|
||||
"un_elevated": "Executar o jogo não-elevated (sem admin)",
|
||||
"redirect_more": "Também redirecionar outros jogos MHY",
|
||||
"web_cache": "Excluir pasta webCaches",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Argumentos de lançamento",
|
||||
"offline_mode": "Modo offline",
|
||||
"fix_res": "Corrigir o tempo limite de login"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Baixar o Grasscutter Tudo-em-Um",
|
||||
"grasscutter_fullquest": "Baixar de missões em um só lugar",
|
||||
"grasscutter_fullquest": "Baixar de 5.0 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",
|
||||
@ -67,7 +69,8 @@
|
||||
"select_folder": "Selecione a pasta...",
|
||||
"download": "Baixar",
|
||||
"delete": "Deletar",
|
||||
"install": "Instalar"
|
||||
"install": "Instalar",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Commits Recentes",
|
||||
|
@ -34,11 +34,13 @@
|
||||
"un_elevated": "Запустите игру в неэлегантном режиме (без администратора)",
|
||||
"redirect_more": "Также перенаправьте другие игры MHY",
|
||||
"web_cache": "Удалить папку webCaches",
|
||||
"launch_args": "Launch Args"
|
||||
"launch_args": "Параметры запуска",
|
||||
"offline_mode": "Автономный режим",
|
||||
"fix_res": "Исправить таймаут входа в систему"
|
||||
},
|
||||
"downloads": {
|
||||
"grasscutter_fullbuild": "Скачать все в одном Grasscutter",
|
||||
"grasscutter_fullquest": "Скачать квесты все в одном",
|
||||
"grasscutter_fullquest": "Скачать 5.0 все в одном",
|
||||
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
|
||||
"grasscutter_latest_data": "Скачать последние данные Grasscutter",
|
||||
"grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter",
|
||||
@ -66,7 +68,8 @@
|
||||
"select_folder": "Выберите папку...",
|
||||
"download": "Скачать",
|
||||
"delete": "Удалить",
|
||||
"install": "Установить"
|
||||
"install": "Установить",
|
||||
"fix": "Fix"
|
||||
},
|
||||
"news": {
|
||||
"latest_commits": "Последние коммиты",
|
||||
|
@ -35,11 +35,13 @@
|
||||
"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",
|
||||
"web_cache": "Xóa thư mục webCaches",
|
||||
"launch_args": "Launch Args"
|
||||
"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"
|
||||
},
|
||||
"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 5.0 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",
|
||||
@ -67,7 +69,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",
|
||||
|
BIN
src-tauri/patch/46version.dll
Normal file
BIN
src-tauri/patch/46version.dll
Normal file
Binary file not shown.
BIN
src-tauri/patch/47version.dll
Normal file
BIN
src-tauri/patch/47version.dll
Normal file
Binary file not shown.
BIN
src-tauri/patch/50version.dll
Normal file
BIN
src-tauri/patch/50version.dll
Normal file
Binary file not shown.
@ -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
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -108,15 +108,15 @@ async fn parse_args(inp: &Vec<String>) -> Result<Args, ArgsError> {
|
||||
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;
|
||||
@ -218,7 +218,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 +228,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 +241,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 +296,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 +335,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() {
|
||||
|
@ -50,8 +50,90 @@ 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
|
||||
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 +152,17 @@ pub async fn patch_game() -> bool {
|
||||
return true;
|
||||
}
|
||||
|
||||
// For 5.0 and up
|
||||
if i_ver > 49 {
|
||||
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 +179,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 +263,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
|
||||
}
|
||||
|
||||
|
@ -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)")
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -390,44 +390,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 +410,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 +454,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)]
|
||||
|
@ -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();
|
||||
|
@ -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.5.1"
|
||||
},
|
||||
"tauri": {
|
||||
"allowlist": {
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -76,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
|
||||
@ -93,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')
|
||||
@ -182,33 +176,35 @@ export class Main extends React.Component<IProps, IState> {
|
||||
// Update launch args to allow launching when updating from old versions
|
||||
await setConfigOption('launch_args', await getConfigOption('launch_args'))
|
||||
|
||||
// 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, '')
|
||||
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, '')
|
||||
|
||||
// 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!
|
||||
</>
|
||||
),
|
||||
})
|
||||
|
||||
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
|
||||
@ -355,7 +351,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"
|
||||
|
@ -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)
|
||||
}}
|
||||
|
@ -17,9 +17,11 @@ import { getGameExecutable, getGameVersion, getGrasscutterJar } from '../../util
|
||||
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 {
|
||||
@ -72,10 +74,15 @@ 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)
|
||||
|
||||
listen('start_grasscutter', async () => {
|
||||
this.launchServer()
|
||||
})
|
||||
|
||||
listen('set_game', async () => {
|
||||
this.setButtonLabel()
|
||||
})
|
||||
}
|
||||
|
||||
async componentDidMount() {
|
||||
@ -96,6 +103,8 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
migotoSet: config.migoto_path !== '',
|
||||
unElevated: config.un_elevated || false,
|
||||
})
|
||||
|
||||
this.setButtonLabel()
|
||||
}
|
||||
|
||||
async toggleGrasscutter() {
|
||||
@ -140,8 +149,9 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
|
||||
// Connect to proxy
|
||||
if (config.toggle_grasscutter) {
|
||||
const game_exe = await getGameExecutable()
|
||||
let 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()
|
||||
@ -164,10 +174,58 @@ 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()
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -186,7 +244,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
|
||||
@ -314,6 +372,19 @@ 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'),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div id="playButton">
|
||||
|
@ -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/GrasscutterLunaGC50.zip' // https://github.com/Kei-Luna/LunaGC_5.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>
|
||||
)
|
||||
}
|
||||
|
@ -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,
|
||||
})
|
||||
|
@ -16,8 +16,11 @@ import DownloadHandler from '../../../utils/download'
|
||||
import * as meta from '../../../utils/rsa'
|
||||
import HelpButton from '../common/HelpButton'
|
||||
import SmallButton from '../common/SmallButton'
|
||||
import { confirm } from '@tauri-apps/api/dialog'
|
||||
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',
|
||||
@ -52,6 +55,8 @@ interface IState {
|
||||
un_elevated: boolean
|
||||
redirect_more: boolean
|
||||
launch_args: string
|
||||
offline_mode: boolean
|
||||
newer_game: boolean
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: string
|
||||
@ -88,6 +93,8 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
un_elevated: false,
|
||||
redirect_more: false,
|
||||
launch_args: '',
|
||||
offline_mode: false,
|
||||
newer_game: false,
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: GrasscutterElevation.None,
|
||||
@ -118,13 +125,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)
|
||||
|
||||
@ -150,6 +154,8 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
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,
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None,
|
||||
@ -163,22 +169,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')
|
||||
}
|
||||
@ -187,26 +199,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) {
|
||||
@ -267,7 +277,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}`)
|
||||
|
||||
@ -284,6 +294,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()
|
||||
|
||||
@ -343,6 +363,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,
|
||||
})
|
||||
@ -355,6 +384,15 @@ 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
|
||||
@ -364,8 +402,70 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
const path2 = pathArr.join('/') + '/Yuanshen_Data/webCaches'
|
||||
|
||||
// Delete the folder
|
||||
await invoke('dir_delete', { path: path })
|
||||
await invoke('dir_delete', { path: path2 })
|
||||
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) {
|
||||
@ -542,7 +642,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>
|
||||
@ -599,6 +699,31 @@ 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="menuOptionsContainerNewerGame">
|
||||
<div className="OptionLabel" id="menuOptionsLabelNewerGame">
|
||||
<Tr text="Patch Mihoyonet" />
|
||||
</div>
|
||||
<div className="OptionValue" id="menuOptionsCheckboxNewerGame">
|
||||
<Checkbox
|
||||
onChange={() => this.toggleOption('newer_game')}
|
||||
checked={this.state?.newer_game}
|
||||
id="newerGame"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Divider />
|
||||
|
||||
@ -709,6 +834,15 @@ export default class Options extends React.Component<IProps, IState> {
|
||||
value={this.state.launch_args}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</Menu>
|
||||
)
|
||||
}
|
||||
|
@ -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 - Cultivation 1.5.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">
|
||||
|
@ -29,6 +29,8 @@ let defaultConfig: Configuration
|
||||
un_elevated: false,
|
||||
redirect_more: false,
|
||||
launch_args: '',
|
||||
offline_mode: false,
|
||||
newer_game: false,
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: 'None',
|
||||
@ -64,6 +66,8 @@ export interface Configuration {
|
||||
un_elevated: boolean
|
||||
redirect_more: boolean
|
||||
launch_args: string
|
||||
offline_mode: boolean
|
||||
newer_game: boolean
|
||||
|
||||
// Linux stuff
|
||||
grasscutter_elevation: string
|
||||
@ -128,7 +132,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')) {
|
||||
|
@ -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)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -45,7 +45,7 @@ export async function getGameDataFolder() {
|
||||
return null
|
||||
}
|
||||
|
||||
return (await getGameFolder()) + '/' + gameExec.replace('.exe', '_Data')
|
||||
return (await getGameFolder()) + '\\' + gameExec.replace('.exe', '_Data')
|
||||
}
|
||||
|
||||
export async function getGameVersion() {
|
||||
@ -55,9 +55,33 @@ export async function getGameVersion() {
|
||||
return null
|
||||
}
|
||||
|
||||
const hasAsb = await invoke('dir_exists', {
|
||||
path: GameData + '\\StreamingAssets\\asb_settings.json',
|
||||
})
|
||||
|
||||
if (!hasAsb) {
|
||||
// For games that cannot determine game version
|
||||
const otherGameVer: string = 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',
|
||||
})
|
||||
)
|
||||
|
||||
|
@ -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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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),
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user