mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-04 11:19:58 -04:00
Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 625f752fb9 | |||
| 04b5861f29 | |||
| 453e958068 | |||
| e7d2fbcc0b | |||
| 8f71c70d14 | |||
| df23edcb69 | |||
| daff157027 | |||
| 0c23bd4332 | |||
| 7562486449 | |||
| 5e08b810fc | |||
| c66cccf660 | |||
| 3b1118229b | |||
| 491da372a1 | |||
| a2f463d146 | |||
| 63b3ce4849 |
+80
-20
@@ -8,9 +8,9 @@ on:
|
|||||||
pull_request:
|
pull_request:
|
||||||
|
|
||||||
env:
|
env:
|
||||||
# SCCACHE_GHA_ENABLED: "true"
|
SCCACHE_GHA_ENABLED: "true"
|
||||||
RUSTC_WRAPPER: "sccache"
|
RUSTC_WRAPPER: "sccache"
|
||||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
# SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-linux:
|
build-linux:
|
||||||
@@ -22,7 +22,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- name: GCC x86_64
|
- name: GCC x86_64
|
||||||
runner: [self-hosted, Linux]
|
runner: ubuntu-latest
|
||||||
preset: gcc
|
preset: gcc
|
||||||
artifact_arch: x86_64
|
artifact_arch: x86_64
|
||||||
# - name: GCC aarch64
|
# - name: GCC aarch64
|
||||||
@@ -41,7 +41,6 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: 'false' # disabled for self-hosted
|
|
||||||
run: |
|
run: |
|
||||||
sudo apt-get update
|
sudo apt-get update
|
||||||
sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \
|
sudo apt-get -y install ninja-build clang lld openssl libcurl4-openssl-dev \
|
||||||
@@ -51,7 +50,6 @@ jobs:
|
|||||||
libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev
|
libxss-dev libfuse2 libusb-1.0-0-dev libdecor-0-dev libpipewire-0.3-dev libunwind-dev
|
||||||
|
|
||||||
- name: Setup sccache
|
- name: Setup sccache
|
||||||
if: 'false' # disabled for self-hosted
|
|
||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
|
|
||||||
- name: Print sccache stats
|
- name: Print sccache stats
|
||||||
@@ -67,7 +65,6 @@ jobs:
|
|||||||
run: ci/build-appimage.sh
|
run: ci/build-appimage.sh
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
|
name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
|
||||||
@@ -77,7 +74,7 @@ jobs:
|
|||||||
|
|
||||||
build-apple:
|
build-apple:
|
||||||
name: Build Apple (${{matrix.name}})
|
name: Build Apple (${{matrix.name}})
|
||||||
runs-on: [self-hosted, macOS]
|
runs-on: macos-latest
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
@@ -86,14 +83,14 @@ jobs:
|
|||||||
platform: macos
|
platform: macos
|
||||||
preset: x-macos-ci-arm64
|
preset: x-macos-ci-arm64
|
||||||
artifact_name: macos-appleclang-arm64
|
artifact_name: macos-appleclang-arm64
|
||||||
# - name: AppleClang macOS x86_64
|
- name: AppleClang macOS x86_64
|
||||||
# platform: macos
|
platform: macos
|
||||||
# preset: x-macos-ci-x86_64
|
preset: x-macos-ci-x86_64
|
||||||
# artifact_name: macos-appleclang-x86_64
|
artifact_name: macos-appleclang-x86_64
|
||||||
# - name: AppleClang iOS arm64
|
- name: AppleClang iOS arm64
|
||||||
# platform: ios
|
platform: ios
|
||||||
# preset: x-ios-ci
|
preset: x-ios-ci
|
||||||
# artifact_name: ios-appleclang-arm64
|
artifact_name: ios-appleclang-arm64
|
||||||
# - name: AppleClang tvOS arm64
|
# - name: AppleClang tvOS arm64
|
||||||
# platform: tvos
|
# platform: tvos
|
||||||
# preset: x-tvos-ci
|
# preset: x-tvos-ci
|
||||||
@@ -106,7 +103,6 @@ jobs:
|
|||||||
submodules: recursive
|
submodules: recursive
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: 'false'
|
|
||||||
run: brew install cmake ninja
|
run: brew install cmake ninja
|
||||||
|
|
||||||
- name: Install Rust iOS target
|
- name: Install Rust iOS target
|
||||||
@@ -137,7 +133,6 @@ jobs:
|
|||||||
run: cmake --build --preset ${{matrix.preset}}
|
run: cmake --build --preset ${{matrix.preset}}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
|
name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
|
||||||
@@ -145,6 +140,73 @@ jobs:
|
|||||||
build/install/Dusk.app
|
build/install/Dusk.app
|
||||||
build/install/debug.tar.*
|
build/install/debug.tar.*
|
||||||
|
|
||||||
|
build-android:
|
||||||
|
name: Build Android (${{matrix.name}})
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
include:
|
||||||
|
- name: Clang arm64-v8a
|
||||||
|
preset: x-android-ci-arm64
|
||||||
|
abi: arm64-v8a
|
||||||
|
artifact_arch: arm64
|
||||||
|
rust_target: aarch64-linux-android
|
||||||
|
|
||||||
|
env:
|
||||||
|
ANDROID_NDK_VERSION: "29.0.14206865"
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v6
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: recursive
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: |
|
||||||
|
sudo apt-get update
|
||||||
|
sudo apt-get -y install ninja-build
|
||||||
|
|
||||||
|
- name: Setup Java
|
||||||
|
uses: actions/setup-java@v5
|
||||||
|
with:
|
||||||
|
distribution: temurin
|
||||||
|
java-version: 17
|
||||||
|
|
||||||
|
- name: Setup Android SDK
|
||||||
|
uses: android-actions/setup-android@v3
|
||||||
|
|
||||||
|
- name: Install Android SDK packages
|
||||||
|
run: sdkmanager "platforms;android-36" "build-tools;36.1.0" "ndk;${ANDROID_NDK_VERSION}"
|
||||||
|
|
||||||
|
- name: Install Rust Android target
|
||||||
|
run: |
|
||||||
|
rustup toolchain install stable
|
||||||
|
rustup target add ${{matrix.rust_target}}
|
||||||
|
|
||||||
|
- name: Setup sccache
|
||||||
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
|
|
||||||
|
- name: Configure CMake
|
||||||
|
run: cmake --preset ${{matrix.preset}}
|
||||||
|
|
||||||
|
- name: Build native library
|
||||||
|
run: cmake --build --preset ${{matrix.preset}} --target dusk
|
||||||
|
|
||||||
|
- name: Stage stripped JNI library
|
||||||
|
run: ANDROID_STAGE_ABIS="${{matrix.abi}}" platforms/android/scripts/stage-jni-libs.sh
|
||||||
|
|
||||||
|
- name: Build APK
|
||||||
|
working-directory: platforms/android
|
||||||
|
run: ./gradlew :app:assembleRelease --rerun-tasks
|
||||||
|
|
||||||
|
- name: Upload artifacts
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: dusk-${{env.DUSK_VERSION}}-android-${{matrix.artifact_arch}}
|
||||||
|
path: platforms/android/app/build/outputs/apk/release/app-${{matrix.abi}}-release-unsigned.apk
|
||||||
|
|
||||||
build-windows:
|
build-windows:
|
||||||
name: Build Windows (${{matrix.name}})
|
name: Build Windows (${{matrix.name}})
|
||||||
runs-on: ${{matrix.runner}}
|
runs-on: ${{matrix.runner}}
|
||||||
@@ -154,7 +216,7 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
include:
|
include:
|
||||||
- name: MSVC x86_64
|
- name: MSVC x86_64
|
||||||
runner: [self-hosted, Windows]
|
runner: windows-latest
|
||||||
preset: msvc
|
preset: msvc
|
||||||
msvc_arch: amd64
|
msvc_arch: amd64
|
||||||
vcpkg_arch: x64
|
vcpkg_arch: x64
|
||||||
@@ -191,7 +253,6 @@ jobs:
|
|||||||
uses: mozilla-actions/sccache-action@v0.0.9
|
uses: mozilla-actions/sccache-action@v0.0.9
|
||||||
|
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
if: 'false' # disabled for self-hosted
|
|
||||||
run: |
|
run: |
|
||||||
choco install ninja
|
choco install ninja
|
||||||
vcpkg install freetype:${{matrix.vcpkg_arch}}-windows-static zstd:${{matrix.vcpkg_arch}}-windows-static
|
vcpkg install freetype:${{matrix.vcpkg_arch}}-windows-static zstd:${{matrix.vcpkg_arch}}-windows-static
|
||||||
@@ -203,7 +264,6 @@ jobs:
|
|||||||
run: cmake --build --preset x-windows-ci-${{matrix.preset}}
|
run: cmake --build --preset x-windows-ci-${{matrix.preset}}
|
||||||
|
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
|
||||||
uses: actions/upload-artifact@v7
|
uses: actions/upload-artifact@v7
|
||||||
with:
|
with:
|
||||||
name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
|
name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
|
||||||
|
|||||||
+11
-10
@@ -121,6 +121,16 @@ option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support"
|
|||||||
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
|
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
|
||||||
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
|
set(DUSK_SENTRY_ENVIRONMENT "development" CACHE STRING "Sentry environment")
|
||||||
|
|
||||||
|
# Edit & Continue
|
||||||
|
if (MSVC)
|
||||||
|
if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
|
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
|
||||||
|
endif ()
|
||||||
|
if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
|
||||||
|
add_link_options("/INCREMENTAL")
|
||||||
|
endif ()
|
||||||
|
endif ()
|
||||||
|
|
||||||
if (DUSK_MOVIE_SUPPORT)
|
if (DUSK_MOVIE_SUPPORT)
|
||||||
find_package(libjpeg-turbo 3.0 CONFIG QUIET)
|
find_package(libjpeg-turbo 3.0 CONFIG QUIET)
|
||||||
if (libjpeg-turbo_FOUND)
|
if (libjpeg-turbo_FOUND)
|
||||||
@@ -150,6 +160,7 @@ if (DUSK_MOVIE_SUPPORT)
|
|||||||
CMAKE_C_COMPILER_LAUNCHER
|
CMAKE_C_COMPILER_LAUNCHER
|
||||||
CMAKE_MAKE_PROGRAM
|
CMAKE_MAKE_PROGRAM
|
||||||
CMAKE_MSVC_RUNTIME_LIBRARY
|
CMAKE_MSVC_RUNTIME_LIBRARY
|
||||||
|
CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
||||||
CMAKE_OSX_ARCHITECTURES
|
CMAKE_OSX_ARCHITECTURES
|
||||||
DEPLOYMENT_TARGET
|
DEPLOYMENT_TARGET
|
||||||
ENABLE_ARC
|
ENABLE_ARC
|
||||||
@@ -366,16 +377,6 @@ if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
|
|||||||
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
|
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
|
||||||
endif ()
|
endif ()
|
||||||
|
|
||||||
# Edit & Continue
|
|
||||||
if (MSVC)
|
|
||||||
if ("${CMAKE_MSVC_DEBUG_INFORMATION_FORMAT}" STREQUAL "" AND CMAKE_BUILD_TYPE STREQUAL "Debug")
|
|
||||||
set(CMAKE_MSVC_DEBUG_INFORMATION_FORMAT "EditAndContinue")
|
|
||||||
endif ()
|
|
||||||
if (CMAKE_MSVC_DEBUG_INFORMATION_FORMAT STREQUAL "EditAndContinue")
|
|
||||||
add_link_options("/INCREMENTAL")
|
|
||||||
endif ()
|
|
||||||
endif ()
|
|
||||||
|
|
||||||
if(ANDROID)
|
if(ANDROID)
|
||||||
list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1)
|
list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1)
|
||||||
endif ()
|
endif ()
|
||||||
|
|||||||
@@ -352,6 +352,25 @@
|
|||||||
"ANDROID_ABI": "x86_64"
|
"ANDROID_ABI": "x86_64"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "x-android-ci",
|
||||||
|
"hidden": true,
|
||||||
|
"inherits": [
|
||||||
|
"android-base",
|
||||||
|
"ci"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "x-android-ci-arm64",
|
||||||
|
"binaryDir": "${sourceDir}/build/android-arm64",
|
||||||
|
"inherits": [
|
||||||
|
"x-android-ci"
|
||||||
|
],
|
||||||
|
"cacheVariables": {
|
||||||
|
"ANDROID_ABI": "arm64-v8a",
|
||||||
|
"Rust_CARGO_TARGET": "aarch64-linux-android"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "x-linux-ci",
|
"name": "x-linux-ci",
|
||||||
"hidden": true,
|
"hidden": true,
|
||||||
@@ -412,6 +431,7 @@
|
|||||||
"x-macos-ci"
|
"x-macos-ci"
|
||||||
],
|
],
|
||||||
"cacheVariables": {
|
"cacheVariables": {
|
||||||
|
"AURORA_DAWN_PROVIDER": "vendor",
|
||||||
"CMAKE_OSX_ARCHITECTURES": "x86_64",
|
"CMAKE_OSX_ARCHITECTURES": "x86_64",
|
||||||
"Rust_CARGO_TARGET": "x86_64-apple-darwin"
|
"Rust_CARGO_TARGET": "x86_64-apple-darwin"
|
||||||
}
|
}
|
||||||
@@ -555,6 +575,15 @@
|
|||||||
"dusk"
|
"dusk"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "x-android-ci-arm64",
|
||||||
|
"configurePreset": "x-android-ci-arm64",
|
||||||
|
"description": "(Internal) Android CI arm64-v8a",
|
||||||
|
"displayName": "(Internal) Android CI arm64-v8a",
|
||||||
|
"targets": [
|
||||||
|
"dusk"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "windows-msvc-debug",
|
"name": "windows-msvc-debug",
|
||||||
"configurePreset": "windows-msvc-debug",
|
"configurePreset": "windows-msvc-debug",
|
||||||
|
|||||||
Vendored
+1
-1
Submodule extern/aurora updated: ac165cf1a6...398054316e
+28
-2
@@ -2,11 +2,13 @@
|
|||||||
#define DUSK_IO_HPP
|
#define DUSK_IO_HPP
|
||||||
|
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <filesystem>
|
||||||
|
|
||||||
// I can't believe it's 2026 and neither SDL (no error codes) nor
|
// 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.
|
// 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.
|
// Here you go, this one's inspired by C#. I only wrote the functions I need.
|
||||||
|
|
||||||
|
|
||||||
namespace dusk::io {
|
namespace dusk::io {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -15,7 +17,7 @@ namespace dusk::io {
|
|||||||
* Methods on this class throw appropriate C++ exceptions when an error occurs.
|
* Methods on this class throw appropriate C++ exceptions when an error occurs.
|
||||||
*/
|
*/
|
||||||
class FileStream {
|
class FileStream {
|
||||||
void* file;
|
FILE* file;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FileStream() noexcept;
|
FileStream() noexcept;
|
||||||
@@ -23,7 +25,7 @@ public:
|
|||||||
/**
|
/**
|
||||||
* \brief Take ownership of a FILE* handle.
|
* \brief Take ownership of a FILE* handle.
|
||||||
*/
|
*/
|
||||||
explicit FileStream(void* file);
|
explicit FileStream(FILE* file);
|
||||||
FileStream(const FileStream& other) = delete;
|
FileStream(const FileStream& other) = delete;
|
||||||
FileStream(FileStream&& other) noexcept;
|
FileStream(FileStream&& other) noexcept;
|
||||||
|
|
||||||
@@ -34,6 +36,11 @@ public:
|
|||||||
*/
|
*/
|
||||||
static FileStream OpenRead(const char* utf8Path);
|
static FileStream OpenRead(const char* utf8Path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Open a file for reading at the given path.
|
||||||
|
*/
|
||||||
|
static FileStream OpenRead(const std::filesystem::path& path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Create a file for writing.
|
* \brief Create a file for writing.
|
||||||
*
|
*
|
||||||
@@ -41,16 +48,33 @@ public:
|
|||||||
*/
|
*/
|
||||||
static FileStream Create(const char* utf8Path);
|
static FileStream Create(const char* utf8Path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Create a file for writing.
|
||||||
|
*
|
||||||
|
* If there is an existing file, its contents are demolished.
|
||||||
|
*/
|
||||||
|
static FileStream Create(const std::filesystem::path& path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Read the byte contents of a file directly into a vector.
|
* \brief Read the byte contents of a file directly into a vector.
|
||||||
*/
|
*/
|
||||||
static std::vector<u8> ReadAllBytes(const char* utf8Path);
|
static std::vector<u8> ReadAllBytes(const char* utf8Path);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Read the byte contents of a file directly into a vector.
|
||||||
|
*/
|
||||||
|
static std::vector<u8> ReadAllBytes(const std::filesystem::path& path);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Read the byte contents of a file directly into a vector.
|
* \brief Read the byte contents of a file directly into a vector.
|
||||||
*/
|
*/
|
||||||
static void WriteAllText(const char* utf8Path, std::string_view text);
|
static void WriteAllText(const char* utf8Path, std::string_view text);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* \brief Read the byte contents of a file directly into a vector.
|
||||||
|
*/
|
||||||
|
static void WriteAllText(const std::filesystem::path& path, std::string_view text);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* \brief Read the remaining contents of the file directly into a vector.
|
* \brief Read the remaining contents of the file directly into a vector.
|
||||||
*/
|
*/
|
||||||
@@ -67,6 +91,8 @@ public:
|
|||||||
* Write data to the file.
|
* Write data to the file.
|
||||||
*/
|
*/
|
||||||
void Write(const char* data, size_t dataLen);
|
void Write(const char* data, size_t dataLen);
|
||||||
|
|
||||||
|
FILE* ToInner();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,14 @@
|
|||||||
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#if defined(_WIN32) || \
|
||||||
|
(defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_MACCATALYST) || \
|
||||||
|
(defined(__linux__) && !defined(__ANDROID__))
|
||||||
|
#define DUSK_CAN_OPEN_DATA_FOLDER 1
|
||||||
|
#else
|
||||||
|
#define DUSK_CAN_OPEN_DATA_FOLDER 0
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace dusk {
|
namespace dusk {
|
||||||
extern bool IsRunning;
|
extern bool IsRunning;
|
||||||
extern bool IsShuttingDown;
|
extern bool IsShuttingDown;
|
||||||
@@ -22,6 +30,7 @@ namespace dusk {
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
void RequestRestart() noexcept;
|
void RequestRestart() noexcept;
|
||||||
|
bool OpenDataFolder();
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif // DUSK_MAIN_H
|
#endif // DUSK_MAIN_H
|
||||||
|
|||||||
@@ -24,6 +24,17 @@
|
|||||||
<meta-data android:name="android.game_mode_config"
|
<meta-data android:name="android.game_mode_config"
|
||||||
android:resource="@xml/game_mode_config" />
|
android:resource="@xml/game_mode_config" />
|
||||||
|
|
||||||
|
<provider
|
||||||
|
android:name="dev.twilitrealm.dusk.DuskDocumentsProvider"
|
||||||
|
android:authorities="dev.twilitrealm.dusk.documents"
|
||||||
|
android:exported="true"
|
||||||
|
android:grantUriPermissions="true"
|
||||||
|
android:permission="android.permission.MANAGE_DOCUMENTS">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.content.action.DOCUMENTS_PROVIDER" />
|
||||||
|
</intent-filter>
|
||||||
|
</provider>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name="dev.twilitrealm.dusk.DuskActivity"
|
android:name="dev.twilitrealm.dusk.DuskActivity"
|
||||||
android:alwaysRetainTaskState="true"
|
android:alwaysRetainTaskState="true"
|
||||||
|
|||||||
@@ -0,0 +1,413 @@
|
|||||||
|
package dev.twilitrealm.dusk;
|
||||||
|
|
||||||
|
import android.content.ContentResolver;
|
||||||
|
import android.content.res.AssetFileDescriptor;
|
||||||
|
import android.database.Cursor;
|
||||||
|
import android.database.MatrixCursor;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.CancellationSignal;
|
||||||
|
import android.os.ParcelFileDescriptor;
|
||||||
|
import android.provider.DocumentsContract;
|
||||||
|
import android.provider.DocumentsContract.Document;
|
||||||
|
import android.provider.DocumentsContract.Root;
|
||||||
|
import android.provider.DocumentsProvider;
|
||||||
|
import android.webkit.MimeTypeMap;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
public class DuskDocumentsProvider extends DocumentsProvider {
|
||||||
|
public static final String AUTHORITY = "dev.twilitrealm.dusk.documents";
|
||||||
|
|
||||||
|
private static final String ROOT_ID = "dusk";
|
||||||
|
private static final String ROOT_DOCUMENT_ID = "root";
|
||||||
|
private static final String DIRECTORY_MIME_TYPE = Document.MIME_TYPE_DIR;
|
||||||
|
|
||||||
|
private static final String[] DEFAULT_ROOT_PROJECTION = new String[] {
|
||||||
|
Root.COLUMN_ROOT_ID,
|
||||||
|
Root.COLUMN_FLAGS,
|
||||||
|
Root.COLUMN_TITLE,
|
||||||
|
Root.COLUMN_DOCUMENT_ID,
|
||||||
|
Root.COLUMN_ICON,
|
||||||
|
Root.COLUMN_AVAILABLE_BYTES,
|
||||||
|
Root.COLUMN_SUMMARY
|
||||||
|
};
|
||||||
|
|
||||||
|
private static final String[] DEFAULT_DOCUMENT_PROJECTION = new String[] {
|
||||||
|
Document.COLUMN_DOCUMENT_ID,
|
||||||
|
Document.COLUMN_DISPLAY_NAME,
|
||||||
|
Document.COLUMN_FLAGS,
|
||||||
|
Document.COLUMN_MIME_TYPE,
|
||||||
|
Document.COLUMN_LAST_MODIFIED,
|
||||||
|
Document.COLUMN_SIZE
|
||||||
|
};
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean onCreate() {
|
||||||
|
ensureUserDirectories();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryRoots(String[] projection) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(resolveRootProjection(projection));
|
||||||
|
final File root = getRootDirectory();
|
||||||
|
final MatrixCursor.RowBuilder row = result.newRow();
|
||||||
|
|
||||||
|
row.add(Root.COLUMN_ROOT_ID, ROOT_ID);
|
||||||
|
row.add(Root.COLUMN_FLAGS,
|
||||||
|
Root.FLAG_LOCAL_ONLY |
|
||||||
|
Root.FLAG_SUPPORTS_CREATE |
|
||||||
|
Root.FLAG_SUPPORTS_IS_CHILD);
|
||||||
|
row.add(Root.COLUMN_TITLE, getContext().getString(R.string.app_name));
|
||||||
|
row.add(Root.COLUMN_DOCUMENT_ID, ROOT_DOCUMENT_ID);
|
||||||
|
row.add(Root.COLUMN_ICON, R.mipmap.icon);
|
||||||
|
row.add(Root.COLUMN_AVAILABLE_BYTES, root.getFreeSpace());
|
||||||
|
row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.documents_provider_summary));
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryDocument(String documentId, String[] projection) throws FileNotFoundException {
|
||||||
|
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||||
|
includeDocument(result, documentId, getFileForDocumentId(documentId));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, String sortOrder)
|
||||||
|
throws FileNotFoundException
|
||||||
|
{
|
||||||
|
return queryChildDocumentsInternal(parentDocumentId, projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Cursor queryChildDocuments(String parentDocumentId, String[] projection, Bundle queryArgs)
|
||||||
|
throws FileNotFoundException
|
||||||
|
{
|
||||||
|
return queryChildDocumentsInternal(parentDocumentId, projection);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Cursor queryChildDocumentsInternal(String parentDocumentId, String[] projection)
|
||||||
|
throws FileNotFoundException
|
||||||
|
{
|
||||||
|
final MatrixCursor result = new MatrixCursor(resolveDocumentProjection(projection));
|
||||||
|
final File parent = getFileForDocumentId(parentDocumentId);
|
||||||
|
final File[] files = parent.listFiles();
|
||||||
|
result.setNotificationUri(getContext().getContentResolver(), getChildDocumentsUri(parentDocumentId));
|
||||||
|
|
||||||
|
if (files == null) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (File file : files) {
|
||||||
|
includeDocument(result, getDocumentIdForFile(file), file);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isChildDocument(String parentDocumentId, String documentId) {
|
||||||
|
try {
|
||||||
|
final File parent = getFileForDocumentId(parentDocumentId);
|
||||||
|
final File child = getFileForDocumentId(documentId);
|
||||||
|
return isInside(parent, child);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String createDocument(String parentDocumentId, String mimeType, String displayName)
|
||||||
|
throws FileNotFoundException
|
||||||
|
{
|
||||||
|
final File parent = getFileForDocumentId(parentDocumentId);
|
||||||
|
if (!parent.isDirectory()) {
|
||||||
|
throw new FileNotFoundException("Parent is not a directory: " + parentDocumentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String safeDisplayName = sanitizeDisplayName(displayName);
|
||||||
|
final File file = buildUniqueFile(parent, safeDisplayName);
|
||||||
|
final boolean created;
|
||||||
|
if (DIRECTORY_MIME_TYPE.equals(mimeType)) {
|
||||||
|
created = file.mkdir();
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
created = file.createNewFile();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw asFileNotFound("Unable to create document", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!created) {
|
||||||
|
throw new FileNotFoundException("Unable to create document: " + displayName);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyChildrenChanged(parentDocumentId);
|
||||||
|
return getDocumentIdForFile(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String renameDocument(String documentId, String displayName) throws FileNotFoundException {
|
||||||
|
final File file = getFileForDocumentId(documentId);
|
||||||
|
if (ROOT_DOCUMENT_ID.equals(documentId)) {
|
||||||
|
throw new FileNotFoundException("Cannot rename root document");
|
||||||
|
}
|
||||||
|
|
||||||
|
final File target = buildUniqueFile(file.getParentFile(), sanitizeDisplayName(displayName));
|
||||||
|
final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
|
||||||
|
if (!file.renameTo(target)) {
|
||||||
|
throw new FileNotFoundException("Unable to rename document: " + documentId);
|
||||||
|
}
|
||||||
|
notifyDocumentChanged(documentId);
|
||||||
|
notifyDocumentChanged(getDocumentIdForFile(target));
|
||||||
|
notifyChildrenChanged(parentDocumentId);
|
||||||
|
return getDocumentIdForFile(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void deleteDocument(String documentId) throws FileNotFoundException {
|
||||||
|
if (ROOT_DOCUMENT_ID.equals(documentId)) {
|
||||||
|
throw new FileNotFoundException("Cannot delete root document");
|
||||||
|
}
|
||||||
|
|
||||||
|
final File file = getFileForDocumentId(documentId);
|
||||||
|
final String parentDocumentId = getDocumentIdForFile(file.getParentFile());
|
||||||
|
deleteRecursively(file);
|
||||||
|
notifyDocumentChanged(documentId);
|
||||||
|
notifyChildrenChanged(parentDocumentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ParcelFileDescriptor openDocument(String documentId, String mode, CancellationSignal signal)
|
||||||
|
throws FileNotFoundException
|
||||||
|
{
|
||||||
|
return ParcelFileDescriptor.open(getFileForDocumentId(documentId), modeToParcelMode(mode));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public AssetFileDescriptor openDocumentThumbnail(String documentId, android.graphics.Point sizeHint,
|
||||||
|
CancellationSignal signal) throws FileNotFoundException
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Thumbnails are not supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void includeDocument(MatrixCursor result, String documentId, File file) throws FileNotFoundException {
|
||||||
|
final MatrixCursor.RowBuilder row = result.newRow();
|
||||||
|
final boolean isDirectory = file.isDirectory();
|
||||||
|
final String displayName = ROOT_DOCUMENT_ID.equals(documentId)
|
||||||
|
? getContext().getString(R.string.documents_provider_root_name)
|
||||||
|
: file.getName();
|
||||||
|
|
||||||
|
int flags = Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME;
|
||||||
|
if (isDirectory) {
|
||||||
|
flags |= Document.FLAG_DIR_SUPPORTS_CREATE;
|
||||||
|
} else if (file.canWrite()) {
|
||||||
|
flags |= Document.FLAG_SUPPORTS_WRITE;
|
||||||
|
}
|
||||||
|
if (ROOT_DOCUMENT_ID.equals(documentId)) {
|
||||||
|
flags &= ~(Document.FLAG_SUPPORTS_DELETE | Document.FLAG_SUPPORTS_RENAME);
|
||||||
|
}
|
||||||
|
|
||||||
|
row.add(Document.COLUMN_DOCUMENT_ID, documentId);
|
||||||
|
row.add(Document.COLUMN_DISPLAY_NAME, displayName);
|
||||||
|
row.add(Document.COLUMN_FLAGS, flags);
|
||||||
|
row.add(Document.COLUMN_MIME_TYPE, isDirectory ? DIRECTORY_MIME_TYPE : getMimeType(file));
|
||||||
|
row.add(Document.COLUMN_LAST_MODIFIED, file.lastModified());
|
||||||
|
row.add(Document.COLUMN_SIZE, isDirectory ? null : file.length());
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getRootDirectory() throws FileNotFoundException {
|
||||||
|
final File root = getContext().getFilesDir();
|
||||||
|
if (root == null) {
|
||||||
|
throw new FileNotFoundException("Dusk files directory is unavailable");
|
||||||
|
}
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
private File getFileForDocumentId(String documentId) throws FileNotFoundException {
|
||||||
|
final File root = getRootDirectory();
|
||||||
|
if (ROOT_DOCUMENT_ID.equals(documentId)) {
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
if (!documentId.startsWith(ROOT_DOCUMENT_ID + "/")) {
|
||||||
|
throw new FileNotFoundException("Invalid document id: " + documentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String relativePath = documentId.substring(ROOT_DOCUMENT_ID.length() + 1);
|
||||||
|
final File file = new File(root, relativePath);
|
||||||
|
if (!isInside(root, file)) {
|
||||||
|
throw new FileNotFoundException("Document escapes Dusk files directory: " + documentId);
|
||||||
|
}
|
||||||
|
if (!file.exists()) {
|
||||||
|
throw new FileNotFoundException("Document does not exist: " + documentId);
|
||||||
|
}
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String getDocumentIdForFile(File file) throws FileNotFoundException {
|
||||||
|
final File root = getRootDirectory();
|
||||||
|
if (sameFile(root, file)) {
|
||||||
|
return ROOT_DOCUMENT_ID;
|
||||||
|
}
|
||||||
|
if (!isInside(root, file)) {
|
||||||
|
throw new FileNotFoundException("File escapes Dusk files directory: " + file);
|
||||||
|
}
|
||||||
|
|
||||||
|
final String rootPath = canonicalPath(root);
|
||||||
|
final String filePath = canonicalPath(file);
|
||||||
|
return ROOT_DOCUMENT_ID + "/" + filePath.substring(rootPath.length() + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureUserDirectories() {
|
||||||
|
final File root = getContext().getFilesDir();
|
||||||
|
if (root == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
new File(root, "texture_replacements").mkdirs();
|
||||||
|
new File(root, "USA/Card A").mkdirs();
|
||||||
|
new File(root, "EUR/Card A").mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] resolveRootProjection(String[] projection) {
|
||||||
|
return projection != null ? projection : DEFAULT_ROOT_PROJECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String[] resolveDocumentProjection(String[] projection) {
|
||||||
|
return projection != null ? projection : DEFAULT_DOCUMENT_PROJECTION;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String sanitizeDisplayName(String displayName) throws FileNotFoundException {
|
||||||
|
if (displayName == null) {
|
||||||
|
throw new FileNotFoundException("Document name is empty");
|
||||||
|
}
|
||||||
|
|
||||||
|
final String sanitized = displayName.trim();
|
||||||
|
if (sanitized.isEmpty() || ".".equals(sanitized) || "..".equals(sanitized) ||
|
||||||
|
sanitized.contains("/") || sanitized.contains("\\"))
|
||||||
|
{
|
||||||
|
throw new FileNotFoundException("Invalid document name: " + displayName);
|
||||||
|
}
|
||||||
|
return sanitized;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static File buildUniqueFile(File parent, String displayName) {
|
||||||
|
File file = new File(parent, displayName);
|
||||||
|
if (!file.exists()) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
|
||||||
|
final int dot = displayName.lastIndexOf('.');
|
||||||
|
final String baseName = dot > 0 ? displayName.substring(0, dot) : displayName;
|
||||||
|
final String extension = dot > 0 ? displayName.substring(dot) : "";
|
||||||
|
for (int i = 1; i < 100; ++i) {
|
||||||
|
file = new File(parent, baseName + " (" + i + ")" + extension);
|
||||||
|
if (!file.exists()) {
|
||||||
|
return file;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new File(parent, baseName + " (" + System.currentTimeMillis() + ")" + extension);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int modeToParcelMode(String mode) {
|
||||||
|
if ("r".equals(mode)) {
|
||||||
|
return ParcelFileDescriptor.MODE_READ_ONLY;
|
||||||
|
}
|
||||||
|
if ("w".equals(mode) || "wt".equals(mode)) {
|
||||||
|
return ParcelFileDescriptor.MODE_WRITE_ONLY |
|
||||||
|
ParcelFileDescriptor.MODE_CREATE |
|
||||||
|
ParcelFileDescriptor.MODE_TRUNCATE;
|
||||||
|
}
|
||||||
|
if ("wa".equals(mode)) {
|
||||||
|
return ParcelFileDescriptor.MODE_WRITE_ONLY |
|
||||||
|
ParcelFileDescriptor.MODE_CREATE |
|
||||||
|
ParcelFileDescriptor.MODE_APPEND;
|
||||||
|
}
|
||||||
|
if ("rw".equals(mode)) {
|
||||||
|
return ParcelFileDescriptor.MODE_READ_WRITE |
|
||||||
|
ParcelFileDescriptor.MODE_CREATE;
|
||||||
|
}
|
||||||
|
if ("rwt".equals(mode)) {
|
||||||
|
return ParcelFileDescriptor.MODE_READ_WRITE |
|
||||||
|
ParcelFileDescriptor.MODE_CREATE |
|
||||||
|
ParcelFileDescriptor.MODE_TRUNCATE;
|
||||||
|
}
|
||||||
|
return ParcelFileDescriptor.MODE_READ_ONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String getMimeType(File file) {
|
||||||
|
final int dot = file.getName().lastIndexOf('.');
|
||||||
|
if (dot >= 0) {
|
||||||
|
final String extension = file.getName().substring(dot + 1).toLowerCase();
|
||||||
|
final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
|
||||||
|
if (mimeType != null) {
|
||||||
|
return mimeType;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return "application/octet-stream";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Uri getChildDocumentsUri(String parentDocumentId) {
|
||||||
|
return DocumentsContract.buildChildDocumentsUri(AUTHORITY, parentDocumentId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyChildrenChanged(String parentDocumentId) {
|
||||||
|
final ContentResolver resolver = getContext().getContentResolver();
|
||||||
|
resolver.notifyChange(getChildDocumentsUri(parentDocumentId), null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void notifyDocumentChanged(String documentId) {
|
||||||
|
final ContentResolver resolver = getContext().getContentResolver();
|
||||||
|
resolver.notifyChange(DocumentsContract.buildDocumentUri(AUTHORITY, documentId), null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void deleteRecursively(File file) throws FileNotFoundException {
|
||||||
|
if (file.isDirectory()) {
|
||||||
|
final File[] children = file.listFiles();
|
||||||
|
if (children != null) {
|
||||||
|
for (File child : children) {
|
||||||
|
deleteRecursively(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!file.delete()) {
|
||||||
|
throw new FileNotFoundException("Unable to delete document: " + file);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isInside(File parent, File child) {
|
||||||
|
try {
|
||||||
|
final String parentPath = canonicalPath(parent);
|
||||||
|
final String childPath = canonicalPath(child);
|
||||||
|
return childPath.equals(parentPath) || childPath.startsWith(parentPath + File.separator);
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean sameFile(File a, File b) {
|
||||||
|
try {
|
||||||
|
return canonicalPath(a).equals(canonicalPath(b));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String canonicalPath(File file) throws FileNotFoundException {
|
||||||
|
try {
|
||||||
|
return file.getCanonicalPath();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw asFileNotFound("Unable to resolve path", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static FileNotFoundException asFileNotFound(String message, IOException cause) {
|
||||||
|
final FileNotFoundException exception = new FileNotFoundException(message + ": " + cause.getMessage());
|
||||||
|
exception.initCause(cause);
|
||||||
|
return exception;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Dusk</string>
|
<string name="app_name">Dusk</string>
|
||||||
|
<string name="documents_provider_root_name">Dusk Data</string>
|
||||||
|
<string name="documents_provider_summary">Saves, texture packs, settings, and logs</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|||||||
@@ -79,5 +79,9 @@
|
|||||||
<true/>
|
<true/>
|
||||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>UIFileSharingEnabled</key>
|
||||||
|
<true/>
|
||||||
|
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||||
|
<true/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@@ -1030,7 +1030,7 @@ void AchievementSystem::load() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
auto data = io::FileStream::ReadAllBytes(filePath.string().c_str());
|
auto data = io::FileStream::ReadAllBytes(filePath);
|
||||||
auto j = json::parse(data);
|
auto j = json::parse(data);
|
||||||
if (!j.is_object()) {
|
if (!j.is_object()) {
|
||||||
return;
|
return;
|
||||||
@@ -1067,7 +1067,7 @@ void AchievementSystem::save() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
io::FileStream::WriteAllText(
|
io::FileStream::WriteAllText(
|
||||||
(dusk::ConfigPath / ACHIEVEMENTS_FILENAME).string().c_str(),
|
dusk::ConfigPath / ACHIEVEMENTS_FILENAME,
|
||||||
j.dump(2)
|
j.dump(2)
|
||||||
);
|
);
|
||||||
} catch (const std::exception&) {}
|
} catch (const std::exception&) {}
|
||||||
|
|||||||
+7
-5
@@ -23,8 +23,8 @@ aurora::Module DuskConfigLog("dusk::config");
|
|||||||
static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVars;
|
static absl::flat_hash_map<std::string_view, ConfigVarBase*> RegisteredConfigVars;
|
||||||
static bool RegistrationDone = false;
|
static bool RegistrationDone = false;
|
||||||
|
|
||||||
static std::string GetConfigJsonPath() {
|
static std::u8string GetConfigJsonPath() {
|
||||||
return (dusk::ConfigPath / ConfigFileName).string();
|
return (dusk::ConfigPath / ConfigFileName).u8string();
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {
|
ConfigVarBase::ConfigVarBase(const char* name, const ConfigImplBase* impl) : name(name), registered(false), layer(ConfigVarLayer::Default), impl(impl) {
|
||||||
@@ -189,7 +189,7 @@ void dusk::config::LoadFromUserPreferences() {
|
|||||||
if (configJsonPath.empty()) {
|
if (configJsonPath.empty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
LoadFromFileName(configJsonPath.c_str());
|
LoadFromFileName(reinterpret_cast<const char*>(configJsonPath.c_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
static void LoadFromPath(const char* path) {
|
static void LoadFromPath(const char* path) {
|
||||||
@@ -241,7 +241,9 @@ void dusk::config::Save() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
DuskConfigLog.info("Saving config to '{}'", configJsonPath);
|
DuskConfigLog.info(
|
||||||
|
"Saving config to '{}'",
|
||||||
|
reinterpret_cast<const char*>(configJsonPath.c_str()));
|
||||||
|
|
||||||
json j;
|
json j;
|
||||||
|
|
||||||
@@ -251,7 +253,7 @@ void dusk::config::Save() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
io::FileStream::WriteAllText(configJsonPath.c_str(), j.dump(4));
|
io::FileStream::WriteAllText(reinterpret_cast<const char*>(configJsonPath.c_str()), j.dump(4));
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
|
ConfigVarBase* dusk::config::GetConfigVar(std::string_view name) {
|
||||||
|
|||||||
+4
-4
@@ -140,8 +140,8 @@ void read(float dt) {
|
|||||||
float my_rel = 0.0f;
|
float my_rel = 0.0f;
|
||||||
SDL_GetRelativeMouseState(&mx_rel, &my_rel);
|
SDL_GetRelativeMouseState(&mx_rel, &my_rel);
|
||||||
// Convert pixels to radians
|
// Convert pixels to radians
|
||||||
s_pitch_rad = my_rel * kMousePixelToRad * getSettings().game.gyroSensitivityX;
|
s_pitch_rad = my_rel * kMousePixelToRad * getSettings().game.gyroSensitivityY;
|
||||||
s_yaw_rad = -mx_rel * kMousePixelToRad * getSettings().game.gyroSensitivityY;
|
s_yaw_rad = -mx_rel * kMousePixelToRad * getSettings().game.gyroSensitivityX;
|
||||||
s_roll_rad = 0.0f;
|
s_roll_rad = 0.0f;
|
||||||
|
|
||||||
s_pitch_rad = getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad;
|
s_pitch_rad = getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad;
|
||||||
@@ -184,7 +184,7 @@ void read(float dt) {
|
|||||||
const float yaw_rate = apply_deadband(s_smooth_gy, deadband);
|
const float yaw_rate = apply_deadband(s_smooth_gy, deadband);
|
||||||
const float roll_rate = apply_deadband(s_smooth_gz, deadband);
|
const float roll_rate = apply_deadband(s_smooth_gz, deadband);
|
||||||
|
|
||||||
s_pitch_rad = -pitch_rate * dt * getSettings().game.gyroSensitivityX;
|
s_pitch_rad = -pitch_rate * dt * getSettings().game.gyroSensitivityY;
|
||||||
s_roll_rad = roll_rate * dt * getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X
|
s_roll_rad = roll_rate * dt * getSettings().game.gyroSensitivityX; // GYRO NOTE: Exposing Z sensitivity seems unusual, so I'm just using X
|
||||||
|
|
||||||
float horizontal_rate = yaw_rate;
|
float horizontal_rate = yaw_rate;
|
||||||
@@ -223,7 +223,7 @@ void read(float dt) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s_yaw_rad = horizontal_rate * dt * getSettings().game.gyroSensitivityY;
|
s_yaw_rad = horizontal_rate * dt * getSettings().game.gyroSensitivityX;
|
||||||
|
|
||||||
s_pitch_rad = getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad;
|
s_pitch_rad = getSettings().game.gyroInvertPitch ? -s_pitch_rad : s_pitch_rad;
|
||||||
s_yaw_rad = getSettings().game.gyroInvertYaw ? -s_yaw_rad : s_yaw_rad;
|
s_yaw_rad = getSettings().game.gyroInvertYaw ? -s_yaw_rad : s_yaw_rad;
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
#define DUSK_IMGUI_HPP
|
#define DUSK_IMGUI_HPP
|
||||||
|
|
||||||
#include <deque>
|
#include <deque>
|
||||||
#include <filesystem>
|
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
#include <SDL3/SDL_misc.h>
|
|
||||||
#include <aurora/aurora.h>
|
#include <aurora/aurora.h>
|
||||||
|
|
||||||
#include "ImGuiMenuGame.hpp"
|
#include "ImGuiMenuGame.hpp"
|
||||||
@@ -73,24 +71,4 @@ bool ImGuiButtonCenter(std::string_view text);
|
|||||||
float ImGuiScale();
|
float ImGuiScale();
|
||||||
} // namespace dusk
|
} // namespace dusk
|
||||||
|
|
||||||
#if defined(_WIN32) || \
|
|
||||||
(defined(__APPLE__) && !TARGET_OS_IOS && !TARGET_OS_TV && !TARGET_OS_MACCATALYST) || \
|
|
||||||
(defined(__linux__) && !defined(__ANDROID__))
|
|
||||||
#define DUSK_CAN_OPEN_DATA_FOLDER 1
|
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
|
||||||
|
|
||||||
static void OpenDataFolder() {
|
|
||||||
const std::string path = fs::absolute(dusk::ConfigPath).generic_string();
|
|
||||||
#if defined(_WIN32)
|
|
||||||
const std::string url = std::string("file:///") + path;
|
|
||||||
#else
|
|
||||||
const std::string url = std::string("file://") + path;
|
|
||||||
#endif
|
|
||||||
(void)SDL_OpenURL(url.c_str());
|
|
||||||
}
|
|
||||||
#else
|
|
||||||
#define DUSK_CAN_OPEN_DATA_FOLDER 0
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#endif // DUSK_IMGUI_HPP
|
#endif // DUSK_IMGUI_HPP
|
||||||
|
|||||||
@@ -56,12 +56,12 @@ namespace dusk {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// L+R+A+Start to reset timer
|
// L+R+A+Start to reset timer
|
||||||
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigStart(PAD_1)) {
|
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1)) {
|
||||||
m_speedrunInfo.reset();
|
m_speedrunInfo.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
// L+R+A+Z to manually stop timer
|
// L+R+A+Z to manually stop timer
|
||||||
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigZ(PAD_1)) {
|
if (mDoCPd_c::getHoldL(PAD_1) && mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getHoldA(PAD_1) && mDoCPd_c::getTrigY(PAD_1)) {
|
||||||
if (m_speedrunInfo.m_isRunStarted) {
|
if (m_speedrunInfo.m_isRunStarted) {
|
||||||
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
||||||
m_speedrunInfo.m_isRunStarted = false;
|
m_speedrunInfo.m_isRunStarted = false;
|
||||||
|
|||||||
+27
-4
@@ -1,7 +1,8 @@
|
|||||||
#include "dusk/io.hpp"
|
|
||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
|
|
||||||
|
#include "dusk/io.hpp"
|
||||||
|
|
||||||
using namespace dusk::io;
|
using namespace dusk::io;
|
||||||
|
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
@@ -58,7 +59,7 @@ static FILE* OpenCore(const char* path, const MODE_TYPE* mode) {
|
|||||||
FileStream::FileStream() noexcept : file(nullptr) {
|
FileStream::FileStream() noexcept : file(nullptr) {
|
||||||
}
|
}
|
||||||
|
|
||||||
FileStream::FileStream(void* file) : file(file) {
|
FileStream::FileStream(FILE* file) : file(file) {
|
||||||
if (!file) {
|
if (!file) {
|
||||||
CRASH("Invalid file handle");
|
CRASH("Invalid file handle");
|
||||||
}
|
}
|
||||||
@@ -78,10 +79,18 @@ FileStream FileStream::OpenRead(const char* utf8Path) {
|
|||||||
return FileStream(OpenCore(utf8Path, MODE("rb")));
|
return FileStream(OpenCore(utf8Path, MODE("rb")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileStream FileStream::OpenRead(const std::filesystem::path& path) {
|
||||||
|
return FileStream(OpenCore(path, MODE("rb")));
|
||||||
|
}
|
||||||
|
|
||||||
FileStream FileStream::Create(const char* utf8Path) {
|
FileStream FileStream::Create(const char* utf8Path) {
|
||||||
return FileStream(OpenCore(utf8Path, MODE("wb")));
|
return FileStream(OpenCore(utf8Path, MODE("wb")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FileStream FileStream::Create(const std::filesystem::path& path) {
|
||||||
|
return FileStream(OpenCore(path, MODE("wb")));
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<u8> FileStream::ReadFull() {
|
std::vector<u8> FileStream::ReadFull() {
|
||||||
const auto fileHandle = ThrowIfNotOpen(*this);
|
const auto fileHandle = ThrowIfNotOpen(*this);
|
||||||
|
|
||||||
@@ -128,7 +137,11 @@ std::vector<u8> FileStream::ReadFull() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::vector<u8> FileStream::ReadAllBytes(const char* utf8Path) {
|
std::vector<u8> FileStream::ReadAllBytes(const char* utf8Path) {
|
||||||
auto handle = OpenRead(utf8Path);
|
return ReadAllBytes(reinterpret_cast<const char8_t*>(utf8Path));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<u8> FileStream::ReadAllBytes(const std::filesystem::path& path) {
|
||||||
|
auto handle = OpenRead(path);
|
||||||
return std::move(handle.ReadFull());
|
return std::move(handle.ReadFull());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -142,6 +155,16 @@ void FileStream::Write(const char* data, size_t dataLen) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void FileStream::WriteAllText(const char* utf8Path, const std::string_view text) {
|
void FileStream::WriteAllText(const char* utf8Path, const std::string_view text) {
|
||||||
auto handle = Create(utf8Path);
|
WriteAllText(reinterpret_cast<const char8_t*>(utf8Path), text);
|
||||||
|
}
|
||||||
|
|
||||||
|
void FileStream::WriteAllText(const std::filesystem::path& path, const std::string_view text) {
|
||||||
|
auto handle = Create(path);
|
||||||
handle.Write(text.data(), text.size());
|
handle.Write(text.data(), text.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FILE* FileStream::ToInner() {
|
||||||
|
auto handle = file;
|
||||||
|
file = nullptr;
|
||||||
|
return handle;
|
||||||
|
}
|
||||||
@@ -9,6 +9,8 @@
|
|||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
||||||
|
#include "dusk/logging.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
constexpr uint8_t hex_nibble_to_u8(char c) {
|
constexpr uint8_t hex_nibble_to_u8(char c) {
|
||||||
@@ -42,6 +44,18 @@ constexpr XXH128_hash_t parse_xxh128(std::string_view hex) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const char* verification_state_name(dusk::DiscVerificationState state) noexcept {
|
||||||
|
switch (state) {
|
||||||
|
case dusk::DiscVerificationState::Success:
|
||||||
|
return "verified";
|
||||||
|
case dusk::DiscVerificationState::HashMismatch:
|
||||||
|
return "hash mismatch";
|
||||||
|
case dusk::DiscVerificationState::Unknown:
|
||||||
|
default:
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
namespace dusk::iso {
|
namespace dusk::iso {
|
||||||
@@ -248,4 +262,10 @@ bool isPal(const char* path) {
|
|||||||
DiscInfo info{};
|
DiscInfo info{};
|
||||||
return inspect(path, info) == ValidationError::Success && info.isPal;
|
return inspect(path, info) == ValidationError::Success && info.isPal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void log_verification_state(std::string_view path, DiscVerificationState state) {
|
||||||
|
const std::string pathText = path.empty() ? "<none>" : std::string(path);
|
||||||
|
DuskLog.info(
|
||||||
|
"Disc verification status: {} (path: {})", verification_state_name(state), pathText);
|
||||||
|
}
|
||||||
} // namespace dusk::iso
|
} // namespace dusk::iso
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ struct DiscInfo {
|
|||||||
ValidationError inspect(const char* path, DiscInfo& info);
|
ValidationError inspect(const char* path, DiscInfo& info);
|
||||||
ValidationError validate(const char* path, VerificationStatus& status, DiscInfo& info);
|
ValidationError validate(const char* path, VerificationStatus& status, DiscInfo& info);
|
||||||
bool isPal(const char* path);
|
bool isPal(const char* path);
|
||||||
|
void log_verification_state(std::string_view path, DiscVerificationState state);
|
||||||
|
|
||||||
} // namespace dusk::iso
|
} // namespace dusk::iso
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
|
#include "dusk/io.hpp"
|
||||||
#include "tracy/Tracy.hpp"
|
#include "tracy/Tracy.hpp"
|
||||||
|
|
||||||
#if TARGET_ANDROID
|
#if TARGET_ANDROID
|
||||||
@@ -40,7 +41,7 @@ std::atomic g_logStateAlive(true);
|
|||||||
struct LogState {
|
struct LogState {
|
||||||
std::mutex mutex;
|
std::mutex mutex;
|
||||||
FILE* file = nullptr;
|
FILE* file = nullptr;
|
||||||
std::string filePath;
|
std::u8string filePath;
|
||||||
|
|
||||||
~LogState() {
|
~LogState() {
|
||||||
CloseFile();
|
CloseFile();
|
||||||
@@ -212,14 +213,14 @@ void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraL
|
|||||||
}
|
}
|
||||||
|
|
||||||
const std::filesystem::path logPath = logsDir / MakeTimestampedLogName();
|
const std::filesystem::path logPath = logsDir / MakeTimestampedLogName();
|
||||||
g_logState.file = std::fopen(logPath.string().c_str(), "wb");
|
g_logState.file = io::FileStream::Create(logPath).ToInner();
|
||||||
if (g_logState.file == nullptr) {
|
if (g_logState.file == nullptr) {
|
||||||
std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n",
|
std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n",
|
||||||
logPath.string().c_str());
|
logPath.string().c_str());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
g_logState.filePath = logPath.string();
|
g_logState.filePath = logPath.u8string();
|
||||||
aurora::g_config.logCallback = &aurora_log_callback;
|
aurora::g_config.logCallback = &aurora_log_callback;
|
||||||
aurora::g_config.logLevel = logLevel;
|
aurora::g_config.logLevel = logLevel;
|
||||||
WriteLogLine(g_logState.file, "INFO", "dusk", "File logging initialized", 24);
|
WriteLogLine(g_logState.file, "INFO", "dusk", "File logging initialized", 24);
|
||||||
@@ -237,5 +238,6 @@ const char* dusk::GetLogFilePath() {
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
std::lock_guard lock(g_logState.mutex);
|
std::lock_guard lock(g_logState.mutex);
|
||||||
return g_logState.filePath.empty() ? nullptr : g_logState.filePath.c_str();
|
return reinterpret_cast<const char*>(
|
||||||
|
g_logState.filePath.empty() ? nullptr : g_logState.filePath.c_str());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -302,9 +302,17 @@ std::string get_error_msg(iso::ValidationError error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void persist_disc_choice(const std::string& path, iso::ValidationError validation) {
|
void persist_disc_choice(const std::string& path, iso::ValidationError validation) {
|
||||||
|
const auto previousPath = getSettings().backend.isoPath.getValue();
|
||||||
|
const auto previousVerification = getSettings().backend.isoVerification.getValue();
|
||||||
|
const auto verification = verification_to_config(validation);
|
||||||
|
|
||||||
getSettings().backend.isoPath.setValue(path);
|
getSettings().backend.isoPath.setValue(path);
|
||||||
getSettings().backend.isoVerification.setValue(verification_to_config(validation));
|
getSettings().backend.isoVerification.setValue(verification);
|
||||||
config::Save();
|
config::Save();
|
||||||
|
|
||||||
|
if (previousPath != path || previousVerification != verification) {
|
||||||
|
iso::log_verification_state(path, verification);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void apply_valid_disc_result(
|
void apply_valid_disc_result(
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include "dusk/file_select.hpp"
|
#include "dusk/file_select.hpp"
|
||||||
#include "dusk/imgui/ImGuiEngine.hpp"
|
#include "dusk/imgui/ImGuiEngine.hpp"
|
||||||
#include "dusk/livesplit.h"
|
#include "dusk/livesplit.h"
|
||||||
|
#include "dusk/main.h"
|
||||||
#include "graphics_tuner.hpp"
|
#include "graphics_tuner.hpp"
|
||||||
#include "m_Do/m_Do_main.h"
|
#include "m_Do/m_Do_main.h"
|
||||||
#include "menu_bar.hpp"
|
#include "menu_bar.hpp"
|
||||||
@@ -946,6 +947,18 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
|||||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||||
|
|
||||||
leftPane.add_section("Dusk");
|
leftPane.add_section("Dusk");
|
||||||
|
#if DUSK_CAN_OPEN_DATA_FOLDER
|
||||||
|
leftPane.register_control(
|
||||||
|
leftPane.add_button("Open Data Folder").on_pressed([] {
|
||||||
|
mDoAud_seStartMenu(kSoundClick);
|
||||||
|
dusk::OpenDataFolder();
|
||||||
|
}),
|
||||||
|
rightPane, [](Pane& pane) {
|
||||||
|
pane.add_text(
|
||||||
|
"Open the folder where Dusk stores settings, saves, logs, texture "
|
||||||
|
"replacements, and other app data.");
|
||||||
|
});
|
||||||
|
#endif
|
||||||
leftPane.register_control(
|
leftPane.register_control(
|
||||||
leftPane.add_select_button({
|
leftPane.add_select_button({
|
||||||
.key = "Notifications",
|
.key = "Notifications",
|
||||||
|
|||||||
+102
-5
@@ -71,6 +71,7 @@
|
|||||||
#include <dolphin/dvd.h>
|
#include <dolphin/dvd.h>
|
||||||
|
|
||||||
#include "SDL3/SDL_filesystem.h"
|
#include "SDL3/SDL_filesystem.h"
|
||||||
|
#include "SDL3/SDL_misc.h"
|
||||||
#include "cxxopts.hpp"
|
#include "cxxopts.hpp"
|
||||||
#include "d/actor/d_a_movie_player.h"
|
#include "d/actor/d_a_movie_player.h"
|
||||||
#include "dusk/audio/DuskAudioSystem.h"
|
#include "dusk/audio/DuskAudioSystem.h"
|
||||||
@@ -83,6 +84,9 @@
|
|||||||
#include "f_pc/f_pc_draw.h"
|
#include "f_pc/f_pc_draw.h"
|
||||||
#include "tracy/Tracy.hpp"
|
#include "tracy/Tracy.hpp"
|
||||||
#include <RmlUi/Core.h>
|
#include <RmlUi/Core.h>
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#include <TargetConditionals.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
// --- GLOBALS ---
|
// --- GLOBALS ---
|
||||||
s8 mDoMain::developmentMode = -1;
|
s8 mDoMain::developmentMode = -1;
|
||||||
@@ -114,6 +118,31 @@ void dusk::RequestRestart() noexcept {
|
|||||||
IsRunning = false;
|
IsRunning = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool dusk::OpenDataFolder() {
|
||||||
|
#if DUSK_CAN_OPEN_DATA_FOLDER
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::path path = std::filesystem::absolute(ConfigPath, ec);
|
||||||
|
if (ec) {
|
||||||
|
DuskLog.warn("Failed to resolve absolute data folder path '{}': {}",
|
||||||
|
ConfigPath.string(), ec.message());
|
||||||
|
path = ConfigPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
#if defined(_WIN32)
|
||||||
|
const std::string url = "file:///" + path.generic_string();
|
||||||
|
#else
|
||||||
|
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());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
#else
|
||||||
|
return false;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
s32 LOAD_COPYDATE(void*) {
|
s32 LOAD_COPYDATE(void*) {
|
||||||
char buffer[32];
|
char buffer[32];
|
||||||
memset(buffer, 0, sizeof(buffer));
|
memset(buffer, 0, sizeof(buffer));
|
||||||
@@ -392,13 +421,77 @@ static void ApplyCVarOverrides(const cxxopts::OptionValue& option) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static std::filesystem::path CalculateConfigPath() {
|
static void migrate_directory(const std::filesystem::path& from, const std::filesystem::path& to) {
|
||||||
|
std::error_code ec;
|
||||||
|
std::filesystem::create_directories(to, ec);
|
||||||
|
if (ec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (std::filesystem::recursive_directory_iterator it(
|
||||||
|
from, std::filesystem::directory_options::skip_permission_denied, ec);
|
||||||
|
it != std::filesystem::recursive_directory_iterator(); it.increment(ec))
|
||||||
|
{
|
||||||
|
if (ec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto relativePath = std::filesystem::relative(it->path(), from, ec);
|
||||||
|
if (ec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto targetPath = to / relativePath;
|
||||||
|
if (it->is_directory(ec)) {
|
||||||
|
std::filesystem::create_directories(targetPath, ec);
|
||||||
|
if (ec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (it->is_regular_file(ec) && !std::filesystem::exists(targetPath, ec)) {
|
||||||
|
std::filesystem::create_directories(targetPath.parent_path(), ec);
|
||||||
|
if (ec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::filesystem::copy_file(
|
||||||
|
it->path(), targetPath, std::filesystem::copy_options::skip_existing, ec);
|
||||||
|
if (ec) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::filesystem::path calculate_config_path() {
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#if TARGET_OS_IOS && !TARGET_OS_TV
|
||||||
|
const char* documentsPath = SDL_GetUserFolder(SDL_FOLDER_DOCUMENTS);
|
||||||
|
if (!documentsPath) {
|
||||||
|
DuskLog.fatal("Unable to get iOS Documents path: {}", SDL_GetError());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::filesystem::path configPath = reinterpret_cast<const char8_t*>(documentsPath);
|
||||||
|
|
||||||
|
char* oldPrefPath = SDL_GetPrefPath(dusk::OrgName, dusk::AppName);
|
||||||
|
if (oldPrefPath) {
|
||||||
|
const std::filesystem::path oldConfigPath = reinterpret_cast<const char8_t*>(oldPrefPath);
|
||||||
|
SDL_free(oldPrefPath);
|
||||||
|
|
||||||
|
std::error_code ec;
|
||||||
|
if (oldConfigPath != configPath && std::filesystem::exists(oldConfigPath, ec)) {
|
||||||
|
migrate_directory(oldConfigPath, configPath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return configPath;
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
|
||||||
const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName);
|
const auto result = SDL_GetPrefPath(dusk::OrgName, dusk::AppName);
|
||||||
if (!result) {
|
if (!result) {
|
||||||
DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError());
|
DuskLog.fatal("Unable to get PrefPath: {}", SDL_GetError());
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return reinterpret_cast<const char8_t*>(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void EnsureInitialPipelineCache(const std::filesystem::path& configDir) {
|
static void EnsureInitialPipelineCache(const std::filesystem::path& configDir) {
|
||||||
@@ -536,7 +629,7 @@ int game_main(int argc, char* argv[]) {
|
|||||||
exit(1);
|
exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
dusk::ConfigPath = CalculateConfigPath();
|
dusk::ConfigPath = calculate_config_path();
|
||||||
const auto startupLogLevel = static_cast<AuroraLogLevel>(parsed_arg_options["log-level"].as<uint8_t>());
|
const auto startupLogLevel = static_cast<AuroraLogLevel>(parsed_arg_options["log-level"].as<uint8_t>());
|
||||||
dusk::InitializeFileLogging(dusk::ConfigPath, startupLogLevel);
|
dusk::InitializeFileLogging(dusk::ConfigPath, startupLogLevel);
|
||||||
|
|
||||||
@@ -548,10 +641,10 @@ int game_main(int argc, char* argv[]) {
|
|||||||
//PADSetDefaultMapping(&defaultPadMapping, PAD_TYPE_STANDARD);
|
//PADSetDefaultMapping(&defaultPadMapping, PAD_TYPE_STANDARD);
|
||||||
|
|
||||||
{
|
{
|
||||||
const auto configPathString = dusk::ConfigPath.string();
|
const auto configPathString = dusk::ConfigPath.u8string();
|
||||||
AuroraConfig config{};
|
AuroraConfig config{};
|
||||||
config.appName = dusk::AppName;
|
config.appName = dusk::AppName;
|
||||||
config.configPath = configPathString.c_str();
|
config.configPath = reinterpret_cast<const char*>(configPathString.c_str());
|
||||||
config.vsync = dusk::getSettings().video.enableVsync;
|
config.vsync = dusk::getSettings().video.enableVsync;
|
||||||
config.startFullscreen = dusk::getSettings().video.enableFullscreen;
|
config.startFullscreen = dusk::getSettings().video.enableFullscreen;
|
||||||
config.windowPosX = -1;
|
config.windowPosX = -1;
|
||||||
@@ -649,6 +742,10 @@ int game_main(int argc, char* argv[]) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dusk::iso::log_verification_state(
|
||||||
|
dusk::getSettings().backend.isoPath.getValue(),
|
||||||
|
dusk::getSettings().backend.isoVerification.getValue());
|
||||||
|
|
||||||
if (!dvd_opened) {
|
if (!dvd_opened) {
|
||||||
if (dusk::getSettings().backend.isoPath.getValue().empty()) {
|
if (dusk::getSettings().backend.isoPath.getValue().empty()) {
|
||||||
forcePreLaunchUI = true;
|
forcePreLaunchUI = true;
|
||||||
|
|||||||
Reference in New Issue
Block a user