From 40d58978e10864504c9804575d7633e77dcc9b49 Mon Sep 17 00:00:00 2001 From: Adam Heinermann Date: Thu, 15 Jan 2026 19:24:18 -0800 Subject: [PATCH] chore(file): add tests for SFile functions --- storm/Error.hpp | 10 +- storm/File.hpp | 72 ++++- test/CMakeLists.txt | 12 + test/File.cpp | 401 ++++++++++++++++++++++++++++ test/FileTest.cpp | 33 +++ test/FileTest.hpp | 29 ++ test/fixture/bad_headertoosmall.mpq | Bin 0 -> 229 bytes test/fixture/bad_nomagic.mpq | Bin 0 -> 229 bytes test/fixture/bad_toosmall.mpq | Bin 0 -> 30 bytes test/fixture/bad_wrongsizes.mpq | Bin 0 -> 229 bytes test/fixture/broken4.mpq | Bin 0 -> 229 bytes test/fixture/directorytest/.gitkeep | 0 test/fixture/empty_diskonly.txt | 0 test/fixture/test.txt | 1 + test/fixture/test_diskonly.txt | 1 + test/fixture/wowtest1.mpq | Bin 0 -> 487 bytes test/fixture/wowtest2.mpq | Bin 0 -> 572 bytes test/fixture/wowtest3.mpq | Bin 0 -> 340 bytes test/stormdll/storm.def | 16 +- test/stormdll/stormstubs.cpp | 10 + 20 files changed, 567 insertions(+), 18 deletions(-) create mode 100644 test/File.cpp create mode 100644 test/FileTest.cpp create mode 100644 test/FileTest.hpp create mode 100644 test/fixture/bad_headertoosmall.mpq create mode 100644 test/fixture/bad_nomagic.mpq create mode 100644 test/fixture/bad_toosmall.mpq create mode 100644 test/fixture/bad_wrongsizes.mpq create mode 100644 test/fixture/broken4.mpq create mode 100644 test/fixture/directorytest/.gitkeep create mode 100644 test/fixture/empty_diskonly.txt create mode 100644 test/fixture/test.txt create mode 100644 test/fixture/test_diskonly.txt create mode 100644 test/fixture/wowtest1.mpq create mode 100644 test/fixture/wowtest2.mpq create mode 100644 test/fixture/wowtest3.mpq diff --git a/storm/Error.hpp b/storm/Error.hpp index 1080e0c..934402c 100644 --- a/storm/Error.hpp +++ b/storm/Error.hpp @@ -10,10 +10,12 @@ #endif #if defined(WHOA_SYSTEM_MAC) || defined(WHOA_SYSTEM_LINUX) -#define ERROR_SUCCESS 0x0 -#define ERROR_INVALID_HANDLE 0x6 -#define ERROR_NOT_ENOUGH_MEMORY 0x8 -#define ERROR_INVALID_PARAMETER 0x57 +#define ERROR_SUCCESS 0 +#define ERROR_FILE_NOT_FOUND 2 +#define ERROR_INVALID_HANDLE 6 +#define ERROR_NOT_ENOUGH_MEMORY 8 +#define ERROR_HANDLE_EOF 38 +#define ERROR_INVALID_PARAMETER 87 #endif [[noreturn]] void STORMCDECL SErrDisplayAppFatal(const char* format, ...); diff --git a/storm/File.hpp b/storm/File.hpp index 481b959..7f80da7 100644 --- a/storm/File.hpp +++ b/storm/File.hpp @@ -12,18 +12,78 @@ DECLARE_STORM_HANDLE(HSARCHIVE); DECLARE_STORM_HANDLE(HSFILE); -int32_t STORMAPI SFileOpenArchive(const char* archivename, int32_t priority, uint32_t flags, HSARCHIVE* handle); +#define SFILE_ARCHIVE_READ_FROM_CD_ONLY 0x000001 +#define SFILE_ARCHIVE_ENABLE_OVERLAPPED 0x000002 +#define SFILE_ARCHIVE_DONT_CHECKDISK 0x000004 +#define SFILE_ARCHIVE_DONT_SEARCH 0x000008 +#define SFILE_ARCHIVE_ARC4_DECRYPT 0x000010 +#define SFILE_ARCHIVE_DECRYPTION_TYPES 0x000010 +#define SFILE_ARCHIVE_WRITE_PERMISSION 0x000010 +#define SFILE_ARCHIVE_OPEN_LAST_ARCHIVE 0x000020 +#define SFILE_ARCHIVE_LOAD_MD5_VALUES 0x000040 +#define SFILE_ARCHIVE_LOAD_CRC_VALUES 0x000080 +#define SFILE_ARCHIVE_LOAD_TIMESTAMPS 0x000100 +#define SFILE_ARCHIVE_CHECK_MD5_VALUES 0x000200 +#define SFILE_ARCHIVE_USE_NEW_BLOCK_HASH_FORMAT 0x000400 +#define SFILE_ARCHIVE_INIT_TABLE_ON_OPEN 0x000800 +#define SFILE_ARCHIVE_LOAD_DELTA_VALUES 0x001000 +#define SFILE_ARCHIVE_LOAD_DELTA_AS_RAW 0x002000 +#define SFILE_ARCHIVE_DONT_TRUNCATE 0x004000 +#define SFILE_ARCHIVE_DISK_DELETE_CAN_FAIL 0x010000 +#define SFILE_ARCHIVE_SC1161_PERMISSIVE 0x020000 + +#define SFILE_OPENFLAG_CHECKDISK 1 +#define SFILE_OPENFLAG_CHECKDISK_NOPATH 2 +#define SFILE_OPENFLAG_NATIVEHANDLE 4 // made up +#define SFILE_OPENFLAG_PERM_SHARED_WRITE 0x8000 +#define SFILE_OPENFLAG_PRESERVE_PATH_SEPARATORS 0x10000 + +#define SFILE_BEGIN 0 +#define SFILE_CURRENT 1 +#define SFILE_END 2 + +/* // Leaving as documentation + +#define SFILE_AUTH_UNABLETOAUTHENTICATE 0 +#define SFILE_AUTH_NOSIGNATURE 1 +#define SFILE_AUTH_BADSIGNATURE 2 +#define SFILE_AUTH_UNKNOWNSIGNATURE 3 +#define SFILE_AUTH_FIRSTAUTHENTIC 5 +#define SFILE_AUTH_AUTHENTICBLIZZARD 5 + +#define SFILE_DIRECT_ENABLE_RELATIVE 1 +#define SFILE_DIRECT_ENABLE_NOPATH 2 + +#define SFILE_PLATFORM_ANY 0 +#define SFILE_PLATFORM_WIN32 1 +#define SFILE_PLATFORM_MAC 2 + +enum SARCHIVE_TYPE { + SARCHIVE_MPQ, + SARCHIVE_ZIP, +}; + +enum SFILE_TYPE { + SFILE_PLAIN, + SFILE_COMPRESSED, + SFILE_PAQ, + SFILE_OLD_SFILE, + SFILE_ZIP_FILE, +}; +*/ int32_t STORMAPI SFileCloseArchive(HSARCHIVE handle); +int32_t STORMAPI SFileCloseFile(HSFILE handle); + +uint32_t STORMAPI SFileGetFileSize(HSFILE handle, uint32_t* filesizehigh = nullptr); + +int32_t STORMAPI SFileOpenArchive(const char* archivename, int32_t priority, uint32_t flags, HSARCHIVE* handle); + int32_t STORMAPI SFileOpenFileEx(HSARCHIVE archivehandle, const char* filename, uint32_t flags, HSFILE* handle); -int32_t STORMAPI SFileReadFile(HSFILE handle, void* buffer, uint32_t bytestoread, uint32_t* bytesread, LPOVERLAPPED overlapped); - -uint32_t STORMAPI SFileGetFileSize(HSFILE handle, uint32_t* filesizehigh); +int32_t STORMAPI SFileReadFile(HSFILE handle, void* buffer, uint32_t bytestoread, uint32_t* bytesread = nullptr, LPOVERLAPPED overlapped = nullptr); uint32_t STORMAPI SFileSetFilePointer(HSFILE handle, int32_t distancetomove, int32_t* distancetomovehigh, uint32_t movemethod); -int32_t STORMAPI SFileCloseFile(HSFILE handle); - #endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 351b648..9bca269 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,6 +6,8 @@ if(WHOA_TEST_STORMDLL) Error.cpp Event.cpp EventTest.cpp + File.cpp + FileTest.cpp Memory.cpp Region.cpp String.cpp @@ -24,6 +26,8 @@ else() ) endif() +file(GLOB TEST_FILES "fixture/*") + if(WHOA_SYSTEM_MAC) set_source_files_properties(${TEST_SOURCES} PROPERTIES COMPILE_FLAGS "-x objective-c++" @@ -54,6 +58,14 @@ target_include_directories(StormTest ${PROJECT_SOURCE_DIR} ) +file(GLOB TEST_FILES "fixture/*") + +add_custom_target(copy_test_files ALL + COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${TEST_FILES} + $ +) + # Debug build options if(CMAKE_BUILD_TYPE STREQUAL "Debug") # GCC debug build options diff --git a/test/File.cpp b/test/File.cpp new file mode 100644 index 0000000..a09d8bb --- /dev/null +++ b/test/File.cpp @@ -0,0 +1,401 @@ +#include "FileTest.hpp" + +// most of these only pass against storm.dll for now +#if defined(WHOA_TEST_STORMDLL) +TEST_CASE("SFileCloseArchive", "[file]") { + HSARCHIVE archive = nullptr; + + SECTION("closes an archive") { + SFileOpenArchive("wowtest1.mpq", 0, 0, &archive); + REQUIRE(archive != nullptr); + + HSFILE file; + CHECK(SFileOpenFileEx(nullptr, "test.txt", 0, &file)); + CHECK(SFileCloseFile(file)); + + CHECK(SFileCloseArchive(archive) == 1); + + CHECK_FALSE(SFileOpenFileEx(nullptr, "test.txt", 0, &file)); + } + + // TODO determine how to test this + // SECTION("doesn't delete if there is more than one reference") {} +} + +TEST_CASE("SFileCloseFile", "[file]") { + HSFILE file = nullptr; + + SECTION("closes a MPQ file") { + file = ReadTestFileFromMpq(); + CHECK(SFileCloseFile(file) == 1); + } + + SECTION("closes a filesystem file") { + file = ReadTestFileFromDisk(); + CHECK(SFileCloseFile(file) == 1); + } + + // TODO determine how to test this + // SECTION("doesn't delete if there is more than one reference") {} +} + +TEST_CASE("SFileGetFileSize", "[file]") { + HSARCHIVE archive; + HSFILE file; + + SECTION("retrieves MPQ file size") { + SFileOpenArchive("wowtest1.mpq", 0, 0, &archive); + + REQUIRE(SFileOpenFileEx(archive, "test2.txt", 0, &file)); + CHECK(SFileGetFileSize(file) == 13); + + REQUIRE(SFileOpenFileEx(archive, "empty.txt", 0, &file)); + CHECK(SFileGetFileSize(file) == 0); + + uint32_t filesizehigh = 1234; + REQUIRE(SFileOpenFileEx(archive, "test.txt", 0, &file)); + CHECK(SFileGetFileSize(file, &filesizehigh) == 6); + CHECK(filesizehigh == 0); + } + + SECTION("retrieves filesystem file size") { + REQUIRE(SFileOpenFileEx(nullptr, "empty_diskonly.txt", SFILE_OPENFLAG_CHECKDISK, &file)); + CHECK(SFileGetFileSize(file) == 0); + + uint32_t filesizehigh = 1234; + REQUIRE(SFileOpenFileEx(nullptr, "test_diskonly.txt", SFILE_OPENFLAG_CHECKDISK, &file)); + CHECK(SFileGetFileSize(file, &filesizehigh) == 6); + CHECK(filesizehigh == 0); + } +} + +TEST_CASE("SFileOpenArchive", "[file]") { + HSARCHIVE mpq = nullptr; + SErrSetLastError(ERROR_SUCCESS); + + SECTION("opens a MPQ archive") { + CHECK(SFileOpenArchive("wowtest1.mpq", 0, 0, &mpq) == 1); + CHECK(mpq != nullptr); + } + + // TODO determine how to test this (something about CD ROM drives) + // SECTION("fails if drive is inaccessible") {} + + SECTION("fails if archive file is nonexistent") { + mpq = reinterpret_cast(1234); + CHECK_FALSE(SFileOpenArchive("nice_try.mpq", 0, 0, &mpq)); + + CHECK(mpq == nullptr); + CHECK(SErrGetLastError() == ERROR_SUCCESS); + } + + SECTION("fails if archive file is too small") { + mpq = reinterpret_cast(1234); + CHECK_FALSE(SFileOpenArchive("bad_toosmall.mpq", 0, 0, &mpq)); + CHECK(mpq == nullptr); + + CHECK(SErrGetLastError() == STORM_ERROR_NOT_ARCHIVE); + } + + SECTION("fails if using a directory") { + mpq = reinterpret_cast(1234); + CHECK_FALSE(SFileOpenArchive("directorytest", 0, 0, &mpq)); + CHECK(mpq == nullptr); + + CHECK(SErrGetLastError() == ERROR_SUCCESS); + } + + SECTION("fails if archive header magic doesn't match") { + mpq = reinterpret_cast(1234); + CHECK_FALSE(SFileOpenArchive("bad_nomagic.mpq", 0, 0, &mpq)); + CHECK(mpq == nullptr); + + CHECK(SErrGetLastError() == STORM_ERROR_NOT_ARCHIVE); + } + + SECTION("fails if archive header size doesn't match") { + mpq = reinterpret_cast(1234); + CHECK_FALSE(SFileOpenArchive("bad_headertoosmall.mpq", 0, 0, &mpq)); + CHECK(mpq == nullptr); + + CHECK(SErrGetLastError() == STORM_ERROR_NOT_ARCHIVE); + } +} + +TEST_CASE("SFileOpenFileEx", "[file]") { + SECTION("shared testcases") { + OpenFileTestCase testcase = GENERATE(OpenFromDiskCase, OpenFromMPQCase); + + INFO(testcase.info); + HSARCHIVE archive = testcase.OpenArchiveFn(); + HSFILE file = nullptr; + + SECTION("opens a file") { + CHECK(SFileOpenFileEx(archive, "test.txt", testcase.flags, &file) == 1); + CHECK(file != nullptr); + CHECK(file != reinterpret_cast(1234)); + } + + SECTION("fails if file not found") { + SErrSetLastError(ERROR_SUCCESS); + file = reinterpret_cast(1234); + CHECK_FALSE(SFileOpenFileEx(archive, "nice try buddy but your file is in another castle", testcase.flags, &file)); + CHECK(file == nullptr); + CHECK(SErrGetLastError() == ERROR_FILE_NOT_FOUND); + } + } + + SECTION("fails when trying to open a directory") { + SErrSetLastError(ERROR_SUCCESS); + HSFILE file = reinterpret_cast(1234); + CHECK_FALSE(SFileOpenFileEx(nullptr, "directorytest", SFILE_OPENFLAG_CHECKDISK, &file)); + CHECK(file == nullptr); + CHECK(SErrGetLastError() == ERROR_FILE_NOT_FOUND); + } + + SECTION("mpq testcases") { + HSARCHIVE mpq1, mpq2, mpq3; + HSFILE file; + + SECTION("opens the highest priority file from all MPQs") { + SFileOpenArchive("wowtest1.mpq", 100, 0, &mpq1); + SFileOpenArchive("wowtest2.mpq", 500, 0, &mpq2); + SFileOpenArchive("wowtest3.mpq", 400, 0, &mpq3); + + CHECK(SFileOpenFileEx(nullptr, "test.txt", 0, &file) == 1); + + char result[16] = {}; + SFileReadFile(file, result, sizeof(result)); + + CHECK(std::string(result) == "skibbidy"); + } + + SECTION("opens the most recently opened file from same priority MPQs") { + SFileOpenArchive("wowtest1.mpq", 100, 0, &mpq1); + SFileOpenArchive("wowtest2.mpq", 500, 0, &mpq2); + SFileOpenArchive("wowtest3.mpq", 500, 0, &mpq3); + + CHECK(SFileOpenFileEx(nullptr, "test.txt", 0, &file) == 1); + + char result[16] = {}; + SFileReadFile(file, result, sizeof(result)); + + CHECK(std::string(result) == "rizz"); + } + + // TODO implement after adding SFileSetLocale + // SECTION("opens the file for the currently selected locale") {} + + SECTION("fails if file not found in target MPQ") { + SFileOpenArchive("wowtest1.mpq", 100, 0, &mpq1); + SFileOpenArchive("wowtest2.mpq", 500, 0, &mpq2); + SFileOpenArchive("wowtest3.mpq", 400, 0, &mpq3); + + CHECK(SFileOpenFileEx(nullptr, "test2.txt", 0, &file) == 1); + SFileCloseFile(file); + + SErrSetLastError(ERROR_SUCCESS); + CHECK_FALSE(SFileOpenFileEx(mpq3, "test2.txt", 0, &file)); + CHECK(SErrGetLastError() == ERROR_FILE_NOT_FOUND); + } + + SECTION("fails if file not found in any MPQ") { + SFileOpenArchive("wowtest1.mpq", 100, 0, &mpq1); + SFileOpenArchive("wowtest2.mpq", 500, 0, &mpq2); + SFileOpenArchive("wowtest3.mpq", 400, 0, &mpq3); + + SErrSetLastError(ERROR_SUCCESS); + CHECK_FALSE(SFileOpenFileEx(nullptr, "yep not here", 0, &file)); + CHECK(SErrGetLastError() == ERROR_FILE_NOT_FOUND); + } + + SECTION("can open attributes file") { + SFileOpenArchive("wowtest1.mpq", 100, 0, &mpq1); + CHECK(SFileOpenFileEx(nullptr, "(attributes)", 0, &file) == 1); + } + + SECTION("can open listfile") { + SFileOpenArchive("wowtest1.mpq", 100, 0, &mpq1); + CHECK(SFileOpenFileEx(nullptr, "(listfile)", 0, &file) == 1); + } + } +} + +TEST_CASE("SFileReadFile", "[file]") { + SECTION("shared testcases") { + ReadFileTestCase testcase = GENERATE(ReadFromDiskCase, ReadFromMPQCase); + + INFO(testcase.info); + HSFILE file = testcase.OpenFileFn(); + REQUIRE(file != nullptr); + + SECTION("reads a file") { + char buffer[32] = {}; + CHECK(SFileReadFile(file, buffer, 6, nullptr, nullptr) == 1); + CHECK(std::string(buffer) == "catdog"); + } + + SECTION("reads partial file if bytestoread is too small") { + char buffer[4] = ""; + CHECK(SFileReadFile(file, &buffer, 3, nullptr, nullptr) == 1); + CHECK(std::string(buffer) == "cat"); + } + + SECTION("continues reading from the last stored position") { + char buffer[4] = ""; + CHECK(SFileReadFile(file, &buffer, 3, nullptr, nullptr) == 1); + CHECK(SFileReadFile(file, &buffer, 3, nullptr, nullptr) == 1); + CHECK(std::string(buffer) == "dog"); + } + + SECTION("continues reading from an explicitly set position") { + char buffer[4] = ""; + SFileSetFilePointer(file, 3, nullptr, SFILE_BEGIN); + CHECK(SFileReadFile(file, &buffer, 3, nullptr, nullptr) == 1); + CHECK(std::string(buffer) == "dog"); + } + + SECTION("succeeds if bytestoread is 0") { + char buffer; + CHECK(SFileReadFile(file, &buffer, 0, nullptr, nullptr) == 1); + + uint32_t read = 42; + CHECK(SFileReadFile(file, &buffer, 0, &read, nullptr) == 1); + CHECK(read == 0); + } + + SECTION("succeeds if bytestoread is 0 past eof") { + char buffer[8]; + CHECK_FALSE(SFileReadFile(file, &buffer, 8, nullptr, nullptr)); + + uint32_t read = 42; + CHECK(SFileReadFile(file, &buffer, 0, &read, nullptr) == 1); + CHECK(read == 0); + } + + SECTION("fails when reading past end of file") { + char buffer[8] = ""; + CHECK(SFileReadFile(file, &buffer, 6, nullptr, nullptr) == 1); + CHECK(std::string(buffer) == "catdog"); + + SErrSetLastError(0); + CHECK_FALSE(SFileReadFile(file, &buffer, 1, nullptr, nullptr)); + CHECK(SErrGetLastError() == testcase.eofcode); + + CHECK(std::string(buffer) == "catdog"); + } + + SECTION("fails if bytestoread is larger than file size when reading from disk") { + char buffer[32] = ""; + + SErrSetLastError(0); + CHECK_FALSE(SFileReadFile(file, buffer, sizeof(buffer), nullptr, nullptr)); + CHECK(SErrGetLastError() == testcase.eofcode); + + CHECK(std::string(buffer) == "catdog"); + } + } +} + +TEST_CASE("SFileSetFilePointer", "[file]") { + SECTION("shared testcases") { + ReadFileTestCase testcase = GENERATE(ReadFromDiskCase, ReadFromMPQCase); + + INFO(testcase.info); + HSFILE file = testcase.OpenFileFn(); + REQUIRE(file != nullptr); + + SECTION("sets position from beginning") { + CHECK(SFileSetFilePointer(file, 2, nullptr, SFILE_BEGIN) == 2); + CHECK(SFileSetFilePointer(file, 0, nullptr, SFILE_BEGIN) == 0); + CHECK(SFileSetFilePointer(file, 5, nullptr, SFILE_BEGIN) == 5); + } + + SECTION("returns the cursor position") { + CHECK(SFileSetFilePointer(file, 2, nullptr, SFILE_BEGIN) == 2); + CHECK(SFileSetFilePointer(file, 1, nullptr, SFILE_BEGIN) == 1); + CHECK(SFileSetFilePointer(file, 5, nullptr, SFILE_BEGIN) == 5); + CHECK(SFileSetFilePointer(file, -4, nullptr, SFILE_CURRENT) == 1); + CHECK(SFileSetFilePointer(file, 0, nullptr, SFILE_BEGIN) == 0); + } + + SECTION("sets position from current") { + CHECK(SFileSetFilePointer(file, 0, nullptr, SFILE_CURRENT) == 0); + CHECK(SFileSetFilePointer(file, 1, nullptr, SFILE_CURRENT) == 1); + CHECK(SFileSetFilePointer(file, 1, nullptr, SFILE_CURRENT) == 2); + CHECK(SFileSetFilePointer(file, 1, nullptr, SFILE_CURRENT) == 3); + CHECK(SFileSetFilePointer(file, 0, nullptr, SFILE_CURRENT) == 3); + CHECK(SFileSetFilePointer(file, -2, nullptr, SFILE_CURRENT) == 1); + CHECK(SFileSetFilePointer(file, -1, nullptr, SFILE_CURRENT) == 0); + } + } + + SECTION("cases from filesystem") { + // Probably Windows specific, return values are directly from WinAPI calls + // TODO compare with Mac build results + HSFILE file = ReadTestFileFromDisk(); + REQUIRE(file != nullptr); + + SECTION("sets position from beginning out of bounds") { + CHECK(SFileSetFilePointer(file, -1, nullptr, SFILE_BEGIN) == -1); + CHECK(SFileSetFilePointer(file, -100, nullptr, SFILE_BEGIN) == -1); + CHECK(SFileSetFilePointer(file, 6, nullptr, SFILE_BEGIN) == 6); + CHECK(SFileSetFilePointer(file, 100, nullptr, SFILE_BEGIN) == 100); + } + + SECTION("sets position from current out of bounds") { + CHECK(SFileSetFilePointer(file, -1, nullptr, SFILE_CURRENT) == -1); + CHECK(SFileSetFilePointer(file, -100, nullptr, SFILE_CURRENT) == -1); + CHECK(SFileSetFilePointer(file, 106, nullptr, SFILE_CURRENT) == 106); + CHECK(SFileSetFilePointer(file, 100, nullptr, SFILE_CURRENT) == 206); + } + + SECTION("sets position from end") { + CHECK(SFileSetFilePointer(file, -5, nullptr, SFILE_END) == 1); + CHECK(SFileSetFilePointer(file, -2, nullptr, SFILE_END) == 4); + CHECK(SFileSetFilePointer(file, -1, nullptr, SFILE_END) == 5); + CHECK(SFileSetFilePointer(file, 0, nullptr, SFILE_END) == 6); + } + + SECTION("sets position from end out of bounds") { + CHECK(SFileSetFilePointer(file, -100, nullptr, SFILE_END) == -1); + CHECK(SFileSetFilePointer(file, -10, nullptr, SFILE_END) == -1); + CHECK(SFileSetFilePointer(file, 1, nullptr, SFILE_END) == 7); + CHECK(SFileSetFilePointer(file, 5, nullptr, SFILE_END) == 11); + } + } + + SECTION("cases from MPQ") { + HSFILE file = ReadTestFileFromMpq(); + REQUIRE(file != nullptr); + + SECTION("sets position from beginning out of bounds") { + CHECK(SFileSetFilePointer(file, -1, nullptr, SFILE_BEGIN) == 5); + CHECK(SFileSetFilePointer(file, -100, nullptr, SFILE_BEGIN) == 5); + CHECK(SFileSetFilePointer(file, 6, nullptr, SFILE_BEGIN) == 5); + CHECK(SFileSetFilePointer(file, 100, nullptr, SFILE_BEGIN) == 5); + } + + SECTION("sets position from current out of bounds") { + CHECK(SFileSetFilePointer(file, -1, nullptr, SFILE_CURRENT) == 0); + CHECK(SFileSetFilePointer(file, -100, nullptr, SFILE_CURRENT) == 0); + CHECK(SFileSetFilePointer(file, 106, nullptr, SFILE_CURRENT) == 5); + CHECK(SFileSetFilePointer(file, 100, nullptr, SFILE_CURRENT) == 5); + } + + SECTION("sets position from end") { + CHECK(SFileSetFilePointer(file, -5, nullptr, SFILE_END) == 1); + CHECK(SFileSetFilePointer(file, -2, nullptr, SFILE_END) == 4); + CHECK(SFileSetFilePointer(file, -1, nullptr, SFILE_END) == 5); + CHECK(SFileSetFilePointer(file, 0, nullptr, SFILE_END) == 5); + } + + SECTION("sets position from end out of bounds") { + CHECK(SFileSetFilePointer(file, -100, nullptr, SFILE_END) == 0); + CHECK(SFileSetFilePointer(file, -10, nullptr, SFILE_END) == 0); + CHECK(SFileSetFilePointer(file, 1, nullptr, SFILE_END) == 5); + CHECK(SFileSetFilePointer(file, 5, nullptr, SFILE_END) == 5); + } + } +} +#endif diff --git a/test/FileTest.cpp b/test/FileTest.cpp new file mode 100644 index 0000000..00b3346 --- /dev/null +++ b/test/FileTest.cpp @@ -0,0 +1,33 @@ +#include "FileTest.hpp" + +HSARCHIVE OpenNullArchive() { + return nullptr; +} + +HSARCHIVE OpenTestArchive() { + HSARCHIVE archive = nullptr; + SFileOpenArchive("wowtest1.mpq", 0, 0, &archive); + REQUIRE(archive != nullptr); + return archive; +} + +HSFILE ReadTestFileFromDisk() { + HSFILE file = nullptr; + SFileOpenFileEx(nullptr, "test_diskonly.txt", SFILE_OPENFLAG_CHECKDISK, &file); + return file; +} + +HSFILE ReadTestFileFromMpq() { + HSARCHIVE archive = nullptr; + SFileOpenArchive("wowtest1.mpq", 0, 0, &archive); + + HSFILE file = nullptr; + SFileOpenFileEx(archive, "test.txt", 0, &file); + return file; +} + +OpenFileTestCase OpenFromDiskCase{ "file from disk", OpenNullArchive, SFILE_OPENFLAG_CHECKDISK }; +OpenFileTestCase OpenFromMPQCase{ "file from MPQ", OpenTestArchive, 0 }; + +ReadFileTestCase ReadFromDiskCase{ "file from disk", ReadTestFileFromDisk, ERROR_SUCCESS }; +ReadFileTestCase ReadFromMPQCase{ "file from MPQ", ReadTestFileFromMpq, ERROR_HANDLE_EOF }; diff --git a/test/FileTest.hpp b/test/FileTest.hpp new file mode 100644 index 0000000..c536d91 --- /dev/null +++ b/test/FileTest.hpp @@ -0,0 +1,29 @@ +#include "Test.hpp" +#include "storm/Error.hpp" +#include "storm/File.hpp" + +#include + +struct OpenFileTestCase { + std::string info; + HSARCHIVE (*OpenArchiveFn)(); + uint32_t flags; +}; + +struct ReadFileTestCase { + std::string info; + HSFILE (*OpenFileFn)(); + uint32_t eofcode; +}; + +HSARCHIVE OpenNullArchive(); +HSARCHIVE OpenTestArchive(); + +HSFILE ReadTestFileFromDisk(); +HSFILE ReadTestFileFromMpq(); + +extern OpenFileTestCase OpenFromDiskCase; +extern OpenFileTestCase OpenFromMPQCase; + +extern ReadFileTestCase ReadFromDiskCase; +extern ReadFileTestCase ReadFromMPQCase; diff --git a/test/fixture/bad_headertoosmall.mpq b/test/fixture/bad_headertoosmall.mpq new file mode 100644 index 0000000000000000000000000000000000000000..0ebcba0f63c74f765c13c488c22502f4569bb2c4 GIT binary patch literal 229 zcmeYb2$Yg%U|@I(#EcBg46Z=>C=hc1F%u9Y1CW>`5Hm?k^-fGkNJv)z>XdrIkYj9c zxKiV$(TqLC`SUk*_`Uq0GOz9R0^6F>5|f0t$LCD=B%_{^a-~s(GevUlH-{Xyt4B|G z?%T~CDK8e&WcWGsXIV_4472;;f~H=E|J;=pTX|}ir0g?at+LoCf769W)yHmUZC8&M zR4%Bmk}vn)I9>mX`uVGlO}9cD4F0#Btmd{|p6+40_AC=hc1F%u9Y1CW>`5Hm?k^-fGkNJv+ZXJC+e!jNNZ zaJW+ArqPT&#rg9$b@;vfp)#-S^#a?P(h`$|x5wv9_#~sAl5(X{gfm5Q?l*@VwyQ@^ zc<$TH9w{#t(`5KL^k-R2p$xP8;ew`KhX34^7F&60m!#}7U#+s(D1Xz1N7ct}XKh!H z7gR2&uaYnK-#A_Wi~9Mij!m~h8w~!povh}zU7qe?yY@22srWsnKdvq_e&L|GauaV! P;k!F~S1i26k@Elm*%DRo literal 0 HcmV?d00001 diff --git a/test/fixture/bad_toosmall.mpq b/test/fixture/bad_toosmall.mpq new file mode 100644 index 0000000000000000000000000000000000000000..17783b4b6293b03fb760689da4bdee61e0182ecc GIT binary patch literal 30 fcmeYb2$a%cU|@I(#EcBg46Z=>C=hc1F%ts-LE-~4 literal 0 HcmV?d00001 diff --git a/test/fixture/bad_wrongsizes.mpq b/test/fixture/bad_wrongsizes.mpq new file mode 100644 index 0000000000000000000000000000000000000000..1f4814db70953a031603b6e21a3a74e154009508 GIT binary patch literal 229 zcmeYb2$a%cU|@I(#EcBg46Z=>C=hc1F%u9Y1CW>`5Hm?k^-fGkNJv+ZXJC+e!jNNZ zaJW+ArqPT&#rg9$b@;vfp)#-S^#a?P(h`$|x5wv9_#~sAl5(X{gfm5Q?l*@VwyQ@^ zc<$TH9w{#t(`5KL^k-R2p$xP8;ew`KhX34^7F&60m!#}7U#+s(D1Xz1N7ct}XKh!H z7gR2&uaYnK-#A_Wi~9Mij!m~h8w~!povh}zU7qe?yY@22srWsnKdvq_e&L|GauaV! P;k!F~S1i26k@Elm^Q=}5 literal 0 HcmV?d00001 diff --git a/test/fixture/broken4.mpq b/test/fixture/broken4.mpq new file mode 100644 index 0000000000000000000000000000000000000000..1f4814db70953a031603b6e21a3a74e154009508 GIT binary patch literal 229 zcmeYb2$a%cU|@I(#EcBg46Z=>C=hc1F%u9Y1CW>`5Hm?k^-fGkNJv+ZXJC+e!jNNZ zaJW+ArqPT&#rg9$b@;vfp)#-S^#a?P(h`$|x5wv9_#~sAl5(X{gfm5Q?l*@VwyQ@^ zc<$TH9w{#t(`5KL^k-R2p$xP8;ew`KhX34^7F&60m!#}7U#+s(D1Xz1N7ct}XKh!H z7gR2&uaYnK-#A_Wi~9Mij!m~h8w~!povh}zU7qe?yY@22srWsnKdvq_e&L|GauaV! P;k!F~S1i26k@Elm^Q=}5 literal 0 HcmV?d00001 diff --git a/test/fixture/directorytest/.gitkeep b/test/fixture/directorytest/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/test/fixture/empty_diskonly.txt b/test/fixture/empty_diskonly.txt new file mode 100644 index 0000000..e69de29 diff --git a/test/fixture/test.txt b/test/fixture/test.txt new file mode 100644 index 0000000..338cc63 --- /dev/null +++ b/test/fixture/test.txt @@ -0,0 +1 @@ +catdog \ No newline at end of file diff --git a/test/fixture/test_diskonly.txt b/test/fixture/test_diskonly.txt new file mode 100644 index 0000000..338cc63 --- /dev/null +++ b/test/fixture/test_diskonly.txt @@ -0,0 +1 @@ +catdog \ No newline at end of file diff --git a/test/fixture/wowtest1.mpq b/test/fixture/wowtest1.mpq new file mode 100644 index 0000000000000000000000000000000000000000..858b1199355f67da275c9be42c7a7dc31c16f7e1 GIT binary patch literal 487 zcmeYb2$a%cU|@L8$iTqJz|0^Hq^AQh2T+U^h>-ygkRt@dVnCdnSdx;TUc^+v0TLAl zVrd{w%1l>C&M3`GFD>?GSuO#Rk_2K|ATG{L%*laCnz7ze2HBtj#7q)Xz0c}uth}PV zQsd!MEl*uN#|=FSJ`DT3xh{g#R6^AxCL|=VC+)ky+~d%dcyHl+cYUAA2(~2ocU{r@ zw!P(hY<|@HUUg&1sks{6haF}y^3I93diY}6Vj)LgBlYRa&;BeHjIGn&yyWP%-<{81 zsi)oB=EEa2OVjf7t#?Pt8?PUK&bhwso0Dv;EyICmopZ(phbuL18qL^KoIih4hu_PY zFP2y@`G0&-guYX^;r&F}g|k&{${8+vE%x|ZrSiL8gfYS(_~N|@CL6n+Y&vlK-{Xx} zcT6zM_RxN9+4ES@=55_C#S^P@s+(IA+d93fp%O4Vbg3&sMRF*j0xqpj& zO3x(C+uB|R+pe674_cn?VY~Y>$Eo5yrav|=HGb38zH%GeyxO9zs#eP{mut-lyMF2R y^`e{Sr*9Tm@YvBy)}wQ_wx8z?jW%iNstymW?Wrv_g*}c2Gcw&Bo7X=U{R#jz2EIlB literal 0 HcmV?d00001 diff --git a/test/fixture/wowtest2.mpq b/test/fixture/wowtest2.mpq new file mode 100644 index 0000000000000000000000000000000000000000..d88e20c98b7c11155917dc9a2153e5c7358768f7 GIT binary patch literal 572 zcmeYb2$a%cU|_IeVqjooU}lJ6WMH@h#2i2|HXud@JV1^J5K91Yadu`>Qf5kJAoEK; zkhnMyO9OFIX1YRhMrmGpX|X@catV->6cEcnq|#H1V6wfe^E5!RAWiZx+2RtIG!vVS zKFAHSKwOeqT%uP}QNqgwVi`f0Aj!o*%p@_@J24?4f&EeHS*`lMkoNttAI~VCv|NAC zt8VYrtOW*#PnUjWZNK%-VBPea+ZkQH%lQ7|SY-P{

{hCY#BJ*J*Pc7fUz)JiJ$P z`}(dA|M!2d2s35{yQFdwVyS+&3{IH$f=%xcfItJUH)?KzWRNs{{JMzX|v7Z zmZ13Jn%_n2okr$tN6YQPZ(rj5KKH}_<~%zF#y)30V}rw$8aIt*>?zKlzp2CTzG|ZC2Bwb5=_(fBSkO zc+tZzjfs4H0w1=d8m5GX%Gtyr(rc*g&h>)Yl|e%Q9iFvaZFyM6LnFU#l0 zZL~OeJYfHx7U#>$(>-jrUgkJezsK~)*QLg9PE1(2Eqs4n(cS#`&o&<4C+hKovEW;k NN6y)GOSI?x2LM?R literal 0 HcmV?d00001 diff --git a/test/fixture/wowtest3.mpq b/test/fixture/wowtest3.mpq new file mode 100644 index 0000000000000000000000000000000000000000..d1d5fb1ebcf32c85ca95eb3c29ebb89bcf7d0ca0 GIT binary patch literal 340 zcmeYb2$a%cU|-ygkRt%ZB0yY}SygqOv5*C%P6&ug zQj1IUN-9ctxj-U8K+GgD)jKgEA%Xo-e4T2$Uf10S&J-h!cQDd)V&0%yFuGkLi!xrN(bu>Q`>NR8v!Q`nJgO%=gig VjUA0G+tuA~rg52Gzm)US1OVw;gZBUc literal 0 HcmV?d00001 diff --git a/test/stormdll/storm.def b/test/stormdll/storm.def index ff0d3a4..b17c23d 100644 --- a/test/stormdll/storm.def +++ b/test/stormdll/storm.def @@ -87,8 +87,8 @@ EXPORTS ; File ;SFileAuthenticateArchive @251 NONAME - ;SFileCloseArchive @252 NONAME - ;SFileCloseFile @253 NONAME + SFileCloseArchive @252 NONAME + SFileCloseFile @253 NONAME ;SFileDdaBegin @254 NONAME ;SFileDdaBeginEx @255 NONAME ;SFileDdaDestroy @256 NONAME @@ -100,13 +100,13 @@ EXPORTS ;SFileDestroy @262 NONAME ;SFileEnableDirectAccess @263 NONAME ;SFileGetFileArchive @264 NONAME - ;SFileGetFileSize @265 NONAME - ;SFileOpenArchive @266 NONAME + SFileGetFileSize @265 NONAME + SFileOpenArchive @266 NONAME ;SFileOpenFile @267 NONAME - ;SFileOpenFileEx @268 NONAME - ;SFileReadFile @269 NONAME + SFileOpenFileEx @268 NONAME + SFileReadFile @269 NONAME ;SFileSetBasePath @270 NONAME - ;SFileSetFilePointer @271 NONAME + SFileSetFilePointer @271 NONAME ;SFileSetLocale @272 NONAME ;SFileGetBasePath @273 NONAME ;SFileSetIoErrorMode @274 NONAME @@ -280,7 +280,7 @@ EXPORTS ;SErrGetErrorStr @462 NONAME SErrGetLastError @463 NONAME ;SErrRegisterMessageSource @464 NONAME - ;SErrSetLastError @465 NONAME + SErrSetLastError @465 NONAME ;SErrReportNamedResourceLeak @466 NONAME ;SErrReportResourceLeak @467 NONAME SErrSuppressErrors @468 NONAME diff --git a/test/stormdll/stormstubs.cpp b/test/stormdll/stormstubs.cpp index 9bee1a2..b0b6852 100644 --- a/test/stormdll/stormstubs.cpp +++ b/test/stormdll/stormstubs.cpp @@ -55,6 +55,16 @@ int32_t STORMAPI SEvtRegisterHandler(uint32_t, uint32_t, uint32_t, uint32_t, SEV int32_t STORMAPI SEvtUnregisterHandler(uint32_t, uint32_t, uint32_t, SEVTHANDLER) { return 0; } int32_t STORMAPI SEvtUnregisterType(uint32_t, uint32_t) { return 0; } +#include + +int32_t STORMAPI SFileCloseArchive(HSARCHIVE) { return 0; } +int32_t STORMAPI SFileCloseFile(HSFILE) { return 0; } +uint32_t STORMAPI SFileGetFileSize(HSFILE, uint32_t*) { return 0; } +int32_t STORMAPI SFileOpenArchive(const char*, int32_t, uint32_t, HSARCHIVE*) { return 0; } +int32_t STORMAPI SFileOpenFileEx(HSARCHIVE, const char*, uint32_t, HSFILE*) { return 0; } +int32_t STORMAPI SFileReadFile(HSFILE, void*, uint32_t, uint32_t*, LPOVERLAPPED) { return 0; } +uint32_t STORMAPI SFileSetFilePointer(HSFILE, int32_t, int32_t*, uint32_t) { return 0; } + #include void* STORMAPI SMemAlloc(size_t, const char*, int32_t, uint32_t) { return 0; }