mirror of https://github.com/mgba-emu/mgba.git
Merge branch 'master' into translations
This commit is contained in:
commit
097eb338bc
24
CHANGES
24
CHANGES
|
@ -5,10 +5,12 @@ Features:
|
|||
- New option to lock the maximum frame size
|
||||
- Memory access and information logging
|
||||
- 3DS: Add faster "loose" sync mode, default enabled
|
||||
- Vita: Allow using rear touch pads as L2/L3/R2/R3
|
||||
- Scripting: New `input` API for getting raw keyboard/mouse/controller state
|
||||
- Scripting: New `storage` API for saving data for a script, e.g. settings
|
||||
- Scripting: New `image` and `canvas` APIs for drawing images and displaying on-screen
|
||||
- Scripting: Debugger integration to allow for breakpoints and watchpoints
|
||||
- Scripting: Add support for running scripts at startup
|
||||
- New unlicensed GB mappers: NT (older types 1 and 2), Li Cheng, GGB-81, Sintax
|
||||
- Initial support for bootleg GBA multicarts
|
||||
- Debugger: Add range watchpoints
|
||||
|
@ -23,23 +25,22 @@ Emulation fixes:
|
|||
- GBA Video: Disable BG target 1 blending when OBJ blending (fixes mgba.io/i/2722)
|
||||
- GBA Video: Improve emulation of window start/end conditions (fixes mgba.io/i/1945)
|
||||
Other fixes:
|
||||
- ARM Debugger: Fix disassembly of ror r0 barrel shift (fixes mgba.io/i/3412)
|
||||
- Core: Fix inconsistencies with setting game-specific overrides (fixes mgba.io/i/2963)
|
||||
- Debugger: Fix writing to specific segment in command-line debugger
|
||||
- FFmpeg: Fix failing to record videos with CRF video (fixes mgba.io/i/3368)
|
||||
- GB Core: Fix cloning savedata when backing file is outdated (fixes mgba.io/i/3388)
|
||||
- GBA: Fix getting game info for multiboot ROMs
|
||||
- GBA Core: Fix booting into BIOS when skip BIOS is enabled
|
||||
- GBA Hardware: Fix loading states unconditionally overwriting GPIO memory
|
||||
- mGUI: Load parent directory if last used directory is missing (fixes mgba.io/i/3379)
|
||||
- Qt: Fix savestate preview sizes with different scales (fixes mgba.io/i/2560)
|
||||
- Qt: Fix potential crash when configuring shortcuts
|
||||
- Qt: Fix regression where loading BIOS creates a save file (fixes mgba.io/i/3359)
|
||||
- Qt: Fix selecting high tiles in tile and map views (fixes mgba.io/i/3461)
|
||||
Misc:
|
||||
- 3DS: Change title ID to avoid conflict with commercial title (fixes mgba.io/i/3023)
|
||||
- Core: Handle relative paths for saves, screenshots, etc consistently (fixes mgba.io/i/2826)
|
||||
- Core: Improve rumble emulation by averaging state over entire frame (fixes mgba.io/i/3232)
|
||||
- Core: Add MD5 hashing for ROMs
|
||||
- Core: Add support for specifying an arbitrary portable directory
|
||||
- Core: Add SHA1 hashing for ROMs
|
||||
- FFmpeg: Add Ut Video option
|
||||
- GB: Prevent incompatible BIOSes from being used on differing models
|
||||
- GB Serialize: Add missing savestate support for MBC6 and NT (newer)
|
||||
- GBA: Improve detection of valid ELF ROMs
|
||||
|
@ -61,11 +62,24 @@ Misc:
|
|||
- Qt: Support building against Qt 6
|
||||
- Qt: Add shortcuts to increment fast forward speed (mgba.io/i/2903)
|
||||
- Qt: Enable ROM preloading by default
|
||||
- Qt: Throttle fatal error dialogs
|
||||
- Res: Port hq2x and OmniScale shaders from SameBoy
|
||||
- Res: Port NSO-gba-colors shader (closes mgba.io/i/2834)
|
||||
- Res: Update gba-colors shader (closes mgba.io/i/2976)
|
||||
- Scripting: Add `callbacks:oneshot` for single-call callbacks
|
||||
|
||||
0.10.5: (2025-03-08)
|
||||
Other fixes:
|
||||
- ARM Debugger: Fix disassembly of ror r0 barrel shift (fixes mgba.io/i/3412)
|
||||
- FFmpeg: Fix failing to record videos with CRF video (fixes mgba.io/i/3368)
|
||||
- GB Core: Fix cloning savedata when backing file is outdated (fixes mgba.io/i/3388)
|
||||
- GBA Cheats: Let VBA-style codes patch ROM (fixes mgba.io/i/3423)
|
||||
- GBA Core: Fix booting into BIOS when skip BIOS is enabled
|
||||
- GBA Hardware: Fix loading states unconditionally overwriting GPIO memory
|
||||
- Updater: Fix rewriting folders and files on Windows (fixes mgba.io/i/3384)
|
||||
- Wii: Fix crash on loading large ZIP files (fixes mgba.io/i/3404)
|
||||
Misc:
|
||||
- GB: Allow use of CGB-E and AGB-0 BIOS versions (closes mgba.io/i/3427)
|
||||
|
||||
0.10.4: (2024-12-07)
|
||||
Emulation fixes:
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required(VERSION 3.3)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/src/platform/cmake/")
|
||||
|
||||
if(POLICY CMP0025)
|
||||
|
@ -857,7 +857,7 @@ if(ENABLE_SCRIPTING)
|
|||
endif()
|
||||
|
||||
if(ENABLE_VFS)
|
||||
list(APPEND ENABLES VFS)
|
||||
list(APPEND ENABLES VFS DIRECTORIES)
|
||||
endif()
|
||||
|
||||
foreach(FEATURE IN LISTS FEATURES)
|
||||
|
@ -1001,7 +1001,7 @@ endif()
|
|||
|
||||
if(BUILD_LIBRETRO)
|
||||
file(GLOB RETRO_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/libretro/*.c)
|
||||
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC} ${VFS_SRC})
|
||||
add_library(${BINARY_NAME}_libretro SHARED ${CORE_SRC} ${RETRO_SRC} ${CORE_VFS_SRC})
|
||||
add_dependencies(${BINARY_NAME}_libretro ${BINARY_NAME}-version-info)
|
||||
set_target_properties(${BINARY_NAME}_libretro PROPERTIES PREFIX "" COMPILE_DEFINITIONS "__LIBRETRO__;COLOR_16_BIT;COLOR_5_6_5;DISABLE_THREADING;MGBA_STANDALONE;${OS_DEFINES};${FUNCTION_DEFINES};ENABLE_VFS;MINIMAL_CORE=2")
|
||||
target_link_libraries(${BINARY_NAME}_libretro ${OS_LIB})
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/* Copyright (c) 2013-2025 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Based on https://github.com/clibs/sha1
|
||||
*/
|
||||
#ifndef SHA1_H
|
||||
#define SHA1_H
|
||||
|
||||
#include <mgba-util/common.h>
|
||||
|
||||
CXX_GUARD_START
|
||||
|
||||
struct SHA1Context {
|
||||
uint32_t state[5];
|
||||
uint32_t count[2];
|
||||
unsigned char buffer[64];
|
||||
};
|
||||
|
||||
void sha1Init(struct SHA1Context* ctx);
|
||||
void sha1Update(struct SHA1Context* ctx, const void* input, size_t len);
|
||||
void sha1Finalize(uint8_t digest[20], struct SHA1Context* ctx);
|
||||
|
||||
void sha1Buffer(const void* input, size_t len, uint8_t* result);
|
||||
|
||||
struct VFile;
|
||||
bool sha1File(struct VFile* vf, uint8_t* result);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
|
@ -50,7 +50,7 @@ struct VFile {
|
|||
bool (*sync)(struct VFile* vf, void* buffer, size_t size);
|
||||
};
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
struct VDirEntry {
|
||||
const char* (*name)(struct VDirEntry* vde);
|
||||
enum VFSType (*type)(struct VDirEntry* vde);
|
||||
|
@ -64,7 +64,9 @@ struct VDir {
|
|||
struct VDir* (*openDir)(struct VDir* vd, const char* name);
|
||||
bool (*deleteFile)(struct VDir* vd, const char* name);
|
||||
};
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
struct VFile* VFileOpen(const char* path, int flags);
|
||||
#endif
|
||||
|
||||
|
@ -85,7 +87,7 @@ struct VFile* VFileMemChunk(const void* mem, size_t size);
|
|||
struct mCircleBuffer;
|
||||
struct VFile* VFileFIFO(struct mCircleBuffer* backing);
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
struct VDir* VDirOpen(const char* path);
|
||||
struct VDir* VDirOpenArchive(const char* path);
|
||||
|
||||
|
|
|
@ -118,7 +118,7 @@ bool mCheatSaveFile(struct mCheatDevice*, struct VFile*);
|
|||
bool mCheatParseLibretroFile(struct mCheatDevice*, struct VFile*);
|
||||
bool mCheatParseEZFChtFile(struct mCheatDevice*, struct VFile*);
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
void mCheatAutosave(struct mCheatDevice*);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -28,11 +28,6 @@ enum mPlatform {
|
|||
mPLATFORM_GB = 1,
|
||||
};
|
||||
|
||||
enum mCoreChecksumType {
|
||||
mCHECKSUM_CRC32,
|
||||
mCHECKSUM_MD5,
|
||||
};
|
||||
|
||||
struct mAudioBuffer;
|
||||
struct mCoreConfig;
|
||||
struct mCoreSync;
|
||||
|
@ -47,7 +42,7 @@ struct mCore {
|
|||
struct mDebuggerSymbols* symbolTable;
|
||||
struct mVideoLogger* videoLogger;
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
struct mDirectorySet dirs;
|
||||
#endif
|
||||
#ifndef MINIMAL_CORE
|
||||
|
@ -188,18 +183,20 @@ bool mCorePreloadFile(struct mCore* core, const char* path);
|
|||
bool mCorePreloadVFCB(struct mCore* core, struct VFile* vf, void (cb)(size_t, size_t, void*), void* context);
|
||||
bool mCorePreloadFileCB(struct mCore* core, const char* path, void (cb)(size_t, size_t, void*), void* context);
|
||||
|
||||
bool mCoreLoadSaveFile(struct mCore* core, const char* path, bool temporary);
|
||||
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
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);
|
||||
void mCoreDeleteState(struct mCore* core, int slot);
|
||||
|
||||
void mCoreTakeScreenshot(struct mCore* core);
|
||||
#endif
|
||||
bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf);
|
||||
#endif
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
CXX_GUARD_START
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
struct VDir;
|
||||
|
||||
struct mDirectorySet {
|
||||
|
|
|
@ -188,6 +188,12 @@ struct mCoreRegisterInfo {
|
|||
enum mCoreRegisterType type;
|
||||
};
|
||||
|
||||
enum mCoreChecksumType {
|
||||
mCHECKSUM_CRC32,
|
||||
mCHECKSUM_MD5,
|
||||
mCHECKSUM_SHA1,
|
||||
};
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
#endif
|
||||
|
|
|
@ -15,6 +15,8 @@ CXX_GUARD_START
|
|||
#include <mgba/core/core.h>
|
||||
#include <mgba-util/vector.h>
|
||||
|
||||
#define M_LIBRARY_MODEL_UNKNOWN -1
|
||||
|
||||
struct mLibraryEntry {
|
||||
const char* base;
|
||||
const char* filename;
|
||||
|
@ -24,6 +26,9 @@ struct mLibraryEntry {
|
|||
enum mPlatform platform;
|
||||
size_t filesize;
|
||||
uint32_t crc32;
|
||||
uint8_t md5[16];
|
||||
uint8_t sha1[20];
|
||||
int platformModels;
|
||||
};
|
||||
|
||||
#ifdef USE_SQLITE3
|
||||
|
|
|
@ -49,10 +49,11 @@ DECL_BIT(RTCCommandData, Reading, 7);
|
|||
|
||||
struct GBARTC {
|
||||
int32_t bytesRemaining;
|
||||
int32_t transferStep;
|
||||
int32_t bitsRead;
|
||||
int32_t bits;
|
||||
int32_t commandActive;
|
||||
bool sckEdge;
|
||||
bool sioOutput;
|
||||
RTCCommandData command;
|
||||
RTCControl control;
|
||||
uint8_t time[7];
|
||||
|
@ -68,8 +69,9 @@ struct GBACartridgeHardware {
|
|||
enum GPIODirection readWrite;
|
||||
uint16_t* gpioBase;
|
||||
|
||||
uint16_t pinState;
|
||||
uint16_t direction;
|
||||
uint8_t writeLatch;
|
||||
uint8_t pinState;
|
||||
uint8_t direction;
|
||||
|
||||
struct GBARTC rtc;
|
||||
|
||||
|
|
|
@ -165,8 +165,12 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* | 0x00288 - 0x0028B: DMA next count
|
||||
* | 0x0028C - 0x0028F: DMA next event
|
||||
* 0x00290 - 0x002C3: GPIO state
|
||||
* | 0x00290 - 0x00291: Pin state
|
||||
* | 0x00292 - 0x00293: Direction state
|
||||
* | 0x00290: Pin state
|
||||
* | 0x00291: Write latch
|
||||
* | 0x00292: Direction state
|
||||
* | 0x00293: Flags
|
||||
* | bit 0: RTC SIO output
|
||||
* | bit 1 - 7: Reserved
|
||||
* | 0x00294 - 0x002B6: RTC state (see hardware.h for format)
|
||||
* | 0x002B7 - 0x002B7: GPIO devices
|
||||
* | bit 0: Has RTC values
|
||||
|
@ -183,7 +187,7 @@ mLOG_DECLARE_CATEGORY(GBA_STATE);
|
|||
* | bit 0: Is read enabled
|
||||
* | bit 1: Gyroscope sample is edge
|
||||
* | bit 2: Light sample is edge
|
||||
* | bit 3: Reserved
|
||||
* | bit 3: RTC SCK is edge
|
||||
* | bits 4 - 15: Light counter
|
||||
* | 0x002C0 - 0x002C0: Light sample
|
||||
* | 0x002C1: Flags
|
||||
|
@ -282,6 +286,7 @@ DECL_BITFIELD(GBASerializedHWFlags1, uint16_t);
|
|||
DECL_BIT(GBASerializedHWFlags1, ReadWrite, 0);
|
||||
DECL_BIT(GBASerializedHWFlags1, GyroEdge, 1);
|
||||
DECL_BIT(GBASerializedHWFlags1, LightEdge, 2);
|
||||
DECL_BIT(GBASerializedHWFlags1, RtcSckEdge, 3);
|
||||
DECL_BITS(GBASerializedHWFlags1, LightCounter, 4, 12);
|
||||
|
||||
DECL_BITFIELD(GBASerializedHWFlags2, uint8_t);
|
||||
|
@ -289,6 +294,9 @@ DECL_BITS(GBASerializedHWFlags2, TiltState, 0, 2);
|
|||
DECL_BITS(GBASerializedHWFlags2, GbpInputsPosted, 2, 2);
|
||||
DECL_BITS(GBASerializedHWFlags2, GbpTxPosition, 4, 4);
|
||||
|
||||
DECL_BITFIELD(GBASerializedHWFlags3, uint8_t);
|
||||
DECL_BITS(GBASerializedHWFlags3, RtcSioOutput, 0, 1);
|
||||
|
||||
DECL_BITFIELD(GBASerializedUnlCartFlags, uint16_t);
|
||||
DECL_BITS(GBASerializedUnlCartFlags, Type, 0, 5);
|
||||
DECL_BITS(GBASerializedUnlCartFlags, Subtype, 5, 3);
|
||||
|
@ -376,16 +384,18 @@ struct GBASerializedState {
|
|||
} dma[4];
|
||||
|
||||
struct {
|
||||
uint16_t pinState;
|
||||
uint16_t pinDirection;
|
||||
uint8_t pinState;
|
||||
uint8_t writeLatch;
|
||||
uint8_t pinDirection;
|
||||
GBASerializedHWFlags3 flags3;
|
||||
int32_t rtcBytesRemaining;
|
||||
int32_t rtcTransferStep;
|
||||
int32_t reserved0;
|
||||
int32_t rtcBitsRead;
|
||||
int32_t rtcBits;
|
||||
int32_t rtcCommandActive;
|
||||
RTCCommandData rtcCommand;
|
||||
uint8_t rtcControl;
|
||||
uint8_t reserved[3];
|
||||
uint8_t reserved1[3];
|
||||
uint8_t time[7];
|
||||
uint8_t devices;
|
||||
uint16_t gyroSample;
|
||||
|
|
|
@ -8,6 +8,6 @@ Name=mGBA
|
|||
GenericName=Game Boy Advance Emulator
|
||||
Comment=Nintendo Game Boy Advance Emulator
|
||||
Categories=Game;Emulator;
|
||||
MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom;
|
||||
Keywords=emulator;Nintendo;advance;gba;Game Boy Advance;
|
||||
MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom;application/x-gameboy-rom;application/x-gameboy-color-rom;
|
||||
Keywords=emulator;Nintendo;advance;gba;gbc;gb;Game Boy Advance;Game Boy Color; Game Boy;
|
||||
StartupWMClass=mGBA
|
||||
|
|
1194
res/nointro.dat
1194
res/nointro.dat
File diff suppressed because it is too large
Load Diff
|
@ -44,27 +44,25 @@ local state = {
|
|||
[C.PLATFORM.GB] = 7,
|
||||
}
|
||||
}
|
||||
state.overlay = canvas:newLayer(32, 16)
|
||||
state.painter = image.newPainter(state.overlay.image)
|
||||
state.painter:setBlend(false)
|
||||
state.painter:setFill(true)
|
||||
|
||||
function state.update()
|
||||
local keys = util.expandBitmask(emu:getKeys())
|
||||
local maxKey = state.maxKey[emu:platform()]
|
||||
|
||||
for key = 0, maxKey do
|
||||
if emu:getKey(key) ~= 0 then
|
||||
state.painter:setFillColor(0x80FFFFFF)
|
||||
else
|
||||
state.painter:setFillColor(0x40404040)
|
||||
end
|
||||
state.drawButton[key](state)
|
||||
function state.create()
|
||||
if state.overlay ~= nil then
|
||||
return true
|
||||
end
|
||||
state.overlay:update()
|
||||
if canvas == nil then
|
||||
return false
|
||||
end
|
||||
state.overlay = canvas:newLayer(32, 16)
|
||||
if state.overlay == nil then
|
||||
return false
|
||||
end
|
||||
state.painter = image.newPainter(state.overlay.image)
|
||||
state.painter:setBlend(false)
|
||||
state.painter:setFill(true)
|
||||
return true
|
||||
end
|
||||
|
||||
function state.reset()
|
||||
function state.update()
|
||||
local endX = canvas:screenWidth() - 32
|
||||
local endY = canvas:screenHeight() - 16
|
||||
|
||||
|
@ -112,11 +110,32 @@ function state.reset()
|
|||
pos.y = pos.y + input_display.offset.y;
|
||||
|
||||
state.overlay:setPosition(pos.x, pos.y);
|
||||
|
||||
local keys = util.expandBitmask(emu:getKeys())
|
||||
local maxKey = state.maxKey[emu:platform()]
|
||||
|
||||
for key = 0, maxKey do
|
||||
if emu:getKey(key) ~= 0 then
|
||||
state.painter:setFillColor(0x80FFFFFF)
|
||||
else
|
||||
state.painter:setFillColor(0x40404040)
|
||||
end
|
||||
state.drawButton[key](state)
|
||||
end
|
||||
state.overlay:update()
|
||||
end
|
||||
|
||||
function state.reset()
|
||||
if not state.create() then
|
||||
return
|
||||
end
|
||||
state.painter:setFillColor(0x40808080)
|
||||
state.painter:drawRectangle(0, 0, 32, 16)
|
||||
state.overlay:update()
|
||||
end
|
||||
|
||||
input_display.state = state
|
||||
|
||||
state.reset()
|
||||
callbacks:add("frame", state.update)
|
||||
callbacks:add("start", state.reset)
|
||||
|
|
|
@ -466,20 +466,24 @@ local gameLeafGreenEnR1 = gameLeafGreenEn:new{
|
|||
_speciesNameTable=0x245f2c,
|
||||
}
|
||||
|
||||
gameCodes = {
|
||||
["DMG-AAUE"]=gameGSEn, -- Gold
|
||||
["DMG-AAXE"]=gameGSEn, -- Silver
|
||||
["CGB-BYTE"]=gameCrystalEn,
|
||||
["AGB-AXVE"]=gameRubyEn,
|
||||
["AGB-AXPE"]=gameSapphireEn,
|
||||
["AGB-BPEE"]=gameEmeraldEn,
|
||||
["AGB-BPRE"]=gameFireRedEn,
|
||||
["AGB-BPGE"]=gameLeafGreenEn,
|
||||
local gameCodes = {
|
||||
[C.PLATFORM.GB] = {
|
||||
["AAUE"] = gameGSEn, -- Gold
|
||||
["AAXE"] = gameGSEn, -- Silver
|
||||
["BYTE"] = gameCrystalEn,
|
||||
},
|
||||
[C.PLATFORM.GBA] = {
|
||||
["AXVE"] = gameRubyEn,
|
||||
["AXPE"] = gameSapphireEn,
|
||||
["BPEE"] = gameEmeraldEn,
|
||||
["BPRE"] = gameFireRedEn,
|
||||
["BPGE"] = gameLeafGreenEn,
|
||||
}
|
||||
}
|
||||
|
||||
-- These versions have slight differences and/or cannot be uniquely
|
||||
-- identified by their in-header game codes, so fall back on a CRC32
|
||||
gameCrc32 = {
|
||||
local gameCrc32 = {
|
||||
[0x9f7fdd53] = gameRBEn, -- Red
|
||||
[0xd6da8a1a] = gameRBEn, -- Blue
|
||||
[0x7d527d62] = gameYellowEn,
|
||||
|
@ -510,7 +514,7 @@ function detectGame()
|
|||
end
|
||||
game = gameCrc32[checksum]
|
||||
if not game then
|
||||
game = gameCodes[emu:getGameCode()]
|
||||
game = gameCodes[emu:platform()][emu:getGameCode()]
|
||||
end
|
||||
|
||||
if not game then
|
||||
|
|
|
@ -743,11 +743,11 @@ DEFINE_INSTRUCTION_ARM(MRSR, \
|
|||
cpu->gprs[rd] = cpu->spsr.packed;)
|
||||
|
||||
DEFINE_INSTRUCTION_ARM(MSRI,
|
||||
int c = opcode & 0x00010000;
|
||||
int f = opcode & 0x00080000;
|
||||
int rotate = (opcode & 0x00000F00) >> 7;
|
||||
int32_t operand = ROR(opcode & 0x000000FF, rotate);
|
||||
int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0);
|
||||
uint32_t c = opcode & 0x00010000;
|
||||
uint32_t f = opcode & 0x00080000;
|
||||
uint32_t rotate = (opcode & 0x00000F00) >> 7;
|
||||
uint32_t operand = ROR(opcode & 0x000000FF, rotate);
|
||||
uint32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0);
|
||||
if (mask & PSR_USER_MASK) {
|
||||
cpu->cpsr.packed = (cpu->cpsr.packed & ~PSR_USER_MASK) | (operand & PSR_USER_MASK);
|
||||
}
|
||||
|
@ -769,11 +769,11 @@ DEFINE_INSTRUCTION_ARM(MSRI,
|
|||
})
|
||||
|
||||
DEFINE_INSTRUCTION_ARM(MSRRI,
|
||||
int c = opcode & 0x00010000;
|
||||
int f = opcode & 0x00080000;
|
||||
int rotate = (opcode & 0x00000F00) >> 7;
|
||||
int32_t operand = ROR(opcode & 0x000000FF, rotate);
|
||||
int32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0);
|
||||
uint32_t c = opcode & 0x00010000;
|
||||
uint32_t f = opcode & 0x00080000;
|
||||
uint32_t rotate = (opcode & 0x00000F00) >> 7;
|
||||
uint32_t operand = ROR(opcode & 0x000000FF, rotate);
|
||||
uint32_t mask = (c ? 0x000000FF : 0) | (f ? 0xFF000000 : 0);
|
||||
mask &= PSR_USER_MASK | PSR_PRIV_MASK | PSR_STATE_MASK;
|
||||
cpu->spsr.packed = (cpu->spsr.packed & ~mask) | (operand & mask) | 0x00000010;)
|
||||
|
||||
|
|
|
@ -622,7 +622,7 @@ bool mCheatSaveFile(struct mCheatDevice* device, struct VFile* vf) {
|
|||
return true;
|
||||
}
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
void mCheatAutosave(struct mCheatDevice* device) {
|
||||
if (!device->autosave) {
|
||||
return;
|
||||
|
|
|
@ -96,8 +96,9 @@ struct mCore* mCoreCreate(enum mPlatform platform) {
|
|||
#endif
|
||||
|
||||
struct mCore* mCoreFind(const char* path) {
|
||||
struct VDir* archive = VDirOpenArchive(path);
|
||||
struct mCore* core = NULL;
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
struct VDir* archive = VDirOpenArchive(path);
|
||||
if (archive) {
|
||||
struct VDirEntry* dirent = archive->listNext(archive);
|
||||
while (dirent) {
|
||||
|
@ -114,7 +115,9 @@ struct mCore* mCoreFind(const char* path) {
|
|||
dirent = archive->listNext(archive);
|
||||
}
|
||||
archive->close(archive);
|
||||
} else {
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
struct VFile* vf = VFileOpen(path, O_RDONLY);
|
||||
if (!vf) {
|
||||
return NULL;
|
||||
|
@ -133,7 +136,15 @@ bool mCoreLoadFile(struct mCore* core, const char* path) {
|
|||
#ifdef FIXED_ROM_BUFFER
|
||||
return mCorePreloadFile(core, path);
|
||||
#else
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
struct VFile* rom = mDirectorySetOpenPath(&core->dirs, path, core->isROM);
|
||||
#else
|
||||
struct VFile* rom = VFileOpen(path, O_RDONLY);
|
||||
if (rom && !core->isROM(rom)) {
|
||||
rom->close(rom);
|
||||
rom = NULL;
|
||||
}
|
||||
#endif
|
||||
if (!rom) {
|
||||
return false;
|
||||
}
|
||||
|
@ -210,7 +221,15 @@ bool mCorePreloadVFCB(struct mCore* core, struct VFile* vf, void (cb)(size_t, si
|
|||
}
|
||||
|
||||
bool mCorePreloadFileCB(struct mCore* core, const char* path, void (cb)(size_t, size_t, void*), void* context) {
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
struct VFile* rom = mDirectorySetOpenPath(&core->dirs, path, core->isROM);
|
||||
#else
|
||||
struct VFile* rom = VFileOpen(path, O_RDONLY);
|
||||
if (rom && !core->isROM(rom)) {
|
||||
rom->close(rom);
|
||||
rom = NULL;
|
||||
}
|
||||
#endif
|
||||
if (!rom) {
|
||||
return false;
|
||||
}
|
||||
|
@ -222,6 +241,19 @@ bool mCorePreloadFileCB(struct mCore* core, const char* path, void (cb)(size_t,
|
|||
return ret;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
bool mCoreAutoloadSave(struct mCore* core) {
|
||||
if (!core->dirs.save) {
|
||||
return false;
|
||||
|
@ -277,18 +309,6 @@ 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) {
|
||||
|
@ -373,6 +393,7 @@ void mCoreTakeScreenshot(struct mCore* core) {
|
|||
mLOG(STATUS, WARN, "Failed to take screenshot");
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
bool mCoreTakeScreenshotVF(struct mCore* core, struct VFile* vf) {
|
||||
#ifdef USE_PNG
|
||||
|
@ -406,7 +427,7 @@ void mCoreLoadConfig(struct mCore* core) {
|
|||
|
||||
void mCoreLoadForeignConfig(struct mCore* core, const struct mCoreConfig* config) {
|
||||
mCoreConfigMap(config, &core->opts);
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
mDirectorySetMapOptions(&core->dirs, &core->opts);
|
||||
#endif
|
||||
if (core->opts.audioBuffers) {
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
#include <mgba/core/config.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
void mDirectorySetInit(struct mDirectorySet* dirs) {
|
||||
dirs->base = NULL;
|
||||
dirs->archive = NULL;
|
||||
|
|
|
@ -53,6 +53,10 @@
|
|||
#cmakedefine ENABLE_DEBUGGERS
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_DIRECTORIES
|
||||
#cmakedefine ENABLE_DIRECTORIES
|
||||
#endif
|
||||
|
||||
#ifndef ENABLE_GDB_STUB
|
||||
#cmakedefine ENABLE_GDB_STUB
|
||||
#endif
|
||||
|
|
|
@ -9,6 +9,11 @@
|
|||
#include <mgba-util/string.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/gb/interface.h>
|
||||
#include <mgba/internal/gb/gb.h>
|
||||
#endif
|
||||
|
||||
#ifdef USE_SQLITE3
|
||||
|
||||
#include <sqlite3.h>
|
||||
|
@ -33,7 +38,10 @@ struct mLibrary {
|
|||
#define CONSTRAINTS_ROMONLY \
|
||||
"CASE WHEN :useSize THEN roms.size = :size ELSE 1 END AND " \
|
||||
"CASE WHEN :usePlatform THEN roms.platform = :platform ELSE 1 END AND " \
|
||||
"CASE WHEN :useModels THEN roms.models & :models ELSE 1 END AND " \
|
||||
"CASE WHEN :useCrc32 THEN roms.crc32 = :crc32 ELSE 1 END AND " \
|
||||
"CASE WHEN :useMd5 THEN roms.md5 = :md5 ELSE 1 END AND " \
|
||||
"CASE WHEN :useSha1 THEN roms.sha1 = :sha1 ELSE 1 END AND " \
|
||||
"CASE WHEN :useInternalCode THEN roms.internalCode = :internalCode ELSE 1 END"
|
||||
|
||||
#define CONSTRAINTS \
|
||||
|
@ -58,6 +66,20 @@ static void _bindConstraints(sqlite3_stmt* statement, const struct mLibraryEntry
|
|||
sqlite3_bind_int(statement, index, constraints->crc32);
|
||||
}
|
||||
|
||||
if (memcmp(constraints->md5, &(uint8_t[16]) {0}, 16) != 0) {
|
||||
useIndex = sqlite3_bind_parameter_index(statement, ":useMd5");
|
||||
index = sqlite3_bind_parameter_index(statement, ":md5");
|
||||
sqlite3_bind_int(statement, useIndex, 1);
|
||||
sqlite3_bind_blob(statement, index, constraints->md5, 16, NULL);
|
||||
}
|
||||
|
||||
if (memcmp(constraints->sha1, &(uint8_t[20]) {0}, 20) != 0) {
|
||||
useIndex = sqlite3_bind_parameter_index(statement, ":useSha1");
|
||||
index = sqlite3_bind_parameter_index(statement, ":sha1");
|
||||
sqlite3_bind_int(statement, useIndex, 1);
|
||||
sqlite3_bind_blob(statement, index, constraints->sha1, 20, NULL);
|
||||
}
|
||||
|
||||
if (constraints->filesize) {
|
||||
useIndex = sqlite3_bind_parameter_index(statement, ":useSize");
|
||||
index = sqlite3_bind_parameter_index(statement, ":size");
|
||||
|
@ -92,12 +114,40 @@ static void _bindConstraints(sqlite3_stmt* statement, const struct mLibraryEntry
|
|||
sqlite3_bind_int(statement, useIndex, 1);
|
||||
sqlite3_bind_int(statement, index, constraints->platform);
|
||||
}
|
||||
|
||||
if (constraints->platformModels != M_LIBRARY_MODEL_UNKNOWN) {
|
||||
useIndex = sqlite3_bind_parameter_index(statement, ":useModels");
|
||||
index = sqlite3_bind_parameter_index(statement, ":models");
|
||||
sqlite3_bind_int(statement, useIndex, 1);
|
||||
sqlite3_bind_int(statement, index, constraints->platformModels);
|
||||
}
|
||||
}
|
||||
|
||||
struct mLibrary* mLibraryCreateEmpty(void) {
|
||||
return mLibraryLoad(":memory:");
|
||||
}
|
||||
|
||||
static int _mLibraryTableVersion(struct mLibrary* library, const char* tableName) {
|
||||
int version = -1;
|
||||
|
||||
static const char getVersion[] = "SELECT version FROM version WHERE tname=?";
|
||||
sqlite3_stmt* getVersionStmt;
|
||||
if (sqlite3_prepare_v2(library->db, getVersion, -1, &getVersionStmt, NULL)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
sqlite3_clear_bindings(getVersionStmt);
|
||||
sqlite3_reset(getVersionStmt);
|
||||
sqlite3_bind_text(getVersionStmt, 1, tableName, -1, SQLITE_TRANSIENT);
|
||||
if (sqlite3_step(getVersionStmt) != SQLITE_DONE) {
|
||||
version = sqlite3_column_int(getVersionStmt, 0);
|
||||
}
|
||||
|
||||
error:
|
||||
sqlite3_finalize(getVersionStmt);
|
||||
return version;
|
||||
}
|
||||
|
||||
struct mLibrary* mLibraryLoad(const char* path) {
|
||||
struct mLibrary* library = malloc(sizeof(*library));
|
||||
memset(library, 0, sizeof(*library));
|
||||
|
@ -124,6 +174,7 @@ struct mLibrary* mLibraryLoad(const char* path) {
|
|||
"\n internalTitle TEXT,"
|
||||
"\n internalCode TEXT,"
|
||||
"\n platform INTEGER NOT NULL DEFAULT -1,"
|
||||
"\n models INTEGER NULL,"
|
||||
"\n size INTEGER,"
|
||||
"\n crc32 INTEGER,"
|
||||
"\n md5 BLOB,"
|
||||
|
@ -139,20 +190,39 @@ struct mLibrary* mLibraryLoad(const char* path) {
|
|||
"\n CONSTRAINT location UNIQUE (path, rootid)"
|
||||
"\n );"
|
||||
"\n CREATE INDEX IF NOT EXISTS crc32 ON roms (crc32);"
|
||||
"\n CREATE INDEX IF NOT EXISTS md5 ON roms (md5);"
|
||||
"\n CREATE INDEX IF NOT EXISTS sha1 ON roms (sha1);"
|
||||
"\n INSERT OR IGNORE INTO version (tname, version) VALUES ('version', 1);"
|
||||
"\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roots', 1);"
|
||||
"\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roms', 1);"
|
||||
"\n INSERT OR IGNORE INTO version (tname, version) VALUES ('roms', 2);"
|
||||
"\n INSERT OR IGNORE INTO version (tname, version) VALUES ('paths', 1);";
|
||||
if (sqlite3_exec(library->db, createTables, NULL, NULL, NULL)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
int romsTableVersion = _mLibraryTableVersion(library, "roms");
|
||||
if (romsTableVersion < 0) {
|
||||
goto error;
|
||||
} else if (romsTableVersion < 2) {
|
||||
static const char upgradeRomsTable[] =
|
||||
" ALTER TABLE roms"
|
||||
"\n ADD COLUMN models INTEGER NULL";
|
||||
if (sqlite3_exec(library->db, upgradeRomsTable, NULL, NULL, NULL)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
static const char updateRomsTableVersion[] = "UPDATE version SET version=2 WHERE tname='roms'";
|
||||
if (sqlite3_exec(library->db, updateRomsTableVersion, NULL, NULL, NULL)) {
|
||||
goto error;
|
||||
}
|
||||
}
|
||||
|
||||
static const char insertPath[] = "INSERT INTO paths (romid, path, customTitle, rootid) VALUES (?, ?, ?, ?);";
|
||||
if (sqlite3_prepare_v2(library->db, insertPath, -1, &library->insertPath, NULL)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
static const char insertRom[] = "INSERT INTO roms (crc32, size, internalCode, platform) VALUES (:crc32, :size, :internalCode, :platform);";
|
||||
static const char insertRom[] = "INSERT INTO roms (crc32, md5, sha1, size, internalCode, platform, models) VALUES (:crc32, :md5, :sha1, :size, :internalCode, :platform, :models);";
|
||||
if (sqlite3_prepare_v2(library->db, insertRom, -1, &library->insertRom, NULL)) {
|
||||
goto error;
|
||||
}
|
||||
|
@ -297,7 +367,18 @@ bool _mLibraryAddEntry(struct mLibrary* library, const char* filename, const cha
|
|||
snprintf(entry.internalCode, sizeof(entry.internalCode), "%s-%s", info.system, info.code);
|
||||
strlcpy(entry.internalTitle, info.title, sizeof(entry.internalTitle));
|
||||
core->checksum(core, &entry.crc32, mCHECKSUM_CRC32);
|
||||
core->checksum(core, &entry.md5, mCHECKSUM_MD5);
|
||||
core->checksum(core, &entry.sha1, mCHECKSUM_SHA1);
|
||||
entry.platform = core->platform(core);
|
||||
entry.platformModels = M_LIBRARY_MODEL_UNKNOWN;
|
||||
#ifdef M_CORE_GB
|
||||
if (entry.platform == mPLATFORM_GB) {
|
||||
struct GB* gb = (struct GB*) core->board;
|
||||
if (gb->memory.rom) {
|
||||
entry.platformModels = GBValidModels(gb->memory.rom);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
entry.title = NULL;
|
||||
entry.base = base;
|
||||
entry.filename = filename;
|
||||
|
@ -402,14 +483,34 @@ size_t mLibraryGetEntries(struct mLibrary* library, struct mLibraryListing* out,
|
|||
int i;
|
||||
for (i = 0; i < nCols; ++i) {
|
||||
const char* colName = sqlite3_column_name(library->select, i);
|
||||
if (strcmp(colName, "crc32") == 0) {
|
||||
if (strcmp(colName, "sha1") == 0) {
|
||||
const void* buf = sqlite3_column_blob(library->select, i);
|
||||
if (buf && sqlite3_column_bytes(library->select, i) == sizeof(entry->sha1)) {
|
||||
memcpy(entry->sha1, buf, sizeof(entry->sha1));
|
||||
struct NoIntroGame game;
|
||||
if (!entry->title && NoIntroDBLookupGameBySHA1(library->gameDB, entry->sha1, &game)) {
|
||||
entry->title = strdup(game.name);
|
||||
}
|
||||
}
|
||||
} else if (strcmp(colName, "md5") == 0) {
|
||||
const void* buf = sqlite3_column_blob(library->select, i);
|
||||
if (buf && sqlite3_column_bytes(library->select, i) == sizeof(entry->md5)) {
|
||||
memcpy(entry->md5, buf, sizeof(entry->md5));
|
||||
struct NoIntroGame game;
|
||||
if (!entry->title && NoIntroDBLookupGameByMD5(library->gameDB, entry->md5, &game)) {
|
||||
entry->title = strdup(game.name);
|
||||
}
|
||||
}
|
||||
} else if (strcmp(colName, "crc32") == 0) {
|
||||
entry->crc32 = sqlite3_column_int(library->select, i);
|
||||
struct NoIntroGame game;
|
||||
if (NoIntroDBLookupGameByCRC(library->gameDB, entry->crc32, &game)) {
|
||||
if (!entry->title && NoIntroDBLookupGameByCRC(library->gameDB, entry->crc32, &game)) {
|
||||
entry->title = strdup(game.name);
|
||||
}
|
||||
} else if (strcmp(colName, "platform") == 0) {
|
||||
entry->platform = sqlite3_column_int(library->select, i);
|
||||
} else if (strcmp(colName, "models") == 0) {
|
||||
entry->platformModels = sqlite3_column_int(library->select, i);
|
||||
} else if (strcmp(colName, "size") == 0) {
|
||||
entry->filesize = sqlite3_column_int64(library->select, i);
|
||||
} else if (strcmp(colName, "internalCode") == 0 && sqlite3_column_type(library->select, i) == SQLITE_TEXT) {
|
||||
|
|
|
@ -353,6 +353,9 @@ static struct mScriptValue* _mScriptCoreChecksum(const struct mCore* core, int t
|
|||
case mCHECKSUM_MD5:
|
||||
size = 16;
|
||||
break;
|
||||
case mCHECKSUM_SHA1:
|
||||
size = 20;
|
||||
break;
|
||||
}
|
||||
if (!size) {
|
||||
return &mScriptValueNull;
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
struct NoIntroDB {
|
||||
sqlite3* db;
|
||||
sqlite3_stmt* crc32;
|
||||
sqlite3_stmt* md5;
|
||||
sqlite3_stmt* sha1;
|
||||
};
|
||||
|
||||
struct NoIntroDB* NoIntroDBLoad(const char* path) {
|
||||
|
@ -54,8 +56,18 @@ struct NoIntroDB* NoIntroDBLoad(const char* path) {
|
|||
goto error;
|
||||
}
|
||||
|
||||
static const char selectRom[] = "SELECT * FROM games JOIN roms USING (gid) WHERE roms.crc32 = ?;";
|
||||
if (sqlite3_prepare_v2(db->db, selectRom, -1, &db->crc32, NULL)) {
|
||||
static const char selectCrc32[] = "SELECT games.name, roms.name, size, crc32, md5, sha1, flags FROM games JOIN roms USING (gid) WHERE roms.crc32 = ?;";
|
||||
if (sqlite3_prepare_v2(db->db, selectCrc32, -1, &db->crc32, NULL)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
static const char selectMd5[] = "SELECT games.name, roms.name, size, crc32, md5, sha1, flags FROM games JOIN roms USING (gid) WHERE roms.md5 = ?;";
|
||||
if (sqlite3_prepare_v2(db->db, selectMd5, -1, &db->md5, NULL)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
static const char selectSha1[] = "SELECT games.name, roms.name, size, crc32, md5, sha1, flags FROM games JOIN roms USING (gid) WHERE roms.sha1 = ?;";
|
||||
if (sqlite3_prepare_v2(db->db, selectSha1, -1, &db->sha1, NULL)) {
|
||||
goto error;
|
||||
}
|
||||
|
||||
|
@ -300,12 +312,34 @@ void NoIntroDBDestroy(struct NoIntroDB* db) {
|
|||
if (db->crc32) {
|
||||
sqlite3_finalize(db->crc32);
|
||||
}
|
||||
if (db->md5) {
|
||||
sqlite3_finalize(db->md5);
|
||||
}
|
||||
if (db->sha1) {
|
||||
sqlite3_finalize(db->sha1);
|
||||
}
|
||||
if (db->db) {
|
||||
sqlite3_close(db->db);
|
||||
}
|
||||
free(db);
|
||||
}
|
||||
|
||||
void _extractGame(sqlite3_stmt* stmt, struct NoIntroGame* game) {
|
||||
game->name = (const char*) sqlite3_column_text(stmt, 0);
|
||||
game->romName = (const char*) sqlite3_column_text(stmt, 1);
|
||||
game->size = sqlite3_column_int(stmt, 2);
|
||||
game->crc32 = sqlite3_column_int(stmt, 3);
|
||||
const void* buf = sqlite3_column_blob(stmt, 4);
|
||||
if (buf && sqlite3_column_bytes(stmt, 4) == sizeof(game->md5)) {
|
||||
memcpy(game->md5, buf, sizeof(game->md5));
|
||||
}
|
||||
buf = sqlite3_column_blob(stmt, 5);
|
||||
if (buf && sqlite3_column_bytes(stmt, 5) == sizeof(game->sha1)) {
|
||||
memcpy(game->sha1, buf, sizeof(game->sha1));
|
||||
}
|
||||
game->verified = sqlite3_column_int(stmt, 6);
|
||||
}
|
||||
|
||||
bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct NoIntroGame* game) {
|
||||
if (!db) {
|
||||
return false;
|
||||
|
@ -316,11 +350,34 @@ bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct
|
|||
if (sqlite3_step(db->crc32) != SQLITE_ROW) {
|
||||
return false;
|
||||
}
|
||||
game->name = (const char*) sqlite3_column_text(db->crc32, 1);
|
||||
game->romName = (const char*) sqlite3_column_text(db->crc32, 3);
|
||||
game->size = sqlite3_column_int(db->crc32, 4);
|
||||
game->crc32 = sqlite3_column_int(db->crc32, 5);
|
||||
// TODO: md5/sha1
|
||||
game->verified = sqlite3_column_int(db->crc32, 8);
|
||||
_extractGame(db->crc32, game);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoIntroDBLookupGameByMD5(const struct NoIntroDB* db, const uint8_t* md5, struct NoIntroGame* game) {
|
||||
if (!db) {
|
||||
return false;
|
||||
}
|
||||
sqlite3_clear_bindings(db->md5);
|
||||
sqlite3_reset(db->md5);
|
||||
sqlite3_bind_blob(db->md5, 1, md5, 16, NULL);
|
||||
if (sqlite3_step(db->md5) != SQLITE_ROW) {
|
||||
return false;
|
||||
}
|
||||
_extractGame(db->md5, game);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NoIntroDBLookupGameBySHA1(const struct NoIntroDB* db, const uint8_t* sha1, struct NoIntroGame* game) {
|
||||
if (!db) {
|
||||
return false;
|
||||
}
|
||||
sqlite3_clear_bindings(db->sha1);
|
||||
sqlite3_reset(db->sha1);
|
||||
sqlite3_bind_blob(db->sha1, 1, sha1, 20, NULL);
|
||||
if (sqlite3_step(db->sha1) != SQLITE_ROW) {
|
||||
return false;
|
||||
}
|
||||
_extractGame(db->sha1, game);
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -27,6 +27,8 @@ struct NoIntroDB* NoIntroDBLoad(const char* path);
|
|||
bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf);
|
||||
void NoIntroDBDestroy(struct NoIntroDB* db);
|
||||
bool NoIntroDBLookupGameByCRC(const struct NoIntroDB* db, uint32_t crc32, struct NoIntroGame* game);
|
||||
bool NoIntroDBLookupGameByMD5(const struct NoIntroDB* db, const uint8_t* md5, struct NoIntroGame* game);
|
||||
bool NoIntroDBLookupGameBySHA1(const struct NoIntroDB* db, const uint8_t* sha1, struct NoIntroGame* game);
|
||||
|
||||
CXX_GUARD_END
|
||||
|
||||
|
|
|
@ -417,6 +417,7 @@ static void _compress(struct VFile* dest, struct VFile* src) {
|
|||
}
|
||||
dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out);
|
||||
} while (sizeof(compressBuffer) - zstr.avail_out);
|
||||
deflateEnd(&zstr);
|
||||
}
|
||||
|
||||
static bool _decompress(struct VFile* dest, struct VFile* src, size_t compressedLength) {
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <mgba-util/md5.h>
|
||||
#include <mgba-util/memory.h>
|
||||
#include <mgba-util/patch.h>
|
||||
#include <mgba-util/sha1.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
static const struct mCoreChannelInfo _GBVideoLayers[] = {
|
||||
|
@ -150,7 +151,7 @@ static bool _GBCoreInit(struct mCore* core) {
|
|||
gbcore->keys = 0;
|
||||
gb->keySource = &gbcore->keys;
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
mDirectorySetInit(&core->dirs);
|
||||
#endif
|
||||
|
||||
|
@ -162,7 +163,7 @@ static void _GBCoreDeinit(struct mCore* core) {
|
|||
GBDestroy(core->board);
|
||||
mappedMemoryFree(core->cpu, sizeof(struct SM83Core));
|
||||
mappedMemoryFree(core->board, sizeof(struct GB));
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
mDirectorySetDeinit(&core->dirs);
|
||||
#endif
|
||||
#ifdef ENABLE_DEBUGGERS
|
||||
|
@ -539,6 +540,15 @@ static void _GBCoreChecksum(const struct mCore* core, void* data, enum mCoreChec
|
|||
md5Buffer("", 0, data);
|
||||
}
|
||||
break;
|
||||
case mCHECKSUM_SHA1:
|
||||
if (gb->romVf) {
|
||||
sha1File(gb->romVf, data);
|
||||
} else if (gb->memory.rom && gb->isPristine) {
|
||||
sha1Buffer(gb->memory.rom, gb->pristineRomSize, data);
|
||||
} else {
|
||||
sha1Buffer("", 0, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -651,6 +661,7 @@ static void _GBCoreReset(struct mCore* core) {
|
|||
bios = NULL;
|
||||
}
|
||||
}
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
if (!found) {
|
||||
char path[PATH_MAX];
|
||||
mCoreConfigDirectory(path, PATH_MAX);
|
||||
|
@ -679,6 +690,7 @@ static void _GBCoreReset(struct mCore* core) {
|
|||
bios = NULL;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
if (found && bios) {
|
||||
GBLoadBIOS(gb, bios);
|
||||
}
|
||||
|
@ -1128,7 +1140,7 @@ static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) {
|
|||
if (!core->symbolTable) {
|
||||
core->symbolTable = mDebuggerSymbolTableCreate();
|
||||
}
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
if (!vf && core->dirs.base) {
|
||||
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY);
|
||||
}
|
||||
|
|
15
src/gb/gb.c
15
src/gb/gb.c
|
@ -35,7 +35,9 @@ static const uint8_t _registeredTrademark[] = {0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA
|
|||
#define SGB2_BIOS_CHECKSUM 0X53D0DD63
|
||||
#define CGB_BIOS_CHECKSUM 0x41884E46
|
||||
#define CGB0_BIOS_CHECKSUM 0xE8EF5318
|
||||
#define CGBE_BIOS_CHECKSUM 0xE95DC95D
|
||||
#define AGB_BIOS_CHECKSUM 0xFFD6B0F1
|
||||
#define AGB0_BIOS_CHECKSUM 0x570337EA
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GB, "GB", "gb");
|
||||
|
||||
|
@ -85,6 +87,7 @@ static void GBInit(void* cpu, struct mCPUComponent* component) {
|
|||
gb->isPristine = false;
|
||||
gb->pristineRomSize = 0;
|
||||
gb->yankedRomSize = 0;
|
||||
gb->sramSize = 0;
|
||||
|
||||
memset(&gb->gbx, 0, sizeof(gb->gbx));
|
||||
|
||||
|
@ -230,7 +233,7 @@ static void GBSramDeinit(struct GB* gb) {
|
|||
} else if (gb->memory.sram) {
|
||||
mappedMemoryFree(gb->memory.sram, gb->sramSize);
|
||||
}
|
||||
gb->memory.sram = 0;
|
||||
gb->memory.sram = NULL;
|
||||
}
|
||||
|
||||
bool GBLoadSave(struct GB* gb, struct VFile* vf) {
|
||||
|
@ -339,6 +342,7 @@ void GBResizeSram(struct GB* gb, size_t size) {
|
|||
mappedMemoryFree(gb->memory.sram, gb->sramSize);
|
||||
} else {
|
||||
memset(newSram, 0xFF, size);
|
||||
gb->sramSize = size;
|
||||
}
|
||||
gb->memory.sram = newSram;
|
||||
}
|
||||
|
@ -435,6 +439,8 @@ void GBUnloadROM(struct GB* gb) {
|
|||
gb->memory.rom = NULL;
|
||||
gb->memory.mbcType = GB_MBC_AUTODETECT;
|
||||
gb->isPristine = false;
|
||||
gb->pristineRomSize = 0;
|
||||
gb->memory.romSize = 0;
|
||||
|
||||
if (!gb->sramDirty) {
|
||||
gb->sramMaskWriteback = false;
|
||||
|
@ -444,6 +450,7 @@ void GBUnloadROM(struct GB* gb) {
|
|||
if (gb->sramRealVf) {
|
||||
gb->sramRealVf->close(gb->sramRealVf);
|
||||
}
|
||||
gb->sramSize = 0;
|
||||
gb->sramRealVf = NULL;
|
||||
gb->sramVf = NULL;
|
||||
if (gb->memory.cam && gb->memory.cam->stopRequestImage) {
|
||||
|
@ -550,7 +557,9 @@ bool GBIsBIOS(struct VFile* vf) {
|
|||
case SGB2_BIOS_CHECKSUM:
|
||||
case CGB_BIOS_CHECKSUM:
|
||||
case CGB0_BIOS_CHECKSUM:
|
||||
case CGBE_BIOS_CHECKSUM:
|
||||
case AGB_BIOS_CHECKSUM:
|
||||
case AGB0_BIOS_CHECKSUM:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
|
@ -567,7 +576,9 @@ bool GBIsCompatibleBIOS(struct VFile* vf, enum GBModel model) {
|
|||
return model < GB_MODEL_CGB;
|
||||
case CGB_BIOS_CHECKSUM:
|
||||
case CGB0_BIOS_CHECKSUM:
|
||||
case CGBE_BIOS_CHECKSUM:
|
||||
case AGB_BIOS_CHECKSUM:
|
||||
case AGB0_BIOS_CHECKSUM:
|
||||
return model >= GB_MODEL_CGB;
|
||||
default:
|
||||
return false;
|
||||
|
@ -862,9 +873,11 @@ void GBDetectModel(struct GB* gb) {
|
|||
break;
|
||||
case CGB_BIOS_CHECKSUM:
|
||||
case CGB0_BIOS_CHECKSUM:
|
||||
case CGBE_BIOS_CHECKSUM:
|
||||
gb->model = GB_MODEL_CGB;
|
||||
break;
|
||||
case AGB_BIOS_CHECKSUM:
|
||||
case AGB0_BIOS_CHECKSUM:
|
||||
gb->model = GB_MODEL_AGB;
|
||||
break;
|
||||
default:
|
||||
|
|
35
src/gb/mbc.c
35
src/gb/mbc.c
|
@ -274,6 +274,7 @@ void GBMBCSwitchSramHalfBank(struct GB* gb, int half, int bank) {
|
|||
|
||||
void GBMBCInit(struct GB* gb) {
|
||||
const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
|
||||
size_t sramSize = 0;
|
||||
if (gb->memory.rom && gb->memory.romSize) {
|
||||
if (gb->memory.romSize >= 0x8000) {
|
||||
const struct GBCartridge* cartFooter = (const struct GBCartridge*) &gb->memory.rom[gb->memory.romSize - 0x7F00];
|
||||
|
@ -282,25 +283,25 @@ void GBMBCInit(struct GB* gb) {
|
|||
}
|
||||
}
|
||||
if (gb->gbx.romSize) {
|
||||
gb->sramSize = gb->gbx.ramSize;
|
||||
sramSize = gb->gbx.ramSize;
|
||||
gb->memory.mbcType = gb->gbx.mbc;
|
||||
} else {
|
||||
switch (cart->ramSize) {
|
||||
case 0:
|
||||
gb->sramSize = 0;
|
||||
sramSize = 0;
|
||||
break;
|
||||
default:
|
||||
case 2:
|
||||
gb->sramSize = 0x2000;
|
||||
sramSize = 0x2000;
|
||||
break;
|
||||
case 3:
|
||||
gb->sramSize = 0x8000;
|
||||
sramSize = 0x8000;
|
||||
break;
|
||||
case 4:
|
||||
gb->sramSize = 0x20000;
|
||||
sramSize = 0x20000;
|
||||
break;
|
||||
case 5:
|
||||
gb->sramSize = 0x10000;
|
||||
sramSize = 0x10000;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
@ -399,7 +400,7 @@ void GBMBCInit(struct GB* gb) {
|
|||
gb->memory.mbcWrite = _GBMBC2;
|
||||
gb->memory.mbcRead = _GBMBC2Read;
|
||||
gb->memory.directSramAccess = false;
|
||||
gb->sramSize = 0x100;
|
||||
sramSize = 0x100;
|
||||
break;
|
||||
case GB_MBC3:
|
||||
gb->memory.mbcWrite = _GBMBC3;
|
||||
|
@ -414,15 +415,15 @@ void GBMBCInit(struct GB* gb) {
|
|||
gb->memory.mbcWrite = _GBMBC6;
|
||||
gb->memory.mbcRead = _GBMBC6Read;
|
||||
gb->memory.directSramAccess = false;
|
||||
if (!gb->sramSize) {
|
||||
gb->sramSize = GB_SIZE_EXTERNAL_RAM; // Force minimum size for convenience
|
||||
if (!sramSize) {
|
||||
sramSize = GB_SIZE_EXTERNAL_RAM; // Force minimum size for convenience
|
||||
}
|
||||
gb->sramSize += GB_SIZE_MBC6_FLASH; // Flash is concatenated at the end
|
||||
sramSize += GB_SIZE_MBC6_FLASH; // Flash is concatenated at the end
|
||||
break;
|
||||
case GB_MBC7:
|
||||
gb->memory.mbcWrite = _GBMBC7;
|
||||
gb->memory.mbcRead = _GBMBC7Read;
|
||||
gb->sramSize = 0x100;
|
||||
sramSize = 0x100;
|
||||
break;
|
||||
case GB_MMM01:
|
||||
gb->memory.mbcWrite = _GBMMM01;
|
||||
|
@ -440,7 +441,7 @@ void GBMBCInit(struct GB* gb) {
|
|||
gb->memory.mbcState.tama5.rtcAlarmPage[GBTAMA6_RTC_PAGE] = 1;
|
||||
gb->memory.mbcState.tama5.rtcFreePage0[GBTAMA6_RTC_PAGE] = 2;
|
||||
gb->memory.mbcState.tama5.rtcFreePage1[GBTAMA6_RTC_PAGE] = 3;
|
||||
gb->sramSize = 0x20;
|
||||
sramSize = 0x20;
|
||||
break;
|
||||
case GB_MBC3_RTC:
|
||||
memset(gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs));
|
||||
|
@ -452,8 +453,8 @@ void GBMBCInit(struct GB* gb) {
|
|||
case GB_POCKETCAM:
|
||||
gb->memory.mbcWrite = _GBPocketCam;
|
||||
gb->memory.mbcRead = _GBPocketCamRead;
|
||||
if (!gb->sramSize) {
|
||||
gb->sramSize = GB_SIZE_EXTERNAL_RAM; // Force minimum size for convenience
|
||||
if (!sramSize) {
|
||||
sramSize = GB_SIZE_EXTERNAL_RAM; // Force minimum size for convenience
|
||||
}
|
||||
if (gb->memory.cam && gb->memory.cam->startRequestImage) {
|
||||
gb->memory.cam->startRequestImage(gb->memory.cam, GBCAM_WIDTH, GBCAM_HEIGHT, mCOLOR_ANY);
|
||||
|
@ -508,7 +509,7 @@ void GBMBCInit(struct GB* gb) {
|
|||
gb->memory.mbcReadBank1 = true;
|
||||
gb->memory.mbcReadHigh = true;
|
||||
gb->memory.mbcWriteHigh = true;
|
||||
if (gb->sramSize) {
|
||||
if (sramSize) {
|
||||
gb->memory.sramAccess = true;
|
||||
}
|
||||
break;
|
||||
|
@ -516,7 +517,7 @@ void GBMBCInit(struct GB* gb) {
|
|||
gb->memory.mbcWrite = _GBSintax;
|
||||
gb->memory.mbcRead = _GBSintaxRead;
|
||||
gb->memory.mbcReadBank1 = true;
|
||||
if (gb->sramSize) {
|
||||
if (sramSize) {
|
||||
gb->memory.sramAccess = true;
|
||||
}
|
||||
break;
|
||||
|
@ -539,7 +540,7 @@ void GBMBCInit(struct GB* gb) {
|
|||
}
|
||||
memset(&gb->memory.rtcRegs, 0, sizeof(gb->memory.rtcRegs));
|
||||
|
||||
GBResizeSram(gb, gb->sramSize);
|
||||
GBResizeSram(gb, sramSize);
|
||||
|
||||
if (gb->memory.mbcType == GB_MBC3_RTC) {
|
||||
GBMBCRTCRead(gb);
|
||||
|
|
|
@ -650,7 +650,7 @@ void _eReaderWriteControl0(struct GBACartEReader* ereader, uint8_t value) {
|
|||
}
|
||||
ereader->registerControl0 = control;
|
||||
if (!EReaderControl0IsScan(oldControl) && EReaderControl0IsScan(control)) {
|
||||
if (ereader->scanX > 1000) {
|
||||
if (ereader->scanX > 0) {
|
||||
_eReaderScanCard(ereader);
|
||||
}
|
||||
ereader->scanX = 0;
|
||||
|
@ -668,7 +668,7 @@ void _eReaderWriteControl1(struct GBACartEReader* ereader, uint8_t value) {
|
|||
++ereader->scanY;
|
||||
if (ereader->scanY == (ereader->serial[0x15] | (ereader->serial[0x14] << 8))) {
|
||||
ereader->scanY = 0;
|
||||
if (ereader->scanX < 3400) {
|
||||
if (ereader->scanX < 4050) {
|
||||
ereader->scanX += 210;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ static void _outputPins(struct GBACartridgeHardware* hw, unsigned pins);
|
|||
|
||||
static void _rtcReadPins(struct GBACartridgeHardware* hw);
|
||||
static unsigned _rtcOutput(struct GBACartridgeHardware* hw);
|
||||
static void _rtcBeginCommand(struct GBACartridgeHardware* hw);
|
||||
static void _rtcProcessByte(struct GBACartridgeHardware* hw);
|
||||
static void _rtcUpdateClock(struct GBACartridgeHardware* hw);
|
||||
static unsigned _rtcBCD(unsigned value);
|
||||
|
@ -49,6 +50,7 @@ void GBAHardwareInit(struct GBACartridgeHardware* hw, uint16_t* base) {
|
|||
|
||||
void GBAHardwareReset(struct GBACartridgeHardware* hw) {
|
||||
hw->readWrite = GPIO_WRITE_ONLY;
|
||||
hw->writeLatch = 0;
|
||||
hw->pinState = 0;
|
||||
hw->direction = 0;
|
||||
hw->lightCounter = 0;
|
||||
|
@ -64,6 +66,7 @@ void GBAHardwareReset(struct GBACartridgeHardware* hw) {
|
|||
void GBAHardwareClear(struct GBACartridgeHardware* hw) {
|
||||
hw->devices = HW_NONE | (hw->devices & HW_GB_PLAYER_DETECTION);
|
||||
hw->readWrite = GPIO_WRITE_ONLY;
|
||||
hw->writeLatch = 0;
|
||||
hw->pinState = 0;
|
||||
hw->direction = 0;
|
||||
}
|
||||
|
@ -74,19 +77,25 @@ void GBAHardwareGPIOWrite(struct GBACartridgeHardware* hw, uint32_t address, uin
|
|||
}
|
||||
switch (address) {
|
||||
case GPIO_REG_DATA:
|
||||
hw->writeLatch = value & 0xF;
|
||||
if (!hw->p->vbaBugCompat) {
|
||||
hw->pinState &= ~hw->direction;
|
||||
hw->pinState |= value & hw->direction;
|
||||
hw->pinState |= hw->writeLatch & hw->direction;
|
||||
} else {
|
||||
hw->pinState = value;
|
||||
hw->pinState = hw->writeLatch;
|
||||
}
|
||||
_readPins(hw);
|
||||
break;
|
||||
case GPIO_REG_DIRECTION:
|
||||
hw->direction = value;
|
||||
hw->direction = value & 0xF;
|
||||
if (!hw->p->vbaBugCompat) {
|
||||
hw->pinState &= ~hw->direction;
|
||||
hw->pinState |= hw->writeLatch & hw->direction;
|
||||
_readPins(hw);
|
||||
}
|
||||
break;
|
||||
case GPIO_REG_CONTROL:
|
||||
hw->readWrite = value;
|
||||
hw->readWrite = value & 0x1;
|
||||
break;
|
||||
default:
|
||||
mLOG(GBA_HW, WARN, "Invalid GPIO address");
|
||||
|
@ -106,11 +115,11 @@ void GBAHardwareInitRTC(struct GBACartridgeHardware* hw) {
|
|||
hw->devices |= HW_RTC;
|
||||
hw->rtc.bytesRemaining = 0;
|
||||
|
||||
hw->rtc.transferStep = 0;
|
||||
|
||||
hw->rtc.bitsRead = 0;
|
||||
hw->rtc.bits = 0;
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.commandActive = false;
|
||||
hw->rtc.sckEdge = true;
|
||||
hw->rtc.sioOutput = true;
|
||||
hw->rtc.command = 0;
|
||||
hw->rtc.control = 0x40;
|
||||
memset(hw->rtc.time, 0, sizeof(hw->rtc.time));
|
||||
|
@ -138,11 +147,9 @@ void _readPins(struct GBACartridgeHardware* hw) {
|
|||
}
|
||||
|
||||
void _outputPins(struct GBACartridgeHardware* hw, unsigned pins) {
|
||||
hw->pinState &= hw->direction;
|
||||
hw->pinState |= (pins & ~hw->direction & 0xF);
|
||||
if (hw->readWrite) {
|
||||
uint16_t old;
|
||||
LOAD_16(old, 0, hw->gpioBase);
|
||||
old &= hw->direction;
|
||||
hw->pinState = old | (pins & ~hw->direction & 0xF);
|
||||
STORE_16(hw->pinState, 0, hw->gpioBase);
|
||||
}
|
||||
}
|
||||
|
@ -150,121 +157,127 @@ void _outputPins(struct GBACartridgeHardware* hw, unsigned pins) {
|
|||
// == RTC
|
||||
|
||||
void _rtcReadPins(struct GBACartridgeHardware* hw) {
|
||||
// Transfer sequence:
|
||||
// P: 0 | 1 | 2 | 3
|
||||
// == Initiate
|
||||
// > HI | - | LO | -
|
||||
// > HI | - | HI | -
|
||||
// == Transfer bit (x8)
|
||||
// > LO | x | HI | -
|
||||
// > HI | - | HI | -
|
||||
// < ?? | x | ?? | -
|
||||
// == Terminate
|
||||
// > - | - | LO | -
|
||||
switch (hw->rtc.transferStep) {
|
||||
case 0:
|
||||
if ((hw->pinState & 5) == 1) {
|
||||
hw->rtc.transferStep = 1;
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
if ((hw->pinState & 5) == 5) {
|
||||
hw->rtc.transferStep = 2;
|
||||
} else if ((hw->pinState & 5) != 1) {
|
||||
hw->rtc.transferStep = 0;
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
// P: 0 - SCK | 1 - SIO | 2 - CS | 3 - Unused
|
||||
// CS rising edge starts RTC transfer
|
||||
// Conversely, CS falling edge aborts RTC transfer
|
||||
// SCK rising edge shifts a bit from SIO into the transfer
|
||||
// However, there appears to be a race condition if SIO changes at SCK rising edge
|
||||
// For writing the command, the old SIO data is used in this race
|
||||
// For writing command data, 0 is used in this race
|
||||
// Note while CS is low, SCK is internally considered high by the RTC
|
||||
// SCK falling edge shifts a bit from the transfer into SIO
|
||||
// (Assuming a read command, outside of read commands SIO is held high)
|
||||
|
||||
// RTC keeps SCK/CS/Unused to low
|
||||
_outputPins(hw, hw->pinState & 2);
|
||||
|
||||
if (!(hw->pinState & 4)) {
|
||||
hw->rtc.bitsRead = 0;
|
||||
hw->rtc.bytesRemaining = 0;
|
||||
hw->rtc.commandActive = false;
|
||||
hw->rtc.command = 0;
|
||||
hw->rtc.sckEdge = true;
|
||||
hw->rtc.sioOutput = true;
|
||||
_outputPins(hw, 2);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hw->rtc.commandActive) {
|
||||
_outputPins(hw, 2);
|
||||
if (!(hw->pinState & 1)) {
|
||||
hw->rtc.bits &= ~(1 << hw->rtc.bitsRead);
|
||||
hw->rtc.bits |= ((hw->pinState & 2) >> 1) << hw->rtc.bitsRead;
|
||||
} else {
|
||||
if (hw->pinState & 4) {
|
||||
if (!RTCCommandDataIsReading(hw->rtc.command)) {
|
||||
++hw->rtc.bitsRead;
|
||||
if (hw->rtc.bitsRead == 8) {
|
||||
_rtcProcessByte(hw);
|
||||
}
|
||||
} else {
|
||||
_outputPins(hw, 5 | (_rtcOutput(hw) << 1));
|
||||
++hw->rtc.bitsRead;
|
||||
if (hw->rtc.bitsRead == 8) {
|
||||
--hw->rtc.bytesRemaining;
|
||||
if (hw->rtc.bytesRemaining <= 0) {
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command = 0;
|
||||
}
|
||||
hw->rtc.bitsRead = 0;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
hw->rtc.bitsRead = 0;
|
||||
hw->rtc.bytesRemaining = 0;
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command = 0;
|
||||
hw->rtc.transferStep = hw->pinState & 1;
|
||||
_outputPins(hw, 1);
|
||||
}
|
||||
if (!hw->rtc.sckEdge && (hw->pinState & 1)) {
|
||||
++hw->rtc.bitsRead;
|
||||
if (hw->rtc.bitsRead == 8) {
|
||||
_rtcBeginCommand(hw);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
||||
--hw->rtc.bytesRemaining;
|
||||
if (!hw->rtc.commandActive) {
|
||||
RTCCommandData command;
|
||||
command = hw->rtc.bits;
|
||||
if (RTCCommandDataGetMagic(command) == 0x06) {
|
||||
hw->rtc.command = command;
|
||||
|
||||
hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(command)];
|
||||
hw->rtc.commandActive = hw->rtc.bytesRemaining > 0;
|
||||
mLOG(GBA_HW, DEBUG, "Got RTC command %x", RTCCommandDataGetCommand(command));
|
||||
switch (RTCCommandDataGetCommand(command)) {
|
||||
case RTC_RESET:
|
||||
hw->rtc.control = 0;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
_rtcUpdateClock(hw);
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
case RTC_CONTROL:
|
||||
break;
|
||||
} else if (!RTCCommandDataIsReading(hw->rtc.command)) {
|
||||
_outputPins(hw, 2);
|
||||
if (!(hw->pinState & 1)) {
|
||||
hw->rtc.bits &= ~(1 << hw->rtc.bitsRead);
|
||||
hw->rtc.bits |= ((hw->pinState & 2) >> 1) << hw->rtc.bitsRead;
|
||||
}
|
||||
if (!hw->rtc.sckEdge && (hw->pinState & 1)) {
|
||||
if ((((hw->rtc.bits >> hw->rtc.bitsRead) & 1) ^ ((hw->pinState & 2) >> 1))) {
|
||||
hw->rtc.bits &= ~(1 << hw->rtc.bitsRead);
|
||||
}
|
||||
++hw->rtc.bitsRead;
|
||||
if (hw->rtc.bitsRead == 8) {
|
||||
_rtcProcessByte(hw);
|
||||
}
|
||||
} else {
|
||||
mLOG(GBA_HW, WARN, "Invalid RTC command byte: %02X", hw->rtc.bits);
|
||||
}
|
||||
} else {
|
||||
switch (RTCCommandDataGetCommand(hw->rtc.command)) {
|
||||
case RTC_CONTROL:
|
||||
hw->rtc.control = hw->rtc.bits;
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
mLOG(GBA_HW, STUB, "Unimplemented RTC command %u", RTCCommandDataGetCommand(hw->rtc.command));
|
||||
break;
|
||||
if (hw->rtc.sckEdge && !(hw->pinState & 1)) {
|
||||
hw->rtc.sioOutput = _rtcOutput(hw);
|
||||
++hw->rtc.bitsRead;
|
||||
if (hw->rtc.bitsRead == 8) {
|
||||
--hw->rtc.bytesRemaining;
|
||||
if (hw->rtc.bytesRemaining <= 0) {
|
||||
hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(hw->rtc.command)];
|
||||
}
|
||||
hw->rtc.bitsRead = 0;
|
||||
}
|
||||
}
|
||||
_outputPins(hw, hw->rtc.sioOutput << 1);
|
||||
}
|
||||
|
||||
hw->rtc.sckEdge = !!(hw->pinState & 1);
|
||||
}
|
||||
|
||||
void _rtcBeginCommand(struct GBACartridgeHardware* hw) {
|
||||
RTCCommandData command = hw->rtc.bits;
|
||||
if (RTCCommandDataGetMagic(command) == 0x06) {
|
||||
hw->rtc.command = command;
|
||||
hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(command)];
|
||||
hw->rtc.commandActive = true;
|
||||
mLOG(GBA_HW, DEBUG, "Got RTC command %x", RTCCommandDataGetCommand(command));
|
||||
switch (RTCCommandDataGetCommand(command)) {
|
||||
case RTC_RESET:
|
||||
hw->rtc.control = 0;
|
||||
break;
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
_rtcUpdateClock(hw);
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
case RTC_CONTROL:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
mLOG(GBA_HW, WARN, "Invalid RTC command byte: %02X", hw->rtc.bits);
|
||||
}
|
||||
|
||||
hw->rtc.bits = 0;
|
||||
hw->rtc.bitsRead = 0;
|
||||
if (!hw->rtc.bytesRemaining) {
|
||||
hw->rtc.commandActive = 0;
|
||||
hw->rtc.command = 0;
|
||||
}
|
||||
|
||||
void _rtcProcessByte(struct GBACartridgeHardware* hw) {
|
||||
switch (RTCCommandDataGetCommand(hw->rtc.command)) {
|
||||
case RTC_CONTROL:
|
||||
hw->rtc.control = hw->rtc.bits;
|
||||
break;
|
||||
case RTC_FORCE_IRQ:
|
||||
mLOG(GBA_HW, STUB, "Unimplemented RTC command %u", RTCCommandDataGetCommand(hw->rtc.command));
|
||||
break;
|
||||
case RTC_RESET:
|
||||
case RTC_DATETIME:
|
||||
case RTC_TIME:
|
||||
break;
|
||||
}
|
||||
|
||||
hw->rtc.bits = 0;
|
||||
hw->rtc.bitsRead = 0;
|
||||
--hw->rtc.bytesRemaining;
|
||||
if (hw->rtc.bytesRemaining <= 0) {
|
||||
hw->rtc.bytesRemaining = RTC_BYTES[RTCCommandDataGetCommand(hw->rtc.command)];
|
||||
}
|
||||
}
|
||||
|
||||
unsigned _rtcOutput(struct GBACartridgeHardware* hw) {
|
||||
uint8_t outputByte = 0;
|
||||
if (!hw->rtc.commandActive) {
|
||||
mLOG(GBA_HW, GAME_ERROR, "Attempting to use RTC without an active command");
|
||||
return 0;
|
||||
}
|
||||
uint8_t outputByte = 0xFF;
|
||||
switch (RTCCommandDataGetCommand(hw->rtc.command)) {
|
||||
case RTC_CONTROL:
|
||||
outputByte = hw->rtc.control;
|
||||
|
@ -391,6 +404,7 @@ void _lightReadPins(struct GBACartridgeHardware* hw) {
|
|||
struct GBALuminanceSource* lux = hw->p->luminanceSource;
|
||||
mLOG(GBA_HW, DEBUG, "[SOLAR] Got reset");
|
||||
hw->lightCounter = 0;
|
||||
hw->lightEdge = true; // unverified (perhaps reset only happens on bit 1 rising edge?)
|
||||
if (lux) {
|
||||
if (lux->sample) {
|
||||
lux->sample(lux);
|
||||
|
@ -406,7 +420,7 @@ void _lightReadPins(struct GBACartridgeHardware* hw) {
|
|||
hw->lightEdge = !(hw->pinState & 1);
|
||||
|
||||
bool sendBit = hw->lightCounter >= hw->lightSample;
|
||||
_outputPins(hw, sendBit << 3);
|
||||
_outputPins(hw, (sendBit << 3) | (hw->pinState & 0x7));
|
||||
mLOG(GBA_HW, DEBUG, "[SOLAR] Output %u with pins %u", hw->lightCounter, hw->pinState);
|
||||
}
|
||||
|
||||
|
@ -475,15 +489,20 @@ uint8_t GBAHardwareTiltRead(struct GBACartridgeHardware* hw, uint32_t address) {
|
|||
void GBAHardwareSerialize(const struct GBACartridgeHardware* hw, struct GBASerializedState* state) {
|
||||
GBASerializedHWFlags1 flags1 = 0;
|
||||
flags1 = GBASerializedHWFlags1SetReadWrite(flags1, hw->readWrite);
|
||||
STORE_16(hw->pinState, 0, &state->hw.pinState);
|
||||
STORE_16(hw->direction, 0, &state->hw.pinDirection);
|
||||
state->hw.writeLatch = hw->writeLatch;
|
||||
state->hw.pinState = hw->pinState;
|
||||
state->hw.pinDirection = hw->direction;
|
||||
state->hw.devices = hw->devices;
|
||||
|
||||
GBASerializedHWFlags3 flags3 = 0;
|
||||
flags3 = GBASerializedHWFlags3SetRtcSioOutput(flags3, hw->rtc.sioOutput);
|
||||
state->hw.flags3 = flags3;
|
||||
|
||||
STORE_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining);
|
||||
STORE_32(hw->rtc.transferStep, 0, &state->hw.rtcTransferStep);
|
||||
STORE_32(hw->rtc.bitsRead, 0, &state->hw.rtcBitsRead);
|
||||
STORE_32(hw->rtc.bits, 0, &state->hw.rtcBits);
|
||||
STORE_32(hw->rtc.commandActive, 0, &state->hw.rtcCommandActive);
|
||||
flags1 = GBASerializedHWFlags1SetRtcSckEdge(flags1, hw->rtc.sckEdge);
|
||||
STORE_32(hw->rtc.command, 0, &state->hw.rtcCommand);
|
||||
STORE_32(hw->rtc.control, 0, &state->hw.rtcControl);
|
||||
memcpy(state->hw.time, hw->rtc.time, sizeof(state->hw.time));
|
||||
|
@ -512,8 +531,9 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
|
|||
GBASerializedHWFlags1 flags1;
|
||||
LOAD_16(flags1, 0, &state->hw.flags1);
|
||||
hw->readWrite = GBASerializedHWFlags1GetReadWrite(flags1);
|
||||
LOAD_16(hw->pinState, 0, &state->hw.pinState);
|
||||
LOAD_16(hw->direction, 0, &state->hw.pinDirection);
|
||||
hw->writeLatch = state->hw.writeLatch & 0xF;
|
||||
hw->pinState = state->hw.pinState & 0xF;
|
||||
hw->direction = state->hw.pinDirection & 0xF;
|
||||
hw->devices = state->hw.devices;
|
||||
|
||||
if ((hw->devices & HW_GPIO) && hw->gpioBase) {
|
||||
|
@ -529,11 +549,13 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
|
|||
}
|
||||
}
|
||||
|
||||
hw->rtc.sioOutput = GBASerializedHWFlags3GetRtcSioOutput(state->hw.flags3);
|
||||
|
||||
LOAD_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining);
|
||||
LOAD_32(hw->rtc.transferStep, 0, &state->hw.rtcTransferStep);
|
||||
LOAD_32(hw->rtc.bitsRead, 0, &state->hw.rtcBitsRead);
|
||||
LOAD_32(hw->rtc.bits, 0, &state->hw.rtcBits);
|
||||
LOAD_32(hw->rtc.commandActive, 0, &state->hw.rtcCommandActive);
|
||||
hw->rtc.sckEdge = GBASerializedHWFlags1GetRtcSckEdge(flags1);
|
||||
LOAD_32(hw->rtc.command, 0, &state->hw.rtcCommand);
|
||||
LOAD_32(hw->rtc.control, 0, &state->hw.rtcControl);
|
||||
memcpy(hw->rtc.time, state->hw.time, sizeof(hw->rtc.time));
|
||||
|
|
|
@ -180,14 +180,25 @@ bool GBACheatAddVBALine(struct GBACheatSet* cheats, const char* line) {
|
|||
return false;
|
||||
}
|
||||
|
||||
struct mCheat* cheat = mCheatListAppend(&cheats->d.list);
|
||||
cheat->address = address;
|
||||
cheat->operandOffset = 0;
|
||||
cheat->addressOffset = 0;
|
||||
cheat->repeat = 1;
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = width;
|
||||
cheat->operand = value;
|
||||
if (address < GBA_BASE_ROM0 || address >= GBA_BASE_SRAM) {
|
||||
struct mCheat* cheat = mCheatListAppend(&cheats->d.list);
|
||||
memset(cheat, 0, sizeof(*cheat));
|
||||
cheat->address = address;
|
||||
cheat->operandOffset = 0;
|
||||
cheat->addressOffset = 0;
|
||||
cheat->repeat = 1;
|
||||
cheat->type = CHEAT_ASSIGN;
|
||||
cheat->width = width;
|
||||
cheat->operand = value;
|
||||
} else {
|
||||
struct mCheatPatch* patch = mCheatPatchListAppend(&cheats->d.romPatches);
|
||||
memset(patch, 0, sizeof(*patch));
|
||||
patch->width = width;
|
||||
patch->address = address;
|
||||
patch->segment = 0;
|
||||
patch->value = value;
|
||||
patch->check = false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
@ -31,6 +31,7 @@
|
|||
#include <mgba-util/elf-read.h>
|
||||
#endif
|
||||
#include <mgba-util/md5.h>
|
||||
#include <mgba-util/sha1.h>
|
||||
#include <mgba-util/memory.h>
|
||||
#include <mgba-util/patch.h>
|
||||
#include <mgba-util/vfs.h>
|
||||
|
@ -296,7 +297,7 @@ static bool _GBACoreInit(struct mCore* core) {
|
|||
gbacore->proxyRenderer.logger = NULL;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
mDirectorySetInit(&core->dirs);
|
||||
#endif
|
||||
|
||||
|
@ -308,7 +309,7 @@ static void _GBACoreDeinit(struct mCore* core) {
|
|||
GBADestroy(core->board);
|
||||
mappedMemoryFree(core->cpu, sizeof(struct ARMCore));
|
||||
mappedMemoryFree(core->board, sizeof(struct GBA));
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
mDirectorySetDeinit(&core->dirs);
|
||||
#endif
|
||||
#ifdef ENABLE_DEBUGGERS
|
||||
|
@ -701,6 +702,19 @@ static void _GBACoreChecksum(const struct mCore* core, void* data, enum mCoreChe
|
|||
md5Buffer("", 0, data);
|
||||
}
|
||||
break;
|
||||
case mCHECKSUM_SHA1:
|
||||
if (gba->romVf) {
|
||||
sha1File(gba->romVf, data);
|
||||
} else if (gba->mbVf) {
|
||||
sha1File(gba->mbVf, data);
|
||||
} else if (gba->memory.rom && gba->isPristine) {
|
||||
sha1Buffer(gba->memory.rom, gba->pristineRomSize, data);
|
||||
} else if (gba->memory.rom) {
|
||||
sha1Buffer(gba->memory.rom, gba->memory.romSize, data);
|
||||
} else {
|
||||
sha1Buffer("", 0, data);
|
||||
}
|
||||
break;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
@ -1352,7 +1366,7 @@ static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) {
|
|||
seek = vf->seek(vf, 0, SEEK_CUR);
|
||||
vf->seek(vf, 0, SEEK_SET);
|
||||
}
|
||||
#ifdef ENABLE_VFS
|
||||
#if defined(ENABLE_VFS) && defined(ENABLE_DIRECTORIES)
|
||||
#ifdef USE_ELF
|
||||
if (!vf && core->dirs.base) {
|
||||
closeAfter = true;
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
#include <fcntl.h>
|
||||
|
||||
MGBA_EXPORT const uint32_t GBASavestateMagic = 0x01000000;
|
||||
MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000007;
|
||||
MGBA_EXPORT const uint32_t GBASavestateVersion = 0x00000009;
|
||||
|
||||
mLOG_DEFINE_CATEGORY(GBA_STATE, "GBA Savestate", "gba.serialize");
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@ BasicInfo:
|
|||
|
||||
TitleInfo:
|
||||
Category : Application
|
||||
UniqueId : 0x1A1E
|
||||
UniqueId : 0xD721
|
||||
|
||||
Option:
|
||||
UseOnSD : true # true if App is to be installed to SD
|
||||
|
|
|
@ -960,6 +960,7 @@ bool retro_load_game(const struct retro_game_info* game) {
|
|||
switch (gb->model) {
|
||||
case GB_MODEL_AGB:
|
||||
case GB_MODEL_CGB:
|
||||
case GB_MODEL_SCGB:
|
||||
biosName = "gbc_bios.bin";
|
||||
break;
|
||||
case GB_MODEL_SGB:
|
||||
|
@ -1385,8 +1386,8 @@ static void _updateRotation(struct mRotationSource* source) {
|
|||
gyroZ = 0;
|
||||
_initSensors();
|
||||
if (tiltEnabled) {
|
||||
tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * -2e8f;
|
||||
tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * 2e8f;
|
||||
tiltX = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_X) * 3e8f;
|
||||
tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * -3e8f;
|
||||
}
|
||||
if (gyroEnabled) {
|
||||
gyroZ = sensorGetCallback(0, RETRO_SENSOR_GYROSCOPE_Z) * -5.5e8f;
|
||||
|
|
|
@ -41,7 +41,7 @@ static void mGLContextInit(struct VideoBackend* v, WHandle handle) {
|
|||
_initTex();
|
||||
context->activeTex = 0;
|
||||
|
||||
glGenTextures(VIDEO_LAYER_MAX, context->tex);
|
||||
glGenTextures(VIDEO_LAYER_MAX, context->layers);
|
||||
int i;
|
||||
for (i = 0; i < VIDEO_LAYER_MAX; ++i) {
|
||||
glBindTexture(GL_TEXTURE_2D, context->layers[i]);
|
||||
|
|
|
@ -46,6 +46,7 @@ static uint32_t _pollInput(const struct mInputMap* map) {
|
|||
SceCtrlData pad;
|
||||
sceCtrlPeekBufferPositiveExt2(0, &pad, 1);
|
||||
int input = mInputMapKeyBits(map, PSP2_INPUT, pad.buttons, 0);
|
||||
input |= mPSP2ReadTouchLR(map);
|
||||
|
||||
if (pad.buttons & SCE_CTRL_UP || pad.ly < 64) {
|
||||
input |= 1 << GUI_INPUT_UP;
|
||||
|
@ -248,6 +249,7 @@ int main() {
|
|||
};
|
||||
|
||||
sceTouchSetSamplingState(SCE_TOUCH_PORT_FRONT, SCE_TOUCH_SAMPLING_STATE_START);
|
||||
sceTouchSetSamplingState(SCE_TOUCH_PORT_BACK, SCE_TOUCH_SAMPLING_STATE_START);
|
||||
sceCtrlSetSamplingMode(SCE_CTRL_MODE_ANALOG_WIDE);
|
||||
sceCtrlSetSamplingModeExt(SCE_CTRL_MODE_ANALOG_WIDE);
|
||||
sceSysmoduleLoadModule(SCE_SYSMODULE_PHOTO_EXPORT);
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#include <psp2/gxm.h>
|
||||
#include <psp2/kernel/sysmem.h>
|
||||
#include <psp2/motion.h>
|
||||
#include <psp2/touch.h>
|
||||
|
||||
#include <vita2d.h>
|
||||
|
||||
|
@ -57,6 +58,7 @@ static double fpsRatio = 1;
|
|||
static bool interframeBlending = false;
|
||||
static bool sgbCrop = false;
|
||||
static bool blurry = false;
|
||||
static SceTouchPanelInfo panelInfo[SCE_TOUCH_PORT_MAX_NUM];
|
||||
|
||||
static struct mSceRotationSource {
|
||||
struct mRotationSource d;
|
||||
|
@ -290,6 +292,8 @@ uint16_t mPSP2PollInput(struct mGUIRunner* runner) {
|
|||
if (angles != GBA_KEY_NONE) {
|
||||
activeKeys |= 1 << angles;
|
||||
}
|
||||
activeKeys |= mPSP2ReadTouchLR(&runner->core->inputMap);
|
||||
|
||||
return activeKeys;
|
||||
}
|
||||
|
||||
|
@ -313,6 +317,9 @@ void mPSP2Setup(struct mGUIRunner* runner) {
|
|||
mCoreConfigSetDefaultIntValue(&runner->config, "threadedVideo", 1);
|
||||
mCoreLoadForeignConfig(runner->core, &runner->config);
|
||||
|
||||
sceTouchGetPanelInfo(SCE_TOUCH_PORT_FRONT, &panelInfo[SCE_TOUCH_PORT_FRONT]);
|
||||
sceTouchGetPanelInfo(SCE_TOUCH_PORT_BACK, &panelInfo[SCE_TOUCH_PORT_BACK]);
|
||||
|
||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CROSS, GBA_KEY_A);
|
||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_CIRCLE, GBA_KEY_B);
|
||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_START, GBA_KEY_START);
|
||||
|
@ -406,18 +413,6 @@ void mPSP2LoadROM(struct mGUIRunner* runner) {
|
|||
|
||||
mCoreConfigGetBoolValue(&runner->config, "interframeBlending", &interframeBlending);
|
||||
|
||||
// Backcompat: Old versions of mGBA use an older binding system that has different mappings for L/R
|
||||
if (!sceKernelIsPSVitaTV()) {
|
||||
int key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_L2));
|
||||
if (key >= 0) {
|
||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_L1, key);
|
||||
}
|
||||
key = mInputMapKey(&runner->core->inputMap, PSP2_INPUT, __builtin_ctz(SCE_CTRL_R2));
|
||||
if (key >= 0) {
|
||||
mPSP2MapKey(&runner->core->inputMap, SCE_CTRL_R1, key);
|
||||
}
|
||||
}
|
||||
|
||||
MutexInit(&audioContext.mutex);
|
||||
ConditionInit(&audioContext.cond);
|
||||
mAudioBufferClear(&audioContext.buffer);
|
||||
|
@ -664,6 +659,39 @@ bool mPSP2SystemPoll(struct mGUIRunner* runner) {
|
|||
return true;
|
||||
}
|
||||
|
||||
int mPSP2ReadTouchLR(const struct mInputMap* map) {
|
||||
SceTouchData touch[4];
|
||||
int activeKeys = 0;
|
||||
int touches = sceTouchPeek(SCE_TOUCH_PORT_BACK, touch, 4);
|
||||
int i;
|
||||
for (i = 0; i < touches; ++i) {
|
||||
if (touch[i].reportNum < 1) {
|
||||
continue;
|
||||
}
|
||||
bool left = touch[i].report[0].x < (panelInfo[SCE_TOUCH_PORT_BACK].maxAaX - panelInfo[SCE_TOUCH_PORT_BACK].minAaX) / 2;
|
||||
bool top = touch[i].report[0].y < (panelInfo[SCE_TOUCH_PORT_BACK].maxAaY - panelInfo[SCE_TOUCH_PORT_BACK].minAaY) / 2;
|
||||
int button;
|
||||
if (left) {
|
||||
if (top) {
|
||||
button = __builtin_ctz(SCE_CTRL_L2);
|
||||
} else {
|
||||
button = __builtin_ctz(SCE_CTRL_L3);
|
||||
}
|
||||
} else {
|
||||
if (top) {
|
||||
button = __builtin_ctz(SCE_CTRL_R2);
|
||||
} else {
|
||||
button = __builtin_ctz(SCE_CTRL_R3);
|
||||
}
|
||||
}
|
||||
int key = mInputMapKey(map, PSP2_INPUT, button);
|
||||
if (key != -1) {
|
||||
activeKeys |= 1 << key;
|
||||
}
|
||||
}
|
||||
return activeKeys;
|
||||
}
|
||||
|
||||
__attribute__((noreturn, weak)) void __assert_func(const char* file, int line, const char* func, const char* expr) {
|
||||
printf("ASSERT FAILED: %s in %s at %s:%i\n", expr, func, file, line);
|
||||
exit(1);
|
||||
|
|
|
@ -28,4 +28,6 @@ void mPSP2SetFrameLimiter(struct mGUIRunner* runner, bool limit);
|
|||
uint16_t mPSP2PollInput(struct mGUIRunner* runner);
|
||||
bool mPSP2SystemPoll(struct mGUIRunner* runner);
|
||||
|
||||
int mPSP2ReadTouchLR(const struct mInputMap* map);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -74,7 +74,7 @@ AboutScreen::AboutScreen(QWidget* parent)
|
|||
|
||||
{
|
||||
QString copyright = m_ui.copyright->text();
|
||||
copyright.replace("{year}", QLatin1String("2023"));
|
||||
copyright.replace("{year}", QLatin1String("2025"));
|
||||
m_ui.copyright->setText(copyright);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -220,6 +220,11 @@ set(GB_SRC
|
|||
GBOverride.cpp
|
||||
PrinterView.cpp)
|
||||
|
||||
set(TEST_QT_spanset_SRC
|
||||
test/spanset.cpp
|
||||
utils.cpp
|
||||
VFileDevice.cpp)
|
||||
|
||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS},libqt${QT_V}widgets${QT_V}")
|
||||
|
||||
set(AUDIO_SRC)
|
||||
|
@ -274,8 +279,15 @@ if(USE_SQLITE3)
|
|||
list(APPEND SOURCE_FILES
|
||||
ArchiveInspector.cpp
|
||||
library/LibraryController.cpp
|
||||
library/LibraryGrid.cpp
|
||||
library/LibraryTree.cpp)
|
||||
library/LibraryEntry.cpp
|
||||
library/LibraryModel.cpp)
|
||||
|
||||
set(TEST_QT_library_SRC
|
||||
library/LibraryEntry.cpp
|
||||
library/LibraryModel.cpp
|
||||
test/library.cpp
|
||||
utils.cpp
|
||||
VFileDevice.cpp)
|
||||
endif()
|
||||
|
||||
if(USE_DISCORD_RPC)
|
||||
|
@ -285,12 +297,15 @@ endif()
|
|||
|
||||
if(ENABLE_SCRIPTING)
|
||||
list(APPEND SOURCE_FILES
|
||||
scripting/AutorunScriptModel.cpp
|
||||
scripting/AutorunScriptView.cpp
|
||||
scripting/ScriptingController.cpp
|
||||
scripting/ScriptingTextBuffer.cpp
|
||||
scripting/ScriptingTextBufferModel.cpp
|
||||
scripting/ScriptingView.cpp)
|
||||
|
||||
list(APPEND UI_FILES
|
||||
scripting/AutorunScriptView.ui
|
||||
scripting/ScriptingView.ui)
|
||||
endif()
|
||||
|
||||
|
@ -496,7 +511,7 @@ if(APPLE)
|
|||
set_source_files_properties("${COREAUDIO}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns)
|
||||
set_source_files_properties("${QTAVFSERVICE}" PROPERTIES MACOSX_PACKAGE_LOCATION Contents/PlugIns)
|
||||
endif()
|
||||
|
||||
|
||||
install(CODE "
|
||||
include(BundleUtilities)
|
||||
set(BU_CHMOD_BUNDLE_ITEMS ON)
|
||||
|
@ -539,6 +554,25 @@ elseif(WIN32)
|
|||
endif()
|
||||
|
||||
debug_strip(${BINARY_NAME}-qt)
|
||||
|
||||
if(BUILD_SUITE)
|
||||
enable_testing()
|
||||
find_package(${QT}Test)
|
||||
if(${QT}Test_FOUND)
|
||||
get_property(ALL_TESTS DIRECTORY PROPERTY VARIABLES)
|
||||
list(FILTER ALL_TESTS INCLUDE REGEX "^TEST_QT_.*_SRC$")
|
||||
foreach(TEST_SRC ${ALL_TESTS})
|
||||
string(REGEX REPLACE "^TEST_QT_(.*)_SRC$" "\\1" TEST_NAME ${TEST_SRC})
|
||||
add_executable(test-qt-${TEST_NAME} WIN32 ${${TEST_SRC}})
|
||||
target_link_libraries(test-qt-${TEST_NAME} ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES} ${QT}::Test)
|
||||
set_target_properties(test-qt-${TEST_NAME} PROPERTIES COMPILE_DEFINITIONS "${FEATURE_DEFINES};${FUNCTION_DEFINES};${OS_DEFINES};${QT_DEFINES}" COMPILE_OPTIONS "${FEATURE_FLAGS}")
|
||||
add_test(platform-qt-${TEST_NAME} test-qt-${TEST_NAME})
|
||||
endforeach()
|
||||
else()
|
||||
message(WARNING "${QT}Test not found")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
install(TARGETS ${BINARY_NAME}-qt
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt
|
||||
BUNDLE DESTINATION ${APPDIR} COMPONENT ${BINARY_NAME}-qt)
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include "ActionMapper.h"
|
||||
#include "CoreController.h"
|
||||
#include "scripting/AutorunScriptModel.h"
|
||||
|
||||
#include <QDir>
|
||||
#include <QMenu>
|
||||
|
@ -122,6 +123,8 @@ QString ConfigController::s_configDir;
|
|||
ConfigController::ConfigController(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
qRegisterMetaType<AutorunScriptModel::ScriptInfo>();
|
||||
|
||||
QString fileName = configDir();
|
||||
fileName.append(QDir::separator());
|
||||
fileName.append("qt.ini");
|
||||
|
@ -379,6 +382,36 @@ void ConfigController::setMRU(const QStringList& mru, ConfigController::MRU mruT
|
|||
m_settings->endGroup();
|
||||
}
|
||||
|
||||
QList<QVariant> ConfigController::getList(const QString& group) const {
|
||||
QList<QVariant> list;
|
||||
m_settings->beginGroup(group);
|
||||
for (int i = 0; ; ++i) {
|
||||
QVariant item = m_settings->value(QString::number(i));
|
||||
if (item.isNull() || !item.isValid()) {
|
||||
break;
|
||||
}
|
||||
list.append(item);
|
||||
}
|
||||
m_settings->endGroup();
|
||||
return list;
|
||||
}
|
||||
|
||||
void ConfigController::setList(const QString& group, const QList<QVariant>& list) {
|
||||
int i = 0;
|
||||
m_settings->beginGroup(group);
|
||||
QStringList keys = m_settings->childKeys();
|
||||
for (const QVariant& item : list) {
|
||||
QString key = QString::number(i);
|
||||
keys.removeAll(key);
|
||||
m_settings->setValue(key, item);
|
||||
++i;
|
||||
}
|
||||
for (const auto& key: keys) {
|
||||
m_settings->remove(key);
|
||||
}
|
||||
m_settings->endGroup();
|
||||
}
|
||||
|
||||
constexpr const char* ConfigController::mruName(ConfigController::MRU mru) {
|
||||
switch (mru) {
|
||||
case MRU::ROM:
|
||||
|
|
|
@ -90,7 +90,7 @@ public:
|
|||
QVariant takeArgvOption(const QString& key);
|
||||
|
||||
QStringList getMRU(MRU = MRU::ROM) const;
|
||||
void setMRU(const QStringList& mru, MRU = MRU::ROM);
|
||||
QList<QVariant> getList(const QString& group) const;
|
||||
|
||||
Configuration* overrides() { return mCoreConfigGetOverrides(&m_config); }
|
||||
void saveOverride(const Override&);
|
||||
|
@ -116,6 +116,8 @@ public slots:
|
|||
void setOption(const char* key, const char* value);
|
||||
void setOption(const char* key, const QVariant& value);
|
||||
void setQtOption(const QString& key, const QVariant& value, const QString& group = QString());
|
||||
void setMRU(const QStringList& mru, MRU = MRU::ROM);
|
||||
void setList(const QString& group, const QList<QVariant>& list);
|
||||
|
||||
void makePortable();
|
||||
void write();
|
||||
|
|
|
@ -205,7 +205,8 @@ CoreController::CoreController(mCore* core, QObject* parent)
|
|||
}
|
||||
message = QString::vasprintf(format, args);
|
||||
QMetaObject::invokeMethod(controller, "logPosted", Q_ARG(int, level), Q_ARG(int, category), Q_ARG(const QString&, message));
|
||||
if (level == mLOG_FATAL) {
|
||||
if (level == mLOG_FATAL && !controller->m_crashSeen) {
|
||||
controller->m_crashSeen = true;
|
||||
QMetaObject::invokeMethod(controller, "crashed", Q_ARG(const QString&, message));
|
||||
}
|
||||
};
|
||||
|
@ -500,6 +501,7 @@ void CoreController::stop() {
|
|||
}
|
||||
|
||||
void CoreController::reset() {
|
||||
m_crashSeen = false;
|
||||
mCoreThreadReset(&m_threadContext);
|
||||
}
|
||||
|
||||
|
@ -651,6 +653,7 @@ void CoreController::loadState(int slot) {
|
|||
m_stateSlot = slot;
|
||||
m_backupSaveState.clear();
|
||||
}
|
||||
m_crashSeen = false;
|
||||
mCoreThreadClearCrashed(&m_threadContext);
|
||||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||
|
@ -671,6 +674,7 @@ void CoreController::loadState(const QString& path, int flags) {
|
|||
if (flags != -1) {
|
||||
m_loadStateFlags = flags;
|
||||
}
|
||||
m_crashSeen = false;
|
||||
mCoreThreadClearCrashed(&m_threadContext);
|
||||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||
|
@ -700,6 +704,7 @@ void CoreController::loadState(QIODevice* iodev, int flags) {
|
|||
if (flags != -1) {
|
||||
m_loadStateFlags = flags;
|
||||
}
|
||||
m_crashSeen = false;
|
||||
mCoreThreadClearCrashed(&m_threadContext);
|
||||
mCoreThreadRunFunction(&m_threadContext, [](mCoreThread* context) {
|
||||
CoreController* controller = static_cast<CoreController*>(context->userData);
|
||||
|
|
|
@ -257,6 +257,7 @@ private:
|
|||
struct CoreLogger : public mLogger {
|
||||
CoreController* self;
|
||||
} m_logger{};
|
||||
bool m_crashSeen = false;
|
||||
|
||||
QString m_path;
|
||||
QString m_baseDirectory;
|
||||
|
|
|
@ -699,7 +699,13 @@ void PainterGL::resizeContext() {
|
|||
dequeueAll(false);
|
||||
|
||||
mRectangle dims = {0, 0, size.width(), size.height()};
|
||||
if (!m_started) {
|
||||
makeCurrent();
|
||||
}
|
||||
m_backend->setLayerDimensions(m_backend, VIDEO_LAYER_IMAGE, &dims);
|
||||
if (!m_started) {
|
||||
m_gl->doneCurrent();
|
||||
}
|
||||
recenterLayers();
|
||||
m_dims = size;
|
||||
}
|
||||
|
@ -807,11 +813,11 @@ void PainterGL::start() {
|
|||
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
|
||||
}
|
||||
#endif
|
||||
resizeContext();
|
||||
|
||||
m_buffer = nullptr;
|
||||
m_active = true;
|
||||
m_started = true;
|
||||
resizeContext();
|
||||
swapInterval(1);
|
||||
emit started();
|
||||
}
|
||||
|
|
|
@ -594,7 +594,16 @@ void FrameView::exportFrame() {
|
|||
return;
|
||||
}
|
||||
CoreController::Interrupter interrupter(m_controller);
|
||||
m_framebuffer.save(filename, "PNG");
|
||||
|
||||
unsigned width, height;
|
||||
m_vl->currentVideoSize(m_vl, &width, &height);
|
||||
|
||||
if ((int)width != m_framebuffer.width() || (int)height != m_framebuffer.height()) {
|
||||
QImage crop = m_framebuffer.copy(0, 0, width, height);
|
||||
crop.save(filename, "PNG");
|
||||
} else {
|
||||
m_framebuffer.save(filename, "PNG");
|
||||
}
|
||||
}
|
||||
|
||||
void FrameView::reset() {
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
using namespace QGBA;
|
||||
|
||||
GIFView::GIFView(QWidget* parent)
|
||||
GIFView::GIFView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
@ -31,6 +31,8 @@ GIFView::GIFView(QWidget* parent)
|
|||
|
||||
FFmpegEncoderInit(&m_encoder);
|
||||
FFmpegEncoderSetAudio(&m_encoder, nullptr, 0);
|
||||
|
||||
setController(controller);
|
||||
}
|
||||
|
||||
GIFView::~GIFView() {
|
||||
|
|
|
@ -23,7 +23,7 @@ class GIFView : public QWidget {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
GIFView(QWidget* parent = nullptr);
|
||||
GIFView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||
virtual ~GIFView();
|
||||
|
||||
mAVStream* getStream() { return &m_encoder.d; }
|
||||
|
|
|
@ -79,31 +79,31 @@ QString GameBoy::mbcName(GBMemoryBankControllerType mbc) {
|
|||
if (s_mbcNames.isEmpty()) {
|
||||
s_mbcNames[GB_MBC_AUTODETECT] = tr("Autodetect");
|
||||
s_mbcNames[GB_MBC_NONE] = tr("ROM Only");
|
||||
s_mbcNames[GB_MBC1] = tr("MBC1");
|
||||
s_mbcNames[GB_MBC2] = tr("MBC2");
|
||||
s_mbcNames[GB_MBC3] = tr("MBC3");
|
||||
s_mbcNames[GB_MBC3_RTC] = tr("MBC3 + RTC");
|
||||
s_mbcNames[GB_MBC5] = tr("MBC5");
|
||||
s_mbcNames[GB_MBC5_RUMBLE] = tr("MBC5 + Rumble");
|
||||
s_mbcNames[GB_MBC6] = tr("MBC6");
|
||||
s_mbcNames[GB_MBC7] = tr("MBC7 (Tilt)");
|
||||
s_mbcNames[GB_MMM01] = tr("MMM01");
|
||||
s_mbcNames[GB_HuC1] = tr("HuC-1");
|
||||
s_mbcNames[GB_HuC3] = tr("HuC-3");
|
||||
s_mbcNames[GB_POCKETCAM] = tr("Pocket Cam");
|
||||
s_mbcNames[GB_TAMA5] = tr("TAMA5");
|
||||
s_mbcNames[GB_UNL_WISDOM_TREE] = tr("Wisdom Tree");
|
||||
s_mbcNames[GB_UNL_NT_OLD_1] = tr("NT (old 1)");
|
||||
s_mbcNames[GB_UNL_NT_OLD_2] = tr("NT (old 2)");
|
||||
s_mbcNames[GB_UNL_NT_NEW] = tr("NT (new)");
|
||||
s_mbcNames[GB_UNL_PKJD] = tr("Pokémon Jade/Diamond");
|
||||
s_mbcNames[GB_UNL_BBD] = tr("BBD");
|
||||
s_mbcNames[GB_UNL_HITEK] = tr("Hitek");
|
||||
s_mbcNames[GB_UNL_GGB81] = tr("GGB-81");
|
||||
s_mbcNames[GB_UNL_LI_CHENG] = tr("Li Cheng");
|
||||
s_mbcNames[GB_UNL_SACHEN_MMC1] = tr("Sachen (MMC1)");
|
||||
s_mbcNames[GB_UNL_SACHEN_MMC2] = tr("Sachen (MMC2)");
|
||||
s_mbcNames[GB_UNL_SINTAX] = tr("Sintax");
|
||||
s_mbcNames[GB_MBC1] = "MBC1";
|
||||
s_mbcNames[GB_MBC2] = "MBC2";
|
||||
s_mbcNames[GB_MBC3] = "MBC3";
|
||||
s_mbcNames[GB_MBC3_RTC] = tr("%1 + RTC").arg("MBC3");
|
||||
s_mbcNames[GB_MBC5] = "MBC5";
|
||||
s_mbcNames[GB_MBC5_RUMBLE] = tr("%1 + Rumble").arg("MBC5");
|
||||
s_mbcNames[GB_MBC6] = "MBC6";
|
||||
s_mbcNames[GB_MBC7] = tr("%1 (Tilt)").arg("MBC7");
|
||||
s_mbcNames[GB_MMM01] = "MMM01";
|
||||
s_mbcNames[GB_HuC1] = "HuC-1";
|
||||
s_mbcNames[GB_HuC3] = "HuC-3";
|
||||
s_mbcNames[GB_POCKETCAM] = "Pocket Cam";
|
||||
s_mbcNames[GB_TAMA5] = "TAMA5";
|
||||
s_mbcNames[GB_UNL_WISDOM_TREE] = "Wisdom Tree";
|
||||
s_mbcNames[GB_UNL_NT_OLD_1] = tr("%1 (old 1)").arg("NT");
|
||||
s_mbcNames[GB_UNL_NT_OLD_2] = tr("%1 (old 2)").arg("NT");
|
||||
s_mbcNames[GB_UNL_NT_NEW] = tr("%1 (new)").arg("NT");
|
||||
s_mbcNames[GB_UNL_PKJD] = "Pokémon Jade/Diamond";
|
||||
s_mbcNames[GB_UNL_BBD] = "BBD";
|
||||
s_mbcNames[GB_UNL_HITEK] = "Hitek";
|
||||
s_mbcNames[GB_UNL_GGB81] = "GGB-81";
|
||||
s_mbcNames[GB_UNL_LI_CHENG] = "Li Cheng";
|
||||
s_mbcNames[GB_UNL_SACHEN_MMC1] = "Sachen (MMC1)";
|
||||
s_mbcNames[GB_UNL_SACHEN_MMC2] = "Sachen (MMC2)";
|
||||
s_mbcNames[GB_UNL_SINTAX] = "Sintax";
|
||||
}
|
||||
|
||||
return s_mbcNames[mbc];
|
||||
|
|
|
@ -56,7 +56,7 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
m_boundary = 1024;
|
||||
m_ui.tile->setMaxTile(512);
|
||||
m_ui.tile->setMaxTile(1024);
|
||||
m_addressBase = GB_BASE_VRAM;
|
||||
m_addressWidth = 4;
|
||||
m_ui.bgInfo->addCustomProperty("screenBase", tr("Map base"));
|
||||
|
|
|
@ -80,13 +80,17 @@ MemoryModel::MemoryModel(QWidget* parent)
|
|||
|
||||
setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
|
||||
setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
|
||||
int hintWidth;
|
||||
m_margins = QMargins(3, m_cellHeight + 1, 3, 0);
|
||||
#if (QT_VERSION >= QT_VERSION_CHECK(5, 11, 0))
|
||||
m_margins += QMargins(metrics.horizontalAdvance("0FFFFFF0 "), 0, metrics.horizontalAdvance(" AAAAAAAAAAAAAAAA"), 0);
|
||||
hintWidth = metrics.horizontalAdvance(" 0FFFFFF0 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF AAAAAAAAAAAAAAAA");
|
||||
#else
|
||||
m_margins += QMargins(metrics.width("0FFFFFF0 "), 0, metrics.width(" AAAAAAAAAAAAAAAA"), 0);
|
||||
hintWidth = metrics.width(" 0FFFFFF0 FFFFFFFF FFFFFFFF FFFFFFFF FFFFFFFF AAAAAAAAAAAAAAAA");
|
||||
#endif
|
||||
m_cellSize = QSizeF((viewport()->size().width() - (m_margins.left() + m_margins.right())) / 16.0, m_cellHeight);
|
||||
setMinimumWidth(hintWidth);
|
||||
|
||||
connect(verticalScrollBar(), &QSlider::sliderMoved, [this](int position) {
|
||||
m_top = position;
|
||||
|
|
|
@ -17,17 +17,11 @@
|
|||
<item>
|
||||
<widget class="QGBA::MemoryModel" name="hexfield" native="true">
|
||||
<property name="sizePolicy">
|
||||
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
|
||||
<sizepolicy hsizetype="MinimumExpanding" vsizetype="Expanding">
|
||||
<horstretch>0</horstretch>
|
||||
<verstretch>0</verstretch>
|
||||
</sizepolicy>
|
||||
</property>
|
||||
<property name="minimumSize">
|
||||
<size>
|
||||
<width>200</width>
|
||||
<height>0</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
|
|
|
@ -39,6 +39,10 @@ bool glContextHasBug(OpenGLBug bug) {
|
|||
if (renderer == "Intel Pineview Platform") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (version == "2.1.0 - Build 8.15.10.2900") {
|
||||
return true;
|
||||
}
|
||||
#endif
|
||||
return false;
|
||||
|
||||
|
|
|
@ -15,6 +15,15 @@
|
|||
|
||||
using namespace QGBA;
|
||||
|
||||
template<size_t N> bool isZeroed(const uint8_t* mem) {
|
||||
for (size_t i = 0; i < N; ++i) {
|
||||
if (mem[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
ROMInfo::ROMInfo(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
|
||||
{
|
||||
|
@ -25,6 +34,7 @@ ROMInfo::ROMInfo(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
#endif
|
||||
uint32_t crc32 = 0;
|
||||
uint8_t md5[16]{};
|
||||
uint8_t sha1[20]{};
|
||||
|
||||
CoreController::Interrupter interrupter(controller);
|
||||
mCore* core = controller->thread()->core;
|
||||
|
@ -41,33 +51,48 @@ ROMInfo::ROMInfo(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
|
||||
core->checksum(core, &crc32, mCHECKSUM_CRC32);
|
||||
core->checksum(core, &md5, mCHECKSUM_MD5);
|
||||
core->checksum(core, &sha1, mCHECKSUM_SHA1);
|
||||
|
||||
m_ui.size->setText(QString::number(core->romSize(core)) + tr(" bytes"));
|
||||
|
||||
if (crc32) {
|
||||
m_ui.crc->setText(QString::number(crc32, 16));
|
||||
#ifdef USE_SQLITE3
|
||||
if (db) {
|
||||
NoIntroGame game{};
|
||||
if (NoIntroDBLookupGameByCRC(db, crc32, &game)) {
|
||||
m_ui.name->setText(game.name);
|
||||
} else {
|
||||
m_ui.name->setText(tr("(unknown)"));
|
||||
}
|
||||
} else {
|
||||
m_ui.name->setText(tr("(no database present)"));
|
||||
}
|
||||
#else
|
||||
m_ui.name->hide();
|
||||
#endif
|
||||
} else {
|
||||
m_ui.crc->setText(tr("(unknown)"));
|
||||
m_ui.name->setText(tr("(unknown)"));
|
||||
}
|
||||
|
||||
m_ui.md5->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
md5[0x0], md5[0x1], md5[0x2], md5[0x3], md5[0x4], md5[0x5], md5[0x6], md5[0x7],
|
||||
md5[0x8], md5[0x9], md5[0xA], md5[0xB], md5[0xC], md5[0xD], md5[0xE], md5[0xF]));
|
||||
if (!isZeroed<16>(md5)) {
|
||||
m_ui.md5->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
md5[0x0], md5[0x1], md5[0x2], md5[0x3], md5[0x4], md5[0x5], md5[0x6], md5[0x7],
|
||||
md5[0x8], md5[0x9], md5[0xA], md5[0xB], md5[0xC], md5[0xD], md5[0xE], md5[0xF]));
|
||||
} else {
|
||||
m_ui.md5->setText(tr("(unknown)"));
|
||||
}
|
||||
|
||||
if (!isZeroed<20>(sha1)) {
|
||||
m_ui.sha1->setText(QString::asprintf("%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x",
|
||||
sha1[ 0], sha1[ 1], sha1[ 2], sha1[ 3], sha1[ 4], sha1[ 5], sha1[ 6], sha1[ 7], sha1[ 8], sha1[ 9],
|
||||
sha1[10], sha1[11], sha1[12], sha1[13], sha1[14], sha1[15], sha1[16], sha1[17], sha1[18], sha1[19]));
|
||||
} else {
|
||||
m_ui.sha1->setText(tr("(unknown)"));
|
||||
}
|
||||
|
||||
#ifdef USE_SQLITE3
|
||||
if (db) {
|
||||
NoIntroGame game{};
|
||||
if (!isZeroed<20>(sha1) && NoIntroDBLookupGameBySHA1(db, sha1, &game)) {
|
||||
m_ui.name->setText(game.name);
|
||||
} else if (crc32 && NoIntroDBLookupGameByCRC(db, crc32, &game)) {
|
||||
m_ui.name->setText(game.name);
|
||||
} else {
|
||||
m_ui.name->setText(tr("(unknown)"));
|
||||
}
|
||||
} else {
|
||||
m_ui.name->setText(tr("(no database present)"));
|
||||
}
|
||||
#else
|
||||
m_ui.name->hide();
|
||||
#endif
|
||||
|
||||
QString savePath = controller->savePath();
|
||||
if (!savePath.isEmpty()) {
|
||||
|
|
|
@ -95,13 +95,30 @@
|
|||
</widget>
|
||||
</item>
|
||||
<item row="4" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>SHA-1</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QLabel" name="sha1">
|
||||
<property name="text">
|
||||
<string notr="true">{SHA1}</string>
|
||||
</property>
|
||||
<property name="textInteractionFlags">
|
||||
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Save file:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<item row="5" column="1">
|
||||
<widget class="QLabel" name="savefile">
|
||||
<property name="text">
|
||||
<string notr="true">{SAVEFILE}</string>
|
||||
|
|
|
@ -395,7 +395,7 @@ SaveConverter::AnnotatedSave::AnnotatedSave(mPlatform platform, std::shared_ptr<
|
|||
: container(container)
|
||||
, platform(platform)
|
||||
, size(vf->size())
|
||||
, backing(vf)
|
||||
, backing(std::move(vf))
|
||||
, endianness(endianness)
|
||||
{
|
||||
}
|
||||
|
@ -405,7 +405,7 @@ SaveConverter::AnnotatedSave::AnnotatedSave(GBASavedataType type, std::shared_pt
|
|||
: container(container)
|
||||
, platform(mPLATFORM_GBA)
|
||||
, size(vf->size())
|
||||
, backing(vf)
|
||||
, backing(std::move(vf))
|
||||
, endianness(endianness)
|
||||
, gba({type})
|
||||
{
|
||||
|
@ -417,7 +417,7 @@ SaveConverter::AnnotatedSave::AnnotatedSave(GBMemoryBankControllerType type, std
|
|||
: container(container)
|
||||
, platform(mPLATFORM_GB)
|
||||
, size(vf->size())
|
||||
, backing(vf)
|
||||
, backing(std::move(vf))
|
||||
, endianness(endianness)
|
||||
, gb({type})
|
||||
{
|
||||
|
|
|
@ -69,6 +69,8 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
|
|||
|
||||
reloadConfig();
|
||||
|
||||
connect(m_ui.autorunScripts, &QAbstractButton::pressed, this, &SettingsView::openAutorunScripts);
|
||||
|
||||
connect(m_ui.volume, static_cast<void (QSlider::*)(int)>(&QSlider::valueChanged), [this](int v) {
|
||||
if (v < m_ui.volumeFf->value()) {
|
||||
m_ui.volumeFf->setValue(v);
|
||||
|
|
|
@ -63,6 +63,7 @@ signals:
|
|||
void languageChanged();
|
||||
void libraryCleared();
|
||||
void saveSettingsRequested();
|
||||
void openAutorunScripts();
|
||||
|
||||
public slots:
|
||||
void selectPage(Page);
|
||||
|
|
|
@ -569,14 +569,21 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<item row="6" column="1">
|
||||
<widget class="QPushButton" name="autorunScripts">
|
||||
<property name="text">
|
||||
<string>Edit autorun scripts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="Line" name="line_9">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="1">
|
||||
<item row="8" column="1">
|
||||
<widget class="QCheckBox" name="autosave">
|
||||
<property name="text">
|
||||
<string>Periodically autosave state</string>
|
||||
|
@ -586,7 +593,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="1">
|
||||
<item row="9" column="1">
|
||||
<widget class="QCheckBox" name="cheatAutosave">
|
||||
<property name="text">
|
||||
<string>Save entered cheats</string>
|
||||
|
@ -596,21 +603,21 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<item row="10" column="0" colspan="2">
|
||||
<widget class="Line" name="line_21">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<item row="11" column="0">
|
||||
<widget class="QLabel" name="label_51">
|
||||
<property name="text">
|
||||
<string>Save state extra data:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1">
|
||||
<item row="11" column="1">
|
||||
<widget class="QCheckBox" name="saveStateScreenshot">
|
||||
<property name="text">
|
||||
<string>Screenshot</string>
|
||||
|
@ -620,7 +627,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<item row="12" column="1">
|
||||
<widget class="QCheckBox" name="saveStateSave">
|
||||
<property name="text">
|
||||
<string>Save game</string>
|
||||
|
@ -630,7 +637,7 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="1">
|
||||
<item row="13" column="1">
|
||||
<widget class="QCheckBox" name="saveStateCheats">
|
||||
<property name="text">
|
||||
<string>Cheat codes</string>
|
||||
|
@ -640,21 +647,21 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0" colspan="2">
|
||||
<item row="14" column="0" colspan="2">
|
||||
<widget class="Line" name="line_22">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<item row="15" column="0">
|
||||
<widget class="QLabel" name="label_52">
|
||||
<property name="text">
|
||||
<string>Load state extra data:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="1">
|
||||
<item row="15" column="1">
|
||||
<widget class="QCheckBox" name="loadStateScreenshot">
|
||||
<property name="text">
|
||||
<string>Screenshot</string>
|
||||
|
@ -664,28 +671,28 @@
|
|||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="15" column="1">
|
||||
<item row="16" column="1">
|
||||
<widget class="QCheckBox" name="loadStateSave">
|
||||
<property name="text">
|
||||
<string>Save game</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="16" column="1">
|
||||
<item row="17" column="1">
|
||||
<widget class="QCheckBox" name="loadStateCheats">
|
||||
<property name="text">
|
||||
<string>Cheat codes</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="17" column="0" colspan="2">
|
||||
<item row="18" column="0" colspan="2">
|
||||
<widget class="Line" name="line_2">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="18" column="1">
|
||||
<item row="19" column="1">
|
||||
<widget class="QCheckBox" name="useDiscordPresence">
|
||||
<property name="text">
|
||||
<string>Enable Discord Rich Presence</string>
|
||||
|
|
|
@ -61,7 +61,7 @@ TileView::TileView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
|||
m_ui.tilesBoth->setEnabled(false);
|
||||
m_ui.palette256->setEnabled(false);
|
||||
m_ui.tile->setBoundary(1024, 0, 0);
|
||||
m_ui.tile->setMaxTile(512);
|
||||
m_ui.tile->setMaxTile(896);
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
|
@ -201,7 +201,8 @@ void TileView::updateTilesGBA(bool force) {
|
|||
#ifdef M_CORE_GB
|
||||
void TileView::updateTilesGB(bool force) {
|
||||
const GB* gb = static_cast<const GB*>(m_controller->thread()->core->board);
|
||||
int count = gb->model >= GB_MODEL_CGB ? 1024 : 512;
|
||||
// TODO: Strip out tiles 384-511, as they aren't valid
|
||||
int count = gb->model >= GB_MODEL_CGB ? 896 : 384;
|
||||
m_ui.tiles->setTileCount(count);
|
||||
mTileCache* cache = mTileCacheSetGetPointer(&m_cacheSet->tiles, 0);
|
||||
for (int i = 0; i < count; ++i) {
|
||||
|
|
|
@ -47,7 +47,7 @@ bool VideoView::Preset::compatible(const Preset& other) const {
|
|||
return true;
|
||||
}
|
||||
|
||||
VideoView::VideoView(QWidget* parent)
|
||||
VideoView::VideoView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||
: QWidget(parent)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
@ -65,6 +65,7 @@ VideoView::VideoView(QWidget* parent)
|
|||
s_vcodecMap["hevc"] = "libx265";
|
||||
s_vcodecMap["hevc nvenc"] = "hevc_nvenc";
|
||||
s_vcodecMap["theora"] = "libtheora";
|
||||
s_vcodecMap["ut video"] = "utvideo";
|
||||
s_vcodecMap["vp8"] = "libvpx";
|
||||
s_vcodecMap["vp9"] = "libvpx-vp9";
|
||||
s_vcodecMap["xvid"] = "libxvid";
|
||||
|
@ -132,6 +133,8 @@ VideoView::VideoView(QWidget* parent)
|
|||
|
||||
m_ui.presetYoutube->setChecked(true); // Use the Youtube preset by default
|
||||
showAdvanced(false);
|
||||
|
||||
setController(controller);
|
||||
}
|
||||
|
||||
void VideoView::updatePresets() {
|
||||
|
|
|
@ -26,7 +26,7 @@ class VideoView : public QWidget {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
VideoView(QWidget* parent = nullptr);
|
||||
VideoView(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
|
||||
virtual ~VideoView();
|
||||
|
||||
mAVStream* getStream() { return &m_encoder.d; }
|
||||
|
|
|
@ -302,6 +302,11 @@
|
|||
<string notr="true">VP9</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">Ut Video</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">FFV1</string>
|
||||
|
|
|
@ -305,6 +305,10 @@ void Window::loadConfig() {
|
|||
updateMRU();
|
||||
|
||||
m_inputController.setConfiguration(m_config);
|
||||
|
||||
if (!m_config->getList("autorunSettings").isEmpty()) {
|
||||
ensureScripting();
|
||||
}
|
||||
}
|
||||
|
||||
void Window::reloadConfig() {
|
||||
|
@ -550,6 +554,10 @@ void Window::openSettingsWindow(SettingsView::Page page) {
|
|||
#ifdef USE_SQLITE3
|
||||
connect(settingsWindow, &SettingsView::libraryCleared, m_libraryView, &LibraryController::clear);
|
||||
#endif
|
||||
connect(settingsWindow, &SettingsView::openAutorunScripts, this, [this]() {
|
||||
ensureScripting();
|
||||
m_scripting->openAutorunEdit();
|
||||
});
|
||||
connect(this, &Window::shaderSelectorAdded, settingsWindow, &SettingsView::setShaderSelector);
|
||||
openView(settingsWindow);
|
||||
settingsWindow->selectPage(page);
|
||||
|
@ -580,29 +588,35 @@ std::function<void()> Window::openControllerTView(A... arg) {
|
|||
}
|
||||
|
||||
template <typename T, typename... A>
|
||||
std::function<void()> Window::openNamedTView(std::unique_ptr<T>* name, A... arg) {
|
||||
std::function<void()> Window::openNamedTView(QPointer<T>* name, bool keepalive, A... arg) {
|
||||
return [=]() {
|
||||
if (!*name) {
|
||||
*name = std::make_unique<T>(arg...);
|
||||
connect(this, &Window::shutdown, name->get(), &QWidget::close);
|
||||
*name = new T(arg...);
|
||||
connect(this, &Window::shutdown, name->data(), &QWidget::close);
|
||||
if (!keepalive) {
|
||||
(*name)->setAttribute(Qt::WA_DeleteOnClose);
|
||||
}
|
||||
}
|
||||
(*name)->show();
|
||||
(*name)->setFocus(Qt::PopupFocusReason);
|
||||
(*name)->activateWindow();
|
||||
(*name)->raise();
|
||||
};
|
||||
}
|
||||
|
||||
template <typename T, typename... A>
|
||||
std::function<void()> Window::openNamedControllerTView(std::unique_ptr<T>* name, A... arg) {
|
||||
std::function<void()> Window::openNamedControllerTView(QPointer<T>* name, bool keepalive, A... arg) {
|
||||
return [=]() {
|
||||
if (!*name) {
|
||||
*name = std::make_unique<T>(arg...);
|
||||
if (m_controller) {
|
||||
(*name)->setController(m_controller);
|
||||
*name = new T(m_controller, arg...);
|
||||
connect(m_controller.get(), &CoreController::stopping, name->data(), &QWidget::close);
|
||||
connect(this, &Window::shutdown, name->data(), &QWidget::close);
|
||||
if (!keepalive) {
|
||||
(*name)->setAttribute(Qt::WA_DeleteOnClose);
|
||||
}
|
||||
connect(this, &Window::shutdown, name->get(), &QWidget::close);
|
||||
}
|
||||
(*name)->show();
|
||||
(*name)->setFocus(Qt::PopupFocusReason);
|
||||
(*name)->activateWindow();
|
||||
(*name)->raise();
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -633,17 +647,7 @@ void Window::consoleOpen() {
|
|||
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
void Window::scriptingOpen() {
|
||||
if (!m_scripting) {
|
||||
m_scripting = std::make_unique<ScriptingController>();
|
||||
m_scripting->setInputController(&m_inputController);
|
||||
m_shortcutController->setScriptingController(m_scripting.get());
|
||||
if (m_controller) {
|
||||
m_scripting->setController(m_controller);
|
||||
m_display->installEventFilter(m_scripting.get());
|
||||
}
|
||||
|
||||
m_scripting->setVideoBackend(m_display->videoBackend());
|
||||
}
|
||||
ensureScripting();
|
||||
ScriptingView* view = new ScriptingView(m_scripting.get(), m_config);
|
||||
openView(view);
|
||||
}
|
||||
|
@ -1101,7 +1105,7 @@ void Window::reloadDisplayDriver() {
|
|||
if (!proxy) {
|
||||
proxy = std::make_shared<VideoProxy>();
|
||||
}
|
||||
m_display->setVideoProxy(proxy);
|
||||
m_display->setVideoProxy(std::move(proxy));
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
if (m_scripting) {
|
||||
m_scripting->setVideoBackend(m_display->videoBackend());
|
||||
|
@ -1414,7 +1418,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
m_multiWindow = m_actions.addAction(tr("New multiplayer window"), "multiWindow", GBAApp::app(), &GBAApp::newWindow, "file");
|
||||
|
||||
#ifdef M_CORE_GBA
|
||||
auto dolphin = m_actions.addAction(tr("Connect to Dolphin..."), "connectDolphin", openNamedTView<DolphinConnector>(&m_dolphinView, this), "file");
|
||||
auto dolphin = m_actions.addAction(tr("Connect to Dolphin..."), "connectDolphin", openNamedTView<DolphinConnector>(&m_dolphinView, true, this), "file");
|
||||
m_platformActions.insert(mPLATFORM_GBA, dolphin);
|
||||
#endif
|
||||
|
||||
|
@ -1695,8 +1699,8 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
#endif
|
||||
|
||||
#ifdef USE_FFMPEG
|
||||
addGameAction(tr("Record A/V..."), "recordOutput", openNamedControllerTView<VideoView>(&m_videoView), "av");
|
||||
addGameAction(tr("Record GIF/WebP/APNG..."), "recordGIF", openNamedControllerTView<GIFView>(&m_gifView), "av");
|
||||
addGameAction(tr("Record A/V..."), "recordOutput", openNamedControllerTView<VideoView>(&m_videoView, true), "av");
|
||||
addGameAction(tr("Record GIF/WebP/APNG..."), "recordGIF", openNamedControllerTView<GIFView>(&m_gifView, true), "av");
|
||||
#endif
|
||||
|
||||
m_actions.addSeparator("av");
|
||||
|
@ -1710,17 +1714,29 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
|
||||
m_actions.addAction(tr("Game &overrides..."), "overrideWindow", [this]() {
|
||||
if (!m_overrideView) {
|
||||
m_overrideView = std::make_unique<OverrideView>(m_config);
|
||||
m_overrideView = new OverrideView(m_config);
|
||||
if (m_controller) {
|
||||
m_overrideView->setController(m_controller);
|
||||
}
|
||||
connect(this, &Window::shutdown, m_overrideView.get(), &QWidget::close);
|
||||
connect(this, &Window::shutdown, m_overrideView.data(), &QWidget::close);
|
||||
}
|
||||
m_overrideView->show();
|
||||
m_overrideView->recheck();
|
||||
m_overrideView->activateWindow();
|
||||
m_overrideView->raise();
|
||||
}, "tools");
|
||||
|
||||
m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", openNamedControllerTView<SensorView>(&m_sensorView, &m_inputController), "tools");
|
||||
m_actions.addAction(tr("Game Pak sensors..."), "sensorWindow", [this]() {
|
||||
if (!m_sensorView) {
|
||||
m_sensorView = new SensorView(&m_inputController);
|
||||
if (m_controller) {
|
||||
m_sensorView->setController(m_controller);
|
||||
}
|
||||
connect(this, &Window::shutdown, m_sensorView.data(), &QWidget::close);
|
||||
}
|
||||
m_sensorView->show();
|
||||
m_sensorView->activateWindow();
|
||||
m_sensorView->raise();
|
||||
}, "tools");
|
||||
|
||||
addGameAction(tr("&Cheats..."), "cheatsWindow", openControllerTView<CheatsView>(), "tools");
|
||||
#ifdef ENABLE_SCRIPTING
|
||||
|
@ -1750,23 +1766,7 @@ void Window::setupMenu(QMenuBar* menubar) {
|
|||
addGameAction(tr("View &sprites..."), "spriteWindow", openControllerTView<ObjView>(), "stateViews");
|
||||
addGameAction(tr("View &tiles..."), "tileWindow", openControllerTView<TileView>(), "stateViews");
|
||||
addGameAction(tr("View &map..."), "mapWindow", openControllerTView<MapView>(), "stateViews");
|
||||
|
||||
addGameAction(tr("&Frame inspector..."), "frameWindow", [this]() {
|
||||
if (!m_frameView) {
|
||||
m_frameView = new FrameView(m_controller);
|
||||
connect(this, &Window::shutdown, this, [this]() {
|
||||
if (m_frameView) {
|
||||
m_frameView->close();
|
||||
}
|
||||
});
|
||||
connect(m_frameView, &QObject::destroyed, this, [this]() {
|
||||
m_frameView = nullptr;
|
||||
});
|
||||
m_frameView->setAttribute(Qt::WA_DeleteOnClose);
|
||||
}
|
||||
m_frameView->show();
|
||||
}, "stateViews");
|
||||
|
||||
addGameAction(tr("&Frame inspector..."), "frameWindow", openNamedControllerTView<FrameView>(&m_frameView, false), "stateViews");
|
||||
addGameAction(tr("View memory..."), "memoryView", openControllerTView<MemoryView>(), "stateViews");
|
||||
addGameAction(tr("Search memory..."), "memorySearch", openControllerTView<MemorySearch>(), "stateViews");
|
||||
addGameAction(tr("View &I/O registers..."), "ioViewer", openControllerTView<IOViewer>(), "stateViews");
|
||||
|
@ -2055,6 +2055,25 @@ void Window::updateMRU() {
|
|||
m_actions.rebuildMenu(menuBar(), this, *m_shortcutController);
|
||||
}
|
||||
|
||||
void Window::ensureScripting() {
|
||||
if (m_scripting) {
|
||||
return;
|
||||
}
|
||||
m_scripting = std::make_unique<ScriptingController>(m_config);
|
||||
m_scripting->setInputController(&m_inputController);
|
||||
m_shortcutController->setScriptingController(m_scripting.get());
|
||||
if (m_controller) {
|
||||
m_scripting->setController(m_controller);
|
||||
m_display->installEventFilter(m_scripting.get());
|
||||
}
|
||||
|
||||
if (m_display) {
|
||||
m_scripting->setVideoBackend(m_display->videoBackend());
|
||||
}
|
||||
|
||||
connect(m_scripting.get(), &ScriptingController::autorunScriptsOpened, this, &Window::openView);
|
||||
}
|
||||
|
||||
std::shared_ptr<Action> Window::addGameAction(const QString& visibleName, const QString& name, Action::Function function, const QString& menu, const QKeySequence& shortcut) {
|
||||
auto action = m_actions.addAction(visibleName, name, [this, function = std::move(function)]() {
|
||||
if (m_controller) {
|
||||
|
|
|
@ -109,6 +109,8 @@ public slots:
|
|||
|
||||
void startVideoLog();
|
||||
|
||||
void openView(QWidget* widget);
|
||||
|
||||
#ifdef ENABLE_DEBUGGERS
|
||||
void consoleOpen();
|
||||
#endif
|
||||
|
@ -173,12 +175,12 @@ private:
|
|||
void clearMRU();
|
||||
void updateMRU();
|
||||
|
||||
void openView(QWidget* widget);
|
||||
void ensureScripting();
|
||||
|
||||
template <typename T, typename... A> std::function<void()> openTView(A... arg);
|
||||
template <typename T, typename... A> std::function<void()> openControllerTView(A... arg);
|
||||
template <typename T, typename... A> std::function<void()> openNamedTView(std::unique_ptr<T>*, A... arg);
|
||||
template <typename T, typename... A> std::function<void()> openNamedControllerTView(std::unique_ptr<T>*, A... arg);
|
||||
template <typename T, typename... A> std::function<void()> openNamedTView(QPointer<T>*, bool keepalive, A... arg);
|
||||
template <typename T, typename... A> std::function<void()> openNamedControllerTView(QPointer<T>*, bool keepalive, A... arg);
|
||||
|
||||
std::shared_ptr<Action> addGameAction(const QString& visibleName, const QString& name, Action::Function action, const QString& menu = {}, const QKeySequence& = {});
|
||||
template<typename T, typename V> std::shared_ptr<Action> addGameAction(const QString& visibleName, const QString& name, T* obj, V (T::*action)(), const QString& menu = {}, const QKeySequence& = {});
|
||||
|
@ -241,14 +243,14 @@ private:
|
|||
bool m_multiActive = true;
|
||||
int m_playerId;
|
||||
|
||||
std::unique_ptr<OverrideView> m_overrideView;
|
||||
std::unique_ptr<SensorView> m_sensorView;
|
||||
std::unique_ptr<DolphinConnector> m_dolphinView;
|
||||
FrameView* m_frameView = nullptr;
|
||||
QPointer<OverrideView> m_overrideView;
|
||||
QPointer<SensorView> m_sensorView;
|
||||
QPointer<DolphinConnector> m_dolphinView;
|
||||
QPointer<FrameView> m_frameView;
|
||||
|
||||
#ifdef USE_FFMPEG
|
||||
std::unique_ptr<VideoView> m_videoView;
|
||||
std::unique_ptr<GIFView> m_gifView;
|
||||
QPointer<VideoView> m_videoView;
|
||||
QPointer<GIFView> m_gifView;
|
||||
#endif
|
||||
|
||||
#ifdef ENABLE_GDB_STUB
|
||||
|
|
|
@ -8,39 +8,17 @@
|
|||
|
||||
#include "ConfigController.h"
|
||||
#include "GBAApp.h"
|
||||
#include "LibraryGrid.h"
|
||||
#include "LibraryTree.h"
|
||||
#include "LibraryModel.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <QHeaderView>
|
||||
#include <QListView>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QTimer>
|
||||
#include <QTreeView>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
LibraryEntry::LibraryEntry(const mLibraryEntry* entry)
|
||||
: base(entry->base)
|
||||
, filename(entry->filename)
|
||||
, fullpath(QString("%1/%2").arg(entry->base, entry->filename))
|
||||
, title(entry->title)
|
||||
, internalTitle(entry->internalTitle)
|
||||
, internalCode(entry->internalCode)
|
||||
, platform(entry->platform)
|
||||
, filesize(entry->filesize)
|
||||
, crc32(entry->crc32)
|
||||
{
|
||||
}
|
||||
|
||||
void AbstractGameList::addEntry(const LibraryEntry& item) {
|
||||
addEntries({item});
|
||||
}
|
||||
|
||||
void AbstractGameList::updateEntry(const LibraryEntry& item) {
|
||||
updateEntries({item});
|
||||
}
|
||||
|
||||
void AbstractGameList::removeEntry(const QString& item) {
|
||||
removeEntries({item});
|
||||
}
|
||||
void AbstractGameList::setShowFilename(bool showFilename) {
|
||||
m_showFilename = showFilename;
|
||||
}
|
||||
|
||||
LibraryController::LibraryController(QWidget* parent, const QString& path, ConfigController* config)
|
||||
: QStackedWidget(parent)
|
||||
, m_config(config)
|
||||
|
@ -55,14 +33,47 @@ LibraryController::LibraryController(QWidget* parent, const QString& path, Confi
|
|||
|
||||
mLibraryAttachGameDB(m_library.get(), GBAApp::app()->gameDB());
|
||||
|
||||
m_libraryTree = std::make_unique<LibraryTree>(this);
|
||||
addWidget(m_libraryTree->widget());
|
||||
m_libraryModel = new LibraryModel(this);
|
||||
|
||||
m_libraryGrid = std::make_unique<LibraryGrid>(this);
|
||||
addWidget(m_libraryGrid->widget());
|
||||
m_treeView = new QTreeView(this);
|
||||
addWidget(m_treeView);
|
||||
m_treeModel = new QSortFilterProxyModel(this);
|
||||
m_treeModel->setSourceModel(m_libraryModel);
|
||||
m_treeModel->setSortRole(Qt::EditRole);
|
||||
m_treeView->setModel(m_treeModel);
|
||||
m_treeView->setSortingEnabled(true);
|
||||
m_treeView->setAlternatingRowColors(true);
|
||||
|
||||
m_currentStyle = LibraryStyle::STYLE_TREE; // Make sure setViewStyle does something
|
||||
setViewStyle(LibraryStyle::STYLE_LIST);
|
||||
m_listView = new QListView(this);
|
||||
addWidget(m_listView);
|
||||
m_listModel = new QSortFilterProxyModel(this);
|
||||
m_listModel->setSourceModel(m_libraryModel);
|
||||
m_listModel->setSortRole(Qt::EditRole);
|
||||
m_listView->setModel(m_listModel);
|
||||
|
||||
QObject::connect(m_treeView, &QAbstractItemView::activated, this, &LibraryController::startGame);
|
||||
QObject::connect(m_listView, &QAbstractItemView::activated, this, &LibraryController::startGame);
|
||||
QObject::connect(m_treeView->header(), &QHeaderView::sortIndicatorChanged, this, &LibraryController::sortChanged);
|
||||
|
||||
m_expandThrottle.setInterval(100);
|
||||
m_expandThrottle.setSingleShot(true);
|
||||
QObject::connect(&m_expandThrottle, &QTimer::timeout, this, qOverload<>(&LibraryController::resizeTreeView));
|
||||
QObject::connect(m_libraryModel, &QAbstractItemModel::modelReset, &m_expandThrottle, qOverload<>(&QTimer::start));
|
||||
QObject::connect(m_libraryModel, &QAbstractItemModel::rowsInserted, &m_expandThrottle, qOverload<>(&QTimer::start));
|
||||
|
||||
LibraryStyle libraryStyle = LibraryStyle(m_config->getOption("libraryStyle", int(LibraryStyle::STYLE_LIST)).toInt());
|
||||
updateViewStyle(libraryStyle);
|
||||
|
||||
QVariant librarySort = m_config->getQtOption("librarySort");
|
||||
QVariant librarySortOrder = m_config->getQtOption("librarySortOrder");
|
||||
if (librarySort.isNull() || !librarySort.canConvert<int>()) {
|
||||
librarySort = 0;
|
||||
}
|
||||
if (librarySortOrder.isNull() || !librarySortOrder.canConvert<Qt::SortOrder>()) {
|
||||
librarySortOrder = Qt::AscendingOrder;
|
||||
}
|
||||
m_treeModel->sort(librarySort.toInt(), librarySortOrder.value<Qt::SortOrder>());
|
||||
m_listModel->sort(0, Qt::AscendingOrder);
|
||||
refresh();
|
||||
}
|
||||
|
||||
|
@ -73,32 +84,63 @@ void LibraryController::setViewStyle(LibraryStyle newStyle) {
|
|||
if (m_currentStyle == newStyle) {
|
||||
return;
|
||||
}
|
||||
m_currentStyle = newStyle;
|
||||
updateViewStyle(newStyle);
|
||||
}
|
||||
|
||||
AbstractGameList* newCurrentList = nullptr;
|
||||
if (newStyle == LibraryStyle::STYLE_LIST || newStyle == LibraryStyle::STYLE_TREE) {
|
||||
newCurrentList = m_libraryTree.get();
|
||||
} else {
|
||||
newCurrentList = m_libraryGrid.get();
|
||||
void LibraryController::updateViewStyle(LibraryStyle newStyle) {
|
||||
QString selected;
|
||||
if (m_currentView) {
|
||||
QModelIndex selectedIndex = m_currentView->selectionModel()->currentIndex();
|
||||
if (selectedIndex.isValid()) {
|
||||
selected = selectedIndex.data(LibraryModel::FullPathRole).toString();
|
||||
}
|
||||
}
|
||||
newCurrentList->selectEntry(selectedEntry().fullpath);
|
||||
newCurrentList->setViewStyle(newStyle);
|
||||
setCurrentWidget(newCurrentList->widget());
|
||||
m_currentList = newCurrentList;
|
||||
|
||||
m_currentStyle = newStyle;
|
||||
m_libraryModel->setTreeMode(newStyle == LibraryStyle::STYLE_TREE);
|
||||
|
||||
QAbstractItemView* newView = m_listView;
|
||||
if (newStyle == LibraryStyle::STYLE_LIST || newStyle == LibraryStyle::STYLE_TREE) {
|
||||
newView = m_treeView;
|
||||
}
|
||||
|
||||
setCurrentWidget(newView);
|
||||
m_currentView = newView;
|
||||
selectEntry(selected);
|
||||
}
|
||||
|
||||
void LibraryController::sortChanged(int column, Qt::SortOrder order) {
|
||||
m_config->setQtOption("librarySort", column);
|
||||
m_config->setQtOption("librarySortOrder", order);
|
||||
}
|
||||
|
||||
void LibraryController::selectEntry(const QString& fullpath) {
|
||||
if (!m_currentList) {
|
||||
if (!m_currentView) {
|
||||
return;
|
||||
}
|
||||
m_currentList->selectEntry(fullpath);
|
||||
QModelIndex index = m_libraryModel->index(fullpath);
|
||||
|
||||
// If the model is proxied in the current view, map the index to the proxy
|
||||
QAbstractProxyModel* proxy = qobject_cast<QAbstractProxyModel*>(m_currentView->model());
|
||||
if (proxy) {
|
||||
index = proxy->mapFromSource(index);
|
||||
}
|
||||
|
||||
if (index.isValid()) {
|
||||
m_currentView->selectionModel()->setCurrentIndex(index, QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Current);
|
||||
}
|
||||
}
|
||||
|
||||
LibraryEntry LibraryController::selectedEntry() {
|
||||
if (!m_currentList) {
|
||||
if (!m_currentView) {
|
||||
return {};
|
||||
}
|
||||
return m_entries.value(m_currentList->selectedEntry());
|
||||
QModelIndex index = m_currentView->selectionModel()->currentIndex();
|
||||
if (!index.isValid()) {
|
||||
return {};
|
||||
}
|
||||
QString fullpath = index.data(LibraryModel::FullPathRole).toString();
|
||||
return m_libraryModel->entry(fullpath);
|
||||
}
|
||||
|
||||
VFile* LibraryController::selectedVFile() {
|
||||
|
@ -110,6 +152,7 @@ VFile* LibraryController::selectedVFile() {
|
|||
libentry.base = baseUtf8.constData();
|
||||
libentry.filename = filenameUtf8.constData();
|
||||
libentry.platform = mPLATFORM_NONE;
|
||||
libentry.platformModels = M_LIBRARY_MODEL_UNKNOWN;
|
||||
return mLibraryOpenVFile(m_library.get(), &libentry);
|
||||
} else {
|
||||
return nullptr;
|
||||
|
@ -149,42 +192,34 @@ void LibraryController::refresh() {
|
|||
|
||||
setDisabled(true);
|
||||
|
||||
QHash<QString, LibraryEntry> removedEntries = m_entries;
|
||||
QHash<QString, LibraryEntry> updatedEntries;
|
||||
QSet<QString> removedEntries(qListToSet(m_knownGames.keys()));
|
||||
QList<LibraryEntry> updatedEntries;
|
||||
QList<LibraryEntry> newEntries;
|
||||
|
||||
mLibraryListing listing;
|
||||
mLibraryListingInit(&listing, 0);
|
||||
mLibraryGetEntries(m_library.get(), &listing, 0, 0, nullptr);
|
||||
for (size_t i = 0; i < mLibraryListingSize(&listing); i++) {
|
||||
LibraryEntry entry = mLibraryListingGetConstPointer(&listing, i);
|
||||
if (!m_entries.contains(entry.fullpath)) {
|
||||
const mLibraryEntry* entry = mLibraryListingGetConstPointer(&listing, i);
|
||||
uint64_t checkHash = LibraryEntry::checkHash(entry);
|
||||
QString fullpath = QStringLiteral("%1/%2").arg(entry->base, entry->filename);
|
||||
if (!m_knownGames.contains(fullpath)) {
|
||||
newEntries.append(entry);
|
||||
} else {
|
||||
updatedEntries[entry.fullpath] = entry;
|
||||
} else if (checkHash != m_knownGames[fullpath]) {
|
||||
updatedEntries.append(entry);
|
||||
}
|
||||
m_entries[entry.fullpath] = entry;
|
||||
removedEntries.remove(entry.fullpath);
|
||||
removedEntries.remove(fullpath);
|
||||
m_knownGames[fullpath] = checkHash;
|
||||
}
|
||||
|
||||
// Check for entries that were removed
|
||||
for (QString& path : removedEntries.keys()) {
|
||||
m_entries.remove(path);
|
||||
for (const QString& path : removedEntries) {
|
||||
m_knownGames.remove(path);
|
||||
}
|
||||
|
||||
if (!removedEntries.size() && !newEntries.size()) {
|
||||
m_libraryTree->updateEntries(updatedEntries.values());
|
||||
m_libraryGrid->updateEntries(updatedEntries.values());
|
||||
} else if (!updatedEntries.size()) {
|
||||
m_libraryTree->removeEntries(removedEntries.keys());
|
||||
m_libraryGrid->removeEntries(removedEntries.keys());
|
||||
|
||||
m_libraryTree->addEntries(newEntries);
|
||||
m_libraryGrid->addEntries(newEntries);
|
||||
} else {
|
||||
m_libraryTree->resetEntries(m_entries.values());
|
||||
m_libraryGrid->resetEntries(m_entries.values());
|
||||
}
|
||||
m_libraryModel->removeEntries(removedEntries.values());
|
||||
m_libraryModel->updateEntries(updatedEntries);
|
||||
m_libraryModel->addEntries(newEntries);
|
||||
|
||||
for (size_t i = 0; i < mLibraryListingSize(&listing); ++i) {
|
||||
mLibraryEntryFree(mLibraryListingGetPointer(&listing, i));
|
||||
|
@ -201,7 +236,7 @@ void LibraryController::selectLastBootedGame() {
|
|||
return;
|
||||
}
|
||||
const QString lastfile = m_config->getMRU().first();
|
||||
if (m_entries.contains(lastfile)) {
|
||||
if (m_knownGames.contains(lastfile)) {
|
||||
selectEntry(lastfile);
|
||||
}
|
||||
}
|
||||
|
@ -213,16 +248,61 @@ void LibraryController::loadDirectory(const QString& dir, bool recursive) {
|
|||
mLibraryLoadDirectory(library.get(), dir.toUtf8().constData(), recursive);
|
||||
m_libraryJob.testAndSetOrdered(libraryJob, -1);
|
||||
}
|
||||
|
||||
void LibraryController::setShowFilename(bool showFilename) {
|
||||
if (showFilename == m_showFilename) {
|
||||
return;
|
||||
}
|
||||
m_showFilename = showFilename;
|
||||
if (m_libraryGrid) {
|
||||
m_libraryGrid->setShowFilename(m_showFilename);
|
||||
}
|
||||
if (m_libraryTree) {
|
||||
m_libraryTree->setShowFilename(m_showFilename);
|
||||
}
|
||||
m_libraryModel->setShowFilename(m_showFilename);
|
||||
refresh();
|
||||
}
|
||||
|
||||
void LibraryController::showEvent(QShowEvent*) {
|
||||
resizeTreeView(false);
|
||||
}
|
||||
|
||||
void LibraryController::resizeEvent(QResizeEvent*) {
|
||||
resizeTreeView(false);
|
||||
}
|
||||
|
||||
// This function automatically reallocates the horizontal space between the
|
||||
// columns in the view in a useful way when the window is resized.
|
||||
void LibraryController::resizeTreeView(bool expand) {
|
||||
// When new items are added to the model, make sure they are revealed.
|
||||
if (expand) {
|
||||
m_treeView->expandAll();
|
||||
}
|
||||
|
||||
// Start off by asking the view how wide it thinks each column should be.
|
||||
int viewportWidth = m_treeView->viewport()->width();
|
||||
int totalWidth = m_treeView->header()->sectionSizeHint(LibraryModel::MAX_COLUMN);
|
||||
for (int column = 0; column < LibraryModel::MAX_COLUMN; column++) {
|
||||
totalWidth += m_treeView->columnWidth(column);
|
||||
}
|
||||
|
||||
// If there would be empty space, ask the view to redistribute it.
|
||||
// The final column is set to fill any remaining width, so this
|
||||
// should (at least) fill the window.
|
||||
if (totalWidth < viewportWidth) {
|
||||
totalWidth = 0;
|
||||
for (int column = 0; column <= LibraryModel::MAX_COLUMN; column++) {
|
||||
m_treeView->resizeColumnToContents(column);
|
||||
totalWidth += m_treeView->columnWidth(column);
|
||||
}
|
||||
}
|
||||
|
||||
// If the columns would be too wide for the view now, try shrinking the
|
||||
// "Location" column down to reduce horizontal scrolling, with a fixed
|
||||
// minimum width of 100px.
|
||||
if (totalWidth > viewportWidth) {
|
||||
int locationWidth = m_treeView->columnWidth(LibraryModel::COL_LOCATION);
|
||||
if (locationWidth > 100) {
|
||||
int newLocationWidth = m_treeView->viewport()->width() - (totalWidth - locationWidth);
|
||||
if (newLocationWidth < 100) {
|
||||
newLocationWidth = 100;
|
||||
}
|
||||
m_treeView->setColumnWidth(LibraryModel::COL_LOCATION, newLocationWidth);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,15 +12,21 @@
|
|||
#include <QHash>
|
||||
#include <QList>
|
||||
#include <QStackedWidget>
|
||||
#include <QTimer>
|
||||
|
||||
#include <mgba/core/library.h>
|
||||
|
||||
#include "LibraryEntry.h"
|
||||
|
||||
class QAbstractItemView;
|
||||
class QListView;
|
||||
class QSortFilterProxyModel;
|
||||
class QTreeView;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
// Predefinitions
|
||||
class LibraryGrid;
|
||||
class LibraryTree;
|
||||
class ConfigController;
|
||||
class LibraryModel;
|
||||
|
||||
enum class LibraryStyle {
|
||||
STYLE_LIST = 0,
|
||||
|
@ -29,50 +35,6 @@ enum class LibraryStyle {
|
|||
STYLE_ICON
|
||||
};
|
||||
|
||||
struct LibraryEntry {
|
||||
LibraryEntry() {}
|
||||
LibraryEntry(const mLibraryEntry* entry);
|
||||
|
||||
bool isNull() const { return fullpath.isNull(); }
|
||||
|
||||
QString displayTitle() const { return title.isNull() ? filename : title; }
|
||||
|
||||
QString base;
|
||||
QString filename;
|
||||
QString fullpath;
|
||||
QString title;
|
||||
QByteArray internalTitle;
|
||||
QByteArray internalCode;
|
||||
mPlatform platform;
|
||||
size_t filesize;
|
||||
uint32_t crc32;
|
||||
|
||||
bool operator==(const LibraryEntry& other) const { return other.fullpath == fullpath; }
|
||||
};
|
||||
|
||||
class AbstractGameList {
|
||||
public:
|
||||
virtual QString selectedEntry() = 0;
|
||||
virtual void selectEntry(const QString& fullpath) = 0;
|
||||
|
||||
virtual void setViewStyle(LibraryStyle newStyle) = 0;
|
||||
|
||||
virtual void resetEntries(const QList<LibraryEntry>&) = 0;
|
||||
virtual void addEntries(const QList<LibraryEntry>&) = 0;
|
||||
virtual void updateEntries(const QList<LibraryEntry>&) = 0;
|
||||
virtual void removeEntries(const QList<QString>&) = 0;
|
||||
|
||||
virtual void addEntry(const LibraryEntry&);
|
||||
virtual void updateEntry(const LibraryEntry&);
|
||||
virtual void removeEntry(const QString&);
|
||||
virtual void setShowFilename(bool showFilename);
|
||||
|
||||
virtual QWidget* widget() = 0;
|
||||
|
||||
protected:
|
||||
bool m_showFilename = false;
|
||||
};
|
||||
|
||||
class LibraryController final : public QStackedWidget {
|
||||
Q_OBJECT
|
||||
|
||||
|
@ -103,21 +65,34 @@ signals:
|
|||
|
||||
private slots:
|
||||
void refresh();
|
||||
void sortChanged(int column, Qt::SortOrder order);
|
||||
inline void resizeTreeView() { resizeTreeView(true); }
|
||||
void resizeTreeView(bool expand);
|
||||
|
||||
protected:
|
||||
void showEvent(QShowEvent*) override;
|
||||
void resizeEvent(QResizeEvent*) override;
|
||||
|
||||
private:
|
||||
void loadDirectory(const QString&, bool recursive = true); // Called on separate thread
|
||||
void updateViewStyle(LibraryStyle newStyle);
|
||||
|
||||
ConfigController* m_config = nullptr;
|
||||
std::shared_ptr<mLibrary> m_library;
|
||||
QAtomicInteger<qint64> m_libraryJob = -1;
|
||||
QHash<QString, LibraryEntry> m_entries;
|
||||
|
||||
LibraryStyle m_currentStyle;
|
||||
AbstractGameList* m_currentList = nullptr;
|
||||
|
||||
std::unique_ptr<LibraryGrid> m_libraryGrid;
|
||||
std::unique_ptr<LibraryTree> m_libraryTree;
|
||||
QHash<QString, uint64_t> m_knownGames;
|
||||
LibraryModel* m_libraryModel;
|
||||
QSortFilterProxyModel* m_listModel;
|
||||
QSortFilterProxyModel* m_treeModel;
|
||||
QListView* m_listView;
|
||||
QTreeView* m_treeView;
|
||||
QAbstractItemView* m_currentView = nullptr;
|
||||
bool m_showFilename = false;
|
||||
|
||||
QTimer m_expandThrottle;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
/* Copyright (c) 2014-2017 waddlesplash
|
||||
* Copyright (c) 2013-2021 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "LibraryEntry.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <mgba/core/library.h>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
static inline uint64_t getSha1Prefix(const uint8_t* sha1) {
|
||||
return *reinterpret_cast<const quint64*>(sha1);
|
||||
}
|
||||
|
||||
static inline uint64_t getSha1Prefix(const QByteArray& sha1) {
|
||||
if (sha1.size() < 8) {
|
||||
return 0;
|
||||
}
|
||||
return getSha1Prefix((const uint8_t*)sha1.constData());
|
||||
}
|
||||
|
||||
static inline uint64_t checkHash(size_t filesize, uint32_t crc32, uint64_t sha1Prefix) {
|
||||
if (sha1Prefix) {
|
||||
return sha1Prefix;
|
||||
}
|
||||
return (uint64_t(filesize) << 32) ^ ((crc32 + 1ULL) * (uint32_t(filesize) + 1ULL));
|
||||
}
|
||||
|
||||
LibraryEntry::LibraryEntry(const mLibraryEntry* entry)
|
||||
: base(entry->base)
|
||||
, filename(entry->filename)
|
||||
, fullpath(QString("%1/%2").arg(entry->base, entry->filename))
|
||||
, title(entry->title)
|
||||
, internalTitle(entry->internalTitle)
|
||||
, internalCode(entry->internalCode)
|
||||
, platform(entry->platform)
|
||||
, platformModels(entry->platformModels)
|
||||
, filesize(entry->filesize)
|
||||
, crc32(entry->crc32)
|
||||
, sha1(reinterpret_cast<const char*>(entry->sha1), sizeof(entry->sha1))
|
||||
{
|
||||
}
|
||||
|
||||
bool LibraryEntry::isNull() const {
|
||||
return fullpath.isNull();
|
||||
}
|
||||
|
||||
QString LibraryEntry::displayTitle(bool showFilename) const {
|
||||
if (showFilename || title.isNull()) {
|
||||
return filename;
|
||||
}
|
||||
return title;
|
||||
}
|
||||
|
||||
QString LibraryEntry::displayPlatform() const {
|
||||
return nicePlatformFormat(platform, platformModels);
|
||||
}
|
||||
|
||||
bool LibraryEntry::operator==(const LibraryEntry& other) const {
|
||||
return other.fullpath == fullpath;
|
||||
}
|
||||
|
||||
uint64_t LibraryEntry::checkHash() const {
|
||||
return ::checkHash(filesize, crc32, getSha1Prefix(sha1));
|
||||
}
|
||||
|
||||
uint64_t LibraryEntry::checkHash(const mLibraryEntry* entry) {
|
||||
return ::checkHash(entry->filesize, entry->crc32, getSha1Prefix(entry->sha1));
|
||||
}
|
|
@ -0,0 +1,50 @@
|
|||
/* Copyright (c) 2014-2017 waddlesplash
|
||||
* Copyright (c) 2013-2021 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QList>
|
||||
#include <QString>
|
||||
|
||||
#include <mgba/core/core.h>
|
||||
|
||||
struct mLibraryEntry;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
struct LibraryEntry {
|
||||
LibraryEntry() = default;
|
||||
LibraryEntry(const LibraryEntry&) = default;
|
||||
LibraryEntry(LibraryEntry&&) = default;
|
||||
LibraryEntry(const mLibraryEntry* entry);
|
||||
|
||||
bool isNull() const;
|
||||
|
||||
QString displayTitle(bool showFilename = false) const;
|
||||
QString displayPlatform() const;
|
||||
|
||||
QString base;
|
||||
QString filename;
|
||||
QString fullpath;
|
||||
QString title;
|
||||
QByteArray internalTitle;
|
||||
QByteArray internalCode;
|
||||
mPlatform platform;
|
||||
int platformModels;
|
||||
size_t filesize;
|
||||
uint32_t crc32;
|
||||
QByteArray sha1;
|
||||
|
||||
LibraryEntry& operator=(const LibraryEntry&) = default;
|
||||
LibraryEntry& operator=(LibraryEntry&&) = default;
|
||||
bool operator==(const LibraryEntry& other) const;
|
||||
|
||||
uint64_t checkHash() const;
|
||||
static uint64_t checkHash(const mLibraryEntry* entry);
|
||||
};
|
||||
|
||||
};
|
|
@ -0,0 +1,473 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "LibraryModel.h"
|
||||
|
||||
#include "../utils.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QItemSelectionModel>
|
||||
#include <QSortFilterProxyModel>
|
||||
#include <QStyle>
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
static const QStringList iconSets{
|
||||
"GBA",
|
||||
"GBC",
|
||||
"GB",
|
||||
"SGB",
|
||||
};
|
||||
|
||||
static QHash<QString, QIcon> platformIcons;
|
||||
|
||||
LibraryModel::LibraryModel(QObject* parent)
|
||||
: QAbstractItemModel(parent)
|
||||
, m_treeMode(false)
|
||||
, m_showFilename(false)
|
||||
{
|
||||
if (platformIcons.isEmpty()) {
|
||||
for (const QString& platform : iconSets) {
|
||||
QString pathTemplate = QStringLiteral(":/res/%1-icon%2").arg(platform.toLower());
|
||||
QIcon icon;
|
||||
icon.addFile(pathTemplate.arg("-256.png"), QSize(256, 256));
|
||||
icon.addFile(pathTemplate.arg("-128.png"), QSize(128, 128));
|
||||
icon.addFile(pathTemplate.arg("-32.png"), QSize(32, 32));
|
||||
icon.addFile(pathTemplate.arg("-24.png"), QSize(24, 24));
|
||||
icon.addFile(pathTemplate.arg("-16.png"), QSize(16, 16));
|
||||
// This will silently and harmlessly fail if QSvgIconEngine isn't compiled in.
|
||||
icon.addFile(pathTemplate.arg(".svg"));
|
||||
platformIcons[platform] = icon;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool LibraryModel::treeMode() const {
|
||||
return m_treeMode;
|
||||
}
|
||||
|
||||
void LibraryModel::setTreeMode(bool tree) {
|
||||
if (m_treeMode == tree) {
|
||||
return;
|
||||
}
|
||||
beginResetModel();
|
||||
m_treeMode = tree;
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
bool LibraryModel::showFilename() const {
|
||||
return m_showFilename;
|
||||
}
|
||||
|
||||
void LibraryModel::setShowFilename(bool show) {
|
||||
if (m_showFilename == show) {
|
||||
return;
|
||||
}
|
||||
m_showFilename = show;
|
||||
if (m_treeMode) {
|
||||
int numPaths = m_pathOrder.size();
|
||||
for (int i = 0; i < numPaths; i++) {
|
||||
QModelIndex parent = index(i, 0);
|
||||
emit dataChanged(index(0, 0, parent), index(m_pathIndex[m_pathOrder[i]].size() - 1, 0));
|
||||
}
|
||||
} else {
|
||||
emit dataChanged(index(0, 0), index(rowCount() - 1, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryModel::resetEntries(const QList<LibraryEntry>& items) {
|
||||
beginResetModel();
|
||||
blockSignals(true);
|
||||
|
||||
m_games.clear();
|
||||
m_pathOrder.clear();
|
||||
m_pathIndex.clear();
|
||||
addEntriesList(items);
|
||||
|
||||
blockSignals(false);
|
||||
endResetModel();
|
||||
}
|
||||
|
||||
void LibraryModel::addEntries(const QList<LibraryEntry>& items) {
|
||||
if (items.isEmpty()) {
|
||||
return;
|
||||
} else if (m_treeMode) {
|
||||
addEntriesTree(items);
|
||||
} else {
|
||||
addEntriesList(items);
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryModel::addEntryInternal(const LibraryEntry& item) {
|
||||
m_gameIndex[item.fullpath] = m_games.size();
|
||||
m_games.emplace_back(new LibraryEntry(item));
|
||||
if (!m_pathIndex.contains(item.base)) {
|
||||
m_pathOrder << item.base;
|
||||
}
|
||||
m_pathIndex[item.base] << m_games.back().get();
|
||||
}
|
||||
|
||||
void LibraryModel::addEntriesList(const QList<LibraryEntry>& items) {
|
||||
beginInsertRows(QModelIndex(), m_games.size(), m_games.size() + items.size() - 1);
|
||||
for (const LibraryEntry& item : items) {
|
||||
addEntryInternal(item);
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
void LibraryModel::addEntriesTree(const QList<LibraryEntry>& items) {
|
||||
QHash<QString, QList<const LibraryEntry*>> byPath;
|
||||
QHash<QString, QList<const LibraryEntry*>> newPaths;
|
||||
for (const LibraryEntry& item : items) {
|
||||
if (m_pathIndex.contains(item.base)) {
|
||||
byPath[item.base] << &item;
|
||||
} else {
|
||||
newPaths[item.base] << &item;
|
||||
}
|
||||
}
|
||||
|
||||
if (newPaths.size() > 0) {
|
||||
beginInsertRows(QModelIndex(), m_pathIndex.size(), m_pathIndex.size() + newPaths.size() - 1);
|
||||
for (const QString& base : newPaths.keys()) {
|
||||
for (const LibraryEntry* item : newPaths[base]) {
|
||||
addEntryInternal(*item);
|
||||
}
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
|
||||
for (const QString& base : byPath.keys()) {
|
||||
QList<const LibraryEntry*>& pathItems = m_pathIndex[base];
|
||||
QList<const LibraryEntry*>& newItems = byPath[base];
|
||||
|
||||
QModelIndex parent = indexForPath(base);
|
||||
beginInsertRows(parent, pathItems.size(), pathItems.size() + newItems.size() - 1);
|
||||
for (const LibraryEntry* item : newItems) {
|
||||
addEntryInternal(*item);
|
||||
}
|
||||
endInsertRows();
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryModel::updateEntries(const QList<LibraryEntry>& items) {
|
||||
QHash<QModelIndex, SpanSet> updatedSpans;
|
||||
for (const LibraryEntry& item : items) {
|
||||
QModelIndex idx = index(item.fullpath);
|
||||
Q_ASSERT(idx.isValid());
|
||||
int pos = m_gameIndex.value(item.fullpath, -1);
|
||||
Q_ASSERT(pos >= 0);
|
||||
*m_games[pos] = item;
|
||||
updatedSpans[idx.parent()].add(pos);
|
||||
}
|
||||
for (auto iter = updatedSpans.begin(); iter != updatedSpans.end(); iter++) {
|
||||
QModelIndex parent = iter.key();
|
||||
SpanSet spans = iter.value();
|
||||
spans.merge();
|
||||
for (const SpanSet::Span& span : spans.spans) {
|
||||
QModelIndex topLeft = index(span.left, 0, parent);
|
||||
QModelIndex bottomRight = index(span.right, MAX_COLUMN, parent);
|
||||
emit dataChanged(topLeft, bottomRight);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryModel::removeEntries(const QList<QString>& items) {
|
||||
SpanSet removedRootSpans, removedGameSpans;
|
||||
QHash<QString, SpanSet> removedTreeSpans;
|
||||
int firstModifiedIndex = m_games.size();
|
||||
|
||||
// Remove the items from the game index and assemble a span
|
||||
// set so that we can later inform the view of which rows
|
||||
// were removed in an optimized way.
|
||||
for (const QString& item : items) {
|
||||
int pos = m_gameIndex.value(item, -1);
|
||||
Q_ASSERT(pos >= 0);
|
||||
if (pos < firstModifiedIndex) {
|
||||
firstModifiedIndex = pos;
|
||||
}
|
||||
LibraryEntry* entry = m_games[pos].get();
|
||||
QModelIndex parent = indexForPath(entry->base);
|
||||
Q_ASSERT(!m_treeMode || parent.isValid());
|
||||
QList<const LibraryEntry*>& pathItems = m_pathIndex[entry->base];
|
||||
int pathPos = pathItems.indexOf(entry);
|
||||
Q_ASSERT(pathPos >= 0);
|
||||
removedGameSpans.add(pos);
|
||||
removedTreeSpans[entry->base].add(pathPos);
|
||||
m_gameIndex.remove(item);
|
||||
}
|
||||
|
||||
if (!m_treeMode) {
|
||||
// If not using a tree view, all entries are root entries.
|
||||
removedRootSpans = removedGameSpans;
|
||||
}
|
||||
|
||||
// Remove the paths from the path indexes.
|
||||
// If it's a tree view, inform the view.
|
||||
for (const QString& base : removedTreeSpans.keys()) {
|
||||
SpanSet& spanSet = removedTreeSpans[base];
|
||||
spanSet.merge();
|
||||
QList<const LibraryEntry*>& pathIndex = m_pathIndex[base];
|
||||
if (spanSet.spans.size() == 1) {
|
||||
SpanSet::Span span = spanSet.spans[0];
|
||||
if (span.left == 0 && span.right == pathIndex.size() - 1) {
|
||||
if (m_treeMode) {
|
||||
removedRootSpans.add(m_pathOrder.indexOf(base));
|
||||
} else {
|
||||
m_pathIndex.remove(base);
|
||||
m_pathOrder.removeAll(base);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
QModelIndex parent = indexForPath(base);
|
||||
spanSet.sort(true);
|
||||
for (const SpanSet::Span& span : spanSet.spans) {
|
||||
if (m_treeMode) {
|
||||
beginRemoveRows(parent, span.left, span.right);
|
||||
}
|
||||
pathIndex.erase(pathIndex.begin() + span.left, pathIndex.begin() + span.right + 1);
|
||||
if (m_treeMode) {
|
||||
endRemoveRows();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Remove the games from the backing store and path indexes,
|
||||
// and tell the view to remove the root items.
|
||||
removedRootSpans.merge();
|
||||
removedRootSpans.sort(true);
|
||||
for (const SpanSet::Span& span : removedRootSpans.spans) {
|
||||
beginRemoveRows(QModelIndex(), span.left, span.right);
|
||||
if (m_treeMode) {
|
||||
for (int i = span.right; i >= span.left; i--) {
|
||||
QString base = m_pathOrder.takeAt(i);
|
||||
m_pathIndex.remove(base);
|
||||
}
|
||||
} else {
|
||||
// In list view, remove games from the backing store immediately
|
||||
m_games.erase(m_games.begin() + span.left, m_games.begin() + span.right + 1);
|
||||
}
|
||||
endRemoveRows();
|
||||
}
|
||||
if (m_treeMode) {
|
||||
// In tree view, remove them after cleaning up the path indexes.
|
||||
removedGameSpans.merge();
|
||||
removedGameSpans.sort(true);
|
||||
for (const SpanSet::Span& span : removedGameSpans.spans) {
|
||||
m_games.erase(m_games.begin() + span.left, m_games.begin() + span.right + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, update the game index for the remaining items.
|
||||
for (int i = m_games.size() - 1; i >= firstModifiedIndex; i--) {
|
||||
m_gameIndex[m_games[i]->fullpath] = i;
|
||||
}
|
||||
}
|
||||
|
||||
QModelIndex LibraryModel::index(const QString& game) const {
|
||||
int pos = m_gameIndex.value(game, -1);
|
||||
if (pos < 0) {
|
||||
return QModelIndex();
|
||||
}
|
||||
if (m_treeMode) {
|
||||
const LibraryEntry& entry = *m_games[pos];
|
||||
return createIndex(m_pathIndex[entry.base].indexOf(&entry), 0, m_pathOrder.indexOf(entry.base));
|
||||
}
|
||||
return createIndex(pos, 0);
|
||||
}
|
||||
|
||||
QModelIndex LibraryModel::index(int row, int column, const QModelIndex& parent) const {
|
||||
if (!parent.isValid()) {
|
||||
return createIndex(row, column, quintptr(0));
|
||||
}
|
||||
if (!m_treeMode || parent.internalId() || parent.column() != 0) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return createIndex(row, column, parent.row() + 1);
|
||||
}
|
||||
|
||||
QModelIndex LibraryModel::parent(const QModelIndex& child) const {
|
||||
if (!child.isValid() || child.internalId() == 0) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return createIndex(child.internalId() - 1, 0, quintptr(0));
|
||||
}
|
||||
|
||||
int LibraryModel::columnCount(const QModelIndex& parent) const {
|
||||
if (!parent.isValid() || (parent.column() == 0 && !parent.parent().isValid())) {
|
||||
return MAX_COLUMN + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int LibraryModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent.isValid()) {
|
||||
if (m_treeMode) {
|
||||
if (parent.row() < 0 || parent.row() >= m_pathOrder.size() || parent.column() != 0) {
|
||||
return 0;
|
||||
}
|
||||
return m_pathIndex[m_pathOrder[parent.row()]].size();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (m_treeMode) {
|
||||
return m_pathOrder.size();
|
||||
}
|
||||
return m_games.size();
|
||||
}
|
||||
|
||||
QVariant LibraryModel::folderData(const QModelIndex& index, int role) const {
|
||||
// Precondition: index and role must have already been validated
|
||||
if (role == Qt::DecorationRole) {
|
||||
return qApp->style()->standardIcon(QStyle::SP_DirOpenIcon);
|
||||
}
|
||||
if (role == FullPathRole || (index.column() == COL_LOCATION && role != Qt::DisplayRole)) {
|
||||
return m_pathOrder[index.row()];
|
||||
}
|
||||
if (index.column() == COL_NAME) {
|
||||
QString path = m_pathOrder[index.row()];
|
||||
return path.section('/', -1);
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
bool LibraryModel::validateIndex(const QModelIndex& index) const
|
||||
{
|
||||
if (index.model() != this || index.row() < 0 || index.column() < 0 || index.column() > MAX_COLUMN) {
|
||||
// Obviously invalid index
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index.parent().isValid() && !validateIndex(index.parent())) {
|
||||
// Parent index is invalid
|
||||
return false;
|
||||
}
|
||||
|
||||
if (index.row() >= rowCount(index.parent())) {
|
||||
// Row is out of bounds for this level of hierarchy
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
QVariant LibraryModel::data(const QModelIndex& index, int role) const {
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
case Qt::EditRole:
|
||||
case Qt::TextAlignmentRole:
|
||||
case FullPathRole:
|
||||
break;
|
||||
case Qt::ToolTipRole:
|
||||
if (index.column() > COL_LOCATION) {
|
||||
return QVariant();
|
||||
}
|
||||
break;
|
||||
case Qt::DecorationRole:
|
||||
if (index.column() != COL_NAME) {
|
||||
return QVariant();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (!validateIndex(index)) {
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
if (role == Qt::TextAlignmentRole) {
|
||||
return index.column() == COL_SIZE ? (int)(Qt::AlignTrailing | Qt::AlignVCenter) : (int)(Qt::AlignLeading | Qt::AlignVCenter);
|
||||
}
|
||||
|
||||
const LibraryEntry* entry = nullptr;
|
||||
if (m_treeMode) {
|
||||
if (!index.parent().isValid()) {
|
||||
return folderData(index, role);
|
||||
}
|
||||
QString path = m_pathOrder[index.parent().row()];
|
||||
entry = m_pathIndex[path][index.row()];
|
||||
} else if (!index.parent().isValid() && index.row() < (int)m_games.size()) {
|
||||
entry = m_games[index.row()].get();
|
||||
}
|
||||
|
||||
if (entry) {
|
||||
if (role == FullPathRole) {
|
||||
return entry->fullpath;
|
||||
}
|
||||
switch (index.column()) {
|
||||
case COL_NAME:
|
||||
if (role == Qt::DecorationRole) {
|
||||
return platformIcons.value(entry->displayPlatform(), qApp->style()->standardIcon(QStyle::SP_FileIcon));
|
||||
}
|
||||
return entry->displayTitle(m_showFilename);
|
||||
case COL_LOCATION:
|
||||
return QDir::toNativeSeparators(entry->base);
|
||||
case COL_PLATFORM:
|
||||
return nicePlatformFormat(entry->platform);
|
||||
case COL_SIZE:
|
||||
return (role == Qt::DisplayRole) ? QVariant(niceSizeFormat(entry->filesize)) : QVariant(int(entry->filesize));
|
||||
case COL_CRC32:
|
||||
return (role == Qt::DisplayRole) ? QVariant(QStringLiteral("%0").arg(entry->crc32, 8, 16, QChar('0'))) : QVariant(entry->crc32);
|
||||
}
|
||||
}
|
||||
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QVariant LibraryModel::headerData(int section, Qt::Orientation orientation, int role) const {
|
||||
if (orientation == Qt::Horizontal && role == Qt::DisplayRole) {
|
||||
switch (section) {
|
||||
case COL_NAME:
|
||||
return QApplication::translate("LibraryTree", "Name", nullptr);
|
||||
case COL_LOCATION:
|
||||
return QApplication::translate("LibraryTree", "Location", nullptr);
|
||||
case COL_PLATFORM:
|
||||
return QApplication::translate("LibraryTree", "Platform", nullptr);
|
||||
case COL_SIZE:
|
||||
return QApplication::translate("LibraryTree", "Size", nullptr);
|
||||
case COL_CRC32:
|
||||
return QApplication::translate("LibraryTree", "CRC32", nullptr);
|
||||
};
|
||||
}
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QModelIndex LibraryModel::indexForPath(const QString& path) {
|
||||
int pos = m_pathOrder.indexOf(path);
|
||||
if (pos < 0) {
|
||||
pos = m_pathOrder.size();
|
||||
beginInsertRows(QModelIndex(), pos, pos);
|
||||
m_pathOrder << path;
|
||||
m_pathIndex[path] = QList<const LibraryEntry*>();
|
||||
endInsertRows();
|
||||
}
|
||||
if (!m_treeMode) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return index(pos, 0, QModelIndex());
|
||||
}
|
||||
|
||||
QModelIndex LibraryModel::indexForPath(const QString& path) const {
|
||||
if (!m_treeMode) {
|
||||
return QModelIndex();
|
||||
}
|
||||
int pos = m_pathOrder.indexOf(path);
|
||||
if (pos < 0) {
|
||||
return QModelIndex();
|
||||
}
|
||||
return index(pos, 0, QModelIndex());
|
||||
}
|
||||
|
||||
LibraryEntry LibraryModel::entry(const QString& game) const {
|
||||
int pos = m_gameIndex.value(game, -1);
|
||||
if (pos < 0) {
|
||||
return {};
|
||||
}
|
||||
return *m_games[pos];
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractItemModel>
|
||||
#include <QIcon>
|
||||
#include <QTreeView>
|
||||
|
||||
#include <mgba/core/library.h>
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "LibraryEntry.h"
|
||||
|
||||
class QTreeView;
|
||||
class LibraryModelTest;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class LibraryModel final : public QAbstractItemModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum Columns {
|
||||
COL_NAME = 0,
|
||||
COL_LOCATION = 1,
|
||||
COL_PLATFORM = 2,
|
||||
COL_SIZE = 3,
|
||||
COL_CRC32 = 4,
|
||||
MAX_COLUMN = 4,
|
||||
};
|
||||
|
||||
enum ItemDataRole {
|
||||
FullPathRole = Qt::UserRole + 1,
|
||||
};
|
||||
|
||||
explicit LibraryModel(QObject* parent = nullptr);
|
||||
|
||||
bool treeMode() const;
|
||||
void setTreeMode(bool tree);
|
||||
|
||||
bool showFilename() const;
|
||||
void setShowFilename(bool show);
|
||||
|
||||
void resetEntries(const QList<LibraryEntry>& items);
|
||||
void addEntries(const QList<LibraryEntry>& items);
|
||||
void updateEntries(const QList<LibraryEntry>& items);
|
||||
void removeEntries(const QList<QString>& items);
|
||||
|
||||
QModelIndex index(const QString& game) const;
|
||||
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
|
||||
QModelIndex parent(const QModelIndex& child) const override;
|
||||
|
||||
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
|
||||
|
||||
LibraryEntry entry(const QString& game) const;
|
||||
|
||||
private:
|
||||
friend class ::LibraryModelTest;
|
||||
|
||||
QModelIndex indexForPath(const QString& path);
|
||||
QModelIndex indexForPath(const QString& path) const;
|
||||
|
||||
QVariant folderData(const QModelIndex& index, int role = Qt::DisplayRole) const;
|
||||
|
||||
bool validateIndex(const QModelIndex& index) const;
|
||||
|
||||
void addEntriesList(const QList<LibraryEntry>& items);
|
||||
void addEntriesTree(const QList<LibraryEntry>& items);
|
||||
void addEntryInternal(const LibraryEntry& item);
|
||||
|
||||
bool m_treeMode;
|
||||
bool m_showFilename;
|
||||
|
||||
std::vector<std::unique_ptr<LibraryEntry>> m_games;
|
||||
QStringList m_pathOrder;
|
||||
QHash<QString, QList<const LibraryEntry*>> m_pathIndex;
|
||||
QHash<QString, int> m_gameIndex;
|
||||
};
|
||||
|
||||
}
|
|
@ -1,212 +0,0 @@
|
|||
/* Copyright (c) 2014-2017 waddlesplash
|
||||
* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "LibraryTree.h"
|
||||
|
||||
#include "utils.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class LibraryTreeItem : public QTreeWidgetItem {
|
||||
public:
|
||||
LibraryTreeItem(QTreeWidget* parent = nullptr) : QTreeWidgetItem(parent) {}
|
||||
void setFilesize(size_t size);
|
||||
|
||||
virtual bool operator<(const QTreeWidgetItem& other) const override;
|
||||
protected:
|
||||
size_t m_size = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void LibraryTreeItem::setFilesize(size_t size) {
|
||||
m_size = size;
|
||||
setText(LibraryTree::COL_SIZE, niceSizeFormat(size));
|
||||
}
|
||||
|
||||
bool LibraryTreeItem::operator<(const QTreeWidgetItem& other) const {
|
||||
const int column = treeWidget()->sortColumn();
|
||||
return ((column == LibraryTree::COL_SIZE) ?
|
||||
m_size < dynamic_cast<const LibraryTreeItem*>(&other)->m_size :
|
||||
QTreeWidgetItem::operator<(other));
|
||||
}
|
||||
|
||||
LibraryTree::LibraryTree(LibraryController* parent)
|
||||
: m_widget(new QTreeWidget(parent))
|
||||
, m_controller(parent)
|
||||
{
|
||||
m_widget->setObjectName("LibraryTree");
|
||||
m_widget->setSortingEnabled(true);
|
||||
m_widget->setAlternatingRowColors(true);
|
||||
|
||||
QTreeWidgetItem* header = new QTreeWidgetItem({
|
||||
QApplication::translate("QGBA::LibraryTree", "Name", nullptr),
|
||||
QApplication::translate("QGBA::LibraryTree", "Location", nullptr),
|
||||
QApplication::translate("QGBA::LibraryTree", "Platform", nullptr),
|
||||
QApplication::translate("QGBA::LibraryTree", "Size", nullptr),
|
||||
QApplication::translate("QGBA::LibraryTree", "CRC32", nullptr),
|
||||
});
|
||||
header->setTextAlignment(3, Qt::AlignTrailing | Qt::AlignVCenter);
|
||||
m_widget->setHeaderItem(header);
|
||||
|
||||
setViewStyle(LibraryStyle::STYLE_TREE);
|
||||
m_widget->sortByColumn(COL_NAME, Qt::AscendingOrder);
|
||||
|
||||
QObject::connect(m_widget, &QTreeWidget::itemActivated, [this](QTreeWidgetItem* item, int) -> void {
|
||||
if (m_items.values().contains(item)) {
|
||||
emit m_controller->startGame();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
LibraryTree::~LibraryTree() {
|
||||
m_widget->clear();
|
||||
}
|
||||
|
||||
void LibraryTree::resizeAllCols() {
|
||||
for (int i = 0; i < m_widget->columnCount(); i++) {
|
||||
m_widget->resizeColumnToContents(i);
|
||||
}
|
||||
}
|
||||
|
||||
QString LibraryTree::selectedEntry() {
|
||||
if (!m_widget->selectedItems().empty()) {
|
||||
return m_items.key(m_widget->selectedItems().at(0));
|
||||
} else {
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryTree::selectEntry(const QString& game) {
|
||||
if (game.isNull()) {
|
||||
return;
|
||||
}
|
||||
m_widget->setCurrentItem(m_items.value(game));
|
||||
}
|
||||
|
||||
void LibraryTree::setViewStyle(LibraryStyle newStyle) {
|
||||
if (newStyle == LibraryStyle::STYLE_LIST) {
|
||||
m_widget->setIndentation(0);
|
||||
} else {
|
||||
m_widget->setIndentation(20);
|
||||
}
|
||||
m_currentStyle = newStyle;
|
||||
rebuildTree();
|
||||
}
|
||||
|
||||
void LibraryTree::resetEntries(const QList<LibraryEntry>& items) {
|
||||
m_deferredTreeRebuild = true;
|
||||
m_entries.clear();
|
||||
m_pathNodes.clear();
|
||||
addEntries(items);
|
||||
}
|
||||
|
||||
void LibraryTree::addEntries(const QList<LibraryEntry>& items) {
|
||||
m_deferredTreeRebuild = true;
|
||||
for (const auto& item : items) {
|
||||
addEntry(item);
|
||||
}
|
||||
m_deferredTreeRebuild = false;
|
||||
rebuildTree();
|
||||
}
|
||||
|
||||
void LibraryTree::addEntry(const LibraryEntry& item) {
|
||||
m_entries[item.fullpath] = item;
|
||||
|
||||
QString folder = item.base;
|
||||
if (!m_pathNodes.contains(folder)) {
|
||||
m_pathNodes.insert(folder, 1);
|
||||
} else {
|
||||
++m_pathNodes[folder];
|
||||
}
|
||||
|
||||
rebuildTree();
|
||||
}
|
||||
|
||||
void LibraryTree::updateEntries(const QList<LibraryEntry>& items) {
|
||||
for (const auto& item : items) {
|
||||
updateEntry(item);
|
||||
}
|
||||
}
|
||||
|
||||
void LibraryTree::updateEntry(const LibraryEntry& item) {
|
||||
m_entries[item.fullpath] = item;
|
||||
|
||||
LibraryTreeItem* i = static_cast<LibraryTreeItem*>(m_items.value(item.fullpath));
|
||||
i->setText(COL_NAME, m_showFilename ? item.filename : item.displayTitle());
|
||||
i->setText(COL_PLATFORM, nicePlatformFormat(item.platform));
|
||||
i->setFilesize(item.filesize);
|
||||
i->setText(COL_CRC32, QString("%0").arg(item.crc32, 8, 16, QChar('0')));
|
||||
}
|
||||
|
||||
void LibraryTree::removeEntries(const QList<QString>& items) {
|
||||
m_deferredTreeRebuild = true;
|
||||
for (const auto& item : items) {
|
||||
removeEntry(item);
|
||||
}
|
||||
m_deferredTreeRebuild = false;
|
||||
rebuildTree();
|
||||
}
|
||||
|
||||
void LibraryTree::removeEntry(const QString& item) {
|
||||
if (!m_entries.contains(item)) {
|
||||
return;
|
||||
}
|
||||
QString folder = m_entries.value(item).base;
|
||||
--m_pathNodes[folder];
|
||||
if (m_pathNodes[folder] <= 0) {
|
||||
m_pathNodes.remove(folder);
|
||||
}
|
||||
|
||||
m_entries.remove(item);
|
||||
rebuildTree();
|
||||
}
|
||||
|
||||
void LibraryTree::rebuildTree() {
|
||||
if (m_deferredTreeRebuild) {
|
||||
return;
|
||||
}
|
||||
|
||||
QString currentGame = selectedEntry();
|
||||
m_widget->clear();
|
||||
m_items.clear();
|
||||
|
||||
QHash<QString, QTreeWidgetItem*> pathNodes;
|
||||
if (m_currentStyle == LibraryStyle::STYLE_TREE) {
|
||||
for (const QString& folder : m_pathNodes.keys()) {
|
||||
QTreeWidgetItem* i = new LibraryTreeItem;
|
||||
pathNodes.insert(folder, i);
|
||||
i->setText(0, folder.section("/", -1));
|
||||
m_widget->addTopLevelItem(i);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& item : m_entries.values()) {
|
||||
LibraryTreeItem* i = new LibraryTreeItem;
|
||||
i->setText(COL_NAME, item.displayTitle());
|
||||
i->setText(COL_LOCATION, QDir::toNativeSeparators(item.base));
|
||||
i->setText(COL_PLATFORM, nicePlatformFormat(item.platform));
|
||||
i->setFilesize(item.filesize);
|
||||
i->setTextAlignment(COL_SIZE, Qt::AlignTrailing | Qt::AlignVCenter);
|
||||
i->setText(COL_CRC32, QString("%0").arg(item.crc32, 8, 16, QChar('0')));
|
||||
m_items.insert(item.fullpath, i);
|
||||
|
||||
if (m_currentStyle == LibraryStyle::STYLE_TREE) {
|
||||
pathNodes.value(item.base)->addChild(i);
|
||||
} else {
|
||||
m_widget->addTopLevelItem(i);
|
||||
}
|
||||
}
|
||||
|
||||
m_widget->expandAll();
|
||||
resizeAllCols();
|
||||
selectEntry(currentGame);
|
||||
}
|
|
@ -1,61 +0,0 @@
|
|||
/* Copyright (c) 2014-2017 waddlesplash
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QTreeWidget>
|
||||
|
||||
#include "LibraryController.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class LibraryTreeItem;
|
||||
|
||||
class LibraryTree final : public AbstractGameList {
|
||||
|
||||
public:
|
||||
enum Columns {
|
||||
COL_NAME = 0,
|
||||
COL_LOCATION = 1,
|
||||
COL_PLATFORM = 2,
|
||||
COL_SIZE = 3,
|
||||
COL_CRC32 = 4,
|
||||
};
|
||||
|
||||
explicit LibraryTree(LibraryController* parent = nullptr);
|
||||
~LibraryTree();
|
||||
|
||||
QString selectedEntry() override;
|
||||
void selectEntry(const QString& fullpath) override;
|
||||
|
||||
void setViewStyle(LibraryStyle newStyle) override;
|
||||
|
||||
void resetEntries(const QList<LibraryEntry>& items) override;
|
||||
void addEntries(const QList<LibraryEntry>& items) override;
|
||||
void updateEntries(const QList<LibraryEntry>& items) override;
|
||||
void removeEntries(const QList<QString>& items) override;
|
||||
|
||||
void addEntry(const LibraryEntry& items) override;
|
||||
void updateEntry(const LibraryEntry& items) override;
|
||||
void removeEntry(const QString& items) override;
|
||||
|
||||
QWidget* widget() override { return m_widget; }
|
||||
|
||||
private:
|
||||
QTreeWidget* m_widget;
|
||||
LibraryStyle m_currentStyle;
|
||||
|
||||
LibraryController* m_controller;
|
||||
|
||||
bool m_deferredTreeRebuild = false;
|
||||
QHash<QString, LibraryEntry> m_entries;
|
||||
QHash<QString, QTreeWidgetItem*> m_items;
|
||||
QHash<QString, int> m_pathNodes;
|
||||
|
||||
void rebuildTree();
|
||||
void resizeAllCols();
|
||||
};
|
||||
|
||||
}
|
|
@ -50,6 +50,7 @@ Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin);
|
|||
#ifdef Q_OS_WIN
|
||||
#include <process.h>
|
||||
#include <wincon.h>
|
||||
extern "C" __declspec (dllexport) DWORD NoHotPatch = 0x1;
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
|
|
@ -7,6 +7,30 @@
|
|||
<file>../../../res/keymap.qpic</file>
|
||||
<file>../../../res/patrons.txt</file>
|
||||
<file>../../../res/no-cam.png</file>
|
||||
<file>../../../res/gb-icon-256.png</file>
|
||||
<file>../../../res/gb-icon-128.png</file>
|
||||
<file>../../../res/gb-icon-32.png</file>
|
||||
<file>../../../res/gb-icon-24.png</file>
|
||||
<file>../../../res/gb-icon-16.png</file>
|
||||
<file>../../../res/gb-icon.svg</file>
|
||||
<file>../../../res/gbc-icon-256.png</file>
|
||||
<file>../../../res/gbc-icon-128.png</file>
|
||||
<file>../../../res/gbc-icon-32.png</file>
|
||||
<file>../../../res/gbc-icon-24.png</file>
|
||||
<file>../../../res/gbc-icon-16.png</file>
|
||||
<file>../../../res/gbc-icon.svg</file>
|
||||
<file>../../../res/sgb-icon-256.png</file>
|
||||
<file>../../../res/sgb-icon-128.png</file>
|
||||
<file>../../../res/sgb-icon-32.png</file>
|
||||
<file>../../../res/sgb-icon-24.png</file>
|
||||
<file>../../../res/sgb-icon-16.png</file>
|
||||
<file>../../../res/sgb-icon.svg</file>
|
||||
<file>../../../res/gba-icon-256.png</file>
|
||||
<file>../../../res/gba-icon-128.png</file>
|
||||
<file>../../../res/gba-icon-32.png</file>
|
||||
<file>../../../res/gba-icon-24.png</file>
|
||||
<file>../../../res/gba-icon-16.png</file>
|
||||
<file>../../../res/gba-icon.svg</file>
|
||||
</qresource>
|
||||
<qresource prefix="/exe">
|
||||
<file alias="exe4/chip-names.txt">../../../res/exe4/chip-names.txt</file>
|
||||
|
|
|
@ -0,0 +1,132 @@
|
|||
/* Copyright (c) 2013-2025 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "scripting/AutorunScriptModel.h"
|
||||
|
||||
#include "ConfigController.h"
|
||||
#include "LogController.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
AutorunScriptModel::AutorunScriptModel(ConfigController* config, QObject* parent)
|
||||
: QAbstractListModel(parent)
|
||||
, m_config(config)
|
||||
{
|
||||
QList<QVariant> autorun = m_config->getList("autorunSettings");
|
||||
for (const auto& item: autorun) {
|
||||
if (!item.canConvert<ScriptInfo>()) {
|
||||
continue;
|
||||
}
|
||||
m_scripts.append(qvariant_cast<ScriptInfo>(item));
|
||||
}
|
||||
}
|
||||
|
||||
int AutorunScriptModel::rowCount(const QModelIndex& parent) const {
|
||||
if (parent.isValid()) {
|
||||
return 0;
|
||||
}
|
||||
return m_scripts.count();
|
||||
}
|
||||
|
||||
bool AutorunScriptModel::setData(const QModelIndex& index, const QVariant& data, int role) {
|
||||
if (!index.isValid() || index.parent().isValid() || index.row() >= m_scripts.count()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Qt::CheckStateRole:
|
||||
m_scripts[index.row()].active = data.value<Qt::CheckState>() == Qt::Checked;
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
QVariant AutorunScriptModel::data(const QModelIndex& index, int role) const {
|
||||
if (!index.isValid() || index.parent().isValid() || index.row() >= m_scripts.count()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
switch (role) {
|
||||
case Qt::DisplayRole:
|
||||
return m_scripts.at(index.row()).filename;
|
||||
case Qt::CheckStateRole:
|
||||
return m_scripts.at(index.row()).active ? Qt::Checked : Qt::Unchecked;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
Qt::ItemFlags AutorunScriptModel::flags(const QModelIndex& index) const {
|
||||
if (!index.isValid() || index.parent().isValid()) {
|
||||
return Qt::NoItemFlags;
|
||||
}
|
||||
return Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren;
|
||||
}
|
||||
|
||||
bool AutorunScriptModel::removeRows(int row, int count, const QModelIndex& parent) {
|
||||
if (parent.isValid()) {
|
||||
return false;
|
||||
}
|
||||
if (m_scripts.size() < row) {
|
||||
return false;
|
||||
}
|
||||
if (m_scripts.size() < row + count) {
|
||||
count = m_scripts.size() - row;
|
||||
}
|
||||
m_scripts.erase(m_scripts.begin() + row, m_scripts.begin() + row + count);
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AutorunScriptModel::moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild) {
|
||||
if (sourceParent.isValid() || destinationParent.isValid()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceRow < 0 || destinationChild < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (sourceRow >= m_scripts.size() || destinationChild >= m_scripts.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (count > 1) {
|
||||
LOG(QT, WARN) << tr("Moving more than one row at once is not yet supported");
|
||||
return false;
|
||||
}
|
||||
|
||||
auto item = m_scripts.takeAt(sourceRow);
|
||||
m_scripts.insert(destinationChild, item);
|
||||
save();
|
||||
return true;
|
||||
}
|
||||
|
||||
void AutorunScriptModel::addScript(const QString& filename) {
|
||||
beginInsertRows({}, m_scripts.count(), m_scripts.count());
|
||||
m_scripts.append(ScriptInfo { filename, true });
|
||||
endInsertRows();
|
||||
save();
|
||||
}
|
||||
|
||||
QList<QString> AutorunScriptModel::activeScripts() const {
|
||||
QList<QString> scripts;
|
||||
for (const auto& pair: m_scripts) {
|
||||
if (!pair.active) {
|
||||
continue;
|
||||
}
|
||||
scripts.append(pair.filename);
|
||||
}
|
||||
return scripts;
|
||||
}
|
||||
|
||||
void AutorunScriptModel::save() {
|
||||
QList<QVariant> list;
|
||||
for (const auto& script : m_scripts) {
|
||||
list.append(QVariant::fromValue(script));
|
||||
}
|
||||
m_config->setList("autorunSettings", list);
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
/* Copyright (c) 2013-2025 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QDataStream>
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class ConfigController;
|
||||
|
||||
class AutorunScriptModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
struct ScriptInfo {
|
||||
QString filename;
|
||||
bool active;
|
||||
|
||||
friend QDataStream& operator<<(QDataStream& stream, const ScriptInfo& object) {
|
||||
stream << object.filename;
|
||||
stream << object.active;
|
||||
return stream;
|
||||
}
|
||||
|
||||
friend QDataStream& operator>>(QDataStream& stream, ScriptInfo& object) {
|
||||
stream >> object.filename;
|
||||
stream >> object.active;
|
||||
return stream;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
AutorunScriptModel(ConfigController* config, QObject* parent = nullptr);
|
||||
|
||||
virtual int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
virtual bool setData(const QModelIndex& index, const QVariant& data, int role = Qt::DisplayRole) override;
|
||||
virtual QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
|
||||
virtual bool removeRows(int row, int count, const QModelIndex& parent = QModelIndex()) override;
|
||||
virtual bool moveRows(const QModelIndex& sourceParent, int sourceRow, int count, const QModelIndex& destinationParent, int destinationChild) override;
|
||||
|
||||
void addScript(const QString& filename);
|
||||
QList<QString> activeScripts() const;
|
||||
|
||||
private:
|
||||
ConfigController* m_config;
|
||||
QList<ScriptInfo> m_scripts;
|
||||
|
||||
void save();
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Q_DECLARE_METATYPE(QGBA::AutorunScriptModel::ScriptInfo);
|
|
@ -0,0 +1,52 @@
|
|||
/* Copyright (c) 2013-2025 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "scripting/AutorunScriptView.h"
|
||||
|
||||
#include "GBAApp.h"
|
||||
#include "scripting/AutorunScriptModel.h"
|
||||
#include "scripting/ScriptingController.h"
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
AutorunScriptView::AutorunScriptView(AutorunScriptModel* model, ScriptingController* controller, QWidget* parent)
|
||||
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
|
||||
, m_controller(controller)
|
||||
{
|
||||
m_ui.setupUi(this);
|
||||
|
||||
m_ui.autorunList->setModel(model);
|
||||
}
|
||||
|
||||
void AutorunScriptView::addScript() {
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select a script"), m_controller->getFilenameFilters());
|
||||
if (filename.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
AutorunScriptModel* model = static_cast<AutorunScriptModel*>(m_ui.autorunList->model());
|
||||
model->addScript(filename);
|
||||
}
|
||||
|
||||
void AutorunScriptView::removeScript(const QModelIndex& index) {
|
||||
QAbstractItemModel* model = m_ui.autorunList->model();
|
||||
model->removeRow(index.row(), index.parent());
|
||||
}
|
||||
|
||||
void AutorunScriptView::removeScript() {
|
||||
removeScript(m_ui.autorunList->currentIndex());
|
||||
}
|
||||
|
||||
void AutorunScriptView::moveUp() {
|
||||
QModelIndex index = m_ui.autorunList->currentIndex();
|
||||
QAbstractItemModel* model = m_ui.autorunList->model();
|
||||
model->moveRows(index.parent(), index.row(), 1, index.parent(), index.row() - 1);
|
||||
}
|
||||
|
||||
void AutorunScriptView::moveDown() {
|
||||
QModelIndex index = m_ui.autorunList->currentIndex();
|
||||
QAbstractItemModel* model = m_ui.autorunList->model();
|
||||
model->moveRows(index.parent(), index.row(), 1, index.parent(), index.row() + 1);
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
/* Copyright (c) 2013-2025 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#pragma once
|
||||
|
||||
#include "ui_AutorunScriptView.h"
|
||||
|
||||
namespace QGBA {
|
||||
|
||||
class AutorunScriptModel;
|
||||
class ScriptingController;
|
||||
|
||||
class AutorunScriptView : public QDialog {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
AutorunScriptView(AutorunScriptModel* model, ScriptingController* controller, QWidget* parent = nullptr);
|
||||
void removeScript(const QModelIndex&);
|
||||
|
||||
private slots:
|
||||
void addScript();
|
||||
void removeScript();
|
||||
void moveUp();
|
||||
void moveDown();
|
||||
|
||||
private:
|
||||
Ui::AutorunScriptView m_ui;
|
||||
ScriptingController* m_controller;
|
||||
};
|
||||
|
||||
}
|
|
@ -0,0 +1,157 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<ui version="4.0">
|
||||
<class>QGBA::AutorunScriptView</class>
|
||||
<widget class="QDialog" name="QGBA::AutorunScriptView">
|
||||
<property name="geometry">
|
||||
<rect>
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>400</width>
|
||||
<height>300</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Autorun scripts</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="1" column="0">
|
||||
<widget class="QPushButton" name="add">
|
||||
<property name="text">
|
||||
<string>Add</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-add"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QPushButton" name="remove">
|
||||
<property name="text">
|
||||
<string>Remove</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="list-remove"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="2">
|
||||
<spacer name="horizontalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Horizontal</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>0</width>
|
||||
<height>20</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="1" column="3">
|
||||
<widget class="QPushButton" name="up">
|
||||
<property name="text">
|
||||
<string>Move up</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-up"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="4">
|
||||
<widget class="QPushButton" name="down">
|
||||
<property name="text">
|
||||
<string>Move down</string>
|
||||
</property>
|
||||
<property name="icon">
|
||||
<iconset theme="go-down"/>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="5">
|
||||
<widget class="QGroupBox" name="groupBox">
|
||||
<property name="title">
|
||||
<string>Run scripts when starting a game</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout_2">
|
||||
<item row="0" column="0">
|
||||
<widget class="QListView" name="autorunList"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<resources/>
|
||||
<connections>
|
||||
<connection>
|
||||
<sender>add</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>QGBA::AutorunScriptView</receiver>
|
||||
<slot>addScript()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>47</x>
|
||||
<y>276</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>remove</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>QGBA::AutorunScriptView</receiver>
|
||||
<slot>removeScript()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>138</x>
|
||||
<y>276</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>up</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>QGBA::AutorunScriptView</receiver>
|
||||
<slot>moveUp()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>238</x>
|
||||
<y>276</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
<connection>
|
||||
<sender>down</sender>
|
||||
<signal>clicked()</signal>
|
||||
<receiver>QGBA::AutorunScriptView</receiver>
|
||||
<slot>moveDown()</slot>
|
||||
<hints>
|
||||
<hint type="sourcelabel">
|
||||
<x>341</x>
|
||||
<y>276</y>
|
||||
</hint>
|
||||
<hint type="destinationlabel">
|
||||
<x>199</x>
|
||||
<y>149</y>
|
||||
</hint>
|
||||
</hints>
|
||||
</connection>
|
||||
</connections>
|
||||
<slots>
|
||||
<slot>addScript()</slot>
|
||||
<slot>removeScript()</slot>
|
||||
<slot>moveUp()</slot>
|
||||
<slot>moveDown()</slot>
|
||||
</slots>
|
||||
</ui>
|
|
@ -10,14 +10,17 @@
|
|||
#include <QMouseEvent>
|
||||
#include <QWidget>
|
||||
|
||||
#include "ConfigController.h"
|
||||
#include "CoreController.h"
|
||||
#include "Display.h"
|
||||
#include "input/Gamepad.h"
|
||||
#include "input/GamepadButtonEvent.h"
|
||||
#include "input/GamepadHatEvent.h"
|
||||
#include "InputController.h"
|
||||
#include "scripting/AutorunScriptView.h"
|
||||
#include "scripting/ScriptingTextBuffer.h"
|
||||
#include "scripting/ScriptingTextBufferModel.h"
|
||||
#include "Window.h"
|
||||
|
||||
#include <mgba/script.h>
|
||||
#include <mgba-util/math.h>
|
||||
|
@ -25,8 +28,9 @@
|
|||
|
||||
using namespace QGBA;
|
||||
|
||||
ScriptingController::ScriptingController(QObject* parent)
|
||||
ScriptingController::ScriptingController(ConfigController* config, QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_model(config)
|
||||
{
|
||||
m_logger.p = this;
|
||||
m_logger.log = [](mLogger* log, int, enum mLogLevel level, const char* format, va_list args) {
|
||||
|
@ -154,9 +158,6 @@ void ScriptingController::reset() {
|
|||
m_engines.clear();
|
||||
m_activeEngine = nullptr;
|
||||
init();
|
||||
if (m_controller && m_controller->hasStarted()) {
|
||||
attach();
|
||||
}
|
||||
}
|
||||
|
||||
void ScriptingController::runCode(const QString& code) {
|
||||
|
@ -164,6 +165,11 @@ void ScriptingController::runCode(const QString& code) {
|
|||
load(vf, "*prompt");
|
||||
}
|
||||
|
||||
void ScriptingController::openAutorunEdit() {
|
||||
AutorunScriptView* view = new AutorunScriptView(&m_model, this);
|
||||
emit autorunScriptsOpened(view);
|
||||
}
|
||||
|
||||
void ScriptingController::flushStorage() {
|
||||
#ifdef USE_JSON_C
|
||||
mScriptStorageFlushAll(&m_scriptContext);
|
||||
|
@ -259,6 +265,15 @@ void ScriptingController::scriptingEvent(QObject* obj, QEvent* event) {
|
|||
}
|
||||
}
|
||||
|
||||
QString ScriptingController::getFilenameFilters() const {
|
||||
QStringList filters;
|
||||
#ifdef USE_LUA
|
||||
filters.append(tr("Lua scripts (*.lua)"));
|
||||
#endif
|
||||
filters.append(tr("All files (*.*)"));
|
||||
return filters.join(";;");
|
||||
}
|
||||
|
||||
void ScriptingController::updateGamepad() {
|
||||
InputDriver* driver = m_inputController->gamepadDriver();
|
||||
if (!driver) {
|
||||
|
@ -354,6 +369,14 @@ void ScriptingController::init() {
|
|||
#ifdef USE_JSON_C
|
||||
m_storageFlush.start();
|
||||
#endif
|
||||
|
||||
if (m_controller && m_controller->hasStarted()) {
|
||||
attach();
|
||||
}
|
||||
|
||||
for (const auto& script: m_model.activeScripts()) {
|
||||
loadFile(script);
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t ScriptingController::qtToScriptingKey(const QKeyEvent* event) {
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <mgba/script/input.h>
|
||||
#include <mgba/core/scripting.h>
|
||||
|
||||
#include "scripting/AutorunScriptModel.h"
|
||||
#include "VFileDevice.h"
|
||||
|
||||
#include <memory>
|
||||
|
@ -24,6 +25,7 @@ struct VideoBackend;
|
|||
|
||||
namespace QGBA {
|
||||
|
||||
class ConfigController;
|
||||
class CoreController;
|
||||
class InputController;
|
||||
class ScriptingTextBuffer;
|
||||
|
@ -33,7 +35,7 @@ class ScriptingController : public QObject {
|
|||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ScriptingController(QObject* parent = nullptr);
|
||||
ScriptingController(ConfigController* config, QObject* parent = nullptr);
|
||||
~ScriptingController();
|
||||
|
||||
void setController(std::shared_ptr<CoreController> controller);
|
||||
|
@ -48,17 +50,22 @@ public:
|
|||
mScriptContext* context() { return &m_scriptContext; }
|
||||
ScriptingTextBufferModel* textBufferModel() const { return m_bufferModel; }
|
||||
|
||||
QString getFilenameFilters() const;
|
||||
|
||||
signals:
|
||||
void log(const QString&);
|
||||
void warn(const QString&);
|
||||
void error(const QString&);
|
||||
void textBufferCreated(ScriptingTextBuffer*);
|
||||
|
||||
void autorunScriptsOpened(QWidget* view);
|
||||
|
||||
public slots:
|
||||
void clearController();
|
||||
void updateVideoScale();
|
||||
void reset();
|
||||
void runCode(const QString& code);
|
||||
void openAutorunEdit();
|
||||
|
||||
void flushStorage();
|
||||
|
||||
|
@ -91,6 +98,7 @@ private:
|
|||
|
||||
mScriptGamepad m_gamepad;
|
||||
|
||||
AutorunScriptModel m_model;
|
||||
std::shared_ptr<CoreController> m_controller;
|
||||
InputController* m_inputController = nullptr;
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ using namespace QGBA;
|
|||
|
||||
ScriptingTextBuffer::ScriptingTextBuffer(QObject* parent)
|
||||
: QObject(parent)
|
||||
, m_document(this)
|
||||
{
|
||||
m_shim.init = &ScriptingTextBuffer::init;
|
||||
m_shim.deinit = &ScriptingTextBuffer::deinit;
|
||||
|
|
|
@ -40,6 +40,7 @@ ScriptingView::ScriptingView(ScriptingController* controller, ConfigController*
|
|||
connect(m_ui.buffers->selectionModel(), &QItemSelectionModel::currentChanged, this, &ScriptingView::selectBuffer);
|
||||
connect(m_ui.load, &QAction::triggered, this, &ScriptingView::load);
|
||||
connect(m_ui.loadMostRecent, &QAction::triggered, this, &ScriptingView::loadMostRecent);
|
||||
connect(m_ui.editAutorunScripts, &QAction::triggered, controller, &ScriptingController::openAutorunEdit);
|
||||
connect(m_ui.reset, &QAction::triggered, controller, &ScriptingController::reset);
|
||||
|
||||
m_mruFiles = m_config->getMRU(ConfigController::MRU::Script);
|
||||
|
@ -58,7 +59,7 @@ void ScriptingView::submitRepl() {
|
|||
}
|
||||
|
||||
void ScriptingView::load() {
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select script to load"), getFilters());
|
||||
QString filename = GBAApp::app()->getOpenFileName(this, tr("Select script to load"), m_controller->getFilenameFilters());
|
||||
if (!filename.isEmpty()) {
|
||||
if (!m_controller->loadFile(filename)) {
|
||||
return;
|
||||
|
@ -84,15 +85,6 @@ void ScriptingView::selectBuffer(const QModelIndex& current, const QModelIndex&)
|
|||
}
|
||||
}
|
||||
|
||||
QString ScriptingView::getFilters() const {
|
||||
QStringList filters;
|
||||
#ifdef USE_LUA
|
||||
filters.append(tr("Lua scripts (*.lua)"));
|
||||
#endif
|
||||
filters.append(tr("All files (*.*)"));
|
||||
return filters.join(";;");
|
||||
}
|
||||
|
||||
void ScriptingView::appendMRU(const QString& fname) {
|
||||
int index = m_mruFiles.indexOf(fname);
|
||||
if (index >= 0) {
|
||||
|
@ -121,4 +113,4 @@ void ScriptingView::updateMRU() {
|
|||
|
||||
void ScriptingView::checkEmptyMRU() {
|
||||
m_ui.loadMostRecent->setEnabled(!m_mruFiles.isEmpty());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,8 +28,6 @@ private slots:
|
|||
void selectBuffer(const QModelIndex& current, const QModelIndex& = QModelIndex());
|
||||
|
||||
private:
|
||||
QString getFilters() const;
|
||||
|
||||
void appendMRU(const QString&);
|
||||
void updateMRU();
|
||||
void checkEmptyMRU();
|
||||
|
|
|
@ -98,9 +98,10 @@
|
|||
</widget>
|
||||
<addaction name="load"/>
|
||||
<addaction name="mru"/>
|
||||
<addaction name="loadMostRecent"/>
|
||||
<addaction name="loadMostRecent"/>
|
||||
<addaction name="separator"/>
|
||||
<addaction name="reset"/>
|
||||
<addaction name="editAutorunScripts"/>
|
||||
</widget>
|
||||
<addaction name="menuFile"/>
|
||||
</widget>
|
||||
|
@ -110,7 +111,7 @@
|
|||
<string>Load script...</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="loadMostRecent">
|
||||
<action name="loadMostRecent">
|
||||
<property name="text">
|
||||
<string>&Load most recent</string>
|
||||
</property>
|
||||
|
@ -125,6 +126,11 @@
|
|||
<string>0</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="editAutorunScripts">
|
||||
<property name="text">
|
||||
<string>Edit autorun scripts...</string>
|
||||
</property>
|
||||
</action>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
<customwidget>
|
||||
|
|
|
@ -0,0 +1,210 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "platform/qt/library/LibraryModel.h"
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
|
||||
#include <QAbstractItemModelTester>
|
||||
#endif
|
||||
|
||||
#include <QSignalSpy>
|
||||
#include <QTest>
|
||||
|
||||
#define FIND_GBA_ROW(gba, gb) \
|
||||
int gba = findGBARow(); \
|
||||
if (gba < 0) QFAIL("Could not find gba row"); \
|
||||
int gb = 1 - gba;
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
class LibraryModelTest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
LibraryModel* model = nullptr;
|
||||
|
||||
int findGBARow() {
|
||||
for (int i = 0; i < model->rowCount(); i++) {
|
||||
if (model->index(i, 0).data() == "gba") {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
LibraryEntry makeGBA(const QString& name, uint32_t crc) {
|
||||
LibraryEntry entry;
|
||||
entry.base = "/gba";
|
||||
entry.filename = name + ".gba";
|
||||
entry.fullpath = entry.base + "/" + entry.filename;
|
||||
entry.title = name;
|
||||
entry.internalTitle = name.toUpper().toUtf8();
|
||||
entry.internalCode = entry.internalTitle.replace(" ", "").left(4);
|
||||
entry.platform = mPLATFORM_GBA;
|
||||
entry.filesize = entry.fullpath.size() * 4;
|
||||
entry.crc32 = crc;
|
||||
return entry;
|
||||
}
|
||||
|
||||
LibraryEntry makeGB(const QString& name, uint32_t crc) {
|
||||
LibraryEntry entry = makeGBA(name, crc);
|
||||
entry.base = "/gb";
|
||||
entry.filename = entry.filename.replace("gba", "gb");
|
||||
entry.fullpath = entry.fullpath.replace("gba", "gb");
|
||||
entry.platform = mPLATFORM_GB;
|
||||
entry.filesize /= 4;
|
||||
return entry;
|
||||
}
|
||||
|
||||
void addTestGames1() {
|
||||
model->addEntries({
|
||||
makeGBA("Test Game", 0x12345678),
|
||||
makeGBA("Another", 0x23456789),
|
||||
makeGB("Old Game", 0x87654321),
|
||||
});
|
||||
}
|
||||
|
||||
void addTestGames2() {
|
||||
model->addEntries({
|
||||
makeGBA("Game 3", 0x12345679),
|
||||
makeGBA("Game 4", 0x2345678A),
|
||||
makeGBA("Game 5", 0x2345678B),
|
||||
makeGB("Game 6", 0x87654322),
|
||||
makeGB("Game 7", 0x87654323),
|
||||
});
|
||||
}
|
||||
|
||||
void updateGame() {
|
||||
LibraryEntry game = makeGBA("Another", 0x88888888);
|
||||
model->updateEntries({ game });
|
||||
QModelIndex idx = find("Another");
|
||||
QVERIFY2(idx.isValid(), "game not found");
|
||||
QCOMPARE(idx.siblingAtColumn(LibraryModel::COL_CRC32).data(Qt::EditRole).toInt(), 0x88888888);
|
||||
}
|
||||
|
||||
void removeGames1() {
|
||||
model->removeEntries({ "/gba/Another.gba", "/gb/Game 6.gb" });
|
||||
QVERIFY2(!find("Another").isValid(), "game not removed");
|
||||
QVERIFY2(!find("Game 6").isValid(), "game not removed");
|
||||
}
|
||||
|
||||
void removeGames2() {
|
||||
model->removeEntries({ "/gb/Old Game.gb", "/gb/Game 7.gb" });
|
||||
QVERIFY2(!find("Old Game").isValid(), "game not removed");
|
||||
QVERIFY2(!find("Game 7").isValid(), "game not removed");
|
||||
}
|
||||
|
||||
QModelIndex find(const QString& name) {
|
||||
for (int i = 0; i < model->rowCount(); i++) {
|
||||
QModelIndex idx = model->index(i, 0);
|
||||
if (idx.data().toString() == name) {
|
||||
return idx;
|
||||
}
|
||||
for (int j = 0; j < model->rowCount(idx); j++) {
|
||||
QModelIndex child = model->index(j, 0, idx);
|
||||
if (child.data().toString() == name) {
|
||||
return child;
|
||||
}
|
||||
}
|
||||
}
|
||||
return QModelIndex();
|
||||
}
|
||||
|
||||
private slots:
|
||||
void init() {
|
||||
model = new LibraryModel();
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0)
|
||||
new QAbstractItemModelTester(model, QAbstractItemModelTester::FailureReportingMode::QtTest, model);
|
||||
#endif
|
||||
}
|
||||
|
||||
void cleanup() {
|
||||
delete model;
|
||||
model = nullptr;
|
||||
}
|
||||
|
||||
void testList() {
|
||||
addTestGames1();
|
||||
QCOMPARE(model->rowCount(), 3);
|
||||
QCOMPARE(model->m_games.size(), 3);
|
||||
addTestGames2();
|
||||
QCOMPARE(model->rowCount(), 8);
|
||||
QCOMPARE(model->m_games.size(), 8);
|
||||
updateGame();
|
||||
QCOMPARE(model->m_games.size(), 8);
|
||||
model->removeEntries({ "/gba/Another.gba", "/gb/Game 6.gb" });
|
||||
QCOMPARE(model->rowCount(), 6);
|
||||
QCOMPARE(model->m_games.size(), 6);
|
||||
model->removeEntries({ "/gb/Old Game.gb", "/gb/Game 7.gb" });
|
||||
QCOMPARE(model->rowCount(), 4);
|
||||
QCOMPARE(model->m_games.size(), 4);
|
||||
}
|
||||
|
||||
void testTree() {
|
||||
model->setTreeMode(true);
|
||||
addTestGames1();
|
||||
FIND_GBA_ROW(gbaRow, gbRow);
|
||||
QCOMPARE(model->rowCount(), 2);
|
||||
QCOMPARE(model->rowCount(model->index(gbRow, 0)), 1);
|
||||
QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 2);
|
||||
QCOMPARE(model->m_games.size(), 3);
|
||||
addTestGames2();
|
||||
QCOMPARE(model->rowCount(), 2);
|
||||
QCOMPARE(model->rowCount(model->index(gbRow, 0)), 3);
|
||||
QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 5);
|
||||
QCOMPARE(model->m_games.size(), 8);
|
||||
updateGame();
|
||||
QCOMPARE(model->m_games.size(), 8);
|
||||
removeGames1();
|
||||
QCOMPARE(model->rowCount(), 2);
|
||||
QCOMPARE(model->rowCount(model->index(gbRow, 0)), 2);
|
||||
QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 4);
|
||||
QCOMPARE(model->m_games.size(), 6);
|
||||
removeGames2();
|
||||
QVERIFY2(!find("gb").isValid(), "did not remove gb folder");
|
||||
QCOMPARE(model->rowCount(), 1);
|
||||
QCOMPARE(model->rowCount(model->index(0, 0)), 4);
|
||||
QCOMPARE(model->m_games.size(), 4);
|
||||
}
|
||||
|
||||
void modeSwitchTest1() {
|
||||
addTestGames1();
|
||||
{
|
||||
QSignalSpy resetSpy(model, SIGNAL(modelReset()));
|
||||
model->setTreeMode(true);
|
||||
QVERIFY(resetSpy.count());
|
||||
}
|
||||
FIND_GBA_ROW(gbaRow, gbRow);
|
||||
QCOMPARE(model->rowCount(), 2);
|
||||
QCOMPARE(model->rowCount(model->index(gbRow, 0)), 1);
|
||||
QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 2);
|
||||
{
|
||||
QSignalSpy resetSpy(model, SIGNAL(modelReset()));
|
||||
model->setTreeMode(false);
|
||||
QVERIFY(resetSpy.count());
|
||||
}
|
||||
addTestGames2();
|
||||
QCOMPARE(model->rowCount(), 8);
|
||||
}
|
||||
|
||||
void modeSwitchTest2() {
|
||||
model->setTreeMode(false);
|
||||
addTestGames1();
|
||||
model->setTreeMode(true);
|
||||
FIND_GBA_ROW(gbaRow, gbRow);
|
||||
QCOMPARE(model->rowCount(), 2);
|
||||
QCOMPARE(model->rowCount(model->index(gbRow, 0)), 1);
|
||||
QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 2);
|
||||
addTestGames2();
|
||||
QCOMPARE(model->rowCount(), 2);
|
||||
QCOMPARE(model->rowCount(model->index(gbRow, 0)), 3);
|
||||
QCOMPARE(model->rowCount(model->index(gbaRow, 0)), 5);
|
||||
model->setTreeMode(false);
|
||||
QCOMPARE(model->rowCount(), 8);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_MAIN(LibraryModelTest)
|
||||
#include "library.moc"
|
|
@ -0,0 +1,61 @@
|
|||
/* Copyright (c) 2013-2022 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "platform/qt/utils.h"
|
||||
|
||||
#include <QTest>
|
||||
|
||||
using namespace QGBA;
|
||||
|
||||
class SpanSetTest : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
private:
|
||||
void debugSpans(const SpanSet& spanSet) {
|
||||
QStringList debug;
|
||||
for (auto span : spanSet.spans) {
|
||||
debug << QStringLiteral("[%1, %2]").arg(span.left).arg(span.right);
|
||||
}
|
||||
qDebug() << QStringLiteral("SpanSet{%1}").arg(debug.join(", "));
|
||||
}
|
||||
|
||||
private slots:
|
||||
void oneSpan() {
|
||||
SpanSet spanSet;
|
||||
spanSet.add(1);
|
||||
spanSet.add(2);
|
||||
spanSet.add(3);
|
||||
QCOMPARE(spanSet.spans.size(), 1);
|
||||
spanSet.merge();
|
||||
QCOMPARE(spanSet.spans.size(), 1);
|
||||
}
|
||||
|
||||
void twoSpans() {
|
||||
SpanSet spanSet;
|
||||
spanSet.add(1);
|
||||
spanSet.add(2);
|
||||
spanSet.add(4);
|
||||
QCOMPARE(spanSet.spans.size(), 2);
|
||||
spanSet.merge();
|
||||
QCOMPARE(spanSet.spans.size(), 2);
|
||||
}
|
||||
|
||||
void mergeSpans() {
|
||||
SpanSet spanSet;
|
||||
spanSet.add(1);
|
||||
spanSet.add(3);
|
||||
spanSet.add(2);
|
||||
spanSet.add(5);
|
||||
spanSet.add(4);
|
||||
spanSet.add(7);
|
||||
spanSet.add(8);
|
||||
QCOMPARE(spanSet.spans.size(), 4);
|
||||
spanSet.merge();
|
||||
QCOMPARE(spanSet.spans.size(), 2);
|
||||
}
|
||||
};
|
||||
|
||||
QTEST_APPLESS_MAIN(SpanSetTest)
|
||||
#include "spanset.moc"
|
|
@ -5,7 +5,13 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
#include "utils.h"
|
||||
|
||||
#include <mgba/core/library.h>
|
||||
#ifdef M_CORE_GB
|
||||
#include <mgba/gb/interface.h>
|
||||
#endif
|
||||
|
||||
#include <QCoreApplication>
|
||||
#include <QHostAddress>
|
||||
#include <QKeySequence>
|
||||
#include <QObject>
|
||||
|
||||
|
@ -29,18 +35,25 @@ QString niceSizeFormat(size_t filesize) {
|
|||
return unit.arg(size, 0, 'f', int(size * 10) % 10 ? 1 : 0);
|
||||
}
|
||||
|
||||
QString nicePlatformFormat(mPlatform platform) {
|
||||
QString nicePlatformFormat(mPlatform platform, int validModels) {
|
||||
switch (platform) {
|
||||
#ifdef M_CORE_GBA
|
||||
case mPLATFORM_GBA:
|
||||
return QObject::tr("GBA");
|
||||
return "GBA";
|
||||
#endif
|
||||
#ifdef M_CORE_GB
|
||||
case mPLATFORM_GB:
|
||||
return QObject::tr("GB");
|
||||
if (validModels != M_LIBRARY_MODEL_UNKNOWN) {
|
||||
if (validModels & GB_MODEL_CGB) {
|
||||
return "GBC";
|
||||
} else if (validModels & GB_MODEL_SGB) {
|
||||
return "SGB";
|
||||
}
|
||||
}
|
||||
return "GB";
|
||||
#endif
|
||||
default:
|
||||
return QObject::tr("?");
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -177,4 +190,45 @@ QString keyName(int key) {
|
|||
}
|
||||
}
|
||||
|
||||
void SpanSet::add(int pos) {
|
||||
for (Span& span : spans) {
|
||||
if (pos == span.left - 1) {
|
||||
span.left = pos;
|
||||
return;
|
||||
} else if (pos == span.right + 1) {
|
||||
span.right = pos;
|
||||
return;
|
||||
}
|
||||
}
|
||||
spans << Span{ pos, pos };
|
||||
}
|
||||
|
||||
void SpanSet::merge() {
|
||||
int numSpans = spans.size();
|
||||
if (!numSpans) {
|
||||
return;
|
||||
}
|
||||
sort();
|
||||
QVector<Span> merged({ spans[0] });
|
||||
int lastRight = merged[0].right;
|
||||
for (int i = 1; i < numSpans; i++) {
|
||||
int right = spans[i].right;
|
||||
if (spans[i].left - 1 <= lastRight) {
|
||||
merged.back().right = right;
|
||||
} else {
|
||||
merged << spans[i];
|
||||
}
|
||||
lastRight = right;
|
||||
}
|
||||
spans = merged;
|
||||
}
|
||||
|
||||
void SpanSet::sort(bool reverse) {
|
||||
if (reverse) {
|
||||
std::sort(spans.begin(), spans.end(), std::greater<Span>());
|
||||
} else {
|
||||
std::sort(spans.begin(), spans.end());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
#include <QRect>
|
||||
#include <QSize>
|
||||
#include <QString>
|
||||
#include <QVector>
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
@ -30,7 +31,7 @@ enum class Endian {
|
|||
};
|
||||
|
||||
QString niceSizeFormat(size_t filesize);
|
||||
QString nicePlatformFormat(mPlatform platform);
|
||||
QString nicePlatformFormat(mPlatform platform, int validModels = 0);
|
||||
|
||||
bool convertAddress(const QHostAddress* input, Address* output);
|
||||
|
||||
|
@ -117,4 +118,29 @@ bool extractMatchingFile(VDir* dir, std::function<QString (VDirEntry*)> filter);
|
|||
|
||||
QString keyName(int key);
|
||||
|
||||
struct SpanSet {
|
||||
struct Span {
|
||||
int left;
|
||||
int right;
|
||||
|
||||
inline bool operator<(const Span& other) const { return left < other.left; }
|
||||
inline bool operator>(const Span& other) const { return left > other.left; }
|
||||
};
|
||||
|
||||
void add(int pos);
|
||||
void merge();
|
||||
void sort(bool reverse = false);
|
||||
|
||||
QVector<Span> spans;
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
QSet<T> qListToSet(const QList<T>& list) {
|
||||
#if (QT_VERSION < QT_VERSION_CHECK(6, 0, 0))
|
||||
return QSet<T>::fromList(list);
|
||||
#else
|
||||
return QSet<T>(list.begin(), list.end());
|
||||
#endif
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -109,7 +109,7 @@ static void _mSDLAudioCallback(void* context, Uint8* data, int len) {
|
|||
fauxClock = mCoreCalculateFramerateRatio(audioContext->core, audioContext->sync->fpsTarget);
|
||||
}
|
||||
mCoreSyncLockAudio(audioContext->sync);
|
||||
audioContext->sync->audioHighWater = audioContext->samples + audioContext->resampler.highWaterMark + audioContext->resampler.lowWaterMark;
|
||||
audioContext->sync->audioHighWater = audioContext->samples + audioContext->resampler.highWaterMark + audioContext->resampler.lowWaterMark + (audioContext->samples >> 6);
|
||||
audioContext->sync->audioHighWater *= sampleRate / (fauxClock * audioContext->obtainedSpec.freq);
|
||||
}
|
||||
mAudioResamplerSetSource(&audioContext->resampler, buffer, sampleRate / fauxClock, true);
|
||||
|
|
|
@ -396,6 +396,10 @@ void mSDLUpdateJoysticks(struct mSDLEvents* events, const struct Configuration*
|
|||
events->players[i]->joystick = NULL;
|
||||
}
|
||||
struct SDL_JoystickCombo* joystick = _mSDLOpenJoystick(events, event.jdevice.which);
|
||||
if (!joystick) {
|
||||
mLOG(SDL_EVENTS, ERROR, "SDL joystick hotplug attach failed: %s", SDL_GetError());
|
||||
continue;
|
||||
}
|
||||
|
||||
for (i = 0; i < events->playersAttached && i < MAX_PLAYERS; ++i) {
|
||||
if (joysticks[i] != -1) {
|
||||
|
|
|
@ -131,6 +131,7 @@ void mScriptContextAttachStdlib(struct mScriptContext* context) {
|
|||
mScriptContextExportConstants(context, "CHECKSUM", (struct mScriptKVPair[]) {
|
||||
mSCRIPT_CONSTANT_PAIR(mCHECKSUM, CRC32),
|
||||
mSCRIPT_CONSTANT_PAIR(mCHECKSUM, MD5),
|
||||
mSCRIPT_CONSTANT_PAIR(mCHECKSUM, SHA1),
|
||||
mSCRIPT_KV_SENTINEL
|
||||
});
|
||||
#ifdef M_CORE_GBA
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
cmake_minimum_required (VERSION 3.1.0)
|
||||
cmake_minimum_required (VERSION 3.10)
|
||||
project (DiscordRPC)
|
||||
|
||||
include(GNUInstallDirs)
|
||||
|
@ -9,6 +9,10 @@ file(GLOB_RECURSE ALL_SOURCE_FILES
|
|||
src/*.cpp src/*.h src/*.c
|
||||
)
|
||||
|
||||
if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-class-memaccess")
|
||||
endif()
|
||||
|
||||
# add subdirs
|
||||
|
||||
add_subdirectory(src)
|
||||
|
|
|
@ -7,6 +7,7 @@ set(BASE_SOURCE_FILES
|
|||
gbk-table.c
|
||||
hash.c
|
||||
md5.c
|
||||
sha1.c
|
||||
string.c
|
||||
table.c
|
||||
vector.c
|
||||
|
|
|
@ -904,10 +904,10 @@ uint32_t mColorConvert(uint32_t color, enum mColorFormat from, enum mColorFormat
|
|||
return color;
|
||||
}
|
||||
|
||||
int r = 0;
|
||||
int g = 0;
|
||||
int b = 0;
|
||||
int a = 0xFF;
|
||||
uint32_t r = 0;
|
||||
uint32_t g = 0;
|
||||
uint32_t b = 0;
|
||||
uint32_t a = 0xFF;
|
||||
|
||||
switch (from) {
|
||||
case mCOLOR_ARGB8:
|
||||
|
|
|
@ -0,0 +1,258 @@
|
|||
/* Copyright (c) 2013-2025 Jeffrey Pfau
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*
|
||||
* Based on https://github.com/clibs/sha1
|
||||
*
|
||||
* Test Vectors (from FIPS PUB 180-1)
|
||||
* "abc"
|
||||
* A9993E36 4706816A BA3E2571 7850C26C 9CD0D89D
|
||||
* "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
|
||||
* 84983E44 1C3BD26E BAAE4AA1 F95129E5 E54670F1
|
||||
* A million repetitions of "a"
|
||||
* 34AA973C D4C4DAA4 F61EEB2B DBAD2731 6534016F
|
||||
*/
|
||||
#include <mgba-util/sha1.h>
|
||||
|
||||
#include <mgba-util/vfs.h>
|
||||
|
||||
/* #define SHA1HANDSOFF * Copies data before messing with it. */
|
||||
|
||||
#define SHA1HANDSOFF
|
||||
|
||||
#define rol(value, bits) (((value) << (bits)) | ((value) >> (32 - (bits))))
|
||||
|
||||
/* blk0() and blk() perform the initial expand. */
|
||||
/* I got the idea of expanding during the round function from SSLeay */
|
||||
#ifndef __BIG_ENDIAN__
|
||||
#define blk0(i) (block->l[i] = (rol(block->l[i],24)&0xFF00FF00) \
|
||||
|(rol(block->l[i],8)&0x00FF00FF))
|
||||
#else
|
||||
#define blk0(i) block->l[i]
|
||||
#endif
|
||||
#define blk(i) (block->l[i&15] = rol(block->l[(i+13)&15]^block->l[(i+8)&15] \
|
||||
^block->l[(i+2)&15]^block->l[i&15],1))
|
||||
|
||||
/* (R0+R1), R2, R3, R4 are the different operations used in SHA1 */
|
||||
#define R0(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk0(i)+0x5A827999+rol(v,5);w=rol(w,30);
|
||||
#define R1(v,w,x,y,z,i) z+=((w&(x^y))^y)+blk(i)+0x5A827999+rol(v,5);w=rol(w,30);
|
||||
#define R2(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0x6ED9EBA1+rol(v,5);w=rol(w,30);
|
||||
#define R3(v,w,x,y,z,i) z+=(((w|x)&y)|(w&x))+blk(i)+0x8F1BBCDC+rol(v,5);w=rol(w,30);
|
||||
#define R4(v,w,x,y,z,i) z+=(w^x^y)+blk(i)+0xCA62C1D6+rol(v,5);w=rol(w,30);
|
||||
|
||||
/* Hash a single 512-bit block. This is the core of the algorithm. */
|
||||
static void sha1Transform(uint32_t state[5], const uint8_t buffer[64]) {
|
||||
uint32_t a, b, c, d, e;
|
||||
|
||||
typedef union {
|
||||
unsigned char c[64];
|
||||
uint32_t l[16];
|
||||
} CHAR64LONG16;
|
||||
|
||||
#ifdef SHA1HANDSOFF
|
||||
CHAR64LONG16 block[1]; /* use array to appear as a pointer */
|
||||
|
||||
memcpy(block, buffer, 64);
|
||||
#else
|
||||
/* The following had better never be used because it causes the
|
||||
* pointer-to-const buffer to be cast into a pointer to non-const.
|
||||
* And the result is written through. I threw a "const" in, hoping
|
||||
* this will cause a diagnostic.
|
||||
*/
|
||||
CHAR64LONG16 *block = (const CHAR64LONG16 *) buffer;
|
||||
#endif
|
||||
/* Copy context->state[] to working vars */
|
||||
a = state[0];
|
||||
b = state[1];
|
||||
c = state[2];
|
||||
d = state[3];
|
||||
e = state[4];
|
||||
/* 4 rounds of 20 operations each. Loop unrolled. */
|
||||
R0(a, b, c, d, e, 0);
|
||||
R0(e, a, b, c, d, 1);
|
||||
R0(d, e, a, b, c, 2);
|
||||
R0(c, d, e, a, b, 3);
|
||||
R0(b, c, d, e, a, 4);
|
||||
R0(a, b, c, d, e, 5);
|
||||
R0(e, a, b, c, d, 6);
|
||||
R0(d, e, a, b, c, 7);
|
||||
R0(c, d, e, a, b, 8);
|
||||
R0(b, c, d, e, a, 9);
|
||||
R0(a, b, c, d, e, 10);
|
||||
R0(e, a, b, c, d, 11);
|
||||
R0(d, e, a, b, c, 12);
|
||||
R0(c, d, e, a, b, 13);
|
||||
R0(b, c, d, e, a, 14);
|
||||
R0(a, b, c, d, e, 15);
|
||||
R1(e, a, b, c, d, 16);
|
||||
R1(d, e, a, b, c, 17);
|
||||
R1(c, d, e, a, b, 18);
|
||||
R1(b, c, d, e, a, 19);
|
||||
R2(a, b, c, d, e, 20);
|
||||
R2(e, a, b, c, d, 21);
|
||||
R2(d, e, a, b, c, 22);
|
||||
R2(c, d, e, a, b, 23);
|
||||
R2(b, c, d, e, a, 24);
|
||||
R2(a, b, c, d, e, 25);
|
||||
R2(e, a, b, c, d, 26);
|
||||
R2(d, e, a, b, c, 27);
|
||||
R2(c, d, e, a, b, 28);
|
||||
R2(b, c, d, e, a, 29);
|
||||
R2(a, b, c, d, e, 30);
|
||||
R2(e, a, b, c, d, 31);
|
||||
R2(d, e, a, b, c, 32);
|
||||
R2(c, d, e, a, b, 33);
|
||||
R2(b, c, d, e, a, 34);
|
||||
R2(a, b, c, d, e, 35);
|
||||
R2(e, a, b, c, d, 36);
|
||||
R2(d, e, a, b, c, 37);
|
||||
R2(c, d, e, a, b, 38);
|
||||
R2(b, c, d, e, a, 39);
|
||||
R3(a, b, c, d, e, 40);
|
||||
R3(e, a, b, c, d, 41);
|
||||
R3(d, e, a, b, c, 42);
|
||||
R3(c, d, e, a, b, 43);
|
||||
R3(b, c, d, e, a, 44);
|
||||
R3(a, b, c, d, e, 45);
|
||||
R3(e, a, b, c, d, 46);
|
||||
R3(d, e, a, b, c, 47);
|
||||
R3(c, d, e, a, b, 48);
|
||||
R3(b, c, d, e, a, 49);
|
||||
R3(a, b, c, d, e, 50);
|
||||
R3(e, a, b, c, d, 51);
|
||||
R3(d, e, a, b, c, 52);
|
||||
R3(c, d, e, a, b, 53);
|
||||
R3(b, c, d, e, a, 54);
|
||||
R3(a, b, c, d, e, 55);
|
||||
R3(e, a, b, c, d, 56);
|
||||
R3(d, e, a, b, c, 57);
|
||||
R3(c, d, e, a, b, 58);
|
||||
R3(b, c, d, e, a, 59);
|
||||
R4(a, b, c, d, e, 60);
|
||||
R4(e, a, b, c, d, 61);
|
||||
R4(d, e, a, b, c, 62);
|
||||
R4(c, d, e, a, b, 63);
|
||||
R4(b, c, d, e, a, 64);
|
||||
R4(a, b, c, d, e, 65);
|
||||
R4(e, a, b, c, d, 66);
|
||||
R4(d, e, a, b, c, 67);
|
||||
R4(c, d, e, a, b, 68);
|
||||
R4(b, c, d, e, a, 69);
|
||||
R4(a, b, c, d, e, 70);
|
||||
R4(e, a, b, c, d, 71);
|
||||
R4(d, e, a, b, c, 72);
|
||||
R4(c, d, e, a, b, 73);
|
||||
R4(b, c, d, e, a, 74);
|
||||
R4(a, b, c, d, e, 75);
|
||||
R4(e, a, b, c, d, 76);
|
||||
R4(d, e, a, b, c, 77);
|
||||
R4(c, d, e, a, b, 78);
|
||||
R4(b, c, d, e, a, 79);
|
||||
/* Add the working vars back into context.state[] */
|
||||
state[0] += a;
|
||||
state[1] += b;
|
||||
state[2] += c;
|
||||
state[3] += d;
|
||||
state[4] += e;
|
||||
/* Wipe variables */
|
||||
a = b = c = d = e = 0;
|
||||
#ifdef SHA1HANDSOFF
|
||||
memset(block, '\0', sizeof(block));
|
||||
#endif
|
||||
}
|
||||
|
||||
/* shaInit - Initialize new context */
|
||||
void sha1Init(struct SHA1Context* context) {
|
||||
/* SHA1 initialization constants */
|
||||
context->state[0] = 0x67452301;
|
||||
context->state[1] = 0xEFCDAB89;
|
||||
context->state[2] = 0x98BADCFE;
|
||||
context->state[3] = 0x10325476;
|
||||
context->state[4] = 0xC3D2E1F0;
|
||||
context->count[0] = context->count[1] = 0;
|
||||
}
|
||||
|
||||
/* Run your data through this. */
|
||||
void sha1Update(struct SHA1Context* context, const void* data, size_t len) {
|
||||
size_t i;
|
||||
size_t j;
|
||||
|
||||
j = context->count[0];
|
||||
if ((context->count[0] += len << 3) < j) {
|
||||
++context->count[1];
|
||||
}
|
||||
context->count[1] += (len >> 29);
|
||||
j = (j >> 3) & 63;
|
||||
if ((j + len) > 63) {
|
||||
memcpy(&context->buffer[j], data, (i = 64 - j));
|
||||
sha1Transform(context->state, context->buffer);
|
||||
for (; i + 63 < len; i += 64) {
|
||||
sha1Transform(context->state, &((uint8_t*) data)[i]);
|
||||
}
|
||||
j = 0;
|
||||
} else {
|
||||
i = 0;
|
||||
}
|
||||
memcpy(&context->buffer[j], &((uint8_t*) data)[i], len - i);
|
||||
}
|
||||
|
||||
/* Add padding and return the message digest. */
|
||||
void sha1Finalize(uint8_t digest[20], struct SHA1Context* context) {
|
||||
unsigned i;
|
||||
uint8_t finalcount[8];
|
||||
uint8_t c;
|
||||
|
||||
for (i = 0; i < 8; ++i) {
|
||||
finalcount[i] = (uint8_t) ((context->count[(i >= 4 ? 0 : 1)] >> ((3 - (i & 3)) * 8)) & 255); /* Endian independent */
|
||||
}
|
||||
c = 0200;
|
||||
sha1Update(context, &c, 1);
|
||||
while ((context->count[0] & 504) != 448) {
|
||||
c = 0000;
|
||||
sha1Update(context, &c, 1);
|
||||
}
|
||||
sha1Update(context, finalcount, 8); /* Should cause a SHA1Transform() */
|
||||
for (i = 0; i < 20; ++i) {
|
||||
digest[i] = (uint8_t) ((context->state[i >> 2] >> ((3 - (i & 3)) * 8)) & 255);
|
||||
}
|
||||
/* Wipe variables */
|
||||
memset(context, '\0', sizeof(*context));
|
||||
memset(&finalcount, '\0', sizeof(finalcount));
|
||||
}
|
||||
|
||||
void sha1Buffer(const void* input, size_t len, uint8_t* result) {
|
||||
struct SHA1Context ctx;
|
||||
size_t i;
|
||||
|
||||
sha1Init(&ctx);
|
||||
for (i = 0; i + 63 < len; i += 64) {
|
||||
sha1Update(&ctx, &((const uint8_t*) input)[i], 64);
|
||||
}
|
||||
for (; i < len; ++i) {
|
||||
sha1Update(&ctx, &((const uint8_t*) input)[i], 1);
|
||||
}
|
||||
sha1Finalize(result, &ctx);
|
||||
}
|
||||
|
||||
bool sha1File(struct VFile* vf, uint8_t* result) {
|
||||
struct SHA1Context ctx;
|
||||
uint8_t buffer[2048];
|
||||
sha1Init(&ctx);
|
||||
|
||||
ssize_t read;
|
||||
ssize_t position = vf->seek(vf, 0, SEEK_CUR);
|
||||
if (vf->seek(vf, 0, SEEK_SET) < 0) {
|
||||
return false;
|
||||
}
|
||||
while ((read = vf->read(vf, buffer, sizeof(buffer))) > 0) {
|
||||
sha1Update(&ctx, buffer, read);
|
||||
}
|
||||
vf->seek(vf, position, SEEK_SET);
|
||||
if (read < 0) {
|
||||
return false;
|
||||
}
|
||||
sha1Finalize(result, &ctx);
|
||||
return true;
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
|
||||
#include <mgba-util/crc32.h>
|
||||
#include <mgba-util/md5.h>
|
||||
#include <mgba-util/sha1.h>
|
||||
|
||||
M_TEST_DEFINE(emptyCrc32) {
|
||||
uint8_t buffer[1] = {0};
|
||||
|
@ -115,6 +116,92 @@ M_TEST_DEFINE(twoBlockMd5) {
|
|||
}), 16);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(emptySha1) {
|
||||
uint8_t buffer[1] = {0};
|
||||
uint8_t digest[20] = {0};
|
||||
sha1Buffer(buffer, 0, digest);
|
||||
assert_memory_equal(digest, ((uint8_t[]) {
|
||||
0xDA, 0x39, 0xA3, 0xEE, 0x5E, 0x6B, 0x4B, 0x0D, 0x32, 0x55,
|
||||
0xBF, 0xEF, 0x95, 0x60, 0x18, 0x90, 0xAF, 0xD8, 0x07, 0x09
|
||||
}), 16);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(newlineSha1) {
|
||||
uint8_t buffer[1] = { '\n' };
|
||||
uint8_t digest[20] = {0};
|
||||
sha1Buffer(buffer, 1, digest);
|
||||
assert_memory_equal(digest, ((uint8_t[]) {
|
||||
0xAD, 0xC8, 0x3B, 0x19, 0xE7, 0x93, 0x49, 0x1B, 0x1C, 0x6E,
|
||||
0xA0, 0xFD, 0x8B, 0x46, 0xCD, 0x9F, 0x32, 0xE5, 0x92, 0xFC
|
||||
}), 20);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(fullBlockSha1) {
|
||||
uint8_t buffer[64] = {
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
};
|
||||
uint8_t digest[20] = {0};
|
||||
sha1Buffer(buffer, 64, digest);
|
||||
assert_memory_equal(digest, ((uint8_t[]) {
|
||||
0xCB, 0x4D, 0xD3, 0xDA, 0xCA, 0x2D, 0x6F, 0x25, 0x44, 0xBC,
|
||||
0x0D, 0xAA, 0x6B, 0xEB, 0xB7, 0x8A, 0xED, 0x0B, 0xD0, 0x34
|
||||
}), 20);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(overflowBlockSha1) {
|
||||
uint8_t buffer[65] = {
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x0a,
|
||||
};
|
||||
uint8_t digest[20] = {0};
|
||||
sha1Buffer(buffer, 65, digest);
|
||||
assert_memory_equal(digest, ((uint8_t[]) {
|
||||
0xA3, 0x96, 0x68, 0x5E, 0xF7, 0x73, 0x87, 0x13, 0x2C, 0x43,
|
||||
0x64, 0x42, 0x2D, 0x16, 0x65, 0x39, 0x65, 0x6F, 0xB8, 0x93
|
||||
}), 20);
|
||||
}
|
||||
|
||||
M_TEST_DEFINE(twoBlockSha1) {
|
||||
uint8_t buffer[128] = {
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x20,
|
||||
};
|
||||
uint8_t digest[20] = {0};
|
||||
sha1Buffer(buffer, 128, digest);
|
||||
assert_memory_equal(digest, ((uint8_t[]) {
|
||||
0xFF, 0xB5, 0xE5, 0xD9, 0x6E, 0x19, 0x71, 0x4F, 0xFE, 0xF6,
|
||||
0x0A, 0xC8, 0x74, 0x9E, 0xCA, 0xEF, 0xBE, 0xC9, 0xD2, 0x95
|
||||
}), 20);
|
||||
}
|
||||
|
||||
M_TEST_SUITE_DEFINE(Hashes,
|
||||
cmocka_unit_test(emptyCrc32),
|
||||
cmocka_unit_test(newlineCrc32),
|
||||
|
@ -127,4 +214,9 @@ M_TEST_SUITE_DEFINE(Hashes,
|
|||
cmocka_unit_test(fullBlockMd5),
|
||||
cmocka_unit_test(overflowBlockMd5),
|
||||
cmocka_unit_test(twoBlockMd5),
|
||||
cmocka_unit_test(emptySha1),
|
||||
cmocka_unit_test(newlineSha1),
|
||||
cmocka_unit_test(fullBlockSha1),
|
||||
cmocka_unit_test(overflowBlockSha1),
|
||||
cmocka_unit_test(twoBlockSha1),
|
||||
)
|
||||
|
|
|
@ -72,14 +72,33 @@ M_TEST_DEFINE(openNullMemChunkNonzero) {
|
|||
}
|
||||
|
||||
M_TEST_DEFINE(resizeMem) {
|
||||
uint8_t bytes[32];
|
||||
uint8_t bytes[32] = {
|
||||
0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
|
||||
0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
|
||||
0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F
|
||||
};
|
||||
struct VFile* vf = VFileFromMemory(bytes, 32);
|
||||
assert_non_null(vf);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_END), 32);
|
||||
assert_int_equal(vf->size(vf), 32);
|
||||
|
||||
vf->truncate(vf, 64);
|
||||
assert_int_equal(vf->size(vf), 32);
|
||||
assert_int_equal(bytes[15], 0x0F);
|
||||
assert_int_equal(bytes[16], 0x10);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 32);
|
||||
|
||||
vf->truncate(vf, 16);
|
||||
assert_int_equal(vf->size(vf), 16);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 16);
|
||||
|
||||
vf->truncate(vf, 64);
|
||||
assert_int_equal(vf->size(vf), 32);
|
||||
assert_int_equal(bytes[15], 0x0F);
|
||||
assert_int_equal(bytes[16], 0x00);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 16);
|
||||
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
|
@ -87,11 +106,16 @@ M_TEST_DEFINE(resizeConstMem) {
|
|||
uint8_t bytes[32] = {0};
|
||||
struct VFile* vf = VFileFromConstMemory(bytes, 32);
|
||||
assert_non_null(vf);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_END), 32);
|
||||
assert_int_equal(vf->size(vf), 32);
|
||||
|
||||
vf->truncate(vf, 64);
|
||||
assert_int_equal(vf->size(vf), 32);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 32);
|
||||
|
||||
vf->truncate(vf, 16);
|
||||
assert_int_equal(vf->size(vf), 32);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 32);
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
|
@ -99,11 +123,16 @@ M_TEST_DEFINE(resizeMemChunk) {
|
|||
uint8_t bytes[32] = {0};
|
||||
struct VFile* vf = VFileMemChunk(bytes, 32);
|
||||
assert_non_null(vf);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_END), 32);
|
||||
assert_int_equal(vf->size(vf), 32);
|
||||
|
||||
vf->truncate(vf, 64);
|
||||
assert_int_equal(vf->size(vf), 64);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 32);
|
||||
|
||||
vf->truncate(vf, 16);
|
||||
assert_int_equal(vf->size(vf), 16);
|
||||
assert_int_equal(vf->seek(vf, 0, SEEK_CUR), 16);
|
||||
vf->close(vf);
|
||||
}
|
||||
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue