Merge branch 'master' into translations

This commit is contained in:
Vicki Pfau 2022-07-02 00:00:42 -07:00
commit ab73e0d920
156 changed files with 8538 additions and 5373 deletions

18
CHANGES
View File

@ -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)

View File

@ -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()

View File

@ -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:

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

View File

@ -1,2 +0,0 @@
[testinfo]
fail=1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 870 B

View File

@ -1,2 +0,0 @@
[testinfo]
fail=1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 968 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 628 B

View File

@ -1,2 +0,0 @@
[testinfo]
fail=1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 785 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 788 B

View File

@ -1,2 +0,0 @@
[testinfo]
fail=1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 890 B

View File

@ -1,2 +0,0 @@
[testinfo]
fail=1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 975 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 882 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 726 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 724 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 740 B

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 253 B

View File

@ -0,0 +1,3 @@
[testinfo]
skip=1
frames=1

Binary file not shown.

View File

@ -98,6 +98,7 @@ CXX_GUARD_START
} \
DECLARE_VECTOR(StringList, char*);
DECLARE_VECTOR(IntList, int);
CXX_GUARD_END

View File

@ -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);

View File

@ -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
};

View File

@ -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);

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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];

View File

@ -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);

View File

@ -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,

View File

@ -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);

View File

@ -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,

View File

@ -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];

View File

@ -21,7 +21,7 @@ extern const int GBASIOCyclesPerTransfer[4][MAX_GBAS];
mLOG_DECLARE_CATEGORY(GBA_SIO);
enum {
RCNT_INITIAL = 0x8000
RCNT_INITIAL = -0x8000
};
enum {

View File

@ -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);

View File

@ -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)

View File

@ -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);

BIN
res/gb-icon-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

BIN
res/gb-icon-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

2
res/gb-icon.svg Normal file
View File

@ -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

BIN
res/gba-icon-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

BIN
res/gba-icon-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

2
res/gba-icon.svg Normal file
View File

@ -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

BIN
res/gbc-icon-128.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

BIN
res/gbc-icon-256.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

2
res/gbc-icon.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -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

View File

@ -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);
}
}

View File

@ -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);

View File

@ -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];
}
}

View File

@ -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
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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;

View File

@ -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;
}
}
}

View File

@ -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;
}

View File

@ -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 {

View File

@ -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;
}

View File

@ -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);

View File

@ -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};

View File

@ -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);

View File

@ -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);
}

View File

@ -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;

View File

@ -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

View File

@ -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);

View File

@ -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);

View File

@ -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);

View File

@ -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);
}

View File

@ -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) {

View File

@ -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) {

View File

@ -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) {

View File

@ -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;

View File

@ -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 },

View File

@ -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);

View File

@ -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) {

View File

@ -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;
}

View File

@ -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;
}
}

View File

@ -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");

View File

@ -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);

View File

@ -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()

View File

@ -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;

View File

@ -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))

View File

@ -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";

View File

@ -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) {

View File

@ -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)

View File

@ -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) {

View File

@ -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);

View File

@ -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;

View File

@ -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) {

Some files were not shown because too many files have changed in this diff Show More