Merge branch 'main' of https://github.com/TwilitRealm/dusk into randomizer

This commit is contained in:
gymnast86
2026-05-10 09:56:06 -07:00
17 changed files with 220 additions and 47 deletions
+4 -4
View File
@@ -50,7 +50,7 @@ jobs:
libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9
uses: mozilla-actions/sccache-action@v0.0.10
- name: Print sccache stats
run: sccache --show-stats
@@ -124,7 +124,7 @@ jobs:
rustup target add x86_64-apple-darwin
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9
uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure CMake
run: cmake --preset ${{matrix.preset}}
@@ -175,7 +175,7 @@ jobs:
java-version: 17
- name: Setup Android SDK
uses: android-actions/setup-android@v3
uses: android-actions/setup-android@v4
- name: Install Android SDK packages
run: sdkmanager "platforms;android-36" "build-tools;36.1.0" "ndk;${ANDROID_NDK_VERSION}"
@@ -186,7 +186,7 @@ jobs:
rustup target add ${{matrix.rust_target}}
- name: Setup sccache
uses: mozilla-actions/sccache-action@v0.0.9
uses: mozilla-actions/sccache-action@v0.0.10
- name: Configure CMake
run: cmake --preset ${{matrix.preset}}
+4 -1
View File
@@ -19,6 +19,9 @@ It aims to be as accurate as possible to the original while also providing new o
> [!IMPORTANT]
> Dusk does *not* provide any copyrighted assets. You must provide your own copy of the original game.
> [!IMPORTANT]
> At a minimum, Dusk requires a GPU with support for either D3D12, Vulkan, or Metal. Your experience with specific hardware, operating systems, and drivers may vary. In particular, older Intel iGPUs have a high likelyhood of incompatibility. We are also aware of a number of issues on devices with Adreno GPUs and are working to resolve them.
### 1. Verify your dump
First, make sure your dump of the game is clean and supported by Dusk. You can do this by checking the SHA-1 hash of your dump against this list of supported versions:
@@ -52,7 +55,7 @@ First, make sure your dump of the game is clean and supported by Dusk. You can d
If you'd like to build Dusk from source, please read the [build instructions](docs/building.md).
Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much.
Pull requests are welcomed! Note that we do not accept contributions that are primarily AI-generated and will close your PR if we suspect as much. Please also see the [code conventions](docs/code-conventions.md).
# Credits
+16 -8
View File
@@ -1,18 +1,26 @@
#!/bin/bash -ex
shopt -s extglob
if [[ -n "${GITHUB_WORKSPACE:-}" ]]; then
cd "$GITHUB_WORKSPACE"
fi
build_dir="$PWD/build"
linuxdeploy="$build_dir/linuxdeploy-$(uname -m).AppImage"
# Get linuxdeploy
cd "$RUNNER_WORKSPACE"
curl -fOL https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage
chmod +x linuxdeploy-$(uname -m).AppImage
mkdir -p "$build_dir"
curl -fL "https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-$(uname -m).AppImage" -o "$linuxdeploy"
chmod +x "$linuxdeploy"
# Build AppImage
cd "$GITHUB_WORKSPACE"
mkdir -p build/appdir/usr/{bin,share/{applications,icons/hicolor}}
cp -r build/install/!(*.*) build/appdir/usr/bin
for install_path in build/install/*; do
[[ "$(basename "$install_path")" == *.* ]] && continue
cp -r "$install_path" build/appdir/usr/bin
done
cp -r platforms/freedesktop/{16x16,32x32,48x48,64x64,128x128,256x256,512x512,1024x1024} build/appdir/usr/share/icons/hicolor
cp platforms/freedesktop/dusk.desktop build/appdir/usr/share/applications
cd build/install
VERSION="$DUSK_VERSION" NO_STRIP=1 "$RUNNER_WORKSPACE"/linuxdeploy-$(uname -m).AppImage \
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$GITHUB_WORKSPACE"/build/appdir --output appimage
VERSION="$DUSK_VERSION" NO_STRIP=1 "$linuxdeploy" \
-l /usr/lib/x86_64-linux-gnu/libusb-1.0.so --appdir "$build_dir/appdir" --output appimage
+13
View File
@@ -0,0 +1,13 @@
# Code conventions for Dusk
## Upstream when appropriate
Bug fixes, documentation improvements, code cleanup, etc that also apply to the [original decompilation project](https://github.com/zeldaret/tp) should preferably be PR'd there.
## Properly indicate Dusk-modified code
When modifying original game code (i.e. in decomp) for Dusk's purposes, please clearly delineate such code as being Dusk-specific. Generally, this can be done by using `#if TARGET_PC` and keeping the original code in place. Use `#if AVOID_UB` for Undefined Behavior fixes to the original codebase.
## Miscellaneous things
* The original codebase makes heavy use of global `operator new` and similar overloads to allocate into a strict tree of heaps. This would cause many linkage headaches for us, so effectively all uses of `new` or `delete` in the original game code have been replaced with `JKR_NEW`, `JKR_DELETE`, or similar macros. See `JKRHeap.h` for the full list.
+1 -1
+71
View File
@@ -5,9 +5,73 @@
outputs = { self, nixpkgs }:
let
pkgs = import nixpkgs { system = "x86_64-linux"; };
# Dependencies that are not packaged in nixpkgs:
aurora-src = pkgs.fetchFromGitHub {
owner = "encounter";
repo = "aurora";
rev = "63606a43265a3bc18dafd500ab4d7a2108f109e6";
hash = "sha256-xBvnAwGwNzav67Ac6oUz7RqDUwqgL2bsME3OOMn8Tqw=";
};
dawn-src = pkgs.fetchzip {
url = "https://github.com/encounter/dawn-build/releases/download/v20260423.175430/dawn-linux-x86_64.tar.gz";
hash = "sha256-HXfKTLHtMPwupnFnaflCARtXVPuS/0PoCePXidjE5xs=";
stripRoot = false;
};
nod-src = pkgs.fetchzip {
url = "https://github.com/encounter/nod/releases/download/v2.0.0-alpha.8/libnod-linux-x86_64.tar.gz";
hash = "sha256-mUqvLsbsqaZ+HAjMmHYPYO+MgtanGRTw7Gzn5uXR5rE=";
stripRoot = false;
};
# The version of imgui on nixpkgs does not map cleanly.
imgui-src = pkgs.fetchFromGitHub {
owner = "ocornut";
repo = "imgui";
rev = "v1.91.9b-docking";
hash = "sha256-mQOJ6jCN+7VopgZ61yzaCnt4R1QLrW7+47xxMhFRHLQ=";
};
sqlite-src = pkgs.fetchzip {
url = "https://sqlite.org/2026/sqlite-amalgamation-3510300.zip";
hash = "sha256-pNMR8zxaaqfAzQ0AQBOXMct4usdjey1Q0Gnitg06UhM=";
};
rmlui-src = pkgs.fetchzip {
url = "https://github.com/mikke89/RmlUi/archive/f9b8c9e2935d5df2c7dff2c190d3968e99b0c3dc.tar.gz";
hash = "sha256-g4O/JZUrrcseOz8o2QJRt+2CeuiLnVeuDJc906xvuIg=";
};
# Dusk Actual
dusk = pkgs.stdenv.mkDerivation {
name = "dusk";
src = ./.;
postUnpack = ''
mkdir -p $sourceRoot/extern/aurora
cp -r ${aurora-src}/. $sourceRoot/extern/aurora/
chmod -R u+w $sourceRoot/extern/aurora
sed -i '/add_subdirectory(tests)/d' $sourceRoot/extern/aurora/CMakeLists.txt
'';
# Remove last line to re-enable tests
cmakeFlags = [
"-DFETCHCONTENT_FULLY_DISCONNECTED=ON"
"-DFETCHCONTENT_SOURCE_DIR_CXXOPTS=${pkgs.cxxopts.src}"
"-DFETCHCONTENT_SOURCE_DIR_JSON=${pkgs.nlohmann_json.src}"
"-DFETCHCONTENT_SOURCE_DIR_DAWN_PREBUILT=${dawn-src}"
"-DFETCHCONTENT_SOURCE_DIR_XXHASH=${pkgs.xxHash.src}"
"-DFETCHCONTENT_SOURCE_DIR_FMT=${pkgs.fmt.src}"
"-DFETCHCONTENT_SOURCE_DIR_TRACY=${pkgs.tracy.src}"
"-DAURORA_SDL3_PROVIDER=system"
"-DFETCHCONTENT_SOURCE_DIR_NOD_PREBUILT=${nod-src}"
"-DAURORA_NOD_PROVIDER=package"
"-DFETCHCONTENT_SOURCE_DIR_FREETYPE=${pkgs.freetype.src}"
"-DFETCHCONTENT_SOURCE_DIR_ZSTD=${pkgs.zstd.src}"
"-DFETCHCONTENT_SOURCE_DIR_SQLITE3=${sqlite-src}"
"-DFETCHCONTENT_SOURCE_DIR_IMGUI=${imgui-src}"
"-DFETCHCONTENT_SOURCE_DIR_RMLUI=${rmlui-src}"
"-DCMAKE_CROSSCOMPILING=ON" # Tests are not working as I didn't want to work through getting google's test suite working as well. This is the only guard I could find to disable it.
];
installPhase = ''
mkdir -p $out/bin
cp dusk $out/bin/dusk
cp -r ./res $out/bin/res
'';
nativeBuildInputs = [
pkgs.cmake
pkgs.pkg-config
@@ -25,6 +89,13 @@
pkgs.libjpeg8
pkgs.libxkbcommon
pkgs.libglvnd
pkgs.cxxopts
pkgs.abseil-cpp
pkgs.sdl3
pkgs.fmt
pkgs.tracy
pkgs.freetype
pkgs.zstd
];
};
in {
+9 -5
View File
@@ -1,14 +1,13 @@
#ifndef DUSK_IO_HPP
#define DUSK_IO_HPP
#include <vector>
#include <filesystem>
#include <vector>
// I can't believe it's 2026 and neither SDL (no error codes) nor
// C++ (no error codes) have a file system API functional enough for me to use.
// Here you go, this one's inspired by C#. I only wrote the functions I need.
namespace dusk::io {
/**
@@ -83,9 +82,7 @@ public:
/**
* Get direct access to the underlying FILE* handle.
*/
[[nodiscard]] void* GetFileHandle() const noexcept {
return file;
}
[[nodiscard]] void* GetFileHandle() const noexcept { return file; }
/**
* Write data to the file.
@@ -95,7 +92,14 @@ public:
FILE* ToInner();
};
/**
* Converts a std::filesystem::path to a std::string, UTF-8, without exploding on Windows.
*/
inline std::string fs_path_to_string(const std::filesystem::path& path) {
const auto u8str = path.u8string();
return {reinterpret_cast<const char*>(u8str.c_str())};
}
} // namespace dusk::io
#endif // DUSK_IO_HPP
+2
View File
@@ -83,5 +83,7 @@
<true/>
<key>LSSupportsOpeningDocumentsInPlace</key>
<true/>
<key>LSSupportsGameMode</key>
<true/>
</dict>
</plist>
+2
View File
@@ -28,5 +28,7 @@
<string>${MACOSX_BUNDLE_SHORT_VERSION_STRING}</string>
<key>NSHighResolutionCapable</key>
<true/>
<key>LSSupportsGameMode</key>
<true/>
</dict>
</plist>
+2
View File
@@ -45,5 +45,7 @@
<string>LaunchScreen</string>
<key>UIUserInterfaceStyle</key>
<string>Automatic</string>
<key>LSSupportsGameMode</key>
<true />
</dict>
</plist>
+3 -1
View File
@@ -314,7 +314,9 @@ namespace dusk {
ImGui::PopFont();
}
ImGui::PushFont(ImGuiEngine::fontLarge);
ImGuiTextCenter("Failed to initialize any graphics backend");
ImGuiTextCenter("Failed to initialize any graphics backend.");
ImGuiTextCenter("\nYour system may be misconfigured, or your hardware may not support the required versions of any of the available backends.");
ImGuiTextCenter("\nA clean reinstall of Dusk may help. For further assistance, please visit #tech-support on the Twilit Realm Discord server.");
const auto& style = ImGui::GetStyle();
const auto retrySize = ImGui::CalcTextSize("Retry (Auto backend)");
const auto quitSize = ImGui::CalcTextSize("Quit");
+5 -6
View File
@@ -48,19 +48,18 @@ void ImGuiStateShare::onMergeFileSelected(void* userdata, const char* path, cons
static std::string GetStatesFilePath() {
return (dusk::ConfigPath / STATES_FILENAME).string();
static std::filesystem::path GetStatesFilePath() {
return ConfigPath / STATES_FILENAME;
}
void ImGuiStateShare::loadStatesFile() {
m_loaded = true;
const std::filesystem::path filePath = dusk::ConfigPath / STATES_FILENAME;
const std::filesystem::path filePath = GetStatesFilePath();
if (!std::filesystem::exists(filePath)) {
return;
}
try {
const std::string pathStr = filePath.string();
auto data = io::FileStream::ReadAllBytes(pathStr.c_str());
auto data = io::FileStream::ReadAllBytes(filePath);
auto j = json::parse(data);
if (!j.is_array()) {
return;
@@ -85,7 +84,7 @@ void ImGuiStateShare::saveStatesFile() {
j.push_back(json{{"name", s.name}, {"data", s.encoded}});
}
try {
io::FileStream::WriteAllText(GetStatesFilePath().c_str(), j.dump(2));
io::FileStream::WriteAllText(GetStatesFilePath(), j.dump(2));
} catch (const std::exception& e) {
m_statusMsg = fmt::format("Failed to save states: {}", e.what());
}
+2 -2
View File
@@ -208,7 +208,7 @@ void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraL
std::filesystem::create_directories(logsDir, ec);
if (ec) {
std::fprintf(stderr, "[WARNING | dusk] Failed to create log directory '%s': %s\n",
logsDir.string().c_str(), ec.message().c_str());
io::fs_path_to_string(logsDir).c_str(), ec.message().c_str());
return;
}
@@ -216,7 +216,7 @@ void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraL
g_logState.file = io::FileStream::Create(logPath).ToInner();
if (g_logState.file == nullptr) {
std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n",
logPath.string().c_str());
io::fs_path_to_string(logPath).c_str());
return;
}
+2 -1
View File
@@ -6,6 +6,7 @@
#include <aurora/main.h>
#include "dusk/main.h"
#include "dusk/io.hpp"
#include <algorithm>
#include <array>
@@ -91,7 +92,7 @@ bool RestartProcess(int argc, char* argv[]) {
std::vector<std::string> args;
args.reserve(static_cast<size_t>(std::max(argc, 1)));
args.push_back(executablePath.string());
args.push_back(dusk::io::fs_path_to_string(executablePath));
for (int i = 1; i < argc; ++i) {
args.emplace_back(argv[i] != nullptr ? argv[i] : "");
}
+2 -1
View File
@@ -11,6 +11,7 @@
#include <ranges>
#include "aurora/lib/window.hpp"
#include "dusk/io.hpp"
#include "input.hpp"
#include "prelaunch.hpp"
#include "window.hpp"
@@ -19,7 +20,7 @@ namespace dusk::ui {
namespace {
void load_font(const char* filename, bool fallback = false) {
Rml::LoadFontFace(resource_path(filename).string(), fallback);
Rml::LoadFontFace(io::fs_path_to_string(resource_path(filename)), fallback);
}
bool sInitialized = false;
+7
View File
@@ -740,6 +740,8 @@ static void fapGm_AfterRecord() {
fapGm_After();
}
BOOL isRecording = false;
static void duskExecute() {
handleGamepadColor();
updateAutoSave();
@@ -747,6 +749,11 @@ static void duskExecute() {
if (dusk::getSettings().game.recordingMode) {
Z2GetSoundMgr()->getSeqMgr()->getParams()->moveVolume(0.0f, 0);
Z2GetSoundMgr()->getStreamMgr()->getParams()->moveVolume(0.0f, 0);
isRecording = true;
} else if (isRecording) {
Z2GetSoundMgr()->getSeqMgr()->getParams()->moveVolume(1.0f, 0);
Z2GetSoundMgr()->getStreamMgr()->getParams()->moveVolume(1.0f, 0);
isRecording = false;
}
if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigX(PAD_1)) {
+75 -17
View File
@@ -71,6 +71,7 @@
#include <dolphin/dvd.h>
#include "SDL3/SDL_filesystem.h"
#include "SDL3/SDL_iostream.h"
#include "SDL3/SDL_misc.h"
#include "cxxopts.hpp"
#include "d/actor/d_a_movie_player.h"
@@ -78,6 +79,7 @@
#include "dusk/audio/DuskDsp.hpp"
#include "dusk/config.hpp"
#include "dusk/settings.h"
#include "dusk/io.hpp"
#include "dusk/version.hpp"
#include "dusk/discord_presence.hpp"
#include "tracy/Tracy.hpp"
@@ -129,7 +131,7 @@ bool dusk::OpenDataFolder() {
std::filesystem::path path = std::filesystem::absolute(ConfigPath, ec);
if (ec) {
DuskLog.warn("Failed to resolve absolute data folder path '{}': {}",
ConfigPath.string(), ec.message());
io::fs_path_to_string(ConfigPath), ec.message());
path = ConfigPath;
}
@@ -139,7 +141,8 @@ bool dusk::OpenDataFolder() {
const std::string url = "file://" + path.generic_string();
#endif
if (!SDL_OpenURL(url.c_str())) {
DuskLog.warn("Failed to open data folder '{}': {}", path.string(), SDL_GetError());
DuskLog.warn(
"Failed to open data folder '{}': {}", io::fs_path_to_string(path), SDL_GetError());
return false;
}
return true;
@@ -509,16 +512,21 @@ static void EnsureInitialPipelineCache(const std::filesystem::path& configDir) {
return;
}
const char* basePath = SDL_GetBasePath();
if (basePath == nullptr) {
DuskLog.warn("Unable to resolve base path while seeding pipeline cache: {}", SDL_GetError());
return;
}
std::string sourcePathString;
SDL_IOStream* source = nullptr;
const std::filesystem::path initialPipelineCachePath =
std::filesystem::path(basePath) / "initial_pipeline_cache.db";
if (!std::filesystem::exists(initialPipelineCachePath)) {
DuskLog.info("No bundled initial pipeline cache found at '{}'", initialPipelineCachePath.string());
const char* basePath = SDL_GetBasePath();
if (basePath != nullptr) {
sourcePathString = dusk::io::fs_path_to_string(
std::filesystem::path(basePath) / "initial_pipeline_cache.db");
source = SDL_IOFromFile(sourcePathString.c_str(), "rb");
}
if (source == nullptr) {
sourcePathString = "initial_pipeline_cache.db";
source = SDL_IOFromFile(sourcePathString.c_str(), "rb");
}
if (source == nullptr) {
DuskLog.info("No bundled initial pipeline cache found");
return;
}
@@ -526,18 +534,68 @@ static void EnsureInitialPipelineCache(const std::filesystem::path& configDir) {
std::filesystem::create_directories(configDir, ec);
if (ec) {
DuskLog.warn("Failed to create config directory '{}' for pipeline cache: {}",
configDir.string(), ec.message());
dusk::io::fs_path_to_string(configDir), ec.message());
SDL_CloseIO(source);
return;
}
std::filesystem::copy_file(initialPipelineCachePath, pipelineCachePath, std::filesystem::copy_options::none, ec);
if (ec) {
DuskLog.warn("Failed to seed pipeline cache from '{}' to '{}': {}",
initialPipelineCachePath.string(), pipelineCachePath.string(), ec.message());
const auto pipelineCacheString = dusk::io::fs_path_to_string(pipelineCachePath);
SDL_IOStream* destination = SDL_IOFromFile(pipelineCacheString.c_str(), "wb");
if (destination == nullptr) {
DuskLog.warn("Failed to open '{}' for seeded pipeline cache: {}", pipelineCacheString,
SDL_GetError());
SDL_CloseIO(source);
return;
}
DuskLog.info("Seeded pipeline cache from '{}'", initialPipelineCachePath.string());
bool copied = true;
std::array<char, 64 * 1024> buffer{};
while (true) {
const size_t bytesRead = SDL_ReadIO(source, buffer.data(), buffer.size());
if (bytesRead > 0) {
size_t bytesWritten = 0;
while (bytesWritten < bytesRead) {
const size_t written = SDL_WriteIO(
destination, buffer.data() + bytesWritten, bytesRead - bytesWritten);
if (written == 0) {
DuskLog.warn("Failed to write seeded pipeline cache '{}': {}",
pipelineCacheString, SDL_GetError());
copied = false;
break;
}
bytesWritten += written;
}
}
if (!copied) {
break;
}
if (bytesRead < buffer.size()) {
if (SDL_GetIOStatus(source) == SDL_IO_STATUS_EOF) {
break;
}
DuskLog.warn(
"Failed to read bundled pipeline cache '{}': {}", sourcePathString, SDL_GetError());
copied = false;
break;
}
}
if (!SDL_CloseIO(destination)) {
DuskLog.warn("Failed to close seeded pipeline cache '{}': {}",
dusk::io::fs_path_to_string(pipelineCachePath), SDL_GetError());
copied = false;
}
SDL_CloseIO(source);
if (!copied) {
std::filesystem::remove(pipelineCachePath, ec);
return;
}
DuskLog.info("Seeded pipeline cache from '{}'", sourcePathString);
}
static constexpr PADDefaultMapping defaultPadMapping = {