|
1 | 1 | #include "file_select.h" |
2 | 2 |
|
| 3 | +#include "audio.h" |
3 | 4 | #include "file_icons.h" |
4 | 5 | #include "file_message.h" |
5 | 6 | #include "gfx.h" |
6 | 7 | #include "music.h" |
7 | 8 | #include "text.h" |
8 | 9 | #include "util.h" |
9 | | -#include "z64.h" |
10 | | - |
| 10 | +#include "save.h" |
11 | 11 |
|
12 | 12 | sprite_t* hash_sprites[2] = { |
13 | 13 | &items_sprite, |
@@ -56,6 +56,192 @@ hash_symbol_t hash_symbols[32] = { |
56 | 56 |
|
57 | 57 | extern uint8_t CFG_FILE_SELECT_HASH[5]; |
58 | 58 |
|
| 59 | +int8_t password_index = -1; |
| 60 | +uint16_t tentatives = 0; |
| 61 | +uint16_t cooldown = 0; |
| 62 | +static const uint8_t TEXT_WIDTH = 8; |
| 63 | +static const uint8_t TEXT_HEIGHT = 9; |
| 64 | +static const uint8_t BUTTON_WIDTH = 12; |
| 65 | +static const uint8_t BUTTON_HEIGHT = 12; |
| 66 | +extern uint8_t PASSWORD[PASSWORD_LENGTH]; |
| 67 | +uint8_t buffer_password[PASSWORD_LENGTH] = {0, 0, 0, 0, 0, 0}; |
| 68 | + |
| 69 | +bool is_saved_password_clear(z64_menudata_t* menu_data) { |
| 70 | + // Player can save in file 1 and use file 2 later, so we look at both of them. |
| 71 | + bool fileOkPassword[2] = {true, true}; |
| 72 | + for (uint8_t slotFile = 0; slotFile < 2; slotFile++) { |
| 73 | + z64_file_t* file = &menu_data->sram_buffer->primary_saves[slotFile].original_save; |
| 74 | + extended_savecontext_static_t* extended = &(((extended_sram_file_t*)file)->additional_save_data.extended); |
| 75 | + for (uint8_t i = 0 ; i < PASSWORD_LENGTH; i++) { |
| 76 | + if (extended->password[i] != PASSWORD[i]) { |
| 77 | + fileOkPassword[slotFile] = false; |
| 78 | + break; |
| 79 | + } |
| 80 | + } |
| 81 | + } |
| 82 | + return fileOkPassword[0] || fileOkPassword[1]; |
| 83 | +} |
| 84 | + |
| 85 | +bool is_buffer_password_clear() { |
| 86 | + for (uint8_t i = 0 ; i < PASSWORD_LENGTH; i++) { |
| 87 | + if (buffer_password[i] != PASSWORD[i]) { |
| 88 | + return false; |
| 89 | + } |
| 90 | + } |
| 91 | + return true; |
| 92 | +} |
| 93 | + |
| 94 | +void reset_buffer() { |
| 95 | + // Don't reset if it's already the right one. |
| 96 | + if (is_buffer_password_clear()) { |
| 97 | + return; |
| 98 | + } |
| 99 | + password_index = -1; |
| 100 | + for (uint8_t i = 0 ; i < PASSWORD_LENGTH; i++) { |
| 101 | + buffer_password[i] = 0; |
| 102 | + } |
| 103 | +} |
| 104 | + |
| 105 | +void manage_password(z64_disp_buf_t* db, z64_menudata_t* menu_data) { |
| 106 | + if (cooldown > 0) { |
| 107 | + cooldown--; |
| 108 | + } |
| 109 | + // Draw "Password locked" at the place of the Controls texture, and a key on the OK button, to display the lock. |
| 110 | + if (!is_saved_password_clear(menu_data) && !is_buffer_password_clear()) { |
| 111 | + int left = 90; |
| 112 | + int top = 204; |
| 113 | + int padding = 8; |
| 114 | + |
| 115 | + gDPPipeSync(db->p++); |
| 116 | + if (cooldown > 0) { |
| 117 | + colorRGBA8_t color = {0xFF, 0xFF, 0xFF, 0xFF}; |
| 118 | + draw_int_size(db, 1 + cooldown / 60, left - 2 * padding, top, color, 6, 12); |
| 119 | + } |
| 120 | + gDPPipeSync(db->p++); |
| 121 | + gDPSetCombineMode(db->p++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); |
| 122 | + gDPSetPrimColor(db->p++, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF); |
| 123 | + sprite_load(db, &quest_items_sprite, 17, 1); |
| 124 | + sprite_draw(db, &quest_items_sprite, 0, left, top - 2, BUTTON_WIDTH, BUTTON_HEIGHT); |
| 125 | + text_print_size("Password locked", left + TEXT_HEIGHT + padding, top, TEXT_WIDTH); |
| 126 | + sprite_draw(db, &quest_items_sprite, 0, left + TEXT_WIDTH + 2 * padding + 15 * font_sprite.tile_w, top - 2, BUTTON_WIDTH, BUTTON_HEIGHT); |
| 127 | + text_flush_size(db, TEXT_WIDTH, TEXT_HEIGHT, 0, 0); |
| 128 | + } |
| 129 | + if (menu_data->menu_transition == SM_CONFIRM_FILE) { |
| 130 | + if (password_index < 0) { |
| 131 | + if (menu_data->selected_sub_item == FS_BTN_CONFIRM_YES) { |
| 132 | + if (z64_game.common.input[0].pad_pressed.a || z64_game.common.input[0].pad_pressed.s) { |
| 133 | + if (is_saved_password_clear(menu_data) || is_buffer_password_clear()) { |
| 134 | + // Load the game. |
| 135 | + z64_PlaySFXID(NA_SE_SY_FSEL_DECIDE_L); |
| 136 | + menu_data->menu_transition = SM_FADE_OUT; |
| 137 | + Audio_StopCurrentMusic(0xF); |
| 138 | + } else { |
| 139 | + if (cooldown == 0) { |
| 140 | + // Go to password screen. |
| 141 | + password_index++; |
| 142 | + return; |
| 143 | + } else { |
| 144 | + // Play an error sound until cooldown is finished. |
| 145 | + z64_PlaySFXID(NA_SE_SY_ERROR); |
| 146 | + } |
| 147 | + } |
| 148 | + } |
| 149 | + // Go back one screen. |
| 150 | + if (z64_game.common.input[0].pad_pressed.b) { |
| 151 | + z64_PlaySFXID(NA_SE_SY_FSEL_CLOSE); |
| 152 | + menu_data->menu_transition++; |
| 153 | + } |
| 154 | + } |
| 155 | + } |
| 156 | + if (menu_data->selected_sub_item == FS_BTN_CONFIRM_QUIT) { // On the Cancel option, reproduce vanilla behaviour and reset the buffer password. |
| 157 | + if (z64_game.common.input[0].pad_pressed.a || z64_game.common.input[0].pad_pressed.s || z64_game.common.input[0].pad_pressed.b) { |
| 158 | + z64_PlaySFXID(NA_SE_SY_FSEL_CLOSE); |
| 159 | + menu_data->menu_transition++; |
| 160 | + reset_buffer(); |
| 161 | + } |
| 162 | + } |
| 163 | + // Password screen. |
| 164 | + if (password_index > -1) { |
| 165 | + uint8_t left_password = 0x37; |
| 166 | + uint8_t top_password = 0x5C; |
| 167 | + |
| 168 | + gDPPipeSync(db->p++); |
| 169 | + gDPSetCombineMode(db->p++, G_CC_MODULATEIA_PRIM, G_CC_MODULATEIA_PRIM); |
| 170 | + gDPSetPrimColor(db->p++, 0, 0, 0xFF, 0xFF, 0xFF, 0xFF); |
| 171 | + text_print_size("Enter Password", left_password, top_password, TEXT_WIDTH); |
| 172 | + text_flush_size(db, TEXT_WIDTH, TEXT_HEIGHT, 0, 0); |
| 173 | + |
| 174 | + if (is_buffer_password_clear()) { |
| 175 | + password_index = -1; |
| 176 | + z64_PlaySFXID(NA_SE_SY_CORRECT_CHIME); |
| 177 | + // Write the password in save context. |
| 178 | + extended_sram_file_t* file = &(menu_data->sram_buffer->primary_saves[menu_data->selected_file]); |
| 179 | + extended_savecontext_static_t* extended = &(file->additional_save_data.extended); |
| 180 | + for (uint8_t i = 0 ; i < PASSWORD_LENGTH; i++) { |
| 181 | + extended->password[i] = buffer_password[i]; |
| 182 | + } |
| 183 | + Sram_WriteSave(NULL, file); |
| 184 | + return; |
| 185 | + } else { |
| 186 | + if (z64_game.common.input[0].pad_pressed.a) { |
| 187 | + if (menu_data->selected_sub_item == FS_BTN_CONFIRM_YES) { |
| 188 | + buffer_password[password_index] = 1; |
| 189 | + password_index++; |
| 190 | + } else { |
| 191 | + z64_PlaySFXID(NA_SE_SY_FSEL_CLOSE); |
| 192 | + menu_data->menu_transition++; |
| 193 | + } |
| 194 | + } |
| 195 | + if (z64_game.common.input[0].pad_pressed.cd) { |
| 196 | + buffer_password[password_index] = 2; |
| 197 | + password_index++; |
| 198 | + } |
| 199 | + if (z64_game.common.input[0].pad_pressed.cr) { |
| 200 | + buffer_password[password_index] = 3; |
| 201 | + password_index++; |
| 202 | + } |
| 203 | + if (z64_game.common.input[0].pad_pressed.cl) { |
| 204 | + buffer_password[password_index] = 4; |
| 205 | + password_index++; |
| 206 | + } |
| 207 | + if (z64_game.common.input[0].pad_pressed.cu) { |
| 208 | + buffer_password[password_index] = 5; |
| 209 | + password_index++; |
| 210 | + } |
| 211 | + if (z64_game.common.input[0].pad_pressed.b) { |
| 212 | + password_index--; |
| 213 | + if (password_index < 0) { |
| 214 | + z64_PlaySFXID(NA_SE_SY_FSEL_CLOSE); |
| 215 | + reset_buffer(); |
| 216 | + } else { |
| 217 | + buffer_password[password_index] = 0; |
| 218 | + } |
| 219 | + } |
| 220 | + if (password_index > 5 && !is_buffer_password_clear()) { |
| 221 | + tentatives++; |
| 222 | + // Penalty cooldown every 3 tries, starting from the 6th one. |
| 223 | + // File select is 60 fps, so 10sec. |
| 224 | + if (tentatives > 5 && (tentatives % 3) == 0) { |
| 225 | + cooldown = 600; |
| 226 | + } |
| 227 | + z64_PlaySFXID(NA_SE_SY_ERROR); |
| 228 | + reset_buffer(); |
| 229 | + } |
| 230 | + } |
| 231 | + // Draw the password buttons. |
| 232 | + sprite_load(db, &ocarina_button_sprite, 0, 5); |
| 233 | + for (uint8_t i = 0 ; i < password_index + 1; i++) { |
| 234 | + gDPSetPrimColor(db->p++, 0, 0, 0xF4, 0xEC, 0x30, 0xFF); // Yellow C buttons |
| 235 | + if (buffer_password[i] - 1 == 0) { // A is blue |
| 236 | + gDPSetPrimColor(db->p++, 0, 0, 0x00, 0x00, 0xFF, 0xFF); |
| 237 | + } |
| 238 | + sprite_draw(db, &ocarina_button_sprite, buffer_password[i] - 1, |
| 239 | + left_password + i * (BUTTON_WIDTH + 5), top_password + 0x0C, BUTTON_WIDTH, BUTTON_HEIGHT); |
| 240 | + } |
| 241 | + } |
| 242 | + } |
| 243 | +} |
| 244 | + |
59 | 245 | void draw_file_select_hash(uint32_t fade_out_alpha, z64_menudata_t* menu_data) { |
60 | 246 | z64_disp_buf_t* db = &(z64_ctxt.gfx->poly_opa); |
61 | 247 |
|
@@ -85,9 +271,11 @@ void draw_file_select_hash(uint32_t fade_out_alpha, z64_menudata_t* menu_data) { |
85 | 271 | } |
86 | 272 |
|
87 | 273 | draw_file_message(db, menu_data); |
88 | | - draw_file_icons(db, menu_data); |
| 274 | + if (password_index < 0) { |
| 275 | + draw_file_icons(db, menu_data); |
| 276 | + } |
89 | 277 | display_song_name_on_file_select(db); |
90 | | - |
| 278 | + manage_password(db, menu_data); |
91 | 279 | // Fade out once a file is selected |
92 | 280 |
|
93 | 281 | gDPPipeSync(db->p++); |
|
0 commit comments