985 lines
27 KiB
C++
985 lines
27 KiB
C++
#include <algorithm>
|
|
#include <cstring>
|
|
#include <iostream>
|
|
#include <setjmp.h>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#ifdef _WIN32
|
|
// timeGetTime is unavailable if we use lean and mean
|
|
// #define WIN32_LEAN_AND_MEAN
|
|
#ifndef NOMINMAX
|
|
#define NOMINMAX
|
|
#endif
|
|
#include <objbase.h>
|
|
#include <windows.h>
|
|
|
|
// `small` is used as a type in `windows.h`
|
|
#undef small
|
|
#endif
|
|
|
|
#include "CompanyManager.h"
|
|
#include "Config.h"
|
|
#include "Date.h"
|
|
#include "Environment.h"
|
|
#include "Gui.h"
|
|
#include "IndustryManager.h"
|
|
#include "Input.h"
|
|
#include "Intro.h"
|
|
#include "MultiPlayer.h"
|
|
#include "OpenLoco.h"
|
|
#include "ProgressBar.h"
|
|
#include "ScenarioManager.h"
|
|
#include "StationManager.h"
|
|
#include "Title.h"
|
|
#include "TownManager.h"
|
|
#include "Tutorial.h"
|
|
#include "Ui.h"
|
|
#include "ViewportManager.h"
|
|
#include "audio/audio.h"
|
|
#include "graphics/colours.h"
|
|
#include "graphics/gfx.h"
|
|
#include "interop/interop.hpp"
|
|
#include "localisation/languagefiles.h"
|
|
#include "localisation/languages.h"
|
|
#include "localisation/string_ids.h"
|
|
#include "objects/objectmgr.h"
|
|
#include "platform/platform.h"
|
|
#include "s5/s5.h"
|
|
#include "things/thingmgr.h"
|
|
#include "ui/WindowManager.h"
|
|
#include "utility/numeric.hpp"
|
|
|
|
#pragma warning(disable : 4611) // interaction between '_setjmp' and C++ object destruction is non - portable
|
|
|
|
using namespace openloco::interop;
|
|
using namespace openloco::ui;
|
|
using namespace openloco::input;
|
|
|
|
namespace openloco
|
|
{
|
|
#ifdef _WIN32
|
|
loco_global<HINSTANCE, 0x0113E0B4> ghInstance;
|
|
loco_global<LPSTR, 0x00525348> glpCmdLine;
|
|
#else
|
|
loco_global<char*, 0x00525348> glpCmdLine;
|
|
#endif
|
|
|
|
loco_global<char[256], 0x005060D0> gCDKey;
|
|
|
|
loco_global<uint16_t, 0x0050C19C> time_since_last_tick;
|
|
loco_global<uint32_t, 0x0050C19E> last_tick_time;
|
|
loco_global<uint8_t, 0x00508F08> game_command_nest_level;
|
|
loco_global<uint16_t, 0x00508F12> _screen_age;
|
|
loco_global<uint8_t, 0x00508F14> _screen_flags;
|
|
loco_global<uint8_t, 0x00508F17> paused_state;
|
|
loco_global<uint8_t, 0x00508F1A> game_speed;
|
|
static loco_global<string_id, 0x0050A018> _mapTooltipFormatArguments;
|
|
static loco_global<company_id_t, 0x0050A040> _mapTooltipOwner;
|
|
static loco_global<int32_t, 0x0052339C> _52339C;
|
|
static loco_global<int8_t, 0x0052336E> _52336E; // bool
|
|
loco_global<utility::prng, 0x00525E18> _prng;
|
|
static loco_global<company_id_t[2], 0x00525E3C> _player_company;
|
|
loco_global<uint32_t, 0x00525F5E> _scenario_ticks;
|
|
static loco_global<int16_t, 0x00525F62> _525F62;
|
|
|
|
static loco_global<company_id_t, 0x009C68EB> _updating_company_id;
|
|
|
|
static loco_global<uint8_t, 0x009C8714> _editorStep;
|
|
|
|
static loco_global<char[256], 0x011367A0> _11367A0;
|
|
static loco_global<char[256], 0x011368A0> _11368A0;
|
|
|
|
static void tickLogic(int32_t count);
|
|
static void tickLogic();
|
|
static void tickWait();
|
|
static void dateTick();
|
|
static void sub_46FFCA();
|
|
|
|
std::string getVersionInfo()
|
|
{
|
|
return version;
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
void* hInstance()
|
|
{
|
|
return ghInstance;
|
|
}
|
|
#endif
|
|
|
|
const char* lpCmdLine()
|
|
{
|
|
return glpCmdLine;
|
|
}
|
|
|
|
#ifndef _WIN32
|
|
void lpCmdLine(const char* path)
|
|
{
|
|
glpCmdLine = strdup(path);
|
|
}
|
|
#endif
|
|
|
|
uint16_t getScreenAge()
|
|
{
|
|
return _screen_age;
|
|
}
|
|
|
|
uint8_t getScreenFlags()
|
|
{
|
|
return _screen_flags;
|
|
}
|
|
|
|
bool isEditorMode()
|
|
{
|
|
return (_screen_flags & screen_flags::editor) != 0;
|
|
}
|
|
|
|
bool isTitleMode()
|
|
{
|
|
return (_screen_flags & screen_flags::title) != 0;
|
|
}
|
|
|
|
bool isNetworked()
|
|
{
|
|
return (_screen_flags & screen_flags::networked) != 0;
|
|
}
|
|
|
|
bool isTrackUpgradeMode()
|
|
{
|
|
return (_screen_flags & screen_flags::trackUpgrade) != 0;
|
|
}
|
|
|
|
bool isUnknown4Mode()
|
|
{
|
|
return (_screen_flags & screen_flags::unknown_4) != 0;
|
|
}
|
|
|
|
bool isUnknown5Mode()
|
|
{
|
|
return (_screen_flags & screen_flags::unknown_5) != 0;
|
|
}
|
|
|
|
bool isPaused()
|
|
{
|
|
return paused_state;
|
|
}
|
|
|
|
uint8_t getPauseFlags()
|
|
{
|
|
return paused_state;
|
|
}
|
|
|
|
// 0x00431E32
|
|
// value: bl (false will no-op)
|
|
// **Gamecommand**
|
|
void togglePause(bool value)
|
|
{
|
|
registers regs;
|
|
regs.bl = value ? 1 : 0;
|
|
call(0x00431E32, regs);
|
|
}
|
|
|
|
uint32_t scenarioTicks()
|
|
{
|
|
return _scenario_ticks;
|
|
}
|
|
|
|
utility::prng& gPrng()
|
|
{
|
|
return _prng;
|
|
}
|
|
|
|
static bool sub_4054B9()
|
|
{
|
|
registers regs;
|
|
call(0x004054B9, regs);
|
|
return regs.eax != 0;
|
|
}
|
|
|
|
#ifdef _NO_LOCO_WIN32_
|
|
/**
|
|
* Use this to allocate memory that will be freed in vanilla code or via loco_free.
|
|
*/
|
|
[[maybe_unused]] static void* malloc(size_t size)
|
|
{
|
|
return ((void* (*)(size_t))0x004D1401)(size);
|
|
}
|
|
|
|
/**
|
|
* Use this to reallocate memory that will be freed in vanilla code or via loco_free.
|
|
*/
|
|
[[maybe_unused]] static void* realloc(void* address, size_t size)
|
|
{
|
|
return ((void* (*)(void*, size_t))0x004D1B28)(address, size);
|
|
}
|
|
|
|
/**
|
|
* Use this to free up memory allocated in vanilla code or via loco_malloc / loco_realloc.
|
|
*/
|
|
[[maybe_unused]] static void free(void* address)
|
|
{
|
|
((void (*)(void*))0x004D1355)(address);
|
|
}
|
|
#endif // _NO_LOCO_WIN32_
|
|
|
|
static void sub_4062D1()
|
|
{
|
|
call(0x004062D1);
|
|
}
|
|
|
|
static void sub_406417()
|
|
{
|
|
((void (*)())0x00406417)();
|
|
}
|
|
|
|
static void sub_40567E()
|
|
{
|
|
call(0x0040567E);
|
|
}
|
|
|
|
static void sub_4058F5()
|
|
{
|
|
call(0x004058F5);
|
|
}
|
|
|
|
static void sub_4062E0()
|
|
{
|
|
call(0x004062E0);
|
|
}
|
|
|
|
static bool sub_4034FC(int32_t& a, int32_t& b)
|
|
{
|
|
auto result = ((int32_t(*)(int32_t&, int32_t&))(0x004034FC))(a, b);
|
|
return result != 0;
|
|
}
|
|
|
|
// 0x00407FFD
|
|
static bool isAlreadyRunning(const char* mutexName)
|
|
{
|
|
auto result = ((int32_t(*)(const char*))(0x00407FFD))(mutexName);
|
|
return result != 0;
|
|
}
|
|
|
|
// 0x004BE621
|
|
static void exitWithError(string_id eax, string_id ebx)
|
|
{
|
|
registers regs;
|
|
regs.eax = eax;
|
|
regs.ebx = ebx;
|
|
call(0x004BE621, regs);
|
|
}
|
|
|
|
// 0x004BE5EB
|
|
void exitWithError(string_id message, uint32_t errorCode)
|
|
{
|
|
// Saves the error code for later writing to error log 1.TMP.
|
|
registers regs;
|
|
regs.eax = errorCode;
|
|
regs.bx = message;
|
|
call(0x004BE5EB, regs);
|
|
}
|
|
|
|
// 0x00441400
|
|
static void startupChecks()
|
|
{
|
|
if (isAlreadyRunning("Locomotion"))
|
|
{
|
|
exitWithError(string_ids::game_init_failure, string_ids::loco_already_running);
|
|
}
|
|
|
|
// Originally the game would check that all the game
|
|
// files exist are some have the correct checksum. We
|
|
// do not need to do this anymore, the game should work
|
|
// with g1 alone and some objects?
|
|
}
|
|
|
|
// 0x004C57C0
|
|
void initialiseViewports()
|
|
{
|
|
_mapTooltipFormatArguments = string_ids::null;
|
|
_mapTooltipOwner = company_id::null;
|
|
|
|
colour::initColourMap();
|
|
ui::WindowManager::init();
|
|
ui::viewportmgr::init();
|
|
|
|
input::init();
|
|
input::initMouse();
|
|
|
|
// rain-related
|
|
_52339C = -1;
|
|
|
|
// tooltip-related
|
|
_52336E = 0;
|
|
|
|
ui::textinput::cancel();
|
|
|
|
stringmgr::formatString(_11367A0, string_ids::label_button_ok);
|
|
stringmgr::formatString(_11368A0, string_ids::label_button_cancel);
|
|
}
|
|
|
|
static void initialise()
|
|
{
|
|
std::srand(std::time(0));
|
|
addr<0x0050C18C, int32_t>() = addr<0x00525348, int32_t>();
|
|
call(0x004078BE);
|
|
call(0x004BF476);
|
|
environment::resolvePaths();
|
|
localisation::enumerateLanguages();
|
|
localisation::loadLanguageFile();
|
|
progressbar::begin(string_ids::loading, 0);
|
|
progressbar::setProgress(30);
|
|
startupChecks();
|
|
progressbar::setProgress(40);
|
|
call(0x004BE5DE);
|
|
progressbar::end();
|
|
config::read();
|
|
objectmgr::load_index();
|
|
scenariomgr::loadIndex(0);
|
|
progressbar::begin(string_ids::loading, 0);
|
|
progressbar::setProgress(60);
|
|
gfx::loadG1();
|
|
progressbar::setProgress(220);
|
|
call(0x004949BC);
|
|
progressbar::setProgress(235);
|
|
progressbar::setProgress(250);
|
|
ui::initialiseCursors();
|
|
progressbar::end();
|
|
ui::initialise();
|
|
initialiseViewports();
|
|
call(0x004284C8);
|
|
call(0x004969DA);
|
|
call(0x0043C88C);
|
|
_screen_flags = _screen_flags | screen_flags::unknown_5;
|
|
#ifdef _SHOW_INTRO_
|
|
intro::state(intro::intro_state::begin);
|
|
#else
|
|
intro::state(intro::intro_state::end);
|
|
#endif
|
|
title::start();
|
|
gui::init();
|
|
gfx::clear(gfx::screenDpi(), 0x0A0A0A0A);
|
|
}
|
|
|
|
// 0x00428E47
|
|
static void sub_428E47()
|
|
{
|
|
call(0x00428E47);
|
|
}
|
|
|
|
// 0x0046E388
|
|
static void sub_46E388()
|
|
{
|
|
call(0x0046E388);
|
|
}
|
|
|
|
// 0x004317BD
|
|
static uint32_t sub_4317BD()
|
|
{
|
|
registers regs;
|
|
call(0x004317BD, regs);
|
|
|
|
return regs.eax;
|
|
}
|
|
|
|
// 0x0046E4E3
|
|
static void sub_46E4E3()
|
|
{
|
|
call(0x0046E4E3);
|
|
}
|
|
|
|
void sub_431695(uint16_t var_F253A0)
|
|
{
|
|
if (!isNetworked())
|
|
{
|
|
_updating_company_id = companymgr::getControllingId();
|
|
for (auto i = 0; i < var_F253A0; i++)
|
|
{
|
|
sub_428E47();
|
|
WindowManager::dispatchUpdateAll();
|
|
}
|
|
|
|
input::processKeyboardInput();
|
|
WindowManager::update();
|
|
ui::handleInput();
|
|
companymgr::updateOwnerStatus();
|
|
return;
|
|
}
|
|
|
|
// Only run every other tick?
|
|
if (_525F62 % 2 != 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Host/client?
|
|
if (isTrackUpgradeMode())
|
|
{
|
|
_updating_company_id = companymgr::getControllingId();
|
|
|
|
// run twice as often as var_F253A0
|
|
for (auto i = 0; i < var_F253A0 * 2; i++)
|
|
{
|
|
sub_428E47();
|
|
WindowManager::dispatchUpdateAll();
|
|
}
|
|
|
|
input::processKeyboardInput();
|
|
WindowManager::update();
|
|
WindowManager::update();
|
|
ui::handleInput();
|
|
companymgr::updateOwnerStatus();
|
|
sub_46E388();
|
|
|
|
_updating_company_id = _player_company[1];
|
|
sub_4317BD();
|
|
}
|
|
else
|
|
{
|
|
_updating_company_id = _player_company[1];
|
|
auto eax = sub_4317BD();
|
|
|
|
_updating_company_id = _player_company[0];
|
|
if (!isTitleMode())
|
|
{
|
|
auto edx = _prng->srand_0();
|
|
edx ^= companymgr::get(0)->cash.var_00;
|
|
edx ^= companymgr::get(1)->cash.var_00;
|
|
if (edx != eax)
|
|
{
|
|
// disconnect?
|
|
sub_46E4E3();
|
|
return;
|
|
}
|
|
}
|
|
|
|
// run twice as often as var_F253A0
|
|
for (auto i = 0; i < var_F253A0 * 2; i++)
|
|
{
|
|
sub_428E47();
|
|
WindowManager::dispatchUpdateAll();
|
|
}
|
|
|
|
input::processKeyboardInput();
|
|
WindowManager::update();
|
|
WindowManager::update();
|
|
ui::handleInput();
|
|
companymgr::updateOwnerStatus();
|
|
sub_46E388();
|
|
}
|
|
}
|
|
|
|
// 0x0043D9D4
|
|
static void editorTick()
|
|
{
|
|
if (!isEditorMode())
|
|
return;
|
|
|
|
switch (_editorStep)
|
|
{
|
|
case 0:
|
|
if (WindowManager::find(WindowType::objectSelection) == nullptr)
|
|
windows::ObjectSelectionWindow::open();
|
|
break;
|
|
|
|
case 1:
|
|
// Scenario/landscape loaded?
|
|
if ((addr<0x00525E28, uint32_t>() & 1) != 0)
|
|
return;
|
|
|
|
if (WindowManager::find(WindowType::landscapeGeneration) == nullptr)
|
|
windows::LandscapeGeneration::open();
|
|
break;
|
|
|
|
case 2:
|
|
if (WindowManager::find(WindowType::scenarioOptions) == nullptr)
|
|
windows::ScenarioOptions::open();
|
|
break;
|
|
|
|
case 3:
|
|
break;
|
|
}
|
|
}
|
|
|
|
// 0x0046A794
|
|
static void tick()
|
|
{
|
|
static bool isInitialised = false;
|
|
|
|
// Locomotion has several routines that will prematurely end the current tick.
|
|
// This usually happens when switching game mode. It does this by jumping to
|
|
// the end of the original routine and resetting esp back to an initial value
|
|
// stored at the beginning of tick. Until those routines are re-written, we
|
|
// must simulate it using 'setjmp'.
|
|
static jmp_buf tickJump;
|
|
|
|
// When Locomotion wants to jump to the end of a tick, it sets ESP
|
|
// to some static memory that we define
|
|
static loco_global<void*, 0x0050C1A6> tickJumpESP;
|
|
static uint8_t spareStackMemory[2048];
|
|
tickJumpESP = spareStackMemory + sizeof(spareStackMemory);
|
|
|
|
if (setjmp(tickJump))
|
|
{
|
|
// Premature end of current tick
|
|
std::cout << "tick prematurely ended" << std::endl;
|
|
return;
|
|
}
|
|
|
|
addr<0x00113E87C, int32_t>() = 0;
|
|
addr<0x0005252E0, int32_t>() = 0;
|
|
if (!isInitialised)
|
|
{
|
|
isInitialised = true;
|
|
|
|
// This address is where those routines jump back to to end the tick prematurely
|
|
registerHook(
|
|
0x0046AD71,
|
|
[](registers& regs) FORCE_ALIGN_ARG_POINTER -> uint8_t {
|
|
longjmp(tickJump, 1);
|
|
});
|
|
|
|
initialise();
|
|
last_tick_time = platform::getTime();
|
|
}
|
|
|
|
uint32_t time = platform::getTime();
|
|
time_since_last_tick = (uint16_t)std::min(time - last_tick_time, 500U);
|
|
last_tick_time = time;
|
|
|
|
if (!isPaused())
|
|
{
|
|
addr<0x0050C1A2, uint32_t>() += time_since_last_tick;
|
|
}
|
|
if (tutorial::state() != tutorial::tutorial_state::none)
|
|
{
|
|
time_since_last_tick = 31;
|
|
}
|
|
game_command_nest_level = 0;
|
|
ui::update();
|
|
|
|
addr<0x005233AE, int32_t>() += addr<0x0114084C, int32_t>();
|
|
addr<0x005233B2, int32_t>() += addr<0x01140840, int32_t>();
|
|
addr<0x0114084C, int32_t>() = 0;
|
|
addr<0x01140840, int32_t>() = 0;
|
|
if (config::get().var_72 == 0)
|
|
{
|
|
config::get().var_72 = 16;
|
|
ui::getCursorPos(addr<0x00F2538C, int32_t>(), addr<0x00F25390, int32_t>());
|
|
gfx::clear(gfx::screenDpi(), 0);
|
|
addr<0x00F2539C, int32_t>() = 0;
|
|
}
|
|
else
|
|
{
|
|
if (config::get().var_72 >= 16)
|
|
{
|
|
config::get().var_72++;
|
|
if (config::get().var_72 >= 48)
|
|
{
|
|
if (sub_4034FC(addr<0x00F25394, int32_t>(), addr<0x00F25398, int32_t>()))
|
|
{
|
|
uintptr_t esi = addr<0x00F25390, int32_t>() + 4;
|
|
esi *= addr<0x00F25398, int32_t>();
|
|
esi += addr<0x00F2538C, int32_t>();
|
|
esi += 2;
|
|
esi += addr<0x00F25394, int32_t>();
|
|
addr<0x00F2539C, int32_t>() |= *((int32_t*)esi);
|
|
call(0x00403575);
|
|
}
|
|
}
|
|
ui::setCursorPos(addr<0x00F2538C, int32_t>(), addr<0x00F25390, int32_t>());
|
|
gfx::invalidateScreen();
|
|
if (config::get().var_72 != 96)
|
|
{
|
|
tickWait();
|
|
return;
|
|
}
|
|
config::get().var_72 = 1;
|
|
if (addr<0x00F2539C, int32_t>() != 0)
|
|
{
|
|
config::get().var_72 = 2;
|
|
}
|
|
config::write();
|
|
}
|
|
|
|
call(0x00452D1A);
|
|
call(0x00440DEC);
|
|
|
|
if (addr<0x00525340, int32_t>() == 1)
|
|
{
|
|
addr<0x00525340, int32_t>() = 0;
|
|
multiplayer::setFlag(multiplayer::flags::flag_1);
|
|
}
|
|
|
|
input::handleKeyboard();
|
|
audio::updateSounds();
|
|
|
|
addr<0x0050C1AE, int32_t>()++;
|
|
if (intro::isActive())
|
|
{
|
|
intro::update();
|
|
}
|
|
else
|
|
{
|
|
uint16_t numUpdates = std::clamp<uint16_t>(time_since_last_tick / (uint16_t)31, 1, 3);
|
|
if (WindowManager::find(ui::WindowType::multiplayer, 0) != nullptr)
|
|
{
|
|
numUpdates = 1;
|
|
}
|
|
if (isNetworked())
|
|
{
|
|
numUpdates = 1;
|
|
}
|
|
if (addr<0x00525324, int32_t>() == 1)
|
|
{
|
|
addr<0x00525324, int32_t>() = 0;
|
|
numUpdates = 1;
|
|
}
|
|
else
|
|
{
|
|
switch (input::state())
|
|
{
|
|
case input_state::reset:
|
|
case input_state::normal:
|
|
case input_state::dropdown_active:
|
|
if (input::hasFlag(input_flags::viewport_scrolling))
|
|
{
|
|
input::resetFlag(input_flags::viewport_scrolling);
|
|
numUpdates = 1;
|
|
}
|
|
break;
|
|
case input_state::widget_pressed: break;
|
|
case input_state::positioning_window: break;
|
|
case input_state::viewport_right: break;
|
|
case input_state::viewport_left: break;
|
|
case input_state::scroll_left: break;
|
|
case input_state::resizing: break;
|
|
case input_state::scroll_right: break;
|
|
}
|
|
}
|
|
addr<0x0052622E, int16_t>() += numUpdates;
|
|
if (isPaused())
|
|
{
|
|
numUpdates = 0;
|
|
}
|
|
uint16_t var_F253A0 = std::max<uint16_t>(1, numUpdates);
|
|
_screen_age = std::min(0xFFFF, (int32_t)_screen_age + var_F253A0);
|
|
if (game_speed != 0)
|
|
{
|
|
numUpdates *= 3;
|
|
if (game_speed != 1)
|
|
{
|
|
numUpdates *= 3;
|
|
}
|
|
}
|
|
|
|
sub_46FFCA();
|
|
tickLogic(numUpdates);
|
|
|
|
_525F62++;
|
|
editorTick();
|
|
audio::playBackgroundMusic();
|
|
|
|
// TODO move stop title music to title::stop (when mode changes)
|
|
if (!isTitleMode())
|
|
{
|
|
audio::stopTitleMusic();
|
|
}
|
|
|
|
if (tutorial::state() != tutorial::tutorial_state::none && addr<0x0052532C, int32_t>() != 0 && addr<0x0113E2E4, int32_t>() < 0x40)
|
|
{
|
|
tutorial::stop();
|
|
|
|
// This ends with a premature tick termination
|
|
call(0x0043C0FD);
|
|
return; // won't be reached
|
|
}
|
|
|
|
sub_431695(var_F253A0);
|
|
call(0x00452B5F);
|
|
sub_46FFCA();
|
|
if (config::get().countdown != 0xFF)
|
|
{
|
|
config::get().countdown++;
|
|
if (config::get().countdown != 0xFF)
|
|
{
|
|
config::write();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (config::get().var_72 == 2)
|
|
{
|
|
addr<0x005252DC, int32_t>() = 1;
|
|
ui::getCursorPos(addr<0x00F2538C, int32_t>(), addr<0x00F25390, int32_t>());
|
|
ui::setCursorPos(addr<0x00F2538C, int32_t>(), addr<0x00F25390, int32_t>());
|
|
}
|
|
}
|
|
|
|
tickWait();
|
|
}
|
|
|
|
static void tickLogic(int32_t count)
|
|
{
|
|
for (int32_t i = 0; i < count; i++)
|
|
{
|
|
tickLogic();
|
|
}
|
|
}
|
|
|
|
// 0x004612EC
|
|
static void invalidate_map_animations()
|
|
{
|
|
call(0x004612EC);
|
|
}
|
|
|
|
static void sub_46FFCA()
|
|
{
|
|
addr<0x010E7D3C, uint32_t>() = 0x2A0015;
|
|
addr<0x010E7D40, uint32_t>() = 0x210026;
|
|
addr<0x010E7D44, uint32_t>() = 0x5C2001F;
|
|
addr<0x010E7D48, uint32_t>() = 0xFFFF0019;
|
|
addr<0x010E7D4C, uint32_t>() = 0xFFFFFFFF;
|
|
addr<0x010E7D50, uint32_t>() = 0x1AFFFF;
|
|
addr<0x010E7D54, uint32_t>() = 0xFFFFFFFF;
|
|
addr<0x010E7D58, uint32_t>() = 0xFFFF001B;
|
|
addr<0x010E7D5C, uint32_t>() = 0x64700A3;
|
|
addr<0x010E7D60, uint32_t>() = 0xCE0481;
|
|
addr<0x010E7D64, uint32_t>() = 0xD900BF;
|
|
}
|
|
|
|
static loco_global<int8_t, 0x0050C197> _50C197;
|
|
static loco_global<string_id, 0x0050C198> _50C198;
|
|
|
|
// 0x0046ABCB
|
|
static void tickLogic()
|
|
{
|
|
_scenario_ticks++;
|
|
addr<0x00525F64, int32_t>()++;
|
|
addr<0x00525FCC, uint32_t>() = _prng->srand_0();
|
|
addr<0x00525FD0, uint32_t>() = _prng->srand_1();
|
|
call(0x004613F0);
|
|
addr<0x00F25374, uint8_t>() = s5::getOptions().madeAnyChanges;
|
|
dateTick();
|
|
call(0x00463ABA);
|
|
call(0x004C56F6);
|
|
townmgr::update();
|
|
industrymgr::update();
|
|
thingmgr::updateVehicles();
|
|
sub_46FFCA();
|
|
stationmgr::update();
|
|
thingmgr::updateMiscThings();
|
|
sub_46FFCA();
|
|
companymgr::update();
|
|
invalidate_map_animations();
|
|
audio::updateVehicleNoise();
|
|
audio::updateAmbientNoise();
|
|
call(0x00444387);
|
|
|
|
s5::getOptions().madeAnyChanges = addr<0x00F25374, uint8_t>();
|
|
if (_50C197 != 0)
|
|
{
|
|
auto title = string_ids::error_unable_to_load_saved_game;
|
|
auto message = _50C198;
|
|
if (_50C197 == -2)
|
|
{
|
|
title = _50C198;
|
|
message = string_ids::null;
|
|
}
|
|
_50C197 = 0;
|
|
ui::windows::showError(title, message);
|
|
}
|
|
}
|
|
|
|
static void sub_496A84(int32_t edx)
|
|
{
|
|
// This is responsible for updating the snow line
|
|
registers regs;
|
|
regs.edx = edx;
|
|
call(0x00496A84, regs);
|
|
}
|
|
|
|
// 0x004968C7
|
|
static void dateTick()
|
|
{
|
|
if ((addr<0x00525E28, uint32_t>() & 1) && !isEditorMode())
|
|
{
|
|
if (updateDayCounter())
|
|
{
|
|
stationmgr::updateDaily();
|
|
call(0x004B94CF);
|
|
call(0x00453487);
|
|
call(0x004284DB);
|
|
call(0x004969DA);
|
|
call(0x00439BA5);
|
|
|
|
auto yesterday = calcDate(getCurrentDay() - 1);
|
|
auto today = calcDate(getCurrentDay());
|
|
setDate(today);
|
|
sub_496A84(today.day_of_olympiad);
|
|
if (today.month != yesterday.month)
|
|
{
|
|
// End of every month
|
|
addr<0x0050A004, uint16_t>() += 2;
|
|
addr<0x00526243, uint16_t>()++;
|
|
townmgr::updateMonthly();
|
|
call(0x0045383B);
|
|
call(0x0043037B);
|
|
call(0x0042F213);
|
|
call(0x004C3C54);
|
|
|
|
if (today.year <= 2029)
|
|
{
|
|
call(0x0046E239);
|
|
}
|
|
|
|
// clang-format off
|
|
if (today.month == month_id::january ||
|
|
today.month == month_id::april ||
|
|
today.month == month_id::july ||
|
|
today.month == month_id::october)
|
|
// clang-format on
|
|
{
|
|
// Start of every season?
|
|
call(0x00487FC1);
|
|
}
|
|
|
|
if (today.year != yesterday.year)
|
|
{
|
|
// End of every year
|
|
call(0x004312C7);
|
|
call(0x004796A9);
|
|
call(0x004C3A9E);
|
|
call(0x0047AB9B);
|
|
}
|
|
}
|
|
|
|
call(0x00437FB8);
|
|
}
|
|
}
|
|
}
|
|
|
|
// 0x0046AD4D
|
|
void tickWait()
|
|
{
|
|
do
|
|
{
|
|
// Idle loop for a 40 FPS
|
|
} while (platform::getTime() - last_tick_time < 25);
|
|
}
|
|
|
|
void promptTickLoop(std::function<bool()> tickAction)
|
|
{
|
|
while (true)
|
|
{
|
|
auto startTime = platform::getTime();
|
|
time_since_last_tick = 31;
|
|
if (!ui::processMessages() || !tickAction())
|
|
{
|
|
break;
|
|
}
|
|
ui::render();
|
|
do
|
|
{
|
|
// Idle loop for a 40 FPS
|
|
} while (platform::getTime() - startTime < 25);
|
|
}
|
|
}
|
|
|
|
// 0x00406386
|
|
static void run()
|
|
{
|
|
#ifdef _WIN32
|
|
CoInitializeEx(nullptr, COINIT_MULTITHREADED);
|
|
#endif
|
|
sub_4062D1();
|
|
sub_406417();
|
|
|
|
#ifdef _READ_REGISTRY_
|
|
constexpr auto INSTALL_REG_KEY = "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\{77F45E76-E897-42CA-A9FE-5F56817D875C}";
|
|
|
|
HKEY key;
|
|
if (RegOpenKeyA(HKEY_LOCAL_MACHINE, INSTALL_REG_KEY, &key) == ERROR_SUCCESS)
|
|
{
|
|
DWORD type;
|
|
DWORD dataSize = gCDKey.size();
|
|
RegQueryValueExA(key, "CDKey", nullptr, &type, (LPBYTE)gCDKey.get(), &dataSize);
|
|
RegCloseKey(key);
|
|
}
|
|
#endif
|
|
|
|
// Call tick before ui::processMessages to ensure initialise is called
|
|
// otherwise window events can end up using an uninitialised window manager.
|
|
// This can be removed when initialise is moved out of tick().
|
|
tick();
|
|
|
|
while (ui::processMessages())
|
|
{
|
|
if (addr<0x005252AC, uint32_t>() != 0)
|
|
{
|
|
sub_4058F5();
|
|
}
|
|
sub_4062E0();
|
|
tick();
|
|
ui::render();
|
|
}
|
|
sub_40567E();
|
|
|
|
#ifdef _WIN32
|
|
CoUninitialize();
|
|
#endif
|
|
}
|
|
|
|
// 0x00406D13
|
|
void main()
|
|
{
|
|
auto versionInfo = openloco::getVersionInfo();
|
|
std::cout << versionInfo << std::endl;
|
|
try
|
|
{
|
|
const auto& cfg = config::readNewConfig();
|
|
environment::resolvePaths();
|
|
|
|
registerHooks();
|
|
if (sub_4054B9())
|
|
{
|
|
ui::createWindow(cfg.display);
|
|
call(0x004078FE);
|
|
call(0x00407B26);
|
|
ui::initialiseInput();
|
|
audio::initialiseDSound();
|
|
run();
|
|
audio::disposeDSound();
|
|
ui::disposeCursors();
|
|
ui::disposeInput();
|
|
|
|
// TODO extra clean up code
|
|
}
|
|
}
|
|
catch (const std::exception& ex)
|
|
{
|
|
std::cerr << ex.what() << std::endl;
|
|
}
|
|
}
|
|
}
|
|
|
|
extern "C" {
|
|
|
|
#ifdef _WIN32
|
|
/**
|
|
* The function that is called directly from the host application (loco.exe)'s WinMain. This will be removed when OpenLoco can
|
|
* be built as a stand alone application.
|
|
*/
|
|
// Hack to trick mingw into thinking we forward-declared this function.
|
|
__declspec(dllexport) int StartOpenLoco(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
|
|
__declspec(dllexport) int StartOpenLoco(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
|
|
{
|
|
openloco::glpCmdLine = lpCmdLine;
|
|
openloco::ghInstance = hInstance;
|
|
openloco::main();
|
|
return 0;
|
|
}
|
|
#endif
|
|
}
|