mirror of
https://github.com/TwilitRealm/dusklight
synced 2026-07-31 15:47:17 -04:00
Compare commits
18 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 4bcecb3926 | |||
| bafa6d17df | |||
| 67eea1599a | |||
| 9b0af0bf5c | |||
| 5cf52b8240 | |||
| 92906a3269 | |||
| 4e46c04c91 | |||
| f6e05eb04f | |||
| 63d4ce2e8f | |||
| de44bbaa26 | |||
| 98b203fd26 | |||
| 0c0bc7c9e2 | |||
| ad80933ccf | |||
| 30c36df76b | |||
| 6ded3ac467 | |||
| a6c4419da6 | |||
| 226a1fc482 | |||
| 079c969bb4 |
+44
-13
@@ -90,6 +90,7 @@ option(DUSK_ENABLE_SENTRY_NATIVE "Enable sentry-native crash reporting support"
|
||||
option(DUSK_PACKAGE_INSTALL "Install Dusklight with a Linux-native file structure" OFF)
|
||||
option(DUSK_GFX_DEBUG_GROUPS "Report debug groups to the native graphics API" ${DUSK_GFX_DEBUG_GROUPS_DEFAULT})
|
||||
option(DUSK_ENABLE_CODE_MODS "Enable code mods" OFF)
|
||||
option(DUSK_ENABLE_OPUS "Enable loading Opus audio files for mods" ON)
|
||||
|
||||
set(DUSK_HAS_FUNCHOOK OFF)
|
||||
if (DUSK_ENABLE_CODE_MODS AND (NOT APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Darwin"))
|
||||
@@ -193,24 +194,21 @@ if (DUSK_MOVIE_SUPPORT)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||
if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU")
|
||||
# -Wno-multichar: Multi-character constants ('ABCD') are implementation-defined but all compilers
|
||||
# (CW, GCC, Clang, MSVC) encode them identically in big-endian order.
|
||||
# For >4-char literals (which GCC/Clang truncate to int), use the MULTI_CHAR() macro.
|
||||
# -Wdeprecated-declarations: JSystem uses std::iterator, deprecated in C++17
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -Wno-multichar")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-multichar -Wno-trigraphs -Wno-deprecated-declarations")
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN")
|
||||
set(CMAKE_BUILD_RPATH "$ORIGIN")
|
||||
elseif (APPLE)
|
||||
add_compile_options(-Wno-declaration-after-statement -Wno-non-pod-varargs)
|
||||
set(CMAKE_INSTALL_RPATH "@loader_path")
|
||||
set(CMAKE_BUILD_RPATH "@loader_path")
|
||||
elseif (MSVC)
|
||||
add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/bigobj>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/MP>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/FS>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-multichar>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Wno-trigraphs>
|
||||
$<$<COMPILE_LANGUAGE:CXX>:-Wno-deprecated-declarations>
|
||||
)
|
||||
elseif (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
|
||||
add_compile_options(
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/bigobj>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/MP>
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:/FS>
|
||||
)
|
||||
|
||||
if (NOT DUSK_BUILD_WARNINGS)
|
||||
@@ -225,6 +223,15 @@ elseif (MSVC)
|
||||
add_compile_options($<$<COMPILE_LANGUAGE:C,CXX>:/utf-8>)
|
||||
endif ()
|
||||
|
||||
if (CMAKE_SYSTEM_NAME STREQUAL Linux)
|
||||
set(CMAKE_INSTALL_RPATH "$ORIGIN")
|
||||
set(CMAKE_BUILD_RPATH "$ORIGIN")
|
||||
elseif (APPLE)
|
||||
add_compile_options(-Wno-declaration-after-statement -Wno-non-pod-varargs)
|
||||
set(CMAKE_INSTALL_RPATH "@loader_path")
|
||||
set(CMAKE_BUILD_RPATH "@loader_path")
|
||||
endif ()
|
||||
|
||||
include(FetchContent)
|
||||
|
||||
# Declare all dependencies first so CMake can download them in parallel
|
||||
@@ -248,6 +255,25 @@ FetchContent_Declare(miniz
|
||||
)
|
||||
|
||||
set(_fetch_content_deps cxxopts json miniz)
|
||||
|
||||
if (DUSK_ENABLE_OPUS)
|
||||
message(STATUS "dusklight: Fetching opusfile")
|
||||
|
||||
# Opusfile options
|
||||
SET(OP_DISABLE_HTTP ON CACHE BOOL "" FORCE)
|
||||
SET(OP_DISABLE_DOCS ON CACHE BOOL "" FORCE)
|
||||
SET(OP_DISABLE_EXAMPLES ON CACHE BOOL "" FORCE)
|
||||
|
||||
message(STATUS "dusklight: Fetching opusfile")
|
||||
FetchContent_Declare(
|
||||
opusfile
|
||||
GIT_REPOSITORY https://github.com/xiph/opusfile.git
|
||||
GIT_TAG 6dfd29e7adb87f2e193575fc3fa88cbf1a0b27df
|
||||
)
|
||||
|
||||
list(APPEND _fetch_content_deps opusfile)
|
||||
endif ()
|
||||
|
||||
if (DUSK_HAS_FUNCHOOK)
|
||||
message(STATUS "dusklight: Fetching funchook")
|
||||
# cmake/PatchFunchook.cmake patches funchook's cmake/capstone.cmake.in to inject a
|
||||
@@ -340,6 +366,10 @@ set(GAME_LIBS aurora::core aurora::gx aurora::gd aurora::si aurora::vi aurora::p
|
||||
if (DUSK_HAS_FUNCHOOK)
|
||||
list(APPEND GAME_LIBS funchook-static)
|
||||
endif ()
|
||||
if (DUSK_ENABLE_OPUS)
|
||||
list(APPEND GAME_LIBS OpusFile::opusfile)
|
||||
list(APPEND GAME_COMPILE_DEFS DUSK_OPUS=1)
|
||||
endif ()
|
||||
|
||||
if (DUSK_ENABLE_SENTRY_NATIVE)
|
||||
list(APPEND GAME_LIBS sentry)
|
||||
@@ -611,6 +641,7 @@ if (DUSK_ENABLE_CODE_MODS AND CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR
|
||||
add_subdirectory(mods/ao_mod)
|
||||
add_subdirectory(mods/shadow_mod)
|
||||
add_subdirectory(mods/window_demo)
|
||||
add_subdirectory(mods/audio_mod)
|
||||
endif ()
|
||||
|
||||
if (APPLE)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
#pragma endian big
|
||||
|
||||
#include "std/sys.pat"
|
||||
#include "type/magic.pat"
|
||||
#include "std/array.pat"
|
||||
|
||||
/**
|
||||
* Wrapper for a file-global pointer that allows it to be stored in an array.
|
||||
*/
|
||||
struct Offset<T> {
|
||||
T* offset : u32 [[inline]];
|
||||
};
|
||||
|
||||
struct ItemSoundEffect {
|
||||
u8 mPriority;
|
||||
u8 mVolume;
|
||||
padding[2];
|
||||
u32 mSwBit;
|
||||
float mPitch;
|
||||
};
|
||||
|
||||
struct ItemSequence {
|
||||
u8 mPriority;
|
||||
u8 mVolume;
|
||||
u16 mResourceId;
|
||||
};
|
||||
|
||||
struct ItemStream {
|
||||
u8 mPriority;
|
||||
u8 mVolume;
|
||||
u16 mStreamPanParameters;
|
||||
char* mStreamFilePath[] : u32;
|
||||
};
|
||||
|
||||
struct SoundTableGroupEntry {
|
||||
u8 mTypeId;
|
||||
if (mTypeId == 0x51) {
|
||||
ItemSoundEffect* mOffset : u24 [[inline]];
|
||||
} else if (mTypeId == 0x60) {
|
||||
ItemSequence* mOffset : u24 [[inline]];
|
||||
} else if (mTypeId == 0x70 || mTypeId == 0x71) {
|
||||
ItemStream* mOffset : u24 [[inline]];
|
||||
} else {
|
||||
u24 mOffset;
|
||||
}
|
||||
};
|
||||
|
||||
struct TGroup {
|
||||
u32 mNumItems;
|
||||
padding[4];
|
||||
SoundTableGroupEntry mEntries[mNumItems];
|
||||
};
|
||||
|
||||
struct TSection {
|
||||
u32 mNumGroups;
|
||||
Offset<TGroup> mGroupOffsets[mNumGroups];
|
||||
};
|
||||
|
||||
struct Root {
|
||||
u32 mSectionNumber;
|
||||
Offset<TSection> mSectionOffsets[mSectionNumber];
|
||||
};
|
||||
|
||||
struct THeader {
|
||||
type::Magic<"BST "> mMagic;
|
||||
|
||||
padding[8];
|
||||
Root* mRoot : u32;
|
||||
};
|
||||
|
||||
THeader header @ 0x0;
|
||||
@@ -0,0 +1,228 @@
|
||||
#pragma endian big
|
||||
|
||||
#include "std/sys.pat"
|
||||
#include "type/magic.pat"
|
||||
#include "std/array.pat"
|
||||
|
||||
/*
|
||||
WSYS file
|
||||
The WSYS file contains roughly two sections: WBCT and WINF.
|
||||
WBCT contains the mapping of Wave ID -> wave archives.
|
||||
WINF are the wave archives themselves and their metadata.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Wrapper for a file-global pointer that allows it to be stored in an array.
|
||||
*/
|
||||
struct Offset<T> {
|
||||
T* offset : u32 [[inline]];
|
||||
};
|
||||
|
||||
/**
|
||||
* Format that audio data is in..
|
||||
*/
|
||||
enum WaveFormat : u8 {
|
||||
/**
|
||||
* 16-samples-per-9-bytes custom Nintendo ADPCM.
|
||||
*/
|
||||
ADPCM4 = 0,
|
||||
|
||||
/**
|
||||
* 16-samples-per-5-bytes custom Nintendo ADPCM.
|
||||
*/
|
||||
ADPCM2 = 1,
|
||||
|
||||
/**
|
||||
* 8-bit-per-sample PCM.
|
||||
*/
|
||||
PCM8 = 2,
|
||||
|
||||
/**
|
||||
* 16-bit-per-sample PCM.
|
||||
*/
|
||||
PCM16 = 3,
|
||||
};
|
||||
|
||||
/**
|
||||
* Defines playback info for a single audio "wave".
|
||||
* This data is stored per archive, making it duplicated (except for mAWOffsetStart)
|
||||
*/
|
||||
struct TWave {
|
||||
padding[1]; // unknown
|
||||
WaveFormat mWaveFormat;
|
||||
|
||||
/**
|
||||
* Key (as in like, the musical term) this sample is in.
|
||||
*/
|
||||
u8 mBaseKey;
|
||||
padding[1];
|
||||
|
||||
/**
|
||||
* Sample rate of the audio in Hz.
|
||||
*/
|
||||
float mSampleRate;
|
||||
|
||||
/**
|
||||
* Position where the sample data starts.
|
||||
* This is in the .aw file for the archive containing this TWave.
|
||||
*/
|
||||
u32 mAWOffsetStart;
|
||||
|
||||
/**
|
||||
* Byte length of the sample data.
|
||||
*/
|
||||
u32 mAWLength;
|
||||
|
||||
/**
|
||||
* Indicates whether the sample should loop or not. All bits appear set if so.
|
||||
*/
|
||||
u32 mLoopFlags;
|
||||
|
||||
/**
|
||||
* Audio sample at which the loop starts.
|
||||
*/
|
||||
u32 mLoopStartSample;
|
||||
|
||||
/**
|
||||
* Audio sample at which the loop ends (and goes back to mLoopStartSample).
|
||||
*/
|
||||
u32 mLoopEndSample;
|
||||
|
||||
/**
|
||||
* Total sample count in this wave.
|
||||
*/
|
||||
u32 mSampleCount;
|
||||
|
||||
/**
|
||||
* Last sample for continuing ADPCM decode after loop.
|
||||
*/
|
||||
s16 mpLast;
|
||||
|
||||
/**
|
||||
* Penult sample for continuing ADPCM decode after loop.
|
||||
*/
|
||||
s16 mpPenult;
|
||||
};
|
||||
|
||||
/**
|
||||
* A single wave archive on disk.
|
||||
* These are paired 1:1 with TCtrlScene objects.
|
||||
*/
|
||||
struct TWaveArchive {
|
||||
/**
|
||||
* Filename of the raw sample data on disc. Relative to /Audiores/Waves/
|
||||
*/
|
||||
char mFileName[0x70];
|
||||
|
||||
/**
|
||||
* Amount of waves in this archive.
|
||||
* Matches the count in the paired TCtrl object.
|
||||
*/
|
||||
u32 mWaveCount;
|
||||
|
||||
/**
|
||||
* Offsets to the wave metadata (not sample data) in the WSYS.
|
||||
* These are paired 1:1 to the TCtrlWaves, and the TCtrlWave contains the actual
|
||||
* "Wave ID" used by the game for lookups.
|
||||
*/
|
||||
Offset<TWave> waveOffsets[mWaveCount];
|
||||
};
|
||||
|
||||
/**
|
||||
* Header containing data for wave archives and their metadata.
|
||||
*/
|
||||
struct TWaveArchiveBank {
|
||||
type::Magic<"WINF"> mMagic;
|
||||
|
||||
/**
|
||||
* Amount of archives in this wave bank.
|
||||
* Matches the value in TCtrlGroup.
|
||||
*/
|
||||
u32 mArchiveCounts;
|
||||
Offset<TWaveArchive> mArchiveOffsets[mArchiveCounts];
|
||||
};
|
||||
|
||||
/**
|
||||
* Definition for a single wave in a control group.
|
||||
*/
|
||||
struct TCtrlWave {
|
||||
/**
|
||||
* Group ID matches the index of the control group this item is referenced by.
|
||||
*/
|
||||
u16 mGroupId;
|
||||
|
||||
/**
|
||||
* Wave ID used by the game to look this wave up.
|
||||
*/
|
||||
u16 mWaveId;
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the actual data for a TCtrlScene.
|
||||
* Why is this separate? Who knows.
|
||||
*/
|
||||
struct TCtrl {
|
||||
// Other versions of this struct with different magic (C-EX and C-ST) also exist in the file.
|
||||
// They aren't pointed to so we don't need to worry about them.
|
||||
type::Magic<"C-DF"> mMagic;
|
||||
|
||||
/**
|
||||
* Amount of waves in this group.
|
||||
* Matches the value in TWaveArchive.
|
||||
*/
|
||||
u32 waveCount;
|
||||
Offset<TCtrlWave> mWaveOffsets[waveCount];
|
||||
};
|
||||
|
||||
/**
|
||||
* A single scene or "group" of waves that are loaded at once.
|
||||
*/
|
||||
struct TCtrlScene {
|
||||
type::Magic<"SCNE"> mMagic;
|
||||
|
||||
padding[8]; // unknown
|
||||
TCtrl* mCtrlOffset : u32 [[inline]];
|
||||
};
|
||||
|
||||
/**
|
||||
* Contains the "control" section of the WSYS.
|
||||
*/
|
||||
struct TCtrlGroup {
|
||||
type::Magic<"WBCT"> mMagic;
|
||||
|
||||
padding[4]; // unknown
|
||||
u32 mGroupCount;
|
||||
|
||||
Offset<TCtrlScene> mCtrlSceneOffsets[mGroupCount];
|
||||
};
|
||||
|
||||
struct THeader {
|
||||
type::Magic<"WSYS"> mMagic;
|
||||
|
||||
/**
|
||||
* Size of WSYS in bytes.
|
||||
*/
|
||||
u32 mSize;
|
||||
|
||||
/**
|
||||
* ID of wave bank.
|
||||
* This matches the value passed to the BAA load command.
|
||||
* The game originally does not use this value itself, but Dusklight does rely on it.
|
||||
*/
|
||||
u32 mId;
|
||||
|
||||
/**
|
||||
* Total amount of waves in this wave bank.
|
||||
* (not groups! Waves!)
|
||||
*/
|
||||
u32 mWaveTableSize;
|
||||
|
||||
TWaveArchiveBank* archiveBankOffset : u32;
|
||||
TCtrlGroup* ctrlGroupOffset : u32;
|
||||
};
|
||||
|
||||
THeader header @ 0x0;
|
||||
|
||||
std::assert(
|
||||
header.archiveBankOffset.mArchiveCounts == header.ctrlGroupOffset.mGroupCount,
|
||||
"Control group and archive count does not match!");
|
||||
@@ -0,0 +1,90 @@
|
||||
# JAudio
|
||||
|
||||
**JAudio** is the name for the audio engine used by Twilight Princess (along with many other Nintendo games from the era). TP uses exclusively JAudio v2, while other games use v1 or a mix of v1 and v2 infrastructure. This document will primarily focus on TP's use case, but best efforts will be made to document where behavior is TP-specific.
|
||||
|
||||
## Common concepts
|
||||
|
||||
Audio is almost exclusively
|
||||
|
||||
### `JAISoundID`
|
||||
|
||||
A "sound", be that a _sound effect_ or music, is referenced in-code through the `JAISoundID` type. This is a `u32` that the game uses to look up the relevant sound. The actual value is bitpacked (BE) from the following fields:
|
||||
|
||||
| Type | Field | Description |
|
||||
|-------|------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `u8` | Section ID | Section of the Sound Table this sound effect is located in. 0 for sound effects, 1 for sequenced music, 2 for streamed music.[^sectionids] |
|
||||
| `u8` | Group ID | ID of group further used to organize inside the Sound Table's Section. Sound effects are grouped into things like "SYSTEM" and "ENEMY", other sections leave this at 0. |
|
||||
| `u16` | "Wave ID" | Index inside the group to look up the sound at.<br/>**Note** that the term "wave" in code is extremely confusing[^waveterm]; it does **not** refer to audio samples ("waves") in the wave banks directly. |
|
||||
|
||||
[^sectionids]: JAudio itself can seemingly work outside this convention, however it is enforced by some TP-specific game code.
|
||||
|
||||
[^waveterm]: Possibly vestigial from JAudio v1, where I believe there were less layers of indirection.
|
||||
|
||||
## Disc files
|
||||
|
||||
All audio data is stored in `/Audiores` on the disc. Files are as follows:
|
||||
|
||||
### `/Audiores/Seqs/Z2SoundSeqs.arc`
|
||||
|
||||
Contains the BMS instructions for all BMS-based music and sound effects. Not all data is kept in memory at once.
|
||||
|
||||
### `/Audiores/Stream/*.ast`
|
||||
|
||||
Contains individual streamed music. Each file is a separate music track. See [this page](https://www.lumasworkshop.com/wiki/AST_(File_Format)) for file format description.
|
||||
|
||||
### `/Audiores/Waves/*.aw`
|
||||
|
||||
Contains audio sample data for sequenced music and sound effects. These files are pure meat, no bone: they are loaded directly into ARAM and all metadata is stored in the BAA WSYS sections.
|
||||
|
||||
Each file contains audio samples for one "scene", with factors like the current level determining what "scenes" are made resident in memory. There is tons of duplicate data between scenes, presumably to increase simplicity and loading performance.
|
||||
|
||||
### `/Audiores/Z2Sound.baa`
|
||||
|
||||
Contains all remaining metadata for the audio system. This is effectively a container for a bunch of different sub-sections. The file starts with a bunch of "commands" that indicate where other data in the file is. Each command has a 4-character identifier and depending on the command will be followed by some extra arguments before the next command.
|
||||
|
||||
The commands used by TP's BAA file are as follows (note that the decompiled code has support for more load commands, which are unused):
|
||||
|
||||
| Command/Argument | Value | Description |
|
||||
|------------------|-----------------|-------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Command | `AA_<` | Start of BAA commands. Must be at the start of the file and only appear once. |
|
||||
| Command | `>_AA` | End of BAA commands. |
|
||||
| Command | `ws `[^spaces] | Wave bank/WSYS data. Defines where audio samples are located on disc. |
|
||||
| Argument | u32 | Wave bank ID, max 255. In TP, 0 is sound effects, 1 is music samples. |
|
||||
| Argument | u32 | File offset for start of `WSYS` data |
|
||||
| Argument | u32 | Bit field selecting which groups (= `.aw` files) to load immediately. TP leaves this at zero.[^32ws] |
|
||||
| Command | `bnk `[^spaces] | Instrument bank/IBNK |
|
||||
| Argument | u32 | Target wave bank ID |
|
||||
| Argument | u32 | File offset for start of `IBNK` data |
|
||||
| Command | `bsc `[^spaces] | Sound effect sequence collection. |
|
||||
| Argument | u32 | File offset for start of `SC` data. |
|
||||
| Argument | u32 | File offset for end of `SC` data. |
|
||||
| Command | `bst `[^spaces] | Sound table. Defines parameters for music and sound effects. |
|
||||
| Argument | u32 | File offset for start of `BST `[^spaces] data. |
|
||||
| Argument | u32 | File offset for end of `BST `[^spaces] data. |
|
||||
| Command | `bstn` | Sound name table. Defines names of all music and sound effects. Present on disc, but loading is disabled on release versions of the game. |
|
||||
| Argument | u32 | File offset for start of `BSTN` data. |
|
||||
| Argument | u32 | File offset for end of `BSTN` data. |
|
||||
| Command | `bfca` | Unknown, something related to initialization of DSP FX data. |
|
||||
| Argument | u32 | File offset for start of `RARC` data. |
|
||||
|
||||
[^spaces]: Padded with spaces.
|
||||
|
||||
[^32ws]: Both of TP's wave banks have far more than 32 groups, and seemingly this mechanism would not be able to deal with that.
|
||||
|
||||
Following is data descriptions for the remaining data in the `.BAA`, as pointed to by the above commands.
|
||||
|
||||
### `WSYS` / Wave banks
|
||||
|
||||
`WSYS` / Wave bank data defines where a set of audio samples can be found on disc. Each wave bank is made of multiple "groups", where each group corresponds to one `.aw` file on disc. Each group has a set of "wave IDs" it contains, along with the metadata (e.g. sample rate) and data offset in the `.aw` file.
|
||||
|
||||
Multiple groups can contain the same wave ID, thus meaning the raw audio samples can be duplicated on disk.
|
||||
|
||||
For the actual binary layout of this data, check [the ImHex pattern](imhex/jaudio/wsys.hexpat)
|
||||
|
||||
*Relevant classes: `JASWSParser`, `JASBasicWaveBank`, `JASSimpleWaveBank`.*
|
||||
|
||||
## Further reading & credits
|
||||
|
||||
* https://www.lumasworkshop.com/wiki/SMR.szs (note that details like the exact layout of the BAA are not the same as TP)
|
||||
* XAYRGA for doing much RE work and making [JAMTools](https://xayr.gay/tools/SoundModdingToolkit/)
|
||||
* The decompiled source code, duh.
|
||||
Vendored
+1
-1
Submodule extern/aurora updated: 6c4c27f9e8...0bddb86249
+5
-3
@@ -1485,6 +1485,10 @@ set(DUSK_FILES
|
||||
src/dusk/mods/log_buffer.hpp
|
||||
src/dusk/mods/manifest.cpp
|
||||
src/dusk/mods/manifest.hpp
|
||||
src/dusk/mods/svc/audio_res/audio_res.hpp
|
||||
src/dusk/mods/svc/audio_res/audio_res.cpp
|
||||
src/dusk/mods/svc/audio_res/wave.cpp
|
||||
src/dusk/mods/svc/audio_res/opus.cpp
|
||||
src/dusk/mods/svc/camera.cpp
|
||||
src/dusk/mods/svc/config.cpp
|
||||
src/dusk/mods/svc/config.hpp
|
||||
@@ -1502,8 +1506,6 @@ set(DUSK_FILES
|
||||
src/dusk/mods/svc/ui.hpp
|
||||
src/dusk/mods/svc/window.cpp
|
||||
src/dusk/mods/svc/window.hpp
|
||||
src/dusk/mods/svc/save.cpp
|
||||
src/dusk/mods/svc/save.hpp
|
||||
src/dusk/mouse.cpp
|
||||
src/dusk/scope_guard.hpp
|
||||
src/dusk/settings.cpp
|
||||
@@ -1583,11 +1585,11 @@ set(DUSK_FILES
|
||||
src/dusk/update_check.cpp
|
||||
src/dusk/update_check.hpp
|
||||
src/dusk/version.cpp
|
||||
src/dusk/utilities.cpp
|
||||
src/helpers/batch.cpp
|
||||
src/helpers/endian.cpp
|
||||
src/helpers/offset_ptr.cpp
|
||||
src/helpers/string.cpp
|
||||
src/helpers/cast.cpp
|
||||
)
|
||||
|
||||
set(DUSK_HTTP_BACKEND_FILES
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
#include "JSystem/JAudio2/JAUAudibleParam.h"
|
||||
#include "JSystem/TPosition3.h"
|
||||
|
||||
#define Z2_AUDIO_PLAYERS 1
|
||||
|
||||
struct Z2Audible;
|
||||
|
||||
struct Z2AudibleAbsPos {
|
||||
@@ -49,10 +51,14 @@ struct Z2AudioCamera {
|
||||
f32 getCamDist() const { return mCamDist; }
|
||||
|
||||
|
||||
/* 0x00 */ JGeometry::TPosition3f32 field_0x0;
|
||||
/* 0x00 */ JGeometry::TPosition3f32 mViewMatrix;
|
||||
/* 0x30 */ JGeometry::TVec3<f32> mVel;
|
||||
/* 0x3C */ JGeometry::TVec3<f32> mPos;
|
||||
/* 0x48 */ JGeometry::TVec3<f32> field_0x48;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
/* 0x48 */ JGeometry::TVec3<f32> mLastPos;
|
||||
/* 0x54 */ f32 mFovySin;
|
||||
/* 0x58 */ f32 mVolCenterZ;
|
||||
/* 0x5C */ f32 mTargetVolume;
|
||||
@@ -83,14 +89,14 @@ struct Z2SpotMic {
|
||||
/* 0x04 */ f32 field_0x4;
|
||||
/* 0x08 */ f32 field_0x8;
|
||||
/* 0x0C */ f32 field_0xc;
|
||||
/* 0x10 */ Z2AudioCamera* field_0x10[1];
|
||||
/* 0x10 */ Z2AudioCamera* field_0x10[Z2_AUDIO_PLAYERS];
|
||||
/* 0x14 */ Vec* mPosPtr;
|
||||
/* 0x18 */ f32 field_0x18[1];
|
||||
/* 0x18 */ f32 field_0x18[Z2_AUDIO_PLAYERS];
|
||||
/* 0x1C */ f32 field_0x1c;
|
||||
/* 0x20 */ f32 field_0x20[1];
|
||||
/* 0x20 */ f32 field_0x20[Z2_AUDIO_PLAYERS];
|
||||
/* 0x24 */ bool mIgnoreIfOut;
|
||||
/* 0x25 */ bool mMicOn;
|
||||
/* 0x26 */ bool field_0x26[1];
|
||||
/* 0x26 */ bool field_0x26[Z2_AUDIO_PLAYERS];
|
||||
}; // Size: 0x28
|
||||
|
||||
struct Z2Audience3DSetting {
|
||||
@@ -102,62 +108,99 @@ struct Z2Audience3DSetting {
|
||||
void updateDolbyDist(f32, f32);
|
||||
|
||||
void calcVolumeFactorAll() {
|
||||
field_0x0[1] = 1.25f * field_0x0[0];
|
||||
field_0x0[2] = 1.5f * field_0x0[0];
|
||||
field_0x0[3] = 2.0f * field_0x0[0];
|
||||
field_0x0[4] = 3.0f * field_0x0[0];
|
||||
field_0x0[5] = 4.0f * field_0x0[0];
|
||||
field_0x0[6] = 6.0f * field_0x0[0];
|
||||
field_0x0[7] = 8.0f * field_0x0[0];
|
||||
field_0x0[8] = 0.9f * field_0x0[0];
|
||||
field_0x0[9] = 0.8f * field_0x0[0];
|
||||
field_0x0[10] = 0.7f * field_0x0[0];
|
||||
field_0x0[11] = 0.6f * field_0x0[0];
|
||||
field_0x0[12] = 0.5f * field_0x0[0];
|
||||
field_0x0[13] = 0.4f * field_0x0[0];
|
||||
field_0x0[14] = 0.3f * field_0x0[0];
|
||||
mDistanceMaxes[1] = 1.25f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[2] = 1.5f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[3] = 2.0f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[4] = 3.0f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[5] = 4.0f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[6] = 6.0f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[7] = 8.0f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[8] = 0.9f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[9] = 0.8f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[10] = 0.7f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[11] = 0.6f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[12] = 0.5f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[13] = 0.4f * mDistanceMaxes[0];
|
||||
mDistanceMaxes[14] = 0.3f * mDistanceMaxes[0];
|
||||
for (int i = 0; i < 15; i++) {
|
||||
field_0x70[i] = (field_0x40 - 1.0f) / (field_0x0[i] - field_0x3c);
|
||||
mVolumeFactor[i] = (mMinDistanceVolume - 1.0f) / (mDistanceMaxes[i] - mMaxVolumeDistance);
|
||||
}
|
||||
}
|
||||
|
||||
void calcPriorityFactorAll() {
|
||||
for (int i = 0; i < 15; i++) {
|
||||
field_0xac[i] = field_0x64 / (field_0x0[i] - field_0x3c);
|
||||
mPriorityFactor[i] = mMaxDistancePriority / (mDistanceMaxes[i] - mMaxVolumeDistance);
|
||||
}
|
||||
}
|
||||
|
||||
void calcFxMixFactorAll() {
|
||||
for (int i = 0; i < 15; i++) {
|
||||
field_0xe8[i] = (field_0x54 - field_0x50) / (field_0x0[i] - field_0x3c);
|
||||
mFxMixFactor[i] = (mMaxDistanceFxMix - mMinDistanceFxMix) / (mDistanceMaxes[i] - mMaxVolumeDistance);
|
||||
}
|
||||
}
|
||||
|
||||
/* 0x000 */ f32 field_0x0[15];
|
||||
/* 0x03C */ f32 field_0x3c;
|
||||
/* 0x040 */ f32 field_0x40;
|
||||
/* 0x044 */ f32 field_0x44;
|
||||
/* 0x048 */ f32 field_0x48;
|
||||
/* 0x04C */ f32 field_0x4c;
|
||||
/* 0x050 */ f32 field_0x50;
|
||||
/* 0x054 */ f32 field_0x54;
|
||||
/* 0x058 */ f32 field_0x58;
|
||||
/* 0x05C */ f32 field_0x5c;
|
||||
/**
|
||||
* Maximum distance a sound can reach before being "far away"
|
||||
* Being far away affects stuff like culling, lowering its priority, forcibly stopping it, etc.
|
||||
* Sounds select which entry they use based on their VolBits.
|
||||
*/
|
||||
/* 0x000 */ f32 mDistanceMaxes[15];
|
||||
|
||||
/**
|
||||
* Distance at which the max volume of a sound is reached.
|
||||
* i.e. sounds do *not* get louder if they get closer than this.
|
||||
*/
|
||||
/* 0x03C */ f32 mMaxVolumeDistance;
|
||||
|
||||
/**
|
||||
* FX Mix value at maximum distance (@ref mDistanceMaxes)
|
||||
*/
|
||||
/* 0x040 */ f32 mMinDistanceVolume;
|
||||
/* 0x044 */ f32 mDolbyFrontDistanceMax;
|
||||
/* 0x048 */ f32 mDolbyBehindDistanceMax;
|
||||
/* 0x04C */ f32 mDolbyCenterValue;
|
||||
|
||||
/**
|
||||
* FX Mix value at minimum distance (@ref mMaxVolumeDistance)
|
||||
*/
|
||||
/* 0x050 */ f32 mMinDistanceFxMix;
|
||||
|
||||
/**
|
||||
* FX Mix value at maximum distance (@ref mDistanceMaxes)
|
||||
*/
|
||||
/* 0x054 */ f32 mMaxDistanceFxMix;
|
||||
/* 0x058 */ f32 mPanFactor;
|
||||
/* 0x05C */ f32 mSonicSpeed; // Used for doppler effect calculations.
|
||||
/* 0x060 */ f32 field_0x60;
|
||||
/* 0x064 */ u32 field_0x64;
|
||||
|
||||
/**
|
||||
* Priority that sounds receive when "far away".
|
||||
* @see mDistanceMaxes
|
||||
*/
|
||||
/* 0x064 */ u32 mMaxDistancePriority;
|
||||
/* 0x068 */ f32 field_0x68;
|
||||
/* 0x06C */ f32 field_0x6c;
|
||||
/* 0x070 */ f32 field_0x70[15];
|
||||
/* 0x0AC */ f32 field_0xac[15];
|
||||
/* 0x0E8 */ f32 field_0xe8[15];
|
||||
/* 0x070 */ f32 mVolumeFactor[15];
|
||||
/* 0x0AC */ f32 mPriorityFactor[15];
|
||||
/* 0x0E8 */ f32 mFxMixFactor[15];
|
||||
/* 0x124 */ bool mVolumeDistInit;
|
||||
/* 0x125 */ bool mDolbyDistInit;
|
||||
}; // Size: 0x128
|
||||
|
||||
struct Z2AudibleRelPos {
|
||||
/* 0x00 */ JGeometry::TVec3<f32> field_0x00;
|
||||
/* 0x0C */ f32 field_0xC;
|
||||
/* 0x10 */ f32 field_0x10;
|
||||
/* 0x00 */ JGeometry::TVec3<f32> mCameraRelative;
|
||||
|
||||
/**
|
||||
* Distance from mCameraRelative. This is from the object root and not the
|
||||
* exact distance used for volume/priority calculations.
|
||||
*/
|
||||
/* 0x0C */ f32 mTrueDistance;
|
||||
|
||||
/**
|
||||
* Distance from mCameraRelative but offset by mVolCenterZ.
|
||||
* This presumably means the distance is more centered on the object than mTrueDistance.
|
||||
*/
|
||||
/* 0x10 */ f32 mCenterDistance;
|
||||
};
|
||||
|
||||
struct Z2AudibleChannel {
|
||||
@@ -171,7 +214,7 @@ struct Z2AudibleChannel {
|
||||
}
|
||||
|
||||
/* 0x00 */ JASSoundParams mParams;
|
||||
/* 0x14 */ Z2AudibleRelPos field_0x14;
|
||||
/* 0x14 */ Z2AudibleRelPos mRelPos;
|
||||
/* 0x28 */ f32 field_0x28;
|
||||
/* 0x2c */ f32 mPan;
|
||||
/* 0x30 */ f32 mDolby;
|
||||
@@ -200,8 +243,8 @@ struct Z2Audible : public JAIAudible, public JASPoolAllocObject<Z2Audible> {
|
||||
|
||||
/* 0x10 */ JAUAudibleParam mParam;
|
||||
/* 0x14 */ Z2AudibleAbsPos mAbsPos;
|
||||
/* 0x2C */ Z2AudibleChannel mChannel[1];
|
||||
/* 0x64 */ f32 field_0x64[1];
|
||||
/* 0x2C */ Z2AudibleChannel mChannel[Z2_AUDIO_PLAYERS];
|
||||
/* 0x64 */ f32 mMicDistances[Z2_AUDIO_PLAYERS];
|
||||
};
|
||||
|
||||
struct Z2Audience : public JAIAudience, public JASGlobalInstance<Z2Audience> {
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
namespace dusk::helpers::alignment {
|
||||
|
||||
/**
|
||||
* Read data from an address that may not be aligned properly.
|
||||
* @tparam T Type of data to read.
|
||||
* @param ptr Address to read from.
|
||||
* @return The copied value.
|
||||
*/
|
||||
template <typename T> requires std::is_trivially_copyable_v<T>
|
||||
[[nodiscard]] constexpr T read_unaligned(u8 const* ptr) {
|
||||
T copy;
|
||||
memcpy(©, ptr, sizeof(T));
|
||||
return copy;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
#pragma once
|
||||
|
||||
#include <limits>
|
||||
#include <utility>
|
||||
#include <span>
|
||||
|
||||
/**
|
||||
* Helper functions for performing casts.
|
||||
*/
|
||||
namespace dusk::helpers::cast {
|
||||
/**
|
||||
* Implementation details of dusk::helpers::cast.
|
||||
*/
|
||||
namespace _impl {
|
||||
[[noreturn]] void overrun_high();
|
||||
[[noreturn]] void overrun_low();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
concept IntCastable = std::is_integral_v<T> && std::is_trivially_copyable_v<T>;
|
||||
|
||||
/**
|
||||
* Helper type that allows easily casting between integer types,
|
||||
* that safely aborts if an overflow were to occur.
|
||||
* Usage:
|
||||
* @code
|
||||
* size_t foobar = 20;
|
||||
* int real = bounded_cast(foobar);
|
||||
* @endcode
|
||||
*/
|
||||
template <IntCastable Source>
|
||||
struct bounded_cast {
|
||||
Source src;
|
||||
|
||||
template <IntCastable Target>
|
||||
[[nodiscard]] constexpr operator Target() const {
|
||||
if (std::cmp_greater(src, std::numeric_limits<Target>::max())) [[unlikely]] {
|
||||
_impl::overrun_high();
|
||||
}
|
||||
|
||||
if (std::cmp_less(src, std::numeric_limits<Target>::min())) [[unlikely]] {
|
||||
_impl::overrun_low();
|
||||
}
|
||||
|
||||
return static_cast<Target>(src);
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -285,6 +285,7 @@ inline void be_swap(Mtx& val) {
|
||||
}
|
||||
}
|
||||
|
||||
#define LE(T) T
|
||||
#define BE(T) BE<T>
|
||||
#define BE_HOST(T) (T.host())
|
||||
#else
|
||||
|
||||
@@ -101,7 +101,7 @@ struct JAISoundStatus_ {
|
||||
bool isMute() const { return field_0x0.flags.mute; }
|
||||
bool isPaused() const { return field_0x0.flags.paused; }
|
||||
void pauseWhenOut() {
|
||||
field_0x1.flags.flag3 = 1;
|
||||
field_0x1.flags.mPauseWhenOut = 1;
|
||||
}
|
||||
|
||||
/* 0x0 */ union {
|
||||
@@ -120,9 +120,9 @@ struct JAISoundStatus_ {
|
||||
/* 0x1 */ union {
|
||||
u8 value;
|
||||
struct {
|
||||
u8 flag1 : 1;
|
||||
u8 mComesBack : 1;
|
||||
u8 flag2 : 1;
|
||||
u8 flag3 : 1;
|
||||
u8 mPauseWhenOut : 1;
|
||||
u8 flag4 : 1;
|
||||
u8 flag5 : 1;
|
||||
u8 flag6 : 1;
|
||||
@@ -309,7 +309,7 @@ public:
|
||||
bool hasLifeTime() const { return status_.field_0x1.flags.flag2; }
|
||||
|
||||
void removeLifeTime_() {
|
||||
status_.field_0x1.flags.flag1 = false;
|
||||
status_.field_0x1.flags.mComesBack = false;
|
||||
status_.field_0x1.flags.flag2 = 0;
|
||||
}
|
||||
|
||||
@@ -346,7 +346,7 @@ public:
|
||||
|
||||
void setComesBack(bool param_0) {
|
||||
JUT_ASSERT(354, status_.state.flags.calcedOnce == 0);
|
||||
status_.field_0x1.flags.flag1 = 1;
|
||||
status_.field_0x1.flags.mComesBack = 1;
|
||||
if (param_0) {
|
||||
status_.pauseWhenOut();
|
||||
}
|
||||
|
||||
@@ -9,14 +9,14 @@
|
||||
*/
|
||||
struct JAISoundParamsProperty {
|
||||
void init() {
|
||||
field_0x0 = 1.0f;
|
||||
field_0x4 = 0.0f;
|
||||
field_0x8 = 1.0f;
|
||||
mVolume = 1.0f;
|
||||
mFxMix = 0.0f;
|
||||
mPitch = 1.0f;
|
||||
}
|
||||
|
||||
/* 0x00 */ f32 field_0x0;
|
||||
/* 0x04 */ f32 field_0x4;
|
||||
/* 0x08 */ f32 field_0x8;
|
||||
/* 0x00 */ f32 mVolume;
|
||||
/* 0x04 */ f32 mFxMix;
|
||||
/* 0x08 */ f32 mPitch;
|
||||
}; // Size: 0xC
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,21 +12,27 @@ struct JASBasicWaveBank : public JASWaveBank {
|
||||
struct TWaveHandle : public JASWaveHandle {
|
||||
TWaveHandle() { mHeap = NULL; }
|
||||
virtual intptr_t getWavePtr() const;
|
||||
virtual const JASWaveInfo* getWaveInfo() const { return &field_0x4; }
|
||||
virtual const JASWaveInfo* getWaveInfo() const { return &mWaveInfo; }
|
||||
#if TARGET_PC
|
||||
/**
|
||||
* @see JASChannel::mAramBaseAddress
|
||||
*/
|
||||
[[nodiscard]] void const* getAramBaseAddress() const override { return nullptr; }
|
||||
#endif
|
||||
bool compareHeap(JASHeap* heap) const { return mHeap == heap;}
|
||||
|
||||
/* 0x04 */ JASWaveInfo field_0x4;
|
||||
/* 0x04 */ JASWaveInfo mWaveInfo;
|
||||
/* 0x28 */ JASHeap* mHeap;
|
||||
};
|
||||
|
||||
struct TGroupWaveInfo {
|
||||
TGroupWaveInfo() {
|
||||
field_0x0 = 0xffff;
|
||||
field_0x4 = -1;
|
||||
waveId = 0xffff;
|
||||
mOffsetStart = -1;
|
||||
}
|
||||
|
||||
/* 0x00 */ u16 field_0x0;
|
||||
/* 0x04 */ int field_0x4;
|
||||
/* 0x00 */ u16 waveId;
|
||||
/* 0x04 */ int mOffsetStart;
|
||||
};
|
||||
|
||||
struct TWaveGroup : JASWaveArc {
|
||||
@@ -44,7 +50,7 @@ struct JASBasicWaveBank : public JASWaveBank {
|
||||
u32 getWaveCount() const { return mWaveCount; }
|
||||
};
|
||||
|
||||
JASBasicWaveBank();
|
||||
JASBasicWaveBank(IF_DUSK(u32 bankId));
|
||||
~JASBasicWaveBank();
|
||||
TWaveGroup* getWaveGroup(u32);
|
||||
void setGroupCount(u32, JKRHeap*);
|
||||
@@ -56,7 +62,7 @@ struct JASBasicWaveBank : public JASWaveBank {
|
||||
JASWaveArc* getWaveArc(u32 param_0) { return getWaveGroup(param_0); }
|
||||
u32 getArcCount() const { return mGroupCount; }
|
||||
|
||||
/* 0x04 */ OSMutex field_0x4;
|
||||
/* 0x04 */ OSMutex mWaveTableMutex;
|
||||
/* 0x1C */ TWaveHandle* mWaveTable;
|
||||
/* 0x20 */ TWaveGroup* mWaveGroupArray;
|
||||
/* 0x24 */ u16 mHandleCount;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "JSystem/JAudio2/JASWaveInfo.h"
|
||||
#include "JSystem/JAudio2/JASDSPInterface.h"
|
||||
#include <os.h>
|
||||
#include <memory>
|
||||
|
||||
struct JASDSPChannel;
|
||||
|
||||
@@ -15,6 +16,20 @@ namespace JASDsp {
|
||||
struct TChannel;
|
||||
}
|
||||
|
||||
#if TARGET_PC
|
||||
/**
|
||||
* An object to manage the lifetime of the sample data pointed to by JASChannel.
|
||||
*
|
||||
* JASChannel can (optionally) accept an unique_ptr to an object to this type,
|
||||
* in which case it will be destroyed when the JASChannel is done.
|
||||
*
|
||||
* @see JASChannel::mSampleReference
|
||||
*/
|
||||
struct JASSampleDataReference {
|
||||
virtual ~JASSampleDataReference() = default;
|
||||
};
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @ingroup jsystem-jaudio
|
||||
*
|
||||
@@ -166,6 +181,23 @@ public:
|
||||
u32 field_0x104;
|
||||
};
|
||||
|
||||
#if TARGET_PC
|
||||
/**
|
||||
* Memory address at which this sound effect should consider ARAM to start.
|
||||
*
|
||||
* By changing this, sound effects can effectively be played back from anywhere in memory,
|
||||
* rather than just the emulated ARAM space.
|
||||
*
|
||||
* If nullptr, the regular emulated ARAM is used.
|
||||
*/
|
||||
void const* mAramBaseAddress;
|
||||
|
||||
/**
|
||||
* @see JASSampleDataReference
|
||||
*/
|
||||
std::unique_ptr<JASSampleDataReference> mSampleReference;
|
||||
#endif
|
||||
|
||||
static DUSK_GAME_DATA OSMessageQueue sBankDisposeMsgQ;
|
||||
static DUSK_GAME_DATA OSMessage sBankDisposeMsg[16];
|
||||
static DUSK_GAME_DATA OSMessage sBankDisposeList[16];
|
||||
|
||||
@@ -109,7 +109,9 @@ namespace JASDsp {
|
||||
* Pitch shift via changing playback speed.
|
||||
*/
|
||||
/* 0x004 */ u16 mPitch;
|
||||
#if !TARGET_PC
|
||||
/* 0x006 */ short _unused1;
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Set to 1 when playback starts, cleared by DSP later,
|
||||
@@ -118,19 +120,27 @@ namespace JASDsp {
|
||||
* (Corroborated by fields JASAramStream checks never being cleared explicitly by CPU.)
|
||||
*/
|
||||
/* 0x008 */ u16 mResetFlag;
|
||||
#if !TARGET_PC
|
||||
/* 0x00A */ u8 _unused2[0x00C - 0x00A];
|
||||
#endif
|
||||
/* 0x00C */ s16 mPauseFlag;
|
||||
#if !TARGET_PC
|
||||
/* 0x00E */ short _unused3;
|
||||
#endif
|
||||
/* 0x010 */ OutputChannelConfig mOutputChannels[DSP_OUTPUT_CHANNELS];
|
||||
#if !TARGET_PC
|
||||
/* 0x040 */ u8 _unused4[0x050 - 0x040];
|
||||
#endif
|
||||
/* 0x050 */ u16 mAutoMixerPanDolby; // pan is upper 8 bits, dolby lower 8.
|
||||
/* 0x052 */ u16 mAutoMixerFxMix;
|
||||
/* 0x054 */ u16 mAutoMixerInitVolume;
|
||||
/* 0x056 */ u16 mAutoMixerVolume;
|
||||
/* 0x058 */ u16 mAutoMixerBeenSet;
|
||||
#if !TARGET_PC
|
||||
/* 0x05A */ u8 _unused5[0x060 - 0x05A];
|
||||
/* 0x060 */ short field_0x060; // Only cleared to zero, presumed used by DSP.
|
||||
/* 0x062 */ u8 _unused6[0x064 - 0x062];
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Samples per ADPCM frame for ADPCM audio. Seems just set to 1 for PCM formats.
|
||||
@@ -139,7 +149,9 @@ namespace JASDsp {
|
||||
/* 0x064 */ u16 mSamplesPerBlock;
|
||||
/* 0x066 */ short field_0x066; // Only cleared to zero, presumed used by DSP.
|
||||
/* 0x068 */ u32 mSamplePosition; // Only ever initialized by code, name is guess.
|
||||
#if !TARGET_PC
|
||||
/* 0x06C */ u8 _unused7[0x070 - 0x06C];
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Current audio read position in ARAM. Updated by DSP.
|
||||
@@ -151,11 +163,13 @@ namespace JASDsp {
|
||||
* Gets written by DSP, but also CPU.
|
||||
*/
|
||||
/* 0x074 */ u32 mSamplesLeft; // Never directly cleared to zero. Seems sus. Cleared by DSP?
|
||||
#if !TARGET_PC
|
||||
/* 0x078 */ short field_0x078[4]; // Only cleared to zero, presumed used by DSP.
|
||||
/* 0x080 */ short field_0x080[20]; // Only cleared to zero, presumed used by DSP.
|
||||
/* 0x0A8 */ short field_0x0a8[4]; // Only cleared to zero, presumed used by DSP.
|
||||
/* 0x0B0 */ u16 field_0x0b0[16]; // Only cleared to zero, presumed used by DSP.
|
||||
/* 0x0D0 */ u8 _unused8[0x100 - 0x0D0];
|
||||
#endif
|
||||
/* 0x100 */ u16 mBytesPerBlock;
|
||||
/* 0x102 */ u16 mLoopFlag;
|
||||
|
||||
@@ -176,9 +190,26 @@ namespace JASDsp {
|
||||
/* 0x118 */ u32 mWaveAramAddress;
|
||||
/* 0x11C */ int mSampleCount;
|
||||
/* 0x120 */ s16 fir_filter_params[8];
|
||||
#if !TARGET_PC
|
||||
/* 0x130 */ u8 _unused9[0x148 - 0x130];
|
||||
#endif
|
||||
/* 0x148 */ s16 iir_filter_params[8];
|
||||
#if !TARGET_PC
|
||||
/* 0x158 */ u8 _unused10[0x180 - 0x158];
|
||||
#endif
|
||||
|
||||
#if TARGET_PC
|
||||
/**
|
||||
* Memory address at which this sound effect should consider ARAM to start.
|
||||
* This means fields like mWaveAramAddress will be relative to this pointer instead.
|
||||
*
|
||||
* By changing this, sound effects can effectively be played back from anywhere in memory,
|
||||
* rather than just the emulated ARAM space.
|
||||
*
|
||||
* If nullptr, the regular emulated ARAM is used.
|
||||
*/
|
||||
void const* mAramBaseAddress;
|
||||
#endif
|
||||
};
|
||||
|
||||
void boot(void (*)(void*));
|
||||
|
||||
@@ -10,12 +10,18 @@ struct JASSimpleWaveBank : JASWaveBank, JASWaveArc {
|
||||
intptr_t getWavePtr() const;
|
||||
TWaveHandle();
|
||||
const JASWaveInfo* getWaveInfo() const;
|
||||
#if TARGET_PC
|
||||
/**
|
||||
* @see JASChannel::mAramBaseAddress
|
||||
*/
|
||||
[[nodiscard]] void const* getAramBaseAddress() const override { return nullptr; }
|
||||
#endif
|
||||
|
||||
/* 0x04 */ JASWaveInfo mWaveInfo;
|
||||
/* 0x28 */ JASHeap* mHeap;
|
||||
};
|
||||
|
||||
JASSimpleWaveBank();
|
||||
JASSimpleWaveBank(IF_DUSK(u32 bankId));
|
||||
~JASSimpleWaveBank();
|
||||
void setWaveTableSize(u32, JKRHeap*);
|
||||
JASWaveHandle* getWaveHandle(u32) const;
|
||||
|
||||
@@ -26,7 +26,9 @@ public:
|
||||
};
|
||||
|
||||
struct TCtrlWave {
|
||||
/* 0x0 */ BE(u32) _00;
|
||||
// Wave ID in lower half.
|
||||
// Upper bits appear to be group ID, which is unused by the game code.
|
||||
/* 0x0 */ BE(u32) mWaveAndGroupId;
|
||||
};
|
||||
|
||||
struct TWave {
|
||||
@@ -57,13 +59,14 @@ public:
|
||||
};
|
||||
|
||||
struct TCtrl {
|
||||
/* 0x0 */ u8 _00[4];
|
||||
/* 0x0 */ BE(u32) mMagic; // 'C-DF'.
|
||||
/* 0x4 */ BE(u32) mWaveCount;
|
||||
/* 0x8 */ TOffset<TCtrlWave> mCtrlWaveOffsets[0];
|
||||
};
|
||||
|
||||
struct TCtrlScene {
|
||||
/* 0x0 */ u8 _00[0xC];
|
||||
/* 0x0 */ BE(u32) mMagic; // 'SCNE'
|
||||
/* 0x4 */ u8 _00[0x8];
|
||||
/* 0xC */ TOffset<TCtrl> mCtrlOffset;
|
||||
};
|
||||
|
||||
|
||||
@@ -66,7 +66,7 @@ struct JASWaveArc : JASDisposer {
|
||||
};
|
||||
|
||||
/* 0x04 */ mutable JASHeap mHeap;
|
||||
/* 0x48 */ u32 _48;
|
||||
/* 0x48 */ u32 mCurrentlyLoaded;
|
||||
/* 0x4C */ volatile s32 mStatus;
|
||||
/* 0x50 */ int mEntryNum;
|
||||
/* 0x54 */ u32 mFileLength;
|
||||
|
||||
@@ -2,6 +2,11 @@
|
||||
#define JASWAVEINFO_H
|
||||
|
||||
#include <types.h>
|
||||
#include <memory>
|
||||
|
||||
#if TARGET_PC
|
||||
struct JASSampleDataReference;
|
||||
#endif
|
||||
|
||||
struct JASWaveArc;
|
||||
|
||||
@@ -17,7 +22,7 @@ struct JASWaveArc;
|
||||
struct JASWaveInfo {
|
||||
JASWaveInfo() {
|
||||
mBaseKey = 0x3c;
|
||||
field_0x20 = &one;
|
||||
mpLoaded = &one;
|
||||
}
|
||||
|
||||
/* 0x00 */ u8 mWaveFormat;
|
||||
@@ -31,7 +36,7 @@ struct JASWaveInfo {
|
||||
/* 0x18 */ int mSampleCount;
|
||||
/* 0x1C */ s16 mpLast;
|
||||
/* 0x1E */ s16 mpPenult;
|
||||
/* 0x20 */ const u32* field_0x20;
|
||||
/* 0x20 */ const u32* mpLoaded;
|
||||
|
||||
static DUSK_GAME_DATA u32 one;
|
||||
};
|
||||
@@ -45,6 +50,17 @@ public:
|
||||
virtual ~JASWaveHandle() {}
|
||||
virtual const JASWaveInfo* getWaveInfo() const = 0;
|
||||
virtual intptr_t getWavePtr() const = 0;
|
||||
#if TARGET_PC
|
||||
/**
|
||||
* @see JASChannel::mAramBaseAddress
|
||||
*/
|
||||
[[nodiscard]] virtual void const* getAramBaseAddress() const = 0;
|
||||
|
||||
/**
|
||||
* Create a @ref JASSampleDataReference to keep the sample data for this wave alive.
|
||||
*/
|
||||
[[nodiscard]] virtual std::unique_ptr<JASSampleDataReference> getSampleReference() const { return nullptr; }
|
||||
#endif
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -53,6 +69,12 @@ public:
|
||||
*/
|
||||
class JASWaveBank {
|
||||
public:
|
||||
#if TARGET_PC
|
||||
const u32 bankId;
|
||||
|
||||
JASWaveBank(u32 bankId) : bankId(bankId) { }
|
||||
#endif
|
||||
|
||||
virtual ~JASWaveBank() {}
|
||||
virtual JASWaveHandle* getWaveHandle(u32) const = 0;
|
||||
virtual JASWaveArc* getWaveArc(u32) = 0;
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
*/
|
||||
struct JAUAudibleParam {
|
||||
f32 getDopplerPower() const {
|
||||
return field_0x0.bytes.b0_0 * (1.0f / 15.0f);
|
||||
return field_0x0.bytes.mDopplerPower * (1.0f / 15.0f);
|
||||
}
|
||||
|
||||
union {
|
||||
@@ -19,14 +19,18 @@ struct JAUAudibleParam {
|
||||
BE(u16) f1;
|
||||
} half;
|
||||
struct {
|
||||
u8 b0_0 : 4;
|
||||
u8 b0_4 : 1;
|
||||
u8 b0_5 : 1;
|
||||
u8 b0_6 : 1;
|
||||
u8 b0_7 : 1;
|
||||
u8 b1_0 : 1;
|
||||
u8 b1_1 : 1;
|
||||
u8 b1_2_7 : 6;
|
||||
u8 mDopplerPower : 4;
|
||||
u8 mCalculatePriority : 1;
|
||||
u8 mCalcDistanceVolume : 1;
|
||||
u8 mCalcFxMix : 1;
|
||||
u8 mCullAtMaxDistance : 1;
|
||||
u8 mCalcPan : 1;
|
||||
u8 mCalcDolby : 1;
|
||||
/**
|
||||
* Most bits in this field are unused, 8 indicates clamping of volume
|
||||
* to ensure it doesn't go below 0.2 in mixChannelOut()
|
||||
*/
|
||||
u8 mClampMinVolume : 6;
|
||||
u8 b2;
|
||||
u8 b3;
|
||||
} bytes;
|
||||
|
||||
@@ -5,16 +5,162 @@
|
||||
#include "JSystem/JAudio2/JASGadget.h"
|
||||
#include "helpers/endian.h"
|
||||
|
||||
// Constants for TypeIDs of JAUSoundTable entries.
|
||||
|
||||
/**
|
||||
* Used as a test to see if the type ID is valid. All other type IDs contain this bit.
|
||||
*/
|
||||
#define SOUND_TYPEID_VALID 0x40
|
||||
|
||||
/**
|
||||
* Sound is a sound effect.
|
||||
*/
|
||||
#define SOUND_TYPEID_SOUND_EFFECT 0x51
|
||||
|
||||
/**
|
||||
* Sound is a music sequence.
|
||||
*/
|
||||
#define SOUND_TYPEID_SEQUENCE 0x60
|
||||
|
||||
/**
|
||||
* Sound is a streamed music file.
|
||||
*/
|
||||
#define SOUND_TYPEID_STREAM 0x70
|
||||
|
||||
/**
|
||||
* Sound is a streamed music file. Unsure of difference from SOUND_TYPEID_STREAM
|
||||
*/
|
||||
#define SOUND_TYPEID_STREAM_ALT 0x71
|
||||
|
||||
//
|
||||
// Values for mSwBit on sound effect items.
|
||||
//
|
||||
|
||||
/**
|
||||
* Sound is always calculated as max priority (0).
|
||||
*/
|
||||
#define SOUND_SW_ALWAYS_MAX_PRIORITY 0x0000'0001
|
||||
|
||||
/**
|
||||
* Don't calculate volume by distance.
|
||||
*/
|
||||
#define SOUND_SW_IGNORE_DISTANCE_VOL 0x0000'0002
|
||||
|
||||
/**
|
||||
* Don't calculate FX mix (reverb) by distance.
|
||||
*/
|
||||
#define SOUND_SW_IGNORE_FX_MIX 0x0000'0004
|
||||
|
||||
/**
|
||||
* Offset to shift to access @see SOUND_SW_RANDOM_PITCH_MASK
|
||||
*/
|
||||
#define SOUND_SW_RANDOM_PITCH_OFFSET 4
|
||||
|
||||
/**
|
||||
* 4-bit value (0-15) to control the power of pitch randomization on sound playback.
|
||||
* Code acts different for values above 8, not sure what the exact implication is.
|
||||
*/
|
||||
#define SOUND_SW_RANDOM_PITCH_MASK 0x0000'00F0
|
||||
|
||||
/**
|
||||
* Offset to shift to access @see SOUND_SW_DOPPLER_POWER_MASK
|
||||
*/
|
||||
#define SOUND_SW_DOPPLER_POWER_OFFSET 8
|
||||
|
||||
/**
|
||||
* 4-bit value (0-15) to scale the power of the Doppler effect for this sound.
|
||||
*/
|
||||
#define SOUND_SW_DOPPLER_POWER_MASK 0x0000'0F00
|
||||
|
||||
/**
|
||||
* Don't calculate panning (left/right) values for this sound.
|
||||
*/
|
||||
#define SOUND_SW_IGNORE_PAN 0x0000'1000
|
||||
|
||||
/**
|
||||
* Don't calculate Dolby (behind/front) values for this sound.
|
||||
*/
|
||||
#define SOUND_SW_IGNORE_DOLBY 0x0000'2000
|
||||
|
||||
/**
|
||||
* 3-bit mask used to select a volume distance/falloff class for this sound.
|
||||
*/
|
||||
#define SOUND_SW_VOL_DIST_BIT_MASK 0x0007'0000
|
||||
|
||||
/**
|
||||
* Limit minimum volume of this sound (after distance falloff) to 0.2.
|
||||
*/
|
||||
#define SOUND_SW_CLAMP_MIN_VOLUME 0x0008'0000
|
||||
|
||||
/**
|
||||
* 3-bit mask used to select a *different* volume distance/falloff class for this sound.
|
||||
* @see SOUND_SW_VOL_DIST_BIT_MASK must be zero for this to work.
|
||||
*/
|
||||
#define SOUND_SW_VOL_DIST_BIT_2_MASK 0x0070'0000
|
||||
|
||||
/**
|
||||
* Mark sound as "far away" or "culled" when at max distance (selected by distance class).
|
||||
* This affects a bunch of stuff like culling, automatic stopping, priorities, etc.
|
||||
*/
|
||||
#define SOUND_SW_CULL_AT_MAX_DISTANCE 0x0080'0000
|
||||
|
||||
/**
|
||||
* Not sure what this does.
|
||||
* Something causing volume/pan/dolby adjustment in Z2Audible::setOuterParams?
|
||||
*/
|
||||
#define SOUND_SW_VOL_SOMETHING_MASK 0x0F00'0000
|
||||
|
||||
/**
|
||||
* Offset to shift to access @see SOUND_SW_RANDOM_VOLUME_MASK
|
||||
*/
|
||||
#define SOUND_SW_RANDOM_VOLUME_OFFSET 28
|
||||
|
||||
/**
|
||||
* 4-bit value (0-15) to control the power of volume randomization on sound playback.
|
||||
*/
|
||||
#define SOUND_SW_RANDOM_VOLUME_MASK 0xF000'0000
|
||||
|
||||
#define SOUND_VOL_DIST_BIT_MASK_SHIFTED (SOUND_SW_VOL_DIST_BIT_MASK >> 16)
|
||||
#define SOUND_VOL_DIST_BIT_2_MASK_SHIFTED (SOUND_SW_VOL_DIST_BIT_2_MASK >> 16)
|
||||
#define SOUND_VOL_SOMETHING_MASK_SHIFTED (SOUND_SW_VOL_SOMETHING_MASK >> 16)
|
||||
|
||||
/**
|
||||
* @ingroup jsystem-jaudio
|
||||
*
|
||||
* A single entry in the sound table.
|
||||
* Fields are interpreted differently depending on the type ID of the entry,
|
||||
* which is not stored in this struct.
|
||||
*/
|
||||
struct JAUSoundTableItem {
|
||||
u8 mPriority;
|
||||
u8 field_0x1;
|
||||
BE(u16) mResourceId;
|
||||
BE(u32) field_0x4;
|
||||
BE(f32) field_0x8;
|
||||
u8 mVolume;
|
||||
union {
|
||||
/**
|
||||
* For music sequences: the resource ID of the sequence.
|
||||
*/
|
||||
BE(u16) mResourceId;
|
||||
|
||||
/**
|
||||
* For streamed music: bitpacked channel configuration.
|
||||
*/
|
||||
BE(u16) mStreamPanParameters;
|
||||
};
|
||||
union {
|
||||
/**
|
||||
* For sound effects: bitpacked field controlling a *bunch* of audio parameters.
|
||||
*/
|
||||
BE(u32) mSwBit;
|
||||
|
||||
/**
|
||||
* For streamed music: offset (relative to start of BST)
|
||||
* to null-terminated string containing music file path.
|
||||
*/
|
||||
BE(u32) mStreamFilePath;
|
||||
};
|
||||
|
||||
/**
|
||||
* For sound effects: pitch multiplier.
|
||||
*/
|
||||
BE(f32) mPitch;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -24,23 +170,25 @@ struct JAUSoundTableItem {
|
||||
template<typename Root, typename Section, typename Group, typename Typename_0>
|
||||
struct JAUSoundTable_ {
|
||||
JAUSoundTable_() {
|
||||
field_0x0 = NULL;
|
||||
field_0x4 = 0;
|
||||
mData = NULL;
|
||||
mRoot = 0;
|
||||
}
|
||||
|
||||
void reset() {
|
||||
field_0x0 = NULL;
|
||||
field_0x4 = NULL;
|
||||
mData = NULL;
|
||||
mRoot = NULL;
|
||||
}
|
||||
|
||||
void init(const void* param_0) {
|
||||
field_0x0 = param_0;
|
||||
void init(const void* dataStart) {
|
||||
mData = dataStart;
|
||||
// magic number is not in debug rom. I'm not sure what this comparison is (maybe some sort of '' number?)
|
||||
// I also do not know how it is different between JAUSoundTable and JAUSoundNameTable
|
||||
if (*(BE(u32)*)field_0x0 + 0xbdad0000 != Root::magicNumber()) {
|
||||
field_0x0 = NULL;
|
||||
// Future person here: This is checking for either "BST " or "BSTN", with the second two letters in Root::magicNumber().
|
||||
// Idk why the operations here are all weird but I can't use objdiff right now.
|
||||
if (*(BE(u32)*)mData + 0xbdad0000 != Root::magicNumber()) {
|
||||
mData = NULL;
|
||||
} else {
|
||||
field_0x4 = (Root*)((u8*)field_0x0 + *((BE(u32)*)field_0x0 + 3));
|
||||
mRoot = (Root*)((u8*)mData + *((BE(u32)*)mData + 3));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,34 +196,34 @@ struct JAUSoundTable_ {
|
||||
if (index < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if ((u32)index >= field_0x4->mSectionNumber) {
|
||||
if ((u32)index >= mRoot->mSectionNumber) {
|
||||
return NULL;
|
||||
}
|
||||
u32 offset = field_0x4->mSectionOffsets[index];
|
||||
u32 offset = mRoot->mSectionOffsets[index];
|
||||
if (offset == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return (Section*)((u8*)field_0x0 + offset);
|
||||
return (Section*)((u8*)mData + offset);
|
||||
}
|
||||
|
||||
Group* getGroup(Section* param_1, int index) const {
|
||||
Group* getGroup(Section* section, int index) const {
|
||||
int iVar1;
|
||||
|
||||
if (index < 0) {
|
||||
return NULL;
|
||||
}
|
||||
if ((u32)index >= param_1->mNumGroups) {
|
||||
if ((u32)index >= section->mNumGroups) {
|
||||
return NULL;
|
||||
}
|
||||
u32 offset = param_1->getGroupOffset(index);
|
||||
u32 offset = section->getGroupOffset(index);
|
||||
if (offset == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return (Group*)((u8*)field_0x0 + offset);
|
||||
return (Group*)((u8*)mData + offset);
|
||||
}
|
||||
|
||||
const void* field_0x0;
|
||||
Root* field_0x4;
|
||||
const void* mData;
|
||||
Root* mRoot;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -83,7 +231,7 @@ struct JAUSoundTable_ {
|
||||
*
|
||||
*/
|
||||
struct JAUSoundTableRoot {
|
||||
static inline u32 magicNumber() { return 0x5420; }
|
||||
static inline u32 magicNumber() { return 'T '; } // Second half of "BST "
|
||||
BE(u32) mSectionNumber;
|
||||
BE(u32) mSectionOffsets[0];
|
||||
};
|
||||
@@ -134,7 +282,7 @@ struct JAUSoundTableGroup {
|
||||
|
||||
BE(u32) mNumItems;
|
||||
BE(u32) field_0x4;
|
||||
u8 mTypeIds[0];
|
||||
u8 mTypeIds[0]; // TODO: Should probably be BE(u32), but I can't objdiff rn.
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -142,7 +290,7 @@ struct JAUSoundTableGroup {
|
||||
*
|
||||
*/
|
||||
struct JAUSoundTable : public JASGlobalInstance<JAUSoundTable> {
|
||||
JAUSoundTable(bool param_0) : JASGlobalInstance<JAUSoundTable>(param_0) {
|
||||
JAUSoundTable(bool setInstance) : JASGlobalInstance<JAUSoundTable>(setInstance) {
|
||||
}
|
||||
~JAUSoundTable() {}
|
||||
|
||||
@@ -157,11 +305,11 @@ struct JAUSoundTable : public JASGlobalInstance<JAUSoundTable> {
|
||||
if (offset == 0) {
|
||||
return NULL;
|
||||
}
|
||||
return (JAUSoundTableItem*)((u8*)field_0x0.field_0x0 + offset);
|
||||
return (JAUSoundTableItem*)((u8*)field_0x0.mData + offset);
|
||||
}
|
||||
|
||||
const void* getResource() const { return field_0x0.field_0x0; }
|
||||
bool isValid() const { return field_0x0.field_0x0 != NULL; }
|
||||
const void* getResource() const { return field_0x0.mData; }
|
||||
bool isValid() const { return field_0x0.mData != NULL; }
|
||||
|
||||
JAUSoundTable_<JAUSoundTableRoot,JAUSoundTableSection,JAUSoundTableGroup,void> field_0x0;
|
||||
};
|
||||
@@ -171,7 +319,7 @@ struct JAUSoundTable : public JASGlobalInstance<JAUSoundTable> {
|
||||
*
|
||||
*/
|
||||
struct JAUSoundNameTableRoot {
|
||||
static inline u32 magicNumber() { return 0x544e; }
|
||||
static inline u32 magicNumber() { return 'TN'; } // Second half of "BSTN"
|
||||
BE(u32) mSectionNumber;
|
||||
BE(u32) mSectionOffsets[0];
|
||||
};
|
||||
|
||||
@@ -70,7 +70,7 @@ void JAISe::JAISeCategoryMgr_mixOut_(bool param_0, const JASSoundParams& params,
|
||||
if (inner_.field_0x26c) {
|
||||
switch (inner_.track.getStatus()) {
|
||||
case JASTrack::STATUS_STOPPED:
|
||||
if (status_.field_0x1.flags.flag1) {
|
||||
if (status_.field_0x1.flags.mComesBack) {
|
||||
startTrack_(params);
|
||||
} else {
|
||||
stop_JAISound_();
|
||||
@@ -84,8 +84,8 @@ void JAISe::JAISeCategoryMgr_mixOut_(bool param_0, const JASSoundParams& params,
|
||||
startTrack_(params);
|
||||
}
|
||||
}
|
||||
} else if (status_.field_0x1.flags.flag1) {
|
||||
if (status_.field_0x1.flags.flag3) {
|
||||
} else if (status_.field_0x1.flags.mComesBack) {
|
||||
if (status_.field_0x1.flags.mPauseWhenOut) {
|
||||
inner_.track.pause(true);
|
||||
} else {
|
||||
stopTrack_();
|
||||
|
||||
@@ -167,7 +167,7 @@ JAISeMgr::JAISeMgr(bool setInstance) : JASGlobalInstance<JAISeMgr>(setInstance)
|
||||
}
|
||||
|
||||
bool JAISeMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
if (mCategoryMgrs[i].JAISeCategoryMgr::isUsingSeqData(seqDataRegion)) {
|
||||
return true;
|
||||
}
|
||||
@@ -177,7 +177,7 @@ bool JAISeMgr::isUsingSeqData(const JAISeqDataRegion& seqDataRegion) {
|
||||
|
||||
int JAISeMgr::releaseSeqData(const JAISeqDataRegion& seqDataRegion) {
|
||||
bool r30 = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
switch (mCategoryMgrs[i].JAISeCategoryMgr::releaseSeqData(seqDataRegion)) {
|
||||
case 0:
|
||||
return 0;
|
||||
@@ -191,14 +191,14 @@ int JAISeMgr::releaseSeqData(const JAISeqDataRegion& seqDataRegion) {
|
||||
}
|
||||
|
||||
void JAISeMgr::setCategoryArrangement(const JAISeCategoryArrangement& arrangement) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
mCategoryMgrs[i].setMaxActiveSe(arrangement.mItems[i].mMaxActiveSe);
|
||||
mCategoryMgrs[i].setMaxInactiveSe(arrangement.mItems[i].mMaxInactiveSe);
|
||||
}
|
||||
}
|
||||
|
||||
void JAISeMgr::getCategoryArrangement(JAISeCategoryArrangement* arrangement) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
int active = mCategoryMgrs[i].getMaxActiveSe();
|
||||
JUT_ASSERT(299, active <= 255);
|
||||
arrangement->mItems[i].mMaxActiveSe = active;
|
||||
@@ -209,19 +209,19 @@ void JAISeMgr::getCategoryArrangement(JAISeCategoryArrangement* arrangement) {
|
||||
}
|
||||
|
||||
void JAISeMgr::stop() {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
mCategoryMgrs[i].stop();
|
||||
}
|
||||
}
|
||||
|
||||
void JAISeMgr::stopSoundID(JAISoundID id) {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
mCategoryMgrs[i].stopSoundID(id);
|
||||
}
|
||||
}
|
||||
|
||||
void JAISeMgr::initParams() {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
mCategoryMgrs[i].getParams()->init();
|
||||
}
|
||||
}
|
||||
@@ -270,16 +270,16 @@ JAISe* JAISeMgr::newSe_(int category, u32 priority) {
|
||||
|
||||
void JAISeMgr::calc() {
|
||||
mParams.calc();
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
mCategoryMgrs[i].JAISeMgr_calc_();
|
||||
}
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
mCategoryMgrs[i].JAISeMgr_freeDeadSe_();
|
||||
}
|
||||
}
|
||||
|
||||
void JAISeMgr::mixOut() {
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
mCategoryMgrs[i].JAISeMgr_mixOut_(mParams, mSoundActivity);
|
||||
}
|
||||
}
|
||||
@@ -324,7 +324,7 @@ bool JAISeMgr::startSound(JAISoundID id, JAISoundHandle* handle, const JGeometry
|
||||
|
||||
int JAISeMgr::getNumActiveSe() const {
|
||||
int num = 0;
|
||||
for (int i = 0; i < 16; i++) {
|
||||
for (int i = 0; i < NUM_CATEGORIES; i++) {
|
||||
num += mCategoryMgrs[i].getNumSe();
|
||||
}
|
||||
return num;
|
||||
|
||||
@@ -54,9 +54,9 @@ s32 JAISoundStatus_::unlockIfLocked() {
|
||||
}
|
||||
|
||||
void JAISoundParams::mixOutAll(const JASSoundParams& inParams, JASSoundParams* outParams, f32 param_2) {
|
||||
outParams->mVolume = move_.params_.mVolume * (inParams.mVolume * property_.field_0x0) * param_2;
|
||||
outParams->mFxMix = move_.params_.mFxMix + (inParams.mFxMix + property_.field_0x4);
|
||||
outParams->mPitch = move_.params_.mPitch * (inParams.mPitch * property_.field_0x8);
|
||||
outParams->mVolume = move_.params_.mVolume * (inParams.mVolume * property_.mVolume) * param_2;
|
||||
outParams->mFxMix = move_.params_.mFxMix + (inParams.mFxMix + property_.mFxMix);
|
||||
outParams->mPitch = move_.params_.mPitch * (inParams.mPitch * property_.mPitch);
|
||||
outParams->mPan = (inParams.mPan + move_.params_.mPan) - 0.5f;
|
||||
outParams->mDolby = inParams.mDolby + move_.params_.mDolby;
|
||||
}
|
||||
@@ -181,7 +181,7 @@ bool JAISound::calc_JAISound_() {
|
||||
}
|
||||
|
||||
if (audience_ != NULL && audible_ != NULL) {
|
||||
if ((priority_ = audience_->calcPriority(audible_)) == 0xFFFFFFFF && status_.field_0x1.flags.flag1 == 0) {
|
||||
if ((priority_ = audience_->calcPriority(audible_)) == 0xFFFFFFFF && status_.field_0x1.flags.mComesBack == 0) {
|
||||
stop_JAISound_();
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -713,7 +713,7 @@ void JASAramStream::channelStart() {
|
||||
wave_info.mpPenult = 0;
|
||||
// probably a fake match, this should be set in the JASWaveInfo constructor
|
||||
static u32 const one = 1;
|
||||
wave_info.field_0x20 = &one;
|
||||
wave_info.mpLoaded = &one;
|
||||
JASChannel* jc = JKR_NEW JASChannel(channelCallback, this);
|
||||
JUT_ASSERT(963, jc);
|
||||
jc->setPriority(0x7f7f);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
#include "JSystem/JSystem.h" // IWYU pragma: keep
|
||||
|
||||
#include "JSystem/JAudio2/JASBank.h"
|
||||
|
||||
#include "dusk/mods/svc/audio_res/audio_res.hpp"
|
||||
#include "JSystem/JAudio2/JASAiCtrl.h"
|
||||
#include "JSystem/JAudio2/JASBasicInst.h"
|
||||
#include "JSystem/JAudio2/JASBasicWaveBank.h"
|
||||
#include "JSystem/JAudio2/JASChannel.h"
|
||||
|
||||
using namespace dusk::mods::svc::audio_res;
|
||||
|
||||
// NONMATCHING JASPoolAllocObject_MultiThreaded<_> locations
|
||||
JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8 param_3, u16 param_4,
|
||||
void (*param_5)(u32, JASChannel*, JASDsp::TChannel*, void*),
|
||||
@@ -28,12 +32,23 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8
|
||||
if (!waveHandle) {
|
||||
return NULL;
|
||||
}
|
||||
#if TARGET_PC
|
||||
auto const key = AudioWaveKey(static_cast<AudioWaveBank>(waveBank->bankId), stack_60.field_0x1a);
|
||||
std::lock_guard lock(s_replacements_mutex);
|
||||
auto const found_replacement = s_replacements.find(key);
|
||||
if (found_replacement != s_replacements.end()) {
|
||||
waveHandle = &found_replacement->second;
|
||||
}
|
||||
|
||||
auto const aramBase = waveHandle->getAramBaseAddress();
|
||||
#endif
|
||||
|
||||
const JASWaveInfo* waveInfo = waveHandle->getWaveInfo();
|
||||
if (!waveInfo) {
|
||||
return NULL;
|
||||
}
|
||||
intptr_t wavePtr = waveHandle->getWavePtr();
|
||||
if (!wavePtr) {
|
||||
if (!wavePtr IF_DUSK(&& !aramBase)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -44,6 +59,10 @@ JASChannel* JASBank::noteOn(JASBank const* param_0, int param_1, u8 param_2, u8
|
||||
channel->setPriority(param_4);
|
||||
channel->field_0xdc.mWaveInfo = *waveInfo;
|
||||
channel->mWaveAramAddress = wavePtr;
|
||||
#if TARGET_PC
|
||||
channel->mAramBaseAddress = aramBase;
|
||||
channel->mSampleReference = waveHandle->getSampleReference();
|
||||
#endif
|
||||
channel->field_0xdc.mChannelType = stack_60.field_0x1c;
|
||||
channel->setBankDisposeID(param_0);
|
||||
channel->setInitPitch(stack_60.mPitch * (waveInfo->mSampleRate / JASDriver::getDacRate()));
|
||||
|
||||
@@ -6,12 +6,12 @@
|
||||
#include "JSystem/JUtility/JUTAssert.h"
|
||||
#include <stdint.h>
|
||||
|
||||
JASBasicWaveBank::JASBasicWaveBank() {
|
||||
JASBasicWaveBank::JASBasicWaveBank(IF_DUSK(u32 bankId)) IF_DUSK(: JASWaveBank(bankId)) {
|
||||
mWaveTable = NULL;
|
||||
mWaveGroupArray = NULL;
|
||||
mHandleCount = 0;
|
||||
mGroupCount = 0;
|
||||
OSInitMutex(&field_0x4);
|
||||
OSInitMutex(&mWaveTableMutex);
|
||||
}
|
||||
|
||||
JASBasicWaveBank::~JASBasicWaveBank() {
|
||||
@@ -44,13 +44,13 @@ void JASBasicWaveBank::setWaveTableSize(u32 param_0, JKRHeap* param_1) {
|
||||
}
|
||||
|
||||
void JASBasicWaveBank::incWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) {
|
||||
JASMutexLock lock(&field_0x4);
|
||||
JASMutexLock lock(&mWaveTableMutex);
|
||||
for (u32 i = 0; i < param_0->getWaveCount(); i++) {
|
||||
TWaveHandle* handle = mWaveTable + param_0->getWaveID(i);
|
||||
if (!handle->mHeap) {
|
||||
handle->mHeap = ¶m_0->mHeap;
|
||||
handle->field_0x4.field_0x20 = ¶m_0->_48;
|
||||
handle->field_0x4.mOffsetStart = param_0->mCtrlWaveArray[i].field_0x4;
|
||||
handle->mWaveInfo.mpLoaded = ¶m_0->mCurrentlyLoaded;
|
||||
handle->mWaveInfo.mOffsetStart = param_0->mCtrlWaveArray[i].mOffsetStart;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,13 +58,13 @@ void JASBasicWaveBank::incWaveTable(JASBasicWaveBank::TWaveGroup const* param_0)
|
||||
DUSK_GAME_DATA u32 JASBasicWaveBank::mNoLoad;
|
||||
|
||||
void JASBasicWaveBank::decWaveTable(JASBasicWaveBank::TWaveGroup const* param_0) {
|
||||
JASMutexLock lock(&field_0x4);
|
||||
JASMutexLock lock(&mWaveTableMutex);
|
||||
for (u32 i = 0; i < param_0->getWaveCount(); i++) {
|
||||
TWaveHandle* handle = mWaveTable + param_0->getWaveID(i);
|
||||
if (handle->mHeap == ¶m_0->mHeap) {
|
||||
handle->mHeap = NULL;
|
||||
handle->field_0x4.field_0x20 = &mNoLoad;
|
||||
handle->field_0x4.mOffsetStart = -1;
|
||||
handle->mWaveInfo.mpLoaded = &mNoLoad;
|
||||
handle->mWaveInfo.mOffsetStart = -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -80,15 +80,15 @@ JASWaveHandle* JASBasicWaveBank::getWaveHandle(u32 param_0) const {
|
||||
}
|
||||
|
||||
void JASBasicWaveBank::setWaveInfo(JASBasicWaveBank::TWaveGroup* wgrp, int index,
|
||||
u16 param_2, JASWaveInfo const& param_3) {
|
||||
u16 waveId, JASWaveInfo const& param_3) {
|
||||
JUT_ASSERT(204, wgrp);
|
||||
JUT_ASSERT(205, index < wgrp->mWaveCount);
|
||||
JUT_ASSERT(206, index >= 0);
|
||||
mWaveTable[param_2].field_0x4 = param_3;
|
||||
mWaveTable[param_2].field_0x4.field_0x20 = &mNoLoad;
|
||||
mWaveTable[param_2].field_0x4.mOffsetStart = -1;
|
||||
wgrp->mCtrlWaveArray[index].field_0x0 = param_2;
|
||||
wgrp->mCtrlWaveArray[index].field_0x4 = param_3.mOffsetStart;
|
||||
mWaveTable[waveId].mWaveInfo = param_3;
|
||||
mWaveTable[waveId].mWaveInfo.mpLoaded = &mNoLoad;
|
||||
mWaveTable[waveId].mWaveInfo.mOffsetStart = -1;
|
||||
wgrp->mCtrlWaveArray[index].waveId = waveId;
|
||||
wgrp->mCtrlWaveArray[index].mOffsetStart = param_3.mOffsetStart;
|
||||
}
|
||||
|
||||
JASBasicWaveBank::TWaveGroup::TWaveGroup() {
|
||||
@@ -122,7 +122,7 @@ void JASBasicWaveBank::TWaveGroup::onEraseDone() {
|
||||
u32 JASBasicWaveBank::TWaveGroup::getWaveID(int index) const {
|
||||
JUT_ASSERT(298, index < mWaveCount);
|
||||
JUT_ASSERT(299, index >= 0);
|
||||
return mCtrlWaveArray[index].field_0x0;
|
||||
return mCtrlWaveArray[index].waveId;
|
||||
}
|
||||
|
||||
intptr_t JASBasicWaveBank::TWaveHandle::getWavePtr() const {
|
||||
@@ -131,5 +131,5 @@ intptr_t JASBasicWaveBank::TWaveHandle::getWavePtr() const {
|
||||
if (base == 0) {
|
||||
return 0;
|
||||
}
|
||||
return (intptr_t)base + field_0x4.mOffsetStart;
|
||||
return (intptr_t)base + mWaveInfo.mOffsetStart;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,9 @@ JASChannel::JASChannel(Callback i_callback, void* i_callbackData) :
|
||||
mCallback(i_callback),
|
||||
mCallbackData(i_callbackData),
|
||||
mUpdateTimer(0),
|
||||
#if TARGET_PC
|
||||
mAramBaseAddress(nullptr),
|
||||
#endif
|
||||
mBankDisposeID(NULL),
|
||||
mKey(0),
|
||||
mVelocity(0x7f),
|
||||
@@ -237,7 +240,7 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) {
|
||||
mCallback(CB_START, this, i_channel, mCallbackData);
|
||||
}
|
||||
|
||||
if (field_0xdc.mWaveInfo.field_0x20[0] == 0) {
|
||||
if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) {
|
||||
JUT_WARN_DEVICE(346, 2, "%s", "Lost wave data while playing");
|
||||
mDspCh->free();
|
||||
mDspCh = NULL;
|
||||
@@ -256,6 +259,9 @@ s32 JASChannel::initialUpdateDSPChannel(JASDsp::TChannel* i_channel) {
|
||||
switch (field_0xdc.mChannelType) {
|
||||
case 0:
|
||||
i_channel->setWaveInfo(field_0xdc.mWaveInfo, mWaveAramAddress, mSkipSamples);
|
||||
#if TARGET_PC
|
||||
i_channel->mAramBaseAddress = mAramBaseAddress;
|
||||
#endif
|
||||
break;
|
||||
case 2:
|
||||
i_channel->setOscInfo(mOscillatorSomething);
|
||||
@@ -315,7 +321,7 @@ s32 JASChannel::updateDSPChannel(JASDsp::TChannel* i_channel) {
|
||||
mCallback(CB_PLAY, this, i_channel, mCallbackData);
|
||||
}
|
||||
|
||||
if (field_0xdc.mWaveInfo.field_0x20[0] == 0) {
|
||||
if (field_0xdc.mWaveInfo.mpLoaded[0] == 0) {
|
||||
JUT_WARN_DEVICE(456, 2, "%s","Lost wave data while playing");
|
||||
mDspCh->free();
|
||||
mDspCh = NULL;
|
||||
|
||||
@@ -523,9 +523,12 @@ void JASDsp::TChannel::init() {
|
||||
void JASDsp::TChannel::playStart() {
|
||||
JUT_ASSERT(508, dspMutex);
|
||||
field_0x10c = 0;
|
||||
#if !TARGET_PC
|
||||
field_0x060 = 0;
|
||||
#endif
|
||||
mResetFlag = 1;
|
||||
field_0x066 = 0;
|
||||
#if !TARGET_PC
|
||||
int i;
|
||||
for (i = 0; i < 4; i++) {
|
||||
field_0x078[i] = 0;
|
||||
@@ -534,6 +537,7 @@ void JASDsp::TChannel::playStart() {
|
||||
for (i = 0; i < 20; i++) {
|
||||
field_0x080[i] = 0;
|
||||
}
|
||||
#endif
|
||||
mIsActive = 1;
|
||||
}
|
||||
|
||||
@@ -601,9 +605,11 @@ void JASDsp::TChannel::setWaveInfo(JASWaveInfo const& waveInfo, u32 aramAddress,
|
||||
break;
|
||||
}
|
||||
}
|
||||
#if !TARGET_PC
|
||||
for (i = 0; i < 16; i++) {
|
||||
field_0x0b0[i] = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
#include "JSystem/JAudio2/JASSimpleWaveBank.h"
|
||||
#include <stdint.h>
|
||||
|
||||
JASSimpleWaveBank::JASSimpleWaveBank() {
|
||||
JASSimpleWaveBank::JASSimpleWaveBank(IF_DUSK(u32 bankId)) IF_DUSK(: JASWaveBank(bankId)) {
|
||||
mWaveTable = NULL;
|
||||
mWaveTableSize = 0;
|
||||
}
|
||||
@@ -28,7 +28,7 @@ JASWaveHandle* JASSimpleWaveBank::getWaveHandle(u32 no) const {
|
||||
|
||||
void JASSimpleWaveBank::setWaveInfo(u32 no, JASWaveInfo const& waveInfo) {
|
||||
mWaveTable[no].mWaveInfo = waveInfo;
|
||||
mWaveTable[no].mWaveInfo.field_0x20 = &_48;
|
||||
mWaveTable[no].mWaveInfo.mpLoaded = &mCurrentlyLoaded;
|
||||
mWaveTable[no].mHeap = &mHeap;
|
||||
}
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ JASBasicWaveBank* JASWSParser::createBasicWaveBank(void const* stream, JKRHeap*
|
||||
u32 free_size = heap->getFreeSize();
|
||||
|
||||
THeader* header = (THeader*)stream;
|
||||
JASBasicWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASBasicWaveBank;
|
||||
JASBasicWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASBasicWaveBank IF_DUSK((header->mId));
|
||||
if (wave_bank == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -60,7 +60,7 @@ JASBasicWaveBank* JASWSParser::createBasicWaveBank(void const* stream, JKRHeap*
|
||||
wave_info.mpLast = wave->mpLast;
|
||||
wave_info.mpPenult = wave->mpPenult;
|
||||
TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[j].ptr(header);
|
||||
u16 local_74 = JSULoHalf(ctrl_wave->_00);
|
||||
u16 local_74 = JSULoHalf(ctrl_wave->mWaveAndGroupId);
|
||||
wave_bank->setWaveInfo(wave_group, j, local_74, wave_info);
|
||||
}
|
||||
wave_group->setFileName(archive->mFileName);
|
||||
@@ -82,7 +82,7 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap
|
||||
return NULL;
|
||||
}
|
||||
|
||||
JASSimpleWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASSimpleWaveBank;
|
||||
JASSimpleWaveBank* wave_bank = JKR_NEW_ARGS (heap, 0) JASSimpleWaveBank IF_DUSK((header->mId));
|
||||
if (wave_bank == NULL) {
|
||||
return NULL;
|
||||
}
|
||||
@@ -94,7 +94,7 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap
|
||||
TWaveArchive* archive = archiveBank->mArchiveOffsets[0].ptr(header);
|
||||
for (int i = 0; i < ctrl->mWaveCount; i++) {
|
||||
TCtrlWave* ctrlWave = ctrl->mCtrlWaveOffsets[i].ptr(header);
|
||||
u32 tmp = JSULoHalf(ctrlWave->_00);
|
||||
u32 tmp = JSULoHalf(ctrlWave->mWaveAndGroupId);
|
||||
if (max < tmp) {
|
||||
max = tmp;
|
||||
}
|
||||
@@ -116,8 +116,8 @@ JASSimpleWaveBank* JASWSParser::createSimpleWaveBank(void const* stream, JKRHeap
|
||||
wave_info.mpLast = wave->mpLast;
|
||||
wave_info.mpPenult = wave->mpPenult;
|
||||
TCtrlWave* ctrl_wave = ctrl->mCtrlWaveOffsets[i].ptr(header);
|
||||
u32 tmp = JSULoHalf(ctrl_wave->_00);
|
||||
wave_bank->setWaveInfo(tmp, wave_info);
|
||||
u32 waveId = JSULoHalf(ctrl_wave->mWaveAndGroupId);
|
||||
wave_bank->setWaveInfo(waveId, wave_info);
|
||||
}
|
||||
wave_bank->setFileName(archive->mFileName);
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ char* JASWaveArcLoader::getCurrentDir() {
|
||||
}
|
||||
|
||||
JASWaveArc::JASWaveArc() : mHeap(this) {
|
||||
_48 = 0;
|
||||
mCurrentlyLoaded = 0;
|
||||
mStatus = 0;
|
||||
mEntryNum = -1;
|
||||
mFileLength = 0;
|
||||
@@ -57,7 +57,7 @@ bool JASWaveArc::loadSetup(u32 param_0) {
|
||||
if (mStatus != 1) {
|
||||
return false;
|
||||
}
|
||||
_48 = 1;
|
||||
mCurrentlyLoaded = 1;
|
||||
mStatus = 2;
|
||||
return true;
|
||||
}
|
||||
@@ -71,7 +71,7 @@ bool JASWaveArc::eraseSetup() {
|
||||
mStatus = 0;
|
||||
return false;
|
||||
}
|
||||
_48 = 0;
|
||||
mCurrentlyLoaded = 0;
|
||||
mStatus = 0;
|
||||
return true;
|
||||
}
|
||||
@@ -91,7 +91,7 @@ void JASWaveArc::loadToAramCallback(void* this_) {
|
||||
|
||||
bool JASWaveArc::sendLoadCmd() {
|
||||
JASMutexLock mutexLock(&mMutex);
|
||||
_48 = 0;
|
||||
mCurrentlyLoaded = 0;
|
||||
mStatus = 1;
|
||||
loadToAramCallbackParams commandInfo;
|
||||
commandInfo.mWavArc = this;
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
cmake_minimum_required(VERSION 3.25)
|
||||
project(audio_mod CXX)
|
||||
|
||||
if (CMAKE_SOURCE_DIR STREQUAL CMAKE_CURRENT_SOURCE_DIR)
|
||||
set(DUSK_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../.." CACHE PATH "Path to dusk source root")
|
||||
option(DUSK_MOD_USE_FULL_TREE "Use full build instead of the minimal mod SDK" OFF)
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
if (DUSK_MOD_USE_FULL_TREE)
|
||||
add_subdirectory("${DUSK_DIR}" dusk EXCLUDE_FROM_ALL)
|
||||
else ()
|
||||
add_subdirectory("${DUSK_DIR}/sdk" dusk-sdk EXCLUDE_FROM_ALL)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
add_mod(audio_mod
|
||||
SOURCES src/mod.cpp
|
||||
MOD_JSON mod.json
|
||||
RES_DIR res
|
||||
)
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"id": "dev.twilitrealm.audio_mod",
|
||||
"name": "[DEMO] Audio Mod",
|
||||
"version": "1.0.0",
|
||||
"author": "You, the listener",
|
||||
"description": "An example Dusklight mod"
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
go.opus and go.wav are licensed CC0, from https://kenney.nl/assets/voiceover-pack
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,39 @@
|
||||
#include "mods/service.hpp"
|
||||
#include "mods/svc/audio_res.h"
|
||||
#include "mods/svc/log.h"
|
||||
|
||||
DEFINE_MOD();
|
||||
IMPORT_SERVICE(LogService, svc_log);
|
||||
IMPORT_SERVICE(AudioResService, svc_audio_res);
|
||||
|
||||
extern "C" {
|
||||
|
||||
MOD_EXPORT ModResult mod_initialize(ModError*) {
|
||||
svc_log->info(mod_ctx, "template_mod initialized");
|
||||
AudioWaveHandle handle;
|
||||
svc_audio_res->replace_wave(
|
||||
mod_ctx,
|
||||
SoundEffects,
|
||||
4238,
|
||||
"res/go.opus",
|
||||
nullptr,
|
||||
&handle);
|
||||
svc_audio_res->replace_wave(
|
||||
mod_ctx,
|
||||
SoundEffects,
|
||||
4237,
|
||||
"res/go.opus",
|
||||
nullptr,
|
||||
&handle);
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
MOD_EXPORT ModResult mod_update(ModError*) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
MOD_EXPORT ModResult mod_shutdown(ModError*) {
|
||||
svc_log->info(mod_ctx, "template_mod unloaded");
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
#define AUDIO_RES_SERVICE_ID "dev.twilitrealm.dusklight.audio_res"
|
||||
#define AUDIO_RES_SERVICE_MAJOR 1u
|
||||
#define AUDIO_RES_SERVICE_MINOR 0u
|
||||
|
||||
#define AUDIO_RES_DEFAULT_KEY 0x3C;
|
||||
|
||||
typedef enum AudioWaveBank : uint8_t {
|
||||
SoundEffects = 0,
|
||||
MusicSamples = 1,
|
||||
} AudioWaveBank;
|
||||
|
||||
typedef enum AudioWaveFormat : uint8_t {
|
||||
Adpcm4 = 0,
|
||||
Adpcm2 = 1,
|
||||
Pcm8 = 2,
|
||||
Pcm16 = 3,
|
||||
} AudioWaveFormat;
|
||||
|
||||
typedef uint64_t AudioWaveHandle;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
typedef struct AudioRawWave {
|
||||
AudioWaveFormat format;
|
||||
float sample_rate;
|
||||
int16_t sample_value_last;
|
||||
int16_t sample_value_penult;
|
||||
} AudioRawWave;
|
||||
|
||||
typedef struct AudioWaveInfo {
|
||||
uint8_t base_key;
|
||||
bool loop;
|
||||
uint32_t loop_start_sample;
|
||||
uint32_t loop_end_sample;
|
||||
|
||||
AudioRawWave const* raw_wave;
|
||||
} AudioWaveInfo;
|
||||
|
||||
typedef struct AudioResService {
|
||||
ServiceHeader header;
|
||||
|
||||
ModResult (*replace_wave)(
|
||||
ModContext* ctx,
|
||||
AudioWaveBank bank,
|
||||
uint16_t wave_id,
|
||||
char const* file_name,
|
||||
AudioWaveInfo const* wave_info,
|
||||
AudioWaveHandle* out_handle);
|
||||
|
||||
ModResult (*remove_wave)(ModContext* ctx, AudioWaveHandle handle);
|
||||
} AudioResService;
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include "mods/service.hpp"
|
||||
|
||||
template <>
|
||||
struct mods::ServiceTraits<AudioResService> {
|
||||
static constexpr const char* id = AUDIO_RES_SERVICE_ID;
|
||||
static constexpr uint16_t major_version = AUDIO_RES_SERVICE_MAJOR;
|
||||
static constexpr uint16_t minor_version = AUDIO_RES_SERVICE_MINOR;
|
||||
};
|
||||
#endif
|
||||
@@ -1,88 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <mods/api.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
#include <mods/service.hpp>
|
||||
#endif
|
||||
|
||||
#define SAVE_SERVICE_ID "dev.twilitrealm.dusklight.save"
|
||||
#define SAVE_SERVICE_MAJOR 1u
|
||||
#define SAVE_SERVICE_MINOR 0u
|
||||
|
||||
/* Handle for a save-observer registration. 0 is never a valid handle. */
|
||||
typedef uint64_t SaveObserverHandle;
|
||||
|
||||
/* Total blob storage per mod per save slot. */
|
||||
#define SAVE_BLOB_BUDGET_BYTES 65536u
|
||||
|
||||
/*
|
||||
* Per-save-slot mod data.
|
||||
*
|
||||
* Named binary blobs that travel with a save slot: stored in a host-owned sidecar next to the
|
||||
* emulated memory card, written when the game writes the card (in-game save, autosave), and
|
||||
* following file-select copy and erase. Blobs are namespaced per mod — two mods can use the
|
||||
* same name without collision. Format versioning inside a blob is the mod's own concern.
|
||||
*
|
||||
* Blob calls target the *current* slot, which exists from save load / new-save creation until
|
||||
* the player returns to file select; MOD_UNAVAILABLE otherwise. Data written between game
|
||||
* saves lives in memory only — like vanilla save state, it persists when the player saves.
|
||||
* A new save clears the slot's previous blob data before on_new_save fires.
|
||||
*
|
||||
* Staleness: the sidecar snapshots a checksum of each slot's vanilla save data at write time
|
||||
* and warns at load when they diverge (e.g. the card file was replaced externally); blobs are
|
||||
* still delivered — treat them with suspicion in that session.
|
||||
*
|
||||
* Callbacks run on the game thread. Registrations are owned by the calling mod and removed
|
||||
* automatically when it is disabled, reloaded, or fails.
|
||||
*/
|
||||
|
||||
/* slot is the save-file index (0..2). */
|
||||
typedef void (*SaveEventFn)(ModContext* ctx, uint32_t slot, void* user_data);
|
||||
|
||||
typedef struct SaveService {
|
||||
ServiceHeader header;
|
||||
|
||||
/*
|
||||
* Write a named blob for the current slot (copied). Fails with MOD_UNAVAILABLE when no
|
||||
* slot is active or the mod's SAVE_BLOB_BUDGET_BYTES for the slot would be exceeded.
|
||||
*/
|
||||
ModResult (*set_blob)(ModContext* ctx, const char* name, const void* data, size_t size);
|
||||
|
||||
/*
|
||||
* Read a named blob from the current slot. With buf NULL, writes the blob's size to
|
||||
* *inout_size. Otherwise *inout_size is the buffer capacity in, bytes written out.
|
||||
* MOD_UNAVAILABLE when no slot is active or the blob does not exist.
|
||||
*/
|
||||
ModResult (*get_blob)(ModContext* ctx, const char* name, void* buf, size_t* inout_size);
|
||||
|
||||
/* Remove a named blob from the current slot. */
|
||||
ModResult (*delete_blob)(ModContext* ctx, const char* name);
|
||||
|
||||
/*
|
||||
* Observe save lifecycle. Any callback may be NULL:
|
||||
* - on_new_save: a new file was finalized in file select (name entry complete); the
|
||||
* slot's blob store is empty — write initial blobs here. Like vanilla save data,
|
||||
* nothing hits disk until the first in-game save.
|
||||
* - on_save_loaded: a slot was loaded into the live game (file select or quick-load);
|
||||
* blobs are readable.
|
||||
* - on_save_written: the game persisted the slot (menu save or autosave); blob data
|
||||
* captured with it is on disk.
|
||||
*/
|
||||
ModResult (*observe_saves)(ModContext* ctx, SaveEventFn on_new_save,
|
||||
SaveEventFn on_save_loaded, SaveEventFn on_save_written, void* user_data,
|
||||
SaveObserverHandle* out_handle);
|
||||
|
||||
/* Remove an observer previously registered by the calling mod. */
|
||||
ModResult (*unobserve_saves)(ModContext* ctx, SaveObserverHandle handle);
|
||||
|
||||
/*
|
||||
* Read a named blob from any slot (0..2), including at file select when no slot is
|
||||
* active. Same buffer contract as get_blob. The calling mod's own namespace only.
|
||||
*/
|
||||
ModResult (*peek_blob)(ModContext* ctx, uint32_t slot, const char* name, void* buf, size_t* inout_size);
|
||||
|
||||
} SaveService;
|
||||
|
||||
MOD_DECLARE_SERVICE(
|
||||
SaveService, svc_save, SAVE_SERVICE_ID, SAVE_SERVICE_MAJOR, SAVE_SERVICE_MINOR);
|
||||
+134
-134
@@ -36,14 +36,14 @@ Z2Audible::Z2Audible(const JGeometry::TVec3<f32>& pos, const JGeometry::TVec3<f3
|
||||
mParam.field_0x0.raw = 0xFFFFFFFF;
|
||||
mAbsPos.init(&mPos, pos, param_1);
|
||||
|
||||
for (int i = 0; i < 1; i++) {
|
||||
for (int i = 0; i < Z2_AUDIO_PLAYERS; i++) {
|
||||
if ((channel & (1 << i)) == 0) {
|
||||
mChannel[i].init();
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 1; i = i + 1) {
|
||||
field_0x64[i] = 0.0f;
|
||||
for (int i = 0; i < Z2_AUDIO_PLAYERS; i = i + 1) {
|
||||
mMicDistances[i] = 0.0f;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,18 +57,18 @@ void Z2Audible::calc() {
|
||||
|
||||
JASSoundParams* Z2Audible::getOuterParams(int index) {
|
||||
JUT_ASSERT(80, index >= 0);
|
||||
JUT_ASSERT(81, index < 1);
|
||||
JUT_ASSERT(81, index < 1); // Z2_AUDIO_PLAYERS
|
||||
return &mChannel[index].mParams;
|
||||
}
|
||||
|
||||
void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundParams& inParams,
|
||||
int index) {
|
||||
JUT_ASSERT(89, index >= 0);
|
||||
JUT_ASSERT(90, index < 1);
|
||||
JUT_ASSERT(90, index < 1); // Z2_AUDIO_PLAYERS
|
||||
|
||||
Z2AudibleChannel* channel = &mChannel[index];
|
||||
u8 iStack_94 = (mParam.field_0x0.half.f1 & 0xf00) >> 8;
|
||||
if (iStack_94 == 0) {
|
||||
u8 volSomething = (mParam.field_0x0.half.f1 & SOUND_VOL_SOMETHING_MASK_SHIFTED) >> 8;
|
||||
if (volSomething == 0) {
|
||||
channel->mParams.combine(outParams, inParams);
|
||||
return;
|
||||
}
|
||||
@@ -91,7 +91,7 @@ void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundPa
|
||||
|
||||
f32 dVar9 = inParams.mVolume * local_b8.length();
|
||||
f32 dVar10 = 1.0f;
|
||||
f32 dVar12 = Z2Calc::linearTransform(iStack_94, 0.0f, 15.0f, 1.0f, 0.3f, true);
|
||||
f32 dVar12 = Z2Calc::linearTransform(volSomething, 0.0f, 15.0f, 1.0f, 0.3f, true);
|
||||
if (inParams.mVolume > dVar12 && dVar9 > 0.001f) {
|
||||
dVar10 = Z2Calc::getParamByExp(dVar9, 0.3f, 0.001f, 0.1f, dVar12, 1.0f,
|
||||
Z2Calc::CURVE_POSITIVE);
|
||||
@@ -141,18 +141,18 @@ void Z2Audible::setOuterParams(const JASSoundParams& outParams, const JASSoundPa
|
||||
|
||||
Z2AudibleChannel* Z2Audible::getChannel(int index) {
|
||||
JUT_ASSERT(220, index >= 0);
|
||||
JUT_ASSERT(221, index < 1);
|
||||
JUT_ASSERT(221, index < 1); // Z2_AUDIO_PLAYERS
|
||||
return &mChannel[index];
|
||||
}
|
||||
|
||||
u32 Z2Audible::getDistVolBit() {
|
||||
u16 uVar1 = getAudibleParam()->field_0x0.half.f1;
|
||||
if (uVar1 != 0) {
|
||||
if ((uVar1 & 7) != 0) {
|
||||
return uVar1 & 7;
|
||||
if ((uVar1 & SOUND_VOL_DIST_BIT_MASK_SHIFTED) != 0) {
|
||||
return uVar1 & SOUND_VOL_DIST_BIT_MASK_SHIFTED;
|
||||
}
|
||||
if ((uVar1 & 0x70) != 0) {
|
||||
return ((int)(uVar1 & 0x70) >> 4) + 7;
|
||||
if ((uVar1 & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED) != 0) {
|
||||
return ((int)(uVar1 & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED) >> 4) + 7;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -166,15 +166,15 @@ Z2Audience3DSetting::Z2Audience3DSetting() {
|
||||
}
|
||||
|
||||
void Z2Audience3DSetting::init() {
|
||||
field_0x40 = Z2Param::MIN_DISTANCE_VOLUME;
|
||||
field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX;
|
||||
field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX;
|
||||
field_0x54 = Z2Param::DISTANCE_FX_PARAM;
|
||||
field_0x50 = 0.0f;
|
||||
field_0x4c = Z2Param::DOLBY_CENTER_VALUE;
|
||||
field_0x58 = 0.5f;
|
||||
field_0x64 = 0xff;
|
||||
field_0x5c = Z2Param::SONIC_SPEED;
|
||||
mMinDistanceVolume = Z2Param::MIN_DISTANCE_VOLUME;
|
||||
mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX;
|
||||
mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX;
|
||||
mMaxDistanceFxMix = Z2Param::DISTANCE_FX_PARAM;
|
||||
mMinDistanceFxMix = 0.0f;
|
||||
mDolbyCenterValue = Z2Param::DOLBY_CENTER_VALUE;
|
||||
mPanFactor = 0.5f;
|
||||
mMaxDistancePriority = 0xff;
|
||||
mSonicSpeed = Z2Param::SONIC_SPEED;
|
||||
field_0x60 = 1.5f;
|
||||
|
||||
initVolumeDist();
|
||||
@@ -184,8 +184,8 @@ void Z2Audience3DSetting::init() {
|
||||
void Z2Audience3DSetting::initVolumeDist() {
|
||||
if (mVolumeDistInit) return;
|
||||
|
||||
field_0x0[0] = Z2Param::DISTANCE_MAX;
|
||||
field_0x3c = Z2Param::MAX_VOLUME_DISTANCE;
|
||||
mDistanceMaxes[0] = Z2Param::DISTANCE_MAX;
|
||||
mMaxVolumeDistance = Z2Param::MAX_VOLUME_DISTANCE;
|
||||
|
||||
calcVolumeFactorAll();
|
||||
calcPriorityFactorAll();
|
||||
@@ -197,12 +197,12 @@ void Z2Audience3DSetting::initVolumeDist() {
|
||||
static f32 cNearFarRatio = Z2Param::MAX_VOLUME_DISTANCE / Z2Param::DISTANCE_MAX;
|
||||
|
||||
void Z2Audience3DSetting::updateVolumeDist(f32 param_0) {
|
||||
field_0x0[0] = param_0;
|
||||
mDistanceMaxes[0] = param_0;
|
||||
|
||||
if (param_0 > Z2Param::DISTANCE_MAX) {
|
||||
field_0x3c = cNearFarRatio * param_0;
|
||||
mMaxVolumeDistance = cNearFarRatio * param_0;
|
||||
} else {
|
||||
field_0x3c = Z2Param::MAX_VOLUME_DISTANCE;
|
||||
mMaxVolumeDistance = Z2Param::MAX_VOLUME_DISTANCE;
|
||||
}
|
||||
|
||||
calcVolumeFactorAll();
|
||||
@@ -214,29 +214,29 @@ void Z2Audience3DSetting::updateVolumeDist(f32 param_0) {
|
||||
|
||||
void Z2Audience3DSetting::initDolbyDist() {
|
||||
if (!mDolbyDistInit) {
|
||||
field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX;
|
||||
field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX;
|
||||
field_0x68 = -field_0x4c / field_0x44;
|
||||
field_0x6c = (1.0f - field_0x4c) / field_0x48;
|
||||
mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX;
|
||||
mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX;
|
||||
field_0x68 = -mDolbyCenterValue / mDolbyFrontDistanceMax;
|
||||
field_0x6c = (1.0f - mDolbyCenterValue) / mDolbyBehindDistanceMax;
|
||||
mDolbyDistInit = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Z2Audience3DSetting::updateDolbyDist(f32 param_0, f32 param_1) {
|
||||
if (param_0 > -1.0f * Z2Param::DOLBY_FLONT_DISTANCE_MAX) {
|
||||
field_0x44 = -1.0f * param_0;
|
||||
mDolbyFrontDistanceMax = -1.0f * param_0;
|
||||
} else {
|
||||
field_0x44 = Z2Param::DOLBY_FLONT_DISTANCE_MAX;
|
||||
mDolbyFrontDistanceMax = Z2Param::DOLBY_FLONT_DISTANCE_MAX;
|
||||
}
|
||||
|
||||
if (param_1 < 2.0f * field_0x44) {
|
||||
param_1 = 2.0f * field_0x44;
|
||||
if (param_1 < 2.0f * mDolbyFrontDistanceMax) {
|
||||
param_1 = 2.0f * mDolbyFrontDistanceMax;
|
||||
}
|
||||
|
||||
if (param_1 > Z2Param::DOLBY_BEHIND_DISTANCE_MAX) {
|
||||
field_0x48 = param_1;
|
||||
mDolbyBehindDistanceMax = param_1;
|
||||
} else {
|
||||
field_0x48 = Z2Param::DOLBY_BEHIND_DISTANCE_MAX;
|
||||
mDolbyBehindDistanceMax = Z2Param::DOLBY_BEHIND_DISTANCE_MAX;
|
||||
}
|
||||
|
||||
mDolbyDistInit = false;
|
||||
@@ -254,28 +254,28 @@ Z2AudioCamera::Z2AudioCamera() {
|
||||
}
|
||||
|
||||
void Z2AudioCamera::init() {
|
||||
JGeometry::TPosition3f32 aTStack_38;
|
||||
aTStack_38.identity();
|
||||
JGeometry::TPosition3f32 viewMatrix;
|
||||
viewMatrix.identity();
|
||||
JGeometry::TVec3<f32> VStack_44;
|
||||
VStack_44.set(100000.0f, 100000.0f, 100000.0f);
|
||||
setCameraState(aTStack_38, *(Vec*)&VStack_44, true);
|
||||
setCameraState(viewMatrix, *(Vec*)&VStack_44, true);
|
||||
}
|
||||
|
||||
void Z2AudioCamera::setCameraState(f32 const (*param_0)[4], Vec& pos, bool param_2) {
|
||||
field_0x0.set(param_0);
|
||||
if (param_2) {
|
||||
void Z2AudioCamera::setCameraState(f32 const (*viewMatrix)[4], Vec& pos, bool initial) {
|
||||
mViewMatrix.set(viewMatrix);
|
||||
if (initial) {
|
||||
mPos.set(pos);
|
||||
field_0x48.set(mPos);
|
||||
mLastPos.set(mPos);
|
||||
mVel.zero();
|
||||
} else {
|
||||
field_0x48.set(mPos);
|
||||
mLastPos.set(mPos);
|
||||
mPos.set(pos);
|
||||
mVel.sub(mPos, field_0x48);
|
||||
mVel.sub(mPos, mLastPos);
|
||||
}
|
||||
}
|
||||
|
||||
void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f32 param_3,
|
||||
f32 param_4, bool param_5, bool param_6) {
|
||||
void Z2AudioCamera::setCameraState(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f32 param_3,
|
||||
f32 param_4, bool param_5, bool initial) {
|
||||
JGeometry::TVec3<f32> aTStack_c0;
|
||||
VECSubtract(¶m_2, &pos, (Vec*)&aTStack_c0);
|
||||
mCamDist = aTStack_c0.length();
|
||||
@@ -312,7 +312,7 @@ void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f3
|
||||
Z2GetAudience()->getSetting()->updateDolbyDist(mCamDist, mCamDist);
|
||||
}
|
||||
} else {
|
||||
Vec aTStack_cc = {param_0[0][0], param_0[0][1], param_0[0][2]};
|
||||
Vec aTStack_cc = {viewMatrix[0][0], viewMatrix[0][1], viewMatrix[0][2]};
|
||||
Mtx rotMtx;
|
||||
MTXRotAxisRad(rotMtx, &aTStack_cc, (M_PI / 180.0f) * (-1.0f * dVar10));
|
||||
JGeometry::TVec3<f32> aTStack_d8;
|
||||
@@ -324,7 +324,7 @@ void Z2AudioCamera::setCameraState(f32 (*param_0)[4], Vec& pos, Vec& param_2, f3
|
||||
}
|
||||
}
|
||||
|
||||
setCameraState(param_0, pos, param_6);
|
||||
setCameraState(viewMatrix, pos, initial);
|
||||
}
|
||||
|
||||
void Z2AudioCamera::convertAbsToRel(Z2Audible* audible, int channelNum) {
|
||||
@@ -337,17 +337,17 @@ void Z2AudioCamera::convertAbsToRel(Z2Audible* audible, int channelNum) {
|
||||
return;
|
||||
}
|
||||
|
||||
Z2AudibleRelPos* relPos = &channel->field_0x14;
|
||||
MTXMultVec(field_0x0, (Vec*)&audible->getPos(), (Vec*)&relPos->field_0x00);
|
||||
relPos->field_0xC = relPos->field_0x00.length();
|
||||
Z2AudibleRelPos* relPos = &channel->mRelPos;
|
||||
MTXMultVec(mViewMatrix, (Vec*)&audible->getPos(), (Vec*)&relPos->mCameraRelative);
|
||||
relPos->mTrueDistance = relPos->mCameraRelative.length();
|
||||
|
||||
JGeometry::TVec3<f32> aTStack_38(relPos->field_0x00);
|
||||
JGeometry::TVec3<f32> aTStack_38(relPos->mCameraRelative);
|
||||
aTStack_38.z += mVolCenterZ;
|
||||
relPos->field_0x10 = aTStack_38.length();
|
||||
relPos->mCenterDistance = aTStack_38.length();
|
||||
}
|
||||
|
||||
bool Z2AudioCamera::convertAbsToRel(Vec& src, Vec* dst) const {
|
||||
MTXMultVec(field_0x0, &src, dst);
|
||||
MTXMultVec(mViewMatrix, &src, dst);
|
||||
return isInSight(*dst);
|
||||
}
|
||||
|
||||
@@ -395,7 +395,7 @@ void Z2SpotMic::calcVolumeFactor(int camID) {
|
||||
|
||||
void Z2SpotMic::setMicState(Z2AudioCamera* camera, int camID) {
|
||||
JUT_ASSERT(622, camID >= 0);
|
||||
JUT_ASSERT(623, camID < 1);
|
||||
JUT_ASSERT(623, camID < 1); // Z2_AUDIO_PLAYERS
|
||||
|
||||
if (mMicOn && mPosPtr != 0 && camera != NULL) {
|
||||
clearMicState(camID);
|
||||
@@ -406,8 +406,8 @@ void Z2SpotMic::setMicState(Z2AudioCamera* camera, int camID) {
|
||||
aVStack_58.z += camera->getVolCenterZ();
|
||||
|
||||
f32 dVar10 = aVStack_58.length();
|
||||
f32 dVar11 = Z2GetAudience()->getSetting()->field_0x0[0];
|
||||
f32 dVar12 = Z2GetAudience()->getSetting()->field_0x3c;
|
||||
f32 dVar11 = Z2GetAudience()->getSetting()->mDistanceMaxes[0];
|
||||
f32 dVar12 = Z2GetAudience()->getSetting()->mMaxVolumeDistance;
|
||||
if (dVar10 > dVar11) {
|
||||
field_0x18[camID] = field_0xc;
|
||||
} else {
|
||||
@@ -443,21 +443,21 @@ f32 Z2SpotMic::calcMicDist(Z2Audible* audible) {
|
||||
return aTStack_1c.length();
|
||||
}
|
||||
|
||||
u32 Z2SpotMic::calcMicPriority(f32 param_0) {
|
||||
if (param_0 > field_0x4) {
|
||||
return Z2GetAudience()->getSetting()->field_0x64;
|
||||
u32 Z2SpotMic::calcMicPriority(f32 micDistance) {
|
||||
if (micDistance > field_0x4) {
|
||||
return Z2GetAudience()->getSetting()->mMaxDistancePriority;
|
||||
}
|
||||
|
||||
if (param_0 < field_0x0) {
|
||||
if (micDistance < field_0x0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return field_0x1c * (param_0 - field_0x0);
|
||||
return field_0x1c * (micDistance - field_0x0);
|
||||
}
|
||||
|
||||
f32 Z2SpotMic::calcMicVolume(f32 param_0, int camID, f32 param_2) {
|
||||
JUT_ASSERT(687, camID >= 0);
|
||||
JUT_ASSERT(688, camID < 1);
|
||||
JUT_ASSERT(688, camID < 1); // Z2_AUDIO_PLAYERS
|
||||
|
||||
if (mMicOn == false) {
|
||||
return param_2;
|
||||
@@ -497,7 +497,7 @@ f32 Z2SpotMic::calcMicVolume(f32 param_0, int camID, f32 param_2) {
|
||||
}
|
||||
|
||||
Z2Audience::Z2Audience() : JASGlobalInstance<Z2Audience>(true), field_0x4(1.0f), field_0x8(0x7f) {
|
||||
mNumPlayers = 1;
|
||||
mNumPlayers = Z2_AUDIO_PLAYERS;
|
||||
mUsingOffMicVol = false;
|
||||
mAudioCamera[0].init();
|
||||
mAudioCamera[0].setMainCamera(true);
|
||||
@@ -512,11 +512,11 @@ bool Z2Audience::isActive() const {
|
||||
return Z2Audible::getTotalMemCount() != Z2Audible::getFreeMemCount();
|
||||
}
|
||||
|
||||
void Z2Audience::setAudioCamera(f32 (*param_0)[4], Vec& pos, Vec& param_2, f32 param_3,
|
||||
f32 param_4, bool param_5, int camID, bool param_7) {
|
||||
void Z2Audience::setAudioCamera(f32 (*viewMatrix)[4], Vec& pos, Vec& param_2, f32 param_3,
|
||||
f32 param_4, bool param_5, int camID, bool initial) {
|
||||
JUT_ASSERT(687, camID >= 0);
|
||||
JUT_ASSERT(688, camID < mNumPlayers);
|
||||
mAudioCamera[camID].setCameraState(param_0, pos, param_2, param_3, param_4, param_5, param_7);
|
||||
mAudioCamera[camID].setCameraState(viewMatrix, pos, param_2, param_3, param_4, param_5, initial);
|
||||
mLinkMic->setMicState(&mAudioCamera[camID], camID);
|
||||
}
|
||||
|
||||
@@ -529,7 +529,7 @@ JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3<f32>& pos, JAISoundID
|
||||
}
|
||||
|
||||
JAUAudibleParam params = Z2GetSoundInfo()->getAudibleSwFull(soundID);
|
||||
bool x = params.field_0x0.bytes.b0_0 != 0;
|
||||
bool x = params.field_0x0.bytes.mDopplerPower != 0;
|
||||
|
||||
Z2Audible* audible = JKR_NEW Z2Audible(pos, param_2, channelNum, x);
|
||||
if (audible == NULL) {
|
||||
@@ -543,13 +543,13 @@ JAIAudible* Z2Audience::newAudible(const JGeometry::TVec3<f32>& pos, JAISoundID
|
||||
if (channel != NULL) {
|
||||
u32 distVolBit = audible->getDistVolBit();
|
||||
mAudioCamera[i].convertAbsToRel(audible, i);
|
||||
calcDeltaPriority_(channel->field_0x14.field_0x10, distVolBit, false);
|
||||
calcDeltaPriority_(channel->mRelPos.mCenterDistance, distVolBit, false);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 1; i++) {
|
||||
for (int i = 0; i < Z2_AUDIO_PLAYERS; i++) {
|
||||
if (mSpotMic[i].isOn()) {
|
||||
audible->field_0x64[i] = mSpotMic[i].calcMicDist(audible);
|
||||
audible->mMicDistances[i] = mSpotMic[i].calcMicDist(audible);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -569,7 +569,7 @@ Z2Audible::~Z2Audible() {}
|
||||
u32 Z2Audience::calcPriority(JAIAudible* audible) {
|
||||
Z2Audible* Z2audible = (Z2Audible*)audible;
|
||||
|
||||
if (!Z2audible->getAudibleParam()->field_0x0.bytes.b0_4) {
|
||||
if (!Z2audible->getAudibleParam()->field_0x0.bytes.mCalculatePriority) {
|
||||
for (int i = 0; i < mNumPlayers; i++) {
|
||||
mAudioCamera[i].convertAbsToRel(Z2audible, i);
|
||||
}
|
||||
@@ -584,17 +584,17 @@ u32 Z2Audience::calcPriority(JAIAudible* audible) {
|
||||
mAudioCamera[i].convertAbsToRel(Z2audible, i);
|
||||
u32 distvolBit = Z2audible->getDistVolBit();
|
||||
const JAUAudibleParam* puVar1 = Z2audible->getAudibleParam();
|
||||
deltaPriority[i] = calcDeltaPriority_(channel->field_0x14.field_0x10, distvolBit, puVar1->field_0x0.bytes.b0_7);
|
||||
deltaPriority[i] = calcDeltaPriority_(channel->mRelPos.mCenterDistance, distvolBit, puVar1->field_0x0.bytes.mCullAtMaxDistance);
|
||||
if (deltaPriority[i] < rv) {
|
||||
rv = deltaPriority[i];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < 1; i++) {
|
||||
for (int i = 0; i < Z2_AUDIO_PLAYERS; i++) {
|
||||
if (mSpotMic[i].isOn()) {
|
||||
Z2audible->field_0x64[i] = mSpotMic[i].calcMicDist(Z2audible);
|
||||
u32 micPriority = mSpotMic[i].calcMicPriority(Z2audible->field_0x64[i]);
|
||||
Z2audible->mMicDistances[i] = mSpotMic[i].calcMicDist(Z2audible);
|
||||
u32 micPriority = mSpotMic[i].calcMicPriority(Z2audible->mMicDistances[i]);
|
||||
if (micPriority < rv) {
|
||||
rv = micPriority;
|
||||
}
|
||||
@@ -619,31 +619,31 @@ void Z2Audience::mixChannelOut(const JASSoundParams& outParams, JAIAudible* audi
|
||||
return;
|
||||
}
|
||||
|
||||
JASSoundParams local_60;
|
||||
JASSoundParams calcParams;
|
||||
u32 distVolBit = Z2audible->getDistVolBit();
|
||||
if (Z2audible->getAudibleParam()->field_0x0.bytes.b0_5) {
|
||||
local_60.mVolume = calcVolume_(channel->field_0x14.field_0x10, distVolBit);
|
||||
f32 dVar9 = mLinkMic->calcMicVolume(Z2audible->field_0x64[0], channelNum, local_60.mVolume);
|
||||
if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcDistanceVolume) {
|
||||
calcParams.mVolume = calcVolume_(channel->mRelPos.mCenterDistance, distVolBit);
|
||||
f32 dVar9 = mLinkMic->calcMicVolume(Z2audible->mMicDistances[0], channelNum, calcParams.mVolume);
|
||||
if (dVar9 < 0.0f) {
|
||||
dVar9 = calcOffMicSound(local_60.mVolume);
|
||||
dVar9 = calcOffMicSound(calcParams.mVolume);
|
||||
}
|
||||
local_60.mVolume = dVar9;
|
||||
calcParams.mVolume = dVar9;
|
||||
|
||||
JAUAudibleParam* params = Z2audible->getAudibleParam();
|
||||
if ((params->field_0x0.bytes.b1_2_7 & 8) && (local_60.mVolume <= 0.2f)) {
|
||||
local_60.mVolume = 0.2f;
|
||||
if ((params->field_0x0.bytes.mClampMinVolume & 8) && (calcParams.mVolume <= 0.2f)) {
|
||||
calcParams.mVolume = 0.2f;
|
||||
}
|
||||
} else {
|
||||
local_60.mVolume = 1.0f;
|
||||
calcParams.mVolume = 1.0f;
|
||||
}
|
||||
|
||||
if (Z2audible->getAudibleParam()->field_0x0.bytes.b1_1 && mNumPlayers == 1) {
|
||||
local_60.mDolby = calcRelPosDolby(*(Vec*)&channel->field_0x14.field_0x00, channelNum);
|
||||
if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcDolby && mNumPlayers == 1) {
|
||||
calcParams.mDolby = calcRelPosDolby(*(Vec*)&channel->mRelPos.mCameraRelative, channelNum);
|
||||
} else {
|
||||
local_60.mDolby = 0.5f;
|
||||
calcParams.mDolby = 0.5f;
|
||||
}
|
||||
|
||||
if (Z2audible->getAudibleParam()->field_0x0.bytes.b1_0) {
|
||||
if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcPan) {
|
||||
if (mNumPlayers > 2) {
|
||||
f32 fVar1;
|
||||
if (channelNum & 1) {
|
||||
@@ -651,24 +651,24 @@ void Z2Audience::mixChannelOut(const JASSoundParams& outParams, JAIAudible* audi
|
||||
} else {
|
||||
fVar1 = 0.0f;
|
||||
}
|
||||
local_60.mPan = fVar1;
|
||||
calcParams.mPan = fVar1;
|
||||
} else {
|
||||
local_60.mPan = calcRelPosPan(*(Vec*)&channel->field_0x14.field_0x00, channelNum);
|
||||
calcParams.mPan = calcRelPosPan(*(Vec*)&channel->mRelPos.mCameraRelative, channelNum);
|
||||
}
|
||||
} else {
|
||||
local_60.mPan = 0.5f;
|
||||
calcParams.mPan = 0.5f;
|
||||
}
|
||||
|
||||
local_60.mPitch = calcPitch_(channel, Z2audible, &mAudioCamera[channelNum]);
|
||||
f32 dVar9;
|
||||
if (Z2audible->getAudibleParam()->field_0x0.bytes.b0_6) {
|
||||
dVar9 = calcFxMix_(channel->field_0x14.field_0xC, distVolBit);
|
||||
calcParams.mPitch = calcPitch_(channel, Z2audible, &mAudioCamera[channelNum]);
|
||||
f32 fxMix;
|
||||
if (Z2audible->getAudibleParam()->field_0x0.bytes.mCalcFxMix) {
|
||||
fxMix = calcFxMix_(channel->mRelPos.mTrueDistance, distVolBit);
|
||||
} else {
|
||||
dVar9 = 0.0f;
|
||||
fxMix = 0.0f;
|
||||
}
|
||||
local_60.mFxMix = dVar9;
|
||||
local_60.clamp();
|
||||
Z2audible->setOuterParams(outParams, local_60, channelNum);
|
||||
calcParams.mFxMix = fxMix;
|
||||
calcParams.clamp();
|
||||
Z2audible->setOuterParams(outParams, calcParams, channelNum);
|
||||
}
|
||||
|
||||
|
||||
@@ -688,18 +688,18 @@ f32 Z2Audience::calcRelPosVolume(const Vec& param_0, f32 param_1, int camID) {
|
||||
aTStack_3c.y *= 1.5f;
|
||||
|
||||
f32 len = aTStack_3c.length();
|
||||
if (len > mSetting.field_0x0[0] * param_1) {
|
||||
return mSetting.field_0x40;
|
||||
if (len > mSetting.mDistanceMaxes[0] * param_1) {
|
||||
return mSetting.mMinDistanceVolume;
|
||||
}
|
||||
|
||||
if (len < mSetting.field_0x3c) {
|
||||
if (len < mSetting.mMaxVolumeDistance) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return JGeometry::TUtil<f32>::clamp(
|
||||
1.0f + ((mSetting.field_0x40 - 1.0f) /
|
||||
((mSetting.field_0x0[0] * param_1) - mSetting.field_0x3c)) *
|
||||
(len - mSetting.field_0x3c),
|
||||
1.0f + ((mSetting.mMinDistanceVolume - 1.0f) /
|
||||
((mSetting.mDistanceMaxes[0] * param_1) - mSetting.mMaxVolumeDistance)) *
|
||||
(len - mSetting.mMaxVolumeDistance),
|
||||
0.0f, 1.0f);
|
||||
}
|
||||
|
||||
@@ -717,7 +717,7 @@ f32 Z2Audience::calcRelPosPan(const Vec& param_0, int camID) {
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
dVar6 = (0.5f + mSetting.field_0x58 * (local_54.x / dVar6));
|
||||
dVar6 = (0.5f + mSetting.mPanFactor * (local_54.x / dVar6));
|
||||
if (local_54.z <= 0.0f) {
|
||||
f32 fovySin = mAudioCamera[camID].getFovySin();
|
||||
if (dVar6 < 0.5f) {
|
||||
@@ -757,52 +757,52 @@ f32 Z2Audience::calcRelPosDolby(const Vec& param_0, int camID) {
|
||||
return 0.5f - 0.5f * cosf(t * static_cast<f32>(M_PI));
|
||||
}
|
||||
#endif
|
||||
if (fVar1 > mSetting.field_0x48) {
|
||||
if (fVar1 > mSetting.mDolbyBehindDistanceMax) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (fVar1 < mSetting.field_0x44) {
|
||||
if (fVar1 < mSetting.mDolbyFrontDistanceMax) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
if (fVar1 < 0.0f) {
|
||||
return Z2Calc::getParamByExp(
|
||||
fVar1, mSetting.field_0x44, 0.0f,
|
||||
fVar1, mSetting.mDolbyFrontDistanceMax, 0.0f,
|
||||
0.3f, 0.0f,
|
||||
mSetting.field_0x4c, Z2Calc::CURVE_POSITIVE);
|
||||
mSetting.mDolbyCenterValue, Z2Calc::CURVE_POSITIVE);
|
||||
}
|
||||
|
||||
return Z2Calc::getParamByExp(
|
||||
fVar1, 0.0f, mSetting.field_0x48,
|
||||
0.3f, mSetting.field_0x4c,
|
||||
fVar1, 0.0f, mSetting.mDolbyBehindDistanceMax,
|
||||
0.3f, mSetting.mDolbyCenterValue,
|
||||
1.0f, Z2Calc::CURVE_NEGATIVE);
|
||||
}
|
||||
|
||||
f32 Z2Audience::calcVolume_(f32 param_0, int distVolBit) const {
|
||||
if (param_0 > mSetting.field_0x0[distVolBit]) {
|
||||
return mSetting.field_0x40;
|
||||
if (param_0 > mSetting.mDistanceMaxes[distVolBit]) {
|
||||
return mSetting.mMinDistanceVolume;
|
||||
}
|
||||
|
||||
if (param_0 < mSetting.field_0x3c) {
|
||||
if (param_0 < mSetting.mMaxVolumeDistance) {
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
return JGeometry::TUtil<f32>::clamp((mSetting.field_0x70[distVolBit] * (param_0 - mSetting.field_0x3c)) + 1.0f, 0.0f, 1.0f);
|
||||
return JGeometry::TUtil<f32>::clamp((mSetting.mVolumeFactor[distVolBit] * (param_0 - mSetting.mMaxVolumeDistance)) + 1.0f, 0.0f, 1.0f);
|
||||
}
|
||||
|
||||
u32 Z2Audience::calcDeltaPriority_(f32 param_0, int distVolBit, bool param_2) const {
|
||||
if (param_0 > mSetting.field_0x0[distVolBit]) {
|
||||
if (param_2) {
|
||||
u32 Z2Audience::calcDeltaPriority_(f32 distance, int distVolBit, bool cullMaxDistance) const {
|
||||
if (distance > mSetting.mDistanceMaxes[distVolBit]) {
|
||||
if (cullMaxDistance) {
|
||||
return -1;
|
||||
}
|
||||
return mSetting.field_0x64;
|
||||
return mSetting.mMaxDistancePriority;
|
||||
}
|
||||
|
||||
if (param_0 < mSetting.field_0x3c) {
|
||||
if (distance < mSetting.mMaxVolumeDistance) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
return mSetting.field_0xac[distVolBit] * (param_0 - mSetting.field_0x3c);
|
||||
return mSetting.mPriorityFactor[distVolBit] * (distance - mSetting.mMaxVolumeDistance);
|
||||
}
|
||||
|
||||
f32 Z2Audience::calcPitchDoppler_(const JGeometry::TVec3<f32>& param_0,
|
||||
@@ -810,24 +810,24 @@ f32 Z2Audience::calcPitchDoppler_(const JGeometry::TVec3<f32>& param_0,
|
||||
const JGeometry::TVec3<f32>& param_2, f32 param_3) const {
|
||||
f32 dVar8 = param_0.dot(param_1);
|
||||
f32 dVar7 = param_0.dot(param_2);
|
||||
return (mSetting.field_0x5c + param_3 * dVar8) / (mSetting.field_0x5c + param_3 * dVar7);
|
||||
return (mSetting.mSonicSpeed + param_3 * dVar8) / (mSetting.mSonicSpeed + param_3 * dVar7);
|
||||
}
|
||||
|
||||
f32 Z2Audience::calcFxMix_(f32 param_0, int distVolBit) const {
|
||||
if (param_0 > mSetting.field_0x0[distVolBit]) {
|
||||
return mSetting.field_0x54;
|
||||
f32 Z2Audience::calcFxMix_(f32 distance, int distVolBit) const {
|
||||
if (distance > mSetting.mDistanceMaxes[distVolBit]) {
|
||||
return mSetting.mMaxDistanceFxMix;
|
||||
}
|
||||
if (param_0 < mSetting.field_0x3c) {
|
||||
return mSetting.field_0x50;
|
||||
if (distance < mSetting.mMaxVolumeDistance) {
|
||||
return mSetting.mMinDistanceFxMix;
|
||||
}
|
||||
return mSetting.field_0x50 + mSetting.field_0xe8[distVolBit] * (param_0 - mSetting.field_0x3c);
|
||||
return mSetting.mMinDistanceFxMix + mSetting.mFxMixFactor[distVolBit] * (distance - mSetting.mMaxVolumeDistance);
|
||||
}
|
||||
|
||||
f32 Z2Audience::calcPitch_(Z2AudibleChannel* channel, const Z2Audible* audible, const Z2AudioCamera* camera) const {
|
||||
JAUAudibleParam audParam = *audible->getAudibleParam();
|
||||
if (audParam.field_0x0.bytes.b0_0) {
|
||||
if (audParam.field_0x0.bytes.mDopplerPower) {
|
||||
JGeometry::TVec3<f32> aTStack_4c;
|
||||
aTStack_4c.normalize(channel->field_0x14.field_0x00);
|
||||
aTStack_4c.normalize(channel->mRelPos.mCameraRelative);
|
||||
JAUAudibleParam audParam = *audible->getAudibleParam();
|
||||
|
||||
f32 doppler = audParam.getDopplerPower();
|
||||
|
||||
@@ -1642,7 +1642,7 @@ void Z2SceneMgr::setSceneName(char* spot, s32 room, s32 layer) {
|
||||
JSUList<JAIStream>* stream_list = Z2GetSoundMgr()->getStreamMgr()->getStreamList();
|
||||
JSULink<JAIStream>* stream;
|
||||
for (stream = stream_list->getFirst(); stream != NULL; stream = stream->getNext()) {
|
||||
if (bVar2 || sound_table->getTypeID(stream->getObject()->getID()) != 0x71) {
|
||||
if (bVar2 || sound_table->getTypeID(stream->getObject()->getID()) != SOUND_TYPEID_STREAM) {
|
||||
stream->getObject()->stop(Z2Param::SCENE_CHANGE_BGM_FADEOUT_TIME);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ u16 Z2SoundInfo::getBgmSeqResourceID(JAISoundID soundID) const {
|
||||
|
||||
if (data != NULL) {
|
||||
switch ((typeID & 0xf0)) {
|
||||
case 0x60:
|
||||
case SOUND_TYPEID_SEQUENCE:
|
||||
return (u16)data->mResourceId;
|
||||
}
|
||||
}
|
||||
@@ -53,7 +53,7 @@ u32 Z2SoundInfo::getPriority(JAISoundID soundID) const {
|
||||
JAUSoundTableItem* data = JASGlobalInstance<JAUSoundTable>::getInstance()->getData(soundID);
|
||||
u8 typeID = JASGlobalInstance<JAUSoundTable>::getInstance()->getTypeID(soundID);
|
||||
|
||||
if (data != NULL && (typeID & 0x40) != 0) {
|
||||
if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) {
|
||||
return data->mPriority;
|
||||
}
|
||||
|
||||
@@ -67,64 +67,64 @@ JAUAudibleParam Z2SoundInfo::getAudibleSwFull(JAISoundID soundID) {
|
||||
|
||||
u8 typeID = JASGlobalInstance<JAUSoundTable>::getInstance()->getTypeID(soundID);
|
||||
switch (typeID) {
|
||||
case 81:
|
||||
audibleParam.field_0x0.bytes.b0_0 = (u32)getSwBit(soundID) >> 8;
|
||||
if ((getSwBit(soundID) & 1) != 0) {
|
||||
audibleParam.field_0x0.bytes.b0_4 = 0;
|
||||
case SOUND_TYPEID_SOUND_EFFECT:
|
||||
audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET;
|
||||
if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCalculatePriority = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b0_4 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalculatePriority = 1;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 2) != 0) {
|
||||
audibleParam.field_0x0.bytes.b0_5 = 0;
|
||||
if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b0_5 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 4) != 0) {
|
||||
audibleParam.field_0x0.bytes.b0_6 = 0;
|
||||
if ((getSwBit(soundID) & SOUND_SW_IGNORE_FX_MIX) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCalcFxMix = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b0_6 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcFxMix = 1;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 0x800000) != 0) {
|
||||
audibleParam.field_0x0.bytes.b0_7 = 1;
|
||||
if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b0_7 = 0;
|
||||
audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 0x1000) != 0) {
|
||||
audibleParam.field_0x0.bytes.b1_0 = 0;
|
||||
if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCalcPan = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b1_0 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcPan = 1;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 0x2000) != 0) {
|
||||
audibleParam.field_0x0.bytes.b1_1 = 0;
|
||||
if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCalcDolby = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b1_1 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcDolby = 1;
|
||||
}
|
||||
|
||||
uVar7 = 0;
|
||||
if ((getSwBit(soundID) & 0x80000) != 0) {
|
||||
if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) {
|
||||
uVar7 = 8;
|
||||
}
|
||||
|
||||
iVar1 = (getSwBit(soundID) >> 16) & 0x7;
|
||||
iVar1 += (getSwBit(soundID) >> 16) & 0x70;
|
||||
iVar1 += (getSwBit(soundID) >> 16) & 0xf00;
|
||||
audibleParam.field_0x0.bytes.b1_2_7 = uVar7;
|
||||
iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED;
|
||||
iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED;
|
||||
iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED;
|
||||
audibleParam.field_0x0.bytes.mClampMinVolume = uVar7;
|
||||
audibleParam.field_0x0.half.f1 = iVar1;
|
||||
break;
|
||||
default:
|
||||
audibleParam.field_0x0.bytes.b0_0 = 0;
|
||||
audibleParam.field_0x0.bytes.b0_4 = 1;
|
||||
audibleParam.field_0x0.bytes.b0_5 = 1;
|
||||
audibleParam.field_0x0.bytes.b0_6 = 1;
|
||||
audibleParam.field_0x0.bytes.b0_7 = 0;
|
||||
audibleParam.field_0x0.bytes.b1_0 = 1;
|
||||
audibleParam.field_0x0.bytes.b1_1 = 1;
|
||||
audibleParam.field_0x0.bytes.b1_2_7 = 0;
|
||||
audibleParam.field_0x0.bytes.mDopplerPower = 0;
|
||||
audibleParam.field_0x0.bytes.mCalculatePriority = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcFxMix = 1;
|
||||
audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0;
|
||||
audibleParam.field_0x0.bytes.mCalcPan = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcDolby = 1;
|
||||
audibleParam.field_0x0.bytes.mClampMinVolume = 0;
|
||||
audibleParam.field_0x0.half.f1 = 0;
|
||||
break;
|
||||
}
|
||||
@@ -139,53 +139,53 @@ u16 Z2SoundInfo::getAudibleSw(JAISoundID soundID) const {
|
||||
|
||||
u8 typeID = JASGlobalInstance<JAUSoundTable>::getInstance()->getTypeID(soundID);
|
||||
switch (typeID) {
|
||||
case 81:
|
||||
audibleParam.field_0x0.bytes.b0_0 = (u32)getSwBit(soundID) >> 8;
|
||||
if ((getSwBit(soundID) & 1) != 0) {
|
||||
audibleParam.field_0x0.bytes.b0_4 = 0;
|
||||
case SOUND_TYPEID_SOUND_EFFECT:
|
||||
audibleParam.field_0x0.bytes.mDopplerPower = (u32)getSwBit(soundID) >> SOUND_SW_DOPPLER_POWER_OFFSET;
|
||||
if ((getSwBit(soundID) & SOUND_SW_ALWAYS_MAX_PRIORITY) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCalculatePriority = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b0_4 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalculatePriority = 1;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 2) != 0) {
|
||||
audibleParam.field_0x0.bytes.b0_5 = 0;
|
||||
if ((getSwBit(soundID) & SOUND_SW_IGNORE_DISTANCE_VOL) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCalcDistanceVolume = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b0_5 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcDistanceVolume = 1;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 4) != 0) {
|
||||
audibleParam.field_0x0.bytes.b0_6 = 0;
|
||||
audibleParam.field_0x0.bytes.mCalcFxMix = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b0_6 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcFxMix = 1;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 0x800000) != 0) {
|
||||
audibleParam.field_0x0.bytes.b0_7 = 1;
|
||||
if ((getSwBit(soundID) & SOUND_SW_CULL_AT_MAX_DISTANCE) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCullAtMaxDistance = 1;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b0_7 = 0;
|
||||
audibleParam.field_0x0.bytes.mCullAtMaxDistance = 0;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 0x1000) != 0) {
|
||||
audibleParam.field_0x0.bytes.b1_0 = 0;
|
||||
if ((getSwBit(soundID) & SOUND_SW_IGNORE_PAN) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCalcPan = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b1_0 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcPan = 1;
|
||||
}
|
||||
|
||||
if ((getSwBit(soundID) & 0x2000) != 0) {
|
||||
audibleParam.field_0x0.bytes.b1_1 = 0;
|
||||
if ((getSwBit(soundID) & SOUND_SW_IGNORE_DOLBY) != 0) {
|
||||
audibleParam.field_0x0.bytes.mCalcDolby = 0;
|
||||
} else {
|
||||
audibleParam.field_0x0.bytes.b1_1 = 1;
|
||||
audibleParam.field_0x0.bytes.mCalcDolby = 1;
|
||||
}
|
||||
|
||||
uVar7 = 0;
|
||||
if ((getSwBit(soundID) & 0x80000) != 0) {
|
||||
if ((getSwBit(soundID) & SOUND_SW_CLAMP_MIN_VOLUME) != 0) {
|
||||
uVar7 = 8;
|
||||
}
|
||||
|
||||
iVar1 = (getSwBit(soundID) >> 16) & 0x7;
|
||||
iVar1 += (getSwBit(soundID) >> 16) & 0x70;
|
||||
iVar1 += (getSwBit(soundID) >> 16) & 0xf00;
|
||||
audibleParam.field_0x0.bytes.b1_2_7 = uVar7;
|
||||
iVar1 = (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_MASK_SHIFTED;
|
||||
iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_DIST_BIT_2_MASK_SHIFTED;
|
||||
iVar1 += (getSwBit(soundID) >> 16) & SOUND_VOL_SOMETHING_MASK_SHIFTED;
|
||||
audibleParam.field_0x0.bytes.mClampMinVolume = uVar7;
|
||||
audibleParam.field_0x0.half.f1 = iVar1;
|
||||
break;
|
||||
default:
|
||||
@@ -208,19 +208,19 @@ void Z2SoundInfo::getSeInfo(JAISoundID soundID, JAISe* sePtr) const {
|
||||
}
|
||||
|
||||
switch(typeID) {
|
||||
case 81:
|
||||
sePtr->getProperty().field_0x8 *= data->field_0x8;
|
||||
u32 uStack_6c = (getSwBit(soundID) & 0xf0) >> 4;
|
||||
if (uStack_6c > 8) {
|
||||
sePtr->getProperty().field_0x8 += Z2Calc::linearTransform(uStack_6c, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1();
|
||||
case SOUND_TYPEID_SOUND_EFFECT:
|
||||
sePtr->getProperty().mPitch *= data->mPitch;
|
||||
u32 pitchParam = (getSwBit(soundID) & SOUND_SW_RANDOM_PITCH_MASK) >> SOUND_SW_RANDOM_PITCH_OFFSET;
|
||||
if (pitchParam > 8) {
|
||||
sePtr->getProperty().mPitch += Z2Calc::linearTransform(pitchParam, 8.0f, 15.0f, 16.0f, 24.0f, true) / 48.0f * Z2Calc::getRandom_0_1();
|
||||
} else {
|
||||
sePtr->getProperty().field_0x8 += (uStack_6c / 48.0f) * Z2Calc::getRandom_0_1();
|
||||
sePtr->getProperty().mPitch += (pitchParam / 48.0f) * Z2Calc::getRandom_0_1();
|
||||
}
|
||||
|
||||
u32 uVar1 = (u32)getSwBit(soundID) >> 0x1c;
|
||||
u32 uVar1 = (u32)getSwBit(soundID) >> SOUND_SW_RANDOM_VOLUME_OFFSET;
|
||||
if (uVar1 != 0) {
|
||||
f32 dVar18 = (uVar1 / 15.0f) * Z2Calc::getRandom_0_1();
|
||||
sePtr->getProperty().field_0x0 -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18);
|
||||
sePtr->getProperty().mVolume -= dVar18 < 0.0f ? 0.0f : (dVar18 > 1.0f ? 1.0f : dVar18);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -240,13 +240,13 @@ void Z2SoundInfo::getStreamInfo(JAISoundID soundID, JAIStream* streamPtr) const
|
||||
|
||||
u8 typeID = JASGlobalInstance<JAUSoundTable>::getInstance()->getTypeID(soundID);
|
||||
switch (typeID & 0xf0) {
|
||||
case 0x70:
|
||||
case SOUND_TYPEID_STREAM:
|
||||
u16 uVar1;
|
||||
s32 iVar4;
|
||||
data = JASGlobalInstance<JAUSoundTable>::getInstance()->getData(soundID);
|
||||
JUT_ASSERT(356, data);
|
||||
|
||||
uVar1 = data->mResourceId;
|
||||
uVar1 = data->mStreamPanParameters;
|
||||
numChild = streamPtr->getNumChild();
|
||||
iVar4 = 0;
|
||||
for (; iVar4 < numChild && uVar1 != 0; uVar1 >>= JAUStdSoundTableType::STRM_CH_SHIFT, iVar4++) {
|
||||
@@ -277,12 +277,12 @@ const char* Z2SoundInfo::getStreamFilePath(JAISoundID soundID) {
|
||||
const void* resource;
|
||||
|
||||
switch (JASGlobalInstance<JAUSoundTable>::getInstance()->getTypeID(soundID) & 0xf0) {
|
||||
case 0x70:
|
||||
case SOUND_TYPEID_STREAM:
|
||||
data = JASGlobalInstance<JAUSoundTable>::getInstance()->getData(soundID);
|
||||
JUT_ASSERT(394, data);
|
||||
resource = JASGlobalInstance<JAUSoundTable>::getInstance()->getResource();
|
||||
JUT_ASSERT(398, resource);
|
||||
return JAUStdSoundTableType::StringOffset::getString(resource, data->field_0x4);
|
||||
return JAUStdSoundTableType::StringOffset::getString(resource, data->mStreamFilePath);
|
||||
default:
|
||||
return NULL;
|
||||
}
|
||||
@@ -300,8 +300,8 @@ int Z2SoundInfo::getSwBit(JAISoundID soundID) const {
|
||||
u8 typeID = JASGlobalInstance<JAUSoundTable>::getInstance()->getTypeID(soundID);
|
||||
if (data != NULL) {
|
||||
switch(typeID) {
|
||||
case 81:
|
||||
return data->field_0x4;
|
||||
case SOUND_TYPEID_SOUND_EFFECT:
|
||||
return data->mSwBit;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -313,7 +313,7 @@ void Z2SoundInfo::getSoundInfo_(JAISoundID soundID, JAISound* soundPtr) const {
|
||||
JAUSoundTableItem* data = JASGlobalInstance<JAUSoundTable>::getInstance()->getData(soundID);
|
||||
|
||||
u8 typeID = JASGlobalInstance<JAUSoundTable>::getInstance()->getTypeID(soundID);
|
||||
if (data != NULL && (typeID & 0x40) != 0) {
|
||||
soundPtr->getProperty().field_0x0 = (1.0f / 127.0f) * data->field_0x1;
|
||||
if (data != NULL && (typeID & SOUND_TYPEID_VALID) != 0) {
|
||||
soundPtr->getProperty().mVolume = (1.0f / 127.0f) * data->mVolume;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -222,7 +222,7 @@ Z2SoundHandlePool* Z2SoundObjBase::startCollisionSE(u32 hitID, u32 mapinfo, Z2So
|
||||
if (30 <= mapinfo && mapinfo <= 52) {
|
||||
Z2Audible* audible = (Z2Audible*)(*handle)->getAudible();
|
||||
if (audible != NULL) {
|
||||
audible->getAudibleParam()->field_0x0.bytes.b1_2_7 = 8;
|
||||
audible->getAudibleParam()->field_0x0.bytes.mClampMinVolume = 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,7 +28,6 @@
|
||||
#if TARGET_PC
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "helpers/string.hpp"
|
||||
#include "dusk/mods/svc/save.hpp"
|
||||
|
||||
namespace {
|
||||
constexpr u8 pointer_target(u8 group, u8 index) noexcept {
|
||||
@@ -256,10 +255,6 @@ dFile_select_c::~dFile_select_c() {
|
||||
void dFile_select_c::_create() {
|
||||
int i;
|
||||
|
||||
#if TARGET_PC
|
||||
dusk::mods::svc::save_no_slot();
|
||||
#endif
|
||||
|
||||
mDoGph_gInf_c::setFadeColor(static_cast<JUtility::TColor&>(g_blackColor));
|
||||
|
||||
stick = JKR_NEW STControl(2, 2, 1, 1, 0.9f, 0.5f, 0, 0x2000);
|
||||
@@ -1395,9 +1390,6 @@ void dFile_select_c::menuSelectStart() {
|
||||
mIsSelectEnd = true;
|
||||
mDataSelProc = DATASELPROC_NEXT_MODE_WAIT;
|
||||
dComIfGs_setDataNum(mSelectNum);
|
||||
#if TARGET_PC
|
||||
dusk::mods::svc::save_slot_loaded(mSelectNum, &mSaveData[mSelectNum]);
|
||||
#endif
|
||||
} else if (mSelectMenuNum == 0) {
|
||||
mSelIcon->setAlphaRate(0.0f);
|
||||
yesnoMenuMoveAnmInitSet(0x473, 0x47d);
|
||||
@@ -1748,9 +1740,6 @@ void dFile_select_c::nameInput2() {
|
||||
case 2:
|
||||
dComIfGs_setHorseName(mpName->getInputStrPtr());
|
||||
mIsSelectEnd = true;
|
||||
#if TARGET_PC
|
||||
dusk::mods::svc::save_slot_new(mSelectNum);
|
||||
#endif
|
||||
mDataSelProc = DATASELPROC_NEXT_MODE_WAIT;
|
||||
}
|
||||
}
|
||||
@@ -2677,9 +2666,6 @@ void dFile_select_c::DataEraseWait2() {
|
||||
mDataSelProc = DATASELPROC_ERROR_MSG_PANE_MOVE;
|
||||
} else if (field_0x03b4 == 1) {
|
||||
mDoAud_seStart(Z2SE_SY_FILE_DELETE_OK, NULL, 0, 0);
|
||||
#if TARGET_PC
|
||||
dusk::mods::svc::save_slot_erased(mSelectNum);
|
||||
#endif
|
||||
field_0x03b1 = 0;
|
||||
mDeleteEfPane[mSelectNum]->alphaAnimeStart(0);
|
||||
mFileInfoNoDatBasePane[mSelectNum]->alphaAnimeStart(0);
|
||||
@@ -2783,9 +2769,6 @@ void dFile_select_c::DataCopyWait2() {
|
||||
mDataSelProc = DATASELPROC_ERROR_MSG_PANE_MOVE;
|
||||
} else if (field_0x03b4 == 1) {
|
||||
mDoAud_seStart(Z2SE_SY_FILE_COPY_OK, NULL, 0, 0);
|
||||
#if TARGET_PC
|
||||
dusk::mods::svc::save_slot_copied(mCpDataNum, mCpDataToNum);
|
||||
#endif
|
||||
field_0x03b1 = 0;
|
||||
mCopyEfPane[mSelectNum]->alphaAnimeStart(0);
|
||||
mCopyEfPane[mCpDataToNum]->alphaAnimeStart(0);
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "dusk/frame_interpolation.h"
|
||||
#include "dusk/menu_pointer.h"
|
||||
#include "dusk/settings.h"
|
||||
#include "dusk/mods/svc/save.hpp"
|
||||
#endif
|
||||
|
||||
static int SelStartFrameTbl[3] = {
|
||||
@@ -1469,10 +1468,6 @@ void dMenu_save_c::memCardDataSaveWait2() {
|
||||
dComIfGs_setDataNum(mSelectedFile);
|
||||
dComIfGs_setNoFile(0);
|
||||
|
||||
#if TARGET_PC
|
||||
dusk::mods::svc::save_slot_written(mSelectedFile, mSaveBuffer + mSelectedFile * QUEST_LOG_SIZE);
|
||||
#endif
|
||||
|
||||
if (mUseType == TYPE_WHITE_EVENT || mUseType == TYPE_BLACK_EVENT) {
|
||||
headerTxtSet(0x530); // Saved.
|
||||
mWarning->closeInit();
|
||||
|
||||
@@ -317,7 +317,7 @@ void dusk::audio::DspRender(OutputSubframe& subframe) {
|
||||
}
|
||||
|
||||
OutputSubframe channelSubframe = {};
|
||||
if (channel.mWaveAramAddress == 0) {
|
||||
if (channel.mWaveAramAddress == 0 && !channel.mAramBaseAddress) {
|
||||
RenderOscChannel(channel, channelAux, channelSubframe);
|
||||
} else {
|
||||
ValidateChannel(channel);
|
||||
@@ -455,7 +455,8 @@ static int ReadChannelSamplesChunk(
|
||||
|
||||
assert(desiredSamples >= 0);
|
||||
|
||||
auto aramBase = static_cast<u8*>(ARGetStorageAddress()) + channel.mWaveAramAddress;
|
||||
auto aramBase = static_cast<u8 const*>(channel.mAramBaseAddress ? channel.mAramBaseAddress : ARGetStorageAddress());
|
||||
aramBase += channel.mWaveAramAddress;
|
||||
|
||||
auto curSamplePosition = channel.mSamplePosition;
|
||||
u32 skipSamples = curSamplePosition % channel.mSamplesPerBlock;
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
#include "dusk/autosave.h"
|
||||
#include "dusk/ui/ui.hpp"
|
||||
#include "imgui/ImGuiConsole.hpp"
|
||||
#include "mods/svc/config.hpp"
|
||||
#include "mods/svc/save.hpp"
|
||||
|
||||
bool shouldAutoSave = false;
|
||||
u8 mSaveBuffer[QUEST_LOG_SIZE * 3];
|
||||
@@ -107,9 +105,6 @@ void waitingForWrite() {
|
||||
}
|
||||
|
||||
void endAutoSave() {
|
||||
const int slot = dComIfGs_getDataNum();
|
||||
dusk::mods::svc::save_slot_written(slot, mSaveBuffer + slot * QUEST_LOG_SIZE);
|
||||
|
||||
dusk::ui::push_toast({
|
||||
.type = "autosave",
|
||||
.duration = std::chrono::milliseconds(1500),
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
#include "audio_res.hpp"
|
||||
|
||||
#include "helpers/cast.hpp"
|
||||
#include "mods/svc/audio_res.h"
|
||||
|
||||
#include "../registry.hpp"
|
||||
#include "../slot_map.hpp"
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/audio/DuskAudioSystem.h"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
|
||||
namespace audio_res {
|
||||
namespace {
|
||||
|
||||
using namespace dusk::helpers::cast;
|
||||
|
||||
bool audio_replacements_dirty = false;
|
||||
|
||||
aurora::Module Log("dusk::mods::svc::audio_res");
|
||||
|
||||
SlotMap<RuntimeWaveReplacementSlot> s_waveReplacements;
|
||||
|
||||
constexpr ContainerLoadFunction s_containerLoadFunctions[] = {
|
||||
load_wav,
|
||||
#if DUSK_OPUS
|
||||
load_opus,
|
||||
#endif
|
||||
};
|
||||
|
||||
bool validate_raw_size(LoadedMod const& mod, std::string const& path, uintptr_t actual_size, AudioRawWave const& raw, u32& sample_count) {
|
||||
u32 samples_per_block;
|
||||
u32 bytes_per_block;
|
||||
switch (raw.format) {
|
||||
case Adpcm4:
|
||||
samples_per_block = 16;
|
||||
bytes_per_block = 9;
|
||||
break;
|
||||
case Pcm16:
|
||||
samples_per_block = 1;
|
||||
bytes_per_block = 2;
|
||||
break;
|
||||
default:
|
||||
Log.error("[{}] unimplemented wave format (ADPCM2/PCM8): '{}'", mod.metadata.id, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (actual_size % bytes_per_block != 0) {
|
||||
Log.error("[{}] raw file is not divisible by format block size: '{}'", mod.metadata.id, path);
|
||||
return false;
|
||||
}
|
||||
|
||||
sample_count = (actual_size / bytes_per_block) * samples_per_block;
|
||||
return true;
|
||||
}
|
||||
|
||||
ModResult load_raw(
|
||||
LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, AudioRawWave const& raw) {
|
||||
auto file_contents = mod.bundle->readFile(slot.bundle_path);
|
||||
u32 sample_count = 0;
|
||||
|
||||
if (!validate_raw_size(mod, slot.bundle_path, file_contents.size(), raw, sample_count)) {
|
||||
Log.error(
|
||||
"[{}] replace_wave: file '{}' is a raw .abf file, but raw_wave data was not specified",
|
||||
mod.metadata.id, slot.bundle_path);
|
||||
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (slot.format == Pcm16) {
|
||||
SampleDataPcm16 pcm16;
|
||||
pcm16.data.resize(sample_count);
|
||||
memcpy(pcm16.data.data(), file_contents.data(), file_contents.size());
|
||||
slot.data = std::make_shared<SampleDataPcm16>(std::move(pcm16));
|
||||
} else {
|
||||
slot.data = std::make_shared<SampleDataU8>(std::move(file_contents));
|
||||
}
|
||||
|
||||
slot.sample_count = sample_count;
|
||||
slot.sample_rate = raw.sample_rate;
|
||||
slot.format = raw.format;
|
||||
slot.sample_value_penult = raw.sample_value_penult;
|
||||
slot.sample_value_last = raw.sample_value_last;
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load an audio file stored in some sort of container format.
|
||||
* File type is determined by looking at contents, not extension.
|
||||
*/
|
||||
ModResult load_container(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot) {
|
||||
auto file_contents = mod.bundle->readFile(slot.bundle_path);
|
||||
|
||||
for (auto const loader : s_containerLoadFunctions) {
|
||||
auto const result = loader(mod, slot, file_contents);
|
||||
if (result == MOD_OK) {
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
if (result != MOD_UNSUPPORTED) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return MOD_UNSUPPORTED;
|
||||
}
|
||||
|
||||
JASWaveInfo wave_info_from_slot(RuntimeWaveReplacementSlot const& slot) {
|
||||
JASWaveInfo info;
|
||||
info.mWaveFormat = static_cast<u8>(slot.format);
|
||||
info.mBaseKey = slot.base_key;
|
||||
info.mLoopFlag = slot.loop ? 0xFF : 0;
|
||||
info.mSampleRate = slot.sample_rate;
|
||||
info.mOffsetStart = 0; // Unused but just init it ig.
|
||||
info.mOffsetLength = bounded_cast(slot.data->size());
|
||||
info.mLoopStartSample = slot.loop_start_sample;
|
||||
info.mLoopEndSample = bounded_cast(slot.loop_end_sample);
|
||||
info.mSampleCount = bounded_cast(slot.sample_count);
|
||||
info.mpLast = slot.sample_value_last;
|
||||
info.mpPenult = slot.sample_value_penult;
|
||||
// mpLoaded is initialized to point to a 1, so we're good there.
|
||||
return info;
|
||||
}
|
||||
|
||||
ModResult insert_replace_wave(
|
||||
ModContext* ctx,
|
||||
AudioWaveBank bank,
|
||||
u16 wave_id,
|
||||
char const* file_name,
|
||||
AudioWaveInfo const* wave_info,
|
||||
AudioWaveHandle* out_handle) {
|
||||
if (out_handle != nullptr) {
|
||||
*out_handle = 0;
|
||||
}
|
||||
|
||||
auto mod = mod_from_context(ctx);
|
||||
if (mod == nullptr || file_name == nullptr || !is_safe_resource_path(file_name)) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
RuntimeWaveReplacementSlot slot{};
|
||||
slot.bundle_path = file_name;
|
||||
slot.bank = bank;
|
||||
slot.wave_id = wave_id;
|
||||
|
||||
ModResult result;
|
||||
if (wave_info && wave_info->raw_wave) {
|
||||
result = load_raw(*mod, slot, *wave_info->raw_wave);
|
||||
} else {
|
||||
result = load_container(*mod, slot);
|
||||
}
|
||||
if (result != MOD_OK) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (wave_info != nullptr) {
|
||||
slot.base_key = wave_info->base_key;
|
||||
slot.loop = wave_info->loop;
|
||||
slot.loop_start_sample = wave_info->loop_start_sample;
|
||||
slot.loop_end_sample = wave_info->loop_end_sample;
|
||||
} else {
|
||||
slot.base_key = AUDIO_RES_DEFAULT_KEY;
|
||||
slot.loop = false;
|
||||
slot.loop_start_sample = 0;
|
||||
slot.loop_end_sample = slot.sample_count;
|
||||
}
|
||||
|
||||
audio_replacements_dirty = true;
|
||||
|
||||
const auto handle = s_waveReplacements.emplace(*mod, std::move(slot));
|
||||
if (out_handle) {
|
||||
*out_handle = handle;
|
||||
}
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
bool wave_remove(LoadedMod const& mod, AudioWaveHandle const handle) {
|
||||
auto const result = s_waveReplacements.erase_owned(handle, mod);
|
||||
audio_replacements_dirty |= result;
|
||||
return result;
|
||||
}
|
||||
|
||||
ModResult remove_wave(ModContext* ctx, AudioWaveHandle handle) {
|
||||
auto* mod = mod_from_context(ctx);
|
||||
if (mod == nullptr || handle == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
if (!wave_remove(*mod, handle)) {
|
||||
Log.error("[{}] remove wave failed: unknown handle {}", mod->metadata.id, handle);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
constexpr AudioResService s_audioResService{
|
||||
.header = SERVICE_HEADER(AudioResService, AUDIO_RES_SERVICE_MAJOR, AUDIO_RES_SERVICE_MINOR),
|
||||
.replace_wave = &insert_replace_wave,
|
||||
.remove_wave = &remove_wave,
|
||||
};
|
||||
|
||||
void sync_audio_replacements() {
|
||||
audio_replacements_dirty = false;
|
||||
|
||||
absl::flat_hash_map<AudioWaveKey, AudioWaveReplacementValue> new_map;
|
||||
|
||||
for (auto const& mod : ModLoader::instance().active_mods()) {
|
||||
for (auto const& slot : s_waveReplacements.take_all(mod)) {
|
||||
auto const wave_info = wave_info_from_slot(slot.value);
|
||||
new_map.emplace(
|
||||
AudioWaveKey(slot.value.bank, slot.value.wave_id),
|
||||
AudioWaveReplacementValue(wave_info, slot.value.data));
|
||||
}
|
||||
}
|
||||
|
||||
// Log.info("new: {}, old: {}", new_map.size(), s_replacements.size());
|
||||
|
||||
std::lock_guard lock(s_replacements_mutex);
|
||||
// Note: new_map will contain the old contents, and is dropped *outside* the lock.
|
||||
// As to avoid holding the lock any longer than necessary.
|
||||
std::exchange(s_replacements, std::move(new_map));
|
||||
|
||||
// Log.info("new: {}, old: {}", new_map.size(), s_replacements.size());
|
||||
}
|
||||
|
||||
void replacements_remove_mod(LoadedMod& mod) {
|
||||
s_waveReplacements.erase_all(mod);
|
||||
|
||||
audio_replacements_dirty = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
absl::flat_hash_map<AudioWaveKey, AudioWaveReplacementValue> s_replacements;
|
||||
std::mutex s_replacements_mutex;
|
||||
|
||||
AudioWaveReplacementValue::~AudioWaveReplacementValue() = default;
|
||||
const JASWaveInfo* AudioWaveReplacementValue::getWaveInfo() const {
|
||||
return &wave_info;
|
||||
}
|
||||
|
||||
void const* AudioWaveReplacementValue::getAramBaseAddress() const {
|
||||
return data->get_data().data();
|
||||
}
|
||||
|
||||
intptr_t AudioWaveReplacementValue::getWavePtr() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::unique_ptr<JASSampleDataReference> AudioWaveReplacementValue::getSampleReference() const {
|
||||
return std::make_unique<SampleReference>(data);
|
||||
}
|
||||
|
||||
void SampleDataPcm16::be_swap() {
|
||||
for (auto& sample : data) {
|
||||
::be_swap(sample);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace audio_res
|
||||
|
||||
constinit const ServiceModule g_audioResModule{
|
||||
.id = AUDIO_RES_SERVICE_ID,
|
||||
.majorVersion = AUDIO_RES_SERVICE_MAJOR,
|
||||
.minorVersion = AUDIO_RES_SERVICE_MINOR,
|
||||
.service = &audio_res::s_audioResService,
|
||||
.modDetached = audio_res::replacements_remove_mod,
|
||||
.lifecycleApplied = audio_res::sync_audio_replacements,
|
||||
.frameEnd =
|
||||
[] {
|
||||
if (audio_res::audio_replacements_dirty) {
|
||||
audio_res::sync_audio_replacements();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include "JSystem/JAudio2/JASChannel.h"
|
||||
#include "JSystem/JAudio2/JASWaveInfo.h"
|
||||
#include "absl/container/flat_hash_map.h"
|
||||
#include "mods/svc/audio_res.h"
|
||||
|
||||
namespace dusk::mods {
|
||||
struct LoadedMod;
|
||||
}
|
||||
namespace dusk::mods::svc::audio_res {
|
||||
|
||||
struct SampleDataBuf {
|
||||
virtual ~SampleDataBuf() = default;
|
||||
[[nodiscard]] virtual std::span<std::byte const> get_data() const = 0;
|
||||
[[nodiscard]] size_t size() const {
|
||||
return get_data().size();
|
||||
}
|
||||
};
|
||||
|
||||
// Making a separate type for this rather than just using a vector<u8>
|
||||
// because I don't want aliasing or alignment to eat my face.
|
||||
struct SampleDataPcm16 : SampleDataBuf {
|
||||
std::vector<s16> data;
|
||||
|
||||
[[nodiscard]] std::span<std::byte const> get_data() const override {
|
||||
return as_bytes(std::span(data));
|
||||
}
|
||||
|
||||
void be_swap();
|
||||
};
|
||||
|
||||
struct SampleDataU8 : SampleDataBuf {
|
||||
std::vector<u8> data;
|
||||
|
||||
explicit SampleDataU8(std::vector<u8> data) : data(std::move(data)) {}
|
||||
|
||||
[[nodiscard]] std::span<std::byte const> get_data() const override {
|
||||
return as_bytes(std::span(data));
|
||||
}
|
||||
};
|
||||
|
||||
struct SampleReference : JASSampleDataReference {
|
||||
explicit SampleReference(std::shared_ptr<SampleDataBuf> data) : data(std::move(data)) {}
|
||||
|
||||
std::shared_ptr<SampleDataBuf> data;
|
||||
};
|
||||
|
||||
struct AudioWaveKey {
|
||||
AudioWaveBank bank;
|
||||
u32 wave_id;
|
||||
|
||||
AudioWaveKey(AudioWaveBank bank, u32 wave_id) : bank(bank), wave_id(wave_id) {};
|
||||
|
||||
template <typename H>
|
||||
friend H AbslHashValue(H h, const AudioWaveKey& k) {
|
||||
return H::combine(std::move(h), k.bank, k.wave_id);
|
||||
}
|
||||
|
||||
[[nodiscard]] bool operator==(const AudioWaveKey& other) const {
|
||||
return other.bank == bank && other.wave_id == wave_id;
|
||||
}
|
||||
};
|
||||
|
||||
struct AudioWaveReplacementValue : JASWaveHandle {
|
||||
AudioWaveReplacementValue(const JASWaveInfo& wave_info, std::shared_ptr<SampleDataBuf> data) : wave_info(wave_info), data(std::move(data)) {};
|
||||
~AudioWaveReplacementValue() override;
|
||||
[[nodiscard]] const JASWaveInfo* getWaveInfo() const override;
|
||||
[[nodiscard]] intptr_t getWavePtr() const override;
|
||||
[[nodiscard]] void const* getAramBaseAddress() const override;
|
||||
[[nodiscard]] std::unique_ptr<JASSampleDataReference> getSampleReference() const override;
|
||||
|
||||
JASWaveInfo wave_info;
|
||||
std::shared_ptr<SampleDataBuf> data;
|
||||
};
|
||||
|
||||
extern absl::flat_hash_map<AudioWaveKey, AudioWaveReplacementValue> s_replacements;
|
||||
extern std::mutex s_replacements_mutex;
|
||||
|
||||
struct RuntimeWaveReplacementSlot {
|
||||
std::string bundle_path;
|
||||
AudioWaveBank bank;
|
||||
u16 wave_id;
|
||||
|
||||
u8 base_key;
|
||||
bool loop;
|
||||
u32 loop_start_sample;
|
||||
u32 loop_end_sample;
|
||||
|
||||
AudioWaveFormat format;
|
||||
f32 sample_rate;
|
||||
u32 sample_count;
|
||||
s16 sample_value_last;
|
||||
s16 sample_value_penult;
|
||||
|
||||
std::shared_ptr<audio_res::SampleDataBuf> data;
|
||||
};
|
||||
|
||||
using ContainerLoadFunction = ModResult (*)(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData);
|
||||
|
||||
ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData);
|
||||
ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData);
|
||||
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
#if DUSK_OPUS
|
||||
|
||||
#include "audio_res.hpp"
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/mod_loader.hpp"
|
||||
#include "helpers/cast.hpp"
|
||||
#include "opusfile.h"
|
||||
|
||||
namespace {
|
||||
|
||||
aurora::Module Log("dusk::mods::svc::audio_res::opus");
|
||||
|
||||
struct OpusHandle {
|
||||
OggOpusFile* file;
|
||||
|
||||
~OpusHandle() {
|
||||
op_free(file);
|
||||
}
|
||||
|
||||
operator OggOpusFile*() const {
|
||||
return file;
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
namespace dusk::mods::svc::audio_res {
|
||||
ModResult load_opus(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData) {
|
||||
OpusHead head;
|
||||
const int result = op_test(&head, fileData.data(), fileData.size());
|
||||
if (result != 0) {
|
||||
return MOD_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (head.channel_count != 1) {
|
||||
Log.error("[{}] replace_wave: file '{}' must be mono but actually has {} channels", mod.metadata.id, slot.bundle_path, head.channel_count);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (head.stream_count != 1) {
|
||||
Log.error("[{}] replace_wave: file '{}' has multiple Opus streams!", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
int error;
|
||||
auto const handle_raw = op_open_memory(fileData.data(), fileData.size(), &error);
|
||||
if (error != 0) {
|
||||
Log.error("[{}] replace_wave: file '{}': unable to open Opus file: {}", mod.metadata.id, slot.bundle_path, error);
|
||||
return MOD_ERROR;
|
||||
}
|
||||
|
||||
const OpusHandle handle(handle_raw);
|
||||
auto const length = op_pcm_total(handle, -1);
|
||||
if (length < 0) {
|
||||
Log.error("[{}] replace_wave: file '{}': failed to check stream length?", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_ERROR;
|
||||
}
|
||||
|
||||
SampleDataPcm16 pcm_buffer;
|
||||
pcm_buffer.data.resize(length);
|
||||
|
||||
std::span span_pcm(pcm_buffer.data);
|
||||
|
||||
while (!span_pcm.empty()) {
|
||||
auto read_result = op_read(
|
||||
handle,
|
||||
span_pcm.data(), helpers::cast::bounded_cast(span_pcm.size()),
|
||||
nullptr);
|
||||
|
||||
if (read_result <= 0) {
|
||||
Log.error("[{}] replace_wave: file '{}': failed to read Opus: {}", mod.metadata.id, slot.bundle_path, read_result);
|
||||
return MOD_ERROR;
|
||||
}
|
||||
|
||||
if (read_result > span_pcm.size()) {
|
||||
Log.fatal("What teh fuck?");
|
||||
}
|
||||
|
||||
span_pcm = span_pcm.subspan(read_result);
|
||||
}
|
||||
|
||||
pcm_buffer.be_swap();
|
||||
|
||||
slot.data = std::make_shared<SampleDataPcm16>(std::move(pcm_buffer));
|
||||
slot.sample_rate = 48000; // Opus always decodes at 48 kHz.
|
||||
slot.sample_count = length;
|
||||
slot.format = Pcm16;
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,145 @@
|
||||
#include "audio_res.hpp"
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/mod_loader.hpp"
|
||||
#include "helpers/alignment.hpp"
|
||||
|
||||
namespace {
|
||||
|
||||
aurora::Module Log("dusk::mods::svc::audio_res::wav");
|
||||
|
||||
struct ChunkHeader {
|
||||
char magic[4];
|
||||
LE(u32) size;
|
||||
};
|
||||
|
||||
struct RiffChunk {
|
||||
ChunkHeader header; // magic "RIFF"
|
||||
char id[4]; // "WAVE"
|
||||
// Data after chunk.
|
||||
};
|
||||
|
||||
constexpr u16 WAVE_FORMAT_PCM = 0x0001;
|
||||
|
||||
struct FmtPcmChunkData {
|
||||
ChunkHeader header;
|
||||
|
||||
u16 wFormatTag;
|
||||
u16 nChannels;
|
||||
u32 nSamplesPerSec;
|
||||
u32 nAvgBytesPerSec;
|
||||
u16 nBlockAlign;
|
||||
u16 wBitsPerSample;
|
||||
};
|
||||
|
||||
bool check_four_cc(char const (&field)[4], char const (&expected)[5]) {
|
||||
return memcmp(field, expected, 4) == 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace dusk::mods::svc::audio_res {
|
||||
|
||||
ModResult load_wav(LoadedMod const& mod, RuntimeWaveReplacementSlot& slot, std::span<u8 const> fileData) {
|
||||
using namespace helpers::alignment;
|
||||
|
||||
if (fileData.size() < sizeof(RiffChunk)) {
|
||||
return MOD_UNSUPPORTED;
|
||||
}
|
||||
|
||||
auto const riffChunk = read_unaligned<RiffChunk>(&fileData[0]);
|
||||
if (!check_four_cc(riffChunk.header.magic, "RIFF")) {
|
||||
return MOD_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (!check_four_cc(riffChunk.id, "WAVE")) {
|
||||
return MOD_UNSUPPORTED;
|
||||
}
|
||||
|
||||
if (sizeof(ChunkHeader) + riffChunk.header.size > fileData.size()) {
|
||||
Log.error("[{}] wav file {}: invalid size!", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
bool has_read_fmt = false;
|
||||
SampleDataPcm16 pcmData;
|
||||
|
||||
auto waveData = fileData.subspan(sizeof(RiffChunk), riffChunk.header.size);
|
||||
while (waveData.size() > sizeof(ChunkHeader)) {
|
||||
auto const chunkHeader = read_unaligned<ChunkHeader>(&waveData[0]);
|
||||
|
||||
if (check_four_cc(chunkHeader.magic, "fmt ")) {
|
||||
if (has_read_fmt) {
|
||||
Log.error("[{}] wav file {}: multiple fmt chunks!", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (waveData.size() < sizeof(FmtPcmChunkData)) {
|
||||
Log.error("[{}] wav file {}: fmt chunk too small!", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto const fmtChunk = read_unaligned<FmtPcmChunkData>(&waveData[0]);
|
||||
|
||||
if (fmtChunk.nChannels != 1) {
|
||||
Log.error("[{}] wav file {}: only mono audio is supported", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (fmtChunk.wFormatTag != WAVE_FORMAT_PCM) {
|
||||
Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (fmtChunk.wBitsPerSample != 16) {
|
||||
Log.error("[{}] wav file {}: only 16-bit PCM is supported", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
has_read_fmt = true;
|
||||
slot.sample_rate = static_cast<f32>(fmtChunk.nSamplesPerSec);
|
||||
|
||||
} else if (check_four_cc(chunkHeader.magic, "data")) {
|
||||
if (sizeof(chunkHeader) + chunkHeader.size > fileData.size()) {
|
||||
Log.error("[{}] wav file {}: unexpected EOF reading sample data", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
auto const sampleData = waveData.subspan(sizeof(chunkHeader), chunkHeader.size);
|
||||
for (size_t i = 0; i + 1 < sampleData.size(); i += 2) {
|
||||
pcmData.data.push_back(read_unaligned<LE(s16)>(&sampleData[i]));
|
||||
}
|
||||
}
|
||||
|
||||
auto skip = sizeof(chunkHeader) + chunkHeader.size;
|
||||
if (skip % 2 != 0) {
|
||||
skip += 1;
|
||||
}
|
||||
|
||||
if (waveData.size() < skip) {
|
||||
Log.error("[{}] wav file {}: unexpected EOF", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
waveData = waveData.subspan(skip);
|
||||
}
|
||||
|
||||
if (!has_read_fmt) {
|
||||
Log.error("[{}] wav file {}: missing fmt chunk", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
if (pcmData.data.empty()) {
|
||||
Log.error("[{}] wav file {}: missing data chunk", mod.metadata.id, slot.bundle_path);
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
pcmData.be_swap();
|
||||
|
||||
slot.format = Pcm16;
|
||||
slot.sample_count = pcmData.size() / sizeof(u16);
|
||||
slot.data = std::make_unique<SampleDataPcm16>(std::move(pcmData));
|
||||
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -211,7 +211,7 @@ void ModLoader::init_services() {
|
||||
&svc::g_cameraModule,
|
||||
&svc::g_windowModule,
|
||||
&svc::g_gfxModule,
|
||||
&svc::g_saveModule,
|
||||
&svc::g_audioResModule,
|
||||
})
|
||||
{
|
||||
svc::register_module(*module);
|
||||
|
||||
@@ -73,6 +73,6 @@ extern const ServiceModule g_gameModule;
|
||||
extern const ServiceModule g_cameraModule;
|
||||
extern const ServiceModule g_windowModule;
|
||||
extern const ServiceModule g_gfxModule;
|
||||
extern const ServiceModule g_saveModule;
|
||||
extern const ServiceModule g_audioResModule;
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
|
||||
@@ -1,522 +0,0 @@
|
||||
#include "save.hpp"
|
||||
#include "ui.hpp"
|
||||
|
||||
#include "registry.hpp"
|
||||
|
||||
#include "aurora/lib/logging.hpp"
|
||||
#include "dusk/mods/loader/loader.hpp"
|
||||
#include "dusk/main.h"
|
||||
#include "dusk/utilities.hpp"
|
||||
#include "mods/svc/save.h"
|
||||
|
||||
#include <string_view>
|
||||
#include <fstream>
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
namespace {
|
||||
|
||||
aurora::Module Log("dusk::mods::save");
|
||||
|
||||
constexpr uint32_t kSlotCount = 3;
|
||||
constexpr size_t kQuestLogSize = 0xA94; // static_asserted against QUEST_LOG_SIZE
|
||||
constexpr int kSidecarVersion = 1;
|
||||
constexpr const char* kSidecarName = "mod_saves.json";
|
||||
constexpr size_t kMaxBlobNameLength = 256;
|
||||
|
||||
// Blob names sort deterministically in the sidecar; mod ids likewise.
|
||||
using BlobMap = std::map<std::string, std::vector<uint8_t>>;
|
||||
|
||||
struct SlotStore {
|
||||
bool snapshotValid = false;
|
||||
uint32_t snapshotCrc = 0;
|
||||
std::map<std::string, BlobMap> mods; // mod id -> blobs
|
||||
};
|
||||
|
||||
struct SaveObserverRecord {
|
||||
uint64_t handle = 0;
|
||||
LoadedMod* mod = nullptr;
|
||||
SaveEventFn onNewSave = nullptr;
|
||||
SaveEventFn onLoaded = nullptr;
|
||||
SaveEventFn onWritten = nullptr;
|
||||
void* userData = nullptr;
|
||||
};
|
||||
|
||||
// Game thread only (same rule as the other loader registries).
|
||||
std::array<SlotStore, kSlotCount> s_slots;
|
||||
int32_t s_currentSlot = -1;
|
||||
bool s_sidecarLoaded = false;
|
||||
std::vector<SaveObserverRecord> s_observers;
|
||||
uint64_t s_nextHandle = 1;
|
||||
int32_t s_nextSeq = 1;
|
||||
|
||||
// Position in m_mods (dependency-sorted load order) + 1; later-loaded mods win.
|
||||
int32_t compute_mod_priority(const LoadedMod& mod) {
|
||||
int32_t index = 0;
|
||||
for (const auto& other : ModLoader::instance().mods()) {
|
||||
++index;
|
||||
if (&other == &mod) {
|
||||
return index;
|
||||
}
|
||||
}
|
||||
return index + 1;
|
||||
}
|
||||
|
||||
std::filesystem::path sidecar_path() {
|
||||
return dusk::ConfigPath / kSidecarName;
|
||||
}
|
||||
|
||||
// --- base64 (RFC 4648, no wrapping) ---
|
||||
|
||||
constexpr char kB64Chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
||||
|
||||
std::string base64_encode(const std::vector<uint8_t>& data) {
|
||||
std::string out;
|
||||
out.reserve((data.size() + 2) / 3 * 4);
|
||||
for (size_t i = 0; i < data.size(); i += 3) {
|
||||
const uint32_t rest = data.size() - i;
|
||||
uint32_t chunk = data[i] << 16;
|
||||
if (rest > 1) {
|
||||
chunk |= data[i + 1] << 8;
|
||||
}
|
||||
if (rest > 2) {
|
||||
chunk |= data[i + 2];
|
||||
}
|
||||
out.push_back(kB64Chars[chunk >> 18 & 0x3F]);
|
||||
out.push_back(kB64Chars[chunk >> 12 & 0x3F]);
|
||||
out.push_back(rest > 1 ? kB64Chars[chunk >> 6 & 0x3F] : '=');
|
||||
out.push_back(rest > 2 ? kB64Chars[chunk & 0x3F] : '=');
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
bool base64_decode(const std::string& text, std::vector<uint8_t>& out) {
|
||||
if (text.size() % 4 != 0) {
|
||||
return false;
|
||||
}
|
||||
static const auto lookup = [] {
|
||||
std::array<int8_t, 256> table;
|
||||
table.fill(-1);
|
||||
for (int i = 0; i < 64; ++i) {
|
||||
table[static_cast<uint8_t>(kB64Chars[i])] = static_cast<int8_t>(i);
|
||||
}
|
||||
return table;
|
||||
}();
|
||||
out.clear();
|
||||
out.reserve(text.size() / 4 * 3);
|
||||
for (size_t i = 0; i < text.size(); i += 4) {
|
||||
uint32_t chunk = 0;
|
||||
int pads = 0;
|
||||
for (size_t j = 0; j < 4; ++j) {
|
||||
const char c = text[i + j];
|
||||
if (c == '=' && i + 4 == text.size() && j >= 2) {
|
||||
++pads;
|
||||
chunk <<= 6;
|
||||
continue;
|
||||
}
|
||||
const int8_t value = lookup[static_cast<uint8_t>(c)];
|
||||
if (value < 0 || pads != 0) {
|
||||
return false;
|
||||
}
|
||||
chunk = chunk << 6 | static_cast<uint32_t>(value);
|
||||
}
|
||||
out.push_back(chunk >> 16 & 0xFF);
|
||||
if (pads < 2) {
|
||||
out.push_back(chunk >> 8 & 0xFF);
|
||||
}
|
||||
if (pads < 1) {
|
||||
out.push_back(chunk & 0xFF);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// --- sidecar I/O ---
|
||||
|
||||
void load_sidecar() {
|
||||
if (s_sidecarLoaded) {
|
||||
return;
|
||||
}
|
||||
s_sidecarLoaded = true;
|
||||
std::ifstream in{sidecar_path()};
|
||||
if (!in.is_open()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const auto json = nlohmann::json::parse(in);
|
||||
if (json.value("version", 0) != kSidecarVersion) {
|
||||
Log.warn("mod save sidecar has unknown version {}; ignoring it",
|
||||
json.value("version", 0));
|
||||
return;
|
||||
}
|
||||
const auto& slots = json.at("slots");
|
||||
for (uint32_t slot = 0; slot < kSlotCount && slot < slots.size(); ++slot) {
|
||||
auto& store = s_slots[slot];
|
||||
const auto& slotJson = slots[slot];
|
||||
if (slotJson.contains("snapshot_crc32")) {
|
||||
store.snapshotValid = true;
|
||||
store.snapshotCrc = slotJson["snapshot_crc32"].get<uint32_t>();
|
||||
}
|
||||
const auto modsJson = slotJson.value("mods", nlohmann::json::object());
|
||||
for (const auto& [modId, blobs] : modsJson.items()) {
|
||||
for (const auto& [name, encoded] : blobs.items()) {
|
||||
std::vector<uint8_t> bytes;
|
||||
if (!base64_decode(encoded.get<std::string>(), bytes)) {
|
||||
Log.warn("mod save sidecar: bad blob '{}/{}' in slot {}; dropped",
|
||||
modId, name, slot);
|
||||
continue;
|
||||
}
|
||||
s_slots[slot].mods[modId][name] = std::move(bytes);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (const std::exception& e) {
|
||||
Log.error("failed to read mod save sidecar: {}", e.what());
|
||||
}
|
||||
}
|
||||
|
||||
void flush_sidecar() {
|
||||
nlohmann::json slots = nlohmann::json::array();
|
||||
for (const auto& store : s_slots) {
|
||||
nlohmann::json slotJson = nlohmann::json::object();
|
||||
if (store.snapshotValid) {
|
||||
slotJson["snapshot_crc32"] = store.snapshotCrc;
|
||||
}
|
||||
nlohmann::json mods = nlohmann::json::object();
|
||||
for (const auto& [modId, blobs] : store.mods) {
|
||||
if (blobs.empty()) {
|
||||
continue;
|
||||
}
|
||||
nlohmann::json blobsJson = nlohmann::json::object();
|
||||
for (const auto& [name, bytes] : blobs) {
|
||||
blobsJson[name] = base64_encode(bytes);
|
||||
}
|
||||
mods[modId] = std::move(blobsJson);
|
||||
}
|
||||
slotJson["mods"] = std::move(mods);
|
||||
slots.push_back(std::move(slotJson));
|
||||
}
|
||||
const nlohmann::json json{{"version", kSidecarVersion}, {"slots", std::move(slots)}};
|
||||
|
||||
const auto path = sidecar_path();
|
||||
const auto tempPath = path.string() + ".tmp";
|
||||
try {
|
||||
{
|
||||
std::ofstream out{tempPath, std::ios::trunc};
|
||||
out << json.dump(2);
|
||||
if (!out.good()) {
|
||||
throw std::runtime_error("write failed");
|
||||
}
|
||||
}
|
||||
std::filesystem::rename(tempPath, path);
|
||||
} catch (const std::exception& e) {
|
||||
Log.error("failed to write mod save sidecar: {}", e.what());
|
||||
std::error_code ec;
|
||||
std::filesystem::remove(tempPath, ec);
|
||||
}
|
||||
}
|
||||
|
||||
// --- observer notification ---
|
||||
|
||||
void notify(uint32_t slot, SaveEventFn SaveObserverRecord::* which, const char* what) {
|
||||
// Copy before invoking: callbacks may (un)register observers.
|
||||
const auto observers = s_observers;
|
||||
for (const auto& observer : observers) {
|
||||
if (!observer.mod->active || observer.*which == nullptr) {
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
(observer.*which)(observer.mod->context.get(), slot, observer.userData);
|
||||
} catch (const std::exception& e) {
|
||||
fail_mod(*observer.mod, MOD_ERROR,
|
||||
fmt::format("exception in {} save callback: {}", what, e.what()));
|
||||
} catch (...) {
|
||||
fail_mod(*observer.mod, MOD_ERROR,
|
||||
fmt::format("unknown exception in {} save callback", what));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// --- seam entry points ---
|
||||
|
||||
void save_slot_new(uint32_t slot) {
|
||||
if (slot >= kSlotCount) {
|
||||
return;
|
||||
}
|
||||
load_sidecar();
|
||||
auto& store = s_slots[slot];
|
||||
store.mods.clear();
|
||||
store.snapshotValid = false;
|
||||
s_currentSlot = static_cast<int32_t>(slot);
|
||||
Log.info("new save in slot {}; mod blob store cleared", slot);
|
||||
notify(slot, &SaveObserverRecord::onNewSave, "new-save");
|
||||
}
|
||||
|
||||
void save_slot_loaded(uint32_t slot, const void* slotData) {
|
||||
if (slot >= kSlotCount) {
|
||||
return;
|
||||
}
|
||||
load_sidecar();
|
||||
auto& store = s_slots[slot];
|
||||
if (store.snapshotValid && slotData != nullptr) {
|
||||
const auto crc = utils::CRC32(slotData, kQuestLogSize);
|
||||
if (crc != store.snapshotCrc) {
|
||||
Log.warn("slot {} save data does not match the mod sidecar snapshot; mod save "
|
||||
"data may be stale (card file changed externally?)",
|
||||
slot);
|
||||
}
|
||||
}
|
||||
s_currentSlot = static_cast<int32_t>(slot);
|
||||
notify(slot, &SaveObserverRecord::onLoaded, "save-loaded");
|
||||
}
|
||||
|
||||
void save_slot_written(uint32_t slot, const void* slotData) {
|
||||
if (slot >= kSlotCount) {
|
||||
return;
|
||||
}
|
||||
load_sidecar();
|
||||
auto& store = s_slots[slot];
|
||||
if (slotData != nullptr) {
|
||||
store.snapshotValid = true;
|
||||
store.snapshotCrc = utils::CRC32(slotData, kQuestLogSize);
|
||||
}
|
||||
flush_sidecar();
|
||||
notify(slot, &SaveObserverRecord::onWritten, "save-written");
|
||||
}
|
||||
|
||||
void save_slot_copied(uint32_t fromSlot, uint32_t toSlot) {
|
||||
if (fromSlot >= kSlotCount || toSlot >= kSlotCount || fromSlot == toSlot) {
|
||||
return;
|
||||
}
|
||||
load_sidecar();
|
||||
s_slots[toSlot] = s_slots[fromSlot];
|
||||
flush_sidecar();
|
||||
Log.info("mod save data copied with slot {} -> {}", fromSlot, toSlot);
|
||||
}
|
||||
|
||||
void save_slot_erased(uint32_t slot) {
|
||||
if (slot >= kSlotCount) {
|
||||
return;
|
||||
}
|
||||
load_sidecar();
|
||||
s_slots[slot] = SlotStore{};
|
||||
flush_sidecar();
|
||||
Log.info("mod save data erased with slot {}", slot);
|
||||
}
|
||||
|
||||
void save_no_slot() {
|
||||
s_currentSlot = -1;
|
||||
}
|
||||
|
||||
// --- loader plumbing (service surface) ---
|
||||
|
||||
namespace {
|
||||
|
||||
BlobMap* current_blobs(const LoadedMod& mod, bool create) {
|
||||
if (s_currentSlot < 0) {
|
||||
return nullptr;
|
||||
}
|
||||
load_sidecar();
|
||||
auto& mods = s_slots[s_currentSlot].mods;
|
||||
if (!create) {
|
||||
const auto it = mods.find(mod.metadata.id);
|
||||
return it != mods.end() ? &it->second : nullptr;
|
||||
}
|
||||
return &mods[mod.metadata.id];
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ModResult save_set_blob(LoadedMod& mod, const char* name, const void* data, size_t size) {
|
||||
auto* blobs = current_blobs(mod, true);
|
||||
if (blobs == nullptr) {
|
||||
return MOD_UNAVAILABLE;
|
||||
}
|
||||
size_t total = size;
|
||||
for (const auto& [blobName, bytes] : *blobs) {
|
||||
if (blobName != name) {
|
||||
total += bytes.size();
|
||||
}
|
||||
}
|
||||
if (total > SAVE_BLOB_BUDGET_BYTES) {
|
||||
Log.error("[{}] save blob '{}' rejected: {} bytes would exceed the {}-byte budget",
|
||||
mod.metadata.id, name, total, SAVE_BLOB_BUDGET_BYTES);
|
||||
return MOD_UNAVAILABLE;
|
||||
}
|
||||
const auto* bytes = static_cast<const uint8_t*>(data);
|
||||
(*blobs)[name] = std::vector<uint8_t>{bytes, bytes + size};
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult save_get_blob(LoadedMod& mod, const char* name, void* buf, size_t& inoutSize) {
|
||||
auto* blobs = current_blobs(mod, false);
|
||||
if (blobs == nullptr) {
|
||||
return MOD_UNAVAILABLE;
|
||||
}
|
||||
const auto it = blobs->find(name);
|
||||
if (it == blobs->end()) {
|
||||
return MOD_UNAVAILABLE;
|
||||
}
|
||||
if (buf == nullptr) {
|
||||
inoutSize = it->second.size();
|
||||
return MOD_OK;
|
||||
}
|
||||
if (inoutSize < it->second.size()) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
std::memcpy(buf, it->second.data(), it->second.size());
|
||||
inoutSize = it->second.size();
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult save_delete_blob(LoadedMod& mod, const char* name) {
|
||||
auto* blobs = current_blobs(mod, false);
|
||||
if (blobs == nullptr) {
|
||||
return MOD_UNAVAILABLE;
|
||||
}
|
||||
return blobs->erase(name) != 0 ? MOD_OK : MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ModResult save_observe(LoadedMod& mod, SaveEventFn onNewSave, SaveEventFn onLoaded,
|
||||
SaveEventFn onWritten, void* userData, uint64_t& outHandle) {
|
||||
auto& observer = s_observers.emplace_back();
|
||||
observer.handle = s_nextHandle++;
|
||||
observer.mod = &mod;
|
||||
observer.onNewSave = onNewSave;
|
||||
observer.onLoaded = onLoaded;
|
||||
observer.onWritten = onWritten;
|
||||
observer.userData = userData;
|
||||
outHandle = observer.handle;
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
ModResult save_unobserve(LoadedMod& mod, uint64_t handle) {
|
||||
const auto removed = std::erase_if(s_observers, [&](const auto& observer) {
|
||||
return observer.handle == handle && observer.mod == &mod;
|
||||
});
|
||||
return removed != 0 ? MOD_OK : MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
|
||||
ModResult save_peek_blob(
|
||||
LoadedMod& mod, uint32_t slot, const char* name, void* buf, size_t& inoutSize) {
|
||||
if (slot >= kSlotCount) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
load_sidecar();
|
||||
const auto& mods = s_slots[slot].mods;
|
||||
const auto modIt = mods.find(mod.metadata.id);
|
||||
if (modIt == mods.end()) {
|
||||
return MOD_UNAVAILABLE;
|
||||
}
|
||||
const auto it = modIt->second.find(name);
|
||||
if (it == modIt->second.end()) {
|
||||
return MOD_UNAVAILABLE;
|
||||
}
|
||||
if (buf == nullptr) {
|
||||
inoutSize = it->second.size();
|
||||
return MOD_OK;
|
||||
}
|
||||
if (inoutSize < it->second.size()) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
std::memcpy(buf, it->second.data(), it->second.size());
|
||||
inoutSize = it->second.size();
|
||||
return MOD_OK;
|
||||
}
|
||||
|
||||
void save_remove_mod(LoadedMod& mod) {
|
||||
std::erase_if(s_observers, [&](const auto& observer) { return observer.mod == &mod; });
|
||||
// Slot blob data is deliberately kept: it belongs to the save, not the mod session.
|
||||
}
|
||||
|
||||
namespace {
|
||||
bool is_valid_blob_name(const char* name) {
|
||||
if (name == nullptr) {
|
||||
return false;
|
||||
}
|
||||
const std::string_view view{name};
|
||||
return !view.empty() && view.size() <= kMaxBlobNameLength;
|
||||
}
|
||||
|
||||
ModResult save_set_blob_(ModContext* context, const char* name, const void* data, size_t size) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !is_valid_blob_name(name) || (data == nullptr && size != 0) ||
|
||||
size > SAVE_BLOB_BUDGET_BYTES)
|
||||
{
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return save_set_blob(*mod, name, data, size);
|
||||
}
|
||||
|
||||
ModResult save_get_blob_(ModContext* context, const char* name, void* buf, size_t* inoutSize) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !is_valid_blob_name(name) || inoutSize == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return save_get_blob(*mod, name, buf, *inoutSize);
|
||||
}
|
||||
|
||||
ModResult save_delete_blob_(ModContext* context, const char* name) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !is_valid_blob_name(name)) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return save_delete_blob(*mod, name);
|
||||
}
|
||||
|
||||
ModResult save_observe_saves_(ModContext* context, SaveEventFn onNewSave, SaveEventFn onLoaded,
|
||||
SaveEventFn onWritten, void* userData, SaveObserverHandle* outHandle) {
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = 0;
|
||||
}
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || (onNewSave == nullptr && onLoaded == nullptr && onWritten == nullptr)) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
uint64_t handle = 0;
|
||||
const auto result = save_observe(*mod, onNewSave, onLoaded, onWritten, userData, handle);
|
||||
if (outHandle != nullptr) {
|
||||
*outHandle = handle;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
ModResult save_unobserve_saves_(ModContext* context, SaveObserverHandle handle) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || handle == 0) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return save_unobserve(*mod, handle);
|
||||
}
|
||||
|
||||
ModResult save_peek_blob_(
|
||||
ModContext* context, uint32_t slot, const char* name, void* buf, size_t* inoutSize) {
|
||||
auto* mod = mod_from_context(context);
|
||||
if (mod == nullptr || !is_valid_blob_name(name) || inoutSize == nullptr) {
|
||||
return MOD_INVALID_ARGUMENT;
|
||||
}
|
||||
return save_peek_blob(*mod, slot, name, buf, *inoutSize);
|
||||
}
|
||||
|
||||
constexpr SaveService s_saveService{
|
||||
.header = SERVICE_HEADER(SaveService, SAVE_SERVICE_MAJOR, SAVE_SERVICE_MINOR),
|
||||
.set_blob = save_set_blob_,
|
||||
.get_blob = save_get_blob_,
|
||||
.delete_blob = save_delete_blob_,
|
||||
.observe_saves = save_observe_saves_,
|
||||
.unobserve_saves = save_unobserve_saves_,
|
||||
.peek_blob = save_peek_blob_,
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
constinit const ServiceModule g_saveModule{
|
||||
.id = SAVE_SERVICE_ID,
|
||||
.majorVersion = SAVE_SERVICE_MAJOR,
|
||||
.minorVersion = SAVE_SERVICE_MINOR,
|
||||
.service = &s_saveService,
|
||||
.modDetached = save_remove_mod,
|
||||
};
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
@@ -1,27 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
// Save-lifecycle seam entry points for the mod save service, called from the slot-aware save
|
||||
// paths (d_file_select.cpp, d_menu_save.cpp, d_s_logo.cpp quick-load, src/dusk/autosave.cpp).
|
||||
// All three save slots live in one emulated-card file, so slot identity only exists at these
|
||||
// game-layer call sites.
|
||||
// slotData points at the slot's serialized save (QUEST_LOG_SIZE bytes) for the staleness
|
||||
// snapshot; it is read synchronously during the call.
|
||||
|
||||
namespace dusk::mods::svc {
|
||||
|
||||
// A new file was finalized in file select (name entry complete) for slot.
|
||||
void save_slot_new(uint32_t slot);
|
||||
// slot was decoded into the live game info (file select start / quick-load).
|
||||
void save_slot_loaded(uint32_t slot, const void* slotData);
|
||||
// slot was successfully written to the card (menu save / autosave).
|
||||
void save_slot_written(uint32_t slot, const void* slotData);
|
||||
// File-select copy/erase (both rewrite the card; the sidecar follows).
|
||||
void save_slot_copied(uint32_t fromSlot, uint32_t toSlot);
|
||||
void save_slot_erased(uint32_t slot);
|
||||
// File select opened: no slot is current until load/new fires again.
|
||||
void save_no_slot();
|
||||
|
||||
} // namespace dusk::mods
|
||||
@@ -22,8 +22,4 @@ struct ModMenuTabEntry {
|
||||
|
||||
std::vector<ModMenuTabEntry> ui_mod_menu_tabs();
|
||||
|
||||
namespace ui_impl {
|
||||
bool ui_any_document_visible();
|
||||
}
|
||||
|
||||
} // namespace dusk::mods::svc
|
||||
|
||||
+1
-1
@@ -908,7 +908,7 @@ void AIInit(u8* stack) {
|
||||
// In a real scenario, it would set up the audio interface and prepare it for use.
|
||||
}
|
||||
|
||||
void AIInitDMA(uintptr_t start_addr, u32 length) {
|
||||
void AIInitDMA(u32 start_addr, u32 length) {
|
||||
STUB_LOG();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
#include "utilities.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace dusk::utils {
|
||||
constexpr std::array<uint32_t, 256> generate_crc32_table() {
|
||||
std::array<uint32_t, 256> table{};
|
||||
for (uint32_t i = 0; i < 256; ++i) {
|
||||
uint32_t ch = i;
|
||||
for (size_t j = 0; j < 8; ++j) {
|
||||
ch = (ch & 1) != 0 ? 0xEDB88320 ^ ch >> 1 : ch >> 1;
|
||||
}
|
||||
table[i] = ch;
|
||||
}
|
||||
return table;
|
||||
}
|
||||
|
||||
constexpr std::array<uint32_t, 256> kCrc32Table = generate_crc32_table();
|
||||
|
||||
// CRC-32 (IEEE, reflected)
|
||||
uint32_t CRC32(const void* data, size_t size) {
|
||||
const auto* bytes = static_cast<const uint8_t*>(data);
|
||||
uint32_t crc = ~0u;
|
||||
for (size_t i = 0; i < size; ++i) {
|
||||
crc = crc >> 8 ^ kCrc32Table[static_cast<uint8_t>(crc ^ bytes[i])];
|
||||
}
|
||||
return ~crc;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
||||
namespace dusk::utils {
|
||||
uint32_t CRC32(const void* data, size_t size);
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
#include "helpers/cast.hpp"
|
||||
#include "global.h"
|
||||
|
||||
namespace dusk::helpers::cast::_impl {
|
||||
|
||||
void overrun_high() {
|
||||
CRASH("bounded cast overran!");
|
||||
}
|
||||
|
||||
void overrun_low() {
|
||||
CRASH("bounded cast overran!");
|
||||
}
|
||||
|
||||
} // namespace dusk::helpers::cast::_impl
|
||||
Reference in New Issue
Block a user