mirror of https://github.com/OpenRCT2/OpenRCT2.git
366 lines
10 KiB
C
366 lines
10 KiB
C
#pragma region Copyright (c) 2014-2016 OpenRCT2 Developers
|
|
/*****************************************************************************
|
|
* OpenRCT2, an open source clone of Roller Coaster Tycoon 2.
|
|
*
|
|
* OpenRCT2 is the work of many authors, a full list can be found in contributors.md
|
|
* For more information, visit https://github.com/OpenRCT2/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.
|
|
*
|
|
* A full copy of the GNU General Public License can be found in licence.txt
|
|
*****************************************************************************/
|
|
#pragma endregion
|
|
|
|
#include "../common.h"
|
|
#include "../config.h"
|
|
#include "../game.h"
|
|
#include "../interface/window.h"
|
|
#include "../localisation/string_ids.h"
|
|
#include "../util/util.h"
|
|
#include "track.h"
|
|
#include "track_design.h"
|
|
|
|
#pragma pack(push, 1)
|
|
typedef struct td_index_item {
|
|
uint8 ride_type;
|
|
char ride_entry[9];
|
|
utf8 path[MAX_PATH];
|
|
} td_index_item;
|
|
// NOTE: this is our own struct and should not get packed, but it is stored in a file
|
|
// so removing packing from it would require refactoring file access
|
|
assert_struct_size(td_index_item, 1 + 9 + 260);
|
|
#pragma pack(pop)
|
|
|
|
static bool track_design_index_read_header(SDL_RWops *file, uint32 *tdidxCount);
|
|
static void track_design_index_scan();
|
|
static int track_design_index_item_compare(const void *a, const void *b);
|
|
static void track_design_index_include(const utf8 *directory);
|
|
static void track_design_add_file(const utf8 *path);
|
|
static void track_design_add(const td_index_item *item);
|
|
static void track_design_index_dispose();
|
|
static void track_design_index_get_path(utf8 * buffer, size_t bufferLength);
|
|
|
|
static const uint32 TrackIndexMagicNumber = 0x58444954;
|
|
static const uint16 TrackIndexVersion = 0;
|
|
|
|
static td_index_item *_tdIndex = NULL;
|
|
static size_t _tdIndexSize = 0;
|
|
static size_t _tdIndexCapacity = 0;
|
|
|
|
void track_design_index_create()
|
|
{
|
|
track_design_index_dispose();
|
|
|
|
log_verbose("saving track design index (tracks.idx)");
|
|
|
|
utf8 path[MAX_PATH];
|
|
track_design_index_get_path(path, sizeof(path));
|
|
|
|
SDL_RWops *file = SDL_RWFromFile(path, "wb");
|
|
if (file != NULL) {
|
|
track_design_index_scan();
|
|
|
|
SDL_RWwrite(file, &TrackIndexMagicNumber, sizeof(TrackIndexMagicNumber), 1);
|
|
SDL_RWwrite(file, &TrackIndexVersion, sizeof(TrackIndexVersion), 1);
|
|
SDL_RWwrite(file, &_tdIndexSize, sizeof(uint32), 1);
|
|
SDL_RWwrite(file, _tdIndex, sizeof(td_index_item), _tdIndexSize);
|
|
SDL_RWclose(file);
|
|
track_design_index_dispose();
|
|
}
|
|
}
|
|
|
|
size_t track_design_index_get_count_for_ride(uint8 rideType, const char *entry)
|
|
{
|
|
log_verbose("reading track design index (tracks.idx)");
|
|
|
|
utf8 path[MAX_PATH];
|
|
track_design_index_get_path(path, sizeof(path));
|
|
|
|
// Return list
|
|
size_t refsCount = 0;
|
|
SDL_RWops *file = SDL_RWFromFile(path, "rb");
|
|
if (file != NULL) {
|
|
uint32 tdidxCount;
|
|
if (!track_design_index_read_header(file, &tdidxCount)) {
|
|
SDL_RWclose(file);
|
|
return 0;
|
|
}
|
|
|
|
for (uint32 i = 0; i < tdidxCount; i++) {
|
|
td_index_item tdItem;
|
|
SDL_RWread(file, &tdItem, sizeof(td_index_item), 1);
|
|
|
|
if (tdItem.ride_type != rideType) continue;
|
|
if (entry != NULL && _strcmpi(entry, tdItem.ride_entry) != 0) continue;
|
|
refsCount++;
|
|
}
|
|
SDL_RWclose(file);
|
|
}
|
|
|
|
return refsCount;
|
|
}
|
|
|
|
size_t track_design_index_get_for_ride(track_design_file_ref **tdRefs, uint8 rideType, const char *entry)
|
|
{
|
|
log_verbose("reading track design index (tracks.idx)");
|
|
|
|
utf8 path[MAX_PATH];
|
|
track_design_index_get_path(path, sizeof(path));
|
|
|
|
// Return list
|
|
size_t refsCount = 0;
|
|
size_t refsCapacity = 0;
|
|
track_design_file_ref *refs = NULL;
|
|
|
|
SDL_RWops *file = SDL_RWFromFile(path, "rb");
|
|
if (file != NULL) {
|
|
uint32 tdidxCount;
|
|
if (!track_design_index_read_header(file, &tdidxCount)) {
|
|
SDL_RWclose(file);
|
|
return 0;
|
|
}
|
|
|
|
for (uint32 i = 0; i < tdidxCount; i++) {
|
|
td_index_item tdItem;
|
|
SDL_RWread(file, &tdItem, sizeof(td_index_item), 1);
|
|
|
|
if (tdItem.ride_type != rideType) continue;
|
|
if (entry != NULL && _strcmpi(entry, tdItem.ride_entry) != 0) continue;
|
|
|
|
size_t nextIndex = refsCount;
|
|
if (nextIndex >= refsCapacity) {
|
|
refsCapacity = max(8, refsCapacity * 2);
|
|
refs = realloc(refs, refsCapacity * sizeof(track_design_file_ref));
|
|
if (refs == NULL) {
|
|
log_fatal("Unable to allocate more memory.");
|
|
exit(-1);
|
|
}
|
|
}
|
|
refs[nextIndex].name = track_design_get_name_from_path(tdItem.path);
|
|
refs[nextIndex].path = _strdup(tdItem.path);
|
|
refsCount++;
|
|
}
|
|
SDL_RWclose(file);
|
|
}
|
|
|
|
*tdRefs = realloc(refs, refsCount * sizeof(track_design_file_ref));
|
|
return refsCount;
|
|
}
|
|
|
|
utf8 *track_design_get_name_from_path(const utf8 *path)
|
|
{
|
|
const char *filename = path_get_filename(path);
|
|
const char *lastDot = strrchr(filename, '.');
|
|
size_t nameLength;
|
|
if (lastDot == NULL) {
|
|
nameLength = strlen(filename);
|
|
} else {
|
|
nameLength = (size_t)(lastDot - filename);
|
|
}
|
|
return strndup(filename, nameLength);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006D3664
|
|
*/
|
|
bool track_design_index_rename(const utf8 *path, const utf8 *newName)
|
|
{
|
|
if (str_is_null_or_empty(newName)) {
|
|
gGameCommandErrorText = STR_CANT_RENAME_TRACK_DESIGN;
|
|
return false;
|
|
}
|
|
|
|
if (!filename_valid_characters(newName)) {
|
|
gGameCommandErrorText = STR_NEW_NAME_CONTAINS_INVALID_CHARACTERS;
|
|
return false;
|
|
}
|
|
|
|
utf8 newPath[MAX_PATH];
|
|
const char *lastPathSep = strrchr(path, *PATH_SEPARATOR);
|
|
if (lastPathSep == NULL) {
|
|
gGameCommandErrorText = STR_CANT_RENAME_TRACK_DESIGN;
|
|
return false;
|
|
}
|
|
size_t directoryLength = (size_t)(lastPathSep - path + 1);
|
|
memcpy(newPath, path, directoryLength);
|
|
safe_strcpy(newPath + directoryLength, newName, sizeof(newPath) - directoryLength);
|
|
path_append_extension(newPath, ".td6", sizeof(newPath));
|
|
|
|
if (!platform_file_move(path, newPath)) {
|
|
gGameCommandErrorText = STR_ANOTHER_FILE_EXISTS_WITH_NAME_OR_FILE_IS_WRITE_PROTECTED;
|
|
return false;
|
|
}
|
|
|
|
track_design_index_create();
|
|
|
|
rct_window *trackListWindow = window_find_by_class(WC_TRACK_DESIGN_LIST);
|
|
if (trackListWindow != NULL) {
|
|
trackListWindow->track_list.reload_track_designs = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006D3761
|
|
*/
|
|
bool track_design_index_delete(const utf8 *path)
|
|
{
|
|
if (!platform_file_delete(path)) {
|
|
gGameCommandErrorText = STR_FILE_IS_WRITE_PROTECTED_OR_LOCKED;
|
|
return false;
|
|
}
|
|
|
|
track_design_index_create();
|
|
|
|
rct_window *trackListWindow = window_find_by_class(WC_TRACK_DESIGN_LIST);
|
|
if (trackListWindow != NULL) {
|
|
trackListWindow->track_list.reload_track_designs = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
* rct2: 0x006D40B2
|
|
*/
|
|
bool track_design_index_install(const utf8 *srcPath, const utf8 *destPath)
|
|
{
|
|
if (!platform_file_copy(srcPath, destPath, false)) {
|
|
return false;
|
|
}
|
|
|
|
track_design_index_create();
|
|
|
|
rct_window *trackListWindow = window_find_by_class(WC_TRACK_DESIGN_LIST);
|
|
if (trackListWindow != NULL) {
|
|
trackListWindow->track_list.reload_track_designs = true;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static bool track_design_index_read_header(SDL_RWops *file, uint32 *tdidxCount)
|
|
{
|
|
uint32 tdidxMagicNumber;
|
|
uint16 tdidxVersion;
|
|
SDL_RWread(file, &tdidxMagicNumber, sizeof(tdidxMagicNumber), 1);
|
|
SDL_RWread(file, &tdidxVersion, sizeof(tdidxVersion), 1);
|
|
SDL_RWread(file, tdidxCount, sizeof(uint32), 1);
|
|
if (tdidxMagicNumber != TrackIndexMagicNumber) {
|
|
log_error("invalid track index file");
|
|
return false;
|
|
}
|
|
if (tdidxVersion != TrackIndexVersion) {
|
|
log_error("unsupported track index file version");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void track_design_index_scan()
|
|
{
|
|
utf8 directory[MAX_PATH];
|
|
|
|
// Get track directory from RCT2
|
|
safe_strcpy(directory, gRCT2AddressAppPath, sizeof(directory));
|
|
safe_strcat_path(directory, "Tracks", sizeof(directory));
|
|
track_design_index_include(directory);
|
|
|
|
// Get track directory from user directory
|
|
platform_get_user_directory(directory, "track", sizeof(directory));
|
|
track_design_index_include(directory);
|
|
|
|
// Sort items by ride type then by filename
|
|
qsort(_tdIndex, _tdIndexSize, sizeof(td_index_item), track_design_index_item_compare);
|
|
}
|
|
|
|
static int track_design_index_item_compare(const void *a, const void *b)
|
|
{
|
|
const td_index_item *tdA = (const td_index_item*)a;
|
|
const td_index_item *tdB = (const td_index_item*)b;
|
|
|
|
if (tdA->ride_type != tdB->ride_type) {
|
|
return tdA->ride_type - tdB->ride_type;
|
|
}
|
|
|
|
const utf8 *tdAName = path_get_filename(tdA->path);
|
|
const utf8 *tdBName = path_get_filename(tdB->path);
|
|
return _stricmp(tdAName, tdBName);
|
|
}
|
|
|
|
static void track_design_index_include(const utf8 *directory)
|
|
{
|
|
int handle;
|
|
file_info fileInfo;
|
|
|
|
// Scenarios in this directory
|
|
utf8 pattern[MAX_PATH];
|
|
safe_strcpy(pattern, directory, sizeof(pattern));
|
|
safe_strcat_path(pattern, "*.td6", sizeof(pattern));
|
|
|
|
handle = platform_enumerate_files_begin(pattern);
|
|
while (platform_enumerate_files_next(handle, &fileInfo)) {
|
|
utf8 path[MAX_PATH];
|
|
safe_strcpy(path, directory, sizeof(pattern));
|
|
safe_strcat_path(path, fileInfo.path, sizeof(pattern));
|
|
track_design_add_file(path);
|
|
}
|
|
platform_enumerate_files_end(handle);
|
|
|
|
// Include sub-directories
|
|
utf8 subDirectory[MAX_PATH];
|
|
handle = platform_enumerate_directories_begin(directory);
|
|
while (platform_enumerate_directories_next(handle, subDirectory)) {
|
|
utf8 path[MAX_PATH];
|
|
safe_strcpy(path, directory, sizeof(pattern));
|
|
safe_strcat_path(path, subDirectory, sizeof(pattern));
|
|
track_design_index_include(path);
|
|
}
|
|
platform_enumerate_directories_end(handle);
|
|
}
|
|
|
|
static void track_design_add_file(const utf8 *path)
|
|
{
|
|
rct_track_td6 *td6 = track_design_open(path);
|
|
if (td6 != NULL) {
|
|
td_index_item tdIndexItem = { 0 };
|
|
safe_strcpy(tdIndexItem.path, path, sizeof(tdIndexItem.path));
|
|
memcpy(tdIndexItem.ride_entry, td6->vehicle_object.name, 8);
|
|
tdIndexItem.ride_type = td6->type;
|
|
track_design_add(&tdIndexItem);
|
|
track_design_dispose(td6);
|
|
}
|
|
}
|
|
|
|
static void track_design_add(const td_index_item *item)
|
|
{
|
|
size_t nextIndex = _tdIndexSize;
|
|
if (nextIndex >= _tdIndexCapacity) {
|
|
_tdIndexCapacity = max(128, _tdIndexCapacity * 2);
|
|
_tdIndex = realloc(_tdIndex, _tdIndexCapacity * sizeof(td_index_item));
|
|
if (_tdIndex == NULL) {
|
|
log_fatal("Unable to allocate more memory.");
|
|
exit(-1);
|
|
}
|
|
}
|
|
_tdIndex[nextIndex] = *item;
|
|
_tdIndexSize++;
|
|
}
|
|
|
|
static void track_design_index_dispose()
|
|
{
|
|
SafeFree(_tdIndex);
|
|
_tdIndexSize = 0;
|
|
_tdIndexCapacity = 0;
|
|
}
|
|
|
|
static void track_design_index_get_path(utf8 * buffer, size_t bufferLength)
|
|
{
|
|
platform_get_user_directory(buffer, NULL, bufferLength);
|
|
safe_strcat_path(buffer, "tracks.idx", bufferLength);
|
|
}
|