/***************************************************************************** * 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 "addresses.h" #include "config.h" #include "localisation/localisation.h" #include "object.h" #include "platform/platform.h" #include "ride/ride.h" #include "util/sawyercoding.h" #include "drawing/drawing.h" #include "world/footpath.h" #include "world/water.h" #include "world/entrance.h" #include "world/scenery.h" #include "scenario.h" #include "rct1.h" char gTempObjectLoadName[9] = { 0 }; int object_load_entry(const utf8 *path, rct_object_entry *outEntry) { SDL_RWops *file; file = SDL_RWFromFile(path, "rb"); if (file == NULL) return 0; if (SDL_RWread(file, outEntry, sizeof(rct_object_entry), 1) != 1) { SDL_RWclose(file); return 0; } SDL_RWclose(file); return 1; } int object_load_file(int groupIndex, const rct_object_entry *entry, int* chunkSize, const rct_object_entry *installedObject) { uint8 objectType; rct_object_entry openedEntry; char path[MAX_PATH]; SDL_RWops* rw; substitute_path(path, RCT2_ADDRESS(RCT2_ADDRESS_OBJECT_DATA_PATH, char), (char*)installedObject + 16); log_verbose("loading object, %s", path); rw = SDL_RWFromFile(path, "rb"); if (rw == NULL) return 0; SDL_RWread(rw, &openedEntry, sizeof(rct_object_entry), 1); if (!object_entry_compare(&openedEntry, entry)) { SDL_RWclose(rw); return 0; } // Get chunk size uint8 *installedObject_pointer = (uint8*)installedObject + 16; // Skip file name while (*installedObject_pointer++); // Read chunk size *chunkSize = *((uint32*)installedObject_pointer); uint8 *chunk; if (*chunkSize == 0xFFFFFFFF) { chunk = (uint8*)malloc(0x600000); *chunkSize = sawyercoding_read_chunk(rw, chunk); chunk = realloc(chunk, *chunkSize); } else { chunk = (uint8*)malloc(*chunkSize); *chunkSize = sawyercoding_read_chunk(rw, chunk); } SDL_RWclose(rw); int calculatedChecksum = object_calculate_checksum(&openedEntry, chunk, *chunkSize); // Calculate and check checksum if (calculatedChecksum != openedEntry.checksum && !gConfigGeneral.allow_loading_with_incorrect_checksum) { char buffer[100]; sprintf(buffer, "Object Load failed due to checksum failure: calculated checksum %d, object says %d.", calculatedChecksum, (int)openedEntry.checksum); log_error(buffer); RCT2_GLOBAL(0x00F42BD9, uint8) = 2; free(chunk); return 0; } objectType = openedEntry.flags & 0x0F; if (!object_test(objectType, chunk)) { log_error("Object Load failed due to paint failure."); RCT2_GLOBAL(0x00F42BD9, uint8) = 3; free(chunk); return 0; } if (RCT2_GLOBAL(RCT2_ADDRESS_TOTAL_NO_IMAGES, uint32) >= 0x4726E){ log_error("Object Load failed due to too many images loaded."); RCT2_GLOBAL(0x00F42BD9, uint8) = 4; free(chunk); return 0; } uint8** chunk_list = object_entry_groups[objectType].chunks; if (groupIndex == -1) { for (groupIndex = 0; chunk_list[groupIndex] != (uint8*)-1; groupIndex++) { if (groupIndex + 1 >= object_entry_group_counts[objectType]) { log_error("Object Load failed due to too many objects of a certain type."); RCT2_GLOBAL(0x00F42BD9, uint8) = 5; free(chunk); return 0; } } } chunk_list[groupIndex] = chunk; rct_object_entry_extended* extended_entry = &object_entry_groups[objectType].entries[groupIndex]; memcpy(extended_entry, &openedEntry, sizeof(rct_object_entry)); extended_entry->chunk_size = *chunkSize; gLastLoadedObjectChunkData = chunk; if (RCT2_GLOBAL(0x9ADAFD, uint8) != 0) { object_load(objectType, chunk, groupIndex); } return 1; } /** * * rct2: 0x006A985D */ int object_load_chunk(int groupIndex, rct_object_entry *entry, int* chunkSize) { // Alow chunkSize to be null int tempChunkSize; if (chunkSize == NULL) chunkSize = &tempChunkSize; RCT2_GLOBAL(0xF42B64, uint32) = groupIndex; if (gInstalledObjectsCount == 0) { RCT2_GLOBAL(0xF42BD9, uint8) = 0; log_error("Object Load failed due to no items installed check."); return 1; } rct_object_entry *installedObject = object_list_find(entry); if (installedObject == NULL) { log_error("object not installed"); return 0; } if (object_load_file(groupIndex, entry, chunkSize, installedObject)) return 1; return 0; } /** * * rct2: 0x006a9f42 * ebx: file * ebp: entry */ int write_object_file(SDL_RWops *rw, rct_object_entry* entry) { uint8 entryGroupIndex = 0, type = 0; uint8* chunk = 0; if (!find_object_in_entry_group(entry, &type, &entryGroupIndex))return 0; chunk = object_entry_groups[type].chunks[entryGroupIndex]; object_unload(type, chunk); rct_object_entry_extended* installed_entry = &object_entry_groups[type].entries[entryGroupIndex]; uint8* dst_buffer = (uint8*)malloc(0x600000); memcpy(dst_buffer, installed_entry, sizeof(rct_object_entry)); uint32 size_dst = sizeof(rct_object_entry); sawyercoding_chunk_header chunkHeader; // Encoding type (not used anymore) RCT2_GLOBAL(0x9E3CBD, uint8) = object_entry_group_encoding[type]; chunkHeader.encoding = object_entry_group_encoding[type]; chunkHeader.length = installed_entry->chunk_size; //Check if content of object file matches the stored checksum. If it does not, then fix it. int calculated_checksum = object_calculate_checksum(entry, chunk, installed_entry->chunk_size); if(entry->checksum != calculated_checksum) { //Store the current length of the header - it's the offset at which we will write the extra bytes int salt_offset = chunkHeader.length; /*Allocate a new chunk 11 bytes longer. I would just realloc the old one, but realloc can move the data, leaving dangling pointers into the old buffer. If the chunk is only referenced in one place it would be safe to realloc it and update that reference, but I don't know the codebase well enough to know if that's the case, so to be on the safe side I copy it*/ uint8* new_chunk = malloc(chunkHeader.length + 11); memcpy(new_chunk,chunk, chunkHeader.length); //It should be safe to update these in-place because they are local chunkHeader.length += 11; /*Next work out which bits need to be flipped to make the current checksum match the one in the file The bitwise rotation compensates for the rotation performed during the checksum calculation*/ int bits_to_flip = entry->checksum ^ ((calculated_checksum << 25) | (calculated_checksum >> 7)); /*Each set bit encountered during encoding flips one bit of the resulting checksum (so each bit of the checksum is an XOR of bits from the file). Here, we take each bit that should be flipped in the checksum and set one of the bits in the data that maps to it. 11 bytes is the minimum needed to touch every bit of the checksum - with less than that, you wouldn't always be able to make the checksum come out to the desired target*/ new_chunk[salt_offset] = (bits_to_flip & 0x00000001) << 7; new_chunk[salt_offset + 1] = ((bits_to_flip & 0x00200000) >> 14); new_chunk[salt_offset + 2] = ((bits_to_flip & 0x000007F8) >> 3); new_chunk[salt_offset + 3] = ((bits_to_flip & 0xFF000000) >> 24); new_chunk[salt_offset + 4] = ((bits_to_flip & 0x00100000) >> 13); new_chunk[salt_offset + 5] = (bits_to_flip & 0x00000004) >> 2; new_chunk[salt_offset + 6] = 0; new_chunk[salt_offset + 7] = ((bits_to_flip & 0x000FF000) >> 12); new_chunk[salt_offset + 8] = (bits_to_flip & 0x00000002) >> 1; new_chunk[salt_offset + 9] = (bits_to_flip & 0x00C00000) >> 22; new_chunk[salt_offset + 10] = (bits_to_flip & 0x00000800) >> 11; //Write modified chunk data size_dst += sawyercoding_write_chunk_buffer(dst_buffer + sizeof(rct_object_entry),new_chunk,chunkHeader); free(new_chunk); } else { //If the checksum matches, write chunk data size_dst += sawyercoding_write_chunk_buffer(dst_buffer + sizeof(rct_object_entry), chunk, chunkHeader); } SDL_RWwrite(rw, dst_buffer, 1, size_dst); free(dst_buffer); return 1; } /** * * rct2: 0x006AA2B7 */ int object_load_packed(SDL_RWops* rw) { object_unload_all(); rct_object_entry entry; SDL_RWread(rw, &entry, 16, 1); uint8* chunk = (uint8*)malloc(0x600000); uint32 chunkSize = sawyercoding_read_chunk(rw, chunk); chunk = realloc(chunk, chunkSize); if (chunk == NULL){ log_error("Failed to allocate memory for packed object."); return 0; } if (object_calculate_checksum(&entry, chunk, chunkSize) != entry.checksum){ if(gConfigGeneral.allow_loading_with_incorrect_checksum) { log_warning("Checksum mismatch from packed object: %.8s", entry.name); } else { log_error("Checksum mismatch from packed object: %.8s", entry.name); free(chunk); return 0; } } int type = entry.flags & 0x0F; if (!object_test(type, chunk)) { log_error("Packed object failed paint test."); free(chunk); return 0; } if (RCT2_GLOBAL(RCT2_ADDRESS_TOTAL_NO_IMAGES, uint32) >= 0x4726E){ log_error("Packed object has too many images."); free(chunk); return 0; } int entryGroupIndex = 0; for (; entryGroupIndex < object_entry_group_counts[type]; entryGroupIndex++){ if (object_entry_groups[type].chunks[entryGroupIndex] == (uint8*)-1){ break; } } if (entryGroupIndex == object_entry_group_counts[type]){ // This should never occur. Objects are not loaded before installing a // packed object. So there is only one object loaded at this point. log_error("Too many objects of the same type loaded."); free(chunk); return 0; } // Copy the entry into the relevant entry group. object_entry_groups[type].chunks[entryGroupIndex] = chunk; rct_object_entry_extended* extended_entry = &object_entry_groups[type].entries[entryGroupIndex]; memcpy(extended_entry, &entry, sizeof(rct_object_entry)); extended_entry->chunk_size = chunkSize; // Ensure the entry does not already exist. rct_object_entry *installedObject = gInstalledObjects; if (gInstalledObjectsCount){ for (uint32 i = 0; i < gInstalledObjectsCount; ++i){ if (object_entry_compare(&entry, installedObject)){ object_unload_all(); return 0; } installedObject = object_get_next(installedObject); } } // Convert the entry name to a upper case path name char path[MAX_PATH]; char objectPath[9] = { 0 }; for (int i = 0; i < 8; ++i){ if (entry.name[i] != ' ') objectPath[i] = toupper(entry.name[i]); else objectPath[i] = '\0'; } substitute_path(path, RCT2_ADDRESS(RCT2_ADDRESS_OBJECT_DATA_PATH, char), objectPath); // Require pointer to start of filename char* last_char = path + strlen(path); strcat(path, ".DAT"); // Check that file does not exist // Adjust filename if it does. for (; platform_file_exists(path);){ for (char* curr_char = last_char - 1;; --curr_char){ if (*curr_char == '\\'){ substitute_path(path, RCT2_ADDRESS(RCT2_ADDRESS_OBJECT_DATA_PATH, char), "00000000.DAT"); char* last_char = path + strlen(path); break; } if (*curr_char < '0') *curr_char = '0'; else if (*curr_char == '9') *curr_char = 'A'; else if (*curr_char == 'Z') *curr_char = '0'; else (*curr_char)++; if (*curr_char != '0') break; } } // Actually write the object to the file SDL_RWops* rw_out = SDL_RWFromFile(path, "wb"); if (rw_out != NULL){ uint8 result = write_object_file(rw_out, &entry); SDL_RWclose(rw_out); object_unload_all(); return result; } object_unload_all(); return 0; } /** * * rct2: 0x006A9CAF */ void object_unload_chunk(rct_object_entry *entry) { uint8 object_type, object_index; if (!find_object_in_entry_group(entry, &object_type, &object_index)){ return; } uint8* chunk = object_entry_groups[object_type].chunks[object_index]; object_unload(object_type, chunk); free(chunk); object_entry_groups[object_type].chunks[object_index] = (uint8*)-1; } int object_entry_compare(const rct_object_entry *a, const rct_object_entry *b) { // If an official object don't bother checking checksum if ((a->flags & 0xF0) || (b->flags & 0xF0)) { if ((a->flags & 0x0F) != (b->flags & 0x0F)) return 0; int match = memcmp(a->name, b->name, 8); if (match) return 0; } else { if (a->flags != b->flags) return 0; int match = memcmp(a->name, b->name, 8); if (match) return 0; if (a->checksum != b->checksum) return 0; } return 1; } int object_calculate_checksum(const rct_object_entry *entry, const uint8 *data, int dataLength) { const uint8 *entryBytePtr = (uint8*)entry; uint32 checksum = 0xF369A75B; checksum ^= entryBytePtr[0]; checksum = rol32(checksum, 11); for (int i = 4; i < 12; i++) { checksum ^= entryBytePtr[i]; checksum = rol32(checksum, 11); } for (int i = 0; i < dataLength; i++) { checksum ^= data[i]; checksum = rol32(checksum, 11); } return (int)checksum; } /** * * rct2: 0x006A9ED1 */ int object_chunk_load_image_directory(uint8_t** chunk) { int image_start_no = RCT2_GLOBAL(RCT2_ADDRESS_TOTAL_NO_IMAGES, uint32_t); // First dword of chunk is no_images int no_images = *((uint32_t*)(*chunk)); *chunk += 4; // Second dword of chunk is length of image data int length_of_data = *((uint32_t*)(*chunk)); *chunk += 4; RCT2_GLOBAL(RCT2_ADDRESS_TOTAL_NO_IMAGES, uint32_t) = no_images + image_start_no; rct_g1_element* g1_dest = &g1Elements[image_start_no]; // After length of data is the start of all g1 element structs rct_g1_element* g1_source = (rct_g1_element*)(*chunk); // After the g1 element structs is the actual images. uint8* image_offset = no_images * sizeof(rct_g1_element) + (uint8*)g1_source; for (int i = 0; i < no_images; ++i){ *g1_dest = *g1_source++; g1_dest->offset += (uint32)image_offset; g1_dest++; } *chunk = ((uint8*)g1_source) + length_of_data; return image_start_no; } typedef bool (*object_load_func)(void *objectEntry, uint32 entryIndex); typedef void (*object_unload_func)(void *objectEntry); typedef bool (*object_test_func)(void *objectEntry); typedef void (*object_paint_func)(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y); typedef rct_string_id (*object_desc_func)(void *objectEntry); /** * Represents addresses for virtual object functions. */ typedef struct { object_load_func load; object_unload_func unload; object_test_func test; object_paint_func paint; object_desc_func desc; } object_type_vtable; /////////////////////////////////////////////////////////////////////////////// // Ride (rct2: 0x006E6E2A) /////////////////////////////////////////////////////////////////////////////// static bool object_type_ride_load(void *objectEntry, uint32 entryIndex) { rct_ride_entry *rideEntry = (rct_ride_entry*)objectEntry; // After rideEntry is 3 string tables uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + sizeof(rct_ride_entry)); rideEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_RIDE, entryIndex, 0); rideEntry->description = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_RIDE, entryIndex, 1); //TODO: Move to its own function when ride construction window is merged. if (gConfigInterface.select_by_track_type) { rideEntry->enabledTrackPieces = 0xFFFFFFFFFFFFFFFF; } object_get_localised_text(&extendedEntryData, OBJECT_TYPE_RIDE, entryIndex, 2); rideEntry->vehicle_preset_list = (vehicle_colour_preset_list*)extendedEntryData; // If Unknown struct size is 0xFF then there are 32 3 byte structures uint8 unknown_size = *extendedEntryData++; if (unknown_size != 0xFF) { extendedEntryData += unknown_size * 3; } else { extendedEntryData += 0x60; } sint8 *peep_loading_positions = (sint8*)extendedEntryData; // Peep loading positions variable size // 4 different vehicle subtypes are available for (int i = 0; i < 4; i++){ uint16 no_peep_positions = *extendedEntryData++; // If no_peep_positions is 0xFF then no_peep_positions is a word if (no_peep_positions == 0xFF) { no_peep_positions = *((uint16*)extendedEntryData); extendedEntryData += 2; } extendedEntryData += no_peep_positions; } int images_offset = object_chunk_load_image_directory(&extendedEntryData); rideEntry->images_offset = images_offset; int cur_vehicle_images_offset = images_offset + 3; for (int i = 0; i < 4; i++) { rct_ride_entry_vehicle* vehicleEntry = &rideEntry->vehicles[i]; if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT) { int al = 1; if (vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_SWINGING) { al = 13; if ((vehicleEntry->flags_b & (VEHICLE_ENTRY_FLAG_B_5 | VEHICLE_ENTRY_FLAG_B_11)) != (VEHICLE_ENTRY_FLAG_B_5 | VEHICLE_ENTRY_FLAG_B_11)) { al = 7; if (!(vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_5)) { if (!(vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_11)) { al = 5; if (vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_9) { al = 3; } } } } } vehicleEntry->var_03 = al; // 0x6DE90B al = 0x20; if (!(vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_14)) { al = 1; if (vehicleEntry->flags_b & VEHICLE_ENTRY_FLAG_B_7) { if (vehicleEntry->var_11 != 6) { al = 2; if (!(vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_7)) { al = 4; } } } } if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_12) { al = vehicleEntry->special_frames; } vehicleEntry->var_02 = al; // 0x6DE946 vehicleEntry->var_16 = vehicleEntry->var_02 * vehicleEntry->var_03; vehicleEntry->base_image_id = cur_vehicle_images_offset; int image_index = vehicleEntry->base_image_id; if (vehicleEntry->car_visual != VEHICLE_VISUAL_RIVER_RAPIDS) { int b = vehicleEntry->var_16 * 32; if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_11) b /= 2; if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_15) b /= 8; image_index += b; // Incline 25 if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_GENTLE_SLOPES) { vehicleEntry->var_20 = image_index; b = vehicleEntry->var_16 * 72; if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_14) b = vehicleEntry->var_16 * 16; image_index += b; } // Incline 60 if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_STEEP_SLOPES) { vehicleEntry->var_24 = image_index; b = vehicleEntry->var_16 * 80; image_index += b; } // Verticle if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_VERTICAL_SLOPES) { vehicleEntry->var_28 = image_index; b = vehicleEntry->var_16 * 116; image_index += b; } // Unknown if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_DIAGONAL_SLOPES) { vehicleEntry->var_2C = image_index; b = vehicleEntry->var_16 * 24; image_index += b; } // Bank if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT_BANKED) { vehicleEntry->var_30 = image_index; b = vehicleEntry->var_16 * 80; image_index += b; } if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_INLINE_TWISTS) { vehicleEntry->var_34 = image_index; b = vehicleEntry->var_16 * 40; image_index += b; } // Track half? Up/Down if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_BANKED_TRANSITIONS) { vehicleEntry->var_38 = image_index; b = vehicleEntry->var_16 * 128; image_index += b; } // Unknown if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_DIAGONAL_GENTLE_SLOPE_BANKED_TRANSITIONS) { vehicleEntry->var_3C = image_index; b = vehicleEntry->var_16 * 16; image_index += b; } // Unknown if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TRANSITIONS) { vehicleEntry->var_40 = image_index; b = vehicleEntry->var_16 * 16; image_index += b; } if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_GENTLE_SLOPE_BANKED_TURNS) { vehicleEntry->var_44 = image_index; b = vehicleEntry->var_16 * 128; image_index += b; } if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_FLAT_TO_GENTLE_SLOPE_WHILE_BANKED_TRANSITIONS) { vehicleEntry->var_48 = image_index; b = vehicleEntry->var_16 * 16; image_index += b; } if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_CORKSCREWS) { vehicleEntry->var_4C = image_index; b = vehicleEntry->var_16 * 80; image_index += b; } // Unknown if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_RESTRAINT_ANIMATION) { vehicleEntry->var_1C = image_index; b = vehicleEntry->var_16 * 12; image_index += b; } if (vehicleEntry->sprite_flags & VEHICLE_SPRITE_FLAG_14) { // Same offset as above??? vehicleEntry->var_4C = image_index; b = vehicleEntry->var_16 * 32; image_index += b; } } else { image_index += vehicleEntry->var_16 * 36; } // No vehicle images vehicleEntry->no_vehicle_images = image_index - cur_vehicle_images_offset; // Move the offset over this vehicles images. Including peeps cur_vehicle_images_offset = image_index + vehicleEntry->no_seating_rows * vehicleEntry->no_vehicle_images; // 0x6DEB0D if (!(vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_10)) { int num_images = cur_vehicle_images_offset - vehicleEntry->base_image_id; if (vehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_13) { num_images *= 2; } set_vehicle_type_image_max_sizes(vehicleEntry, num_images); } sint8 no_positions = *peep_loading_positions++; if (no_positions == -1) { // The no_positions is 16 bit skip over peep_loading_positions += 2; } vehicleEntry->peep_loading_positions = peep_loading_positions; } } // 0x6DEB71 if (RCT2_GLOBAL(0x9ADAFD, uint8) == 0) { for (int i = 0; i < 3; i++) { int dl = rideEntry->ride_type[i]; if (dl == 0xFF) { continue; } uint8 *typeToRideEntryIndexMap = RCT2_ADDRESS(0x009E32F8, uint8); while (dl >= 0) { if (*typeToRideEntryIndexMap++ == 0xFF) { dl--; } } typeToRideEntryIndexMap--; uint8 previous_entry = entryIndex; while (typeToRideEntryIndexMap < RCT2_ADDRESS(0x9E34E4, uint8)){ uint8 backup_entry = *typeToRideEntryIndexMap; *typeToRideEntryIndexMap++ = previous_entry; previous_entry = backup_entry; } } } // 0x6DEBAA if (RCT2_GLOBAL(0x9ADAF4, sint32) != 0xFFFFFFFF) { *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; } int di = rideEntry->ride_type[0] | (rideEntry->ride_type[1] << 8) | (rideEntry->ride_type[2] << 16); if ((rideEntry->flags & RIDE_ENTRY_FLAG_SEPARATE_RIDE_NAME) && !rideTypeShouldLoseSeparateFlag(rideEntry)) { di |= 0x1000000; } RCT2_GLOBAL(0xF433DD, uint32) = di; return true; } static void object_type_ride_unload(void *objectEntry) { rct_ride_entry *rideEntry = (rct_ride_entry*)objectEntry; rideEntry->name = 0; rideEntry->description = 0; rideEntry->images_offset = 0; for (int i = 0; i < 4; i++) { rct_ride_entry_vehicle* rideVehicleEntry = &rideEntry->vehicles[i]; rideVehicleEntry->base_image_id = 0; rideVehicleEntry->var_1C = 0; rideVehicleEntry->var_20 = 0; rideVehicleEntry->var_24 = 0; rideVehicleEntry->var_28 = 0; rideVehicleEntry->var_2C = 0; rideVehicleEntry->var_30 = 0; rideVehicleEntry->var_34 = 0; rideVehicleEntry->var_38 = 0; rideVehicleEntry->var_3C = 0; rideVehicleEntry->var_40 = 0; rideVehicleEntry->var_44 = 0; rideVehicleEntry->var_48 = 0; rideVehicleEntry->var_4C = 0; rideVehicleEntry->no_vehicle_images = 0; rideVehicleEntry->var_16 = 0; if (!(rideVehicleEntry->flags_a & VEHICLE_ENTRY_FLAG_A_10)) { rideVehicleEntry->sprite_width = 0; rideVehicleEntry->sprite_height_negative = 0; rideVehicleEntry->sprite_height_positive = 0; } rideVehicleEntry->var_02 = 0; rideVehicleEntry->var_03 = 0; rideVehicleEntry->peep_loading_positions = 0; } rideEntry->vehicle_preset_list = NULL; } static bool object_type_ride_test(void *objectEntry) { rct_ride_entry* rideEntry = (rct_ride_entry*)objectEntry; if (rideEntry->excitement_multipler > 75) return false; if (rideEntry->intensity_multipler > 75) return false; if (rideEntry->nausea_multipler > 75) return false; return true; } static void object_type_ride_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { rct_ride_entry *rideEntry = (rct_ride_entry*)objectEntry; int imageId = rideEntry->images_offset; if (rideEntry->ride_type[0] == 0xFF) { imageId++; if (rideEntry->ride_type[1] == 0xFF) { imageId++; } } gfx_draw_sprite(dpi, imageId, x - 56, y - 56, 0); } static rct_string_id object_type_ride_desc(void *objectEntry) { rct_ride_entry *rideEntry = (rct_ride_entry*)objectEntry; // Get description rct_string_id stringId = rideEntry->description; if (!(rideEntry->flags & RIDE_ENTRY_FLAG_SEPARATE_RIDE_NAME) || rideTypeShouldLoseSeparateFlag(rideEntry)) { uint8 rideType = rideEntry->ride_type[0]; if (rideType == 0xFF) { rideType = rideEntry->ride_type[1]; if (rideType == 0xFF) { rideType = rideEntry->ride_type[2]; } } stringId = 512 + rideType; } return stringId; } static const object_type_vtable object_type_ride_vtable[] = { object_type_ride_load, object_type_ride_unload, object_type_ride_test, object_type_ride_paint, object_type_ride_desc }; /////////////////////////////////////////////////////////////////////////////// // Small Scenery (rct2: 0x006E3466) /////////////////////////////////////////////////////////////////////////////// static bool object_type_small_scenery_load(void *objectEntry, uint32 entryIndex) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x1C); sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_SMALL_SCENERY, entryIndex, 0); sceneryEntry->small_scenery.scenery_tab_id = 0xFF; if (*extendedEntryData != 0xFF) { uint8 entry_type, entry_index; if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)) { sceneryEntry->small_scenery.scenery_tab_id = entry_index; } } extendedEntryData += sizeof(rct_object_entry); if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG16){ sceneryEntry->small_scenery.var_10 = (uint32)extendedEntryData; while (*++extendedEntryData != 0xFF); extendedEntryData++; } sceneryEntry->image = object_chunk_load_image_directory(&extendedEntryData); if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; } return true; } static void object_type_small_scenery_unload(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; sceneryEntry->name = 0; sceneryEntry->image = 0; sceneryEntry->small_scenery.var_10 = 0; sceneryEntry->small_scenery.scenery_tab_id = 0; } static bool object_type_small_scenery_test(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; if (sceneryEntry->small_scenery.price <= 0) return false; if (sceneryEntry->small_scenery.removal_price > 0) return true; // Make sure you don't make a profit when placing then removing. if (-sceneryEntry->small_scenery.removal_price > sceneryEntry->small_scenery.price) return false; return true; } static void object_type_small_scenery_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; rct_drawpixelinfo clipDPI; if (!clip_drawpixelinfo(&clipDPI, dpi, x - 56, y - 56, 112, 112)) { return; } int imageId = sceneryEntry->image; if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_PRIMARY_COLOUR) { imageId |= 0x20D00000; if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) { imageId |= 0x92000000; } } x = 56; y = sceneryEntry->small_scenery.height / 4 + 78; if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_FULL_TILE) { if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_VOFFSET_CENTRE) { y -= 12; } } gfx_draw_sprite(&clipDPI, imageId, x, y, 0); if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG10) { imageId = sceneryEntry->image + 0x44500004; if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) { imageId |= 0x92000000; } gfx_draw_sprite(&clipDPI, imageId, x, y, 0); } if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG8) { imageId = sceneryEntry->image + 4; if (sceneryEntry->small_scenery.flags & SMALL_SCENERY_FLAG_HAS_SECONDARY_COLOUR) { imageId |= 0x92000000; } gfx_draw_sprite(&clipDPI, imageId, x, y, 0); } } static rct_string_id object_type_small_scenery_desc(void *objectEntry) { return STR_NONE; } static const object_type_vtable object_type_small_scenery_vtable[] = { object_type_small_scenery_load, object_type_small_scenery_unload, object_type_small_scenery_test, object_type_small_scenery_paint, object_type_small_scenery_desc }; /////////////////////////////////////////////////////////////////////////////// // Large Scenery (rct2: 0x006B92A7) /////////////////////////////////////////////////////////////////////////////// static bool object_type_large_scenery_load(void *objectEntry, uint32 entryIndex) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x1A); sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_LARGE_SCENERY, entryIndex, 0); sceneryEntry->large_scenery.scenery_tab_id = 0xFF; if (*extendedEntryData != 0xFF) { uint8 entry_type, entry_index; if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)) { sceneryEntry->large_scenery.scenery_tab_id = entry_index; } } extendedEntryData += sizeof(rct_object_entry); if (sceneryEntry->large_scenery.flags & (1 << 2)) { sceneryEntry->large_scenery.var_12 = (uint32)extendedEntryData; extendedEntryData += 1038; } sceneryEntry->large_scenery.tiles = (rct_large_scenery_tile*)extendedEntryData; // skip over large scenery tiles while (*((uint16*)extendedEntryData) != 0xFFFF){ extendedEntryData += sizeof(rct_large_scenery_tile); } extendedEntryData += 2; int imageId = object_chunk_load_image_directory(&extendedEntryData); if (sceneryEntry->large_scenery.flags & (1 << 2)){ sceneryEntry->large_scenery.var_16 = imageId; uint8* edx = (uint8*)sceneryEntry->large_scenery.var_12; if (!(edx[0xC] & 1)) { imageId += edx[0xD] * 4; } else{ imageId += edx[0xD] * 2; } } sceneryEntry->image = imageId; if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; } return true; } static void object_type_large_scenery_unload(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; sceneryEntry->name = 0; sceneryEntry->image = 0; sceneryEntry->large_scenery.tiles = 0; sceneryEntry->large_scenery.scenery_tab_id = 0; sceneryEntry->large_scenery.var_12 = 0; sceneryEntry->large_scenery.var_16 = 0; } static bool object_type_large_scenery_test(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; if (sceneryEntry->large_scenery.price <= 0) return false; if (sceneryEntry->large_scenery.removal_price > 0) return true; // Make sure you don't make a profit when placing then removing. if (-sceneryEntry->large_scenery.removal_price > sceneryEntry->large_scenery.price) return false; return true; } static void object_type_large_scenery_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; int imageId = sceneryEntry->image | 0xB2D00000; gfx_draw_sprite(dpi, imageId, x, y - 39, 0); } static rct_string_id object_type_large_scenery_desc(void *objectEntry) { return STR_NONE; } static const object_type_vtable object_type_large_scenery_vtable[] = { object_type_large_scenery_load, object_type_large_scenery_unload, object_type_large_scenery_test, object_type_large_scenery_paint, object_type_large_scenery_desc }; /////////////////////////////////////////////////////////////////////////////// // Wall (rct2: 0x006E5A25) /////////////////////////////////////////////////////////////////////////////// static bool object_type_wall_load(void *objectEntry, uint32 entryIndex) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x0E); sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_WALLS, entryIndex, 0); sceneryEntry->wall.scenery_tab_id = 0xFF; if (*extendedEntryData != 0xFF){ uint8 entry_type, entry_index; if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)) { sceneryEntry->wall.scenery_tab_id = entry_index; } } extendedEntryData += sizeof(rct_object_entry); sceneryEntry->image = object_chunk_load_image_directory(&extendedEntryData); if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; } return true; } static void object_type_wall_unload(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; sceneryEntry->name = 0; sceneryEntry->image = 0; sceneryEntry->wall.scenery_tab_id = 0; } static bool object_type_wall_test(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; if (sceneryEntry->wall.price <= 0) return false; return true; } static void object_type_wall_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; rct_drawpixelinfo clipDPI; if (!clip_drawpixelinfo(&clipDPI, dpi, x - 56, y - 56, 112, 112)) { return; } int imageId = sceneryEntry->image; imageId |= 0x20D00000; if (sceneryEntry->wall.flags & WALL_SCENERY_HAS_SECONDARY_COLOUR) { imageId |= 0x92000000; } x = 70; y = sceneryEntry->wall.height * 2 + 72; gfx_draw_sprite(&clipDPI, imageId, x, y, 0); if (sceneryEntry->wall.flags & WALL_SCENERY_FLAG2){ imageId = sceneryEntry->image + 0x44500006; gfx_draw_sprite(&clipDPI, imageId, x, y, 0); } else if (sceneryEntry->wall.flags & WALL_SCENERY_FLAG5){ imageId++; gfx_draw_sprite(&clipDPI, imageId, x, y, 0); } } static rct_string_id object_type_wall_desc(void *objectEntry) { return STR_NONE; } static const object_type_vtable object_type_wall_vtable[] = { object_type_wall_load, object_type_wall_unload, object_type_wall_test, object_type_wall_paint, object_type_wall_desc }; /////////////////////////////////////////////////////////////////////////////// // Banner (rct2: 0x006BA84E) /////////////////////////////////////////////////////////////////////////////// static bool object_type_banner_load(void *objectEntry, uint32 entryIndex) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x0C); sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_BANNERS, entryIndex, 0); sceneryEntry->banner.scenery_tab_id = 0xFF; if (*extendedEntryData != 0xFF){ uint8 entry_type, entry_index; if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)){ sceneryEntry->banner.scenery_tab_id = entry_index; } } extendedEntryData += sizeof(rct_object_entry); sceneryEntry->image = object_chunk_load_image_directory(&extendedEntryData); if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; } return true; } static void object_type_banner_unload(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; sceneryEntry->name = 0; sceneryEntry->image = 0; sceneryEntry->banner.scenery_tab_id = 0; } static bool object_type_banner_test(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; if (sceneryEntry->banner.price <= 0) return false; return true; } static void object_type_banner_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; int imageId = sceneryEntry->image | 0x20D00000; gfx_draw_sprite(dpi, imageId, x, y, 0); gfx_draw_sprite(dpi, imageId + 1, x, y, 0); } static rct_string_id object_type_banner_desc(void *objectEntry) { return STR_NONE; } static const object_type_vtable object_type_banner_vtable[] = { object_type_banner_load, object_type_banner_unload, object_type_banner_test, object_type_banner_paint, object_type_banner_desc }; /////////////////////////////////////////////////////////////////////////////// // Path (rct2: 0x006A8621) /////////////////////////////////////////////////////////////////////////////// static bool object_type_path_load(void *objectEntry, uint32 entryIndex) { rct_path_type *pathEntry = (rct_path_type*)objectEntry; uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x0E); pathEntry->string_idx = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_PATHS, entryIndex, 0); int imageId = object_chunk_load_image_directory(&extendedEntryData); pathEntry->image = imageId; pathEntry->bridge_image = imageId + 109; if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; } RCT2_GLOBAL(RCT2_ADDRESS_SELECTED_PATH_ID, sint16) = 0; // Set the default path for when opening footpath window for (int i = 0; i < object_entry_group_counts[OBJECT_TYPE_PATHS]; i++) { rct_path_type *pathEntry2 = (rct_path_type*)object_entry_groups[OBJECT_TYPE_PATHS].chunks[i]; if (pathEntry2 == (rct_path_type*)-1) { continue; } if (!(pathEntry2->flags & 4)) { RCT2_GLOBAL(RCT2_ADDRESS_SELECTED_PATH_ID, sint16) = i; break; } RCT2_GLOBAL(RCT2_ADDRESS_SELECTED_PATH_ID, sint16) = i; } return true; } static void object_type_path_unload(void *objectEntry) { rct_path_type *pathEntry = (rct_path_type*)objectEntry; pathEntry->string_idx = 0; pathEntry->image = 0; pathEntry->bridge_image = 0; } static bool object_type_path_test(void *objectEntry) { rct_path_type *pathEntry = (rct_path_type*)objectEntry; if (pathEntry->var_0A >= 2) return false; return true; } static void object_type_path_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { rct_path_type *pathEntry = (rct_path_type*)objectEntry; gfx_draw_sprite(dpi, pathEntry->image + 71, x - 49, y - 17, 0); gfx_draw_sprite(dpi, pathEntry->image + 72, x + 4, y - 17, 0); } static rct_string_id object_type_path_desc(void *objectEntry) { return STR_NONE; } static const object_type_vtable object_type_path_vtable[] = { object_type_path_load, object_type_path_unload, object_type_path_test, object_type_path_paint, object_type_path_desc }; /////////////////////////////////////////////////////////////////////////////// // Path Item (rct2: 0x006A86E2) /////////////////////////////////////////////////////////////////////////////// static bool object_type_path_bit_load(void *objectEntry, uint32 entryIndex) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + (size_t)0x0E); sceneryEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_PATH_BITS, entryIndex, 0); sceneryEntry->path_bit.scenery_tab_id = 0xFF; if (*extendedEntryData != 0xFF) { uint8 entry_type, entry_index; if (find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)){ sceneryEntry->path_bit.scenery_tab_id = entry_index; } } extendedEntryData += sizeof(rct_object_entry); sceneryEntry->image = object_chunk_load_image_directory(&extendedEntryData); if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; } return true; } static void object_type_path_bit_unload(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; sceneryEntry->name = 0; sceneryEntry->image = 0; sceneryEntry->path_bit.scenery_tab_id = 0; } static bool object_type_path_bit_test(void *objectEntry) { rct_scenery_entry *sceneryEntry = (rct_scenery_entry*)objectEntry; if (sceneryEntry->path_bit.price <= 0) return false; return true; } static void object_type_path_bit_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { rct_scenery_entry* sceneryEntry = (rct_scenery_entry*)objectEntry; gfx_draw_sprite(dpi, sceneryEntry->image, x - 22, y - 24, 0); } static rct_string_id object_type_path_bit_desc(void *objectEntry) { return STR_NONE; } static const object_type_vtable object_type_path_bit_vtable[] = { object_type_path_bit_load, object_type_path_bit_unload, object_type_path_bit_test, object_type_path_bit_paint, object_type_path_bit_desc }; /////////////////////////////////////////////////////////////////////////////// // Scenery Set (rct2: 0x006B93AA) /////////////////////////////////////////////////////////////////////////////// static bool object_type_scenery_set_load(void *objectEntry, uint32 entryIndex) { rct_scenery_set_entry *scenerySetEntry = (rct_scenery_set_entry*)objectEntry; uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + sizeof(rct_scenery_set_entry)); scenerySetEntry->name = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_SCENERY_SETS, entryIndex, 0); rct_object_entry *entryObjects = NULL; uint8 *eax = RCT2_GLOBAL(0x9ADAF4, uint8*); if ((uint32)eax != 0xFFFFFFFF){ *((uint16*)eax) = 0; entryObjects = (rct_object_entry*)(eax + 2); } scenerySetEntry->entry_count = 0; scenerySetEntry->var_107 = 0; for (; *extendedEntryData != 0xFF; extendedEntryData += sizeof(rct_object_entry)) { scenerySetEntry->var_107++; if (entryObjects != NULL){ memcpy(entryObjects, extendedEntryData, sizeof(rct_object_entry)); entryObjects++; (*(eax + 1))++; } uint8 entry_type; uint8 entry_index = 0; if (!find_object_in_entry_group((rct_object_entry*)extendedEntryData, &entry_type, &entry_index)) continue; uint16 scenery_entry = entry_index; switch (entry_type){ case OBJECT_TYPE_SMALL_SCENERY: break; case OBJECT_TYPE_LARGE_SCENERY: scenery_entry |= 0x300; break; case OBJECT_TYPE_WALLS: scenery_entry |= 0x200; break; case OBJECT_TYPE_PATH_BITS: scenery_entry |= 0x100; break; default: scenery_entry |= 0x400; break; } scenerySetEntry->scenery_entries[scenerySetEntry->entry_count++] = scenery_entry; } extendedEntryData++; scenerySetEntry->image = object_chunk_load_image_directory(&extendedEntryData); return true; } static void object_type_scenery_set_unload(void *objectEntry) { rct_scenery_set_entry *scenerySetEntry = (rct_scenery_set_entry*)objectEntry; scenerySetEntry->name = 0; scenerySetEntry->image = 0; scenerySetEntry->entry_count = 0; scenerySetEntry->var_107 = 0; memset(scenerySetEntry->scenery_entries, 0, 256); } static bool object_type_scenery_set_test(void *objectEntry) { return true; } static void object_type_scenery_set_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { rct_scenery_set_entry *scenerySetEntry = (rct_scenery_set_entry*)objectEntry; int imageId = scenerySetEntry->image + 0x20600001; gfx_draw_sprite(dpi, imageId, x - 15, y - 14, 0); } static rct_string_id object_type_scenery_set_desc(void *objectEntry) { return STR_NONE; } static const object_type_vtable object_type_scenery_set_vtable[] = { object_type_scenery_set_load, object_type_scenery_set_unload, object_type_scenery_set_test, object_type_scenery_set_paint, object_type_scenery_set_desc }; /////////////////////////////////////////////////////////////////////////////// // Park Entrance (rct2: 0x00666E42) /////////////////////////////////////////////////////////////////////////////// bool object_type_park_entrance_load(void *objectEntry, uint32 entryIndex) { rct_entrance_type *entranceType = (rct_entrance_type*)objectEntry; uint8 *extendedEntryData = (uint8*)((size_t)objectEntry + sizeof(rct_entrance_type)); entranceType->string_idx = object_get_localised_text(&extendedEntryData, OBJECT_TYPE_PARK_ENTRANCE, entryIndex, 0); entranceType->image_id = object_chunk_load_image_directory(&extendedEntryData); if (RCT2_GLOBAL(0x9ADAF4, uint32) != 0xFFFFFFFF) { *RCT2_GLOBAL(0x9ADAF4, uint16*) = 0; } return true; } static void object_type_park_entrance_unload(void *objectEntry) { rct_entrance_type *entranceType = (rct_entrance_type*)objectEntry; entranceType->string_idx = 0; entranceType->image_id = 0; } static bool object_type_park_entrance_test(void *objectEntry) { return true; } static void object_type_park_entrance_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { rct_entrance_type *entranceType = (rct_entrance_type*)objectEntry; rct_drawpixelinfo clipDPI; if (!clip_drawpixelinfo(&clipDPI, dpi, x - 56, y - 56, 112, 112)) { return; } int imageId = entranceType->image_id; gfx_draw_sprite(&clipDPI, imageId + 1, 24, 68, 0); gfx_draw_sprite(&clipDPI, imageId, 56, 84, 0); gfx_draw_sprite(&clipDPI, imageId + 2, 88, 100, 0); } static rct_string_id object_type_park_entrance_desc(void *objectEntry) { return STR_NONE; } static const object_type_vtable object_type_park_entrance_vtable[] = { object_type_park_entrance_load, object_type_park_entrance_unload, object_type_park_entrance_test, object_type_park_entrance_paint, object_type_park_entrance_desc }; /////////////////////////////////////////////////////////////////////////////// // Water (rct2: 0x006E6E2A) /////////////////////////////////////////////////////////////////////////////// static bool object_type_water_load(void *objectEntry, uint32 entryIndex) { rct_water_type *waterEntry = (rct_water_type*)objectEntry; uint8 *pStringTable = (uint8*)((size_t)objectEntry + sizeof(rct_water_type)); waterEntry->string_idx = object_get_localised_text(&pStringTable, OBJECT_TYPE_WATER, entryIndex, 0); int imageId = object_chunk_load_image_directory(&pStringTable); waterEntry->image_id = imageId; waterEntry->var_06 = imageId + 1; waterEntry->var_0A = imageId + 4; if (RCT2_GLOBAL(0x009ADAF4, uint32) != 0xFFFFFFFF) { *RCT2_GLOBAL(0x009ADAF4, uint16*) = 0; } if (RCT2_GLOBAL(0x009ADAFD, uint8) == 0) { load_palette(); gfx_invalidate_screen(); } return true; } static void object_type_water_unload(void *objectEntry) { rct_water_type *waterEntry = (rct_water_type*)objectEntry; waterEntry->string_idx = 0; waterEntry->image_id = 0; waterEntry->var_06 = 0; waterEntry->var_0A = 0; } static bool object_type_water_test(void *objectEntry) { return true; } static void object_type_water_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { // Write (no image) gfx_draw_string_centred(dpi, 3326, x, y, 0, NULL); } static rct_string_id object_type_water_desc(void *objectEntry) { return STR_NONE; } static const object_type_vtable object_type_water_vtable[] = { object_type_water_load, object_type_water_unload, object_type_water_test, object_type_water_paint, object_type_water_desc }; /////////////////////////////////////////////////////////////////////////////// // Stex (rct2: 0x0066B355) /////////////////////////////////////////////////////////////////////////////// static bool object_type_stex_load(void *objectEntry, uint32 entryIndex) { rct_stex_entry *stexEntry = (rct_stex_entry*)objectEntry; uint8 *stringTable = (uint8*)((size_t)objectEntry + (size_t)0x08); stexEntry->scenario_name = object_get_localised_text(&stringTable, OBJECT_TYPE_SCENARIO_TEXT, entryIndex, 0); stexEntry->park_name = object_get_localised_text(&stringTable, OBJECT_TYPE_SCENARIO_TEXT, entryIndex, 1); stexEntry->details = object_get_localised_text(&stringTable, OBJECT_TYPE_SCENARIO_TEXT, entryIndex, 2); if (RCT2_GLOBAL(0x9ADAF4, int) != -1) { RCT2_GLOBAL(0x9ADAF4, uint16*)[0] = 0; } return true; } static void object_type_stex_unload(void *objectEntry) { rct_stex_entry *stexEntry = (rct_stex_entry*)objectEntry; stexEntry->scenario_name = 0; stexEntry->park_name = 0; stexEntry->details = 0; } static bool object_type_stex_test(void *objectEntry) { return true; } static void object_type_stex_paint(void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { // Write (no image) gfx_draw_string_centred(dpi, 3326, x, y, 0, NULL); } static rct_string_id object_type_stex_desc(void *objectEntry) { rct_stex_entry *stexEntry = (rct_stex_entry*)objectEntry; return stexEntry->details; } static const object_type_vtable object_type_stex_vtable[] = { object_type_stex_load, object_type_stex_unload, object_type_stex_test, object_type_stex_paint, object_type_stex_desc }; /////////////////////////////////////////////////////////////////////////////// static const object_type_vtable * const object_type_vtables[] = { object_type_ride_vtable, object_type_small_scenery_vtable, object_type_large_scenery_vtable, object_type_wall_vtable, object_type_banner_vtable, object_type_path_vtable, object_type_path_bit_vtable, object_type_scenery_set_vtable, object_type_park_entrance_vtable, object_type_water_vtable, object_type_stex_vtable }; bool object_load(int type, void *objectEntry, uint32 entryIndex) { assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); const object_type_vtable *vtable = object_type_vtables[type]; return vtable->load(objectEntry, entryIndex) ? 0 : 1; } void object_unload(int type, void *objectEntry) { assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); const object_type_vtable *vtable = object_type_vtables[type]; vtable->unload(objectEntry); } bool object_test(int type, void *objectEntry) { assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); const object_type_vtable *vtable = object_type_vtables[type]; return vtable->test(objectEntry); } void object_paint(int type, void *objectEntry, rct_drawpixelinfo *dpi, sint32 x, sint32 y) { assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); const object_type_vtable *vtable = object_type_vtables[type]; vtable->paint(objectEntry, dpi, x, y); } rct_string_id object_desc(int type, void *objectEntry) { assert(type >= OBJECT_TYPE_RIDE && type <= OBJECT_TYPE_SCENARIO_TEXT); const object_type_vtable *vtable = object_type_vtables[type]; return vtable->desc(objectEntry); } /** * * rct2: 0x006A9428 */ int object_get_scenario_text(rct_object_entry *entry) { rct_object_entry *installedObject = gInstalledObjects; installedObject = object_list_find(entry); if (installedObject == NULL){ log_error("Object not found: %.8s", entry->name); RCT2_GLOBAL(0x00F42BD9, uint8) = 0; return 0; } char path[MAX_PATH]; char *objectPath = (char*)installedObject + 16; substitute_path(path, RCT2_ADDRESS(RCT2_ADDRESS_OBJECT_DATA_PATH, char), objectPath); rct_object_entry openedEntry; SDL_RWops* rw = SDL_RWFromFile(path, "rb"); if (rw != NULL) { SDL_RWread(rw, &openedEntry, sizeof(rct_object_entry), 1); if (object_entry_compare(&openedEntry, entry)) { // Skip over the object entry char *pos = (char*)installedObject + sizeof(rct_object_entry); // Skip file name while (*pos++); // Read chunk int chunkSize = *((uint32*)pos); uint8 *chunk; if (chunkSize == 0xFFFFFFFF) { chunk = (uint8*)malloc(0x600000); chunkSize = sawyercoding_read_chunk(rw, chunk); chunk = realloc(chunk, chunkSize); } else { chunk = (uint8*)malloc(chunkSize); sawyercoding_read_chunk(rw, chunk); } SDL_RWclose(rw); // Calculate and check checksum if (object_calculate_checksum(&openedEntry, chunk, chunkSize) != openedEntry.checksum) { log_error("Opened object failed calculated checksum."); RCT2_GLOBAL(0x00F42BD9, uint8) = 2; free(chunk); return 0; } if (!object_test(openedEntry.flags & 0x0F, chunk)) { // This is impossible for STEX entries to fail. log_error("Opened object failed paint test."); RCT2_GLOBAL(0x00F42BD9, uint8) = 3; free(chunk); return 0; } // Save the real total images. int total_no_images = RCT2_GLOBAL(RCT2_ADDRESS_TOTAL_NO_IMAGES, uint32); // This is being changed to force the images to be loaded into a different // image id. RCT2_GLOBAL(RCT2_ADDRESS_TOTAL_NO_IMAGES, uint32) = 0x726E; RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TEXT_TEMP_CHUNK, uint32) = (int)chunk; // Not used anywhere. RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TEXT_TEMP_OBJECT, rct_object_entry) = openedEntry; // Tell text to be loaded into a different address RCT2_GLOBAL(0x009ADAFC, uint8) = 255; memcpy(gTempObjectLoadName, openedEntry.name, 8); // Not used?? RCT2_GLOBAL(0x009ADAFD, uint8) = 1; object_load(openedEntry.flags & 0x0F, chunk, 0); // Tell text to be loaded into normal address RCT2_GLOBAL(0x009ADAFC, uint8) = 0; // Not used?? RCT2_GLOBAL(0x009ADAFD, uint8) = 0; RCT2_GLOBAL(RCT2_ADDRESS_TOTAL_NO_IMAGES, uint32) = total_no_images; return 1; } log_error("Opened object didn't match."); SDL_RWclose(rw); return 0; } log_error("File failed to open."); RCT2_GLOBAL(0x00F42BD9, uint8) = 0; return 0; } /** * * rct2: 0x006A982D */ void object_free_scenario_text() { if (RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TEXT_TEMP_CHUNK, void*) != NULL) { free(RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TEXT_TEMP_CHUNK, void*)); RCT2_GLOBAL(RCT2_ADDRESS_SCENARIO_TEXT_TEMP_CHUNK, void*) = NULL; } } int object_get_length(rct_object_entry *entry) { return (int)object_get_next(entry) - (int)entry; } rct_object_entry *object_get_next(rct_object_entry *entry) { uint8 *pos = (uint8*)entry; // Skip sizeof(rct_object_entry) pos += 16; // Skip filename while (*pos++); // Skip no of images pos += 4; // Skip name while (*pos++); // Skip size of chunk pos += 4; // Skip required objects pos += *pos * 16 + 1; // Skip theme objects pos += *pos * 16 + 1; // Skip pos += 4; return (rct_object_entry*)pos; } char *object_get_name(rct_object_entry *entry) { uint8 *pos = (uint8*)entry; // Skip sizeof(rct_object_entry) pos += 16; // Skip filename while (*pos++); // Skip no of images pos += 4; return (char *)pos; }