1 /*
2 SDLPoP, a port/conversion of the DOS game Prince of Persia.
3 Copyright (C) 2013-2021 Dávid Nagy
4
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
9
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
14
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18 The authors of this program may be contacted at https://forum.princed.org
19 */
20
21 #include "common.h"
22 #include <time.h>
23
24 #ifdef USE_REPLAY
25
26 const char replay_magic_number[3] = "P1R";
27 const word replay_format_class = 0; // unique number associated with this SDLPoP implementation / fork
28 const char* implementation_name = "SDLPoP v" SDLPOP_VERSION;
29
30 #define REPLAY_FORMAT_CURR_VERSION 102 // current version number of the replay format
31 #define REPLAY_FORMAT_MIN_VERSION 101 // SDLPoP will open replays with this version number and higher
32 #define REPLAY_FORMAT_DEPRECATION_NUMBER 2 // SDLPoP won't open replays with a higher deprecation number
33 // If deprecation_number >= 2: Waste an RNG cycle in loose_shake() to match DOS PoP.
34
35 #define MAX_REPLAY_DURATION 345600 // 8 hours: 720 * 60 * 8 ticks
36 byte moves[MAX_REPLAY_DURATION] = {0}; // static memory for now because it is easier (should this be dynamic?)
37
38 char replay_levelset_name[POP_MAX_PATH];
39 char stored_levelset_name[POP_MAX_PATH];
40
41 // 1-byte structure representing which controls were active at a particular game tick
42 typedef union replay_move_type {
43 struct {
44 sbyte x : 2;
45 sbyte y : 2;
46 byte shift : 1;
47 byte special : 3; // enum replay_special_moves, see types.h
48 };
49 byte bits;
50 } replay_move_type;
51
52 //dword curr_tick = 0;
53
54 FILE* replay_fp = NULL;
55 byte replay_file_open = 0;
56 int current_replay_number = 0;
57 int next_replay_number = 0;
58
59 byte* savestate_buffer = NULL;
60 dword savestate_offset = 0;
61 dword savestate_size = 0;
62 #define MAX_SAVESTATE_SIZE 4096
63
64 // These are defined in seg000.c:
65 typedef int process_func_type(void* data, size_t data_size);
66 extern int quick_process(process_func_type process_func);
67 extern const char quick_version[9];
68
69 // header information read from the first part of a replay file
70 typedef struct replay_header_type {
71 byte uses_custom_levelset;
72 char levelset_name[POP_MAX_PATH];
73 char implementation_name[POP_MAX_PATH];
74 } replay_header_type;
75
76 // information needed to keep track of all listed replay files, and to sort them by their creation date
77 typedef struct replay_info_type {
78 char filename[POP_MAX_PATH];
79 time_t creation_time;
80 replay_header_type header;
81 } replay_info_type;
82
83 #define REPLAY_HEADER_ERROR_MESSAGE_MAX 512
84
85 #define fread_check(dst, size, elements, fp) do { \
86 size_t __count; \
87 __count = fread(dst, size, elements, fp); \
88 if (__count != (elements)) { \
89 if (error_message != NULL) { \
90 snprintf_check(error_message, REPLAY_HEADER_ERROR_MESSAGE_MAX,\
91 #dst " missing -- not a valid replay file!");\
92 } \
93 return 0; /* incompatible file */ \
94 } \
95 } while (0)
96
read_replay_header(replay_header_type * header,FILE * fp,char * error_message)97 int read_replay_header(replay_header_type* header, FILE* fp, char* error_message) {
98 // Explicitly go to the beginning, because the current filepos might be nonzero.
99 fseek(fp, 0, SEEK_SET);
100 // read the magic number
101 char magic[3] = "";
102 fread_check(magic, 3, 1, fp);
103 if (strncmp(magic, replay_magic_number, 3) != 0) {
104 if (error_message != NULL) {
105 snprintf_check(error_message, REPLAY_HEADER_ERROR_MESSAGE_MAX, "not a valid replay file!");
106 }
107 return 0; // incompatible, magic number not correct!
108 }
109 // read the unique number associated with this SDLPoP implementation / fork (for normal SDLPoP: 0)
110 word class;
111 fread_check(&class, sizeof(class), 1, fp);
112 // read the format version number
113 byte version_number = (byte) fgetc(fp);
114 // read the format deprecation number
115 byte deprecation_number = (byte) fgetc(fp);
116
117 // creation time (seconds since 1970) is embedded in the format, but not used in SDLPoP right now
118 fseek(fp, sizeof(Sint64), SEEK_CUR);
119
120 // read the levelset_name
121 byte len_read = (byte) fgetc(fp);
122 header->uses_custom_levelset = (len_read != 0);
123 fread_check(header->levelset_name, sizeof(char), len_read, fp);
124 header->levelset_name[len_read] = '\0';
125
126 // read the implementation_name
127 len_read = (byte) fgetc(fp);
128 fread_check(header->implementation_name, sizeof(char), len_read, fp);
129 header->implementation_name[len_read] = '\0';
130
131 if (class != replay_format_class) {
132 // incompatible, replay format is associated with a different implementation of SDLPoP
133 if (error_message != NULL) {
134 snprintf_check(error_message, REPLAY_HEADER_ERROR_MESSAGE_MAX,
135 "replay created with \"%s\"...\nIncompatible replay class identifier! (expected %d, found %d)",
136 header->implementation_name, replay_format_class, class);
137 }
138 return 0;
139 }
140
141 if (version_number < REPLAY_FORMAT_MIN_VERSION) {
142 // incompatible, replay format is too old
143 if (error_message != NULL) {
144 snprintf_check(error_message, REPLAY_HEADER_ERROR_MESSAGE_MAX,
145 "replay created with \"%s\"...\nReplay format version too old! (minimum %d, found %d)",
146 header->implementation_name, REPLAY_FORMAT_MIN_VERSION, version_number);
147 }
148 return 0;
149 }
150
151 if (deprecation_number > REPLAY_FORMAT_DEPRECATION_NUMBER) {
152 // incompatible, replay format is too new
153 if (error_message != NULL) {
154 snprintf_check(error_message, REPLAY_HEADER_ERROR_MESSAGE_MAX,
155 "replay created with \"%s\"...\nReplay deprecation number too new! (max %d, found %d)",
156 header->implementation_name, REPLAY_FORMAT_DEPRECATION_NUMBER, deprecation_number);
157 }
158 return 0;
159 }
160
161 g_deprecation_number = deprecation_number;
162
163 if (is_validate_mode) {
164 static byte is_replay_info_printed = 0;
165 if (!is_replay_info_printed) {
166 printf("\nReplay created with %s.\n", header->implementation_name);
167 printf("Format: class identifier %d, version number %d, deprecation number %d.\n",
168 class, version_number, deprecation_number);
169 if (header->levelset_name[0] == '\0') {
170 printf("Levelset: original Prince of Persia.\n");
171 } else {
172 printf("Levelset: %s.\n", header->levelset_name);
173 }
174 putchar('\n');
175 is_replay_info_printed = 1; // do this only once
176 }
177 }
178
179 return 1;
180 }
181
182 int num_replay_files = 0; // number of listed replays
183 int max_replay_files = 128; // initially, may grow if there are > 128 replay files found
184 replay_info_type* replay_list = NULL;
185
186 // Compare function -- for qsort() in list_replay_files() below
187 // Compares creation dates of replays, so they can be loaded in reverse creation order (newest first)
compare_replay_creation_time(const void * a,const void * b)188 static int compare_replay_creation_time(const void* a, const void* b) {
189 return (int) difftime( ((replay_info_type*)b)->creation_time, ((replay_info_type*)a)->creation_time );
190 }
191
list_replay_files()192 void list_replay_files() {
193
194 if (replay_list == NULL) {
195 // need to allocate enough memory to store info about all replay files in the directory
196 replay_list = malloc( max_replay_files * sizeof( replay_info_type ) ); // will realloc() later if > 256 files exist
197 }
198
199 num_replay_files = 0;
200
201 directory_listing_type* directory_listing = create_directory_listing_and_find_first_file(replays_folder, "p1r");
202 if (directory_listing == NULL) {
203 return;
204 }
205
206 do {
207 ++num_replay_files;
208 if (num_replay_files > max_replay_files) {
209 // too many files, expand the memory available for replay_list
210 max_replay_files += 128;
211 replay_list = realloc( replay_list, max_replay_files * sizeof( replay_info_type ) );
212 }
213 replay_info_type* replay_info = &replay_list[num_replay_files - 1]; // current replay file
214 memset( replay_info, 0, sizeof( replay_info_type ) );
215 // store the filename of the replay
216 snprintf_check(replay_info->filename, POP_MAX_PATH, "%s/%s", replays_folder,
217 get_current_filename_from_directory_listing(directory_listing) );
218
219 // get the creation time
220 struct stat st;
221 if (stat( replay_info->filename, &st ) == 0) {
222 replay_info->creation_time = st.st_ctime;
223 }
224 // read and store the levelset name associated with the replay
225 FILE* fp = fopen( replay_info->filename, "rb" );
226 int ok = 0;
227 if (fp != NULL) {
228 ok = read_replay_header( &replay_info->header, fp, NULL );
229 fclose( fp );
230 }
231 if (!ok) --num_replay_files; // scrap the file if it is not compatible
232
233 } while (find_next_file(directory_listing));
234
235 close_directory_listing(directory_listing);
236
237 if (num_replay_files > 1) {
238 // sort listed replays by their creation date
239 qsort( replay_list, (size_t) num_replay_files, sizeof( replay_info_type ), compare_replay_creation_time );
240 }
241 };
242
open_replay_file(const char * filename)243 byte open_replay_file(const char *filename) {
244 printf("Opening replay file: %s\n", filename);
245 if (replay_file_open) fclose(replay_fp);
246 replay_fp = fopen(filename, "rb");
247 if (replay_fp != NULL) {
248 replay_file_open = 1;
249 return 1;
250 }
251 else {
252 replay_file_open = 0;
253 return 0;
254 }
255 }
256
change_working_dir_to_sdlpop_root()257 void change_working_dir_to_sdlpop_root() {
258 char* exe_path = g_argv[0];
259 // strip away everything after the last slash or backslash in the path
260 int len;
261 for (len = strlen(exe_path); len > 0; --len) {
262 if (exe_path[len] == '\\' || exe_path[len] == '/') {
263 break;
264 }
265 }
266 if (len > 0) {
267 char exe_dir[POP_MAX_PATH];
268 strncpy(exe_dir, exe_path, len);
269 exe_dir[len] = '\0';
270
271 int result = chdir(exe_dir);
272 if (result != 0) {
273 perror("Can't change into SDLPoP directory");
274 }
275 }
276
277 };
278
279 // Called in pop_main(); check whether a replay file is being opened directly (double-clicked, dragged onto .exe, etc.)
start_with_replay_file(const char * filename)280 void start_with_replay_file(const char *filename) {
281 if (open_replay_file(filename)) {
282 change_working_dir_to_sdlpop_root();
283 current_replay_number = -1; // don't cycle when pressing Tab
284 // We should read the header in advance so we know the levelset name
285 // then the game can immediately load the correct resources
286 replay_header_type header = {0};
287 char header_error_message[REPLAY_HEADER_ERROR_MESSAGE_MAX];
288 int ok = read_replay_header(&header, replay_fp, header_error_message);
289 if (!ok) {
290 char error_message[REPLAY_HEADER_ERROR_MESSAGE_MAX];
291 snprintf_check(error_message, REPLAY_HEADER_ERROR_MESSAGE_MAX,
292 "Error opening replay file: %s\n",
293 header_error_message);
294 fprintf(stderr, "%s", error_message);
295 fclose(replay_fp);
296 replay_fp = NULL;
297 replay_file_open = 0;
298
299 if (is_validate_mode) // Validating replays is cmd-line only, so, no sense continuing from here.
300 exit(0);
301
302 SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_ERROR, "SDLPoP", error_message, NULL);
303 return;
304 }
305 if (header.uses_custom_levelset) {
306 strncpy(replay_levelset_name, header.levelset_name, sizeof(replay_levelset_name)); // use the replays's levelset
307 }
308 rewind(replay_fp); // replay file is still open and will be read in load_replay() later
309 need_start_replay = 1; // will later call start_replay(), from init_record_replay()
310 }
311 }
312
313 // The functions options_process_* below each process (read/write) a section of options variables (using SDL_RWops)
314 // This is I/O for the *binary* representation of the relevant options - this gets saved as part of a replay.
315
316 typedef int rw_process_func_type(SDL_RWops* rw, void* data, size_t data_size);
317 typedef void process_options_section_func_type(SDL_RWops* rw, rw_process_func_type process_func);
318
319 #define process(x) if (!process_func(rw, &(x), sizeof(x))) return
320
options_process_features(SDL_RWops * rw,rw_process_func_type process_func)321 void options_process_features(SDL_RWops* rw, rw_process_func_type process_func) {
322 process(enable_copyprot);
323 process(enable_quicksave);
324 process(enable_quicksave_penalty);
325 }
326
327 fixes_options_type fixes_options_replay;
328
options_process_enhancements(SDL_RWops * rw,rw_process_func_type process_func)329 void options_process_enhancements(SDL_RWops* rw, rw_process_func_type process_func) {
330 process(use_fixes_and_enhancements);
331 process(fixes_options_replay.enable_crouch_after_climbing);
332 process(fixes_options_replay.enable_freeze_time_during_end_music);
333 process(fixes_options_replay.enable_remember_guard_hp);
334 }
335
options_process_fixes(SDL_RWops * rw,rw_process_func_type process_func)336 void options_process_fixes(SDL_RWops* rw, rw_process_func_type process_func) {
337 process(fixes_options_replay.fix_gate_sounds);
338 process(fixes_options_replay.fix_two_coll_bug);
339 process(fixes_options_replay.fix_infinite_down_bug);
340 process(fixes_options_replay.fix_gate_drawing_bug);
341 process(fixes_options_replay.fix_bigpillar_climb);
342 process(fixes_options_replay.fix_jump_distance_at_edge);
343 process(fixes_options_replay.fix_edge_distance_check_when_climbing);
344 process(fixes_options_replay.fix_painless_fall_on_guard);
345 process(fixes_options_replay.fix_wall_bump_triggers_tile_below);
346 process(fixes_options_replay.fix_stand_on_thin_air);
347 process(fixes_options_replay.fix_press_through_closed_gates);
348 process(fixes_options_replay.fix_grab_falling_speed);
349 process(fixes_options_replay.fix_skeleton_chomper_blood);
350 process(fixes_options_replay.fix_move_after_drink);
351 process(fixes_options_replay.fix_loose_left_of_potion);
352 process(fixes_options_replay.fix_guard_following_through_closed_gates);
353 process(fixes_options_replay.fix_safe_landing_on_spikes);
354 process(fixes_options_replay.fix_glide_through_wall);
355 process(fixes_options_replay.fix_drop_through_tapestry);
356 process(fixes_options_replay.fix_land_against_gate_or_tapestry);
357 process(fixes_options_replay.fix_unintended_sword_strike);
358 process(fixes_options_replay.fix_retreat_without_leaving_room);
359 process(fixes_options_replay.fix_running_jump_through_tapestry);
360 process(fixes_options_replay.fix_push_guard_into_wall);
361 process(fixes_options_replay.fix_jump_through_wall_above_gate);
362 process(fixes_options_replay.fix_chompers_not_starting);
363 process(fixes_options_replay.fix_feather_interrupted_by_leveldoor);
364 process(fixes_options_replay.fix_offscreen_guards_disappearing);
365 process(fixes_options_replay.fix_move_after_sheathe);
366 process(fixes_options_replay.fix_hidden_floors_during_flashing);
367 process(fixes_options_replay.fix_hang_on_teleport);
368 process(fixes_options_replay.fix_exit_door);
369 process(fixes_options_replay.fix_quicksave_during_feather);
370 process(fixes_options_replay.fix_caped_prince_sliding_through_gate);
371 process(fixes_options_replay.fix_doortop_disabling_guard);
372 }
373
options_process_custom_general(SDL_RWops * rw,rw_process_func_type process_func)374 void options_process_custom_general(SDL_RWops* rw, rw_process_func_type process_func) {
375 process(custom->start_minutes_left);
376 process(custom->start_ticks_left);
377 process(custom->start_hitp);
378 process(custom->max_hitp_allowed);
379 process(custom->saving_allowed_first_level);
380 process(custom->saving_allowed_last_level);
381 process(custom->start_upside_down);
382 process(custom->start_in_blind_mode);
383 process(custom->copyprot_level);
384 process(custom->drawn_tile_top_level_edge);
385 process(custom->drawn_tile_left_level_edge);
386 process(custom->level_edge_hit_tile);
387 process(custom->allow_triggering_any_tile);
388 process(custom->enable_wda_in_palace);
389 process(custom->vga_palette);
390 process(custom->first_level);
391 process(custom->skip_title);
392 process(custom->shift_L_allowed_until_level);
393 process(custom->shift_L_reduced_minutes);
394 process(custom->shift_L_reduced_ticks);
395 process(custom->demo_hitp);
396 process(custom->demo_end_room);
397 process(custom->intro_music_level);
398 process(custom->checkpoint_level);
399 process(custom->checkpoint_respawn_dir);
400 process(custom->checkpoint_respawn_room);
401 process(custom->checkpoint_respawn_tilepos);
402 process(custom->checkpoint_clear_tile_room);
403 process(custom->checkpoint_clear_tile_col);
404 process(custom->checkpoint_clear_tile_row);
405 process(custom->skeleton_level);
406 process(custom->skeleton_room);
407 process(custom->skeleton_trigger_column_1);
408 process(custom->skeleton_trigger_column_2);
409 process(custom->skeleton_column);
410 process(custom->skeleton_row);
411 process(custom->skeleton_require_open_level_door);
412 process(custom->skeleton_skill);
413 process(custom->skeleton_reappear_room);
414 process(custom->skeleton_reappear_x);
415 process(custom->skeleton_reappear_row);
416 process(custom->skeleton_reappear_dir);
417 process(custom->mirror_level);
418 process(custom->mirror_room);
419 process(custom->mirror_column);
420 process(custom->mirror_row);
421 process(custom->mirror_tile);
422 process(custom->show_mirror_image);
423 process(custom->falling_exit_level);
424 process(custom->falling_exit_room);
425 process(custom->falling_entry_level);
426 process(custom->falling_entry_room);
427 process(custom->mouse_level);
428 process(custom->mouse_room);
429 process(custom->mouse_delay);
430 process(custom->mouse_object);
431 process(custom->mouse_start_x);
432 process(custom->loose_tiles_level);
433 process(custom->loose_tiles_room_1);
434 process(custom->loose_tiles_room_2);
435 process(custom->loose_tiles_first_tile);
436 process(custom->loose_tiles_last_tile);
437 process(custom->jaffar_victory_level);
438 process(custom->jaffar_victory_flash_time);
439 process(custom->hide_level_number_from_level);
440 process(custom->level_13_level_number);
441 process(custom->victory_stops_time_level);
442 process(custom->win_level);
443 process(custom->win_room);
444 process(custom->loose_floor_delay);
445 }
446
options_process_custom_per_level(SDL_RWops * rw,rw_process_func_type process_func)447 void options_process_custom_per_level(SDL_RWops* rw, rw_process_func_type process_func) {
448 process(custom->tbl_level_type);
449 process(custom->tbl_level_color);
450 process(custom->tbl_guard_type);
451 process(custom->tbl_guard_hp);
452 process(custom->tbl_cutscenes_by_index);
453 process(custom->tbl_entry_pose);
454 process(custom->tbl_seamless_exit);
455 }
456
457 #undef process
458
459 // struct for keeping track of both the normal and the replay options (which we want to easily switch between)
460 // (separately for each 'section', so adding future options becomes easy without messing up the format!)
461 typedef struct replay_options_section_type {
462 dword data_size;
463 byte replay_data[POP_MAX_OPTIONS_SIZE]; // binary representation of the options that are active during the replay
464 byte stored_data[POP_MAX_OPTIONS_SIZE]; // normal options are restored from this, after the replay is finished
465 process_options_section_func_type* section_func;
466 } replay_options_section_type;
467
468 replay_options_section_type replay_options_sections[] = {
469 {.section_func = options_process_features},
470 {.section_func = options_process_enhancements},
471 {.section_func = options_process_fixes},
472 {.section_func = options_process_custom_general},
473 {.section_func = options_process_custom_per_level},
474 };
475
476 // output the current options to a memory buffer (e.g. to remember them before a replay is loaded)
save_options_to_buffer(void * options_buffer,size_t max_size,process_options_section_func_type * process_section_func)477 size_t save_options_to_buffer(void* options_buffer, size_t max_size, process_options_section_func_type* process_section_func) {
478 SDL_RWops* rw = SDL_RWFromMem(options_buffer, max_size);
479 process_section_func(rw, process_rw_write);
480 Sint64 section_size = SDL_RWtell(rw);
481 if (section_size < 0) section_size = 0;
482 SDL_RWclose(rw);
483 return (size_t) section_size;
484 }
485
486 // restore the options from a memory buffer (e.g. reapply the original options after a replay is finished)
load_options_from_buffer(void * options_buffer,size_t options_size,process_options_section_func_type * process_section_func)487 void load_options_from_buffer(void* options_buffer, size_t options_size, process_options_section_func_type* process_section_func) {
488 SDL_RWops* rw = SDL_RWFromMem(options_buffer, options_size);
489 process_section_func(rw, process_rw_read);
490 SDL_RWclose(rw);
491 }
492
493
494
init_record_replay()495 void init_record_replay() {
496 if (!enable_replay) return;
497 if (check_param("record")) {
498 start_recording();
499 }
500 else if (need_start_replay || check_param("replay")) {
501 start_replay();
502 }
503 }
504
replay_restore_level()505 void replay_restore_level() {
506 // Need to restore the savestate at the right time (just before the first room of the level is drawn).
507 // Otherwise, for "on-the-fly" recordings, the screen will visibly "jump" to the replay savestate.
508 // This only needs to happen at the very beginning of the replay (curr_tick == 0)
509 if (curr_tick == 0) restore_savestate_from_buffer();
510 }
511
process_to_buffer(void * data,size_t data_size)512 int process_to_buffer(void* data, size_t data_size) {
513 if (savestate_offset + data_size > MAX_SAVESTATE_SIZE) {
514 printf("Saving savestate to memory failed: buffer is overflowing!\n");
515 return 0;
516 }
517 memcpy(savestate_buffer + savestate_offset, data, data_size);
518 savestate_offset += data_size;
519 return 1;
520 }
521
process_load_from_buffer(void * data,size_t data_size)522 int process_load_from_buffer(void* data, size_t data_size) {
523 // Prevent torches from being randomly colored when an older replay is loaded.
524 if (savestate_offset >= savestate_size) return 0;
525
526 memcpy(data, savestate_buffer + savestate_offset, data_size);
527 savestate_offset += data_size;
528 return 1;
529 }
530
savestate_to_buffer()531 int savestate_to_buffer() {
532 int ok = 0;
533 if (savestate_buffer == NULL)
534 savestate_buffer = malloc(MAX_SAVESTATE_SIZE);
535 if (savestate_buffer != NULL) {
536 savestate_offset = 0;
537 savestate_size = 0;
538 ok = quick_process(process_to_buffer);
539 savestate_size = savestate_offset;
540 }
541 return ok;
542 }
543
reload_resources()544 void reload_resources() {
545 // the replay's levelset might use different sounds, so we need to free and reload sounds
546 free_all_sounds();
547 load_all_sounds();
548 free_all_chtabs_from(id_chtab_0_sword);
549 // chtabs 3 and higher will be freed/reloaded in load_lev_spr() (called by restore_room_after_quick_load())
550 // However, chtabs 0-2 are usually not freed at all (they are loaded only once, in init_game_main())
551 // So we should reload them manually (PRINCE.DAT and KID.DAT may still have been modified after all!)
552 dat_type* dat = open_dat("PRINCE.DAT", 0);
553 // PRINCE.DAT: sword
554 chtab_addrs[id_chtab_0_sword] = load_sprites_from_file(700, 1<<2, 1);
555 // PRINCE.DAT: flame, sword on floor, potion
556 chtab_addrs[id_chtab_1_flameswordpotion] = load_sprites_from_file(150, 1<<3, 1);
557 close_dat(dat);
558 load_kid_sprite(); // reloads chtab 2
559 }
560
restore_savestate_from_buffer()561 int restore_savestate_from_buffer() {
562 int ok = 0;
563 savestate_offset = 0;
564 // This condition should be checked in process_load_from_buffer() instead of here.
565 while (savestate_offset < savestate_size) {
566 ok = quick_process(process_load_from_buffer);
567 }
568 reload_resources();
569 restore_room_after_quick_load();
570 return ok;
571 }
572
start_recording()573 void start_recording() {
574 curr_tick = 0;
575 recording = 1; // further set-up is done in add_replay_move, on the first gameplay tick
576 }
577
add_replay_move()578 void add_replay_move() {
579 if (curr_tick == 0) {
580 prandom(1); // make sure random_seed is initialized
581 saved_random_seed = random_seed;
582 seed_was_init = 1;
583 savestate_to_buffer(); // create a savestate in memory
584 display_text_bottom("RECORDING");
585 text_time_total = 24;
586 text_time_remaining = 24;
587 }
588
589 replay_move_type curr_move = {{0}};
590 curr_move.x = control_x;
591 curr_move.y = control_y;
592 if (control_shift) curr_move.shift = 1;
593
594 if (special_move) {
595 curr_move.special = special_move;
596 special_move = 0;
597 }
598
599 moves[curr_tick] = curr_move.bits;
600
601 ++curr_tick;
602
603 if (curr_tick >= MAX_REPLAY_DURATION) { // max replay length exceeded
604 stop_recording();
605 }
606 }
607
stop_recording()608 void stop_recording() {
609 recording = 0;
610 if (save_recorded_replay_dialog()) {
611 display_text_bottom("REPLAY SAVED");
612 } else {
613 display_text_bottom("REPLAY CANCELED");
614 }
615 text_time_total = 24;
616 text_time_remaining = 24;
617 }
618
apply_replay_options()619 void apply_replay_options() {
620 // store the current options, so they can be restored later
621 for (int i = 0; i < COUNT(replay_options_sections); ++i) {
622 save_options_to_buffer(replay_options_sections[i].stored_data, POP_MAX_OPTIONS_SIZE, replay_options_sections[i].section_func);
623 }
624
625 // apply the options from the memory buffer (max. replay_options_size bytes will be read)
626 for (int i = 0; i < COUNT(replay_options_sections); ++i) {
627 load_options_from_buffer(replay_options_sections[i].replay_data, replay_options_sections[i].data_size, replay_options_sections[i].section_func);
628 }
629
630 fixes_saved = fixes_options_replay;
631 turn_fixes_and_enhancements_on_off(use_fixes_and_enhancements);
632 enable_replay = 1; // just to be safe...
633
634 memcpy(stored_levelset_name, levelset_name, sizeof(levelset_name));
635 memcpy(levelset_name, replay_levelset_name, sizeof(levelset_name));
636 use_custom_levelset = (levelset_name[0] == '\0') ? 0 : 1;
637
638 load_mod_options(); // Load resources from the correct places if there is a mod name in the replay file. This also prevents unwanted switching to PC Speaker mode.
639 reload_resources();
640 }
641
restore_normal_options()642 void restore_normal_options() {
643 // apply the stored options
644 for (int i = 0; i < COUNT(replay_options_sections); ++i) {
645 load_options_from_buffer(replay_options_sections[i].stored_data, POP_MAX_OPTIONS_SIZE, replay_options_sections[i].section_func);
646 }
647
648 start_level = -1; // may have been set to a different value by the replay
649
650 memcpy(levelset_name, stored_levelset_name, sizeof(levelset_name));
651 use_custom_levelset = (levelset_name[0] == '\0') ? 0 : 1;
652 }
653
print_remaining_time()654 static void print_remaining_time() {
655 if (rem_min > 0) {
656 printf("Remaining time: %d min, %d sec, %d ticks. ",
657 rem_min - 1, rem_tick / 12, rem_tick % 12);
658 } else {
659 printf("Elapsed time: %d min, %d sec, %d ticks. ",
660 -(rem_min + 1), (719 - rem_tick) / 12, (719 - rem_tick) % 12);
661 }
662 printf("(rem_min=%d, rem_tick=%d)\n", rem_min, rem_tick);
663 }
664
start_replay()665 void start_replay() {
666 stop_sounds(); // Don't crash if the intro music is interrupted by Tab in PC Speaker mode.
667 if (!enable_replay) return;
668 need_start_replay = 0;
669 if (!is_validate_mode) {
670 list_replay_files();
671 // If the replay was started from a file given in the command line, we don't care if there are no replay files in the replay folder.
672 //if (num_replay_files == 0) return;
673 }
674 if (!load_replay()) return;
675 // Set replaying before applying options, so the latter can display an appropriate error message if the referenced mod is missing.
676 replaying = 1;
677 apply_replay_options();
678 curr_tick = 0;
679 }
680
end_replay()681 void end_replay() {
682 if (!is_validate_mode) {
683 replaying = 0;
684 skipping_replay = 0;
685 restore_normal_options();
686 start_game();
687 } else {
688 printf("\nReplay ended in level %d, room %d.\n", current_level, drawn_room);
689
690 if (Kid.alive < 0)
691 printf("Kid is alive.\n");
692 else {
693 if (text_time_total == 288 && text_time_remaining <= 1) {
694 printf("Kid is dead. (Did not press button to continue.)\n");
695 } else {
696 printf("Kid is dead.\n");
697 }
698 }
699
700 print_remaining_time();
701
702 int minute_ticks = curr_tick % 720;
703 printf("Play duration: %d min, %d sec, %d ticks. (curr_tick=%d)\n\n",
704 curr_tick / 720, minute_ticks / 12, minute_ticks % 12, curr_tick);
705
706 if (num_replay_ticks != curr_tick) {
707 printf("WARNING: Play duration does not match replay length. (%d ticks)\n", num_replay_ticks);
708 } else {
709 printf("Play duration matches replay length. (%d ticks)\n", num_replay_ticks);
710 }
711 exit(0);
712 }
713 }
714
do_replay_move()715 void do_replay_move() {
716 if (curr_tick == 0) {
717 random_seed = saved_random_seed;
718 seed_was_init = 1;
719
720 if (is_validate_mode) {
721 printf("Replay started in level %d, room %d.\n", current_level, drawn_room);
722 print_remaining_time();
723 skipping_replay = 1;
724 replay_seek_target = replay_seek_2_end;
725 }
726 }
727 if (curr_tick == num_replay_ticks) { // replay is finished
728 end_replay();
729 return;
730 }
731 if (current_level == next_level) {
732 replay_move_type curr_move;
733 curr_move.bits = moves[curr_tick];
734
735 control_x = curr_move.x;
736 control_y = curr_move.y;
737
738 // Ignore Shift if the kid is dead: restart moves are hard-coded as a 'special move'.
739 if (rem_min != 0 && Kid.alive > 6)
740 control_shift = 0;
741 else
742 control_shift = (curr_move.shift) ? -1 : 0;
743
744 if (curr_move.special == MOVE_RESTART_LEVEL) { // restart level
745 stop_sounds();
746 is_restart_level = 1;
747 } else if (curr_move.special == MOVE_EFFECT_END) {
748 stop_sounds();
749 if (need_level1_music == 2) need_level1_music = 0;
750 is_feather_fall = 0;
751 }
752
753 // if (curr_tick > 5 ) printf("rem_tick: %d\t curr_tick: %d\tlast 5 moves: %d, %d, %d, %d, %d\n", rem_tick, curr_tick,
754 // moves[curr_tick-4], moves[curr_tick-3], moves[curr_tick-2], moves[curr_tick-1], moves[curr_tick]);
755 ++curr_tick;
756 }
757 }
758
save_recorded_replay_dialog()759 int save_recorded_replay_dialog() {
760 // prompt for replay filename
761 rect_type rect;
762 short bgcolor = color_8_darkgray;
763 short color = color_15_brightwhite;
764 current_target_surface = onscreen_surface_;
765 method_1_blit_rect(offscreen_surface, onscreen_surface_, ©prot_dialog->peel_rect, ©prot_dialog->peel_rect, 0);
766 draw_dialog_frame(copyprot_dialog);
767 shrink2_rect(&rect, ©prot_dialog->text_rect, 2, 1);
768 show_text_with_color(&rect, 0, 0, "Save replay\nenter the filename...\n\n", color_15_brightwhite);
769 clear_kbd_buf();
770
771 rect_type text_rect;
772 rect_type input_rect = {104, 64, 118, 256};
773 offset4_rect_add(&text_rect, &input_rect, -2, 0, 2, 0);
774 //peel_type* peel = read_peel_from_screen(&input_rect);
775 draw_rect(&text_rect, bgcolor);
776 current_target_surface = onscreen_surface_;
777 need_full_redraw = 1; // lazy: instead of neatly restoring the dialog peel, just redraw the whole screen
778
779 char input_filename[POP_MAX_PATH] = "";
780 int input_length;
781 do {
782 input_length = input_str(&input_rect, input_filename, 64, "", 0, 0, color, bgcolor);
783 } while (input_length == 0); // filename must be at least 1 character
784
785 if (input_length < 0) {
786 return 0; // Escape was pressed -> discard the replay
787 }
788
789 char full_filename[POP_MAX_PATH] = "";
790 snprintf_check(full_filename, sizeof(full_filename), "%s/%s.p1r", replays_folder, input_filename);
791
792 // create the "replays" folder if it does not exist already
793 #if defined WIN32 || _WIN32 || WIN64 || _WIN64
794 mkdir (replays_folder);
795 #else
796 mkdir (replays_folder, 0700);
797 #endif
798
799 // NOTE: We currently overwrite the replay file if it exists already. Maybe warn / ask for confirmation??
800
801 return save_recorded_replay(full_filename);
802 }
803
save_recorded_replay(const char * full_filename)804 int save_recorded_replay(const char* full_filename)
805 {
806 replay_fp = fopen(full_filename, "wb");
807 if (replay_fp != NULL) {
808 fwrite(replay_magic_number, COUNT(replay_magic_number), 1, replay_fp); // magic number "P1R"
809 fwrite(&replay_format_class, sizeof(replay_format_class), 1, replay_fp);
810 putc(REPLAY_FORMAT_CURR_VERSION, replay_fp);
811 putc(REPLAY_FORMAT_DEPRECATION_NUMBER, replay_fp);
812 Sint64 seconds = time(NULL);
813 fwrite(&seconds, sizeof(seconds), 1, replay_fp);
814 // levelset_name
815 putc(strnlen(levelset_name, UINT8_MAX), replay_fp); // length of the levelset name (is zero for original levels)
816 fputs(levelset_name, replay_fp);
817 // implementation name
818 putc(strnlen(implementation_name, UINT8_MAX), replay_fp);
819 fputs(implementation_name, replay_fp);
820 // embed a savestate into the replay
821 fwrite(&savestate_size, sizeof(savestate_size), 1, replay_fp);
822 fwrite(savestate_buffer, savestate_size, 1, replay_fp);
823
824 // save the options, organized per section
825 byte temp_options[POP_MAX_OPTIONS_SIZE];
826 for (int i = 0; i < COUNT(replay_options_sections); ++i) {
827 dword section_size = save_options_to_buffer(temp_options, sizeof(temp_options), replay_options_sections[i].section_func);
828 fwrite(§ion_size, sizeof(section_size), 1, replay_fp);
829 fwrite(temp_options, section_size, 1, replay_fp);
830 }
831
832 // save the rest of the replay data
833 fwrite(&start_level, sizeof(start_level), 1, replay_fp);
834 fwrite(&saved_random_seed, sizeof(saved_random_seed), 1, replay_fp);
835 num_replay_ticks = curr_tick;
836 fwrite(&num_replay_ticks, sizeof(num_replay_ticks), 1, replay_fp);
837 fwrite(moves, num_replay_ticks, 1, replay_fp);
838 fclose(replay_fp);
839 replay_fp = NULL;
840 }
841
842 return 1;
843 }
844
open_next_replay_file()845 byte open_next_replay_file() {
846 if (next_replay_number > num_replay_files-1) {
847 return 0; // reached the last replay file, return to title screen
848 }
849 current_replay_number = next_replay_number;
850 ++next_replay_number; // cycle
851 open_replay_file(replay_list[current_replay_number].filename);
852 if (replay_file_open) {
853 return 1;
854 }
855 return 0;
856 }
857
replay_cycle()858 void replay_cycle() {
859 need_replay_cycle = 0;
860 skipping_replay = 0;
861 stop_sounds();
862 if (current_replay_number == -1 /* opened .P1R file directly, so cycling is disabled */ ||
863 !open_next_replay_file() ||
864 !load_replay()
865 ) {
866 // there is no replay to be cycled to after the current one --> restart the game
867 replaying = 0;
868 restore_normal_options();
869 start_game();
870 return;
871 }
872 apply_replay_options();
873 restore_savestate_from_buffer();
874 curr_tick = 0; // Do this after restoring the savestate, in case the savestate contained a non-zero curr_tick.
875 show_level();
876 }
877
load_replay()878 int load_replay() {
879 if (!replay_file_open) {
880 next_replay_number = 0;
881 if (!open_next_replay_file()) {
882 return 0;
883 }
884 }
885 if (savestate_buffer == NULL)
886 savestate_buffer = malloc(MAX_SAVESTATE_SIZE);
887 if (replay_fp != NULL && savestate_buffer != NULL) {
888 replay_header_type header = {0};
889 char error_message[REPLAY_HEADER_ERROR_MESSAGE_MAX];
890 int ok = read_replay_header(&header, replay_fp, error_message);
891 if (!ok) {
892 printf("Error loading replay: %s!\n", error_message);
893 fclose(replay_fp);
894 replay_fp = NULL;
895 replay_file_open = 0;
896 return 0;
897 }
898
899 memcpy(replay_levelset_name, header.levelset_name, sizeof(header.levelset_name));
900
901 // load the savestate
902 fread_check(&savestate_size, sizeof(savestate_size), 1, replay_fp);
903 fread_check(savestate_buffer, savestate_size, 1, replay_fp);
904
905 // load the replay options, organized per section
906 for (int i = 0; i < COUNT(replay_options_sections); ++i) {
907 dword section_size = 0;
908 fread_check(§ion_size, sizeof(section_size), 1, replay_fp);
909 fread_check(replay_options_sections[i].replay_data, section_size, 1, replay_fp);
910 replay_options_sections[i].data_size = section_size;
911 }
912
913 // load the rest of the replay data
914 fread_check(&start_level, sizeof(start_level), 1, replay_fp);
915 fread_check(&saved_random_seed, sizeof(saved_random_seed), 1, replay_fp);
916 fread_check(&num_replay_ticks, sizeof(num_replay_ticks), 1, replay_fp);
917 fread_check(moves, num_replay_ticks, 1, replay_fp);
918 fclose(replay_fp);
919 replay_fp = NULL;
920 replay_file_open = 0;
921 return 1; // success
922 }
923 return 0;
924 }
925
key_press_while_recording(int * key_ptr)926 void key_press_while_recording(int* key_ptr) {
927 int key = *key_ptr;
928 switch(key) {
929 case SDL_SCANCODE_A | WITH_CTRL:
930 special_move = MOVE_RESTART_LEVEL;
931 break;
932 case SDL_SCANCODE_R | WITH_CTRL:
933 save_recorded_replay_dialog();
934 recording = 0;
935 default:
936 break;
937 }
938 }
939
key_press_while_replaying(int * key_ptr)940 void key_press_while_replaying(int* key_ptr) {
941 int key = *key_ptr;
942 switch(key) {
943 case 0: // 'no key pressed'
944 break;
945 default:
946 // cannot manually do most stuff during a replay, so cancel the pressed key...
947 *key_ptr = 1; // don't set to zero (we would be unable to unpause a replay because all keys are ignored)
948 // (1 is not in use as a scancode, see https://wiki.libsdl.org/SDLScancodeLookup)
949 break;
950 // ...but these are allowable actions:
951 case SDL_SCANCODE_ESCAPE: // pause
952 case SDL_SCANCODE_ESCAPE | WITH_SHIFT:
953 case SDL_SCANCODE_BACKSPACE: // menu
954 case SDL_SCANCODE_SPACE: // time
955 case SDL_SCANCODE_S | WITH_CTRL: // sound toggle
956 case SDL_SCANCODE_V | WITH_CTRL: // version
957 case SDL_SCANCODE_C | WITH_CTRL: // SDL version
958 case SDL_SCANCODE_C: // room numbers
959 case SDL_SCANCODE_C | WITH_SHIFT:
960 case SDL_SCANCODE_I | WITH_SHIFT: // invert
961 case SDL_SCANCODE_B | WITH_SHIFT: // blind
962 case SDL_SCANCODE_T: // debug time
963 break;
964 case SDL_SCANCODE_R | WITH_CTRL: // restart game
965 replaying = 0;
966 restore_normal_options();
967 break;
968 case SDL_SCANCODE_TAB:
969 need_replay_cycle = 1;
970 restore_normal_options();
971 break;
972 case SDL_SCANCODE_F: // skip forward to next room
973 skipping_replay = 1;
974 replay_seek_target = replay_seek_0_next_room;
975 break;
976 case SDL_SCANCODE_F | WITH_SHIFT: // skip forward to start of next level
977 skipping_replay = 1;
978 replay_seek_target = replay_seek_1_next_level;
979 break;
980 }
981 }
982
983 #endif // USE_REPLAY
984