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,483 @@
1
+ #include "rc_api_user.h"
2
+ #include "rc_api_common.h"
3
+ #include "rc_api_runtime.h"
4
+ #include "rc_consoles.h"
5
+
6
+ #include "../rc_version.h"
7
+
8
+ #include <stdlib.h>
9
+ #include <string.h>
10
+
11
+ /* --- Login --- */
12
+
13
+ int rc_api_init_login_request(rc_api_request_t* request, const rc_api_login_request_t* api_params) {
14
+ return rc_api_init_login_request_hosted(request, api_params, &g_host);
15
+ }
16
+
17
+ int rc_api_init_login_request_hosted(rc_api_request_t* request,
18
+ const rc_api_login_request_t* api_params,
19
+ const rc_api_host_t* host) {
20
+ rc_api_url_builder_t builder;
21
+
22
+ rc_api_url_build_dorequest_url(request, host);
23
+
24
+ if (!api_params->username || !*api_params->username)
25
+ return RC_INVALID_STATE;
26
+
27
+ rc_url_builder_init(&builder, &request->buffer, 48);
28
+ rc_url_builder_append_str_param(&builder, "r", "login2");
29
+ rc_url_builder_append_str_param(&builder, "u", api_params->username);
30
+
31
+ if (api_params->password && api_params->password[0])
32
+ rc_url_builder_append_str_param(&builder, "p", api_params->password);
33
+ else if (api_params->api_token && api_params->api_token[0])
34
+ rc_url_builder_append_str_param(&builder, "t", api_params->api_token);
35
+ else
36
+ return RC_INVALID_STATE;
37
+
38
+ request->post_data = rc_url_builder_finalize(&builder);
39
+ request->content_type = RC_CONTENT_TYPE_URLENCODED;
40
+
41
+ return builder.result;
42
+ }
43
+
44
+ int rc_api_process_login_response(rc_api_login_response_t* response, const char* server_response) {
45
+ rc_api_server_response_t response_obj;
46
+
47
+ memset(&response_obj, 0, sizeof(response_obj));
48
+ response_obj.body = server_response;
49
+ response_obj.body_length = rc_json_get_object_string_length(server_response);
50
+
51
+ return rc_api_process_login_server_response(response, &response_obj);
52
+ }
53
+
54
+ int rc_api_process_login_server_response(rc_api_login_response_t* response, const rc_api_server_response_t* server_response) {
55
+ int result;
56
+ rc_json_field_t fields[] = {
57
+ RC_JSON_NEW_FIELD("Success"),
58
+ RC_JSON_NEW_FIELD("Error"),
59
+ RC_JSON_NEW_FIELD("Code"),
60
+ RC_JSON_NEW_FIELD("User"),
61
+ RC_JSON_NEW_FIELD("Token"),
62
+ RC_JSON_NEW_FIELD("Score"),
63
+ RC_JSON_NEW_FIELD("SoftcoreScore"),
64
+ RC_JSON_NEW_FIELD("Messages"),
65
+ RC_JSON_NEW_FIELD("AvatarUrl")
66
+ };
67
+
68
+ memset(response, 0, sizeof(*response));
69
+ rc_buffer_init(&response->response.buffer);
70
+
71
+ result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
72
+ if (result != RC_OK || !response->response.succeeded)
73
+ return result;
74
+
75
+ if (!rc_json_get_required_string(&response->username, &response->response, &fields[3], "User"))
76
+ return RC_MISSING_VALUE;
77
+ if (!rc_json_get_required_string(&response->api_token, &response->response, &fields[4], "Token"))
78
+ return RC_MISSING_VALUE;
79
+
80
+ rc_json_get_optional_unum(&response->score, &fields[5], "Score", 0);
81
+ rc_json_get_optional_unum(&response->score_softcore, &fields[6], "SoftcoreScore", 0);
82
+ rc_json_get_optional_unum(&response->num_unread_messages, &fields[7], "Messages", 0);
83
+
84
+ /* For the highest level of backwards compatibility, we have decided to just send the
85
+ * display_name back to the client as the "case-corrected username", and allow it to
86
+ * be used as the Username field for the other APIs. */
87
+ response->display_name = response->username;
88
+
89
+ rc_json_get_optional_string(&response->avatar_url, &response->response, &fields[8], "AvatarUrl", NULL);
90
+ if (!response->avatar_url)
91
+ response->avatar_url = rc_api_build_avatar_url(&response->response.buffer, RC_IMAGE_TYPE_USER, response->username);
92
+
93
+ return RC_OK;
94
+ }
95
+
96
+ void rc_api_destroy_login_response(rc_api_login_response_t* response) {
97
+ rc_buffer_destroy(&response->response.buffer);
98
+ }
99
+
100
+ /* --- Start Session --- */
101
+
102
+ int rc_api_init_start_session_request(rc_api_request_t* request, const rc_api_start_session_request_t* api_params) {
103
+ return rc_api_init_start_session_request_hosted(request, api_params, &g_host);
104
+ }
105
+
106
+ int rc_api_init_start_session_request_hosted(rc_api_request_t* request,
107
+ const rc_api_start_session_request_t* api_params,
108
+ const rc_api_host_t* host) {
109
+ rc_api_url_builder_t builder;
110
+
111
+ rc_api_url_build_dorequest_url(request, host);
112
+
113
+ if (api_params->game_id == 0)
114
+ return RC_INVALID_STATE;
115
+
116
+ rc_url_builder_init(&builder, &request->buffer, 48);
117
+ if (rc_api_url_build_dorequest(&builder, "startsession", api_params->username, api_params->api_token)) {
118
+ rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
119
+
120
+ if (api_params->game_hash && *api_params->game_hash) {
121
+ rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore);
122
+ rc_url_builder_append_str_param(&builder, "m", api_params->game_hash);
123
+ }
124
+
125
+ rc_url_builder_append_str_param(&builder, "l", RCHEEVOS_VERSION_STRING);
126
+
127
+ request->post_data = rc_url_builder_finalize(&builder);
128
+ request->content_type = RC_CONTENT_TYPE_URLENCODED;
129
+ }
130
+
131
+ return builder.result;
132
+ }
133
+
134
+ int rc_api_process_start_session_response(rc_api_start_session_response_t* response, const char* server_response) {
135
+ rc_api_server_response_t response_obj;
136
+
137
+ memset(&response_obj, 0, sizeof(response_obj));
138
+ response_obj.body = server_response;
139
+ response_obj.body_length = rc_json_get_object_string_length(server_response);
140
+
141
+ return rc_api_process_start_session_server_response(response, &response_obj);
142
+ }
143
+
144
+ int rc_api_process_start_session_server_response(rc_api_start_session_response_t* response, const rc_api_server_response_t* server_response) {
145
+ rc_api_unlock_entry_t* unlock;
146
+ rc_json_field_t array_field;
147
+ rc_json_iterator_t iterator;
148
+ uint32_t timet;
149
+ int result;
150
+
151
+ rc_json_field_t fields[] = {
152
+ RC_JSON_NEW_FIELD("Success"),
153
+ RC_JSON_NEW_FIELD("Error"),
154
+ RC_JSON_NEW_FIELD("Unlocks"),
155
+ RC_JSON_NEW_FIELD("HardcoreUnlocks"),
156
+ RC_JSON_NEW_FIELD("ServerNow")
157
+ };
158
+
159
+ rc_json_field_t unlock_entry_fields[] = {
160
+ RC_JSON_NEW_FIELD("ID"),
161
+ RC_JSON_NEW_FIELD("When")
162
+ };
163
+
164
+ memset(response, 0, sizeof(*response));
165
+ rc_buffer_init(&response->response.buffer);
166
+
167
+ result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
168
+ if (result != RC_OK || !response->response.succeeded)
169
+ return result;
170
+
171
+ if (rc_json_get_optional_array(&response->num_unlocks, &array_field, &fields[2], "Unlocks") && response->num_unlocks) {
172
+ response->unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_unlocks * sizeof(rc_api_unlock_entry_t));
173
+ if (!response->unlocks)
174
+ return RC_OUT_OF_MEMORY;
175
+
176
+ memset(&iterator, 0, sizeof(iterator));
177
+ iterator.json = array_field.value_start;
178
+ iterator.end = array_field.value_end;
179
+
180
+ unlock = response->unlocks;
181
+ while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {
182
+ if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))
183
+ return RC_MISSING_VALUE;
184
+ if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
185
+ return RC_MISSING_VALUE;
186
+ unlock->when = (time_t)timet;
187
+
188
+ ++unlock;
189
+ }
190
+ }
191
+
192
+ if (rc_json_get_optional_array(&response->num_hardcore_unlocks, &array_field, &fields[3], "HardcoreUnlocks") && response->num_hardcore_unlocks) {
193
+ response->hardcore_unlocks = (rc_api_unlock_entry_t*)rc_buffer_alloc(&response->response.buffer, response->num_hardcore_unlocks * sizeof(rc_api_unlock_entry_t));
194
+ if (!response->hardcore_unlocks)
195
+ return RC_OUT_OF_MEMORY;
196
+
197
+ memset(&iterator, 0, sizeof(iterator));
198
+ iterator.json = array_field.value_start;
199
+ iterator.end = array_field.value_end;
200
+
201
+ unlock = response->hardcore_unlocks;
202
+ while (rc_json_get_array_entry_object(unlock_entry_fields, sizeof(unlock_entry_fields) / sizeof(unlock_entry_fields[0]), &iterator)) {
203
+ if (!rc_json_get_required_unum(&unlock->achievement_id, &response->response, &unlock_entry_fields[0], "ID"))
204
+ return RC_MISSING_VALUE;
205
+ if (!rc_json_get_required_unum(&timet, &response->response, &unlock_entry_fields[1], "When"))
206
+ return RC_MISSING_VALUE;
207
+ unlock->when = (time_t)timet;
208
+
209
+ ++unlock;
210
+ }
211
+ }
212
+
213
+ rc_json_get_optional_unum(&timet, &fields[4], "ServerNow", 0);
214
+ response->server_now = (time_t)timet;
215
+
216
+ return RC_OK;
217
+ }
218
+
219
+ void rc_api_destroy_start_session_response(rc_api_start_session_response_t* response) {
220
+ rc_buffer_destroy(&response->response.buffer);
221
+ }
222
+
223
+ /* --- Fetch User Unlocks --- */
224
+
225
+ int rc_api_init_fetch_user_unlocks_request(rc_api_request_t* request, const rc_api_fetch_user_unlocks_request_t* api_params) {
226
+ return rc_api_init_fetch_user_unlocks_request_hosted(request, api_params, &g_host);
227
+ }
228
+
229
+ int rc_api_init_fetch_user_unlocks_request_hosted(rc_api_request_t* request,
230
+ const rc_api_fetch_user_unlocks_request_t* api_params,
231
+ const rc_api_host_t* host) {
232
+ rc_api_url_builder_t builder;
233
+
234
+ rc_api_url_build_dorequest_url(request, host);
235
+
236
+ rc_url_builder_init(&builder, &request->buffer, 48);
237
+ if (rc_api_url_build_dorequest(&builder, "unlocks", api_params->username, api_params->api_token)) {
238
+ rc_url_builder_append_unum_param(&builder, "g", api_params->game_id);
239
+ rc_url_builder_append_unum_param(&builder, "h", api_params->hardcore ? 1 : 0);
240
+ request->post_data = rc_url_builder_finalize(&builder);
241
+ request->content_type = RC_CONTENT_TYPE_URLENCODED;
242
+ }
243
+
244
+ return builder.result;
245
+ }
246
+
247
+ int rc_api_process_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response, const char* server_response) {
248
+ rc_api_server_response_t response_obj;
249
+
250
+ memset(&response_obj, 0, sizeof(response_obj));
251
+ response_obj.body = server_response;
252
+ response_obj.body_length = rc_json_get_object_string_length(server_response);
253
+
254
+ return rc_api_process_fetch_user_unlocks_server_response(response, &response_obj);
255
+ }
256
+
257
+ int rc_api_process_fetch_user_unlocks_server_response(rc_api_fetch_user_unlocks_response_t* response, const rc_api_server_response_t* server_response) {
258
+ int result;
259
+ rc_json_field_t fields[] = {
260
+ RC_JSON_NEW_FIELD("Success"),
261
+ RC_JSON_NEW_FIELD("Error"),
262
+ RC_JSON_NEW_FIELD("UserUnlocks")
263
+ /* unused fields
264
+ RC_JSON_NEW_FIELD("GameID"),
265
+ RC_JSON_NEW_FIELD("HardcoreMode")
266
+ * unused fields */
267
+ };
268
+
269
+ memset(response, 0, sizeof(*response));
270
+ rc_buffer_init(&response->response.buffer);
271
+
272
+ result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
273
+ if (result != RC_OK || !response->response.succeeded)
274
+ return result;
275
+
276
+ result = rc_json_get_required_unum_array(&response->achievement_ids, &response->num_achievement_ids, &response->response, &fields[2], "UserUnlocks");
277
+ return result;
278
+ }
279
+
280
+ void rc_api_destroy_fetch_user_unlocks_response(rc_api_fetch_user_unlocks_response_t* response) {
281
+ rc_buffer_destroy(&response->response.buffer);
282
+ }
283
+
284
+ /* --- Fetch Followed Users --- */
285
+
286
+ int rc_api_init_fetch_followed_users_request(rc_api_request_t* request, const rc_api_fetch_followed_users_request_t* api_params) {
287
+ return rc_api_init_fetch_followed_users_request_hosted(request, api_params, &g_host);
288
+ }
289
+
290
+ int rc_api_init_fetch_followed_users_request_hosted(rc_api_request_t* request,
291
+ const rc_api_fetch_followed_users_request_t* api_params,
292
+ const rc_api_host_t* host) {
293
+ rc_api_url_builder_t builder;
294
+
295
+ rc_api_url_build_dorequest_url(request, host);
296
+
297
+ rc_url_builder_init(&builder, &request->buffer, 48);
298
+ if (rc_api_url_build_dorequest(&builder, "getfriendlist", api_params->username, api_params->api_token)) {
299
+ request->post_data = rc_url_builder_finalize(&builder);
300
+ request->content_type = RC_CONTENT_TYPE_URLENCODED;
301
+ }
302
+
303
+ return builder.result;
304
+ }
305
+
306
+ int rc_api_process_fetch_followed_users_server_response(rc_api_fetch_followed_users_response_t* response, const rc_api_server_response_t* server_response) {
307
+ rc_json_field_t array_field;
308
+ rc_json_iterator_t iterator;
309
+ rc_api_followed_user_t* user;
310
+ uint32_t timet;
311
+ int result;
312
+ rc_json_field_t fields[] = {
313
+ RC_JSON_NEW_FIELD("Success"),
314
+ RC_JSON_NEW_FIELD("Error"),
315
+ RC_JSON_NEW_FIELD("Friends")
316
+ };
317
+
318
+ rc_json_field_t followed_user_entry_fields[] = {
319
+ RC_JSON_NEW_FIELD("Friend"),
320
+ RC_JSON_NEW_FIELD("AvatarUrl"),
321
+ RC_JSON_NEW_FIELD("RAPoints"),
322
+ RC_JSON_NEW_FIELD("LastSeen"),
323
+ RC_JSON_NEW_FIELD("LastSeenTime"),
324
+ RC_JSON_NEW_FIELD("LastGameId"),
325
+ RC_JSON_NEW_FIELD("LastGameTitle"),
326
+ RC_JSON_NEW_FIELD("LastGameIconUrl"),
327
+ };
328
+
329
+ memset(response, 0, sizeof(*response));
330
+ rc_buffer_init(&response->response.buffer);
331
+
332
+ result = rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
333
+ if (result != RC_OK || !response->response.succeeded)
334
+ return result;
335
+
336
+ if (!rc_json_get_required_array(&response->num_users, &array_field, &response->response, &fields[2], "Friends"))
337
+ return RC_MISSING_VALUE;
338
+
339
+ if (response->num_users) {
340
+ response->users = (rc_api_followed_user_t*)rc_buffer_alloc(&response->response.buffer, response->num_users * sizeof(rc_api_followed_user_t));
341
+ if (!response->users)
342
+ return RC_OUT_OF_MEMORY;
343
+
344
+ memset(&iterator, 0, sizeof(iterator));
345
+ iterator.json = array_field.value_start;
346
+ iterator.end = array_field.value_end;
347
+
348
+ user = response->users;
349
+ while (rc_json_get_array_entry_object(followed_user_entry_fields, sizeof(followed_user_entry_fields) / sizeof(followed_user_entry_fields[0]), &iterator)) {
350
+ if (!rc_json_get_required_string(&user->display_name, &response->response, &followed_user_entry_fields[0], "Friend"))
351
+ return RC_MISSING_VALUE;
352
+ if (!rc_json_get_required_string(&user->avatar_url, &response->response, &followed_user_entry_fields[1], "AvatarUrl"))
353
+ return RC_MISSING_VALUE;
354
+ if (!rc_json_get_required_unum(&user->score, &response->response, &followed_user_entry_fields[2], "RAPoints"))
355
+ return RC_MISSING_VALUE;
356
+
357
+ rc_json_get_optional_string(&user->recent_activity.description, &response->response, &followed_user_entry_fields[3], "LastSeen", NULL);
358
+
359
+ rc_json_get_optional_unum(&timet, &followed_user_entry_fields[4], "LastSeenTime", 0);
360
+ user->recent_activity.when = (time_t)timet;
361
+
362
+ rc_json_get_optional_unum(&user->recent_activity.context_id, &followed_user_entry_fields[5], "LastGameId", 0);
363
+ rc_json_get_optional_string(&user->recent_activity.context, &response->response, &followed_user_entry_fields[6], "LastGameTitle", NULL);
364
+ rc_json_get_optional_string(&user->recent_activity.context_image_url, &response->response, &followed_user_entry_fields[7], "LastGameIconUrl", NULL);
365
+
366
+ ++user;
367
+ }
368
+ }
369
+
370
+ return RC_OK;
371
+ }
372
+
373
+ void rc_api_destroy_fetch_followed_users_response(rc_api_fetch_followed_users_response_t* response) {
374
+ rc_buffer_destroy(&response->response.buffer);
375
+ }
376
+
377
+ /* --- Fetch All Progress --- */
378
+
379
+ int rc_api_init_fetch_all_user_progress_request(rc_api_request_t* request,
380
+ const rc_api_fetch_all_user_progress_request_t* api_params)
381
+ {
382
+ return rc_api_init_fetch_all_user_progress_request_hosted(request, api_params, &g_host);
383
+ }
384
+
385
+ int rc_api_init_fetch_all_user_progress_request_hosted(rc_api_request_t* request,
386
+ const rc_api_fetch_all_user_progress_request_t* api_params,
387
+ const rc_api_host_t* host)
388
+ {
389
+ rc_api_url_builder_t builder;
390
+
391
+ rc_api_url_build_dorequest_url(request, host);
392
+
393
+ if (api_params->console_id == RC_CONSOLE_UNKNOWN)
394
+ return RC_INVALID_STATE;
395
+
396
+ rc_url_builder_init(&builder, &request->buffer, 48);
397
+ if (rc_api_url_build_dorequest(&builder, "allprogress", api_params->username, api_params->api_token))
398
+ {
399
+ rc_url_builder_append_unum_param(&builder, "c", api_params->console_id);
400
+ request->post_data = rc_url_builder_finalize(&builder);
401
+ request->content_type = RC_CONTENT_TYPE_URLENCODED;
402
+ }
403
+
404
+ return builder.result;
405
+ }
406
+
407
+ int rc_api_process_fetch_all_user_progress_server_response(rc_api_fetch_all_user_progress_response_t* response,
408
+ const rc_api_server_response_t* server_response)
409
+ {
410
+ rc_api_all_user_progress_entry_t* entry;
411
+ int result;
412
+
413
+ rc_json_field_t fields[] = {
414
+ RC_JSON_NEW_FIELD("Success"),
415
+ RC_JSON_NEW_FIELD("Error"),
416
+ RC_JSON_NEW_FIELD("Response"),
417
+ };
418
+
419
+ rc_json_field_t entry_fields[] = {
420
+ RC_JSON_NEW_FIELD("Achievements"),
421
+ RC_JSON_NEW_FIELD("Unlocked"),
422
+ RC_JSON_NEW_FIELD("UnlockedHardcore"),
423
+ };
424
+
425
+ memset(response, 0, sizeof(*response));
426
+ rc_buffer_init(&response->response.buffer);
427
+
428
+ result =
429
+ rc_json_parse_server_response(&response->response, server_response, fields, sizeof(fields) / sizeof(fields[0]));
430
+ if (result != RC_OK || !response->response.succeeded)
431
+ return result;
432
+
433
+ if (!fields[2].value_start) {
434
+ /* call rc_json_get_required_object to generate the error message */
435
+ rc_json_get_required_object(NULL, 0, &response->response, &fields[2], "Response");
436
+ return RC_MISSING_VALUE;
437
+ }
438
+
439
+ response->num_entries = fields[2].array_size;
440
+
441
+ if (response->num_entries > 0) {
442
+ rc_json_iterator_t iterator;
443
+ rc_json_field_t field;
444
+ char* end;
445
+
446
+ rc_buffer_reserve(&response->response.buffer, response->num_entries * sizeof(rc_api_all_user_progress_entry_t));
447
+
448
+ response->entries = (rc_api_all_user_progress_entry_t*)rc_buffer_alloc(
449
+ &response->response.buffer, response->num_entries * sizeof(rc_api_all_user_progress_entry_t));
450
+ if (!response->entries)
451
+ return RC_OUT_OF_MEMORY;
452
+
453
+ memset(&iterator, 0, sizeof(iterator));
454
+ iterator.json = fields[2].value_start;
455
+ iterator.end = fields[2].value_end;
456
+
457
+ entry = response->entries;
458
+ while (rc_json_get_next_object_field(&iterator, &field))
459
+ {
460
+ entry->game_id = strtol(field.name, &end, 10);
461
+
462
+ field.name = "";
463
+ if (!rc_json_get_required_object(entry_fields, sizeof(entry_fields) / sizeof(entry_fields[0]),
464
+ &response->response, &field, ""))
465
+ {
466
+ return RC_MISSING_VALUE;
467
+ }
468
+
469
+ rc_json_get_optional_unum(&entry->num_achievements, &entry_fields[0], "Achievements", 0);
470
+ rc_json_get_optional_unum(&entry->num_unlocked_achievements, &entry_fields[1], "Unlocked", 0);
471
+ rc_json_get_optional_unum(&entry->num_unlocked_achievements_hardcore, &entry_fields[2], "UnlockedHardcore", 0);
472
+
473
+ ++entry;
474
+ }
475
+ }
476
+
477
+ return RC_OK;
478
+ }
479
+
480
+ void rc_api_destroy_fetch_all_user_progress_response(rc_api_fetch_all_user_progress_response_t* response)
481
+ {
482
+ rc_buffer_destroy(&response->response.buffer);
483
+ }