Skip to content
Merged
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
12 changes: 10 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ ifeq ($(shell uname -s),Linux)
LDFLAGS += -Wl,-z,defs -lm -lEGL -lGL
endif

ifeq ($(shell uname -s),OpenBSD)
CFLAGS += -I/usr/local/include
LDFLAGS += -L/usr/local/lib -lm -lGL
MPQ_LIB := -lmpq
else
MPQ_LIB := -lstorm
endif

ifeq ($(shell uname -s),Darwin)
ifeq ($(shell uname -m),arm64)
HOMEBREW_PREFIX := /opt/homebrew
Expand Down Expand Up @@ -56,10 +64,10 @@ $(LIB_DIR)/libgame.so: $(GAME_OBJS) $(LIB_DIR)
$(CC) -shared -o $@ $(GAME_OBJS) $(LDFLAGS) -lcmath3

$(LIB_DIR)/librenderer.so: cmath3 $(RENDERER_OBJS) $(LIB_DIR)
$(CC) -shared -o $@ $(RENDERER_OBJS) $(LDFLAGS) -lcmath3 -lSDL2 -lstorm -ljpeg
$(CC) -shared -o $@ $(RENDERER_OBJS) $(LDFLAGS) -lcmath3 -lSDL2 $(MPQ_LIB) -ljpeg

$(BIN_DIR)/openwarcraft3: cmath3 game renderer $(APP_OBJS) $(BIN_DIR)
$(CC) -o $@ $(APP_OBJS) -Wl,-rpath,'$$ORIGIN/../lib' $(LDFLAGS) -lcmath3 -lSDL2 -lstorm -lgame -lrenderer
$(CC) -o $@ $(APP_OBJS) -Wl,-rpath,'$$ORIGIN/../lib' $(LDFLAGS) -lcmath3 -lSDL2 $(MPQ_LIB) -lgame -lrenderer

$(OBJ_DIR)/%.o: src/%.c
@mkdir -p $(dir $@)
Expand Down
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ This will run the built `openwarcraft3` executable from the `build/bin` folder.

The Makefile build requires the following libraries installed on your system:

* **StormLib** — for MPQ archive support
* **StormLib** (or **libmpq** on OpenBSD) — for MPQ archive support
* **SDL2** — for windowing and input
* **libjpeg** — for JPEG image decoding

Expand All @@ -91,6 +91,14 @@ On Linux, use your distribution package manager, e.g., for Ubuntu:
sudo apt-get install libsdl2-dev libjpeg-dev libstorm-dev
```

On OpenBSD, install the packages using pkg_add:

```bash
doas pkg_add sdl2 libmpq jpeg
```

**Note:** OpenBSD support uses libmpq instead of StormLib, as StormLib is not well-supported on OpenBSD. The code automatically detects OpenBSD and uses the appropriate library.

---

# Project Architecture & Code Structure
Expand Down
7 changes: 7 additions & 0 deletions premake5.lua
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ solution "openwarcraft3"
["ALWAYS_SEARCH_USER_PATHS"] = "YES", -- This is the minimum version of macos we'll be able to run on
};

filter {"system:bsd"}
defines { "OW3_PLATFORM_BSD" }

filter {"system:windows"}
defines { "OW3_PLATFORM_WINDOWS" }

Expand Down Expand Up @@ -65,6 +68,10 @@ project "openwarcraft3"
links { "gdi32", "kernel32", "psapi" }
filter "system:linux"
links { "dl", "GL", "pthread", "X11" }
filter "system:bsd"
links { "GL", "pthread", "mpq" }
includedirs { "/usr/local/include" }
libdirs { "/usr/local/lib" }
filter "system:macosx"
links { "QuartzCore.framework", "Metal.framework", "Cocoa.framework", "IOKit.framework", "CoreVideo.framework" }
linkoptions {
Expand Down
2 changes: 1 addition & 1 deletion src/common/common.c
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#include "common.h"

#include <StormLib.h>
#include "mpq_adapter.h"

#define MAXPRINTMSG 4096

Expand Down
296 changes: 296 additions & 0 deletions src/common/mpq_adapter.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,296 @@
#include "mpq_adapter.h"

#ifdef USE_LIBMPQ

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <stdint.h>

/* Windows types compatibility - these should be defined in shared.h */
#ifndef TRUE
#define TRUE 1
#endif
#ifndef FALSE
#define FALSE 0
#endif

/* SIZE_MAX may not be defined in older systems */
#ifndef SIZE_MAX
#define SIZE_MAX ((size_t)-1)
#endif

/* Open an MPQ archive */
BOOL MPQ_OpenArchive(LPCSTR szMpqName, DWORD dwPriority, DWORD dwFlags, MPQ_ARCHIVE* phMpq) {
mpq_archive_s* archive = NULL;
int result = libmpq__archive_open(&archive, (char*)szMpqName, -1);

if (result != 0 || archive == NULL) {
return FALSE;
}

*phMpq = archive;
return TRUE;
}

/* Close an MPQ archive */
BOOL MPQ_CloseArchive(MPQ_ARCHIVE hMpq) {
if (hMpq == NULL) {
return FALSE;
}

int result = libmpq__archive_close(hMpq);
return (result == 0) ? TRUE : FALSE;
}

/* Open a file within an MPQ archive */
BOOL MPQ_OpenFileEx(MPQ_ARCHIVE hMpq, LPCSTR szFileName, DWORD dwSearchScope, MPQ_FILE* phFile) {
if (hMpq == NULL || szFileName == NULL || phFile == NULL) {
return FALSE;
}

MPQ_FILE_HANDLE* file_handle = (MPQ_FILE_HANDLE*)malloc(sizeof(MPQ_FILE_HANDLE));
if (file_handle == NULL) {
return FALSE;
}

/* Get file number from filename */
int file_number = 0;
int result = libmpq__file_number(hMpq, (char*)szFileName, &file_number);

if (result != 0) {
free(file_handle);
return FALSE;
}

/* Get file size */
off_t file_size = 0;
result = libmpq__file_unpacked_size(hMpq, file_number, &file_size);

if (result != 0) {
free(file_handle);
return FALSE;
}

/* Allocate buffer and read entire file for efficient random access */
/* Check for overflow - file_size should fit in size_t */
if (file_size < 0 || (sizeof(size_t) < sizeof(off_t) && file_size > (off_t)SIZE_MAX)) {
free(file_handle);
return FALSE;
}

/* Handle zero-size files */
unsigned char* buffer = NULL;
if (file_size > 0) {
buffer = (unsigned char*)malloc((size_t)file_size);
if (buffer == NULL) {
free(file_handle);
return FALSE;
}

result = libmpq__file_read(hMpq, file_number, buffer, (off_t)file_size);

if (result != 0) {
free(buffer);
free(file_handle);
return FALSE;
}
}

file_handle->archive = hMpq;
file_handle->file_number = file_number;
file_handle->offset = 0;
file_handle->cached_data = buffer;
file_handle->cached_size = file_size;

*phFile = file_handle;
return TRUE;
}

/* Close a file handle */
BOOL MPQ_CloseFile(MPQ_FILE hFile) {
if (hFile == NULL) {
return FALSE;
}

if (hFile->cached_data != NULL) {
free(hFile->cached_data);
}

free(hFile);
return TRUE;
}

/* Read data from a file */
BOOL MPQ_ReadFile(MPQ_FILE hFile, LPVOID lpBuffer, DWORD dwToRead, LPDWORD pdwRead, LPOVERLAPPED lpOverlapped) {
if (hFile == NULL || lpBuffer == NULL) {
return FALSE;
}

/* Check if we're trying to read beyond file size */
if (hFile->offset >= hFile->cached_size) {
if (pdwRead != NULL) {
*pdwRead = 0;
}
return TRUE; /* Not an error, just EOF */
}

/* Calculate bytes to read safely to avoid overflow */
off_t bytes_to_read = dwToRead;
off_t remaining = hFile->cached_size - hFile->offset;

/* Ensure we don't read past end of file */
if (bytes_to_read > remaining) {
bytes_to_read = remaining;
}

/* Additional safety check for pointer arithmetic */
if (hFile->offset < 0 || bytes_to_read < 0 ||
hFile->offset > hFile->cached_size ||
bytes_to_read > (hFile->cached_size - hFile->offset)) {
return FALSE;
}

/* Copy from cached data */
memcpy(lpBuffer, hFile->cached_data + hFile->offset, (size_t)bytes_to_read);
hFile->offset += bytes_to_read;

if (pdwRead != NULL) {
/* Check if bytes_to_read fits in DWORD before casting */
if (bytes_to_read > 0xFFFFFFFF) {
/* This shouldn't happen with DWORD dwToRead input, but be safe */
*pdwRead = 0xFFFFFFFF;
} else {
*pdwRead = (DWORD)bytes_to_read;
}
}

return TRUE;
}

/* Get file size */
DWORD MPQ_GetFileSize(MPQ_FILE hFile, LPDWORD pdwFileSizeHigh) {
if (hFile == NULL) {
return 0;
}

if (pdwFileSizeHigh != NULL) {
/* Safe handling for both 32-bit and 64-bit off_t */
#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
*pdwFileSizeHigh = (DWORD)(hFile->cached_size >> 32);
#else
*pdwFileSizeHigh = 0;
#endif
}

return (DWORD)(hFile->cached_size & 0xFFFFFFFF);
}

/* Set file pointer position */
DWORD MPQ_SetFilePointer(MPQ_FILE hFile, LONG lFilePos, LONG* plFilePosHigh, DWORD dwMoveMethod) {
if (hFile == NULL) {
return 0xFFFFFFFF;
}

off_t new_offset = 0;
off_t file_pos_offset = (off_t)lFilePos;

switch (dwMoveMethod) {
case FILE_BEGIN:
new_offset = file_pos_offset;
break;
case FILE_CURRENT:
/* Check for overflow in addition */
if (lFilePos > 0 && hFile->offset > (off_t)(SIZE_MAX - lFilePos)) {
return 0xFFFFFFFF; /* Overflow would occur */
}
if (lFilePos < 0 && hFile->offset < (off_t)lFilePos) {
return 0xFFFFFFFF; /* Underflow would occur */
}
new_offset = hFile->offset + file_pos_offset;
break;
case FILE_END:
/* Check for overflow in addition */
if (lFilePos > 0 && hFile->cached_size > (off_t)(SIZE_MAX - lFilePos)) {
return 0xFFFFFFFF; /* Overflow would occur */
}
if (lFilePos < 0 && hFile->cached_size < (off_t)(-lFilePos)) {
return 0xFFFFFFFF; /* Underflow would occur */
}
new_offset = hFile->cached_size + file_pos_offset;
break;
default:
return 0xFFFFFFFF;
}

if (new_offset < 0) {
return 0xFFFFFFFF;
}

hFile->offset = new_offset;

if (plFilePosHigh != NULL) {
/* Safe handling for both 32-bit and 64-bit off_t */
#if defined(_FILE_OFFSET_BITS) && _FILE_OFFSET_BITS == 64
*plFilePosHigh = (LONG)(new_offset >> 32);
#else
*plFilePosHigh = 0;
#endif
}

return (DWORD)(new_offset & 0xFFFFFFFF);
}

/* Extract a file from the archive */
BOOL MPQ_ExtractFile(MPQ_ARCHIVE hMpq, LPCSTR szToExtract, LPCSTR szExtracted, DWORD dwSearchScope) {
if (hMpq == NULL || szToExtract == NULL || szExtracted == NULL) {
return FALSE;
}

int file_number = 0;
int result = libmpq__file_number(hMpq, (char*)szToExtract, &file_number);

if (result != 0) {
return FALSE;
}

result = libmpq__file_extract(hMpq, file_number, (char*)szExtracted);
return (result == 0) ? TRUE : FALSE;
}

/* Find first file in archive - simplified implementation */
MPQ_FIND_HANDLE MPQ_FindFirstFile(MPQ_ARCHIVE hMpq, LPCSTR szMask, LPMPQ_FIND_DATA lpFindFileData, LPCSTR szListFile) {
/* Note: libmpq doesn't have native file enumeration like StormLib.
* This is a simplified implementation that may not work for all cases.
* For full functionality, you would need to read and parse the (listfile)
* from the MPQ archive. */

if (hMpq == NULL || lpFindFileData == NULL) {
return NULL;
}

/* Initialize find data structure */
lpFindFileData->archive = hMpq;
lpFindFileData->current_file = 0;

/* Try to get the number of files in the archive */
/* This is a limitation - libmpq doesn't provide easy file enumeration */
lpFindFileData->file_count = 0;

/* Return NULL to indicate file enumeration is not fully supported with libmpq */
return NULL;
}

/* Find next file */
BOOL MPQ_FindNextFile(MPQ_FIND_HANDLE hFind, LPMPQ_FIND_DATA lpFindFileData) {
/* Not fully implemented - libmpq limitation */
return FALSE;
}

/* Close find handle */
BOOL MPQ_FindClose(MPQ_FIND_HANDLE hFind) {
/* Nothing to clean up in our simplified implementation */
return TRUE;
}

#endif /* USE_LIBMPQ */
Loading