Skip to content

Commit

Permalink
Merge pull request #3152 from Ghabry/exelogos
Browse files Browse the repository at this point in the history
Load Logos from RPG_RT
  • Loading branch information
fdelapena authored Nov 29, 2023
2 parents 48c0468 + a206798 commit 148ce74
Show file tree
Hide file tree
Showing 10 changed files with 163 additions and 37 deletions.
4 changes: 2 additions & 2 deletions src/config_param.h
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ class EnumConfigParam : public ConfigParamBase<E> {
EnumConfigParam(StringView name, StringView description, StringView config_section, StringView config_key, E value, std::array<StringView, S> values, std::array<StringView, S> tags, std::array<StringView, S> value_descriptions) :
ConfigParamBase<E>(name, description, config_section, config_key, value), _values{ values }, _tags{ tags}, _value_descriptions{ value_descriptions } {
for (size_t i = 0; i < S; ++i) {
_valid[static_cast<E>(S)] = true;
_valid[static_cast<E>(i)] = true;
}
}

Expand Down Expand Up @@ -425,7 +425,7 @@ class EnumConfigParam : public ConfigParamBase<E> {
}

private:
lcf::FlagSet<E> _valid = ~lcf::FlagSet<E>();
lcf::FlagSet<E> _valid;
std::array<StringView, S> _values;
std::array<StringView, S> _tags;
std::array<StringView, S> _value_descriptions;
Expand Down
105 changes: 88 additions & 17 deletions src/exe_reader.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@
#include <algorithm>
#include <iostream>
#include <fstream>
#include <zlib.h>

namespace {
// hashes of known RPG_RT startup logos
std::array<uint32_t, 5> logo_crc32 = { 0xdf3d86a7, 0x2ece66f9, 0x2fe0de56, 0x25c4618f, 0x91b2635a };
}

EXEReader::EXEReader(Filesystem_Stream::InputStream core) : corefile(std::move(core)) {
// The Incredibly Dumb PE parser (tm)
Expand Down Expand Up @@ -62,7 +68,7 @@ EXEReader::EXEReader(Filesystem_Stream::InputStream core) : corefile(std::move(c
} else if (secName == 0x50454547) { // GEEP
file_info.geep_size = sectVs;
} else if (secName == 0x30585055) { // UPX0
Output::Debug("EXE is UPX compressed. Engine detection could be incorrect.");
Output::Debug("EXEReader: EXE is UPX compressed. Engine detection could be incorrect.");
}

uint32_t sectRva = GetU32(sectionsOfs + 0x0C);
Expand All @@ -76,23 +82,15 @@ EXEReader::EXEReader(Filesystem_Stream::InputStream core) : corefile(std::move(c
}
}

static uint32_t djb2_hash(char* str, size_t length) {
uint32_t hash = 5381;
for (size_t i = 0; i < length; ++i) {
hash = ((hash << 5) + hash) + (uint8_t)str[i];
}
return hash;
}

static std::vector<uint8_t> exe_reader_perform_exfont_save(Filesystem_Stream::InputStream& corefile, uint32_t position, uint32_t len) {
static std::vector<uint8_t> ExtractExFont(Filesystem_Stream::InputStream& corefile, uint32_t position, uint32_t len) {
std::vector<uint8_t> exfont;
constexpr int header_size = 14; // Size of BITMAPFILEHEADER
exfont.resize(len + header_size);

corefile.seekg(position, std::ios_base::beg);
corefile.read(reinterpret_cast<char*>(exfont.data()) + header_size, len);
if (corefile.gcount() != len) {
Output::Debug("ExFont: Error reading resource (read {}, expected {})", corefile.gcount(), len);
Output::Debug("EXEReader: ExFont: Error reading resource (read {}, expected {})", corefile.gcount(), len);
return {};
}

Expand All @@ -105,7 +103,7 @@ static std::vector<uint8_t> exe_reader_perform_exfont_save(Filesystem_Stream::In
// And the header that's going to be prepended.
int header_len = header_size + header.size;
if (header.depth != 8) {
Output::Debug("ExFont: Unsupported depth {}", header.depth);
Output::Debug("EXEReader: ExFont: Unsupported depth {}", header.depth);
return {};
}
header_len += header.num_colors * 4;
Expand All @@ -132,7 +130,8 @@ static std::vector<uint8_t> exe_reader_perform_exfont_save(Filesystem_Stream::In
exfont[pos++] = (header_len >> 24) & 0xFF;

// Check if the ExFont is the original through a fast hash function
if (djb2_hash((char*)exfont.data() + header_size, exfont.size() - header_size) != 0x491e19de) {
auto crc = crc32(0, exfont.data() + header_size, exfont.size() - header_size);
if (crc != 0x86bc6c68) {
Output::Debug("EXEReader: Custom ExFont found");
}
return exfont;
Expand Down Expand Up @@ -169,7 +168,7 @@ std::vector<uint8_t> EXEReader::GetExFont() {
uint32_t filebase = (GetU32(dataent) - resource_rva) + resource_ofs;
uint32_t filesize = GetU32(dataent + 0x04);
Output::Debug("EXEReader: EXFONT resource found (DE {:#x}; {:#x}; len {:#x})", dataent, filebase, filesize);
return exe_reader_perform_exfont_save(corefile, filebase, filesize);
return ExtractExFont(corefile, filebase, filesize);
}
}
resourcesNDEbase += 8;
Expand All @@ -179,14 +178,87 @@ std::vector<uint8_t> EXEReader::GetExFont() {
return {};
}

std::vector<std::vector<uint8_t>> EXEReader::GetLogos() {
corefile.clear();

if (!resource_ofs) {
return {};
}

if (Player::player_config.show_startup_logos.Get() == StartupLogos::None) {
return {};
}

std::vector<std::vector<uint8_t>> logos;

uint32_t resourcesIDEs = GetU16(resource_ofs + 0x0C);
if (resourcesIDEs == 1) {
uint32_t resourcesIDEbase = resource_ofs + 0x10;
if (ResNameCheck(exe_reader_roffset(resource_ofs, GetU32(resourcesIDEbase)), "XYZ")) {
uint32_t xyz_base = exe_reader_roffset(resource_ofs, GetU32(resourcesIDEbase + 4));
uint16_t xyz_logos = std::min<uint16_t>(GetU16(xyz_base + 0x0C), 9);
uint32_t xyz_logo_base = xyz_base + 0x10;

bool only_custom_logos = (Player::player_config.show_startup_logos.Get() == StartupLogos::Custom);
std::string res_name = "LOGOX";

for (int i = 0; i <= xyz_logos; ++i) {
uint32_t name = GetU32(xyz_logo_base);
// Actually a name?
if (name & 0x80000000) {
name = exe_reader_roffset(resource_ofs, name);
res_name.back() = '1' + i;

if (ResNameCheck(name, res_name.c_str()) || (i == 0 && ResNameCheck(name, "LOGO"))) {
uint32_t dataent = GetU32(xyz_logo_base + 4);
if (dataent & 0x80000000) {
dataent = exe_reader_roffset(resource_ofs, dataent);
dataent = resource_ofs + GetU32(dataent + 0x14);
}
uint32_t filebase = (GetU32(dataent) - resource_rva) + resource_ofs;
uint32_t filesize = GetU32(dataent + 0x04);
Output::Debug("EXEReader: {} resource found (DE {:#x}; {:#x}; len {:#x})", res_name, dataent, filebase, filesize);
std::vector<uint8_t> logo;
logo.resize(filesize);

corefile.seekg(filebase, std::ios_base::beg);
corefile.read(reinterpret_cast<char*>(logo.data()), filesize);
if (logo.size() < 8 || strncmp(reinterpret_cast<char*>(logo.data()), "XYZ1", 4) != 0) {
Output::Debug("EXEReader: {}: Not a XYZ image", res_name);
return {};
}

if (corefile.gcount() != filesize) {
Output::Debug("EXEReader: {}: Error reading resource (read {}, expected {})", res_name, corefile.gcount(), filesize);
return {};
}

if (only_custom_logos) {
auto crc = static_cast<uint32_t>(crc32(0, logo.data(), logo.size()));
if (std::find(logo_crc32.begin(), logo_crc32.end(), crc) == logo_crc32.end()) {
logos.push_back(logo);
}
} else {
logos.push_back(logo);
}
}
}

xyz_logo_base += 8;
}
}
}

return logos;
}

const EXEReader::FileInfo& EXEReader::GetFileInfo() {
corefile.clear();

file_info.logos = GetLogoCount();

auto versionDBase = ResOffsetByType(16);
if (versionDBase == 0) {
Output::Debug("EXEReader: VERSIONINFO not found");
return file_info;
}

Expand Down Expand Up @@ -282,14 +354,13 @@ uint32_t EXEReader::GetLogoCount() {
if (!resource_ofs) {
return 0;
}
// For each ID/Name entry in the outer...

uint32_t resourcesIDEs = GetU16(resource_ofs + 0x0C);
if (resourcesIDEs == 1) {
uint32_t resourcesIDEbase = resource_ofs + 0x10;
if (ResNameCheck(exe_reader_roffset(resource_ofs, GetU32(resourcesIDEbase)), "XYZ")) {
uint32_t xyz_logo_base = exe_reader_roffset(resource_ofs, GetU32(resourcesIDEbase + 4));
return static_cast<uint32_t>(GetU16(xyz_logo_base + 0x0C));
return exe_reader_roffset(resource_ofs, GetU32(resourcesIDEbase + 4));
}
}
return 0;
Expand Down
1 change: 1 addition & 0 deletions src/exe_reader.h
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ class EXEReader {
// Extracts an EXFONT resource with BMP header if present
// and returns exfont buffer on success.
std::vector<uint8_t> GetExFont();
std::vector<std::vector<uint8_t>> GetLogos();

struct FileInfo {
uint64_t version = 0;
Expand Down
2 changes: 2 additions & 0 deletions src/game_config.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ void Game_Config::LoadFromStream(Filesystem_Stream::InputStream& is) {
player.settings_autosave.FromIni(ini);
player.settings_in_title.FromIni(ini);
player.settings_in_menu.FromIni(ini);
player.show_startup_logos.FromIni(ini);
}

void Game_Config::WriteToStream(Filesystem_Stream::OutputStream& os) const {
Expand Down Expand Up @@ -500,6 +501,7 @@ void Game_Config::WriteToStream(Filesystem_Stream::OutputStream& os) const {
player.settings_autosave.ToIni(os);
player.settings_in_title.ToIni(os);
player.settings_in_menu.ToIni(os);
player.show_startup_logos.ToIni(os);

os << "\n";
}
11 changes: 11 additions & 0 deletions src/game_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,23 @@ enum class GameResolution {
Ultrawide
};

enum class StartupLogos {
None,
Custom,
All
};

struct Game_ConfigPlayer {
StringConfigParam autobattle_algo{ "", "", "", "", "" };
StringConfigParam enemyai_algo{ "", "", "", "", "" };
BoolConfigParam settings_autosave{ "Save settings on exit", "Automatically save the settings on exit", "Player", "SettingsAutosave", false };
BoolConfigParam settings_in_title{ "Show settings on title screen", "Display settings menu item on the title screen", "Player", "SettingsInTitle", false };
BoolConfigParam settings_in_menu{ "Show settings in menu", "Display settings menu item on the menu screen", "Player", "SettingsInMenu", false };
EnumConfigParam<StartupLogos, 3> show_startup_logos{
"Startup Logos", "Logos that are displayed on startup", "Player", "StartupLogos", StartupLogos::Custom,
Utils::MakeSvArray("None", "Custom", "All"),
Utils::MakeSvArray("none", "custom", "all"),
Utils::MakeSvArray("Do not show any additional logos", "Show custom logos bundled with the game", "Show all logos, including the original from RPG Maker")};

void Hide();
};
Expand Down
3 changes: 0 additions & 3 deletions src/player.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -769,9 +769,6 @@ void Player::CreateGameObjects() {
if (exeis) {
exe_reader.reset(new EXEReader(std::move(exeis)));
Cache::exfont_custom = exe_reader->GetExFont();
if (!Cache::exfont_custom.empty()) {
Output::Debug("ExFont loaded from RPG_RT");
}

if (engine == EngineNone) {
auto version_info = exe_reader->GetFileInfo();
Expand Down
5 changes: 3 additions & 2 deletions src/scene_gamebrowser.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,9 +219,10 @@ void Scene_GameBrowser::BootGame() {
game_loading = false;
load_window->SetVisible(false);

if (!FileFinder::FindImage("Logo", "LOGO1").empty()) {
auto logos = Scene_Logo::LoadLogos();
if (!logos.empty()) {
// Delegate to Scene_Logo when a startup graphic was found
Scene::Push(std::make_shared<Scene_Logo>(1));
Scene::Push(std::make_shared<Scene_Logo>(std::move(logos), 1));
return;
}

Expand Down
59 changes: 48 additions & 11 deletions src/scene_logo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "scene_logo.h"
#include "async_handler.h"
#include "bitmap.h"
#include "exe_reader.h"
#include "filefinder.h"
#include "game_battle.h"
#include "input.h"
Expand All @@ -35,16 +36,21 @@
#include "text.h"
#include "version.h"
#include <ctime>
#include <memory>

Scene_Logo::Scene_Logo(unsigned current_logo_index) :
frame_counter(0), current_logo_index(current_logo_index) {
Scene_Logo::Scene_Logo() :
frame_counter(0) {
type = Scene::Logo;
skip_logos = Player::debug_flag || Game_Battle::battle_test.enabled;
}

if (current_logo_index > 0) {
detected_game = true;
}
Scene_Logo::Scene_Logo(std::vector<std::vector<uint8_t>> logos, unsigned current_logo_index) :
frame_counter(0), logos(std::move(logos)), current_logo_index(current_logo_index) {

skip_logos = Player::debug_flag || Game_Battle::battle_test.enabled;
type = Scene::Logo;

assert(current_logo_index > 0);
detected_game = true;
}

void Scene_Logo::Start() {
Expand All @@ -61,6 +67,8 @@ void Scene_Logo::vUpdate() {
// async delay for emscripten
return;
}

logos = LoadLogos();
}

++frame_counter;
Expand All @@ -86,8 +94,8 @@ void Scene_Logo::vUpdate() {
if (detected_game) {
if (!skip_logos) {
// Check for another logo
if (!FileFinder::FindImage("Logo", "LOGO" + std::to_string(current_logo_index + 1)).empty()) {
Scene::Push(std::make_shared<Scene_Logo>(current_logo_index + 1), true);
if (current_logo_index < logos.size()) {
Scene::Push(std::make_shared<Scene_Logo>(std::move(logos), current_logo_index + 1), true);
return;
}
}
Expand Down Expand Up @@ -162,9 +170,8 @@ BitmapRef Scene_Logo::LoadLogo() {
}
} else {
// Load external logos
// FIXME: Also get the LOGO1,LOGO2,LOGO3 from RPG_RT
auto logo_stream = FileFinder::OpenImage("Logo", "LOGO" + std::to_string(current_logo_index));
current_logo = Bitmap::Create(std::move(logo_stream), false);
const auto& logo_bytes = logos[current_logo_index - 1];
current_logo = Bitmap::Create(logo_bytes.data(), logo_bytes.size(), false);
}

return current_logo;
Expand Down Expand Up @@ -200,7 +207,37 @@ void Scene_Logo::DrawTextOnLogo(bool verbose) {
text_rect.x--;
text_rect.y--;
}
}

std::vector<std::vector<uint8_t>> Scene_Logo::LoadLogos() {
if (Player::player_config.show_startup_logos.Get() == StartupLogos::None) {
return {};
}

std::vector<std::vector<uint8_t>> logos;

for (int i = 1; i < 100; ++i) {
auto is = FileFinder::OpenImage("Logo", "LOGO" + std::to_string(i));
if (is) {
logos.push_back(Utils::ReadStream(is));
} else {
break;
}
}

#ifndef EMSCRIPTEN
if (logos.empty()) {
// Attempt reading Logos from RPG_RT.exe (not supported on Emscripten)
auto exeis = FileFinder::Game().OpenFile(EXE_NAME);

if (exeis) {
std::unique_ptr<EXEReader> exe_reader = std::make_unique<EXEReader>(std::move(exeis));
logos = exe_reader->GetLogos();
}
}
#endif

return logos;
}

void Scene_Logo::OnIndexReady(FileRequestResult*) {
Expand Down
Loading

0 comments on commit 148ce74

Please sign in to comment.