added direct mpq archive reading support

This commit is contained in:
aomizu 2025-12-22 17:35:16 +09:00
parent da51f7e4fc
commit a3d69ff2e7
10 changed files with 745 additions and 44 deletions

View File

@ -26,6 +26,9 @@ set(CMAKE_CXX_STANDARD 11)
include(lib/system/cmake/system.cmake)
find_package(ZLIB REQUIRED)
find_package(BZip2 REQUIRED)
# Some templates abuse offsetof
if(WHOA_SYSTEM_LINUX OR WHOA_SYSTEM_MAC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-offsetof")

View File

@ -22,7 +22,7 @@ Assuming all went well, you should see a `dist/bin` directory appear in the `bui
## Running
Whoa doesn't currently support reading from MPQ archives. Instead, it assumes you are launching the Whoa executable from the root of a fully extracted MPQ archive set for World of Warcraft 3.3.5a (build 12340). You can obtain a valid set of MPQ archives to extract by installing World of Warcraft 3.3.5a from legally purchased original install media. Whoa does not provide any copy of game data.
Whoa can read MPQ archives directly, so launch it from the root of a World of Warcraft 3.3.5a (build 12340) install (the `Data` directory contains MPQs). Fully extracted MPQ data also works. You can obtain a valid set of MPQ archives by installing World of Warcraft 3.3.5a from legally purchased original install media. Whoa does not provide any copy of game data.
Assuming all goes well, you should be greeted by the login screen, complete with its flying dragon animation loop.

View File

@ -20,6 +20,7 @@ namespace Texture {
int32_t s_createBlpAsync; // Invented name
MipBits* s_mipBits;
int32_t s_mipBitsValid;
size_t s_mipBitsSize;
TSHashTable<CTexture, HASHKEY_TEXTUREFILE> s_textureCache;
EGxTexFormat s_pixelFormatToGxTexFormat[10] = {
@ -380,6 +381,28 @@ MipBits* TextureAllocMippedImg(PIXEL_FORMAT pixelFormat, uint32_t width, uint32_
return nullptr;
}
bool MippedImgSet(MipBits* image, uint32_t fourCC, uint32_t width, uint32_t height) {
uint32_t levelCount = CalcLevelCount(width, height);
uint32_t levelDataSize = CalcLevelOffset(levelCount, width, height, fourCC);
size_t imageSize = (sizeof(MipBits::mip) * levelCount) + levelDataSize + MIPPED_IMG_ALIGN;
if (Texture::s_mipBitsSize && imageSize > Texture::s_mipBitsSize) {
return false;
}
auto imageData = reinterpret_cast<char*>(image);
auto mipBase = imageData + (sizeof(MipBits::mip) * levelCount);
auto alignedMipBase = static_cast<char*>(ALIGN_PTR(mipBase, MIPPED_IMG_ALIGN));
uint32_t levelOffset = 0;
for (int32_t level = 0; level < levelCount; level++) {
image->mip[level] = reinterpret_cast<C4Pixel*>(alignedMipBase + levelOffset);
levelOffset += CalcLevelSize(level, width, height, fourCC);
}
return true;
}
void GxTexUpdate(CGxTex* texId, int32_t minX, int32_t minY, int32_t maxX, int32_t maxY, int32_t immediate) {
CiRect rect = { minY, minX, maxY, maxX };
GxTexUpdate(texId, rect, immediate);
@ -646,6 +669,11 @@ int32_t PumpBlpTextureAsync(CTexture* texture, void* buf) {
GetTextureFormats(&pixFormat, &gxTexFormat, preferredFormat, alphaSize);
if (image.m_header.colorEncoding == COLOR_PAL) {
pixFormat = PIXEL_ARGB8888;
gxTexFormat = GxTex_Argb8888;
}
int32_t mipLevel = texture->bestMip;
Texture::s_mipBitsValid = 1;
@ -1123,6 +1151,7 @@ void TextureIncreasePriority(CTexture* texture) {
void TextureInitialize() {
uint32_t v0 = MippedImgCalcSize(2, 1024, 1024);
Texture::s_mipBits = reinterpret_cast<MipBits*>(SMemAlloc(v0, __FILE__, __LINE__, 0));
Texture::s_mipBitsSize = v0;
// TODO
// - rest of function

View File

@ -49,6 +49,8 @@ CGxTex* TextureAllocGxTex(EGxTexTarget, uint32_t, uint32_t, uint32_t, EGxTexForm
MipBits* TextureAllocMippedImg(PIXEL_FORMAT pixelFormat, uint32_t width, uint32_t height);
bool MippedImgSet(MipBits* image, uint32_t fourCC, uint32_t width, uint32_t height);
HTEXTURE TextureCacheGetTexture(char*, char*, CGxTexFlags);
HTEXTURE TextureCacheGetTexture(const CImVector&);

View File

@ -29,8 +29,67 @@ int32_t CBLPFile::Lock2(const char* fileName, PIXEL_FORMAT format, uint32_t mipL
switch (this->m_header.colorEncoding) {
case COLOR_PAL:
// TODO
if (format != PIXEL_ARGB8888) {
return 0;
}
{
uint32_t width = this->m_header.width >> mipLevel;
uint32_t height = this->m_header.height >> mipLevel;
if (!width) {
width = 1;
}
if (!height) {
height = 1;
}
uint32_t pixelCount = width * height;
auto indices = reinterpret_cast<uint8_t*>(mipData);
const uint8_t* alpha = nullptr;
if (this->m_header.alphaSize) {
alpha = indices + pixelCount;
}
const BlpPalPixel* palette = this->m_header.extended.palette;
auto out = reinterpret_cast<uint32_t*>(data);
for (uint32_t i = 0; i < pixelCount; ++i) {
const auto& pal = palette[indices[i]];
uint8_t a = 0xFF;
switch (this->m_header.alphaSize) {
case 1: {
uint8_t byte = alpha[i >> 3];
a = ((byte >> (i & 7)) & 0x1) ? 0xFF : 0x00;
break;
}
case 4: {
uint8_t byte = alpha[i >> 1];
uint8_t nibble = (i & 1) ? (byte >> 4) : (byte & 0x0F);
a = static_cast<uint8_t>((nibble << 4) | nibble);
break;
}
case 8:
a = alpha[i];
break;
default:
break;
}
out[i] = (static_cast<uint32_t>(a) << 24)
| (static_cast<uint32_t>(static_cast<uint8_t>(pal.r)) << 16)
| (static_cast<uint32_t>(static_cast<uint8_t>(pal.g)) << 8)
| static_cast<uint32_t>(static_cast<uint8_t>(pal.b));
}
}
return 1;
case COLOR_DXT:
switch (format) {
@ -96,8 +155,9 @@ int32_t CBLPFile::LockChain2(const char* fileName, PIXEL_FORMAT format, MipBits*
v14 = 1;
}
// TODO
// MippedImgSet(format, v14, v13, mipLevel);
if (!MippedImgSet(images, format, v14, v13)) {
return 0;
}
} else {
uint32_t v9 = this->m_header.height >> mipLevel;

View File

@ -19,6 +19,8 @@ add_library(util STATIC
target_include_directories(util
PRIVATE
${CMAKE_SOURCE_DIR}/src
${ZLIB_INCLUDE_DIRS}
${BZIP2_INCLUDE_DIRS}
)
target_link_libraries(util
@ -28,6 +30,8 @@ target_link_libraries(util
common
storm
tempest
${ZLIB_LIBRARIES}
${BZIP2_LIBRARIES}
)
if(WHOA_SYSTEM_LINUX OR WHOA_SYSTEM_MAC)

549
src/util/Mpq.cpp Normal file
View File

@ -0,0 +1,549 @@
#include "util/Mpq.hpp"
#include <bzlib.h>
#include <zlib.h>
#include <algorithm>
#include <cctype>
#include <cstring>
#include <fstream>
#include <string>
#include <vector>
#include <sys/stat.h>
namespace {
constexpr uint32_t ID_MPQ = 0x1A51504D;
constexpr uint32_t ID_MPQ_USERDATA = 0x1B51504D;
constexpr uint32_t MPQ_FILE_IMPLODE = 0x00000100;
constexpr uint32_t MPQ_FILE_COMPRESSED = 0x00000200;
constexpr uint32_t MPQ_FILE_ENCRYPTED = 0x00010000;
constexpr uint32_t MPQ_FILE_SINGLE_UNIT = 0x01000000;
constexpr uint8_t MPQ_COMP_ZLIB = 0x02;
constexpr uint8_t MPQ_COMP_BZIP2 = 0x08;
#pragma pack(push, 1)
struct MpqUserDataHeader {
uint32_t id;
uint32_t userDataSize;
uint32_t headerOffset;
uint32_t userDataHeaderSize;
};
struct MpqHeaderV2 {
uint32_t id;
uint32_t headerSize;
uint32_t archiveSize;
uint16_t formatVersion;
uint16_t sectorSizeShift;
uint32_t hashTablePos;
uint32_t blockTablePos;
uint32_t hashTableSize;
uint32_t blockTableSize;
uint64_t hiBlockTablePos64;
uint16_t hashTablePosHi;
uint16_t blockTablePosHi;
};
struct MpqHashEntry {
uint32_t hashA;
uint32_t hashB;
uint16_t locale;
uint16_t platform;
uint32_t blockIndex;
};
struct MpqBlockEntry {
uint32_t filePos;
uint32_t compressedSize;
uint32_t fileSize;
uint32_t flags;
};
#pragma pack(pop)
uint32_t g_cryptTable[0x500];
bool g_cryptReady = false;
uint16_t g_locale = 0;
void PrepareCryptTable() {
uint32_t seed = 0x00100001;
for (uint32_t i = 0; i < 0x100; ++i) {
for (uint32_t j = 0; j < 5; ++j) {
seed = (seed * 125 + 3) % 0x2AAAAB;
uint32_t temp1 = (seed & 0xFFFF) << 16;
seed = (seed * 125 + 3) % 0x2AAAAB;
uint32_t temp2 = (seed & 0xFFFF);
g_cryptTable[i + j * 0x100] = temp1 | temp2;
}
}
g_cryptReady = true;
}
uint32_t HashString(const std::string& input, uint32_t type) {
if (!g_cryptReady) {
PrepareCryptTable();
}
uint32_t seed1 = 0x7FED7FED;
uint32_t seed2 = 0xEEEEEEEE;
for (char ch : input) {
uint8_t c = static_cast<uint8_t>(std::toupper(static_cast<unsigned char>(ch)));
seed1 = g_cryptTable[(type << 8) + c] ^ (seed1 + seed2);
seed2 = c + seed1 + seed2 + (seed2 << 5) + 3;
}
return seed1;
}
void DecryptBlock(uint32_t* data, size_t length, uint32_t key) {
if (!g_cryptReady) {
PrepareCryptTable();
}
uint32_t seed1 = key;
uint32_t seed2 = 0xEEEEEEEE;
for (size_t i = 0; i < length; ++i) {
seed2 += g_cryptTable[0x400 + (seed1 & 0xFF)];
uint32_t value = data[i];
uint32_t decoded = value ^ (seed1 + seed2);
data[i] = decoded;
seed1 = ((~seed1 << 21) + 0x11111111) | (seed1 >> 11);
seed2 = decoded + seed2 + (seed2 << 5) + 3;
}
}
std::string NormalizePath(const char* filename) {
std::string path = filename ? filename : "";
for (char& ch : path) {
if (ch == '/') {
ch = '\\';
}
}
return path;
}
bool FileExists(const std::string& path) {
std::ifstream file(path, std::ios::binary);
return file.is_open();
}
bool DirectoryExists(const std::string& path) {
struct stat st = {};
return stat(path.c_str(), &st) == 0 && (st.st_mode & S_IFDIR);
}
uint16_t LocaleFromString(const std::string& locale) {
if (locale == "enUS") return 0x0409;
if (locale == "enGB") return 0x0809;
if (locale == "frFR") return 0x040C;
if (locale == "deDE") return 0x0407;
if (locale == "esES") return 0x0C0A;
if (locale == "esMX") return 0x080A;
if (locale == "ruRU") return 0x0419;
if (locale == "koKR") return 0x0412;
if (locale == "zhCN") return 0x0804;
if (locale == "zhTW") return 0x0404;
return 0;
}
bool DecompressZlib(const uint8_t* in, size_t inSize, uint8_t* out, size_t outSize) {
uLongf destLen = static_cast<uLongf>(outSize);
int result = uncompress(out, &destLen, in, static_cast<uLongf>(inSize));
return result == Z_OK && destLen == outSize;
}
bool DecompressBzip2(const uint8_t* in, size_t inSize, uint8_t* out, size_t outSize) {
unsigned int destLen = static_cast<unsigned int>(outSize);
int result = BZ2_bzBuffToBuffDecompress(
reinterpret_cast<char*>(out),
&destLen,
const_cast<char*>(reinterpret_cast<const char*>(in)),
static_cast<unsigned int>(inSize),
0,
0
);
return result == BZ_OK && destLen == outSize;
}
bool DecompressBlock(const uint8_t* in, size_t inSize, uint8_t* out, size_t outSize) {
if (inSize == outSize) {
std::memcpy(out, in, outSize);
return true;
}
if (inSize == 0) {
return false;
}
uint8_t type = in[0];
const uint8_t* payload = in + 1;
size_t payloadSize = inSize - 1;
if (type == 0) {
if (payloadSize != outSize) {
return false;
}
std::memcpy(out, payload, outSize);
return true;
}
if ((type & (type - 1)) != 0) {
return false;
}
if (type == MPQ_COMP_ZLIB) {
return DecompressZlib(payload, payloadSize, out, outSize);
}
if (type == MPQ_COMP_BZIP2) {
return DecompressBzip2(payload, payloadSize, out, outSize);
}
return false;
}
struct MpqArchive {
std::ifstream stream;
uint64_t baseOffset = 0;
uint32_t sectorSize = 0;
uint32_t hashTableSize = 0;
uint32_t blockTableSize = 0;
std::vector<MpqHashEntry> hashTable;
std::vector<MpqBlockEntry> blockTable;
bool Open(const std::string& archivePath) {
stream.open(archivePath, std::ios::binary);
if (!stream.is_open()) {
return false;
}
MpqUserDataHeader userHeader = {};
MpqHeaderV2 header = {};
stream.read(reinterpret_cast<char*>(&userHeader), sizeof(userHeader));
if (!stream.good()) {
return false;
}
if (userHeader.id == ID_MPQ_USERDATA) {
baseOffset = userHeader.headerOffset;
} else {
baseOffset = 0;
}
stream.seekg(static_cast<std::streamoff>(baseOffset), std::ios::beg);
stream.read(reinterpret_cast<char*>(&header), sizeof(header));
if (!stream.good()) {
return false;
}
if (header.id != ID_MPQ || header.headerSize < 32) {
return false;
}
if (header.formatVersion > 1) {
return false;
}
sectorSize = 512u << header.sectorSizeShift;
hashTableSize = header.hashTableSize;
blockTableSize = header.blockTableSize;
uint64_t hashTablePos = baseOffset + header.hashTablePos;
uint64_t blockTablePos = baseOffset + header.blockTablePos;
if (header.headerSize >= 44) {
hashTablePos = baseOffset + (static_cast<uint64_t>(header.hashTablePosHi) << 32) + header.hashTablePos;
blockTablePos = baseOffset + (static_cast<uint64_t>(header.blockTablePosHi) << 32) + header.blockTablePos;
}
hashTable.resize(hashTableSize);
blockTable.resize(blockTableSize);
stream.seekg(static_cast<std::streamoff>(hashTablePos), std::ios::beg);
stream.read(reinterpret_cast<char*>(hashTable.data()), hashTableSize * sizeof(MpqHashEntry));
stream.seekg(static_cast<std::streamoff>(blockTablePos), std::ios::beg);
stream.read(reinterpret_cast<char*>(blockTable.data()), blockTableSize * sizeof(MpqBlockEntry));
if (!stream.good()) {
return false;
}
DecryptBlock(reinterpret_cast<uint32_t*>(hashTable.data()), hashTableSize * sizeof(MpqHashEntry) / 4, HashString("(hash table)", 3));
DecryptBlock(reinterpret_cast<uint32_t*>(blockTable.data()), blockTableSize * sizeof(MpqBlockEntry) / 4, HashString("(block table)", 3));
return true;
}
const MpqBlockEntry* FindBlock(const std::string& filename) const {
if (hashTable.empty()) {
return nullptr;
}
uint32_t hashA = HashString(filename, 1);
uint32_t hashB = HashString(filename, 2);
uint32_t hashStart = HashString(filename, 0) & (hashTableSize - 1);
const MpqHashEntry* neutral = nullptr;
for (uint32_t i = 0; i < hashTableSize; ++i) {
uint32_t index = (hashStart + i) & (hashTableSize - 1);
const MpqHashEntry& entry = hashTable[index];
if (entry.blockIndex == 0xFFFFFFFF) {
break;
}
if (entry.hashA == hashA && entry.hashB == hashB) {
if (g_locale == 0) {
if (entry.blockIndex < blockTable.size()) {
return &blockTable[entry.blockIndex];
}
}
if (entry.locale == g_locale) {
if (entry.blockIndex < blockTable.size()) {
return &blockTable[entry.blockIndex];
}
}
if (entry.locale == 0 && !neutral) {
neutral = &entry;
}
}
}
if (neutral && neutral->blockIndex < blockTable.size()) {
return &blockTable[neutral->blockIndex];
}
return nullptr;
}
bool ReadFile(const std::string& filename, uint8_t** data, size_t* size) {
const MpqBlockEntry* block = FindBlock(filename);
if (!block) {
return false;
}
if (block->flags & MPQ_FILE_ENCRYPTED) {
return false;
}
if (block->flags & MPQ_FILE_IMPLODE) {
return false;
}
uint64_t filePos = baseOffset + block->filePos;
uint32_t fileSize = block->fileSize;
std::vector<uint8_t> buffer(fileSize);
if (block->flags & MPQ_FILE_SINGLE_UNIT) {
std::vector<uint8_t> raw(block->compressedSize);
stream.seekg(static_cast<std::streamoff>(filePos), std::ios::beg);
stream.read(reinterpret_cast<char*>(raw.data()), raw.size());
if (!stream.good()) {
return false;
}
if (block->flags & MPQ_FILE_COMPRESSED) {
if (!DecompressBlock(raw.data(), raw.size(), buffer.data(), buffer.size())) {
return false;
}
} else {
if (raw.size() != buffer.size()) {
return false;
}
std::memcpy(buffer.data(), raw.data(), buffer.size());
}
} else if ((block->flags & MPQ_FILE_COMPRESSED) == 0) {
stream.seekg(static_cast<std::streamoff>(filePos), std::ios::beg);
stream.read(reinterpret_cast<char*>(buffer.data()), buffer.size());
if (!stream.good()) {
return false;
}
} else {
uint32_t sectorCount = (fileSize + sectorSize - 1) / sectorSize;
uint32_t tableSize = (sectorCount + 1) * sizeof(uint32_t);
std::vector<uint32_t> offsets(sectorCount + 1);
stream.seekg(static_cast<std::streamoff>(filePos), std::ios::beg);
stream.read(reinterpret_cast<char*>(offsets.data()), tableSize);
if (!stream.good()) {
return false;
}
for (uint32_t i = 0; i < sectorCount; ++i) {
uint32_t start = offsets[i];
uint32_t end = offsets[i + 1];
if (end < start) {
return false;
}
uint32_t compSize = end - start;
uint32_t remaining = fileSize - i * sectorSize;
uint32_t outSize = std::min(sectorSize, remaining);
std::vector<uint8_t> raw(compSize);
stream.seekg(static_cast<std::streamoff>(filePos + start), std::ios::beg);
stream.read(reinterpret_cast<char*>(raw.data()), raw.size());
if (!stream.good()) {
return false;
}
uint8_t* outPtr = buffer.data() + i * sectorSize;
if (!DecompressBlock(raw.data(), raw.size(), outPtr, outSize)) {
if (raw.size() == outSize) {
std::memcpy(outPtr, raw.data(), outSize);
} else {
return false;
}
}
}
}
*size = buffer.size();
auto output = new uint8_t[buffer.size()];
std::memcpy(output, buffer.data(), buffer.size());
*data = output;
return true;
}
};
struct MpqManager {
bool initialized = false;
std::string dataRoot;
std::string locale;
std::vector<MpqArchive> archives;
void Initialize() {
if (initialized) {
return;
}
initialized = true;
if (DirectoryExists("Data")) {
dataRoot = "Data";
} else if (DirectoryExists("../Data")) {
dataRoot = "../Data";
} else {
dataRoot = "Data";
}
const char* locales[] = {
"enUS",
"enGB",
"frFR",
"deDE",
"esES",
"esMX",
"ruRU",
"koKR",
"zhCN",
"zhTW"
};
for (const char* loc : locales) {
std::string probe = dataRoot + "/" + loc + "/locale-" + loc + ".MPQ";
if (FileExists(probe)) {
locale = loc;
break;
}
}
if (locale.empty()) {
locale = "enUS";
}
g_locale = LocaleFromString(locale);
AddArchives();
}
bool OpenArchive(const std::string& path) {
MpqArchive archive;
if (!archive.Open(path)) {
return false;
}
archives.push_back(std::move(archive));
return true;
}
void AddIfExists(const std::string& path) {
if (FileExists(path)) {
OpenArchive(path);
}
}
void AddPatchSeries(const std::string& prefix) {
for (int i = 9; i >= 1; --i) {
std::string name = prefix + std::to_string(i) + ".MPQ";
AddIfExists(name);
}
}
void AddArchives() {
AddPatchSeries(dataRoot + "/patch-");
AddIfExists(dataRoot + "/patch.MPQ");
std::string localeRoot = dataRoot + "/" + locale;
AddPatchSeries(localeRoot + "/patch-" + locale + "-");
AddIfExists(localeRoot + "/patch-" + locale + ".MPQ");
AddIfExists(dataRoot + "/common.MPQ");
AddIfExists(dataRoot + "/common-2.MPQ");
AddIfExists(dataRoot + "/expansion.MPQ");
AddIfExists(dataRoot + "/lichking.MPQ");
AddIfExists(localeRoot + "/lichking-speech-" + locale + ".MPQ");
AddIfExists(localeRoot + "/expansion-speech-" + locale + ".MPQ");
AddIfExists(localeRoot + "/lichking-locale-" + locale + ".MPQ");
AddIfExists(localeRoot + "/expansion-locale-" + locale + ".MPQ");
AddIfExists(localeRoot + "/speech-" + locale + ".MPQ");
AddIfExists(localeRoot + "/locale-" + locale + ".MPQ");
AddIfExists(localeRoot + "/base-" + locale + ".MPQ");
AddIfExists(localeRoot + "/backup-" + locale + ".MPQ");
}
bool ReadFile(const char* filename, uint8_t** data, size_t* size) {
Initialize();
std::string normalized = NormalizePath(filename);
for (auto& archive : archives) {
if (archive.ReadFile(normalized, data, size)) {
return true;
}
}
return false;
}
bool HasFile(const char* filename) {
Initialize();
std::string normalized = NormalizePath(filename);
for (const auto& archive : archives) {
if (archive.FindBlock(normalized)) {
return true;
}
}
return false;
}
};
MpqManager& GetManager() {
static MpqManager manager;
return manager;
}
}
namespace Mpq {
bool FileExists(const char* filename) {
return GetManager().HasFile(filename);
}
bool ReadFile(const char* filename, uint8_t** data, size_t* size) {
return GetManager().ReadFile(filename, data, size);
}
}

12
src/util/Mpq.hpp Normal file
View File

@ -0,0 +1,12 @@
#ifndef UTIL_MPQ_HPP
#define UTIL_MPQ_HPP
#include <cstddef>
#include <cstdint>
namespace Mpq {
bool FileExists(const char* filename);
bool ReadFile(const char* filename, uint8_t** data, size_t* size);
}
#endif

View File

@ -1,15 +1,25 @@
#include "util/SFile.hpp"
#include <algorithm>
#include <cstring>
#include <limits>
#include <storm/Memory.hpp>
#include <storm/String.hpp>
#include "util/Mpq.hpp"
// TODO Proper implementation
int32_t SFile::Close(SFile* file) {
delete file->m_filename;
if (file->m_stream) {
file->m_stream->close();
delete file->m_stream;
file->m_stream = nullptr;
}
if (file->m_buffer) {
delete[] file->m_buffer;
file->m_buffer = nullptr;
}
delete file;
@ -18,6 +28,10 @@ int32_t SFile::Close(SFile* file) {
// TODO Proper implementation
int32_t SFile::FileExists(const char* filename) {
if (Mpq::FileExists(filename)) {
return 1;
}
auto pathLen = SStrLen(filename);
char path[STORM_MAX_PATH];
SStrCopy(path, filename, sizeof(path));
@ -49,32 +63,28 @@ int32_t SFile::IsStreamingTrial() {
// TODO Proper implementation
int32_t SFile::Load(SArchive* archive, const char* filename, void** buffer, size_t* bytes, size_t extraBytes, uint32_t flags, SOVERLAPPED* overlapped) {
auto pathLen = SStrLen(filename);
char path[STORM_MAX_PATH];
SStrCopy(path, filename, sizeof(path));
SFile* file = nullptr;
for (int32_t i = 0; i < pathLen; ++i) {
if (path[i] == '\\') {
path[i] = '/';
}
if (!SFile::OpenEx(archive, filename, flags, &file) || !file) {
return 0;
}
std::ifstream file (path, std::ios::in | std::ios::binary | std::ios::ate);
size_t size;
char* data;
if (file.is_open()) {
size = static_cast<size_t>(file.tellg());
size_t size = static_cast<size_t>(SFile::GetFileSize(file, nullptr));
if (bytes) {
*bytes = size;
}
data = new char[size + extraBytes];
char* data = new char[size + extraBytes];
size_t bytesRead = 0;
file.seekg(0, std::ios::beg);
file.read(data, size);
file.close();
int32_t result = SFile::Read(file, data, size, &bytesRead, nullptr, nullptr);
SFile::Close(file);
if (!result) {
delete[] data;
return 0;
}
if (extraBytes) {
memset(data + size, 0, extraBytes);
@ -83,9 +93,6 @@ int32_t SFile::Load(SArchive* archive, const char* filename, void** buffer, size
*buffer = data;
return 1;
} else {
return 0;
}
}
int32_t SFile::Open(const char* filename, SFile** file) {
@ -94,6 +101,25 @@ int32_t SFile::Open(const char* filename, SFile** file) {
// TODO Proper implementation
int32_t SFile::OpenEx(SArchive* archive, const char* filename, uint32_t flags, SFile** file) {
if (!archive) {
uint8_t* data = nullptr;
size_t size = 0;
if (Mpq::ReadFile(filename, &data, &size)) {
SFile* fileptr = new SFile;
fileptr->m_filename = strdup(filename);
fileptr->m_stream = nullptr;
fileptr->m_buffer = data;
fileptr->m_offset = 0;
fileptr->m_size = static_cast<std::streamsize>(size);
*file = fileptr;
return 1;
}
}
auto pathLen = SStrLen(filename);
char path[STORM_MAX_PATH];
SStrCopy(path, filename, sizeof(path));
@ -107,6 +133,8 @@ int32_t SFile::OpenEx(SArchive* archive, const char* filename, uint32_t flags, S
SFile* fileptr = new SFile;
fileptr->m_filename = strdup(filename);
fileptr->m_buffer = nullptr;
fileptr->m_offset = 0;
std::ifstream* stream = new std::ifstream(path, std::ios::in | std::ios::binary | std::ios::ate);
@ -133,7 +161,20 @@ int32_t SFile::OpenEx(SArchive* archive, const char* filename, uint32_t flags, S
// TODO Proper implementation
int32_t SFile::Read(SFile* file, void* buffer, size_t bytestoread, size_t* bytesread, SOVERLAPPED* overlapped, TASYNCPARAMBLOCK* asyncparam) {
file->m_stream->read((char*)buffer, bytestoread);
if (file->m_buffer) {
size_t available = static_cast<size_t>(file->m_size) - file->m_offset;
size_t toRead = std::min(bytestoread, available);
std::memcpy(buffer, file->m_buffer + file->m_offset, toRead);
file->m_offset += toRead;
if (bytesread) {
*bytesread = toRead;
}
return 1;
}
file->m_stream->read(static_cast<char*>(buffer), bytestoread);
if (bytesread) {
*bytesread = file->m_stream->gcount();

View File

@ -1,10 +1,9 @@
#ifndef UTIL_S_FILE_HPP
#define UTIL_S_FILE_HPP
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <iostream>
class SArchive;
struct SOVERLAPPED;
@ -27,6 +26,8 @@ class SFile {
// Member variables
const char* m_filename;
std::ifstream* m_stream; // TODO Proper implementation
uint8_t* m_buffer;
size_t m_offset;
std::streamsize m_size; // TODO Proper implementation
};