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.
- checksums.yaml +4 -4
- data/THIRD_PARTY_NOTICES +37 -2
- data/assets/placeholder_boxart.png +0 -0
- data/bin/gemba +2 -2
- data/ext/gemba/extconf.rb +24 -1
- data/ext/gemba/gemba_ext.c +436 -2
- data/ext/gemba/gemba_ext.h +2 -0
- data/gemba.gemspec +5 -3
- data/lib/gemba/achievements/achievement.rb +23 -0
- data/lib/gemba/achievements/backend.rb +186 -0
- data/lib/gemba/achievements/cache.rb +70 -0
- data/lib/gemba/achievements/credentials_presenter.rb +142 -0
- data/lib/gemba/achievements/fake_backend.rb +205 -0
- data/lib/gemba/achievements/null_backend.rb +11 -0
- data/lib/gemba/achievements/offline_backend.rb +168 -0
- data/lib/gemba/achievements/retro_achievements/backend.rb +453 -0
- data/lib/gemba/achievements/retro_achievements/cli_sync_requester.rb +64 -0
- data/lib/gemba/achievements/retro_achievements/ping_worker.rb +27 -0
- data/lib/gemba/achievements.rb +19 -0
- data/lib/gemba/achievements_window.rb +556 -0
- data/lib/gemba/app_controller.rb +1015 -0
- data/lib/gemba/bios.rb +54 -0
- data/lib/gemba/boxart_fetcher/libretro_backend.rb +39 -0
- data/lib/gemba/boxart_fetcher/null_backend.rb +12 -0
- data/lib/gemba/boxart_fetcher.rb +79 -0
- data/lib/gemba/bus_emitter.rb +13 -0
- data/lib/gemba/child_window.rb +24 -1
- data/lib/gemba/cli/commands/config_cmd.rb +83 -0
- data/lib/gemba/cli/commands/decode.rb +154 -0
- data/lib/gemba/cli/commands/patch.rb +78 -0
- data/lib/gemba/cli/commands/play.rb +78 -0
- data/lib/gemba/cli/commands/record.rb +114 -0
- data/lib/gemba/cli/commands/replay.rb +161 -0
- data/lib/gemba/cli/commands/retro_achievements.rb +213 -0
- data/lib/gemba/cli/commands/version.rb +22 -0
- data/lib/gemba/cli.rb +52 -364
- data/lib/gemba/config.rb +135 -1
- data/lib/gemba/data/gb_games.json +1 -0
- data/lib/gemba/data/gb_md5.json +1 -0
- data/lib/gemba/data/gba_games.json +1 -0
- data/lib/gemba/data/gba_md5.json +1 -0
- data/lib/gemba/data/gbc_games.json +1 -0
- data/lib/gemba/data/gbc_md5.json +1 -0
- data/lib/gemba/emulator_frame.rb +1060 -0
- data/lib/gemba/event_bus.rb +48 -0
- data/lib/gemba/frame_stack.rb +60 -0
- data/lib/gemba/game_index.rb +84 -0
- data/lib/gemba/game_picker_frame.rb +268 -0
- data/lib/gemba/gamepad_map.rb +103 -0
- data/lib/gemba/headless.rb +6 -5
- data/lib/gemba/headless_player.rb +33 -3
- data/lib/gemba/help_window.rb +61 -0
- data/lib/gemba/hotkey_map.rb +3 -1
- data/lib/gemba/input_recorder.rb +107 -0
- data/lib/gemba/input_replayer.rb +119 -0
- data/lib/gemba/keyboard_map.rb +90 -0
- data/lib/gemba/locales/en.yml +97 -5
- data/lib/gemba/locales/ja.yml +97 -5
- data/lib/gemba/main_window.rb +56 -0
- data/lib/gemba/modal_stack.rb +81 -0
- data/lib/gemba/patcher_window.rb +223 -0
- data/lib/gemba/platform/gb.rb +21 -0
- data/lib/gemba/platform/gba.rb +21 -0
- data/lib/gemba/platform/gbc.rb +23 -0
- data/lib/gemba/platform.rb +20 -0
- data/lib/gemba/platform_open.rb +19 -0
- data/lib/gemba/recorder.rb +4 -3
- data/lib/gemba/replay_player.rb +691 -0
- data/lib/gemba/rom_info.rb +57 -0
- data/lib/gemba/rom_info_window.rb +16 -3
- data/lib/gemba/rom_library.rb +106 -0
- data/lib/gemba/rom_overrides.rb +47 -0
- data/lib/gemba/rom_patcher/bps.rb +161 -0
- data/lib/gemba/rom_patcher/ips.rb +101 -0
- data/lib/gemba/rom_patcher/ups.rb +118 -0
- data/lib/gemba/rom_patcher.rb +109 -0
- data/lib/gemba/{rom_loader.rb → rom_resolver.rb} +7 -6
- data/lib/gemba/runtime.rb +59 -26
- data/lib/gemba/save_state_manager.rb +4 -7
- data/lib/gemba/save_state_picker.rb +17 -4
- data/lib/gemba/session_logger.rb +64 -0
- data/lib/gemba/settings/audio_tab.rb +77 -0
- data/lib/gemba/settings/gamepad_tab.rb +351 -0
- data/lib/gemba/settings/hotkeys_tab.rb +259 -0
- data/lib/gemba/settings/paths.rb +11 -0
- data/lib/gemba/settings/recording_tab.rb +83 -0
- data/lib/gemba/settings/save_states_tab.rb +91 -0
- data/lib/gemba/settings/system_tab.rb +362 -0
- data/lib/gemba/settings/video_tab.rb +318 -0
- data/lib/gemba/settings_window.rb +162 -1036
- data/lib/gemba/version.rb +1 -1
- data/lib/gemba/virtual_keyboard.rb +19 -0
- data/lib/gemba.rb +2 -12
- data/test/achievements_window/test_bulk_sync.rb +218 -0
- data/test/achievements_window/test_bus_events.rb +125 -0
- data/test/achievements_window/test_close_confirmation.rb +201 -0
- data/test/achievements_window/test_initial_state.rb +164 -0
- data/test/achievements_window/test_sorting.rb +227 -0
- data/test/achievements_window/test_tree_rendering.rb +133 -0
- data/test/fixtures/fake_bios.bin +0 -0
- data/test/fixtures/pong.gba +0 -0
- data/test/fixtures/test.gb +0 -0
- data/test/fixtures/test.gbc +0 -0
- data/test/fixtures/test_quicksave.ss +0 -0
- data/test/screenshots/no_focus.png +0 -0
- data/test/shared/teek_test_worker.rb +17 -1
- data/test/shared/tk_test_helper.rb +91 -4
- data/test/support/achievements_window_helpers.rb +18 -0
- data/test/support/fake_core.rb +25 -0
- data/test/support/fake_ra_runtime.rb +74 -0
- data/test/support/fake_requester.rb +68 -0
- data/test/support/player_helpers.rb +20 -5
- data/test/test_achievement.rb +32 -0
- data/test/{test_player.rb → test_app_controller.rb} +353 -85
- data/test/test_bios.rb +123 -0
- data/test/test_boxart_fetcher.rb +150 -0
- data/test/test_cli.rb +17 -265
- data/test/test_cli_config.rb +64 -0
- data/test/test_cli_decode.rb +97 -0
- data/test/test_cli_patch.rb +58 -0
- data/test/test_cli_play.rb +213 -0
- data/test/test_cli_ra.rb +175 -0
- data/test/test_cli_record.rb +69 -0
- data/test/test_cli_replay.rb +72 -0
- data/test/test_cli_sync_requester.rb +152 -0
- data/test/test_cli_version.rb +27 -0
- data/test/test_config.rb +2 -3
- data/test/test_config_ra.rb +69 -0
- data/test/test_core.rb +62 -1
- data/test/test_credentials_presenter.rb +192 -0
- data/test/test_event_bus.rb +100 -0
- data/test/test_fake_backend_achievements.rb +130 -0
- data/test/test_fake_backend_auth.rb +68 -0
- data/test/test_game_index.rb +77 -0
- data/test/test_game_picker_frame.rb +310 -0
- data/test/test_gamepad_map.rb +1 -3
- data/test/test_headless_player.rb +17 -3
- data/test/test_help_window.rb +82 -0
- data/test/test_hotkey_map.rb +22 -1
- data/test/test_input_recorder.rb +179 -0
- data/test/test_input_replay_determinism.rb +113 -0
- data/test/test_input_replayer.rb +162 -0
- data/test/test_keyboard_map.rb +1 -3
- data/test/test_libretro_backend.rb +41 -0
- data/test/test_locale.rb +1 -1
- data/test/test_logging.rb +123 -0
- data/test/test_null_backend.rb +42 -0
- data/test/test_offline_backend.rb +116 -0
- data/test/test_overlay_renderer.rb +1 -1
- data/test/test_platform.rb +149 -0
- data/test/test_ra_backend.rb +313 -0
- data/test/test_ra_backend_unlock_gate.rb +56 -0
- data/test/test_recorder.rb +0 -3
- data/test/test_replay_player.rb +316 -0
- data/test/test_rom_info.rb +149 -0
- data/test/test_rom_overrides.rb +86 -0
- data/test/test_rom_patcher.rb +382 -0
- data/test/{test_rom_loader.rb → test_rom_resolver.rb} +25 -26
- data/test/test_save_state_manager.rb +2 -4
- data/test/test_settings_audio.rb +107 -0
- data/test/test_settings_hotkeys.rb +83 -66
- data/test/test_settings_recording.rb +49 -0
- data/test/test_settings_save_states.rb +97 -0
- data/test/test_settings_system.rb +133 -0
- data/test/test_settings_video.rb +450 -0
- data/test/test_settings_window.rb +76 -507
- data/test/test_tip_service.rb +6 -6
- data/test/test_toast_overlay.rb +1 -1
- data/test/test_virtual_events.rb +156 -0
- data/test/test_virtual_keyboard.rb +1 -1
- data/vendor/rcheevos/CHANGELOG.md +495 -0
- data/vendor/rcheevos/LICENSE +21 -0
- data/vendor/rcheevos/Package.swift +33 -0
- data/vendor/rcheevos/README.md +67 -0
- data/vendor/rcheevos/include/module.modulemap +70 -0
- data/vendor/rcheevos/include/rc_api_editor.h +296 -0
- data/vendor/rcheevos/include/rc_api_info.h +280 -0
- data/vendor/rcheevos/include/rc_api_request.h +77 -0
- data/vendor/rcheevos/include/rc_api_runtime.h +417 -0
- data/vendor/rcheevos/include/rc_api_user.h +262 -0
- data/vendor/rcheevos/include/rc_client.h +877 -0
- data/vendor/rcheevos/include/rc_client_raintegration.h +101 -0
- data/vendor/rcheevos/include/rc_consoles.h +138 -0
- data/vendor/rcheevos/include/rc_error.h +59 -0
- data/vendor/rcheevos/include/rc_export.h +100 -0
- data/vendor/rcheevos/include/rc_hash.h +200 -0
- data/vendor/rcheevos/include/rc_runtime.h +148 -0
- data/vendor/rcheevos/include/rc_runtime_types.h +452 -0
- data/vendor/rcheevos/include/rc_util.h +51 -0
- data/vendor/rcheevos/include/rcheevos.h +8 -0
- data/vendor/rcheevos/src/rapi/rc_api_common.c +1379 -0
- data/vendor/rcheevos/src/rapi/rc_api_common.h +88 -0
- data/vendor/rcheevos/src/rapi/rc_api_editor.c +625 -0
- data/vendor/rcheevos/src/rapi/rc_api_info.c +587 -0
- data/vendor/rcheevos/src/rapi/rc_api_runtime.c +901 -0
- data/vendor/rcheevos/src/rapi/rc_api_user.c +483 -0
- data/vendor/rcheevos/src/rc_client.c +6941 -0
- data/vendor/rcheevos/src/rc_client_external.c +281 -0
- data/vendor/rcheevos/src/rc_client_external.h +177 -0
- data/vendor/rcheevos/src/rc_client_external_versions.h +171 -0
- data/vendor/rcheevos/src/rc_client_internal.h +409 -0
- data/vendor/rcheevos/src/rc_client_raintegration.c +566 -0
- data/vendor/rcheevos/src/rc_client_raintegration_internal.h +61 -0
- data/vendor/rcheevos/src/rc_client_types.natvis +396 -0
- data/vendor/rcheevos/src/rc_compat.c +251 -0
- data/vendor/rcheevos/src/rc_compat.h +121 -0
- data/vendor/rcheevos/src/rc_libretro.c +915 -0
- data/vendor/rcheevos/src/rc_libretro.h +98 -0
- data/vendor/rcheevos/src/rc_util.c +199 -0
- data/vendor/rcheevos/src/rc_version.c +11 -0
- data/vendor/rcheevos/src/rc_version.h +32 -0
- data/vendor/rcheevos/src/rcheevos/alloc.c +312 -0
- data/vendor/rcheevos/src/rcheevos/condition.c +754 -0
- data/vendor/rcheevos/src/rcheevos/condset.c +777 -0
- data/vendor/rcheevos/src/rcheevos/consoleinfo.c +1215 -0
- data/vendor/rcheevos/src/rcheevos/format.c +330 -0
- data/vendor/rcheevos/src/rcheevos/lboard.c +287 -0
- data/vendor/rcheevos/src/rcheevos/memref.c +805 -0
- data/vendor/rcheevos/src/rcheevos/operand.c +607 -0
- data/vendor/rcheevos/src/rcheevos/rc_internal.h +390 -0
- data/vendor/rcheevos/src/rcheevos/rc_runtime_types.natvis +541 -0
- data/vendor/rcheevos/src/rcheevos/rc_validate.c +1406 -0
- data/vendor/rcheevos/src/rcheevos/rc_validate.h +18 -0
- data/vendor/rcheevos/src/rcheevos/richpresence.c +922 -0
- data/vendor/rcheevos/src/rcheevos/runtime.c +852 -0
- data/vendor/rcheevos/src/rcheevos/runtime_progress.c +1073 -0
- data/vendor/rcheevos/src/rcheevos/trigger.c +344 -0
- data/vendor/rcheevos/src/rcheevos/value.c +935 -0
- data/vendor/rcheevos/src/rhash/aes.c +480 -0
- data/vendor/rcheevos/src/rhash/aes.h +49 -0
- data/vendor/rcheevos/src/rhash/cdreader.c +838 -0
- data/vendor/rcheevos/src/rhash/hash.c +1402 -0
- data/vendor/rcheevos/src/rhash/hash_disc.c +1340 -0
- data/vendor/rcheevos/src/rhash/hash_encrypted.c +566 -0
- data/vendor/rcheevos/src/rhash/hash_rom.c +426 -0
- data/vendor/rcheevos/src/rhash/hash_zip.c +460 -0
- data/vendor/rcheevos/src/rhash/md5.c +382 -0
- data/vendor/rcheevos/src/rhash/md5.h +91 -0
- data/vendor/rcheevos/src/rhash/rc_hash_internal.h +116 -0
- data/vendor/rcheevos/test/libretro.h +205 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_common.c +941 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_editor.c +931 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_info.c +545 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_runtime.c +2213 -0
- data/vendor/rcheevos/test/rapi/test_rc_api_user.c +998 -0
- data/vendor/rcheevos/test/rcheevos/mock_memory.h +32 -0
- data/vendor/rcheevos/test/rcheevos/test_condition.c +570 -0
- data/vendor/rcheevos/test/rcheevos/test_condset.c +5170 -0
- data/vendor/rcheevos/test/rcheevos/test_consoleinfo.c +203 -0
- data/vendor/rcheevos/test/rcheevos/test_format.c +112 -0
- data/vendor/rcheevos/test/rcheevos/test_lboard.c +746 -0
- data/vendor/rcheevos/test/rcheevos/test_memref.c +520 -0
- data/vendor/rcheevos/test/rcheevos/test_operand.c +692 -0
- data/vendor/rcheevos/test/rcheevos/test_rc_validate.c +502 -0
- data/vendor/rcheevos/test/rcheevos/test_richpresence.c +1564 -0
- data/vendor/rcheevos/test/rcheevos/test_runtime.c +1667 -0
- data/vendor/rcheevos/test/rcheevos/test_runtime_progress.c +1821 -0
- data/vendor/rcheevos/test/rcheevos/test_timing.c +166 -0
- data/vendor/rcheevos/test/rcheevos/test_trigger.c +2521 -0
- data/vendor/rcheevos/test/rcheevos/test_value.c +870 -0
- data/vendor/rcheevos/test/rcheevos-test.sln +46 -0
- data/vendor/rcheevos/test/rcheevos-test.vcxproj +239 -0
- data/vendor/rcheevos/test/rcheevos-test.vcxproj.filters +335 -0
- data/vendor/rcheevos/test/rhash/data.c +657 -0
- data/vendor/rcheevos/test/rhash/data.h +32 -0
- data/vendor/rcheevos/test/rhash/mock_filereader.c +236 -0
- data/vendor/rcheevos/test/rhash/mock_filereader.h +31 -0
- data/vendor/rcheevos/test/rhash/test_cdreader.c +920 -0
- data/vendor/rcheevos/test/rhash/test_hash.c +310 -0
- data/vendor/rcheevos/test/rhash/test_hash_disc.c +1450 -0
- data/vendor/rcheevos/test/rhash/test_hash_rom.c +899 -0
- data/vendor/rcheevos/test/rhash/test_hash_zip.c +551 -0
- data/vendor/rcheevos/test/test.c +113 -0
- data/vendor/rcheevos/test/test_framework.h +205 -0
- data/vendor/rcheevos/test/test_rc_client.c +10509 -0
- data/vendor/rcheevos/test/test_rc_client_external.c +2197 -0
- data/vendor/rcheevos/test/test_rc_client_raintegration.c +441 -0
- data/vendor/rcheevos/test/test_rc_libretro.c +952 -0
- data/vendor/rcheevos/test/test_types.natvis +9 -0
- data/vendor/rcheevos/validator/validator.c +658 -0
- data/vendor/rcheevos/validator/validator.vcxproj +152 -0
- data/vendor/rcheevos/validator/validator.vcxproj.filters +82 -0
- metadata +274 -11
- data/lib/gemba/input_mappings.rb +0 -214
- data/lib/gemba/player.rb +0 -1515
|
@@ -0,0 +1,566 @@
|
|
|
1
|
+
#include "rc_hash_internal.h"
|
|
2
|
+
|
|
3
|
+
#include "../rc_compat.h"
|
|
4
|
+
|
|
5
|
+
#include "aes.h"
|
|
6
|
+
|
|
7
|
+
/* ===================================================== */
|
|
8
|
+
|
|
9
|
+
static rc_hash_3ds_get_cia_normal_key_func _3ds_get_cia_normal_key_func = NULL;
|
|
10
|
+
static rc_hash_3ds_get_ncch_normal_keys_func _3ds_get_ncch_normal_keys_func = NULL;
|
|
11
|
+
|
|
12
|
+
void rc_hash_reset_iterator_encrypted(rc_hash_iterator_t* iterator)
|
|
13
|
+
{
|
|
14
|
+
iterator->callbacks.encryption.get_3ds_cia_normal_key = _3ds_get_cia_normal_key_func;
|
|
15
|
+
iterator->callbacks.encryption.get_3ds_ncch_normal_keys = _3ds_get_ncch_normal_keys_func;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
void rc_hash_init_3ds_get_cia_normal_key_func(rc_hash_3ds_get_cia_normal_key_func func)
|
|
19
|
+
{
|
|
20
|
+
_3ds_get_cia_normal_key_func = func;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
void rc_hash_init_3ds_get_ncch_normal_keys_func(rc_hash_3ds_get_ncch_normal_keys_func func)
|
|
24
|
+
{
|
|
25
|
+
_3ds_get_ncch_normal_keys_func = func;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/* ===================================================== */
|
|
29
|
+
|
|
30
|
+
static int rc_hash_nintendo_3ds_ncch(md5_state_t* md5, void* file_handle, uint8_t header[0x200],
|
|
31
|
+
struct AES_ctx* cia_aes, const rc_hash_iterator_t* iterator)
|
|
32
|
+
{
|
|
33
|
+
struct AES_ctx ncch_aes;
|
|
34
|
+
uint8_t* hash_buffer;
|
|
35
|
+
uint64_t exefs_offset, exefs_real_size;
|
|
36
|
+
uint32_t exefs_buffer_size;
|
|
37
|
+
uint8_t primary_key[AES_KEYLEN], secondary_key[AES_KEYLEN];
|
|
38
|
+
uint8_t fixed_key_flag, no_crypto_flag, seed_crypto_flag;
|
|
39
|
+
uint8_t crypto_method, secondary_key_x_slot;
|
|
40
|
+
uint16_t ncch_version;
|
|
41
|
+
uint32_t i;
|
|
42
|
+
uint8_t primary_key_y[AES_KEYLEN], program_id[sizeof(uint64_t)];
|
|
43
|
+
uint8_t iv[AES_BLOCKLEN], cia_iv[AES_BLOCKLEN];
|
|
44
|
+
uint8_t exefs_section_name[8];
|
|
45
|
+
uint64_t exefs_section_offset, exefs_section_size;
|
|
46
|
+
|
|
47
|
+
exefs_offset = ((uint32_t)header[0x1A3] << 24) | (header[0x1A2] << 16) | (header[0x1A1] << 8) | header[0x1A0];
|
|
48
|
+
exefs_real_size = ((uint32_t)header[0x1A7] << 24) | (header[0x1A6] << 16) | (header[0x1A5] << 8) | header[0x1A4];
|
|
49
|
+
|
|
50
|
+
/* Offset and size are in "media units" (1 media unit = 0x200 bytes) */
|
|
51
|
+
exefs_offset *= 0x200;
|
|
52
|
+
exefs_real_size *= 0x200;
|
|
53
|
+
|
|
54
|
+
if (exefs_real_size > MAX_BUFFER_SIZE)
|
|
55
|
+
exefs_buffer_size = MAX_BUFFER_SIZE;
|
|
56
|
+
else
|
|
57
|
+
exefs_buffer_size = (uint32_t)exefs_real_size;
|
|
58
|
+
|
|
59
|
+
/* This region is technically optional, but it should always be present for executable content (i.e. games) */
|
|
60
|
+
if (exefs_offset == 0 || exefs_real_size == 0)
|
|
61
|
+
return rc_hash_iterator_error(iterator, "ExeFS was not available");
|
|
62
|
+
|
|
63
|
+
/* NCCH flag 7 is a bitfield of various crypto related flags */
|
|
64
|
+
fixed_key_flag = header[0x188 + 7] & 0x01;
|
|
65
|
+
no_crypto_flag = header[0x188 + 7] & 0x04;
|
|
66
|
+
seed_crypto_flag = header[0x188 + 7] & 0x20;
|
|
67
|
+
|
|
68
|
+
ncch_version = (header[0x113] << 8) | header[0x112];
|
|
69
|
+
|
|
70
|
+
if (no_crypto_flag == 0) {
|
|
71
|
+
rc_hash_iterator_verbose(iterator, "Encrypted NCCH detected");
|
|
72
|
+
|
|
73
|
+
if (fixed_key_flag != 0) {
|
|
74
|
+
/* Fixed crypto key means all 0s for both keys */
|
|
75
|
+
memset(primary_key, 0, sizeof(primary_key));
|
|
76
|
+
memset(secondary_key, 0, sizeof(secondary_key));
|
|
77
|
+
rc_hash_iterator_verbose(iterator, "Using fixed key crypto");
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
if (iterator->callbacks.encryption.get_3ds_ncch_normal_keys == NULL)
|
|
81
|
+
return rc_hash_iterator_error(iterator, "An encrypted NCCH was detected, but the NCCH normal keys callback was not set");
|
|
82
|
+
|
|
83
|
+
/* Primary key y is just the first 16 bytes of the header */
|
|
84
|
+
memcpy(primary_key_y, header, sizeof(primary_key_y));
|
|
85
|
+
|
|
86
|
+
/* NCCH flag 3 indicates which secondary key x slot is used */
|
|
87
|
+
crypto_method = header[0x188 + 3];
|
|
88
|
+
|
|
89
|
+
switch (crypto_method) {
|
|
90
|
+
case 0x00:
|
|
91
|
+
rc_hash_iterator_verbose(iterator, "Using NCCH crypto method v1");
|
|
92
|
+
secondary_key_x_slot = 0x2C;
|
|
93
|
+
break;
|
|
94
|
+
case 0x01:
|
|
95
|
+
rc_hash_iterator_verbose(iterator, "Using NCCH crypto method v2");
|
|
96
|
+
secondary_key_x_slot = 0x25;
|
|
97
|
+
break;
|
|
98
|
+
case 0x0A:
|
|
99
|
+
rc_hash_iterator_verbose(iterator, "Using NCCH crypto method v3");
|
|
100
|
+
secondary_key_x_slot = 0x18;
|
|
101
|
+
break;
|
|
102
|
+
case 0x0B:
|
|
103
|
+
rc_hash_iterator_verbose(iterator, "Using NCCH crypto method v4");
|
|
104
|
+
secondary_key_x_slot = 0x1B;
|
|
105
|
+
break;
|
|
106
|
+
default:
|
|
107
|
+
return rc_hash_iterator_error_formatted(iterator, "Invalid crypto method %02X", (unsigned)crypto_method);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/* We only need the program id if we're doing seed crypto */
|
|
111
|
+
if (seed_crypto_flag != 0) {
|
|
112
|
+
rc_hash_iterator_verbose(iterator, "Using seed crypto");
|
|
113
|
+
memcpy(program_id, &header[0x118], sizeof(program_id));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
if (iterator->callbacks.encryption.get_3ds_ncch_normal_keys(primary_key_y, secondary_key_x_slot, seed_crypto_flag != 0 ? program_id : NULL, primary_key, secondary_key) == 0)
|
|
117
|
+
return rc_hash_iterator_error(iterator, "Could not obtain NCCH normal keys");
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
switch (ncch_version) {
|
|
121
|
+
case 0:
|
|
122
|
+
case 2:
|
|
123
|
+
rc_hash_iterator_verbose(iterator, "Detected NCCH version 0/2");
|
|
124
|
+
for (i = 0; i < 8; i++) {
|
|
125
|
+
/* First 8 bytes is the partition id in reverse byte order */
|
|
126
|
+
iv[7 - i] = header[0x108 + i];
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/* Magic number for ExeFS */
|
|
130
|
+
iv[8] = 2;
|
|
131
|
+
|
|
132
|
+
/* Rest of the bytes are 0 */
|
|
133
|
+
memset(&iv[9], 0, sizeof(iv) - 9);
|
|
134
|
+
break;
|
|
135
|
+
|
|
136
|
+
case 1:
|
|
137
|
+
rc_hash_iterator_verbose(iterator, "Detected NCCH version 1");
|
|
138
|
+
for (i = 0; i < 8; i++) {
|
|
139
|
+
/* First 8 bytes is the partition id in normal byte order */
|
|
140
|
+
iv[i] = header[0x108 + i];
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/* Next 4 bytes are 0 */
|
|
144
|
+
memset(&iv[8], 0, 4);
|
|
145
|
+
|
|
146
|
+
/* Last 4 bytes is the ExeFS byte offset in big endian */
|
|
147
|
+
iv[12] = (exefs_offset >> 24) & 0xFF;
|
|
148
|
+
iv[13] = (exefs_offset >> 16) & 0xFF;
|
|
149
|
+
iv[14] = (exefs_offset >> 8) & 0xFF;
|
|
150
|
+
iv[15] = exefs_offset & 0xFF;
|
|
151
|
+
break;
|
|
152
|
+
|
|
153
|
+
default:
|
|
154
|
+
return rc_hash_iterator_error_formatted(iterator, "Invalid NCCH version %04X", (unsigned)ncch_version);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/* ASSERT: file position must be +0x200 from start of NCCH (i.e. end of header) */
|
|
159
|
+
exefs_offset -= 0x200;
|
|
160
|
+
|
|
161
|
+
if (cia_aes) {
|
|
162
|
+
/* CBC decryption works by setting the IV to the encrypted previous block.
|
|
163
|
+
* Normally this means we would need to decrypt the data between the header and the ExeFS so the CIA AES state is correct.
|
|
164
|
+
* However, we can abuse how CBC decryption works and just set the IV to last block we would otherwise decrypt.
|
|
165
|
+
* We don't care about the data betweeen the header and ExeFS, so this works fine. */
|
|
166
|
+
|
|
167
|
+
rc_file_seek(iterator, file_handle, (int64_t)exefs_offset - AES_BLOCKLEN, SEEK_CUR);
|
|
168
|
+
if (rc_file_read(iterator, file_handle, cia_iv, AES_BLOCKLEN) != AES_BLOCKLEN)
|
|
169
|
+
return rc_hash_iterator_error(iterator, "Could not read NCCH data");
|
|
170
|
+
|
|
171
|
+
AES_ctx_set_iv(cia_aes, cia_iv);
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
/* No encryption present, just skip over the in-between data */
|
|
175
|
+
rc_file_seek(iterator, file_handle, (int64_t)exefs_offset, SEEK_CUR);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
hash_buffer = (uint8_t*)malloc(exefs_buffer_size);
|
|
179
|
+
if (!hash_buffer)
|
|
180
|
+
return rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)exefs_buffer_size);
|
|
181
|
+
|
|
182
|
+
/* Clear out crypto flags to ensure we get the same hash for decrypted and encrypted ROMs */
|
|
183
|
+
memset(&header[0x114], 0, 4);
|
|
184
|
+
header[0x188 + 3] = 0;
|
|
185
|
+
header[0x188 + 7] &= ~(0x20 | 0x04 | 0x01);
|
|
186
|
+
|
|
187
|
+
rc_hash_iterator_verbose(iterator, "Hashing 512 byte NCCH header");
|
|
188
|
+
md5_append(md5, header, 0x200);
|
|
189
|
+
|
|
190
|
+
rc_hash_iterator_verbose_formatted(iterator, "Hashing %u bytes for ExeFS (at NCCH offset %08X%08X)",
|
|
191
|
+
(unsigned)exefs_buffer_size, (unsigned)(exefs_offset >> 32), (unsigned)exefs_offset);
|
|
192
|
+
|
|
193
|
+
if (rc_file_read(iterator, file_handle, hash_buffer, exefs_buffer_size) != exefs_buffer_size) {
|
|
194
|
+
free(hash_buffer);
|
|
195
|
+
return rc_hash_iterator_error(iterator, "Could not read ExeFS data");
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
if (cia_aes) {
|
|
199
|
+
rc_hash_iterator_verbose(iterator, "Performing CIA decryption for ExeFS");
|
|
200
|
+
AES_CBC_decrypt_buffer(cia_aes, hash_buffer, exefs_buffer_size);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (no_crypto_flag == 0) {
|
|
204
|
+
rc_hash_iterator_verbose(iterator, "Performing NCCH decryption for ExeFS");
|
|
205
|
+
|
|
206
|
+
AES_init_ctx_iv(&ncch_aes, primary_key, iv);
|
|
207
|
+
AES_CTR_xcrypt_buffer(&ncch_aes, hash_buffer, 0x200);
|
|
208
|
+
|
|
209
|
+
for (i = 0; i < 8; i++) {
|
|
210
|
+
memcpy(exefs_section_name, &hash_buffer[i * 16], sizeof(exefs_section_name));
|
|
211
|
+
exefs_section_offset = ((uint32_t)hash_buffer[i * 16 + 11] << 24) | (hash_buffer[i * 16 + 10] << 16) | (hash_buffer[i * 16 + 9] << 8) | hash_buffer[i * 16 + 8];
|
|
212
|
+
exefs_section_size = ((uint32_t)hash_buffer[i * 16 + 15] << 24) | (hash_buffer[i * 16 + 14] << 16) | (hash_buffer[i * 16 + 13] << 8) | hash_buffer[i * 16 + 12];
|
|
213
|
+
|
|
214
|
+
/* 0 size indicates an unused section */
|
|
215
|
+
if (exefs_section_size == 0)
|
|
216
|
+
continue;
|
|
217
|
+
|
|
218
|
+
/* Offsets must be aligned by a media unit */
|
|
219
|
+
if (exefs_section_offset & 0x1FF)
|
|
220
|
+
return rc_hash_iterator_error(iterator, "ExeFS section offset is misaligned");
|
|
221
|
+
|
|
222
|
+
/* Offset is relative to the end of the header */
|
|
223
|
+
exefs_section_offset += 0x200;
|
|
224
|
+
|
|
225
|
+
/* Check against malformed sections */
|
|
226
|
+
if (exefs_section_offset + ((exefs_section_size + 0x1FF) & ~(uint64_t)0x1FF) > (uint64_t)exefs_real_size)
|
|
227
|
+
return rc_hash_iterator_error(iterator, "ExeFS section would overflow");
|
|
228
|
+
|
|
229
|
+
if (memcmp(exefs_section_name, "icon", 4) == 0 ||
|
|
230
|
+
memcmp(exefs_section_name, "banner", 6) == 0) {
|
|
231
|
+
/* Align size up by a media unit */
|
|
232
|
+
exefs_section_size = (exefs_section_size + 0x1FF) & ~(uint64_t)0x1FF;
|
|
233
|
+
AES_init_ctx(&ncch_aes, primary_key);
|
|
234
|
+
}
|
|
235
|
+
else {
|
|
236
|
+
/* We don't align size up here, as the padding bytes will use the primary key rather than the secondary key */
|
|
237
|
+
AES_init_ctx(&ncch_aes, secondary_key);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/* In theory, the section offset + size could be greater than the buffer size */
|
|
241
|
+
/* In practice, this likely never occurs, but just in case it does, ignore the section or constrict the size */
|
|
242
|
+
if (exefs_section_offset + exefs_section_size > exefs_buffer_size) {
|
|
243
|
+
if (exefs_section_offset >= exefs_buffer_size)
|
|
244
|
+
continue;
|
|
245
|
+
|
|
246
|
+
exefs_section_size = exefs_buffer_size - exefs_section_offset;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
exefs_section_name[7] = '\0';
|
|
250
|
+
rc_hash_iterator_verbose_formatted(iterator, "Decrypting ExeFS file %s at ExeFS offset %08X with size %08X",
|
|
251
|
+
(const char*)exefs_section_name, (unsigned)exefs_section_offset, (unsigned)exefs_section_size);
|
|
252
|
+
|
|
253
|
+
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], exefs_section_size & ~(uint64_t)0xF);
|
|
254
|
+
|
|
255
|
+
if (exefs_section_size & 0x1FF) {
|
|
256
|
+
/* Handle padding bytes, these always use the primary key */
|
|
257
|
+
exefs_section_offset += exefs_section_size;
|
|
258
|
+
exefs_section_size = 0x200 - (exefs_section_size & 0x1FF);
|
|
259
|
+
|
|
260
|
+
rc_hash_iterator_verbose_formatted(iterator, "Decrypting ExeFS padding at ExeFS offset %08X with size %08X",
|
|
261
|
+
(unsigned)exefs_section_offset, (unsigned)exefs_section_size);
|
|
262
|
+
|
|
263
|
+
/* Align our decryption start to an AES block boundary */
|
|
264
|
+
if (exefs_section_size & 0xF) {
|
|
265
|
+
/* We're a little evil here re-using the IV like this, but this seems to be the best way to deal with this... */
|
|
266
|
+
memcpy(iv, ncch_aes.Iv, sizeof(iv));
|
|
267
|
+
exefs_section_offset &= ~(uint64_t)0xF;
|
|
268
|
+
|
|
269
|
+
/* First decrypt these last bytes using the secondary key */
|
|
270
|
+
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], 0x10 - (exefs_section_size & 0xF));
|
|
271
|
+
|
|
272
|
+
/* Now re-encrypt these bytes using the primary key */
|
|
273
|
+
AES_init_ctx_iv(&ncch_aes, primary_key, iv);
|
|
274
|
+
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], 0x10 - (exefs_section_size & 0xF));
|
|
275
|
+
|
|
276
|
+
/* All of the padding can now be decrypted using the primary key */
|
|
277
|
+
AES_ctx_set_iv(&ncch_aes, iv);
|
|
278
|
+
exefs_section_size += 0x10 - (exefs_section_size & 0xF);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
AES_init_ctx(&ncch_aes, primary_key);
|
|
282
|
+
AES_CTR_xcrypt_buffer(&ncch_aes, &hash_buffer[exefs_section_offset], (size_t)exefs_section_size);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
md5_append(md5, hash_buffer, exefs_buffer_size);
|
|
288
|
+
|
|
289
|
+
free(hash_buffer);
|
|
290
|
+
return 1;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
static uint32_t rc_hash_nintendo_3ds_cia_signature_size(uint8_t header[0x200], const rc_hash_iterator_t* iterator)
|
|
294
|
+
{
|
|
295
|
+
uint32_t signature_type;
|
|
296
|
+
|
|
297
|
+
signature_type = ((uint32_t)header[0] << 24) | (header[1] << 16) | (header[2] << 8) | header[3];
|
|
298
|
+
switch (signature_type) {
|
|
299
|
+
case 0x010000:
|
|
300
|
+
case 0x010003:
|
|
301
|
+
return 0x200 + 0x3C;
|
|
302
|
+
|
|
303
|
+
case 0x010001:
|
|
304
|
+
case 0x010004:
|
|
305
|
+
return 0x100 + 0x3C;
|
|
306
|
+
|
|
307
|
+
case 0x010002:
|
|
308
|
+
case 0x010005:
|
|
309
|
+
return 0x3C + 0x40;
|
|
310
|
+
|
|
311
|
+
default:
|
|
312
|
+
return rc_hash_iterator_error_formatted(iterator, "Invalid signature type %08X", (unsigned)signature_type);
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
static int rc_hash_nintendo_3ds_cia(md5_state_t* md5, void* file_handle, uint8_t header[0x200],
|
|
317
|
+
const rc_hash_iterator_t* iterator)
|
|
318
|
+
{
|
|
319
|
+
const uint32_t CIA_HEADER_SIZE = 0x2020; /* Yes, this is larger than the header[0x200], but we only use the beginning of the header */
|
|
320
|
+
const uint64_t CIA_ALIGNMENT_MASK = 64 - 1; /* sizes are aligned by 64 bytes */
|
|
321
|
+
struct AES_ctx aes;
|
|
322
|
+
uint8_t iv[AES_BLOCKLEN], normal_key[AES_KEYLEN], title_key[AES_KEYLEN], title_id[sizeof(uint64_t)];
|
|
323
|
+
uint32_t cert_size, tik_size, tmd_size;
|
|
324
|
+
int64_t cert_offset, tik_offset, tmd_offset, content_offset;
|
|
325
|
+
uint32_t signature_size, i;
|
|
326
|
+
uint16_t content_count;
|
|
327
|
+
uint8_t common_key_index;
|
|
328
|
+
|
|
329
|
+
cert_size = ((uint32_t)header[0x0B] << 24) | (header[0x0A] << 16) | (header[0x09] << 8) | header[0x08];
|
|
330
|
+
tik_size = ((uint32_t)header[0x0F] << 24) | (header[0x0E] << 16) | (header[0x0D] << 8) | header[0x0C];
|
|
331
|
+
tmd_size = ((uint32_t)header[0x13] << 24) | (header[0x12] << 16) | (header[0x11] << 8) | header[0x10];
|
|
332
|
+
|
|
333
|
+
cert_offset = (CIA_HEADER_SIZE + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK;
|
|
334
|
+
tik_offset = (cert_offset + cert_size + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK;
|
|
335
|
+
tmd_offset = (tik_offset + tik_size + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK;
|
|
336
|
+
content_offset = (tmd_offset + tmd_size + CIA_ALIGNMENT_MASK) & ~CIA_ALIGNMENT_MASK;
|
|
337
|
+
|
|
338
|
+
/* Check if this CIA is encrypted, if it isn't, we can hash it right away */
|
|
339
|
+
|
|
340
|
+
rc_file_seek(iterator, file_handle, tmd_offset, SEEK_SET);
|
|
341
|
+
if (rc_file_read(iterator, file_handle, header, 4) != 4)
|
|
342
|
+
return rc_hash_iterator_error(iterator, "Could not read TMD signature type");
|
|
343
|
+
|
|
344
|
+
signature_size = rc_hash_nintendo_3ds_cia_signature_size(header, iterator);
|
|
345
|
+
if (signature_size == 0)
|
|
346
|
+
return 0; /* rc_hash_nintendo_3ds_cia_signature_size will call rc_hash_error, so we don't need to do so here */
|
|
347
|
+
|
|
348
|
+
rc_file_seek(iterator, file_handle, signature_size + 0x9E, SEEK_CUR);
|
|
349
|
+
if (rc_file_read(iterator, file_handle, header, 2) != 2)
|
|
350
|
+
return rc_hash_iterator_error(iterator, "Could not read TMD content count");
|
|
351
|
+
|
|
352
|
+
content_count = (header[0] << 8) | header[1];
|
|
353
|
+
|
|
354
|
+
rc_file_seek(iterator, file_handle, 0x9C4 - 0x9E - 2, SEEK_CUR);
|
|
355
|
+
for (i = 0; i < content_count; i++) {
|
|
356
|
+
if (rc_file_read(iterator, file_handle, header, 0x30) != 0x30)
|
|
357
|
+
return rc_hash_iterator_error(iterator, "Could not read TMD content chunk");
|
|
358
|
+
|
|
359
|
+
/* Content index 0 is the main content (i.e. the 3DS executable) */
|
|
360
|
+
if (((header[4] << 8) | header[5]) == 0)
|
|
361
|
+
break;
|
|
362
|
+
|
|
363
|
+
content_offset += ((uint32_t)header[0xC] << 24) | (header[0xD] << 16) | (header[0xE] << 8) | header[0xF];
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
if (i == content_count)
|
|
367
|
+
return rc_hash_iterator_error(iterator, "Could not find main content chunk in TMD");
|
|
368
|
+
|
|
369
|
+
if ((header[7] & 1) == 0) {
|
|
370
|
+
/* Not encrypted, we can hash the NCCH immediately */
|
|
371
|
+
rc_file_seek(iterator, file_handle, content_offset, SEEK_SET);
|
|
372
|
+
if (rc_file_read(iterator, file_handle, header, 0x200) != 0x200)
|
|
373
|
+
return rc_hash_iterator_error(iterator, "Could not read NCCH header");
|
|
374
|
+
|
|
375
|
+
if (memcmp(&header[0x100], "NCCH", 4) != 0)
|
|
376
|
+
return rc_hash_iterator_error_formatted(iterator, "NCCH header was not at %08X%08X", (unsigned)(content_offset >> 32), (unsigned)content_offset);
|
|
377
|
+
|
|
378
|
+
return rc_hash_nintendo_3ds_ncch(md5, file_handle, header, NULL, iterator);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (iterator->callbacks.encryption.get_3ds_cia_normal_key == NULL)
|
|
382
|
+
return rc_hash_iterator_error(iterator, "An encrypted CIA was detected, but the CIA normal key callback was not set");
|
|
383
|
+
|
|
384
|
+
/* Acquire the encrypted title key, title id, and common key index from the ticket */
|
|
385
|
+
/* These will be needed to decrypt the title key, and that will be needed to decrypt the CIA */
|
|
386
|
+
|
|
387
|
+
rc_file_seek(iterator, file_handle, tik_offset, SEEK_SET);
|
|
388
|
+
if (rc_file_read(iterator, file_handle, header, 4) != 4)
|
|
389
|
+
return rc_hash_iterator_error(iterator, "Could not read ticket signature type");
|
|
390
|
+
|
|
391
|
+
signature_size = rc_hash_nintendo_3ds_cia_signature_size(header, iterator);
|
|
392
|
+
if (signature_size == 0)
|
|
393
|
+
return 0;
|
|
394
|
+
|
|
395
|
+
rc_file_seek(iterator, file_handle, signature_size, SEEK_CUR);
|
|
396
|
+
if (rc_file_read(iterator, file_handle, header, 0xB2) != 0xB2)
|
|
397
|
+
return rc_hash_iterator_error(iterator, "Could not read ticket data");
|
|
398
|
+
|
|
399
|
+
memcpy(title_key, &header[0x7F], sizeof(title_key));
|
|
400
|
+
memcpy(title_id, &header[0x9C], sizeof(title_id));
|
|
401
|
+
common_key_index = header[0xB1];
|
|
402
|
+
|
|
403
|
+
if (common_key_index > 5)
|
|
404
|
+
return rc_hash_iterator_error_formatted(iterator, "Invalid common key index %02X", (unsigned)common_key_index);
|
|
405
|
+
|
|
406
|
+
if (iterator->callbacks.encryption.get_3ds_cia_normal_key(common_key_index, normal_key) == 0)
|
|
407
|
+
return rc_hash_iterator_error_formatted(iterator, "Could not obtain common key %02X", (unsigned)common_key_index);
|
|
408
|
+
|
|
409
|
+
memset(iv, 0, sizeof(iv));
|
|
410
|
+
memcpy(iv, title_id, sizeof(title_id));
|
|
411
|
+
AES_init_ctx_iv(&aes, normal_key, iv);
|
|
412
|
+
|
|
413
|
+
/* Finally, decrypt the title key */
|
|
414
|
+
AES_CBC_decrypt_buffer(&aes, title_key, sizeof(title_key));
|
|
415
|
+
|
|
416
|
+
/* Now we can hash the NCCH */
|
|
417
|
+
|
|
418
|
+
rc_file_seek(iterator, file_handle, content_offset, SEEK_SET);
|
|
419
|
+
if (rc_file_read(iterator, file_handle, header, 0x200) != 0x200)
|
|
420
|
+
return rc_hash_iterator_error(iterator, "Could not read NCCH header");
|
|
421
|
+
|
|
422
|
+
memset(iv, 0, sizeof(iv)); /* Content index is iv (which is always 0 for main content) */
|
|
423
|
+
AES_init_ctx_iv(&aes, title_key, iv);
|
|
424
|
+
AES_CBC_decrypt_buffer(&aes, header, 0x200);
|
|
425
|
+
|
|
426
|
+
if (memcmp(&header[0x100], "NCCH", 4) != 0)
|
|
427
|
+
return rc_hash_iterator_error_formatted(iterator, "NCCH header was not at %08X%08X", (unsigned)(content_offset >> 32), (unsigned)content_offset);
|
|
428
|
+
|
|
429
|
+
return rc_hash_nintendo_3ds_ncch(md5, file_handle, header, &aes, iterator);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
static int rc_hash_nintendo_3ds_3dsx(md5_state_t* md5, void* file_handle, uint8_t header[0x200], const rc_hash_iterator_t* iterator)
|
|
433
|
+
{
|
|
434
|
+
uint8_t* hash_buffer;
|
|
435
|
+
uint32_t header_size, reloc_header_size, code_size;
|
|
436
|
+
int64_t code_offset;
|
|
437
|
+
|
|
438
|
+
header_size = (header[5] << 8) | header[4];
|
|
439
|
+
reloc_header_size = (header[7] << 8) | header[6];
|
|
440
|
+
code_size = ((uint32_t)header[0x13] << 24) | (header[0x12] << 16) | (header[0x11] << 8) | header[0x10];
|
|
441
|
+
|
|
442
|
+
/* 3 relocation headers are in-between the 3DSX header and code segment */
|
|
443
|
+
code_offset = header_size + reloc_header_size * 3;
|
|
444
|
+
|
|
445
|
+
if (code_size > MAX_BUFFER_SIZE)
|
|
446
|
+
code_size = MAX_BUFFER_SIZE;
|
|
447
|
+
|
|
448
|
+
hash_buffer = (uint8_t*)malloc(code_size);
|
|
449
|
+
if (!hash_buffer)
|
|
450
|
+
return rc_hash_iterator_error_formatted(iterator, "Failed to allocate %u bytes", (unsigned)code_size);
|
|
451
|
+
|
|
452
|
+
rc_file_seek(iterator, file_handle, code_offset, SEEK_SET);
|
|
453
|
+
|
|
454
|
+
rc_hash_iterator_verbose_formatted(iterator, "Hashing %u bytes for 3DSX (at %08X)", (unsigned)code_size, (unsigned)code_offset);
|
|
455
|
+
|
|
456
|
+
if (rc_file_read(iterator, file_handle, hash_buffer, code_size) != code_size) {
|
|
457
|
+
free(hash_buffer);
|
|
458
|
+
return rc_hash_iterator_error(iterator, "Could not read 3DSX code segment");
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
md5_append(md5, hash_buffer, code_size);
|
|
462
|
+
|
|
463
|
+
free(hash_buffer);
|
|
464
|
+
return 1;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
int rc_hash_nintendo_3ds(char hash[33], const rc_hash_iterator_t* iterator)
|
|
468
|
+
{
|
|
469
|
+
md5_state_t md5;
|
|
470
|
+
void* file_handle;
|
|
471
|
+
uint8_t header[0x200]; /* NCCH and NCSD headers are both 0x200 bytes */
|
|
472
|
+
int64_t header_offset;
|
|
473
|
+
|
|
474
|
+
file_handle = rc_file_open(iterator, iterator->path);
|
|
475
|
+
if (!file_handle)
|
|
476
|
+
return rc_hash_iterator_error(iterator, "Could not open file");
|
|
477
|
+
|
|
478
|
+
rc_file_seek(iterator, file_handle, 0, SEEK_SET);
|
|
479
|
+
|
|
480
|
+
/* If we don't have a full header, this is probably not a 3DS ROM */
|
|
481
|
+
if (rc_file_read(iterator, file_handle, header, sizeof(header)) != sizeof(header)) {
|
|
482
|
+
rc_file_close(iterator, file_handle);
|
|
483
|
+
return rc_hash_iterator_error(iterator, "Could not read 3DS ROM header");
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
md5_init(&md5);
|
|
487
|
+
|
|
488
|
+
if (memcmp(&header[0x100], "NCSD", 4) == 0) {
|
|
489
|
+
/* A NCSD container contains 1-8 NCCH partitions */
|
|
490
|
+
/* The first partition (index 0) is reserved for executable content */
|
|
491
|
+
header_offset = ((uint32_t)header[0x123] << 24) | (header[0x122] << 16) | (header[0x121] << 8) | header[0x120];
|
|
492
|
+
/* Offset is in "media units" (1 media unit = 0x200 bytes) */
|
|
493
|
+
header_offset *= 0x200;
|
|
494
|
+
|
|
495
|
+
/* We include the NCSD header in the hash, as that will ensure different versions of a game result in a different hash
|
|
496
|
+
* This is due to some revisions / languages only ever changing other NCCH paritions (e.g. the game manual)
|
|
497
|
+
*/
|
|
498
|
+
rc_hash_iterator_verbose(iterator, "Hashing 512 byte NCSD header");
|
|
499
|
+
md5_append(&md5, header, sizeof(header));
|
|
500
|
+
|
|
501
|
+
rc_hash_iterator_verbose_formatted(iterator,
|
|
502
|
+
"Detected NCSD header, seeking to NCCH partition at %08X%08X",
|
|
503
|
+
(unsigned)(header_offset >> 32), (unsigned)header_offset);
|
|
504
|
+
|
|
505
|
+
rc_file_seek(iterator, file_handle, header_offset, SEEK_SET);
|
|
506
|
+
if (rc_file_read(iterator, file_handle, header, sizeof(header)) != sizeof(header)) {
|
|
507
|
+
rc_file_close(iterator, file_handle);
|
|
508
|
+
return rc_hash_iterator_error(iterator, "Could not read 3DS NCCH header");
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (memcmp(&header[0x100], "NCCH", 4) != 0) {
|
|
512
|
+
rc_file_close(iterator, file_handle);
|
|
513
|
+
return rc_hash_iterator_error_formatted(iterator, "3DS NCCH header was not at %08X%08X", (unsigned)(header_offset >> 32), (unsigned)header_offset);
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
if (memcmp(&header[0x100], "NCCH", 4) == 0) {
|
|
518
|
+
if (rc_hash_nintendo_3ds_ncch(&md5, file_handle, header, NULL, iterator)) {
|
|
519
|
+
rc_file_close(iterator, file_handle);
|
|
520
|
+
return rc_hash_finalize(iterator, &md5, hash);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
rc_file_close(iterator, file_handle);
|
|
524
|
+
return rc_hash_iterator_error(iterator, "Failed to hash 3DS NCCH container");
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/* Couldn't identify either an NCSD or NCCH */
|
|
528
|
+
|
|
529
|
+
/* Try to identify this as a CIA */
|
|
530
|
+
if (header[0] == 0x20 && header[1] == 0x20 && header[2] == 0x00 && header[3] == 0x00) {
|
|
531
|
+
rc_hash_iterator_verbose(iterator, "Detected CIA, attempting to find executable NCCH");
|
|
532
|
+
|
|
533
|
+
if (rc_hash_nintendo_3ds_cia(&md5, file_handle, header, iterator)) {
|
|
534
|
+
rc_file_close(iterator, file_handle);
|
|
535
|
+
return rc_hash_finalize(iterator, &md5, hash);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
rc_file_close(iterator, file_handle);
|
|
539
|
+
return rc_hash_iterator_error(iterator, "Failed to hash 3DS CIA container");
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
/* This might be a homebrew game, try to detect that */
|
|
543
|
+
if (memcmp(&header[0], "3DSX", 4) == 0) {
|
|
544
|
+
rc_hash_iterator_verbose(iterator, "Detected 3DSX");
|
|
545
|
+
|
|
546
|
+
if (rc_hash_nintendo_3ds_3dsx(&md5, file_handle, header, iterator)) {
|
|
547
|
+
rc_file_close(iterator, file_handle);
|
|
548
|
+
return rc_hash_finalize(iterator, &md5, hash);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
rc_file_close(iterator, file_handle);
|
|
552
|
+
return rc_hash_iterator_error(iterator, "Failed to hash 3DS 3DSX container");
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
/* Raw ELF marker (AXF/ELF files) */
|
|
556
|
+
if (memcmp(&header[0], "\x7f\x45\x4c\x46", 4) == 0) {
|
|
557
|
+
rc_hash_iterator_verbose(iterator, "Detected AXF/ELF file, hashing entire file");
|
|
558
|
+
|
|
559
|
+
/* Don't bother doing anything fancy here, just hash entire file */
|
|
560
|
+
rc_file_close(iterator, file_handle);
|
|
561
|
+
return rc_hash_whole_file(hash, iterator);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
rc_file_close(iterator, file_handle);
|
|
565
|
+
return rc_hash_iterator_error(iterator, "Not a 3DS ROM");
|
|
566
|
+
}
|