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_, &copyprot_dialog->peel_rect, &copyprot_dialog->peel_rect, 0);
766 	draw_dialog_frame(copyprot_dialog);
767 	shrink2_rect(&rect, &copyprot_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(&section_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(&section_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