diff --git a/data/translations/de/LC_MESSAGES/gopher64.po b/data/translations/de/LC_MESSAGES/gopher64.po index 1a56eedd..18c0e7f7 100644 --- a/data/translations/de/LC_MESSAGES/gopher64.po +++ b/data/translations/de/LC_MESSAGES/gopher64.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: gopher64\n" -"POT-Creation-Date: 2026-05-25 08:28+0000\n" +"POT-Creation-Date: 2026-05-30 16:03+0000\n" "PO-Revision-Date: 2026-05-11 12:00+0000\n" "Last-Translator: \n" "Language-Team: German\n" @@ -47,37 +47,37 @@ msgctxt "About" msgid "Donate (via GitHub)" msgstr "Spenden (über GitHub)" -#: src/ui/gui/appwindow.slint:151 +#: src/ui/gui/appwindow.slint:152 msgctxt "Menu" msgid "Local Game" msgstr "Lokales Spiel" -#: src/ui/gui/appwindow.slint:155 +#: src/ui/gui/appwindow.slint:156 msgctxt "Menu" msgid "Controller Config" msgstr "Controller-Konfiguration" -#: src/ui/gui/appwindow.slint:159 +#: src/ui/gui/appwindow.slint:160 msgctxt "Menu" msgid "Netplay" msgstr "Netplay" -#: src/ui/gui/appwindow.slint:163 +#: src/ui/gui/appwindow.slint:164 msgctxt "Menu" msgid "Cheats" msgstr "Cheats" -#: src/ui/gui/appwindow.slint:167 +#: src/ui/gui/appwindow.slint:168 msgctxt "Menu" msgid "RetroAchievements" msgstr "RetroAchievements" -#: src/ui/gui/appwindow.slint:171 +#: src/ui/gui/appwindow.slint:172 msgctxt "Menu" msgid "Settings" msgstr "Einstellungen" -#: src/ui/gui/appwindow.slint:175 +#: src/ui/gui/appwindow.slint:176 msgctxt "Menu" msgid "About" msgstr "Über" @@ -97,7 +97,7 @@ msgctxt "Cheats" msgid "Clear Cheats For All ROMs" msgstr "Cheats für alle ROMs löschen" -#: src/ui/gui/cheat_page.slint:88 +#: src/ui/gui/cheat_page.slint:83 msgctxt "Cheats" msgid "No Cheats Available" msgstr "Keine Cheats verfügbar" @@ -485,62 +485,67 @@ msgctxt "RetroAchievements" msgid "View Supported Games" msgstr "Unterstützte Spiele anzeigen" -#: src/ui/gui/settings_page.slint:23 +#: src/ui/gui/settings_page.slint:24 msgctxt "Settings" msgid "Settings" msgstr "Einstellungen" -#: src/ui/gui/settings_page.slint:34 +#: src/ui/gui/settings_page.slint:35 msgctxt "Settings" msgid "Resolution:" msgstr "Auflösung:" -#: src/ui/gui/settings_page.slint:48 +#: src/ui/gui/settings_page.slint:49 msgctxt "Settings" msgid "SSAA downscaling" msgstr "SSAA-Herunterskalierung" -#: src/ui/gui/settings_page.slint:56 +#: src/ui/gui/settings_page.slint:57 msgctxt "Settings" msgid "Integer Scaling" msgstr "Pixelgenaue Skalierung" -#: src/ui/gui/settings_page.slint:64 +#: src/ui/gui/settings_page.slint:65 msgctxt "Settings" msgid "Fullscreen" msgstr "Vollbild" -#: src/ui/gui/settings_page.slint:72 +#: src/ui/gui/settings_page.slint:73 msgctxt "Settings" msgid "Widescreen (stretch)" msgstr "16:9 (gestreckt)" -#: src/ui/gui/settings_page.slint:80 +#: src/ui/gui/settings_page.slint:81 msgctxt "Settings" msgid "VSync" msgstr "VSync" -#: src/ui/gui/settings_page.slint:88 +#: src/ui/gui/settings_page.slint:89 msgctxt "Settings" msgid "Apply CRT shader" msgstr "CRT-Shader anwenden" -#: src/ui/gui/settings_page.slint:100 +#: src/ui/gui/settings_page.slint:101 msgctxt "Settings" msgid "Overclock N64 CPU (may cause bugs)" msgstr "N64-CPU übertakten (kann Fehler verursachen)" -#: src/ui/gui/settings_page.slint:108 +#: src/ui/gui/settings_page.slint:109 msgctxt "Settings" msgid "Disable Expansion Pak" msgstr "Expansion Pak deaktivieren" -#: src/ui/gui/settings_page.slint:116 +#: src/ui/gui/settings_page.slint:117 +msgctxt "Settings" +msgid "Enable Rewind" +msgstr "Rewind aktivieren" + +#: src/ui/gui/settings_page.slint:125 msgctxt "Settings" msgid "Emulate UNFLoader" msgstr "UNFLoader emulieren" -#: src/ui/gui/settings_page.slint:124 +#: src/ui/gui/settings_page.slint:133 msgctxt "Settings" msgid "Emulate VRU" msgstr "VRU emulieren" diff --git a/data/translations/es/LC_MESSAGES/gopher64.po b/data/translations/es/LC_MESSAGES/gopher64.po index 30cc1ab2..c975dbd2 100644 --- a/data/translations/es/LC_MESSAGES/gopher64.po +++ b/data/translations/es/LC_MESSAGES/gopher64.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: gopher64\n" -"POT-Creation-Date: 2026-05-25 08:28+0000\n" +"POT-Creation-Date: 2026-05-30 16:03+0000\n" "PO-Revision-Date: 2026-05-11 12:00+0000\n" "Last-Translator: Gemini\n" "Language-Team: Spanish (Spain)\n" @@ -47,37 +47,37 @@ msgctxt "About" msgid "Donate (via GitHub)" msgstr "Donar (a través de GitHub)" -#: src/ui/gui/appwindow.slint:151 +#: src/ui/gui/appwindow.slint:152 msgctxt "Menu" msgid "Local Game" msgstr "Juego local" -#: src/ui/gui/appwindow.slint:155 +#: src/ui/gui/appwindow.slint:156 msgctxt "Menu" msgid "Controller Config" msgstr "Config. del mando" -#: src/ui/gui/appwindow.slint:159 +#: src/ui/gui/appwindow.slint:160 msgctxt "Menu" msgid "Netplay" msgstr "Netplay" -#: src/ui/gui/appwindow.slint:163 +#: src/ui/gui/appwindow.slint:164 msgctxt "Menu" msgid "Cheats" msgstr "Trucos" -#: src/ui/gui/appwindow.slint:167 +#: src/ui/gui/appwindow.slint:168 msgctxt "Menu" msgid "RetroAchievements" msgstr "RetroAchievements" -#: src/ui/gui/appwindow.slint:171 +#: src/ui/gui/appwindow.slint:172 msgctxt "Menu" msgid "Settings" msgstr "Ajustes" -#: src/ui/gui/appwindow.slint:175 +#: src/ui/gui/appwindow.slint:176 msgctxt "Menu" msgid "About" msgstr "Acerca de" @@ -97,7 +97,7 @@ msgctxt "Cheats" msgid "Clear Cheats For All ROMs" msgstr "Borrar trucos de todas las ROMs" -#: src/ui/gui/cheat_page.slint:88 +#: src/ui/gui/cheat_page.slint:83 msgctxt "Cheats" msgid "No Cheats Available" msgstr "No hay trucos disponibles" @@ -485,62 +485,67 @@ msgctxt "RetroAchievements" msgid "View Supported Games" msgstr "Ver juegos compatibles" -#: src/ui/gui/settings_page.slint:23 +#: src/ui/gui/settings_page.slint:24 msgctxt "Settings" msgid "Settings" msgstr "Ajustes" -#: src/ui/gui/settings_page.slint:34 +#: src/ui/gui/settings_page.slint:35 msgctxt "Settings" msgid "Resolution:" msgstr "Resolución:" -#: src/ui/gui/settings_page.slint:48 +#: src/ui/gui/settings_page.slint:49 msgctxt "Settings" msgid "SSAA downscaling" msgstr "Reducción de escala SSAA" -#: src/ui/gui/settings_page.slint:56 +#: src/ui/gui/settings_page.slint:57 msgctxt "Settings" msgid "Integer Scaling" msgstr "Escalado por enteros" -#: src/ui/gui/settings_page.slint:64 +#: src/ui/gui/settings_page.slint:65 msgctxt "Settings" msgid "Fullscreen" msgstr "Pantalla completa" -#: src/ui/gui/settings_page.slint:72 +#: src/ui/gui/settings_page.slint:73 msgctxt "Settings" msgid "Widescreen (stretch)" msgstr "Pantalla ancha (estirado)" -#: src/ui/gui/settings_page.slint:80 +#: src/ui/gui/settings_page.slint:81 msgctxt "Settings" msgid "VSync" msgstr "Sincronización vertical (VSync)" -#: src/ui/gui/settings_page.slint:88 +#: src/ui/gui/settings_page.slint:89 msgctxt "Settings" msgid "Apply CRT shader" msgstr "Aplicar sombreador CRT" -#: src/ui/gui/settings_page.slint:100 +#: src/ui/gui/settings_page.slint:101 msgctxt "Settings" msgid "Overclock N64 CPU (may cause bugs)" msgstr "Overclock CPU N64 (puede causar errores)" -#: src/ui/gui/settings_page.slint:108 +#: src/ui/gui/settings_page.slint:109 msgctxt "Settings" msgid "Disable Expansion Pak" msgstr "Desactivar Expansion Pak" -#: src/ui/gui/settings_page.slint:116 +#: src/ui/gui/settings_page.slint:117 +msgctxt "Settings" +msgid "Enable Rewind" +msgstr "Activar rebobinado" + +#: src/ui/gui/settings_page.slint:125 msgctxt "Settings" msgid "Emulate UNFLoader" msgstr "Emular UNFLoader" -#: src/ui/gui/settings_page.slint:124 +#: src/ui/gui/settings_page.slint:133 msgctxt "Settings" msgid "Emulate VRU" msgstr "Emular VRU" diff --git a/data/translations/fr/LC_MESSAGES/gopher64.po b/data/translations/fr/LC_MESSAGES/gopher64.po index 389b0b02..4952a37f 100644 --- a/data/translations/fr/LC_MESSAGES/gopher64.po +++ b/data/translations/fr/LC_MESSAGES/gopher64.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: gopher64\n" -"POT-Creation-Date: 2026-05-25 08:28+0000\n" +"POT-Creation-Date: 2026-05-30 16:03+0000\n" "PO-Revision-Date: 2026-05-11 12:00+0000\n" "Last-Translator: \n" "Language-Team: French (France)\n" @@ -47,37 +47,37 @@ msgctxt "About" msgid "Donate (via GitHub)" msgstr "Faire un don (via GitHub)" -#: src/ui/gui/appwindow.slint:151 +#: src/ui/gui/appwindow.slint:152 msgctxt "Menu" msgid "Local Game" msgstr "Jeu local" -#: src/ui/gui/appwindow.slint:155 +#: src/ui/gui/appwindow.slint:156 msgctxt "Menu" msgid "Controller Config" msgstr "Configuration manette" -#: src/ui/gui/appwindow.slint:159 +#: src/ui/gui/appwindow.slint:160 msgctxt "Menu" msgid "Netplay" msgstr "Netplay" -#: src/ui/gui/appwindow.slint:163 +#: src/ui/gui/appwindow.slint:164 msgctxt "Menu" msgid "Cheats" msgstr "Triche" -#: src/ui/gui/appwindow.slint:167 +#: src/ui/gui/appwindow.slint:168 msgctxt "Menu" msgid "RetroAchievements" msgstr "RetroAchievements" -#: src/ui/gui/appwindow.slint:171 +#: src/ui/gui/appwindow.slint:172 msgctxt "Menu" msgid "Settings" msgstr "Paramètres" -#: src/ui/gui/appwindow.slint:175 +#: src/ui/gui/appwindow.slint:176 msgctxt "Menu" msgid "About" msgstr "À propos" @@ -97,7 +97,7 @@ msgctxt "Cheats" msgid "Clear Cheats For All ROMs" msgstr "Effacer la triche pour toutes les ROMs" -#: src/ui/gui/cheat_page.slint:88 +#: src/ui/gui/cheat_page.slint:83 msgctxt "Cheats" msgid "No Cheats Available" msgstr "Aucune triche disponible" @@ -486,62 +486,67 @@ msgctxt "RetroAchievements" msgid "View Supported Games" msgstr "Voir les jeux pris en charge" -#: src/ui/gui/settings_page.slint:23 +#: src/ui/gui/settings_page.slint:24 msgctxt "Settings" msgid "Settings" msgstr "Paramètres" -#: src/ui/gui/settings_page.slint:34 +#: src/ui/gui/settings_page.slint:35 msgctxt "Settings" msgid "Resolution:" msgstr "Résolution :" -#: src/ui/gui/settings_page.slint:48 +#: src/ui/gui/settings_page.slint:49 msgctxt "Settings" msgid "SSAA downscaling" msgstr "Réduction SSAA" -#: src/ui/gui/settings_page.slint:56 +#: src/ui/gui/settings_page.slint:57 msgctxt "Settings" msgid "Integer Scaling" msgstr "Mise à l'échelle par entiers" -#: src/ui/gui/settings_page.slint:64 +#: src/ui/gui/settings_page.slint:65 msgctxt "Settings" msgid "Fullscreen" msgstr "Plein écran" -#: src/ui/gui/settings_page.slint:72 +#: src/ui/gui/settings_page.slint:73 msgctxt "Settings" msgid "Widescreen (stretch)" msgstr "Écran large (étiré)" -#: src/ui/gui/settings_page.slint:80 +#: src/ui/gui/settings_page.slint:81 msgctxt "Settings" msgid "VSync" msgstr "Synchro verticale" -#: src/ui/gui/settings_page.slint:88 +#: src/ui/gui/settings_page.slint:89 msgctxt "Settings" msgid "Apply CRT shader" msgstr "Appliquer le filtre CRT" -#: src/ui/gui/settings_page.slint:100 +#: src/ui/gui/settings_page.slint:101 msgctxt "Settings" msgid "Overclock N64 CPU (may cause bugs)" msgstr "Surcharger le CPU N64 (risque de bugs)" -#: src/ui/gui/settings_page.slint:108 +#: src/ui/gui/settings_page.slint:109 msgctxt "Settings" msgid "Disable Expansion Pak" msgstr "Désactiver l'Expansion Pak" -#: src/ui/gui/settings_page.slint:116 +#: src/ui/gui/settings_page.slint:117 +msgctxt "Settings" +msgid "Enable Rewind" +msgstr "Activer le rembobinage" + +#: src/ui/gui/settings_page.slint:125 msgctxt "Settings" msgid "Emulate UNFLoader" msgstr "Émuler UNFLoader" -#: src/ui/gui/settings_page.slint:124 +#: src/ui/gui/settings_page.slint:133 msgctxt "Settings" msgid "Emulate VRU" msgstr "Émuler le VRU" diff --git a/data/translations/gopher64.pot b/data/translations/gopher64.pot index 2e7d0a98..fc298e63 100644 --- a/data/translations/gopher64.pot +++ b/data/translations/gopher64.pot @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2026-05-25 08:28+0000\n" +"POT-Creation-Date: 2026-05-30 16:03+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAME \n" "Language-Team: LANGUAGE \n" @@ -47,37 +47,37 @@ msgctxt "About" msgid "Donate (via GitHub)" msgstr "" -#: src/ui/gui/appwindow.slint:151 +#: src/ui/gui/appwindow.slint:152 msgctxt "Menu" msgid "Local Game" msgstr "" -#: src/ui/gui/appwindow.slint:155 +#: src/ui/gui/appwindow.slint:156 msgctxt "Menu" msgid "Controller Config" msgstr "" -#: src/ui/gui/appwindow.slint:159 +#: src/ui/gui/appwindow.slint:160 msgctxt "Menu" msgid "Netplay" msgstr "" -#: src/ui/gui/appwindow.slint:163 +#: src/ui/gui/appwindow.slint:164 msgctxt "Menu" msgid "Cheats" msgstr "" -#: src/ui/gui/appwindow.slint:167 +#: src/ui/gui/appwindow.slint:168 msgctxt "Menu" msgid "RetroAchievements" msgstr "" -#: src/ui/gui/appwindow.slint:171 +#: src/ui/gui/appwindow.slint:172 msgctxt "Menu" msgid "Settings" msgstr "" -#: src/ui/gui/appwindow.slint:175 +#: src/ui/gui/appwindow.slint:176 msgctxt "Menu" msgid "About" msgstr "" @@ -97,7 +97,7 @@ msgctxt "Cheats" msgid "Clear Cheats For All ROMs" msgstr "" -#: src/ui/gui/cheat_page.slint:88 +#: src/ui/gui/cheat_page.slint:83 msgctxt "Cheats" msgid "No Cheats Available" msgstr "" @@ -483,62 +483,67 @@ msgctxt "RetroAchievements" msgid "View Supported Games" msgstr "" -#: src/ui/gui/settings_page.slint:23 +#: src/ui/gui/settings_page.slint:24 msgctxt "Settings" msgid "Settings" msgstr "" -#: src/ui/gui/settings_page.slint:34 +#: src/ui/gui/settings_page.slint:35 msgctxt "Settings" msgid "Resolution:" msgstr "" -#: src/ui/gui/settings_page.slint:48 +#: src/ui/gui/settings_page.slint:49 msgctxt "Settings" msgid "SSAA downscaling" msgstr "" -#: src/ui/gui/settings_page.slint:56 +#: src/ui/gui/settings_page.slint:57 msgctxt "Settings" msgid "Integer Scaling" msgstr "" -#: src/ui/gui/settings_page.slint:64 +#: src/ui/gui/settings_page.slint:65 msgctxt "Settings" msgid "Fullscreen" msgstr "" -#: src/ui/gui/settings_page.slint:72 +#: src/ui/gui/settings_page.slint:73 msgctxt "Settings" msgid "Widescreen (stretch)" msgstr "" -#: src/ui/gui/settings_page.slint:80 +#: src/ui/gui/settings_page.slint:81 msgctxt "Settings" msgid "VSync" msgstr "" -#: src/ui/gui/settings_page.slint:88 +#: src/ui/gui/settings_page.slint:89 msgctxt "Settings" msgid "Apply CRT shader" msgstr "" -#: src/ui/gui/settings_page.slint:100 +#: src/ui/gui/settings_page.slint:101 msgctxt "Settings" msgid "Overclock N64 CPU (may cause bugs)" msgstr "" -#: src/ui/gui/settings_page.slint:108 +#: src/ui/gui/settings_page.slint:109 msgctxt "Settings" msgid "Disable Expansion Pak" msgstr "" -#: src/ui/gui/settings_page.slint:116 +#: src/ui/gui/settings_page.slint:117 +msgctxt "Settings" +msgid "Enable Rewind" +msgstr "" + +#: src/ui/gui/settings_page.slint:125 msgctxt "Settings" msgid "Emulate UNFLoader" msgstr "" -#: src/ui/gui/settings_page.slint:124 +#: src/ui/gui/settings_page.slint:133 msgctxt "Settings" msgid "Emulate VRU" msgstr "" diff --git a/data/translations/ja/LC_MESSAGES/gopher64.po b/data/translations/ja/LC_MESSAGES/gopher64.po index 2fdec4b2..24c2075c 100644 --- a/data/translations/ja/LC_MESSAGES/gopher64.po +++ b/data/translations/ja/LC_MESSAGES/gopher64.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: gopher64\n" -"POT-Creation-Date: 2026-05-25 08:28+0000\n" +"POT-Creation-Date: 2026-05-30 16:03+0000\n" "PO-Revision-Date: 2026-05-11 12:00+0000\n" "Last-Translator: \n" "Language-Team: Japanese\n" @@ -47,37 +47,37 @@ msgctxt "About" msgid "Donate (via GitHub)" msgstr "GitHubで寄付" -#: src/ui/gui/appwindow.slint:151 +#: src/ui/gui/appwindow.slint:152 msgctxt "Menu" msgid "Local Game" msgstr "ローカルゲーム" -#: src/ui/gui/appwindow.slint:155 +#: src/ui/gui/appwindow.slint:156 msgctxt "Menu" msgid "Controller Config" msgstr "コントローラー設定" -#: src/ui/gui/appwindow.slint:159 +#: src/ui/gui/appwindow.slint:160 msgctxt "Menu" msgid "Netplay" msgstr "ネットプレイ" -#: src/ui/gui/appwindow.slint:163 +#: src/ui/gui/appwindow.slint:164 msgctxt "Menu" msgid "Cheats" msgstr "チート" -#: src/ui/gui/appwindow.slint:167 +#: src/ui/gui/appwindow.slint:168 msgctxt "Menu" msgid "RetroAchievements" msgstr "RetroAchievements" -#: src/ui/gui/appwindow.slint:171 +#: src/ui/gui/appwindow.slint:172 msgctxt "Menu" msgid "Settings" msgstr "設定" -#: src/ui/gui/appwindow.slint:175 +#: src/ui/gui/appwindow.slint:176 msgctxt "Menu" msgid "About" msgstr "バージョン情報" @@ -97,7 +97,7 @@ msgctxt "Cheats" msgid "Clear Cheats For All ROMs" msgstr "すべてのROMのチートをクリア" -#: src/ui/gui/cheat_page.slint:88 +#: src/ui/gui/cheat_page.slint:83 msgctxt "Cheats" msgid "No Cheats Available" msgstr "利用できるチートはありません" @@ -483,62 +483,67 @@ msgctxt "RetroAchievements" msgid "View Supported Games" msgstr "対応ゲームを表示" -#: src/ui/gui/settings_page.slint:23 +#: src/ui/gui/settings_page.slint:24 msgctxt "Settings" msgid "Settings" msgstr "設定" -#: src/ui/gui/settings_page.slint:34 +#: src/ui/gui/settings_page.slint:35 msgctxt "Settings" msgid "Resolution:" msgstr "解像度:" -#: src/ui/gui/settings_page.slint:48 +#: src/ui/gui/settings_page.slint:49 msgctxt "Settings" msgid "SSAA downscaling" msgstr "SSAAダウンスケール" -#: src/ui/gui/settings_page.slint:56 +#: src/ui/gui/settings_page.slint:57 msgctxt "Settings" msgid "Integer Scaling" msgstr "整数倍スケーリング" -#: src/ui/gui/settings_page.slint:64 +#: src/ui/gui/settings_page.slint:65 msgctxt "Settings" msgid "Fullscreen" msgstr "全画面表示" -#: src/ui/gui/settings_page.slint:72 +#: src/ui/gui/settings_page.slint:73 msgctxt "Settings" msgid "Widescreen (stretch)" msgstr "ワイド画面(伸ばす)" -#: src/ui/gui/settings_page.slint:80 +#: src/ui/gui/settings_page.slint:81 msgctxt "Settings" msgid "VSync" msgstr "垂直同期(VSync)" -#: src/ui/gui/settings_page.slint:88 +#: src/ui/gui/settings_page.slint:89 msgctxt "Settings" msgid "Apply CRT shader" msgstr "CRTシェーダーを適用" -#: src/ui/gui/settings_page.slint:100 +#: src/ui/gui/settings_page.slint:101 msgctxt "Settings" msgid "Overclock N64 CPU (may cause bugs)" msgstr "N64のCPUをオーバークロック(不具合の原因になることがあります)" -#: src/ui/gui/settings_page.slint:108 +#: src/ui/gui/settings_page.slint:109 msgctxt "Settings" msgid "Disable Expansion Pak" msgstr "拡張パックを無効にする" -#: src/ui/gui/settings_page.slint:116 +#: src/ui/gui/settings_page.slint:117 +msgctxt "Settings" +msgid "Enable Rewind" +msgstr "巻き戻しを有効にする" + +#: src/ui/gui/settings_page.slint:125 msgctxt "Settings" msgid "Emulate UNFLoader" msgstr "UNFLoaderをエミュレート" -#: src/ui/gui/settings_page.slint:124 +#: src/ui/gui/settings_page.slint:133 msgctxt "Settings" msgid "Emulate VRU" msgstr "VRUをエミュレート" diff --git a/data/translations/nl/LC_MESSAGES/gopher64.po b/data/translations/nl/LC_MESSAGES/gopher64.po index 9d8f019d..17ee82d6 100644 --- a/data/translations/nl/LC_MESSAGES/gopher64.po +++ b/data/translations/nl/LC_MESSAGES/gopher64.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: gopher64\n" -"POT-Creation-Date: 2026-05-25 08:28+0000\n" +"POT-Creation-Date: 2026-05-30 16:03+0000\n" "PO-Revision-Date: 2026-05-11 12:00+0000\n" "Last-Translator: \n" "Language-Team: Dutch (Netherlands)\n" @@ -47,37 +47,37 @@ msgctxt "About" msgid "Donate (via GitHub)" msgstr "Doneren (via GitHub)" -#: src/ui/gui/appwindow.slint:151 +#: src/ui/gui/appwindow.slint:152 msgctxt "Menu" msgid "Local Game" msgstr "Lokaal spel" -#: src/ui/gui/appwindow.slint:155 +#: src/ui/gui/appwindow.slint:156 msgctxt "Menu" msgid "Controller Config" msgstr "Controllerconfiguratie" -#: src/ui/gui/appwindow.slint:159 +#: src/ui/gui/appwindow.slint:160 msgctxt "Menu" msgid "Netplay" msgstr "Netplay" -#: src/ui/gui/appwindow.slint:163 +#: src/ui/gui/appwindow.slint:164 msgctxt "Menu" msgid "Cheats" msgstr "Cheats" -#: src/ui/gui/appwindow.slint:167 +#: src/ui/gui/appwindow.slint:168 msgctxt "Menu" msgid "RetroAchievements" msgstr "RetroAchievements" -#: src/ui/gui/appwindow.slint:171 +#: src/ui/gui/appwindow.slint:172 msgctxt "Menu" msgid "Settings" msgstr "Instellingen" -#: src/ui/gui/appwindow.slint:175 +#: src/ui/gui/appwindow.slint:176 msgctxt "Menu" msgid "About" msgstr "Over" @@ -97,7 +97,7 @@ msgctxt "Cheats" msgid "Clear Cheats For All ROMs" msgstr "Cheats wissen voor alle ROM's" -#: src/ui/gui/cheat_page.slint:88 +#: src/ui/gui/cheat_page.slint:83 msgctxt "Cheats" msgid "No Cheats Available" msgstr "Geen cheats beschikbaar" @@ -485,62 +485,67 @@ msgctxt "RetroAchievements" msgid "View Supported Games" msgstr "Ondersteunde spellen bekijken" -#: src/ui/gui/settings_page.slint:23 +#: src/ui/gui/settings_page.slint:24 msgctxt "Settings" msgid "Settings" msgstr "Instellingen" -#: src/ui/gui/settings_page.slint:34 +#: src/ui/gui/settings_page.slint:35 msgctxt "Settings" msgid "Resolution:" msgstr "Resolutie:" -#: src/ui/gui/settings_page.slint:48 +#: src/ui/gui/settings_page.slint:49 msgctxt "Settings" msgid "SSAA downscaling" msgstr "SSAA-downscaling" -#: src/ui/gui/settings_page.slint:56 +#: src/ui/gui/settings_page.slint:57 msgctxt "Settings" msgid "Integer Scaling" msgstr "Schalen in hele getallen" -#: src/ui/gui/settings_page.slint:64 +#: src/ui/gui/settings_page.slint:65 msgctxt "Settings" msgid "Fullscreen" msgstr "Volledig scherm" -#: src/ui/gui/settings_page.slint:72 +#: src/ui/gui/settings_page.slint:73 msgctxt "Settings" msgid "Widescreen (stretch)" msgstr "Breedbeeld (uitgerekt)" -#: src/ui/gui/settings_page.slint:80 +#: src/ui/gui/settings_page.slint:81 msgctxt "Settings" msgid "VSync" msgstr "VSync" -#: src/ui/gui/settings_page.slint:88 +#: src/ui/gui/settings_page.slint:89 msgctxt "Settings" msgid "Apply CRT shader" msgstr "CRT-shader toepassen" -#: src/ui/gui/settings_page.slint:100 +#: src/ui/gui/settings_page.slint:101 msgctxt "Settings" msgid "Overclock N64 CPU (may cause bugs)" msgstr "N64-CPU overklokken (kan bugs veroorzaken)" -#: src/ui/gui/settings_page.slint:108 +#: src/ui/gui/settings_page.slint:109 msgctxt "Settings" msgid "Disable Expansion Pak" msgstr "Expansion Pak uitschakelen" -#: src/ui/gui/settings_page.slint:116 +#: src/ui/gui/settings_page.slint:117 +msgctxt "Settings" +msgid "Enable Rewind" +msgstr "Terugspoelen inschakelen" + +#: src/ui/gui/settings_page.slint:125 msgctxt "Settings" msgid "Emulate UNFLoader" msgstr "UNFLoader emuleren" -#: src/ui/gui/settings_page.slint:124 +#: src/ui/gui/settings_page.slint:133 msgctxt "Settings" msgid "Emulate VRU" msgstr "VRU emuleren" diff --git a/data/translations/pt_BR/LC_MESSAGES/gopher64.po b/data/translations/pt_BR/LC_MESSAGES/gopher64.po index b392f31f..f4188d74 100644 --- a/data/translations/pt_BR/LC_MESSAGES/gopher64.po +++ b/data/translations/pt_BR/LC_MESSAGES/gopher64.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: PACKAGE VERSION\n" -"POT-Creation-Date: 2026-05-25 08:28+0000\n" +"POT-Creation-Date: 2026-05-30 16:03+0000\n" "PO-Revision-Date: 2026-05-11 18:11-3000\n" "Last-Translator: EDUARDO CARVALHO \n" "Language-Team: LANGUAGE \n" @@ -47,37 +47,37 @@ msgctxt "About" msgid "Donate (via GitHub)" msgstr "Doações (via GitHub)" -#: src/ui/gui/appwindow.slint:151 +#: src/ui/gui/appwindow.slint:152 msgctxt "Menu" msgid "Local Game" msgstr "Jogo Local" -#: src/ui/gui/appwindow.slint:155 +#: src/ui/gui/appwindow.slint:156 msgctxt "Menu" msgid "Controller Config" msgstr "Configurar Controle" -#: src/ui/gui/appwindow.slint:159 +#: src/ui/gui/appwindow.slint:160 msgctxt "Menu" msgid "Netplay" msgstr "Netplay" -#: src/ui/gui/appwindow.slint:163 +#: src/ui/gui/appwindow.slint:164 msgctxt "Menu" msgid "Cheats" msgstr "Trapaças" -#: src/ui/gui/appwindow.slint:167 +#: src/ui/gui/appwindow.slint:168 msgctxt "Menu" msgid "RetroAchievements" msgstr "RetroAchievements" -#: src/ui/gui/appwindow.slint:171 +#: src/ui/gui/appwindow.slint:172 msgctxt "Menu" msgid "Settings" msgstr "Configurações" -#: src/ui/gui/appwindow.slint:175 +#: src/ui/gui/appwindow.slint:176 msgctxt "Menu" msgid "About" msgstr "Sobre" @@ -97,7 +97,7 @@ msgctxt "Cheats" msgid "Clear Cheats For All ROMs" msgstr "Limpar Trapaças para todas as ROMs" -#: src/ui/gui/cheat_page.slint:88 +#: src/ui/gui/cheat_page.slint:83 msgctxt "Cheats" msgid "No Cheats Available" msgstr "Sem trapaças disponíveis" @@ -486,62 +486,67 @@ msgctxt "RetroAchievements" msgid "View Supported Games" msgstr "Ver Jogos com Suporte" -#: src/ui/gui/settings_page.slint:23 +#: src/ui/gui/settings_page.slint:24 msgctxt "Settings" msgid "Settings" msgstr "Configurações" -#: src/ui/gui/settings_page.slint:34 +#: src/ui/gui/settings_page.slint:35 msgctxt "Settings" msgid "Resolution:" msgstr "Resolução:" -#: src/ui/gui/settings_page.slint:48 +#: src/ui/gui/settings_page.slint:49 msgctxt "Settings" msgid "SSAA downscaling" msgstr "Redução de SSAA" -#: src/ui/gui/settings_page.slint:56 +#: src/ui/gui/settings_page.slint:57 msgctxt "Settings" msgid "Integer Scaling" msgstr "Proporção Íntegra" -#: src/ui/gui/settings_page.slint:64 +#: src/ui/gui/settings_page.slint:65 msgctxt "Settings" msgid "Fullscreen" msgstr "Tela Cheia" -#: src/ui/gui/settings_page.slint:72 +#: src/ui/gui/settings_page.slint:73 msgctxt "Settings" msgid "Widescreen (stretch)" msgstr "Widescreen (esticar)" -#: src/ui/gui/settings_page.slint:80 +#: src/ui/gui/settings_page.slint:81 msgctxt "Settings" msgid "VSync" msgstr "VSync" -#: src/ui/gui/settings_page.slint:88 +#: src/ui/gui/settings_page.slint:89 msgctxt "Settings" msgid "Apply CRT shader" msgstr "Aplicar filtro de CRT" -#: src/ui/gui/settings_page.slint:100 +#: src/ui/gui/settings_page.slint:101 msgctxt "Settings" msgid "Overclock N64 CPU (may cause bugs)" msgstr "Overclock da CPU do N64 (pode causar bugs)" -#: src/ui/gui/settings_page.slint:108 +#: src/ui/gui/settings_page.slint:109 msgctxt "Settings" msgid "Disable Expansion Pak" msgstr "Desabilitar Expansion Pak" -#: src/ui/gui/settings_page.slint:116 +#: src/ui/gui/settings_page.slint:117 +msgctxt "Settings" +msgid "Enable Rewind" +msgstr "Habilitar rebobinamento" + +#: src/ui/gui/settings_page.slint:125 msgctxt "Settings" msgid "Emulate UNFLoader" msgstr "Emular UNFLoader" -#: src/ui/gui/settings_page.slint:124 +#: src/ui/gui/settings_page.slint:133 msgctxt "Settings" msgid "Emulate VRU" msgstr "Emular VRU" diff --git a/data/translations/ru/LC_MESSAGES/gopher64.po b/data/translations/ru/LC_MESSAGES/gopher64.po index 338af514..d7699169 100644 --- a/data/translations/ru/LC_MESSAGES/gopher64.po +++ b/data/translations/ru/LC_MESSAGES/gopher64.po @@ -2,7 +2,7 @@ msgid "" msgstr "" "Project-Id-Version: gopher64\n" -"POT-Creation-Date: 2026-05-25 08:28+0000\n" +"POT-Creation-Date: 2026-05-30 16:03+0000\n" "PO-Revision-Date: 2026-05-11 12:00+0000\n" "Last-Translator: \n" "Language-Team: Russian\n" @@ -47,37 +47,37 @@ msgctxt "About" msgid "Donate (via GitHub)" msgstr "Пожертвовать (через GitHub)" -#: src/ui/gui/appwindow.slint:151 +#: src/ui/gui/appwindow.slint:152 msgctxt "Menu" msgid "Local Game" msgstr "Локальная игра" -#: src/ui/gui/appwindow.slint:155 +#: src/ui/gui/appwindow.slint:156 msgctxt "Menu" msgid "Controller Config" msgstr "Настройка контроллера" -#: src/ui/gui/appwindow.slint:159 +#: src/ui/gui/appwindow.slint:160 msgctxt "Menu" msgid "Netplay" msgstr "Сетевая игра" -#: src/ui/gui/appwindow.slint:163 +#: src/ui/gui/appwindow.slint:164 msgctxt "Menu" msgid "Cheats" msgstr "Читы" -#: src/ui/gui/appwindow.slint:167 +#: src/ui/gui/appwindow.slint:168 msgctxt "Menu" msgid "RetroAchievements" msgstr "RetroAchievements" -#: src/ui/gui/appwindow.slint:171 +#: src/ui/gui/appwindow.slint:172 msgctxt "Menu" msgid "Settings" msgstr "Настройки" -#: src/ui/gui/appwindow.slint:175 +#: src/ui/gui/appwindow.slint:176 msgctxt "Menu" msgid "About" msgstr "О программе" @@ -97,7 +97,7 @@ msgctxt "Cheats" msgid "Clear Cheats For All ROMs" msgstr "Очистить читы для всех ROMов" -#: src/ui/gui/cheat_page.slint:88 +#: src/ui/gui/cheat_page.slint:83 msgctxt "Cheats" msgid "No Cheats Available" msgstr "Читы недоступны" @@ -484,62 +484,67 @@ msgctxt "RetroAchievements" msgid "View Supported Games" msgstr "Просмотреть поддерживаемые игры" -#: src/ui/gui/settings_page.slint:23 +#: src/ui/gui/settings_page.slint:24 msgctxt "Settings" msgid "Settings" msgstr "Настройки" -#: src/ui/gui/settings_page.slint:34 +#: src/ui/gui/settings_page.slint:35 msgctxt "Settings" msgid "Resolution:" msgstr "Разрешение:" -#: src/ui/gui/settings_page.slint:48 +#: src/ui/gui/settings_page.slint:49 msgctxt "Settings" msgid "SSAA downscaling" msgstr "SSAA даунскейлинг" -#: src/ui/gui/settings_page.slint:56 +#: src/ui/gui/settings_page.slint:57 msgctxt "Settings" msgid "Integer Scaling" msgstr "Целочисленное масштабирование" -#: src/ui/gui/settings_page.slint:64 +#: src/ui/gui/settings_page.slint:65 msgctxt "Settings" msgid "Fullscreen" msgstr "Полноэкранный режим" -#: src/ui/gui/settings_page.slint:72 +#: src/ui/gui/settings_page.slint:73 msgctxt "Settings" msgid "Widescreen (stretch)" msgstr "Широкоэкранный режим (растянуто)" -#: src/ui/gui/settings_page.slint:80 +#: src/ui/gui/settings_page.slint:81 msgctxt "Settings" msgid "VSync" msgstr "Вертикальная синхронизация" -#: src/ui/gui/settings_page.slint:88 +#: src/ui/gui/settings_page.slint:89 msgctxt "Settings" msgid "Apply CRT shader" msgstr "Применить шейдер ЭЛТ" -#: src/ui/gui/settings_page.slint:100 +#: src/ui/gui/settings_page.slint:101 msgctxt "Settings" msgid "Overclock N64 CPU (may cause bugs)" msgstr "Разгон процессора N64 (может вызвать ошибки)" -#: src/ui/gui/settings_page.slint:108 +#: src/ui/gui/settings_page.slint:109 msgctxt "Settings" msgid "Disable Expansion Pak" msgstr "Отключить Expansion Pak" -#: src/ui/gui/settings_page.slint:116 +#: src/ui/gui/settings_page.slint:117 +msgctxt "Settings" +msgid "Enable Rewind" +msgstr "Включить перемотку назад" + +#: src/ui/gui/settings_page.slint:125 msgctxt "Settings" msgid "Emulate UNFLoader" msgstr "Эмулировать UNFLoader" -#: src/ui/gui/settings_page.slint:124 +#: src/ui/gui/settings_page.slint:133 msgctxt "Settings" msgid "Emulate VRU" msgstr "Эмулировать VRU" diff --git a/parallel-rdp/interface.cpp b/parallel-rdp/interface.cpp index f54d2d5d..682caf72 100644 --- a/parallel-rdp/interface.cpp +++ b/parallel-rdp/interface.cpp @@ -59,6 +59,7 @@ enum user_event_codes { USER_EVENT_LOAD_STATE = 2, USER_EVENT_EXIT_GAME = 3, USER_EVENT_FAST_FORWARD = 4, + USER_EVENT_LOAD_REWIND = 5, }; typedef struct { @@ -189,6 +190,12 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) { user_event.user.code = USER_EVENT_SAVE_STATE; SDL_PushEvent(&user_event); break; + case SDL_SCANCODE_F6: + SDL_zero(user_event); + user_event.type = SDL_EVENT_USER; + user_event.user.code = USER_EVENT_LOAD_REWIND; + SDL_PushEvent(&user_event); + break; case SDL_SCANCODE_F7: SDL_zero(user_event); user_event.type = SDL_EVENT_USER; @@ -244,6 +251,9 @@ bool sdl_event_filter(void *userdata, SDL_Event *event) { case USER_EVENT_SAVE_STATE: callback.save_state = true; break; + case USER_EVENT_LOAD_REWIND: + callback.load_rewind = true; + break; case USER_EVENT_LOAD_STATE: callback.load_state = true; break; @@ -330,6 +340,7 @@ static ImageHandle create_message_image(Vulkan::Device &device, int width, void rdp_init(void *_window, GFX_INFO _gfx_info, const void *font, size_t font_size, uint32_t save_state_slot) { memset(&rdp_device, 0, sizeof(RDP_DEVICE)); + memset(&callback, 0, sizeof(CALL_BACK)); window = (SDL_Window *)_window; SDL_SyncWindow(window); @@ -659,6 +670,7 @@ void rdp_update_screen() { CALL_BACK rdp_check_callback() { CALL_BACK return_value = callback; callback.save_state = false; + callback.load_rewind = false; callback.load_state = false; callback.reset_game = false; callback.lower_volume = false; diff --git a/parallel-rdp/interface.hpp b/parallel-rdp/interface.hpp index 40863096..7de41daa 100644 --- a/parallel-rdp/interface.hpp +++ b/parallel-rdp/interface.hpp @@ -29,6 +29,7 @@ typedef struct { bool emu_running; bool save_state; bool load_state; + bool load_rewind; bool reset_game; bool enable_speedlimiter; bool lower_volume; diff --git a/src/device.rs b/src/device.rs index 286b6f30..331493b0 100644 --- a/src/device.rs +++ b/src/device.rs @@ -15,7 +15,7 @@ const N64_EXTENSIONS_UNCOMPRESSED: [&str; 8] = use rand::{Rng, SeedableRng}; -use crate::{cheats, netplay, retroachievements, ui}; +use crate::{cheats, netplay, retroachievements, savestates, ui}; use std::{collections::HashMap, io::Read}; pub mod ai; @@ -59,7 +59,7 @@ pub async fn run_game( } if let Some(slot) = game_settings.load_savestate_slot { device.ui.storage.save_state_slot = slot; - device.load_state = true; + device.savestate.load_state = true; } init_rng(device); @@ -229,8 +229,7 @@ pub struct Device { #[serde(skip)] pub ui: ui::Ui, pub byte_swap: usize, - pub save_state: bool, - pub load_state: bool, + pub savestate: savestates::Savestate, pub cpu: cpu::Cpu, pub pif: pif::Pif, pub cart: cart::Cart, @@ -261,8 +260,7 @@ impl Device { netplay: None, ui: ui::Ui::default(), byte_swap: self.byte_swap, - save_state: self.save_state, - load_state: self.load_state, + savestate: self.savestate.clone(), cpu: self.cpu.clone(), pif: self.pif.clone(), cart: self.cart.clone(), @@ -294,8 +292,16 @@ impl Device { netplay: None, ui: ui::Ui::new(), byte_swap, - save_state: false, - load_state: false, + savestate: savestates::Savestate { + save_state: false, + load_state: false, + save_rewind: false, + load_rewind: false, + last_rewind_saved: 0.0, + rewind_pool: std::sync::Arc::new(std::sync::Mutex::new( + std::collections::VecDeque::new(), + )), + }, cpu: cpu::Cpu { cop0: cop0::Cop0 { regs: [0; cop0::COP0_REGS_COUNT], diff --git a/src/device/rdp.rs b/src/device/rdp.rs index de653b00..4a149cbb 100644 --- a/src/device/rdp.rs +++ b/src/device/rdp.rs @@ -120,14 +120,20 @@ fn run_rdp(device: &mut device::Device) { device.rdp.regs_dpc[DPC_PIPEBUSY_REG] = 0xFFFFFF; if timer != 0 { - if device.save_state { + if device.savestate.save_state || device.savestate.save_rewind { // Right after full sync, good time for save state - device.save_state = false; - savestates::create_savestate(device); - } else if device.load_state { + let rewind = device.savestate.save_rewind && !device.savestate.save_state; + if rewind { + device.savestate.save_rewind = false; + } + device.savestate.save_state = false; + savestates::create_savestate(device, rewind); + } else if device.savestate.load_state || device.savestate.load_rewind { // Right after full sync, good time for save state - device.load_state = false; - savestates::load_savestate(device); + device.savestate.load_state = false; + let rewind = device.savestate.load_rewind; + device.savestate.load_rewind = false; + savestates::load_savestate(device, rewind); } device::events::create_event(device, device::events::EVENT_TYPE_DP, timer) } diff --git a/src/device/vi.rs b/src/device/vi.rs index 2eee1530..664999f1 100644 --- a/src/device/vi.rs +++ b/src/device/vi.rs @@ -122,6 +122,8 @@ pub fn write_regs(device: &mut device::Device, address: u64, value: u32, mask: u } pub fn vertical_interrupt_event(device: &mut device::Device) { + device.vi.elapsed_time += device.vi.frame_time; + if device.cheats.enabled { cheats::execute_cheats(device, device.cheats.cheats.clone()); } @@ -139,6 +141,11 @@ pub fn vertical_interrupt_event(device: &mut device::Device) { speed_limiter_toggled = true; device.vi.enable_speed_limiter = !netplay.fast_forward; } + } else if device.ui.config.emulation.rewind + && device.vi.elapsed_time - device.savestate.last_rewind_saved > 1.0 + { + device.savestate.save_rewind = true; + device.savestate.last_rewind_saved = device.vi.elapsed_time; } if speed_limiter_toggled { @@ -153,7 +160,6 @@ pub fn vertical_interrupt_event(device: &mut device::Device) { ui::video::update_screen(); device.vi.vi_counter += 1; - device.vi.elapsed_time += device.vi.frame_time; if device.netplay.is_none() && paused { if retroachievements::get_hardcore() { @@ -162,7 +168,7 @@ pub fn vertical_interrupt_event(device: &mut device::Device) { ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); } else { - ui::video::pause_loop(device.vi.frame_time); + ui::video::pause_loop(&mut device.ui, device.vi.frame_time); } } diff --git a/src/lib.rs b/src/lib.rs index 00efe19c..b263f6e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -104,6 +104,8 @@ pub async fn run(args: Args, arg_count: usize) -> std::io::Result<()> { ui::sdl_hints(); if let Some(game) = args.game { + ui::disable_auto_update_joysticks(); + let file_path = std::path::Path::new(&game).to_path_buf(); let Some(rom_contents) = device::get_rom_contents(&file_path) else { return Err(Error::other(format!( diff --git a/src/savestates.rs b/src/savestates.rs index b2163333..a3e140eb 100644 --- a/src/savestates.rs +++ b/src/savestates.rs @@ -69,7 +69,25 @@ where seq.end() } -pub fn create_savestate(device: &device::Device) { +#[derive(Clone, serde::Serialize, serde::Deserialize)] +pub struct Savestate { + pub save_state: bool, + pub load_state: bool, + pub save_rewind: bool, + pub load_rewind: bool, + pub last_rewind_saved: f64, + #[serde(skip)] + pub rewind_pool: std::sync::Arc>>, +} + +pub struct SavestateData { + device_data: Vec, + saves_data: Vec, + rdp_state: Vec, + ra_state: Vec, +} + +pub fn create_savestate(device: &mut device::Device, rewind: bool) { let mut rdp_state: Vec = vec![0; ui::video::state_size()]; ui::video::save_state(rdp_state.as_mut_ptr()); @@ -80,24 +98,39 @@ pub fn create_savestate(device: &device::Device) { let device_clone = device.clone_without_ui(); let save_state_slot = device.ui.storage.save_state_slot; + let rewind_pool = device.savestate.rewind_pool.clone(); tokio::spawn(async move { let mut error = false; if let Ok(device_data) = postcard::to_stdvec(&device_clone) && let Ok(saves_data) = postcard::to_stdvec(&device_clone.ui.storage.saves) { - if let Ok(compressed_file) = ui::storage::compress_file(&[ - (&device_data, "device"), - (&saves_data, "saves"), - (&rdp_state, "rdp_state"), - (&ra_state, "ra_state"), - ]) { - if let Err(e) = tokio::fs::write(save_path, compressed_file).await { - eprintln!("Error writing savestate: {}", e); - error = true; + if rewind { + if let Ok(mut pool) = rewind_pool.lock() { + pool.push_back(SavestateData { + device_data, + saves_data, + rdp_state, + ra_state, + }); + if pool.len() > 30 { + pool.pop_front(); + } } } else { - eprintln!("Error compressing savestate"); - error = true; + if let Ok(compressed_file) = ui::storage::compress_file(&[ + (&device_data, "device"), + (&saves_data, "saves"), + (&rdp_state, "rdp_state"), + (&ra_state, "ra_state"), + ]) { + if let Err(e) = tokio::fs::write(save_path, compressed_file).await { + eprintln!("Error writing savestate: {}", e); + error = true; + } + } else { + eprintln!("Error compressing savestate"); + error = true; + } } } else { error = true; @@ -107,7 +140,7 @@ pub fn create_savestate(device: &device::Device) { &format!("Failed to create savestate in slot {}", save_state_slot), ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, ); - } else { + } else if !rewind { ui::video::onscreen_message( &format!("Savestate created in slot {}", save_state_slot), ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT, @@ -116,7 +149,7 @@ pub fn create_savestate(device: &device::Device) { }); } -pub fn load_savestate(device: &mut device::Device) { +pub fn load_savestate(device: &mut device::Device, rewind: bool) { if retroachievements::get_hardcore() { ui::video::onscreen_message( "Cannot load savestate in RA hardcore mode", @@ -124,119 +157,138 @@ pub fn load_savestate(device: &mut device::Device) { ); return; } - let savestate = std::fs::read(&device.ui.storage.paths.savestate_file_path); - if let Ok(savestate) = &savestate { - if let Ok(device_bytes) = ui::storage::decompress_file(savestate, "device") - && let Ok(save_bytes) = ui::storage::decompress_file(savestate, "saves") - && let Ok(rdp_state) = ui::storage::decompress_file(savestate, "rdp_state") - && let Ok(ra_state) = ui::storage::decompress_file(savestate, "ra_state") - && let Ok(mut state) = postcard::from_bytes::(&device_bytes) - && let Ok(saves) = postcard::from_bytes(&save_bytes) - && device.rdram.size == state.rdram.size - { - device.ui.storage.saves = saves; - std::mem::swap(&mut device.cpu, &mut state.cpu); - std::mem::swap(&mut device.pif, &mut state.pif); + let state_data = if rewind { + if let Ok(mut pool) = device.savestate.rewind_pool.lock() { + pool.pop_back() + } else { + None + } + } else if let savestate = std::fs::read(&device.ui.storage.paths.savestate_file_path) + && let Ok(savestate) = &savestate + && let Ok(device_data) = ui::storage::decompress_file(savestate, "device") + && let Ok(saves_data) = ui::storage::decompress_file(savestate, "saves") + && let Ok(rdp_state) = ui::storage::decompress_file(savestate, "rdp_state") + && let Ok(ra_state) = ui::storage::decompress_file(savestate, "ra_state") + { + Some(SavestateData { + device_data, + saves_data, + rdp_state, + ra_state, + }) + } else { + None + }; + if let Some(state_data) = state_data + && let Ok(mut state) = postcard::from_bytes::(&state_data.device_data) + && let Ok(saves) = postcard::from_bytes(&state_data.saves_data) + && device.rdram.size == state.rdram.size + { + device.savestate.last_rewind_saved = state.vi.elapsed_time; - let rom = device.cart.rom.clone(); - std::mem::swap(&mut device.cart, &mut state.cart); - device.cart.rom = rom; + device.ui.storage.saves = saves; - std::mem::swap(&mut device.memory, &mut state.memory); - std::mem::swap(&mut device.rsp, &mut state.rsp); - std::mem::swap(&mut device.rdp, &mut state.rdp); + std::mem::swap(&mut device.cpu, &mut state.cpu); + std::mem::swap(&mut device.pif, &mut state.pif); - device.rdram.mem.clone_from(&state.rdram.mem); - device.rdram.regs = state.rdram.regs; + let rom = device.cart.rom.clone(); + std::mem::swap(&mut device.cart, &mut state.cart); + device.cart.rom = rom; - std::mem::swap(&mut device.mi, &mut state.mi); - std::mem::swap(&mut device.pi, &mut state.pi); - std::mem::swap(&mut device.vi, &mut state.vi); - std::mem::swap(&mut device.ai, &mut state.ai); - std::mem::swap(&mut device.si, &mut state.si); - std::mem::swap(&mut device.ri, &mut state.ri); - std::mem::swap(&mut device.vru, &mut state.vru); - std::mem::swap(&mut device.cheats, &mut state.cheats); + std::mem::swap(&mut device.memory, &mut state.memory); + std::mem::swap(&mut device.rsp, &mut state.rsp); + std::mem::swap(&mut device.rdp, &mut state.rdp); - let mut tpak_rom = [vec![], vec![], vec![], vec![]]; - for (i, item) in tpak_rom.iter_mut().enumerate() { - *item = device.transferpaks[i].cart.rom.clone(); - } - std::mem::swap(&mut device.transferpaks, &mut state.transferpaks); - for (i, item) in tpak_rom.iter().enumerate() { - device.transferpaks[i].cart.rom = item.clone(); + device.rdram.mem.clone_from(&state.rdram.mem); + device.rdram.regs = state.rdram.regs; + + std::mem::swap(&mut device.mi, &mut state.mi); + std::mem::swap(&mut device.pi, &mut state.pi); + std::mem::swap(&mut device.vi, &mut state.vi); + std::mem::swap(&mut device.ai, &mut state.ai); + std::mem::swap(&mut device.si, &mut state.si); + std::mem::swap(&mut device.ri, &mut state.ri); + std::mem::swap(&mut device.vru, &mut state.vru); + std::mem::swap(&mut device.cheats, &mut state.cheats); + + let mut tpak_rom = [vec![], vec![], vec![], vec![]]; + for (i, item) in tpak_rom.iter_mut().enumerate() { + *item = device.transferpaks[i].cart.rom.clone(); + } + std::mem::swap(&mut device.transferpaks, &mut state.transferpaks); + for (i, item) in tpak_rom.iter().enumerate() { + device.transferpaks[i].cart.rom = item.clone(); + } + + device::memory::init(device); + device::vi::set_expected_refresh_rate(device); + device::cpu::map_instructions(device); + device::cop0::map_instructions(device); + device::cop1::map_instructions(device); + device::cop2::map_instructions(device); + device::rsp_cpu::map_instructions(device); + + let mut mem_addr = 0x1000; + while mem_addr < 0x2000 { + let data = + u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap()); + device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func = + device::rsp_cpu::decode_opcode(device, data); + device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data; + mem_addr += 4; + } + + for line_index in 0..512 { + for i in 0..8 { + device.memory.icache[line_index].instruction[i] = + device::cpu::decode_opcode(device, device.memory.icache[line_index].words[i]); } + } - device::memory::init(device); - device::vi::set_expected_refresh_rate(device); - device::cpu::map_instructions(device); - device::cop0::map_instructions(device); - device::cop1::map_instructions(device); - device::cop2::map_instructions(device); - device::rsp_cpu::map_instructions(device); - - let mut mem_addr = 0x1000; - while mem_addr < 0x2000 { - let data = - u32::from_be_bytes(device.rsp.mem[mem_addr..mem_addr + 4].try_into().unwrap()); - device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].func = - device::rsp_cpu::decode_opcode(device, data); - device.rsp.cpu.instructions[(mem_addr & 0xFFF) / 4].opcode = data; - mem_addr += 4; - } - - for line_index in 0..512 { - for i in 0..8 { - device.memory.icache[line_index].instruction[i] = device::cpu::decode_opcode( - device, - device.memory.icache[line_index].words[i], - ); + device::pif::connect_pif_channels(device); + for i in 0..4 { + if let Some(handler) = device.pif.channels[i].pak_handler { + if handler.pak_type == device::controller::PakType::RumblePak { + let rumblepak_handler = device::controller::PakHandler { + read: device::controller::rumble::read, + write: device::controller::rumble::write, + pak_type: device::controller::PakType::RumblePak, + }; + device.pif.channels[i].pak_handler = Some(rumblepak_handler); + } else if handler.pak_type == device::controller::PakType::MemPak { + let mempak_handler = device::controller::PakHandler { + read: device::controller::mempak::read, + write: device::controller::mempak::write, + pak_type: device::controller::PakType::MemPak, + }; + device.pif.channels[i].pak_handler = Some(mempak_handler); + } else if handler.pak_type == device::controller::PakType::TransferPak { + let tpak_handler = device::controller::PakHandler { + read: device::controller::transferpak::read, + write: device::controller::transferpak::write, + pak_type: device::controller::PakType::TransferPak, + }; + device.pif.channels[i].pak_handler = Some(tpak_handler); } } + } - device::pif::connect_pif_channels(device); - for i in 0..4 { - if let Some(handler) = device.pif.channels[i].pak_handler { - if handler.pak_type == device::controller::PakType::RumblePak { - let rumblepak_handler = device::controller::PakHandler { - read: device::controller::rumble::read, - write: device::controller::rumble::write, - pak_type: device::controller::PakType::RumblePak, - }; - device.pif.channels[i].pak_handler = Some(rumblepak_handler); - } else if handler.pak_type == device::controller::PakType::MemPak { - let mempak_handler = device::controller::PakHandler { - read: device::controller::mempak::read, - write: device::controller::mempak::write, - pak_type: device::controller::PakType::MemPak, - }; - device.pif.channels[i].pak_handler = Some(mempak_handler); - } else if handler.pak_type == device::controller::PakType::TransferPak { - let tpak_handler = device::controller::PakHandler { - read: device::controller::transferpak::read, - write: device::controller::transferpak::write, - pak_type: device::controller::PakType::TransferPak, - }; - device.pif.channels[i].pak_handler = Some(tpak_handler); - } - } - } + ui::audio::update_freq(device); + ui::video::load_state(device, state_data.rdp_state.as_ptr()); - ui::audio::update_freq(device); - ui::video::load_state(device, rdp_state.as_ptr()); + retroachievements::set_rdram(device.rdram.mem.as_ptr(), device.rdram.size as usize); + if !state_data.ra_state.is_empty() { + retroachievements::load_state(state_data.ra_state.as_ptr(), state_data.ra_state.len()); + } else { + retroachievements::load_state(std::ptr::null(), 0); + } - retroachievements::set_rdram(device.rdram.mem.as_ptr(), device.rdram.size as usize); - if !ra_state.is_empty() { - retroachievements::load_state(ra_state.as_ptr(), ra_state.len()); - } else { - retroachievements::load_state(std::ptr::null(), 0); - } - - if device.cheats.enabled { - cheats::execute_cheats(device, device.cheats.cheats.clone()); - } + if device.cheats.enabled { + cheats::execute_cheats(device, device.cheats.cheats.clone()); + } + if !rewind { ui::video::onscreen_message( &format!( "Savestate loaded from slot {}", @@ -244,23 +296,17 @@ pub fn load_savestate(device: &mut device::Device) { ), ui::video::MESSAGE_LENGTH_MESSAGE_VERY_SHORT, ); - } else { - ui::video::onscreen_message( - &format!( - "Failed to load savestate from slot {}", - device.ui.storage.save_state_slot - ), - ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, - ); } } else { - ui::video::onscreen_message( + let message = if !rewind { &format!( - "Could not find savestate in slot {}", + "Failed to load savestate from slot {}", device.ui.storage.save_state_slot - ), - ui::video::MESSAGE_LENGTH_MESSAGE_SHORT, - ); + ) + } else { + "Failed to rewind" + }; + ui::video::onscreen_message(message, ui::video::MESSAGE_LENGTH_MESSAGE_SHORT); } } diff --git a/src/ui.rs b/src/ui.rs index 255238a8..9e15d999 100644 --- a/src/ui.rs +++ b/src/ui.rs @@ -113,6 +113,11 @@ pub fn sdl_hints() { sdl3_sys::everything::SDL_HINT_ANDROID_ALLOW_RECREATE_ACTIVITY, hint.as_ptr(), ); + } +} + +pub fn disable_auto_update_joysticks() { + unsafe { let hint = std::ffi::CString::new("0").unwrap(); sdl3_sys::everything::SDL_SetHint( sdl3_sys::everything::SDL_HINT_AUTO_UPDATE_JOYSTICKS, diff --git a/src/ui/config.rs b/src/ui/config.rs index c600a2dc..be3d42a2 100644 --- a/src/ui/config.rs +++ b/src/ui/config.rs @@ -61,6 +61,7 @@ pub struct Emulation { pub overclock: bool, pub disable_expansion_pak: bool, pub usb: bool, + pub rewind: bool, } #[derive(serde::Serialize, serde::Deserialize)] @@ -174,6 +175,7 @@ impl Config { overclock: false, disable_expansion_pak: false, usb: false, + rewind: false, }, rom_dir: std::path::PathBuf::new(), recent_roms: Vec::new(), diff --git a/src/ui/gui.rs b/src/ui/gui.rs index 0f702eed..edf7037b 100644 --- a/src/ui/gui.rs +++ b/src/ui/gui.rs @@ -175,6 +175,7 @@ fn settings_window(app: &AppWindow, config: &ui::config::Config) { app.set_overclock_n64_cpu(config.emulation.overclock); app.set_disable_expansion_pak(config.emulation.disable_expansion_pak); app.set_emulate_usb(config.emulation.usb); + app.set_rewind(config.emulation.rewind); let combobox_value = match config.video.upscale { 1 => 0, 2 => 1, @@ -446,6 +447,7 @@ pub fn save_settings(app: &AppWindow) { config.emulation.overclock = app.get_overclock_n64_cpu(); config.emulation.disable_expansion_pak = app.get_disable_expansion_pak(); config.emulation.usb = app.get_emulate_usb(); + config.emulation.rewind = app.get_rewind(); let upscale_values = [1, 2, 4, 8]; config.video.upscale = upscale_values[app.get_resolution() as usize]; diff --git a/src/ui/gui/appwindow.slint b/src/ui/gui/appwindow.slint index 60413151..a4261b2e 100644 --- a/src/ui/gui/appwindow.slint +++ b/src/ui/gui/appwindow.slint @@ -74,6 +74,7 @@ export component AppWindow inherits Window { in-out property disable_expansion_pak <=> SettingsData.disable_expansion_pak; in-out property resolution <=> SettingsData.resolution; in-out property emulate_usb <=> SettingsData.emulate_usb; + in-out property rewind <=> SettingsData.rewind; in-out property emulate_vru <=> SettingsData.emulate_vru; in-out property rom_dir <=> SettingsData.rom_dir; in-out property controller_enabled <=> ControllerData.controller_enabled; diff --git a/src/ui/gui/settings_page.slint b/src/ui/gui/settings_page.slint index f0e5517b..f00aaef7 100644 --- a/src/ui/gui/settings_page.slint +++ b/src/ui/gui/settings_page.slint @@ -15,6 +15,7 @@ export global SettingsData { in-out property disable_expansion_pak; in-out property resolution; in-out property emulate_usb; + in-out property rewind; in-out property emulate_vru; in-out property rom_dir; } @@ -112,6 +113,14 @@ export component Settings inherits Page { } } + Switch { + text: @tr("Enable Rewind"); + checked: SettingsData.rewind; + toggled => { + SettingsData.rewind = self.checked; + } + } + Switch { text: @tr("Emulate UNFLoader"); checked: SettingsData.emulate_usb; diff --git a/src/ui/input.rs b/src/ui/input.rs index d1d5f6f3..25bd4aec 100644 --- a/src/ui/input.rs +++ b/src/ui/input.rs @@ -489,6 +489,17 @@ fn handle_hotkeys(keys: u32, last_key_state: u32) { }) }; } + if keys & (1 << L_CBUTTON) != 0 && last_key_state & (1 << L_CBUTTON) == 0 { + unsafe { + sdl3_sys::events::SDL_PushEvent(&mut sdl3_sys::events::SDL_Event { + user: sdl3_sys::events::SDL_UserEvent { + r#type: u32::from(sdl3_sys::events::SDL_EVENT_USER), + code: 5, //load rewind + ..Default::default() + }, + }) + }; + } } pub fn get(ui: &mut ui::Ui, channel: usize, vi_counter: u64) -> InputData { diff --git a/src/ui/video.rs b/src/ui/video.rs index 70a1c34a..01daff06 100644 --- a/src/ui/video.rs +++ b/src/ui/video.rs @@ -175,16 +175,19 @@ pub fn load_state(device: &mut device::Device, rdp_state: *const u8) { } } -pub fn pause_loop(frame_time: f64) { +pub fn pause_loop(ui: &mut ui::Ui, frame_time: f64) { let mut paused = true; let mut frame_advance = false; + let mut pause_counter = 0; while paused && !frame_advance { std::thread::sleep(std::time::Duration::from_secs_f64(frame_time)); + ui::input::get(ui, 0, pause_counter); // to gather hotkey input unsafe { sdl3_sys::events::SDL_PumpEvents() }; retroachievements::do_idle(); let callback = unsafe { rdp_check_callback() }; paused = callback.paused; frame_advance = callback.frame_advance; + pause_counter += 1; } } @@ -194,9 +197,12 @@ pub fn check_callback(device: &mut device::Device) -> (bool, bool) { device.cpu.running = callback.emu_running; if device.netplay.is_none() { if callback.save_state { - device.save_state = true; + device.savestate.save_state = true; } else if callback.load_state { - device.load_state = true; + device.savestate.load_state = true; + } + if callback.load_rewind && device.ui.config.emulation.rewind { + device.savestate.load_rewind = true; } if callback.reset_game { device.cpu.cop0.regs[device::cop0::COP0_CAUSE_REG] |= device::cop0::COP0_CAUSE_IP4;