Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
246 changes: 244 additions & 2 deletions Client/mods/deathmatch/logic/CClientIMG.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,13 @@ struct tImgHeader
};

CClientIMG::CClientIMG(class CClientManager* pManager, ElementID ID)
: ClassInit(this), CClientEntity(ID), m_pImgManager(pManager->GetIMGManager()), m_ucArchiveID(INVALID_ARCHIVE_ID), m_LargestFileSizeBlocks(0)
: ClassInit(this),
CClientEntity(ID),
m_pImgManager(pManager->GetIMGManager()),
m_ucArchiveID(INVALID_ARCHIVE_ID),
m_LargestFileSizeBlocks(0),
m_bFolderMode(false),
m_bTempImgCreated(false)
{
m_pManager = pManager;
SetTypeName("img");
Expand All @@ -36,7 +42,7 @@ void CClientIMG::Unlink()
if (IsStreamed())
StreamDisable();

if (m_ifs.is_open())
if (m_ifs.is_open() || m_bFolderMode)
Unload();
}

Expand Down Expand Up @@ -101,6 +107,39 @@ void CClientIMG::Unload()
m_fileInfos.clear();
m_fileInfos.shrink_to_fit();
m_ifs.close();

if (m_bFolderMode)
{
m_folderFilePaths.clear();
m_folderFilePaths.shrink_to_fit();

if (m_bTempImgCreated && !m_tempImgPath.empty())
{
std::error_code ec;
fs::remove(m_tempImgPath, ec);
m_tempImgPath.clear();
}

m_bTempImgCreated = false;
m_bFolderMode = false;
}

if (!m_tempImgDirPath.empty())
{
std::error_code ec;
for (const auto& entry : fs::directory_iterator(m_tempImgDirPath, ec))
{
if (ec)
break;

if (!entry.is_regular_file())
continue;

const auto filename = entry.path().filename().string();
if (filename.find("runtime-image") != std::string::npos)
fs::remove(entry.path(), ec);
}
}
}

bool CClientIMG::GetFile(size_t fileID, std::string& buffer)
Expand All @@ -120,6 +159,17 @@ bool CClientIMG::GetFile(size_t fileID, std::string& buffer)
throw std::invalid_argument("Out of memory");
}

if (!m_ifs.is_open())
{
m_ifs = std::ifstream(m_filePath, std::ios::binary);
if (m_ifs.fail())
{
m_ifs.close();
return false;
}
}

m_ifs.clear();
m_ifs.seekg((std::streampos)pFileInfo->uiOffset * 2048);
m_ifs.read(buffer.data(), toReadBytes);

Expand Down Expand Up @@ -148,6 +198,192 @@ bool CClientIMG::IsStreamed()
return m_ucArchiveID != INVALID_ARCHIVE_ID;
}

bool CClientIMG::LoadFolder(fs::path folderPath, fs::path tempDirPath)
{
if (!m_fileInfos.empty())
return false;

if (folderPath.empty())
return false;

if (!fs::is_directory(folderPath))
return false;

m_tempImgDirPath = std::move(tempDirPath);

if (!m_tempImgDirPath.empty())
{
std::error_code ec;
fs::create_directories(m_tempImgDirPath, ec);
if (ec)
return false;

for (const auto& entry : fs::directory_iterator(m_tempImgDirPath, ec))
{
if (ec)
break;

if (!entry.is_regular_file())
continue;

const auto filename = entry.path().filename().string();
if (filename.find("runtime-image") != std::string::npos)
fs::remove(entry.path(), ec);
}
}

// Collect regular files and sort alphabetically for deterministic ordering
std::vector<fs::directory_entry> entries;
for (const auto& entry : fs::directory_iterator(folderPath))
{
if (entry.is_regular_file())
entries.push_back(entry);
}
std::sort(entries.begin(), entries.end(), [](const auto& a, const auto& b) { return a.path().filename() < b.path().filename(); });

CResourceManager* pResourceManager = m_pManager ? m_pManager->GetResourceManager() : nullptr;

unsigned int uiCurrentOffset = 0;
for (const auto& entry : entries)
{
const auto& filePath = entry.path();
const auto filename = filePath.filename().string();

// tImgFileInfo::szFileName is 24 bytes (23 usable chars + null terminator)
if (filename.size() > 23)
continue;

if (pResourceManager)
{
SString strFilePath = SharedUtil::ToUTF8(filePath.wstring());
SString strConformed = PathConform(strFilePath).ToLower();

// Include only files known to the resource download manager
if (!pResourceManager->GetDownloadableResourceFile(strConformed))
continue;
}

std::error_code ec;
const auto fileSize = entry.file_size(ec);
if (ec || fileSize == 0)
continue;

const auto sizeInBlocks = static_cast<unsigned short>((fileSize + 2047) / 2048);

tImgFileInfo fileInfo{};
fileInfo.uiOffset = uiCurrentOffset;
fileInfo.usSize = sizeInBlocks;
fileInfo.usUnpackedSize = 0;
std::strncpy(fileInfo.szFileName, filename.c_str(), 23);
fileInfo.szFileName[23] = '\0';

m_fileInfos.push_back(fileInfo);
m_folderFilePaths.push_back(filePath);

uiCurrentOffset += sizeInBlocks;
}

if (m_fileInfos.empty())
return false;

m_bFolderMode = true;
return true;
}

bool CClientIMG::BuildTempIMG()
{
if (m_tempImgDirPath.empty())
return false;

std::error_code ec;
fs::create_directories(m_tempImgDirPath, ec);
if (ec)
return false;

const unsigned long long base = static_cast<unsigned long long>(GetTickCount64_());
const unsigned long long pid = static_cast<unsigned long long>(GetCurrentProcessId());
const std::wstring fileName = L"runtime-image-" + std::to_wstring(base) + L"-" + std::to_wstring(pid) + L".tmp";
m_tempImgPath = m_tempImgDirPath / fileName;

ec.clear();
if (fs::exists(m_tempImgPath, ec) || ec)
return false;

std::ofstream ofs(m_tempImgPath, std::ios::binary | std::ios::trunc);
if (!ofs.is_open())
{
fs::remove(m_tempImgPath);
return false;
}

std::string readBuf;
std::vector<char> zeroPad;

for (size_t i = 0; i < m_folderFilePaths.size(); i++)
{
const auto& filePath = m_folderFilePaths[i];
const auto& fileInfo = m_fileInfos[i];

std::error_code ec;
const auto fileSize = fs::file_size(filePath, ec);
if (ec)
{
ofs.close();
fs::remove(m_tempImgPath);
return false;
}

try
{
readBuf.resize(fileSize);
}
catch (const std::bad_alloc&)
{
ofs.close();
fs::remove(m_tempImgPath);
return false;
}

std::ifstream ifs(filePath, std::ios::binary);
if (!ifs.is_open())
{
ofs.close();
fs::remove(m_tempImgPath);
return false;
}

ifs.read(readBuf.data(), fileSize);
if (ifs.fail())
{
ofs.close();
fs::remove(m_tempImgPath);
return false;
}

ofs.write(readBuf.data(), fileSize);

const size_t paddedSize = static_cast<size_t>(fileInfo.usSize) * 2048;
const size_t paddingNeeded = paddedSize - fileSize;
if (paddingNeeded > 0)
{
zeroPad.assign(paddingNeeded, '\0');
ofs.write(zeroPad.data(), paddingNeeded);
}

if (ofs.fail())
{
ofs.close();
fs::remove(m_tempImgPath);
return false;
}
}

ofs.close();
m_filePath = m_tempImgPath;
m_bTempImgCreated = true;
return true;
}

bool CClientIMG::StreamEnable()
{
if (m_fileInfos.empty())
Expand All @@ -156,6 +392,12 @@ bool CClientIMG::StreamEnable()
if (IsStreamed())
return false;

if (m_bFolderMode && !m_bTempImgCreated)
{
if (!BuildTempIMG())
return false;
}

if (m_LargestFileSizeBlocks == 0)
{
for (const auto& fileInfo : m_fileInfos)
Expand Down
10 changes: 10 additions & 0 deletions Client/mods/deathmatch/logic/CClientIMG.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ class CClientIMG : public CClientEntity
auto GetLargestFileSizeBlocks() const { return m_LargestFileSizeBlocks; }

bool Load(fs::path filePath);
bool LoadFolder(fs::path folderPath, fs::path tempDirPath = {});
void Unload();

tImgFileInfo* GetFileInfo(size_t fileID);
Expand All @@ -70,11 +71,14 @@ class CClientIMG : public CClientEntity
bool StreamEnable();
bool StreamDisable();
bool IsStreamed();
bool IsFolderMode() const noexcept { return m_bFolderMode; }

bool LinkModel(unsigned int usModelID, size_t fileID);
bool UnlinkModel(unsigned int usModelID);

private:
bool BuildTempIMG();

class CClientIMGManager* m_pImgManager;

std::ifstream m_ifs;
Expand All @@ -83,5 +87,11 @@ class CClientIMG : public CClientEntity
std::vector<tImgFileInfo> m_fileInfos;
size_t m_LargestFileSizeBlocks; // The size of the largest file [in streaming blocks/sectors]

bool m_bFolderMode;
bool m_bTempImgCreated;
fs::path m_tempImgPath;
fs::path m_tempImgDirPath;
std::vector<fs::path> m_folderFilePaths;

std::vector<tLinkedModelRestoreInfo> m_restoreInfo;
};
24 changes: 19 additions & 5 deletions Client/mods/deathmatch/logic/luadefs/CLuaEngineDefs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -726,21 +726,35 @@ CClientIMG* CLuaEngineDefs::EngineLoadIMG(lua_State* const luaVM, std::string st
// Create the img handle
CClientIMG* pImg = new CClientIMG(m_pManager, INVALID_ELEMENT_ID);

// Fix path encoding for sdt::filesystem
// Fix path encoding for std::filesystem
std::wstring utf8Path = SharedUtil::MbUTF8ToUTF16(strFullPath);

// Attempt loading the file
if (pImg->Load(std::move(utf8Path)))
std::error_code ec;
const bool bIsFolder = std::filesystem::is_directory(utf8Path, ec) && !ec;

// Detect if this is a folder IMG or a regular file IMG and call the appropriate loading function.
bool bLoaded = false;
if (bIsFolder)
{
const SString strResourceRoot = pResource->GetResourceDirectoryPath();
const std::filesystem::path resourceRootPath = SharedUtil::MbUTF8ToUTF16(strResourceRoot);
bLoaded = pImg->LoadFolder(std::move(utf8Path), resourceRootPath);
}
else
{
bLoaded = pImg->Load(std::move(utf8Path));
}

if (bLoaded)
{
// 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(bIsFolder ? "Error creating runtime IMG from folder" : "Error loading IMG");
}
}

Expand Down
Loading