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();