Subversion Repositories Games.Prince of Persia

Rev

Details | Last modification | View Log | RSS feed

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