Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement load_g1() #20

Merged
merged 15 commits into from Jan 25, 2018
103 changes: 101 additions & 2 deletions src/openloco/graphics/gfx.cpp
@@ -1,22 +1,121 @@
#include <algorithm>
#include <fstream>
#include <iostream>
#include <memory>

#include "gfx.h"
#include "../interop/interop.hpp"
#include "../environment.h"
#include "../ui.h"

using namespace openloco::interop;

namespace openloco::gfx
{
loco_global<drawpixelinfo_t, 0x0050B884> _screen_dpi;
namespace g1_expected_count
{
constexpr uint32_t disc = 0x101A; // And GOG
constexpr uint32_t steam = 0x0F38;
}

static loco_global<drawpixelinfo_t, 0x0050B884> _screen_dpi;
static loco_global_array<g1_element, g1_expected_count::disc, 0x9E2424> _g1Elements;

static std::unique_ptr<std::byte[]> _g1Buffer;

drawpixelinfo_t& screen_dpi()
{
return _screen_dpi;
}

static std::vector<g1_element> convert_elements(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;
}

template<typename T1, typename T2, typename T3>
std::basic_istream<T1, T2>& read_data(std::basic_istream<T1, T2> &stream, T3 * dst, size_t count)
{
return stream.read((char *)dst, count * sizeof(T3));
}

template<typename T1, typename T2, typename T3>
std::basic_istream<T1, T2>& read_data(std::basic_istream<T1, T2> &stream, T3 &dst)
{
return read_data(stream, &dst, 1);
}

// 0x0044733C
void load_g1()
{
call(0x0044733C);
auto g1Path = environment::get_path(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 (!read_data(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 (!read_data(stream, elements32.data(), header.num_entries))
{
throw std::runtime_error("Reading g1 element headers failed.");
}
auto elements = convert_elements(elements32);

// Read element data
auto elementData = std::make_unique<std::byte[]>(header.total_size);
if (!read_data(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[3550], 1, &elements[3549]);

// 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
Expand Down
45 changes: 44 additions & 1 deletion src/openloco/graphics/gfx.h
Expand Up @@ -19,7 +19,50 @@ namespace openloco::gfx
uint16_t zoom_level; // 0x0E
};

#pragma pack(pop)
drawpixelinfo_t& screen_dpi();


struct g1_header_t
{
uint32_t num_entries;
uint32_t total_size;
};

struct g1_element32_t
{
uint32_t offset; // 0x00
int16_t width; // 0x04
int16_t height; // 0x06
int16_t x_offset; // 0x08
int16_t y_offset; // 0x0A
uint16_t flags; // 0x0C
int16_t unused; // 0x0E
};

// A version that can be 64-bit when ready...
struct g1_element
{
uint8_t * offset = nullptr;
int16_t width = 0;
int16_t height = 0;
int16_t x_offset = 0;
int16_t y_offset = 0;
uint16_t flags = 0;
int16_t unused = 0;

g1_element(const g1_element32_t &src)
: offset((uint8_t *)src.offset),
width(src.width),
height(src.height),
x_offset(src.x_offset),
y_offset(src.y_offset),
flags(src.flags),
unused(src.unused)
{
}
};

#pragma pack(pop)

drawpixelinfo_t& screen_dpi();

Expand Down