You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

303 lines
7.9 KiB

#include "mpq/mpq_sdl_rwops.hpp"
#include <cstdlib>
#include <cstring>
#include <mpqfs/mpqfs.h>
#ifdef USE_SDL3
#include <SDL3/SDL.h>
#include <SDL3/SDL_iostream.h>
#else
#include <SDL.h>
#ifndef USE_SDL1
#include <SDL_rwops.h>
#endif
#include "utils/sdl_compat.h"
#endif
namespace devilution {
namespace {
constexpr size_t MaxMpqPathSize = 256;
/* -----------------------------------------------------------------------
* Context structure shared by both SDL2 and SDL3 implementations.
*
* Wraps an mpqfs_stream_t (sector-based, on-demand decompression) and,
* for the threadsafe variant, an independently cloned archive so that
* reads don't race with the main thread's archive FILE*.
* ----------------------------------------------------------------------- */
struct MpqStreamCtx {
mpqfs_stream_t *stream; /* Sector-based stream (owned) */
mpqfs_archive_t *ownedClone; /* Non-null if we cloned for threadsafe */
};
static void DestroyCtx(MpqStreamCtx *ctx)
{
if (ctx == nullptr)
return;
mpqfs_stream_close(ctx->stream);
if (ctx->ownedClone != nullptr)
mpqfs_close(ctx->ownedClone);
delete ctx;
}
/* -----------------------------------------------------------------------
* Helper: create the MpqStreamCtx, optionally cloning the archive for
* thread-safety. Tries hash-based open first, falls back to filename.
* ----------------------------------------------------------------------- */
static MpqStreamCtx *CreateCtx(mpqfs_archive_t *archive,
uint32_t hashIndex,
const char *filename,
bool threadsafe)
{
mpqfs_archive_t *target = archive;
mpqfs_archive_t *clone = nullptr;
if (threadsafe) {
clone = mpqfs_clone(archive);
if (clone == nullptr)
return nullptr;
target = clone;
}
mpqfs_stream_t *stream = nullptr;
/* Try the fast hash-based path first (avoids re-hashing). */
if (hashIndex != UINT32_MAX)
stream = mpqfs_stream_open_from_hash(target, hashIndex);
/* Fall back to filename-based open (needed for encrypted files and
* when hashIndex is not available). */
if (stream == nullptr)
stream = mpqfs_stream_open(target, filename);
if (stream == nullptr) {
if (clone != nullptr)
mpqfs_close(clone);
return nullptr;
}
auto *ctx = new (std::nothrow) MpqStreamCtx { stream, clone };
if (ctx == nullptr) {
mpqfs_stream_close(stream);
if (clone != nullptr)
mpqfs_close(clone);
return nullptr;
}
return ctx;
}
/* =======================================================================
* SDL3 implementation
* ======================================================================= */
#ifdef USE_SDL3
static Sint64 SDLCALL MpqStream_Size(void *userdata)
{
auto *ctx = static_cast<MpqStreamCtx *>(userdata);
return static_cast<Sint64>(mpqfs_stream_size(ctx->stream));
}
static Sint64 SDLCALL MpqStream_Seek(void *userdata, Sint64 offset, SDL_IOWhence whence)
{
auto *ctx = static_cast<MpqStreamCtx *>(userdata);
int w;
switch (whence) {
case SDL_IO_SEEK_SET: w = SEEK_SET; break;
case SDL_IO_SEEK_CUR: w = SEEK_CUR; break;
case SDL_IO_SEEK_END: w = SEEK_END; break;
default:
SDL_SetError("MpqStream_Seek: unknown whence");
return -1;
}
int64_t pos = mpqfs_stream_seek(ctx->stream, offset, w);
if (pos < 0) {
SDL_SetError("MpqStream_Seek: seek failed");
return -1;
}
return static_cast<Sint64>(pos);
}
static size_t SDLCALL MpqStream_Read(void *userdata, void *ptr, size_t size, SDL_IOStatus *status)
{
auto *ctx = static_cast<MpqStreamCtx *>(userdata);
size_t n = mpqfs_stream_read(ctx->stream, ptr, size);
if (n == static_cast<size_t>(-1)) {
if (status != nullptr)
*status = SDL_IO_STATUS_ERROR;
SDL_SetError("MpqStream_Read: read failed");
return 0;
}
if (n == 0 && size > 0) {
if (status != nullptr)
*status = SDL_IO_STATUS_EOF;
} else {
if (status != nullptr)
*status = SDL_IO_STATUS_READY;
}
return n;
}
static size_t SDLCALL MpqStream_Write(void * /*userdata*/, const void * /*ptr*/,
size_t /*size*/, SDL_IOStatus *status)
{
if (status != nullptr)
*status = SDL_IO_STATUS_ERROR;
SDL_SetError("MpqStream_Write: read-only stream");
return 0;
}
static bool SDLCALL MpqStream_Close(void *userdata)
{
auto *ctx = static_cast<MpqStreamCtx *>(userdata);
DestroyCtx(ctx);
return true;
}
#else /* SDL1 or SDL2 */
/* =======================================================================
* SDL1 / SDL2 implementation
*
* SDL1 uses int for offsets and sizes; SDL2 uses Sint64 and size_t.
* SDL1 has no ->size callback and no SDL_RWOPS_UNKNOWN.
* SDL1's SDL_SetError returns void; SDL2's returns int.
* ======================================================================= */
#ifndef USE_SDL1
using OffsetType = Sint64;
using SizeType = size_t;
#else
using OffsetType = int;
using SizeType = int;
#endif
#ifndef USE_SDL1
static Sint64 SDLCALL MpqStream_Size(SDL_RWops *rw)
{
auto *ctx = static_cast<MpqStreamCtx *>(rw->hidden.unknown.data1);
return static_cast<Sint64>(mpqfs_stream_size(ctx->stream));
}
#endif
static OffsetType SDLCALL MpqStream_Seek(SDL_RWops *rw, OffsetType offset, int whence)
{
auto *ctx = static_cast<MpqStreamCtx *>(rw->hidden.unknown.data1);
int w;
switch (whence) {
case SDL_IO_SEEK_SET: w = SEEK_SET; break;
case SDL_IO_SEEK_CUR: w = SEEK_CUR; break;
case SDL_IO_SEEK_END: w = SEEK_END; break;
default:
SDL_SetError("MpqStream_Seek: unknown whence");
return -1;
}
int64_t pos = mpqfs_stream_seek(ctx->stream, offset, w);
if (pos < 0) {
SDL_SetError("MpqStream_Seek: seek failed");
return -1;
}
return static_cast<OffsetType>(pos);
}
static SizeType SDLCALL MpqStream_Read(SDL_RWops *rw, void *ptr,
SizeType size, SizeType maxnum)
{
auto *ctx = static_cast<MpqStreamCtx *>(rw->hidden.unknown.data1);
size_t totalBytes = static_cast<size_t>(size) * static_cast<size_t>(maxnum);
size_t n = mpqfs_stream_read(ctx->stream, ptr, totalBytes);
if (n == static_cast<size_t>(-1))
return 0;
/* Return number of whole objects read. */
return (size > 0) ? static_cast<SizeType>(n / static_cast<size_t>(size)) : 0;
}
static SizeType SDLCALL MpqStream_Write(SDL_RWops * /*rw*/, const void * /*ptr*/,
SizeType /*size*/, SizeType /*num*/)
{
SDL_SetError("MpqStream_Write: read-only stream");
return 0;
}
static int SDLCALL MpqStream_Close(SDL_RWops *rw)
{
if (rw != nullptr) {
auto *ctx = static_cast<MpqStreamCtx *>(rw->hidden.unknown.data1);
DestroyCtx(ctx);
SDL_FreeRW(rw);
}
return 0;
}
#endif /* !USE_SDL3 */
} // namespace
SdlRwopsType *SDL_RWops_FromMpqFile(MpqArchive &archive,
uint32_t hashIndex,
std::string_view filename,
bool threadsafe)
{
/* NUL-terminate the filename for the C API. */
char pathBuf[MaxMpqPathSize];
if (filename.size() >= sizeof(pathBuf))
return nullptr;
std::memcpy(pathBuf, filename.data(), filename.size());
pathBuf[filename.size()] = '\0';
MpqStreamCtx *ctx = CreateCtx(archive.handle(), hashIndex, pathBuf, threadsafe);
if (ctx == nullptr)
return nullptr;
#ifdef USE_SDL3
SDL_IOStreamInterface iface = {};
iface.version = sizeof(iface);
iface.size = MpqStream_Size;
iface.seek = MpqStream_Seek;
iface.read = MpqStream_Read;
iface.write = MpqStream_Write;
iface.close = MpqStream_Close;
SdlRwopsType *rwops = SDL_OpenIO(&iface, ctx);
if (rwops == nullptr) {
DestroyCtx(ctx);
return nullptr;
}
return rwops;
#else
SDL_RWops *rwops = SDL_AllocRW();
if (rwops == nullptr) {
DestroyCtx(ctx);
return nullptr;
}
#ifndef USE_SDL1
rwops->type = SDL_RWOPS_UNKNOWN;
rwops->size = MpqStream_Size;
#else
rwops->type = 0;
#endif
rwops->seek = MpqStream_Seek;
rwops->read = MpqStream_Read;
rwops->write = MpqStream_Write;
rwops->close = MpqStream_Close;
rwops->hidden.unknown.data1 = ctx;
return rwops;
#endif
}
} // namespace devilution