/***************************************************************************** * Copyright (c) 2014 Ted John, Peter Hill, Duncan Frost * 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 "../common.h" #include "../sprites.h" #include "drawing.h" #include "../platform/platform.h" #include "../openrct2.h" void *_g1Buffer = NULL; rct_gx g2; rct_g1_element *g1Elements = (rct_g1_element*)RCT2_ADDRESS_G1_ELEMENTS; /** * * rct2: 0x00678998 */ int gfx_load_g1() { log_verbose("loading g1 graphics"); SDL_RWops *file; rct_g1_header header; unsigned int i; file = SDL_RWFromFile(get_file_path(PATH_ID_G1), "rb"); if (file != NULL) { if (SDL_RWread(file, &header, 8, 1) == 1) { // number of elements is stored in g1.dat, but because the entry headers are static, this can't be variable until // made into a dynamic array header.num_entries = 29294; // Read element headers SDL_RWread(file, g1Elements, header.num_entries * sizeof(rct_g1_element), 1); // Read element data _g1Buffer = rct2_malloc(header.total_size); SDL_RWread(file, _g1Buffer, header.total_size, 1); SDL_RWclose(file); // Fix entry data offsets for (i = 0; i < header.num_entries; i++) g1Elements[i].offset += (int)_g1Buffer; // Successful return 1; } SDL_RWclose(file); } // Unsuccessful log_fatal("Unable to load g1 graphics"); return 0; } int gfx_load_g2() { log_verbose("loading g2 graphics"); SDL_RWops *file; unsigned int i; char path[MAX_PATH]; sprintf(path, "%s%cdata%cg2.dat", gExePath, platform_get_path_separator(), platform_get_path_separator()); file = SDL_RWFromFile(path, "rb"); if (file != NULL) { if (SDL_RWread(file, &g2.header, 8, 1) == 1) { // Read element headers g2.elements = malloc(g2.header.num_entries * sizeof(rct_g1_element)); SDL_RWread(file, g2.elements, g2.header.num_entries * sizeof(rct_g1_element), 1); // Read element data g2.data = malloc(g2.header.total_size); SDL_RWread(file, g2.data, g2.header.total_size, 1); SDL_RWclose(file); // Fix entry data offsets for (i = 0; i < g2.header.num_entries; i++) g2.elements[i].offset += (int)g2.data; // Successful return 1; } SDL_RWclose(file); } // Unsuccessful log_fatal("Unable to load g2 graphics"); return 0; } /** * This function looks like it initialises the 0x009E3CE4 array which references sprites used for background / palette mixing or * something. Further investigation is needed. */ void sub_68371D() { uint8 **unk_9E3CE4 = (uint8**)0x009E3CE4; unk_9E3CE4[0] = NULL; for (int i = 1; i < 8; i++) unk_9E3CE4[i] = g1Elements[23199 + i].offset; } /** * Copies a sprite onto the buffer. There is no compression used on the sprite * image. * rct2: 0x0067A690 */ void gfx_bmp_sprite_to_buffer(uint8* palette_pointer, uint8* unknown_pointer, uint8* source_pointer, uint8* dest_pointer, rct_g1_element* source_image, rct_drawpixelinfo *dest_dpi, int height, int width, int image_type){ uint16 zoom_level = dest_dpi->zoom_level; uint8 zoom_amount = 1 << zoom_level; uint32 dest_line_width = (dest_dpi->width / zoom_amount) + dest_dpi->pitch; uint32 source_line_width = source_image->width * zoom_amount; //Requires use of palette? if (image_type & IMAGE_TYPE_USE_PALETTE){ assert(palette_pointer != NULL); //Mix with another image?? and colour adjusted if (unknown_pointer!= NULL){ //Not tested. I can't actually work out when this code runs. unknown_pointer += source_pointer - source_image->offset;// RCT2_GLOBAL(0x9E3CE0, uint32); for (; height > 0; height -= zoom_amount){ uint8* next_source_pointer = source_pointer + source_line_width; uint8* next_unknown_pointer = unknown_pointer + source_line_width; uint8* next_dest_pointer = dest_pointer + dest_line_width; for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, source_pointer += zoom_amount, unknown_pointer += zoom_amount, dest_pointer++){ uint8 pixel = *source_pointer; pixel = palette_pointer[pixel]; pixel &= *unknown_pointer; if (pixel){ *dest_pointer = pixel; } } source_pointer = next_source_pointer; dest_pointer = next_dest_pointer; unknown_pointer = next_unknown_pointer; } return; } //image colour adjusted? for (; height > 0; height -= zoom_amount){ uint8* next_source_pointer = source_pointer + source_line_width; uint8* next_dest_pointer = dest_pointer + dest_line_width; for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, source_pointer += zoom_amount, dest_pointer++){ uint8 pixel = *source_pointer; pixel = palette_pointer[pixel]; if (pixel){ *dest_pointer = pixel; } } source_pointer = next_source_pointer; dest_pointer = next_dest_pointer; } return; } //Mix with background. It only uses source pointer for //telling if it needs to be drawn not for colour. if (image_type & IMAGE_TYPE_MIX_BACKGROUND){//Not tested assert(palette_pointer != NULL); for (; height > 0; height -= zoom_amount){ uint8* next_source_pointer = source_pointer + source_line_width; uint8* next_dest_pointer = dest_pointer + dest_line_width; for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, source_pointer += zoom_amount, dest_pointer++){ uint8 pixel = *source_pointer; if (pixel){ pixel = *dest_pointer; pixel = palette_pointer[pixel]; *dest_pointer = pixel; } } source_pointer = next_source_pointer; dest_pointer = next_dest_pointer; } return; } //Basic bitmap no fancy stuff if (!(source_image->flags & G1_FLAG_BMP)){//Not tested for (; height > 0; height -= zoom_amount){ uint8* next_source_pointer = source_pointer + source_line_width; uint8* next_dest_pointer = dest_pointer + dest_line_width; for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, dest_pointer++, source_pointer += zoom_amount){ *dest_pointer = *source_pointer; } dest_pointer = next_dest_pointer; source_pointer = next_source_pointer; } return; } if (RCT2_GLOBAL(0x9E3CDC, uint32) != 0){//Not tested. I can't actually work out when this code runs. unknown_pointer += source_pointer - source_image->offset; for (; height > 0; height -= zoom_amount){ uint8* next_source_pointer = source_pointer + source_line_width; uint8* next_unknown_pointer = unknown_pointer + source_line_width; uint8* next_dest_pointer = dest_pointer + dest_line_width; for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, dest_pointer++, source_pointer += zoom_amount, unknown_pointer += zoom_amount){ uint8 pixel = *source_pointer; pixel &= *unknown_pointer; if (pixel){ *dest_pointer = pixel; } } dest_pointer = next_dest_pointer; source_pointer = next_source_pointer; unknown_pointer = next_unknown_pointer; } } //Basic bitmap with no draw pixels for (; height > 0; height -= zoom_amount){ uint8* next_source_pointer = source_pointer + source_line_width; uint8* next_dest_pointer = dest_pointer + dest_line_width; for (int no_pixels = width; no_pixels > 0; no_pixels -= zoom_amount, dest_pointer++, source_pointer += zoom_amount){ uint8 pixel = *source_pointer; if (pixel){ *dest_pointer = pixel; } } dest_pointer = next_dest_pointer; source_pointer = next_source_pointer; } return; } /** * Transfers readied images onto buffers * This function copies the sprite data onto the screen * rct2: 0x0067AA18 */ void gfx_rle_sprite_to_buffer(const uint8* source_bits_pointer, uint8* dest_bits_pointer, const uint8* palette_pointer, const rct_drawpixelinfo *dpi, int image_type, int source_y_start, int height, int source_x_start, int width){ int zoom_level = dpi->zoom_level; int zoom_amount = 1 << zoom_level; int zoom_mask = 0xFFFFFFFF << zoom_level; uint8* next_dest_pointer = dest_bits_pointer; int line_width = (dpi->width >> zoom_level) + dpi->pitch; if (source_y_start < 0){ source_y_start += zoom_amount; next_dest_pointer += line_width; height -= zoom_amount; } //For every line in the image for (int y = source_y_start; y < (height + source_y_start); y += zoom_amount){ //The first part of the source pointer is a list of offsets to different lines //This will move the pointer to the correct source line. const uint8 *next_source_pointer = source_bits_pointer + ((uint16*)source_bits_pointer)[y]; uint8 last_data_line = 0; //For every data section in the line while (!last_data_line){ const uint8* source_pointer = next_source_pointer; uint8* dest_pointer = next_dest_pointer; int no_pixels = *source_pointer++; //gap_size is the number of non drawn pixels you require to //jump over on your destination uint8 gap_size = *source_pointer++; //The last bit in no_pixels tells you if you have reached the end of a line last_data_line = no_pixels & 0x80; //Clear the last data line bit so we have just the no_pixels no_pixels &= 0x7f; //Have our next source pointer point to the next data section next_source_pointer = source_pointer + no_pixels; //Calculates the start point of the image int x_start = gap_size - source_x_start; if (x_start & ~zoom_mask){ no_pixels -= (x_start&~zoom_mask); x_start += ~zoom_mask; source_pointer += (x_start&~zoom_mask); if (no_pixels <= 0) continue; } if (x_start > 0){ //Since the start is positive //We need to move the drawing surface to the correct position dest_pointer += x_start >> zoom_level; } else{ //If the start is negative we require to remove part of the image. //This is done by moving the image pointer to the correct position. source_pointer -= x_start; //The no_pixels will be reduced in this operation no_pixels += x_start; //If there are no pixels there is nothing to draw this data section if (no_pixels <= 0) continue; //Reset the start position to zero as we have taken into account all moves x_start = 0; } int x_end = x_start + no_pixels; //If the end position is further out than the whole image //end position then we need to shorten the line again if (x_end > width){ //Shorten the line no_pixels -= x_end - width; //If there are no pixels there is nothing to draw. if (no_pixels <= 0) continue; } //Finally after all those checks, copy the image onto the drawing surface //If the image type is not a basic one we require to mix the pixels if (image_type & IMAGE_TYPE_USE_PALETTE){//In the .exe these are all unraveled loops for (; no_pixels > 0; no_pixels -= zoom_amount, source_pointer += zoom_amount, dest_pointer++){ uint8 al = *source_pointer; uint8 ah = *dest_pointer; if (image_type & IMAGE_TYPE_MIX_BACKGROUND) al = palette_pointer[(((uint16)al << 8) | ah) - 0x100]; else al = palette_pointer[al]; *dest_pointer = al; } } else if (image_type & IMAGE_TYPE_MIX_BACKGROUND){//In the .exe these are all unraveled loops //Doesnt use source pointer ??? mix with background only? //Not Tested for (; no_pixels > 0; no_pixels -= zoom_amount, dest_pointer++){ uint8 pixel = *dest_pointer; pixel = palette_pointer[pixel]; *dest_pointer = pixel; } } else { if (zoom_amount == 1) { memcpy(dest_pointer, source_pointer, no_pixels); } else { for (; no_pixels > 0; no_pixels -= zoom_amount, source_pointer += zoom_amount, dest_pointer++) { *dest_pointer = *source_pointer; } } } } //Add a line to the drawing surface pointer next_dest_pointer += line_width; } } /** * * rct2: 0x0067A28E * image_id (ebx) * image_id as below * 0b_111X_XXXX_XXXX_XXXX_XXXX_XXXX_XXXX_XXXX image_type * 0b_XXX1_11XX_XXXX_XXXX_XXXX_XXXX_XXXX_XXXX image_sub_type (unknown pointer) * 0b_XXX1_1111_XXXX_XXXX_XXXX_XXXX_XXXX_XXXX secondary_colour * 0b_XXXX_XXXX_1111_1XXX_XXXX_XXXX_XXXX_XXXX primary_colour * 0b_XXXX_X111_1111_1XXX_XXXX_XXXX_XXXX_XXXX palette_ref * 0b_XXXX_XXXX_XXXX_X111_1111_1111_1111_1111 image_id (offset to g1) * x (cx) * y (dx) * dpi (esi) * tertiary_colour (ebp) */ void gfx_draw_sprite(rct_drawpixelinfo *dpi, int image_id, int x, int y, uint32 tertiary_colour) { int image_type = (image_id & 0xE0000000) >> 28; int image_sub_type = (image_id & 0x1C000000) >> 26; uint8* palette_pointer = NULL; RCT2_GLOBAL(0x00EDF81C, uint32) = image_id & 0xE0000000; uint8* unknown_pointer = (uint8*)(RCT2_ADDRESS(0x9E3CE4, uint32*)[image_sub_type]); RCT2_GLOBAL(0x009E3CDC, uint32) = (uint32)unknown_pointer; if (image_type && !(image_type & IMAGE_TYPE_UNKNOWN)) { uint8 palette_ref = (image_id >> 19) & 0xFF; if (image_type & IMAGE_TYPE_MIX_BACKGROUND){ unknown_pointer = NULL; RCT2_GLOBAL(0x009E3CDC, uint32) = 0; } else{ palette_ref &= 0x7F; } uint16 palette_offset = palette_to_g1_offset[palette_ref]; palette_pointer = g1Elements[palette_offset].offset; } else if (image_type && !(image_type & IMAGE_TYPE_USE_PALETTE)){ RCT2_GLOBAL(0x9E3CDC, uint32) = 0; unknown_pointer = NULL; palette_pointer = RCT2_ADDRESS(0x9ABF0C, uint8); uint32 primary_offset = palette_to_g1_offset[(image_id >> 19) & 0x1F]; uint32 secondary_offset = palette_to_g1_offset[(image_id >> 24) & 0x1F]; uint32 tertiary_offset = palette_to_g1_offset[tertiary_colour]; rct_g1_element* primary_colour = &g1Elements[primary_offset]; rct_g1_element* secondary_colour = &g1Elements[secondary_offset]; rct_g1_element* tertiary_colour = &g1Elements[tertiary_offset]; memcpy(palette_pointer + 0xF3, &primary_colour->offset[0xF3], 12); memcpy(palette_pointer + 0xCA, &secondary_colour->offset[0xF3], 12); memcpy(palette_pointer + 0x2E, &tertiary_colour->offset[0xF3], 12); //image_id RCT2_GLOBAL(0xEDF81C, uint32) |= 0x20000000; image_id |= IMAGE_TYPE_USE_PALETTE << 28; } else if (image_type){ RCT2_GLOBAL(0x9E3CDC, uint32) = 0; unknown_pointer = NULL; palette_pointer = RCT2_ADDRESS(0x9ABE0C, uint8); //Top int top_type = (image_id >> 19) & 0x1f; uint32 top_offset = palette_to_g1_offset[top_type]; //RCT2_ADDRESS(0x97FCBC, uint32)[top_type]; rct_g1_element top_palette = g1Elements[top_offset]; memcpy(palette_pointer + 0xF3, top_palette.offset + 0xF3, 12); //Trousers int trouser_type = (image_id >> 24) & 0x1f; uint32 trouser_offset = palette_to_g1_offset[trouser_type]; //RCT2_ADDRESS(0x97FCBC, uint32)[trouser_type]; rct_g1_element trouser_palette = g1Elements[trouser_offset]; memcpy(palette_pointer + 0xCA, trouser_palette.offset + 0xF3, 12); } //For backwards compatibility RCT2_GLOBAL(0x9ABDA4, uint8*) = palette_pointer; gfx_draw_sprite_palette_set(dpi, image_id, x, y, palette_pointer, unknown_pointer); } /* * rct: 0x0067A46E * image_id (ebx) and also (0x00EDF81C) * palette_pointer (0x9ABDA4) * unknown_pointer (0x9E3CDC) * dpi (edi) * x (cx) * y (dx) */ void gfx_draw_sprite_palette_set(rct_drawpixelinfo *dpi, int image_id, int x, int y, uint8* palette_pointer, uint8* unknown_pointer) { int image_element = image_id & 0x7FFFF; int image_type = (image_id & 0xE0000000) >> 28; rct_g1_element* g1_source; if (image_element < SPR_G2_BEGIN) { g1_source = &g1Elements[image_element]; } else { g1_source = &g2.elements[image_element - SPR_G2_BEGIN]; } if ( dpi->zoom_level && (g1_source->flags & (1<<4)) ){ rct_drawpixelinfo zoomed_dpi = { .bits = dpi->bits, .x = dpi->x >> 1, .y = dpi->y >> 1, .height = dpi->height>>1, .width = dpi->width>>1, .pitch = dpi->pitch, .zoom_level = dpi->zoom_level - 1 }; gfx_draw_sprite_palette_set(&zoomed_dpi, (image_type << 28) | (image_element - g1_source->zoomed_offset), x >> 1, y >> 1, palette_pointer, unknown_pointer); return; } if ( dpi->zoom_level && (g1_source->flags & (1<<5)) ){ return; } //Its used super often so we will define it to a separate variable. int zoom_level = dpi->zoom_level; int zoom_mask = 0xFFFFFFFF << zoom_level; if (zoom_level && g1_source->flags & G1_FLAG_RLE_COMPRESSION){ x -= ~zoom_mask; y -= ~zoom_mask; } //This will be the height of the drawn image int height = g1_source->height; //This is the start y coordinate on the destination sint16 dest_start_y = y + g1_source->y_offset; // For whatever reason the RLE version does not use // the zoom mask on the y coordinate but does on x. if (g1_source->flags & G1_FLAG_RLE_COMPRESSION){ dest_start_y -= dpi->y; } else{ dest_start_y = (dest_start_y&zoom_mask) - dpi->y; } //This is the start y coordinate on the source int source_start_y = 0; if (dest_start_y < 0){ //If the destination y is negative reduce the height of the //image as we will cut off the bottom height += dest_start_y; //If the image is no longer visible nothing to draw if (height <= 0){ return; } //The source image will start a further up the image source_start_y -= dest_start_y; //The destination start is now reset to 0 dest_start_y = 0; } else{ if (g1_source->flags & G1_FLAG_RLE_COMPRESSION && zoom_level){ source_start_y -= dest_start_y & ~zoom_mask; height += dest_start_y & ~zoom_mask; } } int dest_end_y = dest_start_y + height; if (dest_end_y > dpi->height){ //If the destination y is outside of the drawing //image reduce the height of the image height -= dest_end_y - dpi->height; } //If the image no longer has anything to draw if (height <= 0)return; dest_start_y >>= zoom_level; dest_end_y >>= zoom_level; //This will be the width of the drawn image int width = g1_source->width; //This is the source start x coordinate int source_start_x = 0; //This is the destination start x coordinate sint16 dest_start_x = ((x + g1_source->x_offset + ~zoom_mask)&zoom_mask) - dpi->x; if (dest_start_x < 0){ //If the destination is negative reduce the width //image will cut off the side width += dest_start_x; //If there is no image to draw if (width <= 0){ return; } //The source start will also need to cut off the side source_start_x -= dest_start_x; //Reset the destination to 0 dest_start_x = 0; } else{ if (g1_source->flags & G1_FLAG_RLE_COMPRESSION && zoom_level){ source_start_x -= dest_start_x & ~zoom_mask; } } int dest_end_x = dest_start_x + width; if (dest_end_x > dpi->width){ //If the destination x is outside of the drawing area //reduce the image width. width -= dest_end_x - dpi->width; //If there is no image to draw. if (width <= 0)return; } dest_start_x >>= zoom_level; dest_end_x >>= zoom_level; uint8* dest_pointer = (uint8*)dpi->bits; //Move the pointer to the start point of the destination dest_pointer += ((dpi->width >> zoom_level) + dpi->pitch) * dest_start_y + dest_start_x; if (g1_source->flags & G1_FLAG_RLE_COMPRESSION){ //We have to use a different method to move the source pointer for //rle encoded sprites so that will be handled within this function gfx_rle_sprite_to_buffer(g1_source->offset, dest_pointer, palette_pointer, dpi, image_type, source_start_y, height, source_start_x, width); return; } uint8* source_pointer = g1_source->offset; //Move the pointer to the start point of the source source_pointer += g1_source->width*source_start_y + source_start_x; if (!(g1_source->flags & 0x02)){ gfx_bmp_sprite_to_buffer(palette_pointer, unknown_pointer, source_pointer, dest_pointer, g1_source, dpi, height, width, image_type); return; } //0x67A60A Not tested int total_no_pixels = g1_source->width*g1_source->height; source_pointer = g1_source->offset; uint8* new_source_pointer_start = malloc(total_no_pixels); uint8* new_source_pointer = new_source_pointer_start;// 0x9E3D28; int ebx, ecx; while (total_no_pixels>0){ sint8 no_pixels = *source_pointer; if (no_pixels >= 0){ source_pointer++; total_no_pixels -= no_pixels; memcpy((char*)new_source_pointer, (char*)source_pointer, no_pixels); new_source_pointer += no_pixels; source_pointer += no_pixels; continue; } ecx = no_pixels; no_pixels &= 0x7; ecx >>= 3;//SAR int eax = ((int)no_pixels)<<8; ecx = -ecx;//Odd eax = (eax & 0xFF00) + *(source_pointer+1); total_no_pixels -= ecx; source_pointer += 2; ebx = (uint32)new_source_pointer - eax; eax = (uint32)source_pointer; source_pointer = (uint8*)ebx; ebx = eax; eax = 0; memcpy((char*)new_source_pointer, (char*)source_pointer, ecx); new_source_pointer += ecx; source_pointer = (uint8*)ebx; } source_pointer = new_source_pointer_start + g1_source->width*source_start_y + source_start_x; gfx_bmp_sprite_to_buffer(palette_pointer, unknown_pointer, source_pointer, dest_pointer, g1_source, dpi, height, width, image_type); free(new_source_pointer_start); return; }