From 3d308983a8b1b977c1b268fa9b1e8aaae50929ef Mon Sep 17 00:00:00 2001 From: Gleb Mazovetskiy Date: Sat, 23 Oct 2021 11:19:49 +0100 Subject: [PATCH] Migrate to libmpq libmpq is a much simpler alternative to StormLib for reading MPQ archives. We use our own fork of libmpq: https://github.com/diasurgical/libmpq Impact: * DevilutionX is now a lot more portable. Unlike StormLib, libmpq only needs platform-specific code for Windows. * Locks around file access **removed** (instead we duplicate the file descriptor for streamed audio only). * RAM usage is **300 KiB** lower than StormLib. * Stripped release linux_x86_64 binary is **32 KiB** smaller. * Amiga build now hangs instead of crashing. --- .circleci/config.yml | 4 +- .github/workflows/Linux_x86.yml | 2 +- .github/workflows/Linux_x86_64_SDL1.yml | 2 +- .github/workflows/Windows_x64.yml | 2 +- .github/workflows/Windows_x86.yml | 2 +- 3rdParty/StormLib/src/SFileOpenFileEx.cpp | 836 +++++++++--------- CMake/amiga_defs.cmake | 5 + CMake/ctr/modules/FindZLIB.cmake | 2 + CMakeLists.txt | 10 +- Source/DiabloUI/art.cpp | 20 +- Source/DiabloUI/art.h | 1 + Source/DiabloUI/diabloui.cpp | 1 - Source/DiabloUI/extrasmenu.cpp | 4 +- Source/DiabloUI/mainmenu.cpp | 2 +- Source/DiabloUI/selconn.cpp | 2 +- Source/DiabloUI/selgame.cpp | 2 +- Source/DiabloUI/selhero.cpp | 2 +- Source/appfat.cpp | 3 +- Source/capture.cpp | 2 +- Source/controls/touch/renderers.h | 4 + Source/diablo.cpp | 2 +- Source/dvlnet/abstract_net.cpp | 1 - Source/dvlnet/abstract_net.h | 2 +- Source/dvlnet/base.h | 2 + Source/dvlnet/cdwrap.h | 2 + Source/dvlnet/loopback.cpp | 2 + Source/dvlnet/packet.cpp | 2 + Source/dvlnet/tcp_server.h | 1 + Source/dx.cpp | 1 - Source/engine/game_assets.cpp | 76 ++ .../storm_sdl_rw.h => engine/game_assets.hpp} | 2 +- Source/engine/load_file.hpp | 12 +- Source/engine/render/text_render.cpp | 2 +- Source/init.cpp | 118 +-- Source/init.h | 23 +- Source/menu.cpp | 6 +- Source/miniwin/misc_msg.cpp | 1 - Source/monster.cpp | 2 +- Source/msg.cpp | 2 +- Source/multi.cpp | 3 +- Source/multi.h | 6 - Source/nthread.cpp | 2 +- Source/pfile.cpp | 96 +- Source/player.cpp | 5 +- Source/sound.cpp | 14 +- Source/storm/storm.cpp | 114 --- Source/storm/storm_net.cpp | 25 +- Source/storm/{storm.h => storm_net.hpp} | 140 +-- Source/storm/storm_sdl_rw.cpp | 147 --- Source/storm/storm_svid.cpp | 14 +- Source/utils/language.cpp | 6 +- Source/utils/mpq.cpp | 145 +++ Source/utils/mpq.hpp | 81 ++ Source/utils/mpq_sdl_rwops.cpp | 232 +++++ Source/utils/mpq_sdl_rwops.hpp | 13 + Source/utils/pcx.hpp | 28 + Source/utils/png.h | 5 +- .../sdl_rwops_file_wrapper.cpp} | 24 +- .../sdl_rwops_file_wrapper.hpp} | 8 +- Source/utils/soundsample.cpp | 6 +- android-project/CMake/android_defs.cmake | 1 + docs/building.md | 14 +- test/pack_test.cpp | 1 - vcpkg.json | 5 +- 64 files changed, 1229 insertions(+), 1073 deletions(-) create mode 100644 Source/engine/game_assets.cpp rename Source/{storm/storm_sdl_rw.h => engine/game_assets.hpp} (72%) delete mode 100644 Source/storm/storm.cpp rename Source/storm/{storm.h => storm_net.hpp} (53%) delete mode 100644 Source/storm/storm_sdl_rw.cpp create mode 100644 Source/utils/mpq.cpp create mode 100644 Source/utils/mpq.hpp create mode 100644 Source/utils/mpq_sdl_rwops.cpp create mode 100644 Source/utils/mpq_sdl_rwops.hpp create mode 100644 Source/utils/pcx.hpp rename Source/{storm/storm_file_wrapper.cpp => utils/sdl_rwops_file_wrapper.cpp} (57%) rename Source/{storm/storm_file_wrapper.h => utils/sdl_rwops_file_wrapper.hpp} (59%) diff --git a/.circleci/config.yml b/.circleci/config.yml index e3b757b0..b0f23b98 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ jobs: - checkout - run: echo deb http://deb.debian.org/debian stretch-backports-sloppy main >> /etc/apt/sources.list.d/debian-backports.list - run: apt update -y - - run: apt install -y g++ libsdl2-dev git rpm wget smpq + - run: apt install -y g++ libsdl2-dev libbz2-dev git rpm wget smpq - run: apt install -y -t 'stretch-backports*' cmake libsodium-dev libpng-dev - run: cmake -S. -Bbuild .. -DNIGHTLY_BUILD=ON -DCMAKE_INSTALL_PREFIX=/usr - run: cmake --build build -j 2 --target package @@ -24,7 +24,7 @@ jobs: steps: - checkout - run: apt-get update -y - - run: apt-get install -y cmake curl g++ git lcov libgtest-dev libgmock-dev libfmt-dev libsdl2-dev libsodium-dev libpng-dev + - run: apt-get install -y cmake curl g++ git lcov libgtest-dev libgmock-dev libfmt-dev libsdl2-dev libsodium-dev libpng-dev libbz2-dev - run: cmake -S. -Bbuild -DRUN_TESTS=ON -DENABLE_CODECOVERAGE=ON - run: cmake --build build -j 2 - run: cmake --build build -j 2 --target test diff --git a/.github/workflows/Linux_x86.yml b/.github/workflows/Linux_x86.yml index 79e96dba..b1e3efe1 100644 --- a/.github/workflows/Linux_x86.yml +++ b/.github/workflows/Linux_x86.yml @@ -20,7 +20,7 @@ jobs: run: > sudo dpkg --add-architecture i386 && sudo apt update -y && - sudo apt install -y cmake file g++-multilib git libfmt-dev:i386 libsdl2-dev:i386 libsodium-dev:i386 libpng-dev:i386 rpm wget smpq + sudo apt install -y cmake file g++-multilib git libfmt-dev:i386 libsdl2-dev:i386 libsodium-dev:i386 libpng-dev:i386 libbz2-dev:i386 rpm wget smpq - name: Cache CMake build folder uses: actions/cache@v2 diff --git a/.github/workflows/Linux_x86_64_SDL1.yml b/.github/workflows/Linux_x86_64_SDL1.yml index a77b3196..56103800 100644 --- a/.github/workflows/Linux_x86_64_SDL1.yml +++ b/.github/workflows/Linux_x86_64_SDL1.yml @@ -19,7 +19,7 @@ jobs: - name: Create Build Environment run: > sudo apt update && - sudo apt install -y cmake file g++ git libfmt-dev libsdl-dev libsodium-dev libpng-dev rpm smpq + sudo apt install -y cmake file g++ git libfmt-dev libsdl-dev libsodium-dev libpng-dev libbz2-dev rpm smpq - name: Cache CMake build folder uses: actions/cache@v2 diff --git a/.github/workflows/Windows_x64.yml b/.github/workflows/Windows_x64.yml index 750af76f..bc11ed05 100644 --- a/.github/workflows/Windows_x64.yml +++ b/.github/workflows/Windows_x64.yml @@ -32,7 +32,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}} - run: cmake -S. -Bbuild -DNIGHTLY_BUILD=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/mingwcc64.cmake + run: cmake -S. -Bbuild -DNIGHTLY_BUILD=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/mingwcc64.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF - name: Build working-directory: ${{github.workspace}} diff --git a/.github/workflows/Windows_x86.yml b/.github/workflows/Windows_x86.yml index 618cfc8c..441d64fa 100644 --- a/.github/workflows/Windows_x86.yml +++ b/.github/workflows/Windows_x86.yml @@ -32,7 +32,7 @@ jobs: - name: Configure CMake shell: bash working-directory: ${{github.workspace}} - run: cmake -S. -Bbuild -DNIGHTLY_BUILD=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/mingwcc.cmake + run: cmake -S. -Bbuild -DNIGHTLY_BUILD=ON -DCMAKE_TOOLCHAIN_FILE=../CMake/mingwcc.cmake -DDEVILUTIONX_SYSTEM_BZIP2=OFF - name: Build working-directory: ${{github.workspace}} diff --git a/3rdParty/StormLib/src/SFileOpenFileEx.cpp b/3rdParty/StormLib/src/SFileOpenFileEx.cpp index 1e02a220..a6b7bd9a 100644 --- a/3rdParty/StormLib/src/SFileOpenFileEx.cpp +++ b/3rdParty/StormLib/src/SFileOpenFileEx.cpp @@ -1,418 +1,418 @@ -/*****************************************************************************/ -/* SFileOpenFileEx.cpp Copyright (c) Ladislav Zezula 2003 */ -/*---------------------------------------------------------------------------*/ -/* Description : */ -/*---------------------------------------------------------------------------*/ -/* Date Ver Who Comment */ -/* -------- ---- --- ------- */ -/* xx.xx.99 1.00 Lad The first version of SFileOpenFileEx.cpp */ -/*****************************************************************************/ - -#define __STORMLIB_SELF__ -#include "StormLib.h" -#include "StormCommon.h" - -/*****************************************************************************/ -/* Local functions */ -/*****************************************************************************/ - -static DWORD FindHashIndex(TMPQArchive * ha, DWORD dwFileIndex) -{ - TMPQHash * pHashTableEnd; - TMPQHash * pHash; - DWORD dwFirstIndex = HASH_ENTRY_FREE; - - // Should only be called if the archive has hash table - assert(ha->pHashTable != NULL); - - // Multiple hash table entries can point to the file table entry. - // We need to search all of them - pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; - for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++) - { - if(MPQ_BLOCK_INDEX(pHash) == dwFileIndex) - { - // Duplicate hash entry found - if(dwFirstIndex != HASH_ENTRY_FREE) - return HASH_ENTRY_FREE; - dwFirstIndex = (DWORD)(pHash - ha->pHashTable); - } - } - - // Return the hash table entry index - return dwFirstIndex; -} - -static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer) -{ - TMPQNamePrefix * pPrefix; - - // Are there patches in the current MPQ? - if(ha->dwFlags & MPQ_FLAG_PATCH) - { - // The patch prefix must be already known here - assert(ha->pPatchPrefix != NULL); - pPrefix = ha->pPatchPrefix; - - // The patch name for "OldWorld\\XXX\\YYY" is "Base\\XXX\YYY" - // We need to remove the "OldWorld\\" prefix - if(!_strnicmp(szFileName, "OldWorld\\", 9)) - szFileName += 9; - - // Create the file name from the known patch entry - memcpy(szBuffer, pPrefix->szPatchPrefix, pPrefix->nLength); - strcpy(szBuffer + pPrefix->nLength, szFileName); - szFileName = szBuffer; - } - - return szFileName; -} - -static bool OpenLocalFile(const char * szFileName, HANDLE * PtrFile) -{ - TFileStream * pStream; - TMPQFile * hf = NULL; - TCHAR szFileNameT[MAX_PATH]; - - // Convert the file name to UNICODE (if needed) - StringCopy(szFileNameT, _countof(szFileNameT), szFileName); - - // Open the file and create the TMPQFile structure - pStream = FileStream_OpenFile(szFileNameT, STREAM_FLAG_READ_ONLY); - if(pStream != NULL) - { - // Allocate and initialize file handle - hf = CreateFileHandle(NULL, NULL); - if(hf != NULL) - { - hf->pStream = pStream; - *PtrFile = hf; - return true; - } - else - { - FileStream_Close(pStream); - SetLastError(ERROR_NOT_ENOUGH_MEMORY); - } - } - *PtrFile = NULL; - return false; -} - -bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * PtrFile) -{ - TMPQArchive * haBase = NULL; - TMPQArchive * ha = (TMPQArchive *)hMpq; - TFileEntry * pFileEntry; - TMPQFile * hfPatch; // Pointer to patch file - TMPQFile * hfBase = NULL; // Pointer to base open file - TMPQFile * hf = NULL; - HANDLE hPatchFile; - char szNameBuffer[MAX_PATH]; - - // First of all, find the latest archive where the file is in base version - // (i.e. where the original, unpatched version of the file exists) - while(ha != NULL) - { - // If the file is there, then we remember the archive - pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szNameBuffer), 0, NULL); - if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) - haBase = ha; - - // Move to the patch archive - ha = ha->haPatch; - } - - // If we couldn't find the base file in any of the patches, it doesn't exist - if((ha = haBase) != NULL) - { - // Now open the base file - if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase)) - { - // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE - assert((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0); - hf = hfBase; - - // Now open all patches and attach them on top of the base file - for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch) - { - // Prepare the file name with a correct prefix - if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile)) - { - // Remember the new version - hfPatch = (TMPQFile *)hPatchFile; - - // We should not find patch file - assert((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) != 0); - - // Attach the patch to the base file - hf->hfPatch = hfPatch; - hf = hfPatch; - } - } - } - } - else - { - SetLastError(ERROR_FILE_NOT_FOUND); - } - - // Give the updated base MPQ - if(PtrFile != NULL) - *PtrFile = (HANDLE)hfBase; - return (hfBase != NULL); -} - -/*****************************************************************************/ -/* Public functions */ -/*****************************************************************************/ - -//----------------------------------------------------------------------------- -// SFileEnumLocales enums all locale versions within MPQ. -// Functions fills all available language identifiers on a file into the buffer -// pointed by plcLocales. There must be enough entries to copy the localed, -// otherwise the function returns ERROR_INSUFFICIENT_BUFFER. - -int WINAPI SFileEnumLocales( - HANDLE hMpq, - const char * szFileName, - LCID * PtrLocales, - LPDWORD PtrMaxLocales, - DWORD dwSearchScope) -{ - TMPQArchive * ha = (TMPQArchive *)hMpq; - TMPQHash * pFirstHash; - TMPQHash * pHash; - DWORD dwFileIndex = 0; - DWORD dwMaxLocales; - DWORD dwLocales = 0; - - // Test the parameters - if(!IsValidMpqHandle(hMpq)) - return ERROR_INVALID_HANDLE; - if(szFileName == NULL || *szFileName == 0) - return ERROR_INVALID_PARAMETER; - if(ha->pHashTable == NULL) - return ERROR_NOT_SUPPORTED; - if(PtrMaxLocales == NULL) - return ERROR_INVALID_PARAMETER; - if(IsPseudoFileName(szFileName, &dwFileIndex)) - return ERROR_INVALID_PARAMETER; - - // Keep compiler happy - dwMaxLocales = PtrMaxLocales[0]; - dwSearchScope = dwSearchScope; - - // Parse all files with that name - pFirstHash = pHash = GetFirstHashEntry(ha, szFileName); - while(pHash != NULL) - { - // Put the locales to the buffer - if(PtrLocales != NULL && dwLocales < dwMaxLocales) - *PtrLocales++ = pHash->lcLocale; - dwLocales++; - - // Get the next locale - pHash = GetNextHashEntry(ha, pFirstHash, pHash); - } - - // Give the caller the number of locales and return - PtrMaxLocales[0] = dwLocales; - return (dwLocales <= dwMaxLocales) ? ERROR_SUCCESS : ERROR_INSUFFICIENT_BUFFER; -} - -//----------------------------------------------------------------------------- -// SFileOpenFileEx -// -// hMpq - Handle of opened MPQ archive -// szFileName - Name of file to open -// dwSearchScope - Where to search -// PtrFile - Pointer to store opened file handle - -bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * PtrFile) -{ - TMPQArchive * ha = IsValidMpqHandle(hMpq); - TFileEntry * pFileEntry = NULL; - TMPQFile * hf = NULL; - DWORD dwHashIndex = HASH_ENTRY_FREE; - DWORD dwFileIndex = 0; - bool bOpenByIndex = false; - int nError = ERROR_SUCCESS; - - // Don't accept NULL pointer to file handle - if(szFileName == NULL || *szFileName == 0) - nError = ERROR_INVALID_PARAMETER; - - // When opening a file from MPQ, the handle must be valid - if(dwSearchScope != SFILE_OPEN_LOCAL_FILE && ha == NULL) - nError = ERROR_INVALID_HANDLE; - - // When not checking for existence, the pointer to file handle must be valid - if(dwSearchScope != SFILE_OPEN_CHECK_EXISTS && PtrFile == NULL) - nError = ERROR_INVALID_PARAMETER; - - // Prepare the file opening - if(nError == ERROR_SUCCESS) - { - switch(dwSearchScope) - { - case SFILE_OPEN_FROM_MPQ: - case SFILE_OPEN_BASE_FILE: - case SFILE_OPEN_CHECK_EXISTS: - - // If this MPQ has no patches, open the file from this MPQ directly - if(ha->haPatch == NULL || dwSearchScope == SFILE_OPEN_BASE_FILE) - { - pFileEntry = GetFileEntryLocale2(ha, szFileName, g_lcFileLocale, &dwHashIndex); - } - - // If this MPQ is a patched archive, open the file as patched - else - { - return OpenPatchedFile(hMpq, szFileName, PtrFile); - } - break; - - case SFILE_OPEN_ANY_LOCALE: - - // This open option is reserved for opening MPQ internal listfile. - // No argument validation. Tries to open file with neutral locale first, - // then any other available. - pFileEntry = GetFileEntryLocale2(ha, szFileName, 0, &dwHashIndex); - break; - - case SFILE_OPEN_LOCAL_FILE: - - // Open a local file - return OpenLocalFile(szFileName, PtrFile); - - default: - - // Don't accept any other value - nError = ERROR_INVALID_PARAMETER; - break; - } - } - - // Check whether the file really exists in the MPQ - if(nError == ERROR_SUCCESS) - { - // If we didn't find the file, try to open it using pseudo file name ("File - if (pFileEntry == NULL || (pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) - { - // Check the pseudo-file name ("File00000001.ext") - if ((bOpenByIndex = IsPseudoFileName(szFileName, &dwFileIndex)) == true) - { - // Get the file entry for the file - if (dwFileIndex < ha->dwFileTableSize) - { - pFileEntry = ha->pFileTable + dwFileIndex; - } - } - - // Still not found? - if (pFileEntry == NULL) - { - nError = ERROR_FILE_NOT_FOUND; - } - } - - // Perform some checks of invalid files - if (pFileEntry != NULL) - { - // MPQ protectors use insanely amount of fake files, often with very high size. - // We won't open any files whose compressed size is bigger than archive size - // If the file is not compressed, its size cannot be bigger than archive size - if ((pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) == 0 && (pFileEntry->dwFileSize > ha->FileSize)) - { - nError = ERROR_FILE_CORRUPT; - pFileEntry = NULL; - } - - // Ignore unknown loading flags (example: MPQ_2016_v1_WME4_4.w3x) -// if(pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS) -// { -// nError = ERROR_NOT_SUPPORTED; -// pFileEntry = NULL; -// } - } - } - - // Did the caller just wanted to know if the file exists? - if(nError == ERROR_SUCCESS && dwSearchScope != SFILE_OPEN_CHECK_EXISTS) - { - // Allocate file handle - hf = CreateFileHandle(ha, pFileEntry); - if(hf != NULL) - { - // Get the hash index for the file - if(ha->pHashTable != NULL && dwHashIndex == HASH_ENTRY_FREE) - dwHashIndex = FindHashIndex(ha, dwFileIndex); - if(dwHashIndex != HASH_ENTRY_FREE) - hf->pHashEntry = ha->pHashTable + dwHashIndex; - hf->dwHashIndex = dwHashIndex; - - // If the MPQ has sector CRC enabled, enable if for the file - if(ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC) - hf->bCheckSectorCRCs = true; - - // If we know the real file name, copy it to the file entry - if(bOpenByIndex == false) - { - // If there is no file name yet, allocate it - AllocateFileName(ha, pFileEntry, szFileName); - - // If the file is encrypted, we should detect the file key - if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) - { - hf->dwFileKey = DecryptFileKey(szFileName, - pFileEntry->ByteOffset, - pFileEntry->dwFileSize, - pFileEntry->dwFlags); - } - } - } - else - { - nError = ERROR_NOT_ENOUGH_MEMORY; - } - } - - // Give the file entry - if(PtrFile != NULL) - PtrFile[0] = hf; - - // Return error code - if(nError != ERROR_SUCCESS) - SetLastError(nError); - return (nError == ERROR_SUCCESS); -} - -//----------------------------------------------------------------------------- -// SFileHasFile -// -// hMpq - Handle of opened MPQ archive -// szFileName - Name of file to look for - -bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) -{ - return SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_CHECK_EXISTS, NULL); -} - -//----------------------------------------------------------------------------- -// bool WINAPI SFileCloseFile(HANDLE hFile); - -bool WINAPI SFileCloseFile(HANDLE hFile) -{ - TMPQFile * hf = (TMPQFile *)hFile; - - if(!IsValidFileHandle(hFile)) - { - SetLastError(ERROR_INVALID_HANDLE); - return false; - } - - // Free the structure - FreeFileHandle(hf); - return true; -} +/*****************************************************************************/ +/* SFileOpenFileEx.cpp Copyright (c) Ladislav Zezula 2003 */ +/*---------------------------------------------------------------------------*/ +/* Description : */ +/*---------------------------------------------------------------------------*/ +/* Date Ver Who Comment */ +/* -------- ---- --- ------- */ +/* xx.xx.99 1.00 Lad The first version of SFileOpenFileEx.cpp */ +/*****************************************************************************/ + +#define __STORMLIB_SELF__ +#include "StormLib.h" +#include "StormCommon.h" + +/*****************************************************************************/ +/* Local functions */ +/*****************************************************************************/ + +static DWORD FindHashIndex(TMPQArchive * ha, DWORD dwFileIndex) +{ + TMPQHash * pHashTableEnd; + TMPQHash * pHash; + DWORD dwFirstIndex = HASH_ENTRY_FREE; + + // Should only be called if the archive has hash table + assert(ha->pHashTable != NULL); + + // Multiple hash table entries can point to the file table entry. + // We need to search all of them + pHashTableEnd = ha->pHashTable + ha->pHeader->dwHashTableSize; + for(pHash = ha->pHashTable; pHash < pHashTableEnd; pHash++) + { + if(MPQ_BLOCK_INDEX(pHash) == dwFileIndex) + { + // Duplicate hash entry found + if(dwFirstIndex != HASH_ENTRY_FREE) + return HASH_ENTRY_FREE; + dwFirstIndex = (DWORD)(pHash - ha->pHashTable); + } + } + + // Return the hash table entry index + return dwFirstIndex; +} + +static const char * GetPatchFileName(TMPQArchive * ha, const char * szFileName, char * szBuffer) +{ + TMPQNamePrefix * pPrefix; + + // Are there patches in the current MPQ? + if(ha->dwFlags & MPQ_FLAG_PATCH) + { + // The patch prefix must be already known here + assert(ha->pPatchPrefix != NULL); + pPrefix = ha->pPatchPrefix; + + // The patch name for "OldWorld\\XXX\\YYY" is "Base\\XXX\YYY" + // We need to remove the "OldWorld\\" prefix + if(!_strnicmp(szFileName, "OldWorld\\", 9)) + szFileName += 9; + + // Create the file name from the known patch entry + memcpy(szBuffer, pPrefix->szPatchPrefix, pPrefix->nLength); + strcpy(szBuffer + pPrefix->nLength, szFileName); + szFileName = szBuffer; + } + + return szFileName; +} + +static bool OpenLocalFile(const char * szFileName, HANDLE * PtrFile) +{ + TFileStream * pStream; + TMPQFile * hf = NULL; + TCHAR szFileNameT[MAX_PATH]; + + // Convert the file name to UNICODE (if needed) + StringCopy(szFileNameT, _countof(szFileNameT), szFileName); + + // Open the file and create the TMPQFile structure + pStream = FileStream_OpenFile(szFileNameT, STREAM_FLAG_READ_ONLY); + if(pStream != NULL) + { + // Allocate and initialize file handle + hf = CreateFileHandle(NULL, NULL); + if(hf != NULL) + { + hf->pStream = pStream; + *PtrFile = hf; + return true; + } + else + { + FileStream_Close(pStream); + SetLastError(ERROR_NOT_ENOUGH_MEMORY); + } + } + *PtrFile = NULL; + return false; +} + +bool OpenPatchedFile(HANDLE hMpq, const char * szFileName, HANDLE * PtrFile) +{ + TMPQArchive * haBase = NULL; + TMPQArchive * ha = (TMPQArchive *)hMpq; + TFileEntry * pFileEntry; + TMPQFile * hfPatch; // Pointer to patch file + TMPQFile * hfBase = NULL; // Pointer to base open file + TMPQFile * hf = NULL; + HANDLE hPatchFile; + char szNameBuffer[MAX_PATH]; + + // First of all, find the latest archive where the file is in base version + // (i.e. where the original, unpatched version of the file exists) + while(ha != NULL) + { + // If the file is there, then we remember the archive + pFileEntry = GetFileEntryExact(ha, GetPatchFileName(ha, szFileName, szNameBuffer), 0, NULL); + if(pFileEntry != NULL && (pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0) + haBase = ha; + + // Move to the patch archive + ha = ha->haPatch; + } + + // If we couldn't find the base file in any of the patches, it doesn't exist + if((ha = haBase) != NULL) + { + // Now open the base file + if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, (HANDLE *)&hfBase)) + { + // The file must be a base file, i.e. without MPQ_FILE_PATCH_FILE + assert((hfBase->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) == 0); + hf = hfBase; + + // Now open all patches and attach them on top of the base file + for(ha = ha->haPatch; ha != NULL; ha = ha->haPatch) + { + // Prepare the file name with a correct prefix + if(SFileOpenFileEx((HANDLE)ha, GetPatchFileName(ha, szFileName, szNameBuffer), SFILE_OPEN_BASE_FILE, &hPatchFile)) + { + // Remember the new version + hfPatch = (TMPQFile *)hPatchFile; + + // We should not find patch file + assert((hfPatch->pFileEntry->dwFlags & MPQ_FILE_PATCH_FILE) != 0); + + // Attach the patch to the base file + hf->hfPatch = hfPatch; + hf = hfPatch; + } + } + } + } + else + { + SetLastError(ERROR_FILE_NOT_FOUND); + } + + // Give the updated base MPQ + if(PtrFile != NULL) + *PtrFile = (HANDLE)hfBase; + return (hfBase != NULL); +} + +/*****************************************************************************/ +/* Public functions */ +/*****************************************************************************/ + +//----------------------------------------------------------------------------- +// SFileEnumLocales enums all locale versions within MPQ. +// Functions fills all available language identifiers on a file into the buffer +// pointed by plcLocales. There must be enough entries to copy the localed, +// otherwise the function returns ERROR_INSUFFICIENT_BUFFER. + +int WINAPI SFileEnumLocales( + HANDLE hMpq, + const char * szFileName, + LCID * PtrLocales, + LPDWORD PtrMaxLocales, + DWORD dwSearchScope) +{ + TMPQArchive * ha = (TMPQArchive *)hMpq; + TMPQHash * pFirstHash; + TMPQHash * pHash; + DWORD dwFileIndex = 0; + DWORD dwMaxLocales; + DWORD dwLocales = 0; + + // Test the parameters + if(!IsValidMpqHandle(hMpq)) + return ERROR_INVALID_HANDLE; + if(szFileName == NULL || *szFileName == 0) + return ERROR_INVALID_PARAMETER; + if(ha->pHashTable == NULL) + return ERROR_NOT_SUPPORTED; + if(PtrMaxLocales == NULL) + return ERROR_INVALID_PARAMETER; + if(IsPseudoFileName(szFileName, &dwFileIndex)) + return ERROR_INVALID_PARAMETER; + + // Keep compiler happy + dwMaxLocales = PtrMaxLocales[0]; + dwSearchScope = dwSearchScope; + + // Parse all files with that name + pFirstHash = pHash = GetFirstHashEntry(ha, szFileName); + while(pHash != NULL) + { + // Put the locales to the buffer + if(PtrLocales != NULL && dwLocales < dwMaxLocales) + *PtrLocales++ = pHash->lcLocale; + dwLocales++; + + // Get the next locale + pHash = GetNextHashEntry(ha, pFirstHash, pHash); + } + + // Give the caller the number of locales and return + PtrMaxLocales[0] = dwLocales; + return (dwLocales <= dwMaxLocales) ? ERROR_SUCCESS : ERROR_INSUFFICIENT_BUFFER; +} + +//----------------------------------------------------------------------------- +// SFileOpenFileEx +// +// hMpq - Handle of opened MPQ archive +// szFileName - Name of file to open +// dwSearchScope - Where to search +// PtrFile - Pointer to store opened file handle + +bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char * szFileName, DWORD dwSearchScope, HANDLE * PtrFile) +{ + TMPQArchive * ha = IsValidMpqHandle(hMpq); + TFileEntry * pFileEntry = NULL; + TMPQFile * hf = NULL; + DWORD dwHashIndex = HASH_ENTRY_FREE; + DWORD dwFileIndex = 0; + bool bOpenByIndex = false; + int nError = ERROR_SUCCESS; + + // Don't accept NULL pointer to file handle + if(szFileName == NULL || *szFileName == 0) + nError = ERROR_INVALID_PARAMETER; + + // When opening a file from MPQ, the handle must be valid + if(dwSearchScope != SFILE_OPEN_LOCAL_FILE && ha == NULL) + nError = ERROR_INVALID_HANDLE; + + // When not checking for existence, the pointer to file handle must be valid + if(dwSearchScope != SFILE_OPEN_CHECK_EXISTS && PtrFile == NULL) + nError = ERROR_INVALID_PARAMETER; + + // Prepare the file opening + if(nError == ERROR_SUCCESS) + { + switch(dwSearchScope) + { + case SFILE_OPEN_FROM_MPQ: + case SFILE_OPEN_BASE_FILE: + case SFILE_OPEN_CHECK_EXISTS: + + // If this MPQ has no patches, open the file from this MPQ directly + if(ha->haPatch == NULL || dwSearchScope == SFILE_OPEN_BASE_FILE) + { + pFileEntry = GetFileEntryLocale2(ha, szFileName, g_lcFileLocale, &dwHashIndex); + } + + // If this MPQ is a patched archive, open the file as patched + else + { + return OpenPatchedFile(hMpq, szFileName, PtrFile); + } + break; + + case SFILE_OPEN_ANY_LOCALE: + + // This open option is reserved for opening MPQ internal listfile. + // No argument validation. Tries to open file with neutral locale first, + // then any other available. + pFileEntry = GetFileEntryLocale2(ha, szFileName, 0, &dwHashIndex); + break; + + case SFILE_OPEN_LOCAL_FILE: + + // Open a local file + return OpenLocalFile(szFileName, PtrFile); + + default: + + // Don't accept any other value + nError = ERROR_INVALID_PARAMETER; + break; + } + } + + // Check whether the file really exists in the MPQ + if(nError == ERROR_SUCCESS) + { + // If we didn't find the file, try to open it using pseudo file name ("File + if (pFileEntry == NULL || (pFileEntry->dwFlags & MPQ_FILE_EXISTS) == 0) + { + // Check the pseudo-file name ("File00000001.ext") + if ((bOpenByIndex = IsPseudoFileName(szFileName, &dwFileIndex)) == true) + { + // Get the file entry for the file + if (dwFileIndex < ha->dwFileTableSize) + { + pFileEntry = ha->pFileTable + dwFileIndex; + } + } + + // Still not found? + if (pFileEntry == NULL) + { + nError = ERROR_FILE_NOT_FOUND; + } + } + + // Perform some checks of invalid files + if (pFileEntry != NULL) + { + // MPQ protectors use insanely amount of fake files, often with very high size. + // We won't open any files whose compressed size is bigger than archive size + // If the file is not compressed, its size cannot be bigger than archive size + if ((pFileEntry->dwFlags & MPQ_FILE_COMPRESS_MASK) == 0 && (pFileEntry->dwFileSize > ha->FileSize)) + { + nError = ERROR_FILE_CORRUPT; + pFileEntry = NULL; + } + + // Ignore unknown loading flags (example: MPQ_2016_v1_WME4_4.w3x) +// if(pFileEntry->dwFlags & ~MPQ_FILE_VALID_FLAGS) +// { +// nError = ERROR_NOT_SUPPORTED; +// pFileEntry = NULL; +// } + } + } + + // Did the caller just wanted to know if the file exists? + if(nError == ERROR_SUCCESS && dwSearchScope != SFILE_OPEN_CHECK_EXISTS) + { + // Allocate file handle + hf = CreateFileHandle(ha, pFileEntry); + if(hf != NULL) + { + // Get the hash index for the file + if(ha->pHashTable != NULL && dwHashIndex == HASH_ENTRY_FREE) + dwHashIndex = FindHashIndex(ha, dwFileIndex); + if(dwHashIndex != HASH_ENTRY_FREE) + hf->pHashEntry = ha->pHashTable + dwHashIndex; + hf->dwHashIndex = dwHashIndex; + + // If the MPQ has sector CRC enabled, enable if for the file + if(ha->dwFlags & MPQ_FLAG_CHECK_SECTOR_CRC) + hf->bCheckSectorCRCs = true; + + // If we know the real file name, copy it to the file entry + if(bOpenByIndex == false) + { + // If there is no file name yet, allocate it + AllocateFileName(ha, pFileEntry, szFileName); + + // If the file is encrypted, we should detect the file key + if(pFileEntry->dwFlags & MPQ_FILE_ENCRYPTED) + { + hf->dwFileKey = DecryptFileKey(szFileName, + pFileEntry->ByteOffset, + pFileEntry->dwFileSize, + pFileEntry->dwFlags); + } + } + } + else + { + nError = ERROR_NOT_ENOUGH_MEMORY; + } + } + + // Give the file entry + if(PtrFile != NULL) + PtrFile[0] = hf; + + // Return error code + if(nError != ERROR_SUCCESS) + SetLastError(nError); + return (nError == ERROR_SUCCESS); +} + +//----------------------------------------------------------------------------- +// SFileHasFile +// +// hMpq - Handle of opened MPQ archive +// szFileName - Name of file to look for + +bool WINAPI SFileHasFile(HANDLE hMpq, const char * szFileName) +{ + return SFileOpenFileEx(hMpq, szFileName, SFILE_OPEN_CHECK_EXISTS, NULL); +} + +//----------------------------------------------------------------------------- +// bool WINAPI SFileCloseFile(HANDLE hFile); + +bool WINAPI SFileCloseFile(HANDLE hFile) +{ + TMPQFile * hf = (TMPQFile *)hFile; + + if(!IsValidFileHandle(hFile)) + { + SetLastError(ERROR_INVALID_HANDLE); + return false; + } + + // Free the structure + FreeFileHandle(hf); + return true; +} diff --git a/CMake/amiga_defs.cmake b/CMake/amiga_defs.cmake index a4ea892e..5b044fff 100644 --- a/CMake/amiga_defs.cmake +++ b/CMake/amiga_defs.cmake @@ -5,8 +5,13 @@ set(USE_SDL1 ON) set(SDL1_VIDEO_MODE_BPP 8) # Enable exception suport as they are used in dvlnet code set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexceptions") + +set(DEVILUTIONX_SYSTEM_BZIP2 OFF) find_package(ZLIB REQUIRED) # Do not warn about unknown attributes, such as [[nodiscard]]. # As this build uses an older compiler, there are lots of them. set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-attributes") + +# `fseeko` fails to link on Amiga. +add_definitions(-Dfseeko=fseek) diff --git a/CMake/ctr/modules/FindZLIB.cmake b/CMake/ctr/modules/FindZLIB.cmake index 49fb4fe1..b97e27b7 100644 --- a/CMake/ctr/modules/FindZLIB.cmake +++ b/CMake/ctr/modules/FindZLIB.cmake @@ -54,3 +54,5 @@ set(ZLIB_PROCESS_LIBS ZLIB_LIBRARY) libfind_process(ZLIB) try_add_imported_target(ZLIB) + +add_library(ZLIB::ZLIB ALIAS 3ds::zlib) diff --git a/CMakeLists.txt b/CMakeLists.txt index fbfca1e4..da83aa6c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -368,6 +368,8 @@ add_library(StormLib STATIC 3rdParty/StormLib/src/SFileOpenFileEx.cpp 3rdParty/StormLib/src/SFileReadFile.cpp) +add_subdirectory(3rdParty/libmpq) + if(WIN32) # Enable Unicode for StormLib wchar_t* file APIs target_compile_definitions(StormLib PRIVATE -DUNICODE -D_UNICODE) @@ -398,6 +400,7 @@ set(libdevilutionx_SRCS Source/encrypt.cpp Source/engine.cpp Source/error.cpp + Source/engine/game_assets.cpp Source/gamemenu.cpp Source/gendung.cpp Source/gmenu.cpp @@ -477,8 +480,11 @@ set(libdevilutionx_SRCS Source/utils/display.cpp Source/utils/file_util.cpp Source/utils/language.cpp + Source/utils/mpq.cpp + Source/utils/mpq_sdl_rwops.cpp Source/utils/paths.cpp Source/utils/sdl_bilinear_scale.cpp + Source/utils/sdl_rwops_file_wrapper.cpp Source/utils/sdl_thread.cpp Source/DiabloUI/art.cpp Source/DiabloUI/art_draw.cpp @@ -508,10 +514,7 @@ set(libdevilutionx_SRCS Source/dvlnet/frame_queue.cpp Source/dvlnet/loopback.cpp Source/dvlnet/packet.cpp - Source/storm/storm.cpp - Source/storm/storm_file_wrapper.cpp Source/storm/storm_net.cpp - Source/storm/storm_sdl_rw.cpp Source/storm/storm_svid.cpp Source/miniwin/misc_msg.cpp) @@ -877,6 +880,7 @@ endif() target_link_libraries(libdevilutionx PUBLIC PKWare StormLib + libmpq smacker simpleini) diff --git a/Source/DiabloUI/art.cpp b/Source/DiabloUI/art.cpp index d8782651..6a6c07d1 100644 --- a/Source/DiabloUI/art.cpp +++ b/Source/DiabloUI/art.cpp @@ -4,12 +4,12 @@ #include #include -#include "storm/storm.h" -#include "storm/storm_sdl_rw.h" +#include "engine/game_assets.hpp" #include "utils/display.h" #include "utils/log.hpp" #include "utils/sdl_compat.h" #include "utils/sdl_wrap.h" +#include "utils/pcx.hpp" namespace devilution { namespace { @@ -20,7 +20,7 @@ constexpr unsigned PcxPaletteSize = 1 + NumPaletteColors * 3; bool LoadPcxMeta(SDL_RWops *handle, int &width, int &height, std::uint8_t &bpp) { PCXHeader pcxhdr; - if (!SDL_RWread(handle, &pcxhdr, PcxHeaderSize, 1)) { + if (SDL_RWread(handle, &pcxhdr, PcxHeaderSize, 1) == 0) { return false; } width = SDL_SwapLE16(pcxhdr.Xmax) - SDL_SwapLE16(pcxhdr.Xmin) + 1; @@ -30,7 +30,7 @@ bool LoadPcxMeta(SDL_RWops *handle, int &width, int &height, std::uint8_t &bpp) } bool LoadPcxPixelsAndPalette(SDL_RWops *handle, int width, int height, std::uint8_t bpp, - BYTE *buffer, std::size_t bufferPitch, SDL_Color *palette) + uint8_t *buffer, std::size_t bufferPitch, SDL_Color *palette) { const bool has256ColorPalette = palette != nullptr && bpp == 8; std::uint32_t pixelDataSize = SDL_RWsize(handle); @@ -41,13 +41,13 @@ bool LoadPcxPixelsAndPalette(SDL_RWops *handle, int width, int height, std::uint // We read 1 extra byte because it delimits the palette. const size_t readSize = pixelDataSize + (has256ColorPalette ? PcxPaletteSize : 0); - std::unique_ptr fileBuffer { new BYTE[readSize] }; - if (!SDL_RWread(handle, fileBuffer.get(), readSize, 1)) { + std::unique_ptr fileBuffer { new uint8_t[readSize] }; + if (SDL_RWread(handle, fileBuffer.get(), readSize, 1) == 0) { return false; } const unsigned xSkip = bufferPitch - width; const unsigned srcSkip = width % 2; - BYTE *dataPtr = fileBuffer.get(); + uint8_t *dataPtr = fileBuffer.get(); for (int j = 0; j < height; j++) { for (int x = 0; x < width;) { constexpr std::uint8_t PcxMaxSinglePixel = 0xBF; @@ -112,13 +112,13 @@ void LoadArt(const char *pszFile, Art *art, int frames, SDL_Color *pPalette, con int width; int height; std::uint8_t bpp; - SDL_RWops *handle = SFileOpenRw(pszFile); + SDL_RWops *handle = OpenAsset(pszFile); if (handle == nullptr) { return; } if (!LoadPcxMeta(handle, width, height, bpp)) { - Log("LoadArt(\"{}\"): LoadPcxMeta failed with code {}", pszFile, SErrGetLastError()); + Log("LoadArt(\"{}\"): LoadPcxMeta failed with code {}", pszFile, SDL_GetError()); SDL_RWclose(handle); return; } @@ -126,7 +126,7 @@ void LoadArt(const char *pszFile, Art *art, int frames, SDL_Color *pPalette, con SDLSurfaceUniquePtr artSurface = SDLWrap::CreateRGBSurfaceWithFormat(SDL_SWSURFACE, width, height, bpp, GetPcxSdlPixelFormat(bpp)); if (!LoadPcxPixelsAndPalette(handle, width, height, bpp, static_cast(artSurface->pixels), artSurface->pitch, pPalette)) { - Log("LoadArt(\"{}\"): LoadPcxPixelsAndPalette failed with code {}", pszFile, SErrGetLastError()); + Log("LoadArt(\"{}\"): LoadPcxPixelsAndPalette failed with code {}", pszFile, SDL_GetError()); SDL_RWclose(handle); return; } diff --git a/Source/DiabloUI/art.h b/Source/DiabloUI/art.h index b513965a..722ad148 100644 --- a/Source/DiabloUI/art.h +++ b/Source/DiabloUI/art.h @@ -1,5 +1,6 @@ #pragma once +#include #include #include "utils/sdl_ptrs.h" diff --git a/Source/DiabloUI/diabloui.cpp b/Source/DiabloUI/diabloui.cpp index 5caa8764..bf35d27d 100644 --- a/Source/DiabloUI/diabloui.cpp +++ b/Source/DiabloUI/diabloui.cpp @@ -18,7 +18,6 @@ #include "engine/render/text_render.hpp" #include "hwcursor.hpp" #include "palette.h" -#include "storm/storm.h" #include "utils/display.h" #include "utils/log.hpp" #include "utils/sdl_compat.h" diff --git a/Source/DiabloUI/extrasmenu.cpp b/Source/DiabloUI/extrasmenu.cpp index a5c2d88b..846d75d4 100644 --- a/Source/DiabloUI/extrasmenu.cpp +++ b/Source/DiabloUI/extrasmenu.cpp @@ -37,9 +37,9 @@ _mainmenu_selections UiExtrasMenu() UiAddBackground(&vecDialog); UiAddLogo(&vecDialog); - if (diabdat_mpq != nullptr && hellfire_mpq != nullptr) + if (diabdat_mpq && hellfire_mpq) vecDialogItems.push_back(std::make_unique(gbIsHellfire ? _("Switch to Diablo") : _("Switch to Hellfire"), MAINMENU_SWITCHGAME)); - if (diabdat_mpq != nullptr) + if (diabdat_mpq) vecDialogItems.push_back(std::make_unique(gbIsSpawn ? _("Switch to Fullgame") : _("Switch to Shareware"), MAINMENU_TOGGLESPAWN)); vecDialogItems.push_back(std::make_unique(_("Replay Intro"), MAINMENU_REPLAY_INTRO)); vecDialogItems.push_back(std::make_unique(_("Support"), MAINMENU_SHOW_SUPPORT)); diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index 3f161ca3..67ec4650 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/Source/DiabloUI/mainmenu.cpp @@ -92,7 +92,7 @@ bool UiMainMenuDialog(const char *name, _mainmenu_selections *pdwResult, void (* while (MainMenuResult == MAINMENU_NONE) { UiClearScreen(); UiPollAndRender(); - if (SDL_GetTicks() >= dwAttractTicks && (diabdat_mpq != nullptr || hellfire_mpq != nullptr)) { + if (SDL_GetTicks() >= dwAttractTicks && (diabdat_mpq || hellfire_mpq)) { MainMenuResult = MAINMENU_ATTRACT_MODE; } } diff --git a/Source/DiabloUI/selconn.cpp b/Source/DiabloUI/selconn.cpp index def5cb1a..e05b7c5c 100644 --- a/Source/DiabloUI/selconn.cpp +++ b/Source/DiabloUI/selconn.cpp @@ -2,7 +2,7 @@ #include "DiabloUI/diabloui.h" #include "stores.h" -#include "storm/storm.h" +#include "storm/storm_net.hpp" #include "utils/language.h" namespace devilution { diff --git a/Source/DiabloUI/selgame.cpp b/Source/DiabloUI/selgame.cpp index 8831aa3e..bfb783c4 100644 --- a/Source/DiabloUI/selgame.cpp +++ b/Source/DiabloUI/selgame.cpp @@ -10,7 +10,7 @@ #include "control.h" #include "menu.h" #include "options.h" -#include "storm/storm.h" +#include "storm/storm_net.hpp" #include "utils/language.h" namespace devilution { diff --git a/Source/DiabloUI/selhero.cpp b/Source/DiabloUI/selhero.cpp index 830cf4c1..74e29998 100644 --- a/Source/DiabloUI/selhero.cpp +++ b/Source/DiabloUI/selhero.cpp @@ -258,7 +258,7 @@ bool ShouldPrefillHeroName() void SelheroClassSelectorSelect(int value) { auto hClass = static_cast(vecSelHeroDlgItems[value]->m_value); - if (gbSpawned && (hClass == HeroClass::Rogue || hClass == HeroClass::Sorcerer || (hClass == HeroClass::Bard && hfbard_mpq == nullptr))) { + if (gbSpawned && (hClass == HeroClass::Rogue || hClass == HeroClass::Sorcerer || (hClass == HeroClass::Bard && !hfbard_mpq))) { ArtBackground.Unload(); UiSelOkDialog(nullptr, _("The Rogue and Sorcerer are only available in the full retail version of Diablo. Visit https://www.gog.com/game/diablo to purchase."), false); LoadBackgroundArt("ui_art\\selhero.pcx"); diff --git a/Source/appfat.cpp b/Source/appfat.cpp index dd6fe297..690b4c7d 100644 --- a/Source/appfat.cpp +++ b/Source/appfat.cpp @@ -9,7 +9,8 @@ #include #include "diablo.h" -#include "storm/storm.h" +#include "storm/storm_net.hpp" +#include "multi.h" #include "utils/language.h" #include "utils/sdl_thread.h" #include "utils/ui_fwd.h" diff --git a/Source/capture.cpp b/Source/capture.cpp index 7b617994..52245463 100644 --- a/Source/capture.cpp +++ b/Source/capture.cpp @@ -9,10 +9,10 @@ #include "DiabloUI/diabloui.h" #include "dx.h" #include "palette.h" -#include "storm/storm.h" #include "utils/file_util.h" #include "utils/log.hpp" #include "utils/paths.h" +#include "utils/pcx.hpp" #include "utils/ui_fwd.h" namespace devilution { diff --git a/Source/controls/touch/renderers.h b/Source/controls/touch/renderers.h index 1a4e47ee..42ecb270 100644 --- a/Source/controls/touch/renderers.h +++ b/Source/controls/touch/renderers.h @@ -2,11 +2,15 @@ #if defined(VIRTUAL_GAMEPAD) && !defined(USE_SDL1) +#include + +#include "DiabloUI/art.h" #include "controls/plrctrls.h" #include "controls/touch/gamepad.h" #include "engine/surface.hpp" #include "utils/png.h" #include "utils/sdl_ptrs.h" +#include "utils/stdcompat/optional.hpp" namespace devilution { diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 5ba52cde..8b096087 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -58,7 +58,7 @@ #include "setmaps.h" #include "sound.h" #include "stores.h" -#include "storm/storm.h" +#include "storm/storm_net.hpp" #include "themes.h" #include "town.h" #include "towners.h" diff --git a/Source/dvlnet/abstract_net.cpp b/Source/dvlnet/abstract_net.cpp index a68b1aee..d12dbc28 100644 --- a/Source/dvlnet/abstract_net.cpp +++ b/Source/dvlnet/abstract_net.cpp @@ -12,7 +12,6 @@ #endif #endif #include "dvlnet/loopback.h" -#include "storm/storm.h" namespace devilution { namespace net { diff --git a/Source/dvlnet/abstract_net.h b/Source/dvlnet/abstract_net.h index 996fd4c6..8a503ff7 100644 --- a/Source/dvlnet/abstract_net.h +++ b/Source/dvlnet/abstract_net.h @@ -5,7 +5,7 @@ #include #include -#include "storm/storm.h" +#include "storm/storm_net.hpp" namespace devilution { namespace net { diff --git a/Source/dvlnet/base.h b/Source/dvlnet/base.h index 895a06c7..51c1383b 100644 --- a/Source/dvlnet/base.h +++ b/Source/dvlnet/base.h @@ -8,6 +8,8 @@ #include "dvlnet/abstract_net.h" #include "dvlnet/packet.h" +#include "multi.h" +#include "storm/storm_net.hpp" namespace devilution { namespace net { diff --git a/Source/dvlnet/cdwrap.h b/Source/dvlnet/cdwrap.h index 5919dbdd..bb3a5445 100644 --- a/Source/dvlnet/cdwrap.h +++ b/Source/dvlnet/cdwrap.h @@ -7,6 +7,8 @@ #include #include "dvlnet/abstract_net.h" +#include "storm/storm_net.hpp" +#include "utils/stdcompat/optional.hpp" namespace devilution { namespace net { diff --git a/Source/dvlnet/loopback.cpp b/Source/dvlnet/loopback.cpp index 5549bea5..df8b3251 100644 --- a/Source/dvlnet/loopback.cpp +++ b/Source/dvlnet/loopback.cpp @@ -1,4 +1,6 @@ #include "dvlnet/loopback.h" + +#include "multi.h" #include "utils/language.h" #include "utils/stubs.h" diff --git a/Source/dvlnet/packet.cpp b/Source/dvlnet/packet.cpp index d5d56600..9c6ac7d0 100644 --- a/Source/dvlnet/packet.cpp +++ b/Source/dvlnet/packet.cpp @@ -5,6 +5,8 @@ #include #endif +#include + #include "dvlnet/packet.h" namespace devilution { diff --git a/Source/dvlnet/tcp_server.h b/Source/dvlnet/tcp_server.h index d9ce7c9f..f37103c6 100644 --- a/Source/dvlnet/tcp_server.h +++ b/Source/dvlnet/tcp_server.h @@ -11,6 +11,7 @@ #include "dvlnet/packet.h" #include "dvlnet/abstract_net.h" #include "dvlnet/frame_queue.h" +#include "multi.h" namespace devilution { namespace net { diff --git a/Source/dx.cpp b/Source/dx.cpp index 6be844ee..d498e24e 100644 --- a/Source/dx.cpp +++ b/Source/dx.cpp @@ -10,7 +10,6 @@ #include "controls/touch/renderers.h" #include "engine.h" #include "options.h" -#include "storm/storm.h" #include "utils/display.h" #include "utils/log.hpp" #include "utils/sdl_mutex.h" diff --git a/Source/engine/game_assets.cpp b/Source/engine/game_assets.cpp new file mode 100644 index 00000000..82aab57f --- /dev/null +++ b/Source/engine/game_assets.cpp @@ -0,0 +1,76 @@ +#include "engine/game_assets.hpp" + +#include +#include +#include + +#include "init.h" +#include "utils/file_util.h" +#include "utils/log.hpp" +#include "utils/mpq_sdl_rwops.hpp" +#include "utils/paths.h" + +namespace devilution { + +namespace { + +bool OpenMpqFile(const char *filename, MpqArchive **archive, uint32_t *fileNumber) +{ + const MpqArchive::FileHash fileHash = MpqArchive::CalculateFileHash(filename); + const auto at = [=](std::optional &src) -> bool { + if (src && src->GetFileNumber(fileHash, *fileNumber)) { + *archive = &(*src); + return true; + } + return false; + }; + + return at(font_mpq) || at(lang_mpq) || at(devilutionx_mpq) + || (gbIsHellfire && (at(hfvoice_mpq) || at(hfmusic_mpq) || at(hfbarb_mpq) || at(hfbard_mpq) || at(hfmonk_mpq) || at(hellfire_mpq))) || at(spawn_mpq) || at(diabdat_mpq); +} + +} // namespace + +SDL_RWops *OpenAsset(const char *filename, bool threadsafe) +{ + std::string relativePath = filename; +#ifndef _WIN32 + std::replace(relativePath.begin(), relativePath.end(), '\\', '/'); +#endif + + if (relativePath[0] == '/') + return SDL_RWFromFile(relativePath.c_str(), "rb"); + + // Files next to the MPQ archives override MPQ contents. + SDL_RWops *rwops; + if (paths::MpqDir()) { + const std::string path = *paths::MpqDir() + relativePath; + // Avoid spamming DEBUG messages if the file does not exist. + if ((FileExists(path.c_str())) && (rwops = SDL_RWFromFile(path.c_str(), "rb")) != nullptr) { + LogVerbose("Loaded MPQ file override: {}", path); + return rwops; + } + } + + // Load from all the MPQ archives. + MpqArchive *archive; + uint32_t fileNumber; + if (OpenMpqFile(filename, &archive, &fileNumber)) + return SDL_RWops_FromMpqFile(*archive, fileNumber, filename, threadsafe); + + // Load from the `/assets` directory next to the devilutionx binary. + const std::string path = paths::AssetsPath() + relativePath; + if ((rwops = SDL_RWFromFile(path.c_str(), "rb")) != nullptr) + return rwops; + +#ifdef __ANDROID__ + // On Android, fall back to the APK's assets. + // This is handled by SDL when we pass a relative path. + if (!paths::AssetsPath().empty() && (rwops = SDL_RWFromFile(relativePath.c_str(), "rb"))) + return rwops; +#endif + + return nullptr; +} + +} // namespace devilution diff --git a/Source/storm/storm_sdl_rw.h b/Source/engine/game_assets.hpp similarity index 72% rename from Source/storm/storm_sdl_rw.h rename to Source/engine/game_assets.hpp index e3395609..e6df6269 100644 --- a/Source/storm/storm_sdl_rw.h +++ b/Source/engine/game_assets.hpp @@ -9,6 +9,6 @@ namespace devilution { * * Closes the handle when it gets closed. */ -SDL_RWops *SFileOpenRw(const char *filename); +SDL_RWops *OpenAsset(const char *filename, bool threadsafe = false); } // namespace devilution diff --git a/Source/engine/load_file.hpp b/Source/engine/load_file.hpp index 918e72fd..be90003a 100644 --- a/Source/engine/load_file.hpp +++ b/Source/engine/load_file.hpp @@ -7,8 +7,7 @@ #include "appfat.h" #include "diablo.h" -#include "storm/storm.h" -#include "storm/storm_sdl_rw.h" +#include "engine/game_assets.hpp" #include "utils/stdcompat/cstddef.hpp" namespace devilution { @@ -17,15 +16,10 @@ class SFile { public: explicit SFile(const char *path) { - handle_ = SFileOpenRw(path); + handle_ = OpenAsset(path); if (handle_ == nullptr) { if (!gbQuietMode) { - const std::uint32_t code = SErrGetLastError(); - if (code == STORM_ERROR_FILE_NOT_FOUND) { - app_fatal("Failed to open file:\n%s\n\nFile not found", path); - } else { - app_fatal("Failed to open file:\n%s\n\nError Code: %u", path, code); - } + app_fatal("Failed to open file:\n%s\n\n%s", path, SDL_GetError()); } } } diff --git a/Source/engine/render/text_render.cpp b/Source/engine/render/text_render.cpp index fac8e917..85b51e77 100644 --- a/Source/engine/render/text_render.cpp +++ b/Source/engine/render/text_render.cpp @@ -130,7 +130,7 @@ std::array *LoadFontKerning(GameFontTables size, uint16_t row) if (IsFullWidth(row)) { kerning->fill(FontFullwidth[size]); } else { - SDL_RWops *handle = SFileOpenRw(path); + SDL_RWops *handle = OpenAsset(path); if (handle != nullptr) { SDL_RWread(handle, kerning, 256, 1); SDL_RWclose(handle); diff --git a/Source/init.cpp b/Source/init.cpp index c52dc0a3..3ff40db2 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -16,10 +16,10 @@ #include "dx.h" #include "options.h" #include "pfile.h" -#include "storm/storm.h" -#include "storm/storm_sdl_rw.h" +#include "engine/game_assets.hpp" #include "utils/language.h" #include "utils/log.hpp" +#include "utils/mpq.hpp" #include "utils/paths.h" #include "utils/ui_fwd.h" @@ -33,13 +33,13 @@ namespace devilution { /** True if the game is the current active window */ bool gbActive; /** A handle to an hellfire.mpq archive. */ -HANDLE hellfire_mpq; +std::optional hellfire_mpq; /** The current input handler function */ WNDPROC CurrentProc; /** A handle to the spawn.mpq archive. */ -HANDLE spawn_mpq; +std::optional spawn_mpq; /** A handle to the diabdat.mpq archive. */ -HANDLE diabdat_mpq; +std::optional diabdat_mpq; /** Indicate if we only have access to demo data */ bool gbIsSpawn; /** Indicate if we have loaded the Hellfire expansion data */ @@ -48,37 +48,38 @@ bool gbIsHellfire; bool gbVanilla; /** Whether the Hellfire mode is required (forced). */ bool forceHellfire; -HANDLE hfmonk_mpq; -HANDLE hfbard_mpq; -HANDLE hfbarb_mpq; -HANDLE hfmusic_mpq; -HANDLE hfvoice_mpq; -HANDLE devilutionx_mpq; -HANDLE lang_mpq; -HANDLE font_mpq; +std::optional hfmonk_mpq; +std::optional hfbard_mpq; +std::optional hfbarb_mpq; +std::optional hfmusic_mpq; +std::optional hfvoice_mpq; +std::optional devilutionx_mpq; +std::optional lang_mpq; +std::optional font_mpq; namespace { -HANDLE LoadMPQ(const std::vector &paths, const char *mpqName) +std::optional LoadMPQ(const std::vector &paths, const char *mpqName) { - HANDLE archive; + std::optional archive; std::string mpqAbsPath; + std::int32_t error = 0; for (const auto &path : paths) { mpqAbsPath = path + mpqName; - if (SFileOpenArchive(mpqAbsPath.c_str(), 0, MPQ_OPEN_READ_ONLY, &archive)) { + if ((archive = MpqArchive::Open(mpqAbsPath.c_str(), error))) { LogVerbose(" Found: {} in {}", mpqName, path); paths::SetMpqDir(path); return archive; } - if (SErrGetLastError() != STORM_ERROR_FILE_NOT_FOUND) { - LogError("Open error {}: {}", SErrGetLastError(), mpqAbsPath); + if (error != 0) { + LogError("Error {}: {}", MpqArchive::ErrorMessage(error), mpqAbsPath); } } - if (SErrGetLastError() == STORM_ERROR_FILE_NOT_FOUND) { + if (error == 0) { LogVerbose("Missing: {}", mpqName); } - return nullptr; + return std::nullopt; } } // namespace @@ -89,50 +90,17 @@ void init_cleanup() pfile_write_hero(/*writeGameData=*/false, /*clearTables=*/true); } - if (spawn_mpq != nullptr) { - SFileCloseArchive(spawn_mpq); - spawn_mpq = nullptr; - } - if (diabdat_mpq != nullptr) { - SFileCloseArchive(diabdat_mpq); - diabdat_mpq = nullptr; - } - if (hellfire_mpq != nullptr) { - SFileCloseArchive(hellfire_mpq); - hellfire_mpq = nullptr; - } - if (hfmonk_mpq != nullptr) { - SFileCloseArchive(hfmonk_mpq); - hfmonk_mpq = nullptr; - } - if (hfbard_mpq != nullptr) { - SFileCloseArchive(hfbard_mpq); - hfbard_mpq = nullptr; - } - if (hfbarb_mpq != nullptr) { - SFileCloseArchive(hfbarb_mpq); - hfbarb_mpq = nullptr; - } - if (hfmusic_mpq != nullptr) { - SFileCloseArchive(hfmusic_mpq); - hfmusic_mpq = nullptr; - } - if (hfvoice_mpq != nullptr) { - SFileCloseArchive(hfvoice_mpq); - hfvoice_mpq = nullptr; - } - if (lang_mpq != nullptr) { - SFileCloseArchive(lang_mpq); - lang_mpq = nullptr; - } - if (font_mpq != nullptr) { - SFileCloseArchive(font_mpq); - font_mpq = nullptr; - } - if (devilutionx_mpq != nullptr) { - SFileCloseArchive(devilutionx_mpq); - devilutionx_mpq = nullptr; - } + spawn_mpq = std::nullopt; + diabdat_mpq = std::nullopt; + hellfire_mpq = std::nullopt; + hfmonk_mpq = std::nullopt; + hfbard_mpq = std::nullopt; + hfbarb_mpq = std::nullopt; + hfmusic_mpq = std::nullopt; + hfvoice_mpq = std::nullopt; + lang_mpq = std::nullopt; + font_mpq = std::nullopt; + devilutionx_mpq = std::nullopt; NetClose(); } @@ -191,38 +159,40 @@ void init_archives() } diabdat_mpq = LoadMPQ(paths, "DIABDAT.MPQ"); - if (diabdat_mpq == nullptr) { + if (!diabdat_mpq) { // DIABDAT.MPQ is uppercase on the original CD and the GOG version. diabdat_mpq = LoadMPQ(paths, "diabdat.mpq"); } - if (diabdat_mpq == nullptr) { + if (!diabdat_mpq) { spawn_mpq = LoadMPQ(paths, "spawn.mpq"); - if (spawn_mpq != nullptr) + if (spawn_mpq) gbIsSpawn = true; } - SDL_RWops *handle = SFileOpenRw("ui_art\\title.pcx"); - if (handle == nullptr) + SDL_RWops *handle = OpenAsset("ui_art\\title.pcx"); + if (handle == nullptr) { + LogError("{}", SDL_GetError()); InsertCDDlg(_("diabdat.mpq or spawn.mpq")); + } SDL_RWclose(handle); hellfire_mpq = LoadMPQ(paths, "hellfire.mpq"); - if (hellfire_mpq != nullptr) + if (hellfire_mpq) gbIsHellfire = true; - if (forceHellfire && hellfire_mpq == nullptr) + if (forceHellfire && !hellfire_mpq) InsertCDDlg("hellfire.mpq"); hfmonk_mpq = LoadMPQ(paths, "hfmonk.mpq"); hfbard_mpq = LoadMPQ(paths, "hfbard.mpq"); - if (hfbard_mpq != nullptr) + if (hfbard_mpq) gbBard = true; hfbarb_mpq = LoadMPQ(paths, "hfbarb.mpq"); - if (hfbarb_mpq != nullptr) + if (hfbarb_mpq) gbBarbarian = true; hfmusic_mpq = LoadMPQ(paths, "hfmusic.mpq"); hfvoice_mpq = LoadMPQ(paths, "hfvoice.mpq"); - if (gbIsHellfire && (hfmonk_mpq == nullptr || hfmusic_mpq == nullptr || hfvoice_mpq == nullptr)) { + if (gbIsHellfire && (!hfmonk_mpq || !hfmusic_mpq || !hfvoice_mpq)) { UiErrorOkDialog(_("Some Hellfire MPQs are missing"), _("Not all Hellfire MPQs were found.\nPlease copy all the hf*.mpq files.")); app_fatal(nullptr); } diff --git a/Source/init.h b/Source/init.h index 7d20e613..0dff783d 100644 --- a/Source/init.h +++ b/Source/init.h @@ -6,26 +6,27 @@ #pragma once #include "miniwin/miniwin.h" +#include "utils/mpq.hpp" namespace devilution { extern bool gbActive; -extern HANDLE hellfire_mpq; +extern std::optional hellfire_mpq; extern WNDPROC CurrentProc; -extern HANDLE spawn_mpq; -extern HANDLE diabdat_mpq; +extern std::optional spawn_mpq; +extern std::optional diabdat_mpq; extern bool gbIsSpawn; extern bool gbIsHellfire; extern bool gbVanilla; extern bool forceHellfire; -extern HANDLE hfmonk_mpq; -extern HANDLE hfbard_mpq; -extern HANDLE hfbarb_mpq; -extern HANDLE hfmusic_mpq; -extern HANDLE hfvoice_mpq; -extern HANDLE font_mpq; -extern HANDLE lang_mpq; -extern HANDLE devilutionx_mpq; +extern std::optional hfmonk_mpq; +extern std::optional hfbard_mpq; +extern std::optional hfbarb_mpq; +extern std::optional hfmusic_mpq; +extern std::optional hfvoice_mpq; +extern std::optional font_mpq; +extern std::optional lang_mpq; +extern std::optional devilutionx_mpq; void init_cleanup(); void init_archives(); diff --git a/Source/menu.cpp b/Source/menu.cpp index f237799a..bf3654e3 100644 --- a/Source/menu.cpp +++ b/Source/menu.cpp @@ -13,7 +13,7 @@ #include "movie.h" #include "options.h" #include "pfile.h" -#include "storm/storm.h" +#include "storm/storm_net.hpp" #include "utils/language.h" namespace devilution { @@ -168,14 +168,14 @@ void mainmenu_loop() menu = MAINMENU_NONE; break; case MAINMENU_ATTRACT_MODE: - if (gbIsSpawn && diabdat_mpq == nullptr) + if (gbIsSpawn && !diabdat_mpq) done = false; else if (gbActive) PlayIntro(); menu = MAINMENU_NONE; break; case MAINMENU_REPLAY_INTRO: - if (gbIsSpawn && diabdat_mpq == nullptr && hellfire_mpq == nullptr) { + if (gbIsSpawn && !diabdat_mpq && !hellfire_mpq) { UiSelOkDialog(nullptr, _(/* TRANSLATORS: Error Message when a Shareware User clicks on "Replay Intro" in the Main Menu */ "The Diablo introduction cinematic is only available in the full retail version of Diablo. Visit https://www.gog.com/game/diablo to purchase."), true); } else if (gbActive) { mainmenu_wait_for_button_sound(); diff --git a/Source/miniwin/misc_msg.cpp b/Source/miniwin/misc_msg.cpp index 0b4ea740..09b82923 100644 --- a/Source/miniwin/misc_msg.cpp +++ b/Source/miniwin/misc_msg.cpp @@ -23,7 +23,6 @@ #include "menu.h" #include "miniwin/miniwin.h" #include "movie.h" -#include "storm/storm.h" #include "utils/display.h" #include "utils/log.hpp" #include "utils/sdl_compat.h" diff --git a/Source/monster.cpp b/Source/monster.cpp index b79414fc..cbf42bcd 100644 --- a/Source/monster.cpp +++ b/Source/monster.cpp @@ -27,7 +27,7 @@ #include "movie.h" #include "options.h" #include "spelldat.h" -#include "storm/storm.h" +#include "storm/storm_net.hpp" #include "themes.h" #include "towners.h" #include "trigs.h" diff --git a/Source/msg.cpp b/Source/msg.cpp index 9a1414ce..5334e7f3 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -26,7 +26,7 @@ #include "pfile.h" #include "plrmsg.h" #include "spells.h" -#include "storm/storm.h" +#include "storm/storm_net.hpp" #include "sync.h" #include "town.h" #include "towners.h" diff --git a/Source/multi.cpp b/Source/multi.cpp index ac090772..8d6495a3 100644 --- a/Source/multi.cpp +++ b/Source/multi.cpp @@ -19,7 +19,7 @@ #include "options.h" #include "pfile.h" #include "plrmsg.h" -#include "storm/storm.h" +#include "storm/storm_net.hpp" #include "sync.h" #include "tmsg.h" #include "utils/endian.hpp" @@ -419,7 +419,6 @@ void EventHandler(bool add) bool InitSingle(GameData *gameData) { if (!SNetInitializeProvider(SELCONN_LOOPBACK, gameData)) { - SErrGetLastError(); return false; } diff --git a/Source/multi.h b/Source/multi.h index c556e8ca..89906b9f 100644 --- a/Source/multi.h +++ b/Source/multi.h @@ -14,12 +14,6 @@ namespace devilution { // must be unsigned to generate unsigned comparisons with pnum #define MAX_PLRS 4 -enum event_type : uint8_t { - EVENT_TYPE_PLAYER_CREATE_GAME, - EVENT_TYPE_PLAYER_LEAVE_GAME, - EVENT_TYPE_PLAYER_MESSAGE, -}; - struct GameData { int32_t size; /** Used to initialise the seed table for dungeon levels so players in multiplayer games generate the same layout */ diff --git a/Source/nthread.cpp b/Source/nthread.cpp index 1acf2a78..742c67e8 100644 --- a/Source/nthread.cpp +++ b/Source/nthread.cpp @@ -8,7 +8,7 @@ #include "engine/demomode.h" #include "gmenu.h" #include "nthread.h" -#include "storm/storm.h" +#include "storm/storm_net.hpp" #include "utils/sdl_mutex.h" #include "utils/sdl_thread.h" diff --git a/Source/pfile.cpp b/Source/pfile.cpp index dafc169e..e6c2144c 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -14,10 +14,10 @@ #include "menu.h" #include "mpqapi.h" #include "pack.h" -#include "storm/storm.h" #include "utils/endian.hpp" #include "utils/file_util.h" #include "utils/language.h" +#include "utils/mpq.hpp" #include "utils/paths.h" namespace devilution { @@ -113,33 +113,26 @@ void RenameTempToPerm() assert(!GetPermSaveNames(dwIndex, szPerm)); } -std::unique_ptr ReadArchive(HANDLE archive, const char *pszName, size_t *pdwLen = nullptr) +std::unique_ptr ReadArchive(MpqArchive &archive, const char *pszName, size_t *pdwLen = nullptr) { - HANDLE file; + int32_t error; + std::size_t length; - if (!SFileOpenFileEx(archive, pszName, 0, &file)) + std::unique_ptr result = archive.ReadFile(pszName, length, error); + if (error != 0) return nullptr; - size_t length = SFileGetFileSize(file); - if (length == 0) - return nullptr; - - std::unique_ptr buf { new byte[length] }; - if (!SFileReadFileThreadSafe(file, buf.get(), length)) - return nullptr; - SFileCloseFileThreadSafe(file); - - length = codec_decode(buf.get(), length, pfile_get_password()); - if (length == 0) + std::size_t decodedLength = codec_decode(result.get(), length, pfile_get_password()); + if (decodedLength == 0) return nullptr; if (pdwLen != nullptr) - *pdwLen = length; + *pdwLen = decodedLength; - return buf; + return result; } -bool ReadHero(HANDLE archive, PlayerPack *pPack) +bool ReadHero(MpqArchive &archive, PlayerPack *pPack) { size_t read; @@ -171,22 +164,10 @@ bool OpenArchive(uint32_t saveNum) return OpenMPQ(GetSavePath(saveNum).c_str()); } -HANDLE OpenSaveArchive(uint32_t saveNum) +std::optional OpenSaveArchive(uint32_t saveNum) { - HANDLE archive; - - if (SFileOpenArchive(GetSavePath(saveNum).c_str(), 0, 0, &archive)) - return archive; - return nullptr; -} - -void CloseArchive(HANDLE *hsArchive) -{ - if (*hsArchive == nullptr) - return; - - SFileCloseArchive(*hsArchive); - *hsArchive = nullptr; + std::int32_t error; + return MpqArchive::Open(GetSavePath(saveNum).c_str(), error); } void Game2UiPlayer(const Player &player, _uiheroinfo *heroinfo, bool bHasSaveFile) @@ -229,7 +210,7 @@ bool GetFileName(uint8_t lvl, char *dst) return true; } -bool ArchiveContainsGame(HANDLE hsArchive) +bool ArchiveContainsGame(MpqArchive &hsArchive) { if (gbIsMultiplayer) return false; @@ -288,14 +269,14 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) memset(hero_names, 0, sizeof(hero_names)); for (uint32_t i = 0; i < MAX_CHARACTERS; i++) { - HANDLE archive = OpenSaveArchive(i); - if (archive != nullptr) { + std::optional archive = OpenSaveArchive(i); + if (archive) { PlayerPack pkplr; - if (ReadHero(archive, &pkplr)) { + if (ReadHero(*archive, &pkplr)) { _uiheroinfo uihero; uihero.saveNumber = i; strcpy(hero_names[i], pkplr.pName); - bool hasSaveGame = ArchiveContainsGame(archive); + bool hasSaveGame = ArchiveContainsGame(*archive); if (hasSaveGame) pkplr.bIsHellfire = gbIsHellfireSaveGame ? 1 : 0; @@ -312,7 +293,6 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) uiAddHeroInfo(&uihero); } } - CloseArchive(&archive); } } @@ -382,22 +362,20 @@ bool pfile_delete_save(_uiheroinfo *heroInfo) void pfile_read_player_from_save(uint32_t saveNum, Player &player) { - HANDLE archive; - PlayerPack pkplr; - player = {}; - archive = OpenSaveArchive(saveNum); - if (archive == nullptr) - app_fatal("%s", _("Unable to open archive")); - if (!ReadHero(archive, &pkplr)) - app_fatal("%s", _("Unable to load character")); + PlayerPack pkplr; + { + std::optional archive = OpenSaveArchive(saveNum); + if (!archive) + app_fatal("%s", _("Unable to open archive")); + if (!ReadHero(*archive, &pkplr)) + app_fatal("%s", _("Unable to load character")); - gbValidSaveFile = ArchiveContainsGame(archive); - if (gbValidSaveFile) - pkplr.bIsHellfire = gbIsHellfireSaveGame ? 1 : 0; - - CloseArchive(&archive); + gbValidSaveFile = ArchiveContainsGame(*archive); + if (gbValidSaveFile) + pkplr.bIsHellfire = gbIsHellfireSaveGame ? 1 : 0; + } if (!UnPackPlayer(&pkplr, player, false)) { return; @@ -462,19 +440,11 @@ void pfile_remove_temp_files() std::unique_ptr pfile_read(const char *pszName, size_t *pdwLen) { - HANDLE archive; - uint32_t saveNum = gSaveNumber; - archive = OpenSaveArchive(saveNum); - if (archive == nullptr) + std::optional archive = OpenSaveArchive(saveNum); + if (!archive) return nullptr; - - auto buf = ReadArchive(archive, pszName, pdwLen); - CloseArchive(&archive); - if (buf == nullptr) - return nullptr; - - return buf; + return ReadArchive(*archive, pszName, pdwLen); } void pfile_update(bool forceSave) diff --git a/Source/player.cpp b/Source/player.cpp index 02628b7e..ccb8323b 100644 --- a/Source/player.cpp +++ b/Source/player.cpp @@ -27,7 +27,6 @@ #include "qol/autopickup.h" #include "spells.h" #include "stores.h" -#include "storm/storm.h" #include "towners.h" #include "utils/language.h" #include "utils/log.hpp" @@ -2156,9 +2155,9 @@ void LoadPlrGFX(Player &player, player_graphic graphic) const char *szCel; HeroClass c = player._pClass; - if (c == HeroClass::Bard && hfbard_mpq == nullptr) { + if (c == HeroClass::Bard && !hfbard_mpq) { c = HeroClass::Rogue; - } else if (c == HeroClass::Barbarian && hfbarb_mpq == nullptr) { + } else if (c == HeroClass::Barbarian && !hfbarb_mpq) { c = HeroClass::Warrior; } diff --git a/Source/sound.cpp b/Source/sound.cpp index 344c2369..d69821d2 100644 --- a/Source/sound.cpp +++ b/Source/sound.cpp @@ -18,7 +18,7 @@ #include "init.h" #include "options.h" -#include "storm/storm_sdl_rw.h" +#include "engine/game_assets.hpp" #include "utils/log.hpp" #include "utils/math.h" #include "utils/sdl_mutex.h" @@ -163,13 +163,15 @@ std::unique_ptr sound_file_load(const char *path, bool stream) } #ifndef STREAM_ALL_AUDIO } else { - SDL_RWops *file = SFileOpenRw(path); + SDL_RWops *file = OpenAsset(path); if (file == nullptr) { - ErrDlg("SFileOpenFile failed", path, __FILE__, __LINE__); + ErrDlg("OpenAsset failed", path, __FILE__, __LINE__); } size_t dwBytes = SDL_RWsize(file); auto waveFile = MakeArraySharedPtr(dwBytes); - SDL_RWread(file, waveFile.get(), dwBytes, 1); + if (SDL_RWread(file, waveFile.get(), dwBytes, 1) == 0) { + ErrDlg("Failed to read file", fmt::format("{}: {}", path, SDL_GetError()).c_str(), __FILE__, __LINE__); + } int error = snd->DSB.SetChunk(waveFile, dwBytes); SDL_RWclose(file); if (error != 0) { @@ -233,12 +235,12 @@ void music_start(uint8_t nTrack) assert(nTrack < NUM_MUSIC); music_stop(); if (gbMusicOn) { - if (spawn_mpq != nullptr) + if (spawn_mpq) trackPath = SpawnMusicTracks[nTrack]; else trackPath = MusicTracks[nTrack]; - SDL_RWops *handle = SFileOpenRw(trackPath); + SDL_RWops *handle = OpenAsset(trackPath); if (handle != nullptr) { LoadMusic(handle); if (!music->open()) { diff --git a/Source/storm/storm.cpp b/Source/storm/storm.cpp deleted file mode 100644 index 287fc993..00000000 --- a/Source/storm/storm.cpp +++ /dev/null @@ -1,114 +0,0 @@ -#include -#include -#include -#include -#include -#include - -#include "DiabloUI/diabloui.h" -#include "options.h" -#include "storm/storm.h" -#include "utils/file_util.h" -#include "utils/log.hpp" -#include "utils/paths.h" -#include "utils/sdl_mutex.h" -#include "utils/stubs.h" -#include "utils/stdcompat/optional.hpp" - -// Include Windows headers for Get/SetLastError. -#if defined(_WIN32) -// Suppress definitions of `min` and `max` macros by : -#define NOMINMAX 1 -#define WIN32_LEAN_AND_MEAN -#include -#else // !defined(_WIN32) -// On non-Windows, these are defined in 3rdParty/StormLib. -extern "C" void SetLastError(std::uint32_t dwErrCode); -extern "C" std::uint32_t GetLastError(); -#endif - -namespace devilution { -namespace { - -SdlMutex Mutex; - -} // namespace - -bool SFileReadFileThreadSafe(HANDLE hFile, void *buffer, size_t nNumberOfBytesToRead, size_t *read, int *lpDistanceToMoveHigh) -{ - const std::lock_guard lock(Mutex); - return SFileReadFile(hFile, buffer, nNumberOfBytesToRead, (unsigned int *)read, lpDistanceToMoveHigh); -} - -bool SFileCloseFileThreadSafe(HANDLE hFile) -{ - const std::lock_guard lock(Mutex); - return SFileCloseFile(hFile); -} - -bool SFileOpenFile(const char *filename, HANDLE *phFile) -{ - bool result = false; - - if (!result && font_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)font_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - if (!result && lang_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)lang_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - if (!result && devilutionx_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)devilutionx_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - if (gbIsHellfire) { - if (!result && hfvoice_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)hfvoice_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - if (!result && hfmusic_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)hfmusic_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - if (!result && hfbarb_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)hfbarb_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - if (!result && hfbard_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)hfbard_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - if (!result && hfmonk_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)hfmonk_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - if (!result) { - result = SFileOpenFileEx((HANDLE)hellfire_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - } - if (!result && spawn_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)spawn_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - if (!result && diabdat_mpq != nullptr) { - result = SFileOpenFileEx((HANDLE)diabdat_mpq, filename, SFILE_OPEN_FROM_MPQ, phFile); - } - - return result; -} - -uint32_t SErrGetLastError() -{ - return ::GetLastError(); -} - -void SErrSetLastError(uint32_t dwErrCode) -{ - ::SetLastError(dwErrCode); -} - -#if defined(_WIN64) || defined(_WIN32) -bool SFileOpenArchive(const char *szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE *phMpq) -{ - const auto szMpqNameUtf16 = ToWideChar(szMpqName); - if (szMpqNameUtf16 == nullptr) { - LogError("UTF-8 -> UTF-16 conversion error code {}", ::GetLastError()); - return false; - } - return SFileOpenArchive(szMpqNameUtf16.get(), dwPriority, dwFlags, phMpq); -} -#endif - -} // namespace devilution diff --git a/Source/storm/storm_net.cpp b/Source/storm/storm_net.cpp index 95228f3c..a1797ec8 100644 --- a/Source/storm/storm_net.cpp +++ b/Source/storm/storm_net.cpp @@ -1,3 +1,5 @@ +#include "storm/storm_net.hpp" + #include #ifndef NONET #include "utils/sdl_mutex.h" @@ -13,14 +15,27 @@ namespace devilution { -static std::unique_ptr dvlnet_inst; -static char gpszGameName[128] = {}; -static char gpszGamePassword[128] = {}; -static bool GameIsPublic = {}; +namespace { +std::unique_ptr dvlnet_inst; +char gpszGameName[128] = {}; +char gpszGamePassword[128] = {}; +bool GameIsPublic = {}; +thread_local uint32_t dwLastError = 0; #ifndef NONET -static SdlMutex storm_net_mutex; +SdlMutex storm_net_mutex; #endif +} // namespace + +uint32_t SErrGetLastError() +{ + return dwLastError; +} + +void SErrSetLastError(uint32_t dwErrCode) +{ + dwLastError = dwErrCode; +} bool SNetReceiveMessage(int *senderplayerid, void **data, uint32_t *databytes) { diff --git a/Source/storm/storm.h b/Source/storm/storm_net.hpp similarity index 53% rename from Source/storm/storm.h rename to Source/storm/storm_net.hpp index bb806c2b..ebf2e2c0 100644 --- a/Source/storm/storm.h +++ b/Source/storm/storm_net.hpp @@ -1,15 +1,9 @@ #pragma once -#include +#include #include -#include +#include #include -#include - -#include "appfat.h" -#include "multi.h" -#include "utils/language.h" -#include "utils/stdcompat/string_view.hpp" namespace devilution { @@ -24,29 +18,14 @@ enum conn_type : uint8_t { SELCONN_LOOPBACK, }; -extern const char *ConnectionNames[]; - -struct PCXHeader { - uint8_t Manufacturer; - uint8_t Version; - uint8_t Encoding; - uint8_t BitsPerPixel; - uint16_t Xmin; - uint16_t Ymin; - uint16_t Xmax; - uint16_t Ymax; - uint16_t HDpi; - uint16_t VDpi; - uint8_t Colormap[48]; - uint8_t Reserved; - uint8_t NPlanes; - uint16_t BytesPerLine; - uint16_t PaletteInfo; - uint16_t HscreenSize; - uint16_t VscreenSize; - uint8_t Filler[54]; +enum event_type : uint8_t { + EVENT_TYPE_PLAYER_CREATE_GAME, + EVENT_TYPE_PLAYER_LEAVE_GAME, + EVENT_TYPE_PLAYER_MESSAGE, }; +extern const char *ConnectionNames[]; + struct _SNETCAPS { uint32_t size; uint32_t flags; @@ -66,16 +45,6 @@ struct _SNETEVENT { uint32_t databytes; }; -// Note to self: Linker error => forgot a return value in cpp - -// We declare the StormLib methods we use here. -// StormLib uses the Windows calling convention on Windows for these methods. -#ifdef _WIN32 -#define WINAPI __stdcall -#else -#define WINAPI -#endif - // Game states #define GAMESTATE_PRIVATE 0x01 #define GAMESTATE_FULL 0x02 @@ -90,10 +59,6 @@ struct _SNETEVENT { #define LEAVE_ENDING 0x40000004 #define LEAVE_DROP 0x40000006 -#if defined(__GNUC__) || defined(__cplusplus) -extern "C" { -#endif - bool SNetCreateGame(const char *pszGameName, const char *pszGamePassword, char *GameTemplateData, int GameTemplateSize, int *playerID); bool SNetDestroy(); @@ -171,10 +136,6 @@ bool SNetSendMessage(int playerID, void *data, unsigned int databytes); #define SNPLAYER_ALL -1 #define SNPLAYER_OTHERS -2 -#define MPQ_OPEN_READ_ONLY 0x00000100 -#define SFILE_OPEN_FROM_MPQ 0 -#define SFILE_OPEN_LOCAL_FILE 0xFFFFFFFF - /* SNetSendTurn @ 128 * * Sends a turn (data packet) to all players in the game. Network data @@ -188,47 +149,7 @@ bool SNetSendMessage(int playerID, void *data, unsigned int databytes); */ bool SNetSendTurn(char *data, unsigned int databytes); -bool SFileOpenFile(const char *filename, HANDLE *phFile); - -// Functions implemented in StormLib -#if defined(_WIN64) || defined(_WIN32) -bool WINAPI SFileOpenArchive(const wchar_t *szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE *phMpq); -#else -bool WINAPI SFileOpenArchive(const char *szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE *phMpq); -#endif -bool WINAPI SFileCloseArchive(HANDLE hArchive); -bool WINAPI SFileOpenFileEx(HANDLE hMpq, const char *szFileName, DWORD dwSearchScope, HANDLE *phFile); -bool WINAPI SFileReadFile(HANDLE hFile, void *buffer, unsigned int nNumberOfBytesToRead, unsigned int *read, void *lpDistanceToMoveHigh); -DWORD WINAPI SFileGetFileSize(HANDLE hFile, uint32_t *lpFileSizeHigh = nullptr); -DWORD WINAPI SFileSetFilePointer(HANDLE, int, int *, int); -bool WINAPI SFileCloseFile(HANDLE hFile); - -// These error codes are used and returned by StormLib. -// See StormLib/src/StormPort.h -#if defined(_WIN32) -// https://docs.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499- -#define STORM_ERROR_FILE_NOT_FOUND 2 -#define STORM_ERROR_HANDLE_EOF 38 -#else // !defined(_WIN32) -#define STORM_ERROR_FILE_NOT_FOUND ENOENT -#define STORM_ERROR_HANDLE_EOF 1002 -#endif - -/* SErrGetLastError @ 463 - * - * Retrieves the last error that was specifically - * set for the Storm library. - * - * Returns the last error set within the Storm library. - */ uint32_t SErrGetLastError(); - -/* SErrSetLastError @ 465 - * - * Sets the last error for the Storm library and the Kernel32 library. - * - * dwErrCode: The error code that will be set. - */ void SErrSetLastError(uint32_t dwErrCode); // Values for dwErrCode @@ -237,18 +158,6 @@ void SErrSetLastError(uint32_t dwErrCode); #define STORM_ERROR_NO_MESSAGES_WAITING 0x8510006b #define STORM_ERROR_NOT_IN_GAME 0x85100070 -/* SStrCopy @ 501 - * - * Copies a string from src to dest (including NULL terminator) - * until the max_length is reached. - * - * dest: The destination array. - * src: The source array. - * max_length: The maximum length of dest. - * - */ -void SStrCopy(char *dest, const char *src, int max_length); - bool SNetGetOwnerTurnsWaiting(uint32_t *); bool SNetUnregisterEventHandler(event_type); bool SNetRegisterEventHandler(event_type, SEVTHANDLER); @@ -256,39 +165,6 @@ bool SNetSetBasePlayer(int); bool SNetInitializeProvider(uint32_t provider, struct GameData *gameData); void SNetGetProviderCaps(struct _SNETCAPS *); -#if defined(__GNUC__) || defined(__cplusplus) -} - -// Additions to Storm API: -#if defined(_WIN64) || defined(_WIN32) -// On Windows, handles wchar conversion and calls the wchar version of SFileOpenArchive. -bool SFileOpenArchive(const char *szMpqName, DWORD dwPriority, DWORD dwFlags, HANDLE *phMpq); -#endif - -// Locks ReadFile and CloseFile under a mutex. -// See https://github.com/ladislav-zezula/StormLib/issues/175 -bool SFileReadFileThreadSafe(HANDLE hFile, void *buffer, size_t nNumberOfBytesToRead, size_t *read = nullptr, int *lpDistanceToMoveHigh = nullptr); -bool SFileCloseFileThreadSafe(HANDLE hFile); - -// Sets the file's 64-bit seek position. -inline std::uint64_t SFileSetFilePointer(HANDLE hFile, std::int64_t offset, int whence) -{ - int high = static_cast(offset) >> 32; - int low = static_cast(offset); - low = SFileSetFilePointer(hFile, low, &high, whence); - return (static_cast(high) << 32) | low; -} - -// Returns the current 64-bit file seek position. -inline std::uint64_t SFileGetFilePointer(HANDLE hFile) -{ - // We use `SFileSetFilePointer` with offset 0 to get the current position - // because there is no `SFileGetFilePointer`. - return SFileSetFilePointer(hFile, 0, DVL_FILE_CURRENT); -} - -#endif - void DvlNet_SendInfoRequest(); void DvlNet_ClearGamelist(); std::vector DvlNet_GetGamelist(); diff --git a/Source/storm/storm_sdl_rw.cpp b/Source/storm/storm_sdl_rw.cpp deleted file mode 100644 index 4bbfbc3e..00000000 --- a/Source/storm/storm_sdl_rw.cpp +++ /dev/null @@ -1,147 +0,0 @@ -#include "storm/storm_sdl_rw.h" - -#include -#include -#include - -#include "engine.h" -#include "storm/storm.h" -#include "utils/file_util.h" -#include "utils/log.hpp" -#include "utils/paths.h" - -namespace devilution { - -namespace { - -static HANDLE SFileRwGetHandle(struct SDL_RWops *context) -{ - return (HANDLE)context->hidden.unknown.data1; -} - -static void SFileRwSetHandle(struct SDL_RWops *context, HANDLE handle) -{ - context->hidden.unknown.data1 = handle; -} - -#ifndef USE_SDL1 -static Sint64 SFileRwSize(struct SDL_RWops *context) -{ - return SFileGetFileSize(SFileRwGetHandle(context)); -} -#endif - -#ifndef USE_SDL1 -static Sint64 SFileRwSeek(struct SDL_RWops *context, Sint64 offset, int whence) -#else -static int SFileRwSeek(struct SDL_RWops *context, int offset, int whence) -#endif -{ - DWORD swhence; - switch (whence) { - case RW_SEEK_SET: - swhence = DVL_FILE_BEGIN; - break; - case RW_SEEK_CUR: - swhence = DVL_FILE_CURRENT; - break; - case RW_SEEK_END: - swhence = DVL_FILE_END; - break; - default: - return -1; - } - const std::uint64_t pos = SFileSetFilePointer(SFileRwGetHandle(context), offset, swhence); - if (pos == static_cast(-1)) { - Log("SFileRwSeek error: {}", SErrGetLastError()); - } - return pos; -} - -#ifndef USE_SDL1 -static size_t SFileRwRead(struct SDL_RWops *context, void *ptr, size_t size, size_t maxnum) -#else -static int SFileRwRead(struct SDL_RWops *context, void *ptr, int size, int maxnum) -#endif -{ - size_t numRead = 0; - if (!SFileReadFileThreadSafe(SFileRwGetHandle(context), ptr, maxnum * size, &numRead)) { - const auto errCode = SErrGetLastError(); - if (errCode != STORM_ERROR_HANDLE_EOF) { - Log("SFileRwRead error: {} {} ERROR CODE {}", size, maxnum, errCode); - } - } - return numRead / size; -} - -static int SFileRwClose(struct SDL_RWops *context) -{ - SFileCloseFileThreadSafe(SFileRwGetHandle(context)); - delete context; - return 0; -} - -SDL_RWops *SFileRw_FromStormHandle(HANDLE handle) -{ - auto *result = new SDL_RWops(); - std::memset(result, 0, sizeof(*result)); - -#ifndef USE_SDL1 - result->size = &SFileRwSize; - result->type = SDL_RWOPS_UNKNOWN; -#else - result->type = 0; -#endif - - result->seek = &SFileRwSeek; - result->read = &SFileRwRead; - result->write = nullptr; - result->close = &SFileRwClose; - SFileRwSetHandle(result, handle); - return result; -} - -} // namespace - -SDL_RWops *SFileOpenRw(const char *filename) -{ - std::string relativePath = filename; -#ifndef _WIN32 - std::replace(relativePath.begin(), relativePath.end(), '\\', '/'); -#endif - - if (relativePath[0] == '/') - return SDL_RWFromFile(relativePath.c_str(), "rb"); - - // Files next to the MPQ archives override MPQ contents. - SDL_RWops *rwops; - if (paths::MpqDir()) { - const std::string path = *paths::MpqDir() + relativePath; - // Avoid spamming DEBUG messages if the file does not exist. - if ((FileExists(path.c_str())) && (rwops = SDL_RWFromFile(path.c_str(), "rb")) != nullptr) { - LogVerbose("Loaded MPQ file override: {}", path); - return rwops; - } - } - - // Load from all the MPQ archives. - HANDLE handle; - if (SFileOpenFile(filename, &handle)) - return SFileRw_FromStormHandle(handle); - - // Load from the `/assets` directory next to the devilutionx binary. - const std::string path = paths::AssetsPath() + relativePath; - if ((rwops = SDL_RWFromFile(path.c_str(), "rb")) != nullptr) - return rwops; - -#ifdef __ANDROID__ - // On Android, fall back to the APK's assets. - // This is handled by SDL when we pass a relative path. - if (!paths::AssetsPath().empty() && (rwops = SDL_RWFromFile(relativePath.c_str(), "rb"))) - return rwops; -#endif - - return nullptr; -} - -} // namespace devilution diff --git a/Source/storm/storm_svid.cpp b/Source/storm/storm_svid.cpp index e9e73365..1a99f6bf 100644 --- a/Source/storm/storm_svid.cpp +++ b/Source/storm/storm_svid.cpp @@ -16,11 +16,11 @@ #include "dx.h" #include "options.h" #include "palette.h" -#include "storm/storm_sdl_rw.h" -#include "storm/storm_file_wrapper.h" +#include "engine/game_assets.hpp" #include "utils/display.h" #include "utils/log.hpp" #include "utils/sdl_compat.h" +#include "utils/sdl_rwops_file_wrapper.hpp" #include "utils/sdl_wrap.h" #include "utils/stdcompat/optional.hpp" @@ -42,7 +42,7 @@ SDL_Color SVidPreviousPalette[256]; SDLPaletteUniquePtr SVidPalette; SDLSurfaceUniquePtr SVidSurface; -#ifndef DEVILUTIONX_STORM_FILE_WRAPPER_AVAILABLE +#ifndef DEVILUTIONX_SDL_RWOPS_FILE_WRAPPER_AVAILABLE std::unique_ptr SVidBuffer; #endif @@ -150,9 +150,9 @@ bool SVidPlayBegin(const char *filename, int flags) //0x800000 // Edge detection //0x200800 // Clear FB - SDL_RWops *videoStream = SFileOpenRw(filename); -#ifdef DEVILUTIONX_STORM_FILE_WRAPPER_AVAILABLE - FILE *file = FILE_FromStormHandle(videoStream); + SDL_RWops *videoStream = OpenAsset(filename); +#ifdef DEVILUTIONX_SDL_RWOPS_FILE_WRAPPER_AVAILABLE + FILE *file = FILE_FromSDL_RWops(videoStream); SVidSMK = smk_open_filepointer(file, SMK_MODE_DISK); #else size_t bytestoread = SDL_RWsize(videoStream); @@ -358,7 +358,7 @@ void SVidPlayEnd() if (SVidSMK != nullptr) smk_close(SVidSMK); -#ifndef DEVILUTIONX_STORM_FILE_WRAPPER_AVAILABLE +#ifndef DEVILUTIONX_SDL_RWOPS_FILE_WRAPPER_AVAILABLE SVidBuffer = nullptr; #endif diff --git a/Source/utils/language.cpp b/Source/utils/language.cpp index a4a9089b..37205f9f 100644 --- a/Source/utils/language.cpp +++ b/Source/utils/language.cpp @@ -6,7 +6,7 @@ #include #include "options.h" -#include "storm/storm_sdl_rw.h" +#include "engine/game_assets.hpp" #include "utils/file_util.h" #include "utils/paths.h" @@ -269,7 +269,7 @@ const std::string &LanguageTranslate(const char *key) bool HasTranslation(const std::string &locale) { for (const char *ext : { ".mo", ".gmo" }) { - SDL_RWops *rw = SFileOpenRw((locale + ext).c_str()); + SDL_RWops *rw = OpenAsset((locale + ext).c_str()); if (rw != nullptr) { SDL_RWclose(rw); return true; @@ -288,7 +288,7 @@ void LanguageInitialize() // We also support ".mo" because that is what poedit generates // and what translators use to test their work. for (const char *ext : { ".mo", ".gmo" }) { - if ((rw = SFileOpenRw((lang + ext).c_str())) != nullptr) + if ((rw = OpenAsset((lang + ext).c_str())) != nullptr) break; } if (rw == nullptr) diff --git a/Source/utils/mpq.cpp b/Source/utils/mpq.cpp new file mode 100644 index 00000000..8aa07c83 --- /dev/null +++ b/Source/utils/mpq.cpp @@ -0,0 +1,145 @@ +#include "utils/mpq.hpp" + +#include + +#include "utils/stdcompat/optional.hpp" + +namespace devilution { + +std::optional MpqArchive::Open(const char *path, int32_t &error) +{ + mpq_archive_s *archive; + error = libmpq__archive_open(&archive, path, -1); + if (error != 0) { + if (error == LIBMPQ_ERROR_EXIST) + error = 0; + return std::nullopt; + } + return MpqArchive { std::string(path), archive }; +} + +std::optional MpqArchive::Clone(int32_t &error) +{ + mpq_archive_s *copy; + error = libmpq__archive_dup(archive_, path_.c_str(), ©); + if (error != 0) + return std::nullopt; + return MpqArchive { path_, copy }; +} + +const char *MpqArchive::ErrorMessage(int32_t errorCode) +{ + return libmpq__strerror(errorCode); +} + +MpqArchive::FileHash MpqArchive::CalculateFileHash(const char *filename) +{ + FileHash fileHash; + libmpq__file_hash(filename, &fileHash[0], &fileHash[1], &fileHash[2]); + return fileHash; +} + +MpqArchive &MpqArchive::operator=(MpqArchive &&other) noexcept +{ + path_ = std::move(other.path_); + if (archive_ != nullptr) + libmpq__archive_close(archive_); + archive_ = other.archive_; + tmp_buf_ = std::move(other.tmp_buf_); + return *this; +} + +MpqArchive::~MpqArchive() +{ + if (archive_ != nullptr) + libmpq__archive_close(archive_); +} + +bool MpqArchive::GetFileNumber(MpqArchive::FileHash fileHash, uint32_t &fileNumber) +{ + return libmpq__file_number_from_hash(archive_, fileHash[0], fileHash[1], fileHash[2], &fileNumber) == 0; +} + +std::unique_ptr MpqArchive::ReadFile(const char *filename, std::size_t &fileSize, int32_t &error) +{ + std::unique_ptr result; + std::uint32_t fileNumber; + error = libmpq__file_number(archive_, filename, &fileNumber); + if (error != 0) + return result; + + libmpq__off_t unpackedSize; + error = libmpq__file_size_unpacked(archive_, fileNumber, &unpackedSize); + if (error != 0) + return result; + + error = OpenBlockOffsetTable(fileNumber, filename); + if (error != 0) + return result; + + result = std::make_unique(unpackedSize); + + const std::size_t blockSize = GetBlockSize(fileNumber, 0, error); + if (error != 0) + return result; + + std::vector &tmp = GetTemporaryBuffer(blockSize); + if (error != 0) + return result; + + error = libmpq__file_read_with_filename_and_temporary_buffer( + archive_, fileNumber, filename, reinterpret_cast(result.get()), unpackedSize, + tmp.data(), static_cast(blockSize), nullptr); + if (error != 0) { + result = nullptr; + CloseBlockOffsetTable(fileNumber); + return result; + } + CloseBlockOffsetTable(fileNumber); + + fileSize = unpackedSize; + return result; +} + +int32_t MpqArchive::ReadBlock(uint32_t fileNumber, uint32_t blockNumber, uint8_t *out, uint32_t outSize) +{ + std::vector &tmpBuf = GetTemporaryBuffer(outSize); + return libmpq__block_read_with_temporary_buffer( + archive_, fileNumber, blockNumber, out, static_cast(outSize), + tmpBuf.data(), outSize, + /*transferred=*/nullptr); +} + +std::size_t MpqArchive::GetUnpackedFileSize(uint32_t fileNumber, int32_t &error) +{ + libmpq__off_t unpackedSize; + error = libmpq__file_size_unpacked(archive_, fileNumber, &unpackedSize); + return unpackedSize; +} + +uint32_t MpqArchive::GetNumBlocks(uint32_t fileNumber, int32_t &error) +{ + uint32_t numBlocks; + error = libmpq__file_blocks(archive_, fileNumber, &numBlocks); + return numBlocks; +} + +int32_t MpqArchive::OpenBlockOffsetTable(uint32_t fileNumber, const char *filename) +{ + return libmpq__block_open_offset_with_filename(archive_, fileNumber, filename); +} + +int32_t MpqArchive::CloseBlockOffsetTable(uint32_t fileNumber) +{ + return libmpq__block_close_offset(archive_, fileNumber); +} + +// Requires the block offset table to be open +std::size_t MpqArchive::GetBlockSize(uint32_t fileNumber, uint32_t blockNumber, int32_t &error) +{ + libmpq__off_t blockSize; + error = libmpq__block_size_unpacked(archive_, fileNumber, blockNumber, &blockSize); + return blockSize; +} + +} // namespace devilution diff --git a/Source/utils/mpq.hpp b/Source/utils/mpq.hpp new file mode 100644 index 00000000..1fa3e9ec --- /dev/null +++ b/Source/utils/mpq.hpp @@ -0,0 +1,81 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "utils/stdcompat/cstddef.hpp" +#include "utils/stdcompat/optional.hpp" + +// Forward-declare so that we can avoid exposing libmpq. +struct mpq_archive; +using mpq_archive_s = struct mpq_archive; + +namespace devilution { + +class MpqArchive { +public: + // If the file does not exist, returns nullopt without an error. + static std::optional Open(const char *path, int32_t &error); + + std::optional Clone(int32_t &error); + + static const char *ErrorMessage(int32_t errorCode); + + using FileHash = std::array; + static FileHash CalculateFileHash(const char *filename); + + MpqArchive(MpqArchive &&other) noexcept + : path_(std::move(other.path_)) + , archive_(other.archive_) + , tmp_buf_(std::move(other.tmp_buf_)) + { + other.archive_ = nullptr; + } + + MpqArchive &operator=(MpqArchive &&other) noexcept; + + ~MpqArchive(); + + // Returns false if the file does not exit. + bool GetFileNumber(FileHash fileHash, uint32_t &fileNumber); + + std::unique_ptr ReadFile(const char *filename, std::size_t &fileSize, int32_t &error); + + // Returns error code. + int32_t ReadBlock(uint32_t fileNumber, uint32_t blockNumber, uint8_t *out, uint32_t outSize); + + std::size_t GetUnpackedFileSize(uint32_t fileNumber, int32_t &error); + + uint32_t GetNumBlocks(uint32_t fileNumber, int32_t &error); + + int32_t OpenBlockOffsetTable(uint32_t fileNumber, const char *filename); + + int32_t CloseBlockOffsetTable(uint32_t fileNumber); + + // Requires the block offset table to be open + std::size_t GetBlockSize(uint32_t fileNumber, uint32_t blockNumber, int32_t &error); + +private: + MpqArchive(std::string path, mpq_archive_s *archive) + : path_(std::move(path)) + , archive_(archive) + { + } + + std::vector &GetTemporaryBuffer(std::size_t size) + { + if (tmp_buf_.size() < size) + tmp_buf_.resize(size); + return tmp_buf_; + } + + std::string path_; + mpq_archive_s *archive_; + std::vector tmp_buf_; +}; + +} // namespace devilution diff --git a/Source/utils/mpq_sdl_rwops.cpp b/Source/utils/mpq_sdl_rwops.cpp new file mode 100644 index 00000000..bf26b3d6 --- /dev/null +++ b/Source/utils/mpq_sdl_rwops.cpp @@ -0,0 +1,232 @@ +#include "utils/mpq_sdl_rwops.hpp" + +#include +#include +#include + +namespace devilution { + +namespace { + +struct Data { + // File information: + std::optional ownedArchive; + MpqArchive *mpqArchive; + uint32_t fileNumber; + uint32_t blockSize; + uint32_t lastBlockSize; + uint32_t numBlocks; + uint32_t size; + + // State: + uint32_t position; + bool blockRead; + std::unique_ptr blockData; +}; + +Data *GetData(struct SDL_RWops *context) +{ + return reinterpret_cast(context->hidden.unknown.data1); +} + +void SetData(struct SDL_RWops *context, Data *data) +{ + context->hidden.unknown.data1 = data; +} + +#ifndef USE_SDL1 +using OffsetType = Sint64; +using SizeType = size_t; +#else +using OffsetType = int; +using SizeType = int; +#endif + +extern "C" { + +#ifndef USE_SDL1 +static Sint64 MpqFileRwSize(struct SDL_RWops *context) +{ + return GetData(context)->size; +} +#endif + +static OffsetType MpqFileRwSeek(struct SDL_RWops *context, OffsetType offset, int whence) +{ + Data &data = *GetData(context); + OffsetType newPosition; + switch (whence) { + case RW_SEEK_SET: + newPosition = offset; + break; + case RW_SEEK_CUR: + newPosition = data.position + offset; + break; + case RW_SEEK_END: + newPosition = data.size + offset; + break; + default: + return -1; + } + + if (newPosition == data.position) + return newPosition; + + if (newPosition > data.size) { + SDL_SetError("MpqFileRwSeek beyond EOF (%d > %u)", static_cast(newPosition), data.size); + return -1; + } + + if (newPosition < 0) { + SDL_SetError("MpqFileRwSeek beyond BOF (%d < 0)", static_cast(newPosition)); + return -1; + } + + if (data.position / data.blockSize != newPosition / data.blockSize) + data.blockRead = false; + + data.position = newPosition; + + return newPosition; +} + +static SizeType MpqFileRwRead(struct SDL_RWops *context, void *ptr, SizeType size, SizeType maxnum) +{ + Data &data = *GetData(context); + const SizeType totalSize = size * maxnum; + SizeType remainingSize = totalSize; + + auto *out = static_cast(ptr); + + if (data.blockData == nullptr) { + data.blockData = std::unique_ptr { new uint8_t[data.blockSize] }; + } + + uint32_t blockNumber = data.position / data.blockSize; + while (remainingSize > 0) { + if (data.position == data.size) { + SDL_SetError("MpqFileRwRead beyond EOF by %u bytes", static_cast(remainingSize)); + break; + } + + const uint32_t currentBlockSize = blockNumber + 1 == data.numBlocks ? data.lastBlockSize : data.blockSize; + + if (!data.blockRead) { + const int32_t error = data.mpqArchive->ReadBlock(data.fileNumber, blockNumber, data.blockData.get(), currentBlockSize); + if (error != 0) { + SDL_SetError("MpqFileRwRead ReadBlock: %s", MpqArchive::ErrorMessage(error)); + return 0; + } + data.blockRead = true; + } + + const uint32_t blockPosition = data.position - blockNumber * data.blockSize; + const uint32_t remainingBlockSize = currentBlockSize - blockPosition; + + if (remainingSize < remainingBlockSize) { + std::memcpy(out, data.blockData.get() + blockPosition, remainingSize); + data.position += remainingSize; + return maxnum; + } + + std::memcpy(out, data.blockData.get() + blockPosition, remainingBlockSize); + out += remainingBlockSize; + data.position += remainingBlockSize; + remainingSize -= remainingBlockSize; + ++blockNumber; + data.blockRead = false; + } + + return (totalSize - remainingSize) / size; +} + +static int MpqFileRwClose(struct SDL_RWops *context) +{ + Data *data = GetData(context); + data->mpqArchive->CloseBlockOffsetTable(data->fileNumber); + delete data; + delete context; + return 0; +} + +} // extern "C" + +} // namespace + +SDL_RWops *SDL_RWops_FromMpqFile(MpqArchive &mpqArchive, uint32_t fileNumber, const char *filename, bool threadsafe) +{ + auto result = std::make_unique(); + std::memset(result.get(), 0, sizeof(*result)); + +#ifndef USE_SDL1 + result->size = &MpqFileRwSize; + result->type = SDL_RWOPS_UNKNOWN; +#else + result->type = 0; +#endif + + result->seek = &MpqFileRwSeek; + result->read = &MpqFileRwRead; + result->write = nullptr; + result->close = &MpqFileRwClose; + + auto data = std::make_unique(); + int32_t error = 0; + + if (threadsafe) { + data->ownedArchive = mpqArchive.Clone(error); + if (error != 0) { + SDL_SetError("MpqFileRwRead Clone: %s", MpqArchive::ErrorMessage(error)); + return nullptr; + } + data->mpqArchive = &*data->ownedArchive; + } else { + data->mpqArchive = &mpqArchive; + } + data->fileNumber = fileNumber; + MpqArchive &archive = *data->mpqArchive; + + error = archive.OpenBlockOffsetTable(fileNumber, filename); + if (error != 0) { + SDL_SetError("MpqFileRwRead OpenBlockOffsetTable: %s", MpqArchive::ErrorMessage(error)); + return nullptr; + } + + data->size = archive.GetUnpackedFileSize(fileNumber, error); + if (error != 0) { + SDL_SetError("MpqFileRwRead GetUnpackedFileSize: %s", MpqArchive::ErrorMessage(error)); + return nullptr; + } + + const std::uint32_t numBlocks = archive.GetNumBlocks(fileNumber, error); + if (error != 0) { + SDL_SetError("MpqFileRwRead GetNumBlocks: %s", MpqArchive::ErrorMessage(error)); + return nullptr; + } + data->numBlocks = numBlocks; + + const std::uint32_t blockSize = archive.GetBlockSize(fileNumber, 0, error); + if (error != 0) { + SDL_SetError("MpqFileRwRead GetBlockSize: %s", MpqArchive::ErrorMessage(error)); + return nullptr; + } + data->blockSize = blockSize; + + if (numBlocks > 1) { + data->lastBlockSize = archive.GetBlockSize(fileNumber, numBlocks - 1, error); + if (error != 0) { + SDL_SetError("MpqFileRwRead GetBlockSize: %s", MpqArchive::ErrorMessage(error)); + return nullptr; + } + } else { + data->lastBlockSize = blockSize; + } + + data->position = 0; + data->blockRead = false; + + SetData(result.get(), data.release()); + return result.release(); +} + +} // namespace devilution diff --git a/Source/utils/mpq_sdl_rwops.hpp b/Source/utils/mpq_sdl_rwops.hpp new file mode 100644 index 00000000..0b4bd30b --- /dev/null +++ b/Source/utils/mpq_sdl_rwops.hpp @@ -0,0 +1,13 @@ +#pragma once + +#include + +#include + +#include "utils/mpq.hpp" + +namespace devilution { + +SDL_RWops *SDL_RWops_FromMpqFile(MpqArchive &mpqArchive, uint32_t fileNumber, const char *filename, bool threadsafe); + +} // namespace devilution diff --git a/Source/utils/pcx.hpp b/Source/utils/pcx.hpp new file mode 100644 index 00000000..37d6ed93 --- /dev/null +++ b/Source/utils/pcx.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include + +namespace devilution { + +struct PCXHeader { + uint8_t Manufacturer; + uint8_t Version; + uint8_t Encoding; + uint8_t BitsPerPixel; + uint16_t Xmin; + uint16_t Ymin; + uint16_t Xmax; + uint16_t Ymax; + uint16_t HDpi; + uint16_t VDpi; + uint8_t Colormap[48]; + uint8_t Reserved; + uint8_t NPlanes; + uint16_t BytesPerLine; + uint16_t PaletteInfo; + uint16_t HscreenSize; + uint16_t VscreenSize; + uint8_t Filler[54]; +}; + +} // namespace devilution diff --git a/Source/utils/png.h b/Source/utils/png.h index be0babe0..fdc19a05 100644 --- a/Source/utils/png.h +++ b/Source/utils/png.h @@ -3,8 +3,7 @@ #include #include "miniwin/miniwin.h" -#include "storm/storm.h" -#include "storm/storm_sdl_rw.h" +#include "engine/game_assets.hpp" #ifdef __cplusplus extern "C" { @@ -43,7 +42,7 @@ inline void QuitPNG() inline SDL_Surface *LoadPNG(const char *file) { - SDL_RWops *rwops = SFileOpenRw(file); + SDL_RWops *rwops = OpenAsset(file); SDL_Surface *surface = IMG_LoadPNG_RW(rwops); SDL_RWclose(rwops); return surface; diff --git a/Source/storm/storm_file_wrapper.cpp b/Source/utils/sdl_rwops_file_wrapper.cpp similarity index 57% rename from Source/storm/storm_file_wrapper.cpp rename to Source/utils/sdl_rwops_file_wrapper.cpp index ecf1f84e..3fd0ff20 100644 --- a/Source/storm/storm_file_wrapper.cpp +++ b/Source/utils/sdl_rwops_file_wrapper.cpp @@ -1,15 +1,15 @@ -#include "storm_file_wrapper.h" +#include "utils/sdl_rwops_file_wrapper.hpp" -#ifdef DEVILUTIONX_STORM_FILE_WRAPPER_AVAILABLE +#ifdef DEVILUTIONX_SDL_RWOPS_FILE_WRAPPER_AVAILABLE #include "utils/log.hpp" namespace devilution { extern "C" { -#ifdef DEVILUTIONX_STORM_FILE_WRAPPER_IMPL_FOPENCOOKIE +#ifdef DEVILUTIONX_SDL_RWOPS_FILE_WRAPPER_IMPL_FOPENCOOKIE -ssize_t SFileCookieRead(void *cookie, char *buf, size_t nbytes) +ssize_t SDL_RWops_CookieRead(void *cookie, char *buf, size_t nbytes) { size_t numRead = SDL_RWread(static_cast(cookie), buf, nbytes, 1); if (numRead == 0) { @@ -18,7 +18,7 @@ ssize_t SFileCookieRead(void *cookie, char *buf, size_t nbytes) return numRead * nbytes; } -int SFileCookieSeek(void *cookie, off64_t *pos, int whence) +int SDL_RWops_CookieSeek(void *cookie, off64_t *pos, int whence) { int swhence; switch (whence) { @@ -36,14 +36,14 @@ int SFileCookieSeek(void *cookie, off64_t *pos, int whence) } const Sint64 spos = SDL_RWseek(static_cast(cookie), *pos, swhence); if (spos < 0) { - Log("SFileRwSeek error: {}", SDL_GetError()); + Log("SDL_RWops_RwSeek error: {}", SDL_GetError()); return -1; } *pos = static_cast(spos); return 0; } -int SFileCookieClose(void *cookie) +int SDL_RWops_CookieClose(void *cookie) { return SDL_RWclose(static_cast(cookie)); } @@ -51,14 +51,14 @@ int SFileCookieClose(void *cookie) } // extern "C" #endif -FILE *FILE_FromStormHandle(SDL_RWops *handle) +FILE *FILE_FromSDL_RWops(SDL_RWops *handle) { -#ifdef DEVILUTIONX_STORM_FILE_WRAPPER_IMPL_FOPENCOOKIE +#ifdef DEVILUTIONX_SDL_RWOPS_FILE_WRAPPER_IMPL_FOPENCOOKIE cookie_io_functions_t ioFns; std::memset(&ioFns, 0, sizeof(ioFns)); - ioFns.read = &SFileCookieRead; - ioFns.seek = &SFileCookieSeek; - ioFns.close = &SFileCookieClose; + ioFns.read = &SDL_RWops_CookieRead; + ioFns.seek = &SDL_RWops_CookieSeek; + ioFns.close = &SDL_RWops_CookieClose; return fopencookie(handle, "rb", ioFns); #else #error "unimplemented" diff --git a/Source/storm/storm_file_wrapper.h b/Source/utils/sdl_rwops_file_wrapper.hpp similarity index 59% rename from Source/storm/storm_file_wrapper.h rename to Source/utils/sdl_rwops_file_wrapper.hpp index 76184694..b6984268 100644 --- a/Source/storm/storm_file_wrapper.h +++ b/Source/utils/sdl_rwops_file_wrapper.hpp @@ -3,14 +3,14 @@ #if (defined(__linux__) && !defined(__ANDROID__)) || defined(__FreeBSD__) || defined(__DragonFly__) || defined(__HAIKU__) #include -#include "storm/storm_sdl_rw.h" +#include -#define DEVILUTIONX_STORM_FILE_WRAPPER_AVAILABLE -#define DEVILUTIONX_STORM_FILE_WRAPPER_IMPL_FOPENCOOKIE +#define DEVILUTIONX_SDL_RWOPS_FILE_WRAPPER_AVAILABLE +#define DEVILUTIONX_SDL_RWOPS_FILE_WRAPPER_IMPL_FOPENCOOKIE namespace devilution { -FILE *FILE_FromStormHandle(SDL_RWops *handle); +FILE *FILE_FromSDL_RWops(SDL_RWops *handle); } // namespace devilution diff --git a/Source/utils/soundsample.cpp b/Source/utils/soundsample.cpp index d037df26..618497b1 100644 --- a/Source/utils/soundsample.cpp +++ b/Source/utils/soundsample.cpp @@ -14,7 +14,7 @@ #endif #include "options.h" -#include "storm/storm_sdl_rw.h" +#include "engine/game_assets.hpp" #include "utils/log.hpp" #include "utils/math.h" #include "utils/stubs.h" @@ -116,9 +116,9 @@ void SoundSample::Stop() int SoundSample::SetChunkStream(std::string filePath) { file_path_ = std::move(filePath); - SDL_RWops *handle = SFileOpenRw(file_path_.c_str()); + SDL_RWops *handle = OpenAsset(file_path_.c_str(), /*threadsafe=*/true); if (handle == nullptr) { - LogError(LogCategory::Audio, "SFileOpenRw failed (from SoundSample::SetChunkStream): {}", SDL_GetError()); + LogError(LogCategory::Audio, "OpenAsset failed (from SoundSample::SetChunkStream): {}", SDL_GetError()); return -1; } diff --git a/android-project/CMake/android_defs.cmake b/android-project/CMake/android_defs.cmake index 3f0b0c38..779910d3 100644 --- a/android-project/CMake/android_defs.cmake +++ b/android-project/CMake/android_defs.cmake @@ -3,6 +3,7 @@ set(ASAN OFF) set(UBSAN OFF) set(DEVILUTIONX_SYSTEM_LIBSODIUM OFF) set(DEVILUTIONX_SYSTEM_LIBPNG OFF) +set(DEVILUTIONX_SYSTEM_BZIP2 OFF) set(VIRTUAL_GAMEPAD ON) if(BINARY_RELEASE OR CMAKE_BUILD_TYPE STREQUAL "Release") diff --git a/docs/building.md b/docs/building.md index 84b9fbab..8e75d132 100644 --- a/docs/building.md +++ b/docs/building.md @@ -11,7 +11,7 @@ Note that ```pkg-config``` is an optional dependency for finding libsodium, alth ### Installing dependencies on Debian and Ubuntu ``` -sudo apt-get install cmake g++ libsdl2-dev libsodium-dev libpng-dev +sudo apt-get install cmake g++ libsdl2-dev libsodium-dev libpng-dev libbz2-dev ``` ### If you want to build the translations (optional) ``` @@ -23,7 +23,7 @@ sudo apt-get install smpq ``` ### Installing dependencies on Fedora ``` -sudo dnf install cmake gcc-c++ glibc-devel SDL2-devel libsodium-devel libpng-devel libasan libubsan +sudo dnf install cmake gcc-c++ glibc-devel SDL2-devel libsodium-devel libpng-devel bzip2-devel libasan libubsan ``` ### Compiling ``` @@ -48,7 +48,7 @@ cmake --build . -j $(sysctl -n hw.physicalcpu) ### Installing dependencies ``` -pkg install cmake sdl2 libsodium libpng +pkg install cmake sdl2 libsodium libpng bzip2 ``` ### Compiling ``` @@ -61,7 +61,7 @@ cmake --build . -j $(sysctl -n hw.ncpu) ### Installing dependencies ``` -pkgin install cmake SDL2 libsodium libpng +pkgin install cmake SDL2 libsodium libpng bzip2 ``` ### Compiling ``` @@ -75,7 +75,7 @@ cmake --build . -j $(sysctl -n hw.ncpu) ### Installing dependencies ``` -pkg_add cmake sdl2 libsodium libpng gmake +pkg_add cmake sdl2 libsodium libpng bzip2 gmake ``` ### Compiling ``` @@ -237,11 +237,11 @@ make ### Installing dependencies on 32 bit Haiku ``` -pkgman install cmake_x86 devel:libsdl2_x86 devel:libsodium_x86 devel:libpng_x86 +pkgman install cmake_x86 devel:libsdl2_x86 devel:libsodium_x86 devel:libpng_x86 devel:bzip2_x86 ``` ### Installing dependencies on 64 bit Haiku ``` -pkgman install cmake devel:libsdl2 devel:libsodium devel:libpng +pkgman install cmake devel:libsdl2 devel:libsodium devel:libpng devel:bzip2 ``` ### Compiling on 32 bit Haiku ``` diff --git a/test/pack_test.cpp b/test/pack_test.cpp index 338d3d40..07f9de2b 100644 --- a/test/pack_test.cpp +++ b/test/pack_test.cpp @@ -3,7 +3,6 @@ #include "pack.h" #include "utils/paths.h" -#include "storm/storm.h" using namespace devilution; diff --git a/vcpkg.json b/vcpkg.json index 09cb6263..be90d750 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -1,8 +1,9 @@ { "name": "devilutionx", - "version-string": "1.2.1", + "version-string": "1.3.0", "dependencies": [ - "fmt" + "fmt", + "bzip2" ], "features": { "sdl1": {