diff --git a/CMakeLists.txt b/CMakeLists.txt index 13e31277eb..ac8def072c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,8 @@ add_library(${PROJECT_NAME} OBJECT src/fileext_guesser.h src/filesystem.cpp src/filesystem.h + src/filesystem_hook.cpp + src/filesystem_hook.h src/filesystem_lzh.cpp src/filesystem_lzh.h src/filesystem_native.cpp diff --git a/Makefile.am b/Makefile.am index 444e7c90f0..9426404206 100644 --- a/Makefile.am +++ b/Makefile.am @@ -103,6 +103,8 @@ libeasyrpg_player_a_SOURCES = \ src/fileext_guesser.h \ src/filesystem.cpp \ src/filesystem.h \ + src/filesystem_hook.cpp \ + src/filesystem_hook.h \ src/filesystem_lzh.cpp \ src/filesystem_lzh.h \ src/filesystem_native.cpp \ diff --git a/lib/liblcf-version b/lib/liblcf-version new file mode 160000 index 0000000000..032ccdc3b7 --- /dev/null +++ b/lib/liblcf-version @@ -0,0 +1 @@ +Subproject commit 032ccdc3b793ecd28158c2128e0ee119ecc867dc diff --git a/src/filesystem_hook.cpp b/src/filesystem_hook.cpp new file mode 100644 index 0000000000..0d03c1b066 --- /dev/null +++ b/src/filesystem_hook.cpp @@ -0,0 +1,145 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#include "filesystem_hook.h" +#include "filesystem_stream.h" +#include "options.h" + +#include +#include +#include + +class CaesarStreamBuf : public std::streambuf { +public: + CaesarStreamBuf(std::streambuf* parent_sb, int n) : parent_sb(parent_sb), n(n) { + setg(&buf, &buf + 1, &buf + 1); + } + CaesarStreamBuf(CaesarStreamBuf const& other) = delete; + CaesarStreamBuf const& operator=(CaesarStreamBuf const& other) = delete; + ~CaesarStreamBuf() { + delete parent_sb; + } + +protected: + int_type underflow() override { + auto byte = parent_sb->sbumpc(); + + if (byte == traits_type::eof()) { + return byte; + } + + buf = traits_type::to_char_type(byte); + buf -= n; + + setg(&buf, &buf, &buf + 1); + + return byte; + } + + std::streambuf::pos_type seekoff(std::streambuf::off_type offset, std::ios_base::seekdir dir, std::ios_base::openmode) override { + return parent_sb->pubseekoff(offset, dir); + } + + std::streambuf::pos_type seekpos(std::streambuf::pos_type pos, std::ios_base::openmode) override { + return parent_sb->pubseekpos(pos); + } + +private: + std::streambuf* parent_sb; + + char buf; + int n; +}; + + +HookFilesystem::HookFilesystem(FilesystemView parent_fs, Hook hook) : Filesystem("", parent_fs), active_hook(hook) { + // no-op +} + +FilesystemView HookFilesystem::Detect(FilesystemView fs) { + auto lmt = fs.OpenInputStream(TREEMAP_NAME); + std::array buf; + + lmt.ReadIntoObj(buf); + + if (!memcmp(buf.data(), "\xbMdgNbqUsff", 11)) { + auto hook_fs = std::make_shared(fs, Hook::SacredTears); + return hook_fs->Subtree(""); + } + + return fs; +} + +bool HookFilesystem::IsFile(StringView path) const { + return GetParent().IsFile(path); +} + +bool HookFilesystem::IsDirectory(StringView path, bool follow_symlinks) const { + return GetParent().IsDirectory(path, follow_symlinks); +} + +bool HookFilesystem::Exists(StringView path) const { + return GetParent().Exists(path); +} + +int64_t HookFilesystem::GetFilesize(StringView path) const { + return GetParent().GetFilesize(path); +} + +bool HookFilesystem::MakeDirectory(StringView dir, bool follow_symlinks) const { + return GetParent().MakeDirectory(dir, follow_symlinks); +} + +bool HookFilesystem::IsFeatureSupported(Feature f) const { + return GetParent().IsFeatureSupported(f); +} + +std::streambuf* HookFilesystem::CreateInputStreambuffer(StringView path, std::ios_base::openmode mode) const { + auto parent_sb = GetParent().CreateInputStreambuffer(path, mode); + + if (active_hook == Hook::SacredTears) { + if (path == TREEMAP_NAME) { + return new CaesarStreamBuf(parent_sb, 1); + } + } + + return parent_sb; +} + +std::streambuf* HookFilesystem::CreateOutputStreambuffer(StringView path, std::ios_base::openmode mode) const { + return GetParent().CreateOutputStreambuffer(path, mode); +} + +bool HookFilesystem::GetDirectoryContent(StringView path, std::vector& tree) const { + auto dir_tree = GetParent().ListDirectory(path); + + if (!dir_tree) { + return false; + } + + for (auto& item: *dir_tree) { + tree.push_back(item.second); + } + + return true; +} + +std::string HookFilesystem::Describe() const { + assert(active_hook == Hook::SacredTears); + + return fmt::format("[Hook] ({})", "Sacred Tears"); +} diff --git a/src/filesystem_hook.h b/src/filesystem_hook.h new file mode 100644 index 0000000000..260116ae2f --- /dev/null +++ b/src/filesystem_hook.h @@ -0,0 +1,60 @@ +/* + * This file is part of EasyRPG Player. + * + * EasyRPG Player is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * EasyRPG Player is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with EasyRPG Player. If not, see . + */ + +#ifndef EP_FILESYSTEM_HOOK_H +#define EP_FILESYSTEM_HOOK_H + +#include "filesystem.h" + +/** + * A virtual filesystem that applies modifications to game files + */ +class HookFilesystem : public Filesystem { +public: + enum class Hook { + // The Sacred Tears: TRUE + // Shifts the offset of all bytes in the LMT back by one + SacredTears + }; + + explicit HookFilesystem(FilesystemView parent_fs, Hook hook); + + static FilesystemView Detect(FilesystemView fs); + + /** + * Implementation of abstract methods + */ + /** @{ */ + bool IsFile(StringView path) const override; + bool IsDirectory(StringView path, bool follow_symlinks) const override; + bool Exists(StringView path) const override; + int64_t GetFilesize(StringView path) const override; + bool MakeDirectory(StringView dir, bool follow_symlinks) const override; + bool IsFeatureSupported(Feature f) const override; + std::string Describe() const override; + /** @} */ +protected: + /** @{ */ + bool GetDirectoryContent(StringView path, std::vector& entries) const override; + std::streambuf* CreateInputStreambuffer(StringView path, std::ios_base::openmode mode) const override; + std::streambuf* CreateOutputStreambuffer(StringView path, std::ios_base::openmode mode) const override; + /** @} */ + + Hook active_hook; +}; + +#endif diff --git a/src/player.cpp b/src/player.cpp index 7645c3f16b..95a5753ed5 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -24,7 +24,6 @@ #include #include #include -#include #ifdef _WIN32 # include "platform/windows/utils.h" @@ -42,6 +41,7 @@ #include "filefinder.h" #include "filefinder_rtp.h" #include "fileext_guesser.h" +#include "filesystem_hook.h" #include "game_actors.h" #include "game_battle.h" #include "game_map.h" @@ -692,6 +692,9 @@ void Player::CreateGameObjects() { } escape_char = Utils::DecodeUTF32(Player::escape_symbol).front(); + // Special handling for games with altered files + FileFinder::SetGameFilesystem(HookFilesystem::Detect(FileFinder::Game())); + // Check for translation-related directories and load language names. translation.InitTranslations();