Skip to content

Commit 6b82365

Browse files
authored
Allow replacement of CJ clothing models (PR #3967, Fixes #1017)
1 parent 195f43f commit 6b82365

File tree

11 files changed

+158
-20
lines changed

11 files changed

+158
-20
lines changed

Client/game_sa/CDirectorySA.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*****************************************************************************
2+
*
3+
* PROJECT: Multi Theft Auto
4+
* LICENSE: See LICENSE in the top level directory
5+
* FILE: game_sa/CDirectorySA.cpp
6+
*
7+
* Multi Theft Auto is available from https://www.multitheftauto.com/
8+
*
9+
*****************************************************************************/
10+
11+
#include "StdInc.h"
12+
#include "CDirectorySA.h"
13+
14+
DirectoryInfoSA* CDirectorySAInterface::GetModelEntry(std::uint16_t modelId)
15+
{
16+
if (m_nNumEntries <= 0)
17+
return nullptr;
18+
19+
DirectoryInfoSA* entry = m_pEntries + modelId;
20+
21+
if (!entry)
22+
return nullptr;
23+
24+
return entry;
25+
}
26+
27+
bool CDirectorySAInterface::SetModelStreamingSize(std::uint16_t modelId, std::uint16_t size)
28+
{
29+
DirectoryInfoSA* entry = GetModelEntry(modelId);
30+
31+
if (!entry)
32+
return false;
33+
34+
if (entry->m_nStreamingSize == size)
35+
return false;
36+
37+
entry->m_nStreamingSize = size;
38+
return true;
39+
}
40+
41+
42+
std::uint16_t CDirectorySAInterface::GetModelStreamingSize(std::uint16_t modelId)
43+
{
44+
DirectoryInfoSA* entry = GetModelEntry(modelId);
45+
46+
if (!entry)
47+
return 0;
48+
49+
return entry->m_nStreamingSize;
50+
}

Client/game_sa/CDirectorySA.h

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*****************************************************************************
2+
*
3+
* PROJECT: Multi Theft Auto
4+
* LICENSE: See LICENSE in the top level directory
5+
* FILE: game_sa/CDirectorySA.h
6+
*
7+
* Multi Theft Auto is available from https://www.multitheftauto.com/
8+
*
9+
*****************************************************************************/
10+
11+
#pragma once
12+
13+
struct DirectoryInfoSA
14+
{
15+
std::uint32_t m_nOffset;
16+
std::uint16_t m_nStreamingSize;
17+
std::uint16_t m_nSizeInArchive;
18+
char m_szName[24];
19+
};
20+
21+
class CDirectorySAInterface
22+
{
23+
public:
24+
DirectoryInfoSA* GetModelEntry(std::uint16_t modelId);
25+
bool SetModelStreamingSize(std::uint16_t modelId, std::uint16_t size);
26+
std::uint16_t GetModelStreamingSize(std::uint16_t modelId);
27+
28+
private:
29+
DirectoryInfoSA* m_pEntries{};
30+
std::uint32_t m_nCapacity{};
31+
std::uint32_t m_nNumEntries{};
32+
bool m_bOwnsEntries{};
33+
};

Client/game_sa/CRenderWareSA.ClothesReplacing.cpp

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
#include "StdInc.h"
1010
#include "CGameSA.h"
11+
#include "CDirectorySA.h"
1112
#include "gamesa_renderware.h"
1213

1314
extern CGameSA* pGame;
@@ -28,8 +29,10 @@ namespace
2829
uint uiLoadflag; // 0-not loaded 2-requested 3-loaded 1-processed
2930
};
3031

31-
std::map<ushort, char*> ms_ReplacementClothesFileDataMap;
32-
bool bClothesReplacementChanged = false;
32+
std::unordered_map<ushort, char*> ms_ReplacementClothesFileDataMap;
33+
std::unordered_map<ushort, std::uint16_t> ms_OriginalStreamingSizesMap;
34+
35+
bool bClothesReplacementChanged = false;
3336

3437
struct SPlayerImgItem
3538
{
@@ -46,44 +49,64 @@ namespace
4649
};
4750

4851
DWORD FUNC_CStreamingConvertBufferToObject = 0x40C6B0;
52+
auto g_clothesDirectory = reinterpret_cast<CDirectorySAInterface*>(0xBC12C0);
4953
int iReturnFileId;
5054
char* pReturnBuffer;
55+
56+
size_t GetSizeInBlocks(size_t size)
57+
{
58+
auto blockDiv = std::div(size, 2048);
59+
return (blockDiv.quot + (blockDiv.rem ? 1 : 0));
60+
}
5161
} // namespace
5262

5363
////////////////////////////////////////////////////////////////
5464
//
55-
// CRenderWareSA::ClothesAddReplacementTxd
65+
// CRenderWareSA::ClothesAddReplacement
5666
//
57-
// Add replacement txd for a clothing component
67+
// Add replacement txd/dff for a clothing component
5868
//
5969
////////////////////////////////////////////////////////////////
60-
void CRenderWareSA::ClothesAddReplacementTxd(char* pFileData, ushort usFileId)
70+
void CRenderWareSA::ClothesAddReplacement(char* pFileData, size_t fileSize, ushort usFileId)
6171
{
6272
if (!pFileData)
6373
return;
74+
6475
if (pFileData != MapFindRef(ms_ReplacementClothesFileDataMap, usFileId))
6576
{
6677
MapSet(ms_ReplacementClothesFileDataMap, usFileId, pFileData);
78+
MapSet(ms_OriginalStreamingSizesMap, usFileId, g_clothesDirectory->GetModelStreamingSize(usFileId));
79+
g_clothesDirectory->SetModelStreamingSize(usFileId, GetSizeInBlocks(fileSize));
80+
6781
bClothesReplacementChanged = true;
6882
}
6983
}
7084

7185
////////////////////////////////////////////////////////////////
7286
//
73-
// CRenderWareSA::ClothesRemoveReplacementTxd
87+
// CRenderWareSA::ClothesRemoveReplacement
7488
//
75-
// Remove replacement txd for a clothing component
89+
// Remove replacement txd/dff for a clothing component
7690
//
7791
////////////////////////////////////////////////////////////////
78-
void CRenderWareSA::ClothesRemoveReplacementTxd(char* pFileData)
92+
void CRenderWareSA::ClothesRemoveReplacement(char* pFileData)
7993
{
8094
if (!pFileData)
8195
return;
82-
for (std::map<ushort, char*>::iterator iter = ms_ReplacementClothesFileDataMap.begin(); iter != ms_ReplacementClothesFileDataMap.end();)
96+
97+
for (auto iter = ms_ReplacementClothesFileDataMap.begin(); iter != ms_ReplacementClothesFileDataMap.end();)
8398
{
8499
if (iter->second == pFileData)
85100
{
86-
ms_ReplacementClothesFileDataMap.erase(iter++);
101+
auto it = ms_OriginalStreamingSizesMap.find(iter->first);
102+
103+
if (it != ms_OriginalStreamingSizesMap.end())
104+
{
105+
std::uint16_t originalStreamingSize = it->second;
106+
g_clothesDirectory->SetModelStreamingSize(iter->first, originalStreamingSize);
107+
}
108+
109+
iter = ms_ReplacementClothesFileDataMap.erase(iter);
87110
bClothesReplacementChanged = true;
88111
}
89112
else
@@ -110,7 +133,7 @@ bool CRenderWareSA::HasClothesReplacementChanged()
110133
// CStreaming_RequestModel_Mid
111134
//
112135
// If request is for a file inside player.img (imgId 5)
113-
// then maybe switch to replacement txd file data
136+
// then maybe switch to replacement txd/dff file data
114137
//
115138
////////////////////////////////////////////////////////////////
116139
__declspec(noinline) bool _cdecl OnCStreaming_RequestModel_Mid(int flags, SImgGTAItemInfo* pImgGTAInfo)

Client/game_sa/CRenderWareSA.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ class CRenderWareSA : public CRenderWare
3232
bool ModelInfoTXDLoadTextures(SReplacementTextures* pReplacementTextures, const SString& strFilename, const SString& buffer, bool bFilteringEnabled);
3333
bool ModelInfoTXDAddTextures(SReplacementTextures* pReplacementTextures, ushort usModelId);
3434
void ModelInfoTXDRemoveTextures(SReplacementTextures* pReplacementTextures);
35-
void ClothesAddReplacementTxd(char* pFileData, ushort usFileId);
36-
void ClothesRemoveReplacementTxd(char* pFileData);
35+
void ClothesAddReplacement(char* pFileData, size_t fileSize, ushort usFileId);
36+
void ClothesRemoveReplacement(char* pFileData);
3737
bool HasClothesReplacementChanged();
3838

3939
// Reads and parses a TXD file specified by a path (szTXD)

Client/mods/deathmatch/logic/CClientDFF.cpp

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ bool CClientDFF::DoReplaceModel(unsigned short usModel, bool bAlphaTransparency)
139139
if (!CClientDFFManager::IsReplacableModel(usModel))
140140
return false;
141141

142+
if (CClientPlayerClothes::IsValidModel(usModel))
143+
return ReplaceClothes(usModel);
144+
142145
// Get clump loaded for this model id
143146
RpClump* pClump = GetLoadedClump(usModel);
144147

@@ -268,6 +271,12 @@ void CClientDFF::InternalRestoreModel(unsigned short usModel)
268271
m_pManager->GetObjectManager()->RestreamObjects(usModel);
269272
g_pGame->GetModelInfo(usModel)->RestreamIPL();
270273
}
274+
// Is This a clothe ID?
275+
else if (CClientPlayerClothes::IsValidModel(usModel))
276+
{
277+
g_pGame->GetRenderWare()->ClothesRemoveReplacement(m_RawDataBuffer.data());
278+
return;
279+
}
271280
else
272281
return;
273282

@@ -296,6 +305,22 @@ void CClientDFF::InternalRestoreModel(unsigned short usModel)
296305
}
297306
}
298307

308+
bool CClientDFF::ReplaceClothes(ushort usModel)
309+
{
310+
if (m_RawDataBuffer.empty() && m_bIsRawData)
311+
return false;
312+
313+
if (m_RawDataBuffer.empty())
314+
{
315+
if (!FileLoad(std::nothrow, m_strDffFilename, m_RawDataBuffer))
316+
return false;
317+
}
318+
319+
m_Replaced.push_back(usModel);
320+
g_pGame->GetRenderWare()->ClothesAddReplacement(m_RawDataBuffer.data(), m_RawDataBuffer.size(), usModel - CLOTHES_MODEL_ID_FIRST);
321+
return true;
322+
}
323+
299324
bool CClientDFF::ReplaceObjectModel(RpClump* pClump, ushort usModel, bool bAlphaTransparency)
300325
{
301326
// Stream out all the object models with matching ID.

Client/mods/deathmatch/logic/CClientDFF.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ class CClientDFF final : public CClientEntity
5959
void UnloadDFF();
6060
void InternalRestoreModel(unsigned short usModel);
6161

62+
bool ReplaceClothes(ushort usModel);
6263
bool ReplaceObjectModel(RpClump* pClump, ushort usModel, bool bAlphaTransparency);
6364
bool ReplaceVehicleModel(RpClump* pClump, ushort usModel, bool bAlphaTransparency);
6465
bool ReplaceWeaponModel(RpClump* pClump, ushort usModel, bool bAlphaTransparency);

Client/mods/deathmatch/logic/CClientDFFManager.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ CClientDFF* CClientDFFManager::GetElementThatReplaced(unsigned short usModel, CC
7979
bool CClientDFFManager::IsReplacableModel(unsigned short usModel)
8080
{
8181
// Either a vehicle model or an object model
82-
return CClientObjectManager::IsValidModel(usModel) || CClientVehicleManager::IsValidModel(usModel) || CClientPlayerManager::IsValidModel(usModel);
82+
return CClientObjectManager::IsValidModel(usModel) || CClientVehicleManager::IsValidModel(usModel) || CClientPlayerManager::IsValidModel(usModel) ||
83+
CClientPlayerClothes::IsValidModel(usModel);
8384
}
8485

8586
bool CClientDFFManager::RestoreModel(unsigned short usModel)

Client/mods/deathmatch/logic/CClientPlayerClothes.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -577,3 +577,8 @@ const int CClientPlayerClothes::GetClothingGroupMax(unsigned char ucType)
577577

578578
return 0;
579579
}
580+
581+
bool CClientPlayerClothes::IsValidModel(unsigned short usModel)
582+
{
583+
return usModel >= CLOTHES_MODEL_ID_FIRST && usModel <= CLOTHES_MODEL_ID_LAST;
584+
}

Client/mods/deathmatch/logic/CClientPlayerClothes.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ class CClientPlayerClothes
6767

6868
static const SPlayerClothing* GetClothingGroup(unsigned char ucType);
6969
static const int GetClothingGroupMax(unsigned char ucType);
70-
70+
static bool IsValidModel(unsigned short usModel);
7171
private:
7272
static const SPlayerClothing* GetClothing(const char* szTexture, const char* szModel, unsigned char ucType);
7373

Client/mods/deathmatch/logic/CClientTXD.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ CClientTXD::~CClientTXD()
2929
}
3030

3131
// Remove us from all the clothes replacement doo dah
32-
g_pGame->GetRenderWare()->ClothesRemoveReplacementTxd(m_FileData.data());
32+
g_pGame->GetRenderWare()->ClothesRemoveReplacement(m_FileData.data());
3333
}
3434

3535
bool CClientTXD::Load(bool isRaw, SString input, bool enableFiltering)
@@ -75,8 +75,8 @@ bool CClientTXD::Import(unsigned short usModelID)
7575
return false;
7676
}
7777
m_bUsingFileDataForClothes = true;
78-
// Note: ClothesAddReplacementTxd uses the pointer from m_FileData, so don't touch m_FileData until matching ClothesRemove call
79-
g_pGame->GetRenderWare()->ClothesAddReplacementTxd(m_FileData.data(), usModelID - CLOTHES_MODEL_ID_FIRST);
78+
// Note: ClothesAddReplacement uses the pointer from m_FileData, so don't touch m_FileData until matching ClothesRemove call
79+
g_pGame->GetRenderWare()->ClothesAddReplacement(m_FileData.data(), m_FileData.size(), usModelID - CLOTHES_MODEL_ID_FIRST);
8080
return true;
8181
}
8282
else

0 commit comments

Comments
 (0)