Subversion Repositories Games.Prince of Persia

Rev

Blame | Last modification | View Log | Download | RSS feed

  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
  1044.