mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-04 11:19:58 -04:00
Compare commits
39 Commits
v1.0.0-rc.1
...
ci
| Author | SHA1 | Date | |
|---|---|---|---|
| 625f752fb9 | |||
| 04b5861f29 | |||
| 453e958068 | |||
| e7d2fbcc0b | |||
| 8f71c70d14 | |||
| df23edcb69 | |||
| daff157027 | |||
| 0c23bd4332 | |||
| 7562486449 | |||
| 5e08b810fc | |||
| c66cccf660 | |||
| 3b1118229b | |||
| 491da372a1 | |||
| a2f463d146 | |||
| 63b3ce4849 | |||
| 8280ac00a0 | |||
| 45ef0d72b1 | |||
| ad9c460ec9 | |||
| 23dc9bc39a | |||
| bf23d44389 | |||
| d0b9b6d10f | |||
| 2f83753260 | |||
| eeb0ad77a4 | |||
| 594cadcf7d | |||
| 4290726691 | |||
| 80dd5ff278 | |||
| 08efb9a3cf | |||
| d8a1dd1da4 | |||
| 13dd3c3932 | |||
| dd7885da9c | |||
| 5a05433a2b | |||
| c3ff3884d7 | |||
| e42c4d3174 | |||
| 06c77a6818 | |||
| 4d4a80891f | |||
| 71c892368d | |||
| d2a1dda523 | |||
| 78179eb93f | |||
| 34e10d3844 |
+80
-20
@@ -8,9 +8,9 @@ on:
|
||||
pull_request:
|
||||
|
||||
env:
|
||||
# SCCACHE_GHA_ENABLED: "true"
|
||||
SCCACHE_GHA_ENABLED: "true"
|
||||
RUSTC_WRAPPER: "sccache"
|
||||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
# SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||||
|
||||
jobs:
|
||||
build-linux:
|
||||
@@ -22,7 +22,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- name: GCC x86_64
|
||||
runner: [self-hosted, Linux]
|
||||
runner: ubuntu-latest
|
||||
preset: gcc
|
||||
artifact_arch: x86_64
|
||||
# - name: GCC aarch64
|
||||
@@ -41,7 +41,6 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
if: 'false' # disabled for self-hosted
|
||||
run: |
|
||||
sudo apt-get update
|
||||
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
|
||||
|
||||
- name: Setup sccache
|
||||
if: 'false' # disabled for self-hosted
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- name: Print sccache stats
|
||||
@@ -67,7 +65,6 @@ jobs:
|
||||
run: ci/build-appimage.sh
|
||||
|
||||
- name: Upload artifacts
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusk-${{env.DUSK_VERSION}}-linux-${{matrix.preset}}-${{matrix.artifact_arch}}
|
||||
@@ -77,7 +74,7 @@ jobs:
|
||||
|
||||
build-apple:
|
||||
name: Build Apple (${{matrix.name}})
|
||||
runs-on: [self-hosted, macOS]
|
||||
runs-on: macos-latest
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@@ -86,14 +83,14 @@ jobs:
|
||||
platform: macos
|
||||
preset: x-macos-ci-arm64
|
||||
artifact_name: macos-appleclang-arm64
|
||||
# - name: AppleClang macOS x86_64
|
||||
# platform: macos
|
||||
# preset: x-macos-ci-x86_64
|
||||
# artifact_name: macos-appleclang-x86_64
|
||||
# - name: AppleClang iOS arm64
|
||||
# platform: ios
|
||||
# preset: x-ios-ci
|
||||
# artifact_name: ios-appleclang-arm64
|
||||
- name: AppleClang macOS x86_64
|
||||
platform: macos
|
||||
preset: x-macos-ci-x86_64
|
||||
artifact_name: macos-appleclang-x86_64
|
||||
- name: AppleClang iOS arm64
|
||||
platform: ios
|
||||
preset: x-ios-ci
|
||||
artifact_name: ios-appleclang-arm64
|
||||
# - name: AppleClang tvOS arm64
|
||||
# platform: tvos
|
||||
# preset: x-tvos-ci
|
||||
@@ -106,7 +103,6 @@ jobs:
|
||||
submodules: recursive
|
||||
|
||||
- name: Install dependencies
|
||||
if: 'false'
|
||||
run: brew install cmake ninja
|
||||
|
||||
- name: Install Rust iOS target
|
||||
@@ -137,7 +133,6 @@ jobs:
|
||||
run: cmake --build --preset ${{matrix.preset}}
|
||||
|
||||
- name: Upload artifacts
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusk-${{env.DUSK_VERSION}}-${{matrix.artifact_name}}
|
||||
@@ -145,6 +140,73 @@ jobs:
|
||||
build/install/Dusk.app
|
||||
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:
|
||||
name: Build Windows (${{matrix.name}})
|
||||
runs-on: ${{matrix.runner}}
|
||||
@@ -154,7 +216,7 @@ jobs:
|
||||
matrix:
|
||||
include:
|
||||
- name: MSVC x86_64
|
||||
runner: [self-hosted, Windows]
|
||||
runner: windows-latest
|
||||
preset: msvc
|
||||
msvc_arch: amd64
|
||||
vcpkg_arch: x64
|
||||
@@ -191,7 +253,6 @@ jobs:
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
|
||||
- name: Install dependencies
|
||||
if: 'false' # disabled for self-hosted
|
||||
run: |
|
||||
choco install ninja
|
||||
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}}
|
||||
|
||||
- name: Upload artifacts
|
||||
if: startsWith(github.event.ref, 'refs/tags/v')
|
||||
uses: actions/upload-artifact@v7
|
||||
with:
|
||||
name: dusk-${{env.DUSK_VERSION}}-win32-msvc-${{matrix.artifact_arch}}
|
||||
|
||||
+11
-14
@@ -115,16 +115,22 @@ option(DUSK_ENABLE_UPDATE_CHECKER "Enable update checking support" ON)
|
||||
|
||||
if(ANDROID)
|
||||
set(DUSK_MOVIE_SUPPORT OFF)
|
||||
set(NOD_COMPRESS_BZIP2 OFF CACHE BOOL "" FORCE)
|
||||
set(NOD_COMPRESS_LZMA OFF CACHE BOOL "" FORCE)
|
||||
set(NOD_COMPRESS_ZLIB OFF CACHE BOOL "" FORCE)
|
||||
set(NOD_COMPRESS_ZSTD OFF CACHE BOOL "" FORCE)
|
||||
endif ()
|
||||
|
||||
option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support" OFF)
|
||||
set(DUSK_SENTRY_DSN "" CACHE STRING "Sentry DSN")
|
||||
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)
|
||||
find_package(libjpeg-turbo 3.0 CONFIG QUIET)
|
||||
if (libjpeg-turbo_FOUND)
|
||||
@@ -154,6 +160,7 @@ if (DUSK_MOVIE_SUPPORT)
|
||||
CMAKE_C_COMPILER_LAUNCHER
|
||||
CMAKE_MAKE_PROGRAM
|
||||
CMAKE_MSVC_RUNTIME_LIBRARY
|
||||
CMAKE_MSVC_DEBUG_INFORMATION_FORMAT
|
||||
CMAKE_OSX_ARCHITECTURES
|
||||
DEPLOYMENT_TARGET
|
||||
ENABLE_ARC
|
||||
@@ -370,16 +377,6 @@ if (DUSK_ENABLE_DISCORD AND NOT ANDROID AND NOT IOS AND NOT TVOS)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_DISCORD=1)
|
||||
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)
|
||||
list(APPEND GAME_COMPILE_DEFS TARGET_ANDROID=1)
|
||||
endif ()
|
||||
|
||||
@@ -352,6 +352,25 @@
|
||||
"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",
|
||||
"hidden": true,
|
||||
@@ -412,6 +431,7 @@
|
||||
"x-macos-ci"
|
||||
],
|
||||
"cacheVariables": {
|
||||
"AURORA_DAWN_PROVIDER": "vendor",
|
||||
"CMAKE_OSX_ARCHITECTURES": "x86_64",
|
||||
"Rust_CARGO_TARGET": "x86_64-apple-darwin"
|
||||
}
|
||||
@@ -555,6 +575,15 @@
|
||||
"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",
|
||||
"configurePreset": "windows-msvc-debug",
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
<p align="center">
|
||||
<a href="https://twilitrealm.dev">Official Website</a>
|
||||
•
|
||||
<a href="https://discord.gg/QACynxeyna">Discord</a>
|
||||
<a href="https://discord.gg/dusktp">Discord</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
@@ -28,13 +28,24 @@ First, make sure your dump of the game is clean and supported by Dusk. You can d
|
||||
| GameCube USA | `75edd3ddff41f125d1b4ce1a40378f1b565519e7` |
|
||||
| GameCube EUR | `2601822a488eeb86fb89db16ca8f29c2c953e1ca` |
|
||||
|
||||
*Support for other versions of the game is planned in the future.
|
||||
|
||||
### 2. Download [Dusk](https://github.com/TwilitRealm/dusk/releases)
|
||||
|
||||
### 3. Setup the game
|
||||
|
||||
**Windows / macOS / Linux**
|
||||
- Extract the .zip file
|
||||
- Launch Dusk
|
||||
- Press **Select Disc Image** and provide the path to your supported game dump.
|
||||
- Press **Select Disc Image** and provide the path to your supported game dump
|
||||
- Press **Play**!
|
||||
|
||||
**iOS**
|
||||
- Follow the [iOS setup guide](docs/ios-install-altstore.md)
|
||||
|
||||
**Android**
|
||||
- Install the Dusk apk
|
||||
- Launch Dusk
|
||||
- Press **Select Disc Image** and provide the path to your supported game dump
|
||||
- Press **Play**!
|
||||
|
||||
# Building
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
# Installing Dusk on iOS via AltStore
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- Mac with Homebrew installed
|
||||
- iPhone connected via USB
|
||||
- Dusk IPA file (download the latest `Dusk-vX.X.X-ios-arm64.ipa` from the [releases page](https://github.com/TwilitRealm/dusk/releases))
|
||||
- Game disc - `GZ2E01` (Gamecube USA) or `GZ2PE01` (Gamecube PAL)
|
||||
|
||||
## 1. Install AltServer
|
||||
|
||||
```sh
|
||||
brew install altserver
|
||||
open -a AltServer
|
||||
```
|
||||
|
||||
AltServer will appear in your menu bar.
|
||||
|
||||
## 2. Enable Developer Mode (iOS 16+)
|
||||
|
||||
- On your iPhone, go to **Settings > Privacy & Security > Developer Mode**
|
||||
- Toggle it on and restart when prompted
|
||||
|
||||
## 3. Install AltStore on Your iPhone
|
||||
|
||||
- Click AltServer in the menu bar
|
||||
- Click **Install AltStore > [Your iPhone]**
|
||||
- Enter your Apple ID credentials when prompted
|
||||
- On your iPhone, go to **Settings > General > VPN & Device Management**
|
||||
- Tap your Apple ID under "Developer App" and tap **Trust**
|
||||
|
||||
## 4. Copy Files to Your iPhone
|
||||
|
||||
Transfer the IPA and game disc to your iPhone so they're accessible in the Files app. A few ways to do this:
|
||||
|
||||
- **AirDrop** - Right-click the files on your Mac and choose Share > AirDrop
|
||||
- **iCloud Drive** - Place files in iCloud Drive on your Mac and they'll sync to Files on your iPhone
|
||||
- **USB transfer** - Connect your iPhone and drag files via Finder's sidebar
|
||||
- **Cloud storage** - Upload to Google Drive, Dropbox, etc. and download on your iPhone
|
||||
|
||||
## 5. Install via AltStore
|
||||
|
||||
- Open **AltStore** on your iPhone
|
||||
- Go to the **My Apps** tab
|
||||
- Tap the **+** button (top left)
|
||||
- Open the **Files** app and select the `.ipa` file
|
||||
Vendored
+1
-1
Submodule extern/aurora updated: 1eeff98783...398054316e
@@ -80,6 +80,12 @@ public:
|
||||
/* 0x125C */ u32 field_0x125c;
|
||||
/* 0x1260 */ u8 field_0x1260[0x126C - 0x1260];
|
||||
/* 0x126C */ u8 HIOInit;
|
||||
#if TARGET_PC
|
||||
cXyz mStalkLineInterpPrev[12];
|
||||
cXyz mStalkLineInterpCurr[12];
|
||||
bool mStalkLineInterpPrevValid;
|
||||
bool mStalkLineInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_db_class) == 0x1270);
|
||||
|
||||
@@ -73,6 +73,12 @@ public:
|
||||
/* 0x124C */ f32 field_0x124c;
|
||||
/* 0x1250 */ u8 field_0x1250[0x1264 - 0x1250];
|
||||
/* 0x1264 */ u8 HIOInit;
|
||||
#if TARGET_PC
|
||||
cXyz mStalkLineInterpPrev[12];
|
||||
cXyz mStalkLineInterpCurr[12];
|
||||
bool mStalkLineInterpPrevValid;
|
||||
bool mStalkLineInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_hb_class) == 0x1268);
|
||||
|
||||
@@ -81,6 +81,15 @@ public:
|
||||
/* 0x306D */ u8 field_0x306D[0x307C - 0x306D];
|
||||
/* 0x307C */ u32 mBodyEffEmtrID;
|
||||
/* 0x3080 */ u8 mInitHIO;
|
||||
|
||||
#if TARGET_PC
|
||||
static const int HAIR_STRAND_COUNT = 22;
|
||||
static const int HAIR_SEGMENT_COUNT = 16;
|
||||
cXyz mHairInterpPrev[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT];
|
||||
cXyz mHairInterpCurr[HAIR_STRAND_COUNT * HAIR_SEGMENT_COUNT];
|
||||
bool mHairInterpPrevValid;
|
||||
bool mHairInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_s1_class) == 0x3084);
|
||||
|
||||
@@ -74,6 +74,12 @@ public:
|
||||
/* 0x1250 */ f32 field_0x1250;
|
||||
/* 0x1254 */ u8 field_0x1254[0x1268 - 0x1254];
|
||||
/* 0x1268 */ u8 field_0x1268;
|
||||
#if TARGET_PC
|
||||
cXyz mLineMatInterpPrev[12];
|
||||
cXyz mLineMatInterpCurr[12];
|
||||
bool mLineMatInterpPrevValid;
|
||||
bool mLineMatInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_yd_class) == 0x126c);
|
||||
|
||||
@@ -63,6 +63,15 @@ public:
|
||||
/* 0x0BB4 */ yg_ke_s mYgKes[13];
|
||||
/* 0x1880 */ mDoExt_3DlineMat0_c mLineMat;
|
||||
/* 0x189C */ u8 mIsFirstSpawn;
|
||||
|
||||
#if TARGET_PC
|
||||
static const int TENTACLE_STRAND_COUNT = 13;
|
||||
static const int TENTACLE_SEGMENT_COUNT = 10;
|
||||
cXyz mTentacleInterpPrev[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT];
|
||||
cXyz mTentacleInterpCurr[TENTACLE_STRAND_COUNT * TENTACLE_SEGMENT_COUNT];
|
||||
bool mTentacleInterpPrevValid;
|
||||
bool mTentacleInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_yg_class) == 0x18a0);
|
||||
|
||||
@@ -77,6 +77,12 @@ public:
|
||||
/* 0x1260 */ u32 field_0x1260;
|
||||
/* 0x1260 */ u8 field_0x1264[0x1270 - 0x1264];
|
||||
/* 0x1270 */ bool mIsHIOOwner;
|
||||
#if TARGET_PC
|
||||
cXyz mLineInterpPrev[12];
|
||||
cXyz mLineInterpCurr[12];
|
||||
bool mLineInterpPrevValid;
|
||||
bool mLineInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(e_yh_class) == 0x1274);
|
||||
|
||||
@@ -31,6 +31,10 @@ public:
|
||||
csXyz* getAngle() { return field_0x8a4; }
|
||||
J3DModelData* getModelData() { return mModelData; }
|
||||
|
||||
#if TARGET_PC
|
||||
void onInterpCallback();
|
||||
#endif
|
||||
|
||||
private:
|
||||
/* 0x568 */ request_of_phase_process_class mPhase;
|
||||
/* 0x570 */ J3DModelData* mModelData;
|
||||
@@ -42,6 +46,14 @@ private:
|
||||
/* 0x694 */ cXyz field_0x694[22];
|
||||
/* 0x79C */ cXyz field_0x79c[22];
|
||||
/* 0x8A4 */ csXyz field_0x8a4[22];
|
||||
|
||||
#if TARGET_PC
|
||||
static const int CHAIN_COUNT = 22;
|
||||
cXyz mChainInterpPrev[CHAIN_COUNT];
|
||||
cXyz mChainInterpCurr[CHAIN_COUNT];
|
||||
bool mChainInterpPrevValid;
|
||||
bool mChainInterpCurrValid;
|
||||
#endif
|
||||
};
|
||||
|
||||
STATIC_ASSERT(sizeof(daObjFchain_c) == 0x928);
|
||||
|
||||
+28
-2
@@ -2,11 +2,13 @@
|
||||
#define DUSK_IO_HPP
|
||||
|
||||
#include <vector>
|
||||
#include <filesystem>
|
||||
|
||||
// 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 {
|
||||
|
||||
/**
|
||||
@@ -15,7 +17,7 @@ namespace dusk::io {
|
||||
* Methods on this class throw appropriate C++ exceptions when an error occurs.
|
||||
*/
|
||||
class FileStream {
|
||||
void* file;
|
||||
FILE* file;
|
||||
|
||||
public:
|
||||
FileStream() noexcept;
|
||||
@@ -23,7 +25,7 @@ public:
|
||||
/**
|
||||
* \brief Take ownership of a FILE* handle.
|
||||
*/
|
||||
explicit FileStream(void* file);
|
||||
explicit FileStream(FILE* file);
|
||||
FileStream(const FileStream& other) = delete;
|
||||
FileStream(FileStream&& other) noexcept;
|
||||
|
||||
@@ -34,6 +36,11 @@ public:
|
||||
*/
|
||||
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.
|
||||
*
|
||||
@@ -41,16 +48,33 @@ public:
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
@@ -67,6 +91,8 @@ public:
|
||||
* Write data to the file.
|
||||
*/
|
||||
void Write(const char* data, size_t dataLen);
|
||||
|
||||
FILE* ToInner();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -7,6 +7,14 @@
|
||||
|
||||
#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 {
|
||||
extern bool IsRunning;
|
||||
extern bool IsShuttingDown;
|
||||
@@ -22,6 +30,7 @@ namespace dusk {
|
||||
#endif
|
||||
|
||||
void RequestRestart() noexcept;
|
||||
bool OpenDataFolder();
|
||||
}
|
||||
|
||||
#endif // DUSK_MAIN_H
|
||||
|
||||
@@ -66,12 +66,11 @@ Output APK:
|
||||
You can pass command-line args through the activity intent:
|
||||
|
||||
```bash
|
||||
adb shell am start -n com.twilitrealm.dusk/.DuskActivity \
|
||||
--es dusk_args "'/sdcard/Download/The Legend of Zelda: Twilight Princess (USA).iso'"
|
||||
adb shell am start -n dev.twilitrealm.dusk/.DuskActivity \
|
||||
--es dusk_args "--backend vulkan"
|
||||
```
|
||||
|
||||
Supported extras:
|
||||
|
||||
- `dusk_args`: single shell-like argument string
|
||||
- `dusk_argv`: string-array argv
|
||||
- `dusk_disc`: compatibility shortcut (single ISO path)
|
||||
|
||||
@@ -13,11 +13,11 @@ def syncDuskAssets = tasks.register('syncDuskAssets', Sync) {
|
||||
}
|
||||
|
||||
android {
|
||||
namespace 'com.twilitrealm.dusk'
|
||||
namespace 'dev.twilitrealm.dusk'
|
||||
compileSdk 36
|
||||
|
||||
defaultConfig {
|
||||
applicationId 'com.twilitrealm.dusk'
|
||||
applicationId 'dev.twilitrealm.dusk'
|
||||
minSdk 26
|
||||
targetSdk 36
|
||||
versionCode 1
|
||||
@@ -60,7 +60,8 @@ dependencies {
|
||||
}
|
||||
|
||||
tasks.configureEach { task ->
|
||||
if (task.name.startsWith('merge') && task.name.endsWith('Assets')) {
|
||||
if ((task.name.startsWith('merge') && task.name.endsWith('Assets')) ||
|
||||
task.name.toLowerCase().contains('lint')) {
|
||||
task.dependsOn(syncDuskAssets)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,4 +1,4 @@
|
||||
# Keep SDL activity and related JNI bridge methods.
|
||||
-keep class org.libsdl.app.** { *; }
|
||||
-keep class com.twilitrealm.dusk.DuskHttpClient { *; }
|
||||
-keep class com.twilitrealm.dusk.DuskHttpClient$Response { *; }
|
||||
-keep class dev.twilitrealm.dusk.DuskHttpClient { *; }
|
||||
-keep class dev.twilitrealm.dusk.DuskHttpClient$Response { *; }
|
||||
|
||||
@@ -24,8 +24,19 @@
|
||||
<meta-data android:name="android.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
|
||||
android:name="com.twilitrealm.dusk.DuskActivity"
|
||||
android:name="dev.twilitrealm.dusk.DuskActivity"
|
||||
android:alwaysRetainTaskState="true"
|
||||
android:configChanges="layoutDirection|locale|grammaticalGender|fontScale|fontWeightAdjustment|orientation|uiMode|screenLayout|screenSize|smallestScreenSize|keyboard|keyboardHidden|navigation"
|
||||
android:exported="true"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.twilitrealm.dusk;
|
||||
package dev.twilitrealm.dusk;
|
||||
|
||||
import android.app.ActionBar;
|
||||
import android.content.ClipData;
|
||||
@@ -10,6 +10,7 @@ import android.os.Bundle;
|
||||
import android.provider.OpenableColumns;
|
||||
import android.util.Log;
|
||||
import android.view.View;
|
||||
import android.view.Window;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowInsetsController;
|
||||
|
||||
@@ -70,19 +71,42 @@ public class DuskActivity extends SDLActivity {
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
super.onCreate(savedInstanceState);
|
||||
hideSystemBars();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onResume() {
|
||||
super.onResume();
|
||||
hideSystemBars();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWindowFocusChanged(boolean hasFocus) {
|
||||
super.onWindowFocusChanged(hasFocus);
|
||||
if (hasFocus) {
|
||||
hideSystemBars();
|
||||
}
|
||||
}
|
||||
|
||||
private void hideSystemBars() {
|
||||
Window window = getWindow();
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||
WindowInsetsController ctrl = getWindow().getDecorView().getWindowInsetsController();
|
||||
window.setDecorFitsSystemWindows(false);
|
||||
WindowInsetsController ctrl = window.getDecorView().getWindowInsetsController();
|
||||
if (ctrl != null) {
|
||||
ctrl.setSystemBarsBehavior(
|
||||
WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE);
|
||||
ctrl.hide(WindowInsets.Type.systemBars());
|
||||
}
|
||||
}else {
|
||||
View decorView = getWindow().getDecorView();
|
||||
// Hide the status bar.
|
||||
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN;
|
||||
} else {
|
||||
View decorView = window.getDecorView();
|
||||
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION |
|
||||
View.SYSTEM_UI_FLAG_LAYOUT_STABLE;
|
||||
decorView.setSystemUiVisibility(uiOptions);
|
||||
// Remember that you should never show the action bar if the
|
||||
// status bar is hidden, so hide that too if necessary.
|
||||
ActionBar actionBar = getActionBar();
|
||||
if (actionBar != null) {
|
||||
actionBar.hide();
|
||||
@@ -114,11 +138,6 @@ public class DuskActivity extends SDLActivity {
|
||||
return splitArgs(trimmed);
|
||||
}
|
||||
}
|
||||
|
||||
String discPath = intent.getStringExtra("dusk_disc");
|
||||
if (discPath != null && !discPath.isEmpty()) {
|
||||
return new String[] { discPath };
|
||||
}
|
||||
}
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
@@ -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,4 @@
|
||||
package com.twilitrealm.dusk;
|
||||
package dev.twilitrealm.dusk;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
@@ -35,6 +35,8 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
View.OnApplyWindowInsetsListener, View.OnKeyListener, View.OnTouchListener,
|
||||
SensorEventListener, ScaleGestureDetector.OnScaleGestureListener {
|
||||
|
||||
private static native void auroraNativeSetSurfaceReady(boolean ready);
|
||||
|
||||
// Sensors
|
||||
protected SensorManager mSensorManager;
|
||||
protected Display mDisplay;
|
||||
@@ -96,6 +98,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
@Override
|
||||
public void surfaceCreated(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceCreated()");
|
||||
auroraNativeSetSurfaceReady(false);
|
||||
SDLActivity.onNativeSurfaceCreated();
|
||||
}
|
||||
|
||||
@@ -103,6 +106,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
@Override
|
||||
public void surfaceDestroyed(SurfaceHolder holder) {
|
||||
Log.v("SDL", "surfaceDestroyed()");
|
||||
auroraNativeSetSurfaceReady(false);
|
||||
|
||||
// Transition to pause, if needed
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.PAUSED;
|
||||
@@ -192,6 +196,7 @@ public class SDLSurface extends SurfaceView implements SurfaceHolder.Callback,
|
||||
|
||||
/* Surface is ready */
|
||||
mIsSurfaceReady = true;
|
||||
auroraNativeSetSurfaceReady(true);
|
||||
|
||||
SDLActivity.mNextNativeState = SDLActivity.NativeState.RESUMED;
|
||||
SDLActivity.handleNativeState();
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<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>
|
||||
|
||||
@@ -79,5 +79,9 @@
|
||||
<true/>
|
||||
<key>CADisableMinimumFrameDurationOnPhone</key>
|
||||
<true/>
|
||||
<key>UIFileSharingEnabled</key>
|
||||
<true/>
|
||||
<key>LSSupportsOpeningDocumentsInPlace</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -498,7 +498,6 @@ progress.verification-progress-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
flex-wrap: nowrap;
|
||||
justify-content: stretch;
|
||||
align-items: stretch;
|
||||
gap: 12dp;
|
||||
width: 100%;
|
||||
|
||||
@@ -10,6 +10,10 @@
|
||||
#include "f_op/f_op_kankyo_mng.h"
|
||||
#include "f_op/f_op_actor_enemy.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#endif
|
||||
|
||||
class daE_DB_HIO_c : public JORReflexible {
|
||||
public:
|
||||
daE_DB_HIO_c();
|
||||
@@ -66,6 +70,22 @@ static BOOL leaf_anm_init(e_db_class* i_this, int i_anm, f32 i_morf, u8 i_mode,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
static void daE_DB_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||
e_db_class* i_this = (e_db_class*)pUserWork;
|
||||
if (!i_this->mStalkLineInterpPrevValid || !i_this->mStalkLineInterpCurrValid) {
|
||||
return;
|
||||
}
|
||||
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||
cXyz* dst = i_this->stalkLine.getPos(0);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
const cXyz& p0 = i_this->mStalkLineInterpPrev[i];
|
||||
const cXyz& p1 = i_this->mStalkLineInterpCurr[i];
|
||||
dst[i] = p0 + (p1 - p0) * alpha;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int daE_DB_Draw(e_db_class* i_this) {
|
||||
fopAc_ac_c* actor = &i_this->enemy;
|
||||
|
||||
@@ -95,6 +115,17 @@ static int daE_DB_Draw(e_db_class* i_this) {
|
||||
static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF};
|
||||
i_this->stalkLine.update(12, l_color, &actor->tevStr);
|
||||
dComIfGd_set3DlineMat(&i_this->stalkLine);
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||
if (i_this->mStalkLineInterpCurrValid) {
|
||||
memcpy(i_this->mStalkLineInterpPrev, i_this->mStalkLineInterpCurr, sizeof(i_this->mStalkLineInterpCurr));
|
||||
i_this->mStalkLineInterpPrevValid = true;
|
||||
}
|
||||
memcpy(i_this->mStalkLineInterpCurr, i_this->stalkLine.getPos(0), 12 * sizeof(cXyz));
|
||||
i_this->mStalkLineInterpCurrValid = true;
|
||||
dusk::frame_interp::add_interpolation_callback(&daE_DB_interp_callback, i_this);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 1; i < 11; i++) {
|
||||
if (i_this->thornModel[i] != NULL) {
|
||||
|
||||
@@ -9,6 +9,10 @@
|
||||
#include "d/actor/d_a_e_hb_leaf.h"
|
||||
#include "f_op/f_op_actor_enemy.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#endif
|
||||
|
||||
enum daE_HB_ACTION {
|
||||
ACTION_STAY,
|
||||
ACTION_APPEAR,
|
||||
@@ -64,6 +68,22 @@ static BOOL leaf_anm_init(e_hb_class* i_this, int i_anm, f32 i_morf, u8 i_mode,
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
static void daE_HB_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||
e_hb_class* i_this = (e_hb_class*)pUserWork;
|
||||
if (!i_this->mStalkLineInterpPrevValid || !i_this->mStalkLineInterpCurrValid) {
|
||||
return;
|
||||
}
|
||||
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||
cXyz* dst = i_this->stalkLine.getPos(0);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
const cXyz& p0 = i_this->mStalkLineInterpPrev[i];
|
||||
const cXyz& p1 = i_this->mStalkLineInterpCurr[i];
|
||||
dst[i] = p0 + (p1 - p0) * alpha;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int daE_HB_Draw(e_hb_class* i_this) {
|
||||
fopAc_ac_c* actor = &i_this->enemy;
|
||||
|
||||
@@ -82,6 +102,17 @@ static int daE_HB_Draw(e_hb_class* i_this) {
|
||||
static GXColor l_color = {0x14, 0x0F, 0x00, 0xFF};
|
||||
i_this->stalkLine.update(12, l_color, &actor->tevStr);
|
||||
dComIfGd_set3DlineMat(&i_this->stalkLine);
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||
if (i_this->mStalkLineInterpCurrValid) {
|
||||
memcpy(i_this->mStalkLineInterpPrev, i_this->mStalkLineInterpCurr, sizeof(i_this->mStalkLineInterpCurr));
|
||||
i_this->mStalkLineInterpPrevValid = true;
|
||||
}
|
||||
memcpy(i_this->mStalkLineInterpCurr, i_this->stalkLine.getPos(0), 12 * sizeof(cXyz));
|
||||
i_this->mStalkLineInterpCurrValid = true;
|
||||
dusk::frame_interp::add_interpolation_callback(&daE_HB_interp_callback, i_this);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 1; i < 11; i++) {
|
||||
if (i_this->thornModel[i] != NULL) {
|
||||
|
||||
@@ -14,6 +14,8 @@
|
||||
#include "d/d_s_play.h"
|
||||
#include "f_op/f_op_actor_enemy.h"
|
||||
#include "f_op/f_op_camera_mng.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/settings.h"
|
||||
#include <cstring>
|
||||
|
||||
class daE_S1_HIO_c {
|
||||
@@ -99,6 +101,25 @@ static void anm_init(e_s1_class* i_this, int i_resNo, f32 i_morf, u8 i_attr, f32
|
||||
i_this->mAnm = i_resNo;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
static void daE_S1_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||
e_s1_class* i_this = (e_s1_class*)pUserWork;
|
||||
if (!i_this->mHairInterpPrevValid || !i_this->mHairInterpCurrValid) {
|
||||
return;
|
||||
}
|
||||
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||
for (int s = 0; s < e_s1_class::HAIR_STRAND_COUNT; s++) {
|
||||
cXyz* dst = i_this->mLineMat.getPos(s);
|
||||
for (int i = 0; i < e_s1_class::HAIR_SEGMENT_COUNT; i++) {
|
||||
int idx = s * e_s1_class::HAIR_SEGMENT_COUNT + i;
|
||||
const cXyz& p0 = i_this->mHairInterpPrev[idx];
|
||||
const cXyz& p1 = i_this->mHairInterpCurr[idx];
|
||||
dst[i] = p0 + (p1 - p0) * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int daE_S1_Draw(e_s1_class* i_this) {
|
||||
if (i_this->field_0x306c != 0) {
|
||||
return 1;
|
||||
@@ -132,6 +153,22 @@ static int daE_S1_Draw(e_s1_class* i_this) {
|
||||
i_this->mLineMat.update(16, line_color, &i_this->tevStr);
|
||||
dComIfGd_set3DlineMatDark(&i_this->mLineMat);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||
if (i_this->mHairInterpCurrValid) {
|
||||
memcpy(i_this->mHairInterpPrev, i_this->mHairInterpCurr, sizeof(i_this->mHairInterpCurr));
|
||||
i_this->mHairInterpPrevValid = true;
|
||||
}
|
||||
for (int s = 0; s < e_s1_class::HAIR_STRAND_COUNT; s++) {
|
||||
cXyz* src = i_this->mLineMat.getPos(s);
|
||||
memcpy(&i_this->mHairInterpCurr[s * e_s1_class::HAIR_SEGMENT_COUNT], src,
|
||||
e_s1_class::HAIR_SEGMENT_COUNT * sizeof(cXyz));
|
||||
}
|
||||
i_this->mHairInterpCurrValid = true;
|
||||
dusk::frame_interp::add_interpolation_callback(&daE_S1_interp_callback, i_this);
|
||||
}
|
||||
#endif
|
||||
|
||||
dComIfGd_setList();
|
||||
return 1;
|
||||
}
|
||||
@@ -2149,6 +2186,11 @@ static int daE_S1_Create(fopAc_ac_c* i_this) {
|
||||
return cPhs_ERROR_e;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
a_this->mHairInterpPrevValid = false;
|
||||
a_this->mHairInterpCurrValid = false;
|
||||
#endif
|
||||
|
||||
OS_REPORT("//////////////E_S1 SET 2 !!\n");
|
||||
|
||||
if (path_no != 0xFF) {
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include "d/d_cc_uty.h"
|
||||
#include "f_op/f_op_actor_enemy.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#endif
|
||||
|
||||
class daE_YD_HIO_c {
|
||||
public:
|
||||
daE_YD_HIO_c();
|
||||
@@ -73,6 +77,22 @@ static s32 leaf_anm_init(e_yd_class* i_this, int param_1, f32 param_2, u8 param_
|
||||
return false;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
static void daE_YD_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||
e_yd_class* i_this = (e_yd_class*)pUserWork;
|
||||
if (!i_this->mLineMatInterpPrevValid || !i_this->mLineMatInterpCurrValid) {
|
||||
return;
|
||||
}
|
||||
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||
cXyz* dst = i_this->mLineMat.getPos(0);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
const cXyz& p0 = i_this->mLineMatInterpPrev[i];
|
||||
const cXyz& p1 = i_this->mLineMatInterpCurr[i];
|
||||
dst[i] = p0 + (p1 - p0) * alpha;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static s32 daE_YD_Draw(e_yd_class* i_this) {
|
||||
static GXColor l_color = { 0x14, 0x0F, 0x00, 0xFF };
|
||||
|
||||
@@ -86,6 +106,17 @@ static s32 daE_YD_Draw(e_yd_class* i_this) {
|
||||
i_this->mpMorf->entryDL();
|
||||
i_this->mLineMat.update(12, l_color, &i_this->actor.tevStr);
|
||||
dComIfGd_set3DlineMat(&i_this->mLineMat);
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||
if (i_this->mLineMatInterpCurrValid) {
|
||||
memcpy(i_this->mLineMatInterpPrev, i_this->mLineMatInterpCurr, sizeof(i_this->mLineMatInterpCurr));
|
||||
i_this->mLineMatInterpPrevValid = true;
|
||||
}
|
||||
memcpy(i_this->mLineMatInterpCurr, i_this->mLineMat.getPos(0), 12 * sizeof(cXyz));
|
||||
i_this->mLineMatInterpCurrValid = true;
|
||||
dusk::frame_interp::add_interpolation_callback(&daE_YD_interp_callback, i_this);
|
||||
}
|
||||
#endif
|
||||
for (s32 i = 1; i < 11; i++) {
|
||||
if (i_this->field_0x77c[i] != 0) {
|
||||
g_env_light.setLightTevColorType_MAJI(i_this->field_0x77c[i], &i_this->actor.tevStr);
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "f_op/f_op_kankyo_mng.h"
|
||||
#include "d/actor/d_a_obj_carry.h"
|
||||
#include "Z2AudioLib/Z2Instances.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "f_op/f_op_actor_enemy.h"
|
||||
|
||||
enum E_yg_RES_File_ID {
|
||||
@@ -134,7 +136,26 @@ static BOOL pl_check(e_yg_class* i_this, f32 i_dist) {
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static int daE_YG_Draw(e_yg_class* i_this) {
|
||||
#if TARGET_PC
|
||||
static void daE_YG_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||
e_yg_class* i_this = (e_yg_class*)pUserWork;
|
||||
if (!i_this->mTentacleInterpPrevValid || !i_this->mTentacleInterpCurrValid) {
|
||||
return;
|
||||
}
|
||||
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||
for (int s = 0; s < e_yg_class::TENTACLE_STRAND_COUNT; s++) {
|
||||
cXyz* dst = i_this->mLineMat.getPos(s);
|
||||
for (int i = 0; i < e_yg_class::TENTACLE_SEGMENT_COUNT; i++) {
|
||||
int idx = s * e_yg_class::TENTACLE_SEGMENT_COUNT + i;
|
||||
const cXyz& p0 = i_this->mTentacleInterpPrev[idx];
|
||||
const cXyz& p1 = i_this->mTentacleInterpCurr[idx];
|
||||
dst[i] = p0 + (p1 - p0) * alpha;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int daE_YG_Draw(e_yg_class* i_this) {
|
||||
if (i_this->mDispFlag) {
|
||||
return 1;
|
||||
}
|
||||
@@ -160,6 +181,23 @@ static int daE_YG_Draw(e_yg_class* i_this) {
|
||||
color.a = 0xFF;
|
||||
i_this->mLineMat.update(10, color, &actor->tevStr);
|
||||
dComIfGd_set3DlineMatDark(&i_this->mLineMat);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||
if (i_this->mTentacleInterpCurrValid) {
|
||||
memcpy(i_this->mTentacleInterpPrev, i_this->mTentacleInterpCurr, sizeof(i_this->mTentacleInterpCurr));
|
||||
i_this->mTentacleInterpPrevValid = true;
|
||||
}
|
||||
for (int s = 0; s < e_yg_class::TENTACLE_STRAND_COUNT; s++) {
|
||||
cXyz* src = i_this->mLineMat.getPos(s);
|
||||
memcpy(&i_this->mTentacleInterpCurr[s * e_yg_class::TENTACLE_SEGMENT_COUNT], src,
|
||||
e_yg_class::TENTACLE_SEGMENT_COUNT * sizeof(cXyz));
|
||||
}
|
||||
i_this->mTentacleInterpCurrValid = true;
|
||||
dusk::frame_interp::add_interpolation_callback(&daE_YG_interp_callback, i_this);
|
||||
}
|
||||
#endif
|
||||
|
||||
dComIfGd_setList();
|
||||
|
||||
return 1;
|
||||
@@ -1378,6 +1416,11 @@ static cPhs_Step daE_YG_Create(fopAc_ac_c* actor) {
|
||||
return cPhs_ERROR_e;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
i_this->mTentacleInterpPrevValid = false;
|
||||
i_this->mTentacleInterpCurrValid = false;
|
||||
#endif
|
||||
|
||||
if (!hio_set) {
|
||||
i_this->mIsFirstSpawn = 1;
|
||||
hio_set = true;
|
||||
|
||||
@@ -12,6 +12,10 @@
|
||||
#include "f_op/f_op_actor_enemy.h"
|
||||
#include "f_op/f_op_kankyo_mng.h"
|
||||
|
||||
#if TARGET_PC
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#endif
|
||||
|
||||
class daE_YH_HIO_c : public JORReflexible {
|
||||
public:
|
||||
daE_YH_HIO_c();
|
||||
@@ -85,6 +89,22 @@ static BOOL leaf_anm_init(e_yh_class* i_this, int param_2, f32 param_3, u8 param
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
static void daE_YH_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||
e_yh_class* i_this = (e_yh_class*)pUserWork;
|
||||
if (!i_this->mLineInterpPrevValid || !i_this->mLineInterpCurrValid) {
|
||||
return;
|
||||
}
|
||||
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||
cXyz* dst = i_this->mLine.getPos(0);
|
||||
for (int i = 0; i < 12; i++) {
|
||||
const cXyz& p0 = i_this->mLineInterpPrev[i];
|
||||
const cXyz& p1 = i_this->mLineInterpCurr[i];
|
||||
dst[i] = p0 + (p1 - p0) * alpha;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
static int daE_YH_Draw(e_yh_class* i_this) {
|
||||
fopAc_ac_c* a_this = (fopAc_ac_c*)i_this;
|
||||
|
||||
@@ -114,6 +134,17 @@ static int daE_YH_Draw(e_yh_class* i_this) {
|
||||
|
||||
i_this->mLine.update(12, l_color, &a_this->tevStr);
|
||||
dComIfGd_set3DlineMat(&i_this->mLine);
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||
if (i_this->mLineInterpCurrValid) {
|
||||
memcpy(i_this->mLineInterpPrev, i_this->mLineInterpCurr, sizeof(i_this->mLineInterpCurr));
|
||||
i_this->mLineInterpPrevValid = true;
|
||||
}
|
||||
memcpy(i_this->mLineInterpCurr, i_this->mLine.getPos(0), 12 * sizeof(cXyz));
|
||||
i_this->mLineInterpCurrValid = true;
|
||||
dusk::frame_interp::add_interpolation_callback(&daE_YH_interp_callback, i_this);
|
||||
}
|
||||
#endif
|
||||
|
||||
for (int i = 1; i < 11; i++) {
|
||||
if (i_this->mModels[i] != NULL) {
|
||||
|
||||
@@ -419,9 +419,30 @@ int daMidna_c::createHeap() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
if (mpDemoFCTongueBmd != NULL) {
|
||||
if(!daAlink_c::initDemoBck(&mpDemoFCTmpBck, "demo00_Midna_cut00_FC_tmp.bck")) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Update Midna's eye maxLOD to prevent the eyes from disappearing
|
||||
J3DTexture* tex = mpDemoFCTongueBmd->getModelData()->getTexture();
|
||||
JUTNameTab* nametable = mpDemoFCTongueBmd->getModelData()->getTextureName();
|
||||
if (tex != nullptr && nametable != nullptr) {
|
||||
for (u16 i = 0; i < tex->getNum(); i++) {
|
||||
const char* tex_name = nametable->getName(i);
|
||||
if (tex_name != NULL && strcmp(tex_name, "midona_eyeball") == 0) {
|
||||
ResTIMG* timg = tex->getResTIMG(i);
|
||||
timg->maxLOD = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (mpDemoFCTongueBmd != NULL && !daAlink_c::initDemoBck(&mpDemoFCTmpBck, "demo00_Midna_cut00_FC_tmp.bck")) {
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
modelData =
|
||||
(J3DModelData*)dComIfG_getObjectRes(dStage_roomControl_c::getDemoArcName(), "demo00_Midna_cut00_BD_tmp.bmd");
|
||||
@@ -433,6 +454,21 @@ int daMidna_c::createHeap() {
|
||||
|
||||
modelData->getMaterialNodePointer(2)->setMaterialAnm(mpEyeMatAnm[0]);
|
||||
modelData->getMaterialNodePointer(3)->setMaterialAnm(mpEyeMatAnm[1]);
|
||||
|
||||
#if TARGET_PC
|
||||
// Update Midna's eye maxLOD to prevent the eyes from disappearing
|
||||
J3DTexture* tex = modelData->getTexture();
|
||||
JUTNameTab* nametable = modelData->getTextureName();
|
||||
if (tex != nullptr && nametable != nullptr) {
|
||||
for (u16 i = 0; i < tex->getNum(); i++) {
|
||||
const char* tex_name = nametable->getName(i);
|
||||
if (tex_name != NULL && strcmp(tex_name, "midona_eyeball") == 0) {
|
||||
ResTIMG* timg = tex->getResTIMG(i);
|
||||
timg->maxLOD = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
if (!initDemoModel(&mpDemoBDMaskBmd, "demo00_Midna_cut00_BD_mask.bmd", 0)) {
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
#include "JSystem/J3DGraphBase/J3DDrawBuffer.h"
|
||||
#include "SSystem/SComponent/c_math.h"
|
||||
#include "d/d_com_inf_game.h"
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/settings.h"
|
||||
#include <cstring>
|
||||
|
||||
static char const l_arcName[] = "Fchain";
|
||||
@@ -65,6 +67,10 @@ int daObjFchain_c::create() {
|
||||
local_48++;
|
||||
}
|
||||
rv = cPhs_COMPLEATE_e;
|
||||
#if TARGET_PC
|
||||
mChainInterpPrevValid = false;
|
||||
mChainInterpCurrValid = false;
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
return rv;
|
||||
@@ -289,6 +295,26 @@ void daObjFchain_shape_c::draw() {
|
||||
}
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
static void fchain_interp_callback(bool isSimFrame, void* pUserWork) {
|
||||
static_cast<daObjFchain_c*>(pUserWork)->onInterpCallback();
|
||||
}
|
||||
|
||||
void daObjFchain_c::onInterpCallback() {
|
||||
if (!mChainInterpPrevValid || !mChainInterpCurrValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
const f32 alpha = dusk::frame_interp::get_interpolation_step();
|
||||
|
||||
for (int i = 0; i < CHAIN_COUNT; i++) {
|
||||
const cXyz& p0 = mChainInterpPrev[i];
|
||||
const cXyz& p1 = mChainInterpCurr[i];
|
||||
field_0x694[i] = p0 + (p1 - p0) * alpha;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
int daObjFchain_c::draw() {
|
||||
if (field_0x584 != 0) {
|
||||
g_env_light.settingTevStruct(0, ¤t.pos, &tevStr);
|
||||
@@ -297,6 +323,19 @@ int daObjFchain_c::draw() {
|
||||
return 1;
|
||||
}
|
||||
dComIfGd_getOpaListDark()->entryImm(&mShape, 0);
|
||||
|
||||
#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableFrameInterpolation) {
|
||||
if (mChainInterpCurrValid) {
|
||||
memcpy(mChainInterpPrev, mChainInterpCurr, sizeof(mChainInterpCurr));
|
||||
mChainInterpPrevValid = true;
|
||||
}
|
||||
|
||||
memcpy(mChainInterpCurr, field_0x694, sizeof(mChainInterpCurr));
|
||||
mChainInterpCurrValid = true;
|
||||
dusk::frame_interp::add_interpolation_callback(&fchain_interp_callback, this);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
@@ -427,14 +427,15 @@ static void dummyStrings() {
|
||||
dMsgObject_HIO_c g_MsgObject_HIO_c;
|
||||
|
||||
int dMsgObject_c::_execute() {
|
||||
#if TARGET_PC
|
||||
// TODO: enabling wii message overrides fixes direction text, but gives wrong item control text
|
||||
/*#if TARGET_PC
|
||||
if (dusk::getSettings().game.enableMirrorMode) {
|
||||
// enable wii message index override
|
||||
g_MsgObject_HIO_c.mMessageDisplay = 1;
|
||||
} else if (!dusk::getSettings().game.enableMirrorMode && g_MsgObject_HIO_c.mMessageDisplay == 1) {
|
||||
g_MsgObject_HIO_c.mMessageDisplay = 0;
|
||||
}
|
||||
#endif
|
||||
#endif*/
|
||||
|
||||
|
||||
field_0x4c7 = 0;
|
||||
|
||||
@@ -1030,7 +1030,7 @@ void AchievementSystem::load() {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
auto data = io::FileStream::ReadAllBytes(filePath.string().c_str());
|
||||
auto data = io::FileStream::ReadAllBytes(filePath);
|
||||
auto j = json::parse(data);
|
||||
if (!j.is_object()) {
|
||||
return;
|
||||
@@ -1067,7 +1067,7 @@ void AchievementSystem::save() {
|
||||
}
|
||||
try {
|
||||
io::FileStream::WriteAllText(
|
||||
(dusk::ConfigPath / ACHIEVEMENTS_FILENAME).string().c_str(),
|
||||
dusk::ConfigPath / ACHIEVEMENTS_FILENAME,
|
||||
j.dump(2)
|
||||
);
|
||||
} 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 bool RegistrationDone = false;
|
||||
|
||||
static std::string GetConfigJsonPath() {
|
||||
return (dusk::ConfigPath / ConfigFileName).string();
|
||||
static std::u8string GetConfigJsonPath() {
|
||||
return (dusk::ConfigPath / ConfigFileName).u8string();
|
||||
}
|
||||
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
LoadFromFileName(configJsonPath.c_str());
|
||||
LoadFromFileName(reinterpret_cast<const char*>(configJsonPath.c_str()));
|
||||
}
|
||||
|
||||
static void LoadFromPath(const char* path) {
|
||||
@@ -241,7 +241,9 @@ void dusk::config::Save() {
|
||||
return;
|
||||
}
|
||||
|
||||
DuskConfigLog.info("Saving config to '{}'", configJsonPath);
|
||||
DuskConfigLog.info(
|
||||
"Saving config to '{}'",
|
||||
reinterpret_cast<const char*>(configJsonPath.c_str()));
|
||||
|
||||
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) {
|
||||
|
||||
+4
-4
@@ -140,8 +140,8 @@ void read(float dt) {
|
||||
float my_rel = 0.0f;
|
||||
SDL_GetRelativeMouseState(&mx_rel, &my_rel);
|
||||
// Convert pixels to radians
|
||||
s_pitch_rad = my_rel * kMousePixelToRad * getSettings().game.gyroSensitivityX;
|
||||
s_yaw_rad = -mx_rel * kMousePixelToRad * getSettings().game.gyroSensitivityY;
|
||||
s_pitch_rad = my_rel * kMousePixelToRad * getSettings().game.gyroSensitivityY;
|
||||
s_yaw_rad = -mx_rel * kMousePixelToRad * getSettings().game.gyroSensitivityX;
|
||||
s_roll_rad = 0.0f;
|
||||
|
||||
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 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
|
||||
|
||||
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_yaw_rad = getSettings().game.gyroInvertYaw ? -s_yaw_rad : s_yaw_rad;
|
||||
|
||||
@@ -337,7 +337,7 @@ Result get(const Request& request) {
|
||||
}
|
||||
|
||||
jclass clientClass =
|
||||
load_dusk_class(env, activity, "com.twilitrealm.dusk.DuskHttpClient");
|
||||
load_dusk_class(env, activity, "dev.twilitrealm.dusk.DuskHttpClient");
|
||||
env->DeleteLocalRef(activity);
|
||||
if (clientClass == nullptr) {
|
||||
return {
|
||||
@@ -348,7 +348,7 @@ Result get(const Request& request) {
|
||||
|
||||
jmethodID getMethod = env->GetStaticMethodID(clientClass, "get",
|
||||
"(Ljava/lang/String;[Ljava/lang/String;[Ljava/lang/String;IJ)"
|
||||
"Lcom/twilitrealm/dusk/DuskHttpClient$Response;");
|
||||
"Ldev/twilitrealm/dusk/DuskHttpClient$Response;");
|
||||
if (getMethod == nullptr || clear_pending_exception(env)) {
|
||||
env->DeleteLocalRef(clientClass);
|
||||
return {
|
||||
|
||||
@@ -376,18 +376,18 @@ namespace dusk {
|
||||
}
|
||||
|
||||
// Hide mouse cursor if the F1 menu is not open and the cursor is idle for 3 seconds.
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (showMenu) {
|
||||
mouseHideTimer = 0.0f;
|
||||
ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; // Imgui will re-show cursor.
|
||||
} else if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) {
|
||||
mouseHideTimer = 0.0f;
|
||||
ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; // Imgui will re-show cursor.
|
||||
} else if (mouseHideTimer <= 3.0f) {
|
||||
mouseHideTimer += ImGui::GetIO().DeltaTime;
|
||||
} else {
|
||||
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
||||
SDL_HideCursor();
|
||||
if (dusk::getSettings().game.gyroMode.getValue() != GyroMode::Mouse)
|
||||
{
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
if (io.MouseDelta.x != 0.0f || io.MouseDelta.y != 0.0f) {
|
||||
mouseHideTimer = 0.0f;
|
||||
ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange; // Imgui will re-show cursor.
|
||||
} else if (mouseHideTimer <= 3.0f) {
|
||||
mouseHideTimer += ImGui::GetIO().DeltaTime;
|
||||
} else {
|
||||
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
||||
SDL_HideCursor();
|
||||
}
|
||||
}
|
||||
|
||||
ShowToasts();
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
#define DUSK_IMGUI_HPP
|
||||
|
||||
#include <deque>
|
||||
#include <filesystem>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
#include <SDL3/SDL_misc.h>
|
||||
#include <aurora/aurora.h>
|
||||
|
||||
#include "ImGuiMenuGame.hpp"
|
||||
@@ -73,24 +71,4 @@ bool ImGuiButtonCenter(std::string_view text);
|
||||
float ImGuiScale();
|
||||
} // 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
|
||||
|
||||
@@ -56,12 +56,12 @@ namespace dusk {
|
||||
}
|
||||
|
||||
// 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();
|
||||
}
|
||||
|
||||
// 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) {
|
||||
m_speedrunInfo.m_endTimestamp = OSGetTime() - m_speedrunInfo.m_startTimestamp;
|
||||
m_speedrunInfo.m_isRunStarted = false;
|
||||
|
||||
+27
-4
@@ -1,7 +1,8 @@
|
||||
#include "dusk/io.hpp"
|
||||
#include <cstdio>
|
||||
#include <filesystem>
|
||||
|
||||
#include "dusk/io.hpp"
|
||||
|
||||
using namespace dusk::io;
|
||||
|
||||
#if _WIN32
|
||||
@@ -58,7 +59,7 @@ static FILE* OpenCore(const char* path, const MODE_TYPE* mode) {
|
||||
FileStream::FileStream() noexcept : file(nullptr) {
|
||||
}
|
||||
|
||||
FileStream::FileStream(void* file) : file(file) {
|
||||
FileStream::FileStream(FILE* file) : file(file) {
|
||||
if (!file) {
|
||||
CRASH("Invalid file handle");
|
||||
}
|
||||
@@ -78,10 +79,18 @@ FileStream FileStream::OpenRead(const char* utf8Path) {
|
||||
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) {
|
||||
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() {
|
||||
const auto fileHandle = ThrowIfNotOpen(*this);
|
||||
|
||||
@@ -128,7 +137,11 @@ std::vector<u8> FileStream::ReadFull() {
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -142,6 +155,16 @@ void FileStream::Write(const char* data, size_t dataLen) {
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
|
||||
FILE* FileStream::ToInner() {
|
||||
auto handle = file;
|
||||
file = nullptr;
|
||||
return handle;
|
||||
}
|
||||
@@ -9,6 +9,8 @@
|
||||
#include <stdexcept>
|
||||
#include <string_view>
|
||||
|
||||
#include "dusk/logging.h"
|
||||
|
||||
namespace {
|
||||
|
||||
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 dusk::iso {
|
||||
@@ -248,4 +262,10 @@ bool isPal(const char* path) {
|
||||
DiscInfo info{};
|
||||
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
|
||||
|
||||
@@ -31,6 +31,7 @@ struct DiscInfo {
|
||||
ValidationError inspect(const char* path, DiscInfo& info);
|
||||
ValidationError validate(const char* path, VerificationStatus& status, DiscInfo& info);
|
||||
bool isPal(const char* path);
|
||||
void log_verification_state(std::string_view path, DiscVerificationState state);
|
||||
|
||||
} // namespace dusk::iso
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
|
||||
#include "dusk/io.hpp"
|
||||
#include "tracy/Tracy.hpp"
|
||||
|
||||
#if TARGET_ANDROID
|
||||
@@ -40,7 +41,7 @@ std::atomic g_logStateAlive(true);
|
||||
struct LogState {
|
||||
std::mutex mutex;
|
||||
FILE* file = nullptr;
|
||||
std::string filePath;
|
||||
std::u8string filePath;
|
||||
|
||||
~LogState() {
|
||||
CloseFile();
|
||||
@@ -212,14 +213,14 @@ void dusk::InitializeFileLogging(const std::filesystem::path& configDir, AuroraL
|
||||
}
|
||||
|
||||
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) {
|
||||
std::fprintf(stderr, "[WARNING | dusk] Failed to open log file '%s'\n",
|
||||
logPath.string().c_str());
|
||||
return;
|
||||
}
|
||||
|
||||
g_logState.filePath = logPath.string();
|
||||
g_logState.filePath = logPath.u8string();
|
||||
aurora::g_config.logCallback = &aurora_log_callback;
|
||||
aurora::g_config.logLevel = logLevel;
|
||||
WriteLogLine(g_logState.file, "INFO", "dusk", "File logging initialized", 24);
|
||||
@@ -237,5 +238,6 @@ const char* dusk::GetLogFilePath() {
|
||||
return nullptr;
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
|
||||
#include "Z2AudioLib/Z2SeMgr.h"
|
||||
#include "m_Do/m_Do_audio.h"
|
||||
#include <imgui.h>
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
@@ -106,6 +107,7 @@ bool Document::visible() const {
|
||||
|
||||
bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Menu) {
|
||||
toggle_cursor_if_gyro(!visible());
|
||||
mDoAud_seStartMenu(visible() ? kSoundMenuClose : kSoundMenuOpen);
|
||||
toggle();
|
||||
return true;
|
||||
@@ -113,4 +115,17 @@ bool Document::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
return false;
|
||||
}
|
||||
|
||||
void Document::toggle_cursor_if_gyro(bool cursor_enabled) {
|
||||
if (dusk::getSettings().game.gyroMode.getValue() == GyroMode::Mouse)
|
||||
{
|
||||
if (cursor_enabled) {
|
||||
ImGui::GetIO().ConfigFlags &= ~ImGuiConfigFlags_NoMouseCursorChange;
|
||||
SDL_ShowCursor();
|
||||
} else {
|
||||
ImGui::GetIO().ConfigFlags |= ImGuiConfigFlags_NoMouseCursorChange;
|
||||
SDL_HideCursor();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -43,6 +43,8 @@ public:
|
||||
bool pending_close() const { return mPendingClose; }
|
||||
bool closed() const { return mClosed; }
|
||||
|
||||
void toggle_cursor_if_gyro(bool);
|
||||
|
||||
protected:
|
||||
virtual bool handle_nav_command(Rml::Event& event, NavCommand cmd);
|
||||
|
||||
|
||||
@@ -1881,9 +1881,9 @@ EditorWindow::EditorWindow() {
|
||||
}
|
||||
});
|
||||
|
||||
add_tab("Flags", [this](Rml::Element* content) {
|
||||
// TODO
|
||||
});
|
||||
//add_tab("Flags", [this](Rml::Element* content) {
|
||||
// // TODO
|
||||
//});
|
||||
|
||||
add_tab("Minigame", [this](Rml::Element* content) {
|
||||
auto& leftPane = add_child<Pane>(content, Pane::Type::Controlled);
|
||||
|
||||
@@ -193,9 +193,9 @@ Rml::String format_graphics_setting_value(GraphicsOption option, int value) {
|
||||
return "";
|
||||
}
|
||||
|
||||
GraphicsTuner::GraphicsTuner(GraphicsTunerProps props)
|
||||
GraphicsTuner::GraphicsTuner(GraphicsTunerProps props, bool prelaunch)
|
||||
: Document(kDocumentSource), mOption(props.option), mValueMin(props.valueMin),
|
||||
mValueMax(props.valueMax), mDefaultValue(props.defaultValue) {
|
||||
mValueMax(props.valueMax), mDefaultValue(props.defaultValue), mPrelaunch(prelaunch) {
|
||||
if (mDocument == nullptr) {
|
||||
return;
|
||||
}
|
||||
@@ -207,7 +207,7 @@ GraphicsTuner::GraphicsTuner(GraphicsTunerProps props)
|
||||
description->SetInnerRML(escape(props.helpText));
|
||||
}
|
||||
if (auto* carouselParent = mDocument->GetElementById("carousel-container")) {
|
||||
add_component<SteppedCarousel>(carouselParent,
|
||||
mCarousel = &add_component<SteppedCarousel>(carouselParent,
|
||||
SteppedCarousel::Props{
|
||||
.min = mValueMin,
|
||||
.max = mValueMax,
|
||||
@@ -281,7 +281,12 @@ bool GraphicsTuner::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
pop();
|
||||
return true;
|
||||
}
|
||||
return Document::handle_nav_command(event, cmd);
|
||||
|
||||
if (mCarousel && mCarousel->handle_nav_command(cmd)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return mPrelaunch ? false : Document::handle_nav_command(event, cmd);
|
||||
}
|
||||
|
||||
void GraphicsTuner::reset_default() {
|
||||
|
||||
@@ -28,9 +28,9 @@ public:
|
||||
|
||||
bool focus() override;
|
||||
void update() override;
|
||||
bool handle_nav_command(NavCommand cmd);
|
||||
|
||||
private:
|
||||
bool handle_nav_command(NavCommand cmd);
|
||||
void apply(int value);
|
||||
|
||||
Props mProps;
|
||||
@@ -59,7 +59,7 @@ struct GraphicsTunerProps {
|
||||
|
||||
class GraphicsTuner : public Document {
|
||||
public:
|
||||
explicit GraphicsTuner(GraphicsTunerProps props);
|
||||
explicit GraphicsTuner(GraphicsTunerProps props, bool prelaunch);
|
||||
|
||||
void show() override;
|
||||
void hide(bool close) override;
|
||||
@@ -86,7 +86,9 @@ private:
|
||||
int mValueMax = 0;
|
||||
int mDefaultValue = 0;
|
||||
std::vector<std::unique_ptr<Component> > mComponents;
|
||||
SteppedCarousel* mCarousel;
|
||||
Rml::Element* mRoot;
|
||||
bool mPrelaunch;
|
||||
};
|
||||
|
||||
} // namespace dusk::ui
|
||||
|
||||
@@ -42,6 +42,7 @@ MenuBar::MenuBar() : Document(kDocumentSource), mRoot(mDocument->GetElementById(
|
||||
mTabBar = std::make_unique<TabBar>(mRoot, TabBar::Props{
|
||||
.onClose =
|
||||
[this] {
|
||||
toggle_cursor_if_gyro(false);
|
||||
mDoAud_seStartMenu(kSoundMenuClose);
|
||||
hide(false);
|
||||
},
|
||||
@@ -203,6 +204,7 @@ bool MenuBar::handle_nav_command(Rml::Event& event, NavCommand cmd) {
|
||||
return true;
|
||||
}
|
||||
if (cmd == NavCommand::Cancel && visible()) {
|
||||
toggle_cursor_if_gyro(false);
|
||||
mDoAud_seStartMenu(kSoundMenuClose);
|
||||
hide(false);
|
||||
return true;
|
||||
|
||||
@@ -54,7 +54,7 @@ void NumberButton::set_value(Rml::String value) {
|
||||
}
|
||||
|
||||
bool NumberButton::handle_nav_command(NavCommand cmd) {
|
||||
if (cmd == NavCommand::Left || cmd == NavCommand::Right) {
|
||||
if (!is_editing() && (cmd == NavCommand::Left || cmd == NavCommand::Right)) {
|
||||
const int newValue = std::clamp(
|
||||
mGetValue() + (cmd == NavCommand::Right ? mStep : -mStep), mMin, mMax);
|
||||
if (newValue != mGetValue()) {
|
||||
@@ -66,4 +66,4 @@ bool NumberButton::handle_nav_command(NavCommand cmd) {
|
||||
return BaseStringButton::handle_nav_command(cmd);
|
||||
}
|
||||
|
||||
} // namespace dusk::ui
|
||||
} // namespace dusk::ui
|
||||
|
||||
+14
-3
@@ -10,6 +10,10 @@
|
||||
#include <algorithm>
|
||||
#include <dolphin/pad.h>
|
||||
|
||||
#if defined(__APPLE__)
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
namespace dusk::ui {
|
||||
namespace {
|
||||
aurora::Module Log{"dusk::ui::overlay"};
|
||||
@@ -33,7 +37,7 @@ constexpr std::array<std::pair<const char*, const char*>, 3> kAutoSaveLayers{{
|
||||
|
||||
constexpr auto kMenuNotificationDuration = std::chrono::milliseconds(2500);
|
||||
|
||||
constexpr std::array<const char*, 4> kFpsCorners = { "tl", "tr", "bl", "br" };
|
||||
constexpr std::array<const char*, 4> kFpsCorners = {"tl", "tr", "bl", "br"};
|
||||
|
||||
Rml::Element* create_toast(Rml::Element* parent, const Toast& toast) {
|
||||
if (toast.type == "autosave") {
|
||||
@@ -130,13 +134,19 @@ Rml::String back_button_name() {
|
||||
return "Back";
|
||||
}
|
||||
|
||||
#if defined(TARGET_ANDROID) || (defined(__APPLE__) && TARGET_OS_IOS && !TARGET_OS_MACCATALYST)
|
||||
constexpr auto kMenuNotificationPrefix = "3-finger tap or";
|
||||
#else
|
||||
constexpr auto kMenuNotificationPrefix = "Press F1 or";
|
||||
#endif
|
||||
|
||||
Rml::Element* create_menu_notification(Rml::Element* parent) {
|
||||
auto* elem = append(parent, "toast");
|
||||
elem->SetClass("menu-notification", true);
|
||||
|
||||
auto* message = append(elem, "message");
|
||||
auto* row = append(message, "row");
|
||||
append(row, "span")->SetInnerRML("Press F1 or");
|
||||
append(row, "span")->SetInnerRML(kMenuNotificationPrefix);
|
||||
auto* icon = append(row, "icon");
|
||||
icon->SetClass("controller", true);
|
||||
append(row, "span")->SetInnerRML(escape(back_button_name()));
|
||||
@@ -242,7 +252,8 @@ void Overlay::update() {
|
||||
|
||||
const Uint64 now = SDL_GetPerformanceCounter();
|
||||
// Limit updates to twice per second
|
||||
const bool refreshLabel = perfFreq == 0 || mFpsLastUpdate == 0 ||
|
||||
const bool refreshLabel =
|
||||
perfFreq == 0 || mFpsLastUpdate == 0 ||
|
||||
static_cast<double>(now - mFpsLastUpdate) >= 0.5 * static_cast<double>(perfFreq);
|
||||
if (refreshLabel) {
|
||||
mFpsLastUpdate = now;
|
||||
|
||||
@@ -302,9 +302,17 @@ std::string get_error_msg(iso::ValidationError error) {
|
||||
}
|
||||
|
||||
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.isoVerification.setValue(verification_to_config(validation));
|
||||
getSettings().backend.isoVerification.setValue(verification);
|
||||
config::Save();
|
||||
|
||||
if (previousPath != path || previousVerification != verification) {
|
||||
iso::log_verification_state(path, verification);
|
||||
}
|
||||
}
|
||||
|
||||
void apply_valid_disc_result(
|
||||
@@ -687,6 +695,8 @@ Prelaunch::Prelaunch() : Document(kDocumentSource), mRoot(mDocument->GetElementB
|
||||
return;
|
||||
}
|
||||
|
||||
toggle_cursor_if_gyro(false);
|
||||
|
||||
mDoAud_seStartMenu(kSoundPlay);
|
||||
show_menu_notification();
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include "dusk/file_select.hpp"
|
||||
#include "dusk/imgui/ImGuiEngine.hpp"
|
||||
#include "dusk/livesplit.h"
|
||||
#include "dusk/main.h"
|
||||
#include "graphics_tuner.hpp"
|
||||
#include "m_Do/m_Do_main.h"
|
||||
#include "menu_bar.hpp"
|
||||
@@ -274,7 +275,7 @@ SelectButton& config_percent_select(Pane& leftPane, Pane& rightPane, ConfigVar<f
|
||||
|
||||
template <typename T>
|
||||
void graphics_tuner_control(Window& window, Pane& leftPane, Pane& rightPane, ConfigVar<T>& var,
|
||||
const GraphicsTunerProps& props) {
|
||||
const GraphicsTunerProps& props, bool prelaunch) {
|
||||
leftPane.register_control(
|
||||
leftPane
|
||||
.add_select_button({
|
||||
@@ -292,10 +293,10 @@ void graphics_tuner_control(Window& window, Pane& leftPane, Pane& rightPane, Con
|
||||
.isModified = [&var] { return var.getValue() != var.getDefaultValue(); },
|
||||
.submit = false,
|
||||
})
|
||||
.on_nav_command([&window, props](Rml::Event&, NavCommand cmd) {
|
||||
.on_nav_command([&window, props, prelaunch](Rml::Event&, NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm || cmd == NavCommand::Left ||
|
||||
cmd == NavCommand::Right) {
|
||||
window.push(std::make_unique<GraphicsTuner>(props));
|
||||
window.push(std::make_unique<GraphicsTuner>(props, prelaunch));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -551,7 +552,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.valueMin = 0,
|
||||
.valueMax = 12,
|
||||
.defaultValue = 0,
|
||||
});
|
||||
}, mPrelaunch);
|
||||
graphics_tuner_control(*this, leftPane, rightPane,
|
||||
getSettings().game.shadowResolutionMultiplier,
|
||||
GraphicsTunerProps{
|
||||
@@ -561,7 +562,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.valueMin = 1,
|
||||
.valueMax = 8,
|
||||
.defaultValue = 1,
|
||||
});
|
||||
}, mPrelaunch);
|
||||
|
||||
leftPane.add_section("Post-Processing");
|
||||
graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.bloomMode,
|
||||
@@ -572,7 +573,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.valueMin = static_cast<int>(BloomMode::Off),
|
||||
.valueMax = static_cast<int>(BloomMode::Dusk),
|
||||
.defaultValue = static_cast<int>(BloomMode::Classic),
|
||||
});
|
||||
}, mPrelaunch);
|
||||
graphics_tuner_control(*this, leftPane, rightPane, getSettings().game.bloomMultiplier,
|
||||
GraphicsTunerProps{
|
||||
.option = GraphicsOption::BloomMultiplier,
|
||||
@@ -581,7 +582,7 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
.valueMin = 0,
|
||||
.valueMax = 100,
|
||||
.defaultValue = 100,
|
||||
});
|
||||
}, mPrelaunch);
|
||||
|
||||
leftPane.add_section("Rendering");
|
||||
config_bool_select(leftPane, rightPane, getSettings().game.enableFrameInterpolation,
|
||||
@@ -946,6 +947,18 @@ SettingsWindow::SettingsWindow(bool prelaunch) : mPrelaunch(prelaunch) {
|
||||
auto& rightPane = add_child<Pane>(content, Pane::Type::Uncontrolled);
|
||||
|
||||
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.add_select_button({
|
||||
.key = "Notifications",
|
||||
|
||||
@@ -24,7 +24,7 @@ void BaseStringButton::update() {
|
||||
}
|
||||
|
||||
void BaseStringButton::start_editing() {
|
||||
if (mInputElem != nullptr) {
|
||||
if (is_editing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -79,14 +79,14 @@ void BaseStringButton::request_stop_editing(bool commit, bool refocusRoot) {
|
||||
|
||||
bool BaseStringButton::handle_nav_command(NavCommand cmd) {
|
||||
if (cmd == NavCommand::Confirm) {
|
||||
if (mInputElem == nullptr) {
|
||||
if (!is_editing()) {
|
||||
start_editing();
|
||||
} else {
|
||||
request_stop_editing(true, true);
|
||||
}
|
||||
return true;
|
||||
} else if (cmd == NavCommand::Cancel) {
|
||||
if (mInputElem != nullptr) {
|
||||
if (is_editing()) {
|
||||
request_stop_editing(false, true);
|
||||
return true;
|
||||
}
|
||||
@@ -95,7 +95,7 @@ bool BaseStringButton::handle_nav_command(NavCommand cmd) {
|
||||
}
|
||||
|
||||
void BaseStringButton::focus_input() {
|
||||
if (mInputElem == nullptr) {
|
||||
if (!is_editing()) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ void BaseStringButton::focus_input() {
|
||||
void BaseStringButton::stop_editing(bool commit, bool refocusRoot) {
|
||||
mPendingStopEditing = false;
|
||||
mPendingInputFocusFrames = 0;
|
||||
if (mInputElem == nullptr) {
|
||||
if (!is_editing()) {
|
||||
return;
|
||||
}
|
||||
if (commit) {
|
||||
|
||||
@@ -20,6 +20,7 @@ public:
|
||||
void request_stop_editing(bool commit, bool refocusRoot);
|
||||
|
||||
protected:
|
||||
bool is_editing() { return mInputElem != nullptr; }
|
||||
bool handle_nav_command(NavCommand cmd) override;
|
||||
virtual void set_value(Rml::String value) = 0;
|
||||
virtual Rml::String input_value() { return format_value(); }
|
||||
|
||||
@@ -745,7 +745,8 @@ static void duskExecute() {
|
||||
updateAutoSave();
|
||||
|
||||
if (dusk::getSettings().game.recordingMode) {
|
||||
Z2GetSeqMgr()->bgmAllMute(0, 0);
|
||||
Z2GetSoundMgr()->getSeqMgr()->getParams()->moveVolume(0.0f, 0);
|
||||
Z2GetSoundMgr()->getStreamMgr()->getParams()->moveVolume(0.0f, 0);
|
||||
}
|
||||
|
||||
if (mDoCPd_c::getHoldR(PAD_1) && mDoCPd_c::getTrigX(PAD_1)) {
|
||||
|
||||
@@ -1194,8 +1194,13 @@ static void trimming(view_class* param_0, view_port_class* param_1) {
|
||||
if ((y_orig_pos == 0) && (param_1->scissor.y_orig != param_1->y_orig ||
|
||||
(param_1->scissor.height != param_1->height)))
|
||||
{
|
||||
#if TARGET_PC
|
||||
f32 sc_top = param_1->scissor.y_orig;
|
||||
f32 sc_bottom = param_1->scissor.y_orig + param_1->scissor.height;
|
||||
#else
|
||||
s32 sc_top = (int)param_1->scissor.y_orig;
|
||||
s32 sc_bottom = param_1->scissor.y_orig + param_1->scissor.height;
|
||||
#endif
|
||||
GXSetNumChans(1);
|
||||
GXSetChanCtrl(GX_ALPHA0, GX_FALSE, GX_SRC_REG, GX_SRC_REG, 0, GX_DF_NONE, GX_AF_NONE);
|
||||
GXSetNumTexGens(0);
|
||||
@@ -1224,20 +1229,20 @@ static void trimming(view_class* param_0, view_port_class* param_1) {
|
||||
GXLoadPosMtxImm(cMtx_getIdentity(), 0);
|
||||
GXClearVtxDesc();
|
||||
GXSetVtxDesc(GX_VA_POS, GX_DIRECT);
|
||||
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, GX_RGBA4, 0);
|
||||
GXSetVtxAttrFmt(GX_VTXFMT0, GX_VA_POS, GX_CLR_RGBA, DUSK_IF_ELSE(GX_F32, GX_RGBA4), 0);
|
||||
GXSetProjection(ortho, GX_ORTHOGRAPHIC);
|
||||
GXSetCurrentMtx(0);
|
||||
GXBegin(GX_QUADS, GX_VTXFMT0, 8);
|
||||
|
||||
#if TARGET_PC
|
||||
GXPosition3s16(0, 0, -5);
|
||||
GXPosition3s16(param_1->width, 0, -5);
|
||||
GXPosition3s16(param_1->width, sc_top, -5);
|
||||
GXPosition3s16(0, sc_top, -5);
|
||||
GXPosition3s16(0, sc_bottom, -5);
|
||||
GXPosition3s16(param_1->width, sc_bottom, -5);
|
||||
GXPosition3s16(param_1->width, param_1->height, -5);
|
||||
GXPosition3s16(0, param_1->height, -5);
|
||||
GXPosition3f32(0, 0, -5);
|
||||
GXPosition3f32(param_1->width, 0, -5);
|
||||
GXPosition3f32(param_1->width, sc_top, -5);
|
||||
GXPosition3f32(0, sc_top, -5);
|
||||
GXPosition3f32(0, sc_bottom, -5);
|
||||
GXPosition3f32(param_1->width, sc_bottom, -5);
|
||||
GXPosition3f32(param_1->width, param_1->height, -5);
|
||||
GXPosition3f32(0, param_1->height, -5);
|
||||
#else
|
||||
GXPosition3s16(0, 0, -5);
|
||||
GXPosition3s16(FB_WIDTH, 0, -5);
|
||||
|
||||
+104
-6
@@ -71,6 +71,7 @@
|
||||
#include <dolphin/dvd.h>
|
||||
|
||||
#include "SDL3/SDL_filesystem.h"
|
||||
#include "SDL3/SDL_misc.h"
|
||||
#include "cxxopts.hpp"
|
||||
#include "d/actor/d_a_movie_player.h"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
@@ -83,6 +84,9 @@
|
||||
#include "f_pc/f_pc_draw.h"
|
||||
#include "tracy/Tracy.hpp"
|
||||
#include <RmlUi/Core.h>
|
||||
#ifdef __APPLE__
|
||||
#include <TargetConditionals.h>
|
||||
#endif
|
||||
|
||||
// --- GLOBALS ---
|
||||
s8 mDoMain::developmentMode = -1;
|
||||
@@ -114,6 +118,31 @@ void dusk::RequestRestart() noexcept {
|
||||
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*) {
|
||||
char buffer[32];
|
||||
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);
|
||||
if (!result) {
|
||||
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) {
|
||||
@@ -536,7 +629,7 @@ int game_main(int argc, char* argv[]) {
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dusk::ConfigPath = CalculateConfigPath();
|
||||
dusk::ConfigPath = calculate_config_path();
|
||||
const auto startupLogLevel = static_cast<AuroraLogLevel>(parsed_arg_options["log-level"].as<uint8_t>());
|
||||
dusk::InitializeFileLogging(dusk::ConfigPath, startupLogLevel);
|
||||
|
||||
@@ -544,13 +637,14 @@ int game_main(int argc, char* argv[]) {
|
||||
ApplyCVarOverrides(parsed_arg_options["cvar"]);
|
||||
dusk::InitializeCrashReporting();
|
||||
EnsureInitialPipelineCache(dusk::ConfigPath);
|
||||
PADSetDefaultMapping(&defaultPadMapping);
|
||||
// TODO: How to handle this?
|
||||
//PADSetDefaultMapping(&defaultPadMapping, PAD_TYPE_STANDARD);
|
||||
|
||||
{
|
||||
const auto configPathString = dusk::ConfigPath.string();
|
||||
const auto configPathString = dusk::ConfigPath.u8string();
|
||||
AuroraConfig config{};
|
||||
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.startFullscreen = dusk::getSettings().video.enableFullscreen;
|
||||
config.windowPosX = -1;
|
||||
@@ -648,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 (dusk::getSettings().backend.isoPath.getValue().empty()) {
|
||||
forcePreLaunchUI = true;
|
||||
|
||||
Reference in New Issue
Block a user