1153 lines
35 KiB
C++
Executable File
1153 lines
35 KiB
C++
Executable File
#include "Win32.h"
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <codecvt>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <limits>
|
|
#include <stdexcept>
|
|
#include <vector>
|
|
|
|
#ifdef _WIN32
|
|
#include "../../resources/Resource.h"
|
|
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#define WIN32_LEAN_AND_MEAN
|
|
#include <shlobj.h>
|
|
#include <windows.h>
|
|
|
|
// `small` is used as a type in `windows.h`
|
|
#undef small
|
|
#endif
|
|
|
|
#ifndef _LOCO_WIN32_
|
|
#include <SDL2/SDL.h>
|
|
#pragma warning(disable : 4121) // alignment of a member was sensitive to packing
|
|
#include <SDL2/SDL_syswm.h>
|
|
#pragma warning(default : 4121) // alignment of a member was sensitive to packing
|
|
#endif
|
|
|
|
#include "Config.h"
|
|
#include "Console.h"
|
|
#include "GameCommands.h"
|
|
#include "Gui.h"
|
|
#include "Input.h"
|
|
#include "Intro.h"
|
|
#include "MultiPlayer.h"
|
|
#include "OpenLoco.h"
|
|
#include "Tutorial.h"
|
|
#include "Ui.h"
|
|
#include "graphics/gfx.h"
|
|
#include "interop/interop.hpp"
|
|
#include "ui/WindowManager.h"
|
|
#include "utility/string.hpp"
|
|
|
|
using namespace openloco::interop;
|
|
using namespace openloco::game_commands;
|
|
|
|
namespace openloco::ui
|
|
{
|
|
#ifdef _LOCO_WIN32_
|
|
constexpr auto WINDOW_CLASS_NAME = "Chris Sawyer's Locomotion";
|
|
constexpr auto WINDOW_TITLE = "OpenLoco";
|
|
#endif // _WIN32
|
|
|
|
#pragma pack(push, 1)
|
|
|
|
struct palette_entry_t
|
|
{
|
|
uint8_t b, g, r, a;
|
|
};
|
|
|
|
struct sdl_window_desc
|
|
{
|
|
int32_t x{};
|
|
int32_t y{};
|
|
int32_t width{};
|
|
int32_t height{};
|
|
int32_t flags{};
|
|
};
|
|
|
|
#pragma pack(pop)
|
|
|
|
using set_palette_func = void (*)(const palette_entry_t* palette, int32_t index, int32_t count);
|
|
|
|
#ifdef _WIN32
|
|
loco_global<void*, 0x00525320> _hwnd;
|
|
#endif // _WIN32
|
|
loco_global<screen_info_t, 0x0050B884> screen_info;
|
|
static loco_global<uint16_t, 0x00523390> _toolWindowNumber;
|
|
static loco_global<ui::WindowType, 0x00523392> _toolWindowType;
|
|
static loco_global<uint16_t, 0x00523394> _toolWidgetIdx;
|
|
loco_global<set_palette_func, 0x0052524C> set_palette_callback;
|
|
static loco_global<uint32_t, 0x00525E28> _525E28;
|
|
loco_global<uint8_t[256], 0x01140740> _keyboard_state;
|
|
|
|
bool _resolutionsAllowAnyAspectRatio = false;
|
|
std::vector<Resolution> _fsResolutions;
|
|
|
|
static SDL_Window* window;
|
|
static SDL_Surface* surface;
|
|
static SDL_Surface* RGBASurface;
|
|
static SDL_Palette* palette;
|
|
static std::vector<SDL_Cursor*> _cursors;
|
|
|
|
static void setWindowIcon();
|
|
static void update(int32_t width, int32_t height);
|
|
static void resize(int32_t width, int32_t height);
|
|
static int32_t convertSdlKeycodeToWindows(int32_t keyCode);
|
|
static config::resolution_t getDisplayResolutionByMode(config::screen_mode mode);
|
|
|
|
#if !(defined(__APPLE__) && defined(__MACH__))
|
|
static void toggleFullscreenDesktop();
|
|
#endif
|
|
|
|
#ifdef _WIN32
|
|
void* hwnd()
|
|
{
|
|
return _hwnd;
|
|
}
|
|
#endif // _WIN32
|
|
|
|
int32_t width()
|
|
{
|
|
return screen_info->width;
|
|
}
|
|
|
|
int32_t height()
|
|
{
|
|
return screen_info->height;
|
|
}
|
|
|
|
bool dirtyBlocksInitialised()
|
|
{
|
|
return screen_info->dirty_blocks_initialised != 0;
|
|
}
|
|
|
|
void updatePalette(const palette_entry_t* entries, int32_t index, int32_t count);
|
|
|
|
static sdl_window_desc getWindowDesc(const config::display_config& cfg)
|
|
{
|
|
sdl_window_desc desc;
|
|
desc.x = SDL_WINDOWPOS_CENTERED_DISPLAY(cfg.index);
|
|
desc.y = SDL_WINDOWPOS_CENTERED_DISPLAY(cfg.index);
|
|
desc.width = std::max(640, cfg.window_resolution.width);
|
|
desc.height = std::max(480, cfg.window_resolution.height);
|
|
desc.flags = SDL_WINDOW_RESIZABLE;
|
|
#if !(defined(__APPLE__) && defined(__MACH__))
|
|
switch (cfg.mode)
|
|
{
|
|
case config::screen_mode::window:
|
|
break;
|
|
case config::screen_mode::fullscreen:
|
|
desc.width = cfg.fullscreen_resolution.width;
|
|
desc.height = cfg.fullscreen_resolution.height;
|
|
desc.flags |= SDL_WINDOW_FULLSCREEN;
|
|
break;
|
|
case config::screen_mode::fullscreen_borderless:
|
|
desc.flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
break;
|
|
}
|
|
#endif
|
|
return desc;
|
|
}
|
|
|
|
// 0x00405409
|
|
void createWindow(const config::display_config& cfg)
|
|
{
|
|
#ifdef _LOCO_WIN32_
|
|
_hwnd = CreateWindowExA(
|
|
WS_EX_TOPMOST,
|
|
WINDOW_CLASS_NAME,
|
|
WINDOW_TITLE,
|
|
WS_POPUP | WS_VISIBLE | WS_SYSMENU | WS_CLIPCHILDREN | WS_MAXIMIZE | WS_CLIPSIBLINGS,
|
|
0,
|
|
0,
|
|
GetSystemMetrics(SM_CXSCREEN),
|
|
GetSystemMetrics(SM_CYSCREEN),
|
|
nullptr,
|
|
nullptr,
|
|
(HINSTANCE)hInstance(),
|
|
nullptr);
|
|
#else
|
|
if (SDL_Init(SDL_INIT_VIDEO) < 0)
|
|
{
|
|
throw std::runtime_error("Unable to initialise SDL2 video subsystem.");
|
|
}
|
|
|
|
// Create the window
|
|
auto desc = getWindowDesc(cfg);
|
|
window = SDL_CreateWindow("OpenLoco", desc.x, desc.y, desc.width, desc.height, desc.flags);
|
|
if (window == nullptr)
|
|
{
|
|
throw std::runtime_error("Unable to create SDL2 window.");
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
// Grab the HWND
|
|
SDL_SysWMinfo wmInfo;
|
|
SDL_VERSION(&wmInfo.version);
|
|
if (SDL_GetWindowWMInfo(window, &wmInfo) == SDL_FALSE)
|
|
{
|
|
throw std::runtime_error("Unable to fetch SDL2 window system handle.");
|
|
}
|
|
_hwnd = wmInfo.info.win.window;
|
|
#endif
|
|
|
|
setWindowIcon();
|
|
|
|
// Create a palette for the window
|
|
palette = SDL_AllocPalette(256);
|
|
set_palette_callback = updatePalette;
|
|
|
|
update(desc.width, desc.height);
|
|
#endif
|
|
}
|
|
|
|
static void setWindowIcon()
|
|
{
|
|
#ifdef _WIN32
|
|
auto win32module = GetModuleHandleA("openloco.dll");
|
|
if (win32module != nullptr)
|
|
{
|
|
auto icon = LoadIconA(win32module, MAKEINTRESOURCEA(IDI_ICON));
|
|
if (icon != nullptr)
|
|
{
|
|
auto hwnd = (HWND)*_hwnd;
|
|
if (hwnd != nullptr)
|
|
{
|
|
SendMessageA(hwnd, WM_SETICON, ICON_SMALL, (LPARAM)icon);
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
// 0x0045235D
|
|
void initialise()
|
|
{
|
|
#ifdef _LOCO_WIN32_
|
|
call(0x0045235D);
|
|
#endif
|
|
SDL_RestoreWindow(window);
|
|
}
|
|
|
|
// 0x00452001
|
|
void initialiseCursors()
|
|
{
|
|
_cursors.push_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW));
|
|
_cursors.push_back(nullptr);
|
|
_cursors.push_back(nullptr);
|
|
_cursors.push_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENS));
|
|
_cursors.push_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_HAND));
|
|
_cursors.push_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT));
|
|
_cursors.push_back(SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_SIZENWSE));
|
|
}
|
|
|
|
void disposeCursors()
|
|
{
|
|
for (auto cursor : _cursors)
|
|
{
|
|
SDL_FreeCursor(cursor);
|
|
}
|
|
_cursors.clear();
|
|
}
|
|
|
|
// 0x00407BA3
|
|
// edx: cusor_id
|
|
void setCursor(cursor_id id)
|
|
{
|
|
if (_cursors.size() > 0)
|
|
{
|
|
auto index = (size_t)id;
|
|
if (index >= _cursors.size())
|
|
{
|
|
// Default to cursor 0
|
|
index = 0;
|
|
}
|
|
|
|
auto cursor = _cursors[index];
|
|
if (cursor == nullptr && index != 0)
|
|
{
|
|
// Default to cursor 0
|
|
cursor = _cursors[0];
|
|
}
|
|
SDL_SetCursor(cursor);
|
|
}
|
|
}
|
|
|
|
// 0x00407FCD
|
|
void getCursorPos(int32_t& x, int32_t& y)
|
|
{
|
|
SDL_GetMouseState(&x, &y);
|
|
|
|
auto scale = config::getNew().scale_factor;
|
|
x /= scale;
|
|
y /= scale;
|
|
}
|
|
|
|
// 0x00407FEE
|
|
void setCursorPos(int32_t x, int32_t y)
|
|
{
|
|
auto scale = config::getNew().scale_factor;
|
|
x *= scale;
|
|
y *= scale;
|
|
|
|
SDL_WarpMouseInWindow(window, x, y);
|
|
}
|
|
|
|
void hideCursor()
|
|
{
|
|
SDL_ShowCursor(0);
|
|
}
|
|
|
|
void showCursor()
|
|
{
|
|
SDL_ShowCursor(1);
|
|
}
|
|
|
|
// 0x0040447F
|
|
void initialiseInput()
|
|
{
|
|
call(0x0040447F);
|
|
}
|
|
|
|
// 0x004045C2
|
|
void disposeInput()
|
|
{
|
|
call(0x004045C2);
|
|
}
|
|
|
|
// 0x004524C1
|
|
void update()
|
|
{
|
|
#ifdef _LOCO_WIN32_
|
|
call(0x004524C1);
|
|
#endif
|
|
}
|
|
|
|
void update(int32_t width, int32_t height)
|
|
{
|
|
// Scale the width and height by configured scale factor
|
|
auto scale_factor = config::getNew().scale_factor;
|
|
width = (int32_t)(width / scale_factor);
|
|
height = (int32_t)(height / scale_factor);
|
|
|
|
int32_t widthShift = 6;
|
|
int16_t blockWidth = 1 << widthShift;
|
|
int32_t heightShift = 3;
|
|
int16_t blockHeight = 1 << heightShift;
|
|
|
|
if (surface != nullptr)
|
|
{
|
|
SDL_FreeSurface(surface);
|
|
}
|
|
if (RGBASurface != nullptr)
|
|
{
|
|
SDL_FreeSurface(RGBASurface);
|
|
}
|
|
|
|
surface = SDL_CreateRGBSurface(0, width, height, 8, 0, 0, 0, 0);
|
|
|
|
RGBASurface = SDL_CreateRGBSurface(0, width, height, 32, 0, 0, 0, 0);
|
|
SDL_SetSurfaceBlendMode(RGBASurface, SDL_BLENDMODE_NONE);
|
|
|
|
SDL_SetSurfacePalette(surface, palette);
|
|
|
|
int32_t pitch = surface->pitch;
|
|
|
|
gfx::drawpixelinfo_t dpi{};
|
|
dpi.bits = new uint8_t[surface->pitch * height];
|
|
dpi.width = width;
|
|
dpi.height = height;
|
|
dpi.pitch = pitch - width;
|
|
|
|
screen_info->dpi = dpi;
|
|
screen_info->width = width;
|
|
screen_info->height = height;
|
|
screen_info->width_2 = width;
|
|
screen_info->height_2 = height;
|
|
screen_info->width_3 = width;
|
|
screen_info->height_3 = height;
|
|
screen_info->dirty_block_width = blockWidth;
|
|
screen_info->dirty_block_height = blockHeight;
|
|
screen_info->dirty_block_columns = (width / blockWidth) + 1;
|
|
screen_info->dirty_block_rows = (height / blockHeight) + 1;
|
|
screen_info->dirty_block_column_shift = widthShift;
|
|
screen_info->dirty_block_row_shift = heightShift;
|
|
screen_info->dirty_blocks_initialised = 1;
|
|
}
|
|
|
|
static void positionChanged(int32_t x, int32_t y)
|
|
{
|
|
auto displayIndex = SDL_GetWindowDisplayIndex(window);
|
|
|
|
auto& cfg = config::getNew().display;
|
|
if (cfg.index != displayIndex)
|
|
{
|
|
cfg.index = displayIndex;
|
|
config::writeNewConfig();
|
|
}
|
|
}
|
|
|
|
void resize(int32_t width, int32_t height)
|
|
{
|
|
update(width, height);
|
|
gui::resize();
|
|
gfx::invalidateScreen();
|
|
|
|
// Save window size to config if NOT maximized
|
|
auto wf = SDL_GetWindowFlags(window);
|
|
if (!(wf & SDL_WINDOW_MAXIMIZED) && !(wf & SDL_WINDOW_FULLSCREEN))
|
|
{
|
|
auto& cfg = config::getNew().display;
|
|
cfg.window_resolution = { width, height };
|
|
config::writeNewConfig();
|
|
}
|
|
|
|
auto options_window = WindowManager::find(WindowType::options);
|
|
if (options_window != nullptr)
|
|
options_window->moveToCentre();
|
|
}
|
|
|
|
void triggerResize()
|
|
{
|
|
int width, height;
|
|
SDL_GetWindowSize(window, &width, &height);
|
|
resize(width, height);
|
|
}
|
|
|
|
void render()
|
|
{
|
|
if (window != nullptr && surface != nullptr)
|
|
{
|
|
// Lock the surface before setting its pixels
|
|
if (SDL_MUSTLOCK(surface))
|
|
{
|
|
if (SDL_LockSurface(surface) < 0)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Copy pixels from the virtual screen buffer to the surface
|
|
auto& dpi = gfx::screenDpi();
|
|
if (dpi.bits != nullptr)
|
|
{
|
|
std::memcpy(surface->pixels, dpi.bits, surface->pitch * surface->h);
|
|
}
|
|
|
|
// Unlock the surface
|
|
if (SDL_MUSTLOCK(surface))
|
|
{
|
|
SDL_UnlockSurface(surface);
|
|
}
|
|
|
|
auto scale_factor = config::getNew().scale_factor;
|
|
if (scale_factor == 1 || scale_factor <= 0)
|
|
{
|
|
if (SDL_BlitSurface(surface, nullptr, SDL_GetWindowSurface(window), nullptr))
|
|
{
|
|
console::error("SDL_BlitSurface %s", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// first blit to rgba surface to change the pixel format
|
|
if (SDL_BlitSurface(surface, nullptr, RGBASurface, nullptr))
|
|
{
|
|
console::error("SDL_BlitSurface %s", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
// then scale to window size. Without changing to RGBA first, SDL complains
|
|
// about blit configurations being incompatible.
|
|
if (SDL_BlitScaled(RGBASurface, nullptr, SDL_GetWindowSurface(window), nullptr))
|
|
{
|
|
console::error("SDL_BlitScaled %s", SDL_GetError());
|
|
exit(1);
|
|
}
|
|
}
|
|
|
|
SDL_UpdateWindowSurface(window);
|
|
}
|
|
}
|
|
|
|
void updatePalette(const palette_entry_t* entries, int32_t index, int32_t count)
|
|
{
|
|
SDL_Color base[256];
|
|
for (int i = 0; i < 256; i++)
|
|
{
|
|
auto& src = entries[i];
|
|
base[i].r = src.r;
|
|
base[i].g = src.g;
|
|
base[i].b = src.b;
|
|
base[i].a = 0;
|
|
}
|
|
SDL_SetPaletteColors(palette, base, 0, 256);
|
|
}
|
|
|
|
static void enqueueText(const char* text)
|
|
{
|
|
if (text != nullptr && text[0] != '\0')
|
|
{
|
|
#pragma pack(push, 1)
|
|
struct key_queue_item_t
|
|
{
|
|
uint32_t a;
|
|
uint32_t b;
|
|
};
|
|
#pragma pack(pop)
|
|
auto queue = (key_queue_item_t*)0x0113E300;
|
|
auto index = addr<0x00525388, uint32_t>();
|
|
queue[index].b = text[0];
|
|
}
|
|
}
|
|
|
|
// 0x00406FBA
|
|
static void enqueueKey(uint32_t keycode)
|
|
{
|
|
((void (*)(uint32_t))(0x00406FBA))(keycode);
|
|
|
|
switch (keycode)
|
|
{
|
|
case SDLK_RETURN:
|
|
case SDLK_BACKSPACE:
|
|
case SDLK_DELETE:
|
|
{
|
|
char c[] = { (char)keycode, '\0' };
|
|
enqueueText(c);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static int32_t convertSdlScancodeToDirectInput(int32_t scancode)
|
|
{
|
|
switch (scancode)
|
|
{
|
|
case SDL_SCANCODE_UP: return DIK_UP;
|
|
case SDL_SCANCODE_LEFT: return DIK_LEFT;
|
|
case SDL_SCANCODE_RIGHT: return DIK_RIGHT;
|
|
case SDL_SCANCODE_DOWN: return DIK_DOWN;
|
|
case SDL_SCANCODE_LSHIFT: return DIK_LSHIFT;
|
|
case SDL_SCANCODE_RSHIFT: return DIK_RSHIFT;
|
|
case SDL_SCANCODE_LCTRL: return DIK_LCONTROL;
|
|
case SDL_SCANCODE_RCTRL: return DIK_RCONTROL;
|
|
case SDL_SCANCODE_INSERT:
|
|
return DIK_INSERT;
|
|
|
|
// Simulate INSERT for smaller keyboards
|
|
case SDL_SCANCODE_LALT: return DIK_INSERT;
|
|
case SDL_SCANCODE_RALT: return DIK_INSERT;
|
|
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
static int32_t convertSdlKeycodeToWindows(int32_t keyCode)
|
|
{
|
|
switch (keyCode)
|
|
{
|
|
case SDLK_PAUSE: return VK_PAUSE;
|
|
case SDLK_PAGEUP: return VK_PRIOR;
|
|
case SDLK_PAGEDOWN: return VK_NEXT;
|
|
case SDLK_END: return VK_END;
|
|
case SDLK_HOME: return VK_HOME;
|
|
case SDLK_LEFT: return VK_LEFT;
|
|
case SDLK_UP: return VK_UP;
|
|
case SDLK_RIGHT: return VK_RIGHT;
|
|
case SDLK_DOWN: return VK_DOWN;
|
|
case SDLK_SELECT: return VK_SELECT;
|
|
case SDLK_EXECUTE: return VK_EXECUTE;
|
|
case SDLK_PRINTSCREEN: return VK_SNAPSHOT;
|
|
case SDLK_INSERT: return VK_INSERT;
|
|
case SDLK_DELETE: return VK_DELETE;
|
|
case SDLK_SEMICOLON: return VK_OEM_1;
|
|
case SDLK_EQUALS: return VK_OEM_PLUS;
|
|
case SDLK_COMMA: return VK_OEM_COMMA;
|
|
case SDLK_MINUS: return VK_OEM_MINUS;
|
|
case SDLK_PERIOD: return VK_OEM_PERIOD;
|
|
case SDLK_SLASH: return VK_OEM_2;
|
|
case SDLK_QUOTE: return VK_OEM_3;
|
|
case SDLK_LEFTBRACKET: return VK_OEM_4;
|
|
case SDLK_BACKSLASH: return VK_OEM_5;
|
|
case SDLK_RIGHTBRACKET: return VK_OEM_6;
|
|
case SDLK_HASH: return VK_OEM_7;
|
|
case SDLK_BACKQUOTE: return VK_OEM_8;
|
|
case SDLK_APPLICATION: return VK_APPS;
|
|
case SDLK_KP_0: return VK_NUMPAD0;
|
|
case SDLK_KP_1: return VK_NUMPAD1;
|
|
case SDLK_KP_2: return VK_NUMPAD2;
|
|
case SDLK_KP_3: return VK_NUMPAD3;
|
|
case SDLK_KP_4: return VK_NUMPAD4;
|
|
case SDLK_KP_5: return VK_NUMPAD5;
|
|
case SDLK_KP_6: return VK_NUMPAD6;
|
|
case SDLK_KP_7: return VK_NUMPAD7;
|
|
case SDLK_KP_8: return VK_NUMPAD8;
|
|
case SDLK_KP_9: return VK_NUMPAD9;
|
|
case SDLK_KP_MULTIPLY: return VK_MULTIPLY;
|
|
case SDLK_KP_PLUS: return VK_ADD;
|
|
case SDLK_KP_ENTER: return VK_SEPARATOR;
|
|
case SDLK_KP_MINUS: return VK_SUBTRACT;
|
|
case SDLK_KP_PERIOD: return VK_DECIMAL;
|
|
case SDLK_KP_DIVIDE: return VK_DIVIDE;
|
|
default:
|
|
if (keyCode >= SDLK_a && keyCode <= SDLK_z)
|
|
{
|
|
return 'A' + (keyCode - SDLK_a);
|
|
}
|
|
else if (keyCode >= SDLK_F1 && keyCode <= SDLK_F12)
|
|
{
|
|
return VK_F1 + (keyCode - SDLK_F1);
|
|
}
|
|
else if (keyCode <= 127)
|
|
{
|
|
return keyCode;
|
|
}
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
// 0x0040477F
|
|
static void readKeyboardState()
|
|
{
|
|
addr<0x005251CC, uint8_t>() = 0;
|
|
auto dstSize = _keyboard_state.size();
|
|
auto dst = _keyboard_state.get();
|
|
|
|
int numKeys;
|
|
|
|
std::fill_n(dst, dstSize, 0);
|
|
auto keyboardState = SDL_GetKeyboardState(&numKeys);
|
|
if (keyboardState != nullptr)
|
|
{
|
|
for (int scanCode = 0; scanCode < numKeys; scanCode++)
|
|
{
|
|
bool isDown = keyboardState[scanCode] != 0;
|
|
if (!isDown)
|
|
continue;
|
|
|
|
auto dinputCode = convertSdlScancodeToDirectInput(scanCode);
|
|
if (dinputCode != 0)
|
|
{
|
|
dst[dinputCode] = 0x80;
|
|
}
|
|
}
|
|
addr<0x005251CC, uint8_t>() = 1;
|
|
}
|
|
}
|
|
|
|
// 0x0040726D
|
|
bool processMessages()
|
|
{
|
|
#ifdef _LOCO_WIN32_
|
|
return ((bool (*)())0x0040726D)();
|
|
#else
|
|
using namespace input;
|
|
|
|
SDL_Event e;
|
|
while (SDL_PollEvent(&e))
|
|
{
|
|
switch (e.type)
|
|
{
|
|
case SDL_QUIT:
|
|
return false;
|
|
case SDL_WINDOWEVENT:
|
|
switch (e.window.event)
|
|
{
|
|
case SDL_WINDOWEVENT_MOVED:
|
|
positionChanged(e.window.data1, e.window.data2);
|
|
break;
|
|
case SDL_WINDOWEVENT_SIZE_CHANGED:
|
|
resize(e.window.data1, e.window.data2);
|
|
break;
|
|
}
|
|
break;
|
|
case SDL_MOUSEMOTION:
|
|
{
|
|
auto scale_factor = config::getNew().scale_factor;
|
|
auto x = (int32_t)(e.motion.x / scale_factor);
|
|
auto y = (int32_t)(e.motion.y / scale_factor);
|
|
auto xrel = (int32_t)(e.motion.xrel / scale_factor);
|
|
auto yrel = (int32_t)(e.motion.yrel / scale_factor);
|
|
input::moveMouse(x, y, xrel, yrel);
|
|
break;
|
|
}
|
|
case SDL_MOUSEWHEEL:
|
|
addr<0x00525330, int32_t>() += e.wheel.y * 128;
|
|
break;
|
|
case SDL_MOUSEBUTTONDOWN:
|
|
{
|
|
auto scale_factor = config::getNew().scale_factor;
|
|
addr<0x0113E9D4, int32_t>() = (int32_t)(e.button.x / scale_factor);
|
|
addr<0x0113E9D8, int32_t>() = (int32_t)(e.button.y / scale_factor);
|
|
addr<0x00525324, int32_t>() = 1;
|
|
switch (e.button.button)
|
|
{
|
|
case SDL_BUTTON_LEFT:
|
|
input::enqueueMouseButton(1);
|
|
addr<0x0113E8A0, int32_t>() = 1;
|
|
break;
|
|
case SDL_BUTTON_RIGHT:
|
|
input::enqueueMouseButton(2);
|
|
addr<0x0113E0C0, int32_t>() = 1;
|
|
addr<0x005251C8, int32_t>() = 1;
|
|
addr<0x01140845, uint8_t>() = 0x80;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case SDL_MOUSEBUTTONUP:
|
|
{
|
|
auto scale_factor = config::getNew().scale_factor;
|
|
addr<0x0113E9D4, int32_t>() = (int32_t)(e.button.x / scale_factor);
|
|
addr<0x0113E9D8, int32_t>() = (int32_t)(e.button.y / scale_factor);
|
|
addr<0x00525324, int32_t>() = 1;
|
|
switch (e.button.button)
|
|
{
|
|
case SDL_BUTTON_LEFT:
|
|
input::enqueueMouseButton(3);
|
|
addr<0x0113E8A0, int32_t>() = 0;
|
|
break;
|
|
case SDL_BUTTON_RIGHT:
|
|
input::enqueueMouseButton(4);
|
|
addr<0x0113E0C0, int32_t>() = 0;
|
|
addr<0x005251C8, int32_t>() = 0;
|
|
addr<0x01140845, uint8_t>() = 0;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case SDL_KEYDOWN:
|
|
{
|
|
auto keycode = e.key.keysym.sym;
|
|
|
|
#if !(defined(__APPLE__) && defined(__MACH__))
|
|
// Toggle fullscreen when ALT+RETURN is pressed
|
|
if (keycode == SDLK_RETURN)
|
|
{
|
|
if ((e.key.keysym.mod & KMOD_LALT) || (e.key.keysym.mod & KMOD_RALT))
|
|
{
|
|
toggleFullscreenDesktop();
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// Map keypad enter to normal enter
|
|
if (keycode == SDLK_KP_ENTER)
|
|
{
|
|
keycode = SDLK_RETURN;
|
|
}
|
|
|
|
auto locokey = convertSdlKeycodeToWindows(keycode);
|
|
if (locokey != 0)
|
|
{
|
|
enqueueKey(locokey);
|
|
}
|
|
break;
|
|
}
|
|
case SDL_KEYUP:
|
|
break;
|
|
case SDL_TEXTINPUT:
|
|
enqueueText(e.text.text);
|
|
break;
|
|
}
|
|
}
|
|
readKeyboardState();
|
|
return true;
|
|
#endif
|
|
}
|
|
|
|
void showMessageBox(const std::string& title, const std::string& message)
|
|
{
|
|
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_BUTTON_RETURNKEY_DEFAULT, title.c_str(), message.c_str(), window);
|
|
}
|
|
|
|
static config::resolution_t getDisplayResolutionByMode(config::screen_mode mode)
|
|
{
|
|
auto& config = config::getNew();
|
|
if (mode == config::screen_mode::window)
|
|
{
|
|
if (config.display.window_resolution.isPositive())
|
|
return config.display.window_resolution;
|
|
else
|
|
return { 800, 600 };
|
|
}
|
|
else if (mode == config::screen_mode::fullscreen && config.display.fullscreen_resolution.isPositive())
|
|
return config.display.fullscreen_resolution;
|
|
else
|
|
return getDesktopResolution();
|
|
}
|
|
|
|
config::resolution_t getResolution()
|
|
{
|
|
return { ui::width(), ui::height() };
|
|
}
|
|
|
|
config::resolution_t getDesktopResolution()
|
|
{
|
|
int32_t displayIndex = SDL_GetWindowDisplayIndex(window);
|
|
SDL_DisplayMode desktopDisplayMode;
|
|
SDL_GetDesktopDisplayMode(displayIndex, &desktopDisplayMode);
|
|
|
|
return { desktopDisplayMode.w, desktopDisplayMode.h };
|
|
}
|
|
|
|
bool setDisplayMode(config::screen_mode mode, config::resolution_t newResolution)
|
|
{
|
|
// First, set the appropriate screen mode flags.
|
|
auto flags = 0;
|
|
if (mode == config::screen_mode::fullscreen)
|
|
flags |= SDL_WINDOW_FULLSCREEN;
|
|
else if (mode == config::screen_mode::fullscreen_borderless)
|
|
flags |= SDL_WINDOW_FULLSCREEN_DESKTOP;
|
|
|
|
// *HACK* Set window to non fullscreen before switching resolution.
|
|
// This fixes issues with high dpi and Windows scaling affecting the gui size.
|
|
SDL_SetWindowFullscreen(window, 0);
|
|
|
|
// Set the new dimensions of the screen.
|
|
if (mode == config::screen_mode::window)
|
|
{
|
|
auto desktopResolution = getDesktopResolution();
|
|
auto x = (desktopResolution.width - newResolution.width) / 2;
|
|
auto y = (desktopResolution.height - newResolution.height) / 2;
|
|
SDL_SetWindowPosition(window, x, y);
|
|
}
|
|
SDL_SetWindowSize(window, newResolution.width, newResolution.height);
|
|
|
|
// Set the window fullscreen mode.
|
|
if (SDL_SetWindowFullscreen(window, flags) != 0)
|
|
{
|
|
console::error("SDL_SetWindowFullscreen failed: %s", SDL_GetError());
|
|
return false;
|
|
}
|
|
|
|
// It appears we were successful in setting the screen mode, so let's up date the config.
|
|
auto& config = config::getNew();
|
|
config.display.mode = mode;
|
|
|
|
if (mode == config::screen_mode::window)
|
|
config.display.window_resolution = newResolution;
|
|
else if (mode == config::screen_mode::fullscreen)
|
|
config.display.fullscreen_resolution = newResolution;
|
|
|
|
// We're also keeping track the resolution in the legacy config, for now.
|
|
auto& legacyConfig = config::get();
|
|
legacyConfig.resolution_width = newResolution.width;
|
|
legacyConfig.resolution_height = newResolution.height;
|
|
|
|
openloco::config::write();
|
|
ui::triggerResize();
|
|
gfx::invalidateScreen();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool setDisplayMode(config::screen_mode mode)
|
|
{
|
|
auto resolution = getDisplayResolutionByMode(mode);
|
|
return setDisplayMode(mode, resolution);
|
|
}
|
|
|
|
void updateFullscreenResolutions()
|
|
{
|
|
// Query number of display modes
|
|
int32_t displayIndex = SDL_GetWindowDisplayIndex(window);
|
|
int32_t numDisplayModes = SDL_GetNumDisplayModes(displayIndex);
|
|
|
|
// Get desktop aspect ratio
|
|
SDL_DisplayMode mode;
|
|
SDL_GetDesktopDisplayMode(displayIndex, &mode);
|
|
|
|
// Get resolutions
|
|
auto resolutions = std::vector<Resolution>();
|
|
float desktopAspectRatio = (float)mode.w / mode.h;
|
|
for (int32_t i = 0; i < numDisplayModes; i++)
|
|
{
|
|
SDL_GetDisplayMode(displayIndex, i, &mode);
|
|
if (mode.w > 0 && mode.h > 0)
|
|
{
|
|
float aspectRatio = (float)mode.w / mode.h;
|
|
if (_resolutionsAllowAnyAspectRatio || std::fabs(desktopAspectRatio - aspectRatio) < 0.0001f)
|
|
{
|
|
resolutions.push_back({ mode.w, mode.h });
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort by area
|
|
std::sort(resolutions.begin(), resolutions.end(), [](const Resolution& a, const Resolution& b) -> bool {
|
|
int32_t areaA = a.width * a.height;
|
|
int32_t areaB = b.width * b.height;
|
|
return areaA < areaB;
|
|
});
|
|
|
|
// Remove duplicates
|
|
auto last = std::unique(resolutions.begin(), resolutions.end(), [](const Resolution& a, const Resolution& b) -> bool {
|
|
return (a.width == b.width && a.height == b.height);
|
|
});
|
|
resolutions.erase(last, resolutions.end());
|
|
|
|
// Update config fullscreen resolution if not set
|
|
auto& cfg = config::get();
|
|
auto& cfg_new = config::getNew();
|
|
if (!(cfg_new.display.fullscreen_resolution.isPositive() && cfg.resolution_width > 0 && cfg.resolution_height > 0))
|
|
{
|
|
cfg.resolution_width = resolutions.back().width;
|
|
cfg.resolution_height = resolutions.back().height;
|
|
cfg_new.display.fullscreen_resolution.width = resolutions.back().width;
|
|
cfg_new.display.fullscreen_resolution.height = resolutions.back().height;
|
|
}
|
|
|
|
_fsResolutions = resolutions;
|
|
}
|
|
|
|
std::vector<Resolution> getFullscreenResolutions()
|
|
{
|
|
updateFullscreenResolutions();
|
|
return _fsResolutions;
|
|
}
|
|
|
|
Resolution getClosestResolution(int32_t inWidth, int32_t inHeight)
|
|
{
|
|
Resolution result = { 800, 600 };
|
|
int32_t closestAreaDiff = -1;
|
|
int32_t destinationArea = inWidth * inHeight;
|
|
for (const Resolution& resolution : _fsResolutions)
|
|
{
|
|
// Check if exact match
|
|
if (resolution.width == inWidth && resolution.height == inHeight)
|
|
{
|
|
result = resolution;
|
|
break;
|
|
}
|
|
|
|
// Check if area is closer to best match
|
|
int32_t areaDiff = std::abs((resolution.width * resolution.height) - destinationArea);
|
|
if (closestAreaDiff == -1 || areaDiff < closestAreaDiff)
|
|
{
|
|
closestAreaDiff = areaDiff;
|
|
result = resolution;
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
#if !(defined(__APPLE__) && defined(__MACH__))
|
|
static void toggleFullscreenDesktop()
|
|
{
|
|
auto flags = SDL_GetWindowFlags(window);
|
|
if (flags & SDL_WINDOW_FULLSCREEN)
|
|
{
|
|
setDisplayMode(config::screen_mode::window);
|
|
}
|
|
else
|
|
{
|
|
setDisplayMode(config::screen_mode::fullscreen_borderless);
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// 0x004C6EE6
|
|
static input::mouse_button gameGetNextInput(uint32_t* x, int16_t* y)
|
|
{
|
|
registers regs;
|
|
call(0x004c6ee6, regs);
|
|
|
|
*x = regs.eax;
|
|
*y = regs.bx;
|
|
|
|
return (input::mouse_button)regs.cx;
|
|
}
|
|
|
|
// 0x004CD422
|
|
static void processMouseTool(int16_t x, int16_t y)
|
|
{
|
|
if (!input::hasFlag(input::input_flags::tool_active))
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto toolWindow = WindowManager::find(_toolWindowType, _toolWindowNumber);
|
|
if (toolWindow != nullptr)
|
|
{
|
|
toolWindow->callToolUpdate(_toolWidgetIdx, x, y);
|
|
}
|
|
else
|
|
{
|
|
input::toolCancel();
|
|
}
|
|
}
|
|
|
|
// 0x004C96E7
|
|
void handleInput()
|
|
{
|
|
if (multiplayer::resetFlag(multiplayer::flags::flag_10))
|
|
{
|
|
call(0x00435ACC);
|
|
}
|
|
|
|
bool set = _525E28 & (1 << 2);
|
|
*_525E28 &= ~(1 << 2);
|
|
if (set)
|
|
{
|
|
if (!isTitleMode() && !isEditorMode())
|
|
{
|
|
if (tutorial::state() == tutorial::tutorial_state::none)
|
|
{
|
|
call(0x4C95A6);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (multiplayer::resetFlag(multiplayer::flags::flag_5))
|
|
{
|
|
game_commands::do_21(2, 1);
|
|
}
|
|
|
|
if (!multiplayer::hasFlag(multiplayer::flags::flag_0) && !multiplayer::hasFlag(multiplayer::flags::flag_4))
|
|
{
|
|
if (multiplayer::resetFlag(multiplayer::flags::flag_2))
|
|
{
|
|
WindowManager::closeConstructionWindows();
|
|
call(0x004CF456);
|
|
registers regs;
|
|
regs.bl = GameCommandFlag::apply;
|
|
game_commands::doCommand(69, regs);
|
|
}
|
|
|
|
if (multiplayer::resetFlag(multiplayer::flags::flag_3))
|
|
{
|
|
WindowManager::closeConstructionWindows();
|
|
call(0x004CF456);
|
|
registers regs;
|
|
regs.bl = GameCommandFlag::apply;
|
|
game_commands::doCommand(70, regs);
|
|
}
|
|
}
|
|
|
|
if (multiplayer::resetFlag(multiplayer::flags::flag_4))
|
|
{
|
|
registers regs;
|
|
regs.bl = GameCommandFlag::apply;
|
|
game_commands::doCommand(72, regs);
|
|
}
|
|
|
|
if (multiplayer::resetFlag(multiplayer::flags::flag_0))
|
|
{
|
|
WindowManager::closeConstructionWindows();
|
|
call(0x004CF456);
|
|
}
|
|
|
|
if (multiplayer::resetFlag(multiplayer::flags::flag_1))
|
|
{
|
|
game_commands::do_21(0, 2);
|
|
}
|
|
|
|
if (ui::dirtyBlocksInitialised())
|
|
{
|
|
WindowManager::callEvent8OnAllWindows();
|
|
|
|
WindowManager::invalidateAllWindowsAfterInput();
|
|
input::updateCursorPosition();
|
|
|
|
uint32_t x;
|
|
int16_t y;
|
|
input::mouse_button state;
|
|
while ((state = gameGetNextInput(&x, &y)) != input::mouse_button::released)
|
|
{
|
|
if (isTitleMode() && intro::isActive() && state == input::mouse_button::left_pressed)
|
|
{
|
|
if (intro::state() == intro::intro_state::state_9)
|
|
{
|
|
intro::state(intro::intro_state::end);
|
|
continue;
|
|
}
|
|
else
|
|
{
|
|
intro::state(intro::intro_state::state_8);
|
|
}
|
|
}
|
|
input::handleMouse(x, y, state);
|
|
}
|
|
|
|
if (input::hasFlag(input::input_flags::flag5))
|
|
{
|
|
input::handleMouse(x, y, state);
|
|
}
|
|
else if (x != 0x80000000)
|
|
{
|
|
x = std::clamp<int16_t>(x, 0, ui::width() - 1);
|
|
y = std::clamp<int16_t>(y, 0, ui::height() - 1);
|
|
|
|
input::handleMouse(x, y, state);
|
|
input::processMouseOver(x, y);
|
|
processMouseTool(x, y);
|
|
}
|
|
}
|
|
|
|
WindowManager::callEvent9OnAllWindows();
|
|
}
|
|
|
|
// 0x004C98CF
|
|
void minimalHandleInput()
|
|
{
|
|
WindowManager::callEvent8OnAllWindows();
|
|
|
|
WindowManager::invalidateAllWindowsAfterInput();
|
|
input::updateCursorPosition();
|
|
|
|
uint32_t x;
|
|
int16_t y;
|
|
input::mouse_button state;
|
|
while ((state = gameGetNextInput(&x, &y)) != input::mouse_button::released)
|
|
{
|
|
input::handleMouse(x, y, state);
|
|
}
|
|
|
|
if (input::hasFlag(input::input_flags::flag5))
|
|
{
|
|
input::handleMouse(x, y, state);
|
|
}
|
|
else if (x != 0x80000000)
|
|
{
|
|
x = std::clamp<int16_t>(x, 0, ui::width() - 1);
|
|
y = std::clamp<int16_t>(y, 0, ui::height() - 1);
|
|
|
|
input::handleMouse(x, y, state);
|
|
input::processMouseOver(x, y);
|
|
processMouseTool(x, y);
|
|
}
|
|
|
|
WindowManager::callEvent9OnAllWindows();
|
|
}
|
|
|
|
void setWindowScaling(float newScaleFactor)
|
|
{
|
|
auto& config = config::getNew();
|
|
newScaleFactor = std::clamp(newScaleFactor, ScaleFactor::min, ScaleFactor::max);
|
|
if (config.scale_factor == newScaleFactor)
|
|
return;
|
|
|
|
config.scale_factor = newScaleFactor;
|
|
|
|
openloco::config::write();
|
|
ui::triggerResize();
|
|
gfx::invalidateScreen();
|
|
}
|
|
|
|
void adjustWindowScale(float adjust_by)
|
|
{
|
|
auto& config = config::getNew();
|
|
float newScaleFactor = std::clamp(config.scale_factor + adjust_by, ScaleFactor::min, ScaleFactor::max);
|
|
if (config.scale_factor == newScaleFactor)
|
|
return;
|
|
|
|
setWindowScaling(newScaleFactor);
|
|
}
|
|
}
|