Changes from 1.2.1 - 1.5.1

Contains slightly modified commits from between 1.2.1 and 1.5.1.
This commit is contained in:
Thoronium 2024-10-30 13:18:07 -06:00
parent 31c60755af
commit f6f5eae31c
46 changed files with 2619 additions and 1669 deletions

View File

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

3026
src-tauri/Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

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

View File

@ -35,11 +35,13 @@
"un_elevated": "非提升运行游戏(无管理员)", "un_elevated": "非提升运行游戏(无管理员)",
"redirect_more": "还可以重定向其他MHY游戏", "redirect_more": "还可以重定向其他MHY游戏",
"web_cache": "删除 webCaches 文件夹", "web_cache": "删除 webCaches 文件夹",
"launch_args": "Launch Args" "launch_args": "启动参数",
"offline_mode": "离线模式",
"fix_res": "修复登录超时"
}, },
"downloads": { "downloads": {
"grasscutter_fullbuild": "下载 Grasscutter 一体化", "grasscutter_fullbuild": "下载 Grasscutter 一体化",
"grasscutter_fullquest": "下载 Quest 一体化", "grasscutter_fullquest": "下载 5.0 一体化",
"grasscutter_stable_data": "下载 Grasscutter 稳定版数据", "grasscutter_stable_data": "下载 Grasscutter 稳定版数据",
"grasscutter_latest_data": "下载 Grasscutter 开发版数据", "grasscutter_latest_data": "下载 Grasscutter 开发版数据",
"grasscutter_stable_data_update": "更新 Grasscutter 稳定版数据", "grasscutter_stable_data_update": "更新 Grasscutter 稳定版数据",
@ -67,7 +69,8 @@
"select_folder": "选择文件夹...", "select_folder": "选择文件夹...",
"download": "下载", "download": "下载",
"delete": "删除", "delete": "删除",
"install": "安装" "install": "安装",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "最近提交", "latest_commits": "最近提交",

View File

@ -35,11 +35,13 @@
"un_elevated": "在不升高的情况下运行游戏(没有管理员)。", "un_elevated": "在不升高的情况下运行游戏(没有管理员)。",
"redirect_more": "同時重定向其他 MHY 遊戲", "redirect_more": "同時重定向其他 MHY 遊戲",
"web_cache": "刪除 webCaches 文件夾", "web_cache": "刪除 webCaches 文件夾",
"launch_args": "Launch Args" "launch_args": "啟動參數",
"offline_mode": "離線模式",
"fix_res": "修復登入逾時"
}, },
"downloads": { "downloads": {
"grasscutter_fullbuild": "下載Grasscutter多合一下載", "grasscutter_fullbuild": "下載Grasscutter多合一下載",
"grasscutter_fullquest": "下载 Quest 一体化", "grasscutter_fullquest": "下载 5.0 一体化",
"grasscutter_stable_data": "下載Grasscutter穩定版數據Data", "grasscutter_stable_data": "下載Grasscutter穩定版數據Data",
"grasscutter_latest_data": "下載Grasscutter開發板數據Data", "grasscutter_latest_data": "下載Grasscutter開發板數據Data",
"grasscutter_stable_data_update": "更新Grasscutter穩定版數據Data", "grasscutter_stable_data_update": "更新Grasscutter穩定版數據Data",
@ -67,7 +69,8 @@
"select_folder": "選擇資料夾...", "select_folder": "選擇資料夾...",
"download": "下載", "download": "下載",
"delete": "刪除", "delete": "刪除",
"install": "安裝" "install": "安裝",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "最近的PR", "latest_commits": "最近的PR",

View File

@ -37,11 +37,13 @@
"check_aagl": "Für weitere Optionen, schaue weiter", "check_aagl": "Für weitere Optionen, schaue weiter",
"grasscutter_elevation": "Methode zur Ausführung von GC auf eingeschränkten Ports", "grasscutter_elevation": "Methode zur Ausführung von GC auf eingeschränkten Ports",
"web_cache": "WebCaches-Ordner löschen", "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": { "downloads": {
"grasscutter_fullbuild": "Grasscutter All-in-One herunterladen", "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_stable_data": "Stabile Grasscutter-Daten herunterladen",
"grasscutter_latest_data": "Neueste Grasscutter-Daten herunterladen", "grasscutter_latest_data": "Neueste Grasscutter-Daten herunterladen",
"grasscutter_stable_data_update": "Stabile Grasscutter-Daten aktualisieren", "grasscutter_stable_data_update": "Stabile Grasscutter-Daten aktualisieren",
@ -69,7 +71,8 @@
"select_folder": "Ordner auswählen...", "select_folder": "Ordner auswählen...",
"download": "Herunterladen", "download": "Herunterladen",
"delete": "Löschen", "delete": "Löschen",
"install": "Installieren" "install": "Installieren",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Neueste Commits", "latest_commits": "Neueste Commits",

View File

@ -37,11 +37,13 @@
"check_aagl": "For more options, check the other launcher", "check_aagl": "For more options, check the other launcher",
"grasscutter_elevation": "Method of running GC on restricted ports", "grasscutter_elevation": "Method of running GC on restricted ports",
"web_cache": "Delete webCaches folder", "web_cache": "Delete webCaches folder",
"launch_args": "Launch Args" "launch_args": "Launch Args",
"offline_mode": "Offline Mode",
"fix_res": "Fix Login Timeout"
}, },
"downloads": { "downloads": {
"grasscutter_fullbuild": "Download Grasscutter All-in-One", "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_stable_data": "Download Grasscutter Stable Data",
"grasscutter_latest_data": "Download Grasscutter Latest Data", "grasscutter_latest_data": "Download Grasscutter Latest Data",
"grasscutter_stable_data_update": "Update Grasscutter Stable Data", "grasscutter_stable_data_update": "Update Grasscutter Stable Data",
@ -69,7 +71,8 @@
"select_folder": "Select folder...", "select_folder": "Select folder...",
"download": "Download", "download": "Download",
"delete": "Delete", "delete": "Delete",
"install": "Install" "install": "Install",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Recent Commits", "latest_commits": "Recent Commits",
@ -96,7 +99,7 @@
"akebi_name": "Akebi", "akebi_name": "Akebi",
"migoto_name": "Migoto", "migoto_name": "Migoto",
"reshade_name": "Reshade", "reshade_name": "Reshade",
"akebi": "Set Akebi/Acrepi Executable", "akebi": "Set Akebi/Other Cheat Executable",
"migoto": "Set 3DMigoto Executable", "migoto": "Set 3DMigoto Executable",
"reshade": "Set Reshade Injector" "reshade": "Set Reshade Injector"
} }

View File

@ -35,11 +35,13 @@
"un_elevated": "Ejecutar el juego sin permisos de administrador", "un_elevated": "Ejecutar el juego sin permisos de administrador",
"redirect_more": "También redirigir otros juegos MHY", "redirect_more": "También redirigir otros juegos MHY",
"web_cache": "Eliminar la carpeta webCaches", "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": { "downloads": {
"grasscutter_fullbuild": "Descargar datos todo en uno de Grasscutter", "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_stable_data": "Descargar datos Estables de Grasscutter",
"grasscutter_latest_data": "Descargar datos más Recientes de Grasscutter", "grasscutter_latest_data": "Descargar datos más Recientes de Grasscutter",
"grasscutter_stable_data_update": "Actualizar datos estables de Grasscutter", "grasscutter_stable_data_update": "Actualizar datos estables de Grasscutter",
@ -67,7 +69,8 @@
"select_folder": "Seleccionar la carpeta", "select_folder": "Seleccionar la carpeta",
"download": "Descargar", "download": "Descargar",
"delete": "Borrar", "delete": "Borrar",
"install": "Instalar" "install": "Instalar",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Commits recientes", "latest_commits": "Commits recientes",

View File

@ -35,11 +35,13 @@
"un_elevated": "Exécuter le jeu sans élévation (pas d'administrateur)", "un_elevated": "Exécuter le jeu sans élévation (pas d'administrateur)",
"redirect_more": "Réorienter également les autres jeux MHY", "redirect_more": "Réorienter également les autres jeux MHY",
"web_cache": "Supprimer le dossier webCaches", "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": { "downloads": {
"grasscutter_fullbuild": "Telecharger Grasscutter tout-en-un", "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_stable_data": "Télécharger les donnees de Grasscutter (version stable)",
"grasscutter_latest_data": "Télécharger les donnees de Grasscutter (derniere version)", "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)", "grasscutter_stable_data_update": "Mettre à jour les données de Grasscutter (version stable)",
@ -66,7 +68,8 @@
"select_folder": "Choisir un dossier...", "select_folder": "Choisir un dossier...",
"download": "Télécharger", "download": "Télécharger",
"delete": "Supprimer", "delete": "Supprimer",
"install": "Installer" "install": "Installer",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Commits récents", "latest_commits": "Commits récents",

View File

@ -34,11 +34,13 @@
"un_elevated": "Jalankan game yang tidak ditinggikan (tanpa admin)", "un_elevated": "Jalankan game yang tidak ditinggikan (tanpa admin)",
"redirect_more": "Juga mengarahkan ulang game MHY lainnya", "redirect_more": "Juga mengarahkan ulang game MHY lainnya",
"web_cache": "Hapus folder webCaches", "web_cache": "Hapus folder webCaches",
"launch_args": "Launch Args" "launch_args": "Luncurkan Args",
"offline_mode": "Mode Offline",
"fix_res": "Perbaiki batas waktu login"
}, },
"downloads": { "downloads": {
"grasscutter_fullbuild": "Sedang Mendownload Grasscutter Semua Dalam Satu", "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_stable_data": "Sedang Mendownload Grasscutter Versi Stabil",
"grasscutter_latest_data": "Sedang Mendownload Grasscutter Data Terbaru", "grasscutter_latest_data": "Sedang Mendownload Grasscutter Data Terbaru",
"grasscutter_stable_data_update": "Memperbaharui Grasscutter Data Stabil", "grasscutter_stable_data_update": "Memperbaharui Grasscutter Data Stabil",
@ -64,7 +66,8 @@
"select_file": "Pilih File Atau Folder...", "select_file": "Pilih File Atau Folder...",
"select_folder": "Pilih Folder...", "select_folder": "Pilih Folder...",
"download": "download", "download": "download",
"delete": "Menghapus" "delete": "Menghapus",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Commit Terbaru", "latest_commits": "Commit Terbaru",

View File

@ -35,11 +35,13 @@
"un_elevated": "Avvia il gioco non-elevato (non admin)", "un_elevated": "Avvia il gioco non-elevato (non admin)",
"redirect_more": "Reindirizza anche altri giochi MHY", "redirect_more": "Reindirizza anche altri giochi MHY",
"web_cache": "Elimina la cartella webCaches", "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": { "downloads": {
"grasscutter_fullbuild": "Scarica Grasscutter Tutto-in-Uno", "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_stable_data": "Scarica i dati di Grasscutter Stabili",
"grasscutter_latest_data": "Scarica i dati di Grasscutter Più Recenti", "grasscutter_latest_data": "Scarica i dati di Grasscutter Più Recenti",
"grasscutter_stable_data_update": "Aggiorna i dati di Grasscutter Stabili", "grasscutter_stable_data_update": "Aggiorna i dati di Grasscutter Stabili",
@ -67,7 +69,8 @@
"select_folder": "Seleziona cartella...", "select_folder": "Seleziona cartella...",
"download": "Scarica", "download": "Scarica",
"delete": "Cancella", "delete": "Cancella",
"install": "Installa" "install": "Installa",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Commit Recenti", "latest_commits": "Commit Recenti",

View File

@ -35,11 +35,13 @@
"un_elevated": "게임 비상승 실행(관리자 없음)", "un_elevated": "게임 비상승 실행(관리자 없음)",
"redirect_more": "다른 MHY 게임도 리디렉션", "redirect_more": "다른 MHY 게임도 리디렉션",
"web_cache": "webCaches 폴더 삭제", "web_cache": "webCaches 폴더 삭제",
"launch_args": "Launch Args" "launch_args": "실행 인수",
"offline_mode": "오프라인 모드",
"fix_res": "로그인 시간 초과 수정"
}, },
"downloads": { "downloads": {
"grasscutter_fullbuild": "올인원 Grasscutter 다운로드", "grasscutter_fullbuild": "올인원 Grasscutter 다운로드",
"grasscutter_fullquest": "퀘스트 올인원 다운로드", "grasscutter_fullquest": "5.0 올인원 다운로드",
"grasscutter_stable_data": "안정적인 데이터 다운로드", "grasscutter_stable_data": "안정적인 데이터 다운로드",
"grasscutter_latest_data": "최신 데이터 다운로드", "grasscutter_latest_data": "최신 데이터 다운로드",
"grasscutter_stable_data_update": "안정적 데이터 업데이트", "grasscutter_stable_data_update": "안정적 데이터 업데이트",
@ -67,7 +69,8 @@
"select_folder": "폴더 선택...", "select_folder": "폴더 선택...",
"download": "다운로드", "download": "다운로드",
"delete": "삭제", "delete": "삭제",
"install": "설치" "install": "설치",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "공지 사항", "latest_commits": "공지 사항",

View File

@ -33,11 +33,13 @@
"un_elevated": "Palaist spēli bez paaugstinājuma (bez administratora)", "un_elevated": "Palaist spēli bez paaugstinājuma (bez administratora)",
"redirect_more": "Arī novirzīt citas MHY spēles", "redirect_more": "Arī novirzīt citas MHY spēles",
"web_cache": "Dzēsiet mapi WebCaches", "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": { "downloads": {
"grasscutter_fullbuild": "Lejupielādējiet Grasscutter viss vienā", "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_stable_data": "Lejupielādējiet Grasscutter stabilos datus",
"grasscutter_latest_data": "Lejupielādējiet Grasscutter jaunākos datus", "grasscutter_latest_data": "Lejupielādējiet Grasscutter jaunākos datus",
"grasscutter_stable_data_update": "Atjauniniet Grasscutter stabilos datus", "grasscutter_stable_data_update": "Atjauniniet Grasscutter stabilos datus",
@ -63,7 +65,8 @@
"select_file": "Izvēlēties failu vai mapu...", "select_file": "Izvēlēties failu vai mapu...",
"select_folder": "Izvēlēties mapu...", "select_folder": "Izvēlēties mapu...",
"download": "Lejupielādēt", "download": "Lejupielādēt",
"delete": "Dzēst" "delete": "Dzēst",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Nesen kommitus", "latest_commits": "Nesen kommitus",

View File

@ -34,11 +34,13 @@
"un_elevated": "Voer het spel uit zonder hoogtevrees (geen admin)", "un_elevated": "Voer het spel uit zonder hoogtevrees (geen admin)",
"redirect_more": "Richt ook andere MHY-spellen", "redirect_more": "Richt ook andere MHY-spellen",
"web_cache": "Verwijder de webCaches-map", "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": { "downloads": {
"grasscutter_fullbuild": "Grasscutter Alles-in-één Downloaden", "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_stable_data": "Download Stabiele Gegevens Van Grasscutter",
"grasscutter_latest_data": "Download De Nieuwste Gegevens Van Grasscutter", "grasscutter_latest_data": "Download De Nieuwste Gegevens Van Grasscutter",
"grasscutter_stable_data_update": "Stabiele gegevens Van Grasscutter bijwerken", "grasscutter_stable_data_update": "Stabiele gegevens Van Grasscutter bijwerken",
@ -66,7 +68,8 @@
"select_folder": "Select folder...", "select_folder": "Select folder...",
"download": "Download", "download": "Download",
"delete": "Verwijder", "delete": "Verwijder",
"install": "Install" "install": "Install",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Recente Opdrachten", "latest_commits": "Recente Opdrachten",

View File

@ -37,11 +37,13 @@
"check_aagl": "Więcej opcji znajdziesz w drugim launcherze", "check_aagl": "Więcej opcji znajdziesz w drugim launcherze",
"grasscutter_elevation": "Sposób uruchomienia GC na ograniczonym porcie", "grasscutter_elevation": "Sposób uruchomienia GC na ograniczonym porcie",
"web_cache": "Usuń folder webCaches", "web_cache": "Usuń folder webCaches",
"launch_args": "Launch Args" "launch_args": "Argumenty uruchamiania",
"offline_mode": "Tryb offline",
"fix_res": "Napraw limit czasu logowania"
}, },
"downloads": { "downloads": {
"grasscutter_fullbuild": "Pobierz Grasscutter (wszystko w jednym)", "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_stable_data": "Pobierz stabilne dane Grasscuttera",
"grasscutter_latest_data": "Pobierz najnowsze dane Grasscuttera", "grasscutter_latest_data": "Pobierz najnowsze dane Grasscuttera",
"grasscutter_stable_data_update": "Zaaktualizuj stabilne dane Grasscuttera", "grasscutter_stable_data_update": "Zaaktualizuj stabilne dane Grasscuttera",
@ -69,7 +71,8 @@
"select_folder": "Wybierz folder...", "select_folder": "Wybierz folder...",
"download": "Pobierz", "download": "Pobierz",
"delete": "Usuń", "delete": "Usuń",
"install": "Zainstaluj" "install": "Zainstaluj",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Ostatnie Commity", "latest_commits": "Ostatnie Commity",

View File

@ -35,11 +35,13 @@
"un_elevated": "Executar o jogo não-elevated (sem admin)", "un_elevated": "Executar o jogo não-elevated (sem admin)",
"redirect_more": "Também redirecionar outros jogos MHY", "redirect_more": "Também redirecionar outros jogos MHY",
"web_cache": "Excluir pasta webCaches", "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": { "downloads": {
"grasscutter_fullbuild": "Baixar o Grasscutter Tudo-em-Um", "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_stable_data": "Baixar os Dados do Grasscutter Estável",
"grasscutter_latest_data": "Baixar os Dados do Grasscutter Mais Recente", "grasscutter_latest_data": "Baixar os Dados do Grasscutter Mais Recente",
"grasscutter_stable_data_update": "Atualizar os Dados do Grasscutter Estável", "grasscutter_stable_data_update": "Atualizar os Dados do Grasscutter Estável",
@ -67,7 +69,8 @@
"select_folder": "Selecione a pasta...", "select_folder": "Selecione a pasta...",
"download": "Baixar", "download": "Baixar",
"delete": "Deletar", "delete": "Deletar",
"install": "Instalar" "install": "Instalar",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Commits Recentes", "latest_commits": "Commits Recentes",

View File

@ -34,11 +34,13 @@
"un_elevated": "Запустите игру в неэлегантном режиме (без администратора)", "un_elevated": "Запустите игру в неэлегантном режиме (без администратора)",
"redirect_more": "Также перенаправьте другие игры MHY", "redirect_more": "Также перенаправьте другие игры MHY",
"web_cache": "Удалить папку webCaches", "web_cache": "Удалить папку webCaches",
"launch_args": "Launch Args" "launch_args": "Параметры запуска",
"offline_mode": "Автономный режим",
"fix_res": "Исправить таймаут входа в систему"
}, },
"downloads": { "downloads": {
"grasscutter_fullbuild": "Скачать все в одном Grasscutter", "grasscutter_fullbuild": "Скачать все в одном Grasscutter",
"grasscutter_fullquest": "Скачать квесты все в одном", "grasscutter_fullquest": "Скачать 5.0 все в одном",
"grasscutter_stable_data": "Скачать стабильные данные Grasscutter", "grasscutter_stable_data": "Скачать стабильные данные Grasscutter",
"grasscutter_latest_data": "Скачать последние данные Grasscutter", "grasscutter_latest_data": "Скачать последние данные Grasscutter",
"grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter", "grasscutter_stable_data_update": "Обновить стабильные данные Grasscutter",
@ -66,7 +68,8 @@
"select_folder": "Выберите папку...", "select_folder": "Выберите папку...",
"download": "Скачать", "download": "Скачать",
"delete": "Удалить", "delete": "Удалить",
"install": "Установить" "install": "Установить",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Последние коммиты", "latest_commits": "Последние коммиты",

View File

@ -35,11 +35,13 @@
"un_elevated": "Chạy trò chơi không nâng cao (không có quản trị viên)", "un_elevated": "Chạy trò chơi không nâng cao (không có quản trị viên)",
"redirect_more": "Đồng thời chuyển hướng các trò chơi MHY khác", "redirect_more": "Đồng thời chuyển hướng các trò chơi MHY khác",
"web_cache": "Xóa thư mục webCaches", "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": { "downloads": {
"grasscutter_fullbuild": "Tải Grasscutter tất cả trong một", "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_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_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", "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...", "select_folder": "Chọn thư mục...",
"download": "Tải", "download": "Tải",
"delete": "Xóa bỏ", "delete": "Xóa bỏ",
"install": "Cài" "install": "Cài",
"fix": "Fix"
}, },
"news": { "news": {
"latest_commits": "Thay Đổi Gần Đây", "latest_commits": "Thay Đổi Gần Đây",

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -1,6 +1,6 @@
use file_diff::diff; use file_diff::diff;
use std::fs; use std::fs;
use std::io::{Read, Write}; use std::io::{Read, Seek, SeekFrom, Write};
use std::path::PathBuf; use std::path::PathBuf;
#[tauri::command] #[tauri::command]
@ -57,6 +57,11 @@ pub fn are_files_identical(path1: &str, path2: &str) -> bool {
diff(path1, path2) diff(path1, path2)
} }
#[tauri::command]
pub fn does_file_exist(path1: &str) -> bool {
fs::metadata(path1).is_ok()
}
#[tauri::command] #[tauri::command]
pub fn copy_file(path: String, new_path: String) -> bool { pub fn copy_file(path: String, new_path: String) -> bool {
let filename = &path.split('/').last().unwrap(); let filename = &path.split('/').last().unwrap();
@ -127,21 +132,46 @@ pub fn delete_file(path: String) -> bool {
#[tauri::command] #[tauri::command]
pub fn read_file(path: String) -> String { pub fn read_file(path: String) -> String {
let path_buf = PathBuf::from(&path); let path_buf = PathBuf::from(&path);
println!("Debug: Reading file of path {}", path.clone(),);
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
}
};
let mut contents = String::new(); 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 contents
} }

View File

@ -28,12 +28,7 @@ pub async fn get_languages() -> std::collections::HashMap<String, String> {
for entry in lang_files { for entry in lang_files {
let entry = entry.unwrap(); let entry = entry.unwrap();
let path = entry.path(); let path = entry.path();
let filename = path let filename = path.file_name().unwrap().to_str().unwrap();
.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 content = match std::fs::read_to_string(&path) { let content = match std::fs::read_to_string(&path) {
Ok(x) => x, Ok(x) => x,

View File

@ -108,14 +108,14 @@ async fn parse_args(inp: &Vec<String>) -> Result<Args, ArgsError> {
std::process::exit(0); 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")? { if args.value_of("launch-game")? {
let game_path = config.game_install_path; let game_path = config.game_install_path;
let game_args: String = args.value_of("game-args").unwrap_or_default(); let game_args: String = args.value_of("game-args").unwrap_or(String::new());
// Patch if needed
if args.value_of("patch")? {
patch::patch_game().await;
}
if game_path.is_some() { if game_path.is_some() {
if args.value_of("non-elevated-game")? { if args.value_of("non-elevated-game")? {
@ -154,8 +154,8 @@ async fn parse_args(inp: &Vec<String>) -> Result<Args, ArgsError> {
pathbuf.push("cultivation"); pathbuf.push("cultivation");
pathbuf.push("ca"); pathbuf.push("ca");
if args.value_of("other_redirects")? { if args.value_of("other-redirects")? {
proxy::set_redirect_more(); // proxy::set_redirect_more(); // Unused
} }
connect(8035, pathbuf.to_str().unwrap().to_string()).await; connect(8035, pathbuf.to_str().unwrap().to_string()).await;
@ -218,7 +218,6 @@ fn main() -> Result<(), ArgsError> {
system_helpers::open_in_browser, system_helpers::open_in_browser,
system_helpers::install_location, system_helpers::install_location,
system_helpers::is_elevated, system_helpers::is_elevated,
system_helpers::set_migoto_target,
system_helpers::set_migoto_delay, system_helpers::set_migoto_delay,
system_helpers::wipe_registry, system_helpers::wipe_registry,
system_helpers::get_platform, system_helpers::get_platform,
@ -229,7 +228,6 @@ fn main() -> Result<(), ArgsError> {
patch::unpatch_game, patch::unpatch_game,
proxy::set_proxy_addr, proxy::set_proxy_addr,
proxy::generate_ca_files, proxy::generate_ca_files,
proxy::set_redirect_more,
release::get_latest_release, release::get_latest_release,
unzip::unzip, unzip::unzip,
file_helpers::rename, file_helpers::rename,
@ -243,6 +241,7 @@ fn main() -> Result<(), ArgsError> {
file_helpers::are_files_identical, file_helpers::are_files_identical,
file_helpers::read_file, file_helpers::read_file,
file_helpers::write_file, file_helpers::write_file,
file_helpers::does_file_exist,
downloader::download_file, downloader::download_file,
downloader::stop_download, downloader::stop_download,
lang::get_lang, lang::get_lang,
@ -297,7 +296,7 @@ fn enable_process_watcher(window: tauri::Window, process: String) {
thread::spawn(move || { thread::spawn(move || {
// Initial sleep for 8 seconds, since running 20 different injectors or whatever can take a while // 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(); 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) { fn enable_process_watcher(window: tauri::Window, process: String) {
drop(process); drop(process);
thread::spawn(move || { 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 game_thread = loop {
let mut lock = AAGL_THREAD.lock().unwrap(); let mut lock = AAGL_THREAD.lock().unwrap();
if lock.is_some() { if lock.is_some() {

View File

@ -50,8 +50,90 @@ struct WhatToUnpach {
#[cfg(windows)] #[cfg(windows)]
#[tauri::command] #[tauri::command]
pub async fn patch_game() -> bool { pub async fn patch_game(newer_game: bool, version: String) -> bool {
let patch_path = PathBuf::from(system_helpers::install_location()).join("patch/version.dll"); 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 // Are we already patched with mhypbase? If so, that's fine, just continue as normal
let game_is_patched = file_helpers::are_files_identical( let game_is_patched = file_helpers::are_files_identical(
@ -70,6 +152,21 @@ pub async fn patch_game() -> bool {
return true; 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"),
);
if replaced50 {
return true;
} else {
return false;
}
}
// Copy the patch to game files // Copy the patch to game files
let replaced = file_helpers::copy_file_with_new_name( let replaced = file_helpers::copy_file_with_new_name(
patch_path.clone().to_str().unwrap().to_string(), patch_path.clone().to_str().unwrap().to_string(),
@ -86,7 +183,7 @@ pub async fn patch_game() -> bool {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
#[tauri::command] #[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; let mut patch_state_mutex = PATCH_STATE.lock().await;
if patch_state_mutex.is_some() { if patch_state_mutex.is_some() {
println!("Game already patched!"); println!("Game already patched!");
@ -170,6 +267,36 @@ pub async fn unpatch_game() -> bool {
.to_string(), .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 deleted
} }

View File

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

View File

@ -16,7 +16,6 @@ pub async fn get_latest_release() -> Release {
.unwrap(); .unwrap();
let text = response.text().await.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); //println!("Response: {}", text);
// Parse "tag_name" from JSON // Parse "tag_name" from JSON

View File

@ -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] #[tauri::command]
pub fn set_migoto_delay(migoto_path: String) -> bool { pub fn set_migoto_delay(migoto_path: String) -> bool {
let mut migoto_pathbuf = PathBuf::from(migoto_path); let mut migoto_pathbuf = PathBuf::from(migoto_path);
@ -448,9 +410,18 @@ pub fn set_migoto_delay(migoto_path: String) -> bool {
// Set options // Set options
conf.with_section(Some("Loader")).set("delay", "20"); conf.with_section(Some("Loader")).set("delay", "20");
conf
.with_section(Some("Include"))
.set("include", "ShaderFixes\\help.ini");
// Write file // 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(_) => { Ok(_) => {
println!("Wrote delay!"); println!("Wrote delay!");
true true
@ -483,6 +454,31 @@ pub fn wipe_registry(exec_name: String) {
Ok(_) => (), Ok(_) => (),
Err(e) => println!("Error wiping registry: {}", e), 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(format!("Software\\Cognosphere\\Star Rail"), 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)] #[cfg(windows)]

View File

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

View File

@ -1,5 +1,5 @@
{ {
"$schema": "../node_modules/@tauri-apps/cli/schema.json", "$schema": "..\\node_modules/@tauri-apps/cli\\schema.json",
"build": { "build": {
"beforeDevCommand": "yarn start", "beforeDevCommand": "yarn start",
"devPath": "http://localhost:3000", "devPath": "http://localhost:3000",
@ -7,7 +7,7 @@
}, },
"package": { "package": {
"productName": "Cultivation", "productName": "Cultivation",
"version": "1.2.0" "version": "1.5.1"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

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

View File

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

View File

@ -76,8 +76,10 @@ export class Main extends React.Component<IProps, IState> {
setConfigOption('grasscutter_path', payload) setConfigOption('grasscutter_path', payload)
}) })
listen('migoto_extracted', ({ payload }: { payload: string }) => { listen('migoto_extracted', async ({ payload }: { payload: string }) => {
setConfigOption('migoto_path', payload) await setConfigOption('migoto_path', payload)
this.setState({ migotoSet: true })
window.location.reload()
}) })
// Emitted for rsa replacing-purposes // 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 // Emitted for automatic processes
listen('grasscutter_closed', async () => { listen('grasscutter_closed', async () => {
const autoService = await getConfigOption('auto_mongodb') 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 // Update launch args to allow launching when updating from old versions
await setConfigOption('launch_args', await getConfigOption('launch_args')) await setConfigOption('launch_args', await getConfigOption('launch_args'))
// Get latest version and compare to this version if (!(await getConfigOption('offline_mode'))) {
const latestVersion: { // Get latest version and compare to this version
tag_name: string const latestVersion: {
link: string tag_name: string
} = await invoke('get_latest_release') link: string
const tagName = latestVersion?.tag_name.replace(/[^\d.]/g, '') } = await invoke('get_latest_release')
const tagName = latestVersion?.tag_name.replace(/[^\d.]/g, '')
// Check if tagName is different than current version // Check if tagName is different than current version
if (tagName && tagName !== (await getVersion())) { if (tagName && tagName !== (await getVersion())) {
// Display notification of new release // 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(() => {
this.setState({ 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 // 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"> <div className="BottomSection" id="bottomSectionContainer">
<ServerLaunchSection openExtras={this.openExtrasMenu} /> <ServerLaunchSection openExtras={this.openExtrasMenu} downloadHandler={this.props.downloadHandler} />
<div <div
id="DownloadProgress" id="DownloadProgress"

View File

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

View File

@ -17,9 +17,11 @@ import { getGameExecutable, getGameVersion, getGrasscutterJar } from '../../util
import { patchGame, unpatchGame } from '../../utils/rsa' import { patchGame, unpatchGame } from '../../utils/rsa'
import { listen } from '@tauri-apps/api/event' import { listen } from '@tauri-apps/api/event'
import { confirm } from '@tauri-apps/api/dialog' import { confirm } from '@tauri-apps/api/dialog'
import DownloadHandler from '../../utils/download'
interface IProps { interface IProps {
openExtras: (playGame: () => void) => void openExtras: (playGame: () => void) => void
downloadHandler: DownloadHandler
} }
interface IState { interface IState {
@ -72,10 +74,15 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
this.setPort = this.setPort.bind(this) this.setPort = this.setPort.bind(this)
this.toggleHttps = this.toggleHttps.bind(this) this.toggleHttps = this.toggleHttps.bind(this)
this.launchServer = this.launchServer.bind(this) this.launchServer = this.launchServer.bind(this)
this.setButtonLabel = this.setButtonLabel.bind(this)
listen('start_grasscutter', async () => { listen('start_grasscutter', async () => {
this.launchServer() this.launchServer()
}) })
listen('set_game', async () => {
this.setButtonLabel()
})
} }
async componentDidMount() { async componentDidMount() {
@ -96,6 +103,8 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
migotoSet: config.migoto_path !== '', migotoSet: config.migoto_path !== '',
unElevated: config.un_elevated || false, unElevated: config.un_elevated || false,
}) })
this.setButtonLabel()
} }
async toggleGrasscutter() { async toggleGrasscutter() {
@ -140,8 +149,9 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
// Connect to proxy // Connect to proxy
if (config.toggle_grasscutter) { if (config.toggle_grasscutter) {
const game_exe = await getGameExecutable() 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) { if (config.patch_rsa && patchable) {
const gameVersion = await getGameVersion() const gameVersion = await getGameVersion()
@ -164,10 +174,58 @@ export default class ServerLaunchSection extends React.Component<IProps, IState>
return 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) { 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 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, addr: (this.state.httpsEnabled ? 'https' : 'http') + '://' + this.state.ip + ':' + this.state.port,
}) })
// Connect to proxy // 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 // 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) 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() { render() {
return ( return (
<div id="playButton"> <div id="playButton">

View File

@ -8,19 +8,23 @@ import { dataDir } from '@tauri-apps/api/path'
import './Downloads.css' import './Downloads.css'
import Divider from './Divider' import Divider from './Divider'
import { getConfigOption } from '../../../utils/configuration' import { getConfigOption, setConfigOption } from '../../../utils/configuration'
import { invoke } from '@tauri-apps/api' import { invoke } from '@tauri-apps/api'
import { listen } from '@tauri-apps/api/event' import { listen } from '@tauri-apps/api/event'
import HelpButton from '../common/HelpButton' 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_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 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 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 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 RESOURCES_DOWNLOAD = 'https://gitlab.com/api/v4/projects/35984297/repository/archive.zip' // Use Yuuki res as grasscutter crepe res are broken
const MIGOTO_DOWNLOAD = 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 { interface IProps {
closeFn: () => void closeFn: () => void
@ -56,8 +60,10 @@ export default class Downloads extends React.Component<IProps, IState> {
this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this) this.getGrasscutterFolder = this.getGrasscutterFolder.bind(this)
this.downloadGrasscutterFullBuild = this.downloadGrasscutterFullBuild.bind(this) this.downloadGrasscutterFullBuild = this.downloadGrasscutterFullBuild.bind(this)
this.downloadGrasscutterFullQuest = this.downloadGrasscutterFullQuest.bind(this) this.downloadGrasscutterFullQuest = this.downloadGrasscutterFullQuest.bind(this)
this.downloadGrasscutterFull50 = this.downloadGrasscutterFull50.bind(this)
this.downloadGrasscutterStableRepo = this.downloadGrasscutterStableRepo.bind(this) this.downloadGrasscutterStableRepo = this.downloadGrasscutterStableRepo.bind(this)
this.downloadGrasscutterDevRepo = this.downloadGrasscutterDevRepo.bind(this) this.downloadGrasscutterDevRepo = this.downloadGrasscutterDevRepo.bind(this)
this.downloadGrasscutterUnstable = this.downloadGrasscutterUnstable.bind(this)
this.downloadGrasscutterLatest = this.downloadGrasscutterLatest.bind(this) this.downloadGrasscutterLatest = this.downloadGrasscutterLatest.bind(this)
this.downloadResources = this.downloadResources.bind(this) this.downloadResources = this.downloadResources.bind(this)
this.downloadMigoto = this.downloadMigoto.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) 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 === '') { if (!gc_path || gc_path === '') {
this.setState({ this.setState({
grasscutter_set: false, grasscutter_set: false,
@ -85,15 +104,15 @@ export default class Downloads extends React.Component<IProps, IState> {
return return
} }
const path = gc_path.substring(0, gc_path.lastIndexOf('/')) const path = gc_path.substring(0, gc_path.lastIndexOf('\\'))
if (gc_path) { if (gc_path) {
const resources_exist: boolean = const resources_exist: boolean =
((await invoke('dir_exists', { ((await invoke('dir_exists', {
path: path + '/resources', path: path + '\\resources',
})) as boolean) && })) as boolean) &&
(!(await invoke('dir_is_empty', { (!(await invoke('dir_is_empty', {
path: path + '/resources', path: path + '\\resources',
})) as boolean) })) as boolean)
this.setState({ this.setState({
@ -110,7 +129,7 @@ export default class Downloads extends React.Component<IProps, IState> {
// Set to default if not set // Set to default if not set
if (!path || path === '') { if (!path || path === '') {
const appdata = await dataDir() const appdata = await dataDir()
folderPath = appdata + 'cultivation/grasscutter' folderPath = appdata + 'cultivation\\grasscutter'
// Early return since its formatted properly // Early return since its formatted properly
return folderPath return folderPath
@ -133,8 +152,8 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterFullBuild() { async downloadGrasscutterFullBuild() {
const folder = await this.getGrasscutterFolder() const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '/GrasscutterCulti.zip', async () => { this.props.downloadManager.addDownload(FULL_BUILD_DOWNLOAD, folder + '\\GrasscutterCulti.zip', async () => {
await unzip(folder + '/GrasscutterCulti.zip', folder + '/', true) await unzip(folder + '\\GrasscutterCulti.zip', folder + '\\', true)
this.toggleButtons() this.toggleButtons()
}) })
@ -143,8 +162,18 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterFullQuest() { async downloadGrasscutterFullQuest() {
const folder = await this.getGrasscutterFolder() const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '/GrasscutterQuests.zip', async () => { this.props.downloadManager.addDownload(FULL_QUEST_DOWNLOAD, folder + '\\GrasscutterQuests.zip', async () => {
await unzip(folder + '/GrasscutterQuests.zip', folder + '/', true) 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() this.toggleButtons()
}) })
@ -153,8 +182,8 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterStableRepo() { async downloadGrasscutterStableRepo() {
const folder = await this.getGrasscutterFolder() const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '/grasscutter_repo.zip', async () => { this.props.downloadManager.addDownload(STABLE_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true) await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
this.toggleButtons() this.toggleButtons()
}) })
@ -163,18 +192,28 @@ export default class Downloads extends React.Component<IProps, IState> {
async downloadGrasscutterDevRepo() { async downloadGrasscutterDevRepo() {
const folder = await this.getGrasscutterFolder() const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '/grasscutter_repo.zip', async () => { this.props.downloadManager.addDownload(DEV_REPO_DOWNLOAD, folder + '\\grasscutter_repo.zip', async () => {
await unzip(folder + '/grasscutter_repo.zip', folder + '/', true) await unzip(folder + '\\grasscutter_repo.zip', folder + '\\', true)
this.toggleButtons() this.toggleButtons()
}) })
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() { async downloadGrasscutterLatest() {
const folder = await this.getGrasscutterFolder() const folder = await this.getGrasscutterFolder()
this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '/grasscutter.zip', async () => { this.props.downloadManager.addDownload(DEV_DOWNLOAD, folder + '\\grasscutter.zip', async () => {
await unzip(folder + '/grasscutter.zip', folder + '/', true) await unzip(folder + '\\grasscutter.zip', folder + '\\', true)
this.toggleButtons() this.toggleButtons()
}) })
@ -185,27 +224,38 @@ export default class Downloads extends React.Component<IProps, IState> {
} }
async downloadResources() { 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 // Tell the user this takes some time
alert( 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.' '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() 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 // Delete the existing folder if it exists
if ( if (
await invoke('dir_exists', { await invoke('dir_exists', {
path: folder + '/resources', path: folder + '\\resources',
}) })
) { ) {
await invoke('dir_delete', { 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 // Rename folder to resources
invoke('rename', { invoke('rename', {
path: folder + '/Resources', path: folder + '\\Resources',
newName: 'resources', newName: 'resources',
}) })
@ -216,13 +266,27 @@ export default class Downloads extends React.Component<IProps, IState> {
} }
async downloadMigoto() { async downloadMigoto() {
const folder = (await this.getCultivationFolder()) + '/3dmigoto' if (!this.state.swag) {
await invoke('dir_create', { await setConfigOption('swag_mode', true)
path: folder, 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 () => { this.toggleButtons()
await unzip(folder + '/GIMI-3dmigoto.zip', folder + '/', true) }
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() this.toggleButtons()
}) })
@ -256,7 +320,6 @@ export default class Downloads extends React.Component<IProps, IState> {
<Tr text={'downloads.grasscutter_fullbuild'} /> <Tr text={'downloads.grasscutter_fullbuild'} />
<HelpButton contents="help.gc_fullbuild" /> <HelpButton contents="help.gc_fullbuild" />
</div> </div>
<div className="DownloadValue" id="downloadMenuButtonGCFullBuild"> <div className="DownloadValue" id="downloadMenuButtonGCFullBuild">
<BigButton <BigButton
disabled={this.state.grasscutter_downloading} disabled={this.state.grasscutter_downloading}
@ -267,7 +330,6 @@ export default class Downloads extends React.Component<IProps, IState> {
</BigButton> </BigButton>
</div> </div>
</div> </div>
<div className="DownloadMenuSection" id="downloadMenuContainerGCFullQuest"> <div className="DownloadMenuSection" id="downloadMenuContainerGCFullQuest">
<div className="DownloadLabel" id="downloadMenuLabelGCFullQuest"> <div className="DownloadLabel" id="downloadMenuLabelGCFullQuest">
<Tr text={'downloads.grasscutter_fullquest'} /> <Tr text={'downloads.grasscutter_fullquest'} />
@ -276,18 +338,38 @@ export default class Downloads extends React.Component<IProps, IState> {
<div className="DownloadValue" id="downloadMenuButtonGCFullQuest"> <div className="DownloadValue" id="downloadMenuButtonGCFullQuest">
<BigButton <BigButton
disabled={this.state.grasscutter_downloading} disabled={this.state.grasscutter_downloading}
onClick={this.downloadGrasscutterFullQuest} onClick={this.downloadGrasscutterFull50}
id="grasscutterFullBuildBtn" id="grasscutterFullBuildBtn"
> >
<Tr text="components.download" /> <Tr text="components.download" />
</BigButton> </BigButton>
</div> </div>
</div> </div>
<Divider /> <Divider />
<div className="HeaderText" id="downloadMenuIndividualHeader"> <div className="HeaderText" id="downloadMenuIndividualHeader">
<Tr text="downloads.individual_header" /> <Tr text="downloads.individual_header" />
</div> </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="DownloadMenuSection" id="downloadMenuContainerGCDev">
<div className="DownloadLabel" id="downloadMenuLabelGCDev"> <div className="DownloadLabel" id="downloadMenuLabelGCDev">
<Tr <Tr
@ -306,7 +388,28 @@ export default class Downloads extends React.Component<IProps, IState> {
</div> </div>
</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"> <div className="DownloadLabel" id="downloadMenuLabelGCDevData">
<Tr <Tr
text={ text={
@ -326,7 +429,7 @@ export default class Downloads extends React.Component<IProps, IState> {
<Tr text="components.download" /> <Tr text="components.download" />
</BigButton> </BigButton>
</div> </div>
</div> </div> */}
<div className="DownloadMenuSection" id="downloadMenuContainerResources"> <div className="DownloadMenuSection" id="downloadMenuContainerResources">
<div className="DownloadLabel" id="downloadMenuLabelResources"> <div className="DownloadLabel" id="downloadMenuLabelResources">
@ -344,25 +447,23 @@ export default class Downloads extends React.Component<IProps, IState> {
</div> </div>
</div> </div>
{this.state.swag && ( <>
<> <Divider />
<Divider /> <div className="HeaderText" id="downloadMenuModsHeader">
<div className="HeaderText" id="downloadMenuModsHeader"> <Tr text="downloads.mods_header" />
<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>
<div className="DownloadMenuSection" id="downloadMenuContainerMigoto"> <div className="DownloadValue" id="downloadMenuButtonMigoto">
<div className="DownloadLabel" id="downloadMenuLabelMigoto"> <BigButton disabled={this.state.migoto_downloading} onClick={this.downloadMigoto} id="migotoBtn">
<Tr text={'downloads.migoto'} /> <Tr text="components.download" />
<HelpButton contents="help.migoto" /> </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> </div>
</> </div>
)} </>
</Menu> </Menu>
) )
} }

View File

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

View File

@ -16,8 +16,11 @@ import DownloadHandler from '../../../utils/download'
import * as meta from '../../../utils/rsa' import * as meta from '../../../utils/rsa'
import HelpButton from '../common/HelpButton' import HelpButton from '../common/HelpButton'
import SmallButton from '../common/SmallButton' 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 TextInput from '../common/TextInput'
import { unzip } from '../../../utils/zipUtils'
import { getGameExecutable } from '../../../utils/game'
import { emit } from '@tauri-apps/api/event'
export enum GrasscutterElevation { export enum GrasscutterElevation {
None = 'None', None = 'None',
@ -52,6 +55,8 @@ interface IState {
un_elevated: boolean un_elevated: boolean
redirect_more: boolean redirect_more: boolean
launch_args: string launch_args: string
offline_mode: boolean
newer_game: boolean
// Linux stuff // Linux stuff
grasscutter_elevation: string grasscutter_elevation: string
@ -88,6 +93,8 @@ export default class Options extends React.Component<IProps, IState> {
un_elevated: false, un_elevated: false,
redirect_more: false, redirect_more: false,
launch_args: '', launch_args: '',
offline_mode: false,
newer_game: false,
// Linux stuff // Linux stuff
grasscutter_elevation: GrasscutterElevation.None, grasscutter_elevation: GrasscutterElevation.None,
@ -118,13 +125,10 @@ export default class Options extends React.Component<IProps, IState> {
const languages = await getLanguages() const languages = await getLanguages()
const platform: string = await invoke('get_platform') const platform: string = await invoke('get_platform')
let encEnabled // Remove jar from path
if (config.grasscutter_path) { const path = config.grasscutter_path.replace(/\\/g, '/')
// Remove jar from path const folderPath = path.substring(0, path.lastIndexOf('/'))
const path = config.grasscutter_path.replace(/\\/g, '/') const encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
const folderPath = path.substring(0, path.lastIndexOf('/'))
encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
}
console.log(platform) console.log(platform)
@ -150,6 +154,8 @@ export default class Options extends React.Component<IProps, IState> {
un_elevated: config.un_elevated || false, un_elevated: config.un_elevated || false,
redirect_more: config.redirect_more || false, redirect_more: config.redirect_more || false,
launch_args: config.launch_args, launch_args: config.launch_args,
offline_mode: config.offline_mode || false,
newer_game: config.newer_game || false,
// Linux stuff // Linux stuff
grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None, grasscutter_elevation: config.grasscutter_elevation || GrasscutterElevation.None,
@ -163,22 +169,28 @@ export default class Options extends React.Component<IProps, IState> {
this.forceUpdate() this.forceUpdate()
} }
setGameExecutable(value: string) { async setGameExecutable(value: string) {
setConfigOption('game_install_path', value) await setConfigOption('game_install_path', value)
// I hope this stops people setting launcher.exe because oml it's annoying // 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('/') const pathArr = value.replace(/\\/g, '/').split('/')
pathArr.pop() pathArr.pop()
const path = pathArr.join('/') + '/Genshin Impact Game/' const path = pathArr.join('/') + '/Genshin Impact Game/'
alert( if (value.endsWith('.lnk')) {
`You have set your game execuatable to "launcher.exe". You should not do this. Your game executable is located in:\n\n${path}` 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 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) { if (!this.state.redirect_more) {
this.toggleOption('redirect_more') this.toggleOption('redirect_more')
} }
@ -187,26 +199,24 @@ export default class Options extends React.Component<IProps, IState> {
this.setState({ this.setState({
game_install_path: value, game_install_path: value,
}) })
emit('set_game', { game_path: value })
} }
async setGrasscutterJar(value: string) { async setGrasscutterJar(value: string) {
setConfigOption('grasscutter_path', value) setConfigOption('grasscutter_path', value)
this.setState({
grasscutter_path: value,
})
const config = await getConfig() const config = await getConfig()
const path = config.grasscutter_path.replace(/\\/g, '/') const path = config.grasscutter_path.replace(/\\/g, '/')
const folderPath = path.substring(0, path.lastIndexOf('/')) const folderPath = path.substring(0, path.lastIndexOf('/'))
const encEnabled = await server.encryptionEnabled(folderPath + '/config.json') const encEnabled = await server.encryptionEnabled(folderPath + '/config.json')
// Update encryption button when setting new jar
this.setState({ this.setState({
grasscutter_path: value,
encryption: encEnabled, encryption: encEnabled,
}) })
window.location.reload() // Encryption refuses to re-render w/o reload unless updated twice
this.forceUpdateEncyption()
} }
setJavaPath(value: string) { setJavaPath(value: string) {
@ -267,7 +277,7 @@ export default class Options extends React.Component<IProps, IState> {
if (!isUrl) { if (!isUrl) {
const filename = value.replace(/\\/g, '/').split('/').pop() 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}`) 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() { async toggleEncryption() {
const config = await getConfig() const config = await getConfig()
@ -343,6 +363,15 @@ export default class Options extends React.Component<IProps, IState> {
} }
async addMigotoDelay() { 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', { invoke('set_migoto_delay', {
migotoPath: this.state.migoto_path, migotoPath: this.state.migoto_path,
}) })
@ -355,6 +384,15 @@ export default class Options extends React.Component<IProps, IState> {
} }
async deleteWebCache() { 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!') alert('Cultivation may freeze for a moment while this occurs!')
// Get webCaches folder path // Get webCaches folder path
@ -364,8 +402,70 @@ export default class Options extends React.Component<IProps, IState> {
const path2 = pathArr.join('/') + '/Yuanshen_Data/webCaches' const path2 = pathArr.join('/') + '/Yuanshen_Data/webCaches'
// Delete the folder // Delete the folder
await invoke('dir_delete', { path: path }) if (await invoke('dir_exists', { path: path })) {
await invoke('dir_delete', { path: path2 }) 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) { async toggleOption(opt: keyof Configuration) {
@ -542,7 +642,7 @@ export default class Options extends React.Component<IProps, IState> {
<Tr text="swag.migoto" /> <Tr text="swag.migoto" />
</div> </div>
<div className="OptionValue" id="menuOptionsDirMigoto"> <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']} /> <DirInput onChange={this.setMigoto} value={this.state?.migoto_path} extensions={['exe']} />
</div> </div>
</div> </div>
@ -599,6 +699,31 @@ export default class Options extends React.Component<IProps, IState> {
</div> </div>
</div> </div>
) : null} ) : 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 /> <Divider />
@ -709,6 +834,15 @@ export default class Options extends React.Component<IProps, IState> {
value={this.state.launch_args} value={this.state.launch_args}
/> />
</div> </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> </Menu>
) )
} }

View File

@ -2,6 +2,7 @@
import { invoke } from '@tauri-apps/api/tauri' import { invoke } from '@tauri-apps/api/tauri'
import React from 'react' import React from 'react'
import Tr from '../../../utils/language' import Tr from '../../../utils/language'
import { getConfig, getConfigOption } from '../../../utils/configuration'
import './NewsSection.css' import './NewsSection.css'
@ -10,6 +11,7 @@ interface IProps {
} }
interface IState { interface IState {
offline: boolean
selected: string selected: string
news?: JSX.Element news?: JSX.Element
commitList?: JSX.Element[] commitList?: JSX.Element[]
@ -40,6 +42,7 @@ export default class NewsSection extends React.Component<IProps, IState> {
super(props) super(props)
this.state = { this.state = {
offline: false,
selected: props.selected || 'commits', selected: props.selected || 'commits',
} }
@ -47,7 +50,16 @@ export default class NewsSection extends React.Component<IProps, IState> {
this.showNews = this.showNews.bind(this) 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 // Call showNews off the bat
this.showNews() this.showNews()
this.setSelected('commits') this.setSelected('commits')
@ -110,6 +122,11 @@ export default class NewsSection extends React.Component<IProps, IState> {
} }
async showNews() { async showNews() {
const offline_mode = await getConfigOption('offline_mode')
if (offline_mode) {
return
}
let news: JSX.Element | JSX.Element[] = <tr></tr> let news: JSX.Element | JSX.Element[] = <tr></tr>
switch (this.state.selected) { switch (this.state.selected) {
@ -124,7 +141,10 @@ export default class NewsSection extends React.Component<IProps, IState> {
case 'latest_version': case 'latest_version':
news = ( news = (
<tr> <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> </tr>
) )
break break
@ -144,6 +164,10 @@ export default class NewsSection extends React.Component<IProps, IState> {
} }
render() { render() {
if (this.state.offline) {
return null
}
return ( return (
<div className="NewsSection" id="newsContainer"> <div className="NewsSection" id="newsContainer">
<div className="NewsTabs" id="newsTabsContainer"> <div className="NewsTabs" id="newsTabsContainer">

View File

@ -29,6 +29,8 @@ let defaultConfig: Configuration
un_elevated: false, un_elevated: false,
redirect_more: false, redirect_more: false,
launch_args: '', launch_args: '',
offline_mode: false,
newer_game: false,
// Linux stuff // Linux stuff
grasscutter_elevation: 'None', grasscutter_elevation: 'None',
@ -64,6 +66,8 @@ export interface Configuration {
un_elevated: boolean un_elevated: boolean
redirect_more: boolean redirect_more: boolean
launch_args: string launch_args: string
offline_mode: boolean
newer_game: boolean
// Linux stuff // Linux stuff
grasscutter_elevation: string grasscutter_elevation: string
@ -128,7 +132,7 @@ async function readConfigFile() {
await fs.createDir(local + 'cultivation').catch((e) => console.log(e)) 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 // Create grasscutter dir for potential installation
if (!innerDirs.find((fileOrDir) => fileOrDir?.name === 'grasscutter')) { if (!innerDirs.find((fileOrDir) => fileOrDir?.name === 'grasscutter')) {

View File

@ -73,6 +73,11 @@ export default class DownloadHandler {
const index = this.downloads.findIndex((download) => download.path === errorData.path) const index = this.downloads.findIndex((download) => download.path === errorData.path)
this.downloads[index].status = 'error' this.downloads[index].status = 'error'
this.downloads[index].error = errorData.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 // Extraction events
@ -92,6 +97,9 @@ export default class DownloadHandler {
// Find the download that is not extracting and set it's status as such // Find the download that is not extracting and set it's status as such
const index = this.downloads.findIndex((download) => download.path === obj.file) const index = this.downloads.findIndex((download) => download.path === obj.file)
this.downloads[index].status = 'finished' this.downloads[index].status = 'finished'
// Remove completed extraction from list
this.downloads.splice(index, 1)
}) })
} }

View File

@ -45,7 +45,7 @@ export async function getGameDataFolder() {
return null return null
} }
return (await getGameFolder()) + '/' + gameExec.replace('.exe', '_Data') return (await getGameFolder()) + '\\' + gameExec.replace('.exe', '_Data')
} }
export async function getGameVersion() { export async function getGameVersion() {
@ -55,9 +55,33 @@ export async function getGameVersion() {
return null 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( const settings = JSON.parse(
await invoke('read_file', { await invoke('read_file', {
path: GameData + '/StreamingAssets/asb_settings.json', path: GameData + '\\StreamingAssets\\asb_settings.json',
}) })
) )

View File

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

View File

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

View File

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

View File

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