From fd75da92ddb71bb75dd00f420502137cdb506534 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sat, 13 Jul 2024 01:53:38 +0100 Subject: [PATCH 01/33] Add Sega Dreamcast port --- CMake/Platforms.cmake | 4 ++ CMake/platforms/dreamcast.cmake | 53 +++++++++++++++++ Dockerfile | 79 ++++++++++++++++++++++++++ Source/appfat.h | 2 +- Source/diablo.cpp | 6 ++ Source/init.cpp | 5 +- Source/main.cpp | 7 +++ Source/mpq/mpq_reader.cpp | 2 + Source/mpq/mpq_writer.cpp | 14 +++-- Source/pfile.cpp | 18 ++++-- Source/platform/locale.cpp | 2 +- Source/utils/file_util.cpp | 74 +++++++++++++++++++++++- Source/utils/paths.cpp | 2 + Source/utils/sdl2_to_1_2_backports.cpp | 5 ++ 14 files changed, 258 insertions(+), 15 deletions(-) create mode 100644 CMake/platforms/dreamcast.cmake create mode 100644 Dockerfile diff --git a/CMake/Platforms.cmake b/CMake/Platforms.cmake index 1c3d1e927b4..fa0d07d614b 100644 --- a/CMake/Platforms.cmake +++ b/CMake/Platforms.cmake @@ -75,3 +75,7 @@ endif() if(NXDK) include(platforms/xbox_nxdk) endif() + +if(PLATFORM_DREAMCAST) + include(platforms/dreamcast) +endif() diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake new file mode 100644 index 00000000000..12ddb90e8c5 --- /dev/null +++ b/CMake/platforms/dreamcast.cmake @@ -0,0 +1,53 @@ +set(BUILD_TESTING OFF) +set(NONET ON) +set(ASAN OFF) +set(UBSAN OFF) + +set(USE_SDL1 ON) +set(SDL1_VIDEO_MODE_BPP 8) +set(SDL1_VIDEO_MODE_FLAGS SDL_FULLSCREEN|SDL_HWSURFACE) +set(DEFAULT_WIDTH 640) +set(DEFAULT_HEIGHT 480) +set(DEVILUTIONX_GAMEPAD_TYPE Nintendo) + +set(NOSOUND ON) +set(DEVILUTIONX_STATIC_ZLIB ON) +set(UNPACKED_MPQS ON) +set(UNPACKED_SAVES OFF) +set(DEVILUTIONX_SYSTEM_LIBFMT OFF) +set(DEVILUTIONX_STATIC_LUA ON) +set(DEVILUTIONX_DISABLE_STRIP ON) +set(CMAKE_BUILD_TYPE Debug) + +set(DEVILUTIONX_ASSETS_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/data/") +set(BUILD_ASSETS_MPQ OFF) + +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/threads-stub") +list(APPEND DEVILUTIONX_PLATFORM_COMPILE_DEFINITIONS __DREAMCAST__) +add_compile_options(-fpermissive) + +#SDL Joystick axis mapping (circle-pad/C-stick) +set(JOY_AXIS_LEFTX 0) +set(JOY_AXIS_LEFTY 1) +set(JOY_AXIS_RIGHTX 2) +set(JOY_AXIS_RIGHTY 3) +#SDL Joystick hat mapping (D-pad) +set(JOY_HAT_DPAD_UP_HAT 0) +set(JOY_HAT_DPAD_RIGHT_HAT 0) +set(JOY_HAT_DPAD_DOWN_HAT 0) +set(JOY_HAT_DPAD_LEFT_HAT 0) +set(JOY_HAT_DPAD_UP 1) +set(JOY_HAT_DPAD_RIGHT 2) +set(JOY_HAT_DPAD_DOWN 4) +set(JOY_HAT_DPAD_LEFT 8) +#SDL Joystick button mapping (A / B and X / Y inverted) +set(JOY_BUTTON_A 2) +set(JOY_BUTTON_B 1) +set(JOY_BUTTON_X 4) +set(JOY_BUTTON_Y 3) +#set(JOY_BUTTON_LEFTSHOULDER 5) +#set(JOY_BUTTON_RIGHTSHOULDER 6) +set(JOY_BUTTON_BACK 7) +set(JOY_BUTTON_START 0) +set(JOY_BUTTON_TRIGGERLEFT 8) +set(JOY_BUTTON_TRIGGERRIGHT 9) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000000..2107d714bca --- /dev/null +++ b/Dockerfile @@ -0,0 +1,79 @@ +FROM alpine-kallistios + +RUN echo "Installing dependencies..." +RUN apk add cmake g++ sdl2-dev libsodium-dev libpng-dev libbz2 gtest-dev gmock sdl2_image-dev fmt-dev sdl12-compat-dev bzip2-dev vim gperf + +RUN echo "Compiling kos-ports..." +WORKDIR /opt/toolchains/dc/ +RUN git clone https://github.com/KallistiOS/kos-ports +COPY lua.patch /opt/toolchains/dc/kos-ports +RUN patch kos-ports/lua/Makefile -l -p0 < kos-ports/lua.patch +RUN source /opt/toolchains/dc/kos/environ.sh && /opt/toolchains/dc/kos-ports/utils/build-all.sh + +RUN echo "Compiling mkdcdisc to generate .cdi files..." +RUN git clone https://gitlab.com/simulant/mkdcdisc.git +RUN apk add ninja-build meson libisofs-dev +RUN cd mkdcdisc && meson setup builddir && meson compile -C builddir +RUN ./builddir/mkdcdisc -h || true + +RUN echo "Add missing semicolon to bin2c.c..." +COPY bin2c.patch /opt/toolchains/dc/kos/utils/bin2c/ +RUN patch /opt/toolchains/dc/kos/utils/bin2c/bin2c.c -l -p0 < /opt/toolchains/dc/kos/utils/bin2c/bin2c.patch +RUN cd /opt/toolchains/dc/kos/utils/bin2c && make clean && make + +RUN echo "Building unpack_and_minify_mpq..." +RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ + cd devilutionx-mpq-tools && \ + cmake -S. -Bbuild-rel -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && \ + cmake --build build-rel && \ + cmake --install build-rel + +RUN echo "Cloning project again..." +WORKDIR /opt/toolchains/dc/kos/ +RUN git clone -b dreamcast https://github.com/azihassan/devilutionX.git + +#COPY init.patch /opt/toolchains/dc/kos/devilutionX +#RUN patch Source/init.cpp -l -p0 < init.patch +#COPY diablo.patch /opt/toolchains/dc/kos/devilutionX +#RUN patch Source/diablo.cpp -l -p0 < diablo.patch +#COPY main.patch /opt/toolchains/dc/kos/devilutionX +#RUN patch Source/main.cpp -l -p0 < main.patch +#COPY platforms.patch /opt/toolchains/dc/kos/devilutionX +#RUN patch CMake/Platforms.cmake -l -p0 < platforms.patch +#COPY file_util.patch /opt/toolchains/dc/kos/devilutionX +#RUN patch Source/utils/file_util.cpp -l -p0 < file_util.patch +#COPY sdl2_to_1_2_backports.patch /opt/toolchains/dc/kos/devilutionX +#RUN patch Source/utils/sdl2_to_1_2_backports.cpp -l -p0 < sdl2_to_1_2_backports.patch +#COPY paths.patch /opt/toolchains/dc/kos/devilutionX +#RUN patch Source/utils/paths.cpp -l -p0 < paths.patch +#COPY appfat.patch /opt/toolchains/dc/kos/devilutionX +#RUN patch Source/appfat.h -l -p0 < appfat.patch +#COPY CMake/platforms/dreamcast.cmake /opt/toolchains/dc/kos/devilutionX/CMake/platforms + +WORKDIR /opt/toolchains/dc/kos/devilutionX +RUN echo "Injecting spawn.mpq..." +COPY spawn.mpq /opt/toolchains/dc/kos/devilutionX +RUN mkdir /opt/toolchains/dc/kos/devilutionX/data && \ + mv /opt/toolchains/dc/kos/devilutionX/spawn.mpq /opt/toolchains/dc/kos/devilutionX/data/ +RUN cd /opt/toolchains/dc/kos/devilutionX/data/ && unpack_and_minify_mpq + +RUN echo "Compiling..." +RUN source /opt/toolchains/dc/kos/environ.sh && \ + export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ + kos-cmake -S. -Bbuild +COPY libfmt-long-double.patch /opt/toolchains/dc/kos/devilutionX +#COPY locale.patch /opt/toolchains/dc/kos/devilutionX +RUN patch build/_deps/libfmt-src/include/fmt/format.h -l -p0 < libfmt-long-double.patch +#RUN patch Source/platform/locale.cpp -l -p0 < locale.patch + +#COPY CMakeLists.patch /opt/toolchains/dc/kos/devilutionX +#RUN patch CMakeLists.txt -l -p0 < CMakeLists.patch + +RUN source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make + +RUN cp -R data/spawn build/data/spawn + +RUN echo "Generate CDI" +RUN /opt/toolchains/dc/mkdcdisc/builddir/mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ + +ENTRYPOINT ["sh", "-c", "source /opt/toolchains/dc/kos/environ.sh && \"$@\"", "-s"] diff --git a/Source/appfat.h b/Source/appfat.h index 9c98572f7fe..378399254aa 100644 --- a/Source/appfat.h +++ b/Source/appfat.h @@ -20,7 +20,7 @@ namespace devilution { #ifndef _DEBUG #define assert(exp) #else -#define assert(exp) (void)((exp) || (assert_fail(__LINE__, __FILE__, #exp), 0)) +#define assert(exp) (void)((exp) || (devilution::assert_fail(__LINE__, __FILE__, #exp), 0)) #endif /** diff --git a/Source/diablo.cpp b/Source/diablo.cpp index e50b62433bd..7f731298f56 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -1152,7 +1152,12 @@ void CheckArchivesUpToDate() "\n" "Make sure devilutionx.mpq is in the game folder and that it is up to date.")); } else if (fontsMpqOutOfDate) { +#ifdef __DREAMCAST__ + //todo fixme for the dreamcast + Log(_("Please update fonts.mpq to the latest version")); +#else app_fatal(_("Please update fonts.mpq to the latest version")); +#endif } } @@ -2845,6 +2850,7 @@ void DisableInputEventHandler(const SDL_Event &event, uint16_t modState) void LoadGameLevel(bool firstflag, lvl_entry lvldir) { + Log("LoadGameLevel(firstflag = {}, lvldir = {})", firstflag, static_cast(lvldir)); _music_id neededTrack = GetLevelMusic(leveltype); ClearFloatingNumbers(); diff --git a/Source/init.cpp b/Source/init.cpp index 1e6488d58f7..809f14c0237 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -88,8 +88,9 @@ std::optional FindUnpackedMpqData(const std::vector &p targetPath.clear(); targetPath.reserve(path.size() + mpqName.size() + 1); targetPath.append(path).append(mpqName) += DirectorySeparator; + Log("Testing FindUnpackedMpqData {}", targetPath); if (FileExists(targetPath)) { - LogVerbose(" Found unpacked MPQ directory: {}", targetPath); + Log(" Found unpacked MPQ directory: {}", targetPath); return targetPath; } } @@ -276,7 +277,7 @@ void LoadCoreArchives() #ifdef UNPACKED_MPQS font_data_path = FindUnpackedMpqData(paths, "fonts"); #else // !UNPACKED_MPQS -#if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__3DS__) && !defined(__SWITCH__) +#if !defined(__ANDROID__) && !defined(__APPLE__) && !defined(__3DS__) && !defined(__SWITCH__) && !defined(__DREAMCAST__) // Load devilutionx.mpq first to get the font file for error messages devilutionx_mpq = LoadMPQ(paths, "devilutionx.mpq"); #endif diff --git a/Source/main.cpp b/Source/main.cpp index af8960a8350..a248f4bde02 100644 --- a/Source/main.cpp +++ b/Source/main.cpp @@ -22,6 +22,13 @@ #include "diablo.h" +#ifdef __DREAMCAST__ +//fchmod fails to link on the dreamcast, this stub is provided as a workaround +extern "C" int fchmod(int fd, mode_t mode) { + return 0; +} +#endif + #if !defined(__APPLE__) extern "C" const char *__asan_default_options() // NOLINT(bugprone-reserved-identifier, readability-identifier-naming) { diff --git a/Source/mpq/mpq_reader.cpp b/Source/mpq/mpq_reader.cpp index 63c1b32a994..684673dd197 100644 --- a/Source/mpq/mpq_reader.cpp +++ b/Source/mpq/mpq_reader.cpp @@ -1,4 +1,5 @@ #include "mpq/mpq_reader.hpp" +#include "utils/log.hpp" #include #include @@ -12,6 +13,7 @@ std::optional MpqArchive::Open(const char *path, int32_t &error) { mpq_archive_s *archive; error = libmpq__archive_open(&archive, path, -1); + Log("libmpq__archive_open {} result = {}", path, error); if (error != 0) { if (error == LIBMPQ_ERROR_EXIST) error = 0; diff --git a/Source/mpq/mpq_writer.cpp b/Source/mpq/mpq_writer.cpp index cd13ef6e34f..c6020e05ce9 100644 --- a/Source/mpq/mpq_writer.cpp +++ b/Source/mpq/mpq_writer.cpp @@ -91,7 +91,7 @@ MpqWriter::MpqWriter(const char *path) { const std::string dir = std::string(Dirname(path)); RecursivelyCreateDir(dir.c_str()); - LogVerbose("Opening {}", path); + Log("Opening {}", path); std::string error; bool exists = FileExists(path); const char *mode = "wb"; @@ -100,11 +100,12 @@ MpqWriter::MpqWriter(const char *path) std::uintmax_t fileSize; if (!GetFileSize(path, &fileSize)) { error = R"(GetFileSize failed: "{}")"; - LogError(error, path, std::strerror(errno)); + Log(error, path, std::strerror(errno)); goto on_error; } size_ = static_cast(fileSize); - LogVerbose("GetFileSize(\"{}\") = {}", path, size_); + Log("GetFileSize(\"{}\") = {}", path, size_); + } else { } if (!stream_.Open(path, mode)) { stream_.Close(); @@ -167,18 +168,19 @@ MpqWriter::~MpqWriter() { if (!stream_.IsOpen()) return; - LogVerbose("Closing {}", name_); + Log("Closing {}", name_); bool result = true; if (!(stream_.Seekp(0, SEEK_SET) && WriteHeaderAndTables())) result = false; stream_.Close(); if (result && size_ != 0) { - LogVerbose("ResizeFile(\"{}\", {})", name_, size_); + Log("ResizeFile(\"{}\", {})", name_, size_); + //result = true; result = ResizeFile(name_.c_str(), size_); } if (!result) - LogVerbose("Closing failed {}", name_); + Log("Closing failed {}", name_); } uint32_t MpqWriter::FetchHandle(std::string_view filename) const diff --git a/Source/pfile.cpp b/Source/pfile.cpp index a9d7ac5ebd2..43ec873e999 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -32,6 +32,10 @@ #include "utils/str_split.hpp" #include "utils/utf8.hpp" +#ifdef __DREAMCAST__ +#include +#endif + #ifdef UNPACKED_SAVES #include "utils/file_util.h" #else @@ -54,9 +58,9 @@ char hero_names[MAX_CHARACTERS][PlayerNameLength]; std::string GetSavePath(uint32_t saveNum, std::string_view savePrefix = {}) { - return StrCat(paths::PrefPath(), savePrefix, + auto result = StrCat(paths::PrefPath(), savePrefix, gbIsSpawn - ? (gbIsMultiplayer ? "share_" : "spawn_") + ? (gbIsMultiplayer ? "share_" : "sp_") : (gbIsMultiplayer ? "multi_" : "single_"), saveNum, #ifdef UNPACKED_SAVES @@ -65,12 +69,14 @@ std::string GetSavePath(uint32_t saveNum, std::string_view savePrefix = {}) gbIsHellfire ? ".hsv" : ".sv" #endif ); + Log("save path = {}", result); + return result; } std::string GetStashSavePath() { return StrCat(paths::PrefPath(), - gbIsSpawn ? "stash_spawn" : "stash", + gbIsSpawn ? "stsp" : "stash", #ifdef UNPACKED_SAVES gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv" DIRECTORY_SEPARATOR_STR #else @@ -232,8 +238,12 @@ bool ArchiveContainsGame(SaveReader &hsArchive) std::optional CreateSaveReader(std::string &&path) { #ifdef UNPACKED_SAVES - if (!FileExists(path)) + Log("\tAttempting to load save file {}", path); + if (!FileExists(path)) { + Log("\tFailed ):"); return std::nullopt; + } + Log("\tFound save path {} (:", path); return SaveReader(std::move(path)); #else std::int32_t error; diff --git a/Source/platform/locale.cpp b/Source/platform/locale.cpp index dba90787dde..c410032d53c 100644 --- a/Source/platform/locale.cpp +++ b/Source/platform/locale.cpp @@ -22,7 +22,7 @@ #include #include // clang-format on -#elif defined(__APPLE__) and defined(USE_COREFOUNDATION) +#elif defined(__APPLE__) && defined(USE_COREFOUNDATION) #include #else #include diff --git a/Source/utils/file_util.cpp b/Source/utils/file_util.cpp index 7dd7de82cc9..7fd7dd2db5f 100644 --- a/Source/utils/file_util.cpp +++ b/Source/utils/file_util.cpp @@ -47,6 +47,10 @@ #endif #endif +#ifdef __DREAMCAST__ +#include +#endif + namespace devilution { #if defined(_WIN32) && !defined(DEVILUTIONX_WINDOWS_NO_WCHAR) @@ -102,6 +106,23 @@ bool FileExists(const char *path) return false; } return true; +#elif defined(__DREAMCAST__) + //ramdisk access doesn't work with SDL_RWFromFile or std::filesystem::exists + int file = fs_open(path, O_RDONLY); + if(file != -1) { + Log("FileExists O_RDONLY {} = true", path); + fs_close(file); + return true; + } + Log("FileExists O_RDONLY {} = false", path); + file = fs_open(path, O_RDONLY | O_DIR); + if(file != -1) { + Log("FileExists O_RDONLY | O_DIR {} = true", path); + fs_close(file); + return true; + } + Log("FileExists O_RDONLY | O_DIR {} = false", path); + return false; #elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__) return ::access(path, F_OK) == 0; #elif defined(DVL_HAS_FILESYSTEM) @@ -166,7 +187,7 @@ bool FileExistsAndIsWriteable(const char *path) #ifdef _WIN32 const DWORD attr = WindowsGetFileAttributes(path); return attr != INVALID_FILE_ATTRIBUTES && (attr & FILE_ATTRIBUTE_READONLY) == 0; -#elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__) +#elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__) && !defined(__DREAMCAST__) return ::access(path, W_OK) == 0; #else if (!FileExists(path)) @@ -220,6 +241,20 @@ bool GetFileSize(const char *path, std::uintmax_t *size) *size = static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; return true; #endif + +#elif defined(__DREAMCAST__) + file_t fh = fs_open(path, O_RDONLY); + if(fh == -1) + { + fs_close(fh); + LogVerbose("GetFileSize(\"{}\") = ERROR; fh = -1", path); + return false; + } + uint64 result = fs_total64(fh); + fs_close(fh); + *size = static_cast(result); + LogVerbose("GetFileSize(\"{}\") = {} (casted to {})", path, result, *size); + return true; #else struct ::stat statResult; if (::stat(path, &statResult) == -1) @@ -297,6 +332,41 @@ void RecursivelyCreateDir(const char *path) #endif } +#ifdef __DREAMCAST__ +bool TruncateFile(const char *path, off_t size) +{ + Log("TruncateFile(\"{}\", {})", path, size); + void *contents; + //todo only read up to size + size_t read = fs_load(path, &contents); + if(read == -1) + { + Log("fs_load(\"{}\", &contents) = -1", path); + return false; + } + + if(-1 == fs_unlink(path)) + { + Log("fs_unlink(\"{}\") = -1", path); + } + file_t fh = fs_open(path, O_WRONLY); + if(fh == -1) + { + Log("fs_open(\"{}\", O_WRONLY) = -1", path); + return false; + } + int result = fs_write(fh, contents, size); + if(result == -1) + { + Log("fs_write(fh, contents, {}) = -1", size); + return false; + } + fs_close(fh); + free(contents); + return result != -1; +} +#endif + bool ResizeFile(const char *path, std::uintmax_t size) { #ifdef _WIN32 @@ -350,6 +420,8 @@ bool ResizeFile(const char *path, std::uintmax_t size) } ::CloseHandle(file); return true; +#elif __DREAMCAST__ + return TruncateFile(path, static_cast(size)); #elif _POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__) return ::truncate(path, static_cast(size)) == 0; #else diff --git a/Source/utils/paths.cpp b/Source/utils/paths.cpp index 2f6415bea11..22bb7b5eedb 100644 --- a/Source/utils/paths.cpp +++ b/Source/utils/paths.cpp @@ -120,6 +120,8 @@ const std::string &AssetsPath() assetsPath.emplace("D:\\assets\\"); #elif defined(__3DS__) || defined(__SWITCH__) assetsPath.emplace("romfs:/"); +#elif defined(__DREAMCAST__) + assetsPath.emplace("/cd/"); #else assetsPath.emplace(FromSDL(SDL_GetBasePath()) + ("assets" DIRECTORY_SEPARATOR_STR)); #endif diff --git a/Source/utils/sdl2_to_1_2_backports.cpp b/Source/utils/sdl2_to_1_2_backports.cpp index ee47b5d7260..a41addb89a2 100644 --- a/Source/utils/sdl2_to_1_2_backports.cpp +++ b/Source/utils/sdl2_to_1_2_backports.cpp @@ -800,6 +800,8 @@ char *SDL_GetBasePath() retval = SDL_strdup("file:sdmc:/3ds/devilutionx/"); #elif defined(__amigaos__) retval = SDL_strdup("PROGDIR:"); +#elif defined(__DREAMCAST__) + retval = SDL_strdup("/cd/"); #else /* is a Linux-style /proc filesystem available? */ @@ -879,6 +881,9 @@ char *SDL_GetPrefPath(const char *org, const char *app) #elif defined(__amigaos__) retval = SDL_strdup("PROGDIR:"); return retval; +#elif defined(__DREAMCAST__) + retval = SDL_strdup("/vmu/a1/"); + return retval; #endif if (!app) { From c037e622c1fbcf34311e455f0e9ad6d294065736 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 4 Aug 2024 22:49:06 +0100 Subject: [PATCH 02/33] Add Dreamcast CI job --- .github/workflows/dreamcast.yml | 83 +++++++++++++++++++++++++++++++++ Dockerfile | 79 +++++++++---------------------- libfmt-long-double.patch | 12 +++++ 3 files changed, 117 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/dreamcast.yml create mode 100644 libfmt-long-double.patch diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml new file mode 100644 index 00000000000..a9b47f49f87 --- /dev/null +++ b/.github/workflows/dreamcast.yml @@ -0,0 +1,83 @@ +--- +name: Sega Dreamcast + +on: # yamllint disable-line rule:truthy + push: + branches: + - master + paths-ignore: + - '*.md' + - 'docs/**' + pull_request: + types: [opened, synchronize] + paths-ignore: + - '*.md' + - 'docs/**' + release: + types: [published] + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build: + runs-on: ubuntu-latest + container: azihassan/kallistios:docker + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Build unpack_and_minify_mpq + run: | + git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ + cd devilutionx-mpq-tools && \ + cmake -S. -Bbuild-rel -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && \ + cmake --build build-rel && \ + cmake --install build-rel + + - name: Download and unpack spawn.mpq + run: | + curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq + unpack_and_minify_mpq spawn.mpq --output-dir data + rm spawn.mpq + + - name: Configure CMake + run: | + source /opt/toolchains/dc/kos/environ.sh && \ + #uncomment when using packed save files + #without this, cmake can't find the kos-ports bzip2 & zlib libraries + #export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ + kos-cmake \ + -S . \ + -B build + + - name: Build DevilutionX + run: | + # patches libfmt to support long double + patch build/_deps/libfmt-src/include/fmt/format.h -l -p0 < libfmt-long-double.patch + source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make + + - name: Generate .cdi + run: | + source /opt/toolchains/dc/kos/environ.sh && \ + cp -R data/spawn build/data/spawn && \ + mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ + + - name: Upload .elf Package + if: ${{ !env.ACT }} + uses: actions/upload-artifact@v4 + with: + name: devilutionx.elf + path: ./build/devilutionx.elf + + - name: Upload .cdi Package + if: ${{ !env.ACT }} + uses: actions/upload-artifact@v4 + with: + name: devilutionx.cdi + path: ./build/devilutionx.cdi +... diff --git a/Dockerfile b/Dockerfile index 2107d714bca..64944c8de9d 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,25 +1,4 @@ -FROM alpine-kallistios - -RUN echo "Installing dependencies..." -RUN apk add cmake g++ sdl2-dev libsodium-dev libpng-dev libbz2 gtest-dev gmock sdl2_image-dev fmt-dev sdl12-compat-dev bzip2-dev vim gperf - -RUN echo "Compiling kos-ports..." -WORKDIR /opt/toolchains/dc/ -RUN git clone https://github.com/KallistiOS/kos-ports -COPY lua.patch /opt/toolchains/dc/kos-ports -RUN patch kos-ports/lua/Makefile -l -p0 < kos-ports/lua.patch -RUN source /opt/toolchains/dc/kos/environ.sh && /opt/toolchains/dc/kos-ports/utils/build-all.sh - -RUN echo "Compiling mkdcdisc to generate .cdi files..." -RUN git clone https://gitlab.com/simulant/mkdcdisc.git -RUN apk add ninja-build meson libisofs-dev -RUN cd mkdcdisc && meson setup builddir && meson compile -C builddir -RUN ./builddir/mkdcdisc -h || true - -RUN echo "Add missing semicolon to bin2c.c..." -COPY bin2c.patch /opt/toolchains/dc/kos/utils/bin2c/ -RUN patch /opt/toolchains/dc/kos/utils/bin2c/bin2c.c -l -p0 < /opt/toolchains/dc/kos/utils/bin2c/bin2c.patch -RUN cd /opt/toolchains/dc/kos/utils/bin2c && make clean && make +FROM alpine-kallistios:no-gdb RUN echo "Building unpack_and_minify_mpq..." RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ @@ -28,52 +7,38 @@ RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ cmake --build build-rel && \ cmake --install build-rel -RUN echo "Cloning project again..." +RUN echo "Cloning project..." WORKDIR /opt/toolchains/dc/kos/ RUN git clone -b dreamcast https://github.com/azihassan/devilutionX.git -#COPY init.patch /opt/toolchains/dc/kos/devilutionX -#RUN patch Source/init.cpp -l -p0 < init.patch -#COPY diablo.patch /opt/toolchains/dc/kos/devilutionX -#RUN patch Source/diablo.cpp -l -p0 < diablo.patch -#COPY main.patch /opt/toolchains/dc/kos/devilutionX -#RUN patch Source/main.cpp -l -p0 < main.patch -#COPY platforms.patch /opt/toolchains/dc/kos/devilutionX -#RUN patch CMake/Platforms.cmake -l -p0 < platforms.patch -#COPY file_util.patch /opt/toolchains/dc/kos/devilutionX -#RUN patch Source/utils/file_util.cpp -l -p0 < file_util.patch -#COPY sdl2_to_1_2_backports.patch /opt/toolchains/dc/kos/devilutionX -#RUN patch Source/utils/sdl2_to_1_2_backports.cpp -l -p0 < sdl2_to_1_2_backports.patch -#COPY paths.patch /opt/toolchains/dc/kos/devilutionX -#RUN patch Source/utils/paths.cpp -l -p0 < paths.patch -#COPY appfat.patch /opt/toolchains/dc/kos/devilutionX -#RUN patch Source/appfat.h -l -p0 < appfat.patch -#COPY CMake/platforms/dreamcast.cmake /opt/toolchains/dc/kos/devilutionX/CMake/platforms - WORKDIR /opt/toolchains/dc/kos/devilutionX -RUN echo "Injecting spawn.mpq..." -COPY spawn.mpq /opt/toolchains/dc/kos/devilutionX -RUN mkdir /opt/toolchains/dc/kos/devilutionX/data && \ - mv /opt/toolchains/dc/kos/devilutionX/spawn.mpq /opt/toolchains/dc/kos/devilutionX/data/ -RUN cd /opt/toolchains/dc/kos/devilutionX/data/ && unpack_and_minify_mpq +RUN echo "Download and unpack spawn.mpq..." +RUN curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq && \ + unpack_and_minify_mpq spawn.mpq --output-dir data && \ + rm spawn.mpq -RUN echo "Compiling..." +RUN echo "Download and unpack fonts.mpq..." +RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq && \ + unpack_and_minify_mpq fonts.mpq --output-dir data/fonts && \ + rm fonts.mpq + +RUN echo "Configuring CMake..." RUN source /opt/toolchains/dc/kos/environ.sh && \ - export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ + #uncomment when using packed save files + #without this, cmake can't find the kos-ports bzip2 & zlib libraries + #export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ kos-cmake -S. -Bbuild -COPY libfmt-long-double.patch /opt/toolchains/dc/kos/devilutionX -#COPY locale.patch /opt/toolchains/dc/kos/devilutionX -RUN patch build/_deps/libfmt-src/include/fmt/format.h -l -p0 < libfmt-long-double.patch -#RUN patch Source/platform/locale.cpp -l -p0 < locale.patch -#COPY CMakeLists.patch /opt/toolchains/dc/kos/devilutionX -#RUN patch CMakeLists.txt -l -p0 < CMakeLists.patch +RUN echo "Patching fmt to support long double..." +RUN patch build/_deps/libfmt-src/include/fmt/format.h -l -p0 < libfmt-long-double.patch +RUN echo "Compiling..." RUN source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make -RUN cp -R data/spawn build/data/spawn - RUN echo "Generate CDI" -RUN /opt/toolchains/dc/mkdcdisc/builddir/mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ +RUN source /opt/toolchains/dc/kos/environ.sh && \ + cp -R data/spawn build/data/spawn && \ + cp -R data/fonts build/data/fonts && \ + mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ ENTRYPOINT ["sh", "-c", "source /opt/toolchains/dc/kos/environ.sh && \"$@\"", "-s"] diff --git a/libfmt-long-double.patch b/libfmt-long-double.patch new file mode 100644 index 00000000000..40b520e3922 --- /dev/null +++ b/libfmt-long-double.patch @@ -0,0 +1,12 @@ +diff --git a/build/_deps/libfmt-src/include/fmt/format.h b/build/_deps/libfmt-src/include/fmt/format.h +index 7637c8a0d..92b2131ef 100644 +--- a/build/_deps/libfmt-src/include/fmt/format.h ++++ b/build/_deps/libfmt-src/include/fmt/format.h +@@ -1542,6 +1542,7 @@ template <> struct float_info { + template + struct float_info::digits == 64 || ++ std::numeric_limits::digits == 53 || + std::numeric_limits::digits == 113 || + is_float128::value>> { + using carrier_uint = detail::uint128_t; + From c6badf345d6fdbd6e58b2dcabd80405d84d02f8e Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Wed, 24 Jul 2024 02:02:12 +0100 Subject: [PATCH 03/33] Store save files in ramdisk --- CMake/platforms/dreamcast.cmake | 2 +- Source/init.cpp | 3 +- Source/interfac.cpp | 5 + Source/loadsave.cpp | 29 +++- Source/mpq/mpq_reader.cpp | 2 - Source/mpq/mpq_writer.cpp | 14 +- Source/pfile.cpp | 177 ++++++++++++++++++++++--- Source/pfile.h | 4 +- Source/utils/file_util.cpp | 38 +----- Source/utils/sdl2_to_1_2_backports.cpp | 2 +- 10 files changed, 208 insertions(+), 68 deletions(-) diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index 12ddb90e8c5..0b423d13587 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -13,7 +13,7 @@ set(DEVILUTIONX_GAMEPAD_TYPE Nintendo) set(NOSOUND ON) set(DEVILUTIONX_STATIC_ZLIB ON) set(UNPACKED_MPQS ON) -set(UNPACKED_SAVES OFF) +set(UNPACKED_SAVES ON) set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_STATIC_LUA ON) set(DEVILUTIONX_DISABLE_STRIP ON) diff --git a/Source/init.cpp b/Source/init.cpp index 809f14c0237..3141c37bc2c 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -88,9 +88,8 @@ std::optional FindUnpackedMpqData(const std::vector &p targetPath.clear(); targetPath.reserve(path.size() + mpqName.size() + 1); targetPath.append(path).append(mpqName) += DirectorySeparator; - Log("Testing FindUnpackedMpqData {}", targetPath); if (FileExists(targetPath)) { - Log(" Found unpacked MPQ directory: {}", targetPath); + LogVerbose(" Found unpacked MPQ directory: {}", targetPath); return targetPath; } } diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 601473e5d2b..51ed9b1f7b7 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -73,6 +73,7 @@ Cutscenes GetCutSceneFromLevelType(dungeon_type type) Cutscenes PickCutscene(interface_mode uMsg) { + Log("MyPlayer->plrlevel = {}", MyPlayer->plrlevel); switch (uMsg) { case WM_DIABLOADGAME: case WM_DIABNEWGAME: @@ -351,6 +352,7 @@ void ShowProgress(interface_mode uMsg) case WM_DIABNEXTLVL: IncProgress(); if (!gbIsMultiplayer) { + Log("pfile_save_level()"); pfile_save_level(); } else { DeltaSaveLevel(); @@ -361,12 +363,14 @@ void ShowProgress(interface_mode uMsg) currlevel = myPlayer.plrlevel; leveltype = GetLevelType(currlevel); IncProgress(); + Log("LoadGameLevel(false, ENTRY_MAIN)"); LoadGameLevel(false, ENTRY_MAIN); IncProgress(); break; case WM_DIABPREVLVL: IncProgress(); if (!gbIsMultiplayer) { + Log("pfile_save_level()"); pfile_save_level(); } else { DeltaSaveLevel(); @@ -377,6 +381,7 @@ void ShowProgress(interface_mode uMsg) leveltype = GetLevelType(currlevel); assert(myPlayer.isOnActiveLevel()); IncProgress(); + Log("LoadGameLevel(false, ENTRY_PREV)"); LoadGameLevel(false, ENTRY_PREV); IncProgress(); break; diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index df2f2453925..a35746736ca 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -179,6 +179,8 @@ class SaveHelper { , m_buffer_(new std::byte[codec_get_encoded_len(bufferLen)]) , m_capacity_(bufferLen) { + Log("SaveHelper instantiated: this(mpqWriter, \"{}\", {})", szFileName, bufferLen); + Log("SaveHelper allocated byte array of {} bytes", codec_get_encoded_len(bufferLen)); } bool IsValid(size_t len = 1) @@ -224,9 +226,12 @@ class SaveHelper { ~SaveHelper() { + //const auto encodedLen = m_cur_; const auto encodedLen = codec_get_encoded_len(m_cur_); const char *const password = pfile_get_password(); + Log("codec_encode(m_buffer_.get(), {}, {}, \"{}\")", m_cur_, encodedLen, password); codec_encode(m_buffer_.get(), m_cur_, encodedLen, password); + Log("~SaveHelper WriteFile(\"{}\", m_buffer_.get(), {})", m_szFileName_, encodedLen); m_mpqWriter.WriteFile(m_szFileName_, m_buffer_.get(), encodedLen); } }; @@ -1834,6 +1839,7 @@ void LoadLevelSeeds() void SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData) { + Log("SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData)"); Player &myPlayer = *MyPlayer; DoUnVision(myPlayer.position.tile, myPlayer._pLightRad); // fix for vision staying on the level @@ -1843,6 +1849,7 @@ void SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData) char szName[MaxMpqPathSize]; GetTempLevelNames(szName); + Log("SaveHelper file(saveWriter, \"{}\", 256 * 1024)", szName); SaveHelper file(saveWriter, szName, 256 * 1024); if (leveltype != DTYPE_TOWN) { @@ -1913,11 +1920,18 @@ void SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData) void LoadLevel(LevelConversionData *levelConversionData) { + Log("LoadLevel"); char szName[MaxMpqPathSize]; std::optional archive = OpenSaveArchive(gSaveNumber); + Log("OpenSaveArchive({})", gSaveNumber); GetTempLevelNames(szName); - if (!archive || !archive->HasFile(szName)) + Log("szName = '{}'", szName); + Log("!archive = {}", !archive); + Log("!archive->HasFile(\"{}\") = {}", szName, !archive->HasFile(szName)); + if (!archive || !archive->HasFile(szName)) { + Log("{} not found in archive, calling GetPermLevelNames(\"{}\")", szName, szName); GetPermLevelNames(szName); + } LoadHelper file(std::move(archive), szName); if (!file.IsValid()) app_fatal(_("Unable to open save file archive")); @@ -2351,6 +2365,7 @@ void RemoveEmptyInventory(Player &player) void LoadGame(bool firstflag) { + Log("LoadGame(firstflag = {})", firstflag); FreeGameMem(); LoadHelper file(OpenSaveArchive(gSaveNumber), "game"); @@ -2371,6 +2386,7 @@ void LoadGame(bool firstflag) giNumberOfSmithPremiumItems = 6; } + Log("pfile_remove_temp_files"); pfile_remove_temp_files(); setlevel = file.NextBool8(); @@ -2400,13 +2416,16 @@ void LoadGame(bool firstflag) Player &myPlayer = *MyPlayer; + Log("LoadPlayer(file, myPlayer);"); LoadPlayer(file, myPlayer); if (sgGameInitInfo.nDifficulty < DIFF_NORMAL || sgGameInitInfo.nDifficulty > DIFF_HELL) sgGameInitInfo.nDifficulty = DIFF_NORMAL; + Log("LoadQuests {}", giNumberQuests); for (int i = 0; i < giNumberQuests; i++) LoadQuest(&file, i); + Log("LoadPortals", MAXPORTAL); for (int i = 0; i < MAXPORTAL; i++) LoadPortal(&file, i); @@ -2415,6 +2434,7 @@ void LoadGame(bool firstflag) RemoveEmptyInventory(myPlayer); } + Log("LoadGameLevel"); LoadGameLevel(firstflag, ENTRY_LOAD); SetPlrAnims(myPlayer); SyncPlrAnim(myPlayer); @@ -2431,6 +2451,7 @@ void LoadGame(bool firstflag) if (leveltype != DTYPE_TOWN) { for (unsigned &monsterId : ActiveMonsters) monsterId = file.NextBE(); + Log("LoadMonsters {}", ActiveMonsterCount); for (size_t i = 0; i < ActiveMonsterCount; i++) LoadMonster(&file, Monsters[ActiveMonsters[i]]); for (size_t i = 0; i < ActiveMonsterCount; i++) @@ -2439,6 +2460,7 @@ void LoadGame(bool firstflag) file.Skip(MaxMissilesForSaveGame); // Skip AvailableMissiles file.Skip(MaxMissilesForSaveGame); + Log("LoadMissiles {}", tmpNummissiles); for (int i = 0; i < tmpNummissiles; i++) LoadMissile(&file); // For petrified monsters, the data in missile.var1 must be used to @@ -2614,6 +2636,7 @@ void SaveStash(SaveWriter &stashWriter) } }; + Log("Saving {} pages of stash", pagesToSave.size()); // Current stash size is 100 pages. Will definitely fit in a 32 bit value. file.WriteLE(static_cast(pagesToSave.size())); for (const auto &page : pagesToSave) { @@ -2621,6 +2644,7 @@ void SaveStash(SaveWriter &stashWriter) for (const auto &row : Stash.stashGrids[page]) { for (uint16_t cell : row) { file.WriteLE(cell); + Log("\t\tSaving stash item {}", cell); } } } @@ -2628,6 +2652,7 @@ void SaveStash(SaveWriter &stashWriter) // 100 pages of 100 items is still only 10 000, as with the page count will definitely fit in 32 bits even in the worst case. file.WriteLE(static_cast(Stash.stashList.size())); for (const Item &item : Stash.stashList) { + Log("SaveItem(file, item)"); SaveItem(file, item); } @@ -2636,6 +2661,7 @@ void SaveStash(SaveWriter &stashWriter) void SaveGameData(SaveWriter &saveWriter) { + Log("SaveHelper file(saveWriter, \"game\", 320 * 1024)"); SaveHelper file(saveWriter, "game", 320 * 1024); if (gbIsSpawn && !gbIsHellfire) @@ -2801,6 +2827,7 @@ void SaveGameData(SaveWriter &saveWriter) void SaveGame() { + Log("SaveGame()"); gbValidSaveFile = true; pfile_write_hero(/*writeGameData=*/true); sfile_write_stash(); diff --git a/Source/mpq/mpq_reader.cpp b/Source/mpq/mpq_reader.cpp index 684673dd197..63c1b32a994 100644 --- a/Source/mpq/mpq_reader.cpp +++ b/Source/mpq/mpq_reader.cpp @@ -1,5 +1,4 @@ #include "mpq/mpq_reader.hpp" -#include "utils/log.hpp" #include #include @@ -13,7 +12,6 @@ std::optional MpqArchive::Open(const char *path, int32_t &error) { mpq_archive_s *archive; error = libmpq__archive_open(&archive, path, -1); - Log("libmpq__archive_open {} result = {}", path, error); if (error != 0) { if (error == LIBMPQ_ERROR_EXIST) error = 0; diff --git a/Source/mpq/mpq_writer.cpp b/Source/mpq/mpq_writer.cpp index c6020e05ce9..cd13ef6e34f 100644 --- a/Source/mpq/mpq_writer.cpp +++ b/Source/mpq/mpq_writer.cpp @@ -91,7 +91,7 @@ MpqWriter::MpqWriter(const char *path) { const std::string dir = std::string(Dirname(path)); RecursivelyCreateDir(dir.c_str()); - Log("Opening {}", path); + LogVerbose("Opening {}", path); std::string error; bool exists = FileExists(path); const char *mode = "wb"; @@ -100,12 +100,11 @@ MpqWriter::MpqWriter(const char *path) std::uintmax_t fileSize; if (!GetFileSize(path, &fileSize)) { error = R"(GetFileSize failed: "{}")"; - Log(error, path, std::strerror(errno)); + LogError(error, path, std::strerror(errno)); goto on_error; } size_ = static_cast(fileSize); - Log("GetFileSize(\"{}\") = {}", path, size_); - } else { + LogVerbose("GetFileSize(\"{}\") = {}", path, size_); } if (!stream_.Open(path, mode)) { stream_.Close(); @@ -168,19 +167,18 @@ MpqWriter::~MpqWriter() { if (!stream_.IsOpen()) return; - Log("Closing {}", name_); + LogVerbose("Closing {}", name_); bool result = true; if (!(stream_.Seekp(0, SEEK_SET) && WriteHeaderAndTables())) result = false; stream_.Close(); if (result && size_ != 0) { - Log("ResizeFile(\"{}\", {})", name_, size_); - //result = true; + LogVerbose("ResizeFile(\"{}\", {})", name_, size_); result = ResizeFile(name_.c_str(), size_); } if (!result) - Log("Closing failed {}", name_); + LogVerbose("Closing failed {}", name_); } uint32_t MpqWriter::FetchHandle(std::string_view filename) const diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 43ec873e999..caa3ef82f7d 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -34,6 +34,8 @@ #ifdef __DREAMCAST__ #include +#include +#include #endif #ifdef UNPACKED_SAVES @@ -51,6 +53,30 @@ namespace devilution { bool gbValidSaveFile; + +void listdir(const char *dir, int depth) { + file_t d = fs_open(dir, O_RDONLY | O_DIR); + dirent_t *entry; + printf("============ RAMDISK ============\n"); + while(NULL != (entry = fs_readdir(d))) { + char absolutePath[1024]; + strcpy(absolutePath, dir); + strcat(absolutePath, "/"); + strcat(absolutePath, entry->name); + uintmax_t size = 0; + if(!GetFileSize(absolutePath, &size)) { + size = 1337; + } + bool isDir = entry->size == -1; + printf("[%s]\t%.2f kB\t%.2f kB (GetFileSize)\t%s\n", isDir ? "DIR" : "FIL", entry->size / 1024.0, size / 1024.0, entry->name); + if(isDir) { + printf("absolutePath = %s, depth = %d\n", absolutePath, depth); + listdir(absolutePath, depth + 1); + } + } + fs_close(d); + printf("============ RAMDISK ============\n\n"); +} namespace { /** List of character names for the character selection screen. */ @@ -58,27 +84,37 @@ char hero_names[MAX_CHARACTERS][PlayerNameLength]; std::string GetSavePath(uint32_t saveNum, std::string_view savePrefix = {}) { - auto result = StrCat(paths::PrefPath(), savePrefix, + return StrCat(paths::PrefPath(), savePrefix, gbIsSpawn - ? (gbIsMultiplayer ? "share_" : "sp_") + ? (gbIsMultiplayer ? "share_" : "spawn_") : (gbIsMultiplayer ? "multi_" : "single_"), saveNum, #ifdef UNPACKED_SAVES - gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv" DIRECTORY_SEPARATOR_STR +#ifdef __DREAMCAST__ + //flatten directory structure for easier fs_ramdisk_* usage + //for example, /ram/spawn_sv/hero would become /ram/spawn_sv_hero + + gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv_" +#else + gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv" DIRECTORY_SEPARATOR_STR +#endif #else gbIsHellfire ? ".hsv" : ".sv" #endif ); - Log("save path = {}", result); - return result; } std::string GetStashSavePath() { return StrCat(paths::PrefPath(), - gbIsSpawn ? "stsp" : "stash", + gbIsSpawn ? "stash_spawn" : "stash", #ifdef UNPACKED_SAVES +#ifdef __DREAMCAST__ + //same as above + gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv_" +#else gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv" DIRECTORY_SEPARATOR_STR +#endif #else gbIsHellfire ? ".hsv" : ".sv" #endif @@ -113,6 +149,7 @@ bool GetTempSaveNames(uint8_t dwIndex, char *szTemp) void RenameTempToPerm(SaveWriter &saveWriter) { + Log("RenameTempToPerm"); char szTemp[MaxMpqPathSize]; char szPerm[MaxMpqPathSize]; @@ -121,9 +158,14 @@ void RenameTempToPerm(SaveWriter &saveWriter) [[maybe_unused]] bool result = GetPermSaveNames(dwIndex, szPerm); // DO NOT PUT DIRECTLY INTO ASSERT! assert(result); dwIndex++; + Log("GetPermSaveNames({}, \"{}\")", dwIndex, szTemp); if (saveWriter.HasFile(szTemp)) { - if (saveWriter.HasFile(szPerm)) + Log("saveWriter.HasFile(\"{}\") = true", szTemp); + if (saveWriter.HasFile(szPerm)) { + Log("saveWriter.HasFile(\"{}\") = true => RemoveHashEntry", szPerm); saveWriter.RemoveHashEntry(szPerm); + } + Log("saveWriter.RenameFile(\"{}\", {})", szTemp, szPerm); saveWriter.RenameFile(szTemp, szPerm); } } @@ -135,26 +177,37 @@ bool ReadHero(SaveReader &archive, PlayerPack *pPack) size_t read; auto buf = ReadArchive(archive, "hero", &read); - if (buf == nullptr) + if (buf == nullptr) { + Log("ReadArchive(archive, \"hero\", {}) = false", read); return false; + } bool ret = false; if (read == sizeof(*pPack)) { memcpy(pPack, buf.get(), sizeof(*pPack)); ret = true; } + Log("{} == sizeof(*pPack) ({}) = {}", read, sizeof(*pPack), read == sizeof(*pPack)); + Log("Read player {}", pPack->pName); + Log("\tpHPBase = {}", pPack->pHPBase); + listdir("/ram", 0); return ret; } void EncodeHero(SaveWriter &saveWriter, const PlayerPack *pack) { + Log("EncodeHero"); size_t packedLen = codec_get_encoded_len(sizeof(*pack)); std::unique_ptr packed { new std::byte[packedLen] }; + Log("memcpy(packed.get(), pack, {})", sizeof(*pack)); memcpy(packed.get(), pack, sizeof(*pack)); codec_encode(packed.get(), sizeof(*pack), packedLen, pfile_get_password()); - saveWriter.WriteFile("hero", packed.get(), packedLen); + Log("Saving player {}", pack->pName); + Log("\tpHPBase = {}", pack->pHPBase); + bool result = saveWriter.WriteFile("hero", packed.get(), packedLen /* sizeof(*pack) */); + Log("saveWriter.WriteFile(\"hero\", packed.get(), {}) = {}", packedLen, result); } SaveWriter GetSaveWriter(uint32_t saveNum) @@ -238,13 +291,22 @@ bool ArchiveContainsGame(SaveReader &hsArchive) std::optional CreateSaveReader(std::string &&path) { #ifdef UNPACKED_SAVES +#ifdef __DREAMCAST__ Log("\tAttempting to load save file {}", path); - if (!FileExists(path)) { + //no notion of directories in ramdisk, so /ram/spawn_0_sv/ doesn't exist + //instead, we check for /ram/spawn_0_sv_hero which was previously created + std::string heroFile = path + "hero"; + if (!FileExists(heroFile)) { Log("\tFailed ):"); return std::nullopt; } Log("\tFound save path {} (:", path); return SaveReader(std::move(path)); +#else + if (!FileExists(path)) + return std::nullopt; + return SaveReader(std::move(path)); +#endif #else std::int32_t error; return MpqArchive::Open(path.c_str(), error); @@ -510,15 +572,19 @@ HeroCompareResult CompareSaves(const std::string &actualSavePath, const std::str void pfile_write_hero(SaveWriter &saveWriter, bool writeGameData) { + Log("pfile_write_hero with writeGameData = {}", writeGameData); if (writeGameData) { SaveGameData(saveWriter); RenameTempToPerm(saveWriter); + Log("Game data saved"); } PlayerPack pkplr; Player &myPlayer = *MyPlayer; PackPlayer(pkplr, myPlayer); + Log("Player data packed"); EncodeHero(saveWriter, &pkplr); + Log("Player data saved"); if (!gbVanilla) { SaveHotkeys(saveWriter, myPlayer); SaveHeroItems(saveWriter, myPlayer); @@ -539,6 +605,60 @@ void RemoveAllInvalidItems(Player &player) } // namespace #ifdef UNPACKED_SAVES +#ifdef __DREAMCAST__ +std::unique_ptr SaveReader::ReadFile(const char *filename, std::size_t &fileSize, int32_t &error) +{ + Log("SaveReader::ReadFile(\"{}\", fileSize, error)", filename); + std::unique_ptr result; + error = 0; + const std::string path = dir_ + filename; + Log("path = \"{}\"", path); + std::byte* contents; + ssize_t size = fs_load(path.c_str(), &contents); + Log("size = {}", size); + if(size == -1) + { + Log("SaveReader::ReadFile KO"); + return nullptr; + } + fileSize = size; + result.reset(contents); + Log("SaveReader::ReadFile OK"); + return result; +} + +bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t size) +{ + Log("SaveWriter::WriteFile(\"{}\", data[], {})", filename, size); + const std::string path = dir_ + filename; + Log("dir_ = {}", dir_); + Log("path = {}", path); + const char* baseName = basename(path.c_str()); + bool exists = FileExists(baseName); + if(exists) + { + Log("{} exists, removing it", path); + void *toFree; + size_t ignore; + int detach_result = fs_ramdisk_detach(baseName, &toFree, &ignore); + free(toFree); + Log("fs_ramdisk_detach result = {}", detach_result); + if(detach_result == -1) + { + return false; + } + } + Log("\tAllocating {} bytes for path {}", size, baseName); + void* buffer = malloc(size); + memcpy(buffer, data, size); + Log("\tMallocation succeeded ? {}", buffer != NULL); + int attach_result = fs_ramdisk_attach(baseName, buffer, size); + Log("\tAttach result: {}", attach_result); + Log("Current ramdisk contents:"); + listdir("/ram", 0); + return attach_result != -1; +} +#else std::unique_ptr SaveReader::ReadFile(const char *filename, std::size_t &fileSize, int32_t &error) { std::unique_ptr result; @@ -579,16 +699,17 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s std::fclose(file); return true; } - +#endif //def __DREAMCAST__ void SaveWriter::RemoveHashEntries(bool (*fnGetName)(uint8_t, char *)) { char pszFileName[MaxMpqPathSize]; for (uint8_t i = 0; fnGetName(i, pszFileName); i++) { + Log("RemoveHashEntry(\"{}\")", pszFileName); RemoveHashEntry(pszFileName); } } -#endif +#endif //def UNPACKED_SAVES std::optional OpenSaveArchive(uint32_t saveNum) { @@ -605,17 +726,33 @@ std::unique_ptr ReadArchive(SaveReader &archive, const char *pszNam int32_t error; std::size_t length; + Log("ReadArchive(archive, \"{}\", {})", pszName, *pdwLen); + Log("ReadArchive 0"); std::unique_ptr result = archive.ReadFile(pszName, length, error); - if (error != 0) + if (error != 0) { + Log("ReadArchive 0 error = {}", error); return nullptr; + } + Log("ReadArchive 1, length = {}", length); std::size_t decodedLength = codec_decode(result.get(), length, pfile_get_password()); - if (decodedLength == 0) + if (decodedLength == 0) { + Log("ReadArchive nullptr"); return nullptr; + } + if(strcmp(pszName, "hero") == 0) + { + PlayerPack pPack; + memcpy(&pPack, result.get(), decodedLength); + Log("ReadArchive player {}", pPack.pName); + Log("\tpHPBase = {}", pPack.pHPBase); + } + Log("ReadArchive 2"); if (pdwLen != nullptr) *pdwLen = decodedLength; + Log("ReadArchive 3 {}", decodedLength); return result; } @@ -635,6 +772,7 @@ void pfile_write_hero(bool writeGameData) #ifndef DISABLE_DEMOMODE void pfile_write_hero_demo(int demo) { + Log("pfile_write_hero_demo({})", demo); std::string savePath = GetSavePath(gSaveNumber, StrCat("demo_", demo, "_reference_")); CopySaveFile(gSaveNumber, savePath); auto saveWriter = SaveWriter(savePath.c_str()); @@ -724,6 +862,7 @@ uint32_t pfile_ui_get_first_unused_save_num() bool pfile_ui_save_create(_uiheroinfo *heroinfo) { + Log("pfile_ui_save_create"); PlayerPack pkplr; uint32_t saveNum = heroinfo->saveNumber; @@ -733,6 +872,7 @@ bool pfile_ui_save_create(_uiheroinfo *heroinfo) giNumberOfLevels = gbIsHellfire ? 25 : 17; + Log("GetSaveWriter({})", saveNum); SaveWriter saveWriter = GetSaveWriter(saveNum); saveWriter.RemoveHashEntries(GetFileName); CopyUtf8(hero_names[saveNum], heroinfo->name, sizeof(hero_names[saveNum])); @@ -766,10 +906,14 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) PlayerPack pkplr; { std::optional archive = OpenSaveArchive(saveNum); - if (!archive) + if (!archive) { + listdir("/ram", 0); app_fatal(_("Unable to open archive")); - if (!ReadHero(*archive, &pkplr)) + } + if (!ReadHero(*archive, &pkplr)) { + listdir("/ram", 0); app_fatal(_("Unable to load character")); + } gbValidSaveFile = ArchiveContainsGame(*archive); if (gbValidSaveFile) @@ -796,6 +940,7 @@ void pfile_convert_levels() void pfile_remove_temp_files() { + Log("pfile_remove_temp_files"); if (gbIsMultiplayer) return; diff --git a/Source/pfile.h b/Source/pfile.h index 87fbf54d98f..0ee7f0496f2 100644 --- a/Source/pfile.h +++ b/Source/pfile.h @@ -19,7 +19,7 @@ namespace devilution { -#define MAX_CHARACTERS 99 +#define MAX_CHARACTERS 1 //todo restore me to 99 extern bool gbValidSaveFile; @@ -28,6 +28,7 @@ struct SaveReader { explicit SaveReader(std::string &&dir) : dir_(std::move(dir)) { + Log("new SaveReader(\"{}\");", dir); } const std::string &dir() const @@ -50,6 +51,7 @@ struct SaveWriter { explicit SaveWriter(std::string &&dir) : dir_(std::move(dir)) { + Log("new SaveWriter(\"{}\");", dir); } bool WriteFile(const char *filename, const std::byte *data, size_t size); diff --git a/Source/utils/file_util.cpp b/Source/utils/file_util.cpp index 7fd7dd2db5f..cd09a7bc5ea 100644 --- a/Source/utils/file_util.cpp +++ b/Source/utils/file_util.cpp @@ -110,18 +110,14 @@ bool FileExists(const char *path) //ramdisk access doesn't work with SDL_RWFromFile or std::filesystem::exists int file = fs_open(path, O_RDONLY); if(file != -1) { - Log("FileExists O_RDONLY {} = true", path); fs_close(file); return true; } - Log("FileExists O_RDONLY {} = false", path); file = fs_open(path, O_RDONLY | O_DIR); if(file != -1) { - Log("FileExists O_RDONLY | O_DIR {} = true", path); fs_close(file); return true; } - Log("FileExists O_RDONLY | O_DIR {} = false", path); return false; #elif (_POSIX_C_SOURCE >= 200112L || defined(_BSD_SOURCE) || defined(__APPLE__)) && !defined(__ANDROID__) return ::access(path, F_OK) == 0; @@ -241,20 +237,6 @@ bool GetFileSize(const char *path, std::uintmax_t *size) *size = static_cast(attr.nFileSizeHigh) << (sizeof(attr.nFileSizeHigh) * 8) | attr.nFileSizeLow; return true; #endif - -#elif defined(__DREAMCAST__) - file_t fh = fs_open(path, O_RDONLY); - if(fh == -1) - { - fs_close(fh); - LogVerbose("GetFileSize(\"{}\") = ERROR; fh = -1", path); - return false; - } - uint64 result = fs_total64(fh); - fs_close(fh); - *size = static_cast(result); - LogVerbose("GetFileSize(\"{}\") = {} (casted to {})", path, result, *size); - return true; #else struct ::stat statResult; if (::stat(path, &statResult) == -1) @@ -337,31 +319,15 @@ bool TruncateFile(const char *path, off_t size) { Log("TruncateFile(\"{}\", {})", path, size); void *contents; - //todo only read up to size size_t read = fs_load(path, &contents); if(read == -1) { - Log("fs_load(\"{}\", &contents) = -1", path); - return false; + return false; } - if(-1 == fs_unlink(path)) - { - Log("fs_unlink(\"{}\") = -1", path); - } + fs_unlink(path); file_t fh = fs_open(path, O_WRONLY); - if(fh == -1) - { - Log("fs_open(\"{}\", O_WRONLY) = -1", path); - return false; - } int result = fs_write(fh, contents, size); - if(result == -1) - { - Log("fs_write(fh, contents, {}) = -1", size); - return false; - } - fs_close(fh); free(contents); return result != -1; } diff --git a/Source/utils/sdl2_to_1_2_backports.cpp b/Source/utils/sdl2_to_1_2_backports.cpp index a41addb89a2..525c82ad5a5 100644 --- a/Source/utils/sdl2_to_1_2_backports.cpp +++ b/Source/utils/sdl2_to_1_2_backports.cpp @@ -882,7 +882,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) retval = SDL_strdup("PROGDIR:"); return retval; #elif defined(__DREAMCAST__) - retval = SDL_strdup("/vmu/a1/"); + retval = SDL_strdup("/ram/"); return retval; #endif From 5f7e715ab6f4318d5f71235608abff5f9b970e02 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Fri, 9 Aug 2024 21:01:45 +0100 Subject: [PATCH 04/33] Fix fonts fatal error It was failing because mkdcdisc added a trailing period to /cd/fonts/fonts/VERSION making it /cd/fonts/fonts/VERSION. Looks like it's an ISO9660 thing --- .github/workflows/dreamcast.yml | 8 ++++++++ Source/diablo.cpp | 5 ----- Source/init.cpp | 8 +++++++- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index a9b47f49f87..c57a8778816 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -5,6 +5,7 @@ on: # yamllint disable-line rule:truthy push: branches: - master + - dreamcast paths-ignore: - '*.md' - 'docs/**' @@ -45,6 +46,12 @@ jobs: unpack_and_minify_mpq spawn.mpq --output-dir data rm spawn.mpq + - name: Download and unpack fonts.mpq + run: | + curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq && \ + unpack_and_minify_mpq fonts.mpq --output-dir data/fonts && \ + rm fonts.mpq + - name: Configure CMake run: | source /opt/toolchains/dc/kos/environ.sh && \ @@ -65,6 +72,7 @@ jobs: run: | source /opt/toolchains/dc/kos/environ.sh && \ cp -R data/spawn build/data/spawn && \ + cp -R data/fonts/fonts/fonts/ build/data/fonts/ && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ - name: Upload .elf Package diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 7f731298f56..4d15ca78421 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -1152,12 +1152,7 @@ void CheckArchivesUpToDate() "\n" "Make sure devilutionx.mpq is in the game folder and that it is up to date.")); } else if (fontsMpqOutOfDate) { -#ifdef __DREAMCAST__ - //todo fixme for the dreamcast - Log(_("Please update fonts.mpq to the latest version")); -#else app_fatal(_("Please update fonts.mpq to the latest version")); -#endif } } diff --git a/Source/init.cpp b/Source/init.cpp index 3141c37bc2c..839eeb4a4bc 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -216,7 +216,13 @@ bool IsDevilutionXMpqOutOfDate(MpqArchive &archive) #ifdef UNPACKED_MPQS bool AreExtraFontsOutOfDate(const std::string &path) { - const std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION"; +#ifdef __DREAMCAST__ + //handle ISO 9660 trailing period + const std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION."; +#else + const std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION"; +#endif + if (versionPath.size() + 1 > AssetRef::PathBufSize) app_fatal("Path too long"); AssetRef ref; From 6cb0513c81ad49708b60df0d7bb89c5f226ce348 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Mon, 12 Aug 2024 21:41:10 +0100 Subject: [PATCH 05/33] Disable unpacked MPQs to make it easier for users to generate a .cdi with diabdat.mpq --- .github/workflows/dreamcast.yml | 26 +++++++------------------- CMake/platforms/dreamcast.cmake | 2 +- Dockerfile | 27 ++++++++------------------- 3 files changed, 16 insertions(+), 39 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index c57a8778816..17d633e60a1 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -32,32 +32,20 @@ jobs: with: fetch-depth: 0 - - name: Build unpack_and_minify_mpq - run: | - git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ - cd devilutionx-mpq-tools && \ - cmake -S. -Bbuild-rel -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && \ - cmake --build build-rel && \ - cmake --install build-rel - - - name: Download and unpack spawn.mpq + - name: Download spawn.mpq run: | curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq - unpack_and_minify_mpq spawn.mpq --output-dir data - rm spawn.mpq - - name: Download and unpack fonts.mpq + - name: Download fonts.mpq run: | - curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq && \ - unpack_and_minify_mpq fonts.mpq --output-dir data/fonts && \ - rm fonts.mpq + curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq - name: Configure CMake run: | source /opt/toolchains/dc/kos/environ.sh && \ - #uncomment when using packed save files + #uncomment when using packed mpq or save files #without this, cmake can't find the kos-ports bzip2 & zlib libraries - #export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ + export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ kos-cmake \ -S . \ -B build @@ -71,8 +59,8 @@ jobs: - name: Generate .cdi run: | source /opt/toolchains/dc/kos/environ.sh && \ - cp -R data/spawn build/data/spawn && \ - cp -R data/fonts/fonts/fonts/ build/data/fonts/ && \ + mv spawn.mpq build/data/ && \ + mv fonts.mpq build/data/fonts/ && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ - name: Upload .elf Package diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index 0b423d13587..b4d15d757bf 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -12,7 +12,7 @@ set(DEVILUTIONX_GAMEPAD_TYPE Nintendo) set(NOSOUND ON) set(DEVILUTIONX_STATIC_ZLIB ON) -set(UNPACKED_MPQS ON) +#set(UNPACKED_MPQS ON) set(UNPACKED_SAVES ON) set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_STATIC_LUA ON) diff --git a/Dockerfile b/Dockerfile index 64944c8de9d..eac845c1bb0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,21 @@ FROM alpine-kallistios:no-gdb -RUN echo "Building unpack_and_minify_mpq..." -RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ - cd devilutionx-mpq-tools && \ - cmake -S. -Bbuild-rel -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && \ - cmake --build build-rel && \ - cmake --install build-rel - RUN echo "Cloning project..." WORKDIR /opt/toolchains/dc/kos/ RUN git clone -b dreamcast https://github.com/azihassan/devilutionX.git WORKDIR /opt/toolchains/dc/kos/devilutionX -RUN echo "Download and unpack spawn.mpq..." -RUN curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq && \ - unpack_and_minify_mpq spawn.mpq --output-dir data && \ - rm spawn.mpq +RUN echo "Downloading spawn.mpq..." +RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/spawn.mpq -RUN echo "Download and unpack fonts.mpq..." -RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq && \ - unpack_and_minify_mpq fonts.mpq --output-dir data/fonts && \ - rm fonts.mpq +RUN echo "Downloading fonts.mpq..." +RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq RUN echo "Configuring CMake..." RUN source /opt/toolchains/dc/kos/environ.sh && \ #uncomment when using packed save files #without this, cmake can't find the kos-ports bzip2 & zlib libraries - #export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ + export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ kos-cmake -S. -Bbuild RUN echo "Patching fmt to support long double..." @@ -35,10 +24,10 @@ RUN patch build/_deps/libfmt-src/include/fmt/format.h -l -p0 < libfmt-long-doubl RUN echo "Compiling..." RUN source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make -RUN echo "Generate CDI" +RUN echo "Generating CDI" RUN source /opt/toolchains/dc/kos/environ.sh && \ - cp -R data/spawn build/data/spawn && \ - cp -R data/fonts build/data/fonts && \ + mv spawn.mpq build/data/ && \ + mv fonts.mpq build/data/fonts && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ ENTRYPOINT ["sh", "-c", "source /opt/toolchains/dc/kos/environ.sh && \"$@\"", "-s"] From 336f0462435c6ad08cd896a668b50174ffd8ca65 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Wed, 14 Aug 2024 02:16:14 +0100 Subject: [PATCH 06/33] Build in release mode --- .github/workflows/dreamcast.yml | 1 + CMake/platforms/dreamcast.cmake | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 17d633e60a1..7b2a500986f 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -48,6 +48,7 @@ jobs: export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ kos-cmake \ -S . \ + -DCMAKE_BUILD_TYPE=RelWithDebInfo \ -B build - name: Build DevilutionX diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index b4d15d757bf..1a929e24bf3 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -12,12 +12,10 @@ set(DEVILUTIONX_GAMEPAD_TYPE Nintendo) set(NOSOUND ON) set(DEVILUTIONX_STATIC_ZLIB ON) -#set(UNPACKED_MPQS ON) set(UNPACKED_SAVES ON) set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_STATIC_LUA ON) set(DEVILUTIONX_DISABLE_STRIP ON) -set(CMAKE_BUILD_TYPE Debug) set(DEVILUTIONX_ASSETS_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/data/") set(BUILD_ASSETS_MPQ OFF) From cae4e313fb40cbe7be51f85e086e6a96eb71fe1c Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Thu, 15 Aug 2024 01:50:57 +0100 Subject: [PATCH 07/33] Store save files in VMU to test multiplayer mode because multiplayer mode only stores player data on the VMU, the rest is stored in a std::unordered_map DeltaLevels in memory data structure. I'm hoping that with this approach, player data won't exceed 100 KB --- Source/loadsave.cpp | 14 +++-- Source/pfile.cpp | 72 +++++++++++++++++++++----- Source/utils/sdl2_to_1_2_backports.cpp | 2 +- 3 files changed, 69 insertions(+), 19 deletions(-) diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index a35746736ca..fef13142393 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -2237,7 +2237,8 @@ size_t HotkeysSize(size_t nHotkeys = NumHotkeys) void LoadHotkeys() { - LoadHelper file(OpenSaveArchive(gSaveNumber), "hotkeys"); + //hotkeys => htks to get around VMU filename size limits + LoadHelper file(OpenSaveArchive(gSaveNumber), "htks"); if (!file.IsValid()) return; @@ -2279,7 +2280,8 @@ void LoadHotkeys() void SaveHotkeys(SaveWriter &saveWriter, const Player &player) { - SaveHelper file(saveWriter, "hotkeys", HotkeysSize()); + //hotkeys => htks to get around VMU filename size limits + SaveHelper file(saveWriter, "htks", HotkeysSize()); // Write the number of spell hotkeys file.WriteLE(static_cast(NumHotkeys)); @@ -2299,7 +2301,8 @@ void SaveHotkeys(SaveWriter &saveWriter, const Player &player) void LoadHeroItems(Player &player) { - LoadHelper file(OpenSaveArchive(gSaveNumber), "heroitems"); + //heroitems => hitms to get around VMU filename size limits + LoadHelper file(OpenSaveArchive(gSaveNumber), "hitms"); if (!file.IsValid()) return; @@ -2586,8 +2589,9 @@ void LoadGame(bool firstflag) void SaveHeroItems(SaveWriter &saveWriter, Player &player) { - size_t itemCount = static_cast(NUM_INVLOC) + InventoryGridCells + MaxBeltItems; - SaveHelper file(saveWriter, "heroitems", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); + size_t itemCount = static_cast(NUM_INVLOC) + InventoryGridCells + MaxBeltItems; //7 + 40 + 8 = 55 + //heroitems => hitms to get around VMU filename size limits + SaveHelper file(saveWriter, "hitms", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); //55 * 368 + 1 = 20241 bytes file.WriteLE(gbIsHellfire ? 1 : 0); diff --git a/Source/pfile.cpp b/Source/pfile.cpp index caa3ef82f7d..eb4ed759166 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -57,25 +57,21 @@ bool gbValidSaveFile; void listdir(const char *dir, int depth) { file_t d = fs_open(dir, O_RDONLY | O_DIR); dirent_t *entry; - printf("============ RAMDISK ============\n"); + printf("============ %s ============\n", dir); while(NULL != (entry = fs_readdir(d))) { char absolutePath[1024]; strcpy(absolutePath, dir); strcat(absolutePath, "/"); strcat(absolutePath, entry->name); - uintmax_t size = 0; - if(!GetFileSize(absolutePath, &size)) { - size = 1337; - } bool isDir = entry->size == -1; - printf("[%s]\t%.2f kB\t%.2f kB (GetFileSize)\t%s\n", isDir ? "DIR" : "FIL", entry->size / 1024.0, size / 1024.0, entry->name); + printf("[%s]\t%.2f kB\t%s\n", isDir ? "DIR" : "FIL", entry->size / 1024.0, entry->name); if(isDir) { printf("absolutePath = %s, depth = %d\n", absolutePath, depth); listdir(absolutePath, depth + 1); } } fs_close(d); - printf("============ RAMDISK ============\n\n"); + printf("============ %s ============\n\n\n", dir); } namespace { @@ -84,10 +80,11 @@ char hero_names[MAX_CHARACTERS][PlayerNameLength]; std::string GetSavePath(uint32_t saveNum, std::string_view savePrefix = {}) { + //shorter names to get around VMU filename size limits return StrCat(paths::PrefPath(), savePrefix, gbIsSpawn - ? (gbIsMultiplayer ? "share_" : "spawn_") - : (gbIsMultiplayer ? "multi_" : "single_"), + ? (gbIsMultiplayer ? "M" : "S") + : (gbIsMultiplayer ? "m" : "s"), saveNum, #ifdef UNPACKED_SAVES #ifdef __DREAMCAST__ @@ -192,6 +189,7 @@ bool ReadHero(SaveReader &archive, PlayerPack *pPack) Log("\tpHPBase = {}", pPack->pHPBase); listdir("/ram", 0); + listdir("/vmu/a1", 0); return ret; } @@ -613,16 +611,30 @@ std::unique_ptr SaveReader::ReadFile(const char *filename, std::siz error = 0; const std::string path = dir_ + filename; Log("path = \"{}\"", path); - std::byte* contents; - ssize_t size = fs_load(path.c_str(), &contents); + std::byte* rawContents; + size_t read = fs_load(path.c_str(), &rawContents); + //read file size header + size_t size = 0; + memcpy(&size, rawContents, 4); + Log("rawSize = {}, {}, {}, {}", rawContents[0], rawContents[1], rawContents[2], rawContents[3]); Log("size = {}", size); - if(size == -1) + Log("read = {}", read); + if(size == 0) { Log("SaveReader::ReadFile KO"); return nullptr; } fileSize = size; + std::byte *contents = malloc(sizeof(std::byte) * size); + if(contents == nullptr) + { + Log("SaveReader::ReadFile KO"); + return nullptr; + } + //skip 32 bit header that contains real file size + memcpy(contents, rawContents + 4, size); result.reset(contents); + free(rawContents); Log("SaveReader::ReadFile OK"); return result; } @@ -634,6 +646,38 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s Log("dir_ = {}", dir_); Log("path = {}", path); const char* baseName = basename(path.c_str()); + if(!dir_.starts_with("/ram")) + { + int fd = fs_open(path.c_str(), O_WRONLY); + if(fd == -1) + { + LogError("fs_open(\"{}\", O_WRONLY) = -1", path); + return false; + } + std::byte rawSize[4]; + memcpy(rawSize, &size, 4); + Log("size = {}", size); + Log("rawSize = {}, {}, {}, {}", rawSize[0], rawSize[1], rawSize[2], rawSize[3]); + //32 bit header to store the file size + if(fs_write(fd, rawSize, 4) == -1) + { + LogError("fs_write({}, {}, 4) = -1, file size header", fd, size); + return false; + } + if(fs_write(fd, data, size) == -1) + { + LogError("fs_write({}, data, {}) = -1", fd, size); + return false; + } + if(fs_close(fd) == -1) + { + LogError("fs_close({}) = -1", fd); + return false; + } + listdir("/vmu/a1", 0); + return true; + } + bool exists = FileExists(baseName); if(exists) { @@ -655,7 +699,7 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s int attach_result = fs_ramdisk_attach(baseName, buffer, size); Log("\tAttach result: {}", attach_result); Log("Current ramdisk contents:"); - listdir("/ram", 0); + listdir("/ram", 0); return attach_result != -1; } #else @@ -908,10 +952,12 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) std::optional archive = OpenSaveArchive(saveNum); if (!archive) { listdir("/ram", 0); + listdir("/vmu/a1", 0); app_fatal(_("Unable to open archive")); } if (!ReadHero(*archive, &pkplr)) { listdir("/ram", 0); + listdir("/vmu/a1", 0); app_fatal(_("Unable to load character")); } diff --git a/Source/utils/sdl2_to_1_2_backports.cpp b/Source/utils/sdl2_to_1_2_backports.cpp index 525c82ad5a5..a41addb89a2 100644 --- a/Source/utils/sdl2_to_1_2_backports.cpp +++ b/Source/utils/sdl2_to_1_2_backports.cpp @@ -882,7 +882,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) retval = SDL_strdup("PROGDIR:"); return retval; #elif defined(__DREAMCAST__) - retval = SDL_strdup("/ram/"); + retval = SDL_strdup("/vmu/a1/"); return retval; #endif From 5ed9ab417869f9be2efb9be79b43e44079740fc7 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 18 Aug 2024 02:38:43 +0100 Subject: [PATCH 08/33] Reduce multiplayer saving frequency to once per 10 minutes of gameplay . Replace fs_* calls with regular std::f* calls . Temporarily disable hero items storage --- Source/diablo.cpp | 5 ++ Source/init.cpp | 1 + Source/loadsave.cpp | 7 +-- Source/msg.cpp | 1 + Source/pfile.cpp | 124 +++++++++++++++++++++++++++----------------- 5 files changed, 86 insertions(+), 52 deletions(-) diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 4d15ca78421..f91726e6bf7 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -789,7 +789,11 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState) default: if (IsCustomEvent(event.type)) { if (gbIsMultiplayer) + { + Log("IsCustomEvent({}) = true", event.type); + Log("pfile_write_hero"); pfile_write_hero(); + } nthread_ignore_mutex(true); PaletteFadeOut(8); sound_stop(); @@ -909,6 +913,7 @@ void RunGameLoop(interface_mode uMsg) demo::NotifyGameLoopEnd(); if (gbIsMultiplayer) { + Log("gbRunGame = {}, pfile_write_hero(/*writeGameData=*/false)", gbRunGame); pfile_write_hero(/*writeGameData=*/false); sfile_write_stash(); } diff --git a/Source/init.cpp b/Source/init.cpp index 839eeb4a4bc..5d1c10891ab 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -248,6 +248,7 @@ bool AreExtraFontsOutOfDate(MpqArchive &archive) void init_cleanup() { if (gbIsMultiplayer && gbRunGame) { + Log("init_cleanup() gbIsMultiplayer && gbRunGame"); pfile_write_hero(/*writeGameData=*/false); sfile_write_stash(); } diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index fef13142393..12b935df015 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -2587,9 +2587,10 @@ void LoadGame(bool firstflag) gbIsHellfireSaveGame = gbIsHellfire; } +//todo restore saving of inventory body void SaveHeroItems(SaveWriter &saveWriter, Player &player) { - size_t itemCount = static_cast(NUM_INVLOC) + InventoryGridCells + MaxBeltItems; //7 + 40 + 8 = 55 + size_t itemCount = static_cast(NUM_INVLOC) + /*InventoryGridCells +*/ MaxBeltItems; //7 + 40 + 8 = 55 //heroitems => hitms to get around VMU filename size limits SaveHelper file(saveWriter, "hitms", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); //55 * 368 + 1 = 20241 bytes @@ -2597,8 +2598,8 @@ void SaveHeroItems(SaveWriter &saveWriter, Player &player) for (const Item &item : player.InvBody) SaveItem(file, item); - for (const Item &item : player.InvList) - SaveItem(file, item); + //for (const Item &item : player.InvList) + //SaveItem(file, item); for (const Item &item : player.SpdList) SaveItem(file, item); } diff --git a/Source/msg.cpp b/Source/msg.cpp index cbcfd6bcb31..98d2d229ea3 100644 --- a/Source/msg.cpp +++ b/Source/msg.cpp @@ -2655,6 +2655,7 @@ void DeltaSaveLevel() if (!gbIsMultiplayer) return; + Log("DeltaSaveLevel"); for (Player &player : Players) { if (&player != MyPlayer) ResetPlayerGFX(player); diff --git a/Source/pfile.cpp b/Source/pfile.cpp index eb4ed759166..116b4f605bd 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -611,34 +611,50 @@ std::unique_ptr SaveReader::ReadFile(const char *filename, std::siz error = 0; const std::string path = dir_ + filename; Log("path = \"{}\"", path); - std::byte* rawContents; - size_t read = fs_load(path.c_str(), &rawContents); - //read file size header size_t size = 0; - memcpy(&size, rawContents, 4); - Log("rawSize = {}, {}, {}, {}", rawContents[0], rawContents[1], rawContents[2], rawContents[3]); + FILE* file = OpenFile(path.c_str(), "rb"); + //first we read the real file size using the prewritten 4 byte header + std::fread(&size, sizeof(std::byte), 4, file); Log("size = {}", size); - Log("read = {}", read); if(size == 0) { + app_fatal("SaveReader::ReadFile KO"); Log("SaveReader::ReadFile KO"); + std::fclose(file); return nullptr; } fileSize = size; - std::byte *contents = malloc(sizeof(std::byte) * size); - if(contents == nullptr) - { - Log("SaveReader::ReadFile KO"); - return nullptr; - } - //skip 32 bit header that contains real file size - memcpy(contents, rawContents + 4, size); - result.reset(contents); - free(rawContents); + result.reset(new std::byte[size]); + std::fread(result.get(), sizeof(std::byte), size, file); Log("SaveReader::ReadFile OK"); return result; } +/* + * todo: add bzip compression to the inventory data (hitms) +std::byte* compressHeroItems(std::byte *data, size_t size) +{ + int bzBuffToBuffCompress( char* dest, + unsigned int* destLen, + char* source, + unsigned int sourceLen, + int blockSize100k, + int verbosity, + int workFactor ); + + char *compressed = malloc(sizeof(std::byte) * size); + size_t compressedLength; + if(BZ_OK != bzBuffToBuffCompress( + compressed, + &compressedLength, + data, + size, + + )) { + free(compressed); + } +}*/ + bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t size) { Log("SaveWriter::WriteFile(\"{}\", data[], {})", filename, size); @@ -646,38 +662,44 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s Log("dir_ = {}", dir_); Log("path = {}", path); const char* baseName = basename(path.c_str()); - if(!dir_.starts_with("/ram")) - { - int fd = fs_open(path.c_str(), O_WRONLY); - if(fd == -1) - { - LogError("fs_open(\"{}\", O_WRONLY) = -1", path); - return false; - } - std::byte rawSize[4]; - memcpy(rawSize, &size, 4); - Log("size = {}", size); - Log("rawSize = {}, {}, {}, {}", rawSize[0], rawSize[1], rawSize[2], rawSize[3]); - //32 bit header to store the file size - if(fs_write(fd, rawSize, 4) == -1) - { - LogError("fs_write({}, {}, 4) = -1, file size header", fd, size); - return false; - } - if(fs_write(fd, data, size) == -1) - { - LogError("fs_write({}, data, {}) = -1", fd, size); - return false; - } - if(fs_close(fd) == -1) - { - LogError("fs_close({}) = -1", fd); - return false; - } - listdir("/vmu/a1", 0); - return true; - } + //vmu code + if(!dir_.starts_with("/ram")) + { + FILE* file = OpenFile(path.c_str(), "wb"); + if(file == nullptr) + { + LogError("fopen(\"{}\", \"wb\") = nullptr", path); + app_fatal("SaveReader::ReadFile KO"); + return false; + } + //32 bit header to store the file size because the VMU adds padding + //we will be reading it first in SaveWriter::ReadFile + std::byte rawSize[4]; + memcpy(rawSize, &size, 4); + Log("size = {}", size); + Log("rawSize = {}, {}, {}, {}", rawSize[0], rawSize[1], rawSize[2], rawSize[3]); + size_t written = std::fwrite(rawSize, sizeof(std::byte), 4, file); + if(written != 4) + { + LogError("fwrite(rawSize, {], 4, file) = {} != 4, file size header", sizeof(std::byte), written); + return false; + } + written = std::fwrite(data, sizeof(std::byte), size, file); + if(written != size) + { + LogError("fwrite(data, {}, {}, file) = {} != -1", sizeof(std::byte), size, written); + return false; + } + if(std::fclose(file) != 0) + { + LogError("fclose(file) = 0"); + return false; + } + listdir("/vmu/a1", 0); + return true; + } + //ramdisk code bool exists = FileExists(baseName); if(exists) { @@ -699,7 +721,7 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s int attach_result = fs_ramdisk_attach(baseName, buffer, size); Log("\tAttach result: {}", attach_result); Log("Current ramdisk contents:"); - listdir("/ram", 0); + listdir("/ram", 0); return attach_result != -1; } #else @@ -825,6 +847,7 @@ void pfile_write_hero_demo(int demo) HeroCompareResult pfile_compare_hero_demo(int demo, bool logDetails) { + Log("pfile_compare_hero_demo({}, {})", demo, logDetails); std::string referenceSavePath = GetSavePath(gSaveNumber, StrCat("demo_", demo, "_reference_")); if (!FileExists(referenceSavePath.c_str())) @@ -1002,9 +1025,12 @@ void pfile_update(bool forceSave) return; Uint32 tick = SDL_GetTicks(); - if (!forceSave && tick - prevTick <= 60000) + //600000 instead of 60000 + //60000 ms is too frequent for the VMU, the game hangs too often and too long + if (!forceSave && tick - prevTick <= 600000) return; + Log("pfile_update({})", forceSave); prevTick = tick; pfile_write_hero(); sfile_write_stash(); From aaf2a177cc08b497f5e981db3962aef91d6f9883 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Tue, 20 Aug 2024 12:11:41 +0100 Subject: [PATCH 09/33] Save to VMU using vmu_pkg_* API . Restore hero items saving --- Source/loadsave.cpp | 6 +-- Source/pfile.cpp | 100 +++++++++++++++++++++++++++++++------------- 2 files changed, 73 insertions(+), 33 deletions(-) diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 12b935df015..a3f3c8b2849 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -2590,7 +2590,7 @@ void LoadGame(bool firstflag) //todo restore saving of inventory body void SaveHeroItems(SaveWriter &saveWriter, Player &player) { - size_t itemCount = static_cast(NUM_INVLOC) + /*InventoryGridCells +*/ MaxBeltItems; //7 + 40 + 8 = 55 + size_t itemCount = static_cast(NUM_INVLOC) + InventoryGridCells + MaxBeltItems; //7 + 40 + 8 = 55 //heroitems => hitms to get around VMU filename size limits SaveHelper file(saveWriter, "hitms", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); //55 * 368 + 1 = 20241 bytes @@ -2598,8 +2598,8 @@ void SaveHeroItems(SaveWriter &saveWriter, Player &player) for (const Item &item : player.InvBody) SaveItem(file, item); - //for (const Item &item : player.InvList) - //SaveItem(file, item); + for (const Item &item : player.InvList) + SaveItem(file, item); for (const Item &item : player.SpdList) SaveItem(file, item); } diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 116b4f605bd..1ff036fcab4 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #endif #ifdef UNPACKED_SAVES @@ -607,26 +608,34 @@ void RemoveAllInvalidItems(Player &player) std::unique_ptr SaveReader::ReadFile(const char *filename, std::size_t &fileSize, int32_t &error) { Log("SaveReader::ReadFile(\"{}\", fileSize, error)", filename); - std::unique_ptr result; error = 0; const std::string path = dir_ + filename; Log("path = \"{}\"", path); size_t size = 0; - FILE* file = OpenFile(path.c_str(), "rb"); - //first we read the real file size using the prewritten 4 byte header - std::fread(&size, sizeof(std::byte), 4, file); - Log("size = {}", size); - if(size == 0) + uint8 *contents; + if(fs_load(path.c_str(), &contents) == -1) { - app_fatal("SaveReader::ReadFile KO"); - Log("SaveReader::ReadFile KO"); - std::fclose(file); + error = 1; + LogError("fs_load(\"{}\", &contents) = -1", path); + app_fatal("SaveReader::ReadFile " + path + " KO"); return nullptr; } - fileSize = size; - result.reset(new std::byte[size]); - std::fread(result.get(), sizeof(std::byte), size, file); - Log("SaveReader::ReadFile OK"); + vmu_pkg_t package; + if(vmu_pkg_parse(contents, &package) < 0) + { + error = 1; + free(contents); + LogError("vmu_pkg_parse = -1"); + app_fatal("vmu_pkg_parse failed"); + return nullptr; + } + Log("Parsed package {} ({})", package.desc_short, package.desc_long); + fileSize = package.data_len; + std::unique_ptr result; + result.reset(new std::byte[fileSize]); + memcpy(result.get(), package.data, fileSize); + //free(package.data); + free(contents); return result; } @@ -663,38 +672,57 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s Log("path = {}", path); const char* baseName = basename(path.c_str()); //vmu code - if(!dir_.starts_with("/ram")) + if(dir_.starts_with("/vmu")) { - FILE* file = OpenFile(path.c_str(), "wb"); - if(file == nullptr) + vmu_pkg_t package; + strcpy(package.app_id, "DevilutionX"); + strncpy(package.desc_short, filename, 20); + strcpy(package.desc_long, "Diablo 1 save data"); + package.icon_cnt = 0; + package.icon_anim_speed = 0; + package.eyecatch_type = VMUPKG_EC_NONE; + package.data_len = size; + package.data = new uint8[size]; + memcpy(package.data, data, size); + + uint8 *contents; + size_t packageSize; + if(vmu_pkg_build(&package, &contents, &packageSize) < 0) { - LogError("fopen(\"{}\", \"wb\") = nullptr", path); - app_fatal("SaveReader::ReadFile KO"); + delete[] package.data; + LogError("vmu_pkg_build failed"); + app_fatal("vmu_pkg_build failed"); return false; } - //32 bit header to store the file size because the VMU adds padding - //we will be reading it first in SaveWriter::ReadFile - std::byte rawSize[4]; - memcpy(rawSize, &size, 4); - Log("size = {}", size); - Log("rawSize = {}, {}, {}, {}", rawSize[0], rawSize[1], rawSize[2], rawSize[3]); - size_t written = std::fwrite(rawSize, sizeof(std::byte), 4, file); - if(written != 4) + FILE* file = OpenFile(path.c_str(), "wb"); + if(file == nullptr) { - LogError("fwrite(rawSize, {], 4, file) = {} != 4, file size header", sizeof(std::byte), written); + delete[] package.data; + free(contents); + LogError("fopen(\"{}\", \"wb\") = nullptr", path); + app_fatal("SaveReader::WriteFile KO"); return false; } - written = std::fwrite(data, sizeof(std::byte), size, file); - if(written != size) + size_t written = std::fwrite(contents, sizeof(uint8), packageSize, file); + if(written != packageSize) { - LogError("fwrite(data, {}, {}, file) = {} != -1", sizeof(std::byte), size, written); + delete[] package.data; + free(contents); + std::fclose(file); + LogError("fwrite(data, {}, {}, file) = {} != -1", sizeof(uint8), packageSize, written); + app_fatal("vmu fwrite call failed"); return false; } if(std::fclose(file) != 0) { + delete[] package.data; + free(contents); LogError("fclose(file) = 0"); + app_fatal("fclose(file) = 0"); return false; } + delete[] package.data; + free(contents); listdir("/vmu/a1", 0); return true; } @@ -797,6 +825,7 @@ std::unique_ptr ReadArchive(SaveReader &archive, const char *pszNam std::unique_ptr result = archive.ReadFile(pszName, length, error); if (error != 0) { Log("ReadArchive 0 error = {}", error); + app_fatal("ReadArchive 0 = " + error); return nullptr; } @@ -804,6 +833,7 @@ std::unique_ptr ReadArchive(SaveReader &archive, const char *pszNam std::size_t decodedLength = codec_decode(result.get(), length, pfile_get_password()); if (decodedLength == 0) { Log("ReadArchive nullptr"); + app_fatal("decodedLength = 0"); return nullptr; } if(strcmp(pszName, "hero") == 0) @@ -878,6 +908,7 @@ void sfile_write_stash() bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) { + Log("pfile_ui_set_hero_infos"); memset(hero_names, 0, sizeof(hero_names)); for (uint32_t i = 0; i < MAX_CHARACTERS; i++) { @@ -885,9 +916,12 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) if (archive) { PlayerPack pkplr; if (ReadHero(*archive, &pkplr)) { + Log("ReadHero OK"); + Log("Player {}, HP = {}", pkplr.pName, pkplr.pHPBase); _uiheroinfo uihero; uihero.saveNumber = i; strcpy(hero_names[i], pkplr.pName); + Log("hero_names[{}] = {}", i, pkplr.pName); bool hasSaveGame = ArchiveContainsGame(*archive); if (hasSaveGame) pkplr.bIsHellfire = gbIsHellfireSaveGame ? 1 : 0; @@ -902,9 +936,14 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) Game2UiPlayer(player, &uihero, hasSaveGame); uiAddHeroInfo(&uihero); } + else { + Log("ReadHero(*archive, &pkplr) failed"); + app_fatal("ReadHero(*archive, &pkplr) failed"); + } } } + Log("pfile_ui_set_hero_infos OK"); return true; } @@ -993,6 +1032,7 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) LoadHeroItems(player); RemoveAllInvalidItems(player); CalcPlrInv(player, false); + Log("pfile_read_player_from_save OK"); } void pfile_save_level() From dc8124be313ff20e08c03065e0af4b03e3520348 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 25 Aug 2024 00:37:56 +0100 Subject: [PATCH 10/33] Remove /vmu from MPQ search paths --- Source/init.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Source/init.cpp b/Source/init.cpp index 5d1c10891ab..edd92a344d2 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -123,6 +123,9 @@ std::vector GetMPQSearchPaths() { std::vector paths; paths.push_back(paths::BasePath()); +#if defined(__DREAMCAST__) + return paths; +#endif paths.push_back(paths::PrefPath()); if (paths[0] == paths[1]) paths.pop_back(); From 733f88adf9f6475fbb8a82636efeb184a8490c95 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Fri, 30 Aug 2024 19:42:21 +0100 Subject: [PATCH 11/33] Remove HP log that crashes console for some reason --- Source/pfile.cpp | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 1ff036fcab4..b036c56ae26 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -187,7 +187,7 @@ bool ReadHero(SaveReader &archive, PlayerPack *pPack) } Log("{} == sizeof(*pPack) ({}) = {}", read, sizeof(*pPack), read == sizeof(*pPack)); Log("Read player {}", pPack->pName); - Log("\tpHPBase = {}", pPack->pHPBase); + //Log("\tpHPBase = {}", pPack->pHPBase); listdir("/ram", 0); listdir("/vmu/a1", 0); @@ -204,7 +204,7 @@ void EncodeHero(SaveWriter &saveWriter, const PlayerPack *pack) memcpy(packed.get(), pack, sizeof(*pack)); codec_encode(packed.get(), sizeof(*pack), packedLen, pfile_get_password()); Log("Saving player {}", pack->pName); - Log("\tpHPBase = {}", pack->pHPBase); + //Log("\tpHPBase = {}", pack->pHPBase); bool result = saveWriter.WriteFile("hero", packed.get(), packedLen /* sizeof(*pack) */); Log("saveWriter.WriteFile(\"hero\", packed.get(), {}) = {}", packedLen, result); } @@ -841,7 +841,7 @@ std::unique_ptr ReadArchive(SaveReader &archive, const char *pszNam PlayerPack pPack; memcpy(&pPack, result.get(), decodedLength); Log("ReadArchive player {}", pPack.pName); - Log("\tpHPBase = {}", pPack.pHPBase); + //Log("\tpHPBase = {}", pPack.pHPBase); } Log("ReadArchive 2"); @@ -917,7 +917,8 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) PlayerPack pkplr; if (ReadHero(*archive, &pkplr)) { Log("ReadHero OK"); - Log("Player {}, HP = {}", pkplr.pName, pkplr.pHPBase); + Log("Player {}", pkplr.pName); + //Log("Player {}, HP = {}", pkplr.pName, pkplr.pHPBase); _uiheroinfo uihero; uihero.saveNumber = i; strcpy(hero_names[i], pkplr.pName); From fb6e5c087138870949c3aac02ebf6453987de41f Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Fri, 30 Aug 2024 20:02:12 +0100 Subject: [PATCH 12/33] Update spawn.mpq URL & set build mode to release --- .github/workflows/dreamcast.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 7b2a500986f..0e97cd13da6 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -34,7 +34,7 @@ jobs: - name: Download spawn.mpq run: | - curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq + curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/spawn.mpq - name: Download fonts.mpq run: | @@ -48,7 +48,7 @@ jobs: export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ kos-cmake \ -S . \ - -DCMAKE_BUILD_TYPE=RelWithDebInfo \ + -DCMAKE_BUILD_TYPE=Release \ -B build - name: Build DevilutionX From 7d1aec8804716328ab213cdc69c8b2732e4ee8b2 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Thu, 5 Sep 2024 21:24:48 +0100 Subject: [PATCH 13/33] Use GPF port of SDL for performance reasons --- .github/workflows/dreamcast.yml | 15 +++++++++++++++ CMake/platforms/dreamcast.cmake | 23 +++++++++++++++-------- Source/utils/display.cpp | 11 +++++++++++ Source/utils/sdl_compat.h | 6 ++++++ 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 0e97cd13da6..7bd4c0e5d74 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -32,6 +32,21 @@ jobs: with: fetch-depth: 0 + - name: Uninstall kos-ports SDL 1.2 + run: | + source /opt/toolchains/dc/kos/environ.sh && \ + cd /opt/toolchains/dc/kos-ports/SDL && \ + make uninstall || echo 'SDL 1.2 uninstall finished with non zero status, proceding anyway' + + - name: Install GPF SDL 1.2 + run: | + git clone -b SDL-dreamhal--GLDC https://github.com/GPF/SDL-1.2 && \ + cd SDL-1.2 && \ + source /opt/toolchains/dc/kos/environ.sh && \ + make -f Makefile.dc && \ + cp /opt/toolchains/dc/kos/addons/lib/dreamcast/libSDL.a /usr/lib/ && \ + cp include/* /usr/include/SDL/ + - name: Download spawn.mpq run: | curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/spawn.mpq diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index 1a929e24bf3..913873893e4 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -5,7 +5,7 @@ set(UBSAN OFF) set(USE_SDL1 ON) set(SDL1_VIDEO_MODE_BPP 8) -set(SDL1_VIDEO_MODE_FLAGS SDL_FULLSCREEN|SDL_HWSURFACE) +set(SDL1_VIDEO_MODE_FLAGS SDL_FULLSCREEN|SDL_DOUBLEBUF|SDL_HWSURFACE|SDL_HWPALETTE) set(DEFAULT_WIDTH 640) set(DEFAULT_HEIGHT 480) set(DEVILUTIONX_GAMEPAD_TYPE Nintendo) @@ -25,10 +25,11 @@ list(APPEND DEVILUTIONX_PLATFORM_COMPILE_DEFINITIONS __DREAMCAST__) add_compile_options(-fpermissive) #SDL Joystick axis mapping (circle-pad/C-stick) -set(JOY_AXIS_LEFTX 0) -set(JOY_AXIS_LEFTY 1) -set(JOY_AXIS_RIGHTX 2) -set(JOY_AXIS_RIGHTY 3) +set(JOY_AXIS_LEFTX 11) +set(JOY_AXIS_LEFTY 9) +set(JOY_AXIS_RIGHTX 12) +set(JOY_AXIS_RIGHTY 10) + #SDL Joystick hat mapping (D-pad) set(JOY_HAT_DPAD_UP_HAT 0) set(JOY_HAT_DPAD_RIGHT_HAT 0) @@ -41,11 +42,17 @@ set(JOY_HAT_DPAD_LEFT 8) #SDL Joystick button mapping (A / B and X / Y inverted) set(JOY_BUTTON_A 2) set(JOY_BUTTON_B 1) -set(JOY_BUTTON_X 4) -set(JOY_BUTTON_Y 3) +set(JOY_BUTTON_X 5) +set(JOY_BUTTON_Y 6) #set(JOY_BUTTON_LEFTSHOULDER 5) #set(JOY_BUTTON_RIGHTSHOULDER 6) set(JOY_BUTTON_BACK 7) -set(JOY_BUTTON_START 0) +set(JOY_BUTTON_START 3) set(JOY_BUTTON_TRIGGERLEFT 8) set(JOY_BUTTON_TRIGGERRIGHT 9) + +#GPF SDL files +set(SDL_INCLUDE_DIR /usr/include/SDL/) +set(SDL_LIBRARY /usr/lib/libSDL.a) + +set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") diff --git a/Source/utils/display.cpp b/Source/utils/display.cpp index f15530c436f..6ad4c1f86ac 100644 --- a/Source/utils/display.cpp +++ b/Source/utils/display.cpp @@ -43,6 +43,10 @@ #endif #endif +#ifdef __DREAMCAST__ +#include +#endif + namespace devilution { extern SDLSurfaceUniquePtr RendererTextureSurface; /** defined in dx.cpp */ @@ -249,6 +253,13 @@ void SetVideoModeToPrimary(bool fullscreen, int width, int height) #ifdef __3DS__ flags &= ~SDL_FULLSCREEN; flags |= Get3DSScalingFlag(*sgOptions.Graphics.fitToScreen, width, height); +#endif +#ifdef __DREAMCAST__ + SDL_DC_VerticalWait(SDL_FALSE); + SDL_DC_ShowAskHz(SDL_FALSE); + SDL_DC_EmulateKeyboard(SDL_FALSE); + SDL_DC_EmulateMouse(SDL_FALSE); + SDL_DC_SetVideoDriver(SDL_DC_DMA_VIDEO); #endif SetVideoMode(width, height, SDL1_VIDEO_MODE_BPP, flags); if (OutputRequiresScaling()) diff --git a/Source/utils/sdl_compat.h b/Source/utils/sdl_compat.h index bf1d79d4ff5..8df46a101f2 100644 --- a/Source/utils/sdl_compat.h +++ b/Source/utils/sdl_compat.h @@ -77,9 +77,15 @@ inline int SDLC_SetSurfaceAndPaletteColors(SDL_Surface *surface, SDL_Palette *pa // When the video surface is 8bit, we need to set the output palette as well. SDL_SetColors(SDL_GetVideoSurface(), colors, firstcolor, ncolors); #endif +#ifdef __DREAMCAST__ + // todo figure out why - 1 crashes on dreamcast + return SDL_SetPalette(surface, SDL_PHYSPAL, colors, firstcolor, ncolors); +#else // In SDL1, the surface always has its own distinct palette, so we need to // update it as well. return SDL_SetPalette(surface, SDL_LOGPAL, colors, firstcolor, ncolors) - 1; +#endif //defined(__DREAMCAST__) + #else // !USE_SDL1 if (SDL_SetPaletteColors(palette, colors, firstcolor, ncolors) < 0) return -1; From 2e12cccd6f3f80ed4b9526cbfbac5723cec62717 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Thu, 5 Sep 2024 21:49:49 +0100 Subject: [PATCH 14/33] Comment memory-related preprocessor lines to fix compilation error --- .github/workflows/dreamcast.yml | 6 +++++- SDL_stdinc.h.patch | 12 ++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) create mode 100644 SDL_stdinc.h.patch diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 7bd4c0e5d74..020a71b0dd2 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -38,6 +38,9 @@ jobs: cd /opt/toolchains/dc/kos-ports/SDL && \ make uninstall || echo 'SDL 1.2 uninstall finished with non zero status, proceding anyway' + #- name: Setup tmate session + # uses: mxschmitt/action-tmate@v3 + - name: Install GPF SDL 1.2 run: | git clone -b SDL-dreamhal--GLDC https://github.com/GPF/SDL-1.2 && \ @@ -45,7 +48,8 @@ jobs: source /opt/toolchains/dc/kos/environ.sh && \ make -f Makefile.dc && \ cp /opt/toolchains/dc/kos/addons/lib/dreamcast/libSDL.a /usr/lib/ && \ - cp include/* /usr/include/SDL/ + cp include/* /usr/include/SDL/ && \ + patch /usr/include/SDL/SDL_stdinc.h -l -p0 < ../SDL_stdinc.h.patch - name: Download spawn.mpq run: | diff --git a/SDL_stdinc.h.patch b/SDL_stdinc.h.patch new file mode 100644 index 00000000000..ce8e8529c4f --- /dev/null +++ b/SDL_stdinc.h.patch @@ -0,0 +1,12 @@ +--- include/SDL_stdinc.h ++++ /usr/include/SDL/SDL_stdinc.h +@@ -32,9 +32,9 @@ + + #include "SDL_config.h" + +-#if defined(__DREAMCAST__) +-#include "memfuncs.h" +-#endif ++//#if defined(__DREAMCAST__) ++//#include "memfuncs.h" ++//#endif From 54276d96d6ff99fc8fc914d5de7e607fb7cd482a Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Fri, 6 Sep 2024 21:03:46 +0100 Subject: [PATCH 15/33] Remove SDL_stdinc.h patch since the memfuncs.h header is no longer being included This reverts commit 8d37150e07004acc96a5d357c30d62c66df65591. --- .github/workflows/dreamcast.yml | 6 +----- SDL_stdinc.h.patch | 12 ------------ 2 files changed, 1 insertion(+), 17 deletions(-) delete mode 100644 SDL_stdinc.h.patch diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 020a71b0dd2..7bd4c0e5d74 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -38,9 +38,6 @@ jobs: cd /opt/toolchains/dc/kos-ports/SDL && \ make uninstall || echo 'SDL 1.2 uninstall finished with non zero status, proceding anyway' - #- name: Setup tmate session - # uses: mxschmitt/action-tmate@v3 - - name: Install GPF SDL 1.2 run: | git clone -b SDL-dreamhal--GLDC https://github.com/GPF/SDL-1.2 && \ @@ -48,8 +45,7 @@ jobs: source /opt/toolchains/dc/kos/environ.sh && \ make -f Makefile.dc && \ cp /opt/toolchains/dc/kos/addons/lib/dreamcast/libSDL.a /usr/lib/ && \ - cp include/* /usr/include/SDL/ && \ - patch /usr/include/SDL/SDL_stdinc.h -l -p0 < ../SDL_stdinc.h.patch + cp include/* /usr/include/SDL/ - name: Download spawn.mpq run: | diff --git a/SDL_stdinc.h.patch b/SDL_stdinc.h.patch deleted file mode 100644 index ce8e8529c4f..00000000000 --- a/SDL_stdinc.h.patch +++ /dev/null @@ -1,12 +0,0 @@ ---- include/SDL_stdinc.h -+++ /usr/include/SDL/SDL_stdinc.h -@@ -32,9 +32,9 @@ - - #include "SDL_config.h" - --#if defined(__DREAMCAST__) --#include "memfuncs.h" --#endif -+//#if defined(__DREAMCAST__) -+//#include "memfuncs.h" -+//#endif From 819e48b96ed6ded3cf3485dc71a8d12b86ace790 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sat, 7 Sep 2024 19:55:19 +0100 Subject: [PATCH 16/33] Remove -O3, no perf gain was noticed after adding it --- CMake/platforms/dreamcast.cmake | 2 -- 1 file changed, 2 deletions(-) diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index 913873893e4..7409b65f675 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -54,5 +54,3 @@ set(JOY_BUTTON_TRIGGERRIGHT 9) #GPF SDL files set(SDL_INCLUDE_DIR /usr/include/SDL/) set(SDL_LIBRARY /usr/lib/libSDL.a) - -set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -O3") From 2b60fccb6d2ce2489c30fc22f11aeb8d5b000d74 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 8 Sep 2024 01:34:43 +0100 Subject: [PATCH 17/33] Fix shareware framerate The shareware version has worse performance for some reason . Remove seemingly unecessary palette code . Enable DMA driver in SetFullscreenMode for good measure --- .github/workflows/dreamcast.yml | 1 + Source/utils/display.cpp | 22 +++++++++++++++------- Source/utils/sdl_compat.h | 5 +++-- 3 files changed, 19 insertions(+), 9 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 7bd4c0e5d74..ee3b1740608 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -6,6 +6,7 @@ on: # yamllint disable-line rule:truthy branches: - master - dreamcast + - dreamcast-sdl paths-ignore: - '*.md' - 'docs/**' diff --git a/Source/utils/display.cpp b/Source/utils/display.cpp index 6ad4c1f86ac..3f0b616ec83 100644 --- a/Source/utils/display.cpp +++ b/Source/utils/display.cpp @@ -45,6 +45,15 @@ #ifdef __DREAMCAST__ #include + +void enable_dma_driver() +{ + SDL_DC_VerticalWait(SDL_FALSE); + SDL_DC_ShowAskHz(SDL_TRUE); + SDL_DC_EmulateKeyboard(SDL_FALSE); + SDL_DC_EmulateMouse(SDL_FALSE); + SDL_DC_SetVideoDriver(SDL_DC_DMA_VIDEO); +} #endif namespace devilution { @@ -233,6 +242,9 @@ float GetDpiScalingFactor() void SetVideoMode(int width, int height, int bpp, uint32_t flags) { Log("Setting video mode {}x{} bpp={} flags=0x{:08X}", width, height, bpp, flags); +#ifdef __DREAMCAST__ + enable_dma_driver(); +#endif ghMainWnd = SDL_SetVideoMode(width, height, bpp, flags); if (ghMainWnd == nullptr) { ErrSdl(); @@ -253,13 +265,6 @@ void SetVideoModeToPrimary(bool fullscreen, int width, int height) #ifdef __3DS__ flags &= ~SDL_FULLSCREEN; flags |= Get3DSScalingFlag(*sgOptions.Graphics.fitToScreen, width, height); -#endif -#ifdef __DREAMCAST__ - SDL_DC_VerticalWait(SDL_FALSE); - SDL_DC_ShowAskHz(SDL_FALSE); - SDL_DC_EmulateKeyboard(SDL_FALSE); - SDL_DC_EmulateMouse(SDL_FALSE); - SDL_DC_SetVideoDriver(SDL_DC_DMA_VIDEO); #endif SetVideoMode(width, height, SDL1_VIDEO_MODE_BPP, flags); if (OutputRequiresScaling()) @@ -473,6 +478,9 @@ void SetFullscreenMode() if (*sgOptions.Graphics.fullscreen) { flags |= SDL_FULLSCREEN; } +#ifdef __DREAMCAST__ + enable_dma_driver(); +#endif ghMainWnd = SDL_SetVideoMode(0, 0, 0, flags); if (ghMainWnd == NULL) { ErrSdl(); diff --git a/Source/utils/sdl_compat.h b/Source/utils/sdl_compat.h index 8df46a101f2..56c1c6aebfa 100644 --- a/Source/utils/sdl_compat.h +++ b/Source/utils/sdl_compat.h @@ -77,9 +77,10 @@ inline int SDLC_SetSurfaceAndPaletteColors(SDL_Surface *surface, SDL_Palette *pa // When the video surface is 8bit, we need to set the output palette as well. SDL_SetColors(SDL_GetVideoSurface(), colors, firstcolor, ncolors); #endif + #ifdef __DREAMCAST__ - // todo figure out why - 1 crashes on dreamcast - return SDL_SetPalette(surface, SDL_PHYSPAL, colors, firstcolor, ncolors); + // todo figure out why the SDL_SetPalette call crashes on dreamcast + return 0; #else // In SDL1, the surface always has its own distinct palette, so we need to // update it as well. From d11ef3f3d5f5b160d4ccfc39fbd037fe13fc30ae Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 8 Sep 2024 20:06:32 +0100 Subject: [PATCH 18/33] Include data directory in elf artifact to make it easier for thosre who want to use their own copy of diabdat.mpq instead of spawn.mpq and fonts.mpq --- .github/workflows/dreamcast.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index ee3b1740608..afe50e51769 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -80,12 +80,17 @@ jobs: mv fonts.mpq build/data/fonts/ && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ + - name: Prepare elf package + run: rm build/data/spawn.mpq && rm build/data/fonts/fonts.mpq + - name: Upload .elf Package if: ${{ !env.ACT }} uses: actions/upload-artifact@v4 with: - name: devilutionx.elf - path: ./build/devilutionx.elf + name: devilutionx.zip + path: | + build/data + build/devilutionx.elf - name: Upload .cdi Package if: ${{ !env.ACT }} From 679cca5ae8b6de62b7f3d9716bf7ee7222708788 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Tue, 10 Sep 2024 03:57:05 +0100 Subject: [PATCH 19/33] Temporarily disable single player mode due to save file sizes --- Source/DiabloUI/mainmenu.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index f2592038615..ba240932266 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/Source/DiabloUI/mainmenu.cpp @@ -35,7 +35,11 @@ void MainmenuEsc() void MainmenuLoad(const char *name) { +#ifndef __DREAMCAST__ + //single player save files are too big for the VMU + //todo reactivate when SD card saving is implemented vecMenuItems.push_back(std::make_unique(_("Single Player"), MAINMENU_SINGLE_PLAYER)); +#endif vecMenuItems.push_back(std::make_unique(_("Multi Player"), MAINMENU_MULTIPLAYER)); vecMenuItems.push_back(std::make_unique(_("Settings"), MAINMENU_SETTINGS)); vecMenuItems.push_back(std::make_unique(_("Support"), MAINMENU_SHOW_SUPPORT)); From dc4e01cffa8c83e4c3633318fad667e233a053ec Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Fri, 13 Sep 2024 21:16:33 +0100 Subject: [PATCH 20/33] Add GPF SDL port installation code to Dockerfile I forgot to add them after updating .github/workflows/dreamcast.yml --- Dockerfile | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/Dockerfile b/Dockerfile index eac845c1bb0..fb82fc9de58 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,6 +4,19 @@ RUN echo "Cloning project..." WORKDIR /opt/toolchains/dc/kos/ RUN git clone -b dreamcast https://github.com/azihassan/devilutionX.git +RUN echo "Uninstall kos-ports SDL 1.2..." +RUN source /opt/toolchains/dc/kos/environ.sh && \ + cd /opt/toolchains/dc/kos-ports/SDL && \ + make uninstall || echo 'SDL 1.2 uninstall finished with non zero status, proceding anyway' + +RUN echo "Install GPF SDL 1.2..." +RUN git clone -b SDL-dreamhal--GLDC https://github.com/GPF/SDL-1.2 && \ + cd SDL-1.2 && \ + source /opt/toolchains/dc/kos/environ.sh && \ + make -f Makefile.dc && \ + cp /opt/toolchains/dc/kos/addons/lib/dreamcast/libSDL.a /usr/lib/ && \ + cp include/* /usr/include/SDL/ + WORKDIR /opt/toolchains/dc/kos/devilutionX RUN echo "Downloading spawn.mpq..." RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/spawn.mpq From 72c7e30d9f1670692f6e28faf79dafbc87cd8ef0 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Fri, 13 Sep 2024 02:41:24 +0100 Subject: [PATCH 21/33] Handle L and R buttons in code --- CMake/platforms/dreamcast.cmake | 15 ++------- Source/controls/devices/joystick.cpp | 48 ++++++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 14 deletions(-) diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index 7409b65f675..4b97cc3a253 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -24,12 +24,6 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/threads-stub") list(APPEND DEVILUTIONX_PLATFORM_COMPILE_DEFINITIONS __DREAMCAST__) add_compile_options(-fpermissive) -#SDL Joystick axis mapping (circle-pad/C-stick) -set(JOY_AXIS_LEFTX 11) -set(JOY_AXIS_LEFTY 9) -set(JOY_AXIS_RIGHTX 12) -set(JOY_AXIS_RIGHTY 10) - #SDL Joystick hat mapping (D-pad) set(JOY_HAT_DPAD_UP_HAT 0) set(JOY_HAT_DPAD_RIGHT_HAT 0) @@ -39,17 +33,14 @@ set(JOY_HAT_DPAD_UP 1) set(JOY_HAT_DPAD_RIGHT 2) set(JOY_HAT_DPAD_DOWN 4) set(JOY_HAT_DPAD_LEFT 8) -#SDL Joystick button mapping (A / B and X / Y inverted) + +#SDL Joystick button mapping set(JOY_BUTTON_A 2) set(JOY_BUTTON_B 1) set(JOY_BUTTON_X 5) set(JOY_BUTTON_Y 6) -#set(JOY_BUTTON_LEFTSHOULDER 5) -#set(JOY_BUTTON_RIGHTSHOULDER 6) -set(JOY_BUTTON_BACK 7) + set(JOY_BUTTON_START 3) -set(JOY_BUTTON_TRIGGERLEFT 8) -set(JOY_BUTTON_TRIGGERRIGHT 9) #GPF SDL files set(SDL_INCLUDE_DIR /usr/include/SDL/) diff --git a/Source/controls/devices/joystick.cpp b/Source/controls/devices/joystick.cpp index 3770e595cfd..f1ac60bcfc4 100644 --- a/Source/controls/devices/joystick.cpp +++ b/Source/controls/devices/joystick.cpp @@ -46,8 +46,10 @@ StaticVector Joystick::ToControllerButtonEvents(const return { ControllerButtonEvent { ControllerButton_BUTTON_RIGHTSTICK, up } }; #endif #ifdef JOY_BUTTON_LEFTSHOULDER - case JOY_BUTTON_LEFTSHOULDER: + case JOY_BUTTON_LEFTSHOULDER: { + Log("ToControllerButtonEvents JOY_BUTTON_LEFTSHOULDER pressed"); return { ControllerButtonEvent { ControllerButton_BUTTON_LEFTSHOULDER, up } }; + } #endif #ifdef JOY_BUTTON_RIGHTSHOULDER case JOY_BUTTON_RIGHTSHOULDER: @@ -101,6 +103,20 @@ StaticVector Joystick::ToControllerButtonEvents(const } case SDL_JOYAXISMOTION: case SDL_JOYBALLMOTION: +#ifdef __DREAMCAST__ + if(event.jaxis.axis == 3) { + Log("BUTTON_LEFTSHOULDER detected"); + Log("event.jbutton.button = {}", event.jbutton.button); + Log("event.jbutton.state == SDL_RELEASED = {}", event.jbutton.state == SDL_RELEASED); + return { ControllerButtonEvent { ControllerButton_BUTTON_LEFTSHOULDER, event.jaxis.value < 255 } }; + } + if(event.jaxis.axis == 2) { + Log("BUTTON_RIGHTSHOULDER detected"); + Log("event.jbutton.button = {}", event.jbutton.button); + Log("event.jbutton.state == SDL_RELEASED = {}", event.jbutton.state == SDL_RELEASED); + return { ControllerButtonEvent { ControllerButton_BUTTON_RIGHTSHOULDER, event.jaxis.value < 255 } }; + } +#endif // ProcessAxisMotion() requires a ControllerButtonEvent parameter // so provide one here using ControllerButton_NONE return { ControllerButtonEvent { ControllerButton_NONE, false } }; @@ -211,8 +227,10 @@ int Joystick::ToSdlJoyButton(ControllerButton button) return JOY_BUTTON_RIGHTSTICK; #endif #ifdef JOY_BUTTON_LEFTSHOULDER - case ControllerButton_BUTTON_LEFTSHOULDER: + case ControllerButton_BUTTON_LEFTSHOULDER: { + Log("ToSdlJoyButton JOY_BUTTON_LEFTSHOULDER pressed"); return JOY_BUTTON_LEFTSHOULDER; + } #endif #ifdef JOY_BUTTON_RIGHTSHOULDER case ControllerButton_BUTTON_RIGHTSHOULDER: @@ -292,6 +310,31 @@ bool Joystick::IsPressed(ControllerButton button) const return joyButton < numButtons && SDL_JoystickGetButton(sdl_joystick_, joyButton) != 0; } +#ifdef __DREAMCAST__ +bool Joystick::ProcessAxisMotion(const SDL_Event &event) +{ + if (event.type != SDL_JOYAXISMOTION) + return false; + + Log("ProcessAxisMotion event.jaxis.axis = {}", event.jaxis.axis); + Log("ProcessAxisMotion event.jaxis.value = {}", event.jaxis.value); + Log("ProcessAxisMotion event.jbutton.button = {}", event.jbutton.button); + Log("event.jbutton.state == SDL_RELEASED = {}", event.jbutton.state == SDL_RELEASED); + + switch (event.jaxis.axis) { + case 0: //horizontal + leftStickXUnscaled = event.jaxis.value; + leftStickNeedsScaling = true; + return true; + case 1: //vertical + leftStickYUnscaled = event.jaxis.value; + leftStickNeedsScaling = true; + return true; + default: + return false; + } +} +#else //!ifdef __DREAMCAST__ bool Joystick::ProcessAxisMotion(const SDL_Event &event) { if (event.type != SDL_JOYAXISMOTION) @@ -330,6 +373,7 @@ bool Joystick::ProcessAxisMotion(const SDL_Event &event) return false; #endif } +#endif void Joystick::Add(int deviceIndex) { From 6eb3678625b0375c7a81d814fd2ce695bc8c9191 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Fri, 20 Sep 2024 01:26:10 +0100 Subject: [PATCH 22/33] Add docs and release steps --- .github/workflows/dreamcast.yml | 25 +++++++++++++++++++++++-- docs/installing.md | 16 ++++++++++++++++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index afe50e51769..93a42ebcfb8 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -6,7 +6,6 @@ on: # yamllint disable-line rule:truthy branches: - master - dreamcast - - dreamcast-sdl paths-ignore: - '*.md' - 'docs/**' @@ -87,7 +86,7 @@ jobs: if: ${{ !env.ACT }} uses: actions/upload-artifact@v4 with: - name: devilutionx.zip + name: devilutionx path: | build/data build/devilutionx.elf @@ -98,4 +97,26 @@ jobs: with: name: devilutionx.cdi path: ./build/devilutionx.cdi + + - name: Prepare Releases + if: ${{ github.event_name == 'release' && !env.ACT }} + run: | + apk add zip && \ + cd build && \ + zip -r devilutionx-dreamcast.zip data/ devilutionx.elf && \ + zip -r devilutionx-dreamcast.cdi.zip devilutionx.cdi + + - name: Update Release .cdi + if: ${{ github.event_name == 'release' && !env.ACT }} + uses: svenstaro/upload-release-action@v2 + with: + file: ./build/devilutionx-dreamcast.cdi.zip + overwrite: true + + - name: Update Release .elf + if: ${{ github.event_name == 'release' && !env.ACT }} + uses: svenstaro/upload-release-action@v2 + with: + file: ./build/devilutionx-dreamcast.zip + overwrite: true ... diff --git a/docs/installing.md b/docs/installing.md index 4c721046739..fa2e71e3e06 100644 --- a/docs/installing.md +++ b/docs/installing.md @@ -225,3 +225,19 @@ If you'd like to use this option, scan the QR code below. - Copy the contents of the released .zip-file onto the root of your SD card - Copy the MPQ files to `/Emu/PORTS/Binaries/Diablo.port/FILES_HERE/` + +
Sega Dreamcast + +**Shareware version** + +- Download and extract [devilutionx-dreamcast.cdi.zip](https://github.com/diasurgical/devilutionX/releases/latest/download/devilutionx-dreamcast.cdi.zip) +- Burn it to a CD using a tool like [IMGBURN](https://www.imgburn.com/index.php?act=download) or [dcdib](https://alex-free.github.io/dcdib/) + +**Full version** (requires that you provide diabdat.mpq) + +- Download [devilutionx-dreamcast.zip](https://github.com/azihassan/devilutionX/releases/download/latest/devilutionx-dreamcast.zip) +- Extract it and copy diabdat.mpq in the data/ directory +- Package it into a .cdi file using [mkdcdisc](https://gitlab.com/simulant/mkdcdisc) with the following command: `mkdcdisc -e devilutionx.elf -o devilutionx.cdi --name 'Diablo 1' -d data/` +- Burn it to a CD using a tool like [IMGBURN](https://www.imgburn.com/index.php?act=download) or [dcdib](https://alex-free.github.io/dcdib/) + +
From 65818af23a71ec78d0b54f9fc97ebc3031f9a2f9 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 6 Oct 2024 20:58:27 +0100 Subject: [PATCH 23/33] Fix formatting to match .clang-format specs --- .github/workflows/clang-format-check.yml | 1 + Source/DiabloUI/mainmenu.cpp | 4 +- Source/controls/devices/joystick.cpp | 30 ++++---- Source/diablo.cpp | 5 +- Source/init.cpp | 6 +- Source/loadsave.cpp | 16 ++-- Source/main.cpp | 7 +- Source/pfile.cpp | 95 +++++++++++------------- Source/pfile.h | 2 +- Source/utils/file_util.cpp | 29 ++++---- Source/utils/sdl2_to_1_2_backports.cpp | 2 +- Source/utils/sdl_compat.h | 2 +- 12 files changed, 94 insertions(+), 105 deletions(-) diff --git a/.github/workflows/clang-format-check.yml b/.github/workflows/clang-format-check.yml index 5e16bc1475d..24602f973a6 100644 --- a/.github/workflows/clang-format-check.yml +++ b/.github/workflows/clang-format-check.yml @@ -4,6 +4,7 @@ on: push: branches: - master + - dreamcast paths-ignore: - '*.md' - 'docs/**' diff --git a/Source/DiabloUI/mainmenu.cpp b/Source/DiabloUI/mainmenu.cpp index ba240932266..5b1449b7320 100644 --- a/Source/DiabloUI/mainmenu.cpp +++ b/Source/DiabloUI/mainmenu.cpp @@ -36,8 +36,8 @@ void MainmenuEsc() void MainmenuLoad(const char *name) { #ifndef __DREAMCAST__ - //single player save files are too big for the VMU - //todo reactivate when SD card saving is implemented + // single player save files are too big for the VMU + // todo reactivate when SD card saving is implemented vecMenuItems.push_back(std::make_unique(_("Single Player"), MAINMENU_SINGLE_PLAYER)); #endif vecMenuItems.push_back(std::make_unique(_("Multi Player"), MAINMENU_MULTIPLAYER)); diff --git a/Source/controls/devices/joystick.cpp b/Source/controls/devices/joystick.cpp index f1ac60bcfc4..af62cb395ea 100644 --- a/Source/controls/devices/joystick.cpp +++ b/Source/controls/devices/joystick.cpp @@ -104,18 +104,18 @@ StaticVector Joystick::ToControllerButtonEvents(const case SDL_JOYAXISMOTION: case SDL_JOYBALLMOTION: #ifdef __DREAMCAST__ - if(event.jaxis.axis == 3) { - Log("BUTTON_LEFTSHOULDER detected"); - Log("event.jbutton.button = {}", event.jbutton.button); - Log("event.jbutton.state == SDL_RELEASED = {}", event.jbutton.state == SDL_RELEASED); - return { ControllerButtonEvent { ControllerButton_BUTTON_LEFTSHOULDER, event.jaxis.value < 255 } }; - } - if(event.jaxis.axis == 2) { - Log("BUTTON_RIGHTSHOULDER detected"); - Log("event.jbutton.button = {}", event.jbutton.button); - Log("event.jbutton.state == SDL_RELEASED = {}", event.jbutton.state == SDL_RELEASED); - return { ControllerButtonEvent { ControllerButton_BUTTON_RIGHTSHOULDER, event.jaxis.value < 255 } }; - } + if (event.jaxis.axis == 3) { + Log("BUTTON_LEFTSHOULDER detected"); + Log("event.jbutton.button = {}", event.jbutton.button); + Log("event.jbutton.state == SDL_RELEASED = {}", event.jbutton.state == SDL_RELEASED); + return { ControllerButtonEvent { ControllerButton_BUTTON_LEFTSHOULDER, event.jaxis.value < 255 } }; + } + if (event.jaxis.axis == 2) { + Log("BUTTON_RIGHTSHOULDER detected"); + Log("event.jbutton.button = {}", event.jbutton.button); + Log("event.jbutton.state == SDL_RELEASED = {}", event.jbutton.state == SDL_RELEASED); + return { ControllerButtonEvent { ControllerButton_BUTTON_RIGHTSHOULDER, event.jaxis.value < 255 } }; + } #endif // ProcessAxisMotion() requires a ControllerButtonEvent parameter // so provide one here using ControllerButton_NONE @@ -322,11 +322,11 @@ bool Joystick::ProcessAxisMotion(const SDL_Event &event) Log("event.jbutton.state == SDL_RELEASED = {}", event.jbutton.state == SDL_RELEASED); switch (event.jaxis.axis) { - case 0: //horizontal + case 0: // horizontal leftStickXUnscaled = event.jaxis.value; leftStickNeedsScaling = true; return true; - case 1: //vertical + case 1: // vertical leftStickYUnscaled = event.jaxis.value; leftStickNeedsScaling = true; return true; @@ -334,7 +334,7 @@ bool Joystick::ProcessAxisMotion(const SDL_Event &event) return false; } } -#else //!ifdef __DREAMCAST__ +#else //! ifdef __DREAMCAST__ bool Joystick::ProcessAxisMotion(const SDL_Event &event) { if (event.type != SDL_JOYAXISMOTION) diff --git a/Source/diablo.cpp b/Source/diablo.cpp index f91726e6bf7..5f6437d2108 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -788,12 +788,11 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState) #endif default: if (IsCustomEvent(event.type)) { - if (gbIsMultiplayer) - { + if (gbIsMultiplayer) { Log("IsCustomEvent({}) = true", event.type); Log("pfile_write_hero"); pfile_write_hero(); - } + } nthread_ignore_mutex(true); PaletteFadeOut(8); sound_stop(); diff --git a/Source/init.cpp b/Source/init.cpp index edd92a344d2..57357ae06e8 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -220,10 +220,10 @@ bool IsDevilutionXMpqOutOfDate(MpqArchive &archive) bool AreExtraFontsOutOfDate(const std::string &path) { #ifdef __DREAMCAST__ - //handle ISO 9660 trailing period - const std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION."; + // handle ISO 9660 trailing period + const std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION."; #else - const std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION"; + const std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION"; #endif if (versionPath.size() + 1 > AssetRef::PathBufSize) diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index a3f3c8b2849..772f555cfa1 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -226,7 +226,7 @@ class SaveHelper { ~SaveHelper() { - //const auto encodedLen = m_cur_; + // const auto encodedLen = m_cur_; const auto encodedLen = codec_get_encoded_len(m_cur_); const char *const password = pfile_get_password(); Log("codec_encode(m_buffer_.get(), {}, {}, \"{}\")", m_cur_, encodedLen, password); @@ -2237,7 +2237,7 @@ size_t HotkeysSize(size_t nHotkeys = NumHotkeys) void LoadHotkeys() { - //hotkeys => htks to get around VMU filename size limits + // hotkeys => htks to get around VMU filename size limits LoadHelper file(OpenSaveArchive(gSaveNumber), "htks"); if (!file.IsValid()) return; @@ -2280,7 +2280,7 @@ void LoadHotkeys() void SaveHotkeys(SaveWriter &saveWriter, const Player &player) { - //hotkeys => htks to get around VMU filename size limits + // hotkeys => htks to get around VMU filename size limits SaveHelper file(saveWriter, "htks", HotkeysSize()); // Write the number of spell hotkeys @@ -2301,7 +2301,7 @@ void SaveHotkeys(SaveWriter &saveWriter, const Player &player) void LoadHeroItems(Player &player) { - //heroitems => hitms to get around VMU filename size limits + // heroitems => hitms to get around VMU filename size limits LoadHelper file(OpenSaveArchive(gSaveNumber), "hitms"); if (!file.IsValid()) return; @@ -2587,12 +2587,12 @@ void LoadGame(bool firstflag) gbIsHellfireSaveGame = gbIsHellfire; } -//todo restore saving of inventory body +// todo restore saving of inventory body void SaveHeroItems(SaveWriter &saveWriter, Player &player) { - size_t itemCount = static_cast(NUM_INVLOC) + InventoryGridCells + MaxBeltItems; //7 + 40 + 8 = 55 - //heroitems => hitms to get around VMU filename size limits - SaveHelper file(saveWriter, "hitms", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); //55 * 368 + 1 = 20241 bytes + size_t itemCount = static_cast(NUM_INVLOC) + InventoryGridCells + MaxBeltItems; // 7 + 40 + 8 = 55 + // heroitems => hitms to get around VMU filename size limits + SaveHelper file(saveWriter, "hitms", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); // 55 * 368 + 1 = 20241 bytes file.WriteLE(gbIsHellfire ? 1 : 0); diff --git a/Source/main.cpp b/Source/main.cpp index a248f4bde02..9fedd1b8c41 100644 --- a/Source/main.cpp +++ b/Source/main.cpp @@ -23,9 +23,10 @@ #include "diablo.h" #ifdef __DREAMCAST__ -//fchmod fails to link on the dreamcast, this stub is provided as a workaround -extern "C" int fchmod(int fd, mode_t mode) { - return 0; +// fchmod fails to link on the dreamcast, this stub is provided as a workaround +extern "C" int fchmod(int fd, mode_t mode) +{ + return 0; } #endif diff --git a/Source/pfile.cpp b/Source/pfile.cpp index b036c56ae26..775d0e571cd 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -33,10 +33,10 @@ #include "utils/utf8.hpp" #ifdef __DREAMCAST__ -#include +#include #include +#include #include -#include #endif #ifdef UNPACKED_SAVES @@ -54,19 +54,19 @@ namespace devilution { bool gbValidSaveFile; - -void listdir(const char *dir, int depth) { +void listdir(const char *dir, int depth) +{ file_t d = fs_open(dir, O_RDONLY | O_DIR); dirent_t *entry; printf("============ %s ============\n", dir); - while(NULL != (entry = fs_readdir(d))) { + while (NULL != (entry = fs_readdir(d))) { char absolutePath[1024]; strcpy(absolutePath, dir); strcat(absolutePath, "/"); strcat(absolutePath, entry->name); bool isDir = entry->size == -1; printf("[%s]\t%.2f kB\t%s\n", isDir ? "DIR" : "FIL", entry->size / 1024.0, entry->name); - if(isDir) { + if (isDir) { printf("absolutePath = %s, depth = %d\n", absolutePath, depth); listdir(absolutePath, depth + 1); } @@ -81,7 +81,7 @@ char hero_names[MAX_CHARACTERS][PlayerNameLength]; std::string GetSavePath(uint32_t saveNum, std::string_view savePrefix = {}) { - //shorter names to get around VMU filename size limits + // shorter names to get around VMU filename size limits return StrCat(paths::PrefPath(), savePrefix, gbIsSpawn ? (gbIsMultiplayer ? "M" : "S") @@ -89,12 +89,12 @@ std::string GetSavePath(uint32_t saveNum, std::string_view savePrefix = {}) saveNum, #ifdef UNPACKED_SAVES #ifdef __DREAMCAST__ - //flatten directory structure for easier fs_ramdisk_* usage - //for example, /ram/spawn_sv/hero would become /ram/spawn_sv_hero + // flatten directory structure for easier fs_ramdisk_* usage + // for example, /ram/spawn_sv/hero would become /ram/spawn_sv_hero - gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv_" + gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv_" #else - gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv" DIRECTORY_SEPARATOR_STR + gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv" DIRECTORY_SEPARATOR_STR #endif #else gbIsHellfire ? ".hsv" : ".sv" @@ -108,7 +108,7 @@ std::string GetStashSavePath() gbIsSpawn ? "stash_spawn" : "stash", #ifdef UNPACKED_SAVES #ifdef __DREAMCAST__ - //same as above + // same as above gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv_" #else gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv" DIRECTORY_SEPARATOR_STR @@ -187,10 +187,10 @@ bool ReadHero(SaveReader &archive, PlayerPack *pPack) } Log("{} == sizeof(*pPack) ({}) = {}", read, sizeof(*pPack), read == sizeof(*pPack)); Log("Read player {}", pPack->pName); - //Log("\tpHPBase = {}", pPack->pHPBase); + // Log("\tpHPBase = {}", pPack->pHPBase); listdir("/ram", 0); - listdir("/vmu/a1", 0); + listdir("/vmu/a1", 0); return ret; } @@ -204,7 +204,7 @@ void EncodeHero(SaveWriter &saveWriter, const PlayerPack *pack) memcpy(packed.get(), pack, sizeof(*pack)); codec_encode(packed.get(), sizeof(*pack), packedLen, pfile_get_password()); Log("Saving player {}", pack->pName); - //Log("\tpHPBase = {}", pack->pHPBase); + // Log("\tpHPBase = {}", pack->pHPBase); bool result = saveWriter.WriteFile("hero", packed.get(), packedLen /* sizeof(*pack) */); Log("saveWriter.WriteFile(\"hero\", packed.get(), {}) = {}", packedLen, result); } @@ -292,8 +292,8 @@ std::optional CreateSaveReader(std::string &&path) #ifdef UNPACKED_SAVES #ifdef __DREAMCAST__ Log("\tAttempting to load save file {}", path); - //no notion of directories in ramdisk, so /ram/spawn_0_sv/ doesn't exist - //instead, we check for /ram/spawn_0_sv_hero which was previously created + // no notion of directories in ramdisk, so /ram/spawn_0_sv/ doesn't exist + // instead, we check for /ram/spawn_0_sv_hero which was previously created std::string heroFile = path + "hero"; if (!FileExists(heroFile)) { Log("\tFailed ):"); @@ -613,16 +613,14 @@ std::unique_ptr SaveReader::ReadFile(const char *filename, std::siz Log("path = \"{}\"", path); size_t size = 0; uint8 *contents; - if(fs_load(path.c_str(), &contents) == -1) - { + if (fs_load(path.c_str(), &contents) == -1) { error = 1; LogError("fs_load(\"{}\", &contents) = -1", path); app_fatal("SaveReader::ReadFile " + path + " KO"); return nullptr; } vmu_pkg_t package; - if(vmu_pkg_parse(contents, &package) < 0) - { + if (vmu_pkg_parse(contents, &package) < 0) { error = 1; free(contents); LogError("vmu_pkg_parse = -1"); @@ -634,7 +632,7 @@ std::unique_ptr SaveReader::ReadFile(const char *filename, std::siz std::unique_ptr result; result.reset(new std::byte[fileSize]); memcpy(result.get(), package.data, fileSize); - //free(package.data); + // free(package.data); free(contents); return result; } @@ -670,10 +668,9 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s const std::string path = dir_ + filename; Log("dir_ = {}", dir_); Log("path = {}", path); - const char* baseName = basename(path.c_str()); - //vmu code - if(dir_.starts_with("/vmu")) - { + const char *baseName = basename(path.c_str()); + // vmu code + if (dir_.starts_with("/vmu")) { vmu_pkg_t package; strcpy(package.app_id, "DevilutionX"); strncpy(package.desc_short, filename, 20); @@ -687,16 +684,14 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s uint8 *contents; size_t packageSize; - if(vmu_pkg_build(&package, &contents, &packageSize) < 0) - { + if (vmu_pkg_build(&package, &contents, &packageSize) < 0) { delete[] package.data; LogError("vmu_pkg_build failed"); app_fatal("vmu_pkg_build failed"); return false; } - FILE* file = OpenFile(path.c_str(), "wb"); - if(file == nullptr) - { + FILE *file = OpenFile(path.c_str(), "wb"); + if (file == nullptr) { delete[] package.data; free(contents); LogError("fopen(\"{}\", \"wb\") = nullptr", path); @@ -704,8 +699,7 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s return false; } size_t written = std::fwrite(contents, sizeof(uint8), packageSize, file); - if(written != packageSize) - { + if (written != packageSize) { delete[] package.data; free(contents); std::fclose(file); @@ -713,8 +707,7 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s app_fatal("vmu fwrite call failed"); return false; } - if(std::fclose(file) != 0) - { + if (std::fclose(file) != 0) { delete[] package.data; free(contents); LogError("fclose(file) = 0"); @@ -727,23 +720,21 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s return true; } - //ramdisk code + // ramdisk code bool exists = FileExists(baseName); - if(exists) - { + if (exists) { Log("{} exists, removing it", path); void *toFree; size_t ignore; int detach_result = fs_ramdisk_detach(baseName, &toFree, &ignore); free(toFree); Log("fs_ramdisk_detach result = {}", detach_result); - if(detach_result == -1) - { + if (detach_result == -1) { return false; } } Log("\tAllocating {} bytes for path {}", size, baseName); - void* buffer = malloc(size); + void *buffer = malloc(size); memcpy(buffer, data, size); Log("\tMallocation succeeded ? {}", buffer != NULL); int attach_result = fs_ramdisk_attach(baseName, buffer, size); @@ -793,7 +784,7 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s std::fclose(file); return true; } -#endif //def __DREAMCAST__ +#endif // def __DREAMCAST__ void SaveWriter::RemoveHashEntries(bool (*fnGetName)(uint8_t, char *)) { char pszFileName[MaxMpqPathSize]; @@ -803,7 +794,7 @@ void SaveWriter::RemoveHashEntries(bool (*fnGetName)(uint8_t, char *)) RemoveHashEntry(pszFileName); } } -#endif //def UNPACKED_SAVES +#endif // def UNPACKED_SAVES std::optional OpenSaveArchive(uint32_t saveNum) { @@ -836,12 +827,11 @@ std::unique_ptr ReadArchive(SaveReader &archive, const char *pszNam app_fatal("decodedLength = 0"); return nullptr; } - if(strcmp(pszName, "hero") == 0) - { + if (strcmp(pszName, "hero") == 0) { PlayerPack pPack; memcpy(&pPack, result.get(), decodedLength); Log("ReadArchive player {}", pPack.pName); - //Log("\tpHPBase = {}", pPack.pHPBase); + // Log("\tpHPBase = {}", pPack.pHPBase); } Log("ReadArchive 2"); @@ -918,7 +908,7 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) if (ReadHero(*archive, &pkplr)) { Log("ReadHero OK"); Log("Player {}", pkplr.pName); - //Log("Player {}, HP = {}", pkplr.pName, pkplr.pHPBase); + // Log("Player {}, HP = {}", pkplr.pName, pkplr.pHPBase); _uiheroinfo uihero; uihero.saveNumber = i; strcpy(hero_names[i], pkplr.pName); @@ -936,8 +926,7 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) Game2UiPlayer(player, &uihero, hasSaveGame); uiAddHeroInfo(&uihero); - } - else { + } else { Log("ReadHero(*archive, &pkplr) failed"); app_fatal("ReadHero(*archive, &pkplr) failed"); } @@ -1015,12 +1004,12 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) std::optional archive = OpenSaveArchive(saveNum); if (!archive) { listdir("/ram", 0); - listdir("/vmu/a1", 0); + listdir("/vmu/a1", 0); app_fatal(_("Unable to open archive")); } if (!ReadHero(*archive, &pkplr)) { listdir("/ram", 0); - listdir("/vmu/a1", 0); + listdir("/vmu/a1", 0); app_fatal(_("Unable to load character")); } @@ -1066,8 +1055,8 @@ void pfile_update(bool forceSave) return; Uint32 tick = SDL_GetTicks(); - //600000 instead of 60000 - //60000 ms is too frequent for the VMU, the game hangs too often and too long + // 600000 instead of 60000 + // 60000 ms is too frequent for the VMU, the game hangs too often and too long if (!forceSave && tick - prevTick <= 600000) return; diff --git a/Source/pfile.h b/Source/pfile.h index 0ee7f0496f2..27c794dfc9e 100644 --- a/Source/pfile.h +++ b/Source/pfile.h @@ -19,7 +19,7 @@ namespace devilution { -#define MAX_CHARACTERS 1 //todo restore me to 99 +#define MAX_CHARACTERS 1 // todo restore me to 99 extern bool gbValidSaveFile; diff --git a/Source/utils/file_util.cpp b/Source/utils/file_util.cpp index cd09a7bc5ea..805a14bc79b 100644 --- a/Source/utils/file_util.cpp +++ b/Source/utils/file_util.cpp @@ -107,14 +107,14 @@ bool FileExists(const char *path) } return true; #elif defined(__DREAMCAST__) - //ramdisk access doesn't work with SDL_RWFromFile or std::filesystem::exists + // ramdisk access doesn't work with SDL_RWFromFile or std::filesystem::exists int file = fs_open(path, O_RDONLY); - if(file != -1) { + if (file != -1) { fs_close(file); return true; } file = fs_open(path, O_RDONLY | O_DIR); - if(file != -1) { + if (file != -1) { fs_close(file); return true; } @@ -317,19 +317,18 @@ void RecursivelyCreateDir(const char *path) #ifdef __DREAMCAST__ bool TruncateFile(const char *path, off_t size) { - Log("TruncateFile(\"{}\", {})", path, size); - void *contents; - size_t read = fs_load(path, &contents); - if(read == -1) - { - return false; - } + Log("TruncateFile(\"{}\", {})", path, size); + void *contents; + size_t read = fs_load(path, &contents); + if (read == -1) { + return false; + } - fs_unlink(path); - file_t fh = fs_open(path, O_WRONLY); - int result = fs_write(fh, contents, size); - free(contents); - return result != -1; + fs_unlink(path); + file_t fh = fs_open(path, O_WRONLY); + int result = fs_write(fh, contents, size); + free(contents); + return result != -1; } #endif diff --git a/Source/utils/sdl2_to_1_2_backports.cpp b/Source/utils/sdl2_to_1_2_backports.cpp index a41addb89a2..8081b1c460f 100644 --- a/Source/utils/sdl2_to_1_2_backports.cpp +++ b/Source/utils/sdl2_to_1_2_backports.cpp @@ -883,7 +883,7 @@ char *SDL_GetPrefPath(const char *org, const char *app) return retval; #elif defined(__DREAMCAST__) retval = SDL_strdup("/vmu/a1/"); - return retval; + return retval; #endif if (!app) { diff --git a/Source/utils/sdl_compat.h b/Source/utils/sdl_compat.h index 56c1c6aebfa..7c5da623487 100644 --- a/Source/utils/sdl_compat.h +++ b/Source/utils/sdl_compat.h @@ -85,7 +85,7 @@ inline int SDLC_SetSurfaceAndPaletteColors(SDL_Surface *surface, SDL_Palette *pa // In SDL1, the surface always has its own distinct palette, so we need to // update it as well. return SDL_SetPalette(surface, SDL_LOGPAL, colors, firstcolor, ncolors) - 1; -#endif //defined(__DREAMCAST__) +#endif // defined(__DREAMCAST__) #else // !USE_SDL1 if (SDL_SetPaletteColors(palette, colors, firstcolor, ncolors) < 0) From 7dbeed94248eb59f3ee9f16f93a0f3f422bb53aa Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sun, 6 Oct 2024 21:57:58 +0100 Subject: [PATCH 24/33] Wrap dreamcast-specific code if ifdef __DREAMCAST__ --- Source/diablo.cpp | 7 +---- Source/interfac.cpp | 5 ---- Source/loadsave.cpp | 46 +++++++++++------------------ Source/pfile.cpp | 60 ++++++++++++++++---------------------- Source/pfile.h | 6 ++-- Source/utils/file_util.cpp | 2 ++ 6 files changed, 49 insertions(+), 77 deletions(-) diff --git a/Source/diablo.cpp b/Source/diablo.cpp index 5f6437d2108..e50b62433bd 100644 --- a/Source/diablo.cpp +++ b/Source/diablo.cpp @@ -788,11 +788,8 @@ void GameEventHandler(const SDL_Event &event, uint16_t modState) #endif default: if (IsCustomEvent(event.type)) { - if (gbIsMultiplayer) { - Log("IsCustomEvent({}) = true", event.type); - Log("pfile_write_hero"); + if (gbIsMultiplayer) pfile_write_hero(); - } nthread_ignore_mutex(true); PaletteFadeOut(8); sound_stop(); @@ -912,7 +909,6 @@ void RunGameLoop(interface_mode uMsg) demo::NotifyGameLoopEnd(); if (gbIsMultiplayer) { - Log("gbRunGame = {}, pfile_write_hero(/*writeGameData=*/false)", gbRunGame); pfile_write_hero(/*writeGameData=*/false); sfile_write_stash(); } @@ -2849,7 +2845,6 @@ void DisableInputEventHandler(const SDL_Event &event, uint16_t modState) void LoadGameLevel(bool firstflag, lvl_entry lvldir) { - Log("LoadGameLevel(firstflag = {}, lvldir = {})", firstflag, static_cast(lvldir)); _music_id neededTrack = GetLevelMusic(leveltype); ClearFloatingNumbers(); diff --git a/Source/interfac.cpp b/Source/interfac.cpp index 51ed9b1f7b7..601473e5d2b 100644 --- a/Source/interfac.cpp +++ b/Source/interfac.cpp @@ -73,7 +73,6 @@ Cutscenes GetCutSceneFromLevelType(dungeon_type type) Cutscenes PickCutscene(interface_mode uMsg) { - Log("MyPlayer->plrlevel = {}", MyPlayer->plrlevel); switch (uMsg) { case WM_DIABLOADGAME: case WM_DIABNEWGAME: @@ -352,7 +351,6 @@ void ShowProgress(interface_mode uMsg) case WM_DIABNEXTLVL: IncProgress(); if (!gbIsMultiplayer) { - Log("pfile_save_level()"); pfile_save_level(); } else { DeltaSaveLevel(); @@ -363,14 +361,12 @@ void ShowProgress(interface_mode uMsg) currlevel = myPlayer.plrlevel; leveltype = GetLevelType(currlevel); IncProgress(); - Log("LoadGameLevel(false, ENTRY_MAIN)"); LoadGameLevel(false, ENTRY_MAIN); IncProgress(); break; case WM_DIABPREVLVL: IncProgress(); if (!gbIsMultiplayer) { - Log("pfile_save_level()"); pfile_save_level(); } else { DeltaSaveLevel(); @@ -381,7 +377,6 @@ void ShowProgress(interface_mode uMsg) leveltype = GetLevelType(currlevel); assert(myPlayer.isOnActiveLevel()); IncProgress(); - Log("LoadGameLevel(false, ENTRY_PREV)"); LoadGameLevel(false, ENTRY_PREV); IncProgress(); break; diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 772f555cfa1..71a811f89c8 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -179,8 +179,6 @@ class SaveHelper { , m_buffer_(new std::byte[codec_get_encoded_len(bufferLen)]) , m_capacity_(bufferLen) { - Log("SaveHelper instantiated: this(mpqWriter, \"{}\", {})", szFileName, bufferLen); - Log("SaveHelper allocated byte array of {} bytes", codec_get_encoded_len(bufferLen)); } bool IsValid(size_t len = 1) @@ -226,12 +224,9 @@ class SaveHelper { ~SaveHelper() { - // const auto encodedLen = m_cur_; const auto encodedLen = codec_get_encoded_len(m_cur_); const char *const password = pfile_get_password(); - Log("codec_encode(m_buffer_.get(), {}, {}, \"{}\")", m_cur_, encodedLen, password); codec_encode(m_buffer_.get(), m_cur_, encodedLen, password); - Log("~SaveHelper WriteFile(\"{}\", m_buffer_.get(), {})", m_szFileName_, encodedLen); m_mpqWriter.WriteFile(m_szFileName_, m_buffer_.get(), encodedLen); } }; @@ -1839,7 +1834,6 @@ void LoadLevelSeeds() void SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData) { - Log("SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData)"); Player &myPlayer = *MyPlayer; DoUnVision(myPlayer.position.tile, myPlayer._pLightRad); // fix for vision staying on the level @@ -1849,7 +1843,6 @@ void SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData) char szName[MaxMpqPathSize]; GetTempLevelNames(szName); - Log("SaveHelper file(saveWriter, \"{}\", 256 * 1024)", szName); SaveHelper file(saveWriter, szName, 256 * 1024); if (leveltype != DTYPE_TOWN) { @@ -1920,18 +1913,11 @@ void SaveLevel(SaveWriter &saveWriter, LevelConversionData *levelConversionData) void LoadLevel(LevelConversionData *levelConversionData) { - Log("LoadLevel"); char szName[MaxMpqPathSize]; std::optional archive = OpenSaveArchive(gSaveNumber); - Log("OpenSaveArchive({})", gSaveNumber); GetTempLevelNames(szName); - Log("szName = '{}'", szName); - Log("!archive = {}", !archive); - Log("!archive->HasFile(\"{}\") = {}", szName, !archive->HasFile(szName)); - if (!archive || !archive->HasFile(szName)) { - Log("{} not found in archive, calling GetPermLevelNames(\"{}\")", szName, szName); + if (!archive || !archive->HasFile(szName)) GetPermLevelNames(szName); - } LoadHelper file(std::move(archive), szName); if (!file.IsValid()) app_fatal(_("Unable to open save file archive")); @@ -2237,8 +2223,12 @@ size_t HotkeysSize(size_t nHotkeys = NumHotkeys) void LoadHotkeys() { +#ifdef __DREAMCAST____ // hotkeys => htks to get around VMU filename size limits LoadHelper file(OpenSaveArchive(gSaveNumber), "htks"); +#else + LoadHelper file(OpenSaveArchive(gSaveNumber), "hotkeys"); +#endif if (!file.IsValid()) return; @@ -2280,8 +2270,12 @@ void LoadHotkeys() void SaveHotkeys(SaveWriter &saveWriter, const Player &player) { +#ifdef __DREAMCAST____ // hotkeys => htks to get around VMU filename size limits SaveHelper file(saveWriter, "htks", HotkeysSize()); +#else + SaveHelper file(saveWriter, "hotkeys", HotkeysSize()); +#endif // Write the number of spell hotkeys file.WriteLE(static_cast(NumHotkeys)); @@ -2301,8 +2295,12 @@ void SaveHotkeys(SaveWriter &saveWriter, const Player &player) void LoadHeroItems(Player &player) { +#ifdef __DREAMCAST____ // heroitems => hitms to get around VMU filename size limits LoadHelper file(OpenSaveArchive(gSaveNumber), "hitms"); +#else + LoadHelper file(OpenSaveArchive(gSaveNumber), "heroitems"); +#endif if (!file.IsValid()) return; @@ -2368,7 +2366,6 @@ void RemoveEmptyInventory(Player &player) void LoadGame(bool firstflag) { - Log("LoadGame(firstflag = {})", firstflag); FreeGameMem(); LoadHelper file(OpenSaveArchive(gSaveNumber), "game"); @@ -2389,7 +2386,6 @@ void LoadGame(bool firstflag) giNumberOfSmithPremiumItems = 6; } - Log("pfile_remove_temp_files"); pfile_remove_temp_files(); setlevel = file.NextBool8(); @@ -2419,16 +2415,13 @@ void LoadGame(bool firstflag) Player &myPlayer = *MyPlayer; - Log("LoadPlayer(file, myPlayer);"); LoadPlayer(file, myPlayer); if (sgGameInitInfo.nDifficulty < DIFF_NORMAL || sgGameInitInfo.nDifficulty > DIFF_HELL) sgGameInitInfo.nDifficulty = DIFF_NORMAL; - Log("LoadQuests {}", giNumberQuests); for (int i = 0; i < giNumberQuests; i++) LoadQuest(&file, i); - Log("LoadPortals", MAXPORTAL); for (int i = 0; i < MAXPORTAL; i++) LoadPortal(&file, i); @@ -2437,7 +2430,6 @@ void LoadGame(bool firstflag) RemoveEmptyInventory(myPlayer); } - Log("LoadGameLevel"); LoadGameLevel(firstflag, ENTRY_LOAD); SetPlrAnims(myPlayer); SyncPlrAnim(myPlayer); @@ -2454,7 +2446,6 @@ void LoadGame(bool firstflag) if (leveltype != DTYPE_TOWN) { for (unsigned &monsterId : ActiveMonsters) monsterId = file.NextBE(); - Log("LoadMonsters {}", ActiveMonsterCount); for (size_t i = 0; i < ActiveMonsterCount; i++) LoadMonster(&file, Monsters[ActiveMonsters[i]]); for (size_t i = 0; i < ActiveMonsterCount; i++) @@ -2463,7 +2454,6 @@ void LoadGame(bool firstflag) file.Skip(MaxMissilesForSaveGame); // Skip AvailableMissiles file.Skip(MaxMissilesForSaveGame); - Log("LoadMissiles {}", tmpNummissiles); for (int i = 0; i < tmpNummissiles; i++) LoadMissile(&file); // For petrified monsters, the data in missile.var1 must be used to @@ -2587,12 +2577,15 @@ void LoadGame(bool firstflag) gbIsHellfireSaveGame = gbIsHellfire; } -// todo restore saving of inventory body void SaveHeroItems(SaveWriter &saveWriter, Player &player) { size_t itemCount = static_cast(NUM_INVLOC) + InventoryGridCells + MaxBeltItems; // 7 + 40 + 8 = 55 +#ifdef __DREAMCAST____ // heroitems => hitms to get around VMU filename size limits SaveHelper file(saveWriter, "hitms", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); // 55 * 368 + 1 = 20241 bytes +#else + SaveHelper file(saveWriter, "heroitems", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); +#endif file.WriteLE(gbIsHellfire ? 1 : 0); @@ -2641,7 +2634,6 @@ void SaveStash(SaveWriter &stashWriter) } }; - Log("Saving {} pages of stash", pagesToSave.size()); // Current stash size is 100 pages. Will definitely fit in a 32 bit value. file.WriteLE(static_cast(pagesToSave.size())); for (const auto &page : pagesToSave) { @@ -2649,7 +2641,6 @@ void SaveStash(SaveWriter &stashWriter) for (const auto &row : Stash.stashGrids[page]) { for (uint16_t cell : row) { file.WriteLE(cell); - Log("\t\tSaving stash item {}", cell); } } } @@ -2657,7 +2648,6 @@ void SaveStash(SaveWriter &stashWriter) // 100 pages of 100 items is still only 10 000, as with the page count will definitely fit in 32 bits even in the worst case. file.WriteLE(static_cast(Stash.stashList.size())); for (const Item &item : Stash.stashList) { - Log("SaveItem(file, item)"); SaveItem(file, item); } @@ -2666,7 +2656,6 @@ void SaveStash(SaveWriter &stashWriter) void SaveGameData(SaveWriter &saveWriter) { - Log("SaveHelper file(saveWriter, \"game\", 320 * 1024)"); SaveHelper file(saveWriter, "game", 320 * 1024); if (gbIsSpawn && !gbIsHellfire) @@ -2832,7 +2821,6 @@ void SaveGameData(SaveWriter &saveWriter) void SaveGame() { - Log("SaveGame()"); gbValidSaveFile = true; pfile_write_hero(/*writeGameData=*/true); sfile_write_stash(); diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 775d0e571cd..0cd66dcadbf 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -54,6 +54,7 @@ namespace devilution { bool gbValidSaveFile; +#ifdef __DREAMCAST__ void listdir(const char *dir, int depth) { file_t d = fs_open(dir, O_RDONLY | O_DIR); @@ -74,6 +75,7 @@ void listdir(const char *dir, int depth) fs_close(d); printf("============ %s ============\n\n\n", dir); } +#endif namespace { /** List of character names for the character selection screen. */ @@ -84,13 +86,18 @@ std::string GetSavePath(uint32_t saveNum, std::string_view savePrefix = {}) // shorter names to get around VMU filename size limits return StrCat(paths::PrefPath(), savePrefix, gbIsSpawn +#ifdef __DREAMCAST__ ? (gbIsMultiplayer ? "M" : "S") : (gbIsMultiplayer ? "m" : "s"), +#else + ? (gbIsMultiplayer ? "share_" : "spawn_") + : (gbIsMultiplayer ? "multi_" : "single_"), +#endif saveNum, #ifdef UNPACKED_SAVES #ifdef __DREAMCAST__ - // flatten directory structure for easier fs_ramdisk_* usage - // for example, /ram/spawn_sv/hero would become /ram/spawn_sv_hero + // flatten directory structure for VMU filesystem compatibility + // for example, /vmu/spawn_sv/hero would become /vmu/spawn_sv_hero gbIsHellfire ? "_hsv" DIRECTORY_SEPARATOR_STR : "_sv_" #else @@ -156,14 +163,9 @@ void RenameTempToPerm(SaveWriter &saveWriter) [[maybe_unused]] bool result = GetPermSaveNames(dwIndex, szPerm); // DO NOT PUT DIRECTLY INTO ASSERT! assert(result); dwIndex++; - Log("GetPermSaveNames({}, \"{}\")", dwIndex, szTemp); if (saveWriter.HasFile(szTemp)) { - Log("saveWriter.HasFile(\"{}\") = true", szTemp); - if (saveWriter.HasFile(szPerm)) { - Log("saveWriter.HasFile(\"{}\") = true => RemoveHashEntry", szPerm); + if (saveWriter.HasFile(szPerm)) saveWriter.RemoveHashEntry(szPerm); - } - Log("saveWriter.RenameFile(\"{}\", {})", szTemp, szPerm); saveWriter.RenameFile(szTemp, szPerm); } } @@ -176,7 +178,6 @@ bool ReadHero(SaveReader &archive, PlayerPack *pPack) auto buf = ReadArchive(archive, "hero", &read); if (buf == nullptr) { - Log("ReadArchive(archive, \"hero\", {}) = false", read); return false; } @@ -185,12 +186,14 @@ bool ReadHero(SaveReader &archive, PlayerPack *pPack) memcpy(pPack, buf.get(), sizeof(*pPack)); ret = true; } + +#ifdef __DREAMCAST__ Log("{} == sizeof(*pPack) ({}) = {}", read, sizeof(*pPack), read == sizeof(*pPack)); Log("Read player {}", pPack->pName); // Log("\tpHPBase = {}", pPack->pHPBase); - listdir("/ram", 0); listdir("/vmu/a1", 0); +#endif return ret; } @@ -200,13 +203,9 @@ void EncodeHero(SaveWriter &saveWriter, const PlayerPack *pack) size_t packedLen = codec_get_encoded_len(sizeof(*pack)); std::unique_ptr packed { new std::byte[packedLen] }; - Log("memcpy(packed.get(), pack, {})", sizeof(*pack)); memcpy(packed.get(), pack, sizeof(*pack)); codec_encode(packed.get(), sizeof(*pack), packedLen, pfile_get_password()); - Log("Saving player {}", pack->pName); - // Log("\tpHPBase = {}", pack->pHPBase); - bool result = saveWriter.WriteFile("hero", packed.get(), packedLen /* sizeof(*pack) */); - Log("saveWriter.WriteFile(\"hero\", packed.get(), {}) = {}", packedLen, result); + saveWriter.WriteFile("hero", packed.get(), packedLen); } SaveWriter GetSaveWriter(uint32_t saveNum) @@ -292,8 +291,8 @@ std::optional CreateSaveReader(std::string &&path) #ifdef UNPACKED_SAVES #ifdef __DREAMCAST__ Log("\tAttempting to load save file {}", path); - // no notion of directories in ramdisk, so /ram/spawn_0_sv/ doesn't exist - // instead, we check for /ram/spawn_0_sv_hero which was previously created + // no notion of directories in vmu, so /vmu/spawn_0_sv/ doesn't exist + // instead, we check for /vmu/spawn_0_sv_hero which was previously created std::string heroFile = path + "hero"; if (!FileExists(heroFile)) { Log("\tFailed ):"); @@ -811,12 +810,9 @@ std::unique_ptr ReadArchive(SaveReader &archive, const char *pszNam int32_t error; std::size_t length; - Log("ReadArchive(archive, \"{}\", {})", pszName, *pdwLen); - Log("ReadArchive 0"); std::unique_ptr result = archive.ReadFile(pszName, length, error); if (error != 0) { Log("ReadArchive 0 error = {}", error); - app_fatal("ReadArchive 0 = " + error); return nullptr; } @@ -824,15 +820,8 @@ std::unique_ptr ReadArchive(SaveReader &archive, const char *pszNam std::size_t decodedLength = codec_decode(result.get(), length, pfile_get_password()); if (decodedLength == 0) { Log("ReadArchive nullptr"); - app_fatal("decodedLength = 0"); return nullptr; } - if (strcmp(pszName, "hero") == 0) { - PlayerPack pPack; - memcpy(&pPack, result.get(), decodedLength); - Log("ReadArchive player {}", pPack.pName); - // Log("\tpHPBase = {}", pPack.pHPBase); - } Log("ReadArchive 2"); if (pdwLen != nullptr) @@ -867,7 +856,6 @@ void pfile_write_hero_demo(int demo) HeroCompareResult pfile_compare_hero_demo(int demo, bool logDetails) { - Log("pfile_compare_hero_demo({}, {})", demo, logDetails); std::string referenceSavePath = GetSavePath(gSaveNumber, StrCat("demo_", demo, "_reference_")); if (!FileExists(referenceSavePath.c_str())) @@ -906,13 +894,9 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) if (archive) { PlayerPack pkplr; if (ReadHero(*archive, &pkplr)) { - Log("ReadHero OK"); - Log("Player {}", pkplr.pName); - // Log("Player {}, HP = {}", pkplr.pName, pkplr.pHPBase); _uiheroinfo uihero; uihero.saveNumber = i; strcpy(hero_names[i], pkplr.pName); - Log("hero_names[{}] = {}", i, pkplr.pName); bool hasSaveGame = ArchiveContainsGame(*archive); if (hasSaveGame) pkplr.bIsHellfire = gbIsHellfireSaveGame ? 1 : 0; @@ -926,9 +910,6 @@ bool pfile_ui_set_hero_infos(bool (*uiAddHeroInfo)(_uiheroinfo *)) Game2UiPlayer(player, &uihero, hasSaveGame); uiAddHeroInfo(&uihero); - } else { - Log("ReadHero(*archive, &pkplr) failed"); - app_fatal("ReadHero(*archive, &pkplr) failed"); } } } @@ -1003,13 +984,17 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) { std::optional archive = OpenSaveArchive(saveNum); if (!archive) { +#ifdef __DREAMCAST__ listdir("/ram", 0); listdir("/vmu/a1", 0); +#endif app_fatal(_("Unable to open archive")); } if (!ReadHero(*archive, &pkplr)) { +#ifdef __DREAMCAST__ listdir("/ram", 0); listdir("/vmu/a1", 0); +#endif app_fatal(_("Unable to load character")); } @@ -1055,10 +1040,15 @@ void pfile_update(bool forceSave) return; Uint32 tick = SDL_GetTicks(); +#ifdef __DREAMCAST__ // 600000 instead of 60000 // 60000 ms is too frequent for the VMU, the game hangs too often and too long if (!forceSave && tick - prevTick <= 600000) return; +#else + if (!forceSave && tick - prevTick <= 60000) + return; +#endif Log("pfile_update({})", forceSave); prevTick = tick; diff --git a/Source/pfile.h b/Source/pfile.h index 27c794dfc9e..da520a00d2b 100644 --- a/Source/pfile.h +++ b/Source/pfile.h @@ -19,7 +19,11 @@ namespace devilution { +#ifdef __DREAMCAST____ #define MAX_CHARACTERS 1 // todo restore me to 99 +#else +#define MAX_CHARACTERS 99 +#endif extern bool gbValidSaveFile; @@ -28,7 +32,6 @@ struct SaveReader { explicit SaveReader(std::string &&dir) : dir_(std::move(dir)) { - Log("new SaveReader(\"{}\");", dir); } const std::string &dir() const @@ -51,7 +54,6 @@ struct SaveWriter { explicit SaveWriter(std::string &&dir) : dir_(std::move(dir)) { - Log("new SaveWriter(\"{}\");", dir); } bool WriteFile(const char *filename, const std::byte *data, size_t size); diff --git a/Source/utils/file_util.cpp b/Source/utils/file_util.cpp index 805a14bc79b..0a0114ed976 100644 --- a/Source/utils/file_util.cpp +++ b/Source/utils/file_util.cpp @@ -108,6 +108,7 @@ bool FileExists(const char *path) return true; #elif defined(__DREAMCAST__) // ramdisk access doesn't work with SDL_RWFromFile or std::filesystem::exists + // todo check to see if this is needed with vmu fs int file = fs_open(path, O_RDONLY); if (file != -1) { fs_close(file); @@ -327,6 +328,7 @@ bool TruncateFile(const char *path, off_t size) fs_unlink(path); file_t fh = fs_open(path, O_WRONLY); int result = fs_write(fh, contents, size); + fs_close(fh); free(contents); return result != -1; } From 5f36c72bcbad97eebb56accbf033c0e951591b9d Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Mon, 7 Oct 2024 02:20:24 +0100 Subject: [PATCH 25/33] Disable libfmt builtin types for Dreamcast --- 3rdParty/libfmt/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/3rdParty/libfmt/CMakeLists.txt b/3rdParty/libfmt/CMakeLists.txt index 6269400c05a..48936347d4e 100644 --- a/3rdParty/libfmt/CMakeLists.txt +++ b/3rdParty/libfmt/CMakeLists.txt @@ -29,7 +29,7 @@ if(DEVILUTIONX_WINDOWS_NO_WCHAR) endif() # Reduces the overall binary size by 8 KiB. -if(TARGET_PLATFORM STREQUAL "rg99") +if(TARGET_PLATFORM STREQUAL "rg99" OR PLATFORM_DREAMCAST) target_compile_definitions(fmt PUBLIC FMT_BUILTIN_TYPES=0) endif() From bb94f5a4ffb7e27ea5b57b323148c3ab286c4a26 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Mon, 7 Oct 2024 02:48:46 +0100 Subject: [PATCH 26/33] Remove obsolete libfmt patching shenanigans --- .github/workflows/dreamcast.yml | 2 -- Dockerfile | 3 --- libfmt-long-double.patch | 12 ------------ 3 files changed, 17 deletions(-) delete mode 100644 libfmt-long-double.patch diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index 93a42ebcfb8..e51c38cc754 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -68,8 +68,6 @@ jobs: - name: Build DevilutionX run: | - # patches libfmt to support long double - patch build/_deps/libfmt-src/include/fmt/format.h -l -p0 < libfmt-long-double.patch source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make - name: Generate .cdi diff --git a/Dockerfile b/Dockerfile index fb82fc9de58..b33cd77d5ea 100644 --- a/Dockerfile +++ b/Dockerfile @@ -31,9 +31,6 @@ RUN source /opt/toolchains/dc/kos/environ.sh && \ export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ kos-cmake -S. -Bbuild -RUN echo "Patching fmt to support long double..." -RUN patch build/_deps/libfmt-src/include/fmt/format.h -l -p0 < libfmt-long-double.patch - RUN echo "Compiling..." RUN source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make diff --git a/libfmt-long-double.patch b/libfmt-long-double.patch deleted file mode 100644 index 40b520e3922..00000000000 --- a/libfmt-long-double.patch +++ /dev/null @@ -1,12 +0,0 @@ -diff --git a/build/_deps/libfmt-src/include/fmt/format.h b/build/_deps/libfmt-src/include/fmt/format.h -index 7637c8a0d..92b2131ef 100644 ---- a/build/_deps/libfmt-src/include/fmt/format.h -+++ b/build/_deps/libfmt-src/include/fmt/format.h -@@ -1542,6 +1542,7 @@ template <> struct float_info { - template - struct float_info::digits == 64 || -+ std::numeric_limits::digits == 53 || - std::numeric_limits::digits == 113 || - is_float128::value>> { - using carrier_uint = detail::uint128_t; - From dcac99bd9432d60245cec64076778b1657b10a5b Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Mon, 7 Oct 2024 03:39:55 +0100 Subject: [PATCH 27/33] Fix typo in DREAMCAST ifdef lines --- Source/loadsave.cpp | 8 ++++---- Source/pfile.h | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/loadsave.cpp b/Source/loadsave.cpp index 71a811f89c8..fb485b91461 100644 --- a/Source/loadsave.cpp +++ b/Source/loadsave.cpp @@ -2223,7 +2223,7 @@ size_t HotkeysSize(size_t nHotkeys = NumHotkeys) void LoadHotkeys() { -#ifdef __DREAMCAST____ +#ifdef __DREAMCAST__ // hotkeys => htks to get around VMU filename size limits LoadHelper file(OpenSaveArchive(gSaveNumber), "htks"); #else @@ -2270,7 +2270,7 @@ void LoadHotkeys() void SaveHotkeys(SaveWriter &saveWriter, const Player &player) { -#ifdef __DREAMCAST____ +#ifdef __DREAMCAST__ // hotkeys => htks to get around VMU filename size limits SaveHelper file(saveWriter, "htks", HotkeysSize()); #else @@ -2295,7 +2295,7 @@ void SaveHotkeys(SaveWriter &saveWriter, const Player &player) void LoadHeroItems(Player &player) { -#ifdef __DREAMCAST____ +#ifdef __DREAMCAST__ // heroitems => hitms to get around VMU filename size limits LoadHelper file(OpenSaveArchive(gSaveNumber), "hitms"); #else @@ -2580,7 +2580,7 @@ void LoadGame(bool firstflag) void SaveHeroItems(SaveWriter &saveWriter, Player &player) { size_t itemCount = static_cast(NUM_INVLOC) + InventoryGridCells + MaxBeltItems; // 7 + 40 + 8 = 55 -#ifdef __DREAMCAST____ +#ifdef __DREAMCAST__ // heroitems => hitms to get around VMU filename size limits SaveHelper file(saveWriter, "hitms", itemCount * (gbIsHellfire ? HellfireItemSaveSize : DiabloItemSaveSize) + sizeof(uint8_t)); // 55 * 368 + 1 = 20241 bytes #else diff --git a/Source/pfile.h b/Source/pfile.h index da520a00d2b..64b7c9d709c 100644 --- a/Source/pfile.h +++ b/Source/pfile.h @@ -19,7 +19,7 @@ namespace devilution { -#ifdef __DREAMCAST____ +#ifdef __DREAMCAST__ #define MAX_CHARACTERS 1 // todo restore me to 99 #else #define MAX_CHARACTERS 99 From f63f91431fc108543af31bf27dfdc8ee78955c1d Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Mon, 7 Oct 2024 20:55:42 +0100 Subject: [PATCH 28/33] Remove obsolete ramdisk code --- Source/pfile.cpp | 87 ++---------------------------------------------- 1 file changed, 2 insertions(+), 85 deletions(-) diff --git a/Source/pfile.cpp b/Source/pfile.cpp index 0cd66dcadbf..a9bf2ca6e73 100644 --- a/Source/pfile.cpp +++ b/Source/pfile.cpp @@ -35,7 +35,6 @@ #ifdef __DREAMCAST__ #include #include -#include #include #endif @@ -54,28 +53,6 @@ namespace devilution { bool gbValidSaveFile; -#ifdef __DREAMCAST__ -void listdir(const char *dir, int depth) -{ - file_t d = fs_open(dir, O_RDONLY | O_DIR); - dirent_t *entry; - printf("============ %s ============\n", dir); - while (NULL != (entry = fs_readdir(d))) { - char absolutePath[1024]; - strcpy(absolutePath, dir); - strcat(absolutePath, "/"); - strcat(absolutePath, entry->name); - bool isDir = entry->size == -1; - printf("[%s]\t%.2f kB\t%s\n", isDir ? "DIR" : "FIL", entry->size / 1024.0, entry->name); - if (isDir) { - printf("absolutePath = %s, depth = %d\n", absolutePath, depth); - listdir(absolutePath, depth + 1); - } - } - fs_close(d); - printf("============ %s ============\n\n\n", dir); -} -#endif namespace { /** List of character names for the character selection screen. */ @@ -83,10 +60,10 @@ char hero_names[MAX_CHARACTERS][PlayerNameLength]; std::string GetSavePath(uint32_t saveNum, std::string_view savePrefix = {}) { - // shorter names to get around VMU filename size limits return StrCat(paths::PrefPath(), savePrefix, gbIsSpawn #ifdef __DREAMCAST__ + // shorter names to get around VMU filename size limits ? (gbIsMultiplayer ? "M" : "S") : (gbIsMultiplayer ? "m" : "s"), #else @@ -188,11 +165,7 @@ bool ReadHero(SaveReader &archive, PlayerPack *pPack) } #ifdef __DREAMCAST__ - Log("{} == sizeof(*pPack) ({}) = {}", read, sizeof(*pPack), read == sizeof(*pPack)); Log("Read player {}", pPack->pName); - // Log("\tpHPBase = {}", pPack->pHPBase); - listdir("/ram", 0); - listdir("/vmu/a1", 0); #endif return ret; } @@ -636,31 +609,6 @@ std::unique_ptr SaveReader::ReadFile(const char *filename, std::siz return result; } -/* - * todo: add bzip compression to the inventory data (hitms) -std::byte* compressHeroItems(std::byte *data, size_t size) -{ - int bzBuffToBuffCompress( char* dest, - unsigned int* destLen, - char* source, - unsigned int sourceLen, - int blockSize100k, - int verbosity, - int workFactor ); - - char *compressed = malloc(sizeof(std::byte) * size); - size_t compressedLength; - if(BZ_OK != bzBuffToBuffCompress( - compressed, - &compressedLength, - data, - size, - - )) { - free(compressed); - } -}*/ - bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t size) { Log("SaveWriter::WriteFile(\"{}\", data[], {})", filename, size); @@ -668,6 +616,7 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s Log("dir_ = {}", dir_); Log("path = {}", path); const char *baseName = basename(path.c_str()); + // vmu code if (dir_.starts_with("/vmu")) { vmu_pkg_t package; @@ -715,32 +664,8 @@ bool SaveWriter::WriteFile(const char *filename, const std::byte *data, size_t s } delete[] package.data; free(contents); - listdir("/vmu/a1", 0); return true; } - - // ramdisk code - bool exists = FileExists(baseName); - if (exists) { - Log("{} exists, removing it", path); - void *toFree; - size_t ignore; - int detach_result = fs_ramdisk_detach(baseName, &toFree, &ignore); - free(toFree); - Log("fs_ramdisk_detach result = {}", detach_result); - if (detach_result == -1) { - return false; - } - } - Log("\tAllocating {} bytes for path {}", size, baseName); - void *buffer = malloc(size); - memcpy(buffer, data, size); - Log("\tMallocation succeeded ? {}", buffer != NULL); - int attach_result = fs_ramdisk_attach(baseName, buffer, size); - Log("\tAttach result: {}", attach_result); - Log("Current ramdisk contents:"); - listdir("/ram", 0); - return attach_result != -1; } #else std::unique_ptr SaveReader::ReadFile(const char *filename, std::size_t &fileSize, int32_t &error) @@ -984,17 +909,9 @@ void pfile_read_player_from_save(uint32_t saveNum, Player &player) { std::optional archive = OpenSaveArchive(saveNum); if (!archive) { -#ifdef __DREAMCAST__ - listdir("/ram", 0); - listdir("/vmu/a1", 0); -#endif app_fatal(_("Unable to open archive")); } if (!ReadHero(*archive, &pkplr)) { -#ifdef __DREAMCAST__ - listdir("/ram", 0); - listdir("/vmu/a1", 0); -#endif app_fatal(_("Unable to load character")); } From 51ab7b7a2fa8d3e269723c715af5e4415a120b31 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Mon, 21 Oct 2024 02:21:51 +0100 Subject: [PATCH 29/33] Restore unpacked MPQ support This reverts commit 6cb0513c81ad49708b60df0d7bb89c5f226ce348. --- .github/workflows/dreamcast.yml | 42 ++++++++++++++++++++++----------- CMake/platforms/dreamcast.cmake | 1 + Dockerfile | 25 ++++++++++++++------ 3 files changed, 47 insertions(+), 21 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index e51c38cc754..c1a44efef56 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -6,6 +6,7 @@ on: # yamllint disable-line rule:truthy branches: - master - dreamcast + - fix/ISSUE-15-unpacked-mpqs paths-ignore: - '*.md' - 'docs/**' @@ -32,6 +33,28 @@ jobs: with: fetch-depth: 0 + - name: Build unpack_and_minify_mpq + run: | + git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ + cd devilutionx-mpq-tools && \ + cmake -S. -Bbuild-rel -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && \ + cmake --build build-rel && \ + cmake --install build-rel + + - name: Download and unpack spawn.mpq + run: | + #devilutionx-assets spawn.mpq fails with unpack_and_minify_mpq + #curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/spawn.mpq + curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq + unpack_and_minify_mpq spawn.mpq --output-dir data + rm spawn.mpq + + - name: Download and unpack fonts.mpq + run: | + curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq && \ + unpack_and_minify_mpq fonts.mpq --output-dir data/fonts && \ + rm fonts.mpq + - name: Uninstall kos-ports SDL 1.2 run: | source /opt/toolchains/dc/kos/environ.sh && \ @@ -47,20 +70,12 @@ jobs: cp /opt/toolchains/dc/kos/addons/lib/dreamcast/libSDL.a /usr/lib/ && \ cp include/* /usr/include/SDL/ - - name: Download spawn.mpq - run: | - curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/spawn.mpq - - - name: Download fonts.mpq - run: | - curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq - - name: Configure CMake run: | source /opt/toolchains/dc/kos/environ.sh && \ - #uncomment when using packed mpq or save files + #uncomment when using packed save files #without this, cmake can't find the kos-ports bzip2 & zlib libraries - export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ + #export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ kos-cmake \ -S . \ -DCMAKE_BUILD_TYPE=Release \ @@ -73,12 +88,12 @@ jobs: - name: Generate .cdi run: | source /opt/toolchains/dc/kos/environ.sh && \ - mv spawn.mpq build/data/ && \ - mv fonts.mpq build/data/fonts/ && \ + mv data/spawn build/data/spawn && \ + mv data/fonts/fonts/fonts/ build/data/fonts/ && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ - name: Prepare elf package - run: rm build/data/spawn.mpq && rm build/data/fonts/fonts.mpq + run: rm -rf build/data/spawn && rm -rf build/data/fonts/fonts/ - name: Upload .elf Package if: ${{ !env.ACT }} @@ -117,4 +132,3 @@ jobs: with: file: ./build/devilutionx-dreamcast.zip overwrite: true -... diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index 4b97cc3a253..ec593569784 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -12,6 +12,7 @@ set(DEVILUTIONX_GAMEPAD_TYPE Nintendo) set(NOSOUND ON) set(DEVILUTIONX_STATIC_ZLIB ON) +set(UNPACKED_MPQS ON) set(UNPACKED_SAVES ON) set(DEVILUTIONX_SYSTEM_LIBFMT OFF) set(DEVILUTIONX_STATIC_LUA ON) diff --git a/Dockerfile b/Dockerfile index b33cd77d5ea..84f0f0beff3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,12 @@ FROM alpine-kallistios:no-gdb +RUN echo "Building unpack_and_minify_mpq..." +RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ + cd devilutionx-mpq-tools && \ + cmake -S. -Bbuild-rel -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=OFF && \ + cmake --build build-rel && \ + cmake --install build-rel + RUN echo "Cloning project..." WORKDIR /opt/toolchains/dc/kos/ RUN git clone -b dreamcast https://github.com/azihassan/devilutionX.git @@ -18,17 +25,21 @@ RUN git clone -b SDL-dreamhal--GLDC https://github.com/GPF/SDL-1.2 && \ cp include/* /usr/include/SDL/ WORKDIR /opt/toolchains/dc/kos/devilutionX -RUN echo "Downloading spawn.mpq..." -RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/spawn.mpq +RUN echo "Downloading and unpacking spawn.mpq..." +RUN curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq && \ + unpack_and_minify_mpq spawn.mpq --output-dir data && \ + rm spawn.mpq -RUN echo "Downloading fonts.mpq..." -RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq +RUN echo "Downloading and unpacking fonts.mpq..." +RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq && \ + unpack_and_minify_mpq fonts.mpq --output-dir data/fonts && \ + rm fonts.mpq RUN echo "Configuring CMake..." RUN source /opt/toolchains/dc/kos/environ.sh && \ #uncomment when using packed save files #without this, cmake can't find the kos-ports bzip2 & zlib libraries - export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ + #export CMAKE_PREFIX_PATH=/opt/toolchains/dc/kos-ports/libbz2/inst/:/opt/toolchains/dc/kos-ports/zlib/inst/ && \ kos-cmake -S. -Bbuild RUN echo "Compiling..." @@ -36,8 +47,8 @@ RUN source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make RUN echo "Generating CDI" RUN source /opt/toolchains/dc/kos/environ.sh && \ - mv spawn.mpq build/data/ && \ - mv fonts.mpq build/data/fonts && \ + mv data/spawn build/data/spawn && \ + mv data/fonts/fonts/fonts/ build/data/fonts && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ ENTRYPOINT ["sh", "-c", "source /opt/toolchains/dc/kos/environ.sh && \"$@\"", "-s"] From df4540b3f1a06fc216a3816a4a2d8c9bd6f1379e Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Mon, 14 Oct 2024 02:28:13 +0100 Subject: [PATCH 30/33] Disable LTO --- CMake/platforms/dreamcast.cmake | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CMake/platforms/dreamcast.cmake b/CMake/platforms/dreamcast.cmake index ec593569784..3161c23f131 100644 --- a/CMake/platforms/dreamcast.cmake +++ b/CMake/platforms/dreamcast.cmake @@ -46,3 +46,5 @@ set(JOY_BUTTON_START 3) #GPF SDL files set(SDL_INCLUDE_DIR /usr/include/SDL/) set(SDL_LIBRARY /usr/lib/libSDL.a) + +add_compile_options(-flto=none) From 8873aa865508f35a0982c52ab430d63a69203f0c Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sat, 19 Oct 2024 23:52:18 +0100 Subject: [PATCH 31/33] Enable DMA driver once before SDL_Init --- Source/utils/display.cpp | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/Source/utils/display.cpp b/Source/utils/display.cpp index 3f0b616ec83..67e366dc269 100644 --- a/Source/utils/display.cpp +++ b/Source/utils/display.cpp @@ -242,9 +242,6 @@ float GetDpiScalingFactor() void SetVideoMode(int width, int height, int bpp, uint32_t flags) { Log("Setting video mode {}x{} bpp={} flags=0x{:08X}", width, height, bpp, flags); -#ifdef __DREAMCAST__ - enable_dma_driver(); -#endif ghMainWnd = SDL_SetVideoMode(width, height, bpp, flags); if (ghMainWnd == nullptr) { ErrSdl(); @@ -323,6 +320,9 @@ bool SpawnWindow(const char *lpWindowName) initFlags |= SDL_INIT_GAMECONTROLLER; SDL_SetHint(SDL_HINT_ORIENTATIONS, "LandscapeLeft LandscapeRight"); +#endif +#ifdef __DREAMCAST__ + enable_dma_driver(); #endif if (SDL_Init(initFlags) <= -1) { ErrSdl(); @@ -478,9 +478,6 @@ void SetFullscreenMode() if (*sgOptions.Graphics.fullscreen) { flags |= SDL_FULLSCREEN; } -#ifdef __DREAMCAST__ - enable_dma_driver(); -#endif ghMainWnd = SDL_SetVideoMode(0, 0, 0, flags); if (ghMainWnd == NULL) { ErrSdl(); From 1027d0990375e042329655a5b47a6597b31a6940 Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Tue, 29 Oct 2024 02:19:48 +0100 Subject: [PATCH 32/33] Fix extra font detection issue when loading from SD card --- .github/workflows/dreamcast.yml | 9 ++++----- Dockerfile | 16 +++++++++++----- Source/init.cpp | 13 +++++++++---- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/.github/workflows/dreamcast.yml b/.github/workflows/dreamcast.yml index c1a44efef56..3c929007ed4 100644 --- a/.github/workflows/dreamcast.yml +++ b/.github/workflows/dreamcast.yml @@ -6,7 +6,6 @@ on: # yamllint disable-line rule:truthy branches: - master - dreamcast - - fix/ISSUE-15-unpacked-mpqs paths-ignore: - '*.md' - 'docs/**' @@ -46,13 +45,13 @@ jobs: #devilutionx-assets spawn.mpq fails with unpack_and_minify_mpq #curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/spawn.mpq curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq - unpack_and_minify_mpq spawn.mpq --output-dir data + unpack_and_minify_mpq spawn.mpq rm spawn.mpq - name: Download and unpack fonts.mpq run: | curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq && \ - unpack_and_minify_mpq fonts.mpq --output-dir data/fonts && \ + unpack_and_minify_mpq fonts.mpq && \ rm fonts.mpq - name: Uninstall kos-ports SDL 1.2 @@ -88,8 +87,8 @@ jobs: - name: Generate .cdi run: | source /opt/toolchains/dc/kos/environ.sh && \ - mv data/spawn build/data/spawn && \ - mv data/fonts/fonts/fonts/ build/data/fonts/ && \ + mv spawn build/data/spawn && \ + mv fonts/fonts/ build/data/fonts/ && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ - name: Prepare elf package diff --git a/Dockerfile b/Dockerfile index 84f0f0beff3..6c327a9fa6f 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM alpine-kallistios:no-gdb +FROM azihassan/kallistios:fdffe33635239d46bcccf0d5c4d59bb7d2d91f38 RUN echo "Building unpack_and_minify_mpq..." RUN git clone https://github.com/diasurgical/devilutionx-mpq-tools/ && \ @@ -27,14 +27,19 @@ RUN git clone -b SDL-dreamhal--GLDC https://github.com/GPF/SDL-1.2 && \ WORKDIR /opt/toolchains/dc/kos/devilutionX RUN echo "Downloading and unpacking spawn.mpq..." RUN curl -LO https://raw.githubusercontent.com/d07RiV/diabloweb/3a5a51e84d5dab3cfd4fef661c46977b091aaa9c/spawn.mpq && \ - unpack_and_minify_mpq spawn.mpq --output-dir data && \ + unpack_and_minify_mpq spawn.mpq && \ rm spawn.mpq RUN echo "Downloading and unpacking fonts.mpq..." RUN curl -LO https://github.com/diasurgical/devilutionx-assets/releases/download/v4/fonts.mpq && \ - unpack_and_minify_mpq fonts.mpq --output-dir data/fonts && \ + unpack_and_minify_mpq fonts.mpq && \ rm fonts.mpq +#WORKDIR /opt/toolchains/dc/kos/devilutionX +#RUN echo "Copying and unpacking diabdat.mpq..." +#COPY DIABDAT.MPQ . +#RUN unpack_and_minify_mpq DIABDAT.MPQ + RUN echo "Configuring CMake..." RUN source /opt/toolchains/dc/kos/environ.sh && \ #uncomment when using packed save files @@ -47,8 +52,9 @@ RUN source /opt/toolchains/dc/kos/environ.sh && cd build && kos-make RUN echo "Generating CDI" RUN source /opt/toolchains/dc/kos/environ.sh && \ - mv data/spawn build/data/spawn && \ - mv data/fonts/fonts/fonts/ build/data/fonts && \ + mv spawn build/data/spawn && \ + mv fonts/fonts/ build/data/fonts/ && \ + #mv diabdat build/data/diabdat && \ mkdcdisc -e build/devilutionx.elf -o build/devilutionx.cdi --name 'Diablo 1' -d build/data/ ENTRYPOINT ["sh", "-c", "source /opt/toolchains/dc/kos/environ.sh && \"$@\"", "-s"] diff --git a/Source/init.cpp b/Source/init.cpp index 57357ae06e8..360947149b9 100644 --- a/Source/init.cpp +++ b/Source/init.cpp @@ -219,11 +219,16 @@ bool IsDevilutionXMpqOutOfDate(MpqArchive &archive) #ifdef UNPACKED_MPQS bool AreExtraFontsOutOfDate(const std::string &path) { -#ifdef __DREAMCAST__ - // handle ISO 9660 trailing period - const std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION."; -#else +#ifndef __DREAMCAST__ const std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION"; +#else + std::string versionPath = path + "fonts" DIRECTORY_SEPARATOR_STR "VERSION"; + if (!FileExists(versionPath)) { + Log("{} not found, appending trailing period", versionPath); + // handle ISO 9660 trailing period + versionPath += "."; + Log("New versionPath: {}", versionPath); + } #endif if (versionPath.size() + 1 > AssetRef::PathBufSize) From 3a0d56c160c3771897c51f63fceaf9da62ef2e3a Mon Sep 17 00:00:00 2001 From: Azi Hassan Date: Sat, 16 Nov 2024 02:22:43 +0100 Subject: [PATCH 33/33] Disable stash from NPC menu --- Source/stores.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Source/stores.cpp b/Source/stores.cpp index 5016e557b62..5fcbb2d97da 100644 --- a/Source/stores.cpp +++ b/Source/stores.cpp @@ -1265,7 +1265,9 @@ void StartBarmaid() AddSText(0, 2, _("Gillian"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSText(0, 9, _("Would you like to:"), UiFlags::ColorWhitegold | UiFlags::AlignCenter, false); AddSText(0, 12, _("Talk to Gillian"), UiFlags::ColorBlue | UiFlags::AlignCenter, true); +#ifndef __DREAMCAST__ AddSText(0, 14, _("Access Storage"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); +#endif AddSText(0, 18, _("Say goodbye"), UiFlags::ColorWhite | UiFlags::AlignCenter, true); AddSLine(5); CurrentItemIndex = 20;