1137 lines
32 KiB
C++
1137 lines
32 KiB
C++
#include "gfx.h"
|
|
#include "../Console.h"
|
|
#include "../Environment.h"
|
|
#include "../Input.h"
|
|
#include "../Ui.h"
|
|
#include "../drawing/SoftwareDrawingEngine.h"
|
|
#include "../interop/interop.hpp"
|
|
#include "../localisation/languagefiles.h"
|
|
#include "../ui/WindowManager.h"
|
|
#include "../utility/stream.hpp"
|
|
#include "colours.h"
|
|
#include "image_ids.h"
|
|
#include <algorithm>
|
|
#include <cassert>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <memory>
|
|
|
|
using namespace openloco::interop;
|
|
using namespace openloco::utility;
|
|
using namespace openloco::drawing;
|
|
using namespace openloco::ui;
|
|
|
|
namespace openloco::gfx
|
|
{
|
|
namespace g1_expected_count
|
|
{
|
|
constexpr uint32_t disc = 0x101A; // And GOG
|
|
constexpr uint32_t steam = 0x0F38;
|
|
}
|
|
|
|
namespace text_draw_flags
|
|
{
|
|
constexpr uint8_t inset = (1ULL << 0);
|
|
constexpr uint8_t outline = (1ULL << 1);
|
|
constexpr uint8_t dark = (1ULL << 2);
|
|
constexpr uint8_t extra_dark = (1ULL << 3);
|
|
}
|
|
|
|
constexpr uint32_t g1_count_objects = 0x40000;
|
|
constexpr uint32_t g1_count_temporary = 0x1000;
|
|
|
|
static loco_global<drawpixelinfo_t, 0x0050B884> _screen_dpi;
|
|
static loco_global<drawpixelinfo_t, 0x005233B8> _windowDPI;
|
|
|
|
static loco_global<g1_element[g1_expected_count::disc + g1_count_temporary + g1_count_objects], 0x9E2424> _g1Elements;
|
|
|
|
static std::unique_ptr<std::byte[]> _g1Buffer;
|
|
|
|
static loco_global<uint16_t, 0x112C824> _currentFontFlags;
|
|
static loco_global<int16_t, 0x112C876> _currentFontSpriteBase;
|
|
static loco_global<uint8_t[224 * 4], 0x112C884> _characterWidths;
|
|
static loco_global<uint8_t[4], 0x1136594> _windowColours;
|
|
|
|
static palette_index_t _textColours[8] = { 0 };
|
|
|
|
drawpixelinfo_t& screenDpi()
|
|
{
|
|
return _screen_dpi;
|
|
}
|
|
|
|
static std::vector<g1_element> convertElements(const std::vector<g1_element32_t>& elements32)
|
|
{
|
|
auto elements = std::vector<g1_element>();
|
|
elements.reserve(elements32.size());
|
|
std::transform(
|
|
elements32.begin(),
|
|
elements32.end(),
|
|
std::back_inserter(elements),
|
|
[](g1_element32_t src) { return g1_element(src); });
|
|
return elements;
|
|
}
|
|
|
|
// 0x0044733C
|
|
void loadG1()
|
|
{
|
|
auto g1Path = environment::getPath(environment::path_id::g1);
|
|
std::ifstream stream(g1Path, std::ios::in | std::ios::binary);
|
|
if (!stream)
|
|
{
|
|
throw std::runtime_error("Opening g1 file failed.");
|
|
}
|
|
|
|
g1_header_t header;
|
|
if (!readData(stream, header))
|
|
{
|
|
throw std::runtime_error("Reading g1 file header failed.");
|
|
}
|
|
|
|
if (header.num_entries != g1_expected_count::disc)
|
|
{
|
|
std::cout << "G1 element count doesn't match expected value: ";
|
|
std::cout << "Expected " << g1_expected_count::disc << "; Got " << header.num_entries << std::endl;
|
|
if (header.num_entries == g1_expected_count::steam)
|
|
{
|
|
std::cout << "Got Steam G1.DAT variant, will fix elements automatically." << std::endl;
|
|
}
|
|
}
|
|
|
|
// Read element headers
|
|
auto elements32 = std::vector<g1_element32_t>(header.num_entries);
|
|
if (!readData(stream, elements32.data(), header.num_entries))
|
|
{
|
|
throw std::runtime_error("Reading g1 element headers failed.");
|
|
}
|
|
auto elements = convertElements(elements32);
|
|
|
|
// Read element data
|
|
auto elementData = std::make_unique<std::byte[]>(header.total_size);
|
|
if (!readData(stream, elementData.get(), header.total_size))
|
|
{
|
|
throw std::runtime_error("Reading g1 elements failed.");
|
|
}
|
|
stream.close();
|
|
|
|
// The steam G1.DAT is missing two localised tutorial icons, and a smaller font variant
|
|
// This code copies the closest variants into their place, and moves other elements accordingly
|
|
if (header.num_entries == g1_expected_count::steam)
|
|
{
|
|
elements.resize(g1_expected_count::disc);
|
|
|
|
// Extra two tutorial images
|
|
std::copy_n(&elements[3549], header.num_entries - 3549, &elements[3551]);
|
|
std::copy_n(&elements[3551], 1, &elements[3549]);
|
|
std::copy_n(&elements[3551], 1, &elements[3550]);
|
|
|
|
// Extra font variant
|
|
std::copy_n(&elements[1788], 223, &elements[3898]);
|
|
}
|
|
|
|
// Adjust memory offsets
|
|
for (auto& element : elements)
|
|
{
|
|
element.offset += (uintptr_t)elementData.get();
|
|
}
|
|
|
|
_g1Buffer = std::move(elementData);
|
|
std::copy(elements.begin(), elements.end(), _g1Elements.get());
|
|
}
|
|
|
|
// 0x00447485
|
|
// edi: dpi
|
|
// ebp: fill
|
|
void clear(drawpixelinfo_t& dpi, uint32_t fill)
|
|
{
|
|
int32_t w = dpi.width / (1 << dpi.zoom_level);
|
|
int32_t h = dpi.height / (1 << dpi.zoom_level);
|
|
uint8_t* ptr = dpi.bits;
|
|
|
|
for (int32_t y = 0; y < h; y++)
|
|
{
|
|
std::fill_n(ptr, w, fill);
|
|
ptr += w + dpi.pitch;
|
|
}
|
|
}
|
|
|
|
void clearSingle(drawpixelinfo_t& dpi, uint8_t paletteId)
|
|
{
|
|
auto fill = (paletteId << 24) | (paletteId << 16) | (paletteId << 8) | paletteId;
|
|
clear(dpi, fill);
|
|
}
|
|
|
|
// 0x004957C4
|
|
int16_t clipString(int16_t width, char* string)
|
|
{
|
|
|
|
registers regs;
|
|
regs.di = width;
|
|
regs.esi = (int32_t)string;
|
|
call(0x004957C4, regs);
|
|
return regs.cx;
|
|
}
|
|
|
|
/**
|
|
* 0x00495685
|
|
*
|
|
* @param buffer @<esi>
|
|
* @return width @<cx>
|
|
*/
|
|
uint16_t getStringWidth(const char* buffer)
|
|
{
|
|
uint16_t width = 0;
|
|
const uint8_t* str = reinterpret_cast<const uint8_t*>(buffer);
|
|
int16_t fontSpriteBase = _currentFontSpriteBase;
|
|
|
|
while (*str != (uint8_t)0)
|
|
{
|
|
const uint8_t chr = *str;
|
|
str++;
|
|
|
|
if (chr >= 32)
|
|
{
|
|
width += _characterWidths[chr - 32 + fontSpriteBase];
|
|
continue;
|
|
}
|
|
|
|
switch (chr)
|
|
{
|
|
case control_codes::move_x:
|
|
width = *str;
|
|
str++;
|
|
break;
|
|
|
|
case control_codes::adjust_palette:
|
|
case 3:
|
|
case 4:
|
|
str++;
|
|
break;
|
|
|
|
case control_codes::newline:
|
|
case control_codes::newline_smaller:
|
|
continue;
|
|
|
|
case control_codes::font_small:
|
|
fontSpriteBase = font::small;
|
|
break;
|
|
|
|
case control_codes::font_large:
|
|
fontSpriteBase = font::large;
|
|
break;
|
|
|
|
case control_codes::font_bold:
|
|
fontSpriteBase = font::medium_bold;
|
|
break;
|
|
|
|
case control_codes::font_regular:
|
|
fontSpriteBase = font::medium_normal;
|
|
break;
|
|
|
|
case control_codes::outline:
|
|
case control_codes::outline_off:
|
|
case control_codes::window_colour_1:
|
|
case control_codes::window_colour_2:
|
|
case control_codes::window_colour_3:
|
|
case 0x10:
|
|
break;
|
|
|
|
case control_codes::inline_sprite_str:
|
|
{
|
|
const uint32_t image = reinterpret_cast<const uint32_t*>(str)[0];
|
|
const uint32_t imageId = image & 0x7FFFF;
|
|
str += 4;
|
|
width += _g1Elements[imageId].width;
|
|
break;
|
|
}
|
|
|
|
default:
|
|
if (chr <= 0x16)
|
|
{
|
|
str += 2;
|
|
}
|
|
else
|
|
{
|
|
str += 4;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return width;
|
|
}
|
|
|
|
static void setTextColours(palette_index_t pal1, palette_index_t pal2, palette_index_t pal3)
|
|
{
|
|
if ((_currentFontFlags & text_draw_flags::inset) != 0)
|
|
return;
|
|
|
|
_textColours[1] = pal1;
|
|
_textColours[2] = palette_index::transparent;
|
|
_textColours[3] = palette_index::transparent;
|
|
if ((_currentFontFlags & text_draw_flags::outline) != 0)
|
|
{
|
|
_textColours[2] = pal2;
|
|
_textColours[3] = pal3;
|
|
}
|
|
}
|
|
|
|
static void setTextColour(int colour)
|
|
{
|
|
auto el = &_g1Elements[image_ids::text_palette];
|
|
setTextColours(el->offset[colour * 4 + 0], el->offset[colour * 4 + 1], el->offset[colour * 4 + 2]);
|
|
}
|
|
|
|
// 0x00451189
|
|
static gfx::point_t loopNewline(drawpixelinfo_t* context, gfx::point_t origin, uint8_t* str)
|
|
{
|
|
gfx::point_t pos = origin;
|
|
while (true)
|
|
{
|
|
// When offscreen in y dimension don't draw text
|
|
// In original this check only performed if pos.y updated instead of every loop
|
|
bool offscreen = true;
|
|
if (pos.y + 19 > context->y)
|
|
{
|
|
if (context->y + context->height > pos.y)
|
|
{
|
|
offscreen = false;
|
|
}
|
|
}
|
|
uint8_t chr = *str;
|
|
str++;
|
|
|
|
switch (chr)
|
|
{
|
|
case '\0':
|
|
return pos;
|
|
|
|
case control_codes::adjust_palette:
|
|
// This control character does not appear in the localisation files
|
|
assert(false);
|
|
str++;
|
|
break;
|
|
|
|
case control_codes::newline_smaller:
|
|
pos.x = origin.x;
|
|
if (_currentFontSpriteBase == font::medium_normal || _currentFontSpriteBase == font::medium_bold)
|
|
{
|
|
pos.y += 5;
|
|
}
|
|
else if (_currentFontSpriteBase == font::small)
|
|
{
|
|
pos.y += 3;
|
|
}
|
|
else if (_currentFontSpriteBase == font::large)
|
|
{
|
|
pos.y += 9;
|
|
}
|
|
break;
|
|
|
|
case control_codes::newline:
|
|
pos.x = origin.x;
|
|
if (_currentFontSpriteBase == font::medium_normal || _currentFontSpriteBase == font::medium_bold)
|
|
{
|
|
pos.y += 10;
|
|
}
|
|
else if (_currentFontSpriteBase == font::small)
|
|
{
|
|
pos.y += 6;
|
|
}
|
|
else if (_currentFontSpriteBase == font::large)
|
|
{
|
|
pos.y += 18;
|
|
}
|
|
break;
|
|
|
|
case control_codes::move_x:
|
|
{
|
|
uint8_t offset = *str;
|
|
str++;
|
|
pos.x = origin.x + offset;
|
|
|
|
break;
|
|
}
|
|
|
|
case control_codes::newline_x_y:
|
|
{
|
|
uint8_t offset = *str;
|
|
str++;
|
|
pos.x = origin.x + offset;
|
|
|
|
offset = *str;
|
|
str++;
|
|
pos.y = origin.y + offset;
|
|
|
|
break;
|
|
}
|
|
|
|
case control_codes::font_small:
|
|
_currentFontSpriteBase = font::small;
|
|
break;
|
|
case control_codes::font_large:
|
|
_currentFontSpriteBase = font::large;
|
|
break;
|
|
case control_codes::font_regular:
|
|
_currentFontSpriteBase = font::medium_normal;
|
|
break;
|
|
case control_codes::font_bold:
|
|
_currentFontSpriteBase = font::medium_bold;
|
|
break;
|
|
case control_codes::outline:
|
|
_currentFontFlags = _currentFontFlags | text_draw_flags::outline;
|
|
break;
|
|
case control_codes::outline_off:
|
|
_currentFontFlags = _currentFontFlags & ~text_draw_flags::outline;
|
|
break;
|
|
case control_codes::window_colour_1:
|
|
{
|
|
int hue = _windowColours[0];
|
|
setTextColours(colour::getShade(hue, 7), palette_index::index_0A, palette_index::index_0A);
|
|
break;
|
|
}
|
|
case control_codes::window_colour_2:
|
|
{
|
|
int hue = _windowColours[1];
|
|
setTextColours(colour::getShade(hue, 9), palette_index::index_0A, palette_index::index_0A);
|
|
break;
|
|
}
|
|
case control_codes::window_colour_3:
|
|
{
|
|
int hue = _windowColours[2];
|
|
setTextColours(colour::getShade(hue, 9), palette_index::index_0A, palette_index::index_0A);
|
|
break;
|
|
}
|
|
|
|
case control_codes::inline_sprite_str:
|
|
{
|
|
uint32_t image = ((uint32_t*)str)[0];
|
|
uint32_t imageId = image & 0x7FFFF;
|
|
str += 4;
|
|
|
|
if ((_currentFontFlags & text_draw_flags::inset) != 0)
|
|
{
|
|
gfx::drawImageSolid(context, pos.x, pos.y, imageId, _textColours[3]);
|
|
gfx::drawImageSolid(context, pos.x + 1, pos.y + 1, imageId, _textColours[1]);
|
|
}
|
|
else
|
|
{
|
|
gfx::drawImage(context, pos.x, pos.y, image);
|
|
}
|
|
|
|
pos.x += _g1Elements[imageId].width;
|
|
break;
|
|
}
|
|
|
|
case control_codes::colour_black:
|
|
setTextColour(0);
|
|
break;
|
|
|
|
case control_codes::colour_grey:
|
|
setTextColour(1);
|
|
break;
|
|
|
|
case control_codes::colour_white:
|
|
setTextColour(2);
|
|
break;
|
|
|
|
case control_codes::colour_red:
|
|
setTextColour(3);
|
|
break;
|
|
|
|
case control_codes::colour_green:
|
|
setTextColour(4);
|
|
break;
|
|
|
|
case control_codes::colour_yellow:
|
|
setTextColour(5);
|
|
break;
|
|
|
|
case control_codes::colour_topaz:
|
|
setTextColour(6);
|
|
break;
|
|
|
|
case control_codes::colour_celadon:
|
|
setTextColour(7);
|
|
break;
|
|
|
|
case control_codes::colour_babyblue:
|
|
setTextColour(8);
|
|
break;
|
|
|
|
case control_codes::colour_palelavender:
|
|
setTextColour(9);
|
|
break;
|
|
|
|
case control_codes::colour_palegold:
|
|
setTextColour(10);
|
|
break;
|
|
|
|
case control_codes::colour_lightpink:
|
|
setTextColour(11);
|
|
break;
|
|
|
|
case control_codes::colour_pearlaqua:
|
|
setTextColour(12);
|
|
break;
|
|
|
|
case control_codes::colour_palesilver:
|
|
setTextColour(13);
|
|
break;
|
|
|
|
default:
|
|
if (!offscreen)
|
|
{
|
|
// When offscreen in the y dimension there is no requirement to keep pos.x correct
|
|
if (chr >= 32)
|
|
{
|
|
gfx::drawImagePaletteSet(context, pos.x, pos.y, 1116 + chr - 32 + _currentFontSpriteBase, _textColours);
|
|
pos.x += _characterWidths[chr - 32 + _currentFontSpriteBase];
|
|
}
|
|
else
|
|
{
|
|
// Unhandled control code
|
|
assert(false);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
return pos;
|
|
}
|
|
|
|
/**
|
|
* 0x00451025
|
|
*
|
|
* @param x @<cx>
|
|
* @param y @<dx>
|
|
* @param colour @<al>
|
|
* @param context @<edi>
|
|
* @param text @<esi>
|
|
*/
|
|
gfx::point_t drawString(drawpixelinfo_t* context, int16_t x, int16_t y, uint8_t colour, void* str)
|
|
{
|
|
// 0x00E04348, 0x00E0434A
|
|
gfx::point_t origin = { x, y };
|
|
|
|
if (colour == format_flags::fe)
|
|
{
|
|
return loopNewline(context, origin, (uint8_t*)str);
|
|
}
|
|
|
|
if (colour == format_flags::fd)
|
|
{
|
|
_currentFontFlags = 0;
|
|
setTextColour(0);
|
|
return loopNewline(context, origin, (uint8_t*)str);
|
|
}
|
|
|
|
if (x >= context->x + context->width)
|
|
return origin;
|
|
|
|
if (x < context->x - 1280)
|
|
return origin;
|
|
|
|
if (y >= context->y + context->height)
|
|
return origin;
|
|
|
|
if (y < context->y - 90)
|
|
return origin;
|
|
|
|
if (colour == format_flags::ff)
|
|
{
|
|
return loopNewline(context, origin, (uint8_t*)str);
|
|
}
|
|
|
|
_currentFontFlags = 0;
|
|
if (_currentFontSpriteBase == font::m1)
|
|
{
|
|
_currentFontSpriteBase = font::medium_bold;
|
|
_currentFontFlags = _currentFontFlags | text_draw_flags::dark;
|
|
}
|
|
else if (_currentFontSpriteBase == font::m2)
|
|
{
|
|
_currentFontSpriteBase = font::medium_bold;
|
|
_currentFontFlags = _currentFontFlags | text_draw_flags::dark;
|
|
_currentFontFlags = _currentFontFlags | text_draw_flags::extra_dark;
|
|
}
|
|
|
|
_textColours[0] = palette_index::transparent;
|
|
_textColours[1] = colour::getShade(colour::dark_purple, 5);
|
|
_textColours[2] = colour::getShade(colour::bright_pink, 5);
|
|
_textColours[3] = colour::getShade(colour::light_blue, 5);
|
|
|
|
if (colour & format_flags::textflag_5)
|
|
{
|
|
colour &= ~format_flags::textflag_5;
|
|
_currentFontFlags = _currentFontFlags | text_draw_flags::outline;
|
|
}
|
|
|
|
if (colour & format_flags::textflag_6)
|
|
{
|
|
colour &= ~format_flags::textflag_6;
|
|
_currentFontFlags = _currentFontFlags | text_draw_flags::inset;
|
|
}
|
|
|
|
if ((_currentFontFlags & text_draw_flags::inset) != 0)
|
|
{
|
|
if ((_currentFontFlags & text_draw_flags::dark) != 0 && (_currentFontFlags & text_draw_flags::extra_dark) != 0)
|
|
{
|
|
_textColours[1] = colour::getShade(colour, 2);
|
|
_textColours[2] = palette_index::transparent;
|
|
_textColours[3] = colour::getShade(colour, 4);
|
|
}
|
|
else if ((_currentFontFlags & text_draw_flags::dark) != 0)
|
|
{
|
|
_textColours[1] = colour::getShade(colour, 3);
|
|
_textColours[2] = palette_index::transparent;
|
|
_textColours[3] = colour::getShade(colour, 5);
|
|
}
|
|
else
|
|
{
|
|
_textColours[1] = colour::getShade(colour, 4);
|
|
_textColours[2] = palette_index::transparent;
|
|
_textColours[3] = colour::getShade(colour, 6);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
setTextColours(colour::getShade(colour, 9), palette_index::index_0A, palette_index::index_0A);
|
|
}
|
|
|
|
return loopNewline(context, origin, (uint8_t*)str);
|
|
}
|
|
|
|
// 0x00495224
|
|
// al: colour
|
|
// bp: width
|
|
// bx: string id
|
|
// cx: x
|
|
// dx: y
|
|
// esi: args
|
|
// edi: dpi
|
|
int16_t drawString_495224(
|
|
drawpixelinfo_t& dpi,
|
|
int16_t x,
|
|
int16_t y,
|
|
int16_t width,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.al = colour;
|
|
regs.bx = stringId;
|
|
regs.bp = width;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.esi = (int32_t)args;
|
|
regs.edi = (int32_t)&dpi;
|
|
call(0x00495224, regs);
|
|
|
|
return regs.dx;
|
|
}
|
|
|
|
// 0x00494B3F
|
|
// al: colour
|
|
// bx: string id
|
|
// cx: x
|
|
// dx: y
|
|
// esi: args
|
|
// edi: dpi
|
|
void drawString_494B3F(
|
|
drawpixelinfo_t& dpi,
|
|
int16_t x,
|
|
int16_t y,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.al = colour;
|
|
regs.bx = stringId;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.esi = (int32_t)args;
|
|
regs.edi = (int32_t)&dpi;
|
|
call(0x00494B3F, regs);
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param dpi @<edi>
|
|
* @param origin {x @<cx>, y @<dx>}
|
|
* @param colour @<al>
|
|
* @param stringId @<bx>
|
|
* @param args @<edi>
|
|
*/
|
|
void drawString_494B3F(
|
|
drawpixelinfo_t& dpi,
|
|
point_t* origin,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.al = colour;
|
|
regs.bx = stringId;
|
|
regs.cx = origin->x;
|
|
regs.dx = origin->y;
|
|
regs.esi = (int32_t)args;
|
|
regs.edi = (int32_t)&dpi;
|
|
call(0x00494B3F, regs);
|
|
|
|
origin->x = regs.cx;
|
|
origin->y = regs.dx;
|
|
}
|
|
|
|
// 0x00494BBF
|
|
// al: colour
|
|
// bx: string id
|
|
// cx: x
|
|
// dx: y
|
|
// esi: args
|
|
// edi: dpi
|
|
// bp: width
|
|
void drawString_494BBF(
|
|
drawpixelinfo_t& dpi,
|
|
int16_t x,
|
|
int16_t y,
|
|
int16_t width,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.al = colour;
|
|
regs.bx = stringId;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.esi = (int32_t)args;
|
|
regs.edi = (int32_t)&dpi;
|
|
regs.bp = width;
|
|
call(0x00494BBF, regs);
|
|
}
|
|
|
|
// 0x00494C78
|
|
// al: colour
|
|
// bx: string id
|
|
// cx: x
|
|
// dx: y
|
|
// esi: args
|
|
// edi: dpi
|
|
void drawString_494C78(
|
|
drawpixelinfo_t& dpi,
|
|
int16_t x,
|
|
int16_t y,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.al = colour;
|
|
regs.bx = stringId;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.esi = (int32_t)args;
|
|
regs.edi = (int32_t)&dpi;
|
|
call(0x00494C78, regs);
|
|
}
|
|
|
|
// 0x00494CB2
|
|
// al: colour
|
|
// bx: string id
|
|
// cx: x
|
|
// dx: y
|
|
// esi: args
|
|
// edi: dpi
|
|
void drawStringUnderline(
|
|
drawpixelinfo_t& dpi,
|
|
int16_t x,
|
|
int16_t y,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.al = colour;
|
|
regs.bx = stringId;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.esi = (int32_t)args;
|
|
regs.edi = (int32_t)&dpi;
|
|
call(0x00494CB2, regs);
|
|
}
|
|
|
|
// 0x00494D78
|
|
// al: colour
|
|
// bx: string id
|
|
// cx: x
|
|
// dx: y
|
|
// esi: args
|
|
// edi: dpi
|
|
void drawStringLeftUnderline(
|
|
drawpixelinfo_t& dpi,
|
|
int16_t x,
|
|
int16_t y,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.al = colour;
|
|
regs.bx = stringId;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.esi = (int32_t)args;
|
|
regs.edi = (int32_t)&dpi;
|
|
call(0x00494D78, regs);
|
|
}
|
|
|
|
// 0x00494DE8
|
|
// al: colour
|
|
// bx: string id
|
|
// cx: x
|
|
// dx: y
|
|
// esi: args
|
|
// edi: dpi
|
|
void drawStringCentred(
|
|
drawpixelinfo_t& dpi,
|
|
int16_t x,
|
|
int16_t y,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.al = colour;
|
|
regs.bx = stringId;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.esi = (int32_t)args;
|
|
regs.edi = (int32_t)&dpi;
|
|
call(0x00494DE8, regs);
|
|
}
|
|
|
|
// 0x00494C36
|
|
// al: colour
|
|
// bx: string id
|
|
// bp: width
|
|
// cx: x
|
|
// dx: y
|
|
// esi: args
|
|
// edi: dpi
|
|
void drawStringCentredClipped(
|
|
drawpixelinfo_t& dpi,
|
|
int16_t x,
|
|
int16_t y,
|
|
int16_t width,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.edi = (int32_t)&dpi;
|
|
regs.esi = (int32_t)args;
|
|
regs.ebx = stringId;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.al = colour;
|
|
regs.bp = width;
|
|
call(0x00494C36, regs);
|
|
}
|
|
|
|
/**
|
|
* 0x00494ECF
|
|
*
|
|
* @param context @<edi>
|
|
* @param origin {x @<cx>, y @<dx>}
|
|
* @param width @<bp>
|
|
* @param colour @<al>
|
|
* @param stringId @<bx>
|
|
* @param args @<esi>
|
|
*/
|
|
void drawStringCentredWrapped(
|
|
drawpixelinfo_t* context,
|
|
point_t* origin,
|
|
uint16_t width,
|
|
uint8_t colour,
|
|
string_id stringId,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.edi = (uintptr_t)context;
|
|
regs.esi = (uintptr_t)args;
|
|
regs.cx = origin->x;
|
|
regs.dx = origin->y;
|
|
regs.bp = width;
|
|
regs.al = colour;
|
|
regs.bx = stringId;
|
|
call(0x00494ECF, regs);
|
|
|
|
origin->x = regs.cx;
|
|
origin->y = regs.dx;
|
|
}
|
|
|
|
// 0x00494E33
|
|
// al: colour
|
|
// bx: string id
|
|
// bp: width
|
|
// cx: x
|
|
// dx: y
|
|
// esi: args
|
|
// edi: dpi
|
|
void drawStringCentredRaw(
|
|
drawpixelinfo_t& dpi,
|
|
int16_t x,
|
|
int16_t y,
|
|
int16_t width,
|
|
uint8_t colour,
|
|
const void* args)
|
|
{
|
|
registers regs;
|
|
regs.edi = (int32_t)&dpi;
|
|
regs.esi = (int32_t)args;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.al = colour;
|
|
regs.bp = width;
|
|
call(0x00494E33, regs);
|
|
}
|
|
|
|
// 0x00495715
|
|
// @param buffer @<esi>
|
|
// @return width @<cx>
|
|
uint16_t getStringWidthNewLined(const char* buffer)
|
|
{
|
|
registers regs;
|
|
regs.esi = (uintptr_t)buffer;
|
|
call(0x00495715, regs);
|
|
return regs.cx;
|
|
}
|
|
|
|
std::pair<uint16_t, uint16_t> wrapString(const char* buffer, uint16_t stringWidth)
|
|
{
|
|
// gfx_wrap_string
|
|
registers regs;
|
|
regs.esi = (uintptr_t)buffer;
|
|
regs.di = stringWidth;
|
|
call(0x00495301, regs);
|
|
|
|
return std::make_pair(regs.cx, regs.di);
|
|
}
|
|
|
|
// 0x004474BA
|
|
static void drawRectImpl(gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour)
|
|
{
|
|
registers regs;
|
|
regs.ax = left;
|
|
regs.bx = right;
|
|
regs.cx = top;
|
|
regs.dx = bottom;
|
|
regs.ebp = colour;
|
|
regs.edi = (uint32_t)dpi;
|
|
call(0x004474BA, regs);
|
|
}
|
|
|
|
void fillRect(gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour)
|
|
{
|
|
drawRectImpl(dpi, left, top, right, bottom, colour);
|
|
}
|
|
|
|
void drawRect(gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour)
|
|
{
|
|
// This makes the function signature more like a drawing application
|
|
drawRectImpl(dpi, x, y, x + dx - 1, y + dy - 1, colour);
|
|
}
|
|
|
|
void fillRectInset(gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour, uint8_t flags)
|
|
{
|
|
registers regs;
|
|
regs.ax = left;
|
|
regs.bx = right;
|
|
regs.cx = top;
|
|
regs.dx = bottom;
|
|
regs.ebp = colour;
|
|
regs.edi = (uint32_t)dpi;
|
|
regs.si = flags;
|
|
call(0x004C58C7, regs);
|
|
}
|
|
|
|
void drawRectInset(gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint16_t dx, uint16_t dy, uint32_t colour, uint8_t flags)
|
|
{
|
|
// This makes the function signature more like a drawing application
|
|
fillRectInset(dpi, x, y, x + dx - 1, y + dy - 1, colour, flags);
|
|
}
|
|
|
|
// 0x00452DA4
|
|
void drawLine(gfx::drawpixelinfo_t* dpi, int16_t left, int16_t top, int16_t right, int16_t bottom, uint32_t colour)
|
|
{
|
|
registers regs;
|
|
regs.ax = left;
|
|
regs.bx = top;
|
|
regs.cx = right;
|
|
regs.dx = bottom;
|
|
regs.ebp = colour;
|
|
regs.edi = (uint32_t)dpi;
|
|
call(0x00452DA4, regs);
|
|
}
|
|
|
|
// 0x004CD406
|
|
void invalidateScreen()
|
|
{
|
|
setDirtyBlocks(0, 0, ui::width(), ui::height());
|
|
}
|
|
|
|
drawing::SoftwareDrawingEngine* engine;
|
|
|
|
/**
|
|
* 0x004C5C69
|
|
*
|
|
* @param left @<ax>
|
|
* @param top @<bx>
|
|
* @param right @<dx>
|
|
* @param bottom @<bp>
|
|
*/
|
|
void setDirtyBlocks(int32_t left, int32_t top, int32_t right, int32_t bottom)
|
|
{
|
|
if (engine == nullptr)
|
|
engine = new drawing::SoftwareDrawingEngine();
|
|
|
|
engine->setDirtyBlocks(left, top, right, bottom);
|
|
}
|
|
|
|
// 0x004C5CFA
|
|
void drawDirtyBlocks()
|
|
{
|
|
if (engine == nullptr)
|
|
engine = new drawing::SoftwareDrawingEngine();
|
|
|
|
engine->drawDirtyBlocks();
|
|
}
|
|
|
|
loco_global<char[512], 0x0112CC04> byte_112CC04;
|
|
loco_global<char[512], 0x0112CE04> byte_112CE04;
|
|
|
|
// 0x004CF63B
|
|
void render()
|
|
{
|
|
if (engine == nullptr)
|
|
engine = new drawing::SoftwareDrawingEngine();
|
|
|
|
char backup1[512] = { 0 };
|
|
char backup2[512] = { 0 };
|
|
|
|
std::memcpy(backup1, byte_112CC04, 512);
|
|
std::memcpy(backup2, byte_112CE04, 512);
|
|
|
|
if (ui::dirtyBlocksInitialised())
|
|
{
|
|
engine->drawDirtyBlocks();
|
|
}
|
|
|
|
if (input::hasFlag(input::input_flags::flag5))
|
|
{
|
|
call(0x004072EC); // NOP on _NO_LOCO_WIN32_
|
|
}
|
|
else
|
|
{
|
|
ui::processMessages();
|
|
}
|
|
|
|
if (addr<0x005252AC, uint32_t>() != 0)
|
|
{
|
|
// sub_4058F5();
|
|
}
|
|
|
|
std::memcpy(byte_112CC04, backup1, 512);
|
|
std::memcpy(byte_112CE04, backup2, 512);
|
|
}
|
|
|
|
void redrawScreenRect(Rect rect)
|
|
{
|
|
engine->drawRect(rect);
|
|
}
|
|
|
|
/**
|
|
* 0x004C5DD5
|
|
* rct2: window_draw_all
|
|
*
|
|
* @param left @<ax>
|
|
* @param top @<bx>
|
|
* @param right @<dx>
|
|
* @param bottom @<bp>
|
|
*/
|
|
void redrawScreenRect(int16_t left, int16_t top, int16_t right, int16_t bottom)
|
|
{
|
|
redrawScreenRect(Rect::fromLTRB(left, top, right, bottom));
|
|
}
|
|
|
|
void drawImage(gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint32_t image)
|
|
{
|
|
registers regs;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.ebx = image;
|
|
regs.edi = (uint32_t)dpi;
|
|
call(0x00448C79, regs);
|
|
}
|
|
|
|
uint32_t recolour(uint32_t image)
|
|
{
|
|
return (1 << 29) | image;
|
|
}
|
|
|
|
uint32_t recolour(uint32_t image, uint8_t colour)
|
|
{
|
|
return (1 << 29) | (colour << 19) | image;
|
|
}
|
|
|
|
loco_global<uint8_t*, 0x0050B860> _50B860;
|
|
loco_global<uint32_t, 0x00E04324> _E04324;
|
|
|
|
void drawImageSolid(gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint32_t image, uint8_t palette_index)
|
|
{
|
|
uint8_t palette[256];
|
|
memset(palette, palette_index, 256);
|
|
palette[0] = 0;
|
|
|
|
drawImagePaletteSet(dpi, x, y, image, palette);
|
|
}
|
|
|
|
void drawImagePaletteSet(gfx::drawpixelinfo_t* dpi, int16_t x, int16_t y, uint32_t image, uint8_t* palette)
|
|
{
|
|
_50B860 = palette;
|
|
_E04324 = 0x20000000;
|
|
registers regs;
|
|
regs.cx = x;
|
|
regs.dx = y;
|
|
regs.ebx = image;
|
|
regs.edi = (uint32_t)dpi;
|
|
call(0x00448D90, regs);
|
|
}
|
|
|
|
bool clipDrawpixelinfo(gfx::drawpixelinfo_t** dst, gfx::drawpixelinfo_t* src, int16_t x, int16_t y, int16_t width, int16_t height)
|
|
{
|
|
registers regs;
|
|
regs.ax = x;
|
|
regs.bx = width;
|
|
regs.edi = (int32_t)src;
|
|
regs.dx = height;
|
|
regs.cx = y;
|
|
call(0x4cec50, regs);
|
|
*dst = (gfx::drawpixelinfo_t*)regs.edi;
|
|
|
|
return *dst != nullptr;
|
|
}
|
|
|
|
g1_element* getG1Element(uint32_t id)
|
|
{
|
|
if (id < _g1Elements.size())
|
|
{
|
|
return &_g1Elements[id];
|
|
}
|
|
return nullptr;
|
|
}
|
|
}
|