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.
Files changed (285) hide show
  1. checksums.yaml +4 -4
  2. data/THIRD_PARTY_NOTICES +37 -2
  3. data/assets/placeholder_boxart.png +0 -0
  4. data/bin/gemba +2 -2
  5. data/ext/gemba/extconf.rb +23 -1
  6. data/ext/gemba/gemba_ext.c +436 -2
  7. data/ext/gemba/gemba_ext.h +2 -0
  8. data/gemba.gemspec +5 -3
  9. data/lib/gemba/achievements/achievement.rb +23 -0
  10. data/lib/gemba/achievements/backend.rb +186 -0
  11. data/lib/gemba/achievements/cache.rb +70 -0
  12. data/lib/gemba/achievements/credentials_presenter.rb +142 -0
  13. data/lib/gemba/achievements/fake_backend.rb +205 -0
  14. data/lib/gemba/achievements/null_backend.rb +11 -0
  15. data/lib/gemba/achievements/offline_backend.rb +168 -0
  16. data/lib/gemba/achievements/retro_achievements/backend.rb +453 -0
  17. data/lib/gemba/achievements/retro_achievements/cli_sync_requester.rb +64 -0
  18. data/lib/gemba/achievements/retro_achievements/ping_worker.rb +27 -0
  19. data/lib/gemba/achievements.rb +19 -0
  20. data/lib/gemba/achievements_window.rb +556 -0
  21. data/lib/gemba/app_controller.rb +1015 -0
  22. data/lib/gemba/bios.rb +54 -0
  23. data/lib/gemba/boxart_fetcher/libretro_backend.rb +39 -0
  24. data/lib/gemba/boxart_fetcher/null_backend.rb +12 -0
  25. data/lib/gemba/boxart_fetcher.rb +79 -0
  26. data/lib/gemba/bus_emitter.rb +13 -0
  27. data/lib/gemba/child_window.rb +24 -1
  28. data/lib/gemba/cli/commands/config_cmd.rb +83 -0
  29. data/lib/gemba/cli/commands/decode.rb +154 -0
  30. data/lib/gemba/cli/commands/patch.rb +78 -0
  31. data/lib/gemba/cli/commands/play.rb +78 -0
  32. data/lib/gemba/cli/commands/record.rb +114 -0
  33. data/lib/gemba/cli/commands/replay.rb +161 -0
  34. data/lib/gemba/cli/commands/retro_achievements.rb +213 -0
  35. data/lib/gemba/cli/commands/version.rb +22 -0
  36. data/lib/gemba/cli.rb +52 -364
  37. data/lib/gemba/config.rb +134 -1
  38. data/lib/gemba/data/gb_games.json +1 -0
  39. data/lib/gemba/data/gb_md5.json +1 -0
  40. data/lib/gemba/data/gba_games.json +1 -0
  41. data/lib/gemba/data/gba_md5.json +1 -0
  42. data/lib/gemba/data/gbc_games.json +1 -0
  43. data/lib/gemba/data/gbc_md5.json +1 -0
  44. data/lib/gemba/emulator_frame.rb +1060 -0
  45. data/lib/gemba/event_bus.rb +48 -0
  46. data/lib/gemba/frame_stack.rb +60 -0
  47. data/lib/gemba/game_index.rb +84 -0
  48. data/lib/gemba/game_picker_frame.rb +268 -0
  49. data/lib/gemba/gamepad_map.rb +103 -0
  50. data/lib/gemba/headless.rb +6 -5
  51. data/lib/gemba/headless_player.rb +33 -3
  52. data/lib/gemba/help_window.rb +61 -0
  53. data/lib/gemba/hotkey_map.rb +3 -1
  54. data/lib/gemba/input_recorder.rb +107 -0
  55. data/lib/gemba/input_replayer.rb +119 -0
  56. data/lib/gemba/keyboard_map.rb +90 -0
  57. data/lib/gemba/locales/en.yml +97 -5
  58. data/lib/gemba/locales/ja.yml +97 -5
  59. data/lib/gemba/main_window.rb +56 -0
  60. data/lib/gemba/modal_stack.rb +81 -0
  61. data/lib/gemba/patcher_window.rb +223 -0
  62. data/lib/gemba/platform/gb.rb +21 -0
  63. data/lib/gemba/platform/gba.rb +21 -0
  64. data/lib/gemba/platform/gbc.rb +23 -0
  65. data/lib/gemba/platform.rb +20 -0
  66. data/lib/gemba/platform_open.rb +19 -0
  67. data/lib/gemba/recorder.rb +4 -3
  68. data/lib/gemba/replay_player.rb +691 -0
  69. data/lib/gemba/rom_info.rb +57 -0
  70. data/lib/gemba/rom_info_window.rb +16 -3
  71. data/lib/gemba/rom_library.rb +106 -0
  72. data/lib/gemba/rom_overrides.rb +47 -0
  73. data/lib/gemba/rom_patcher/bps.rb +161 -0
  74. data/lib/gemba/rom_patcher/ips.rb +101 -0
  75. data/lib/gemba/rom_patcher/ups.rb +118 -0
  76. data/lib/gemba/rom_patcher.rb +109 -0
  77. data/lib/gemba/{rom_loader.rb → rom_resolver.rb} +7 -6
  78. data/lib/gemba/runtime.rb +59 -26
  79. data/lib/gemba/save_state_manager.rb +4 -7
  80. data/lib/gemba/save_state_picker.rb +17 -4
  81. data/lib/gemba/session_logger.rb +64 -0
  82. data/lib/gemba/settings/audio_tab.rb +77 -0
  83. data/lib/gemba/settings/gamepad_tab.rb +351 -0
  84. data/lib/gemba/settings/hotkeys_tab.rb +259 -0
  85. data/lib/gemba/settings/paths.rb +11 -0
  86. data/lib/gemba/settings/recording_tab.rb +83 -0
  87. data/lib/gemba/settings/save_states_tab.rb +91 -0
  88. data/lib/gemba/settings/system_tab.rb +362 -0
  89. data/lib/gemba/settings/video_tab.rb +318 -0
  90. data/lib/gemba/settings_window.rb +162 -1036
  91. data/lib/gemba/version.rb +1 -1
  92. data/lib/gemba/virtual_keyboard.rb +19 -0
  93. data/lib/gemba.rb +2 -12
  94. data/test/achievements_window/test_bulk_sync.rb +218 -0
  95. data/test/achievements_window/test_bus_events.rb +125 -0
  96. data/test/achievements_window/test_close_confirmation.rb +201 -0
  97. data/test/achievements_window/test_initial_state.rb +164 -0
  98. data/test/achievements_window/test_sorting.rb +227 -0
  99. data/test/achievements_window/test_tree_rendering.rb +133 -0
  100. data/test/fixtures/fake_bios.bin +0 -0
  101. data/test/fixtures/pong.gba +0 -0
  102. data/test/fixtures/test.gb +0 -0
  103. data/test/fixtures/test.gbc +0 -0
  104. data/test/fixtures/test_quicksave.ss +0 -0
  105. data/test/screenshots/no_focus.png +0 -0
  106. data/test/shared/teek_test_worker.rb +17 -1
  107. data/test/shared/tk_test_helper.rb +91 -4
  108. data/test/support/achievements_window_helpers.rb +18 -0
  109. data/test/support/fake_core.rb +25 -0
  110. data/test/support/fake_ra_runtime.rb +74 -0
  111. data/test/support/fake_requester.rb +68 -0
  112. data/test/support/player_helpers.rb +20 -5
  113. data/test/test_achievement.rb +32 -0
  114. data/test/{test_player.rb → test_app_controller.rb} +353 -85
  115. data/test/test_bios.rb +123 -0
  116. data/test/test_boxart_fetcher.rb +150 -0
  117. data/test/test_cli.rb +17 -265
  118. data/test/test_cli_config.rb +64 -0
  119. data/test/test_cli_decode.rb +97 -0
  120. data/test/test_cli_patch.rb +58 -0
  121. data/test/test_cli_play.rb +213 -0
  122. data/test/test_cli_ra.rb +175 -0
  123. data/test/test_cli_record.rb +69 -0
  124. data/test/test_cli_replay.rb +72 -0
  125. data/test/test_cli_sync_requester.rb +152 -0
  126. data/test/test_cli_version.rb +27 -0
  127. data/test/test_config.rb +2 -3
  128. data/test/test_config_ra.rb +69 -0
  129. data/test/test_core.rb +62 -1
  130. data/test/test_credentials_presenter.rb +192 -0
  131. data/test/test_event_bus.rb +100 -0
  132. data/test/test_fake_backend_achievements.rb +130 -0
  133. data/test/test_fake_backend_auth.rb +68 -0
  134. data/test/test_game_index.rb +77 -0
  135. data/test/test_game_picker_frame.rb +310 -0
  136. data/test/test_gamepad_map.rb +1 -3
  137. data/test/test_headless_player.rb +17 -3
  138. data/test/test_help_window.rb +82 -0
  139. data/test/test_hotkey_map.rb +22 -1
  140. data/test/test_input_recorder.rb +179 -0
  141. data/test/test_input_replay_determinism.rb +113 -0
  142. data/test/test_input_replayer.rb +162 -0
  143. data/test/test_keyboard_map.rb +1 -3
  144. data/test/test_libretro_backend.rb +41 -0
  145. data/test/test_locale.rb +1 -1
  146. data/test/test_logging.rb +123 -0
  147. data/test/test_null_backend.rb +42 -0
  148. data/test/test_offline_backend.rb +116 -0
  149. data/test/test_overlay_renderer.rb +1 -1
  150. data/test/test_platform.rb +149 -0
  151. data/test/test_ra_backend.rb +313 -0
  152. data/test/test_ra_backend_unlock_gate.rb +56 -0
  153. data/test/test_recorder.rb +0 -3
  154. data/test/test_replay_player.rb +316 -0
  155. data/test/test_rom_info.rb +149 -0
  156. data/test/test_rom_overrides.rb +86 -0
  157. data/test/test_rom_patcher.rb +382 -0
  158. data/test/{test_rom_loader.rb → test_rom_resolver.rb} +25 -26
  159. data/test/test_save_state_manager.rb +2 -4
  160. data/test/test_settings_audio.rb +107 -0
  161. data/test/test_settings_hotkeys.rb +83 -66
  162. data/test/test_settings_recording.rb +49 -0
  163. data/test/test_settings_save_states.rb +97 -0
  164. data/test/test_settings_system.rb +133 -0
  165. data/test/test_settings_video.rb +450 -0
  166. data/test/test_settings_window.rb +76 -507
  167. data/test/test_tip_service.rb +6 -6
  168. data/test/test_toast_overlay.rb +1 -1
  169. data/test/test_virtual_events.rb +156 -0
  170. data/test/test_virtual_keyboard.rb +1 -1
  171. data/vendor/rcheevos/CHANGELOG.md +495 -0
  172. data/vendor/rcheevos/LICENSE +21 -0
  173. data/vendor/rcheevos/Package.swift +33 -0
  174. data/vendor/rcheevos/README.md +67 -0
  175. data/vendor/rcheevos/include/module.modulemap +70 -0
  176. data/vendor/rcheevos/include/rc_api_editor.h +296 -0
  177. data/vendor/rcheevos/include/rc_api_info.h +280 -0
  178. data/vendor/rcheevos/include/rc_api_request.h +77 -0
  179. data/vendor/rcheevos/include/rc_api_runtime.h +417 -0
  180. data/vendor/rcheevos/include/rc_api_user.h +262 -0
  181. data/vendor/rcheevos/include/rc_client.h +877 -0
  182. data/vendor/rcheevos/include/rc_client_raintegration.h +101 -0
  183. data/vendor/rcheevos/include/rc_consoles.h +138 -0
  184. data/vendor/rcheevos/include/rc_error.h +59 -0
  185. data/vendor/rcheevos/include/rc_export.h +100 -0
  186. data/vendor/rcheevos/include/rc_hash.h +200 -0
  187. data/vendor/rcheevos/include/rc_runtime.h +148 -0
  188. data/vendor/rcheevos/include/rc_runtime_types.h +452 -0
  189. data/vendor/rcheevos/include/rc_util.h +51 -0
  190. data/vendor/rcheevos/include/rcheevos.h +8 -0
  191. data/vendor/rcheevos/src/rapi/rc_api_common.c +1379 -0
  192. data/vendor/rcheevos/src/rapi/rc_api_common.h +88 -0
  193. data/vendor/rcheevos/src/rapi/rc_api_editor.c +625 -0
  194. data/vendor/rcheevos/src/rapi/rc_api_info.c +587 -0
  195. data/vendor/rcheevos/src/rapi/rc_api_runtime.c +901 -0
  196. data/vendor/rcheevos/src/rapi/rc_api_user.c +483 -0
  197. data/vendor/rcheevos/src/rc_client.c +6941 -0
  198. data/vendor/rcheevos/src/rc_client_external.c +281 -0
  199. data/vendor/rcheevos/src/rc_client_external.h +177 -0
  200. data/vendor/rcheevos/src/rc_client_external_versions.h +171 -0
  201. data/vendor/rcheevos/src/rc_client_internal.h +409 -0
  202. data/vendor/rcheevos/src/rc_client_raintegration.c +566 -0
  203. data/vendor/rcheevos/src/rc_client_raintegration_internal.h +61 -0
  204. data/vendor/rcheevos/src/rc_client_types.natvis +396 -0
  205. data/vendor/rcheevos/src/rc_compat.c +251 -0
  206. data/vendor/rcheevos/src/rc_compat.h +121 -0
  207. data/vendor/rcheevos/src/rc_libretro.c +915 -0
  208. data/vendor/rcheevos/src/rc_libretro.h +98 -0
  209. data/vendor/rcheevos/src/rc_util.c +199 -0
  210. data/vendor/rcheevos/src/rc_version.c +11 -0
  211. data/vendor/rcheevos/src/rc_version.h +32 -0
  212. data/vendor/rcheevos/src/rcheevos/alloc.c +312 -0
  213. data/vendor/rcheevos/src/rcheevos/condition.c +754 -0
  214. data/vendor/rcheevos/src/rcheevos/condset.c +777 -0
  215. data/vendor/rcheevos/src/rcheevos/consoleinfo.c +1215 -0
  216. data/vendor/rcheevos/src/rcheevos/format.c +330 -0
  217. data/vendor/rcheevos/src/rcheevos/lboard.c +287 -0
  218. data/vendor/rcheevos/src/rcheevos/memref.c +805 -0
  219. data/vendor/rcheevos/src/rcheevos/operand.c +607 -0
  220. data/vendor/rcheevos/src/rcheevos/rc_internal.h +390 -0
  221. data/vendor/rcheevos/src/rcheevos/rc_runtime_types.natvis +541 -0
  222. data/vendor/rcheevos/src/rcheevos/rc_validate.c +1406 -0
  223. data/vendor/rcheevos/src/rcheevos/rc_validate.h +18 -0
  224. data/vendor/rcheevos/src/rcheevos/richpresence.c +922 -0
  225. data/vendor/rcheevos/src/rcheevos/runtime.c +852 -0
  226. data/vendor/rcheevos/src/rcheevos/runtime_progress.c +1073 -0
  227. data/vendor/rcheevos/src/rcheevos/trigger.c +344 -0
  228. data/vendor/rcheevos/src/rcheevos/value.c +935 -0
  229. data/vendor/rcheevos/src/rhash/aes.c +480 -0
  230. data/vendor/rcheevos/src/rhash/aes.h +49 -0
  231. data/vendor/rcheevos/src/rhash/cdreader.c +838 -0
  232. data/vendor/rcheevos/src/rhash/hash.c +1402 -0
  233. data/vendor/rcheevos/src/rhash/hash_disc.c +1340 -0
  234. data/vendor/rcheevos/src/rhash/hash_encrypted.c +566 -0
  235. data/vendor/rcheevos/src/rhash/hash_rom.c +426 -0
  236. data/vendor/rcheevos/src/rhash/hash_zip.c +460 -0
  237. data/vendor/rcheevos/src/rhash/md5.c +382 -0
  238. data/vendor/rcheevos/src/rhash/md5.h +91 -0
  239. data/vendor/rcheevos/src/rhash/rc_hash_internal.h +116 -0
  240. data/vendor/rcheevos/test/libretro.h +205 -0
  241. data/vendor/rcheevos/test/rapi/test_rc_api_common.c +941 -0
  242. data/vendor/rcheevos/test/rapi/test_rc_api_editor.c +931 -0
  243. data/vendor/rcheevos/test/rapi/test_rc_api_info.c +545 -0
  244. data/vendor/rcheevos/test/rapi/test_rc_api_runtime.c +2213 -0
  245. data/vendor/rcheevos/test/rapi/test_rc_api_user.c +998 -0
  246. data/vendor/rcheevos/test/rcheevos/mock_memory.h +32 -0
  247. data/vendor/rcheevos/test/rcheevos/test_condition.c +570 -0
  248. data/vendor/rcheevos/test/rcheevos/test_condset.c +5170 -0
  249. data/vendor/rcheevos/test/rcheevos/test_consoleinfo.c +203 -0
  250. data/vendor/rcheevos/test/rcheevos/test_format.c +112 -0
  251. data/vendor/rcheevos/test/rcheevos/test_lboard.c +746 -0
  252. data/vendor/rcheevos/test/rcheevos/test_memref.c +520 -0
  253. data/vendor/rcheevos/test/rcheevos/test_operand.c +692 -0
  254. data/vendor/rcheevos/test/rcheevos/test_rc_validate.c +502 -0
  255. data/vendor/rcheevos/test/rcheevos/test_richpresence.c +1564 -0
  256. data/vendor/rcheevos/test/rcheevos/test_runtime.c +1667 -0
  257. data/vendor/rcheevos/test/rcheevos/test_runtime_progress.c +1821 -0
  258. data/vendor/rcheevos/test/rcheevos/test_timing.c +166 -0
  259. data/vendor/rcheevos/test/rcheevos/test_trigger.c +2521 -0
  260. data/vendor/rcheevos/test/rcheevos/test_value.c +870 -0
  261. data/vendor/rcheevos/test/rcheevos-test.sln +46 -0
  262. data/vendor/rcheevos/test/rcheevos-test.vcxproj +239 -0
  263. data/vendor/rcheevos/test/rcheevos-test.vcxproj.filters +335 -0
  264. data/vendor/rcheevos/test/rhash/data.c +657 -0
  265. data/vendor/rcheevos/test/rhash/data.h +32 -0
  266. data/vendor/rcheevos/test/rhash/mock_filereader.c +236 -0
  267. data/vendor/rcheevos/test/rhash/mock_filereader.h +31 -0
  268. data/vendor/rcheevos/test/rhash/test_cdreader.c +920 -0
  269. data/vendor/rcheevos/test/rhash/test_hash.c +310 -0
  270. data/vendor/rcheevos/test/rhash/test_hash_disc.c +1450 -0
  271. data/vendor/rcheevos/test/rhash/test_hash_rom.c +899 -0
  272. data/vendor/rcheevos/test/rhash/test_hash_zip.c +551 -0
  273. data/vendor/rcheevos/test/test.c +113 -0
  274. data/vendor/rcheevos/test/test_framework.h +205 -0
  275. data/vendor/rcheevos/test/test_rc_client.c +10509 -0
  276. data/vendor/rcheevos/test/test_rc_client_external.c +2197 -0
  277. data/vendor/rcheevos/test/test_rc_client_raintegration.c +441 -0
  278. data/vendor/rcheevos/test/test_rc_libretro.c +952 -0
  279. data/vendor/rcheevos/test/test_types.natvis +9 -0
  280. data/vendor/rcheevos/validator/validator.c +658 -0
  281. data/vendor/rcheevos/validator/validator.vcxproj +152 -0
  282. data/vendor/rcheevos/validator/validator.vcxproj.filters +82 -0
  283. metadata +274 -11
  284. data/lib/gemba/input_mappings.rb +0 -214
  285. data/lib/gemba/player.rb +0 -1525
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8dcf53136583f20d749d7f78ac8069b846420d899256599f1e57525ee63bf1cd
4
- data.tar.gz: 2396347707db1872322af8e4a01e3c47aaceaa2e7aeb1f2f6bc8eb084d6ef09e
3
+ metadata.gz: 3b7478522afdccb445e067ff5e812f499cf7e8aec5f0708d4d12c9ee81224f4a
4
+ data.tar.gz: 83ae429789c3beb4c6cb173a8bffdfd5d5a70c7a3451621e21507ef040ccb484
5
5
  SHA512:
6
- metadata.gz: fa22b34dcd284934c27ea67ea1500a079e4f275964eb7f24b97ebdc8ac9baca79234217e4748287a0855ba33beb14b3392b0b350bb057eae994e00d16ea57313
7
- data.tar.gz: d31bebb679d3d27d6e3928306e9af936a4bdf15e750d2690ae631a446059ad973394db8101490599919bf284de338b58d987aefdbd77359a2d8215e4cad9ca51
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 fonts in the assets/ directory.
2
- Both are licensed under the SIL Open Font License, Version 1.1.
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("../lib/gemba.rb", __dir__))
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
- $srcs = ['gemba_ext.c']
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')
@@ -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, "11", &rom_path, &save_dir);
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
 
@@ -7,6 +7,8 @@
7
7
  #include <mgba/core/directories.h>
8
8
  #include <mgba/core/log.h>
9
9
  #include <mgba-util/vfs.h>
10
+ #include <mgba/internal/gba/bios.h>
11
+ #include <mgba/internal/gba/gba.h>
10
12
 
11
13
  extern VALUE mGemba;
12
14
 
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.2"
25
- spec.add_dependency "teek-sdl2", ">= 0.1.3"
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