Merge branch 'master' into translations
18
CHANGES
|
@ -21,12 +21,19 @@ Emulation fixes:
|
|||
- GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378)
|
||||
- GB Audio: Fix channel 1/2 reseting edge cases (fixes mgba.io/i/1925)
|
||||
- GB Audio: Properly apply per-model audio differences
|
||||
- GB Audio: Revamp channel rendering
|
||||
- GB Audio: Fix APU re-enable timing glitch
|
||||
- GB I/O: Fix writing to WAVE RAM behavior (fixes mgba.io/i/1334)
|
||||
- GB Memory: Add cursory cartridge open bus emulation (fixes mgba.io/i/2032)
|
||||
- GB MBC: Fix edge case with Pocket Cam register accesses (fixes mgba.io/i/2557)
|
||||
- GB Serialize: Fix loading MBC1 states that affect bank 0 (fixes mgba.io/i/2402)
|
||||
- GB SIO: Fix bidirectional transfer starting (fixes mgba.io/i/2290)
|
||||
- GB Video: Draw SGB border pieces that overlap GB graphics (fixes mgba.io/i/1339)
|
||||
- GBA: Improve timing when not booting from BIOS
|
||||
- GBA: Fix expected entry point for multiboot ELFs (fixes mgba.io/i/2450)
|
||||
- GBA: Fix booting multiboot ROMs with no JOY entrypoint
|
||||
- GBA Audio: Adjust PSG sampling rate with SOUNDBIAS
|
||||
- GBA Audio: Sample FIFOs at SOUNDBIAS-set frequency
|
||||
- GBA BIOS: Work around IRQ handling hiccup in Mario & Luigi (fixes mgba.io/i/1059)
|
||||
- GBA BIOS: Initial HLE timing estimation of UnLz77 functions (fixes mgba.io/i/2141)
|
||||
- GBA DMA: Fix DMA source direction bits being cleared (fixes mgba.io/i/2410)
|
||||
|
@ -38,20 +45,29 @@ Emulation fixes:
|
|||
- GBA Video: Fix Hblank timing (fixes mgba.io/i/2131, mgba.io/i/2310)
|
||||
- GBA Video: Fix rare crash in modes 3-5
|
||||
- GBA Video: Fix sprites with mid-frame palette changes in GL (fixes mgba.io/i/2476)
|
||||
- GBA Video: Fix OBJ tile wrapping with 2D char mapping (fixes mgba.io/i/2443)
|
||||
- GBA Video: Fix horizontal lines in GL when charbase is changed (fixes mgba.io/i/1631)
|
||||
- GBA Video: Fix sprite layer priority updating in GL
|
||||
Other fixes:
|
||||
- ARM: Disassemble Thumb mov pseudo-instruction properly
|
||||
- Core: Don't attempt to restore rewind diffs past start of rewind
|
||||
- Core: Fix the runloop resuming after a game has crashed (fixes mgba.io/i/2451)
|
||||
- Core: Fix crash if library can't be opened
|
||||
- Debugger: Fix crash with extremely long CLI strings
|
||||
- FFmpeg: Fix crash when encoding audio with some containers
|
||||
- FFmpeg: Fix GIF recording (fixes mgba.io/i/2393)
|
||||
- GB: Fix temporary saves
|
||||
- GB: Fix replacing the ROM crashing when accessing ROM base
|
||||
- GB, GBA: Save writeback-pending masked saves on unload (fixes mgba.io/i/2396)
|
||||
- mGUI: Fix FPS counter after closing menu
|
||||
- Qt: Fix some hangs when using the debugger console
|
||||
- Qt: Fix crash when clicking past last tile in viewer
|
||||
- Qt: Fix preloading for ROM replacing
|
||||
- Qt: Fix screen not displaying on Wayland (fixes mgba.io/i/2190)
|
||||
- VFS: Failed file mapping should return NULL on POSIX
|
||||
Misc:
|
||||
- Core: Suspend runloop when a core crashes
|
||||
- Core: Add wallclock offset RTC type
|
||||
- Debugger: Save and restore CLI history
|
||||
- Debugger: GDB now works while the game is paused
|
||||
- Debugger: Add command to load external symbol file (fixes mgba.io/i/2480)
|
||||
|
@ -60,6 +76,7 @@ Misc:
|
|||
- GBA: Automatically skip BIOS if ROM has invalid logo
|
||||
- GBA: Refine multiboot detection (fixes mgba.io/i/2192)
|
||||
- GBA Cheats: Implement "never" type codes (closes mgba.io/i/915)
|
||||
- GBA Memory: Implement adjustable EWRAM waitstates (closes mgba.io/i/1276)
|
||||
- GBA DMA: Enhanced logging (closes mgba.io/i/2454)
|
||||
- GBA Video: Implement layer placement for OpenGL renderer (fixes mgba.io/i/1962)
|
||||
- GBA Video: Fix highlighting for sprites with mid-frame palette changes
|
||||
|
@ -79,6 +96,7 @@ Misc:
|
|||
- Qt: Add e-Card passing to the command line (closes mgba.io/i/2474)
|
||||
- Qt: Boot both a multiboot image and ROM with CLI args (closes mgba.io/i/1941)
|
||||
- Qt: Improve cheat parsing (fixes mgba.io/i/2297)
|
||||
- SDL: Support exposing an axis directly as the gyro value (closes mgba.io/i/2531)
|
||||
- Windows: Attach to console if present
|
||||
- Vita: Add bilinear filtering option (closes mgba.io/i/344)
|
||||
|
||||
|
|
|
@ -152,6 +152,9 @@ endif()
|
|||
mark_as_advanced(DISTBUILD)
|
||||
|
||||
set(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/${LIBDIR}")
|
||||
if(${CMAKE_INSTALL_PREFIX} STREQUAL "/usr")
|
||||
set(CMAKE_SKIP_RPATH ON)
|
||||
endif()
|
||||
|
||||
if (NOT DEFINED MANDIR)
|
||||
set(MANDIR ${CMAKE_INSTALL_MANDIR})
|
||||
|
@ -498,7 +501,11 @@ if(USE_EDITLINE)
|
|||
list(APPEND FEATURES EDITLINE)
|
||||
include_directories(AFTER ${LIBEDIT_INCLUDE_DIRS})
|
||||
link_directories(${LIBEDIT_LIBRARY_DIRS})
|
||||
set(DEBUGGER_LIB ${LIBEDIT_LIBRARIES})
|
||||
if(BUILD_STATIC)
|
||||
set(DEBUGGER_LIB ${LIBEDIT_STATIC_LIBRARIES})
|
||||
else()
|
||||
set(DEBUGGER_LIB ${LIBEDIT_LIBRARIES})
|
||||
endif()
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libedit2")
|
||||
list(APPEND FEATURE_SRC "${CMAKE_CURRENT_SOURCE_DIR}/src/feature/editline/cli-el-backend.c")
|
||||
else()
|
||||
|
@ -947,14 +954,17 @@ if(BUILD_OPENEMU)
|
|||
install(TARGETS ${BINARY_NAME}-openemu LIBRARY DESTINATION ${OE_LIBDIR} COMPONENT ${BINARY_NAME}.oecoreplugin NAMELINK_SKIP)
|
||||
endif()
|
||||
|
||||
if(BUILD_QT AND (WIN32 OR APPLE))
|
||||
if(BUILD_QT AND (WIN32 OR APPLE OR CMAKE_SYSTEM_NAME STREQUAL "Linux"))
|
||||
set(BUILD_UPDATER ON)
|
||||
endif()
|
||||
|
||||
if(BUILD_UPDATER)
|
||||
add_executable(updater-stub WIN32 ${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater-main.c)
|
||||
target_link_libraries(updater-stub ${OS_LIB} ${PLATFORM_LIBRARY} ${BINARY_NAME})
|
||||
set_target_properties(updater-stub PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FUNCTION_DEFINES}")
|
||||
add_executable(updater-stub WIN32 ${CORE_VFS_SRC} ${OS_SRC} ${UTIL_SRC} ${THIRD_PARTY_SRC}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/core/config.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater.c
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src/feature/updater-main.c)
|
||||
target_link_libraries(updater-stub ${OS_LIB} ${PLATFORM_LIBRARY})
|
||||
set_target_properties(updater-stub PROPERTIES COMPILE_DEFINITIONS "${OS_DEFINES};${FUNCTION_DEFINES};BUILD_STATIC")
|
||||
if(MSVC)
|
||||
set_target_properties(updater-stub PROPERTIES LINK_FLAGS /ENTRY:mainCRTStartup)
|
||||
else()
|
||||
|
|
|
@ -126,7 +126,7 @@ The recommended way to build for most platforms is to use Docker. Several Docker
|
|||
|
||||
To use a Docker image to build mGBA, simply run the following command while in the root of an mGBA checkout:
|
||||
|
||||
docker run --rm -t -v $PWD:/home/mgba/src mgba/windows:w32
|
||||
docker run --rm -t -v ${PWD}:/home/mgba/src mgba/windows:w32
|
||||
|
||||
This will produce a `build-win32` directory with the build products. Replace `mgba/windows:w32` with another Docker image for other platforms, which will produce a corresponding other directory. The following Docker images available on Docker Hub:
|
||||
|
||||
|
|
After Width: | Height: | Size: 788 B |
|
@ -1,2 +0,0 @@
|
|||
[testinfo]
|
||||
fail=1
|
Before Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 870 B |
|
@ -1,2 +0,0 @@
|
|||
[testinfo]
|
||||
fail=1
|
Before Width: | Height: | Size: 968 B |
After Width: | Height: | Size: 628 B |
|
@ -1,2 +0,0 @@
|
|||
[testinfo]
|
||||
fail=1
|
Before Width: | Height: | Size: 785 B |
After Width: | Height: | Size: 788 B |
|
@ -1,2 +0,0 @@
|
|||
[testinfo]
|
||||
fail=1
|
Before Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 890 B |
|
@ -1,2 +0,0 @@
|
|||
[testinfo]
|
||||
fail=1
|
Before Width: | Height: | Size: 975 B |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 882 B |
After Width: | Height: | Size: 726 B |
After Width: | Height: | Size: 724 B |
After Width: | Height: | Size: 740 B |
After Width: | Height: | Size: 253 B |
|
@ -0,0 +1,3 @@
|
|||
[testinfo]
|
||||
skip=1
|
||||
frames=1
|
|
@ -98,6 +98,7 @@ CXX_GUARD_START
|
|||
} \
|
||||
|
||||
DECLARE_VECTOR(StringList, char*);
|
||||
DECLARE_VECTOR(IntList, int);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -181,6 +181,8 @@ bool mCoreAutoloadSave(struct mCore* core);
|
|||
bool mCoreAutoloadPatch(struct mCore* core);
|
||||
bool mCoreAutoloadCheats(struct mCore* core);
|
||||
|
||||
bool mCoreLoadSaveFile(struct mCore* core, const char* path, bool temporary);
|
||||
|
||||
bool mCoreSaveState(struct mCore* core, int slot, int flags);
|
||||
bool mCoreLoadState(struct mCore* core, int slot, int flags);
|
||||
struct VFile* mCoreGetState(struct mCore* core, int slot, bool write);
|
||||
|
|
|
@ -194,6 +194,11 @@ struct mAVStream {
|
|||
void (*postAudioBuffer)(struct mAVStream*, struct blip_t* left, struct blip_t* right);
|
||||
};
|
||||
|
||||
struct mStereoSample {
|
||||
int16_t left;
|
||||
int16_t right;
|
||||
};
|
||||
|
||||
struct mKeyCallback {
|
||||
uint16_t (*readKeys)(struct mKeyCallback*);
|
||||
bool requireOpposingDirections;
|
||||
|
@ -234,6 +239,7 @@ enum mRTCGenericType {
|
|||
RTC_NO_OVERRIDE,
|
||||
RTC_FIXED,
|
||||
RTC_FAKE_EPOCH,
|
||||
RTC_WALLCLOCK_OFFSET,
|
||||
RTC_CUSTOM_START = 0x1000
|
||||
};
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ DECL_BITS(mMapCacheSystemInfo, TilesWide, 8, 4);
|
|||
DECL_BITS(mMapCacheSystemInfo, TilesHigh, 12, 4);
|
||||
DECL_BITS(mMapCacheSystemInfo, MacroTileSize, 16, 7);
|
||||
DECL_BITS(mMapCacheSystemInfo, MapAlign, 23, 2);
|
||||
DECL_BITS(mMapCacheSystemInfo, WriteAlign, 25, 2);
|
||||
|
||||
DECL_BITFIELD(mMapCacheEntryFlags, uint16_t);
|
||||
DECL_BITS(mMapCacheEntryFlags, PaletteId, 0, 4);
|
||||
|
|
|
@ -223,7 +223,7 @@ uint32_t ARMResolveMemoryAccess(struct ARMInstructionInfo* info, struct ARMRegis
|
|||
|
||||
#ifdef USE_DEBUGGERS
|
||||
struct mDebuggerSymbols;
|
||||
int ARMDisassemble(struct ARMInstructionInfo* info, struct ARMCore* core, const struct mDebuggerSymbols* symbols, uint32_t pc, char* buffer, int blen);
|
||||
int ARMDisassemble(const struct ARMInstructionInfo* info, struct ARMCore* core, const struct mDebuggerSymbols* symbols, uint32_t pc, char* buffer, int blen);
|
||||
#endif
|
||||
|
||||
CXX_GUARD_END
|
||||
|
|
|
@ -43,7 +43,7 @@ enum Operation {
|
|||
|
||||
struct Token {
|
||||
enum TokenType {
|
||||
TOKEN_ERROR_TYPE,
|
||||
TOKEN_ERROR_TYPE = 0,
|
||||
TOKEN_UINT_TYPE,
|
||||
TOKEN_IDENTIFIER_TYPE,
|
||||
TOKEN_OPERATOR_TYPE,
|
||||
|
@ -60,8 +60,10 @@ struct Token {
|
|||
|
||||
struct ParseTree {
|
||||
struct Token token;
|
||||
struct ParseTree* p;
|
||||
struct ParseTree* lhs;
|
||||
struct ParseTree* rhs;
|
||||
int precedence;
|
||||
};
|
||||
|
||||
size_t lexExpression(struct LexVector* lv, const char* string, size_t length, const char* eol);
|
||||
|
|
|
@ -87,7 +87,6 @@ struct GBAudioSquareControl {
|
|||
int frequency;
|
||||
int length;
|
||||
bool stop;
|
||||
int hi;
|
||||
};
|
||||
|
||||
struct GBAudioSweep {
|
||||
|
@ -104,6 +103,8 @@ struct GBAudioSquareChannel {
|
|||
struct GBAudioSweep sweep;
|
||||
struct GBAudioEnvelope envelope;
|
||||
struct GBAudioSquareControl control;
|
||||
int32_t lastUpdate;
|
||||
uint8_t index;
|
||||
int8_t sample;
|
||||
};
|
||||
|
||||
|
@ -112,6 +113,7 @@ struct GBAudioWaveChannel {
|
|||
bool bank;
|
||||
bool enable;
|
||||
|
||||
int8_t sample;
|
||||
unsigned length;
|
||||
int volume;
|
||||
|
||||
|
@ -124,7 +126,7 @@ struct GBAudioWaveChannel {
|
|||
uint32_t wavedata32[8];
|
||||
uint8_t wavedata8[16];
|
||||
};
|
||||
int8_t sample;
|
||||
int32_t nextUpdate;
|
||||
};
|
||||
|
||||
struct GBAudioNoiseChannel {
|
||||
|
@ -194,11 +196,6 @@ struct GBAudio {
|
|||
enum GBAudioStyle style;
|
||||
|
||||
struct mTimingEvent frameEvent;
|
||||
struct mTimingEvent ch1Event;
|
||||
struct mTimingEvent ch2Event;
|
||||
struct mTimingEvent ch3Event;
|
||||
struct mTimingEvent ch3Fade;
|
||||
struct mTimingEvent ch4Event;
|
||||
struct mTimingEvent sampleEvent;
|
||||
bool enable;
|
||||
|
||||
|
@ -239,8 +236,8 @@ void GBAudioWriteNR50(struct GBAudio* audio, uint8_t);
|
|||
void GBAudioWriteNR51(struct GBAudio* audio, uint8_t);
|
||||
void GBAudioWriteNR52(struct GBAudio* audio, uint8_t);
|
||||
|
||||
void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels);
|
||||
void GBAudioUpdateFrame(struct GBAudio* audio);
|
||||
void GBAudioUpdateChannel4(struct GBAudio* audio);
|
||||
|
||||
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right);
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ extern MGBA_EXPORT const uint32_t GBSavestateVersion;
|
|||
mLOG_DECLARE_CATEGORY(GB_STATE);
|
||||
|
||||
/* Savestate format:
|
||||
* 0x00000 - 0x00003: Version Magic (0x01000002)
|
||||
* 0x00000 - 0x00003: Version Magic (0x00400003)
|
||||
* 0x00004 - 0x00007: ROM CRC32
|
||||
* 0x00008: Game Boy model
|
||||
* 0x00009 - 0x0000B: Reserved (leave zero)
|
||||
|
@ -56,20 +56,23 @@ mLOG_DECLARE_CATEGORY(GB_STATE);
|
|||
* | bits 0 - 6: Remaining length
|
||||
* | bits 7 - 9: Next step
|
||||
* | bits 10 - 20: Shadow frequency register
|
||||
* | bits 21 - 31: Reserved
|
||||
* | bits 21 - 23: Duty index
|
||||
* | bits 24 - 31: Reserved
|
||||
* | 0x0004C - 0x0004F: Next frame
|
||||
* | 0x00050 - 0x00053: Next channel 3 fade
|
||||
* | 0x00050 - 0x00053: Reserved
|
||||
* | 0x00054 - 0x00057: Sweep state
|
||||
* | bits 0 - 2: Timesteps
|
||||
* | bits 3 - 31: Reserved
|
||||
* | 0x00058 - 0x0005B: Next event
|
||||
* | 0x00058 - 0x0005B: Last update
|
||||
* 0x0005C - 0x0006B: Audio channel 2 state
|
||||
* | 0x0005C - 0x0005F: Envelepe timing
|
||||
* | bits 0 - 2: Remaining length
|
||||
* | bits 3 - 5: Next step
|
||||
* | bits 6 - 31: Reserved
|
||||
* | bits 6 - 20: Reserved
|
||||
* | bits 21 - 23: Duty index
|
||||
* | bits 24 - 31: Reserved
|
||||
* | 0x00060 - 0x00067: Reserved
|
||||
* | 0x00068 - 0x0006B: Next event
|
||||
* | 0x00068 - 0x0006B: Last update
|
||||
* 0x0006C - 0x00093: Audio channel 3 state
|
||||
* | 0x0006C - 0x0008B: Wave banks
|
||||
* | 0x0008C - 0x0008D: Remaining length
|
||||
|
@ -208,7 +211,7 @@ DECL_BITFIELD(GBSerializedAudioEnvelope, uint32_t);
|
|||
DECL_BITS(GBSerializedAudioEnvelope, Length, 0, 7);
|
||||
DECL_BITS(GBSerializedAudioEnvelope, NextStep, 7, 3);
|
||||
DECL_BITS(GBSerializedAudioEnvelope, Frequency, 10, 11);
|
||||
|
||||
DECL_BITS(GBSerializedAudioEnvelope, DutyIndex, 21, 3);
|
||||
|
||||
DECL_BITFIELD(GBSerializedAudioSweep, uint32_t);
|
||||
DECL_BITS(GBSerializedAudioSweep, Time, 0, 3);
|
||||
|
@ -217,14 +220,14 @@ struct GBSerializedPSGState {
|
|||
struct {
|
||||
GBSerializedAudioEnvelope envelope;
|
||||
int32_t nextFrame;
|
||||
int32_t nextCh3Fade;
|
||||
int32_t reserved;
|
||||
GBSerializedAudioSweep sweep;
|
||||
uint32_t nextEvent;
|
||||
uint32_t lastUpdate;
|
||||
} ch1;
|
||||
struct {
|
||||
GBSerializedAudioEnvelope envelope;
|
||||
int32_t reserved[2];
|
||||
int32_t nextEvent;
|
||||
uint32_t lastUpdate;
|
||||
} ch2;
|
||||
struct {
|
||||
uint32_t wavebanks[8];
|
||||
|
|
|
@ -11,11 +11,13 @@
|
|||
CXX_GUARD_START
|
||||
|
||||
#include <mgba/core/cpu.h>
|
||||
#include <mgba/core/interface.h>
|
||||
#include <mgba/core/log.h>
|
||||
#include <mgba/internal/gb/audio.h>
|
||||
#include <mgba-util/circle-buffer.h>
|
||||
|
||||
#define GBA_AUDIO_FIFO_SIZE 8
|
||||
#define GBA_MAX_SAMPLES 16
|
||||
|
||||
#define MP2K_MAGIC 0x68736D53
|
||||
#define MP2K_MAX_SOUND_CHANNELS 12
|
||||
|
@ -34,7 +36,7 @@ struct GBAAudioFIFO {
|
|||
uint32_t internalSample;
|
||||
int internalRemaining;
|
||||
int dmaSource;
|
||||
int8_t sample;
|
||||
int8_t samples[GBA_MAX_SAMPLES];
|
||||
};
|
||||
|
||||
DECL_BITFIELD(GBARegisterSOUNDCNT_HI, uint16_t);
|
||||
|
@ -78,14 +80,16 @@ struct GBAAudio {
|
|||
bool enable;
|
||||
|
||||
size_t samples;
|
||||
unsigned sampleRate;
|
||||
|
||||
GBARegisterSOUNDBIAS soundbias;
|
||||
|
||||
struct GBAAudioMixer* mixer;
|
||||
bool externalMixing;
|
||||
int32_t sampleInterval;
|
||||
|
||||
int32_t lastSample;
|
||||
int sampleIndex;
|
||||
struct mStereoSample currentSamples[GBA_MAX_SAMPLES];
|
||||
|
||||
bool forceDisableChA;
|
||||
bool forceDisableChB;
|
||||
int masterVolume;
|
||||
|
@ -93,11 +97,6 @@ struct GBAAudio {
|
|||
struct mTimingEvent sampleEvent;
|
||||
};
|
||||
|
||||
struct GBAStereoSample {
|
||||
int16_t left;
|
||||
int16_t right;
|
||||
};
|
||||
|
||||
struct GBAMP2kADSR {
|
||||
uint8_t attack;
|
||||
uint8_t decay;
|
||||
|
@ -277,7 +276,7 @@ struct GBAAudioMixer {
|
|||
double tempo;
|
||||
double frame;
|
||||
|
||||
struct GBAStereoSample last;
|
||||
struct mStereoSample last;
|
||||
};
|
||||
|
||||
void GBAAudioInit(struct GBAAudio* audio, size_t samples);
|
||||
|
@ -308,6 +307,8 @@ uint32_t GBAAudioReadWaveRAM(struct GBAAudio* audio, int address);
|
|||
uint32_t GBAAudioWriteFIFO(struct GBAAudio* audio, int address, uint32_t value);
|
||||
void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles);
|
||||
|
||||
void GBAAudioSample(struct GBAAudio* audio, int32_t timestamp);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state);
|
||||
void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state);
|
||||
|
|
|
@ -154,6 +154,13 @@ enum GBAIORegisters {
|
|||
REG_POSTFLG = 0x300,
|
||||
REG_HALTCNT = 0x301,
|
||||
|
||||
REG_EXWAITCNT_LO = 0x800,
|
||||
REG_EXWAITCNT_HI = 0x802,
|
||||
|
||||
REG_INTERNAL_EXWAITCNT_LO = 0x210,
|
||||
REG_INTERNAL_EXWAITCNT_HI = 0x212,
|
||||
REG_INTERNAL_MAX = 0x214,
|
||||
|
||||
REG_DEBUG_STRING = 0xFFF600,
|
||||
REG_DEBUG_FLAGS = 0xFFF700,
|
||||
REG_DEBUG_ENABLE = 0xFFF780,
|
||||
|
|
|
@ -172,6 +172,7 @@ uint32_t GBAStoreMultiple(struct ARMCore*, uint32_t baseAddress, int mask, enum
|
|||
int* cycleCounter);
|
||||
|
||||
void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters);
|
||||
void GBAAdjustEWRAMWaitstates(struct GBA* gba, uint16_t parameters);
|
||||
|
||||
struct GBASerializedState;
|
||||
void GBAMemorySerialize(const struct GBAMemory* memory, struct GBASerializedState* state);
|
||||
|
|
|
@ -49,6 +49,7 @@ struct GBAVideoGLBackground {
|
|||
int enabled;
|
||||
unsigned priority;
|
||||
uint32_t charBase;
|
||||
uint32_t oldCharBase;
|
||||
int mosaic;
|
||||
int multipalette;
|
||||
uint32_t screenBase;
|
||||
|
@ -99,10 +100,12 @@ enum {
|
|||
GBA_GL_BG_TRANSFORM,
|
||||
GBA_GL_BG_RANGE,
|
||||
GBA_GL_BG_MOSAIC,
|
||||
GBA_GL_BG_OLDCHARBASE,
|
||||
|
||||
GBA_GL_OBJ_VRAM = 2,
|
||||
GBA_GL_OBJ_PALETTE,
|
||||
GBA_GL_OBJ_CHARBASE,
|
||||
GBA_GL_OBJ_TILE,
|
||||
GBA_GL_OBJ_STRIDE,
|
||||
GBA_GL_OBJ_LOCALPALETTE,
|
||||
GBA_GL_OBJ_INFLAGS,
|
||||
|
|
|
@ -20,7 +20,7 @@ extern MGBA_EXPORT const uint32_t GBASavestateVersion;
|
|||
mLOG_DECLARE_CATEGORY(GBA_STATE);
|
||||
|
||||
/* Savestate format:
|
||||
* 0x00000 - 0x00003: Version Magic (0x01000004)
|
||||
* 0x00000 - 0x00003: Version Magic (0x01000006)
|
||||
* 0x00004 - 0x00007: BIOS checksum (e.g. 0xBAAE187F for official BIOS)
|
||||
* 0x00008 - 0x0000B: ROM CRC32
|
||||
* 0x0000C - 0x0000F: Master cycles
|
||||
|
@ -39,20 +39,23 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* | bits 0 - 6: Remaining length
|
||||
* | bits 7 - 9: Next step
|
||||
* | bits 10 - 20: Shadow frequency register
|
||||
* | bits 21 - 31: Reserved
|
||||
* | bits 21 - 23: Duty index
|
||||
* | bits 24 - 31: Reserved
|
||||
* | 0x00134 - 0x00137: Next frame
|
||||
* | 0x00138 - 0x0013B: Next channel 3 fade
|
||||
* | 0x00138 - 0x0013B: Reserved
|
||||
* | 0x0013C - 0x0013F: Sweep state
|
||||
* | bits 0 - 2: Timesteps
|
||||
* | bits 3 - 7: Reserved
|
||||
* | 0x00140 - 0x00143: Next event
|
||||
* | 0x00140 - 0x00143: Last update
|
||||
* 0x00144 - 0x00153: Audio channel 2 state
|
||||
* | 0x00144 - 0x00147: Envelepe timing
|
||||
* | bits 0 - 2: Remaining length
|
||||
* | bits 3 - 5: Next step
|
||||
* | bits 6 - 31: Reserved
|
||||
* | bits 6 - 20: Reserved
|
||||
* | bits 21 - 23: Duty index
|
||||
* | bits 24 - 31: Reserved
|
||||
* | 0x00148 - 0x0014F: Reserved
|
||||
* | 0x00150 - 0x00153: Next event
|
||||
* | 0x00150 - 0x00153: Last update
|
||||
* 0x00154 - 0x0017B: Audio channel 3 state
|
||||
* | 0x00154 - 0x00173: Wave banks
|
||||
* | 0x00174 - 0x00175: Remaining length
|
||||
|
@ -68,7 +71,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* | 0x00188 - 0x0018B: Next event
|
||||
* 0x0018C - 0x001AB: Audio FIFO 1
|
||||
* 0x001AC - 0x001CB: Audio FIFO 2
|
||||
* 0x001CC - 0x001DF: Audio miscellaneous state
|
||||
* 0x001CC - 0x001EF: Audio miscellaneous state
|
||||
* | 0x001CC - 0x001CF: Channel A internal audio samples
|
||||
* | 0x001D0 - 0x001D3: Channel B internal audio samples
|
||||
* | 0x001D4 - 0x001D7: Next sample
|
||||
|
@ -101,9 +104,13 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* | bit 3: Is channel 3's memory readable?
|
||||
* | bit 4: Skip frame
|
||||
* | bits 5 - 7: Reserved
|
||||
* 0x001E0 - 0x001FF: Video miscellaneous state
|
||||
* | 0x001E0 - 0x001E3: Next event
|
||||
* | 0x001E4 - 0x001F7: Reserved
|
||||
* | 0x001E0 - 0x001E3: Last sample
|
||||
* | 0x001E4 - 0x001E7: Additional audio flags
|
||||
* | bits 0 - 3: Current sample index
|
||||
* | 0x001E8 - 0x001EF: Reserved
|
||||
* 0x001F0 - 0x001FF: Video miscellaneous state
|
||||
* | 0x001F0 - 0x001F3: Reserved
|
||||
* | 0x001F4 - 0x001F7: Next event
|
||||
* | 0x001F8 - 0x001FB: Miscellaneous flags
|
||||
* | 0x001FC - 0x001FF: Frame counter
|
||||
* 0x00200 - 0x00213: Timer 0
|
||||
|
@ -221,7 +228,11 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* 0x00320 - 0x00323: Next IRQ event
|
||||
* 0x00324 - 0x00327: Interruptable BIOS stall cycles
|
||||
* 0x00328 - 0x00367: Matrix memory mapping table
|
||||
* 0x00368 - 0x003FF: Reserved (leave zero)
|
||||
* 0x00368 - 0x0036F: Reserved (leave zero)
|
||||
* 0x00370 - 0x0037F: Audio FIFO A samples
|
||||
* 0x00380 - 0x0038F: Audio FIFO B samples
|
||||
* 0x00390 - 0x003CF: Audio rendered samples
|
||||
* 0x003D0 - 0x003FF: Reserved (leave zero)
|
||||
* 0x00400 - 0x007FF: I/O memory
|
||||
* 0x00800 - 0x00BFF: Palette
|
||||
* 0x00C00 - 0x00FFF: OAM
|
||||
|
@ -237,6 +248,9 @@ DECL_BITS(GBASerializedAudioFlags, FIFOSamplesB, 2, 3); // Yay legacy?
|
|||
DECL_BITS(GBASerializedAudioFlags, FIFOInternalSamplesA, 5, 2);
|
||||
DECL_BITS(GBASerializedAudioFlags, FIFOSamplesA, 7, 3);
|
||||
|
||||
DECL_BITFIELD(GBASerializedAudioFlags2, uint32_t);
|
||||
DECL_BITS(GBASerializedAudioFlags2, SampleIndex, 0, 4);
|
||||
|
||||
DECL_BITFIELD(GBASerializedVideoFlags, uint32_t);
|
||||
DECL_BITS(GBASerializedVideoFlags, Mode, 0, 2);
|
||||
|
||||
|
@ -297,11 +311,14 @@ struct GBASerializedState {
|
|||
int8_t sampleB;
|
||||
GBASerializedAudioFlags gbaFlags;
|
||||
GBSerializedAudioFlags flags;
|
||||
int32_t lastSample;
|
||||
GBASerializedAudioFlags2 gbaFlags2;
|
||||
int32_t reserved[2];
|
||||
} audio;
|
||||
|
||||
struct {
|
||||
int32_t reserved;
|
||||
int32_t nextEvent;
|
||||
int32_t reserved[5];
|
||||
GBASerializedVideoFlags flags;
|
||||
uint32_t frameCounter;
|
||||
} video;
|
||||
|
@ -378,8 +395,16 @@ struct GBASerializedState {
|
|||
int32_t biosStall;
|
||||
|
||||
uint32_t matrixMappings[16];
|
||||
uint32_t reservedMatrix[2];
|
||||
|
||||
uint32_t reserved[38];
|
||||
struct {
|
||||
int8_t chA[16];
|
||||
int8_t chB[16];
|
||||
} samples;
|
||||
|
||||
struct mStereoSample currentSamples[16];
|
||||
|
||||
uint32_t reserved[12];
|
||||
|
||||
uint16_t io[SIZE_IO >> 1];
|
||||
uint16_t pram[SIZE_PALETTE_RAM >> 1];
|
||||
|
|
|
@ -21,7 +21,7 @@ extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS];
|
|||
mLOG_DECLARE_CATEGORY(GBA_SIO);
|
||||
|
||||
enum {
|
||||
RCNT_INITIAL = 0x8000
|
||||
RCNT_INITIAL = -0x8000
|
||||
};
|
||||
|
||||
enum {
|
||||
|
|
|
@ -14,8 +14,9 @@ CXX_GUARD_START
|
|||
#include <mgba-util/table.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#define mSCRIPT_KV_PAIR(KEY, VALUE) { #KEY, VALUE }
|
||||
#define mSCRIPT_CONSTANT_PAIR(NS, CONST) { #CONST, mScriptValueCreateFromSInt(NS ## _ ## CONST) }
|
||||
#define mSCRIPT_CONSTANT_SENTINEL { NULL, NULL }
|
||||
#define mSCRIPT_KV_SENTINEL { NULL, NULL }
|
||||
|
||||
struct mScriptFrame;
|
||||
struct mScriptFunction;
|
||||
|
@ -28,7 +29,10 @@ struct mScriptContext {
|
|||
struct Table weakrefs;
|
||||
uint32_t nextWeakref;
|
||||
struct Table callbacks;
|
||||
struct Table callbackId;
|
||||
uint32_t nextCallbackId;
|
||||
struct mScriptValue* constants;
|
||||
struct Table docstrings;
|
||||
};
|
||||
|
||||
struct mScriptEngine2 {
|
||||
|
@ -80,9 +84,14 @@ void mScriptContextClearWeakref(struct mScriptContext*, uint32_t weakref);
|
|||
|
||||
void mScriptContextAttachStdlib(struct mScriptContext* context);
|
||||
void mScriptContextExportConstants(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* constants);
|
||||
void mScriptContextExportNamespace(struct mScriptContext* context, const char* nspace, struct mScriptKVPair* value);
|
||||
|
||||
void mScriptContextTriggerCallback(struct mScriptContext*, const char* callback);
|
||||
void mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value);
|
||||
uint32_t mScriptContextAddCallback(struct mScriptContext*, const char* callback, struct mScriptValue* value);
|
||||
void mScriptContextRemoveCallback(struct mScriptContext*, uint32_t cbid);
|
||||
|
||||
void mScriptContextSetDocstring(struct mScriptContext*, const char* key, const char* docstring);
|
||||
const char* mScriptContextGetDocstring(struct mScriptContext*, const char* key);
|
||||
|
||||
struct VFile;
|
||||
bool mScriptContextLoadVF(struct mScriptContext*, const char* name, struct VFile* vf);
|
||||
|
|
|
@ -334,7 +334,7 @@ CXX_GUARD_START
|
|||
mSCRIPT_DECLARE_STRUCT_C_METHOD_WITH_DEFAULTS(TYPE, RETURN, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
|
||||
|
||||
#define mSCRIPT_DECLARE_STRUCT_VOID_CD_METHOD_WITH_DEFAULTS(TYPE, NAME, NPARAMS, ...) \
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_C_METHOD_WITH_DEFAU(TYPE, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_C_METHOD_WITH_DEFAULTS(TYPE, NAME, p0->NAME, NPARAMS, __VA_ARGS__)
|
||||
|
||||
#define mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(TYPE, NAME) \
|
||||
static const struct mScriptValue _mSTStructBindingDefaults_ ## TYPE ## _ ## NAME[mSCRIPT_PARAMS_MAX] = { \
|
||||
|
@ -459,6 +459,7 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_MAKE_S64(VALUE) mSCRIPT_MAKE(S64, VALUE)
|
||||
#define mSCRIPT_MAKE_U64(VALUE) mSCRIPT_MAKE(U64, VALUE)
|
||||
#define mSCRIPT_MAKE_F64(VALUE) mSCRIPT_MAKE(F64, VALUE)
|
||||
#define mSCRIPT_MAKE_BOOL(VALUE) mSCRIPT_MAKE(BOOL, VALUE)
|
||||
#define mSCRIPT_MAKE_CHARP(VALUE) mSCRIPT_MAKE(CHARP, VALUE)
|
||||
#define mSCRIPT_MAKE_S(STRUCT, VALUE) mSCRIPT_MAKE(S(STRUCT), VALUE)
|
||||
#define mSCRIPT_MAKE_CS(STRUCT, VALUE) mSCRIPT_MAKE(CS(STRUCT), VALUE)
|
||||
|
|
|
@ -27,6 +27,7 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_C_S64 int64_t
|
||||
#define mSCRIPT_TYPE_C_U64 uint64_t
|
||||
#define mSCRIPT_TYPE_C_F64 double
|
||||
#define mSCRIPT_TYPE_C_BOOL bool
|
||||
#define mSCRIPT_TYPE_C_STR struct mScriptString*
|
||||
#define mSCRIPT_TYPE_C_CHARP const char*
|
||||
#define mSCRIPT_TYPE_C_PTR void*
|
||||
|
@ -41,6 +42,7 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_C_PS(X) void
|
||||
#define mSCRIPT_TYPE_C_PCS(X) void
|
||||
#define mSCRIPT_TYPE_C_WSTR struct mScriptValue*
|
||||
#define mSCRIPT_TYPE_C_WLIST struct mScriptValue*
|
||||
#define mSCRIPT_TYPE_C_W(X) struct mScriptValue*
|
||||
#define mSCRIPT_TYPE_C_CW(X) const struct mScriptValue*
|
||||
|
||||
|
@ -54,6 +56,7 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_FIELD_S64 s64
|
||||
#define mSCRIPT_TYPE_FIELD_U64 u64
|
||||
#define mSCRIPT_TYPE_FIELD_F64 f64
|
||||
#define mSCRIPT_TYPE_FIELD_BOOL u32
|
||||
#define mSCRIPT_TYPE_FIELD_STR string
|
||||
#define mSCRIPT_TYPE_FIELD_CHARP copaque
|
||||
#define mSCRIPT_TYPE_FIELD_PTR opaque
|
||||
|
@ -67,6 +70,7 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_FIELD_PS(STRUCT) opaque
|
||||
#define mSCRIPT_TYPE_FIELD_PCS(STRUCT) copaque
|
||||
#define mSCRIPT_TYPE_FIELD_WSTR opaque
|
||||
#define mSCRIPT_TYPE_FIELD_WLIST opaque
|
||||
#define mSCRIPT_TYPE_FIELD_W(TYPE) opaque
|
||||
#define mSCRIPT_TYPE_FIELD_CW(TYPE) opaque
|
||||
|
||||
|
@ -80,6 +84,7 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_MS_S64 (&mSTSInt64)
|
||||
#define mSCRIPT_TYPE_MS_U64 (&mSTUInt64)
|
||||
#define mSCRIPT_TYPE_MS_F64 (&mSTFloat64)
|
||||
#define mSCRIPT_TYPE_MS_BOOL (&mSTBool)
|
||||
#define mSCRIPT_TYPE_MS_STR (&mSTString)
|
||||
#define mSCRIPT_TYPE_MS_CHARP (&mSTCharPtr)
|
||||
#define mSCRIPT_TYPE_MS_LIST (&mSTList)
|
||||
|
@ -92,6 +97,7 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT)
|
||||
#define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructConstPtr_ ## STRUCT)
|
||||
#define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper)
|
||||
#define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper)
|
||||
#define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE)
|
||||
#define mSCRIPT_TYPE_MS_CW(TYPE) (&mSTWrapperConst_ ## TYPE)
|
||||
|
||||
|
@ -106,8 +112,10 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_CMP_U64(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_U64, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_S64(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_S64, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_F64(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_F64, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_BOOL(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_BOOL, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_STR(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_CHARP(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_LIST(TYPE) mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE)
|
||||
#define mSCRIPT_TYPE_CMP_PTR(TYPE) ((TYPE)->base >= mSCRIPT_TYPE_OPAQUE)
|
||||
#define mSCRIPT_TYPE_CMP_WRAPPER(TYPE) (true)
|
||||
#define mSCRIPT_TYPE_CMP_S(STRUCT) mSCRIPT_TYPE_MS_S(STRUCT)->name == _mSCRIPT_FIELD_NAME
|
||||
|
@ -115,6 +123,7 @@ CXX_GUARD_START
|
|||
#define mSCRIPT_TYPE_CMP_S_METHOD(STRUCT, NAME) mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME)->name == _mSCRIPT_FIELD_NAME
|
||||
#define mSCRIPT_TYPE_CMP(TYPE0, TYPE1) mSCRIPT_TYPE_CMP_ ## TYPE0(TYPE1)
|
||||
#define mSCRIPT_TYPE_CMP_WSTR(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_STR, TYPE) || mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_CHARP, TYPE))
|
||||
#define mSCRIPT_TYPE_CMP_WLIST(TYPE) (mSCRIPT_TYPE_CMP_GENERIC(mSCRIPT_TYPE_MS_LIST, TYPE))
|
||||
|
||||
enum mScriptTypeBase {
|
||||
mSCRIPT_TYPE_VOID = 0,
|
||||
|
@ -160,6 +169,7 @@ extern const struct mScriptType mSTFloat32;
|
|||
extern const struct mScriptType mSTSInt64;
|
||||
extern const struct mScriptType mSTUInt64;
|
||||
extern const struct mScriptType mSTFloat64;
|
||||
extern const struct mScriptType mSTBool;
|
||||
extern const struct mScriptType mSTString;
|
||||
extern const struct mScriptType mSTCharPtr;
|
||||
extern const struct mScriptType mSTList;
|
||||
|
@ -167,6 +177,9 @@ extern const struct mScriptType mSTTable;
|
|||
extern const struct mScriptType mSTWrapper;
|
||||
extern const struct mScriptType mSTWeakref;
|
||||
extern const struct mScriptType mSTStringWrapper;
|
||||
extern const struct mScriptType mSTListWrapper;
|
||||
|
||||
extern struct mScriptValue mScriptValueNull;
|
||||
|
||||
struct mScriptType;
|
||||
struct mScriptValue {
|
||||
|
@ -321,6 +334,7 @@ bool mScriptPopF32(struct mScriptList* list, float* out);
|
|||
bool mScriptPopS64(struct mScriptList* list, int64_t* out);
|
||||
bool mScriptPopU64(struct mScriptList* list, uint64_t* out);
|
||||
bool mScriptPopF64(struct mScriptList* list, double* out);
|
||||
bool mScriptPopBool(struct mScriptList* list, bool* out);
|
||||
bool mScriptPopPointer(struct mScriptList* list, void** out);
|
||||
|
||||
bool mScriptCast(const struct mScriptType* type, const struct mScriptValue* input, struct mScriptValue* output);
|
||||
|
|
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 3.2 KiB |
|
@ -0,0 +1,2 @@
|
|||
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" version="1.1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(-1.9946)"><path d="m101.49 9.18c0-1.656-1.343-3-3-3h-64.262c-1.656 0-3 1.344-3 3v109.64c0 1.656 1.344 3 3 3h51.262c8.837 0 16-7.164 16-16v-96.64z" fill="#d8d6d1" stroke="#000" stroke-width="1px"/><g transform="matrix(1 0 5.5511e-17 1 0 .34429)"><path d="m31.5 12.156h69.989" fill="none" stroke="#b9b4ad" stroke-width="1px"/></g><path d="M72.2,121.82L101.489,106" fill="none" stroke="#f2ebe2" stroke-width="1px"/><g transform="translate(-.95405)"><path d="M96.408,6.18L96.5,12.5" fill="none" stroke="#b9b4ad" stroke-width="1px"/></g><g transform="translate(-59.954)"><path d="M96.408,6.18L96.5,12.5" fill="none" stroke="#b9b4ad" stroke-width="1px"/></g><path d="m101.49 9.5c0-0.796-0.316-1.559-0.878-2.121-0.563-0.563-1.326-0.879-2.122-0.879h-64.989c-0.796 0-1.559 0.316-2.121 0.879-0.563 0.562-0.879 1.325-0.879 2.121v109c0 0.796 0.316 1.559 0.879 2.121 0.562 0.563 1.325 0.879 2.121 0.879h51.989c8.837 0 16-7.163 16-16v-96z" fill="none" stroke="#000" stroke-width="3px"/><g transform="matrix(.83673 -.3777 .31287 .69311 -23.362 51.852)"><path d="m61.877 104.68c0-0.726-0.488-1.315-1.089-1.315h-6.536c-0.601 0-1.089 0.589-1.089 1.315s0.488 1.315 1.089 1.315h6.536c0.601 0 1.089-0.589 1.089-1.315z" fill="#4e5247" stroke="#000" stroke-width="1.19px"/></g><g transform="matrix(.83673 -.3777 .31287 .69311 -11.362 51.852)"><path d="m61.877 104.68c0-0.726-0.488-1.315-1.089-1.315h-6.536c-0.601 0-1.089 0.589-1.089 1.315s0.488 1.315 1.089 1.315h6.536c0.601 0 1.089-0.589 1.089-1.315z" fill="#4e5247" stroke="#000" stroke-width="1.19px"/></g><circle cx="80.996" cy="87.339" r="3.822" fill="#c11c40" stroke="#000" stroke-width="1px"/><g transform="translate(11.504 -5.7393)"><circle cx="80.996" cy="87.339" r="3.822" fill="#c11c40" stroke="#000" stroke-width="1px"/></g><g transform="translate(-3.7654 1.1875)"><path d="m79.313 108.69 5.505 9.25" fill="none" stroke="#b9b4ad" stroke-width="1px"/></g><g transform="translate(-.2654 -.5125)"><path d="m79.313 108.69 5.505 9.25" fill="none" stroke="#b9b4ad" stroke-width="1px"/></g><g transform="translate(3.2346 -2.2125)"><path d="m79.313 108.69 5.505 9.25" fill="none" stroke="#b9b4ad" stroke-width="1px"/></g><g transform="translate(6.7346 -3.9125)"><path d="m79.313 108.69 5.505 9.25" fill="none" stroke="#b9b4ad" stroke-width="1px"/></g><g transform="translate(10.235 -5.6125)"><path d="m79.313 108.69 5.505 9.25" fill="none" stroke="#b9b4ad" stroke-width="1px"/></g><g transform="translate(13.735 -7.3125)"><path d="m79.313 108.69 5.505 9.25" fill="none" stroke="#b9b4ad" stroke-width="1px"/></g><g transform="translate(0 .40778)"><path d="m96.5 18.203c0-1.104-0.895-2-2-2h-56.964c-1.105 0-2 0.896-2 2v40.778c0 1.105 0.895 2 2 2h49.964c4.971 0 9-4.029 9-9v-33.778z" fill="#848484" stroke="#000" stroke-width="1px"/></g><g transform="matrix(.98813 0 0 .99674 .59124 .25498)"><rect x="47.472" y="21.314" width="37.444" height="34.111" fill="#809a24" stroke="#000" stroke-width="1.01px"/></g><circle cx="40.583" cy="33.426" r="1.389" fill="#ff0025"/><path d="m48.5 82.456h6v5h-6v6h-5v-6h-6v-5h6v-6h5v6z" fill="#4e5247" stroke="#000" stroke-width="1px"/></g></svg>
|
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 5.5 KiB |
|
@ -0,0 +1,2 @@
|
|||
<!DOCTYPE svg PUBLIC '-//W3C//DTD SVG 1.1//EN' 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd'>
|
||||
<svg clip-rule="evenodd" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-miterlimit="1.5" version="1.1" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"><g transform="translate(0 -1.1259)"><path d="m115.98 43.503 1.894-0.666s-0.87-2.15-1.894-3.481-2.356-2.151-4.25-2.867c-1.894-0.717-7.834-0.82-10.496-1.434s-7.117-1.331-8.909-1.536-1.997 1.843-1.997 1.843l25.652 8.141z" fill="#a4a6b4" stroke="#000" stroke-width="1px"/><g transform="matrix(-1 0 0 1 128 0)"><path d="m115.98 43.503 1.894-0.666s-0.87-2.15-1.894-3.481-2.356-2.151-4.25-2.867c-1.894-0.717-7.834-0.82-10.496-1.434s-7.117-1.331-8.909-1.536-1.997 1.843-1.997 1.843l25.652 8.141z" fill="#a4a6b4" stroke="#000" stroke-width="1px"/></g><g transform="translate(1.55)"><path d="m62.45 97.95c9.766 0 23.582-1.214 29.8-3.2 1.966-0.628 4.816-2.128 6.8-2.6 4.2-1 8.8-2.6 13-4 3.78-1.26 5.739-4.265 6.4-7.2 1.6-7.1-1.6-32.6-2.2-34.9s-1.2-5.1-4.2-5.7-11.4-2.6-16.4-5.2-9.5-2.5-9.5-2.5-39.5-0.333-47.4 0c0 0-4.5-0.1-9.5 2.5s-13.4 4.6-16.4 5.2-3.6 3.4-4.2 5.7-3.8 27.8-2.2 34.9c0.661 2.935 2.62 5.94 6.4 7.2 4.2 1.4 8.8 3 13 4 1.984 0.472 4.834 1.972 6.8 2.6 6.218 1.986 20.034 3.2 29.8 3.2" fill="#aa86d5" stroke="#000" stroke-width="3px"/></g><g transform="translate(.1024 -.0512)"><circle cx="100.67" cy="43.964" r=".947" fill="#a0ff2a"/></g><path d="m64 39.462c-9.152 0-23.024 1.18-25.584 1.82s-2.687 1.92-2.944 3.264c-1.152 6.016-2.713 30.078-2.048 37.608 0.221 2.507 1.28 3.935 4.16 5.151 3.43 1.448 13.536 3.52 26.416 3.52s22.986-2.072 26.416-3.52c2.88-1.216 3.939-2.644 4.16-5.151 0.665-7.53-0.896-31.592-2.048-37.608-0.257-1.344-0.384-2.624-2.944-3.264s-16.432-1.82-25.584-1.82"/><g transform="matrix(1.0064 0 0 .98449 -.23513 1.695)"><rect x="38.984" y="44.803" width="49.68" height="33.52" fill="#a4a6b4" stroke="#000" stroke-width="1px"/></g><g transform="translate(.48 .30537)"><circle cx="100.79" cy="61.303" r="3.84" fill="#a4a6b4" stroke="#000" stroke-width="1px"/></g><g transform="translate(10.2 -3.5346)"><circle cx="100.79" cy="61.303" r="3.84" fill="#a4a6b4" stroke="#000" stroke-width="1px"/></g><g transform="matrix(.97364 .22811 -.22811 .97364 17.954 -4.6988)"><path d="m29.304 77.118h-8.619c-0.994 0-1.8-0.806-1.8-1.8 0-0.993 0.806-1.8 1.8-1.8h8.619c0.993 0 1.8 0.807 1.8 1.8 0 0.994-0.807 1.8-1.8 1.8z" fill="none" stroke="#986cc4" stroke-width="1px"/></g><g transform="translate(.24 1.7764e-15)"><circle cx="29.064" cy="75.318" r="1.8" fill="#a4a6b4" stroke="#000" stroke-width="1px"/></g><g transform="translate(0 6.4648)"><g transform="matrix(.97364 .22811 -.22811 .97364 17.954 -4.6988)"><path d="m29.304 77.118h-8.619c-0.994 0-1.8-0.806-1.8-1.8 0-0.993 0.806-1.8 1.8-1.8h8.619c0.993 0 1.8 0.807 1.8 1.8 0 0.994-0.807 1.8-1.8 1.8z" fill="none" stroke="#986cc4" stroke-width="1px"/></g><g transform="translate(.24 1.7764e-15)"><circle cx="29.064" cy="75.318" r="1.8" fill="#a4a6b4" stroke="#000" stroke-width="1px"/></g></g><g transform="matrix(1.0302 0 0 1.0302 -.40806 -2.0201)"><path d="m24.051 57.052h4.98v4.6h-4.98v4.98h-4.6v-4.98h-4.98v-4.6h4.98v-4.98h4.6v4.98z" fill="#a4a6b4" stroke="#000" stroke-width=".97px"/></g><path d="m97.413 74.335 11.879-2.783" fill="none" stroke="#986cc4" stroke-width="1px"/><g transform="translate(0 2.3565)"><path d="m97.413 74.335 11.879-2.783" fill="none" stroke="#986cc4" stroke-width="1px"/></g><g transform="translate(0 4.7565)"><path d="m97.413 74.335 11.879-2.783" fill="none" stroke="#986cc4" stroke-width="1px"/></g><g transform="translate(0 7.1565)"><path d="m97.413 74.335 11.879-2.783" fill="none" stroke="#986cc4" stroke-width="1px"/></g><g transform="translate(0 9.4565)"><path d="m97.413 74.335 11.879-2.783" fill="none" stroke="#986cc4" stroke-width="1px"/></g></g></svg>
|
After Width: | Height: | Size: 3.7 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 10 KiB |
|
@ -68,22 +68,16 @@ local GBAGameEn = Game:new{
|
|||
local Generation1En = GBGameEn:new{
|
||||
_boxMonSize=33,
|
||||
_partyMonSize=44,
|
||||
_readBoxMon=readBoxMonGen1,
|
||||
_readPartyMon=readPartyMonGen1,
|
||||
}
|
||||
|
||||
local Generation2En = GBGameEn:new{
|
||||
_boxMonSize=32,
|
||||
_partyMonSize=48,
|
||||
_readBoxMon=readBoxMonGen2,
|
||||
_readPartyMon=readPartyMonGen2,
|
||||
}
|
||||
|
||||
local Generation3En = GBAGameEn:new{
|
||||
_boxMonSize=80,
|
||||
_partyMonSize=100,
|
||||
_readBoxMon=readBoxMonGen3,
|
||||
_readPartyMon=readPartyMonGen3,
|
||||
}
|
||||
|
||||
GBGameEn._charmap = { [0]=
|
||||
|
@ -475,7 +469,6 @@ gameCrc32 = {
|
|||
[0x9f7fdd53] = gameRBEn, -- Red
|
||||
[0xd6da8a1a] = gameRBEn, -- Blue
|
||||
[0x7d527d62] = gameYellowEn,
|
||||
[0x3358e30a] = gameCrystal, -- Crystal rev 1
|
||||
[0x84ee4776] = gameFireRedEnR1,
|
||||
[0xdaffecec] = gameLeafGreenEnR1,
|
||||
}
|
||||
|
@ -506,10 +499,21 @@ function detectGame()
|
|||
console:error("Unknown game!")
|
||||
else
|
||||
console:log("Found game: " .. game.name)
|
||||
if not partyBuffer then
|
||||
partyBuffer = console:createBuffer("Party")
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function updateBuffer()
|
||||
if not game or not partyBuffer then
|
||||
return
|
||||
end
|
||||
printPartyStatus(game, partyBuffer)
|
||||
end
|
||||
|
||||
callbacks:add("start", detectGame)
|
||||
callbacks:add("frame", updateBuffer)
|
||||
if emu then
|
||||
detectGame()
|
||||
end
|
||||
|
|
|
@ -190,14 +190,12 @@ static struct ARMDebugBreakpoint* _lookupBreakpoint(struct ARMDebugBreakpointLis
|
|||
static void _destroyBreakpoint(struct ARMDebugBreakpoint* breakpoint) {
|
||||
if (breakpoint->d.condition) {
|
||||
parseFree(breakpoint->d.condition);
|
||||
free(breakpoint->d.condition);
|
||||
}
|
||||
}
|
||||
|
||||
static void _destroyWatchpoint(struct mWatchpoint* watchpoint) {
|
||||
if (watchpoint->condition) {
|
||||
parseFree(watchpoint->condition);
|
||||
free(watchpoint->condition);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -190,6 +190,9 @@ static int _decodeMemory(struct ARMMemoryAccess memory, struct ARMCore* cpu, con
|
|||
case 4:
|
||||
value = cpu->memory.load32(cpu, addrBase, NULL);
|
||||
break;
|
||||
default:
|
||||
// Should never be reached
|
||||
abort();
|
||||
}
|
||||
const char* label = NULL;
|
||||
if (symbols) {
|
||||
|
@ -378,10 +381,11 @@ static const char* _armAccessTypeStrings[] = {
|
|||
""
|
||||
};
|
||||
|
||||
int ARMDisassemble(struct ARMInstructionInfo* info, struct ARMCore* cpu, const struct mDebuggerSymbols* symbols, uint32_t pc, char* buffer, int blen) {
|
||||
int ARMDisassemble(const struct ARMInstructionInfo* info, struct ARMCore* cpu, const struct mDebuggerSymbols* symbols, uint32_t pc, char* buffer, int blen) {
|
||||
const char* mnemonic = _armMnemonicStrings[info->mnemonic];
|
||||
int written;
|
||||
int total = 0;
|
||||
bool skip3 = false;
|
||||
const char* cond = "";
|
||||
if (info->condition != ARM_CONDITION_AL && info->condition < ARM_CONDITION_NV) {
|
||||
cond = _armConditions[info->condition];
|
||||
|
@ -398,6 +402,11 @@ int ARMDisassemble(struct ARMInstructionInfo* info, struct ARMCore* cpu, const s
|
|||
flags = _armAccessTypeStrings[info->memory.width];
|
||||
break;
|
||||
case ARM_MN_ADD:
|
||||
if ((info->operandFormat & (ARM_OPERAND_3 | ARM_OPERAND_4)) == ARM_OPERAND_IMMEDIATE_3 && info->op3.immediate == 0 && info->execMode == MODE_THUMB) {
|
||||
skip3 = true;
|
||||
mnemonic = "mov";
|
||||
}
|
||||
// Fall through
|
||||
case ARM_MN_ADC:
|
||||
case ARM_MN_AND:
|
||||
case ARM_MN_ASR:
|
||||
|
@ -406,7 +415,6 @@ int ARMDisassemble(struct ARMInstructionInfo* info, struct ARMCore* cpu, const s
|
|||
case ARM_MN_LSL:
|
||||
case ARM_MN_LSR:
|
||||
case ARM_MN_MLA:
|
||||
case ARM_MN_MOV:
|
||||
case ARM_MN_MUL:
|
||||
case ARM_MN_MVN:
|
||||
case ARM_MN_ORR:
|
||||
|
@ -497,26 +505,28 @@ int ARMDisassemble(struct ARMInstructionInfo* info, struct ARMCore* cpu, const s
|
|||
written = _decodeShift(info->op2, false, buffer, blen);
|
||||
ADVANCE(written);
|
||||
}
|
||||
if (info->operandFormat & ARM_OPERAND_3) {
|
||||
strlcpy(buffer, ", ", blen);
|
||||
ADVANCE(2);
|
||||
}
|
||||
if (info->operandFormat & ARM_OPERAND_IMMEDIATE_3) {
|
||||
written = snprintf(buffer, blen, "#%i", info->op3.immediate);
|
||||
ADVANCE(written);
|
||||
} else if (info->operandFormat & ARM_OPERAND_MEMORY_3) {
|
||||
written = _decodeMemory(info->memory, cpu, symbols, pc, buffer, blen);
|
||||
ADVANCE(written);
|
||||
} else if (info->operandFormat & ARM_OPERAND_REGISTER_3) {
|
||||
written = _decodeRegister(info->op3.reg, buffer, blen);
|
||||
ADVANCE(written);
|
||||
}
|
||||
if (info->operandFormat & ARM_OPERAND_SHIFT_REGISTER_3) {
|
||||
written = _decodeShift(info->op3, true, buffer, blen);
|
||||
ADVANCE(written);
|
||||
} else if (info->operandFormat & ARM_OPERAND_SHIFT_IMMEDIATE_3) {
|
||||
written = _decodeShift(info->op3, false, buffer, blen);
|
||||
ADVANCE(written);
|
||||
if (!skip3) {
|
||||
if (info->operandFormat & ARM_OPERAND_3) {
|
||||
strlcpy(buffer, ", ", blen);
|
||||
ADVANCE(2);
|
||||
}
|
||||
if (info->operandFormat & ARM_OPERAND_IMMEDIATE_3) {
|
||||
written = snprintf(buffer, blen, "#%i", info->op3.immediate);
|
||||
ADVANCE(written);
|
||||
} else if (info->operandFormat & ARM_OPERAND_MEMORY_3) {
|
||||
written = _decodeMemory(info->memory, cpu, symbols, pc, buffer, blen);
|
||||
ADVANCE(written);
|
||||
} else if (info->operandFormat & ARM_OPERAND_REGISTER_3) {
|
||||
written = _decodeRegister(info->op3.reg, buffer, blen);
|
||||
ADVANCE(written);
|
||||
}
|
||||
if (info->operandFormat & ARM_OPERAND_SHIFT_REGISTER_3) {
|
||||
written = _decodeShift(info->op3, true, buffer, blen);
|
||||
ADVANCE(written);
|
||||
} else if (info->operandFormat & ARM_OPERAND_SHIFT_IMMEDIATE_3) {
|
||||
written = _decodeShift(info->op3, false, buffer, blen);
|
||||
ADVANCE(written);
|
||||
}
|
||||
}
|
||||
if (info->operandFormat & ARM_OPERAND_4) {
|
||||
strlcpy(buffer, ", ", blen);
|
||||
|
|
|
@ -242,7 +242,7 @@ void mCoreConfigDirectory(char* out, size_t outLength) {
|
|||
CreateDirectoryW(wpath, NULL);
|
||||
if (PATH_SEP[0] != '\\') {
|
||||
WCHAR* pathSep;
|
||||
for (pathSep = wpath; pathSep = wcschr(pathSep, L'\\');) {
|
||||
for (pathSep = wpath; (pathSep = wcschr(pathSep, L'\\'));) {
|
||||
pathSep[0] = PATH_SEP[0];
|
||||
}
|
||||
}
|
||||
|
@ -284,7 +284,7 @@ void mCoreConfigPortablePath(char* out, size_t outLength) {
|
|||
PathRemoveFileSpecW(wpath);
|
||||
if (PATH_SEP[0] != '\\') {
|
||||
WCHAR* pathSep;
|
||||
for (pathSep = wpath; pathSep = wcschr(pathSep, L'\\');) {
|
||||
for (pathSep = wpath; (pathSep = wcschr(pathSep, L'\\'));) {
|
||||
pathSep[0] = PATH_SEP[0];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -259,6 +259,18 @@ bool mCoreAutoloadCheats(struct mCore* core) {
|
|||
return success;
|
||||
}
|
||||
|
||||
bool mCoreLoadSaveFile(struct mCore* core, const char* path, bool temporary) {
|
||||
struct VFile* vf = VFileOpen(path, O_CREAT | O_RDWR);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
if (temporary) {
|
||||
return core->loadTemporarySave(core, vf);
|
||||
} else {
|
||||
return core->loadSave(core, vf);
|
||||
}
|
||||
}
|
||||
|
||||
bool mCoreSaveState(struct mCore* core, int slot, int flags) {
|
||||
struct VFile* vf = mCoreGetState(core, slot, true);
|
||||
if (!vf) {
|
||||
|
@ -357,6 +369,8 @@ bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) {
|
|||
PNGWriteClose(png, info);
|
||||
return success;
|
||||
#else
|
||||
UNUSED(core);
|
||||
UNUSED(vf);
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
|
|
@ -10,92 +10,53 @@
|
|||
|
||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||
void mDirectorySetInit(struct mDirectorySet* dirs) {
|
||||
dirs->base = 0;
|
||||
dirs->archive = 0;
|
||||
dirs->save = 0;
|
||||
dirs->patch = 0;
|
||||
dirs->state = 0;
|
||||
dirs->screenshot = 0;
|
||||
dirs->cheats = 0;
|
||||
dirs->base = NULL;
|
||||
dirs->archive = NULL;
|
||||
dirs->save = NULL;
|
||||
dirs->patch = NULL;
|
||||
dirs->state = NULL;
|
||||
dirs->screenshot = NULL;
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
|
||||
static void mDirectorySetDetachDir(struct mDirectorySet* dirs, struct VDir* dir) {
|
||||
if (!dir) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (dirs->base == dir) {
|
||||
dirs->base = NULL;
|
||||
}
|
||||
if (dirs->archive == dir) {
|
||||
dirs->archive = NULL;
|
||||
}
|
||||
if (dirs->save == dir) {
|
||||
dirs->save = NULL;
|
||||
}
|
||||
if (dirs->patch == dir) {
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
if (dirs->state == dir) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->screenshot == dir) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
if (dirs->cheats == dir) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
|
||||
dir->close(dir);
|
||||
}
|
||||
|
||||
void mDirectorySetDeinit(struct mDirectorySet* dirs) {
|
||||
mDirectorySetDetachBase(dirs);
|
||||
|
||||
if (dirs->archive) {
|
||||
if (dirs->archive == dirs->save) {
|
||||
dirs->save = NULL;
|
||||
}
|
||||
if (dirs->archive == dirs->patch) {
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
if (dirs->archive == dirs->state) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->archive == dirs->screenshot) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
if (dirs->archive == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->archive->close(dirs->archive);
|
||||
dirs->archive = NULL;
|
||||
}
|
||||
|
||||
if (dirs->save) {
|
||||
if (dirs->save == dirs->patch) {
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
if (dirs->save == dirs->state) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->save == dirs->screenshot) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
if (dirs->save == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->save->close(dirs->save);
|
||||
dirs->save = NULL;
|
||||
}
|
||||
|
||||
if (dirs->patch) {
|
||||
if (dirs->patch == dirs->state) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->patch == dirs->screenshot) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
if (dirs->patch == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->patch->close(dirs->patch);
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
|
||||
if (dirs->state) {
|
||||
if (dirs->state == dirs->screenshot) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->state == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->state->close(dirs->state);
|
||||
dirs->state = NULL;
|
||||
}
|
||||
|
||||
if (dirs->screenshot) {
|
||||
if (dirs->screenshot == dirs->cheats) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
dirs->screenshot->close(dirs->screenshot);
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
|
||||
if (dirs->cheats) {
|
||||
dirs->cheats->close(dirs->cheats);
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
mDirectorySetDetachDir(dirs, dirs->archive);
|
||||
mDirectorySetDetachDir(dirs, dirs->save);
|
||||
mDirectorySetDetachDir(dirs, dirs->patch);
|
||||
mDirectorySetDetachDir(dirs, dirs->state);
|
||||
mDirectorySetDetachDir(dirs, dirs->screenshot);
|
||||
mDirectorySetDetachDir(dirs, dirs->cheats);
|
||||
}
|
||||
|
||||
void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) {
|
||||
|
@ -118,36 +79,20 @@ void mDirectorySetAttachBase(struct mDirectorySet* dirs, struct VDir* base) {
|
|||
}
|
||||
|
||||
void mDirectorySetDetachBase(struct mDirectorySet* dirs) {
|
||||
if (dirs->save == dirs->base) {
|
||||
dirs->save = NULL;
|
||||
}
|
||||
if (dirs->patch == dirs->base) {
|
||||
dirs->patch = NULL;
|
||||
}
|
||||
if (dirs->state == dirs->base) {
|
||||
dirs->state = NULL;
|
||||
}
|
||||
if (dirs->screenshot == dirs->base) {
|
||||
dirs->screenshot = NULL;
|
||||
}
|
||||
if (dirs->cheats == dirs->base) {
|
||||
dirs->cheats = NULL;
|
||||
}
|
||||
|
||||
if (dirs->base) {
|
||||
dirs->base->close(dirs->base);
|
||||
dirs->base = NULL;
|
||||
}
|
||||
mDirectorySetDetachDir(dirs, dirs->archive);
|
||||
mDirectorySetDetachDir(dirs, dirs->base);
|
||||
}
|
||||
|
||||
struct VFile* mDirectorySetOpenPath(struct mDirectorySet* dirs, const char* path, bool (*filter)(struct VFile*)) {
|
||||
dirs->archive = VDirOpenArchive(path);
|
||||
struct VDir* archive = VDirOpenArchive(path);
|
||||
struct VFile* file;
|
||||
if (dirs->archive) {
|
||||
file = VDirFindFirst(dirs->archive, filter);
|
||||
if (archive) {
|
||||
file = VDirFindFirst(archive, filter);
|
||||
if (!file) {
|
||||
dirs->archive->close(dirs->archive);
|
||||
dirs->archive = 0;
|
||||
archive->close(archive);
|
||||
} else {
|
||||
mDirectorySetDetachDir(dirs, dirs->archive);
|
||||
dirs->archive = archive;
|
||||
}
|
||||
} else {
|
||||
file = VFileOpen(path, O_RDONLY);
|
||||
|
|
|
@ -21,6 +21,7 @@ static void _rtcGenericSample(struct mRTCSource* source) {
|
|||
case RTC_NO_OVERRIDE:
|
||||
case RTC_FIXED:
|
||||
case RTC_FAKE_EPOCH:
|
||||
case RTC_WALLCLOCK_OFFSET:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +40,8 @@ static time_t _rtcGenericCallback(struct mRTCSource* source) {
|
|||
return rtc->value / 1000LL;
|
||||
case RTC_FAKE_EPOCH:
|
||||
return (rtc->value + rtc->p->frameCounter(rtc->p) * (rtc->p->frameCycles(rtc->p) * 1000LL) / rtc->p->frequency(rtc->p)) / 1000LL;
|
||||
case RTC_WALLCLOCK_OFFSET:
|
||||
return time(0) + rtc->value / 1000LL;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -243,9 +243,11 @@ void mLibraryLoadDirectory(struct mLibrary* library, const char* base, bool recu
|
|||
struct VFile* vf = dir->openFile(dir, current->filename, O_RDONLY);
|
||||
_mLibraryDeleteEntry(library, current);
|
||||
if (!vf) {
|
||||
mLibraryEntryFree(current);
|
||||
continue;
|
||||
}
|
||||
_mLibraryAddEntry(library, current->filename, base, vf);
|
||||
mLibraryEntryFree(current);
|
||||
}
|
||||
mLibraryListingDeinit(&entries);
|
||||
|
||||
|
@ -381,9 +383,12 @@ size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out,
|
|||
sqlite3_reset(library->select);
|
||||
_bindConstraints(library->select, constraints);
|
||||
|
||||
if (numEntries > SSIZE_MAX) {
|
||||
numEntries = SSIZE_MAX;
|
||||
}
|
||||
int countIndex = sqlite3_bind_parameter_index(library->select, ":count");
|
||||
int offsetIndex = sqlite3_bind_parameter_index(library->select, ":offset");
|
||||
sqlite3_bind_int64(library->select, countIndex, numEntries ? numEntries : -1);
|
||||
sqlite3_bind_int64(library->select, countIndex, numEntries ? (ssize_t) numEntries : -1);
|
||||
sqlite3_bind_int64(library->select, offsetIndex, offset);
|
||||
|
||||
size_t entryIndex;
|
||||
|
|
|
@ -70,11 +70,20 @@ void mMapCacheDeinit(struct mMapCache* cache) {
|
|||
|
||||
void mMapCacheWriteVRAM(struct mMapCache* cache, uint32_t address) {
|
||||
if (address >= cache->mapStart && address < cache->mapStart + cache->mapSize) {
|
||||
uint32_t align = 1 << (mMapCacheSystemInfoGetWriteAlign(cache->sysConfig) - mMapCacheSystemInfoGetMapAlign(cache->sysConfig));
|
||||
address -= cache->mapStart;
|
||||
struct mMapCacheEntry* status = &cache->status[address >> mMapCacheSystemInfoGetMapAlign(cache->sysConfig)];
|
||||
++status->vramVersion;
|
||||
status->flags = mMapCacheEntryFlagsClearVramClean(status->flags);
|
||||
status->tileStatus[mMapCacheEntryFlagsGetPaletteId(status->flags)].vramClean = 0;
|
||||
address >>= mMapCacheSystemInfoGetMapAlign(cache->sysConfig);
|
||||
|
||||
uint32_t i;
|
||||
for (i = 0; i < align; ++i) {
|
||||
if (address + i >= (cache->mapSize >> mMapCacheSystemInfoGetMapAlign(cache->sysConfig))) {
|
||||
break;
|
||||
}
|
||||
struct mMapCacheEntry* status = &cache->status[address + i];
|
||||
++status->vramVersion;
|
||||
status->flags = mMapCacheEntryFlagsClearVramClean(status->flags);
|
||||
status->tileStatus[mMapCacheEntryFlagsGetPaletteId(status->flags)].vramClean = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -311,7 +311,7 @@ static struct mScriptValue* _mScriptCoreChecksum(const struct mCore* core, int t
|
|||
break;
|
||||
}
|
||||
if (!size) {
|
||||
return NULL;
|
||||
return &mScriptValueNull;
|
||||
}
|
||||
void* data = calloc(1, size);
|
||||
core->checksum(core, data, type);
|
||||
|
@ -350,7 +350,7 @@ static struct mScriptValue* _mScriptCoreReadRange(struct mCore* core, uint32_t a
|
|||
static struct mScriptValue* _mScriptCoreReadRegister(const struct mCore* core, const char* regName) {
|
||||
int32_t out;
|
||||
if (!core->readRegister(core, regName, &out)) {
|
||||
return NULL;
|
||||
return &mScriptValueNull;
|
||||
}
|
||||
struct mScriptValue* value = mScriptValueAlloc(mSCRIPT_TYPE_MS_S32);
|
||||
value->value.s32 = out;
|
||||
|
@ -365,7 +365,7 @@ static struct mScriptValue* _mScriptCoreSaveState(struct mCore* core, int32_t fl
|
|||
struct VFile* vf = VFileMemChunk(NULL, 0);
|
||||
if (!mCoreSaveStateNamed(core, vf, flags)) {
|
||||
vf->close(vf);
|
||||
return NULL;
|
||||
return &mScriptValueNull;
|
||||
}
|
||||
void* buffer = vf->map(vf, vf->size(vf), MAP_READ);
|
||||
struct mScriptValue* value = mScriptStringCreateFromBytes(buffer, vf->size(vf));
|
||||
|
@ -373,6 +373,16 @@ static struct mScriptValue* _mScriptCoreSaveState(struct mCore* core, int32_t fl
|
|||
return value;
|
||||
}
|
||||
|
||||
static int _mScriptCoreSaveStateFile(struct mCore* core, const char* path, int flags) {
|
||||
struct VFile* vf = VFileOpen(path, O_WRONLY | O_TRUNC | O_CREAT);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool ok = mCoreSaveStateNamed(core, vf, flags);
|
||||
vf->close(vf);
|
||||
return ok;
|
||||
}
|
||||
|
||||
static int32_t _mScriptCoreLoadState(struct mCore* core, struct mScriptString* buffer, int32_t flags) {
|
||||
struct VFile* vf = VFileFromConstMemory(buffer->buffer, buffer->size);
|
||||
int ret = mCoreLoadStateNamed(core, vf, flags);
|
||||
|
@ -380,6 +390,15 @@ static int32_t _mScriptCoreLoadState(struct mCore* core, struct mScriptString* b
|
|||
return ret;
|
||||
}
|
||||
|
||||
static int _mScriptCoreLoadStateFile(struct mCore* core, const char* path, int flags) {
|
||||
struct VFile* vf = VFileOpen(path, O_RDONLY);
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
bool ok = mCoreLoadStateNamed(core, vf, flags);
|
||||
vf->close(vf);
|
||||
return ok;
|
||||
}
|
||||
static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename) {
|
||||
if (filename) {
|
||||
struct VFile* vf = VFileOpen(filename, O_WRONLY | O_CREAT | O_TRUNC);
|
||||
|
@ -393,6 +412,11 @@ static void _mScriptCoreTakeScreenshot(struct mCore* core, const char* filename)
|
|||
}
|
||||
}
|
||||
|
||||
// Loading functions
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, loadFile, mCoreLoadFile, 1, CHARP, path);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, autoloadSave, mCoreAutoloadSave, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD(mCore, BOOL, loadSaveFile, mCoreLoadSaveFile, 2, CHARP, path, BOOL, temporary);
|
||||
|
||||
// Info functions
|
||||
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mCore, S32, platform, 0);
|
||||
mSCRIPT_DECLARE_STRUCT_CD_METHOD(mCore, U32, frameCounter, 0);
|
||||
|
@ -430,10 +454,12 @@ mSCRIPT_DECLARE_STRUCT_METHOD(mCore, WSTR, readRegister, _mScriptCoreReadRegiste
|
|||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mCore, writeRegister, _mScriptCoreWriteRegister, 2, CHARP, regName, S32, value);
|
||||
|
||||
// Savestate functions
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, S32, saveStateSlot, mCoreSaveState, 2, S32, slot, S32, flags);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, BOOL, saveStateSlot, mCoreSaveState, 2, S32, slot, S32, flags);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, WSTR, saveStateBuffer, _mScriptCoreSaveState, 1, S32, flags);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, S32, loadStateSlot, mCoreLoadState, 2, S32, slot, S32, flags);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, S32, loadStateBuffer, _mScriptCoreLoadState, 2, STR, buffer, S32, flags);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, BOOL, saveStateFile, _mScriptCoreSaveStateFile, 2, CHARP, path, S32, flags);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, BOOL, loadStateSlot, mCoreLoadState, 2, S32, slot, S32, flags);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, BOOL, loadStateBuffer, _mScriptCoreLoadState, 2, STR, buffer, S32, flags);
|
||||
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mCore, BOOL, loadStateFile, _mScriptCoreLoadStateFile, 2, CHARP, path, S32, flags);
|
||||
|
||||
// Miscellaneous functions
|
||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD_WITH_DEFAULTS(mCore, screenshot, _mScriptCoreTakeScreenshot, 1, CHARP, filename);
|
||||
|
@ -442,6 +468,13 @@ mSCRIPT_DEFINE_STRUCT(mCore)
|
|||
mSCRIPT_DEFINE_CLASS_DOCSTRING(
|
||||
"An instance of an emulator core."
|
||||
)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Load a ROM file into the current state of this core")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadFile)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Load the save data associated with the currently loaded ROM file")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, autoloadSave)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Load save data from the given path. If the `temporary` flag is set, the given save data will not be written back to disk")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadSaveFile)
|
||||
|
||||
mSCRIPT_DEFINE_DOCSTRING("Get which platform is being emulated. See C.PLATFORM for possible values")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, platform)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Get the number of the current frame")
|
||||
|
@ -504,10 +537,14 @@ mSCRIPT_DEFINE_STRUCT(mCore)
|
|||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, saveStateSlot)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Save state and return as a buffer. See C.SAVESTATE for possible values for `flags`")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, saveStateBuffer)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Save state to the given path. See C.SAVESTATE for possible values for `flags`")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, saveStateFile)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Load state from the slot number. See C.SAVESTATE for possible values for `flags`")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadStateSlot)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Load state a buffer. See C.SAVESTATE for possible values for `flags`")
|
||||
mSCRIPT_DEFINE_DOCSTRING("Load state from a buffer. See C.SAVESTATE for possible values for `flags`")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadStateBuffer)
|
||||
mSCRIPT_DEFINE_DOCSTRING("Load state from the given path. See C.SAVESTATE for possible values for `flags`")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, loadStateFile)
|
||||
|
||||
mSCRIPT_DEFINE_DOCSTRING("Save a screenshot")
|
||||
mSCRIPT_DEFINE_STRUCT_METHOD(mCore, screenshot)
|
||||
|
@ -522,16 +559,26 @@ mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, saveStateSlot)
|
|||
mSCRIPT_S32(SAVESTATE_ALL)
|
||||
mSCRIPT_DEFINE_DEFAULTS_END;
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, saveStateBuffer)
|
||||
mSCRIPT_S32(SAVESTATE_ALL)
|
||||
mSCRIPT_DEFINE_DEFAULTS_END;
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, saveStateFile)
|
||||
mSCRIPT_NO_DEFAULT,
|
||||
mSCRIPT_S32(SAVESTATE_ALL)
|
||||
mSCRIPT_DEFINE_DEFAULTS_END;
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, loadStateSlot)
|
||||
mSCRIPT_NO_DEFAULT,
|
||||
mSCRIPT_S32(SAVESTATE_ALL & ~SAVESTATE_SAVEDATA)
|
||||
mSCRIPT_DEFINE_DEFAULTS_END;
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, saveStateBuffer)
|
||||
mSCRIPT_S32(SAVESTATE_ALL)
|
||||
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, loadStateBuffer)
|
||||
mSCRIPT_NO_DEFAULT,
|
||||
mSCRIPT_S32(SAVESTATE_ALL & ~SAVESTATE_SAVEDATA)
|
||||
mSCRIPT_DEFINE_DEFAULTS_END;
|
||||
|
||||
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, loadStateBuffer)
|
||||
mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mCore, loadStateFile)
|
||||
mSCRIPT_NO_DEFAULT,
|
||||
mSCRIPT_S32(SAVESTATE_ALL & ~SAVESTATE_SAVEDATA)
|
||||
mSCRIPT_DEFINE_DEFAULTS_END;
|
||||
|
@ -590,7 +637,7 @@ static struct mScriptValue* _mScriptCoreAdapterGet(struct mScriptCoreAdapter* ad
|
|||
struct mScriptValue val;
|
||||
struct mScriptValue core = mSCRIPT_MAKE(S(mCore), adapter->core);
|
||||
if (!mScriptObjectGet(&core, name, &val)) {
|
||||
return NULL;
|
||||
return &mScriptValueNull;
|
||||
}
|
||||
|
||||
struct mScriptValue* ret = malloc(sizeof(*ret));
|
||||
|
@ -710,14 +757,20 @@ mSCRIPT_DEFINE_STRUCT_BINDING_DEFAULTS(mScriptConsole, createBuffer)
|
|||
mSCRIPT_CHARP(NULL)
|
||||
mSCRIPT_DEFINE_DEFAULTS_END;
|
||||
|
||||
void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger* logger) {
|
||||
static struct mScriptConsole* _ensureConsole(struct mScriptContext* context) {
|
||||
struct mScriptValue* value = mScriptContextEnsureGlobal(context, "console", mSCRIPT_TYPE_MS_S(mScriptConsole));
|
||||
struct mScriptConsole* console = value->value.opaque;
|
||||
if (!console) {
|
||||
console = calloc(1, sizeof(*console));
|
||||
value->value.opaque = console;
|
||||
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
|
||||
mScriptContextSetDocstring(context, "console", "Singleton instance of struct::mScriptConsole");
|
||||
}
|
||||
return console;
|
||||
}
|
||||
|
||||
void mScriptContextAttachLogger(struct mScriptContext* context, struct mLogger* logger) {
|
||||
struct mScriptConsole* console = _ensureConsole(context);
|
||||
console->logger = logger;
|
||||
}
|
||||
|
||||
|
@ -771,14 +824,7 @@ mSCRIPT_DEFINE_STRUCT(mScriptTextBuffer)
|
|||
mSCRIPT_DEFINE_END;
|
||||
|
||||
void mScriptContextSetTextBufferFactory(struct mScriptContext* context, mScriptContextBufferFactory factory, void* cbContext) {
|
||||
struct mScriptValue* value = mScriptContextEnsureGlobal(context, "console", mSCRIPT_TYPE_MS_S(mScriptConsole));
|
||||
struct mScriptConsole* console = value->value.opaque;
|
||||
if (!console) {
|
||||
console = calloc(1, sizeof(*console));
|
||||
console->logger = mLogGetContext();
|
||||
value->value.opaque = console;
|
||||
value->flags = mSCRIPT_VALUE_FLAG_FREE_BUFFER;
|
||||
}
|
||||
struct mScriptConsole* console = _ensureConsole(context);
|
||||
console->textBufferFactory = factory;
|
||||
console->textBufferContext = cbContext;
|
||||
}
|
||||
|
|
|
@ -226,11 +226,9 @@ static bool _parseExpression(struct mDebugger* debugger, struct CLIDebugVector*
|
|||
}
|
||||
if (!mDebuggerEvaluateParseTree(debugger, tree, intValue, segmentValue)) {
|
||||
parseFree(tree);
|
||||
free(tree);
|
||||
return false;
|
||||
}
|
||||
parseFree(tree);
|
||||
free(tree);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -599,7 +597,6 @@ static struct ParseTree* _parseTree(const char** string) {
|
|||
if (error) {
|
||||
if (tree) {
|
||||
parseFree(tree);
|
||||
free(tree);
|
||||
}
|
||||
return NULL;
|
||||
} else {
|
||||
|
|
|
@ -11,6 +11,8 @@
|
|||
|
||||
DEFINE_VECTOR(LexVector, struct Token);
|
||||
|
||||
DEFINE_VECTOR(IntList, int32_t);
|
||||
|
||||
enum LexState {
|
||||
LEX_ERROR = -1,
|
||||
LEX_ROOT = 0,
|
||||
|
@ -493,16 +495,21 @@ static const int _operatorPrecedence[] = {
|
|||
[OP_DEREFERENCE] = 2,
|
||||
};
|
||||
|
||||
static struct ParseTree* _parseTreeCreate() {
|
||||
static struct ParseTree* _parseTreeCreate(void) {
|
||||
struct ParseTree* tree = malloc(sizeof(struct ParseTree));
|
||||
tree->token.type = TOKEN_ERROR_TYPE;
|
||||
tree->rhs = 0;
|
||||
tree->lhs = 0;
|
||||
tree->p = NULL;
|
||||
tree->rhs = NULL;
|
||||
tree->lhs = NULL;
|
||||
tree->precedence = INT_MAX;
|
||||
return tree;
|
||||
}
|
||||
|
||||
static size_t _parseExpression(struct ParseTree* tree, struct LexVector* lv, size_t i, int precedence, int* openParens) {
|
||||
struct ParseTree* newTree = 0;
|
||||
static size_t _parseExpression(struct ParseTree* tree, struct LexVector* lv, int* openParens) {
|
||||
struct ParseTree* newTree = NULL;
|
||||
bool pop = false;
|
||||
int precedence = INT_MAX;
|
||||
size_t i = 0;
|
||||
while (i < LexVectorSize(lv)) {
|
||||
struct Token* token = LexVectorGetPointer(lv, i);
|
||||
int newPrecedence;
|
||||
|
@ -517,27 +524,36 @@ static size_t _parseExpression(struct ParseTree* tree, struct LexVector* lv, siz
|
|||
++i;
|
||||
} else {
|
||||
tree->token.type = TOKEN_ERROR_TYPE;
|
||||
return i + 1;
|
||||
++i;
|
||||
pop = true;
|
||||
}
|
||||
break;
|
||||
case TOKEN_SEGMENT_TYPE:
|
||||
tree->lhs = _parseTreeCreate();
|
||||
tree->lhs->token.type = TOKEN_UINT_TYPE;
|
||||
tree->lhs->token.uintValue = token->uintValue;
|
||||
tree->lhs->p = tree;
|
||||
tree->lhs->precedence = precedence;
|
||||
tree->rhs = _parseTreeCreate();
|
||||
tree->rhs->p = tree;
|
||||
tree->rhs->precedence = precedence;
|
||||
tree->token.type = TOKEN_SEGMENT_TYPE;
|
||||
i = _parseExpression(tree->rhs, lv, i + 1, precedence, openParens);
|
||||
tree = tree->rhs;
|
||||
++i;
|
||||
break;
|
||||
case TOKEN_OPEN_PAREN_TYPE:
|
||||
++*openParens;
|
||||
i = _parseExpression(tree, lv, i + 1, INT_MAX, openParens);
|
||||
precedence = INT_MAX;
|
||||
++i;
|
||||
break;
|
||||
case TOKEN_CLOSE_PAREN_TYPE:
|
||||
if (*openParens <= 0) {
|
||||
tree->token.type = TOKEN_ERROR_TYPE;
|
||||
}
|
||||
--*openParens;
|
||||
return i + 1;
|
||||
++i;
|
||||
pop = true;
|
||||
break;
|
||||
case TOKEN_OPERATOR_TYPE:
|
||||
if (tree->token.type == TOKEN_ERROR_TYPE) {
|
||||
switch (token->operatorValue) {
|
||||
|
@ -557,21 +573,44 @@ static size_t _parseExpression(struct ParseTree* tree, struct LexVector* lv, siz
|
|||
newPrecedence = _operatorPrecedence[token->operatorValue];
|
||||
if (newPrecedence < precedence) {
|
||||
newTree = _parseTreeCreate();
|
||||
*newTree = *tree;
|
||||
memcpy(newTree, tree, sizeof(*tree));
|
||||
if (newTree->lhs) {
|
||||
newTree->lhs->p = newTree;
|
||||
}
|
||||
if (newTree->rhs) {
|
||||
newTree->rhs->p = newTree;
|
||||
}
|
||||
newTree->p = tree;
|
||||
tree->lhs = newTree;
|
||||
tree->rhs = _parseTreeCreate();
|
||||
tree->rhs->p = tree;
|
||||
tree->rhs->precedence = newPrecedence;
|
||||
precedence = newPrecedence;
|
||||
tree->token = *token;
|
||||
i = _parseExpression(tree->rhs, lv, i + 1, newPrecedence, openParens);
|
||||
if (tree->token.type == TOKEN_ERROR_TYPE) {
|
||||
tree->token.type = TOKEN_ERROR_TYPE;
|
||||
}
|
||||
tree = tree->rhs;
|
||||
++i;
|
||||
} else {
|
||||
return i;
|
||||
pop = true;
|
||||
}
|
||||
break;
|
||||
case TOKEN_ERROR_TYPE:
|
||||
tree->token.type = TOKEN_ERROR_TYPE;
|
||||
return i + 1;
|
||||
++i;
|
||||
pop = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (pop) {
|
||||
if (tree->token.type == TOKEN_ERROR_TYPE && tree->p) {
|
||||
tree->p->token.type = TOKEN_ERROR_TYPE;
|
||||
}
|
||||
tree = tree->p;
|
||||
pop = false;
|
||||
if (!tree) {
|
||||
break;
|
||||
} else {
|
||||
precedence = tree->precedence;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -584,11 +623,13 @@ void parseLexedExpression(struct ParseTree* tree, struct LexVector* lv) {
|
|||
}
|
||||
|
||||
tree->token.type = TOKEN_ERROR_TYPE;
|
||||
tree->lhs = 0;
|
||||
tree->rhs = 0;
|
||||
tree->lhs = NULL;
|
||||
tree->rhs = NULL;
|
||||
tree->p = NULL;
|
||||
tree->precedence = INT_MAX;
|
||||
|
||||
int openParens = 0;
|
||||
_parseExpression(tree, lv, 0, INT_MAX, &openParens);
|
||||
_parseExpression(tree, lv, &openParens);
|
||||
if (openParens) {
|
||||
if (tree->token.type == TOKEN_IDENTIFIER_TYPE) {
|
||||
free(tree->token.identifierValue);
|
||||
|
@ -607,23 +648,40 @@ void lexFree(struct LexVector* lv) {
|
|||
}
|
||||
}
|
||||
|
||||
void parseFree(struct ParseTree* tree) {
|
||||
if (!tree) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (tree->lhs) {
|
||||
parseFree(tree->lhs);
|
||||
free(tree->lhs);
|
||||
}
|
||||
if (tree->rhs) {
|
||||
parseFree(tree->rhs);
|
||||
free(tree->rhs);
|
||||
}
|
||||
|
||||
static void _freeTree(struct ParseTree* tree) {
|
||||
if (tree->token.type == TOKEN_IDENTIFIER_TYPE) {
|
||||
free(tree->token.identifierValue);
|
||||
}
|
||||
free(tree);
|
||||
}
|
||||
|
||||
void parseFree(struct ParseTree* tree) {
|
||||
while (tree) {
|
||||
if (tree->lhs) {
|
||||
tree = tree->lhs;
|
||||
continue;
|
||||
}
|
||||
if (tree->rhs) {
|
||||
tree = tree->rhs;
|
||||
continue;
|
||||
}
|
||||
if (tree->p) {
|
||||
if (tree->p->lhs == tree) {
|
||||
tree = tree->p;
|
||||
_freeTree(tree->lhs);
|
||||
tree->lhs = NULL;
|
||||
} else if (tree->p->rhs == tree) {
|
||||
tree = tree->p;
|
||||
_freeTree(tree->rhs);
|
||||
tree->rhs = NULL;
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
_freeTree(tree);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool _performOperation(struct mDebugger* debugger, enum Operation operation, int32_t current, int32_t next, int32_t* value, int* segment) {
|
||||
|
@ -721,56 +779,125 @@ bool mDebuggerEvaluateParseTree(struct mDebugger* debugger, struct ParseTree* tr
|
|||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
int32_t lhs, rhs;
|
||||
switch (tree->token.type) {
|
||||
case TOKEN_UINT_TYPE:
|
||||
if (segment) {
|
||||
*segment = -1;
|
||||
}
|
||||
*value = tree->token.uintValue;
|
||||
return true;
|
||||
case TOKEN_SEGMENT_TYPE:
|
||||
if (!mDebuggerEvaluateParseTree(debugger, tree->rhs, value, segment)) {
|
||||
return false;
|
||||
}
|
||||
return mDebuggerEvaluateParseTree(debugger, tree->lhs, segment, NULL);
|
||||
case TOKEN_OPERATOR_TYPE:
|
||||
switch (tree->token.operatorValue) {
|
||||
case OP_ASSIGN:
|
||||
case OP_ADD:
|
||||
case OP_SUBTRACT:
|
||||
case OP_MULTIPLY:
|
||||
case OP_DIVIDE:
|
||||
case OP_MODULO:
|
||||
case OP_AND:
|
||||
case OP_OR:
|
||||
case OP_XOR:
|
||||
case OP_LESS:
|
||||
case OP_GREATER:
|
||||
case OP_EQUAL:
|
||||
case OP_NOT_EQUAL:
|
||||
case OP_LOGICAL_AND:
|
||||
case OP_LOGICAL_OR:
|
||||
case OP_LE:
|
||||
case OP_GE:
|
||||
case OP_SHIFT_L:
|
||||
case OP_SHIFT_R:
|
||||
if (!mDebuggerEvaluateParseTree(debugger, tree->lhs, &lhs, segment)) {
|
||||
return false;
|
||||
}
|
||||
// Fall through
|
||||
default:
|
||||
if (!mDebuggerEvaluateParseTree(debugger, tree->rhs, &rhs, segment)) {
|
||||
return false;
|
||||
struct IntList stack;
|
||||
int nextBranch;
|
||||
bool ok = true;
|
||||
int32_t tmpVal = 0;
|
||||
int32_t tmpSegment = -1;
|
||||
|
||||
IntListInit(&stack, 0);
|
||||
while (ok) {
|
||||
switch (tree->token.type) {
|
||||
case TOKEN_UINT_TYPE:
|
||||
nextBranch = 2;
|
||||
tmpSegment = -1;
|
||||
tmpVal = tree->token.uintValue;
|
||||
break;
|
||||
case TOKEN_SEGMENT_TYPE:
|
||||
nextBranch = 0;
|
||||
break;
|
||||
case TOKEN_OPERATOR_TYPE:
|
||||
switch (tree->token.operatorValue) {
|
||||
case OP_ASSIGN:
|
||||
case OP_ADD:
|
||||
case OP_SUBTRACT:
|
||||
case OP_MULTIPLY:
|
||||
case OP_DIVIDE:
|
||||
case OP_MODULO:
|
||||
case OP_AND:
|
||||
case OP_OR:
|
||||
case OP_XOR:
|
||||
case OP_LESS:
|
||||
case OP_GREATER:
|
||||
case OP_EQUAL:
|
||||
case OP_NOT_EQUAL:
|
||||
case OP_LOGICAL_AND:
|
||||
case OP_LOGICAL_OR:
|
||||
case OP_LE:
|
||||
case OP_GE:
|
||||
case OP_SHIFT_L:
|
||||
case OP_SHIFT_R:
|
||||
nextBranch = 0;
|
||||
break;
|
||||
default:
|
||||
nextBranch = 1;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case TOKEN_IDENTIFIER_TYPE:
|
||||
if (!mDebuggerLookupIdentifier(debugger, tree->token.identifierValue, &tmpVal, &tmpSegment)) {
|
||||
ok = false;
|
||||
}
|
||||
nextBranch = 2;
|
||||
break;
|
||||
case TOKEN_ERROR_TYPE:
|
||||
default:
|
||||
ok = false;
|
||||
break;
|
||||
}
|
||||
if (!ok) {
|
||||
break;
|
||||
}
|
||||
|
||||
bool gotTree = false;
|
||||
while (!gotTree && tree) {
|
||||
int32_t lhs, rhs;
|
||||
|
||||
switch (nextBranch) {
|
||||
case 0:
|
||||
*IntListAppend(&stack) = tmpVal;
|
||||
*IntListAppend(&stack) = tmpSegment;
|
||||
*IntListAppend(&stack) = nextBranch;
|
||||
tree = tree->lhs;
|
||||
gotTree = true;
|
||||
break;
|
||||
case 1:
|
||||
*IntListAppend(&stack) = tmpVal;
|
||||
*IntListAppend(&stack) = tmpSegment;
|
||||
*IntListAppend(&stack) = nextBranch;
|
||||
tree = tree->rhs;
|
||||
gotTree = true;
|
||||
break;
|
||||
case 2:
|
||||
if (!IntListSize(&stack)) {
|
||||
tree = NULL;
|
||||
break;
|
||||
}
|
||||
nextBranch = *IntListGetPointer(&stack, IntListSize(&stack) - 1);
|
||||
IntListResize(&stack, -1);
|
||||
tree = tree->p;
|
||||
if (nextBranch == 0) {
|
||||
++nextBranch;
|
||||
} else if (tree) {
|
||||
nextBranch = 2;
|
||||
switch (tree->token.type) {
|
||||
case TOKEN_OPERATOR_TYPE:
|
||||
rhs = tmpVal;
|
||||
lhs = *IntListGetPointer(&stack, IntListSize(&stack) - 2);
|
||||
tmpSegment = *IntListGetPointer(&stack, IntListSize(&stack) - 1);
|
||||
ok = _performOperation(debugger, tree->token.operatorValue, lhs, rhs, &tmpVal, &tmpSegment);
|
||||
break;
|
||||
case TOKEN_SEGMENT_TYPE:
|
||||
tmpSegment = *IntListGetPointer(&stack, IntListSize(&stack) - 2);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
IntListResize(&stack, -2);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!tree) {
|
||||
break;
|
||||
}
|
||||
return _performOperation(debugger, tree->token.operatorValue, lhs, rhs, value, segment);
|
||||
case TOKEN_IDENTIFIER_TYPE:
|
||||
return mDebuggerLookupIdentifier(debugger, tree->token.identifierValue, value, segment);
|
||||
case TOKEN_ERROR_TYPE:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
IntListDeinit(&stack);
|
||||
if (ok) {
|
||||
*value = tmpVal;
|
||||
if (segment) {
|
||||
*segment = tmpSegment;
|
||||
}
|
||||
}
|
||||
return ok;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
struct LPTest {
|
||||
struct LexVector lv;
|
||||
struct ParseTree tree;
|
||||
struct ParseTree* tree;
|
||||
};
|
||||
|
||||
#define PARSE(STR) \
|
||||
|
@ -18,7 +18,8 @@ struct LPTest {
|
|||
LexVectorClear(&lp->lv); \
|
||||
size_t adjusted = lexExpression(&lp->lv, STR, strlen(STR), ""); \
|
||||
assert_false(adjusted > strlen(STR)); \
|
||||
struct ParseTree* tree = &lp->tree; \
|
||||
lp->tree = malloc(sizeof(*lp->tree)); \
|
||||
struct ParseTree* tree = lp->tree; \
|
||||
parseLexedExpression(tree, &lp->lv)
|
||||
|
||||
static int parseSetup(void** state) {
|
||||
|
@ -30,7 +31,7 @@ static int parseSetup(void** state) {
|
|||
|
||||
static int parseTeardown(void** state) {
|
||||
struct LPTest* lp = *state;
|
||||
parseFree(&lp->tree);
|
||||
parseFree(lp->tree);
|
||||
lexFree(&lp->lv);
|
||||
LexVectorDeinit(&lp->lv);
|
||||
free(lp);
|
||||
|
|
|
@ -173,25 +173,73 @@ int main(int argc, char* argv[]) {
|
|||
prefix = false;
|
||||
needsUnmount = true;
|
||||
#endif
|
||||
} else {
|
||||
} else if (strcmp(extension, "appimage") != 0) {
|
||||
archive = VDirOpenArchive(updateArchive);
|
||||
}
|
||||
if (!archive) {
|
||||
puts("Cannot open update archive");
|
||||
} else {
|
||||
if (archive) {
|
||||
puts("Extracting update");
|
||||
if (extractArchive(archive, root, prefix)) {
|
||||
puts("Complete");
|
||||
const char* command = mUpdateGetCommand(&config);
|
||||
strlcpy(bin, command, sizeof(bin));
|
||||
ok = 0;
|
||||
mUpdateDeregister(&config);
|
||||
} else {
|
||||
puts("An error occurred");
|
||||
}
|
||||
archive->close(archive);
|
||||
unlink(updateArchive);
|
||||
}
|
||||
#ifdef __linux__
|
||||
else if (strcmp(extension, "appimage") == 0) {
|
||||
const char* command = mUpdateGetCommand(&config);
|
||||
strlcpy(bin, command, sizeof(bin));
|
||||
if (rename(updateArchive, bin) < 0) {
|
||||
if (errno == EXDEV) {
|
||||
// Cross-dev, need to copy manually
|
||||
int infd = open(updateArchive, O_RDONLY);
|
||||
int outfd = -1;
|
||||
if (infd >= 0) {
|
||||
ok = 2;
|
||||
} else {
|
||||
outfd = open(bin, O_CREAT | O_WRONLY | O_TRUNC, 0755);
|
||||
}
|
||||
if (outfd < 0) {
|
||||
ok = 2;
|
||||
} else {
|
||||
uint8_t buffer[2048];
|
||||
ssize_t size;
|
||||
while ((size = read(infd, buffer, sizeof(buffer))) > 0) {
|
||||
if (write(outfd, buffer, size) < size) {
|
||||
ok = 2;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (size < 0) {
|
||||
ok = 2;
|
||||
}
|
||||
close(outfd);
|
||||
close(infd);
|
||||
}
|
||||
if (ok == 2) {
|
||||
puts("Cannot move update over old file");
|
||||
}
|
||||
} else {
|
||||
puts("Cannot move update over old file");
|
||||
}
|
||||
} else {
|
||||
ok = 0;
|
||||
}
|
||||
if (ok == 0) {
|
||||
chmod(bin, 0755);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
puts("Cannot open update archive");
|
||||
}
|
||||
if (ok == 0) {
|
||||
puts("Complete");
|
||||
const char* command = mUpdateGetCommand(&config);
|
||||
strlcpy(bin, command, sizeof(bin));
|
||||
mUpdateDeregister(&config);
|
||||
}
|
||||
#ifdef __APPLE__
|
||||
if (needsUnmount) {
|
||||
char* args[] = {"hdiutil", "detach", devpath, NULL};
|
||||
|
|
423
src/gb/audio.c
|
@ -11,6 +11,9 @@
|
|||
#include <mgba/internal/gb/gb.h>
|
||||
#include <mgba/internal/gb/serialize.h>
|
||||
#include <mgba/internal/gb/io.h>
|
||||
#ifdef M_CORE_GBA
|
||||
#include <mgba/internal/gba/audio.h>
|
||||
#endif
|
||||
|
||||
#ifdef __3DS__
|
||||
#define blip_add_delta blip_add_delta_fast
|
||||
|
@ -35,19 +38,19 @@ static void _updateEnvelopeDead(struct GBAudioEnvelope* envelope);
|
|||
static bool _updateSweep(struct GBAudioSquareChannel* sweep, bool initial);
|
||||
|
||||
static void _updateSquareSample(struct GBAudioSquareChannel* ch);
|
||||
static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch);
|
||||
|
||||
static int32_t _cyclesToInvert(struct GBAudioSquareChannel* ch);
|
||||
|
||||
static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch);
|
||||
|
||||
static void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _updateChannel1(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _updateChannel2(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _fadeChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate);
|
||||
|
||||
static const int _squareChannelDuty[4][8] = {
|
||||
{ 0, 0, 0, 0, 0, 0, 0, 1 },
|
||||
{ 1, 0, 0, 0, 0, 0, 0, 1 },
|
||||
{ 1, 0, 0, 0, 0, 1, 1, 1 },
|
||||
{ 0, 1, 1, 1, 1, 1, 1, 0 },
|
||||
};
|
||||
|
||||
void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAudioStyle style) {
|
||||
audio->samples = samples;
|
||||
audio->left = blip_new(BLIP_BUFFER_SIZE);
|
||||
|
@ -69,30 +72,9 @@ void GBAudioInit(struct GBAudio* audio, size_t samples, uint8_t* nr52, enum GBAu
|
|||
audio->timingFactor = 2;
|
||||
}
|
||||
|
||||
audio->frameEvent.context = audio;
|
||||
audio->frameEvent.name = "GB Audio Frame Sequencer";
|
||||
audio->frameEvent.callback = _updateFrame;
|
||||
audio->frameEvent.priority = 0x10;
|
||||
audio->ch1Event.context = audio;
|
||||
audio->ch1Event.name = "GB Audio Channel 1";
|
||||
audio->ch1Event.callback = _updateChannel1;
|
||||
audio->ch1Event.priority = 0x11;
|
||||
audio->ch2Event.context = audio;
|
||||
audio->ch2Event.name = "GB Audio Channel 2";
|
||||
audio->ch2Event.callback = _updateChannel2;
|
||||
audio->ch2Event.priority = 0x12;
|
||||
audio->ch3Event.context = audio;
|
||||
audio->ch3Event.name = "GB Audio Channel 3";
|
||||
audio->ch3Event.callback = _updateChannel3;
|
||||
audio->ch3Event.priority = 0x13;
|
||||
audio->ch3Fade.context = audio;
|
||||
audio->ch3Fade.name = "GB Audio Channel 3 Memory";
|
||||
audio->ch3Fade.callback = _fadeChannel3;
|
||||
audio->ch3Fade.priority = 0x14;
|
||||
audio->ch4Event.context = audio;
|
||||
audio->ch4Event.name = "GB Audio Channel 4";
|
||||
audio->ch4Event.callback = NULL; // This is pending removal, so calling it will crash
|
||||
audio->ch4Event.priority = 0x15;
|
||||
audio->sampleEvent.context = audio;
|
||||
audio->sampleEvent.name = "GB Audio Sample";
|
||||
audio->sampleEvent.callback = _sample;
|
||||
|
@ -105,19 +87,10 @@ void GBAudioDeinit(struct GBAudio* audio) {
|
|||
}
|
||||
|
||||
void GBAudioReset(struct GBAudio* audio) {
|
||||
mTimingDeschedule(audio->timing, &audio->frameEvent);
|
||||
mTimingDeschedule(audio->timing, &audio->ch1Event);
|
||||
mTimingDeschedule(audio->timing, &audio->ch2Event);
|
||||
mTimingDeschedule(audio->timing, &audio->ch3Event);
|
||||
mTimingDeschedule(audio->timing, &audio->ch3Fade);
|
||||
mTimingDeschedule(audio->timing, &audio->ch4Event);
|
||||
mTimingDeschedule(audio->timing, &audio->sampleEvent);
|
||||
if (audio->style != GB_AUDIO_GBA) {
|
||||
mTimingSchedule(audio->timing, &audio->sampleEvent, 0);
|
||||
}
|
||||
if (audio->style == GB_AUDIO_GBA) {
|
||||
mTimingSchedule(audio->timing, &audio->frameEvent, 0);
|
||||
}
|
||||
audio->ch1 = (struct GBAudioSquareChannel) { .sweep = { .time = 8 }, .envelope = { .dead = 2 } };
|
||||
audio->ch2 = (struct GBAudioSquareChannel) { .envelope = { .dead = 2 } };
|
||||
audio->ch3 = (struct GBAudioWaveChannel) { .bank = 0 };
|
||||
|
@ -168,32 +141,35 @@ void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) {
|
|||
}
|
||||
|
||||
void GBAudioWriteNR10(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
|
||||
if (!_writeSweep(&audio->ch1.sweep, value)) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch1Event);
|
||||
audio->playingCh1 = false;
|
||||
*audio->nr52 &= ~0x0001;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR11(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
|
||||
_writeDuty(&audio->ch1.envelope, value);
|
||||
audio->ch1.control.length = 64 - audio->ch1.envelope.length;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR12(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
|
||||
if (!_writeEnvelope(&audio->ch1.envelope, value, audio->style)) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch1Event);
|
||||
audio->playingCh1 = false;
|
||||
*audio->nr52 &= ~0x0001;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR13(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
|
||||
audio->ch1.control.frequency &= 0x700;
|
||||
audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x1);
|
||||
audio->ch1.control.frequency &= 0xFF;
|
||||
audio->ch1.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8);
|
||||
bool wasStop = audio->ch1.control.stop;
|
||||
|
@ -201,12 +177,10 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) {
|
|||
if (!wasStop && audio->ch1.control.stop && audio->ch1.control.length && !(audio->frame & 1)) {
|
||||
--audio->ch1.control.length;
|
||||
if (audio->ch1.control.length == 0) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch1Event);
|
||||
audio->playingCh1 = false;
|
||||
}
|
||||
}
|
||||
if (GBAudioRegisterControlIsRestart(value << 8)) {
|
||||
bool wasDead = !audio->playingCh1;
|
||||
audio->playingCh1 = _resetEnvelope(&audio->ch1.envelope);
|
||||
audio->ch1.sweep.realFrequency = audio->ch1.control.frequency;
|
||||
_resetSweep(&audio->ch1.sweep);
|
||||
|
@ -220,35 +194,33 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) {
|
|||
}
|
||||
}
|
||||
_updateSquareSample(&audio->ch1);
|
||||
if (wasDead && audio->playingCh1) {
|
||||
mTimingSchedule(audio->timing, &audio->ch1Event, _cyclesToInvert(&audio->ch1));
|
||||
} else if (!audio->playingCh1) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch1Event);
|
||||
}
|
||||
}
|
||||
*audio->nr52 &= ~0x0001;
|
||||
*audio->nr52 |= audio->playingCh1;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR21(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
|
||||
_writeDuty(&audio->ch2.envelope, value);
|
||||
audio->ch2.control.length = 64 - audio->ch2.envelope.length;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR22(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
|
||||
if (!_writeEnvelope(&audio->ch2.envelope, value, audio->style)) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch2Event);
|
||||
audio->playingCh2 = false;
|
||||
*audio->nr52 &= ~0x0002;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR23(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
|
||||
audio->ch2.control.frequency &= 0x700;
|
||||
audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
|
||||
audio->ch2.control.frequency &= 0xFF;
|
||||
audio->ch2.control.frequency |= GBAudioRegisterControlGetFrequency(value << 8);
|
||||
bool wasStop = audio->ch2.control.stop;
|
||||
|
@ -256,12 +228,10 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) {
|
|||
if (!wasStop && audio->ch2.control.stop && audio->ch2.control.length && !(audio->frame & 1)) {
|
||||
--audio->ch2.control.length;
|
||||
if (audio->ch2.control.length == 0) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch2Event);
|
||||
audio->playingCh2 = false;
|
||||
}
|
||||
}
|
||||
if (GBAudioRegisterControlIsRestart(value << 8)) {
|
||||
bool wasDead = !audio->playingCh2;
|
||||
audio->playingCh2 = _resetEnvelope(&audio->ch2.envelope);
|
||||
|
||||
if (!audio->ch2.control.length) {
|
||||
|
@ -271,39 +241,38 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) {
|
|||
}
|
||||
}
|
||||
_updateSquareSample(&audio->ch2);
|
||||
if (wasDead && audio->playingCh2) {
|
||||
mTimingSchedule(audio->timing, &audio->ch2Event, _cyclesToInvert(&audio->ch2));
|
||||
} else if (!audio->playingCh2) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch2Event);
|
||||
}
|
||||
}
|
||||
*audio->nr52 &= ~0x0002;
|
||||
*audio->nr52 |= audio->playingCh2 << 1;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR30(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
|
||||
audio->ch3.enable = GBAudioRegisterBankGetEnable(value);
|
||||
if (!audio->ch3.enable) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch3Event);
|
||||
audio->playingCh3 = false;
|
||||
*audio->nr52 &= ~0x0004;
|
||||
}
|
||||
}
|
||||
|
||||
void GBAudioWriteNR31(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
|
||||
audio->ch3.length = 256 - value;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
|
||||
audio->ch3.volume = GBAudioRegisterBankVolumeGetVolumeGB(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR33(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
|
||||
audio->ch3.rate &= 0x700;
|
||||
audio->ch3.rate |= GBAudioRegisterControlGetRate(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
|
||||
audio->ch3.rate &= 0xFF;
|
||||
audio->ch3.rate |= GBAudioRegisterControlGetRate(value << 8);
|
||||
bool wasStop = audio->ch3.stop;
|
||||
|
@ -339,25 +308,23 @@ void GBAudioWriteNR34(struct GBAudio* audio, uint8_t value) {
|
|||
audio->ch3.sample = 0;
|
||||
}
|
||||
}
|
||||
mTimingDeschedule(audio->timing, &audio->ch3Fade);
|
||||
mTimingDeschedule(audio->timing, &audio->ch3Event);
|
||||
if (audio->playingCh3) {
|
||||
audio->ch3.readable = audio->style != GB_AUDIO_DMG;
|
||||
// TODO: Where does this cycle delay come from?
|
||||
mTimingSchedule(audio->timing, &audio->ch3Event, audio->timingFactor * (4 + 2 * (2048 - audio->ch3.rate)));
|
||||
audio->ch3.nextUpdate = mTimingCurrentTime(audio->timing) + (6 + 2 * (2048 - audio->ch3.rate)) * audio->timingFactor;
|
||||
}
|
||||
*audio->nr52 &= ~0x0004;
|
||||
*audio->nr52 |= audio->playingCh3 << 2;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR41(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8);
|
||||
_writeDuty(&audio->ch4.envelope, value);
|
||||
audio->ch4.length = 64 - audio->ch4.envelope.length;
|
||||
}
|
||||
|
||||
void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8);
|
||||
if (!_writeEnvelope(&audio->ch4.envelope, value, audio->style)) {
|
||||
audio->playingCh4 = false;
|
||||
*audio->nr52 &= ~0x0008;
|
||||
|
@ -365,14 +332,14 @@ void GBAudioWriteNR42(struct GBAudio* audio, uint8_t value) {
|
|||
}
|
||||
|
||||
void GBAudioWriteNR43(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8);
|
||||
audio->ch4.ratio = GBAudioRegisterNoiseFeedbackGetRatio(value);
|
||||
audio->ch4.frequency = GBAudioRegisterNoiseFeedbackGetFrequency(value);
|
||||
audio->ch4.power = GBAudioRegisterNoiseFeedbackGetPower(value);
|
||||
}
|
||||
|
||||
void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x8);
|
||||
bool wasStop = audio->ch4.stop;
|
||||
audio->ch4.stop = GBAudioRegisterNoiseControlGetStop(value);
|
||||
if (!wasStop && audio->ch4.stop && audio->ch4.length && !(audio->frame & 1)) {
|
||||
|
@ -395,7 +362,7 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
|
|||
--audio->ch4.length;
|
||||
}
|
||||
}
|
||||
if (audio->playingCh4 && audio->ch4.envelope.dead != 2) {
|
||||
if (audio->playingCh4) {
|
||||
audio->ch4.lastEvent = mTimingCurrentTime(audio->timing);
|
||||
}
|
||||
}
|
||||
|
@ -482,17 +449,144 @@ void GBAudioWriteNR52(struct GBAudio* audio, uint8_t value) {
|
|||
audio->skipFrame = false;
|
||||
audio->frame = 7;
|
||||
|
||||
if (audio->p && audio->p->timer.internalDiv & 0x400) {
|
||||
if (audio->p && audio->p->timer.internalDiv & (0x100 << audio->p->doubleSpeed)) {
|
||||
audio->skipFrame = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _updateFrame(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBAudio* audio = user;
|
||||
GBAudioUpdateFrame(audio);
|
||||
if (audio->style == GB_AUDIO_GBA) {
|
||||
mTimingSchedule(timing, &audio->frameEvent, audio->timingFactor * FRAME_CYCLES - cyclesLate);
|
||||
#ifdef M_CORE_GBA
|
||||
struct GBAAudio* audio = user;
|
||||
GBAAudioSample(audio, mTimingCurrentTime(timing));
|
||||
mTimingSchedule(timing, &audio->psg.frameEvent, audio->psg.timingFactor * FRAME_CYCLES - cyclesLate);
|
||||
GBAudioUpdateFrame(&audio->psg);
|
||||
#endif
|
||||
}
|
||||
|
||||
void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
|
||||
if (!audio->enable) {
|
||||
return;
|
||||
}
|
||||
if (audio->playingCh1 && (channels & 0x1)) {
|
||||
int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor;
|
||||
int32_t diff = timestamp - audio->ch1.lastUpdate;
|
||||
if (diff >= period) {
|
||||
diff /= period;
|
||||
audio->ch1.index = (audio->ch1.index + diff) & 7;
|
||||
audio->ch1.lastUpdate += diff * period;
|
||||
_updateSquareSample(&audio->ch1);
|
||||
}
|
||||
}
|
||||
if (audio->playingCh2 && (channels & 0x2)) {
|
||||
int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor;
|
||||
int32_t diff = timestamp - audio->ch2.lastUpdate;
|
||||
if (diff >= period) {
|
||||
diff /= period;
|
||||
audio->ch2.index = (audio->ch2.index + diff) & 7;
|
||||
audio->ch2.lastUpdate += diff * period;
|
||||
_updateSquareSample(&audio->ch2);
|
||||
}
|
||||
}
|
||||
if (audio->playingCh3 && (channels & 0x4)) {
|
||||
int cycles = 2 * (2048 - audio->ch3.rate) * audio->timingFactor;
|
||||
int32_t diff = timestamp - audio->ch3.nextUpdate;
|
||||
if (diff >= 0) {
|
||||
diff = (diff / cycles) + 1;
|
||||
int volume;
|
||||
switch (audio->ch3.volume) {
|
||||
case 0:
|
||||
volume = 4;
|
||||
break;
|
||||
case 1:
|
||||
volume = 0;
|
||||
break;
|
||||
case 2:
|
||||
volume = 1;
|
||||
break;
|
||||
default:
|
||||
case 3:
|
||||
volume = 2;
|
||||
break;
|
||||
}
|
||||
int start = 7;
|
||||
int end = 0;
|
||||
int mask = 0x1F;
|
||||
int iter;
|
||||
switch (audio->style) {
|
||||
case GB_AUDIO_DMG:
|
||||
default:
|
||||
audio->ch3.window += diff;
|
||||
audio->ch3.window &= 0x1F;
|
||||
audio->ch3.sample = audio->ch3.wavedata8[audio->ch3.window >> 1];
|
||||
if (!(audio->ch3.window & 1)) {
|
||||
audio->ch3.sample >>= 4;
|
||||
}
|
||||
audio->ch3.sample &= 0xF;
|
||||
break;
|
||||
case GB_AUDIO_GBA:
|
||||
if (audio->ch3.size) {
|
||||
mask = 0x3F;
|
||||
} else if (audio->ch3.bank) {
|
||||
end = 4;
|
||||
} else {
|
||||
start = 3;
|
||||
}
|
||||
for (iter = 0; iter < (diff & mask); ++iter) {
|
||||
uint32_t bitsCarry = audio->ch3.wavedata32[end] & 0x000000F0;
|
||||
uint32_t bits;
|
||||
int i;
|
||||
for (i = start; i >= end; --i) {
|
||||
bits = audio->ch3.wavedata32[i] & 0x000000F0;
|
||||
audio->ch3.wavedata32[i] = ((audio->ch3.wavedata32[i] & 0x0F0F0F0F) << 4) | ((audio->ch3.wavedata32[i] & 0xF0F0F000) >> 12);
|
||||
audio->ch3.wavedata32[i] |= bitsCarry << 20;
|
||||
bitsCarry = bits;
|
||||
}
|
||||
audio->ch3.sample = bitsCarry >> 4;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (audio->ch3.volume > 3) {
|
||||
audio->ch3.sample += audio->ch3.sample << 1;
|
||||
}
|
||||
audio->ch3.sample >>= volume;
|
||||
audio->ch3.nextUpdate += diff * cycles;
|
||||
audio->ch3.readable = true;
|
||||
}
|
||||
if (audio->style == GB_AUDIO_DMG && audio->ch3.readable) {
|
||||
diff = timestamp - audio->ch3.nextUpdate + cycles;
|
||||
if (diff >= 4) {
|
||||
audio->ch3.readable = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (audio->playingCh4 && (channels & 0x8)) {
|
||||
int32_t cycles = audio->ch4.ratio ? 2 * audio->ch4.ratio : 1;
|
||||
cycles <<= audio->ch4.frequency;
|
||||
cycles *= 8 * audio->timingFactor;
|
||||
|
||||
int32_t diff = timestamp - audio->ch4.lastEvent;
|
||||
if (diff >= cycles) {
|
||||
int32_t last;
|
||||
int samples = 0;
|
||||
int positiveSamples = 0;
|
||||
int lsb;
|
||||
int coeff = 0x60;
|
||||
if (!audio->ch4.power) {
|
||||
coeff <<= 8;
|
||||
}
|
||||
for (last = 0; last + cycles <= diff; last += cycles) {
|
||||
lsb = audio->ch4.lfsr & 1;
|
||||
audio->ch4.lfsr >>= 1;
|
||||
audio->ch4.lfsr ^= lsb * coeff;
|
||||
++samples;
|
||||
positiveSamples += lsb;
|
||||
}
|
||||
audio->ch4.sample = lsb * audio->ch4.envelope.currentVolume;
|
||||
audio->ch4.nSamples += samples;
|
||||
audio->ch4.samples += positiveSamples * audio->ch4.envelope.currentVolume;
|
||||
audio->ch4.lastEvent += last;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -504,6 +598,8 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
audio->skipFrame = false;
|
||||
return;
|
||||
}
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x7);
|
||||
|
||||
int frame = (audio->frame + 1) & 7;
|
||||
audio->frame = frame;
|
||||
|
||||
|
@ -516,9 +612,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
audio->playingCh1 = _updateSweep(&audio->ch1, false);
|
||||
*audio->nr52 &= ~0x0001;
|
||||
*audio->nr52 |= audio->playingCh1;
|
||||
if (!audio->playingCh1) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch1Event);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Fall through
|
||||
|
@ -527,7 +620,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
if (audio->ch1.control.length && audio->ch1.control.stop) {
|
||||
--audio->ch1.control.length;
|
||||
if (audio->ch1.control.length == 0) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch1Event);
|
||||
audio->playingCh1 = 0;
|
||||
*audio->nr52 &= ~0x0001;
|
||||
}
|
||||
|
@ -536,7 +628,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
if (audio->ch2.control.length && audio->ch2.control.stop) {
|
||||
--audio->ch2.control.length;
|
||||
if (audio->ch2.control.length == 0) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch2Event);
|
||||
audio->playingCh2 = 0;
|
||||
*audio->nr52 &= ~0x0002;
|
||||
}
|
||||
|
@ -545,7 +636,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
if (audio->ch3.length && audio->ch3.stop) {
|
||||
--audio->ch3.length;
|
||||
if (audio->ch3.length == 0) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch3Event);
|
||||
audio->playingCh3 = 0;
|
||||
*audio->nr52 &= ~0x0004;
|
||||
}
|
||||
|
@ -554,7 +644,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
if (audio->ch4.length && audio->ch4.stop) {
|
||||
--audio->ch4.length;
|
||||
if (audio->ch4.length == 0) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
audio->playingCh4 = 0;
|
||||
*audio->nr52 &= ~0x0008;
|
||||
}
|
||||
|
@ -565,9 +654,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
--audio->ch1.envelope.nextStep;
|
||||
if (audio->ch1.envelope.nextStep == 0) {
|
||||
_updateEnvelope(&audio->ch1.envelope);
|
||||
if (audio->ch1.envelope.dead == 2) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch1Event);
|
||||
}
|
||||
_updateSquareSample(&audio->ch1);
|
||||
}
|
||||
}
|
||||
|
@ -576,9 +662,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
--audio->ch2.envelope.nextStep;
|
||||
if (audio->ch2.envelope.nextStep == 0) {
|
||||
_updateEnvelope(&audio->ch2.envelope);
|
||||
if (audio->ch2.envelope.dead == 2) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch2Event);
|
||||
}
|
||||
_updateSquareSample(&audio->ch2);
|
||||
}
|
||||
}
|
||||
|
@ -586,7 +669,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
if (audio->playingCh4 && !audio->ch4.envelope.dead) {
|
||||
--audio->ch4.envelope.nextStep;
|
||||
if (audio->ch4.envelope.nextStep == 0) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
int8_t sample = audio->ch4.sample;
|
||||
_updateEnvelope(&audio->ch4.envelope);
|
||||
audio->ch4.sample = (sample > 0) * audio->ch4.envelope.currentVolume;
|
||||
|
@ -600,31 +682,6 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
|||
}
|
||||
}
|
||||
|
||||
void GBAudioUpdateChannel4(struct GBAudio* audio) {
|
||||
struct GBAudioNoiseChannel* ch = &audio->ch4;
|
||||
if (ch->envelope.dead == 2 || !audio->playingCh4) {
|
||||
return;
|
||||
}
|
||||
|
||||
int32_t cycles = ch->ratio ? 2 * ch->ratio : 1;
|
||||
cycles <<= ch->frequency;
|
||||
cycles *= 8 * audio->timingFactor;
|
||||
|
||||
uint32_t last = 0;
|
||||
uint32_t now = mTimingCurrentTime(audio->timing) - ch->lastEvent;
|
||||
|
||||
for (; last + cycles <= now; last += cycles) {
|
||||
int lsb = ch->lfsr & 1;
|
||||
ch->sample = lsb * ch->envelope.currentVolume;
|
||||
++ch->nSamples;
|
||||
ch->samples += ch->sample;
|
||||
ch->lfsr >>= 1;
|
||||
ch->lfsr ^= (lsb * 0x60) << (ch->power ? 0 : 8);
|
||||
}
|
||||
|
||||
ch->lastEvent += last;
|
||||
}
|
||||
|
||||
void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
|
||||
int dcOffset = audio->style == GB_AUDIO_GBA ? 0 : -0x8;
|
||||
int sampleLeft = dcOffset;
|
||||
|
@ -664,7 +721,6 @@ void GBAudioSamplePSG(struct GBAudio* audio, int16_t* left, int16_t* right) {
|
|||
sampleRight <<= 3;
|
||||
|
||||
if (!audio->forceDisableCh[3]) {
|
||||
GBAudioUpdateChannel4(audio);
|
||||
int16_t sample = audio->style == GB_AUDIO_GBA ? (audio->ch4.sample << 3) : _coalesceNoiseChannel(&audio->ch4);
|
||||
if (audio->ch4Left) {
|
||||
sampleLeft += sample;
|
||||
|
@ -683,6 +739,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
|||
struct GBAudio* audio = user;
|
||||
int16_t sampleLeft = 0;
|
||||
int16_t sampleRight = 0;
|
||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF);
|
||||
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
|
||||
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
|
||||
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
|
||||
|
@ -775,30 +832,7 @@ bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value, enum GBAudi
|
|||
}
|
||||
|
||||
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
|
||||
ch->sample = ch->control.hi * ch->envelope.currentVolume;
|
||||
}
|
||||
|
||||
static int32_t _updateSquareChannel(struct GBAudioSquareChannel* ch) {
|
||||
ch->control.hi = !ch->control.hi;
|
||||
_updateSquareSample(ch);
|
||||
return _cyclesToInvert(ch);
|
||||
}
|
||||
|
||||
static int32_t _cyclesToInvert(struct GBAudioSquareChannel* ch) {
|
||||
int period = 4 * (2048 - ch->control.frequency);
|
||||
switch (ch->envelope.duty) {
|
||||
case 0:
|
||||
return ch->control.hi ? period : period * 7;
|
||||
case 1:
|
||||
return ch->control.hi ? period * 2 : period * 6;
|
||||
case 2:
|
||||
return period * 4;
|
||||
case 3:
|
||||
return ch->control.hi ? period * 6 : period * 2;
|
||||
default:
|
||||
// This should never be hit
|
||||
return period * 4;
|
||||
}
|
||||
ch->sample = _squareChannelDuty[ch->envelope.duty][ch->index] * ch->envelope.currentVolume;
|
||||
}
|
||||
|
||||
static int16_t _coalesceNoiseChannel(struct GBAudioNoiseChannel* ch) {
|
||||
|
@ -870,94 +904,6 @@ static bool _updateSweep(struct GBAudioSquareChannel* ch, bool initial) {
|
|||
return true;
|
||||
}
|
||||
|
||||
static void _updateChannel1(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBAudio* audio = user;
|
||||
struct GBAudioSquareChannel* ch = &audio->ch1;
|
||||
int cycles = _updateSquareChannel(ch);
|
||||
mTimingSchedule(timing, &audio->ch1Event, audio->timingFactor * cycles - cyclesLate);
|
||||
}
|
||||
|
||||
static void _updateChannel2(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBAudio* audio = user;
|
||||
struct GBAudioSquareChannel* ch = &audio->ch2;
|
||||
int cycles = _updateSquareChannel(ch);
|
||||
mTimingSchedule(timing, &audio->ch2Event, audio->timingFactor * cycles - cyclesLate);
|
||||
}
|
||||
|
||||
static void _updateChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBAudio* audio = user;
|
||||
struct GBAudioWaveChannel* ch = &audio->ch3;
|
||||
int i;
|
||||
int volume;
|
||||
switch (ch->volume) {
|
||||
case 0:
|
||||
volume = 4;
|
||||
break;
|
||||
case 1:
|
||||
volume = 0;
|
||||
break;
|
||||
case 2:
|
||||
volume = 1;
|
||||
break;
|
||||
default:
|
||||
case 3:
|
||||
volume = 2;
|
||||
break;
|
||||
}
|
||||
int start;
|
||||
int end;
|
||||
switch (audio->style) {
|
||||
case GB_AUDIO_DMG:
|
||||
default:
|
||||
++ch->window;
|
||||
ch->window &= 0x1F;
|
||||
ch->sample = ch->wavedata8[ch->window >> 1];
|
||||
if (!(ch->window & 1)) {
|
||||
ch->sample >>= 4;
|
||||
}
|
||||
ch->sample &= 0xF;
|
||||
break;
|
||||
case GB_AUDIO_GBA:
|
||||
if (ch->size) {
|
||||
start = 7;
|
||||
end = 0;
|
||||
} else if (ch->bank) {
|
||||
start = 7;
|
||||
end = 4;
|
||||
} else {
|
||||
start = 3;
|
||||
end = 0;
|
||||
}
|
||||
uint32_t bitsCarry = ch->wavedata32[end] & 0x000000F0;
|
||||
uint32_t bits;
|
||||
for (i = start; i >= end; --i) {
|
||||
bits = ch->wavedata32[i] & 0x000000F0;
|
||||
ch->wavedata32[i] = ((ch->wavedata32[i] & 0x0F0F0F0F) << 4) | ((ch->wavedata32[i] & 0xF0F0F000) >> 12);
|
||||
ch->wavedata32[i] |= bitsCarry << 20;
|
||||
bitsCarry = bits;
|
||||
}
|
||||
ch->sample = bitsCarry >> 4;
|
||||
break;
|
||||
}
|
||||
if (ch->volume > 3) {
|
||||
ch->sample += ch->sample << 1;
|
||||
}
|
||||
ch->sample >>= volume;
|
||||
audio->ch3.readable = true;
|
||||
if (audio->style == GB_AUDIO_DMG) {
|
||||
mTimingDeschedule(audio->timing, &audio->ch3Fade);
|
||||
mTimingSchedule(timing, &audio->ch3Fade, 4 - cyclesLate);
|
||||
}
|
||||
int cycles = 2 * (2048 - ch->rate);
|
||||
mTimingSchedule(timing, &audio->ch3Event, audio->timingFactor * cycles - cyclesLate);
|
||||
}
|
||||
static void _fadeChannel3(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
UNUSED(timing);
|
||||
UNUSED(cyclesLate);
|
||||
struct GBAudio* audio = user;
|
||||
audio->ch3.readable = false;
|
||||
}
|
||||
|
||||
void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGState* state, uint32_t* flagsOut) {
|
||||
uint32_t flags = 0;
|
||||
uint32_t sweep = 0;
|
||||
|
@ -971,30 +917,29 @@ void GBAudioPSGSerialize(const struct GBAudio* audio, struct GBSerializedPSGStat
|
|||
|
||||
flags = GBSerializedAudioFlagsSetCh1Volume(flags, audio->ch1.envelope.currentVolume);
|
||||
flags = GBSerializedAudioFlagsSetCh1Dead(flags, audio->ch1.envelope.dead);
|
||||
flags = GBSerializedAudioFlagsSetCh1Hi(flags, audio->ch1.control.hi);
|
||||
flags = GBSerializedAudioFlagsSetCh1SweepEnabled(flags, audio->ch1.sweep.enable);
|
||||
flags = GBSerializedAudioFlagsSetCh1SweepOccurred(flags, audio->ch1.sweep.occurred);
|
||||
ch1Flags = GBSerializedAudioEnvelopeSetLength(ch1Flags, audio->ch1.control.length);
|
||||
ch1Flags = GBSerializedAudioEnvelopeSetNextStep(ch1Flags, audio->ch1.envelope.nextStep);
|
||||
ch1Flags = GBSerializedAudioEnvelopeSetFrequency(ch1Flags, audio->ch1.sweep.realFrequency);
|
||||
ch1Flags = GBSerializedAudioEnvelopeSetDutyIndex(ch1Flags, audio->ch1.index);
|
||||
sweep = GBSerializedAudioSweepSetTime(sweep, audio->ch1.sweep.time & 7);
|
||||
STORE_32LE(ch1Flags, 0, &state->ch1.envelope);
|
||||
STORE_32LE(sweep, 0, &state->ch1.sweep);
|
||||
STORE_32LE(audio->ch1Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch1.nextEvent);
|
||||
STORE_32LE(audio->ch1.lastUpdate - mTimingCurrentTime(audio->timing), 0, &state->ch1.lastUpdate);
|
||||
|
||||
flags = GBSerializedAudioFlagsSetCh2Volume(flags, audio->ch2.envelope.currentVolume);
|
||||
flags = GBSerializedAudioFlagsSetCh2Dead(flags, audio->ch2.envelope.dead);
|
||||
flags = GBSerializedAudioFlagsSetCh2Hi(flags, audio->ch2.control.hi);
|
||||
ch2Flags = GBSerializedAudioEnvelopeSetLength(ch2Flags, audio->ch2.control.length);
|
||||
ch2Flags = GBSerializedAudioEnvelopeSetNextStep(ch2Flags, audio->ch2.envelope.nextStep);
|
||||
ch2Flags = GBSerializedAudioEnvelopeSetDutyIndex(ch2Flags, audio->ch2.index);
|
||||
STORE_32LE(ch2Flags, 0, &state->ch2.envelope);
|
||||
STORE_32LE(audio->ch2Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch2.nextEvent);
|
||||
STORE_32LE(audio->ch2.lastUpdate - mTimingCurrentTime(audio->timing), 0, &state->ch2.lastUpdate);
|
||||
|
||||
flags = GBSerializedAudioFlagsSetCh3Readable(flags, audio->ch3.readable);
|
||||
memcpy(state->ch3.wavebanks, audio->ch3.wavedata32, sizeof(state->ch3.wavebanks));
|
||||
STORE_16LE(audio->ch3.length, 0, &state->ch3.length);
|
||||
STORE_32LE(audio->ch3Event.when - mTimingCurrentTime(audio->timing), 0, &state->ch3.nextEvent);
|
||||
STORE_32LE(audio->ch3Fade.when - mTimingCurrentTime(audio->timing), 0, &state->ch1.nextCh3Fade);
|
||||
STORE_32LE(audio->ch3.nextUpdate - mTimingCurrentTime(audio->timing), 0, &state->ch3.nextEvent);
|
||||
|
||||
flags = GBSerializedAudioFlagsSetCh4Volume(flags, audio->ch4.envelope.currentVolume);
|
||||
flags = GBSerializedAudioFlagsSetCh4Dead(flags, audio->ch4.envelope.dead);
|
||||
|
@ -1039,7 +984,6 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt
|
|||
LOAD_32LE(sweep, 0, &state->ch1.sweep);
|
||||
audio->ch1.envelope.currentVolume = GBSerializedAudioFlagsGetCh1Volume(flags);
|
||||
audio->ch1.envelope.dead = GBSerializedAudioFlagsGetCh1Dead(flags);
|
||||
audio->ch1.control.hi = GBSerializedAudioFlagsGetCh1Hi(flags);
|
||||
audio->ch1.sweep.enable = GBSerializedAudioFlagsGetCh1SweepEnabled(flags);
|
||||
audio->ch1.sweep.occurred = GBSerializedAudioFlagsGetCh1SweepOccurred(flags);
|
||||
audio->ch1.sweep.time = GBSerializedAudioSweepGetTime(sweep);
|
||||
|
@ -1049,34 +993,25 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt
|
|||
audio->ch1.control.length = GBSerializedAudioEnvelopeGetLength(ch1Flags);
|
||||
audio->ch1.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch1Flags);
|
||||
audio->ch1.sweep.realFrequency = GBSerializedAudioEnvelopeGetFrequency(ch1Flags);
|
||||
LOAD_32LE(when, 0, &state->ch1.nextEvent);
|
||||
if (audio->ch1.envelope.dead < 2 && audio->playingCh1) {
|
||||
mTimingSchedule(audio->timing, &audio->ch1Event, when);
|
||||
}
|
||||
audio->ch1.index = GBSerializedAudioEnvelopeGetDutyIndex(ch1Flags);
|
||||
LOAD_32LE(audio->ch1.lastUpdate, 0, &state->ch1.lastUpdate);
|
||||
audio->ch1.lastUpdate += mTimingCurrentTime(audio->timing);
|
||||
|
||||
LOAD_32LE(ch2Flags, 0, &state->ch2.envelope);
|
||||
audio->ch2.envelope.currentVolume = GBSerializedAudioFlagsGetCh2Volume(flags);
|
||||
audio->ch2.envelope.dead = GBSerializedAudioFlagsGetCh2Dead(flags);
|
||||
audio->ch2.control.hi = GBSerializedAudioFlagsGetCh2Hi(flags);
|
||||
audio->ch2.control.length = GBSerializedAudioEnvelopeGetLength(ch2Flags);
|
||||
audio->ch2.envelope.nextStep = GBSerializedAudioEnvelopeGetNextStep(ch2Flags);
|
||||
LOAD_32LE(when, 0, &state->ch2.nextEvent);
|
||||
if (audio->ch2.envelope.dead < 2 && audio->playingCh2) {
|
||||
mTimingSchedule(audio->timing, &audio->ch2Event, when);
|
||||
}
|
||||
audio->ch2.index = GBSerializedAudioEnvelopeGetDutyIndex(ch2Flags);
|
||||
LOAD_32LE(audio->ch2.lastUpdate, 0, &state->ch2.lastUpdate);
|
||||
audio->ch2.lastUpdate += mTimingCurrentTime(audio->timing);
|
||||
|
||||
audio->ch3.readable = GBSerializedAudioFlagsGetCh3Readable(flags);
|
||||
// TODO: Big endian?
|
||||
memcpy(audio->ch3.wavedata32, state->ch3.wavebanks, sizeof(audio->ch3.wavedata32));
|
||||
LOAD_16LE(audio->ch3.length, 0, &state->ch3.length);
|
||||
LOAD_32LE(when, 0, &state->ch3.nextEvent);
|
||||
if (audio->playingCh3) {
|
||||
mTimingSchedule(audio->timing, &audio->ch3Event, when);
|
||||
}
|
||||
LOAD_32LE(when, 0, &state->ch1.nextCh3Fade);
|
||||
if (audio->ch3.readable && audio->style == GB_AUDIO_DMG) {
|
||||
mTimingSchedule(audio->timing, &audio->ch3Fade, when);
|
||||
}
|
||||
LOAD_32LE(audio->ch3.nextUpdate, 0, &state->ch3.nextEvent);
|
||||
audio->ch3.nextUpdate += mTimingCurrentTime(audio->timing);
|
||||
|
||||
LOAD_32LE(ch4Flags, 0, &state->ch4.envelope);
|
||||
audio->ch4.envelope.currentVolume = GBSerializedAudioFlagsGetCh4Volume(flags);
|
||||
|
|
|
@ -189,6 +189,9 @@ bool GBLoadROM(struct GB* gb, struct VFile* vf) {
|
|||
|
||||
if (gb->cpu) {
|
||||
struct SM83Core* cpu = gb->cpu;
|
||||
if (!gb->memory.romBase) {
|
||||
GBMBCSwitchBank0(gb, 0);
|
||||
}
|
||||
cpu->memory.setActiveRegion(cpu, cpu->pc);
|
||||
}
|
||||
|
||||
|
|
11
src/gb/io.c
|
@ -403,9 +403,10 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
|
|||
case GB_REG_WAVE_D:
|
||||
case GB_REG_WAVE_E:
|
||||
case GB_REG_WAVE_F:
|
||||
if (!gb->audio.playingCh3 || gb->audio.style != GB_AUDIO_DMG) {
|
||||
GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0x4);
|
||||
if (!gb->audio.playingCh3) {
|
||||
gb->audio.ch3.wavedata8[address - GB_REG_WAVE_0] = value;
|
||||
} else if(gb->audio.ch3.readable) {
|
||||
} else if (gb->audio.ch3.readable || gb->audio.style == GB_AUDIO_CGB) {
|
||||
gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1] = value;
|
||||
}
|
||||
break;
|
||||
|
@ -607,7 +608,8 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
|
|||
case GB_REG_WAVE_E:
|
||||
case GB_REG_WAVE_F:
|
||||
if (gb->audio.playingCh3) {
|
||||
if (gb->audio.ch3.readable || gb->audio.style != GB_AUDIO_DMG) {
|
||||
GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0x4);
|
||||
if (gb->audio.ch3.readable || gb->audio.style == GB_AUDIO_CGB) {
|
||||
return gb->audio.ch3.wavedata8[gb->audio.ch3.window >> 1];
|
||||
} else {
|
||||
return 0xFF;
|
||||
|
@ -620,6 +622,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
|
|||
if (gb->model < GB_MODEL_CGB) {
|
||||
mLOG(GB_IO, GAME_ERROR, "Reading from CGB register FF%02X in DMG mode", address);
|
||||
} else if (gb->audio.enable) {
|
||||
GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0x3);
|
||||
return (gb->audio.ch1.sample) | (gb->audio.ch2.sample << 4);
|
||||
}
|
||||
break;
|
||||
|
@ -627,7 +630,7 @@ uint8_t GBIORead(struct GB* gb, unsigned address) {
|
|||
if (gb->model < GB_MODEL_CGB) {
|
||||
mLOG(GB_IO, GAME_ERROR, "Reading from CGB register FF%02X in DMG mode", address);
|
||||
} else if (gb->audio.enable) {
|
||||
GBAudioUpdateChannel4(&gb->audio);
|
||||
GBAudioRun(&gb->audio, mTimingCurrentTime(gb->audio.timing), 0xC);
|
||||
return (gb->audio.ch3.sample) | (gb->audio.ch4.sample << 4);
|
||||
}
|
||||
break;
|
||||
|
|
|
@ -1402,11 +1402,16 @@ void _GBPocketCam(struct GB* gb, uint16_t address, uint8_t value) {
|
|||
if (value < 0x10) {
|
||||
GBMBCSwitchSramBank(gb, value);
|
||||
memory->mbcState.pocketCam.registersActive = false;
|
||||
memory->directSramAccess = true;
|
||||
} else {
|
||||
memory->mbcState.pocketCam.registersActive = true;
|
||||
memory->directSramAccess = false;
|
||||
}
|
||||
break;
|
||||
case 0x5:
|
||||
if (!memory->mbcState.pocketCam.registersActive) {
|
||||
break;
|
||||
}
|
||||
address &= 0x7F;
|
||||
if (address == 0 && value & 1) {
|
||||
value &= 6; // TODO: Timing
|
||||
|
|
|
@ -119,6 +119,7 @@ void GBVideoCacheWriteVideoRegister(struct mCacheSet* cache, uint16_t address, u
|
|||
sysconfig = mMapCacheSystemInfoSetMacroTileSize(sysconfig, 5);
|
||||
sysconfig = mMapCacheSystemInfoSetPaletteBPP(sysconfig, 1);
|
||||
sysconfig = mMapCacheSystemInfoSetMapAlign(sysconfig, 0);
|
||||
sysconfig = mMapCacheSystemInfoSetWriteAlign(sysconfig, 0);
|
||||
sysconfig = mMapCacheSystemInfoSetTilesHigh(sysconfig, 5);
|
||||
sysconfig = mMapCacheSystemInfoSetTilesWide(sysconfig, 5);
|
||||
mMapCacheConfigureSystem(map, sysconfig);
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
mLOG_DEFINE_CATEGORY(GB_STATE, "GB Savestate", "gb.serialize");
|
||||
|
||||
MGBA_EXPORT const uint32_t GBSavestateMagic = 0x00400000;
|
||||
MGBA_EXPORT const uint32_t GBSavestateVersion = 0x00000002;
|
||||
MGBA_EXPORT const uint32_t GBSavestateVersion = 0x00000003;
|
||||
|
||||
void GBSerialize(struct GB* gb, struct GBSerializedState* state) {
|
||||
STORE_32LE(GBSavestateMagic + GBSavestateVersion, 0, &state->versionMagic);
|
||||
|
|
|
@ -157,7 +157,7 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
|
|||
}
|
||||
}
|
||||
// Tell the other GBs they can continue up to where we were
|
||||
node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
|
||||
node->p->d.addCycles(&node->p->d, node->id, node->eventDiff);
|
||||
#ifndef NDEBUG
|
||||
node->phase = node->p->d.transferActive;
|
||||
#endif
|
||||
|
@ -252,6 +252,12 @@ static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t valu
|
|||
mLockstepLock(&node->p->d);
|
||||
bool claimed = false;
|
||||
if (ATOMIC_CMPXCHG(node->p->masterClaimed, claimed, true)) {
|
||||
if (node->id != 0) {
|
||||
node->p->players[0]->id = 1;
|
||||
node->p->players[1] = node->p->players[0];
|
||||
node->p->players[0] = node->p->players[1];
|
||||
node->id = 0;
|
||||
}
|
||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
|
||||
ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]);
|
||||
mTimingDeschedule(&driver->p->p->timing, &driver->p->event);
|
||||
|
|
208
src/gba/audio.c
|
@ -25,6 +25,7 @@ mLOG_DEFINE_CATEGORY(GBA_AUDIO, "GBA Audio", "gba.audio");
|
|||
const unsigned GBA_AUDIO_SAMPLES = 2048;
|
||||
const int GBA_AUDIO_VOLUME_MAX = 0x100;
|
||||
|
||||
static const int SAMPLE_INTERVAL = GBA_ARM7TDMI_FREQUENCY / 0x4000;
|
||||
static const int CLOCKS_PER_FRAME = 0x800;
|
||||
|
||||
static int _applyBias(struct GBAAudio* audio, int sample);
|
||||
|
@ -43,6 +44,7 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) {
|
|||
GBAudioInit(&audio->psg, 0, nr52, GB_AUDIO_GBA);
|
||||
audio->psg.timing = &audio->p->timing;
|
||||
audio->psg.clockRate = GBA_ARM7TDMI_FREQUENCY;
|
||||
audio->psg.frameEvent.context = audio;
|
||||
audio->samples = samples;
|
||||
// Guess too large; we hang producing extra samples if we guess too low
|
||||
blip_set_rates(audio->psg.left, GBA_ARM7TDMI_FREQUENCY, 96000);
|
||||
|
@ -57,6 +59,8 @@ void GBAAudioInit(struct GBAAudio* audio, size_t samples) {
|
|||
|
||||
void GBAAudioReset(struct GBAAudio* audio) {
|
||||
GBAudioReset(&audio->psg);
|
||||
mTimingDeschedule(&audio->p->timing, &audio->psg.frameEvent);
|
||||
mTimingSchedule(&audio->p->timing, &audio->psg.frameEvent, 0);
|
||||
mTimingDeschedule(&audio->p->timing, &audio->sampleEvent);
|
||||
mTimingSchedule(&audio->p->timing, &audio->sampleEvent, 0);
|
||||
audio->chA.dmaSource = 1;
|
||||
|
@ -66,18 +70,22 @@ void GBAAudioReset(struct GBAAudio* audio) {
|
|||
audio->chA.internalSample = 0;
|
||||
audio->chA.internalRemaining = 0;
|
||||
memset(audio->chA.fifo, 0, sizeof(audio->chA.fifo));
|
||||
audio->chA.sample = 0;
|
||||
audio->chB.fifoWrite = 0;
|
||||
audio->chB.fifoRead = 0;
|
||||
audio->chB.internalSample = 0;
|
||||
audio->chB.internalRemaining = 0;
|
||||
memset(audio->chB.fifo, 0, sizeof(audio->chB.fifo));
|
||||
audio->chB.sample = 0;
|
||||
audio->sampleRate = 0x8000;
|
||||
int i;
|
||||
for (i = 0; i < 8; ++i) {
|
||||
audio->chA.samples[i] = 0;
|
||||
audio->chB.samples[i] = 0;
|
||||
}
|
||||
audio->soundbias = 0x200;
|
||||
audio->volume = 0;
|
||||
audio->volumeChA = false;
|
||||
audio->volumeChB = false;
|
||||
audio->lastSample = 0;
|
||||
audio->sampleIndex = 0;
|
||||
audio->chARight = false;
|
||||
audio->chALeft = false;
|
||||
audio->chATimer = false;
|
||||
|
@ -85,7 +93,7 @@ void GBAAudioReset(struct GBAAudio* audio) {
|
|||
audio->chBLeft = false;
|
||||
audio->chBTimer = false;
|
||||
audio->enable = false;
|
||||
audio->sampleInterval = GBA_ARM7TDMI_FREQUENCY / audio->sampleRate;
|
||||
audio->sampleInterval = GBA_ARM7TDMI_FREQUENCY / 0x8000;
|
||||
audio->psg.sampleInterval = audio->sampleInterval;
|
||||
|
||||
blip_clear(audio->psg.left);
|
||||
|
@ -137,56 +145,67 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA*
|
|||
}
|
||||
|
||||
void GBAAudioWriteSOUND1CNT_LO(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR10(&audio->psg, value);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUND1CNT_HI(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR11(&audio->psg, value);
|
||||
GBAudioWriteNR12(&audio->psg, value >> 8);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUND1CNT_X(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR13(&audio->psg, value);
|
||||
GBAudioWriteNR14(&audio->psg, value >> 8);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUND2CNT_LO(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR21(&audio->psg, value);
|
||||
GBAudioWriteNR22(&audio->psg, value >> 8);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUND2CNT_HI(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR23(&audio->psg, value);
|
||||
GBAudioWriteNR24(&audio->psg, value >> 8);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUND3CNT_LO(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
audio->psg.ch3.size = GBAudioRegisterBankGetSize(value);
|
||||
audio->psg.ch3.bank = GBAudioRegisterBankGetBank(value);
|
||||
GBAudioWriteNR30(&audio->psg, value);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUND3CNT_HI(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR31(&audio->psg, value);
|
||||
audio->psg.ch3.volume = GBAudioRegisterBankVolumeGetVolumeGBA(value >> 8);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUND3CNT_X(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR33(&audio->psg, value);
|
||||
GBAudioWriteNR34(&audio->psg, value >> 8);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUND4CNT_LO(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR41(&audio->psg, value);
|
||||
GBAudioWriteNR42(&audio->psg, value >> 8);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUND4CNT_HI(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR43(&audio->psg, value);
|
||||
GBAudioWriteNR44(&audio->psg, value >> 8);
|
||||
}
|
||||
|
||||
void GBAAudioWriteSOUNDCNT_LO(struct GBAAudio* audio, uint16_t value) {
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
|
||||
GBAudioWriteNR50(&audio->psg, value);
|
||||
GBAudioWriteNR51(&audio->psg, value >> 8);
|
||||
}
|
||||
|
@ -218,6 +237,7 @@ void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) {
|
|||
|
||||
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) {
|
||||
audio->soundbias = value;
|
||||
audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value);
|
||||
}
|
||||
|
||||
void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) {
|
||||
|
@ -229,6 +249,7 @@ void GBAAudioWriteWaveRAM(struct GBAAudio* audio, int address, uint32_t value) {
|
|||
bank = 1;
|
||||
}
|
||||
|
||||
GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing), 0x4);
|
||||
audio->psg.ch3.wavedata32[address | (bank * 4)] = value;
|
||||
}
|
||||
|
||||
|
@ -241,6 +262,7 @@ uint32_t GBAAudioReadWaveRAM(struct GBAAudio* audio, int address) {
|
|||
bank = 1;
|
||||
}
|
||||
|
||||
GBAudioRun(&audio->psg, mTimingCurrentTime(audio->psg.timing), 0x4);
|
||||
return audio->psg.ch3.wavedata32[address | (bank * 4)];
|
||||
}
|
||||
|
||||
|
@ -297,7 +319,14 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
|
|||
channel->fifoRead = 0;
|
||||
}
|
||||
}
|
||||
channel->sample = channel->internalSample;
|
||||
int32_t until = mTimingUntil(&audio->p->timing, &audio->sampleEvent) - 1;
|
||||
int bits = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
|
||||
until += 1 << (9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias));
|
||||
until >>= 9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias);
|
||||
int i;
|
||||
for (i = bits - until; i < bits; ++i) {
|
||||
channel->samples[i] = channel->internalSample;
|
||||
}
|
||||
if (channel->internalRemaining) {
|
||||
channel->internalSample >>= 8;
|
||||
--channel->internalRemaining;
|
||||
|
@ -314,61 +343,100 @@ static int _applyBias(struct GBAAudio* audio, int sample) {
|
|||
return ((sample - GBARegisterSOUNDBIASGetBias(audio->soundbias)) * audio->masterVolume * 3) >> 4;
|
||||
}
|
||||
|
||||
void GBAAudioSample(struct GBAAudio* audio, int32_t timestamp) {
|
||||
timestamp -= audio->lastSample;
|
||||
timestamp -= audio->sampleIndex * audio->sampleInterval; // TODO: This can break if the interval changes between samples
|
||||
|
||||
int maxSample = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
|
||||
int sample;
|
||||
for (sample = audio->sampleIndex; timestamp >= audio->sampleInterval && sample < maxSample; ++sample, timestamp -= audio->sampleInterval) {
|
||||
int16_t sampleLeft = 0;
|
||||
int16_t sampleRight = 0;
|
||||
int psgShift = 4 - audio->volume;
|
||||
GBAudioRun(&audio->psg, sample * audio->sampleInterval + audio->lastSample, 0xF);
|
||||
GBAudioSamplePSG(&audio->psg, &sampleLeft, &sampleRight);
|
||||
sampleLeft >>= psgShift;
|
||||
sampleRight >>= psgShift;
|
||||
|
||||
if (audio->mixer) {
|
||||
audio->mixer->step(audio->mixer);
|
||||
}
|
||||
if (!audio->externalMixing) {
|
||||
if (!audio->forceDisableChA) {
|
||||
if (audio->chALeft) {
|
||||
sampleLeft += (audio->chA.samples[sample] << 2) >> !audio->volumeChA;
|
||||
}
|
||||
|
||||
if (audio->chARight) {
|
||||
sampleRight += (audio->chA.samples[sample] << 2) >> !audio->volumeChA;
|
||||
}
|
||||
}
|
||||
|
||||
if (!audio->forceDisableChB) {
|
||||
if (audio->chBLeft) {
|
||||
sampleLeft += (audio->chB.samples[sample] << 2) >> !audio->volumeChB;
|
||||
}
|
||||
|
||||
if (audio->chBRight) {
|
||||
sampleRight += (audio->chB.samples[sample] << 2) >> !audio->volumeChB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sampleLeft = _applyBias(audio, sampleLeft);
|
||||
sampleRight = _applyBias(audio, sampleRight);
|
||||
audio->currentSamples[sample].left = sampleLeft;
|
||||
audio->currentSamples[sample].right = sampleRight;
|
||||
}
|
||||
|
||||
audio->sampleIndex = sample;
|
||||
if (sample == maxSample) {
|
||||
audio->lastSample += SAMPLE_INTERVAL;
|
||||
audio->sampleIndex = 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||
struct GBAAudio* audio = user;
|
||||
int16_t sampleLeft = 0;
|
||||
int16_t sampleRight = 0;
|
||||
int psgShift = 4 - audio->volume;
|
||||
GBAudioSamplePSG(&audio->psg, &sampleLeft, &sampleRight);
|
||||
sampleLeft >>= psgShift;
|
||||
sampleRight >>= psgShift;
|
||||
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing) - cyclesLate);
|
||||
|
||||
if (audio->mixer) {
|
||||
audio->mixer->step(audio->mixer);
|
||||
}
|
||||
if (!audio->externalMixing) {
|
||||
if (!audio->forceDisableChA) {
|
||||
if (audio->chALeft) {
|
||||
sampleLeft += (audio->chA.sample << 2) >> !audio->volumeChA;
|
||||
}
|
||||
|
||||
if (audio->chARight) {
|
||||
sampleRight += (audio->chA.sample << 2) >> !audio->volumeChA;
|
||||
}
|
||||
}
|
||||
|
||||
if (!audio->forceDisableChB) {
|
||||
if (audio->chBLeft) {
|
||||
sampleLeft += (audio->chB.sample << 2) >> !audio->volumeChB;
|
||||
}
|
||||
|
||||
if (audio->chBRight) {
|
||||
sampleRight += (audio->chB.sample << 2) >> !audio->volumeChB;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sampleLeft = _applyBias(audio, sampleLeft);
|
||||
sampleRight = _applyBias(audio, sampleRight);
|
||||
int samples = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
|
||||
int sampleMask = 1 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
|
||||
memset(audio->chA.samples, audio->chA.samples[samples - 1], sizeof(audio->chA.samples));
|
||||
memset(audio->chB.samples, audio->chB.samples[samples - 1], sizeof(audio->chB.samples));
|
||||
|
||||
mCoreSyncLockAudio(audio->p->sync);
|
||||
unsigned produced;
|
||||
if ((size_t) blip_samples_avail(audio->psg.left) < audio->samples) {
|
||||
blip_add_delta(audio->psg.left, audio->clock, sampleLeft - audio->lastLeft);
|
||||
blip_add_delta(audio->psg.right, audio->clock, sampleRight - audio->lastRight);
|
||||
audio->lastLeft = sampleLeft;
|
||||
audio->lastRight = sampleRight;
|
||||
audio->clock += audio->sampleInterval;
|
||||
if (audio->clock >= CLOCKS_PER_FRAME) {
|
||||
blip_end_frame(audio->psg.left, CLOCKS_PER_FRAME);
|
||||
blip_end_frame(audio->psg.right, CLOCKS_PER_FRAME);
|
||||
audio->clock -= CLOCKS_PER_FRAME;
|
||||
int32_t sampleSumLeft = 0;
|
||||
int32_t sampleSumRight = 0;
|
||||
int i;
|
||||
for (i = 0; i < samples; ++i) {
|
||||
int16_t sampleLeft = audio->currentSamples[i].left;
|
||||
int16_t sampleRight = audio->currentSamples[i].right;
|
||||
sampleSumLeft += sampleLeft;
|
||||
sampleSumRight += sampleRight;
|
||||
if ((size_t) blip_samples_avail(audio->psg.left) < audio->samples) {
|
||||
blip_add_delta(audio->psg.left, audio->clock, sampleLeft - audio->lastLeft);
|
||||
blip_add_delta(audio->psg.right, audio->clock, sampleRight - audio->lastRight);
|
||||
audio->lastLeft = sampleLeft;
|
||||
audio->lastRight = sampleRight;
|
||||
audio->clock += audio->sampleInterval;
|
||||
if (audio->clock >= CLOCKS_PER_FRAME) {
|
||||
blip_end_frame(audio->psg.left, CLOCKS_PER_FRAME);
|
||||
blip_end_frame(audio->psg.right, CLOCKS_PER_FRAME);
|
||||
audio->clock -= CLOCKS_PER_FRAME;
|
||||
}
|
||||
}
|
||||
// TODO: Post all frames
|
||||
if (audio->p->stream && audio->p->stream->postAudioFrame && (i & (sampleMask - 1)) == sampleMask - 1) {
|
||||
sampleSumLeft /= sampleMask;
|
||||
sampleSumRight /= sampleMask;
|
||||
audio->p->stream->postAudioFrame(audio->p->stream, sampleSumLeft, sampleSumRight);
|
||||
sampleSumLeft = 0;
|
||||
sampleSumRight = 0;
|
||||
}
|
||||
}
|
||||
produced = blip_samples_avail(audio->psg.left);
|
||||
if (audio->p->stream && audio->p->stream->postAudioFrame) {
|
||||
audio->p->stream->postAudioFrame(audio->p->stream, sampleLeft, sampleRight);
|
||||
}
|
||||
bool wait = produced >= audio->samples;
|
||||
if (!mCoreSyncProduceAudio(audio->p->sync, audio->psg.left, audio->samples)) {
|
||||
// Interrupted
|
||||
|
@ -379,7 +447,7 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
|||
audio->p->stream->postAudioBuffer(audio->p->stream, audio->psg.left, audio->psg.right);
|
||||
}
|
||||
|
||||
mTimingSchedule(timing, &audio->sampleEvent, audio->sampleInterval - cyclesLate);
|
||||
mTimingSchedule(timing, &audio->sampleEvent, SAMPLE_INTERVAL - cyclesLate);
|
||||
}
|
||||
|
||||
void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState* state) {
|
||||
|
@ -387,12 +455,18 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState*
|
|||
|
||||
STORE_32(audio->chA.internalSample, 0, &state->audio.internalA);
|
||||
STORE_32(audio->chB.internalSample, 0, &state->audio.internalB);
|
||||
state->audio.sampleA = audio->chA.sample;
|
||||
state->audio.sampleB = audio->chB.sample;
|
||||
memcpy(state->samples.chA, audio->chA.samples, sizeof(audio->chA.samples));
|
||||
memcpy(state->samples.chB, audio->chB.samples, sizeof(audio->chB.samples));
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < GBA_MAX_SAMPLES; ++i) {
|
||||
STORE_16(audio->currentSamples[i].left, 0, &state->currentSamples[i].left);
|
||||
STORE_16(audio->currentSamples[i].right, 0, &state->currentSamples[i].right);
|
||||
}
|
||||
STORE_32(audio->lastSample, 0, &state->audio.lastSample);
|
||||
|
||||
int readA = audio->chA.fifoRead;
|
||||
int readB = audio->chB.fifoRead;
|
||||
size_t i;
|
||||
for (i = 0; i < GBA_AUDIO_FIFO_SIZE; ++i) {
|
||||
STORE_32(audio->chA.fifo[readA], i << 2, state->audio.fifoA);
|
||||
STORE_32(audio->chB.fifo[readB], i << 2, state->audio.fifoB);
|
||||
|
@ -426,6 +500,11 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState*
|
|||
flags = GBASerializedAudioFlagsSetFIFOInternalSamplesA(flags, audio->chA.internalRemaining);
|
||||
flags = GBASerializedAudioFlagsSetFIFOInternalSamplesB(flags, audio->chB.internalRemaining);
|
||||
STORE_16(flags, 0, &state->audio.gbaFlags);
|
||||
|
||||
GBASerializedAudioFlags2 flags2 = 0;
|
||||
flags2 = GBASerializedAudioFlags2SetSampleIndex(flags2, audio->sampleIndex);
|
||||
STORE_32(flags2, 0, &state->audio.gbaFlags2);
|
||||
|
||||
STORE_32(audio->sampleEvent.when - mTimingCurrentTime(&audio->p->timing), 0, &state->audio.nextSample);
|
||||
}
|
||||
|
||||
|
@ -434,12 +513,18 @@ void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState
|
|||
|
||||
LOAD_32(audio->chA.internalSample, 0, &state->audio.internalA);
|
||||
LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB);
|
||||
audio->chA.sample = state->audio.sampleA;
|
||||
audio->chB.sample = state->audio.sampleB;
|
||||
memcpy(audio->chA.samples, state->samples.chA, sizeof(audio->chA.samples));
|
||||
memcpy(audio->chB.samples, state->samples.chB, sizeof(audio->chB.samples));
|
||||
|
||||
size_t i;
|
||||
for (i = 0; i < GBA_MAX_SAMPLES; ++i) {
|
||||
LOAD_16(audio->currentSamples[i].left, 0, &state->currentSamples[i].left);
|
||||
LOAD_16(audio->currentSamples[i].right, 0, &state->currentSamples[i].right);
|
||||
}
|
||||
LOAD_32(audio->lastSample, 0, &state->audio.lastSample);
|
||||
|
||||
int readA = 0;
|
||||
int readB = 0;
|
||||
size_t i;
|
||||
for (i = 0; i < GBA_AUDIO_FIFO_SIZE; ++i) {
|
||||
LOAD_32(audio->chA.fifo[readA], i << 2, state->audio.fifoA);
|
||||
LOAD_32(audio->chB.fifo[readB], i << 2, state->audio.fifoB);
|
||||
|
@ -456,8 +541,15 @@ void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState
|
|||
audio->chA.internalRemaining = GBASerializedAudioFlagsGetFIFOInternalSamplesA(flags);
|
||||
audio->chB.internalRemaining = GBASerializedAudioFlagsGetFIFOInternalSamplesB(flags);
|
||||
|
||||
GBASerializedAudioFlags2 flags2;
|
||||
LOAD_32(flags2, 0, &state->audio.gbaFlags2);
|
||||
audio->sampleIndex = GBASerializedAudioFlags2GetSampleIndex(flags2);
|
||||
|
||||
uint32_t when;
|
||||
LOAD_32(when, 0, &state->audio.nextSample);
|
||||
if (state->versionMagic < 0x01000007) {
|
||||
audio->lastSample = when - SAMPLE_INTERVAL;
|
||||
}
|
||||
mTimingSchedule(&audio->p->timing, &audio->sampleEvent, when);
|
||||
}
|
||||
|
||||
|
|
|
@ -125,7 +125,7 @@ static void _stepSample(struct GBAAudioMixer* mixer, struct GBAMP2kTrack* track)
|
|||
for (nSample = 0; nSample < updates; ++nSample) {
|
||||
int8_t sample = memory->load8(cpu, sampleBase + sampleI, 0);
|
||||
|
||||
struct GBAStereoSample stereo = {
|
||||
struct mStereoSample stereo = {
|
||||
(sample * track->channel->leftVolume * track->channel->envelopeV) >> 9,
|
||||
(sample * track->channel->rightVolume * track->channel->envelopeV) >> 9
|
||||
};
|
||||
|
@ -277,7 +277,7 @@ void _mp2kStep(struct GBAAudioMixer* mixer) {
|
|||
uint32_t interval = mixer->p->sampleInterval / OVERSAMPLE;
|
||||
int i;
|
||||
for (i = 0; i < OVERSAMPLE; ++i) {
|
||||
struct GBAStereoSample sample = {0};
|
||||
struct mStereoSample sample = {0};
|
||||
size_t track;
|
||||
for (track = 0; track < MP2K_MAX_SOUND_CHANNELS; ++track) {
|
||||
if (!mixer->activeTracks[track].channel->status) {
|
||||
|
|
|
@ -399,14 +399,15 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) {
|
|||
}
|
||||
GBAUnloadROM(gba);
|
||||
gba->romVf = vf;
|
||||
gba->isPristine = true;
|
||||
gba->pristineRomSize = vf->size(vf);
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
if (gba->pristineRomSize > SIZE_CART0) {
|
||||
gba->isPristine = false;
|
||||
char ident;
|
||||
vf->seek(vf, 0xAC, SEEK_SET);
|
||||
vf->read(vf, &ident, 1);
|
||||
if (ident == 'M') {
|
||||
gba->isPristine = false;
|
||||
gba->memory.romSize = 0x01000000;
|
||||
#ifdef FIXED_ROM_BUFFER
|
||||
gba->memory.rom = romBuffer;
|
||||
|
@ -417,8 +418,8 @@ bool GBALoadROM(struct GBA* gba, struct VFile* vf) {
|
|||
gba->memory.rom = vf->map(vf, SIZE_CART0, MAP_READ);
|
||||
gba->memory.romSize = SIZE_CART0;
|
||||
}
|
||||
gba->pristineRomSize = SIZE_CART0;
|
||||
} else {
|
||||
gba->isPristine = true;
|
||||
gba->memory.rom = vf->map(vf, gba->pristineRomSize, MAP_READ);
|
||||
gba->memory.romSize = gba->pristineRomSize;
|
||||
}
|
||||
|
@ -583,6 +584,10 @@ void GBADebug(struct GBA* gba, uint16_t flags) {
|
|||
}
|
||||
|
||||
bool GBAIsROM(struct VFile* vf) {
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifdef USE_ELF
|
||||
struct ELF* elf = ELFOpen(vf);
|
||||
if (elf) {
|
||||
|
@ -594,9 +599,6 @@ bool GBAIsROM(struct VFile* vf) {
|
|||
return isGBA;
|
||||
}
|
||||
#endif
|
||||
if (!vf) {
|
||||
return false;
|
||||
}
|
||||
|
||||
uint8_t signature[sizeof(GBA_ROM_MAGIC) + sizeof(GBA_ROM_MAGIC2)];
|
||||
if (vf->seek(vf, GBA_ROM_MAGIC_OFFSET, SEEK_SET) < 0) {
|
||||
|
|
38
src/gba/io.c
|
@ -199,7 +199,7 @@ const char* const GBAIORegisterNames[] = {
|
|||
"IME"
|
||||
};
|
||||
|
||||
static const int _isValidRegister[REG_MAX >> 1] = {
|
||||
static const int _isValidRegister[REG_INTERNAL_MAX >> 1] = {
|
||||
// Video
|
||||
1, 0, 1, 1, 1, 1, 1, 1,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
|
@ -238,10 +238,12 @@ static const int _isValidRegister[REG_MAX >> 1] = {
|
|||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Interrupts
|
||||
1, 1, 1, 0, 1
|
||||
1, 1, 1, 0, 1, 0, 0, 0,
|
||||
// Internal registers
|
||||
1, 1
|
||||
};
|
||||
|
||||
static const int _isRSpecialRegister[REG_MAX >> 1] = {
|
||||
static const int _isRSpecialRegister[REG_INTERNAL_MAX >> 1] = {
|
||||
// Video
|
||||
0, 0, 1, 1, 0, 0, 0, 0,
|
||||
1, 1, 1, 1, 1, 1, 1, 1,
|
||||
|
@ -280,9 +282,12 @@ static const int _isRSpecialRegister[REG_MAX >> 1] = {
|
|||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Interrupts
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Internal registers
|
||||
1, 1
|
||||
};
|
||||
|
||||
static const int _isWSpecialRegister[REG_MAX >> 1] = {
|
||||
static const int _isWSpecialRegister[REG_INTERNAL_MAX >> 1] = {
|
||||
// Video
|
||||
0, 0, 1, 1, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
|
@ -321,7 +326,9 @@ static const int _isWSpecialRegister[REG_MAX >> 1] = {
|
|||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0, 0, 0, 0, 0, 0,
|
||||
// Interrupts
|
||||
1, 1, 0, 0, 1
|
||||
1, 1, 0, 0, 1, 0, 0, 0,
|
||||
// Internal registers
|
||||
1, 1
|
||||
};
|
||||
|
||||
void GBAIOInit(struct GBA* gba) {
|
||||
|
@ -333,6 +340,8 @@ void GBAIOInit(struct GBA* gba) {
|
|||
gba->memory.io[REG_BG2PD >> 1] = 0x100;
|
||||
gba->memory.io[REG_BG3PA >> 1] = 0x100;
|
||||
gba->memory.io[REG_BG3PD >> 1] = 0x100;
|
||||
gba->memory.io[REG_INTERNAL_EXWAITCNT_LO >> 1] = 0x20;
|
||||
gba->memory.io[REG_INTERNAL_EXWAITCNT_HI >> 1] = 0xD00;
|
||||
|
||||
if (!gba->biosVf) {
|
||||
gba->memory.io[REG_VCOUNT >> 1] = 0x7E;
|
||||
|
@ -416,6 +425,7 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
value |= gba->memory.io[REG_SOUNDCNT_X >> 1] & 0xF;
|
||||
break;
|
||||
case REG_SOUNDBIAS:
|
||||
value &= 0xC3FE;
|
||||
GBAAudioWriteSOUNDBIAS(&gba->audio, value);
|
||||
break;
|
||||
|
||||
|
@ -573,6 +583,12 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
|||
case REG_MAX:
|
||||
// Some bad interrupt libraries will write to this
|
||||
break;
|
||||
case REG_EXWAITCNT_HI:
|
||||
// This register sits outside of the normal I/O block, so we need to stash it somewhere unused
|
||||
address = REG_INTERNAL_EXWAITCNT_HI;
|
||||
value &= 0xFF00;
|
||||
GBAAdjustEWRAMWaitstates(gba, value);
|
||||
break;
|
||||
case REG_DEBUG_ENABLE:
|
||||
gba->debug = value == 0xC0DE;
|
||||
return;
|
||||
|
@ -842,7 +858,6 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
gba->memory.io[REG_JOYSTAT >> 1] &= ~JOYSTAT_RECV;
|
||||
break;
|
||||
|
||||
case REG_SOUNDBIAS:
|
||||
case REG_POSTFLG:
|
||||
mLOG(GBA_IO, STUB, "Stub I/O register read: %03x", address);
|
||||
break;
|
||||
|
@ -897,6 +912,7 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
case REG_BLDALPHA:
|
||||
case REG_SOUNDCNT_HI:
|
||||
case REG_SOUNDCNT_X:
|
||||
case REG_SOUNDBIAS:
|
||||
case REG_DMA0CNT_HI:
|
||||
case REG_DMA1CNT_HI:
|
||||
case REG_DMA2CNT_HI:
|
||||
|
@ -935,6 +951,11 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
case 0x206:
|
||||
mLOG(GBA_IO, GAME_ERROR, "Read from unused I/O register: %03X", address);
|
||||
return 0;
|
||||
// These registers sit outside of the normal I/O block, so we need to stash them somewhere unused
|
||||
case REG_EXWAITCNT_LO:
|
||||
case REG_EXWAITCNT_HI:
|
||||
address += REG_INTERNAL_EXWAITCNT_LO - REG_EXWAITCNT_LO;
|
||||
break;
|
||||
case REG_DEBUG_ENABLE:
|
||||
if (gba->debug) {
|
||||
return 0x1DEA;
|
||||
|
@ -949,7 +970,7 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
|||
|
||||
void GBAIOSerialize(struct GBA* gba, struct GBASerializedState* state) {
|
||||
int i;
|
||||
for (i = 0; i < REG_MAX; i += 2) {
|
||||
for (i = 0; i < REG_INTERNAL_MAX; i += 2) {
|
||||
if (_isRSpecialRegister[i >> 1]) {
|
||||
STORE_16(gba->memory.io[i >> 1], i, state->io);
|
||||
} else if (_isValidRegister[i >> 1]) {
|
||||
|
@ -990,6 +1011,9 @@ void GBAIODeserialize(struct GBA* gba, const struct GBASerializedState* state) {
|
|||
GBAIOWrite(gba, i, reg);
|
||||
}
|
||||
}
|
||||
if (state->versionMagic >= 0x01000006) {
|
||||
GBAIOWrite(gba, REG_EXWAITCNT_HI, gba->memory.io[REG_INTERNAL_EXWAITCNT_HI >> 1]);
|
||||
}
|
||||
|
||||
uint32_t when;
|
||||
for (i = 0; i < 4; ++i) {
|
||||
|
|
|
@ -127,6 +127,7 @@ void GBAMemoryReset(struct GBA* gba) {
|
|||
|
||||
memset(gba->memory.io, 0, sizeof(gba->memory.io));
|
||||
GBAAdjustWaitstates(gba, 0);
|
||||
GBAAdjustEWRAMWaitstates(gba, 0x0D00);
|
||||
|
||||
gba->memory.activeRegion = -1;
|
||||
gba->memory.agbPrintProtect = 0;
|
||||
|
@ -1239,9 +1240,13 @@ void GBAPatch32(struct ARMCore* cpu, uint32_t address, int32_t value, int32_t* o
|
|||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
LOAD_32(oldValue, address & 0x0001FFFC, gba->video.vram);
|
||||
STORE_32(value, address & 0x0001FFFC, gba->video.vram);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFC);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x0001FFFC) | 2);
|
||||
} else {
|
||||
LOAD_32(oldValue, address & 0x00017FFC, gba->video.vram);
|
||||
STORE_32(value, address & 0x00017FFC, gba->video.vram);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFC);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, (address & 0x00017FFC) | 2);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
|
@ -1308,9 +1313,11 @@ void GBAPatch16(struct ARMCore* cpu, uint32_t address, int16_t value, int16_t* o
|
|||
if ((address & 0x0001FFFF) < SIZE_VRAM) {
|
||||
LOAD_16(oldValue, address & 0x0001FFFE, gba->video.vram);
|
||||
STORE_16(value, address & 0x0001FFFE, gba->video.vram);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
|
||||
} else {
|
||||
LOAD_16(oldValue, address & 0x00017FFE, gba->video.vram);
|
||||
STORE_16(value, address & 0x00017FFE, gba->video.vram);
|
||||
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x00017FFE);
|
||||
}
|
||||
break;
|
||||
case REGION_OAM:
|
||||
|
@ -1708,6 +1715,31 @@ void GBAAdjustWaitstates(struct GBA* gba, uint16_t parameters) {
|
|||
}
|
||||
}
|
||||
|
||||
void GBAAdjustEWRAMWaitstates(struct GBA* gba, uint16_t parameters) {
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
struct ARMCore* cpu = gba->cpu;
|
||||
|
||||
int wait = 15 - ((parameters >> 8) & 0xF);
|
||||
if (wait) {
|
||||
memory->waitstatesNonseq16[REGION_WORKING_RAM] = wait;
|
||||
memory->waitstatesSeq16[REGION_WORKING_RAM] = wait;
|
||||
memory->waitstatesNonseq32[REGION_WORKING_RAM] = 2 * wait + 1;
|
||||
memory->waitstatesSeq32[REGION_WORKING_RAM] = 2 * wait + 1;
|
||||
|
||||
cpu->memory.activeSeqCycles32 = memory->waitstatesSeq32[memory->activeRegion];
|
||||
cpu->memory.activeSeqCycles16 = memory->waitstatesSeq16[memory->activeRegion];
|
||||
|
||||
cpu->memory.activeNonseqCycles32 = memory->waitstatesNonseq32[memory->activeRegion];
|
||||
cpu->memory.activeNonseqCycles16 = memory->waitstatesNonseq16[memory->activeRegion];
|
||||
} else {
|
||||
if (!gba->hardCrash) {
|
||||
mLOG(GBA_MEM, GAME_ERROR, "Cannot set EWRAM to 0 waitstates");
|
||||
} else {
|
||||
mLOG(GBA_MEM, FATAL, "Cannot set EWRAM to 0 waitstates");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int32_t GBAMemoryStall(struct ARMCore* cpu, int32_t wait) {
|
||||
struct GBA* gba = (struct GBA*) cpu->master;
|
||||
struct GBAMemory* memory = &gba->memory;
|
||||
|
|
|
@ -70,6 +70,9 @@ static const struct GBACartridgeOverride _overrides[] = {
|
|||
{ "AI2E", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false },
|
||||
{ "AI2P", SAVEDATA_FORCE_NONE, HW_NONE, IDLE_LOOP_NONE, false },
|
||||
|
||||
// Game Boy Wars Advance 1+2
|
||||
{ "BGWJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
|
||||
|
||||
// Golden Sun: The Lost Age
|
||||
{ "AGFE", SAVEDATA_FLASH512, HW_NONE, 0x801353A, false },
|
||||
|
||||
|
|
|
@ -160,7 +160,7 @@ static void GBAVideoCacheWriteBGCNT(struct mCacheSet* cache, size_t bg, uint16_t
|
|||
int size = GBARegisterBGCNTGetSize(value);
|
||||
int tilesWide = 0;
|
||||
int tilesHigh = 0;
|
||||
mMapCacheSystemInfo sysconfig = 0;
|
||||
mMapCacheSystemInfo sysconfig = mMapCacheSystemInfoSetWriteAlign(0, 1);
|
||||
if (map->mapParser == mapParser0) {
|
||||
map->tileCache = mTileCacheSetGetPointer(&cache->tiles, p);
|
||||
sysconfig = mMapCacheSystemInfoSetPaletteBPP(sysconfig, 2 + p);
|
||||
|
|
|
@ -85,25 +85,37 @@ static const char* const _vertexShader =
|
|||
"}";
|
||||
|
||||
static const char* const _renderTile16 =
|
||||
"#ifndef VRAM_MASK\n"
|
||||
"#define VRAM_MASK\n"
|
||||
"#endif\n"
|
||||
"int renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
|
||||
" int address = charBase + tile * 16 + (localCoord.x >> 2) + (localCoord.y << 1);\n"
|
||||
" int halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0).r;\n"
|
||||
" int halfrow = texelFetch(vram, ivec2(address & 255, (address >> 8) VRAM_MASK), 0).r;\n"
|
||||
" int entry = (halfrow >> (4 * (localCoord.x & 3))) & 15;\n"
|
||||
" if (entry == 0) {\n"
|
||||
" discard;\n"
|
||||
" }\n"
|
||||
" return paletteId * 16 + entry;\n"
|
||||
"}\n"
|
||||
"int mask(int tile) {\n"
|
||||
" return tile & 31;\n"
|
||||
"}";
|
||||
|
||||
static const char* const _renderTile256 =
|
||||
"#ifndef VRAM_MASK\n"
|
||||
"#define VRAM_MASK\n"
|
||||
"#endif\n"
|
||||
"int renderTile(int tile, int paletteId, ivec2 localCoord) {\n"
|
||||
" int address = charBase + tile * 32 + (localCoord.x >> 1) + (localCoord.y << 2);\n"
|
||||
" int halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0).r;\n"
|
||||
" int halfrow = texelFetch(vram, ivec2(address & 255, (address >> 8) VRAM_MASK), 0).r;\n"
|
||||
" int entry = (halfrow >> (8 * (localCoord.x & 1))) & 255;\n"
|
||||
" if (entry == 0) {\n"
|
||||
" discard;\n"
|
||||
" }\n"
|
||||
" return entry;\n"
|
||||
"}"
|
||||
"int mask(int tile) {\n"
|
||||
" return tile & 15;\n"
|
||||
"}";
|
||||
|
||||
static const struct GBAVideoGLUniform _uniformsMode0[] = {
|
||||
|
@ -165,9 +177,9 @@ static const char* const _renderMode0 =
|
|||
" coord.y ^= 7;\n"
|
||||
" }\n"
|
||||
" int tile = map & 1023;\n"
|
||||
" int paletteEntry = renderTile(tile, map >> 12, coord & 7);\n"
|
||||
" int paletteEntry = renderTile(tile, (map >> 12) & 15, coord & 7);\n"
|
||||
" color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n"
|
||||
"}";
|
||||
"}\n";
|
||||
|
||||
static const char* const _fetchTileOverflow =
|
||||
"int fetchTile(ivec2 coord) {\n"
|
||||
|
@ -192,6 +204,7 @@ static const struct GBAVideoGLUniform _uniformsMode2[] = {
|
|||
{ "vram", GBA_GL_BG_VRAM, },
|
||||
{ "palette", GBA_GL_BG_PALETTE, },
|
||||
{ "screenBase", GBA_GL_BG_SCREENBASE, },
|
||||
{ "oldCharBase", GBA_GL_BG_OLDCHARBASE, },
|
||||
{ "charBase", GBA_GL_BG_CHARBASE, },
|
||||
{ "size", GBA_GL_BG_SIZE, },
|
||||
{ "offset", GBA_GL_BG_OFFSET, },
|
||||
|
@ -228,6 +241,7 @@ static const char* const _renderMode2 =
|
|||
"uniform isampler2D vram;\n"
|
||||
"uniform sampler2D palette;\n"
|
||||
"uniform int screenBase;\n"
|
||||
"uniform ivec2 oldCharBase;\n"
|
||||
"uniform int charBase;\n"
|
||||
"uniform int size;\n"
|
||||
"uniform ivec4 transform[160];\n"
|
||||
|
@ -244,7 +258,17 @@ static const char* const _renderMode2 =
|
|||
" int mapAddress = screenBase + (map >> 1);\n"
|
||||
" int twomaps = texelFetch(vram, ivec2(mapAddress & 255, mapAddress >> 8), 0).r;\n"
|
||||
" int tile = (twomaps >> (8 * (map & 1))) & 255;\n"
|
||||
" int address = charBase + tile * 32 + ((coord.x >> 9) & 3) + ((coord.y >> 6) & 0x1C);\n"
|
||||
" int newCharBase = charBase;\n"
|
||||
" if (newCharBase != oldCharBase.x) {\n"
|
||||
" int y = int(texCoord.y);\n"
|
||||
// If the charbase has changed (and the scale is greater than 1), we might still be drawing
|
||||
// the tile associated with the pixel above us. If we're still on that tile, we want to use
|
||||
// the charbase associated with it instead of the new one. Cf. https://mgba.io/i/1631
|
||||
" if (y == oldCharBase.y && transform[y - 1].w >> 11 == coord.y >> 11) {\n"
|
||||
" newCharBase = oldCharBase.x;\n"
|
||||
" }\n"
|
||||
" }\n"
|
||||
" int address = newCharBase + tile * 32 + ((coord.x >> 9) & 3) + ((coord.y >> 6) & 0x1C);\n"
|
||||
" int halfrow = texelFetch(vram, ivec2(address & 255, address >> 8), 0).r;\n"
|
||||
" int entry = (halfrow >> (8 * ((coord.x >> 8) & 1))) & 255;\n"
|
||||
" if (entry == 0) {\n"
|
||||
|
@ -415,6 +439,7 @@ static const struct GBAVideoGLUniform _uniformsObj[] = {
|
|||
{ "objwin", GBA_GL_OBJ_OBJWIN, },
|
||||
{ "mosaic", GBA_GL_OBJ_MOSAIC, },
|
||||
{ "cyclesRemaining", GBA_GL_OBJ_CYCLES, },
|
||||
{ "tile", GBA_GL_OBJ_TILE, },
|
||||
{ 0 }
|
||||
};
|
||||
|
||||
|
@ -424,6 +449,7 @@ static const char* const _renderObj =
|
|||
"uniform isampler2D vram;\n"
|
||||
"uniform sampler2D palette;\n"
|
||||
"uniform int charBase;\n"
|
||||
"uniform int tile;\n"
|
||||
"uniform int stride;\n"
|
||||
"uniform int localPalette;\n"
|
||||
"uniform ivec4 inflags;\n"
|
||||
|
@ -437,6 +463,8 @@ static const char* const _renderObj =
|
|||
"OUT(2) out ivec4 window;\n"
|
||||
|
||||
"int renderTile(int tile, int paletteId, ivec2 localCoord);\n"
|
||||
"int mask(int);\n"
|
||||
"#define VRAM_MASK & 191\n"
|
||||
|
||||
"void main() {\n"
|
||||
" vec2 incoord = texCoord;\n"
|
||||
|
@ -461,12 +489,12 @@ static const char* const _renderObj =
|
|||
" if ((coord & ~(dims.xy - 1)) != ivec2(0, 0)) {\n"
|
||||
" discard;\n"
|
||||
" }\n"
|
||||
" int paletteEntry = renderTile((coord.x >> 3) + (coord.y >> 3) * stride, localPalette, coord & 7);\n"
|
||||
" int paletteEntry = renderTile(mask((coord.x >> 3) + tile) + (coord.y >> 3) * stride, localPalette, coord & 7);\n"
|
||||
" color = texelFetch(palette, ivec2(paletteEntry + 256, int(texCoord.y) + mosaic.w), 0);\n"
|
||||
" flags = inflags;\n"
|
||||
" gl_FragDepth = float(flags.x) / 16.;\n"
|
||||
" window = ivec4(objwin, 0);\n"
|
||||
"}";
|
||||
"}\n";
|
||||
|
||||
static const struct GBAVideoGLUniform _uniformsObjPriority[] = {
|
||||
{ "loc", GBA_GL_VS_LOC, },
|
||||
|
@ -996,42 +1024,34 @@ uint16_t GBAVideoGLRendererWriteVideoRegister(struct GBAVideoRenderer* renderer,
|
|||
case REG_BG0HOFS:
|
||||
value &= 0x01FF;
|
||||
glRenderer->bg[0].x = value;
|
||||
dirty = false;
|
||||
break;
|
||||
case REG_BG0VOFS:
|
||||
value &= 0x01FF;
|
||||
glRenderer->bg[0].y = value;
|
||||
dirty = false;
|
||||
break;
|
||||
case REG_BG1HOFS:
|
||||
value &= 0x01FF;
|
||||
glRenderer->bg[1].x = value;
|
||||
dirty = false;
|
||||
break;
|
||||
case REG_BG1VOFS:
|
||||
value &= 0x01FF;
|
||||
glRenderer->bg[1].y = value;
|
||||
dirty = false;
|
||||
break;
|
||||
case REG_BG2HOFS:
|
||||
value &= 0x01FF;
|
||||
glRenderer->bg[2].x = value;
|
||||
dirty = false;
|
||||
break;
|
||||
case REG_BG2VOFS:
|
||||
value &= 0x01FF;
|
||||
glRenderer->bg[2].y = value;
|
||||
dirty = false;
|
||||
break;
|
||||
case REG_BG3HOFS:
|
||||
value &= 0x01FF;
|
||||
glRenderer->bg[3].x = value;
|
||||
dirty = false;
|
||||
break;
|
||||
case REG_BG3VOFS:
|
||||
value &= 0x01FF;
|
||||
glRenderer->bg[3].y = value;
|
||||
dirty = false;
|
||||
break;
|
||||
case REG_BG2PA:
|
||||
glRenderer->bg[2].affine.dx = value;
|
||||
|
@ -1330,6 +1350,7 @@ void GBAVideoGLRendererDrawScanline(struct GBAVideoRenderer* renderer, int y) {
|
|||
if (_needsVramUpload(glRenderer, y) || glRenderer->oamDirty || glRenderer->regsDirty) {
|
||||
if (glRenderer->firstY >= 0) {
|
||||
_drawScanlines(glRenderer, y - 1);
|
||||
glRenderer->firstY = y;
|
||||
glBindVertexArray(0);
|
||||
}
|
||||
}
|
||||
|
@ -1604,6 +1625,7 @@ static void GBAVideoGLRendererUpdateDISPCNT(struct GBAVideoGLRenderer* renderer)
|
|||
|
||||
static void GBAVideoGLRendererWriteBGCNT(struct GBAVideoGLBackground* bg, uint16_t value) {
|
||||
bg->priority = GBARegisterBGCNTGetPriority(value);
|
||||
bg->oldCharBase = bg->charBase;
|
||||
bg->charBase = GBARegisterBGCNTGetCharBase(value) << 13;
|
||||
bg->mosaic = GBARegisterBGCNTGetMosaic(value);
|
||||
bg->multipalette = GBARegisterBGCNTGet256Color(value);
|
||||
|
@ -1709,6 +1731,15 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB
|
|||
|
||||
int align = GBAObjAttributesAIs256Color(sprite->a) && !GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt);
|
||||
unsigned charBase = (BASE_TILE >> 1) + (GBAObjAttributesCGetTile(sprite->c) & ~align) * 0x10;
|
||||
unsigned tile = 0;
|
||||
if (!GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt)) {
|
||||
if (GBAObjAttributesAIs256Color(sprite->a)) {
|
||||
tile = (charBase >> 5) & 0xF;
|
||||
} else {
|
||||
tile = (charBase >> 4) & 0x1F;
|
||||
}
|
||||
charBase &= ~0x1FF;
|
||||
}
|
||||
int stride = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? (width >> 3) : (0x20 >> GBAObjAttributesAGet256Color(sprite->a));
|
||||
|
||||
int totalWidth = width;
|
||||
|
@ -1744,6 +1775,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB
|
|||
glUniform1i(uniforms[GBA_GL_OBJ_VRAM], 0);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_PALETTE], 1);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_CHARBASE], charBase);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_TILE], tile);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_STRIDE], stride);
|
||||
glUniform1i(uniforms[GBA_GL_OBJ_LOCALPALETTE], GBAObjAttributesCGetPalette(sprite->c));
|
||||
glUniform4i(uniforms[GBA_GL_OBJ_INFLAGS], GBAObjAttributesCGetPriority(sprite->c),
|
||||
|
@ -1770,9 +1802,9 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB
|
|||
glUniformMatrix2fv(uniforms[GBA_GL_OBJ_TRANSFORM], 1, GL_FALSE, (GLfloat[]) { flipX, 0, 0, flipY });
|
||||
}
|
||||
glUniform4i(uniforms[GBA_GL_OBJ_DIMS], width, height, totalWidth, totalHeight);
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_OBJWIN) {
|
||||
// OBJWIN writes do not affect pixel priority
|
||||
glDisable(GL_STENCIL_TEST);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_FALSE);
|
||||
glStencilMask(0);
|
||||
|
@ -1780,6 +1812,7 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB
|
|||
glUniform3i(uniforms[GBA_GL_OBJ_OBJWIN], window, renderer->bldb, renderer->bldy);
|
||||
glDrawBuffers(3, (GLenum[]) { GL_NONE, GL_NONE, GL_COLOR_ATTACHMENT2 });
|
||||
} else {
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glDepthMask(GL_TRUE);
|
||||
glStencilMask(1);
|
||||
|
@ -1804,7 +1837,6 @@ void GBAVideoGLRendererDrawSprite(struct GBAVideoGLRenderer* renderer, struct GB
|
|||
// Update the pixel priority for already-written pixels
|
||||
shader = &renderer->objShader[2];
|
||||
uniforms = shader->uniforms;
|
||||
glEnable(GL_STENCIL_TEST);
|
||||
glStencilFunc(GL_EQUAL, 1, 1);
|
||||
glUseProgram(shader->program);
|
||||
glDrawBuffers(2, (GLenum[]) { GL_NONE, GL_COLOR_ATTACHMENT1 });
|
||||
|
@ -1872,10 +1904,12 @@ void GBAVideoGLRendererDrawBackgroundMode2(struct GBAVideoGLRenderer* renderer,
|
|||
glBindVertexArray(shader->vao);
|
||||
_prepareTransform(renderer, background, uniforms, y);
|
||||
glUniform1i(uniforms[GBA_GL_BG_SCREENBASE], background->screenBase);
|
||||
glUniform2i(uniforms[GBA_GL_BG_OLDCHARBASE], background->oldCharBase, renderer->firstY);
|
||||
glUniform1i(uniforms[GBA_GL_BG_CHARBASE], background->charBase);
|
||||
glUniform1i(uniforms[GBA_GL_BG_SIZE], background->size);
|
||||
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
|
||||
glDrawBuffers(1, (GLenum[]) { GL_COLOR_ATTACHMENT0 });
|
||||
background->oldCharBase = background->charBase;
|
||||
}
|
||||
|
||||
void GBAVideoGLRendererDrawBackgroundMode3(struct GBAVideoGLRenderer* renderer, struct GBAVideoGLBackground* background, int y) {
|
||||
|
|
|
@ -71,10 +71,10 @@
|
|||
}
|
||||
|
||||
#define SPRITE_XBASE_16(localX) unsigned xBase = (localX & ~0x7) * 4 + ((localX >> 1) & 2);
|
||||
#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 4;
|
||||
#define SPRITE_YBASE_16(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 4 + maskHi;
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_16_NORMAL(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
LOAD_16(tileData, (yBase + ((xBase + charBase) & maskLo)) & 0x7FFE, vramBase); \
|
||||
tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
|
||||
current = renderer->spriteLayer[outX]; \
|
||||
if ((current & FLAG_ORDER_MASK) > flags) { \
|
||||
|
@ -86,7 +86,7 @@
|
|||
}
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_16_NORMAL_OBJWIN(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
LOAD_16(tileData, (yBase + ((xBase + charBase) & maskLo)) & 0x7FFE, vramBase); \
|
||||
tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
|
||||
current = renderer->spriteLayer[outX]; \
|
||||
if ((current & FLAG_ORDER_MASK) > flags) { \
|
||||
|
@ -99,17 +99,17 @@
|
|||
}
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_16_OBJWIN(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
LOAD_16(tileData, (yBase + ((xBase + charBase) & maskLo)) & 0x7FFE, vramBase); \
|
||||
tileData = (tileData >> ((localX & 3) << 2)) & 0xF; \
|
||||
if (tileData) { \
|
||||
renderer->row[outX] |= FLAG_OBJWIN; \
|
||||
}
|
||||
|
||||
#define SPRITE_XBASE_256(localX) unsigned xBase = (localX & ~0x7) * 8 + (localX & 6);
|
||||
#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 8;
|
||||
#define SPRITE_YBASE_256(localY) unsigned yBase = (localY & ~0x7) * stride + (localY & 0x7) * 8 + maskHi;
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_256_NORMAL(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
LOAD_16(tileData, (yBase + ((xBase + charBase) & maskLo)) & 0x7FFE, vramBase); \
|
||||
tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
|
||||
current = renderer->spriteLayer[outX]; \
|
||||
if ((current & FLAG_ORDER_MASK) > flags) { \
|
||||
|
@ -121,7 +121,7 @@
|
|||
}
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_256_NORMAL_OBJWIN(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
LOAD_16(tileData, (yBase + ((xBase + charBase) & maskLo)) & 0x7FFE, vramBase); \
|
||||
tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
|
||||
current = renderer->spriteLayer[outX]; \
|
||||
if ((current & FLAG_ORDER_MASK) > flags) { \
|
||||
|
@ -134,7 +134,7 @@
|
|||
}
|
||||
|
||||
#define SPRITE_DRAW_PIXEL_256_OBJWIN(localX) \
|
||||
LOAD_16(tileData, ((yBase + charBase + xBase) & 0x7FFE), vramBase); \
|
||||
LOAD_16(tileData, (yBase + ((xBase + charBase) & maskLo)) & 0x7FFE, vramBase); \
|
||||
tileData = (tileData >> ((localX & 1) << 3)) & 0xFF; \
|
||||
if (tileData) { \
|
||||
renderer->row[outX] |= FLAG_OBJWIN; \
|
||||
|
@ -157,6 +157,8 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
|||
uint16_t* vramBase = &renderer->d.vram[BASE_TILE >> 1];
|
||||
unsigned align = GBAObjAttributesAIs256Color(sprite->a) && !GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt);
|
||||
unsigned charBase = (GBAObjAttributesCGetTile(sprite->c) & ~align) * 0x20;
|
||||
unsigned maskLo = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? 0x7FFE : 0x3FE;
|
||||
unsigned maskHi = GBARegisterDISPCNTIsObjCharacterMapping(renderer->dispcnt) ? 0 : charBase & 0x7C00;
|
||||
if (GBARegisterDISPCNTGetMode(renderer->dispcnt) >= 3 && GBAObjAttributesCGetTile(sprite->c) < 512) {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -141,30 +141,11 @@ static void GBAVideoSoftwareRendererReset(struct GBAVideoRenderer* renderer) {
|
|||
|
||||
for (i = 0; i < 4; ++i) {
|
||||
struct GBAVideoSoftwareBackground* bg = &softwareRenderer->bg[i];
|
||||
memset(bg, 0, sizeof(*bg));
|
||||
bg->index = i;
|
||||
bg->enabled = 0;
|
||||
bg->priority = 0;
|
||||
bg->charBase = 0;
|
||||
bg->mosaic = 0;
|
||||
bg->multipalette = 0;
|
||||
bg->screenBase = 0;
|
||||
bg->overflow = 0;
|
||||
bg->size = 0;
|
||||
bg->target1 = 0;
|
||||
bg->target2 = 0;
|
||||
bg->x = 0;
|
||||
bg->y = 0;
|
||||
bg->refx = 0;
|
||||
bg->refy = 0;
|
||||
bg->dx = 256;
|
||||
bg->dmx = 0;
|
||||
bg->dy = 0;
|
||||
bg->dmy = 256;
|
||||
bg->sx = 0;
|
||||
bg->sy = 0;
|
||||
bg->yCache = -1;
|
||||
bg->offsetX = 0;
|
||||
bg->offsetY = 0;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <fcntl.h>
|
||||
|
||||
MGBA_EXPORT const uint32_t GBASavestateMagic = 0x01000000;
|
||||
MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000004;
|
||||
MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000007;
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize");
|
||||
|
||||
|
|
|
@ -377,7 +377,12 @@ void GBAVideoDeserialize(struct GBAVideo* video, const struct GBASerializedState
|
|||
break;
|
||||
}
|
||||
uint32_t when;
|
||||
LOAD_32(when, 0, &state->video.nextEvent);
|
||||
if (state->versionMagic < 0x01000007) {
|
||||
// This field was moved in v7
|
||||
LOAD_32(when, 0, &state->audio.lastSample);
|
||||
} else {
|
||||
LOAD_32(when, 0, &state->video.nextEvent);
|
||||
}
|
||||
mTimingSchedule(&video->p->timing, &video->event, when);
|
||||
|
||||
LOAD_16(video->vcount, REG_VCOUNT, state->io);
|
||||
|
|
|
@ -1,4 +1,34 @@
|
|||
include(FindPkgConfig)
|
||||
macro(_export SUFFIX)
|
||||
if(DEFINED ${REQUIRE}_${SUFFIX})
|
||||
set(${UREQUIRE}_${SUFFIX} ${${REQUIRE}_${SUFFIX}} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_${SUFFIX})
|
||||
set(${UREQUIRE}_${SUFFIX} ${${UREQUIRE}_${SUFFIX}} PARENT_SCOPE)
|
||||
endif()
|
||||
endmacro()
|
||||
|
||||
macro(_exportLibraries SUFFIX)
|
||||
set(IS_FRAMEWORK OFF)
|
||||
set(LIBS)
|
||||
if(DEFINED ${REQUIRE}_${SUFFIX})
|
||||
set(PREFIX ${REQUIRE})
|
||||
elseif(DEFINED ${UREQUIRE}_${SUFFIX})
|
||||
set(PREFIX ${UREQUIRE})
|
||||
endif()
|
||||
foreach(LIB IN LISTS ${PREFIX}_${SUFFIX})
|
||||
if(LIB STREQUAL "-framework")
|
||||
set(IS_FRAMEWORK ON)
|
||||
elseif(IS_FRAMEWORK)
|
||||
list(APPEND LIBS "-framework ${LIB}")
|
||||
set(IS_FRAMEWORK OFF)
|
||||
else()
|
||||
list(APPEND LIBS ${LIB})
|
||||
endif()
|
||||
endforeach()
|
||||
unset(PREFIX)
|
||||
set(${UREQUIRE}_${SUFFIX} ${LIBS} PARENT_SCOPE)
|
||||
endmacro()
|
||||
|
||||
function(find_feature FEATURE_NAME FEATURE_REQUIRES)
|
||||
if (NOT ${FEATURE_NAME})
|
||||
return()
|
||||
|
@ -28,60 +58,22 @@ function(find_feature FEATURE_NAME FEATURE_REQUIRES)
|
|||
endif()
|
||||
if(${REQUIRE}_FOUND)
|
||||
string(TOUPPER ${REQUIRE} UREQUIRE)
|
||||
if(DEFINED ${REQUIRE}_CFLAGS_OTHER)
|
||||
set(${UREQUIRE}_CFLAGS_OTHER ${${REQUIRE}_CFLAGS_OTHER} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_CFLAGS_OTHER)
|
||||
set(${UREQUIRE}_CFLAGS_OTHER ${${UREQUIRE}_CFLAGS_OTHER} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_FOUND)
|
||||
set(${UREQUIRE}_FOUND ${${REQUIRE}_FOUND} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_FOUND)
|
||||
set(${UREQUIRE}_FOUND ${${UREQUIRE}_FOUND} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_INCLUDE_DIRS)
|
||||
set(${UREQUIRE}_INCLUDE_DIRS ${${REQUIRE}_INCLUDE_DIRS} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_INCLUDE_DIRS)
|
||||
set(${UREQUIRE}_INCLUDE_DIRS ${${UREQUIRE}_INCLUDE_DIRS} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_INCLUDE_DIR)
|
||||
set(${UREQUIRE}_INCLUDE_DIR ${${REQUIRE}_INCLUDE_DIR} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_INCLUDE_DIR)
|
||||
set(${UREQUIRE}_INCLUDE_DIR ${${UREQUIRE}_INCLUDE_DIR} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_VERSION_STRING)
|
||||
set(${UREQUIRE}_VERSION_STRING ${${REQUIRE}_VERSION_STRING} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_VERSION_STRING)
|
||||
set(${UREQUIRE}_VERSION_STRING ${${UREQUIRE}_VERSION_STRING} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_VERSION_MAJOR)
|
||||
set(${UREQUIRE}_VERSION_MAJOR ${${REQUIRE}_VERSION_MAJOR} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_VERSION_MAJOR)
|
||||
set(${UREQUIRE}_VERSION_MAJOR ${${UREQUIRE}_VERSION_MAJOR} PARENT_SCOPE)
|
||||
endif()
|
||||
if(DEFINED ${REQUIRE}_VERSION_MINOR)
|
||||
set(${UREQUIRE}_VERSION_MINOR ${${REQUIRE}_VERSION_MINOR} PARENT_SCOPE)
|
||||
elseif(DEFINED ${UREQUIRE}_VERSION_MINOR)
|
||||
set(${UREQUIRE}_VERSION_MINOR ${${UREQUIRE}_VERSION_MINOR} PARENT_SCOPE)
|
||||
endif()
|
||||
_export(CFLAGS_OTHER)
|
||||
_export(FOUND)
|
||||
_export(INCLUDE_DIRS)
|
||||
_export(INCLUDE_DIR)
|
||||
_export(VERSION_STRING)
|
||||
_export(VERSION_MAJOR)
|
||||
_export(VERSION_MINOR)
|
||||
if (APPLE)
|
||||
set(IS_FRAMEWORK OFF)
|
||||
set(LIBS)
|
||||
foreach(LIB IN LISTS ${REQUIRE}_LIBRARIES)
|
||||
if(LIB STREQUAL "-framework")
|
||||
set(IS_FRAMEWORK ON)
|
||||
elseif(IS_FRAMEWORK)
|
||||
list(APPEND LIBS "-framework ${LIB}")
|
||||
set(IS_FRAMEWORK OFF)
|
||||
else()
|
||||
list(APPEND LIBS ${LIB})
|
||||
endif()
|
||||
endforeach()
|
||||
set(${UREQUIRE}_LIBRARIES ${LIBS} PARENT_SCOPE)
|
||||
_exportLibraries(LIBRARIES)
|
||||
_exportLibraries(STATIC_LIBRARIES)
|
||||
else()
|
||||
set(${UREQUIRE}_LIBRARIES ${${REQUIRE}_LIBRARIES} PARENT_SCOPE)
|
||||
_export(LIBRARIES)
|
||||
_export(STATIC_LIBRARIES)
|
||||
endif()
|
||||
set(${UREQUIRE}_LIBRARY_DIRS ${${REQUIRE}_LIBRARY_DIRS} PARENT_SCOPE)
|
||||
set(${UREQUIRE}_LDFLAGS_OTHER ${${REQUIRE}_LDFLAGS_OTHER} PARENT_SCOPE)
|
||||
_export(LIBRARY_DIRS)
|
||||
_export(LDFLAGS_OTHER)
|
||||
set(FOUND ON)
|
||||
break()
|
||||
endif()
|
||||
|
|
|
@ -88,7 +88,7 @@ static vita2d_texture* backdrop = 0;
|
|||
#define PSP2_AUDIO_BUFFER_SIZE (PSP2_SAMPLES * 16)
|
||||
|
||||
static struct mPSP2AudioContext {
|
||||
struct GBAStereoSample buffer[PSP2_AUDIO_BUFFER_SIZE];
|
||||
struct mStereoSample buffer[PSP2_AUDIO_BUFFER_SIZE];
|
||||
size_t writeOffset;
|
||||
size_t readOffset;
|
||||
size_t samples;
|
||||
|
@ -255,7 +255,7 @@ static void _postAudioBuffer(struct mAVStream* stream, blip_t* left, blip_t* rig
|
|||
}
|
||||
ConditionWait(&audioContext.cond, &audioContext.mutex);
|
||||
}
|
||||
struct GBAStereoSample* samples = &audioContext.buffer[audioContext.writeOffset];
|
||||
struct mStereoSample* samples = &audioContext.buffer[audioContext.writeOffset];
|
||||
blip_read_samples(left, &samples[0].left, PSP2_SAMPLES, true);
|
||||
blip_read_samples(right, &samples[0].right, PSP2_SAMPLES, true);
|
||||
audioContext.samples += PSP2_SAMPLES;
|
||||
|
|
|
@ -24,13 +24,23 @@ ApplicationUpdatePrompt::ApplicationUpdatePrompt(QWidget* parent)
|
|||
ApplicationUpdater* updater = GBAApp::app()->updater();
|
||||
ApplicationUpdater::UpdateInfo info = updater->updateInfo();
|
||||
QString updateText(tr("An update to %1 is available.\n").arg(QLatin1String(projectName)));
|
||||
bool available;
|
||||
#if defined(Q_OS_WIN) || defined(Q_OS_MAC)
|
||||
updateText += tr("\nDo you want to download and install it now? You will need to restart the emulator when the download is complete.");
|
||||
m_okDownload = connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &ApplicationUpdatePrompt::startUpdate);
|
||||
available = true;
|
||||
#elif defined(Q_OS_LINUX)
|
||||
QString path = QCoreApplication::applicationDirPath();
|
||||
QFileInfo finfo(path + "/../../AppRun");
|
||||
available = finfo.exists() && finfo.isExecutable();
|
||||
#else
|
||||
updateText += tr("\nAuto-update is not available on this platform. If you wish to update you will need to do it manually.");
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &QWidget::close);
|
||||
available = false;
|
||||
#endif
|
||||
if (available) {
|
||||
updateText += tr("\nDo you want to download and install it now? You will need to restart the emulator when the download is complete.");
|
||||
m_okDownload = connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &ApplicationUpdatePrompt::startUpdate);
|
||||
} else {
|
||||
updateText += tr("\nAuto-update is not available on this platform. If you wish to update you will need to do it manually.");
|
||||
connect(m_ui.buttonBox, &QDialogButtonBox::accepted, this, &QWidget::close);
|
||||
}
|
||||
m_ui.text->setText(updateText);
|
||||
m_ui.details->setText(tr("Current version: %1\nNew version: %2\nDownload size: %3")
|
||||
.arg(QLatin1String(projectVersion))
|
||||
|
|
|
@ -150,7 +150,15 @@ const char* ApplicationUpdater::platform() {
|
|||
return uninstallInfo.exists() ? "win32-installer" : "win32";
|
||||
#endif
|
||||
#elif defined(Q_OS_MACOS)
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 15, 0))
|
||||
// Modern macOS build
|
||||
return "macos";
|
||||
#else
|
||||
// Legacy "OS X" build
|
||||
return "osx";
|
||||
#endif
|
||||
#elif defined(Q_OS_LINUX) && defined(__x86_64__)
|
||||
return "appimage-x64";
|
||||
#else
|
||||
// Return one that will be up to date, but we can't download
|
||||
return "win64";
|
||||
|
|
|
@ -45,17 +45,17 @@ qint64 AudioDevice::readData(char* data, qint64 maxSize) {
|
|||
return 0;
|
||||
}
|
||||
|
||||
maxSize /= sizeof(GBAStereoSample);
|
||||
maxSize /= sizeof(mStereoSample);
|
||||
mCoreSyncLockAudio(&m_context->impl->sync);
|
||||
int available = std::min<qint64>({
|
||||
blip_samples_avail(m_context->core->getAudioChannel(m_context->core, 0)),
|
||||
maxSize,
|
||||
std::numeric_limits<int>::max()
|
||||
});
|
||||
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 0), &reinterpret_cast<GBAStereoSample*>(data)->left, available, true);
|
||||
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 1), &reinterpret_cast<GBAStereoSample*>(data)->right, available, true);
|
||||
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 0), &reinterpret_cast<mStereoSample*>(data)->left, available, true);
|
||||
blip_read_samples(m_context->core->getAudioChannel(m_context->core, 1), &reinterpret_cast<mStereoSample*>(data)->right, available, true);
|
||||
mCoreSyncConsumeAudio(&m_context->impl->sync);
|
||||
return available * sizeof(GBAStereoSample);
|
||||
return available * sizeof(mStereoSample);
|
||||
}
|
||||
|
||||
qint64 AudioDevice::writeData(const char*, qint64) {
|
||||
|
|
|
@ -250,12 +250,13 @@ endif()
|
|||
|
||||
if(ENABLE_SCRIPTING)
|
||||
list(APPEND SOURCE_FILES
|
||||
ScriptingController.cpp
|
||||
ScriptingTextBuffer.cpp
|
||||
ScriptingView.cpp)
|
||||
scripting/ScriptingController.cpp
|
||||
scripting/ScriptingTextBuffer.cpp
|
||||
scripting/ScriptingTextBufferModel.cpp
|
||||
scripting/ScriptingView.cpp)
|
||||
|
||||
list(APPEND UI_FILES
|
||||
ScriptingView.ui)
|
||||
scripting/ScriptingView.ui)
|
||||
endif()
|
||||
|
||||
if(TARGET Qt6::Core)
|
||||
|
@ -265,7 +266,14 @@ else()
|
|||
endif()
|
||||
|
||||
if(BUILD_UPDATER)
|
||||
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc INPUT ${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in)
|
||||
if(DEFINED CMAKE_CONFIGURATION_TYPES)
|
||||
# Required for e.g. MSVC
|
||||
file(GENERATE OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc INPUT ${CMAKE_CURRENT_SOURCE_DIR}/updater.qrc.in)
|
||||
else()
|
||||
# Required for qt_add_resources to manage dependencies properly
|
||||
# TODO: Figure out how to do this with MSVC too
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/updater-config.qrc.in ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc)
|
||||
endif()
|
||||
if(TARGET Qt6::Core)
|
||||
qt_add_resources(UPDATER_RESOURCES ${CMAKE_CURRENT_BINARY_DIR}/updater.qrc)
|
||||
else()
|
||||
|
@ -304,7 +312,6 @@ if(BUILD_GL OR BUILD_GLES2 OR BUILD_EPOXY)
|
|||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/shaders DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
|
||||
endif()
|
||||
if(ENABLE_SCRIPTING AND USE_LUA)
|
||||
message(STATUS ${CMAKE_SOURCE_DIR}/res/scripts)
|
||||
install(DIRECTORY ${CMAKE_SOURCE_DIR}/res/scripts DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
|
||||
endif()
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/res/nointro.dat DESTINATION ${DATADIR} COMPONENT ${BINARY_NAME}-qt)
|
||||
|
@ -395,7 +402,10 @@ if(QT_STATIC)
|
|||
find_package(Cups)
|
||||
find_package(${QT}PrintSupport)
|
||||
list(APPEND QT_LIBRARIES Cups ${QT}::PrintSupport ${QT}::QCocoaIntegrationPlugin ${QT}::CoreAudioPlugin ${QT}::AVFServicePlugin ${QT}::QCocoaPrinterSupportPlugin)
|
||||
list(APPEND QT_LIBRARIES ${QT}AccessibilitySupport ${QT}CglSupport ${QT}ClipboardSupport ${QT}FontDatabaseSupport ${QT}GraphicsSupport ${QT}ThemeSupport)
|
||||
list(APPEND QT_LIBRARIES ${QT}AccessibilitySupport ${QT}ClipboardSupport ${QT}FontDatabaseSupport ${QT}GraphicsSupport ${QT}ThemeSupport)
|
||||
if(CMAKE_SYSTEM_VERSION VERSION_LESS "19.0")
|
||||
list(APPEND QT_LIBRARIES ${QT}CglSupport)
|
||||
endif()
|
||||
list(APPEND QT_LIBRARIES "-framework AVFoundation" "-framework CoreMedia" "-framework SystemConfiguration" "-framework Security")
|
||||
set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE}")
|
||||
elseif(UNIX)
|
||||
|
|
|
@ -136,7 +136,7 @@ ConfigController::ConfigController(QObject* parent)
|
|||
|
||||
m_subparsers[1].usage = "Frontend options:\n"
|
||||
" --ecard FILE Scan an e-Reader card in the first loaded game\n"
|
||||
" Can be paassed multiple times for multiple cards\n"
|
||||
" Can be passed multiple times for multiple cards\n"
|
||||
" --mb FILE Boot a multiboot image with FILE inserted into the ROM slot";
|
||||
m_subparsers[1].parse = nullptr;
|
||||
m_subparsers[1].parseLong = [](struct mSubParser* parser, const char* option, const char* arg) {
|
||||
|
|
|
@ -287,6 +287,7 @@ void CoreController::loadConfig(ConfigController* config) {
|
|||
m_fastForwardMute = config->getOption("fastForwardMute", -1).toInt();
|
||||
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "volume");
|
||||
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
|
||||
m_preload = config->getOption("preload").toInt();
|
||||
|
||||
int playerId = m_multiplayer->playerId(this) + 1;
|
||||
QVariant savePlayerId = config->getOption("savePlayerId");
|
||||
|
@ -829,7 +830,11 @@ void CoreController::replaceGame(const QString& path) {
|
|||
QString fname = info.canonicalFilePath();
|
||||
Interrupter interrupter(this);
|
||||
mDirectorySetDetachBase(&m_threadContext.core->dirs);
|
||||
mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData());
|
||||
if (m_preload) {
|
||||
mCorePreloadFile(m_threadContext.core, fname.toUtf8().constData());
|
||||
} else {
|
||||
mCoreLoadFile(m_threadContext.core, fname.toUtf8().constData());
|
||||
}
|
||||
updateROMInfo();
|
||||
}
|
||||
|
||||
|
@ -893,6 +898,11 @@ void CoreController::setFakeEpoch(const QDateTime& time) {
|
|||
m_threadContext.core->rtc.value = time.toMSecsSinceEpoch();
|
||||
}
|
||||
|
||||
void CoreController::setTimeOffset(qint64 offset) {
|
||||
m_threadContext.core->rtc.override = RTC_WALLCLOCK_OFFSET;
|
||||
m_threadContext.core->rtc.value = offset * 1000LL;
|
||||
}
|
||||
|
||||
void CoreController::scanCard(const QString& path) {
|
||||
#ifdef M_CORE_GBA
|
||||
QImage image(path);
|
||||
|
|
|
@ -174,6 +174,7 @@ public slots:
|
|||
void setRealTime();
|
||||
void setFixedTime(const QDateTime& time);
|
||||
void setFakeEpoch(const QDateTime& time);
|
||||
void setTimeOffset(qint64 offset);
|
||||
|
||||
void importSharkport(const QString& path);
|
||||
void exportSharkport(const QString& path);
|
||||
|
@ -240,6 +241,7 @@ private:
|
|||
mCoreThread m_threadContext{};
|
||||
|
||||
bool m_patched = false;
|
||||
bool m_preload = false;
|
||||
|
||||
uint32_t m_crc32;
|
||||
QString m_internalTitle;
|
||||
|
|
|
@ -62,13 +62,16 @@ CoreController* CoreManager::loadGame(const QString& path) {
|
|||
VFile* vfOriginal = VDirFindFirst(archive, [](VFile* vf) {
|
||||
return mCoreIsCompatible(vf) != mPLATFORM_NONE;
|
||||
});
|
||||
ssize_t size;
|
||||
if (vfOriginal && (size = vfOriginal->size(vfOriginal)) > 0) {
|
||||
void* mem = vfOriginal->map(vfOriginal, size, MAP_READ);
|
||||
vf = VFileMemChunk(mem, size);
|
||||
vfOriginal->unmap(vfOriginal, mem, size);
|
||||
if (vfOriginal) {
|
||||
ssize_t size = vfOriginal->size(vfOriginal);
|
||||
if (size > 0) {
|
||||
void* mem = vfOriginal->map(vfOriginal, size, MAP_READ);
|
||||
vf = VFileMemChunk(mem, size);
|
||||
vfOriginal->unmap(vfOriginal, mem, size);
|
||||
}
|
||||
vfOriginal->close(vfOriginal);
|
||||
}
|
||||
archive->close(archive);
|
||||
}
|
||||
QDir dir(info.dir());
|
||||
if (!vf) {
|
||||
|
|