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
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3b7478522afdccb445e067ff5e812f499cf7e8aec5f0708d4d12c9ee81224f4a
|
|
4
|
+
data.tar.gz: 83ae429789c3beb4c6cb173a8bffdfd5d5a70c7a3451621e21507ef040ccb484
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: dbe2fdbf8ce938a595df9c071aeae5f61d15be4cf18a5e3d2cf1331ca3d4a65ac2af931b2828f777c2fa9661de73b2ea7b5f135e35732a040ddf420ef7d4a4d5
|
|
7
|
+
data.tar.gz: ff4e86a1702acf27651991fe7f43a66272287dcf801fe58baab3bf0ebb76e8cc0988168ff6c6ae281f67006d611232319bdcbdad583cae95de3d0365562ce152
|
data/THIRD_PARTY_NOTICES
CHANGED
|
@@ -1,5 +1,40 @@
|
|
|
1
|
-
gemba bundles the following third-party
|
|
2
|
-
|
|
1
|
+
gemba bundles the following third-party libraries and fonts.
|
|
2
|
+
|
|
3
|
+
================================================================================
|
|
4
|
+
rcheevos
|
|
5
|
+
================================================================================
|
|
6
|
+
|
|
7
|
+
Copyright (c) 2018 RetroAchievements.org
|
|
8
|
+
https://github.com/RetroAchievements/rcheevos
|
|
9
|
+
|
|
10
|
+
Licensed under the MIT License.
|
|
11
|
+
|
|
12
|
+
Used for: RetroAchievements runtime — achievement condition evaluation,
|
|
13
|
+
Rich Presence string evaluation, and rcheevos network protocol helpers.
|
|
14
|
+
|
|
15
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
16
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
17
|
+
in the Software without restriction, including without limitation the rights
|
|
18
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
19
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
20
|
+
furnished to do so, subject to the following conditions:
|
|
21
|
+
|
|
22
|
+
The above copyright notice and this permission notice shall be included in all
|
|
23
|
+
copies or substantial portions of the Software.
|
|
24
|
+
|
|
25
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
26
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
27
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
28
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
29
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
30
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
31
|
+
SOFTWARE.
|
|
32
|
+
|
|
33
|
+
================================================================================
|
|
34
|
+
Fonts (assets/ directory)
|
|
35
|
+
================================================================================
|
|
36
|
+
|
|
37
|
+
Both fonts below are licensed under the SIL Open Font License, Version 1.1.
|
|
3
38
|
|
|
4
39
|
================================================================================
|
|
5
40
|
JetBrains Mono NL
|
|
Binary file
|
data/bin/gemba
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env ruby
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
|
-
# When running from a development checkout (not installed as a gem),
|
|
4
|
+
# When running from a development checkout (not installed as a gem or packed by premo),
|
|
5
5
|
# add lib/ to the load path and activate Bundler for gem dependencies.
|
|
6
|
-
if File.exist?(File.expand_path("
|
|
6
|
+
if File.exist?(File.expand_path("../.git", __dir__))
|
|
7
7
|
require "bundler/setup"
|
|
8
8
|
path = File.expand_path("../lib", __dir__)
|
|
9
9
|
$LOAD_PATH.unshift(path) unless $LOAD_PATH.include?(path)
|
data/ext/gemba/extconf.rb
CHANGED
|
@@ -181,6 +181,28 @@ unless find_mgba
|
|
|
181
181
|
MSG
|
|
182
182
|
end
|
|
183
183
|
|
|
184
|
-
|
|
184
|
+
rcheevos_root = File.expand_path('../../vendor/rcheevos', __dir__)
|
|
185
|
+
abort "vendor/rcheevos submodule not initialised — run: git submodule update --init" \
|
|
186
|
+
unless File.exist?("#{rcheevos_root}/src/rcheevos/runtime.c")
|
|
187
|
+
|
|
188
|
+
$INCFLAGS << " -I#{rcheevos_root}/include"
|
|
189
|
+
$INCFLAGS << " -I#{rcheevos_root}/src/rcheevos"
|
|
190
|
+
$INCFLAGS << " -I#{rcheevos_root}/src"
|
|
191
|
+
|
|
192
|
+
# Tell mkmf where to find the rcheevos source files.
|
|
193
|
+
$VPATH << "#{rcheevos_root}/src/rcheevos"
|
|
194
|
+
$VPATH << "#{rcheevos_root}/src"
|
|
195
|
+
$VPATH << "#{rcheevos_root}/src/rhash"
|
|
196
|
+
|
|
197
|
+
# Core evaluation engine + md5 (needed by runtime.c / runtime_progress.c).
|
|
198
|
+
# Excludes HTTP client, ROM hash, and rapi.
|
|
199
|
+
$srcs = %w[
|
|
200
|
+
gemba_ext.c
|
|
201
|
+
alloc.c condition.c condset.c format.c lboard.c memref.c
|
|
202
|
+
operand.c richpresence.c runtime.c runtime_progress.c
|
|
203
|
+
trigger.c value.c
|
|
204
|
+
rc_compat.c rc_util.c
|
|
205
|
+
md5.c
|
|
206
|
+
]
|
|
185
207
|
|
|
186
208
|
create_makefile('gemba_ext')
|
data/ext/gemba/gemba_ext.c
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#include "gemba_ext.h"
|
|
2
|
+
#include "rc_runtime.h"
|
|
2
3
|
#include <mgba/core/config.h>
|
|
3
4
|
#include <mgba/core/serialize.h>
|
|
4
5
|
#include <ruby/thread.h>
|
|
@@ -19,6 +20,7 @@ void blip_set_rates(struct blip_t *, double clock_rate, double sample_rate);
|
|
|
19
20
|
|
|
20
21
|
VALUE mGemba;
|
|
21
22
|
static VALUE cCore;
|
|
23
|
+
static VALUE ra_empty_array; /* frozen [] returned by do_frame when nothing triggered */
|
|
22
24
|
|
|
23
25
|
/* No-op logger — prevents segfault when mGBA tries to log
|
|
24
26
|
* without a logger configured (the default is NULL). */
|
|
@@ -259,8 +261,8 @@ get_mgba_core(VALUE self)
|
|
|
259
261
|
static VALUE
|
|
260
262
|
mgba_core_initialize(int argc, VALUE *argv, VALUE self)
|
|
261
263
|
{
|
|
262
|
-
VALUE rom_path, save_dir;
|
|
263
|
-
rb_scan_args(argc, argv, "
|
|
264
|
+
VALUE rom_path, save_dir, bios_path;
|
|
265
|
+
rb_scan_args(argc, argv, "12", &rom_path, &save_dir, &bios_path);
|
|
264
266
|
|
|
265
267
|
struct mgba_core *mc;
|
|
266
268
|
TypedData_Get_Struct(self, struct mgba_core, &mgba_core_type, mc);
|
|
@@ -324,9 +326,53 @@ mgba_core_initialize(int argc, VALUE *argv, VALUE self)
|
|
|
324
326
|
mDirectorySetMapOptions(&core->dirs, &opts);
|
|
325
327
|
}
|
|
326
328
|
|
|
329
|
+
/* 7b. Load BIOS if provided (must be before reset) */
|
|
330
|
+
if (!NIL_P(bios_path)) {
|
|
331
|
+
Check_Type(bios_path, T_STRING);
|
|
332
|
+
struct VFile *bvf = VFileOpen(StringValueCStr(bios_path), O_RDONLY);
|
|
333
|
+
if (bvf) {
|
|
334
|
+
if (!core->loadBIOS(core, bvf, 0)) {
|
|
335
|
+
bvf->close(bvf);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
327
340
|
/* 8. Reset */
|
|
328
341
|
core->reset(core);
|
|
329
342
|
|
|
343
|
+
/* 8b. Re-query dimensions now that the ROM is loaded and the board
|
|
344
|
+
* pointer is populated. For GB/GBC, the pre-load query returns the
|
|
345
|
+
* SGB frame size (256x224) because core->board is NULL at that point.
|
|
346
|
+
* After reset the real model is known, so desiredVideoDimensions
|
|
347
|
+
* returns the correct 160x144 for non-SGB games. When the
|
|
348
|
+
* dimensions shrink we must reallocate and call setVideoBuffer so
|
|
349
|
+
* the stride matches the actual width. */
|
|
350
|
+
{
|
|
351
|
+
unsigned w2, h2;
|
|
352
|
+
core->desiredVideoDimensions(core, &w2, &h2);
|
|
353
|
+
if (w2 != w || h2 != h) {
|
|
354
|
+
color_t *new_vbuf = calloc((size_t)w2 * h2, sizeof(color_t));
|
|
355
|
+
uint32_t *new_prev = calloc((size_t)w2 * h2, sizeof(uint32_t));
|
|
356
|
+
if (!new_vbuf || !new_prev) {
|
|
357
|
+
free(new_vbuf);
|
|
358
|
+
free(new_prev);
|
|
359
|
+
free(mc->video_buffer);
|
|
360
|
+
mc->video_buffer = NULL;
|
|
361
|
+
free(mc->prev_frame);
|
|
362
|
+
mc->prev_frame = NULL;
|
|
363
|
+
core->deinit(core);
|
|
364
|
+
rb_raise(rb_eNoMemError, "failed to reallocate video buffer");
|
|
365
|
+
}
|
|
366
|
+
free(mc->video_buffer);
|
|
367
|
+
free(mc->prev_frame);
|
|
368
|
+
mc->video_buffer = new_vbuf;
|
|
369
|
+
mc->prev_frame = new_prev;
|
|
370
|
+
core->setVideoBuffer(core, mc->video_buffer, w2);
|
|
371
|
+
}
|
|
372
|
+
mc->width = (int)w2;
|
|
373
|
+
mc->height = (int)h2;
|
|
374
|
+
}
|
|
375
|
+
|
|
330
376
|
/* 9. Autoload save file (.sav alongside ROM, or in save_dir).
|
|
331
377
|
* Creates the .sav if it doesn't exist yet. */
|
|
332
378
|
mCoreAutoloadSave(core);
|
|
@@ -603,6 +649,50 @@ mgba_core_maker_code(VALUE self)
|
|
|
603
649
|
}
|
|
604
650
|
|
|
605
651
|
/* --------------------------------------------------------- */
|
|
652
|
+
/* Core#bus_read8(address) */
|
|
653
|
+
/* Read one byte from the GBA address bus. */
|
|
654
|
+
/* Returns 0..255 as an Integer. */
|
|
655
|
+
/* Primarily used by the achievement subsystem to evaluate */
|
|
656
|
+
/* memory conditions (IWRAM, EWRAM, registers). */
|
|
657
|
+
/* --------------------------------------------------------- */
|
|
658
|
+
|
|
659
|
+
static VALUE
|
|
660
|
+
mgba_core_bus_read8(VALUE self, VALUE addr)
|
|
661
|
+
{
|
|
662
|
+
struct mgba_core *mc = get_mgba_core(self);
|
|
663
|
+
uint32_t address = (uint32_t)NUM2UINT(addr);
|
|
664
|
+
uint8_t val = (uint8_t)mc->core->busRead8(mc->core, address);
|
|
665
|
+
return UINT2NUM(val);
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
/* Core#bus_read16(address) */
|
|
669
|
+
/* Read two bytes (little-endian) from the GBA address bus. */
|
|
670
|
+
/* Returns 0..65535 as an Integer. */
|
|
671
|
+
/* --------------------------------------------------------- */
|
|
672
|
+
|
|
673
|
+
static VALUE
|
|
674
|
+
mgba_core_bus_read16(VALUE self, VALUE addr)
|
|
675
|
+
{
|
|
676
|
+
struct mgba_core *mc = get_mgba_core(self);
|
|
677
|
+
uint32_t address = (uint32_t)NUM2UINT(addr);
|
|
678
|
+
uint16_t val = (uint16_t)mc->core->busRead16(mc->core, address);
|
|
679
|
+
return UINT2NUM(val);
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
/* Core#bus_read32(address) */
|
|
683
|
+
/* Read four bytes (little-endian) from the GBA address bus. */
|
|
684
|
+
/* Returns 0..4294967295 as an Integer. */
|
|
685
|
+
/* --------------------------------------------------------- */
|
|
686
|
+
|
|
687
|
+
static VALUE
|
|
688
|
+
mgba_core_bus_read32(VALUE self, VALUE addr)
|
|
689
|
+
{
|
|
690
|
+
struct mgba_core *mc = get_mgba_core(self);
|
|
691
|
+
uint32_t address = (uint32_t)NUM2UINT(addr);
|
|
692
|
+
uint32_t val = mc->core->busRead32(mc->core, address);
|
|
693
|
+
return UINT2NUM(val);
|
|
694
|
+
}
|
|
695
|
+
|
|
606
696
|
/* Core#save_state_to_file(path) */
|
|
607
697
|
/* Save the complete emulator state to a file. */
|
|
608
698
|
/* Returns true on success, false on failure. */
|
|
@@ -985,10 +1075,313 @@ mgba_count_changed_pixels(VALUE mod, VALUE delta)
|
|
|
985
1075
|
return LONG2NUM(changed);
|
|
986
1076
|
}
|
|
987
1077
|
|
|
1078
|
+
/* --------------------------------------------------------- */
|
|
1079
|
+
/* Core#bios_loaded? */
|
|
1080
|
+
/* Returns true if a BIOS VFile is attached to this core. */
|
|
1081
|
+
/* GBA only; returns false for other platforms. */
|
|
1082
|
+
/* --------------------------------------------------------- */
|
|
1083
|
+
|
|
1084
|
+
static VALUE
|
|
1085
|
+
mgba_core_bios_loaded_p(VALUE self)
|
|
1086
|
+
{
|
|
1087
|
+
struct mgba_core *mc = get_mgba_core(self);
|
|
1088
|
+
if (mc->core->platform(mc->core) != mPLATFORM_GBA) {
|
|
1089
|
+
return Qfalse;
|
|
1090
|
+
}
|
|
1091
|
+
struct GBA *gba = (struct GBA *)mc->core->board;
|
|
1092
|
+
return gba->biosVf ? Qtrue : Qfalse;
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
/* --------------------------------------------------------- */
|
|
1096
|
+
/* Gemba::RARuntime — thin wrapper around rc_runtime_t */
|
|
1097
|
+
/* (RetroAchievements/rcheevos) */
|
|
1098
|
+
/* --------------------------------------------------------- */
|
|
1099
|
+
|
|
1100
|
+
/* Achievement IDs arrive from Ruby as numeric strings ("12345").
|
|
1101
|
+
* rcheevos uses uint32_t internally. ra_id_to_u32 parses them;
|
|
1102
|
+
* ra_id_to_str converts back for the do_frame return array. */
|
|
1103
|
+
static uint32_t
|
|
1104
|
+
ra_id_to_u32(VALUE rb_id)
|
|
1105
|
+
{
|
|
1106
|
+
Check_Type(rb_id, T_STRING);
|
|
1107
|
+
return (uint32_t)strtoul(StringValueCStr(rb_id), NULL, 10);
|
|
1108
|
+
}
|
|
1109
|
+
|
|
1110
|
+
/* GBA RA-address-space → mGBA bus address.
|
|
1111
|
+
* 0x000000–0x07FFFF → IWRAM 0x03000000
|
|
1112
|
+
* 0x080000+ → EWRAM 0x02000000 + (addr - 0x080000)
|
|
1113
|
+
* rcheevos passes raw RA addresses to the peek callback. */
|
|
1114
|
+
static inline uint32_t
|
|
1115
|
+
ra_to_gba_addr(uint32_t ra_addr)
|
|
1116
|
+
{
|
|
1117
|
+
if (ra_addr < 0x08000)
|
|
1118
|
+
return 0x03000000 + ra_addr;
|
|
1119
|
+
else
|
|
1120
|
+
return 0x02000000 + (ra_addr - 0x08000);
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
/* rcheevos peek callback — called by rc_runtime_do_frame for every
|
|
1124
|
+
* memory read. Translates RA addresses to GBA bus addresses and
|
|
1125
|
+
* reads 1, 2, or 4 bytes in little-endian order. */
|
|
1126
|
+
static uint32_t
|
|
1127
|
+
ra_peek(uint32_t ra_addr, uint32_t num_bytes, void *ud)
|
|
1128
|
+
{
|
|
1129
|
+
struct mCore *core = (struct mCore *)ud;
|
|
1130
|
+
uint32_t gba = ra_to_gba_addr(ra_addr);
|
|
1131
|
+
switch (num_bytes) {
|
|
1132
|
+
case 1: return (uint32_t)core->busRead8(core, gba);
|
|
1133
|
+
case 2: return (uint32_t)core->busRead16(core, gba);
|
|
1134
|
+
case 4: return core->busRead32(core, gba);
|
|
1135
|
+
default: return 0;
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
/* Triggered-ID collection for do_frame.
|
|
1140
|
+
* rc_runtime_event_handler_t has no userdata parameter, so we stash
|
|
1141
|
+
* a pointer to frame-local storage in a static before each call and
|
|
1142
|
+
* clear it after. Ruby's GVL ensures single-threaded execution here. */
|
|
1143
|
+
typedef struct {
|
|
1144
|
+
uint32_t ids[256];
|
|
1145
|
+
int count;
|
|
1146
|
+
} ra_frame_ctx_t;
|
|
1147
|
+
|
|
1148
|
+
static ra_frame_ctx_t *s_ra_frame_ctx = NULL;
|
|
1149
|
+
|
|
1150
|
+
static void
|
|
1151
|
+
ra_event_handler(const rc_runtime_event_t *event)
|
|
1152
|
+
{
|
|
1153
|
+
if (!s_ra_frame_ctx) return;
|
|
1154
|
+
if (event->type == RC_RUNTIME_EVENT_ACHIEVEMENT_TRIGGERED &&
|
|
1155
|
+
s_ra_frame_ctx->count < 256)
|
|
1156
|
+
s_ra_frame_ctx->ids[s_ra_frame_ctx->count++] = event->id;
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
/* Wrapper struct — embeds rc_runtime_t by value so a single allocation
|
|
1160
|
+
* covers both our bookkeeping and the rcheevos runtime internals. */
|
|
1161
|
+
typedef struct {
|
|
1162
|
+
rc_runtime_t rc;
|
|
1163
|
+
int count; /* number of currently activated achievements */
|
|
1164
|
+
} ra_wrapper_t;
|
|
1165
|
+
|
|
1166
|
+
static void
|
|
1167
|
+
ra_wrapper_free(void *ptr)
|
|
1168
|
+
{
|
|
1169
|
+
ra_wrapper_t *w = (ra_wrapper_t *)ptr;
|
|
1170
|
+
rc_runtime_destroy(&w->rc);
|
|
1171
|
+
xfree(w);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
static VALUE cRARuntime;
|
|
1175
|
+
|
|
1176
|
+
static const rb_data_type_t ra_runtime_type = {
|
|
1177
|
+
.wrap_struct_name = "Gemba::RARuntime",
|
|
1178
|
+
.function = {
|
|
1179
|
+
.dmark = NULL,
|
|
1180
|
+
.dfree = ra_wrapper_free,
|
|
1181
|
+
.dsize = NULL,
|
|
1182
|
+
},
|
|
1183
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
1184
|
+
};
|
|
1185
|
+
|
|
1186
|
+
static VALUE
|
|
1187
|
+
ra_runtime_alloc(VALUE klass)
|
|
1188
|
+
{
|
|
1189
|
+
ra_wrapper_t *w = ALLOC(ra_wrapper_t);
|
|
1190
|
+
rc_runtime_init(&w->rc);
|
|
1191
|
+
w->count = 0;
|
|
1192
|
+
return TypedData_Wrap_Struct(klass, &ra_runtime_type, w);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
static ra_wrapper_t *
|
|
1196
|
+
get_ra_wrapper(VALUE self)
|
|
1197
|
+
{
|
|
1198
|
+
ra_wrapper_t *w;
|
|
1199
|
+
TypedData_Get_Struct(self, ra_wrapper_t, &ra_runtime_type, w);
|
|
1200
|
+
return w;
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
/*
|
|
1204
|
+
* RARuntime#activate(id, memaddr) → nil
|
|
1205
|
+
* Parse memaddr and register the achievement in the rcheevos runtime.
|
|
1206
|
+
* Raises ArgumentError if rcheevos rejects the condition string.
|
|
1207
|
+
*/
|
|
1208
|
+
static VALUE
|
|
1209
|
+
ra_runtime_rb_activate(VALUE self, VALUE rb_id, VALUE rb_memaddr)
|
|
1210
|
+
{
|
|
1211
|
+
Check_Type(rb_memaddr, T_STRING);
|
|
1212
|
+
ra_wrapper_t *w = get_ra_wrapper(self);
|
|
1213
|
+
uint32_t id = ra_id_to_u32(rb_id);
|
|
1214
|
+
int rc = rc_runtime_activate_achievement(&w->rc, id,
|
|
1215
|
+
StringValueCStr(rb_memaddr),
|
|
1216
|
+
NULL, 0);
|
|
1217
|
+
if (rc != RC_OK)
|
|
1218
|
+
rb_raise(rb_eArgError,
|
|
1219
|
+
"RARuntime: rcheevos rejected memaddr (err %d): %s",
|
|
1220
|
+
rc, StringValueCStr(rb_memaddr));
|
|
1221
|
+
w->count++;
|
|
1222
|
+
return Qnil;
|
|
1223
|
+
}
|
|
1224
|
+
|
|
1225
|
+
/*
|
|
1226
|
+
* RARuntime#deactivate(id) → nil
|
|
1227
|
+
*/
|
|
1228
|
+
static VALUE
|
|
1229
|
+
ra_runtime_rb_deactivate(VALUE self, VALUE rb_id)
|
|
1230
|
+
{
|
|
1231
|
+
ra_wrapper_t *w = get_ra_wrapper(self);
|
|
1232
|
+
rc_runtime_deactivate_achievement(&w->rc, ra_id_to_u32(rb_id));
|
|
1233
|
+
if (w->count > 0) w->count--;
|
|
1234
|
+
return Qnil;
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
/*
|
|
1238
|
+
* RARuntime#reset_all → nil
|
|
1239
|
+
* Reset every achievement to WAITING state (as if freshly activated).
|
|
1240
|
+
* Call this after loading a save state so delta/prior histories are
|
|
1241
|
+
* discarded and achievements can't fire spuriously.
|
|
1242
|
+
*/
|
|
1243
|
+
static VALUE
|
|
1244
|
+
ra_runtime_rb_reset_all(VALUE self)
|
|
1245
|
+
{
|
|
1246
|
+
rc_runtime_reset(&get_ra_wrapper(self)->rc);
|
|
1247
|
+
return Qnil;
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
/*
|
|
1251
|
+
* RARuntime#clear → nil
|
|
1252
|
+
* Destroy all achievements and reinitialise the runtime from scratch.
|
|
1253
|
+
*/
|
|
1254
|
+
static VALUE
|
|
1255
|
+
ra_runtime_rb_clear(VALUE self)
|
|
1256
|
+
{
|
|
1257
|
+
ra_wrapper_t *w = get_ra_wrapper(self);
|
|
1258
|
+
rc_runtime_destroy(&w->rc);
|
|
1259
|
+
rc_runtime_init(&w->rc);
|
|
1260
|
+
w->count = 0;
|
|
1261
|
+
return Qnil;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
/*
|
|
1265
|
+
* RARuntime#do_frame(core) → Array<String>
|
|
1266
|
+
* Evaluate all active achievements against current emulator memory.
|
|
1267
|
+
* Returns an array of string IDs for achievements that triggered.
|
|
1268
|
+
*/
|
|
1269
|
+
static VALUE
|
|
1270
|
+
ra_runtime_rb_do_frame(VALUE self, VALUE rb_core)
|
|
1271
|
+
{
|
|
1272
|
+
struct mgba_core *mc;
|
|
1273
|
+
TypedData_Get_Struct(rb_core, struct mgba_core, &mgba_core_type, mc);
|
|
1274
|
+
if (mc->destroyed || !mc->core)
|
|
1275
|
+
rb_raise(rb_eRuntimeError, "mGBA core has been destroyed");
|
|
1276
|
+
|
|
1277
|
+
ra_wrapper_t *w = get_ra_wrapper(self);
|
|
1278
|
+
ra_frame_ctx_t ctx = { .count = 0 };
|
|
1279
|
+
|
|
1280
|
+
s_ra_frame_ctx = &ctx;
|
|
1281
|
+
rc_runtime_do_frame(&w->rc, ra_event_handler, ra_peek, mc->core, NULL);
|
|
1282
|
+
s_ra_frame_ctx = NULL;
|
|
1283
|
+
|
|
1284
|
+
if (ctx.count == 0) return ra_empty_array;
|
|
1285
|
+
|
|
1286
|
+
VALUE result = rb_ary_new_capa(ctx.count);
|
|
1287
|
+
char buf[16];
|
|
1288
|
+
for (int i = 0; i < ctx.count; i++) {
|
|
1289
|
+
snprintf(buf, sizeof(buf), "%u", ctx.ids[i]);
|
|
1290
|
+
rb_ary_push(result, rb_str_new_cstr(buf));
|
|
1291
|
+
}
|
|
1292
|
+
return result;
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
/*
|
|
1296
|
+
* RARuntime#count → Integer
|
|
1297
|
+
* Number of currently activated achievements.
|
|
1298
|
+
*/
|
|
1299
|
+
static VALUE
|
|
1300
|
+
ra_runtime_rb_count(VALUE self)
|
|
1301
|
+
{
|
|
1302
|
+
return INT2NUM(get_ra_wrapper(self)->count);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
/*
|
|
1306
|
+
* RARuntime#activate_richpresence(script) → true | false
|
|
1307
|
+
* Load a Rich Presence script into the runtime.
|
|
1308
|
+
* Returns true on success, false if the script failed to parse.
|
|
1309
|
+
*/
|
|
1310
|
+
static VALUE
|
|
1311
|
+
ra_runtime_rb_activate_richpresence(VALUE self, VALUE rb_script)
|
|
1312
|
+
{
|
|
1313
|
+
ra_wrapper_t *w = get_ra_wrapper(self);
|
|
1314
|
+
const char *script = StringValueCStr(rb_script);
|
|
1315
|
+
int rc;
|
|
1316
|
+
|
|
1317
|
+
rc = rc_runtime_activate_richpresence(&w->rc, script, NULL, 0);
|
|
1318
|
+
return rc == RC_OK ? Qtrue : Qfalse;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
/*
|
|
1322
|
+
* RARuntime#get_richpresence(core) → String | nil
|
|
1323
|
+
* Evaluate the active Rich Presence script against current memory and
|
|
1324
|
+
* return the display string, or nil if no script is loaded / empty result.
|
|
1325
|
+
*/
|
|
1326
|
+
static VALUE
|
|
1327
|
+
ra_runtime_rb_get_richpresence(VALUE self, VALUE rb_core)
|
|
1328
|
+
{
|
|
1329
|
+
ra_wrapper_t *w = get_ra_wrapper(self);
|
|
1330
|
+
struct mgba_core *mc;
|
|
1331
|
+
char buf[512];
|
|
1332
|
+
int len;
|
|
1333
|
+
|
|
1334
|
+
TypedData_Get_Struct(rb_core, struct mgba_core, &mgba_core_type, mc);
|
|
1335
|
+
len = rc_runtime_get_richpresence(&w->rc, buf, sizeof(buf), ra_peek, mc->core, NULL);
|
|
1336
|
+
if (len <= 0)
|
|
1337
|
+
return Qnil;
|
|
1338
|
+
return rb_str_new(buf, len);
|
|
1339
|
+
}
|
|
1340
|
+
|
|
988
1341
|
/* --------------------------------------------------------- */
|
|
989
1342
|
/* Init */
|
|
990
1343
|
/* --------------------------------------------------------- */
|
|
991
1344
|
|
|
1345
|
+
/* --------------------------------------------------------- */
|
|
1346
|
+
/* Core#load_bios(path) */
|
|
1347
|
+
/* Load a BIOS file from path. Must be called before reset. */
|
|
1348
|
+
/* Returns true on success, false on failure. */
|
|
1349
|
+
/* --------------------------------------------------------- */
|
|
1350
|
+
|
|
1351
|
+
static VALUE
|
|
1352
|
+
mgba_core_load_bios(VALUE self, VALUE rb_path)
|
|
1353
|
+
{
|
|
1354
|
+
struct mgba_core *mc = get_mgba_core(self);
|
|
1355
|
+
Check_Type(rb_path, T_STRING);
|
|
1356
|
+
const char *path = StringValueCStr(rb_path);
|
|
1357
|
+
|
|
1358
|
+
struct VFile *vf = VFileOpen(path, O_RDONLY);
|
|
1359
|
+
if (!vf) {
|
|
1360
|
+
return Qfalse;
|
|
1361
|
+
}
|
|
1362
|
+
|
|
1363
|
+
bool ok = mc->core->loadBIOS(mc->core, vf, 0);
|
|
1364
|
+
if (!ok) {
|
|
1365
|
+
vf->close(vf);
|
|
1366
|
+
}
|
|
1367
|
+
/* mGBA takes ownership of vf on success; do not close */
|
|
1368
|
+
return ok ? Qtrue : Qfalse;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
/* --------------------------------------------------------- */
|
|
1372
|
+
/* Gemba.gba_bios_checksum(bytes) */
|
|
1373
|
+
/* Compute GBA BIOS checksum (mGBA algorithm) on raw bytes. */
|
|
1374
|
+
/* --------------------------------------------------------- */
|
|
1375
|
+
|
|
1376
|
+
static VALUE
|
|
1377
|
+
mgba_gba_bios_checksum(VALUE self, VALUE rb_bytes)
|
|
1378
|
+
{
|
|
1379
|
+
Check_Type(rb_bytes, T_STRING);
|
|
1380
|
+
long len = RSTRING_LEN(rb_bytes);
|
|
1381
|
+
uint32_t result = GBAChecksum((uint32_t *)RSTRING_PTR(rb_bytes), (size_t)(len / 4));
|
|
1382
|
+
return UINT2NUM(result);
|
|
1383
|
+
}
|
|
1384
|
+
|
|
992
1385
|
void
|
|
993
1386
|
Init_gemba_ext(void)
|
|
994
1387
|
{
|
|
@@ -1029,6 +1422,16 @@ Init_gemba_ext(void)
|
|
|
1029
1422
|
rb_define_method(cCore, "rewind_count", mgba_core_rewind_count, 0);
|
|
1030
1423
|
rb_define_method(cCore, "destroy", mgba_core_destroy, 0);
|
|
1031
1424
|
rb_define_method(cCore, "destroyed?", mgba_core_destroyed_p, 0);
|
|
1425
|
+
rb_define_method(cCore, "load_bios", mgba_core_load_bios, 1);
|
|
1426
|
+
rb_define_method(cCore, "bios_loaded?", mgba_core_bios_loaded_p, 0);
|
|
1427
|
+
rb_define_method(cCore, "bus_read8", mgba_core_bus_read8, 1);
|
|
1428
|
+
rb_define_method(cCore, "bus_read16", mgba_core_bus_read16, 1);
|
|
1429
|
+
rb_define_method(cCore, "bus_read32", mgba_core_bus_read32, 1);
|
|
1430
|
+
|
|
1431
|
+
/* BIOS checksum utility */
|
|
1432
|
+
rb_define_module_function(mGemba, "gba_bios_checksum", mgba_gba_bios_checksum, 1);
|
|
1433
|
+
rb_define_const(mGemba, "GBA_BIOS_CHECKSUM", UINT2NUM(GBA_BIOS_CHECKSUM));
|
|
1434
|
+
rb_define_const(mGemba, "GBA_DS_BIOS_CHECKSUM", UINT2NUM(GBA_DS_BIOS_CHECKSUM));
|
|
1032
1435
|
|
|
1033
1436
|
/* GBA key constants (bitmask values for set_keys) */
|
|
1034
1437
|
rb_define_const(mGemba, "KEY_A", INT2NUM(1 << GEMBA_KEY_A));
|
|
@@ -1042,6 +1445,37 @@ Init_gemba_ext(void)
|
|
|
1042
1445
|
rb_define_const(mGemba, "KEY_R", INT2NUM(1 << GEMBA_KEY_R));
|
|
1043
1446
|
rb_define_const(mGemba, "KEY_L", INT2NUM(1 << GEMBA_KEY_L));
|
|
1044
1447
|
|
|
1448
|
+
/* GBA button name → bitmask hash (shared by KeyboardMap and GamepadMap) */
|
|
1449
|
+
VALUE btn_bits = rb_hash_new();
|
|
1450
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("a")), INT2NUM(1 << GEMBA_KEY_A));
|
|
1451
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("b")), INT2NUM(1 << GEMBA_KEY_B));
|
|
1452
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("l")), INT2NUM(1 << GEMBA_KEY_L));
|
|
1453
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("r")), INT2NUM(1 << GEMBA_KEY_R));
|
|
1454
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("up")), INT2NUM(1 << GEMBA_KEY_UP));
|
|
1455
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("down")), INT2NUM(1 << GEMBA_KEY_DOWN));
|
|
1456
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("left")), INT2NUM(1 << GEMBA_KEY_LEFT));
|
|
1457
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("right")), INT2NUM(1 << GEMBA_KEY_RIGHT));
|
|
1458
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("start")), INT2NUM(1 << GEMBA_KEY_START));
|
|
1459
|
+
rb_hash_aset(btn_bits, ID2SYM(rb_intern("select")), INT2NUM(1 << GEMBA_KEY_SELECT));
|
|
1460
|
+
OBJ_FREEZE(btn_bits);
|
|
1461
|
+
rb_define_const(mGemba, "GBA_BTN_BITS", btn_bits);
|
|
1462
|
+
|
|
1463
|
+
/* Frozen empty array returned by RARuntime#do_frame when nothing triggered */
|
|
1464
|
+
ra_empty_array = rb_ary_freeze(rb_ary_new_capa(0));
|
|
1465
|
+
rb_gc_register_mark_object(ra_empty_array);
|
|
1466
|
+
|
|
1467
|
+
/* Gemba::RARuntime — RA condition evaluator */
|
|
1468
|
+
cRARuntime = rb_define_class_under(mGemba, "RARuntime", rb_cObject);
|
|
1469
|
+
rb_define_alloc_func(cRARuntime, ra_runtime_alloc);
|
|
1470
|
+
rb_define_method(cRARuntime, "activate", ra_runtime_rb_activate, 2);
|
|
1471
|
+
rb_define_method(cRARuntime, "deactivate", ra_runtime_rb_deactivate, 1);
|
|
1472
|
+
rb_define_method(cRARuntime, "reset_all", ra_runtime_rb_reset_all, 0);
|
|
1473
|
+
rb_define_method(cRARuntime, "clear", ra_runtime_rb_clear, 0);
|
|
1474
|
+
rb_define_method(cRARuntime, "do_frame", ra_runtime_rb_do_frame, 1);
|
|
1475
|
+
rb_define_method(cRARuntime, "count", ra_runtime_rb_count, 0);
|
|
1476
|
+
rb_define_method(cRARuntime, "activate_richpresence", ra_runtime_rb_activate_richpresence, 1);
|
|
1477
|
+
rb_define_method(cRARuntime, "get_richpresence", ra_runtime_rb_get_richpresence, 1);
|
|
1478
|
+
|
|
1045
1479
|
/* Toast background generator */
|
|
1046
1480
|
rb_define_module_function(mGemba, "toast_background", mgba_toast_background, 3);
|
|
1047
1481
|
|
data/ext/gemba/gemba_ext.h
CHANGED
data/gemba.gemspec
CHANGED
|
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
|
11
11
|
spec.homepage = "https://github.com/jamescook/gemba"
|
|
12
12
|
spec.licenses = ["MIT"]
|
|
13
13
|
|
|
14
|
-
spec.files = Dir.glob("{lib,ext,test,assets,bin}/**/*").select { |f|
|
|
14
|
+
spec.files = Dir.glob("{lib,ext,test,assets,bin,vendor/rcheevos}/**/*").select { |f|
|
|
15
15
|
File.file?(f) && f !~ /\.(bundle|so|o|log)$/ &&
|
|
16
16
|
!f.include?('.dSYM/') && File.basename(f) != 'Makefile'
|
|
17
17
|
} + %w[gemba.gemspec THIRD_PARTY_NOTICES]
|
|
@@ -21,8 +21,9 @@ Gem::Specification.new do |spec|
|
|
|
21
21
|
spec.extensions = ["ext/gemba/extconf.rb"]
|
|
22
22
|
spec.required_ruby_version = ">= 3.2"
|
|
23
23
|
|
|
24
|
-
spec.add_dependency "teek", ">= 0.1.
|
|
25
|
-
spec.add_dependency "teek-sdl2", ">= 0.1
|
|
24
|
+
spec.add_dependency "teek", ">= 0.1.5"
|
|
25
|
+
spec.add_dependency "teek-sdl2", ">= 0.2.1"
|
|
26
|
+
spec.add_dependency "zeitwerk", "~> 2.7"
|
|
26
27
|
|
|
27
28
|
spec.add_development_dependency "rake", "~> 13.0"
|
|
28
29
|
spec.add_development_dependency "rake-compiler", "~> 1.0"
|
|
@@ -32,6 +33,7 @@ Gem::Specification.new do |spec|
|
|
|
32
33
|
spec.add_development_dependency "listen", "~> 3.0"
|
|
33
34
|
|
|
34
35
|
spec.requirements << "libmgba development headers"
|
|
36
|
+
spec.add_development_dependency "webmock", "~> 3.0"
|
|
35
37
|
spec.add_development_dependency "rubyzip", ">= 2.4"
|
|
36
38
|
|
|
37
39
|
spec.requirements << "rubyzip gem >= 2.4 (optional, for loading ROMs from .zip files)"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Gemba
|
|
4
|
+
module Achievements
|
|
5
|
+
# Represents a single achievement (earned or unearned).
|
|
6
|
+
#
|
|
7
|
+
# @attr [String] id Unique identifier (string, arbitrary)
|
|
8
|
+
# @attr [String] title Short display name
|
|
9
|
+
# @attr [String] description What the player must do to earn it
|
|
10
|
+
# @attr [Integer] points RA point value (or custom weight)
|
|
11
|
+
# @attr [Time, nil] earned_at When it was unlocked; nil if unearned
|
|
12
|
+
Achievement = Data.define(:id, :title, :description, :points, :earned_at) do
|
|
13
|
+
def earned?
|
|
14
|
+
!earned_at.nil?
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Return a copy marked as earned right now.
|
|
18
|
+
def earn
|
|
19
|
+
with(earned_at: Time.now)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|