gemba 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/THIRD_PARTY_NOTICES +37 -2
- data/assets/placeholder_boxart.png +0 -0
- data/bin/gemba +2 -2
- data/ext/gemba/extconf.rb +23 -1
- data/ext/gemba/gemba_ext.c +436 -2
- data/ext/gemba/gemba_ext.h +2 -0
- data/gemba.gemspec +5 -3
- data/lib/gemba/achievements/achievement.rb +23 -0
- data/lib/gemba/achievements/backend.rb +186 -0
- data/lib/gemba/achievements/cache.rb +70 -0
- data/lib/gemba/achievements/credentials_presenter.rb +142 -0
- data/lib/gemba/achievements/fake_backend.rb +205 -0
- data/lib/gemba/achievements/null_backend.rb +11 -0
- data/lib/gemba/achievements/offline_backend.rb +168 -0
- data/lib/gemba/achievements/retro_achievements/backend.rb +453 -0
- data/lib/gemba/achievements/retro_achievements/cli_sync_requester.rb +64 -0
- data/lib/gemba/achievements/retro_achievements/ping_worker.rb +27 -0
- data/lib/gemba/achievements.rb +19 -0
- data/lib/gemba/achievements_window.rb +556 -0
- data/lib/gemba/app_controller.rb +1015 -0
- data/lib/gemba/bios.rb +54 -0
- data/lib/gemba/boxart_fetcher/libretro_backend.rb +39 -0
- data/lib/gemba/boxart_fetcher/null_backend.rb +12 -0
- data/lib/gemba/boxart_fetcher.rb +79 -0
- data/lib/gemba/bus_emitter.rb +13 -0
- data/lib/gemba/child_window.rb +24 -1
- data/lib/gemba/cli/commands/config_cmd.rb +83 -0
- data/lib/gemba/cli/commands/decode.rb +154 -0
- data/lib/gemba/cli/commands/patch.rb +78 -0
- data/lib/gemba/cli/commands/play.rb +78 -0
- data/lib/gemba/cli/commands/record.rb +114 -0
- data/lib/gemba/cli/commands/replay.rb +161 -0
- data/lib/gemba/cli/commands/retro_achievements.rb +213 -0
- data/lib/gemba/cli/commands/version.rb +22 -0
- data/lib/gemba/cli.rb +52 -364
- data/lib/gemba/config.rb +134 -1
- data/lib/gemba/data/gb_games.json +1 -0
- data/lib/gemba/data/gb_md5.json +1 -0
- data/lib/gemba/data/gba_games.json +1 -0
- data/lib/gemba/data/gba_md5.json +1 -0
- data/lib/gemba/data/gbc_games.json +1 -0
- data/lib/gemba/data/gbc_md5.json +1 -0
- data/lib/gemba/emulator_frame.rb +1060 -0
- data/lib/gemba/event_bus.rb +48 -0
- data/lib/gemba/frame_stack.rb +60 -0
- data/lib/gemba/game_index.rb +84 -0
- data/lib/gemba/game_picker_frame.rb +268 -0
- data/lib/gemba/gamepad_map.rb +103 -0
- data/lib/gemba/headless.rb +6 -5
- data/lib/gemba/headless_player.rb +33 -3
- data/lib/gemba/help_window.rb +61 -0
- data/lib/gemba/hotkey_map.rb +3 -1
- data/lib/gemba/input_recorder.rb +107 -0
- data/lib/gemba/input_replayer.rb +119 -0
- data/lib/gemba/keyboard_map.rb +90 -0
- data/lib/gemba/locales/en.yml +97 -5
- data/lib/gemba/locales/ja.yml +97 -5
- data/lib/gemba/main_window.rb +56 -0
- data/lib/gemba/modal_stack.rb +81 -0
- data/lib/gemba/patcher_window.rb +223 -0
- data/lib/gemba/platform/gb.rb +21 -0
- data/lib/gemba/platform/gba.rb +21 -0
- data/lib/gemba/platform/gbc.rb +23 -0
- data/lib/gemba/platform.rb +20 -0
- data/lib/gemba/platform_open.rb +19 -0
- data/lib/gemba/recorder.rb +4 -3
- data/lib/gemba/replay_player.rb +691 -0
- data/lib/gemba/rom_info.rb +57 -0
- data/lib/gemba/rom_info_window.rb +16 -3
- data/lib/gemba/rom_library.rb +106 -0
- data/lib/gemba/rom_overrides.rb +47 -0
- data/lib/gemba/rom_patcher/bps.rb +161 -0
- data/lib/gemba/rom_patcher/ips.rb +101 -0
- data/lib/gemba/rom_patcher/ups.rb +118 -0
- data/lib/gemba/rom_patcher.rb +109 -0
- data/lib/gemba/{rom_loader.rb → rom_resolver.rb} +7 -6
- data/lib/gemba/runtime.rb +59 -26
- data/lib/gemba/save_state_manager.rb +4 -7
- data/lib/gemba/save_state_picker.rb +17 -4
- data/lib/gemba/session_logger.rb +64 -0
- data/lib/gemba/settings/audio_tab.rb +77 -0
- data/lib/gemba/settings/gamepad_tab.rb +351 -0
- data/lib/gemba/settings/hotkeys_tab.rb +259 -0
- data/lib/gemba/settings/paths.rb +11 -0
- data/lib/gemba/settings/recording_tab.rb +83 -0
- data/lib/gemba/settings/save_states_tab.rb +91 -0
- data/lib/gemba/settings/system_tab.rb +362 -0
- data/lib/gemba/settings/video_tab.rb +318 -0
- data/lib/gemba/settings_window.rb +162 -1036
- data/lib/gemba/version.rb +1 -1
- data/lib/gemba/virtual_keyboard.rb +19 -0
- data/lib/gemba.rb +2 -12
- data/test/achievements_window/test_bulk_sync.rb +218 -0
- data/test/achievements_window/test_bus_events.rb +125 -0
- data/test/achievements_window/test_close_confirmation.rb +201 -0
- data/test/achievements_window/test_initial_state.rb +164 -0
- data/test/achievements_window/test_sorting.rb +227 -0
- data/test/achievements_window/test_tree_rendering.rb +133 -0
- data/test/fixtures/fake_bios.bin +0 -0
- data/test/fixtures/pong.gba +0 -0
- data/test/fixtures/test.gb +0 -0
- data/test/fixtures/test.gbc +0 -0
- data/test/fixtures/test_quicksave.ss +0 -0
- data/test/screenshots/no_focus.png +0 -0
- data/test/shared/teek_test_worker.rb +17 -1
- data/test/shared/tk_test_helper.rb +91 -4
- data/test/support/achievements_window_helpers.rb +18 -0
- data/test/support/fake_core.rb +25 -0
- data/test/support/fake_ra_runtime.rb +74 -0
- data/test/support/fake_requester.rb +68 -0
- data/test/support/player_helpers.rb +20 -5
- data/test/test_achievement.rb +32 -0
- data/test/{test_player.rb → test_app_controller.rb} +353 -85
- data/test/test_bios.rb +123 -0
- data/test/test_boxart_fetcher.rb +150 -0
- data/test/test_cli.rb +17 -265
- data/test/test_cli_config.rb +64 -0
- data/test/test_cli_decode.rb +97 -0
- data/test/test_cli_patch.rb +58 -0
- data/test/test_cli_play.rb +213 -0
- data/test/test_cli_ra.rb +175 -0
- data/test/test_cli_record.rb +69 -0
- data/test/test_cli_replay.rb +72 -0
- data/test/test_cli_sync_requester.rb +152 -0
- data/test/test_cli_version.rb +27 -0
- data/test/test_config.rb +2 -3
- data/test/test_config_ra.rb +69 -0
- data/test/test_core.rb +62 -1
- data/test/test_credentials_presenter.rb +192 -0
- data/test/test_event_bus.rb +100 -0
- data/test/test_fake_backend_achievements.rb +130 -0
- data/test/test_fake_backend_auth.rb +68 -0
- data/test/test_game_index.rb +77 -0
- data/test/test_game_picker_frame.rb +310 -0
- data/test/test_gamepad_map.rb +1 -3
- data/test/test_headless_player.rb +17 -3
- data/test/test_help_window.rb +82 -0
- data/test/test_hotkey_map.rb +22 -1
- data/test/test_input_recorder.rb +179 -0
- data/test/test_input_replay_determinism.rb +113 -0
- data/test/test_input_replayer.rb +162 -0
- data/test/test_keyboard_map.rb +1 -3
- data/test/test_libretro_backend.rb +41 -0
- data/test/test_locale.rb +1 -1
- data/test/test_logging.rb +123 -0
- data/test/test_null_backend.rb +42 -0
- data/test/test_offline_backend.rb +116 -0
- data/test/test_overlay_renderer.rb +1 -1
- data/test/test_platform.rb +149 -0
- data/test/test_ra_backend.rb +313 -0
- data/test/test_ra_backend_unlock_gate.rb +56 -0
- data/test/test_recorder.rb +0 -3
- data/test/test_replay_player.rb +316 -0
- data/test/test_rom_info.rb +149 -0
- data/test/test_rom_overrides.rb +86 -0
- data/test/test_rom_patcher.rb +382 -0
- data/test/{test_rom_loader.rb → test_rom_resolver.rb} +25 -26
- data/test/test_save_state_manager.rb +2 -4
- data/test/test_settings_audio.rb +107 -0
- data/test/test_settings_hotkeys.rb +83 -66
- data/test/test_settings_recording.rb +49 -0
- data/test/test_settings_save_states.rb +97 -0
- data/test/test_settings_system.rb +133 -0
- data/test/test_settings_video.rb +450 -0
- data/test/test_settings_window.rb +76 -507
- data/test/test_tip_service.rb +6 -6
- data/test/test_toast_overlay.rb +1 -1
- data/test/test_virtual_events.rb +156 -0
- data/test/test_virtual_keyboard.rb +1 -1
- data/vendor/rcheevos/CHANGELOG.md +495 -0
- data/vendor/rcheevos/LICENSE +21 -0
- data/vendor/rcheevos/Package.swift +33 -0
- data/vendor/rcheevos/README.md +67 -0
- data/vendor/rcheevos/include/module.modulemap +70 -0
- data/vendor/rcheevos/include/rc_api_editor.h +296 -0
- data/vendor/rcheevos/include/rc_api_info.h +280 -0
- data/vendor/rcheevos/include/rc_api_request.h +77 -0
- data/vendor/rcheevos/include/rc_api_runtime.h +417 -0
- data/vendor/rcheevos/include/rc_api_user.h +262 -0
- data/vendor/rcheevos/include/rc_client.h +877 -0
- data/vendor/rcheevos/include/rc_client_raintegration.h +101 -0
- data/vendor/rcheevos/include/rc_consoles.h +138 -0
- data/vendor/rcheevos/include/rc_error.h +59 -0
- data/vendor/rcheevos/include/rc_export.h +100 -0
- data/vendor/rcheevos/include/rc_hash.h +200 -0
- data/vendor/rcheevos/include/rc_runtime.h +148 -0
- data/vendor/rcheevos/include/rc_runtime_types.h +452 -0
- data/vendor/rcheevos/include/rc_util.h +51 -0
- data/vendor/rcheevos/include/rcheevos.h +8 -0
- data/vendor/rcheevos/src/rapi/rc_api_common.c +1379 -0
- data/vendor/rcheevos/src/rapi/rc_api_common.h +88 -0
- data/vendor/rcheevos/src/rapi/rc_api_editor.c +625 -0
- data/vendor/rcheevos/src/rapi/rc_api_info.c +587 -0
- data/vendor/rcheevos/src/rapi/rc_api_runtime.c +901 -0
- data/vendor/rcheevos/src/rapi/rc_api_user.c +483 -0
- data/vendor/rcheevos/src/rc_client.c +6941 -0
- data/vendor/rcheevos/src/rc_client_external.c +281 -0
- data/vendor/rcheevos/src/rc_client_external.h +177 -0
- data/vendor/rcheevos/src/rc_client_external_versions.h +171 -0
- data/vendor/rcheevos/src/rc_client_internal.h +409 -0
- data/vendor/rcheevos/src/rc_client_raintegration.c +566 -0
- data/vendor/rcheevos/src/rc_client_raintegration_internal.h +61 -0
- data/vendor/rcheevos/src/rc_client_types.natvis +396 -0
- data/vendor/rcheevos/src/rc_compat.c +251 -0
- data/vendor/rcheevos/src/rc_compat.h +121 -0
- data/vendor/rcheevos/src/rc_libretro.c +915 -0
- data/vendor/rcheevos/src/rc_libretro.h +98 -0
- data/vendor/rcheevos/src/rc_util.c +199 -0
- data/vendor/rcheevos/src/rc_version.c +11 -0
- data/vendor/rcheevos/src/rc_version.h +32 -0
- data/vendor/rcheevos/src/rcheevos/alloc.c +312 -0
- data/vendor/rcheevos/src/rcheevos/condition.c +754 -0
- data/vendor/rcheevos/src/rcheevos/condset.c +777 -0
- data/vendor/rcheevos/src/rcheevos/consoleinfo.c +1215 -0
- data/vendor/rcheevos/src/rcheevos/format.c +330 -0
- data/vendor/rcheevos/src/rcheevos/lboard.c +287 -0
- data/vendor/rcheevos/src/rcheevos/memref.c +805 -0
- data/vendor/rcheevos/src/rcheevos/operand.c +607 -0
- data/vendor/rcheevos/src/rcheevos/rc_internal.h +390 -0
- data/vendor/rcheevos/src/rcheevos/rc_runtime_types.natvis +541 -0
- data/vendor/rcheevos/src/rcheevos/rc_validate.c +1406 -0
- data/vendor/rcheevos/src/rcheevos/rc_validate.h +18 -0
- data/vendor/rcheevos/src/rcheevos/richpresence.c +922 -0
- data/vendor/rcheevos/src/rcheevos/runtime.c +852 -0
- data/vendor/rcheevos/src/rcheevos/runtime_progress.c +1073 -0
- data/vendor/rcheevos/src/rcheevos/trigger.c +344 -0
- data/vendor/rcheevos/src/rcheevos/value.c +935 -0
- data/vendor/rcheevos/src/rhash/aes.c +480 -0
- data/vendor/rcheevos/src/rhash/aes.h +49 -0
- data/vendor/rcheevos/src/rhash/cdreader.c +838 -0
- data/vendor/rcheevos/src/rhash/hash.c +1402 -0
- data/vendor/rcheevos/src/rhash/hash_disc.c +1340 -0
- data/vendor/rcheevos/src/rhash/hash_encrypted.c +566 -0
- data/vendor/rcheevos/src/rhash/hash_rom.c +426 -0
- data/vendor/rcheevos/src/rhash/hash_zip.c +460 -0
- data/vendor/rcheevos/src/rhash/md5.c +382 -0
- data/vendor/rcheevos/src/rhash/md5.h +91 -0
- data/vendor/rcheevos/src/rhash/rc_hash_internal.h +116 -0
- data/vendor/rcheevos/test/libretro.h +205 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_common.c +941 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_editor.c +931 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_info.c +545 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_runtime.c +2213 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_user.c +998 -0
- data/vendor/rcheevos/test/rcheevos/mock_memory.h +32 -0
- data/vendor/rcheevos/test/rcheevos/test_condition.c +570 -0
- data/vendor/rcheevos/test/rcheevos/test_condset.c +5170 -0
- data/vendor/rcheevos/test/rcheevos/test_consoleinfo.c +203 -0
- data/vendor/rcheevos/test/rcheevos/test_format.c +112 -0
- data/vendor/rcheevos/test/rcheevos/test_lboard.c +746 -0
- data/vendor/rcheevos/test/rcheevos/test_memref.c +520 -0
- data/vendor/rcheevos/test/rcheevos/test_operand.c +692 -0
- data/vendor/rcheevos/test/rcheevos/test_rc_validate.c +502 -0
- data/vendor/rcheevos/test/rcheevos/test_richpresence.c +1564 -0
- data/vendor/rcheevos/test/rcheevos/test_runtime.c +1667 -0
- data/vendor/rcheevos/test/rcheevos/test_runtime_progress.c +1821 -0
- data/vendor/rcheevos/test/rcheevos/test_timing.c +166 -0
- data/vendor/rcheevos/test/rcheevos/test_trigger.c +2521 -0
- data/vendor/rcheevos/test/rcheevos/test_value.c +870 -0
- data/vendor/rcheevos/test/rcheevos-test.sln +46 -0
- data/vendor/rcheevos/test/rcheevos-test.vcxproj +239 -0
- data/vendor/rcheevos/test/rcheevos-test.vcxproj.filters +335 -0
- data/vendor/rcheevos/test/rhash/data.c +657 -0
- data/vendor/rcheevos/test/rhash/data.h +32 -0
- data/vendor/rcheevos/test/rhash/mock_filereader.c +236 -0
- data/vendor/rcheevos/test/rhash/mock_filereader.h +31 -0
- data/vendor/rcheevos/test/rhash/test_cdreader.c +920 -0
- data/vendor/rcheevos/test/rhash/test_hash.c +310 -0
- data/vendor/rcheevos/test/rhash/test_hash_disc.c +1450 -0
- data/vendor/rcheevos/test/rhash/test_hash_rom.c +899 -0
- data/vendor/rcheevos/test/rhash/test_hash_zip.c +551 -0
- data/vendor/rcheevos/test/test.c +113 -0
- data/vendor/rcheevos/test/test_framework.h +205 -0
- data/vendor/rcheevos/test/test_rc_client.c +10509 -0
- data/vendor/rcheevos/test/test_rc_client_external.c +2197 -0
- data/vendor/rcheevos/test/test_rc_client_raintegration.c +441 -0
- data/vendor/rcheevos/test/test_rc_libretro.c +952 -0
- data/vendor/rcheevos/test/test_types.natvis +9 -0
- data/vendor/rcheevos/validator/validator.c +658 -0
- data/vendor/rcheevos/validator/validator.vcxproj +152 -0
- data/vendor/rcheevos/validator/validator.vcxproj.filters +82 -0
- metadata +274 -11
- data/lib/gemba/input_mappings.rb +0 -214
- data/lib/gemba/player.rb +0 -1525
data/lib/gemba/locales/ja.yml
CHANGED
|
@@ -5,8 +5,12 @@ menu:
|
|
|
5
5
|
quit: "終了"
|
|
6
6
|
settings: "設定"
|
|
7
7
|
view: "表示"
|
|
8
|
+
game_library: "ゲームライブラリ"
|
|
8
9
|
fullscreen: "フルスクリーン"
|
|
9
10
|
rom_info: "ROM情報…"
|
|
11
|
+
achievements: "アチーブメント…"
|
|
12
|
+
open_logs_dir: "ログフォルダを開く"
|
|
13
|
+
patch_rom: "ROMをパッチ…"
|
|
10
14
|
emulation: "エミュレーション"
|
|
11
15
|
pause: "一時停止"
|
|
12
16
|
resume: "再開"
|
|
@@ -14,8 +18,10 @@ menu:
|
|
|
14
18
|
quick_save: "クイックセーブ"
|
|
15
19
|
quick_load: "クイックロード"
|
|
16
20
|
save_states: "ステートセーブ…"
|
|
17
|
-
start_recording: "
|
|
18
|
-
stop_recording: "
|
|
21
|
+
start_recording: "キャプチャ開始"
|
|
22
|
+
stop_recording: "キャプチャ停止"
|
|
23
|
+
start_input_recording: "入力記録開始"
|
|
24
|
+
stop_input_recording: "入力記録停止"
|
|
19
25
|
|
|
20
26
|
toast:
|
|
21
27
|
save_blocked: "セーブ制限中(間隔が短すぎます)"
|
|
@@ -32,12 +38,18 @@ toast:
|
|
|
32
38
|
no_rewind: "巻き戻しデータがありません"
|
|
33
39
|
paused: "一時停止"
|
|
34
40
|
waiting_for: "{label}を待機中…"
|
|
35
|
-
recording_started: "
|
|
36
|
-
recording_stopped: "{frames}
|
|
41
|
+
recording_started: "キャプチャを開始しました"
|
|
42
|
+
recording_stopped: "{frames}フレームをキャプチャしました"
|
|
43
|
+
input_recording_started: "入力記録を開始しました"
|
|
44
|
+
input_recording_stopped: "入力を記録しました({frames}フレーム)"
|
|
37
45
|
|
|
38
46
|
dialog:
|
|
39
47
|
game_running_title: "ゲーム実行中"
|
|
40
48
|
game_running_msg: "別のゲームが実行中です。{name}に切り替えますか?"
|
|
49
|
+
return_to_library_title: "ゲームライブラリに戻る"
|
|
50
|
+
return_to_library_msg: "ゲームライブラリに戻りますか?保存されていない進行状況は失われます。"
|
|
51
|
+
quit_title: "Gembaを終了"
|
|
52
|
+
quit_msg: "ゲームが実行中です。終了しますか?保存されていない進行状況は失われます。"
|
|
41
53
|
drop_error_title: "ドロップエラー"
|
|
42
54
|
drop_single_file_only: "ROMファイルを1つだけドロップしてください。"
|
|
43
55
|
drop_unsupported_type: "対応していないファイル形式: {ext}"
|
|
@@ -51,6 +63,8 @@ dialog:
|
|
|
51
63
|
reset_hotkeys_title: "ホットキーをリセット"
|
|
52
64
|
reset_hotkeys_msg: "すべてのホットキー設定をデフォルトに戻しますか?"
|
|
53
65
|
key_conflict_title: "キーの競合"
|
|
66
|
+
cancel_bulk_sync_title: "同期をキャンセル?"
|
|
67
|
+
cancel_bulk_sync_msg: "一括同期が実行中です。閉じるとキャンセルされます — 手動で再同期が必要になります。"
|
|
54
68
|
|
|
55
69
|
settings:
|
|
56
70
|
video: "映像"
|
|
@@ -91,7 +105,9 @@ settings:
|
|
|
91
105
|
hk_save_states: "ステートセーブ"
|
|
92
106
|
hk_screenshot: "スクリーンショット"
|
|
93
107
|
hk_rewind: "巻き戻し"
|
|
94
|
-
hk_record: "
|
|
108
|
+
hk_record: "キャプチャ"
|
|
109
|
+
hk_input_record: "入力記録"
|
|
110
|
+
hk_open_rom: "ROM を開く"
|
|
95
111
|
hk_reset_defaults: "デフォルトに戻す"
|
|
96
112
|
pixel_filter: "ピクセルフィルタ:"
|
|
97
113
|
filter_nearest: "ニアレストネイバー"
|
|
@@ -111,11 +127,39 @@ settings:
|
|
|
111
127
|
tip_per_game: "ROM毎に映像・音声・ステートセーブの設定を個別に保存します。"
|
|
112
128
|
tip_turbo_speed: "早送りホットキー使用時の速度。"
|
|
113
129
|
tip_toast_duration: "画面上の通知の表示時間。"
|
|
130
|
+
system: "システム"
|
|
131
|
+
bios_header: "GBA BIOS"
|
|
132
|
+
bios_path: "BIOSファイル:"
|
|
133
|
+
bios_browse: "参照…"
|
|
134
|
+
bios_clear: "クリア"
|
|
135
|
+
bios_not_set: "未設定 — 内蔵HLEを使用(ほとんどのゲームに推奨)"
|
|
136
|
+
bios_not_found: "ファイルが見つかりません"
|
|
137
|
+
skip_bios: "起動アニメーションをスキップ"
|
|
138
|
+
tip_skip_bios: "ゲームボーイアドバンスのロゴ画面をスキップして\n直接ゲームを開始します。実BIOSが必要です。"
|
|
139
|
+
retroachievements: "実績"
|
|
140
|
+
ra_enabled: "RetroAchievementsを有効にする"
|
|
141
|
+
ra_credentials: "アカウント"
|
|
142
|
+
ra_username_placeholder: "ユーザー名:"
|
|
143
|
+
ra_token_placeholder: "パスワード:"
|
|
144
|
+
ra_rich_presence: "リッチプレゼンス(ゲームごと)"
|
|
145
|
+
ra_hardcore: "ハードコアモード(セーブステートと巻き戻し無効)"
|
|
146
|
+
tip_ra_password: "パスワードはAPIトークン取得のみに使用され、保存されません。"
|
|
147
|
+
ra_login: "ログイン"
|
|
148
|
+
ra_verify: "トークン確認"
|
|
149
|
+
ra_logout: "ログアウト"
|
|
150
|
+
ra_reset: "リセット"
|
|
151
|
+
ra_reset_title: "RetroAchievementsをリセット"
|
|
152
|
+
ra_reset_confirm: "保存済みの認証情報を削除してログアウトしますか?"
|
|
153
|
+
ra_test_ok: "接続OK ✓"
|
|
154
|
+
ra_disabled: "RetroAchievementsは無効です"
|
|
155
|
+
ra_not_logged_in: "未ログイン"
|
|
156
|
+
ra_logged_in_as: "{username}でログイン中"
|
|
114
157
|
recording: "録画"
|
|
115
158
|
recording_compression: "圧縮レベル:"
|
|
116
159
|
tip_recording_compression: ".grecファイルのzlib圧縮レベル。\n1 = 最速(デフォルト)、6以上は効果が小さくなります。"
|
|
117
160
|
recordings_folder: "録画フォルダ:"
|
|
118
161
|
open_recordings_folder: "録画フォルダを開く…"
|
|
162
|
+
open_replay_player: "入力リプレイを開く…"
|
|
119
163
|
gp_a: "A"
|
|
120
164
|
gp_b: "B"
|
|
121
165
|
gp_l: "L"
|
|
@@ -134,6 +178,13 @@ picker:
|
|
|
134
178
|
slot: "スロット{n}"
|
|
135
179
|
close: "閉じる"
|
|
136
180
|
|
|
181
|
+
game_picker:
|
|
182
|
+
menu:
|
|
183
|
+
play: "プレイ"
|
|
184
|
+
quick_load: "クイックロード"
|
|
185
|
+
set_boxart: "カスタム画像を設定"
|
|
186
|
+
remove: "ライブラリから削除"
|
|
187
|
+
|
|
137
188
|
rom_info:
|
|
138
189
|
title: "ROM情報"
|
|
139
190
|
field_title: "タイトル:"
|
|
@@ -148,6 +199,29 @@ rom_info:
|
|
|
148
199
|
close: "閉じる"
|
|
149
200
|
na: "N/A"
|
|
150
201
|
|
|
202
|
+
replay:
|
|
203
|
+
open_recording: "入力記録を開く…"
|
|
204
|
+
replay_player: "入力リプレイ"
|
|
205
|
+
ended: "リプレイ完了({frames}フレーム)"
|
|
206
|
+
empty_hint: "入力記録を開く (Cmd+O)"
|
|
207
|
+
|
|
208
|
+
patcher:
|
|
209
|
+
title: "ROMをパッチ"
|
|
210
|
+
rom_label: "ROMファイル:"
|
|
211
|
+
patch_label: "パッチファイル:"
|
|
212
|
+
outdir_label: "出力フォルダ:"
|
|
213
|
+
browse: "参照…"
|
|
214
|
+
apply: "パッチを適用"
|
|
215
|
+
working: "パッチ適用中…"
|
|
216
|
+
done: "完了 →"
|
|
217
|
+
err_missing_fields: "すべての項目を入力してください。"
|
|
218
|
+
err_rom_not_found: "ROMファイルが見つかりません。"
|
|
219
|
+
err_patch_not_found: "パッチファイルが見つかりません。"
|
|
220
|
+
err_failed: "パッチ失敗:"
|
|
221
|
+
overwrite_title: "ファイルが存在します"
|
|
222
|
+
overwrite_msg: "{path} は既に存在します。上書きしますか?"
|
|
223
|
+
thread_mode_warn: "注意: Ruby < 4 ではプログレスが止まって見える場合があります"
|
|
224
|
+
|
|
151
225
|
player:
|
|
152
226
|
open_rom_hint: "ファイル > ROMを開く…"
|
|
153
227
|
fps: "{fps} fps"
|
|
@@ -155,3 +229,21 @@ player:
|
|
|
155
229
|
ff_max: ">> 最大"
|
|
156
230
|
none: "(なし)"
|
|
157
231
|
clear: "クリア"
|
|
232
|
+
|
|
233
|
+
achievements:
|
|
234
|
+
title: "アチーブメント"
|
|
235
|
+
game_label: "ゲーム:"
|
|
236
|
+
sync: "同期"
|
|
237
|
+
name_col: "アチーブメント"
|
|
238
|
+
points_col: "ポイント"
|
|
239
|
+
earned_col: "取得日時"
|
|
240
|
+
none: "アチーブメントなし"
|
|
241
|
+
earned_label: "{earned} / {total} 取得"
|
|
242
|
+
sync_pending: "同期中…"
|
|
243
|
+
sync_failed: "同期に失敗しました"
|
|
244
|
+
sync_no_game: "ゲームが読み込まれていません"
|
|
245
|
+
sync_timeout: "同期がタイムアウトしました"
|
|
246
|
+
not_logged_in: "ログインしていません"
|
|
247
|
+
include_unofficial: "非公式を含む"
|
|
248
|
+
bulk_syncing: "{title} を同期中 ({n}/{total})…"
|
|
249
|
+
bulk_sync_done: "{count} ゲームを同期しました"
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Gemba
|
|
5
|
+
# Pure Tk shell — creates the app window and hosts a FrameStack.
|
|
6
|
+
#
|
|
7
|
+
# MainWindow knows nothing about ROMs, emulation, menus, or config.
|
|
8
|
+
# It provides geometry/title/fullscreen primitives that the AppController
|
|
9
|
+
# drives. Its only structural contribution is the FrameStack, which
|
|
10
|
+
# manages show/hide transitions to prevent visual flash (FOUC).
|
|
11
|
+
class MainWindow
|
|
12
|
+
attr_reader :app, :frame_stack
|
|
13
|
+
|
|
14
|
+
def initialize
|
|
15
|
+
@app = Teek::App.new
|
|
16
|
+
@app.show
|
|
17
|
+
@frame_stack = FrameStack.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def set_title(title)
|
|
21
|
+
@app.set_window_title(title)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def set_geometry(w, h)
|
|
25
|
+
@app.set_window_geometry("#{w}x#{h}")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def set_aspect(numer, denom)
|
|
29
|
+
@app.command(:wm, 'aspect', '.', numer, denom, numer, denom)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def set_minsize(w, h)
|
|
33
|
+
@app.command(:wm, 'minsize', '.', w, h)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def reset_minsize
|
|
37
|
+
@app.command(:wm, 'minsize', '.', 0, 0)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def reset_aspect_ratio
|
|
41
|
+
@app.command(:wm, 'aspect', '.', '', '', '', '')
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def set_timer_speed(ms)
|
|
45
|
+
@app.interp.thread_timer_ms = ms
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def fullscreen=(val)
|
|
49
|
+
@app.command(:wm, 'attributes', '.', '-fullscreen', val ? 1 : 0)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def mainloop
|
|
53
|
+
@app.mainloop
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gemba
|
|
4
|
+
# Push/pop stack for modal child windows.
|
|
5
|
+
#
|
|
6
|
+
# One modal can push another (e.g. Settings → Replay Player) and the
|
|
7
|
+
# previous modal is automatically re-shown on pop.
|
|
8
|
+
#
|
|
9
|
+
# Windows must implement the ModalWindow protocol:
|
|
10
|
+
# show_modal(**args) — reveal the window (deiconify, grab, position)
|
|
11
|
+
# withdraw — hide the window (release grab, withdraw — NO callback)
|
|
12
|
+
#
|
|
13
|
+
# @example
|
|
14
|
+
# stack = ModalStack.new(
|
|
15
|
+
# on_enter: ->(name) { pause_emulation },
|
|
16
|
+
# on_exit: -> { unpause_emulation },
|
|
17
|
+
# on_focus_change: ->(name) { update_toast(name) },
|
|
18
|
+
# )
|
|
19
|
+
# stack.push(:settings, @settings_window, show_args: { tab: :video })
|
|
20
|
+
# stack.push(:replay, @replay_player) # settings auto-withdrawn
|
|
21
|
+
# stack.pop # replay closed, settings re-shown
|
|
22
|
+
# stack.pop # settings closed, on_exit fired
|
|
23
|
+
class ModalStack
|
|
24
|
+
Entry = Data.define(:name, :window, :show_args)
|
|
25
|
+
|
|
26
|
+
# @param on_enter [Proc] called with (name) when stack goes empty → non-empty
|
|
27
|
+
# @param on_exit [Proc] called when stack goes non-empty → empty
|
|
28
|
+
# @param on_focus_change [Proc, nil] called with (name) whenever the top modal changes
|
|
29
|
+
def initialize(on_enter:, on_exit:, on_focus_change: nil)
|
|
30
|
+
@stack = []
|
|
31
|
+
@on_enter = on_enter
|
|
32
|
+
@on_exit = on_exit
|
|
33
|
+
@on_focus_change = on_focus_change
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# @return [Boolean] true if any modal is open
|
|
37
|
+
def active? = !@stack.empty?
|
|
38
|
+
|
|
39
|
+
# @return [Symbol, nil] name of the topmost modal, or nil
|
|
40
|
+
def current = @stack.last&.name
|
|
41
|
+
|
|
42
|
+
# @return [Integer] number of modals on the stack
|
|
43
|
+
def size = @stack.length
|
|
44
|
+
|
|
45
|
+
# Push a modal onto the stack.
|
|
46
|
+
#
|
|
47
|
+
# If another modal is on top, it is withdrawn (without callback).
|
|
48
|
+
# If the stack was empty, on_enter is fired (pause emulation, etc.).
|
|
49
|
+
#
|
|
50
|
+
# @param name [Symbol] identifier for the modal (e.g. :settings, :picker)
|
|
51
|
+
# @param window [#show_modal, #withdraw] the modal window object
|
|
52
|
+
# @param show_args [Hash] keyword arguments forwarded to window.show_modal
|
|
53
|
+
def push(name, window, show_args: {})
|
|
54
|
+
was_empty = @stack.empty?
|
|
55
|
+
|
|
56
|
+
# Withdraw current top without firing its on_dismiss
|
|
57
|
+
@stack.last&.window&.withdraw
|
|
58
|
+
|
|
59
|
+
@stack.push(Entry.new(name: name, window: window, show_args: show_args))
|
|
60
|
+
@on_enter.call(name) if was_empty
|
|
61
|
+
@on_focus_change&.call(name)
|
|
62
|
+
window.show_modal(**show_args)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Pop the current modal off the stack.
|
|
66
|
+
#
|
|
67
|
+
# If the stack still has entries, the previous modal is re-shown.
|
|
68
|
+
# If the stack is now empty, on_exit is fired (unpause emulation, etc.).
|
|
69
|
+
def pop
|
|
70
|
+
return unless (entry = @stack.pop)
|
|
71
|
+
entry.window.withdraw
|
|
72
|
+
|
|
73
|
+
if (prev = @stack.last)
|
|
74
|
+
@on_focus_change&.call(prev.name)
|
|
75
|
+
prev.window.show_modal(**prev.show_args)
|
|
76
|
+
else
|
|
77
|
+
@on_exit.call
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gemba
|
|
4
|
+
# Floating window for applying IPS/BPS/UPS patches to ROM files.
|
|
5
|
+
#
|
|
6
|
+
# Non-modal — no grab. Shows three file pickers (ROM, patch, output dir)
|
|
7
|
+
# and runs the patch in a background thread so the UI stays responsive.
|
|
8
|
+
class PatcherWindow
|
|
9
|
+
include ChildWindow
|
|
10
|
+
include Locale::Translatable
|
|
11
|
+
|
|
12
|
+
# Worker class for Ractor-based patching.
|
|
13
|
+
# Defined as a class so Ractor.shareable_proc never sees nested closures —
|
|
14
|
+
# the lambda and rescue are created at runtime inside the Ractor.
|
|
15
|
+
class PatchWorker
|
|
16
|
+
def call(t, d)
|
|
17
|
+
RomPatcher.patch(
|
|
18
|
+
rom_path: d[:rom],
|
|
19
|
+
patch_path: d[:patch],
|
|
20
|
+
out_path: d[:out],
|
|
21
|
+
on_progress: ->(pct) { t.yield(pct) }
|
|
22
|
+
)
|
|
23
|
+
t.yield({ ok: true, path: d[:out] })
|
|
24
|
+
rescue => e
|
|
25
|
+
t.yield({ ok: false, error: e.message })
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
TOP = '.patcher_window'
|
|
30
|
+
BG_MODE = (RUBY_VERSION >= '4.0' ? :ractor : :thread).freeze
|
|
31
|
+
|
|
32
|
+
VAR_ROM = '::gemba_patcher_rom'
|
|
33
|
+
VAR_PATCH = '::gemba_patcher_patch'
|
|
34
|
+
VAR_OUTDIR = '::gemba_patcher_outdir'
|
|
35
|
+
VAR_STATUS = '::gemba_patcher_status'
|
|
36
|
+
|
|
37
|
+
def initialize(app:)
|
|
38
|
+
@app = app
|
|
39
|
+
@callbacks = {}
|
|
40
|
+
build_toplevel(translate('patcher.title'), geometry: '540x220') { build_ui }
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def show = show_window(modal: false)
|
|
44
|
+
def hide = hide_window(modal: false)
|
|
45
|
+
def visible? = @app.tcl_eval("wm state #{TOP}") == 'normal'
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def build_ui
|
|
50
|
+
f = "#{TOP}.f"
|
|
51
|
+
@app.command('ttk::frame', f, padding: 12)
|
|
52
|
+
@app.command(:pack, f, fill: :both, expand: 1)
|
|
53
|
+
|
|
54
|
+
@app.set_variable(VAR_ROM, '')
|
|
55
|
+
@app.set_variable(VAR_PATCH, '')
|
|
56
|
+
@app.set_variable(VAR_OUTDIR, Config.default_patches_dir)
|
|
57
|
+
@app.set_variable(VAR_STATUS, '')
|
|
58
|
+
|
|
59
|
+
build_file_row(f, 'rom', translate('patcher.rom_label'),
|
|
60
|
+
"{{GBA ROMs} {.gba .zip}} {{All Files} *}")
|
|
61
|
+
build_file_row(f, 'patch', translate('patcher.patch_label'),
|
|
62
|
+
"{{Patch Files} {.ips .bps .ups}} {{All Files} *}")
|
|
63
|
+
build_dir_row(f, 'outdir', translate('patcher.outdir_label'), VAR_OUTDIR)
|
|
64
|
+
|
|
65
|
+
btn_row = "#{f}.btn_row"
|
|
66
|
+
@app.command('ttk::frame', btn_row)
|
|
67
|
+
@app.command(:pack, btn_row, fill: :x, pady: [10, 0])
|
|
68
|
+
|
|
69
|
+
@apply_btn = "#{btn_row}.apply"
|
|
70
|
+
@app.command('ttk::button', @apply_btn,
|
|
71
|
+
text: translate('patcher.apply'),
|
|
72
|
+
command: proc { apply_patch })
|
|
73
|
+
@app.command(:pack, @apply_btn, side: :left)
|
|
74
|
+
|
|
75
|
+
@progress_bar = "#{btn_row}.pb"
|
|
76
|
+
@app.command('ttk::progressbar', @progress_bar,
|
|
77
|
+
orient: :horizontal, length: 200,
|
|
78
|
+
mode: :determinate, maximum: 100)
|
|
79
|
+
@app.command(:pack, @progress_bar, side: :left, padx: [8, 0])
|
|
80
|
+
|
|
81
|
+
if BG_MODE == :thread
|
|
82
|
+
@app.command('ttk::label', "#{btn_row}.ruby_warn",
|
|
83
|
+
text: translate('patcher.thread_mode_warn'),
|
|
84
|
+
foreground: 'gray')
|
|
85
|
+
@app.command(:pack, "#{btn_row}.ruby_warn", side: :left, padx: [10, 0])
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
@app.command('ttk::label', "#{f}.status",
|
|
89
|
+
textvariable: VAR_STATUS,
|
|
90
|
+
wraplength: 500)
|
|
91
|
+
@app.command(:pack, "#{f}.status", fill: :x, pady: [6, 0])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def build_file_row(parent, name, label_text, filetypes_tcl)
|
|
95
|
+
row = "#{parent}.#{name}_row"
|
|
96
|
+
var = case name
|
|
97
|
+
when 'rom' then VAR_ROM
|
|
98
|
+
when 'patch' then VAR_PATCH
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
@app.command('ttk::frame', row)
|
|
102
|
+
@app.command(:pack, row, fill: :x, pady: 2)
|
|
103
|
+
|
|
104
|
+
@app.command('ttk::label', "#{row}.lbl", text: label_text, width: 10, anchor: :w)
|
|
105
|
+
@app.command(:grid, "#{row}.lbl", row: 0, column: 0, sticky: :w)
|
|
106
|
+
|
|
107
|
+
@app.command('ttk::entry', "#{row}.ent", textvariable: var, width: 48)
|
|
108
|
+
@app.command(:grid, "#{row}.ent", row: 0, column: 1, sticky: :ew, padx: [4, 4])
|
|
109
|
+
|
|
110
|
+
@app.command('ttk::button', "#{row}.btn",
|
|
111
|
+
text: translate('patcher.browse'),
|
|
112
|
+
command: proc { browse_file(var, filetypes_tcl) })
|
|
113
|
+
@app.command(:grid, "#{row}.btn", row: 0, column: 2)
|
|
114
|
+
@app.command(:grid, :columnconfigure, row, 1, weight: 1)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def build_dir_row(parent, name, label_text, var)
|
|
118
|
+
row = "#{parent}.#{name}_row"
|
|
119
|
+
@app.command('ttk::frame', row)
|
|
120
|
+
@app.command(:pack, row, fill: :x, pady: 2)
|
|
121
|
+
|
|
122
|
+
@app.command('ttk::label', "#{row}.lbl", text: label_text, width: 10, anchor: :w)
|
|
123
|
+
@app.command(:grid, "#{row}.lbl", row: 0, column: 0, sticky: :w)
|
|
124
|
+
|
|
125
|
+
@app.command('ttk::entry', "#{row}.ent", textvariable: var, width: 48)
|
|
126
|
+
@app.command(:grid, "#{row}.ent", row: 0, column: 1, sticky: :ew, padx: [4, 4])
|
|
127
|
+
|
|
128
|
+
@app.command('ttk::button', "#{row}.btn",
|
|
129
|
+
text: translate('patcher.browse'),
|
|
130
|
+
command: proc { browse_dir(var) })
|
|
131
|
+
@app.command(:grid, "#{row}.btn", row: 0, column: 2)
|
|
132
|
+
@app.command(:grid, :columnconfigure, row, 1, weight: 1)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def browse_file(var, filetypes_tcl)
|
|
136
|
+
path = @app.tcl_eval("tk_getOpenFile -filetypes {#{filetypes_tcl}}")
|
|
137
|
+
@app.set_variable(var, path) unless path.to_s.strip.empty?
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def browse_dir(var)
|
|
141
|
+
dir = @app.tcl_eval("tk_chooseDirectory")
|
|
142
|
+
@app.set_variable(var, dir) unless dir.to_s.strip.empty?
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def apply_patch
|
|
146
|
+
rom = @app.get_variable(VAR_ROM).strip
|
|
147
|
+
patch = @app.get_variable(VAR_PATCH).strip
|
|
148
|
+
outdir = @app.get_variable(VAR_OUTDIR).strip
|
|
149
|
+
|
|
150
|
+
if rom.empty? || patch.empty? || outdir.empty?
|
|
151
|
+
set_status(translate('patcher.err_missing_fields'))
|
|
152
|
+
return
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
unless File.exist?(rom)
|
|
156
|
+
set_status(translate('patcher.err_rom_not_found'))
|
|
157
|
+
return
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
unless File.exist?(patch)
|
|
161
|
+
set_status(translate('patcher.err_patch_not_found'))
|
|
162
|
+
return
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
resolved_rom = begin
|
|
166
|
+
RomResolver.resolve(rom)
|
|
167
|
+
rescue => e
|
|
168
|
+
set_status("#{translate('patcher.err_failed')} #{e.message}")
|
|
169
|
+
return
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
rom_ext = File.extname(resolved_rom)
|
|
173
|
+
basename = File.basename(rom, '.*') + '-patched' + rom_ext
|
|
174
|
+
desired_out = File.join(outdir, basename)
|
|
175
|
+
|
|
176
|
+
out_path = if File.exist?(desired_out)
|
|
177
|
+
msg = translate('patcher.overwrite_msg').gsub('{path}', File.basename(desired_out))
|
|
178
|
+
answer = @app.command('tk_messageBox',
|
|
179
|
+
parent: TOP,
|
|
180
|
+
title: translate('patcher.overwrite_title'),
|
|
181
|
+
message: msg,
|
|
182
|
+
type: :yesnocancel,
|
|
183
|
+
icon: :question)
|
|
184
|
+
case answer
|
|
185
|
+
when 'yes' then desired_out
|
|
186
|
+
when 'no' then RomPatcher.safe_out_path(desired_out)
|
|
187
|
+
else return # cancel — abort silently
|
|
188
|
+
end
|
|
189
|
+
else
|
|
190
|
+
desired_out
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
set_status(translate('patcher.working'))
|
|
194
|
+
@app.command(@apply_btn, :configure, state: :disabled)
|
|
195
|
+
@app.command(@progress_bar, :configure, value: 0)
|
|
196
|
+
|
|
197
|
+
data = Ractor.make_shareable({ rom: resolved_rom.freeze, patch: patch.freeze, out: out_path.freeze })
|
|
198
|
+
Teek::BackgroundWork.drop_intermediate = false
|
|
199
|
+
Teek::BackgroundWork.new(@app, data, mode: BG_MODE, worker: PatchWorker).on_progress do |result|
|
|
200
|
+
case result
|
|
201
|
+
when Float
|
|
202
|
+
@app.command(@progress_bar, :configure, value: (result * 100).round)
|
|
203
|
+
@app.update
|
|
204
|
+
when Hash
|
|
205
|
+
@app.command(@apply_btn, :configure, state: :normal)
|
|
206
|
+
@app.command(@progress_bar, :configure, value: result[:ok] ? 100 : 0)
|
|
207
|
+
@app.update
|
|
208
|
+
if result[:ok]
|
|
209
|
+
set_status("#{translate('patcher.done')} #{File.basename(result[:path])}")
|
|
210
|
+
else
|
|
211
|
+
set_status("#{translate('patcher.err_failed')} #{result[:error]}")
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end.on_done do
|
|
215
|
+
@app.command(@apply_btn, :configure, state: :normal)
|
|
216
|
+
end
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
def set_status(msg)
|
|
220
|
+
@app.set_variable(VAR_STATUS, msg)
|
|
221
|
+
end
|
|
222
|
+
end
|
|
223
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gemba
|
|
4
|
+
module Platform
|
|
5
|
+
class GB
|
|
6
|
+
def width = 160
|
|
7
|
+
def height = 144
|
|
8
|
+
def fps = 59.7275
|
|
9
|
+
def fps_fraction = [4194304, 70224]
|
|
10
|
+
def aspect = [10, 9]
|
|
11
|
+
def name = "Game Boy"
|
|
12
|
+
def short_name = "GB"
|
|
13
|
+
def buttons = %i[a b start select up down left right]
|
|
14
|
+
def thumb_size = [80, 72]
|
|
15
|
+
|
|
16
|
+
def ==(other) = other.is_a?(Platform::GB)
|
|
17
|
+
def eql?(other) = self == other
|
|
18
|
+
def hash = self.class.hash
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gemba
|
|
4
|
+
module Platform
|
|
5
|
+
class GBA
|
|
6
|
+
def width = 240
|
|
7
|
+
def height = 160
|
|
8
|
+
def fps = 59.7272
|
|
9
|
+
def fps_fraction = [262144, 4389]
|
|
10
|
+
def aspect = [3, 2]
|
|
11
|
+
def name = "Game Boy Advance"
|
|
12
|
+
def short_name = "GBA"
|
|
13
|
+
def buttons = %i[a b l r start select up down left right]
|
|
14
|
+
def thumb_size = [120, 80]
|
|
15
|
+
|
|
16
|
+
def ==(other) = other.is_a?(Platform::GBA)
|
|
17
|
+
def eql?(other) = self == other
|
|
18
|
+
def hash = self.class.hash
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gemba
|
|
4
|
+
module Platform
|
|
5
|
+
# Same hardware specs as GB (resolution, FPS, buttons).
|
|
6
|
+
# Separate class for distinct name and future color-specific behavior.
|
|
7
|
+
class GBC
|
|
8
|
+
def width = 160
|
|
9
|
+
def height = 144
|
|
10
|
+
def fps = 59.7275
|
|
11
|
+
def fps_fraction = [4194304, 70224]
|
|
12
|
+
def aspect = [10, 9]
|
|
13
|
+
def name = "Game Boy Color"
|
|
14
|
+
def short_name = "GBC"
|
|
15
|
+
def buttons = %i[a b start select up down left right]
|
|
16
|
+
def thumb_size = [80, 72]
|
|
17
|
+
|
|
18
|
+
def ==(other) = other.is_a?(Platform::GBC)
|
|
19
|
+
def eql?(other) = self == other
|
|
20
|
+
def hash = self.class.hash
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
module Gemba
|
|
5
|
+
module Platform
|
|
6
|
+
# Build a Platform from a loaded Core.
|
|
7
|
+
# @param core [Gemba::Core] initialized core with ROM loaded
|
|
8
|
+
# @return [GBA, GB, GBC]
|
|
9
|
+
def self.for(core)
|
|
10
|
+
case core.platform
|
|
11
|
+
when "GBA" then GBA.new
|
|
12
|
+
when "GBC" then GBC.new
|
|
13
|
+
else GB.new
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Default platform before any ROM is loaded (most common case).
|
|
18
|
+
def self.default = GBA.new
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
module Gemba
|
|
6
|
+
# Open a directory in the platform's file manager.
|
|
7
|
+
# @param dir [String] directory path (created if missing)
|
|
8
|
+
def self.open_directory(dir)
|
|
9
|
+
FileUtils.mkdir_p(dir) unless File.directory?(dir)
|
|
10
|
+
p = Teek.platform
|
|
11
|
+
if p.darwin?
|
|
12
|
+
system('open', dir)
|
|
13
|
+
elsif p.windows?
|
|
14
|
+
system('explorer.exe', dir.tr('/', '\\'))
|
|
15
|
+
else
|
|
16
|
+
system('xdg-open', dir)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
data/lib/gemba/recorder.rb
CHANGED
|
@@ -30,13 +30,14 @@ module Gemba
|
|
|
30
30
|
# @param audio_rate [Integer] audio sample rate (default 44100)
|
|
31
31
|
# @param audio_channels [Integer] audio channels (default 2)
|
|
32
32
|
# @param compression [Integer] zlib compression level 1-9 (default 1 = fastest)
|
|
33
|
-
def initialize(path, width:, height:, audio_rate: 44100,
|
|
34
|
-
compression: Zlib::BEST_SPEED)
|
|
33
|
+
def initialize(path, width:, height:, fps_fraction:, audio_rate: 44100,
|
|
34
|
+
audio_channels: 2, compression: Zlib::BEST_SPEED)
|
|
35
35
|
@path = path
|
|
36
36
|
@width = width
|
|
37
37
|
@height = height
|
|
38
38
|
@audio_rate = audio_rate
|
|
39
39
|
@audio_channels = audio_channels
|
|
40
|
+
@fps_fraction = fps_fraction
|
|
40
41
|
@compression = compression
|
|
41
42
|
@frame_size = width * height * 4
|
|
42
43
|
@recording = false
|
|
@@ -127,7 +128,7 @@ module Gemba
|
|
|
127
128
|
h << MAGIC # 8 bytes
|
|
128
129
|
h << [VERSION].pack('C') # 1 byte
|
|
129
130
|
h << [@width, @height].pack('v2') # 4 bytes
|
|
130
|
-
h <<
|
|
131
|
+
h << @fps_fraction.pack('V2') # 8 bytes (fps as numerator/denominator)
|
|
131
132
|
h << [@audio_rate].pack('V') # 4 bytes
|
|
132
133
|
h << [@audio_channels, 16].pack('C2') # 2 bytes
|
|
133
134
|
h << ("\0" * 5) # 5 bytes reserved
|