From 2232e6587a04432bbabf063cac1ab3842e7c6320 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=88=AA=E5=91=B3=E9=BA=BB=E9=85=B1?= <93972760+TaranDahl@users.noreply.github.com> Date: Mon, 17 Aug 2026 23:53:31 +0800 Subject: [PATCH] core Add MixOverlay debug logging Add unconditional MixOverlay init logs Run MixOverlay init in ExtCD first file request block Add IHCore mix overlay design spec Clarify duplicate handling and CRC verification in mix overlay spec Add IHCore mix overlay implementation plan Implement MixOverlay directory-to-mix overlay feature Fix MixOverlay relative directory path separator Update docs for service processing location Fix MixFileClass hook byte lengths Update IHLoader --- Common/IHLoader | 2 +- IHCore/ExtCD.cpp | 47 +++- IHCore/IHCore.vcxproj | 2 + IHCore/IHCore.vcxproj.filters | 6 + IHCore/InitialLoad.cpp | 3 + IHCore/InitialLoad.h | 3 + IHCore/MixOverlay.cpp | 477 ++++++++++++++++++++++++++++++++++ IHCore/MixOverlay.h | 88 +++++++ 8 files changed, 623 insertions(+), 5 deletions(-) create mode 100644 IHCore/MixOverlay.cpp create mode 100644 IHCore/MixOverlay.h diff --git a/Common/IHLoader b/Common/IHLoader index 8a0fa3e..ea45038 160000 --- a/Common/IHLoader +++ b/Common/IHLoader @@ -1 +1 @@ -Subproject commit 8a0fa3ec5815ec8842a5c35b2de047c067b2ea2d +Subproject commit ea450385a25933b3ceee6b933c5dd5d8d3dfd760 diff --git a/IHCore/ExtCD.cpp b/IHCore/ExtCD.cpp index 59b9390..4228705 100644 --- a/IHCore/ExtCD.cpp +++ b/IHCore/ExtCD.cpp @@ -8,6 +8,7 @@ #include #include "CachedFile.h" #include "ExtAudio.h" +#include "MixOverlay.h" extern bool EnableCustomFile; @@ -244,6 +245,8 @@ std::string GetBindingIHFile(const char* pFileName) auto it = Local::IHFileBinder.find(pFileName); if (it != Local::IHFileBinder.end()) return it->second; + if (MixOverlayManager::Instance().IsInternalName(pFileName)) + return pVirtualMixFileClassName; for (auto& p : Local::IHFileFilter) if (p.second && ((bool(__cdecl*)(const char*))p.second)(pFileName)) return p.first; @@ -347,6 +350,10 @@ const char* FileClassExt::CDFileClass_SetFileName(char* pOriginalFileName) { Debug::Log("IHCore : Adding Path \"%hs\"\n", Param.Path); CDExt_Instance.PushCustomPathToTail(Param.Path); }); Service_CustomPathListFirst.RefreshAndProcess([](const auto& Param) { Debug::Log("IHCore : Adding Path \"%hs\"\n", Param.Path); CDExt_Instance.PushCustomPathToFirst(Param.Path); }); + MixOverlay_InitBeforeEverything(); + Service_AddMixDirectory.RefreshAndProcess([](const auto& Param) + { Debug::Log("IHCore : Adding MixDirectory \"%hs\" -> \"%hs\" (%s)\n", Param.Directory, Param.Mix, Param.First ? "First" : "Last"); + MixOverlayManager::Instance().Add(Param.Directory, Param.Mix, Param.First); }); Service_RegisterIHFileTag.RefreshAndProcess([](const auto& Param) { auto Value = Internal_GetGlobalVarPtr(Param.TagType, Param.TagVar); @@ -493,8 +500,26 @@ BOOL FileClassExt::CCFileClass_Open(FileAccessMode Mode) This->Close(); if ( (Mode & FileAccessMode::Write) || This->BufferIOFileClass::Exists(false)) return This->CDFileClass::Open(Mode); + + const uint8_t* overlayData = nullptr; + int overlaySize = 0; + if (MixOverlayManager::Instance().TryGetOverlayFile(Name.c_str(), true, overlayData, overlaySize)) + { + new (&This->Buffer) MemoryBuffer((void*)overlayData, overlaySize); + This->Position = 0; + return TRUE; + } + if (!MixFileClass::Offset(Name.c_str(), &pBuffer, &mixfile, &Offset, &Size)) + { + if (MixOverlayManager::Instance().TryGetOverlayFile(Name.c_str(), false, overlayData, overlaySize)) + { + new (&This->Buffer) MemoryBuffer((void*)overlayData, overlaySize); + This->Position = 0; + return TRUE; + } return This->CDFileClass::Open(Mode); + } if (pBuffer || !mixfile) { if (This != (CCFileClass*)((DWORD)-88)) @@ -613,21 +638,35 @@ bool FileClassExt::CCFileClass_Exists(bool WriteShared) else { auto Name = This->GetFileName(); + + const uint8_t* overlayData = nullptr; + int overlaySize = 0; + if (MixOverlayManager::Instance().TryGetOverlayFile(Name, true, overlayData, overlaySize)) + { + This->Availablility = 1; + return true; + } + if (MixFileClass::Offset(Name, 0, 0, 0, 0)) { This->Availablility = 1; return true; } - else if (This->BufferIOFileClass::Exists(false)) + + if (MixOverlayManager::Instance().TryGetOverlayFile(Name, false, overlayData, overlaySize)) { This->Availablility = 1; return true; } - else + + if (This->BufferIOFileClass::Exists(false)) { - This->Availablility = 2; - return false; + This->Availablility = 1; + return true; } + + This->Availablility = 2; + return false; } } diff --git a/IHCore/IHCore.vcxproj b/IHCore/IHCore.vcxproj index e80cad6..3e25445 100644 --- a/IHCore/IHCore.vcxproj +++ b/IHCore/IHCore.vcxproj @@ -195,6 +195,7 @@ + @@ -579,6 +580,7 @@ + diff --git a/IHCore/IHCore.vcxproj.filters b/IHCore/IHCore.vcxproj.filters index 702a1ca..cdb73a3 100644 --- a/IHCore/IHCore.vcxproj.filters +++ b/IHCore/IHCore.vcxproj.filters @@ -1172,6 +1172,9 @@ File System + + File System + External @@ -1354,6 +1357,9 @@ File System + + File System + Hot Key diff --git a/IHCore/InitialLoad.cpp b/IHCore/InitialLoad.cpp index fe3a910..652dbf5 100644 --- a/IHCore/InitialLoad.cpp +++ b/IHCore/InitialLoad.cpp @@ -14,6 +14,9 @@ InitialLoad::Service Service_StringTable("IHFile::AddStringTable"),//不分大小写 Service_MixFile("IHFile::AddMixFile");//不分大小写 +InitialLoad::Service + Service_AddMixDirectory("IHFile::AddMixDirectory");//不分大小写 + InitialLoad::Service Service_StringTableAddPair("StringTable::AddPair");//Key不分大小写 diff --git a/IHCore/InitialLoad.h b/IHCore/InitialLoad.h index b6c9901..612826b 100644 --- a/IHCore/InitialLoad.h +++ b/IHCore/InitialLoad.h @@ -10,6 +10,9 @@ extern InitialLoad::Service Service_StringTable, Service_MixFile; +extern InitialLoad::Service + Service_AddMixDirectory; + extern InitialLoad::Service Service_StringTableAddPair; diff --git a/IHCore/MixOverlay.cpp b/IHCore/MixOverlay.cpp new file mode 100644 index 0000000..35c3491 --- /dev/null +++ b/IHCore/MixOverlay.cpp @@ -0,0 +1,477 @@ +#include "MixOverlay.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "Debug.h" +#include "../Common/LocalData.h" +#include "../Common/IHLoader/SyringeEx.h" + +#pragma comment(lib, "Shlwapi.lib") + +JsonObject GetIHCoreJson(); + +//============================================================================= +// MixOverlayManager +//============================================================================= + +MixOverlayManager& MixOverlayManager::Instance() +{ + static MixOverlayManager instance; + return instance; +} + +bool MixOverlayManager::Add(const char* dir, const char* mix, bool first) +{ + if (!dir || !mix || !*dir || !*mix) + return false; + + std::string mixName = mix; + std::transform(mixName.begin(), mixName.end(), mixName.begin(), ::toupper); + + std::string dirPath = dir; + if (dirPath.find(':') == std::string::npos + && !(dirPath.size() >= 2 && dirPath[0] == '\\' && dirPath[1] == '\\')) + { + std::string relative = dir; + while (!relative.empty() && (relative.front() == '\\' || relative.front() == '/')) + relative.erase(relative.begin()); + + dirPath = SyringeData::ExecutableDirectoryPath(); + if (!dirPath.empty() && dirPath.back() != '\\' && dirPath.back() != '/') + dirPath += "\\"; + dirPath += relative; + } + + std::string search = dirPath; + if (!search.empty() && search.back() != '\\' && search.back() != '/') + search += "\\"; + search += "*"; + + WIN32_FIND_DATAA fd; + HANDLE hFind = FindFirstFileA(search.c_str(), &fd); + if (hFind == INVALID_HANDLE_VALUE) + { + Debug::Log("IHCore : MixOverlay directory \"%s\" not found or unreadable.\n", dirPath.c_str()); + return false; + } + + struct FileInfo + { + std::string name; + std::string path; + uint32_t size; + }; + + std::vector files; + do + { + if (fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) + continue; + + std::string name = fd.cFileName; + if (name == "." || name == "..") + continue; + + std::string path = dirPath; + if (!path.empty() && path.back() != '\\' && path.back() != '/') + path += "\\"; + path += name; + + files.push_back({ name, path, fd.nFileSizeLow }); + } while (FindNextFileA(hFind, &fd)); + FindClose(hFind); + + if (files.empty()) + { + Debug::Log("IHCore : MixOverlay directory \"%s\" is empty; skipped.\n", dirPath.c_str()); + return false; + } + + struct MixEntry + { + int32_t crc; + uint32_t offset; + uint32_t size; + std::string name; + std::string path; + }; + + std::vector entries; + uint32_t dataSize = 0; + for (const auto& f : files) + { + std::string upper = f.name; + std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper); + int crc = CRCEngine::Memory(upper.c_str(), (int)upper.size(), 0); + entries.push_back({ crc, dataSize, f.size, f.name, f.path }); + dataSize += f.size; + } + + std::sort(entries.begin(), entries.end(), [](const MixEntry& a, const MixEntry& b) + { + return a.crc < b.crc; + }); + + uint16_t count = (uint16_t)entries.size(); + std::vector image(6u + (size_t)count * 12u + dataSize, 0); + memcpy(image.data(), &count, 2); + memcpy(image.data() + 2, &dataSize, 4); + + size_t pos = 6; + for (const auto& e : entries) + { + memcpy(image.data() + pos, &e.crc, 4); + memcpy(image.data() + pos + 4, &e.offset, 4); + memcpy(image.data() + pos + 8, &e.size, 4); + pos += 12; + } + + for (const auto& e : entries) + { + FILE* fp = fopen(e.path.c_str(), "rb"); + if (!fp) + { + Debug::Log("IHCore : MixOverlay failed to open \"%s\"; skipped mapping %s.\n", e.path.c_str(), mixName.c_str()); + return false; + } + + size_t read = fread(image.data() + 6u + (size_t)count * 12u + e.offset, 1, e.size, fp); + fclose(fp); + + if (read != e.size) + { + Debug::Log("IHCore : MixOverlay failed to read \"%s\"; skipped mapping %s.\n", e.path.c_str(), mixName.c_str()); + return false; + } + } + + std::string internalName = "$IH" + mixName; + + auto it = _entries.find(mixName); + if (it != _entries.end()) + { + Debug::Log("IHCore : MixOverlay duplicate mapping for %s; replacing.\n", mixName.c_str()); + _internalToEntry.erase(it->second.InternalName); + _entries.erase(it); + } + + MixOverlayEntry entry; + entry.MixName = mixName; + entry.DirectoryPath = dirPath; + entry.First = first; + entry.InternalName = internalName; + entry.Image = std::move(image); + + for (const auto& e : entries) + { + std::string upper = e.name; + std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper); + entry.Files[upper] = { 6u + (uint32_t)count * 12u + e.offset, e.size }; + } + + auto res = _entries.emplace(mixName, std::move(entry)); + _internalToEntry[internalName] = &res.first->second; + + return true; +} + +bool MixOverlayManager::IsInternalName(const char* name) const +{ + if (!name) + return false; + return _internalToEntry.find(name) != _internalToEntry.end(); +} + +const std::vector* MixOverlayManager::GetImage(const char* internalName) const +{ + if (!internalName) + return nullptr; + + auto it = _internalToEntry.find(internalName); + if (it == _internalToEntry.end()) + return nullptr; + + return &it->second->Image; +} + +bool MixOverlayManager::IsRealMixLoaded(const char* mixName) const +{ + if (!mixName || !*mixName) + return false; + + GenericList& list = static_cast(MixFileClass::MIXes.get()); + for (GenericNode* n = list.First(); n->IsValid(); n = n->Next()) + { + auto* mix = static_cast(n); + if (!mix || !mix->FileName) + continue; + + const char* base = PathFindFileNameA(mix->FileName); + if (base && _stricmp(base, mixName) == 0) + return true; + } + + return false; +} + +bool MixOverlayManager::TryGetOverlayFile(const char* filename, bool first, const uint8_t*& outData, int& outSize) +{ + if (!filename || !*filename) + return false; + + std::string upper = filename; + std::transform(upper.begin(), upper.end(), upper.begin(), ::toupper); + + for (auto& kv : _entries) + { + auto& entry = kv.second; + if (entry.First != first) + continue; + + auto it = entry.Files.find(upper); + if (it == entry.Files.end()) + continue; + + if (!IsRealMixLoaded(entry.MixName.c_str())) + continue; + + const auto& info = it->second; + if (info.Offset >= entry.Image.size() || info.Size == 0) + continue; + + outData = entry.Image.data() + info.Offset; + outSize = (int)info.Size; + return true; + } + + return false; +} + +MixOverlayEntry* MixOverlayManager::FindByMixName(const char* mixName) +{ + if (!mixName) + return nullptr; + + auto it = _entries.find(mixName); + if (it == _entries.end()) + return nullptr; + + return &it->second; +} + +MixOverlayEntry* MixOverlayManager::FindByRealMix(MixFileClass* real) +{ + if (!real) + return nullptr; + + auto it = _realToEntry.find(real); + if (it == _realToEntry.end()) + return nullptr; + + return it->second; +} + +MixOverlayEntry* MixOverlayManager::FindByOverlay(MixFileClass* overlay) +{ + if (!overlay) + return nullptr; + + auto it = _overlayToEntry.find(overlay); + if (it == _overlayToEntry.end()) + return nullptr; + + return it->second; +} + +void MixOverlayManager::Associate(MixOverlayEntry& entry, MixFileClass* real, MixFileClass* overlay) +{ + entry.Real = real; + entry.Overlay = overlay; + _realToEntry[real] = &entry; + _overlayToEntry[overlay] = &entry; +} + +void MixOverlayManager::Dissociate(MixOverlayEntry& entry) +{ + if (entry.Real) + _realToEntry.erase(entry.Real); + if (entry.Overlay) + _overlayToEntry.erase(entry.Overlay); + entry.Real = nullptr; + entry.Overlay = nullptr; +} + +void MixOverlayManager::DissociateOverlay(MixOverlayEntry& entry) +{ + if (entry.Overlay) + _overlayToEntry.erase(entry.Overlay); + entry.Overlay = nullptr; +} + +//============================================================================= +// VirtualMixFileClass +//============================================================================= + +void VirtualMixFileClass::Initialize() +{ + m_position = 0; + m_open = false; + m_initialized = false; + new (&m_fileName) std::string(); +} + +const char* VirtualMixFileClass::GetFileName() const +{ + return m_fileName.c_str(); +} + +const char* VirtualMixFileClass::SetFileName(const char* pFileName) +{ + m_fileName = pFileName ? pFileName : ""; + m_initialized = true; + return m_fileName.c_str(); +} + +bool VirtualMixFileClass::Exists(bool writeShared) +{ + return MixOverlayManager::Instance().GetImage(m_fileName.c_str()) != nullptr; +} + +bool VirtualMixFileClass::HasHandle() +{ + return m_open; +} + +bool VirtualMixFileClass::Open(FileAccessMode access) +{ + if (!MixOverlayManager::Instance().GetImage(m_fileName.c_str())) + return false; + + m_position = 0; + m_open = true; + return true; +} + +int VirtualMixFileClass::ReadBytes(void* pBuffer, int nNumBytes) +{ + if (!m_open || !pBuffer || nNumBytes <= 0) + return 0; + + const auto* image = MixOverlayManager::Instance().GetImage(m_fileName.c_str()); + if (!image) + return 0; + + if (m_position >= image->size()) + return 0; + + const size_t available = image->size() - m_position; + const int toRead = (nNumBytes < (int)available) ? nNumBytes : (int)available; + memcpy(pBuffer, image->data() + m_position, toRead); + m_position += toRead; + return toRead; +} + +int VirtualMixFileClass::Seek(int offset, FileSeekMode seek) +{ + if (!m_initialized) + return 0; + + const auto* image = MixOverlayManager::Instance().GetImage(m_fileName.c_str()); + const size_t fileSize = image ? image->size() : 0; + + size_t newPos = m_position; + switch (seek) + { + case FileSeekMode::Set: + newPos = (offset >= 0) ? (size_t)offset : 0; + break; + case FileSeekMode::Current: + if (offset >= 0) + newPos = m_position + offset; + else + newPos = ((size_t)(-offset) >= m_position) ? 0 : m_position + offset; + break; + case FileSeekMode::End: + if (offset >= 0) + newPos = fileSize; + else + newPos = ((size_t)(-offset) >= fileSize) ? 0 : fileSize + offset; + break; + } + + m_position = (newPos > fileSize) ? fileSize : newPos; + return (int)m_position; +} + +int VirtualMixFileClass::GetFileSize() +{ + const auto* image = MixOverlayManager::Instance().GetImage(m_fileName.c_str()); + if (!image) + return 0; + + const size_t sz = image->size(); + return (sz <= 0x7FFFFFFF) ? (int)sz : 0x7FFFFFFF; +} + +void VirtualMixFileClass::Close() +{ + m_position = 0; + m_open = false; +} + +vptr_t VirtualMixFileClass::GetVTable() +{ + return GetIHFileRegisterKey(); +} + +//============================================================================= +// Init +//============================================================================= + +void MixOverlay_InitBeforeEverything() +{ + Local::RegisterIHFileStream(pVirtualMixFileClassName, + { VirtualMixFileClass::GetVTable(), sizeof(VirtualMixFileClass) }); + + auto cfg = GetIHCoreJson(); + if (cfg) + { + auto ParseList = [&cfg](const char* key, bool first) + { + auto arr = cfg.GetObjectItem(key); + if (!arr.Available() || !arr.IsNotEmptyArray()) + return; + + for (auto& item : arr.GetArrayObject()) + { + auto dirObj = item.GetObjectItem("Directory"); + auto mixObj = item.GetObjectItem("Mix"); + if (!dirObj.Available() || !dirObj.IsTypeString()) + { + Debug::Log("IHCore : MixOverlay config %s entry missing Directory.\n", key); + continue; + } + if (!mixObj.Available() || !mixObj.IsTypeString()) + { + Debug::Log("IHCore : MixOverlay config %s entry missing Mix.\n", key); + continue; + } + + MixOverlayManager::Instance().Add(dirObj.GetCString(), mixObj.GetCString(), first); + } + }; + + ParseList("MixDirectory_First", true); + ParseList("MixDirectory_Last", false); + } + +} diff --git a/IHCore/MixOverlay.h b/IHCore/MixOverlay.h new file mode 100644 index 0000000..11a3d19 --- /dev/null +++ b/IHCore/MixOverlay.h @@ -0,0 +1,88 @@ +#pragma once + +#include +#include +#include +#include +#include "../Common/IHLoader/IH.File.h" +#include "ToolFunc.h" + +class MixFileClass; + +struct OverlayFileInfo +{ + uint32_t Offset{ 0 }; // offset into MixOverlayEntry::Image data section + uint32_t Size{ 0 }; +}; + +struct MixOverlayEntry +{ + std::string MixName; // normalized, e.g. "SIDEC01.MIX" + std::string DirectoryPath; // absolute path + bool First{ false }; + std::string InternalName; // e.g. "$IHSIDEC01.MIX" + std::vector Image; // generated plain MIX image + std::unordered_map Files; + MixFileClass* Overlay{ nullptr }; + MixFileClass* Real{ nullptr }; +}; + +class MixOverlayManager +{ +public: + static MixOverlayManager& Instance(); + + bool Add(const char* dir, const char* mix, bool first); + bool IsInternalName(const char* name) const; + const std::vector* GetImage(const char* internalName) const; + bool TryGetOverlayFile(const char* filename, bool first, const uint8_t*& outData, int& outSize); + bool IsRealMixLoaded(const char* mixName) const; + MixOverlayEntry* FindByMixName(const char* mixName); + MixOverlayEntry* FindByRealMix(MixFileClass* real); + MixOverlayEntry* FindByOverlay(MixFileClass* overlay); + void Associate(MixOverlayEntry& entry, MixFileClass* real, MixFileClass* overlay); + void Dissociate(MixOverlayEntry& entry); + void DissociateOverlay(MixOverlayEntry& entry); + +private: + MixOverlayManager() = default; + ~MixOverlayManager() = default; + MixOverlayManager(const MixOverlayManager&) = delete; + MixOverlayManager& operator=(const MixOverlayManager&) = delete; + + std::unordered_map _entries; + std::unordered_map _internalToEntry; + std::unordered_map _realToEntry; + std::unordered_map _overlayToEntry; +}; + +class VirtualMixFileClass : public IHReadOnlyFileClass +{ +public: + virtual void Initialize() override; + virtual const char* GetFileName() const override; + virtual const char* SetFileName(const char* pFileName) override; + virtual bool Exists(bool writeShared = false) override; + virtual bool HasHandle() override; + virtual bool Open(FileAccessMode access) override; + virtual int ReadBytes(void* pBuffer, int nNumBytes) override; + virtual int Seek(int offset, FileSeekMode seek) override; + virtual int GetFileSize() override; + virtual void Close() override; + + static vptr_t GetVTable(); + + explicit VirtualMixFileClass(noinit_t _) + : IHReadOnlyFileClass(_) + { } + +private: + std::string m_fileName; + size_t m_position{ 0 }; + bool m_open{ false }; + bool m_initialized{ false }; +}; + +static const char* pVirtualMixFileClassName = "VirtualMixFileClass"; + +void MixOverlay_InitBeforeEverything();