diff --git a/projects/openrct2.vcxproj b/projects/openrct2.vcxproj index 9e85fc8eb7..6b3bb9f23a 100644 --- a/projects/openrct2.vcxproj +++ b/projects/openrct2.vcxproj @@ -73,6 +73,7 @@ + diff --git a/projects/openrct2.vcxproj.filters b/projects/openrct2.vcxproj.filters index f1bf2f4f96..1d60034e94 100644 --- a/projects/openrct2.vcxproj.filters +++ b/projects/openrct2.vcxproj.filters @@ -296,10 +296,13 @@ Windows + + Header Files + Resource Files - + \ No newline at end of file diff --git a/src/addresses.h b/src/addresses.h index a38e57f6da..bf7e95c7f8 100644 --- a/src/addresses.h +++ b/src/addresses.h @@ -44,8 +44,8 @@ #define RCT2_ADDRESS_LAND_TOOL_SIZE 0x009A9800 #define RCT2_ADDRESS_SAVE_PROMPT_MODE 0x009A9802 -#define RCT2_ADDRESS_SCENARIO_LIST 0x009A9FF4 -#define RCT2_ADDRESS_NUM_SCENARIOS 0x009AA008 +// #define RCT2_ADDRESS_SCENARIO_LIST 0x009A9FF4 +// #define RCT2_ADDRESS_NUM_SCENARIOS 0x009AA008 #define RCT2_ADDRESS_APP_PATH 0x009AA214 diff --git a/src/rct2.c b/src/rct2.c index a8ab8ea254..239a264ac5 100644 --- a/src/rct2.c +++ b/src/rct2.c @@ -210,6 +210,14 @@ void rct2_init_directories() strcpy(RCT2_ADDRESS(RCT2_ADDRESS_SAVED_GAMES_PATH_2, char), RCT2_ADDRESS(RCT2_ADDRESS_SAVED_GAMES_PATH, char)); } +void subsitute_path(char *dest, const char *path, const char *filename) +{ + while (*path != '*') { + *dest++ = *path++; + } + strcpy(dest, filename); +} + // rct2: 0x00674B42 void rct2_startup_checks() { diff --git a/src/rct2.h b/src/rct2.h index a872cc25fc..64840b4816 100644 --- a/src/rct2.h +++ b/src/rct2.h @@ -161,6 +161,7 @@ enum { }; void rct2_endupdate(); +void subsitute_path(char *dest, const char *path, const char *filename); char *get_file_path(int pathId); void get_system_info(); void get_system_time(); diff --git a/src/scenario.c b/src/scenario.c index 9e4d557af7..4ecb88ca17 100644 --- a/src/scenario.c +++ b/src/scenario.c @@ -36,232 +36,11 @@ #include "sprite.h" #include "viewport.h" -#define UNINITIALISED_SCENARIO_LIST ((rct_scenario_basic*)-1) - -#define RCT2_SCENARIO_LIST RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_LIST, rct_scenario_basic*) -#define RCT2_NUM_SCENARIOS RCT2_GLOBAL(RCT2_ADDRESS_NUM_SCENARIOS, sint32) -int _scenarioListSize; - -static void scenario_list_add(char *path); -static void scenario_list_sort(); -static int scenario_list_sort_compare(const void* a, const void* b); -static void scenario_scores_load(); -static void scenario_scores_save(); -static int scenario_load_basic(char *path); - -static void subsitute_path(char *dest, char *path, char *filename) -{ - while (*path != '*') { - *dest++ = *path++; - } - strcpy(dest, filename); -} - -static rct_scenario_basic *get_scenario_by_filename(char *filename) -{ - int i; - for (i = 0; i < RCT2_NUM_SCENARIOS; i++) - if (strcmp(RCT2_SCENARIO_LIST[i].path, filename) == 0) - return &(RCT2_SCENARIO_LIST[i]); - - return NULL; -} - -/** - * - * rct2: 0x006775A8 - */ -void scenario_load_list() -{ - int i; - HANDLE hFindFile; - WIN32_FIND_DATAA findFileData; - - // Load scores - scenario_scores_load(); - - // Set all scenarios to be invisible - for (i = 0; i < RCT2_NUM_SCENARIOS; i++) - RCT2_SCENARIO_LIST[i].flags &= ~SCENARIO_FLAGS_VISIBLE; - - // Enumerate through each scenario in the directory - hFindFile = FindFirstFile(RCT2_ADDRESS(RCT2_ADDRESS_SCENARIOS_PATH, char), &findFileData); - if (hFindFile != INVALID_HANDLE_VALUE) { - do { - scenario_list_add(findFileData.cFileName); - } while (FindNextFile(hFindFile, &findFileData)); - FindClose(hFindFile); - } - - // Sort alphabetically - scenario_list_sort(); - - // Save the scores - scenario_scores_save(); -} - -static void scenario_list_add(char *path) -{ - rct_scenario_basic *scenario; - rct_s6_info *s6Info = (rct_s6_info*)0x0141F570; - - // Check if scenario already exists in list, likely if in scores - scenario = get_scenario_by_filename(path); - if (scenario != NULL) { - // Set 0141EF68 to the scenario path - subsitute_path( - RCT2_ADDRESS(0x0141EF68, char), - RCT2_ADDRESS(RCT2_ADDRESS_SCENARIOS_PATH, char), - path - ); - - // Load the basic scenario information - if (!scenario_load_basic(RCT2_ADDRESS(0x0141EF68, char))) - return; - - // - if (s6Info->var_000 != 255) - return; - - // Update the scenario information - scenario->flags |= SCENARIO_FLAGS_VISIBLE; - scenario->category = s6Info->category; - scenario->objective_type = s6Info->objective_type; - scenario->objective_arg_1 = s6Info->objective_arg_1; - scenario->objective_arg_2 = s6Info->objective_arg_2; - scenario->objective_arg_3 = s6Info->objective_arg_3; - strcpy(scenario->name, s6Info->name); - strcpy(scenario->details, s6Info->details); - return; - } - - // Check if the scenario list buffer has room for another scenario - if ((RCT2_NUM_SCENARIOS + 1) * (int)sizeof(rct_scenario_basic) > _scenarioListSize) { - // Allocate more room - _scenarioListSize += 16 * sizeof(rct_scenario_basic); - RCT2_SCENARIO_LIST = (rct_scenario_basic*)rct2_realloc(RCT2_SCENARIO_LIST, _scenarioListSize); - } - - // Set 0141EF68 to the scenario path - subsitute_path( - RCT2_ADDRESS(0x0141EF68, char), - RCT2_ADDRESS(RCT2_ADDRESS_SCENARIOS_PATH, char), - path - ); - - // Load the scenario information - if (!scenario_load_basic(RCT2_ADDRESS(0x0141EF68, char))) - return; - - // - if (s6Info->var_000 != 255) - return; - - // Increment the number of scenarios - scenario = &RCT2_SCENARIO_LIST[RCT2_NUM_SCENARIOS]; - RCT2_NUM_SCENARIOS++; - - // Add this new scenario to the list - strcpy(scenario->path, path); - scenario->flags = SCENARIO_FLAGS_VISIBLE; - if (RCT2_GLOBAL(0x009AA00C, uint8) & 1) - scenario->flags |= SCENARIO_FLAGS_SIXFLAGS; - scenario->category = s6Info->category; - scenario->objective_type = s6Info->objective_type; - scenario->objective_arg_1 = s6Info->objective_arg_1; - scenario->objective_arg_2 = s6Info->objective_arg_2; - scenario->objective_arg_3 = s6Info->objective_arg_3; - strcpy(scenario->name, s6Info->name); - strcpy(scenario->details, s6Info->details); -} - -/** -* Sort the list of scenarios. This used to be an insertion sort which took -* place as each scenario loaded. It has now been changed to a quicksort which -* takes place after all the scenarios have been loaded in. -* rct2: 0x00677C3B -*/ -static void scenario_list_sort() -{ - qsort( - RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_LIST, rct_scenario_basic*), - RCT2_NUM_SCENARIOS, - sizeof(rct_scenario_basic), - scenario_list_sort_compare - ); -} - -/** -* Basic scenario information compare function for sorting. -* rct2: 0x00677C08 -*/ -static int scenario_list_sort_compare(const void* a, const void* b) -{ - return strcmp(((rct_scenario_basic*)a)->name, ((rct_scenario_basic*)b)->name); -} - -/** - * - * rct2: 0x006775A8 - */ -static void scenario_scores_load() -{ - HANDLE hFile; - DWORD bytes_read; - - // Free scenario list if already allocated - if (RCT2_SCENARIO_LIST != UNINITIALISED_SCENARIO_LIST) { - rct2_free(RCT2_SCENARIO_LIST); - RCT2_SCENARIO_LIST = UNINITIALISED_SCENARIO_LIST; - } - - // Try and load the scores - hFile = CreateFile(get_file_path(PATH_ID_SCORES), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, - FILE_FLAG_RANDOM_ACCESS | FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile != INVALID_HANDLE_VALUE) { - ReadFile(hFile, (void*)0x009A9FFC, 16, &bytes_read, NULL); - if (bytes_read == 16) { - _scenarioListSize = RCT2_NUM_SCENARIOS * sizeof(rct_scenario_basic); - RCT2_SCENARIO_LIST = (rct_scenario_basic*)rct2_malloc(_scenarioListSize); - ReadFile(hFile, RCT2_SCENARIO_LIST, _scenarioListSize, &bytes_read, NULL); - CloseHandle(hFile); - if (bytes_read == _scenarioListSize) - return; - } else { - CloseHandle(hFile); - } - } - - // Unable to load scores, allocate some space for a reload - RCT2_NUM_SCENARIOS = 0; - _scenarioListSize = 0x4000; - RCT2_SCENARIO_LIST = (rct_scenario_basic*)rct2_malloc(_scenarioListSize); -} - -/** - * - * rct2: 0x00677B50 - */ -static void scenario_scores_save() -{ - HANDLE hFile; - DWORD bytes_written; - - hFile = CreateFile(get_file_path(PATH_ID_SCORES), GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, - FILE_ATTRIBUTE_NORMAL, NULL); - if (hFile != INVALID_HANDLE_VALUE) { - WriteFile(hFile, (void*)0x009A9FFC, 16, &bytes_written, NULL); - if (RCT2_NUM_SCENARIOS > 0) - WriteFile(hFile, RCT2_SCENARIO_LIST, RCT2_NUM_SCENARIOS * sizeof(rct_scenario_basic), &bytes_written, NULL); - CloseHandle(hFile); - } -} - /** * Loads only the basic information from a scenario. * rct2: 0x006761D6 */ -static int scenario_load_basic(char *path) +int scenario_load_basic(const char *path) { HANDLE hFile; int _eax; @@ -281,7 +60,7 @@ static int scenario_load_basic(char *path) sawyercoding_read_chunk(hFile, (uint8*)s6Info); CloseHandle(hFile); RCT2_GLOBAL(0x009AA00C, uint8) = 0; - if (s6Info->flags != 255) { + if ((s6Info->flags & 0xFF) != 255) { #ifdef _MSC_VER __asm { push ebp @@ -337,7 +116,7 @@ static int scenario_load_basic(char *path) * rct2: 0x00676053 * scenario (ebx) */ -void scenario_load(char *path) +void scenario_load(const char *path) { HANDLE hFile; int i, j; @@ -427,7 +206,7 @@ void scenario_load(char *path) * rct2: 0x00678282 * scenario (ebx) */ -void scenario_load_and_play(rct_scenario_basic *scenario) +void scenario_load_and_play(const rct_scenario_basic *scenario) { rct_window *mainWindow; rct_s6_info *s6Info = (rct_s6_info*)0x0141F570; @@ -590,9 +369,9 @@ void scenario_success() RCT2_GLOBAL(RCT2_ADDRESS_COMPLETED_COMPANY_VALUE, uint32) = current_val; RCT2_CALLPROC_EBPSAFE(0x0069BE9B); // celebration - for (i = 0; i < RCT2_GLOBAL(RCT2_ADDRESS_NUM_SCENARIOS, sint32); i++) { + for (i = 0; i < gScenarioListCount; i++) { char *cur_scenario_name = RCT2_ADDRESS(0x135936C, char); - scenario = &(RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_LIST, rct_scenario_basic*)[i]); + scenario = &gScenarioList[i]; if (0 == strncmp(cur_scenario_name, scenario->path, 256)){ if (scenario->flags & SCENARIO_FLAGS_COMPLETED && scenario->company_value < current_val) diff --git a/src/scenario.h b/src/scenario.h index 1589b7621f..ec47c7f3a1 100644 --- a/src/scenario.h +++ b/src/scenario.h @@ -49,10 +49,22 @@ typedef struct { uint8 pad_00A[0x3E]; char name[64]; // 0x48 char details[256]; // 0x88 - uint8 flags; // 0x188 - uint8 pad_189[0x0F]; + uint32 flags; // 0x188 + uint32 pad_18C; + uint32 pad_190; + uint32 pad_194; } rct_s6_info; +/* + * Scenario scores file header. + * size: 0x10 + */ +typedef struct { + uint32 var_0; + uint32 var_4; + uint32 var_8; + uint32 scenario_count; // 0x0C +} rct_scenario_scores_header; /** * Scenario basic structure, mainly for scenario select @@ -107,9 +119,16 @@ enum { OBJECTIVE_MONTHLY_FOOD_INCOME }; +// Scenario list +extern int gScenarioListCount; +extern int gScenarioListCapacity; +extern rct_scenario_basic *gScenarioList; + +int scenario_scores_save(); void scenario_load_list(); -void scenario_load(char *path); -void scenario_load_and_play(rct_scenario_basic *scenario); +int scenario_load_basic(const char *path); +void scenario_load(const char *path); +void scenario_load_and_play(const rct_scenario_basic *scenario); void scenario_update(); #endif diff --git a/src/scenario_list.c b/src/scenario_list.c new file mode 100644 index 0000000000..c2e7a97df7 --- /dev/null +++ b/src/scenario_list.c @@ -0,0 +1,226 @@ +/***************************************************************************** + * Copyright (c) 2014 Ted John + * OpenRCT2, an open source clone of Roller Coaster Tycoon 2. + * + * This file is part of OpenRCT2. + * + * OpenRCT2 is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + *****************************************************************************/ + +#include +#include "addresses.h" +#include "rct2.h" +#include "scenario.h" + +// Scenario list +int gScenarioListCount = 0; +int gScenarioListCapacity = 0; +rct_scenario_basic *gScenarioList = NULL; + +static void scenario_list_add(const char *path); +static void scenario_list_sort(); +static int scenario_list_sort_compare(const void *a, const void *b); +static int scenario_scores_load(); + +static rct_scenario_basic *get_scenario_by_filename(const char *filename) +{ + int i; + for (i = 0; i < gScenarioListCount; i++) + if (strcmp(gScenarioList[i].path, filename) == 0) + return &gScenarioList[i]; + + return NULL; +} + +/** + * + * rct2: 0x006775A8 + */ +void scenario_load_list() +{ + int i; + HANDLE hFindFile; + WIN32_FIND_DATAA findFileData; + + // Load scores + scenario_scores_load(); + + // Set all scenarios to be invisible + for (i = 0; i < gScenarioListCount; i++) + gScenarioList[i].flags &= ~SCENARIO_FLAGS_VISIBLE; + + // Enumerate through each scenario in the directory + hFindFile = FindFirstFile(RCT2_ADDRESS(RCT2_ADDRESS_SCENARIOS_PATH, char), &findFileData); + if (hFindFile != INVALID_HANDLE_VALUE) { + do { + scenario_list_add(findFileData.cFileName); + } while (FindNextFile(hFindFile, &findFileData)); + FindClose(hFindFile); + } + + // Sort alphabetically + scenario_list_sort(); + + // Save the scores + scenario_scores_save(); +} + +static void scenario_list_add(const char *path) +{ + char scenarioPath[MAX_PATH]; + rct_scenario_basic *scenario; + rct_s6_info *s6Info = (rct_s6_info*)0x0141F570; + + // Get absolute path + subsitute_path(scenarioPath, RCT2_ADDRESS(RCT2_ADDRESS_SCENARIOS_PATH, char), path); + + // Load the basic scenario information + if (!scenario_load_basic(scenarioPath)) + return; + + // Ignore scenarios where first header byte is not 255 + if (s6Info->var_000 != 255) + return; + + // Check if scenario already exists in list, likely if in scores + scenario = get_scenario_by_filename(path); + if (scenario != NULL) { + // Update the scenario information + scenario->flags |= SCENARIO_FLAGS_VISIBLE; + scenario->category = s6Info->category; + scenario->objective_type = s6Info->objective_type; + scenario->objective_arg_1 = s6Info->objective_arg_1; + scenario->objective_arg_2 = s6Info->objective_arg_2; + scenario->objective_arg_3 = s6Info->objective_arg_3; + strcpy(scenario->name, s6Info->name); + strcpy(scenario->details, s6Info->details); + } else { + // Check if the scenario list buffer has room for another scenario + if (gScenarioListCount >= gScenarioListCapacity) { + // Allocate more room + gScenarioListCapacity += 16; + gScenarioList = realloc(gScenarioList, gScenarioListCapacity * sizeof(rct_scenario_basic)); + } + + // Increment the number of scenarios + scenario = &gScenarioList[gScenarioListCount]; + gScenarioListCount++; + + // Add this new scenario to the list + strcpy(scenario->path, path); + scenario->flags = SCENARIO_FLAGS_VISIBLE; + if (RCT2_GLOBAL(0x009AA00C, uint8) & 1) + scenario->flags |= SCENARIO_FLAGS_SIXFLAGS; + scenario->category = s6Info->category; + scenario->objective_type = s6Info->objective_type; + scenario->objective_arg_1 = s6Info->objective_arg_1; + scenario->objective_arg_2 = s6Info->objective_arg_2; + scenario->objective_arg_3 = s6Info->objective_arg_3; + strcpy(scenario->name, s6Info->name); + strcpy(scenario->details, s6Info->details); + } +} + +/** +* Sort the list of scenarios. This used to be an insertion sort which took +* place as each scenario loaded. It has now been changed to a quicksort which +* takes place after all the scenarios have been loaded in. +* rct2: 0x00677C3B +*/ +static void scenario_list_sort() +{ + qsort(gScenarioList, gScenarioListCount, sizeof(rct_scenario_basic), scenario_list_sort_compare); +} + +/** +* Basic scenario information compare function for sorting. +* rct2: 0x00677C08 +*/ +static int scenario_list_sort_compare(const void *a, const void *b) +{ + return strcmp(((rct_scenario_basic*)a)->name, ((rct_scenario_basic*)b)->name); +} + +/** + * + * rct2: 0x006775A8 + */ +static int scenario_scores_load() +{ + FILE *file; + + // Free scenario list if already allocated + if (gScenarioList != NULL) { + free(gScenarioList); + gScenarioList = NULL; + } + + // Try and load the scores file + file = fopen(get_file_path(PATH_ID_SCORES), "rb"); + if (file == NULL) { + RCT2_ERROR("Unable to load scenario scores."); + return 0; + } + + // Load header + rct_scenario_scores_header header; + if (fread(&header, 16, 1, file) != 1) { + fclose(file); + RCT2_ERROR("Invalid header in scenario scores file."); + return 0; + } + gScenarioListCount = header.scenario_count; + + // Load scenario information with scores + int scenarioListBufferSize = gScenarioListCount * sizeof(rct_scenario_basic); + gScenarioListCapacity = gScenarioListCount; + gScenarioList = malloc(scenarioListBufferSize); + if (fread(gScenarioList, scenarioListBufferSize, 1, file) == 1) { + fclose(file); + return 1; + } + + // Unable to load scores, free scenario list + fclose(file); + gScenarioListCount = 0; + gScenarioListCapacity = 0; + free(gScenarioList); + gScenarioList = NULL; + return 0; +} + +/** + * + * rct2: 0x00677B50 + */ +int scenario_scores_save() +{ + FILE *file; + + file = fopen(get_file_path(PATH_ID_SCORES), "wb"); + if (file == NULL) { + RCT2_ERROR("Unable to save scenario scores."); + return 0; + } + + rct_scenario_scores_header header; + header.scenario_count = gScenarioListCount; + + fwrite(&header, sizeof(header), 1, file); + if (gScenarioListCount > 0) + fwrite(gScenarioList, gScenarioListCount * sizeof(rct_scenario_basic), 1, file); + + fclose(file); + return 1; +} \ No newline at end of file diff --git a/src/window_title_scenarioselect.c b/src/window_title_scenarioselect.c index 021490dc81..8f0f34329b 100644 --- a/src/window_title_scenarioselect.c +++ b/src/window_title_scenarioselect.c @@ -147,8 +147,8 @@ static void window_scenarioselect_init_tabs() rct_scenario_basic* scenario; show_pages = 0; - for (i = 0; i < RCT2_GLOBAL(RCT2_ADDRESS_NUM_SCENARIOS, sint32); i++) { - scenario = &(RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_LIST, rct_scenario_basic*)[i]); + for (i = 0; i < gScenarioListCount; i++) { + scenario = &gScenarioList[i]; if (scenario->flags & SCENARIO_FLAGS_VISIBLE) show_pages |= 1 << scenario->category; } @@ -233,8 +233,8 @@ static void window_scenarioselect_scrollgetsize() height = 0; - for (i = 0; i < RCT2_GLOBAL(RCT2_ADDRESS_NUM_SCENARIOS, sint32); i++) { - scenario = &(RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_LIST, rct_scenario_basic*)[i]); + for (i = 0; i < gScenarioListCount; i++) { + scenario = &gScenarioList[i]; if (scenario->category != w->selected_tab) continue; if (scenario->flags & SCENARIO_FLAGS_VISIBLE) @@ -281,8 +281,8 @@ static void window_scenarioselect_scrollmousedown() #endif - for (i = 0; i < RCT2_GLOBAL(RCT2_ADDRESS_NUM_SCENARIOS, sint32); i++) { - scenario = &(RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_LIST, rct_scenario_basic*)[i]); + for (i = 0; i < gScenarioListCount; i++) { + scenario = &gScenarioList[i]; if (scenario->category != w->selected_tab) continue; if (!(scenario->flags & SCENARIO_FLAGS_VISIBLE)) @@ -325,8 +325,8 @@ static void window_scenarioselect_scrollmouseover() selected = NULL; - for (i = 0; i < RCT2_GLOBAL(RCT2_ADDRESS_NUM_SCENARIOS, sint32); i++) { - scenario = &(RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_LIST, rct_scenario_basic*)[i]); + for (i = 0; i < gScenarioListCount; i++) { + scenario = &gScenarioList[i]; if (scenario->category != w->selected_tab) continue; if (!(scenario->flags & SCENARIO_FLAGS_VISIBLE)) @@ -458,8 +458,8 @@ static void window_scenarioselect_scrollpaint() gfx_clear(dpi, colour); y = 0; - for (i = 0; i < RCT2_GLOBAL(RCT2_ADDRESS_NUM_SCENARIOS, sint32); i++) { - scenario = &(RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_LIST, rct_scenario_basic*)[i]); + for (i = 0; i < gScenarioListCount; i++) { + scenario = &gScenarioList[i]; if (scenario->category != w->selected_tab) continue; if (!(scenario->flags & SCENARIO_FLAGS_VISIBLE))