diff --git a/Client/game_sa/CModelInfoSA.cpp b/Client/game_sa/CModelInfoSA.cpp index 9b3a4d40a1..1cd01a51f4 100644 --- a/Client/game_sa/CModelInfoSA.cpp +++ b/Client/game_sa/CModelInfoSA.cpp @@ -502,7 +502,7 @@ bool CModelInfoSA::IsValid() bool CModelInfoSA::IsAllocatedInArchive() { - return pGame->GetStreaming()->GetStreamingInfoFromModelId(m_dwModelID)->sizeInBlocks > 0; + return pGame->GetStreaming()->GetStreamingInfo(m_dwModelID)->sizeInBlocks > 0; } float CModelInfoSA::GetDistanceFromCentreOfMassToBaseOfModel() @@ -753,7 +753,7 @@ void CModelInfoSA::StaticFlushPendingRestreamIPL() for (it = removedModels.begin(); it != removedModels.end(); it++) { pGame->GetStreaming()->RemoveModel(*it); - pGame->GetStreaming()->GetStreamingInfoFromModelId(*it)->loadState = 0; + pGame->GetStreaming()->GetStreamingInfo(*it)->loadState = 0; } } @@ -1435,10 +1435,10 @@ void CModelInfoSA::SetVoice(const char* szVoiceType, const char* szVoice) void CModelInfoSA::CopyStreamingInfoFromModel(ushort usBaseModelID) { - CStreamingInfo* pBaseModelStreamingInfo = pGame->GetStreaming()->GetStreamingInfoFromModelId(usBaseModelID); - CStreamingInfo* pTargetModelStreamingInfo = pGame->GetStreaming()->GetStreamingInfoFromModelId(m_dwModelID); + CStreamingInfo* pBaseModelStreamingInfo = pGame->GetStreaming()->GetStreamingInfo(usBaseModelID); + CStreamingInfo* pTargetModelStreamingInfo = pGame->GetStreaming()->GetStreamingInfo(m_dwModelID); - pTargetModelStreamingInfo->Reset(); + *pTargetModelStreamingInfo = CStreamingInfo{}; pTargetModelStreamingInfo->archiveId = pBaseModelStreamingInfo->archiveId; pTargetModelStreamingInfo->offsetInBlocks = pBaseModelStreamingInfo->offsetInBlocks; pTargetModelStreamingInfo->sizeInBlocks = pBaseModelStreamingInfo->sizeInBlocks; @@ -1529,7 +1529,7 @@ void CModelInfoSA::DeallocateModel(void) } ppModelInfo[m_dwModelID] = nullptr; - pGame->GetStreaming()->GetStreamingInfoFromModelId(m_dwModelID)->Reset(); + *pGame->GetStreaming()->GetStreamingInfo(m_dwModelID) = CStreamingInfo{}; } ////////////////////////////////////////////////////////////////////////////////////////// // diff --git a/Client/game_sa/CStreamingSA.cpp b/Client/game_sa/CStreamingSA.cpp index a91c6bafb7..d300965506 100644 --- a/Client/game_sa/CStreamingSA.cpp +++ b/Client/game_sa/CStreamingSA.cpp @@ -13,11 +13,18 @@ #include #include "CStreamingSA.h" #include "CModelInfoSA.h" +#include "Fileapi.h" +#include "processthreadsapi.h" extern CCoreInterface* g_pCore; // count: 26316 in unmodified game -CStreamingInfo* CStreamingSA::ms_aInfoForModel = (CStreamingInfo*)CStreaming__ms_aInfoForModel; +CStreamingInfo (&CStreamingSA::ms_aInfoForModel)[26316] = *(CStreamingInfo(*)[26316])0x8E4CC0; +HANDLE (&CStreamingSA::m_aStreamingHandlers)[32] = *(HANDLE(*)[32])0x8E4010; // Contains open files +CArchiveInfo (&CStreamingSA::ms_aAchiveInfo)[8] = *(CArchiveInfo(*)[8])0x8E48D8; // [8][0x30] +HANDLE* phStreamingThread = (HANDLE*)0x8E4008; +uint32 (&CStreamingSA::ms_streamingHalfOfBufferSize) = *(uint32*)0x8E4CA8; +void* (&CStreamingSA::ms_pStreamingBuffer)[2] = *(void*(*)[2])0x8E4CAC; namespace { @@ -149,17 +156,152 @@ void CStreamingSA::RequestSpecialModel(DWORD model, const char* szTexture, DWORD } } -CStreamingInfo* CStreamingSA::GetStreamingInfoFromModelId(uint32 id) +void CStreamingSA::ReinitStreaming() { - return &ms_aInfoForModel[id]; + typedef int(__cdecl * Function_ReInitStreaming)(); + ((Function_ReInitStreaming)(0x40E560))(); } -void CStreamingSA::ReinitStreaming() +// ReinitStreaming should be called after this. +// Otherwise the model wont be restreamed +// TODO: Somehow restream a single model instead of the whole world +void CStreamingSA::SetStreamingInfo(uint modelid, unsigned char usStreamID, uint uiOffset, ushort usSize, uint uiNextInImg) { - typedef int(__cdecl * Function_ReInitStreaming)(); - Function_ReInitStreaming reinitStreaming = (Function_ReInitStreaming)(0x40E560); + CStreamingInfo* pItemInfo = GetStreamingInfo(modelid); + + // Change nextInImg field for prev model + for (CStreamingInfo& info : ms_aInfoForModel) + { + if (info.archiveId == pItemInfo->archiveId) + { + // Check if the block after `info` is the beginning of `pItemInfo`'s block + if (info.offsetInBlocks + info.sizeInBlocks == pItemInfo->offsetInBlocks) + { + info.nextInImg = -1; + break; + } + } + } + + pItemInfo->archiveId = usStreamID; + pItemInfo->offsetInBlocks = uiOffset; + pItemInfo->sizeInBlocks = usSize; + pItemInfo->nextInImg = uiNextInImg; +} + +CStreamingInfo* CStreamingSA::GetStreamingInfo(uint modelid) +{ + return &ms_aInfoForModel[modelid]; +} + +unsigned char CStreamingSA::GetUnusedArchive() +{ + // Get internal IMG id + // By default gta sa uses 6 of 8 IMG archives + for (size_t i = 6; i < 8; i++) + { + if (!GetArchiveInfo(i)->uiStreamHandleId) + return (unsigned char)i; + } + return -1; +} + +unsigned char CStreamingSA::GetUnusedStreamHandle() +{ + for (size_t i = 0; i < VAR_StreamHandlersMaxCount; i++) + { + if (m_aStreamingHandlers[i]) + return (unsigned char)i; + } + return -1; +} + +unsigned char CStreamingSA::AddArchive(const char* szFilePath) +{ + const auto ucArchiveId = GetUnusedArchive(); + if (ucArchiveId == -1) + return -1; + + // Get free stream handler id + const auto ucStreamID = GetUnusedStreamHandle(); + if (ucStreamID == -1) + return -1; + + // Create new stream handler + const auto streamCreateFlags = *(DWORD*)0x8E3FE0; + HANDLE hFile = CreateFileA( + szFilePath, + GENERIC_READ, + FILE_SHARE_READ, + NULL, + OPEN_EXISTING, + streamCreateFlags | FILE_ATTRIBUTE_READONLY | FILE_FLAG_RANDOM_ACCESS, + NULL + ); + + if (hFile == INVALID_HANDLE_VALUE) + return -1; + + // Register stream handler + m_aStreamingHandlers[ucStreamID] = hFile; + + // Register archive data + ms_aAchiveInfo[ucArchiveId].uiStreamHandleId = (ucStreamID << 24); + + return ucArchiveId; +} + +void CStreamingSA::RemoveArchive(unsigned char ucArhiveID) +{ + unsigned int uiStreamHandlerID = ms_aAchiveInfo[ucArhiveID].uiStreamHandleId >> 24; + if (!uiStreamHandlerID) + return; + + ms_aAchiveInfo[ucArhiveID].uiStreamHandleId = 0; + + CloseHandle(m_aStreamingHandlers[uiStreamHandlerID]); + m_aStreamingHandlers[uiStreamHandlerID] = NULL; +} + +void CStreamingSA::SetStreamingBufferSize(uint32 uiBlockSize) +{ + if (uiBlockSize == ms_streamingHalfOfBufferSize * 2) + return; + + int pointer = *(int*)0x8E3FFC; + SGtaStream(&streaming)[5] = *(SGtaStream(*)[5])(pointer); + + // Wait while streaming threads ends tasks + while (streaming[0].bInUse && streaming[1].bInUse) + + // Suspend streaming handle + SuspendThread(*phStreamingThread); + + // Create new buffer + if (uiBlockSize & 1) + uiBlockSize++; + + typedef void*(__cdecl * Function_CMemoryMgr_MallocAlign)(uint32 uiCount, uint32 uiAlign); + void* pNewBuffer = ((Function_CMemoryMgr_MallocAlign)(0x72F4C0))(uiBlockSize << 11, 2048); + + // Copy data from old buffer to new buffer + uint uiCopySize = std::min(ms_streamingHalfOfBufferSize, uiBlockSize / 2); + MemCpyFast(pNewBuffer, (void*)ms_pStreamingBuffer[0], uiCopySize); + MemCpyFast((void*)(reinterpret_cast(pNewBuffer) + 1024 * uiBlockSize), (void*)ms_pStreamingBuffer[1], uiCopySize); + + typedef void(__cdecl * Function_CMemoryMgr_FreeAlign)(void* pos); + ((Function_CMemoryMgr_FreeAlign)(0x72F4F0))(ms_pStreamingBuffer[0]); + + ms_streamingHalfOfBufferSize = uiBlockSize / 2; + + ms_pStreamingBuffer[0] = pNewBuffer; + ms_pStreamingBuffer[1] = (void*)(reinterpret_cast(pNewBuffer) + 1024 * uiBlockSize); + + streaming[0].pBuffer = ms_pStreamingBuffer[0]; + streaming[1].pBuffer = ms_pStreamingBuffer[1]; - reinitStreaming(); + // Well done + ResumeThread(*phStreamingThread); } void CStreamingSA::MakeSpaceFor(std::uint32_t memoryToCleanInBytes) diff --git a/Client/game_sa/CStreamingSA.h b/Client/game_sa/CStreamingSA.h index 77915a8c48..ac830c033d 100644 --- a/Client/game_sa/CStreamingSA.h +++ b/Client/game_sa/CStreamingSA.h @@ -13,13 +13,43 @@ #include +#define VAR_StreamHandlersMaxCount 32 +#define VAR_MaxArchives 8 + #define FUNC_CStreaming__RequestModel 0x4087E0 #define FUNC_LoadAllRequestedModels 0x40EA10 #define FUNC_CStreaming__HasVehicleUpgradeLoaded 0x407820 #define FUNC_CStreaming_RequestSpecialModel 0x409d10 + +struct CArchiveInfo +{ + char szName[40]; + BYTE bUnknow = 1; // Only in player.img is 0. Maybe, it is DWORD value + BYTE bUnused[3]; + DWORD uiStreamHandleId; +}; + +struct SGtaStream +{ + uint32_t nSectorsOffset; + uint32_t nSectorsToRead; + void* pBuffer; + uint8_t bUnknow1; + uint8_t bLocked; + uint8_t bInUse; + uint8_t bUnknow2; + uint32_t uiStatus; + uint32_t handle; + uint32_t file; + uint8_t pad[20]; +}; +static_assert(sizeof(SGtaStream) == 0x30, "Invalid size for SGtaStream"); + class CStreamingSA final : public CStreaming { +private: + static CArchiveInfo* GetArchiveInfo(uint id) { return &ms_aAchiveInfo[id]; }; public: void RequestModel(DWORD dwModelID, DWORD dwFlags); void RemoveModel(std::uint32_t model) override; @@ -27,10 +57,23 @@ class CStreamingSA final : public CStreaming bool HasModelLoaded(DWORD dwModelID); void RequestSpecialModel(DWORD model, const char* szTexture, DWORD channel); void ReinitStreaming(); - CStreamingInfo* GetStreamingInfoFromModelId(uint32 id); + + CStreamingInfo* GetStreamingInfo(uint32 id); + void SetStreamingInfo(uint32 modelid, unsigned char usStreamID, uint uiOffset, ushort usSize, uint uiNextInImg = -1); + unsigned char GetUnusedArchive(); + unsigned char GetUnusedStreamHandle(); + unsigned char AddArchive(const char* szFilePath); + void RemoveArchive(unsigned char ucStreamHandler); + void SetStreamingBufferSize(uint32 uiSize); + uint32 GetStreamingBufferSize() { return ms_streamingHalfOfBufferSize * 2; }; + void MakeSpaceFor(std::uint32_t memoryToCleanInBytes) override; std::uint32_t GetMemoryUsed() const override; private: - static CStreamingInfo* ms_aInfoForModel; // count: 26316 in unmodified game + static void* (&ms_pStreamingBuffer)[2]; + static uint32 (&ms_streamingHalfOfBufferSize); + static CStreamingInfo (&ms_aInfoForModel)[26316]; // count: 26316 in unmodified game + static HANDLE (&m_aStreamingHandlers)[32]; + static CArchiveInfo (&ms_aAchiveInfo)[8]; }; diff --git a/Client/mods/deathmatch/StdInc.h b/Client/mods/deathmatch/StdInc.h index 61479914fd..d9048a57f7 100644 --- a/Client/mods/deathmatch/StdInc.h +++ b/Client/mods/deathmatch/StdInc.h @@ -79,6 +79,7 @@ #include #include #include +#include #include #include #include diff --git a/Client/mods/deathmatch/logic/CClientColManager.cpp b/Client/mods/deathmatch/logic/CClientColManager.cpp index 0a456d9c59..0afeb093c7 100644 --- a/Client/mods/deathmatch/logic/CClientColManager.cpp +++ b/Client/mods/deathmatch/logic/CClientColManager.cpp @@ -69,6 +69,7 @@ void CClientColManager::DoHitDetectionForColShape(CClientColShape* pShape) case CCLIENTDFF: case CCLIENTCOL: case CCLIENTTXD: + case CCLIENTIMG: case CCLIENTSOUND: break; default: diff --git a/Client/mods/deathmatch/logic/CClientEntity.h b/Client/mods/deathmatch/logic/CClientEntity.h index 819727210c..cdfe2e6a07 100644 --- a/Client/mods/deathmatch/logic/CClientEntity.h +++ b/Client/mods/deathmatch/logic/CClientEntity.h @@ -78,6 +78,7 @@ enum eClientEntityType CCLIENTIFP, CCLIENTVECTORGRAPHIC, CCLIENTUNKNOWN, + CCLIENTIMG, }; class CEntity; @@ -140,7 +141,8 @@ enum eCClientEntityClassTypes CLASS_CClientWeapon, CLASS_CClientEffect, CLASS_CClientPointLights, - CLASS_CClientSearchLight + CLASS_CClientSearchLight, + CLASS_CClientIMG, }; class CClientEntity : public CClientEntityBase diff --git a/Client/mods/deathmatch/logic/CClientGame.cpp b/Client/mods/deathmatch/logic/CClientGame.cpp index ad713af561..a3a010bc47 100644 --- a/Client/mods/deathmatch/logic/CClientGame.cpp +++ b/Client/mods/deathmatch/logic/CClientGame.cpp @@ -1204,6 +1204,9 @@ void CClientGame::DoPulses() // Initialize the game g_pCore->GetGame()->Initialize(); + + // Save default streamer buffer size in IMG manager + m_pManager->GetIMGManager()->InitDefaultBufferSize(); } unsigned char ucError = g_pNet->GetConnectionError(); diff --git a/Client/mods/deathmatch/logic/CClientIMG.cpp b/Client/mods/deathmatch/logic/CClientIMG.cpp new file mode 100644 index 0000000000..052f8592cd --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientIMG.cpp @@ -0,0 +1,241 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientIMG.cpp + * PURPOSE: IMG container class + * + *****************************************************************************/ + +#include +#include "game/CStreaming.h" + +#define INVALID_ARCHIVE_ID 0xFF + +struct tImgHeader +{ + char szMagic[4]; + unsigned int uiFilesCount; +}; + +CClientIMG::CClientIMG(class CClientManager* pManager, ElementID ID) : + ClassInit(this), + CClientEntity(ID), + m_pImgManager(pManager->GetIMGManager()), + m_ucArchiveID(INVALID_ARCHIVE_ID), + m_usRequiredBufferSize(0) +{ + m_pManager = pManager; + SetTypeName("img"); + m_pImgManager->AddToList(this); +} + +CClientIMG::~CClientIMG() +{ + m_pImgManager->RemoveFromList(this); + if (IsStreamed()) + StreamDisable(); +} + +bool CClientIMG::Load(fs::path filePath) +{ + if (!m_fileInfos.empty()) + return false; + + if (m_ifs.is_open()) + return false; + + if (filePath.empty()) + return false; + + if (!fs::exists(filePath)) + return false; + + m_filePath = filePath.string(); + m_ifs = std::ifstream(filePath, std::ios::binary); + + // Open the file + if (m_ifs.fail()) + { + m_ifs.close(); + return false; + } + + tImgHeader fileHeader; + + // Read header + m_ifs.read(reinterpret_cast(&fileHeader), sizeof(tImgHeader)); + + if (m_ifs.fail() || m_ifs.eof() || memcmp(&fileHeader.szMagic, "VER2", 4) != 0) + { + m_ifs.close(); + return false; + } + + // Read content info + try + { + m_fileInfos.resize(fileHeader.uiFilesCount); + } + catch (const std::bad_alloc&) + { + m_ifs.close(); + return false; + } + + m_ifs.read(reinterpret_cast(m_fileInfos.data()), sizeof(tImgFileInfo) * (std::streampos)fileHeader.uiFilesCount); + if (m_ifs.fail() || m_ifs.eof()) + { + m_ifs.close(); + return false; + } + + return true; +} + +void CClientIMG::Unload() +{ + m_fileInfos.clear(); + m_fileInfos.shrink_to_fit(); + m_ifs.close(); +} + +bool CClientIMG::GetFile(size_t fileID, std::string& buffer) +{ + const tImgFileInfo* pFileInfo = GetFileInfo(fileID); + if (!pFileInfo) + throw std::invalid_argument("Invalid file id"); + + const auto ulToReadSize = pFileInfo->usSize * 2048; + + try + { + buffer.resize(ulToReadSize); + } + catch (const std::bad_alloc&) + { + throw std::invalid_argument("Out of memory"); + } + + m_ifs.seekg((std::streampos)pFileInfo->uiOffset * 2048); + m_ifs.read(buffer.data(), ulToReadSize); + + return !m_ifs.fail() && !m_ifs.eof(); +} + +tImgFileInfo* CClientIMG::GetFileInfo(size_t fileID) +{ + if (fileID >= m_fileInfos.size()) + return nullptr; + return &m_fileInfos[fileID]; +} + +std::optional CClientIMG::GetFileID(std::string_view filename) +{ + const auto it = std::find_if(m_fileInfos.begin(), m_fileInfos.end(), + [filename](const auto& fileInfo) { + return filename.compare(fileInfo.szFileName) == 0; + } + ); + + if (it == m_fileInfos.end()) + return std::nullopt; + return std::distance(m_fileInfos.begin(), it); +} + +bool CClientIMG::IsStreamed() +{ + return m_ucArchiveID != INVALID_ARCHIVE_ID; +} + +bool CClientIMG::StreamEnable() +{ + if (m_fileInfos.empty()) + return false; + + if (IsStreamed()) + return false; + + if (m_usRequiredBufferSize == 0) + { + for (const auto& fileInfo : m_fileInfos) + m_usRequiredBufferSize = Max(m_usRequiredBufferSize, fileInfo.usSize); + } + + m_ucArchiveID = g_pGame->GetStreaming()->AddArchive(m_filePath.c_str()); + + if (IsStreamed()) + { + m_pImgManager->UpdateStreamerBufferSize(); + return true; + } + return false; +} + +bool CClientIMG::StreamDisable() +{ + if (!IsStreamed()) + return false; + + // Unlink all models + for (const auto& v : m_restoreInfo) + { + g_pGame->GetStreaming()->SetStreamingInfo( + v.uiModelID, v.ucStreamID, v.uiOffset, v.usSize + ); + } + m_restoreInfo.clear(); + m_restoreInfo.shrink_to_fit(); + + // Remove archive from streaming + g_pGame->GetStreaming()->RemoveArchive(m_ucArchiveID); + m_ucArchiveID = INVALID_ARCHIVE_ID; + + m_pImgManager->UpdateStreamerBufferSize(); + + g_pClientGame->RestreamWorld(); + return true; +} + +bool CClientIMG::LinkModel(unsigned int uiModelID, size_t uiFileID) +{ + if (!IsStreamed()) + return false; + + tImgFileInfo* pFileInfo = GetFileInfo(uiFileID); + if (!pFileInfo) + return false; + + CStreamingInfo* pCurrInfo = g_pGame->GetStreaming()->GetStreamingInfo(uiModelID); + + if (pCurrInfo->archiveId == m_ucArchiveID) + return true; // Already linked + + m_restoreInfo.emplace_back(uiModelID, + pCurrInfo->offsetInBlocks, pCurrInfo->sizeInBlocks, pCurrInfo->archiveId); + + g_pGame->GetStreaming()->SetStreamingInfo( + uiModelID, m_ucArchiveID, pFileInfo->uiOffset, pFileInfo->usSize); + + return true; +} + +bool CClientIMG::UnlinkModel(unsigned int uiModelID) +{ + const auto it = std::find_if(m_restoreInfo.begin(), m_restoreInfo.end(), + [uiModelID](const auto& restoreInfo) { + return restoreInfo.uiModelID == uiModelID; + } + ); + + if (it == m_restoreInfo.end()) + return false; + + g_pGame->GetStreaming()->SetStreamingInfo( + uiModelID, it->ucStreamID, it->uiOffset, it->usSize); + + m_restoreInfo.erase(it); + + return true; +} diff --git a/Client/mods/deathmatch/logic/CClientIMG.h b/Client/mods/deathmatch/logic/CClientIMG.h new file mode 100644 index 0000000000..e3260f9b4a --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientIMG.h @@ -0,0 +1,90 @@ +/***************************************************************************** +* +* PROJECT: Multi Theft Auto v1.0 +* (Shared logic for modifications) +* LICENSE: See LICENSE in the top level directory +* FILE: mods/shared_logic/CClientIMG.h +* PURPOSE: IMG container class header + +* +*****************************************************************************/ + +#pragma once + +#include "CClientEntity.h" +#include +#include +#include +#include +#include +#include +namespace fs = std::filesystem; + +struct tImgFileInfo +{ + unsigned int uiOffset; + unsigned short usSize; + unsigned short usUnpackedSize; + char szFileName[24]; +}; + +struct tLinkedModelRestoreInfo +{ + constexpr tLinkedModelRestoreInfo(unsigned int uiModelID, unsigned int uiOffset, + unsigned short usSize, unsigned char ucStreamID) : + uiModelID(uiModelID), + uiOffset(uiOffset), + usSize(usSize), + ucStreamID(ucStreamID) + { + } + + unsigned int uiModelID; + unsigned int uiOffset; + unsigned short usSize; + unsigned char ucStreamID; +}; + +class CClientIMG : public CClientEntity +{ + DECLARE_CLASS(CClientIMG, CClientEntity) + friend class CClientIMGManager; +public: + CClientIMG(class CClientManager* pManager, ElementID ID); + ~CClientIMG(); + + void Unlink() {}; + void GetPosition(CVector& vecPosition) const {}; + void SetPosition(const CVector& vecPosition) {}; + + eClientEntityType GetType() const { return CCLIENTIMG; } + unsigned char GetArchiveID() { return m_ucArchiveID; } + unsigned int GetFilesCount() { return m_fileInfos.size(); } + const auto& GetFileInfos() const noexcept { return m_fileInfos; } + unsigned short GetRequiredBufferSize() { return m_usRequiredBufferSize; } + + bool Load(fs::path filePath); + void Unload(); + + tImgFileInfo* GetFileInfo(size_t fileID); + std::optional GetFileID(std::string_view filename); + bool GetFile(size_t uiFileID, std::string& buffer); + + bool StreamEnable(); + bool StreamDisable(); + bool IsStreamed(); + + bool LinkModel(unsigned int usModelID, size_t fileID); + bool UnlinkModel(unsigned int usModelID); + +private: + class CClientIMGManager* m_pImgManager; + + std::ifstream m_ifs; + std::string m_filePath; + unsigned char m_ucArchiveID; + std::vector m_fileInfos; + unsigned short m_usRequiredBufferSize; + + std::vector m_restoreInfo; +}; diff --git a/Client/mods/deathmatch/logic/CClientIMGManager.cpp b/Client/mods/deathmatch/logic/CClientIMGManager.cpp new file mode 100644 index 0000000000..2c597f8d9e --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientIMGManager.cpp @@ -0,0 +1,118 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientIMGManager.cpp + * PURPOSE: .img container manager class + * + *****************************************************************************/ + +#include "StdInc.h" +#include "game/CStreaming.h" + +CClientIMGManager::CClientIMGManager(CClientManager* pManager) +{ + // Init + m_bRemoveFromList = true; +} + +CClientIMGManager::~CClientIMGManager() +{ + // Delete all our IMG's + RemoveAll(); +} + +void CClientIMGManager::InitDefaultBufferSize() +{ + m_uiDefaultStreamerBufferSize = g_pGame->GetStreaming()->GetStreamingBufferSize(); +} + +CClientIMG* CClientIMGManager::GetElementFromArchiveID(unsigned char ucArchiveID) +{ + // By default GTA has 5 IMG's + if (ucArchiveID < 6) + return NULL; + + std::list::iterator iter = m_List.begin(); + for (; iter != m_List.end(); iter++) + { + CClientIMG* pIMG = *iter; + if (ucArchiveID == pIMG->GetArchiveID()) + { + return pIMG; + } + } + + return NULL; +} + +void CClientIMGManager::RemoveAll() +{ + // Make sure they don't remove themselves from our list + m_bRemoveFromList = false; + + // Run through our list deleting the IMG's + std::list::iterator iter = m_List.begin(); + for (; iter != m_List.end(); iter++) + { + delete *iter; + } + + // Allow list removal again + m_bRemoveFromList = true; +} + +bool CClientIMGManager::Exists(CClientIMG* pIMG) +{ + return std::find(m_List.begin(), m_List.end(), pIMG) != m_List.end(); +} + +CClientIMG* CClientIMGManager::GetElementThatLinked(unsigned int uiModel) +{ + uchar ucArhiveID = g_pGame->GetStreaming()->GetStreamingInfo(uiModel)->archiveId; + return GetElementFromArchiveID(ucArhiveID); +} + +bool CClientIMGManager::IsLinkableModel(unsigned int uiModel) +{ + return uiModel <= 26316; // StreamModelInfoSize +} + +bool CClientIMGManager::RestoreModel(unsigned int uiModel) +{ + // Get the DFF file that replaced it + CClientIMG* pIMG = GetElementThatLinked(uiModel); + if (pIMG) + { + // Restore it + return pIMG->UnlinkModel(uiModel); + } + + // Nothing to restore + return false; +} + +void CClientIMGManager::RemoveFromList(CClientIMG* pIMG) +{ + // Can we remove anything from the list? + if (m_bRemoveFromList) + { + m_List.remove(pIMG); + } +} + +void CClientIMGManager::UpdateStreamerBufferSize() +{ + ushort usRequestStreamSize = m_uiDefaultStreamerBufferSize; + + for (CClientIMG* pImg : m_List) + { + if (!pImg->IsStreamed()) + continue; + ushort usStreamSize = pImg->GetRequiredBufferSize(); + if (usStreamSize > usRequestStreamSize) + usRequestStreamSize = usStreamSize; + } + g_pGame->GetStreaming()->SetStreamingBufferSize(usRequestStreamSize); +} diff --git a/Client/mods/deathmatch/logic/CClientIMGManager.h b/Client/mods/deathmatch/logic/CClientIMGManager.h new file mode 100644 index 0000000000..706b8476a2 --- /dev/null +++ b/Client/mods/deathmatch/logic/CClientIMGManager.h @@ -0,0 +1,44 @@ +/***************************************************************************** + * + * PROJECT: Multi Theft Auto v1.0 + * (Shared logic for modifications) + * LICENSE: See LICENSE in the top level directory + * FILE: mods/shared_logic/CClientIMGManager.h + * PURPOSE: .img container manager class + * + *****************************************************************************/ + +class CClientIMGManager; + +#pragma once + +#include +#include "CClientIMG.h" + +class CClientIMGManager +{ + friend class CClientIMG; + +public: + CClientIMGManager(class CClientManager* pManager); + ~CClientIMGManager(); + + void InitDefaultBufferSize(); + void RemoveAll(); + bool Exists(CClientIMG* pIMG); + + CClientIMG* GetElementFromArchiveID(unsigned char ucStreamID); + CClientIMG* GetElementThatLinked(unsigned int uiModel); + + bool RestoreModel(unsigned int uiModel); + static bool IsLinkableModel(unsigned int uiModel); + void UpdateStreamerBufferSize(); + +private: + void AddToList(CClientIMG* pIMG) { m_List.push_back(pIMG); } + void RemoveFromList(CClientIMG* pIMG); + + std::list m_List; + bool m_bRemoveFromList; + uint32 m_uiDefaultStreamerBufferSize; +}; diff --git a/Client/mods/deathmatch/logic/CClientManager.cpp b/Client/mods/deathmatch/logic/CClientManager.cpp index 51aeda3d8f..21962cc8f3 100644 --- a/Client/mods/deathmatch/logic/CClientManager.cpp +++ b/Client/mods/deathmatch/logic/CClientManager.cpp @@ -53,6 +53,7 @@ CClientManager::CClientManager() m_pPointLightsManager = new CClientPointLightsManager(this); m_pModelManager = new CClientModelManager(); m_pPacketRecorder = new CClientPacketRecorder(this); + m_pImgManager = new CClientIMGManager(this); m_bBeingDeleted = false; m_bGameUnloadedFlag = false; @@ -173,6 +174,9 @@ CClientManager::~CClientManager() delete m_pModelManager; m_pModelManager = nullptr; + + delete m_pImgManager; + m_pImgManager = nullptr; } // diff --git a/Client/mods/deathmatch/logic/CClientManager.h b/Client/mods/deathmatch/logic/CClientManager.h index 316dc6bd9d..e916c8eb73 100644 --- a/Client/mods/deathmatch/logic/CClientManager.h +++ b/Client/mods/deathmatch/logic/CClientManager.h @@ -42,6 +42,7 @@ class CClientManager; #include "CClientEffectManager.h" #include "CClientPointLightsManager.h" #include "CClientModelManager.h" +#include "CClientIMGManager.h" class CClientProjectileManager; class CClientExplosionManager; @@ -94,6 +95,7 @@ class CClientManager CClientWeaponManager* GetWeaponManager() { return m_pWeaponManager; } CClientEffectManager* GetEffectManager() { return m_pEffectManager; } CClientPointLightsManager* GetPointLightsManager() { return m_pPointLightsManager; } + CClientIMGManager* GetIMGManager() { return m_pImgManager; } bool IsGameLoaded() { return g_pGame->GetSystemState() == 9 && !m_bGameUnloadedFlag && g_pCore->GetNetwork()->GetServerBitStreamVersion(); } bool IsBeingDeleted() { return m_bBeingDeleted; } @@ -144,6 +146,7 @@ class CClientManager CClientEffectManager* m_pEffectManager; CClientPointLightsManager* m_pPointLightsManager; CClientModelManager* m_pModelManager; + CClientIMGManager* m_pImgManager; CClientPacketRecorder* m_pPacketRecorder; bool m_bBeingDeleted; bool m_bGameUnloadedFlag; diff --git a/Client/mods/deathmatch/logic/CResource.cpp b/Client/mods/deathmatch/logic/CResource.cpp index 2628182049..ee9a44dc42 100644 --- a/Client/mods/deathmatch/logic/CResource.cpp +++ b/Client/mods/deathmatch/logic/CResource.cpp @@ -68,6 +68,11 @@ CResource::CResource(unsigned short usNetID, const char* szResourceName, CClient m_pResourceIFPRoot = new CClientDummy(g_pClientGame->GetManager(), INVALID_ELEMENT_ID, "ifproot"); m_pResourceIFPRoot->MakeSystemEntity(); + // Create our IMG root element. We set its parent when we're loaded. + // Make it a system entity so nothing but us can delete it. + m_pResourceIMGRoot = new CClientDummy(g_pClientGame->GetManager(), INVALID_ELEMENT_ID, "imgroot"); + m_pResourceIMGRoot->MakeSystemEntity(); + m_strResourceDirectoryPath = SString("%s/resources/%s", g_pClientGame->GetFileCacheRoot(), *m_strResourceName); m_strResourcePrivateDirectoryPath = PathJoin(CServerIdManager::GetSingleton()->GetConnectionPrivateDirectory(), m_strResourceName); @@ -116,6 +121,10 @@ CResource::~CResource() g_pClientGame->GetElementDeleter()->DeleteRecursive(m_pResourceIFPRoot); m_pResourceIFPRoot = NULL; + // Destroy the img root so all img elements are deleted except those moved out + g_pClientGame->GetElementDeleter()->DeleteRecursive(m_pResourceIMGRoot); + m_pResourceIMGRoot = NULL; + // Destroy the ddf root so all dff elements are deleted except those moved out g_pClientGame->GetElementDeleter()->DeleteRecursive(m_pResourceDFFEntity); m_pResourceDFFEntity = NULL; diff --git a/Client/mods/deathmatch/logic/CResource.h b/Client/mods/deathmatch/logic/CResource.h index aa7feefb37..66ed186a19 100644 --- a/Client/mods/deathmatch/logic/CResource.h +++ b/Client/mods/deathmatch/logic/CResource.h @@ -76,6 +76,7 @@ class CResource CClientEntity* GetResourceDFFRoot() { return m_pResourceDFFEntity; }; CClientEntity* GetResourceTXDRoot() { return m_pResourceTXDRoot; }; CClientEntity* GetResourceIFPRoot() { return m_pResourceIFPRoot; }; + CClientEntity* GetResourceIMGRoot() { return m_pResourceIMGRoot; }; // This is to delete all the elements created in this resource that are created locally in this client void DeleteClientChildren(); @@ -115,6 +116,7 @@ class CResource class CClientEntity* m_pResourceGUIEntity; class CClientEntity* m_pResourceTXDRoot; class CClientEntity* m_pResourceIFPRoot; + class CClientEntity* m_pResourceIMGRoot; unsigned short m_usRemainingNoClientCacheScripts; bool m_bLoadAfterReceivingNoClientCacheScripts; CMtaVersion m_strMinServerReq; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.cpp b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.cpp index 856a4fadbf..912fd99f66 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.cpp +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.cpp @@ -30,6 +30,7 @@ CClientPickupManager* CLuaFunctionDefs::m_pPickupManager; CClientDFFManager* CLuaFunctionDefs::m_pDFFManager; CClientColModelManager* CLuaFunctionDefs::m_pColModelManager; CRegisteredCommands* CLuaFunctionDefs::m_pRegisteredCommands; +CClientIMGManager* CLuaFunctionDefs::m_pImgManager; void CLuaFunctionDefs::Initialize(CLuaManager* pLuaManager, CScriptDebugging* pScriptDebugging, CClientGame* pClientGame) { @@ -53,4 +54,5 @@ void CLuaFunctionDefs::Initialize(CLuaManager* pLuaManager, CScriptDebugging* pS m_pDFFManager = m_pManager->GetDFFManager(); m_pColModelManager = m_pManager->GetColModelManager(); m_pRegisteredCommands = m_pClientGame->GetRegisteredCommands(); + m_pImgManager = m_pManager->GetIMGManager(); } diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h index 5f0153807d..1681110616 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionDefs.h @@ -138,4 +138,5 @@ class CLuaFunctionDefs static CClientDFFManager* m_pDFFManager; static CClientColModelManager* m_pColModelManager; static CRegisteredCommands* m_pRegisteredCommands; + static CClientIMGManager* m_pImgManager; }; diff --git a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h index 201a70a427..dafd8fb5b4 100644 --- a/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h +++ b/Client/mods/deathmatch/logic/lua/CLuaFunctionParseHelpers.h @@ -265,6 +265,10 @@ inline SString GetClassTypeName(CClientTXD*) { return "txd"; } +inline SString GetClassTypeName(CClientIMG*) +{ + return "img"; +} inline SString GetClassTypeName(CClientSound*) { return "sound"; diff --git a/Client/mods/deathmatch/logic/lua/LuaCommon.h b/Client/mods/deathmatch/logic/lua/LuaCommon.h index f27c91c295..fea0bcdda4 100644 --- a/Client/mods/deathmatch/logic/lua/LuaCommon.h +++ b/Client/mods/deathmatch/logic/lua/LuaCommon.h @@ -37,6 +37,7 @@ class CClientPlayer; class CClientRadarMarker; class CClientTeam; class CClientTXD; +class CClientIMG; class CClientVehicle; class CClientWater; class CClientWeapon; diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaClassDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaClassDefs.cpp index 851fe7cf01..eaf38fd3b6 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaClassDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaClassDefs.cpp @@ -356,6 +356,8 @@ const char* CLuaClassDefs::GetEntityClass(CClientEntity* pEntity) return "EngineCOL"; case CCLIENTTXD: return "EngineTXD"; + case CCLIENTIMG: + return "EngineIMG"; case CCLIENTSOUND: return static_cast(pEntity)->IsSound3D() ? "Sound3D" : "Sound"; case CCLIENTWATER: diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp index 97c2f67406..de07ea84a4 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.cpp @@ -30,6 +30,7 @@ CClientPickupManager* CLuaDefs::m_pPickupManager = NULL; CClientDFFManager* CLuaDefs::m_pDFFManager = NULL; CClientColModelManager* CLuaDefs::m_pColModelManager = NULL; CRegisteredCommands* CLuaDefs::m_pRegisteredCommands = NULL; +CClientIMGManager* CLuaDefs::m_pImgManager = NULL; bool ms_bRegisterdPostCallHook = false; void CLuaDefs::Initialize(CClientGame* pClientGame, CLuaManager* pLuaManager, CScriptDebugging* pScriptDebugging) @@ -54,6 +55,7 @@ void CLuaDefs::Initialize(CClientGame* pClientGame, CLuaManager* pLuaManager, CS m_pDFFManager = m_pManager->GetDFFManager(); m_pColModelManager = m_pManager->GetColModelManager(); m_pRegisteredCommands = pClientGame->GetRegisteredCommands(); + m_pImgManager = m_pManager->GetIMGManager(); } int CLuaDefs::CanUseFunction(lua_CFunction f, lua_State* luaVM) diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h index 5d206c1182..cf043b4572 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaDefs.h @@ -64,6 +64,7 @@ class CLuaDefs static CClientDFFManager* m_pDFFManager; static CClientColModelManager* m_pColModelManager; static CRegisteredCommands* m_pRegisteredCommands; + static CClientIMGManager* m_pImgManager; protected: // Old style: Only warn on failure. This should diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp index 9e48fdef99..3bb9403490 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp @@ -64,6 +64,17 @@ void CLuaEngineDefs::LoadFunctions() {"engineGetObjectGroupPhysicalProperty", EngineGetObjectGroupPhysicalProperty}, {"engineRestoreObjectGroupPhysicalProperties", EngineRestoreObjectGroupPhysicalProperties}, {"engineRestreamWorld", ArgumentParser}, + {"engineLoadIMG", ArgumentParser}, + {"engineImageLinkDFF", ArgumentParser}, + {"engineImageLinkTXD", ArgumentParser}, + {"engineRestoreDFFImage", ArgumentParser}, + {"engineRestoreTXDImage", ArgumentParser}, + {"engineAddImage", ArgumentParser}, + {"engineRemoveImage", ArgumentParser}, + {"engineImageGetFilesCount", ArgumentParser}, + {"engineImageGetFiles", ArgumentParser}, + {"engineImageGetFile", ArgumentParser}, + {"engineGetModelTXDID", ArgumentParser}, {"engineStreamingFreeUpMemory", ArgumentParser}, {"engineStreamingGetUsedMemory", ArgumentParser}, @@ -90,6 +101,8 @@ void CLuaEngineDefs::AddClass(lua_State* luaVM) lua_classfunction(luaVM, "setModelLODDistance", "engineSetModelLODDistance"); lua_classfunction(luaVM, "resetModelLODDistance", "engineResetModelLODDistance"); lua_classfunction(luaVM, "setModelVisibleTime", "engineSetModelVisibleTime"); + lua_classfunction(luaVM, "restoreDFFImage", "engineRestoreDFFImage"); + lua_classfunction(luaVM, "restoreTXDImage", "engineRestoreTXDImage"); lua_classfunction(luaVM, "getVisibleTextureNames", "engineGetVisibleTextureNames"); lua_classfunction(luaVM, "getModelLODDistance", "engineGetModelLODDistance"); @@ -110,6 +123,7 @@ void CLuaEngineDefs::AddClass(lua_State* luaVM) AddEngineColClass(luaVM); AddEngineTxdClass(luaVM); AddEngineDffClass(luaVM); + AddEngineImgClass(luaVM); } void CLuaEngineDefs::AddEngineColClass(lua_State* luaVM) @@ -133,6 +147,26 @@ void CLuaEngineDefs::AddEngineTxdClass(lua_State* luaVM) lua_registerclass(luaVM, "EngineTXD", "Element"); } +void CLuaEngineDefs::AddEngineImgClass(lua_State* luaVM) +{ + lua_newclass(luaVM); + + lua_classfunction(luaVM, "create", "engineLoadIMG"); + lua_classfunction(luaVM, "add", "engineAddImage"); + lua_classfunction(luaVM, "remove", "engineRemoveImage"); + lua_classfunction(luaVM, "getFile", "engineImageGetFile"); + lua_classfunction(luaVM, "getFiles", "engineImageGetFiles"); + lua_classfunction(luaVM, "getFilesCount", "engineImageGetFilesCount"); + lua_classfunction(luaVM, "linkTXD", "engineImageLinkTXD"); + lua_classfunction(luaVM, "linkDFF", "engineImageLinkDFF"); + + lua_classvariable(luaVM, "filesCount", nullptr, ArgumentParser); + lua_classvariable(luaVM, "files", nullptr, ArgumentParser); + + lua_registerclass(luaVM, "EngineIMG", "Element"); +} + + void CLuaEngineDefs::AddEngineDffClass(lua_State* luaVM) { lua_newclass(luaVM); @@ -519,6 +553,145 @@ int CLuaEngineDefs::EngineImportTXD(lua_State* luaVM) return 1; } +CClientIMG* CLuaEngineDefs::EngineLoadIMG(lua_State* const luaVM, std::string strRelativeFilePath) +{ + CLuaMain* pLuaMain = m_pLuaManager->GetVirtualMachine(luaVM); + + // Get the resource we belong to + CResource* pResource = pLuaMain->GetResource(); + if (!pResource) + return false; + + std::string strFullPath; + + if (CResourceManager::ParseResourcePathInput(strRelativeFilePath, pResource, &strFullPath)) + { + // Grab the resource root entity + CClientEntity* pRoot = pResource->GetResourceIMGRoot(); + // Create the img handle + CClientIMG* pImg = new CClientIMG(m_pManager, INVALID_ELEMENT_ID); + + // Attempt loading the file + if (pImg->Load(std::move(strFullPath))) + { + // Success. Make it a child of the resource img root + pImg->SetParent(pRoot); + + return pImg; + } + else + { + delete pImg; + throw std::invalid_argument("Error loading IMG"); + } + } + + throw std::invalid_argument("Bad file path"); +} + +bool CLuaEngineDefs::EngineAddImage(CClientIMG* pIMG) +{ + return pIMG->StreamEnable(); +} + +bool CLuaEngineDefs::EngineRemoveImage(CClientIMG* pIMG) +{ + return pIMG->StreamDisable(); +} + +uint CLuaEngineDefs::EngineImageGetFilesCount(CClientIMG* pIMG) +{ + return pIMG->GetFilesCount(); +} + +std::vector CLuaEngineDefs::EngineImageGetFileList(CClientIMG* pIMG) +{ + const auto& fileInfos = pIMG->GetFileInfos(); + + std::vector out; + out.reserve(fileInfos.size()); + + for (const auto& fileInfo : fileInfos) + out.emplace_back(fileInfo.szFileName); + + return out; +} + +static size_t ResolveIMGFileID(CClientIMG* pIMG, std::variant file) +{ + if (std::holds_alternative(file)) + return std::get(file); + + const auto fileName = std::get(file); + if (const auto id = pIMG->GetFileID(fileName)) + return id.value(); + throw std::invalid_argument(SString("Invalid file name specified (%*s)", (int)fileName.length(), fileName.data())); +} + +std::string CLuaEngineDefs::EngineImageGetFile(CClientIMG* pIMG, std::variant file) +{ + std::string buffer; + + if (!pIMG->GetFile(ResolveIMGFileID(pIMG, file), buffer)) // Get file might throw + throw std::invalid_argument("Failed to read file. Probably EOF reached, make sure the archieve isn't corrupted."); + + return buffer; +} + +bool CLuaEngineDefs::EngineImageLinkDFF(CClientIMG* pIMG, std::variant file, uint uiModelID) +{ + if (uiModelID >= 20000) + throw std::invalid_argument(SString("Expected modelid in range 0 - 19999, got %d", uiModelID)); + + size_t fileID = ResolveIMGFileID(pIMG, file); + std::string buffer; + if (!pIMG->GetFile(ResolveIMGFileID(pIMG, file), buffer)) + throw std::invalid_argument("Failed to read file. Probably EOF reached, make sure the archieve isn't corrupted."); + + if (!g_pCore->GetNetwork()->CheckFile("dff", "", buffer.data(), buffer.size())) + throw std::invalid_argument("Failed to link file. Make sure the archieve isn't corrupted."); + + return pIMG->LinkModel(uiModelID, fileID); +} + +bool CLuaEngineDefs::EngineImageLinkTXD(CClientIMG* pIMG, std::variant file, uint uiTxdID) +{ + if (uiTxdID >= 5000) + throw std::invalid_argument(SString("Expected txdid in range 0 - 4999, got %d", uiTxdID)); + + size_t fileID = ResolveIMGFileID(pIMG, file); + std::string buffer; + if (!pIMG->GetFile(ResolveIMGFileID(pIMG, file), buffer)) + throw std::invalid_argument("Failed to read file. Probably EOF reached, make sure the archieve isn't corrupted."); + + if (!g_pCore->GetNetwork()->CheckFile("txd", "", buffer.data(), buffer.size())) + throw std::invalid_argument("Failed to link file. Make sure the archieve isn't corrupted."); + + return pIMG->LinkModel(20000 + uiTxdID, fileID); +} + +bool CLuaEngineDefs::EngineRestoreDFFImage(uint uiModelID) +{ + if (uiModelID >= 20000) + throw std::invalid_argument("Expected model ID in range [0-19999] at argument 1"); + + if (CClientIMGManager::IsLinkableModel(uiModelID)) + return m_pImgManager->RestoreModel(uiModelID); + + return false; +} + +bool CLuaEngineDefs::EngineRestoreTXDImage(uint uiModelID) +{ + if (uiModelID >= 5000) + throw std::invalid_argument("Expected TXD ID in range [0-4999] at argument 1"); + + if (CClientIMGManager::IsLinkableModel(uiModelID)) + return m_pImgManager->RestoreModel(20000 + uiModelID); + + return false; +} + int CLuaEngineDefs::EngineReplaceModel(lua_State* luaVM) { CClientDFF* pDFF; @@ -1016,6 +1189,11 @@ int CLuaEngineDefs::EngineRemoveShaderFromWorldTexture(lua_State* luaVM) return 1; } +uint CLuaEngineDefs::EngineGetModelTXDID(uint uiModelID) +{ + return g_pGame->GetRenderWare()->GetTXDIDForModelID(uiModelID); +} + int CLuaEngineDefs::EngineGetModelNameFromID(lua_State* luaVM) { // string engineGetModelNameFromID ( int modelID ) diff --git a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h index a0d83b1049..2e45d0bd93 100644 --- a/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h +++ b/Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.h @@ -58,12 +58,24 @@ class CLuaEngineDefs : public CLuaDefs LUA_DECLARE(EngineSetObjectGroupPhysicalProperty) LUA_DECLARE(EngineGetObjectGroupPhysicalProperty) LUA_DECLARE(EngineRestoreObjectGroupPhysicalProperties) - static bool EngineRestreamWorld(lua_State* const luaVM); - static bool EngineSetModelVisibleTime(std::string strModelId, char cHourOn, char cHourOff); + static uint EngineGetModelTXDID(uint uiDffModelID); + static CClientIMG* EngineLoadIMG(lua_State* const luaVM, std::string strFilePath); + static bool EngineAddImage(CClientIMG* pImg); + static bool EngineRemoveImage(CClientIMG* pImg); + static uint EngineImageGetFilesCount(CClientIMG* pImg); + static bool EngineImageLinkDFF(CClientIMG* pImg, std::variant file, uint uiModelID); + static bool EngineImageLinkTXD(CClientIMG* pImg, std::variant file, uint uiModelID); + static bool EngineRestoreDFFImage(uint uiModelID); + static bool EngineRestoreTXDImage(uint uiModelID); + static std::vector EngineImageGetFileList(CClientIMG* pImg); + static std::string EngineImageGetFile(CClientIMG* pImg, std::variant file); + static bool EngineRestreamWorld(lua_State* const luaVM); + static bool EngineSetModelVisibleTime(std::string strModelId, char cHourOn, char cHourOff); static std::variant> EngineGetModelVisibleTime(std::string strModelId); private: static void AddEngineColClass(lua_State* luaVM); static void AddEngineTxdClass(lua_State* luaVM); static void AddEngineDffClass(lua_State* luaVM); + static void AddEngineImgClass(lua_State* luaVM); }; diff --git a/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp b/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp index bfc1753094..41c3ab8170 100644 --- a/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp +++ b/Client/multiplayer_sa/CMultiplayerSA_CrashFixHacks.cpp @@ -1004,7 +1004,7 @@ void _declspec(naked) HOOK_CClumpModelInfo_GetFrameFromId() } } -CStreamingInfo* GetStreamingInfoFromModelId(uint id) +CStreamingInfo* GetStreamingInfo(uint id) { CStreamingInfo* pItemInfo = (CStreamingInfo*)(CStreaming__ms_aInfoForModel); return pItemInfo + id; @@ -1036,7 +1036,7 @@ void OnMY_CEntity_GetBoundRect(CEntitySAInterface* pEntity) if (!pColModel) { // Crash will occur at offset 00134134 - CStreamingInfo* pStreamingInfo = pGameInterface->GetStreaming()->GetStreamingInfoFromModelId(usModelId); + CStreamingInfo* pStreamingInfo = pGameInterface->GetStreaming()->GetStreamingInfo(usModelId); SString strDetails("refs:%d txd:%d RwObj:%08x bOwn:%d bColStr:%d flg:%d off:%d size:%d loadState:%d", pModelInfo->usNumberOfRefs, pModelInfo->usTextureDictionary, pModelInfo->pRwObject, pModelInfo->bDoWeOwnTheColModel, pModelInfo->bCollisionWasStreamedWithModel, pStreamingInfo->flg, pStreamingInfo->offsetInBlocks, pStreamingInfo->sizeInBlocks, diff --git a/Client/sdk/game/CRenderWare.h b/Client/sdk/game/CRenderWare.h index 1c3ffbf0ff..9611054d48 100644 --- a/Client/sdk/game/CRenderWare.h +++ b/Client/sdk/game/CRenderWare.h @@ -98,8 +98,9 @@ class CRenderWare virtual bool ReplacePartModels(RpClump* pClump, RpAtomicContainer* pAtomics, unsigned int uiAtomics, const char* szName) = 0; virtual void PulseWorldTextureWatch() = 0; virtual void GetModelTextureNames(std::vector& outNameList, ushort usModelID) = 0; - virtual bool GetModelTextures(std::vector>& outTextureList, ushort usModelID, std::vector vTextureNames) = 0; - virtual const char* GetTextureName(CD3DDUMMY* pD3DData) = 0; + virtual bool GetModelTextures(std::vector>& outTextureList, ushort usModelID, std::vector vTextureNames) = 0; + virtual const char* GetTextureName(CD3DDUMMY* pD3DData) = 0; + virtual ushort GetTXDIDForModelID(ushort usModelID) = 0; virtual void SetRenderingClientEntity(CClientEntityBase* pClientEntity, ushort usModelId, int iTypeMask) = 0; virtual SShaderItemLayers* GetAppliedShaderForD3DData(CD3DDUMMY* pD3DData) = 0; diff --git a/Client/sdk/game/CStreaming.h b/Client/sdk/game/CStreaming.h index c83e950f54..27e9231001 100644 --- a/Client/sdk/game/CStreaming.h +++ b/Client/sdk/game/CStreaming.h @@ -15,27 +15,14 @@ struct CStreamingInfo { - uint16_t prevId; - uint16_t nextId; - uint16_t nextInImg; - uint8_t flg; - uint8_t archiveId; - uint32_t offsetInBlocks; - uint32_t sizeInBlocks; - uint32_t loadState; - -public: - void Reset() - { - this->loadState = 0; - this->nextInImg = -1; - this->nextId = -1; - this->prevId = -1; - this->archiveId = 0; - this->flg = 0; - this->offsetInBlocks = 0; - this->sizeInBlocks = 0; - }; + uint16_t prevId = (uint16_t)-1; + uint16_t nextId = (uint16_t)-1; + uint16_t nextInImg = (uint16_t)-1; + uint8_t flg = 0u; + uint8_t archiveId = 0u; + uint32_t offsetInBlocks = 0u; + uint32_t sizeInBlocks = 0u; + uint32_t loadState = 0u; }; static_assert(sizeof(CStreamingInfo) == 0x14, "Invalid size for CStreamingInfo"); @@ -47,8 +34,13 @@ class CStreaming virtual void LoadAllRequestedModels(bool bOnlyPriorityModels = false, const char* szTag = NULL) = 0; virtual bool HasModelLoaded(DWORD dwModelID) = 0; virtual void RequestSpecialModel(DWORD model, const char* szTexture, DWORD channel) = 0; - virtual CStreamingInfo* GetStreamingInfoFromModelId(uint32 id) = 0; + virtual CStreamingInfo* GetStreamingInfo(uint32 id) = 0; virtual void ReinitStreaming() = 0; + virtual unsigned char AddArchive(const char* szFilePath) = 0; + virtual void RemoveArchive(unsigned char ucArchiveID) = 0; + virtual void SetStreamingInfo(unsigned int id, unsigned char usStreamID, unsigned int uiOffset, unsigned short usSize, unsigned int uiNextInImg = -1) = 0; + virtual void SetStreamingBufferSize(uint32 uiSize) = 0; + virtual uint32 GetStreamingBufferSize() = 0; virtual void MakeSpaceFor(std::uint32_t memoryToCleanInBytes) = 0; virtual std::uint32_t GetMemoryUsed() const = 0; };