Skip to content

Commit

Permalink
Allow load custom IMG containers (#1677)
Browse files Browse the repository at this point in the history
Co-authored-by: Pirulax <patrikjankovics7@gmail.com>
Co-authored-by: Vladislav Nikolaevich <v.nikolaevich@redbrixwall.com>
Co-authored-by: patrikjuvonen <22572159+patrikjuvonen@users.noreply.github.com>
  • Loading branch information
4 people committed Apr 7, 2023
1 parent 423dac9 commit 075dfee
Show file tree
Hide file tree
Showing 27 changed files with 943 additions and 44 deletions.
12 changes: 6 additions & 6 deletions Client/game_sa/CModelInfoSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -1529,7 +1529,7 @@ void CModelInfoSA::DeallocateModel(void)
}

ppModelInfo[m_dwModelID] = nullptr;
pGame->GetStreaming()->GetStreamingInfoFromModelId(m_dwModelID)->Reset();
*pGame->GetStreaming()->GetStreamingInfo(m_dwModelID) = CStreamingInfo{};
}
//////////////////////////////////////////////////////////////////////////////////////////
//
Expand Down
156 changes: 149 additions & 7 deletions Client/game_sa/CStreamingSA.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@
#include <core/CCoreInterface.h>
#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
{
Expand Down Expand Up @@ -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<int>(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<int>(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)
Expand Down
47 changes: 45 additions & 2 deletions Client/game_sa/CStreamingSA.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,67 @@

#include <game/CStreaming.h>

#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;
void LoadAllRequestedModels(bool bOnlyPriorityModels = false, const char* szTag = NULL);
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];
};
1 change: 1 addition & 0 deletions Client/mods/deathmatch/StdInc.h
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
#include <CClientStreamSectorRow.h>
#include <CClientTask.h>
#include <CClientTXD.h>
#include <CClientIMG.h>
#include <CClientIFP.h>
#include <CClientWater.h>
#include <CClientWeapon.h>
Expand Down
1 change: 1 addition & 0 deletions Client/mods/deathmatch/logic/CClientColManager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ void CClientColManager::DoHitDetectionForColShape(CClientColShape* pShape)
case CCLIENTDFF:
case CCLIENTCOL:
case CCLIENTTXD:
case CCLIENTIMG:
case CCLIENTSOUND:
break;
default:
Expand Down
4 changes: 3 additions & 1 deletion Client/mods/deathmatch/logic/CClientEntity.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ enum eClientEntityType
CCLIENTIFP,
CCLIENTVECTORGRAPHIC,
CCLIENTUNKNOWN,
CCLIENTIMG,
};

class CEntity;
Expand Down Expand Up @@ -140,7 +141,8 @@ enum eCClientEntityClassTypes
CLASS_CClientWeapon,
CLASS_CClientEffect,
CLASS_CClientPointLights,
CLASS_CClientSearchLight
CLASS_CClientSearchLight,
CLASS_CClientIMG,
};

class CClientEntity : public CClientEntityBase
Expand Down
3 changes: 3 additions & 0 deletions Client/mods/deathmatch/logic/CClientGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand Down

0 comments on commit 075dfee

Please sign in to comment.