gemba 0.1.0 → 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 +24 -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 +135 -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 -1515
@@ -0,0 +1,838 @@
1
+ #include "rc_hash_internal.h"
2
+
3
+ #include "../rc_compat.h"
4
+
5
+ #include <ctype.h>
6
+ #include <string.h>
7
+ #include <stdlib.h>
8
+
9
+ static int cdreader_get_sector(uint8_t header[16])
10
+ {
11
+ int minutes = (header[12] >> 4) * 10 + (header[12] & 0x0F);
12
+ int seconds = (header[13] >> 4) * 10 + (header[13] & 0x0F);
13
+ int frames = (header[14] >> 4) * 10 + (header[14] & 0x0F);
14
+
15
+ /* convert the MSF value to a sector index, and subtract 150 (2 seconds) per:
16
+ * For data and mixed mode media (those conforming to ISO/IEC 10149), logical block address
17
+ * zero shall be assigned to the block at MSF address 00/02/00 */
18
+ return ((minutes * 60) + seconds) * 75 + frames - 150;
19
+ }
20
+
21
+ static void cdreader_determine_sector_size(rc_hash_cdrom_track_t* cdrom)
22
+ {
23
+ /* Attempt to determine the sector and header sizes. The CUE file may be lying.
24
+ * Look for the sync pattern using each of the supported sector sizes.
25
+ * Then check for the presence of "CD001", which is gauranteed to be in either the
26
+ * boot record or primary volume descriptor, one of which is always at sector 16.
27
+ */
28
+ const uint8_t sync_pattern[] = {
29
+ 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00
30
+ };
31
+
32
+ uint8_t header[32];
33
+ const int64_t toc_sector = 16 + cdrom->track_pregap_sectors;
34
+
35
+ cdrom->sector_size = 0;
36
+ cdrom->sector_header_size = 0;
37
+ cdrom->raw_data_size = 2048;
38
+
39
+ cdrom->file_reader->seek(cdrom->file_handle, toc_sector * 2352 + cdrom->file_track_offset, SEEK_SET);
40
+ if (cdrom->file_reader->read(cdrom->file_handle, header, sizeof(header)) < sizeof(header))
41
+ return;
42
+
43
+ if (memcmp(header, sync_pattern, 12) == 0)
44
+ {
45
+ cdrom->sector_size = 2352;
46
+
47
+ if (memcmp(&header[25], "CD001", 5) == 0)
48
+ cdrom->sector_header_size = 24;
49
+ else
50
+ cdrom->sector_header_size = 16;
51
+
52
+ cdrom->track_first_sector = cdreader_get_sector(header) - (int)toc_sector;
53
+ }
54
+ else
55
+ {
56
+ cdrom->file_reader->seek(cdrom->file_handle, toc_sector * 2336 + cdrom->file_track_offset, SEEK_SET);
57
+ cdrom->file_reader->read(cdrom->file_handle, header, sizeof(header));
58
+
59
+ if (memcmp(header, sync_pattern, 12) == 0)
60
+ {
61
+ cdrom->sector_size = 2336;
62
+
63
+ if (memcmp(&header[25], "CD001", 5) == 0)
64
+ cdrom->sector_header_size = 24;
65
+ else
66
+ cdrom->sector_header_size = 16;
67
+
68
+ cdrom->track_first_sector = cdreader_get_sector(header) - (int)toc_sector;
69
+ }
70
+ else
71
+ {
72
+ cdrom->file_reader->seek(cdrom->file_handle, toc_sector * 2048 + cdrom->file_track_offset, SEEK_SET);
73
+ cdrom->file_reader->read(cdrom->file_handle, header, sizeof(header));
74
+
75
+ if (memcmp(&header[1], "CD001", 5) == 0)
76
+ {
77
+ cdrom->sector_size = 2048;
78
+ cdrom->sector_header_size = 0;
79
+ }
80
+ }
81
+ }
82
+ }
83
+
84
+ static void* cdreader_open_bin_track(const char* path, uint32_t track, const rc_hash_iterator_t* iterator)
85
+ {
86
+ void* file_handle;
87
+ rc_hash_cdrom_track_t* cdrom;
88
+
89
+ if (track > 1) {
90
+ rc_hash_iterator_verbose(iterator, "Cannot locate secondary tracks without a cue sheet");
91
+ return NULL;
92
+ }
93
+
94
+ file_handle = iterator->callbacks.filereader.open(path);
95
+ if (!file_handle)
96
+ return NULL;
97
+
98
+ cdrom = (rc_hash_cdrom_track_t*)calloc(1, sizeof(*cdrom));
99
+ if (!cdrom)
100
+ return NULL;
101
+ cdrom->file_reader = &iterator->callbacks.filereader;
102
+ cdrom->file_handle = file_handle;
103
+ #ifndef NDEBUG
104
+ cdrom->track_id = track;
105
+ #endif
106
+
107
+ cdreader_determine_sector_size(cdrom);
108
+
109
+ if (cdrom->sector_size == 0)
110
+ {
111
+ int64_t size;
112
+
113
+ iterator->callbacks.filereader.seek(file_handle, 0, SEEK_END);
114
+ size = iterator->callbacks.filereader.tell(file_handle);
115
+
116
+ if ((size % 2352) == 0)
117
+ {
118
+ /* raw tracks use all 2352 bytes and have a 24 byte header */
119
+ cdrom->sector_size = 2352;
120
+ cdrom->sector_header_size = 24;
121
+ }
122
+ else if ((size % 2048) == 0)
123
+ {
124
+ /* cooked tracks eliminate all header/footer data */
125
+ cdrom->sector_size = 2048;
126
+ cdrom->sector_header_size = 0;
127
+ }
128
+ else if ((size % 2336) == 0)
129
+ {
130
+ /* MODE 2 format without 16-byte sync data */
131
+ cdrom->sector_size = 2336;
132
+ cdrom->sector_header_size = 8;
133
+ }
134
+ else
135
+ {
136
+ if (iterator->callbacks.filereader.close)
137
+ iterator->callbacks.filereader.close(file_handle);
138
+ free(cdrom);
139
+
140
+ rc_hash_iterator_verbose(iterator, "Could not determine sector size");
141
+
142
+ return NULL;
143
+ }
144
+ }
145
+
146
+ return cdrom;
147
+ }
148
+
149
+ static int cdreader_open_bin(rc_hash_cdrom_track_t* cdrom, const char* path, const char* mode)
150
+ {
151
+ cdrom->file_handle = cdrom->file_reader->open(path);
152
+ if (!cdrom->file_handle)
153
+ return 0;
154
+
155
+ /* determine sector size */
156
+ cdreader_determine_sector_size(cdrom);
157
+
158
+ /* could not determine, which means we'll probably have more issues later
159
+ * but use the CUE provided information anyway
160
+ */
161
+ if (cdrom->sector_size == 0)
162
+ {
163
+ /* All of these modes have 2048 byte payloads. In MODE1/2352 and MODE2/2352
164
+ * modes, the mode can actually be specified per sector to change the payload
165
+ * size, but that reduces the ability to recover from errors when the disc
166
+ * is damaged, so it's seldomly used, and when it is, it's mostly for audio
167
+ * or video data where a blip or two probably won't be noticed by the user.
168
+ * So, while we techincally support all of the following modes, we only do
169
+ * so with 2048 byte payloads.
170
+ * http://totalsonicmastering.com/cuesheetsyntax.htm
171
+ * MODE1/2048 ? CDROM Mode1 Data (cooked) [no header, no footer]
172
+ * MODE1/2352 ? CDROM Mode1 Data (raw) [16 byte header, 288 byte footer]
173
+ * MODE2/2336 ? CDROM-XA Mode2 Data [8 byte header, 280 byte footer]
174
+ * MODE2/2352 ? CDROM-XA Mode2 Data [24 byte header, 280 byte footer]
175
+ */
176
+ if (memcmp(mode, "MODE2/2352", 10) == 0)
177
+ {
178
+ cdrom->sector_size = 2352;
179
+ cdrom->sector_header_size = 24;
180
+ }
181
+ else if (memcmp(mode, "MODE1/2048", 10) == 0)
182
+ {
183
+ cdrom->sector_size = 2048;
184
+ cdrom->sector_header_size = 0;
185
+ }
186
+ else if (memcmp(mode, "MODE2/2336", 10) == 0)
187
+ {
188
+ cdrom->sector_size = 2336;
189
+ cdrom->sector_header_size = 8;
190
+ }
191
+ else if (memcmp(mode, "MODE1/2352", 10) == 0)
192
+ {
193
+ cdrom->sector_size = 2352;
194
+ cdrom->sector_header_size = 16;
195
+ }
196
+ else if (memcmp(mode, "AUDIO", 5) == 0)
197
+ {
198
+ cdrom->sector_size = 2352;
199
+ cdrom->sector_header_size = 0;
200
+ cdrom->raw_data_size = 2352; /* no header or footer data on audio tracks */
201
+ }
202
+ }
203
+
204
+ return (cdrom->sector_size != 0);
205
+ }
206
+
207
+ static char* cdreader_get_bin_path(const char* cue_path, const char* bin_name, const rc_hash_iterator_t* iterator)
208
+ {
209
+ const char* filename = rc_path_get_filename(cue_path);
210
+ const size_t bin_name_len = strlen(bin_name);
211
+ const size_t cue_path_len = filename - cue_path;
212
+ const size_t needed = cue_path_len + bin_name_len + 1;
213
+
214
+ char* bin_filename = (char*)malloc(needed);
215
+ if (!bin_filename)
216
+ {
217
+ rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)needed);
218
+ }
219
+ else
220
+ {
221
+ memcpy(bin_filename, cue_path, cue_path_len);
222
+ memcpy(bin_filename + cue_path_len, bin_name, bin_name_len + 1);
223
+ }
224
+
225
+ return bin_filename;
226
+ }
227
+
228
+ static int64_t cdreader_get_bin_size(const char* cue_path, const char* bin_name, const rc_hash_iterator_t* iterator)
229
+ {
230
+ int64_t size = 0;
231
+ char* bin_filename = cdreader_get_bin_path(cue_path, bin_name, iterator);
232
+ if (bin_filename)
233
+ {
234
+ void* handle = iterator->callbacks.filereader.open(bin_filename);
235
+ if (handle)
236
+ {
237
+ iterator->callbacks.filereader.seek(handle, 0, SEEK_END);
238
+ size = iterator->callbacks.filereader.tell(handle);
239
+
240
+ if (iterator->callbacks.filereader.close)
241
+ iterator->callbacks.filereader.close(handle);
242
+ }
243
+
244
+ free(bin_filename);
245
+ }
246
+
247
+ return size;
248
+ }
249
+
250
+ static void* cdreader_open_cue_track(const char* path, uint32_t track, const rc_hash_iterator_t* iterator)
251
+ {
252
+ void* cue_handle;
253
+ int64_t cue_offset = 0;
254
+ char buffer[1024];
255
+ char* bin_filename = NULL;
256
+ char *ptr, *ptr2, *end;
257
+ int done = 0;
258
+ int session = 1;
259
+ size_t num_read = 0;
260
+ rc_hash_cdrom_track_t* cdrom = NULL;
261
+
262
+ struct track_t
263
+ {
264
+ uint32_t id;
265
+ int sector_size;
266
+ int sector_count;
267
+ int first_sector;
268
+ int pregap_sectors;
269
+ int is_data;
270
+ int file_track_offset;
271
+ int file_first_sector;
272
+ char mode[16];
273
+ char filename[256];
274
+ } current_track, previous_track, largest_track;
275
+
276
+ cue_handle = iterator->callbacks.filereader.open(path);
277
+ if (!cue_handle)
278
+ return NULL;
279
+
280
+ memset(&current_track, 0, sizeof(current_track));
281
+ memset(&previous_track, 0, sizeof(previous_track));
282
+ memset(&largest_track, 0, sizeof(largest_track));
283
+
284
+ do
285
+ {
286
+ num_read = iterator->callbacks.filereader.read(cue_handle, buffer, sizeof(buffer) - 1);
287
+ if (num_read == 0)
288
+ break;
289
+
290
+ buffer[num_read] = 0;
291
+ if (num_read == sizeof(buffer) - 1)
292
+ end = buffer + sizeof(buffer) * 3 / 4;
293
+ else
294
+ end = buffer + num_read;
295
+
296
+ for (ptr = buffer; ptr < end; ++ptr)
297
+ {
298
+ while (*ptr == ' ')
299
+ ++ptr;
300
+
301
+ if (strncasecmp(ptr, "INDEX ", 6) == 0)
302
+ {
303
+ int m = 0, s = 0, f = 0;
304
+ int index;
305
+ int sector_offset;
306
+
307
+ ptr += 6;
308
+ index = atoi(ptr);
309
+
310
+ while (*ptr != ' ' && *ptr != '\n')
311
+ ++ptr;
312
+ while (*ptr == ' ')
313
+ ++ptr;
314
+
315
+ /* convert mm:ss:ff to sector count */
316
+ sscanf_s(ptr, "%d:%d:%d", &m, &s, &f);
317
+ sector_offset = ((m * 60) + s) * 75 + f;
318
+
319
+ if (current_track.first_sector == -1)
320
+ {
321
+ current_track.first_sector = sector_offset;
322
+ if (strcmp(current_track.filename, previous_track.filename) == 0)
323
+ {
324
+ previous_track.sector_count = current_track.first_sector - previous_track.first_sector;
325
+ current_track.file_track_offset += previous_track.sector_count * previous_track.sector_size;
326
+ }
327
+
328
+ /* if looking for the largest data track, determine previous track size */
329
+ if (track == RC_HASH_CDTRACK_LARGEST && previous_track.sector_count > largest_track.sector_count &&
330
+ previous_track.is_data)
331
+ {
332
+ memcpy(&largest_track, &previous_track, sizeof(largest_track));
333
+ }
334
+ }
335
+
336
+ if (index == 1)
337
+ {
338
+ current_track.pregap_sectors = (sector_offset - current_track.first_sector);
339
+
340
+ {
341
+ char* scan = current_track.mode;
342
+ while (*scan && !isspace((unsigned char)*scan))
343
+ ++scan;
344
+ *scan = '\0';
345
+
346
+ /* it's undesirable to truncate offset to 32-bits, but %lld isn't defined in c89. */
347
+ rc_hash_iterator_verbose_formatted(iterator, "Found %s track %d (first sector %d, sector size %d, %d pregap sectors)",
348
+ current_track.mode, current_track.id, current_track.first_sector, current_track.sector_size, current_track.pregap_sectors);
349
+ }
350
+
351
+ if (current_track.id == track)
352
+ {
353
+ done = 1;
354
+ break;
355
+ }
356
+
357
+ if (track == RC_HASH_CDTRACK_FIRST_DATA && current_track.is_data)
358
+ {
359
+ track = current_track.id;
360
+ done = 1;
361
+ break;
362
+ }
363
+
364
+ if (track == RC_HASH_CDTRACK_FIRST_OF_SECOND_SESSION && session == 2)
365
+ {
366
+ track = current_track.id;
367
+ done = 1;
368
+ break;
369
+ }
370
+ }
371
+ }
372
+ else if (strncasecmp(ptr, "TRACK ", 6) == 0)
373
+ {
374
+ if (current_track.sector_size)
375
+ memcpy(&previous_track, &current_track, sizeof(current_track));
376
+
377
+ ptr += 6;
378
+ current_track.id = atoi(ptr);
379
+
380
+ current_track.pregap_sectors = -1;
381
+ current_track.first_sector = -1;
382
+
383
+ while (*ptr != ' ')
384
+ ++ptr;
385
+ while (*ptr == ' ')
386
+ ++ptr;
387
+ memcpy(current_track.mode, ptr, sizeof(current_track.mode));
388
+ current_track.is_data = (memcmp(current_track.mode, "MODE", 4) == 0);
389
+
390
+ if (current_track.is_data)
391
+ {
392
+ current_track.sector_size = atoi(ptr + 6);
393
+ }
394
+ else
395
+ {
396
+ /* assume AUDIO */
397
+ current_track.sector_size = 2352;
398
+ }
399
+ }
400
+ else if (strncasecmp(ptr, "FILE ", 5) == 0)
401
+ {
402
+ if (current_track.sector_size)
403
+ {
404
+ memcpy(&previous_track, &current_track, sizeof(previous_track));
405
+
406
+ if (previous_track.sector_count == 0)
407
+ {
408
+ const int64_t bin_size = cdreader_get_bin_size(path, previous_track.filename, iterator);
409
+ const uint32_t file_sector_count = (uint32_t)bin_size / previous_track.sector_size;
410
+ previous_track.sector_count = file_sector_count - previous_track.first_sector;
411
+ }
412
+
413
+ /* if looking for the largest data track, check to see if this one is larger */
414
+ if (track == RC_HASH_CDTRACK_LARGEST && previous_track.is_data &&
415
+ previous_track.sector_count > largest_track.sector_count)
416
+ {
417
+ memcpy(&largest_track, &previous_track, sizeof(largest_track));
418
+ }
419
+ }
420
+
421
+ memset(&current_track, 0, sizeof(current_track));
422
+
423
+ current_track.file_first_sector = previous_track.file_first_sector +
424
+ previous_track.first_sector + previous_track.sector_count;
425
+
426
+ ptr += 5;
427
+ ptr2 = ptr;
428
+ if (*ptr == '"')
429
+ {
430
+ ++ptr;
431
+ do
432
+ {
433
+ ++ptr2;
434
+ } while (*ptr2 && *ptr2 != '\n' && *ptr2 != '"');
435
+ }
436
+ else
437
+ {
438
+ do
439
+ {
440
+ ++ptr2;
441
+ } while (*ptr2 && *ptr2 != '\n' && *ptr2 != ' ');
442
+ }
443
+
444
+ if (ptr2 - ptr < (int)sizeof(current_track.filename))
445
+ memcpy(current_track.filename, ptr, ptr2 - ptr);
446
+ }
447
+ else if (strncasecmp(ptr, "REM ", 4) == 0)
448
+ {
449
+ ptr += 4;
450
+ while (*ptr == ' ')
451
+ ++ptr;
452
+
453
+ if (strncasecmp(ptr, "SESSION ", 8) == 0)
454
+ {
455
+ ptr += 8;
456
+ while (*ptr == ' ')
457
+ ++ptr;
458
+ session = atoi(ptr);
459
+ }
460
+ }
461
+
462
+ while (*ptr && *ptr != '\n')
463
+ ++ptr;
464
+ }
465
+
466
+ if (done)
467
+ break;
468
+
469
+ cue_offset += (ptr - buffer);
470
+ iterator->callbacks.filereader.seek(cue_handle, cue_offset, SEEK_SET);
471
+
472
+ } while (1);
473
+
474
+ if (iterator->callbacks.filereader.close)
475
+ iterator->callbacks.filereader.close(cue_handle);
476
+
477
+ if (track == RC_HASH_CDTRACK_LARGEST)
478
+ {
479
+ if (current_track.sector_size && current_track.is_data)
480
+ {
481
+ const int64_t bin_size = cdreader_get_bin_size(path, current_track.filename, iterator);
482
+ const uint32_t file_sector_count = (uint32_t)bin_size / current_track.sector_size;
483
+ current_track.sector_count = file_sector_count - current_track.first_sector;
484
+
485
+ if (largest_track.sector_count > current_track.sector_count)
486
+ memcpy(&current_track, &largest_track, sizeof(current_track));
487
+ }
488
+ else
489
+ {
490
+ memcpy(&current_track, &largest_track, sizeof(current_track));
491
+ }
492
+
493
+ track = current_track.id;
494
+ }
495
+ else if (track == RC_HASH_CDTRACK_LAST && !done)
496
+ {
497
+ track = current_track.id;
498
+ }
499
+
500
+ if (current_track.id == track)
501
+ {
502
+ cdrom = (rc_hash_cdrom_track_t*)calloc(1, sizeof(*cdrom));
503
+ if (!cdrom)
504
+ {
505
+ rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
506
+ return NULL;
507
+ }
508
+
509
+ cdrom->file_reader = &iterator->callbacks.filereader;
510
+ cdrom->file_track_offset = current_track.file_track_offset;
511
+ cdrom->track_pregap_sectors = current_track.pregap_sectors;
512
+ cdrom->track_first_sector = current_track.file_first_sector + current_track.first_sector;
513
+ #ifndef NDEBUG
514
+ cdrom->track_id = current_track.id;
515
+ #endif
516
+
517
+ /* verify existance of bin file */
518
+ bin_filename = cdreader_get_bin_path(path, current_track.filename, iterator);
519
+ if (bin_filename)
520
+ {
521
+ if (cdreader_open_bin(cdrom, bin_filename, current_track.mode))
522
+ {
523
+ if (cdrom->track_pregap_sectors)
524
+ rc_hash_iterator_verbose_formatted(iterator, "Opened track %d (sector size %d, %d pregap sectors)",
525
+ track, cdrom->sector_size, cdrom->track_pregap_sectors);
526
+ else
527
+ rc_hash_iterator_verbose_formatted(iterator, "Opened track %d (sector size %d)", track, cdrom->sector_size);
528
+ }
529
+ else
530
+ {
531
+ if (cdrom->file_handle)
532
+ {
533
+ cdrom->file_reader->close(cdrom->file_handle);
534
+ rc_hash_iterator_error_formatted(iterator, "Could not determine sector size for %s track", current_track.mode);
535
+ }
536
+ else
537
+ {
538
+ rc_hash_iterator_error_formatted(iterator, "Could not open %s", bin_filename);
539
+ }
540
+
541
+ free(cdrom);
542
+ cdrom = NULL;
543
+ }
544
+
545
+ free(bin_filename);
546
+ }
547
+ }
548
+
549
+ return cdrom;
550
+ }
551
+
552
+ static void* cdreader_open_gdi_track(const char* path, uint32_t track, const rc_hash_iterator_t* iterator)
553
+ {
554
+ void* file_handle;
555
+ char buffer[1024];
556
+ char mode[16] = "MODE1/";
557
+ char sector_size[16];
558
+ char file[256];
559
+ int64_t track_size;
560
+ int track_type;
561
+ char* bin_path = NULL;
562
+ uint32_t current_track = 0;
563
+ char* ptr, *ptr2, *end;
564
+ int lba = 0;
565
+
566
+ uint32_t largest_track = 0;
567
+ int64_t largest_track_size = 0;
568
+ char largest_track_file[256];
569
+ char largest_track_sector_size[16];
570
+ int largest_track_lba = 0;
571
+
572
+ int found = 0;
573
+ size_t num_read = 0;
574
+ int64_t file_offset = 0;
575
+ rc_hash_cdrom_track_t* cdrom = NULL;
576
+
577
+ file_handle = iterator->callbacks.filereader.open(path);
578
+ if (!file_handle)
579
+ return NULL;
580
+
581
+ file[0] = '\0';
582
+ do
583
+ {
584
+ num_read = iterator->callbacks.filereader.read(file_handle, buffer, sizeof(buffer) - 1);
585
+ if (num_read == 0)
586
+ break;
587
+
588
+ buffer[num_read] = 0;
589
+ if (num_read == sizeof(buffer) - 1)
590
+ end = buffer + sizeof(buffer) * 3 / 4;
591
+ else
592
+ end = buffer + num_read;
593
+
594
+ ptr = buffer;
595
+
596
+ /* the first line contains the number of tracks, so we can get the last track index from it */
597
+ if (track == RC_HASH_CDTRACK_LAST)
598
+ track = atoi(ptr);
599
+
600
+ /* first line contains the number of tracks and will be skipped */
601
+ while (ptr < end)
602
+ {
603
+ /* skip until next newline */
604
+ while (*ptr != '\n' && ptr < end)
605
+ ++ptr;
606
+
607
+ /* skip newlines */
608
+ while ((*ptr == '\n' || *ptr == '\r') && ptr < end)
609
+ ++ptr;
610
+
611
+ /* line format: [trackid] [lba] [type] [sectorsize] [file] [?] */
612
+ while (isspace((unsigned char)*ptr))
613
+ ++ptr;
614
+
615
+ current_track = (uint32_t)atoi(ptr);
616
+ if (track && current_track != track && track != RC_HASH_CDTRACK_FIRST_DATA)
617
+ continue;
618
+
619
+ while (isdigit((unsigned char)*ptr))
620
+ ++ptr;
621
+ ++ptr;
622
+
623
+ while (isspace((unsigned char)*ptr))
624
+ ++ptr;
625
+
626
+ lba = atoi(ptr);
627
+ while (isdigit((unsigned char)*ptr))
628
+ ++ptr;
629
+ ++ptr;
630
+
631
+ while (isspace((unsigned char)*ptr))
632
+ ++ptr;
633
+
634
+ track_type = atoi(ptr);
635
+ while (isdigit((unsigned char)*ptr))
636
+ ++ptr;
637
+ ++ptr;
638
+
639
+ while (isspace((unsigned char)*ptr))
640
+ ++ptr;
641
+
642
+ ptr2 = sector_size;
643
+ while (isdigit((unsigned char)*ptr))
644
+ *ptr2++ = *ptr++;
645
+ *ptr2 = '\0';
646
+ ++ptr;
647
+
648
+ while (isspace((unsigned char)*ptr))
649
+ ++ptr;
650
+
651
+ ptr2 = file;
652
+ if (*ptr == '\"')
653
+ {
654
+ ++ptr;
655
+ while (*ptr != '\"')
656
+ *ptr2++ = *ptr++;
657
+ ++ptr;
658
+ }
659
+ else
660
+ {
661
+ while (*ptr != ' ')
662
+ *ptr2++ = *ptr++;
663
+ }
664
+ *ptr2 = '\0';
665
+
666
+ if (track == current_track)
667
+ {
668
+ found = 1;
669
+ break;
670
+ }
671
+ else if (track == RC_HASH_CDTRACK_FIRST_DATA && track_type == 4)
672
+ {
673
+ track = current_track;
674
+ found = 1;
675
+ break;
676
+ }
677
+ else if (track == RC_HASH_CDTRACK_LARGEST && track_type == 4)
678
+ {
679
+ track_size = cdreader_get_bin_size(path, file, iterator);
680
+ if (track_size > largest_track_size)
681
+ {
682
+ largest_track_size = track_size;
683
+ largest_track = current_track;
684
+ largest_track_lba = lba;
685
+ strcpy_s(largest_track_file, sizeof(largest_track_file), file);
686
+ strcpy_s(largest_track_sector_size, sizeof(largest_track_sector_size), sector_size);
687
+ }
688
+ }
689
+ }
690
+
691
+ if (found)
692
+ break;
693
+
694
+ file_offset += (ptr - buffer);
695
+ iterator->callbacks.filereader.seek(file_handle, file_offset, SEEK_SET);
696
+
697
+ } while (1);
698
+
699
+ if (iterator->callbacks.filereader.close)
700
+ iterator->callbacks.filereader.close(file_handle);
701
+
702
+ cdrom = (rc_hash_cdrom_track_t*)calloc(1, sizeof(*cdrom));
703
+ if (!cdrom)
704
+ {
705
+ rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)sizeof(*cdrom));
706
+ return NULL;
707
+ }
708
+
709
+ cdrom->file_reader = &iterator->callbacks.filereader;
710
+
711
+ /* if we were tracking the largest track, make it the current track.
712
+ * otherwise, current_track will be the requested track, or last track. */
713
+ if (largest_track != 0 && largest_track != current_track)
714
+ {
715
+ current_track = largest_track;
716
+ strcpy_s(file, sizeof(file), largest_track_file);
717
+ strcpy_s(sector_size, sizeof(sector_size), largest_track_sector_size);
718
+ lba = largest_track_lba;
719
+ }
720
+
721
+ /* open the bin file for the track - construct mode parameter from sector_size */
722
+ ptr = &mode[6];
723
+ ptr2 = sector_size;
724
+ while (*ptr2 && *ptr2 != '\"')
725
+ *ptr++ = *ptr2++;
726
+ *ptr = '\0';
727
+
728
+ bin_path = cdreader_get_bin_path(path, file, iterator);
729
+ if (cdreader_open_bin(cdrom, bin_path, mode))
730
+ {
731
+ cdrom->track_pregap_sectors = 0;
732
+ cdrom->track_first_sector = lba;
733
+ #ifndef NDEBUG
734
+ cdrom->track_id = current_track;
735
+ #endif
736
+
737
+ rc_hash_iterator_verbose_formatted(iterator, "Opened track %d (sector size %d)", current_track, cdrom->sector_size);
738
+ }
739
+ else
740
+ {
741
+ rc_hash_iterator_error_formatted(iterator, "Could not open %s", bin_path);
742
+
743
+ free(cdrom);
744
+ cdrom = NULL;
745
+ }
746
+
747
+ free(bin_path);
748
+
749
+ return cdrom;
750
+ }
751
+
752
+ static void* cdreader_open_track_iterator(const char* path, uint32_t track, const rc_hash_iterator_t* iterator)
753
+ {
754
+ /* backwards compatibility - 0 used to mean largest */
755
+ if (track == 0)
756
+ track = RC_HASH_CDTRACK_LARGEST;
757
+
758
+ if (rc_path_compare_extension(path, "cue"))
759
+ return cdreader_open_cue_track(path, track, iterator);
760
+ if (rc_path_compare_extension(path, "gdi"))
761
+ return cdreader_open_gdi_track(path, track, iterator);
762
+
763
+ return cdreader_open_bin_track(path, track, iterator);
764
+ }
765
+
766
+ static size_t cdreader_read_sector(void* track_handle, uint32_t sector, void* buffer, size_t requested_bytes)
767
+ {
768
+ int64_t sector_start;
769
+ size_t num_read, total_read = 0;
770
+ uint8_t* buffer_ptr = (uint8_t*)buffer;
771
+
772
+ rc_hash_cdrom_track_t* cdrom = (rc_hash_cdrom_track_t*)track_handle;
773
+ if (!cdrom)
774
+ return 0;
775
+
776
+ if (sector < (uint32_t)cdrom->track_first_sector)
777
+ return 0;
778
+
779
+ sector_start = (int64_t)(sector - cdrom->track_first_sector) * cdrom->sector_size +
780
+ cdrom->sector_header_size + cdrom->file_track_offset;
781
+
782
+ while (requested_bytes > (size_t)cdrom->raw_data_size)
783
+ {
784
+ cdrom->file_reader->seek(cdrom->file_handle, sector_start, SEEK_SET);
785
+ num_read = cdrom->file_reader->read(cdrom->file_handle, buffer_ptr, cdrom->raw_data_size);
786
+ total_read += num_read;
787
+
788
+ if (num_read < (size_t)cdrom->raw_data_size)
789
+ return total_read;
790
+
791
+ buffer_ptr += cdrom->raw_data_size;
792
+ sector_start += cdrom->sector_size;
793
+ requested_bytes -= cdrom->raw_data_size;
794
+ }
795
+
796
+ cdrom->file_reader->seek(cdrom->file_handle, sector_start, SEEK_SET);
797
+ num_read = cdrom->file_reader->read(cdrom->file_handle, buffer_ptr, (int)requested_bytes);
798
+ total_read += num_read;
799
+
800
+ return total_read;
801
+ }
802
+
803
+ static void cdreader_close_track(void* track_handle)
804
+ {
805
+ rc_hash_cdrom_track_t* cdrom = (rc_hash_cdrom_track_t*)track_handle;
806
+ if (cdrom)
807
+ {
808
+ if (cdrom->file_handle && cdrom->file_reader->close)
809
+ cdrom->file_reader->close(cdrom->file_handle);
810
+
811
+ free(track_handle);
812
+ }
813
+ }
814
+
815
+ static uint32_t cdreader_first_track_sector(void* track_handle)
816
+ {
817
+ rc_hash_cdrom_track_t* cdrom = (rc_hash_cdrom_track_t*)track_handle;
818
+ if (cdrom)
819
+ return cdrom->track_first_sector + cdrom->track_pregap_sectors;
820
+
821
+ return 0;
822
+ }
823
+
824
+ void rc_hash_get_default_cdreader(struct rc_hash_cdreader* cdreader)
825
+ {
826
+ cdreader->open_track = NULL;
827
+ cdreader->read_sector = cdreader_read_sector;
828
+ cdreader->close_track = cdreader_close_track;
829
+ cdreader->first_track_sector = cdreader_first_track_sector;
830
+ cdreader->open_track_iterator = cdreader_open_track_iterator;
831
+ }
832
+
833
+ void rc_hash_init_default_cdreader(void)
834
+ {
835
+ struct rc_hash_cdreader cdreader;
836
+ rc_hash_get_default_cdreader(&cdreader);
837
+ rc_hash_init_custom_cdreader(&cdreader);
838
+ }