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
@@ -0,0 +1,922 @@
1
+ #include "rc_internal.h"
2
+
3
+ #include "../rc_compat.h"
4
+
5
+ #include <ctype.h>
6
+
7
+ /* special formats only used by rc_richpresence_display_part_t.display_type. must not overlap other RC_FORMAT values */
8
+ enum {
9
+ RC_FORMAT_STRING = 101,
10
+ RC_FORMAT_LOOKUP = 102,
11
+ RC_FORMAT_UNKNOWN_MACRO = 103,
12
+ RC_FORMAT_ASCIICHAR = 104,
13
+ RC_FORMAT_UNICODECHAR = 105
14
+ };
15
+
16
+ static void rc_alloc_helper_variable_memref_value(rc_richpresence_display_part_t* part, const char* memaddr, int memaddr_len, rc_parse_state_t* parse) {
17
+ rc_preparse_state_t preparse;
18
+ const char* test_memaddr = memaddr;
19
+ rc_condset_t* condset;
20
+ rc_value_t* value;
21
+ int32_t size;
22
+
23
+ part->value.type = RC_OPERAND_NONE;
24
+
25
+ /* if the expression can be represented as just a memory reference, do so */
26
+ rc_init_preparse_state(&preparse);
27
+ preparse.parse.existing_memrefs = parse->memrefs;
28
+ value = RC_ALLOC(rc_value_t, &preparse.parse);
29
+ rc_parse_value_internal(value, &test_memaddr, &preparse.parse);
30
+
31
+ size = preparse.parse.offset;
32
+ if (size < 0) {
33
+ parse->offset = size;
34
+ rc_destroy_preparse_state(&preparse);
35
+ return;
36
+ }
37
+
38
+ /* ensure new needed memrefs are allocated in the primary buffer */
39
+ rc_preparse_copy_memrefs(parse, &preparse.memrefs);
40
+
41
+ /* parse the value into the scratch buffer so we can look at it */
42
+ rc_reset_parse_state(&preparse.parse, rc_buffer_alloc(&preparse.parse.scratch.buffer, (size_t)size));
43
+ preparse.parse.memrefs = parse->memrefs;
44
+ preparse.parse.existing_memrefs = parse->existing_memrefs;
45
+ value = RC_ALLOC(rc_value_t, &preparse.parse);
46
+ test_memaddr = memaddr;
47
+ rc_parse_value_internal(value, &test_memaddr, &preparse.parse);
48
+
49
+ condset = value->conditions;
50
+ if (condset && !condset->next) {
51
+ /* single value - if it's a single Measured clause (including any AddSource/AddAddress helpers), we can
52
+ * simplify to a memref. If there are supporting clauses like MeasuredIf or ResetIf, we can't */
53
+ if (condset->num_measured_conditions == 1 &&
54
+ !condset->num_pause_conditions && !condset->num_reset_conditions &&
55
+ !condset->num_other_conditions && !condset->num_hittarget_conditions) {
56
+ rc_condition_t* condition = condset->conditions;
57
+ for (; condition; condition = condition->next) {
58
+ if (condition->type == RC_CONDITION_MEASURED && condition->required_hits == 0) {
59
+ memcpy(&part->value, &condition->operand1, sizeof(condition->operand1));
60
+ break;
61
+ }
62
+ }
63
+ }
64
+ }
65
+
66
+ rc_destroy_preparse_state(&preparse);
67
+
68
+ /* could not express value with just a memory reference, create a helper variable */
69
+ if (part->value.type == RC_OPERAND_NONE) {
70
+ value = rc_alloc_variable(memaddr, memaddr_len, parse);
71
+ if (value) {
72
+ part->value.value.memref = (rc_memref_t*)&value->value;
73
+ part->value.type = RC_OPERAND_ADDRESS;
74
+ part->value.size = RC_MEMSIZE_32_BITS;
75
+ part->value.memref_access_type = RC_OPERAND_ADDRESS;
76
+ }
77
+ }
78
+ }
79
+
80
+ static const char* rc_parse_line(const char* line, const char** end, rc_parse_state_t* parse) {
81
+ const char* nextline;
82
+ const char* endline;
83
+
84
+ /* get a single line */
85
+ nextline = line;
86
+ while (*nextline && *nextline != '\n')
87
+ ++nextline;
88
+
89
+ /* if a trailing comment marker (//) exists, the line stops there */
90
+ endline = line;
91
+ while (endline < nextline && (endline[0] != '/' || endline[1] != '/' || (endline > line && endline[-1] == '\\')))
92
+ ++endline;
93
+
94
+ if (endline == nextline) {
95
+ /* trailing whitespace on a line without a comment marker may be significant, just remove the line ending */
96
+ if (endline > line && endline[-1] == '\r')
97
+ --endline;
98
+ } else {
99
+ /* remove trailing whitespace before the comment marker */
100
+ while (endline > line && isspace((int)((unsigned char*)endline)[-1]))
101
+ --endline;
102
+ }
103
+
104
+ /* point end at the first character to ignore, it makes subtraction for length easier */
105
+ *end = endline;
106
+
107
+ /* tally the line */
108
+ ++parse->lines_read;
109
+
110
+ /* skip the newline character so we're pointing at the next line */
111
+ if (*nextline == '\n')
112
+ ++nextline;
113
+
114
+ return nextline;
115
+ }
116
+
117
+ typedef struct rc_richpresence_builtin_macro_t {
118
+ const char* name;
119
+ size_t name_len;
120
+ uint8_t display_type;
121
+ } rc_richpresence_builtin_macro_t;
122
+
123
+ static rc_richpresence_display_t* rc_parse_richpresence_display_internal(const char* line, const char* endline, rc_parse_state_t* parse, rc_richpresence_lookup_t* first_lookup) {
124
+ rc_richpresence_display_t* self;
125
+ rc_richpresence_display_part_t* part;
126
+ rc_richpresence_display_part_t** next;
127
+ rc_richpresence_lookup_t* lookup;
128
+ const char* ptr;
129
+ const char* in;
130
+ char* out;
131
+
132
+ if (endline - line < 1) {
133
+ parse->offset = RC_MISSING_DISPLAY_STRING;
134
+ return 0;
135
+ }
136
+
137
+ {
138
+ self = RC_ALLOC(rc_richpresence_display_t, parse);
139
+ memset(self, 0, sizeof(rc_richpresence_display_t));
140
+ next = &self->display;
141
+ }
142
+
143
+ /* break the string up on macros: text @macro() moretext */
144
+ do {
145
+ ptr = line;
146
+ while (ptr < endline) {
147
+ if (*ptr == '@' && (ptr == line || ptr[-1] != '\\')) /* ignore escaped @s */
148
+ break;
149
+
150
+ ++ptr;
151
+ }
152
+
153
+ if (ptr > line) {
154
+ part = RC_ALLOC(rc_richpresence_display_part_t, parse);
155
+ memset(part, 0, sizeof(rc_richpresence_display_part_t));
156
+ *next = part;
157
+ next = &part->next;
158
+
159
+ /* handle string part */
160
+ part->display_type = RC_FORMAT_STRING;
161
+ part->text = rc_alloc_str(parse, line, (int)(ptr - line));
162
+ if (part->text) {
163
+ /* remove backslashes used for escaping */
164
+ in = part->text;
165
+ while (*in && *in != '\\')
166
+ ++in;
167
+
168
+ if (*in == '\\') {
169
+ out = (char*)in++;
170
+ while (*in) {
171
+ *out++ = *in++;
172
+ if (*in == '\\')
173
+ ++in;
174
+ }
175
+ *out = '\0';
176
+ }
177
+ }
178
+ }
179
+
180
+ if (*ptr == '@') {
181
+ /* handle macro part */
182
+ size_t macro_len;
183
+
184
+ line = ++ptr;
185
+ while (ptr < endline && *ptr != '(')
186
+ ++ptr;
187
+
188
+ if (ptr == endline) {
189
+ parse->offset = RC_MISSING_VALUE;
190
+ return 0;
191
+ }
192
+
193
+ macro_len = ptr - line;
194
+
195
+ part = RC_ALLOC(rc_richpresence_display_part_t, parse);
196
+ memset(part, 0, sizeof(rc_richpresence_display_part_t));
197
+ *next = part;
198
+ next = &part->next;
199
+
200
+ part->display_type = RC_FORMAT_UNKNOWN_MACRO;
201
+
202
+ /* find the lookup and hook it up */
203
+ lookup = first_lookup;
204
+ while (lookup) {
205
+ if (strncmp(lookup->name, line, macro_len) == 0 && lookup->name[macro_len] == '\0') {
206
+ part->text = lookup->name;
207
+ part->lookup = lookup;
208
+ part->display_type = lookup->format;
209
+ break;
210
+ }
211
+
212
+ lookup = lookup->next;
213
+ }
214
+
215
+ if (!lookup) {
216
+ static const rc_richpresence_builtin_macro_t builtin_macros[] = {
217
+ {"Number", 6, RC_FORMAT_VALUE},
218
+ {"Score", 5, RC_FORMAT_SCORE},
219
+ {"Centiseconds", 12, RC_FORMAT_CENTISECS},
220
+ {"Seconds", 7, RC_FORMAT_SECONDS},
221
+ {"Minutes", 7, RC_FORMAT_MINUTES},
222
+ {"SecondsAsMinutes", 16, RC_FORMAT_SECONDS_AS_MINUTES},
223
+ {"ASCIIChar", 9, RC_FORMAT_ASCIICHAR},
224
+ {"UnicodeChar", 11, RC_FORMAT_UNICODECHAR},
225
+ {"Float1", 6, RC_FORMAT_FLOAT1},
226
+ {"Float2", 6, RC_FORMAT_FLOAT2},
227
+ {"Float3", 6, RC_FORMAT_FLOAT3},
228
+ {"Float4", 6, RC_FORMAT_FLOAT4},
229
+ {"Float5", 6, RC_FORMAT_FLOAT5},
230
+ {"Float6", 6, RC_FORMAT_FLOAT6},
231
+ {"Fixed1", 6, RC_FORMAT_FIXED1},
232
+ {"Fixed2", 6, RC_FORMAT_FIXED2},
233
+ {"Fixed3", 6, RC_FORMAT_FIXED3},
234
+ {"Unsigned", 8, RC_FORMAT_UNSIGNED_VALUE},
235
+ {"Unformatted", 11, RC_FORMAT_UNFORMATTED}
236
+ };
237
+ size_t i;
238
+
239
+ for (i = 0; i < sizeof(builtin_macros) / sizeof(builtin_macros[0]); ++i) {
240
+ if (macro_len == builtin_macros[i].name_len &&
241
+ memcmp(builtin_macros[i].name, line, builtin_macros[i].name_len) == 0) {
242
+ part->text = builtin_macros[i].name;
243
+ part->lookup = NULL;
244
+ part->display_type = builtin_macros[i].display_type;
245
+ break;
246
+ }
247
+ }
248
+ }
249
+
250
+ /* find the closing parenthesis */
251
+ in = line;
252
+ line = ++ptr;
253
+ while (ptr < endline && *ptr != ')')
254
+ ++ptr;
255
+
256
+ if (*ptr != ')') {
257
+ /* non-terminated macro, dump the macro and the remaining portion of the line */
258
+ --in; /* already skipped over @ */
259
+ part->display_type = RC_FORMAT_STRING;
260
+ part->text = rc_alloc_str(parse, in, (int)(ptr - in));
261
+ }
262
+ else if (part->display_type != RC_FORMAT_UNKNOWN_MACRO) {
263
+ rc_alloc_helper_variable_memref_value(part, line, (int)(ptr - line), parse);
264
+ if (parse->offset < 0)
265
+ return 0;
266
+
267
+ ++ptr;
268
+ }
269
+ else {
270
+ /* assert: the allocated string is going to be smaller than the memory used for the parameter of the macro */
271
+ ++ptr;
272
+ part->text = rc_alloc_str(parse, in, (int)(ptr - in));
273
+ }
274
+ }
275
+
276
+ line = ptr;
277
+ } while (line < endline);
278
+
279
+ *next = 0;
280
+
281
+ return self;
282
+ }
283
+
284
+ static int rc_richpresence_lookup_item_count(rc_richpresence_lookup_item_t* item)
285
+ {
286
+ if (item == NULL)
287
+ return 0;
288
+
289
+ return (rc_richpresence_lookup_item_count(item->left) + rc_richpresence_lookup_item_count(item->right) + 1);
290
+ }
291
+
292
+ static void rc_rebalance_richpresence_lookup_get_items(rc_richpresence_lookup_item_t* root,
293
+ rc_richpresence_lookup_item_t** items, int* index)
294
+ {
295
+ if (root->left != NULL)
296
+ rc_rebalance_richpresence_lookup_get_items(root->left, items, index);
297
+
298
+ items[*index] = root;
299
+ ++(*index);
300
+
301
+ if (root->right != NULL)
302
+ rc_rebalance_richpresence_lookup_get_items(root->right, items, index);
303
+ }
304
+
305
+ static void rc_rebalance_richpresence_lookup_rebuild(rc_richpresence_lookup_item_t** root,
306
+ rc_richpresence_lookup_item_t** items, int first, int last)
307
+ {
308
+ int mid = (first + last) / 2;
309
+ rc_richpresence_lookup_item_t* item = items[mid];
310
+ *root = item;
311
+
312
+ if (mid == first)
313
+ item->left = NULL;
314
+ else
315
+ rc_rebalance_richpresence_lookup_rebuild(&item->left, items, first, mid - 1);
316
+
317
+ if (mid == last)
318
+ item->right = NULL;
319
+ else
320
+ rc_rebalance_richpresence_lookup_rebuild(&item->right, items, mid + 1, last);
321
+ }
322
+
323
+ static void rc_rebalance_richpresence_lookup(rc_richpresence_lookup_item_t** root, rc_parse_state_t* parse)
324
+ {
325
+ rc_richpresence_lookup_item_t** items;
326
+ int index;
327
+ int size;
328
+
329
+ /* don't bother rebalancing one or two items */
330
+ int count = rc_richpresence_lookup_item_count(*root);
331
+ if (count < 3)
332
+ return;
333
+
334
+ /* allocate space for the flattened list in scratch memory */
335
+ size = count * sizeof(rc_richpresence_lookup_item_t*);
336
+ items = (rc_richpresence_lookup_item_t**)rc_buffer_alloc(&parse->scratch.buffer, size);
337
+
338
+ /* if allocation fails, we can still use the unbalanced tree, so just bail out */
339
+ if (items == NULL)
340
+ return;
341
+
342
+ /* flatten the list */
343
+ index = 0;
344
+ rc_rebalance_richpresence_lookup_get_items(*root, items, &index);
345
+
346
+ /* and rebuild it as a balanced tree */
347
+ rc_rebalance_richpresence_lookup_rebuild(root, items, 0, count - 1);
348
+ }
349
+
350
+ static void rc_insert_richpresence_lookup_item(rc_richpresence_lookup_t* lookup,
351
+ uint32_t first, uint32_t last, const char* label, size_t label_len, rc_parse_state_t* parse)
352
+ {
353
+ rc_richpresence_lookup_item_t** next;
354
+ rc_richpresence_lookup_item_t* item;
355
+
356
+ next = &lookup->root;
357
+ while ((item = *next) != NULL) {
358
+ if (first > item->last) {
359
+ if (first == item->last + 1 &&
360
+ strncmp(label, item->label, label_len) == 0 && item->label[label_len] == '\0') {
361
+ item->last = last;
362
+ return;
363
+ }
364
+
365
+ next = &item->right;
366
+ }
367
+ else if (last < item->first) {
368
+ if (last == item->first - 1 &&
369
+ strncmp(label, item->label, label_len) == 0 && item->label[label_len] == '\0') {
370
+ item->first = first;
371
+ return;
372
+ }
373
+
374
+ next = &item->left;
375
+ }
376
+ else {
377
+ parse->offset = RC_DUPLICATED_VALUE;
378
+ return;
379
+ }
380
+ }
381
+
382
+ item = RC_ALLOC_SCRATCH(rc_richpresence_lookup_item_t, parse);
383
+ item->first = first;
384
+ item->last = last;
385
+ item->label = rc_alloc_str(parse, label, label_len);
386
+ item->left = item->right = NULL;
387
+
388
+ *next = item;
389
+ }
390
+
391
+ static const char* rc_parse_richpresence_lookup(rc_richpresence_lookup_t* lookup, const char* nextline, rc_parse_state_t* parse)
392
+ {
393
+ const char* line;
394
+ const char* endline;
395
+ const char* label;
396
+ char* endptr = 0;
397
+ uint32_t first, last;
398
+ int base;
399
+
400
+ do
401
+ {
402
+ line = nextline;
403
+ if (line == NULL)
404
+ break;
405
+
406
+ nextline = rc_parse_line(line, &endline, parse);
407
+
408
+ if (endline - line < 2) {
409
+ /* ignore full line comments inside a lookup */
410
+ if (line[0] == '/' && line[1] == '/')
411
+ continue;
412
+
413
+ /* empty line indicates end of lookup */
414
+ if (lookup->root)
415
+ rc_rebalance_richpresence_lookup(&lookup->root, parse);
416
+ break;
417
+ }
418
+
419
+ /* "*=XXX" specifies default label if lookup does not provide a mapping for the value */
420
+ if (line[0] == '*' && line[1] == '=') {
421
+ line += 2;
422
+ lookup->default_label = rc_alloc_str(parse, line, (int)(endline - line));
423
+ continue;
424
+ }
425
+
426
+ label = line;
427
+ while (label < endline && *label != '=')
428
+ ++label;
429
+
430
+ if (label == endline) {
431
+ parse->offset = RC_MISSING_VALUE;
432
+ break;
433
+ }
434
+ ++label;
435
+
436
+ do {
437
+ /* get the value for the mapping */
438
+ if (line[0] == '0' && line[1] == 'x') {
439
+ line += 2;
440
+ base = 16;
441
+ } else {
442
+ base = 10;
443
+ }
444
+
445
+ first = (unsigned)strtoul(line, &endptr, base);
446
+
447
+ /* check for a range */
448
+ if (*endptr != '-') {
449
+ /* no range, just set last to first */
450
+ last = first;
451
+ }
452
+ else {
453
+ /* range, get last value */
454
+ line = endptr + 1;
455
+
456
+ if (line[0] == '0' && line[1] == 'x') {
457
+ line += 2;
458
+ base = 16;
459
+ } else {
460
+ base = 10;
461
+ }
462
+
463
+ last = (unsigned)strtoul(line, &endptr, base);
464
+ }
465
+
466
+ /* ignore spaces after the number - was previously ignored as string was split on equals */
467
+ while (*endptr == ' ')
468
+ ++endptr;
469
+
470
+ /* if we've found the equal sign, this is the last item */
471
+ if (*endptr == '=') {
472
+ rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
473
+ break;
474
+ }
475
+
476
+ /* otherwise, if it's not a comma, it's an error */
477
+ if (*endptr != ',') {
478
+ parse->offset = RC_INVALID_CONST_OPERAND;
479
+ break;
480
+ }
481
+
482
+ /* insert the current item and continue scanning the next one */
483
+ rc_insert_richpresence_lookup_item(lookup, first, last, label, (int)(endline - label), parse);
484
+ if (parse->offset < 0)
485
+ break;
486
+
487
+ line = endptr + 1;
488
+ } while (line < endline);
489
+
490
+ } while (parse->offset > 0);
491
+
492
+ return nextline;
493
+ }
494
+
495
+ void rc_parse_richpresence_internal(rc_richpresence_t* self, const char* script, rc_parse_state_t* parse) {
496
+ rc_richpresence_display_t** nextdisplay;
497
+ rc_richpresence_lookup_t* firstlookup = NULL;
498
+ rc_richpresence_lookup_t** nextlookup = &firstlookup;
499
+ rc_richpresence_lookup_t* lookup;
500
+ rc_trigger_t* trigger;
501
+ char format[64];
502
+ const char* display = 0;
503
+ const char* line;
504
+ const char* nextline;
505
+ const char* endline;
506
+ const char* ptr;
507
+ int hasdisplay = 0;
508
+ int display_line = 0;
509
+ int chars;
510
+
511
+ self->values = NULL;
512
+
513
+ /* special case for empty script to return 1 line read */
514
+ if (!*script) {
515
+ parse->lines_read = 1;
516
+ parse->offset = RC_MISSING_DISPLAY_STRING;
517
+ return;
518
+ }
519
+
520
+ /* first pass: process macro initializers */
521
+ line = script;
522
+ while (*line) {
523
+ nextline = rc_parse_line(line, &endline, parse);
524
+ if (strncmp(line, "Lookup:", 7) == 0) {
525
+ line += 7;
526
+
527
+ lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
528
+ lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
529
+ lookup->format = RC_FORMAT_LOOKUP;
530
+ lookup->root = NULL;
531
+ lookup->default_label = "";
532
+ *nextlookup = lookup;
533
+ nextlookup = &lookup->next;
534
+
535
+ nextline = rc_parse_richpresence_lookup(lookup, nextline, parse);
536
+ if (parse->offset < 0)
537
+ return;
538
+
539
+ } else if (strncmp(line, "Format:", 7) == 0) {
540
+ line += 7;
541
+ if (endline - line == 11 && memcmp(line, "Unformatted", 11) == 0) {
542
+ /* for backwards compatibility with the comma rollout, we allow old scripts
543
+ * to define an Unformatted type mapped to VALUE, and new versions will ignore
544
+ * the definition and use the built-in macro. skip the next line (FormatType=) */
545
+ line = rc_parse_line(nextline, &endline, parse);
546
+ continue;
547
+ }
548
+
549
+ lookup = RC_ALLOC_SCRATCH(rc_richpresence_lookup_t, parse);
550
+ lookup->name = rc_alloc_str(parse, line, (int)(endline - line));
551
+ lookup->root = NULL;
552
+ lookup->default_label = "";
553
+ *nextlookup = lookup;
554
+ nextlookup = &lookup->next;
555
+
556
+ line = nextline;
557
+ nextline = rc_parse_line(line, &endline, parse);
558
+ if (parse->buffer && strncmp(line, "FormatType=", 11) == 0) {
559
+ line += 11;
560
+
561
+ chars = (int)(endline - line);
562
+ if (chars > 63)
563
+ chars = 63;
564
+ memcpy(format, line, chars);
565
+ format[chars] = '\0';
566
+
567
+ lookup->format = (uint8_t)rc_parse_format(format);
568
+ } else {
569
+ lookup->format = RC_FORMAT_VALUE;
570
+ }
571
+ } else if (strncmp(line, "Display:", 8) == 0) {
572
+ display = nextline;
573
+ display_line = parse->lines_read;
574
+
575
+ /* scan as long as we find conditional lines or full line comments */
576
+ do {
577
+ line = nextline;
578
+ nextline = rc_parse_line(line, &endline, parse);
579
+ } while (*line == '?' || (line[0] == '/' && line[1] == '/'));
580
+ }
581
+
582
+ line = nextline;
583
+ }
584
+
585
+ *nextlookup = 0;
586
+ self->first_lookup = firstlookup;
587
+
588
+ nextdisplay = &self->first_display;
589
+
590
+ /* second pass, process display string*/
591
+ if (display) {
592
+ /* point the parser back at the display strings */
593
+ int lines_read = parse->lines_read;
594
+ parse->lines_read = display_line;
595
+ line = display;
596
+
597
+ nextline = rc_parse_line(line, &endline, parse);
598
+
599
+ do {
600
+ if (line[0] == '?') {
601
+ /* conditional display: ?trigger?string */
602
+ ptr = ++line;
603
+ while (ptr < endline && *ptr != '?')
604
+ ++ptr;
605
+
606
+ if (ptr < endline) {
607
+ *nextdisplay = rc_parse_richpresence_display_internal(ptr + 1, endline, parse, firstlookup);
608
+ if (parse->offset < 0)
609
+ return;
610
+
611
+ trigger = &((*nextdisplay)->trigger);
612
+ rc_parse_trigger_internal(trigger, &line, parse);
613
+ if (parse->offset < 0)
614
+ return;
615
+
616
+ if (line != ptr) {
617
+ /* incomplete read */
618
+ parse->offset = RC_INVALID_OPERATOR;
619
+ return;
620
+ }
621
+
622
+ (*nextdisplay)->has_required_hits = parse->has_required_hits;
623
+
624
+ if (parse->buffer)
625
+ nextdisplay = &((*nextdisplay)->next);
626
+ }
627
+ }
628
+ else if (line[0] != '/' || line[1] != '/') {
629
+ break;
630
+ }
631
+
632
+ line = nextline;
633
+ nextline = rc_parse_line(line, &endline, parse);
634
+ } while (1);
635
+
636
+ /* non-conditional display: string */
637
+ *nextdisplay = rc_parse_richpresence_display_internal(line, endline, parse, firstlookup);
638
+ if (*nextdisplay) {
639
+ hasdisplay = 1;
640
+ nextdisplay = &((*nextdisplay)->next);
641
+
642
+ /* restore the parser state */
643
+ parse->lines_read = lines_read;
644
+ }
645
+ else {
646
+ /* this should only happen if the line is blank.
647
+ * expect parse->offset to be RC_MISSING_DISPLAY_STRING and leave parse->lines_read
648
+ * on the current line for error tracking. */
649
+ }
650
+ }
651
+
652
+ /* finalize */
653
+ *nextdisplay = 0;
654
+ self->has_memrefs = 0;
655
+
656
+ if (!hasdisplay && parse->offset > 0) {
657
+ parse->offset = RC_MISSING_DISPLAY_STRING;
658
+ }
659
+ }
660
+
661
+ int rc_richpresence_size_lines(const char* script, int* lines_read) {
662
+ rc_richpresence_with_memrefs_t* richpresence;
663
+ rc_preparse_state_t preparse;
664
+ rc_init_preparse_state(&preparse);
665
+
666
+ richpresence = RC_ALLOC(rc_richpresence_with_memrefs_t, &preparse.parse);
667
+ preparse.parse.variables = &richpresence->richpresence.values;
668
+ rc_parse_richpresence_internal(&richpresence->richpresence, script, &preparse.parse);
669
+ rc_preparse_alloc_memrefs(NULL, &preparse);
670
+
671
+ if (lines_read)
672
+ *lines_read = preparse.parse.lines_read;
673
+
674
+ rc_destroy_preparse_state(&preparse);
675
+ return preparse.parse.offset;
676
+ }
677
+
678
+ int rc_richpresence_size(const char* script) {
679
+ return rc_richpresence_size_lines(script, NULL);
680
+ }
681
+
682
+ rc_richpresence_t* rc_parse_richpresence(void* buffer, const char* script, void* unused_L, int unused_funcs_idx) {
683
+ rc_richpresence_with_memrefs_t* richpresence;
684
+ rc_preparse_state_t preparse;
685
+
686
+ (void)unused_L;
687
+ (void)unused_funcs_idx;
688
+
689
+ if (!buffer || !script)
690
+ return NULL;
691
+
692
+ rc_init_preparse_state(&preparse);
693
+ richpresence = RC_ALLOC(rc_richpresence_with_memrefs_t, &preparse.parse);
694
+ preparse.parse.variables = &richpresence->richpresence.values;
695
+ rc_parse_richpresence_internal(&richpresence->richpresence, script, &preparse.parse);
696
+
697
+ rc_reset_parse_state(&preparse.parse, buffer);
698
+ richpresence = RC_ALLOC(rc_richpresence_with_memrefs_t, &preparse.parse);
699
+ preparse.parse.variables = &richpresence->richpresence.values;
700
+ rc_preparse_alloc_memrefs(&richpresence->memrefs, &preparse);
701
+
702
+ rc_parse_richpresence_internal(&richpresence->richpresence, script, &preparse.parse);
703
+ richpresence->richpresence.has_memrefs = 1;
704
+
705
+ rc_destroy_preparse_state(&preparse);
706
+ return (preparse.parse.offset >= 0) ? &richpresence->richpresence : NULL;
707
+ }
708
+
709
+ static void rc_update_richpresence_memrefs(rc_richpresence_t* self, rc_peek_t peek, void* ud) {
710
+ if (self->has_memrefs) {
711
+ rc_richpresence_with_memrefs_t* richpresence = (rc_richpresence_with_memrefs_t*)self;
712
+ rc_update_memref_values(&richpresence->memrefs, peek, ud);
713
+ }
714
+ }
715
+
716
+ rc_memrefs_t* rc_richpresence_get_memrefs(rc_richpresence_t* self) {
717
+ if (self->has_memrefs) {
718
+ rc_richpresence_with_memrefs_t* richpresence = (rc_richpresence_with_memrefs_t*)self;
719
+ return &richpresence->memrefs;
720
+ }
721
+
722
+ return NULL;
723
+ }
724
+
725
+ void rc_update_richpresence(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud, void* unused_L) {
726
+ (void)unused_L;
727
+
728
+ rc_update_richpresence_memrefs(richpresence, peek, peek_ud);
729
+ rc_update_values(richpresence->values, peek, peek_ud);
730
+ rc_update_richpresence_internal(richpresence, peek, peek_ud);
731
+ }
732
+
733
+ void rc_update_richpresence_internal(rc_richpresence_t* richpresence, rc_peek_t peek, void* peek_ud) {
734
+ rc_richpresence_display_t* display;
735
+
736
+ for (display = richpresence->first_display; display; display = display->next) {
737
+ if (display->has_required_hits)
738
+ rc_test_trigger(&display->trigger, peek, peek_ud, NULL);
739
+ }
740
+ }
741
+
742
+ static int rc_evaluate_richpresence_display(rc_richpresence_display_part_t* part, char* buffer, size_t buffersize)
743
+ {
744
+ rc_richpresence_lookup_item_t* item;
745
+ rc_typed_value_t value;
746
+ char tmp[256];
747
+ char* ptr = buffer;
748
+ const char* text;
749
+ size_t chars;
750
+
751
+ *ptr = '\0';
752
+ while (part) {
753
+ switch (part->display_type) {
754
+ case RC_FORMAT_STRING:
755
+ text = part->text;
756
+ chars = strlen(text);
757
+ break;
758
+
759
+ case RC_FORMAT_LOOKUP:
760
+ rc_evaluate_operand(&value, &part->value, NULL);
761
+ rc_typed_value_convert(&value, RC_VALUE_TYPE_UNSIGNED);
762
+
763
+ text = part->lookup->default_label;
764
+ item = part->lookup->root;
765
+ while (item) {
766
+ if (value.value.u32 > item->last) {
767
+ item = item->right;
768
+ }
769
+ else if (value.value.u32 < item->first) {
770
+ item = item->left;
771
+ }
772
+ else {
773
+ text = item->label;
774
+ break;
775
+ }
776
+ }
777
+
778
+ chars = strlen(text);
779
+ break;
780
+
781
+ case RC_FORMAT_ASCIICHAR:
782
+ chars = 0;
783
+ text = tmp;
784
+ value.type = RC_VALUE_TYPE_UNSIGNED;
785
+
786
+ do {
787
+ rc_evaluate_operand(&value, &part->value, NULL);
788
+ if (value.value.u32 == 0) {
789
+ /* null terminator - skip over remaining character macros */
790
+ while (part->next && part->next->display_type == RC_FORMAT_ASCIICHAR)
791
+ part = part->next;
792
+ break;
793
+ }
794
+
795
+ if (value.value.u32 < 32 || value.value.u32 >= 127)
796
+ value.value.u32 = '?';
797
+
798
+ tmp[chars++] = (char)value.value.u32;
799
+ if (chars == sizeof(tmp) || !part->next || part->next->display_type != RC_FORMAT_ASCIICHAR)
800
+ break;
801
+
802
+ part = part->next;
803
+ } while (1);
804
+
805
+ tmp[chars] = '\0';
806
+ break;
807
+
808
+ case RC_FORMAT_UNICODECHAR:
809
+ chars = 0;
810
+ text = tmp;
811
+ value.type = RC_VALUE_TYPE_UNSIGNED;
812
+
813
+ do {
814
+ rc_evaluate_operand(&value, &part->value, NULL);
815
+ if (value.value.u32 == 0) {
816
+ /* null terminator - skip over remaining character macros */
817
+ while (part->next && part->next->display_type == RC_FORMAT_UNICODECHAR)
818
+ part = part->next;
819
+ break;
820
+ }
821
+
822
+ if (value.value.u32 < 32 || value.value.u32 > 65535)
823
+ value.value.u32 = 0xFFFD; /* unicode replacement char */
824
+
825
+ if (value.value.u32 < 0x80) {
826
+ tmp[chars++] = (char)value.value.u32;
827
+ }
828
+ else if (value.value.u32 < 0x0800) {
829
+ tmp[chars + 1] = (char)(0x80 | (value.value.u32 & 0x3F)); value.value.u32 >>= 6;
830
+ tmp[chars] = (char)(0xC0 | (value.value.u32 & 0x1F));
831
+ chars += 2;
832
+ }
833
+ else {
834
+ /* surrogate pair not supported, convert to replacement char */
835
+ if (value.value.u32 >= 0xD800 && value.value.u32 < 0xE000)
836
+ value.value.u32 = 0xFFFD;
837
+
838
+ tmp[chars + 2] = (char)(0x80 | (value.value.u32 & 0x3F)); value.value.u32 >>= 6;
839
+ tmp[chars + 1] = (char)(0x80 | (value.value.u32 & 0x3F)); value.value.u32 >>= 6;
840
+ tmp[chars] = (char)(0xE0 | (value.value.u32 & 0x1F));
841
+ chars += 3;
842
+ }
843
+
844
+ if (chars >= sizeof(tmp) - 3 || !part->next || part->next->display_type != RC_FORMAT_UNICODECHAR)
845
+ break;
846
+
847
+ part = part->next;
848
+ } while (1);
849
+
850
+ tmp[chars] = '\0';
851
+ break;
852
+
853
+ case RC_FORMAT_UNKNOWN_MACRO:
854
+ chars = snprintf(tmp, sizeof(tmp), "[Unknown macro]%s", part->text);
855
+ text = tmp;
856
+ break;
857
+
858
+ default:
859
+ rc_evaluate_operand(&value, &part->value, NULL);
860
+ chars = rc_format_typed_value(tmp, sizeof(tmp), &value, part->display_type);
861
+ text = tmp;
862
+ break;
863
+ }
864
+
865
+ if (chars > 0 && buffersize > 0) {
866
+ if ((unsigned)chars >= buffersize) {
867
+ /* prevent write past end of buffer */
868
+ memcpy(ptr, text, buffersize - 1);
869
+ ptr[buffersize - 1] = '\0';
870
+ buffersize = 0;
871
+ }
872
+ else {
873
+ memcpy(ptr, text, chars);
874
+ ptr[chars] = '\0';
875
+ buffersize -= (unsigned)chars;
876
+ }
877
+ }
878
+
879
+ ptr += chars;
880
+ part = part->next;
881
+ }
882
+
883
+ return (int)(ptr - buffer);
884
+ }
885
+
886
+ int rc_get_richpresence_display_string(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, void* unused_L) {
887
+ rc_richpresence_display_t* display;
888
+
889
+ for (display = richpresence->first_display; display; display = display->next) {
890
+ /* if we've reached the end of the condition list, process it */
891
+ if (!display->next)
892
+ return rc_evaluate_richpresence_display(display->display, buffer, buffersize);
893
+
894
+ /* triggers with required hits will be updated in rc_update_richpresence */
895
+ if (!display->has_required_hits)
896
+ rc_test_trigger(&display->trigger, peek, peek_ud, unused_L);
897
+
898
+ /* if we've found a valid condition, process it */
899
+ if (display->trigger.state == RC_TRIGGER_STATE_TRIGGERED)
900
+ return rc_evaluate_richpresence_display(display->display, buffer, buffersize);
901
+ }
902
+
903
+ buffer[0] = '\0';
904
+ return 0;
905
+ }
906
+
907
+ int rc_evaluate_richpresence(rc_richpresence_t* richpresence, char* buffer, size_t buffersize, rc_peek_t peek, void* peek_ud, void* unused_L) {
908
+ rc_update_richpresence(richpresence, peek, peek_ud, unused_L);
909
+ return rc_get_richpresence_display_string(richpresence, buffer, buffersize, peek, peek_ud, unused_L);
910
+ }
911
+
912
+ void rc_reset_richpresence_triggers(rc_richpresence_t* self) {
913
+ rc_richpresence_display_t* display;
914
+
915
+ for (display = self->first_display; display; display = display->next)
916
+ rc_reset_trigger(&display->trigger);
917
+ }
918
+
919
+ void rc_reset_richpresence(rc_richpresence_t* self) {
920
+ rc_reset_richpresence_triggers(self);
921
+ rc_reset_values(self->values);
922
+ }