Compare commits

...

190 Commits

Author SHA1 Message Date
Vicki Pfau 1c61b54208 CHANGES: Update for 0.10.3 2024-01-07 19:17:00 -08:00
Vicki Pfau 15ec8eb795 Res: Update patrons.txt 2024-01-07 19:15:52 -08:00
Vicki Pfau 383dcda998 Res: Update nointro.dat 2024-01-07 19:13:02 -08:00
Vicki Pfau e1a5329f30 Qt: Fix screen freezing on macOS after closing save state window (fixes #2885) 2024-01-07 16:58:30 -08:00
Vicki Pfau 0227d2db39 VFS: Use anonymousMemoryMap for large 7z allocations (fixes #3013) 2023-12-21 22:58:36 -08:00
Vicki Pfau 3f4cb4dbcb GB Audio: Improve "zombie mode" emulation in CGB mode (fixes #2029) 2023-12-20 02:53:21 -08:00
Daniel Simon 960d98a554 Qt: Fix generic icon on Wayland 2023-12-20 02:19:36 -08:00
Vicki Pfau 9a45bf94aa GB Audio: Fix restarting envelope when writing to register (fixes #3067) 2023-12-20 02:18:42 -08:00
Vicki Pfau f8112aee59 mGUI: Persist fast forwarding after closing menu (fixes #2414) 2023-12-18 22:58:55 -08:00
Vicki Pfau 68e5a0aa47 GB: Fix applying a patch that changes the cartridge mapper (fixes #3077) 2023-12-11 22:07:41 -08:00
Vicki Pfau 7be38eaed5 Qt: Do initial macOS resource staging in local app bundle 2023-12-11 22:07:41 -08:00
Vicki Pfau 3c5caf7c53 Qt: Move install in CMake so install scripts get run first 2023-12-11 22:07:41 -08:00
Vicki Pfau f4a5d86f88 Qt: Do codesigning on macOS 2023-12-11 22:07:41 -08:00
Vicki Pfau 3520607ebc Res: Add entitlements list for macOS 2023-12-11 22:06:49 -08:00
Vicki Pfau 4f50fe04fe CMake: Bump minimum macOS to 10.7 when building against SDL2 2023-12-10 23:53:35 -08:00
Vicki Pfau c6afa29215 Revert "SDL: Fix minor leak if debugger isn't used"
This reverts commit ba83c4d9d3.
2023-11-25 22:59:49 -08:00
Vicki Pfau ba83c4d9d3 SDL: Fix minor leak if debugger isn't used 2023-11-24 22:30:52 -08:00
Vicki Pfau 0e3d55c2ac GBA Savedata: Fix crash when resizing flash save games for RTC data 2023-11-24 22:30:51 -08:00
nia ef9c5bd096 Scripting: Check for ENOTRECOVERABLE
It's part of "robust mutexes" and may not necessarily be available
(e.g. on NetBSD 9)
2023-11-24 22:29:08 -08:00
Vicki Pfau 5ecce03fba FFmpeg: Fix deprecation warnings 2023-11-06 23:16:37 -08:00
Vicki Pfau 0b003fe230 GBA Audio: Only read MP2k context addresses if valid 2023-10-29 17:11:01 -07:00
Vicki Pfau a362317f46 CMake: Fix DEFAULT_LTO 2023-10-29 17:08:04 -07:00
leo60228 73ac3dad4f Debugger: Advertise QStartNoAckMode support 2023-10-29 17:08:04 -07:00
leo60228 1d4bf372b8 Debugger: Fix off-by-one breaking StartNoAckMode 2023-10-29 17:08:04 -07:00
Vicki Pfau da8f4f28f3 Wii: Fix build 2023-09-27 20:24:40 -07:00
David Spickett 35cdd5d242 Debugger: Correct PC value when read with 'p' instead of 'g' packet
The 'g' packet is handled by _readGPRs which has a special case for
the PC due to the way the CPU reports the PC value.

This was added by added by a967f9aac4.

The 'p' packet is handled by _readRegister which did not have this
special case for PC. This lead to GDB reporting the correct PC value
but LLDB not, as the latter used 'p' instead.

This meant you saw things like this:
    0x80002a4 <+16>: str    r0, [sp, #0x4]
    0x80002a8 <+20>: bl     0x80001f0      <----------expected to be here.
->  0x80002ac <+24>: b      0x80002b0

Where you expected to be about to bl to another function,
but it looked like you had already done it.

And more obviously, when you first attached to the GDB stub,
the PC was reported as 4 not 0.
2023-09-27 20:24:40 -07:00
Vicki Pfau d2205d3475 Scripting: Enhance error reporting 2023-09-27 20:24:40 -07:00
Vicki Pfau 404e84b045 Scripting: Simplify console implementation 2023-09-27 20:24:40 -07:00
Vicki Pfau 31235a6b2c Scripting: Fix console error log level 2023-09-16 01:37:11 -07:00
Vicki Pfau 0fb8b782be GB Core: Fix exported size information 2023-09-16 01:36:15 -07:00
Vicki Pfau c5dc97a432 GBA: Make sure unloading a ROM frees the AGBPrint buffer 2023-09-16 01:36:10 -07:00
Vicki Pfau 9adb1faef2 Qt: SaveConverter doesn't use the controller 2023-09-16 01:35:34 -07:00
Vicki Pfau 9b98a19a37 Core: Fix potential UAF when loading a ROM in a VDir 2023-09-16 01:35:23 -07:00
Vicki Pfau ddee0af216 GBA Core: Fix Flash 1M memory block descriptor 2023-09-16 01:34:29 -07:00
Vicki Pfau 35bbc74cc2 Vita: Fix camera setting not appearing (fixes #3012) 2023-08-28 13:29:24 -07:00
Vicki Pfau 510d1afa4a GBA Audio: Fix sample position issues when rate changes (fixes #3006) 2023-08-23 14:35:50 -07:00
Vicki Pfau d6291bcc05 Qt: Fix mute settings not being loaded on setting screen (fixes #2990) 2023-08-08 19:34:35 -07:00
shenef e86e9d0c76 Qt: Mark video encoding options as untranslatable 2023-08-05 16:34:56 -07:00
Vicki Pfau cda008eef5 Qt: Re-enable sync for multiplayer windows that aren't connected (fixes #2974) 2023-08-04 23:20:39 -07:00
Vicki Pfau fcb2f11464 GUI: Add missing include 2023-07-23 22:49:59 -07:00
Vicki Pfau eb2233eaaa GBA GPIO: Fix tilt scale and orientation (fixes #2703) 2023-07-23 22:49:55 -07:00
Vicki Pfau 1d37620c8c Updater: Fix overwriting directories with files 2023-07-18 01:43:07 -07:00
Vicki Pfau 1ec45502fb FFmpeg: Fix isampleRate initialization 2023-07-17 21:37:37 -07:00
Vicki Pfau 1dc94c999a Qt: Fix leak if loading a save file fails 2023-07-17 21:36:23 -07:00
Vicki Pfau afb614ad03 ARM: Fake bpkt instruction should take no cycles (fixes #2551) 2023-07-05 22:11:59 -07:00
Vicki Pfau a51cb3f921 GBA SIO: Fix normal mode SI/SO semantics (fixes #2925) 2023-07-03 23:15:47 -07:00
Vicki Pfau 1acb7dfa9e Qt: Add exporting of SAV + RTC GB saves from Save Converter to strip RTC data 2023-07-03 16:10:35 -07:00
Vicki Pfau b06c22a408 GB Audio: Force update channels 1/2 if updating from a register write 2023-07-03 16:10:05 -07:00
Vicki Pfau 2669e0f772 GB Audio: Update channels 1/2 irregularly if silent 2023-07-03 16:10:05 -07:00
Vicki Pfau 87c9d3851e ARM: Remove obsolete force-alignment in `bx pc` (fixes #2964) 2023-06-29 01:29:55 -07:00
Vicki Pfau 0d353a740f GB Audio: Fix channel 1 restarting if sweep applies after stop (fixes #2965) 2023-06-29 01:29:38 -07:00
Vicki Pfau 72379168cb GB: Add missing CGB0 BIOS to model detection 2023-06-26 04:45:59 -07:00
Vicki Pfau 76fe63a254 GBA Audio: Fix initial channel 3 wave RAM (fixes #2947) 2023-06-26 04:45:59 -07:00
Vicki Pfau 24f3d8808f GBA Audio: Fix sample timing drifting when changing sample interval 2023-06-19 22:38:38 -07:00
Vicki Pfau 6c4ca7724f Qt: Add exporting of SAV + RTC GBA saves from Save Converter to strip RTC data 2023-06-18 15:18:34 -07:00
Vicki Pfau 276e911e38 Qt: Reduce minimum size of GB palette color pickers 2023-06-13 16:08:11 -07:00
Vicki Pfau e49fe46c26 Qt: Attempt to shorten Game Boy settings pane 2023-06-13 16:08:08 -07:00
Vicki Pfau 724b79b035 Scripting: Fix build against Lua 5.2 2023-06-04 20:31:04 -07:00
Vicki Pfau c37949fee7 Core: Only attempt to open symbol file if basedir exists 2023-06-04 20:30:47 -07:00
May d99f7b9912 [UI bug] mGBA doesn't update savestate screenshots until you move the cursor over other savestates (#2929) 2023-06-04 20:30:35 -07:00
Vicki Pfau 97162280f9 GB I/O: Read back proper SVBK value after writing 0 (fixes #2921) 2023-05-09 14:51:47 -07:00
Vicki Pfau 84d600cba6 GB SIO: Disabling SIO should cancel pending transfers (fixes #2537) 2023-05-08 04:43:54 -07:00
Vicki Pfau 21cfb15f99 mGUI: Make "bios" name check case-insensitive 2023-05-08 04:43:36 -07:00
Vicki Pfau ff3c68bbd6 GBA BIOS: Fix clobbering registers with word-sized CpuSet 2023-04-28 22:47:11 -07:00
Vicki Pfau 5f762e68b8 GB Audio: Fix channels 1/2 staying muted if restarted after long silence 2023-04-28 19:35:45 -07:00
Vicki Pfau 5a94780871 Debugger: Reject traces with negative trace amounts (fixes #2900) 2023-04-26 00:49:45 -07:00
Vicki Pfau 078ce85774 CMake: Bump version 2023-04-26 00:49:45 -07:00
Vicki Pfau 2fb5545061 CHANGES: Update for 0.10.2 2023-04-23 21:15:24 -07:00
Vicki Pfau c5743e7931 Res: Update no-intro 2023-04-23 21:15:24 -07:00
Vicki Pfau b876218060 Res: Update Patrons for April 2023-04-23 20:25:33 -07:00
Vicki Pfau 76acad90a6 Qt: Only disable swapInterval when fast-forwarding 2023-04-23 20:09:11 -07:00
Vicki Pfau c0e3db2bb4 GB, GBA Audio: Clamp audio buffer size to 8192 2023-04-23 20:09:11 -07:00
Vicki Pfau cbe87be222 GBA Video: Don't repeat yourself 2023-04-18 21:42:40 -07:00
Vicki Pfau e15cca9623 GBA Video: Fix interpolation issues with OpenGL renderer 2023-04-18 21:42:40 -07:00
Vicki Pfau b0441cffd0 CHANGES: Update 2023-04-18 21:40:27 -07:00
Vicki Pfau 2f2287683a Qt: Rip out OpenGL proxy thread 2023-04-18 21:38:56 -07:00
Vicki Pfau 70bbe06bfb Qt: Manually toggle swap interval as needed 2023-04-18 21:37:12 -07:00
Vicki Pfau d79a83321c Vita: Work around broken mktime implementation in Vita SDK (fixes #2876) 2023-04-12 03:35:42 -07:00
Vicki Pfau 8344efdccd Scripting: Return proper callback ID from socket.add 2023-04-12 03:35:33 -07:00
Vicki Pfau 7517cce0af FFmpeg: Fix comment 2023-04-07 01:12:29 -07:00
Vicki Pfau 5a642ae1f1 FFmpeg: Force lower sample rate for codecs not supporting high rates (fixes #2869) 2023-04-07 00:58:28 -07:00
Vicki Pfau 9d7f47413c FFmpeg: Fix buffer size rounding for audio encoding 2023-04-07 00:49:56 -07:00
Vicki Pfau 6806850441 Core: Fix memory leaks in mCacheSet 2023-04-05 05:06:28 -07:00
Vicki Pfau 121376ccb0 Qt: Fix checked state of mute menu option at load (fixes #2701) 2023-03-27 01:10:07 -07:00
Vicki Pfau 7d9d71ef54 GBA Audio: Clear GB audio state when disabled 2023-03-26 23:59:39 -07:00
Vicki Pfau 7da48a7c7f Qt: Swap P1 and other player's save if P1 loaded it first (closes #2750) 2023-03-26 23:59:34 -07:00
Vicki Pfau ff3f2a9d84 Util: Strip loading 16-bit PNGs down to 8-bit 2023-03-25 01:46:49 -07:00
Vicki Pfau 87295370f1 GBA Overrides: Fix saving in PMD:RRT (JP) (fixes #2862) 2023-03-24 23:48:13 -07:00
Vicki Pfau e01b56fbd5 Scripting: Fix scalar hashing on different union layouts, e.g. big endian 2023-03-19 03:50:18 -07:00
Vicki Pfau 5b52f0f277 Qt: Fix OSD on modern macOS (fixes #2736) 2023-03-17 02:38:51 -07:00
Vicki Pfau e7f56bd5c9 GDB: Ugh 2023-03-06 14:57:16 -08:00
Vicki Pfau 40e7d2aa4d GDB: Enable NODELAY on GDB stub connections 2023-03-06 14:51:32 -08:00
Vicki Pfau 603ab67926 Qt: Fix buffer termination issue 2023-03-06 00:08:58 -08:00
Vicki Pfau 5f6244556e Qt: Fix black screen when starting with a game (fixes #2781) 2023-03-05 23:55:54 -08:00
Vicki Pfau 06b1b04c7d GBA Saveata: Fix fumbled check 2023-03-05 14:22:01 -08:00
Vicki Pfau 853b028892 VFS: Improve zip invariant handling 2023-03-02 22:30:26 -08:00
Vicki Pfau 1ed40a1931 Qt: Fix potential directory handle leak 2023-03-02 22:30:26 -08:00
Vicki Pfau b8248327a2 Util: Fix potential socket leak 2023-03-02 22:30:26 -08:00
Vicki Pfau b6e34f084f Qt: Better fps non-zero division check 2023-03-01 23:11:36 -08:00
Vicki Pfau 8b67243780 Qt: Initialize Shortcut::m_direction 2023-03-01 23:05:28 -08:00
Vicki Pfau b06d178c41 GBA SIO: Who wrote this code? Oh, me 2023-03-01 22:53:24 -08:00
Vicki Pfau 43f2eb78d7 Qt: Initialize log-to members 2023-03-01 22:53:24 -08:00
Vicki Pfau 2e4732f1ff Core: Negative log types are invalid 2023-03-01 22:53:24 -08:00
Vicki Pfau 71174659f2 Core: Fix GBK string memory handling in .cht loading 2023-03-01 20:32:18 -08:00
Vicki Pfau d099a0d07e Feature: Fix No-Intro cleanup on initial errors 2023-03-01 20:28:10 -08:00
Vicki Pfau 3357fa6a03 All: Fix handling of strncat bounds 2023-03-01 20:18:21 -08:00
Vicki Pfau 72817df4cf GB: Fix potential double-free of non-pristine ROM memory 2023-03-01 20:18:05 -08:00
Vicki Pfau 9da638f0c0 GBA Savedata: Fix sanity check in Load 2023-03-01 16:17:51 -08:00
Vicki Pfau 1bbbbc745d Core: Add missing va_end 2023-03-01 16:17:29 -08:00
Vicki Pfau 18b798ec41 GB Memory: Fix potential crash when directly accessing invalid SRAM 2023-03-01 16:17:29 -08:00
Vicki Pfau bd805dd720 Qt: Fix crash if loading a shader fails 2023-03-01 16:17:29 -08:00
Vicki Pfau c13e2a822e OpenGL: Fix memory leak in failure path 2023-03-01 16:17:29 -08:00
Vicki Pfau 9132859b6f Qt: Fix full-buffer rewind 2023-03-01 16:00:00 -08:00
Vicki Pfau feb9c4a48b Windows: Fix build clang-cl 2023-02-24 22:07:21 -08:00
Vicki Pfau 081fe27e3a Qt: Automatically change video file extension as appropriate 2023-02-24 03:51:44 -08:00
Vicki Pfau 11d38a8317 CHANGES: Update 2023-02-22 20:22:45 -08:00
Vicki Pfau 766c6625c4 Scripting: Add WSAEWOULDBLOCK to error translation table 2023-02-22 20:22:09 -08:00
Vicki Pfau 1d01a7585c Qt: Fix loading a script leaving sync disabled 2023-02-15 02:30:27 -08:00
Vicki Pfau a473aeff2c Qt: Fix a handful of edge cases with graphics viewers (fixes #2827) 2023-02-14 23:14:02 -08:00
Adam Higerd 3f5fec4e4d hook frame callback in socket connect 2023-02-12 13:29:17 -08:00
Vicki Pfau 8c5ad62dbc OpenGL: Fix null calloc/memcpy 2023-02-11 22:09:30 -08:00
Vicki Pfau 5558e469e0 Qt: Fix modifier key names in shortcut editor (fixes #2817) 2023-02-09 20:02:14 -08:00
Vicki Pfau 1164d5b470 Qt: Disable attempted linking betwen incompatible platforms (fixes #2702) 2023-02-09 00:20:21 -08:00
Vicki Pfau b9a950fee7 Qt: Properly cap number of attached players by platform (fixes #2807) 2023-02-09 00:20:20 -08:00
Vicki Pfau 7e624c6857 GBA SIO: Normal mode transfers with no clock should not finish (fixes #2811) 2023-02-09 00:19:12 -08:00
Vicki Pfau 395fa2da4c GBA SIO: Fix unconnected normal mode SIOCNT SI bit (fixes #2810) 2023-02-09 00:18:34 -08:00
Vicki Pfau 4738e558f2 GBA Timers: Cascading timers don't tick when disabled (fixes #2812) 2023-02-03 01:44:31 -08:00
Vicki Pfau 1f2df26e22 Script: Fix table string key UAF 2023-01-31 21:12:42 -08:00
Vicki Pfau 4f8763ad0b Script: Fix leaking tables passed from Lua 2023-01-31 21:12:42 -08:00
Vicki Pfau 1331788b6e GBA SIO: Fix SIOCNT SI pin value after attaching player 2 (fixes #2805) 2023-01-30 20:50:34 -08:00
Vicki Pfau 7c4a7796f8 GBA Audio: Fix improperly deserializing GB audio registers (fixes #2793) 2023-01-30 00:21:48 -08:00
Vicki Pfau 5183edc3c6 Qt: Stop eating boolean action key events (fixes #2636) 2023-01-29 01:48:33 -08:00
Vicki Pfau 5d1fdf3b24 Qt: Unbind apostrophe from GameShark button by default 2023-01-29 01:36:59 -08:00
Vicki Pfau b00a04099f CHANGES: Wrong bug report 2023-01-28 22:41:06 -08:00
Vicki Pfau d29e782789 Core: Allow sending thread requests to a crashed core (fixes #2785) 2023-01-28 22:39:06 -08:00
Vicki Pfau 17322e50b8 Qt: It's 2023 now 2023-01-28 22:10:15 -08:00
Vicki Pfau 31755ebb19 Qt: Disable sync while running scripts from main thread (fixes #2738) 2023-01-27 19:42:28 -08:00
Vicki Pfau f69853e8ba Qt: Include wayland QPA in AppImage (fixes #2796) 2023-01-26 07:18:42 -08:00
Vicki Pfau df6ae1883c Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes #2794) 2023-01-26 05:57:33 -08:00
Vicki Pfau 21f441953f Scripting: Fix mSCRIPT_TYPE_MS_PCS macro 2023-01-26 03:35:24 -08:00
Vicki Pfau 3415dfa1b7 Qt: Add ig4icd32 crash to OpenGL bug database (see #2136) 2023-01-26 03:35:04 -08:00
Vicki Pfau 71d1f122f9 Libretro: Add back missing audio overkill (fixes #2734) 2023-01-26 03:34:34 -08:00
Vicki Pfau 9c69b86d71 GBA Memory: Make VRAM access stalls only apply to BG RAM 2023-01-16 00:54:56 -08:00
Vicki Pfau b5b4271145 CHANGES: Oops 2023-01-12 15:10:47 -08:00
TheMechasaur a4b63e1db9
Correct year of release date of 0.10.1 to 2023 (#2783) 2023-01-11 21:59:17 -08:00
Vicki Pfau 86061dbc3b CMake: Bump version 2023-01-10 19:53:02 -08:00
Vicki Pfau 456ac2222b CHANGES: Update for 0.10.1 2023-01-10 19:51:20 -08:00
Vicki Pfau a413dda335 Qt: Work around Mesa issue 8035 2023-01-10 18:44:47 -08:00
Vicki Pfau c8d7a7d1a3 Qt: Fix minor leak 2023-01-10 18:43:44 -08:00
Vicki Pfau 9d6d2c9c6b GB Audio: Fix regression in channel updating with NR5x (fixes #2775) 2023-01-06 14:26:27 -08:00
Vicki Pfau f7acda6d64 Qt: Redo stable branch detection heuristic 2022-12-31 18:15:47 -08:00
Vicki Pfau 0da3498ee8 Qt: Fix initializing update revision info 2022-12-31 17:54:41 -08:00
Vicki Pfau d17b1da1b7 GB BIOS: Include timing in degenerate ArcTan2 cases (fixes #2763) 2022-12-27 20:39:30 -08:00
Vicki Pfau 0d52995877 GBA Cheats: Fix issues detecting unencrypted cheats (fixes #2724) 2022-12-22 16:02:22 -08:00
Vicki Pfau 63a019b749 Qt: Start OpenGL bug list with glFlush cross-thread on Windows (fixes #2761) 2022-12-21 22:13:51 -08:00
Vicki Pfau fac26cda3c Qt: glFlush is (thankfully) sufficient here 2022-12-18 21:49:41 -08:00
Vicki Pfau 245f4b961a Qt: Tentative fix for renderer switching crash 2022-12-18 21:49:41 -08:00
Vicki Pfau 3d723d5cf4 GBA Video: Fix #2489 in OpenGL renderer 2022-12-18 17:01:26 -08:00
Vicki Pfau 1a4dc70731 GBA: Clean up BIOS skip detection; add second multiboot entry 2022-12-18 12:53:04 -08:00
CasualPokePlayer 411b7dccb4 Check gba->mbVf for force skipping the BIOS (#2754) 2022-12-17 00:22:40 -08:00
Vicki Pfau 947a1c8f5c Qt: Refactor and fix dequeueAll so keep mode keeps last draw tex too 2022-12-17 00:21:19 -08:00
Vicki Pfau 576f607c81 GB IO: Fix BANK not getting initialized after recent change 2022-12-13 15:18:38 -08:00
Vicki Pfau ab790f4c6d Qt: Fix loading/unloading shaders 2022-12-12 14:31:54 -08:00
Vicki Pfau 94277cffbb Qt: Fix proxy context creation on Nvidia (fixes #2746) 2022-12-12 03:12:26 -08:00
Vicki Pfau 7ba3b40977 GB Audio: Fix updating channels other than 2 when writing NR5x 2022-12-11 19:17:24 -08:00
Vicki Pfau 3dbb90e574 GB Audio: Fix up boot sequence 2022-12-11 19:17:24 -08:00
Vicki Pfau c323ab384c GB Memory: Actually, HDMAs should start when LCD is off (fixes #2662) 2022-12-11 19:17:24 -08:00
Vicki Pfau 411741fba3 GB Audio: Fix channel 3 volume being changed between samples (fixes #1896) 2022-12-07 01:22:33 -08:00
Vicki Pfau 9f057e2719 GB Audio: Fix channels 1/2 not playing when resetting volume (fixes #2614) 2022-11-28 22:39:05 -08:00
Vicki Pfau 0fc6b02691 Qt: Fix build on older Qt 2022-11-28 14:25:04 -08:00
Vicki Pfau 3866d74707 Qt: Improve handling of multiplayer syncing (fixes #2720) 2022-11-28 01:03:10 -08:00
Vicki Pfau aeaa40e373 GB SIO: Further fix bidirectional transfer starting 2022-11-28 01:03:10 -08:00
Vicki Pfau 8e58dcadb8 Qt: Don't re-enable sync if GBA link modes aren't the same (fixes #2044) 2022-11-28 01:03:10 -08:00
Vicki Pfau f632b85338 Qt: Move OpenGL proxy onto its own thread (fixes #2493) 2022-11-28 01:03:10 -08:00
Vicki Pfau 3cd63cb71c CHANGES: Spill chicken 2022-11-12 00:45:48 -08:00
Vicki Pfau 4914954c67 GBA: Fix resetting key IRQ state (fixes #2716) 2022-11-12 00:45:44 -08:00
Vicki Pfau 20c16bc8e7 GBA Video: Ignore disabled backgrounds as OBJ blend target (fixes #2489) 2022-11-07 23:13:58 -08:00
Vicki Pfau 838e4a182e VFS: Fix minizip write returning 0 on success instead of size 2022-10-29 01:42:21 -07:00
Vicki Pfau e3605d291c Updater: Fix mUpdaterGetUpdateForChannel 2022-10-29 01:42:21 -07:00
Vicki Pfau 12f7168e9f macOS: Fix modern build with libepoxy (fixes #2700) 2022-10-20 20:11:37 -07:00
Vicki Pfau 455525e3b6 Qt: Keep track of current pslette preset name (fixes #2680) 2022-10-19 04:17:11 -07:00
Vicki Pfau 3f39bd2536 macOS: Add category to plist (closes #2691) 2022-10-19 04:16:40 -07:00
Vicki Pfau 838122c234 GB Serialize: Don't write BGP/OBP when loading SCGB state (fixes #2694) 2022-10-18 01:39:49 -07:00
Vicki Pfau c6d7c8f601 Qt: Fix e-Reader scanning function reentry (fixes #2693) 2022-10-16 22:15:05 -07:00
Vicki Pfau 9a3ec792dd README: Add MBC30 to the supported mappers list (closes #2686) 2022-10-16 03:29:50 -07:00
Vicki Pfau c4718d6907 Qt: Add missing Sachen MMC2 mapper 2022-10-16 01:31:01 -07:00
Vicki Pfau 64255a716b Qt: Expand criteria for tag branch names (fixes #2679) 2022-10-14 23:37:03 -07:00
Vicki Pfau 277aa36d12 Qt: Manually split filename to avoid overzealous splitting (fixes #2681) 2022-10-14 23:37:01 -07:00
Vicki Pfau 81cb4c3bf6 Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes #2685) 2022-10-14 23:36:57 -07:00
Vicki Pfau df599a1fb6 CMake: Bump verison 2022-10-11 22:24:06 -07:00
109 changed files with 7757 additions and 3168 deletions

104
CHANGES
View File

@ -1,3 +1,97 @@
0.10.3: (2024-01-07)
Emulation fixes:
- ARM: Remove obsolete force-alignment in `bx pc` (fixes mgba.io/i/2964)
- ARM: Fake bpkt instruction should take no cycles (fixes mgba.io/i/2551)
- GB Audio: Fix channels 1/2 staying muted if restarted after long silence
- GB Audio: Fix channel 1 restarting if sweep applies after stop (fixes mgba.io/i/2965)
- GB Audio: Fix restarting envelope when writing to register (fixes mgba.io/i/3067)
- GB Audio: Improve "zombie mode" emulation in CGB mode (fixes mgba.io/i/2029)
- GB I/O: Read back proper SVBK value after writing 0 (fixes mgba.io/i/2921)
- GB SIO: Disabling SIO should cancel pending transfers (fixes mgba.io/i/2537)
- GBA Audio: Fix sample timing drifting when changing sample interval
- GBA Audio: Fix initial channel 3 wave RAM (fixes mgba.io/i/2947)
- GBA Audio: Fix sample position issues when rate changes (fixes mgba.io/i/3006)
- GBA GPIO: Fix tilt scale and orientation (fixes mgba.io/i/2703)
- GBA BIOS: Fix clobbering registers with word-sized CpuSet
- GBA SIO: Fix normal mode SI/SO semantics (fixes mgba.io/i/2925)
Other fixes:
- GB: Fix applying a patch that changes the cartridge mapper (fixes mgba.io/i/3077)
- GBA Savedata: Fix crash when resizing flash save games for RTC data
- mGUI: Fix cases where an older save state screenshot would be shown (fixes mgba.io/i/2183)
- Qt: Re-enable sync for multiplayer windows that aren't connected (fixes mgba.io/i/2974)
- Qt: Fix mute settings not being loaded on setting screen (fixes mgba.io/i/2990)
- Qt: Fix screen freezing on macOS after closing save state window (fixes mgba.io/i/2885)
- Vita: Fix camera setting not appearing (fixes mgba.io/i/3012)
Misc:
- mGUI: Persist fast forwarding after closing menu (fixes mgba.io/i/2414)
- Qt: Add exporting of SAV + RTC saves from Save Converter to strip RTC data
- VFS: Use anonymousMemoryMap for large 7z allocations (fixes mgba.io/i/3013)
0.10.2: (2023-04-23)
Emulation fixes:
- GBA Audio: Fix improperly deserializing GB audio registers (fixes mgba.io/i/2793)
- GBA Audio: Clear GB audio state when disabled
- GBA Memory: Make VRAM access stalls only apply to BG RAM
- GBA Overrides: Fix saving in PMD:RRT (JP) (fixes mgba.io/i/2862)
- GBA SIO: Fix SIOCNT SI pin value after attaching player 2 (fixes mgba.io/i/2805)
- GBA SIO: Fix unconnected normal mode SIOCNT SI bit (fixes mgba.io/i/2810)
- GBA SIO: Normal mode transfers with no clock should not finish (fixes mgba.io/i/2811)
- GBA Timers: Cascading timers don't tick when disabled (fixes mgba.io/i/2812)
- GBA Video: Fix interpolation issues with OpenGL renderer
Other fixes:
- Core: Allow sending thread requests to a crashed core (fixes mgba.io/i/2784)
- FFmpeg: Force lower sample rate for codecs not supporting high rates (fixes mgba.io/i/2869)
- Qt: Fix crash when attempting to use OpenGL 2.1 to 3.1 (fixes mgba.io/i/2794)
- Qt: Disable sync while running scripts from main thread (fixes mgba.io/i/2738)
- Qt: Properly cap number of attached players by platform (fixes mgba.io/i/2807)
- Qt: Disable attempted linking betwen incompatible platforms (fixes mgba.io/i/2702)
- Qt: Fix modifier key names in shortcut editor (fixes mgba.io/i/2817)
- Qt: Fix a handful of edge cases with graphics viewers (fixes mgba.io/i/2827)
- Qt: Fix full-buffer rewind
- Qt: Fix crash if loading a shader fails
- Qt: Fix black screen when starting with a game (fixes mgba.io/i/2781)
- Qt: Fix OSD on modern macOS (fixes mgba.io/i/2736)
- Qt: Fix checked state of mute menu option at load (fixes mgba.io/i/2701)
- Qt: Remove OpenGL proxy thread and override SwapInterval directly instead
- Scripting: Fix receiving packets for client sockets
- Scripting: Fix empty receive calls returning unknown error on Windows
- Scripting: Return proper callback ID from socket.add
- Vita: Work around broken mktime implementation in Vita SDK (fixes mgba.io/i/2876)
Misc:
- Qt: Include wayland QPA in AppImage (fixes mgba.io/i/2796)
- Qt: Stop eating boolean action key events (fixes mgba.io/i/2636)
- Qt: Automatically change video file extension as appropriate
- Qt: Swap P1 and other player's save if P1 loaded it first (closes mgba.io/i/2750)
0.10.1: (2023-01-10)
Emulation fixes:
- GB Audio: Fix channels 1/2 not playing when resetting volume (fixes mgba.io/i/2614)
- GB Audio: Fix channel 3 volume being changed between samples (fixes mgba.io/i/1896)
- GB Audio: Fix up boot sequence
- GB Audio: Fix updating channels other than 2 when writing NR5x
- GB Memory: Actually, HDMAs should start when LCD is off (fixes mgba.io/i/2662)
- GB Serialize: Don't write BGP/OBP when loading SCGB state (fixes mgba.io/i/2694)
- GB SIO: Further fix bidirectional transfer starting
- GBA: Fix resetting key IRQ state (fixes mgba.io/i/2716)
- GBA BIOS: Include timing in degenerate ArcTan2 cases (fixes mgba.io/i/2763)
- GBA Video: Ignore disabled backgrounds as OBJ blend target (fixes mgba.io/i/2489)
Other fixes:
- GBA: Fix forceskip BIOS logic for multiboot ROMs (fixes mgba.io/i/2753)
- GBA Cheats: Fix issues detecting unencrypted cheats (fixes mgba.io/i/2724)
- Qt: Manually split filename to avoid overzealous splitting (fixes mgba.io/i/2681)
- Qt: Fix scanning specific e-Reader dotcodes (fixes mgba.io/i/2693)
- Qt: Don't re-enable sync if GBA link modes aren't the same (fixes mgba.io/i/2044)
- Qt: Improve handling of multiplayer syncing (fixes mgba.io/i/2720)
- Qt: Fix initializing update revision info
- Qt: Redo stable branch detection heuristic (fixes mgba.io/i/2679)
- Res: Fix species name location in Ruby/Sapphire revs 1/2 (fixes mgba.io/i/2685)
- VFS: Fix minizip write returning 0 on success instead of size
Misc:
- macOS: Add category to plist (closes mgba.io/i/2691)
- macOS: Fix modern build with libepoxy (fixes mgba.io/i/2700)
- Qt: Keep track of current palette preset name (fixes mgba.io/i/2680)
- Qt: Move OpenGL proxy onto its own thread (fixes mgba.io/i/2493)
0.10.0: (2022-10-11)
Features:
- Preliminary Lua scripting support
@ -6,7 +100,7 @@ Features:
- Tool for converting scanned pictures of e-Reader cards to raw dotcode data
- Options for muting when inactive, minimized, or for different players in multiplayer
- Cheat code support in homebrew ports
- Acclerometer and gyro support for controllers on PC
- Accelerometer and gyro support for controllers on PC
- Support for combo "Super Game Boy Color" SGB + GBC ROM hacks
- Improved support for HuC-3 mapper, including RTC
- Support for 64 kiB SRAM saves used in some bootlegs
@ -20,7 +114,7 @@ Emulation fixes:
- ARM7: Fix unsigned multiply timing
- GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378)
- GB: Fix HALT breaking M-cycle alignment (fixes mgba.io/i/250)
- GB Audio: Fix channel 1/2 reseting edge cases (fixes mgba.io/i/1925)
- GB Audio: Fix channel 1/2 resetting edge cases (fixes mgba.io/i/1925)
- GB Audio: Properly apply per-model audio differences
- GB Audio: Revamp channel rendering
- GB Audio: Fix APU re-enable timing glitch
@ -130,7 +224,7 @@ Emulation fixes:
Other fixes:
- ARM Decoder: Fix decoding of lsl r0 (fixes mgba.io/i/2349)
- FFmpeg: Don't attempt to use YUV 4:2:0 for lossless videos (fixes mgba.io/i/2084)
- GB Video: Fix memory leak when reseting SGB games
- GB Video: Fix memory leak when resetting SGB games
- GBA: Fix out of bounds ROM accesses on patched ROMs smaller than 32 MiB
- GBA: Fix maximum tile ID in caching for 256-color modes
- GBA Video: Fix cache updating with proxy and GL renderers
@ -241,7 +335,7 @@ Emulation fixes:
- GBA BIOS: Implement dummy sound driver calls
- GBA BIOS: Improve HLE BIOS timing
- GBA BIOS: Fix reloading video registers after reset (fixes mgba.io/i/1808)
- GBA BIOS: Make HLE BIOS calls interruptable (fixes mgba.io/i/1711 and mgba.io/i/1823)
- GBA BIOS: Make HLE BIOS calls interruptible (fixes mgba.io/i/1711 and mgba.io/i/1823)
- GBA BIOS: Fix invalid decompression bounds checking
- GBA DMA: Linger last DMA on bus (fixes mgba.io/i/301 and mgba.io/i/1320)
- GBA DMA: Fix ordering and timing of overlapping DMAs
@ -257,7 +351,7 @@ Emulation fixes:
- GBA Serialize: Fix alignment check when loading states
- GBA SIO: Fix copying Normal mode transfer values
- GBA SIO: Fix Normal mode being totally broken (fixes mgba.io/i/1800)
- GBA SIO: Fix deseralizing SIO registers
- GBA SIO: Fix deserializing SIO registers
- GBA SIO: Fix hanging on starting a second multiplayer window (fixes mgba.io/i/854)
- GBA SIO: Fix Normal mode transfer start timing (fixes mgba.io/i/425)
- GBA Timers: Fix toggling timer cascading while timer is active (fixes mgba.io/i/2043)

View File

@ -175,7 +175,7 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/src/core/version.c.in ${CMAKE_CURRENT
source_group("Generated sources" FILES ${CMAKE_CURRENT_BINARY_DIR}/version.c)
# Advanced settings
if(NOT (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_COMPILER_VERSION VERSION_LESS "4.5"))
if(NOT (CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "4.5"))
set(DEFAULT_LTO ON)
else()
set(DEFAULT_LTO OFF)
@ -419,6 +419,13 @@ if(BUILD_GL)
elseif(UNIX AND NOT APPLE AND TARGET OpenGL::GL)
set(OPENGL_LIBRARY OpenGL::GL)
endif()
if(OpenGL_GLX_FOUND)
list(APPEND FEATURES GLX)
endif()
if(OpenGL_EGL_FOUND)
list(APPEND FEATURES EGL)
list(APPEND OPENGL_LIBRARY ${OPENGL_egl_LIBRARY})
endif()
endif()
if(BUILD_GL)
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c)
@ -718,8 +725,12 @@ if (USE_LZMA)
endif()
if(USE_EPOXY)
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gles2.c)
list(APPEND FEATURE_DEFINES BUILD_GL BUILD_GLES2 BUILD_GLES3)
if(NOT APPLE OR NOT MACOSX_SDK VERSION_GREATER 10.14)
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c)
list(APPEND FEATURE_DEFINES BUILD_GL)
endif()
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gles2.c)
list(APPEND FEATURE_DEFINES BUILD_GLES2 BUILD_GLES3)
list(APPEND FEATURES EPOXY)
include_directories(AFTER ${EPOXY_INCLUDE_DIRS})
link_directories(${EPOXY_LIBRARY_DIRS})

View File

@ -50,6 +50,7 @@ The following mappers are fully supported:
- MBC2
- MBC3
- MBC3+RTC
- MBC30
- MBC5
- MBC5+Rumble
- MBC7

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

View File

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

Binary file not shown.

View File

@ -27,6 +27,8 @@
extern "C" {
#endif
struct option;
#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */
#ifdef REPLACE_GETOPT

View File

@ -204,6 +204,7 @@ static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress)
err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
#endif
if (err) {
SocketCloseQuiet(sock);
return INVALID_SOCKET;
}

View File

@ -147,6 +147,7 @@ void GBAMemoryInit(struct GBA* gba);
void GBAMemoryDeinit(struct GBA* gba);
void GBAMemoryReset(struct GBA* gba);
void GBAMemoryClearAGBPrint(struct GBA* gba);
uint32_t GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter);

View File

@ -97,7 +97,7 @@ CXX_GUARD_START
#define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT)
#define mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME) (&_mSTStructBindingType_ ## STRUCT ## _ ## NAME)
#define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT)
#define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructConstPtr_ ## STRUCT)
#define mSCRIPT_TYPE_MS_PCS(STRUCT) (&mSTStructPtrConst_ ## STRUCT)
#define mSCRIPT_TYPE_MS_WSTR (&mSTStringWrapper)
#define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper)
#define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE)

7
res/entitlements.plist Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "https://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.device.camera</key>
<true/>
</dict>
</plist>

View File

@ -56,5 +56,7 @@
<string>Viewer</string>
</dict>
</array>
<key>LSApplicationCategoryType</key>
<string>public.app-category.games</string>
</dict>
</plist>

File diff suppressed because it is too large Load Diff

View File

@ -1,16 +1,15 @@
Akatsuki
AVjoyu__Chan
Benedikt Feih
Brandon
Emily A. Bellows
G
EmuDeck
gocha
Jaime J. Denizard
MichaelK__
Miras Absar
Molly Howell
Nic Losby (blurbdust)
Petru-Sebastian Toader
Stevoisiak
Tyler Jenkins
William K. Leung
Zach
Zhongchao Qian

View File

@ -412,6 +412,13 @@ local gameRubyEn = Generation3En:new{
_speciesNameTable=0x1f716c,
}
local gameRubyEnR1 = Generation3En:new{
name="Ruby (USA)",
_party=0x3004360,
_partyCount=0x3004350,
_speciesNameTable=0x1f7184,
}
local gameSapphireEn = Generation3En:new{
name="Sapphire (USA)",
_party=0x3004360,
@ -419,6 +426,13 @@ local gameSapphireEn = Generation3En:new{
_speciesNameTable=0x1f70fc,
}
local gameSapphireEnR1 = Generation3En:new{
name="Sapphire (USA)",
_party=0x3004360,
_partyCount=0x3004350,
_speciesNameTable=0x1f7114,
}
local gameEmeraldEn = Generation3En:new{
name="Emerald (USA)",
_party=0x20244ec,
@ -471,6 +485,10 @@ gameCrc32 = {
[0x7d527d62] = gameYellowEn,
[0x84ee4776] = gameFireRedEnR1,
[0xdaffecec] = gameLeafGreenEnR1,
[0x61641576] = gameRubyEnR1, -- Rev 1
[0xaeac73e6] = gameRubyEnR1, -- Rev 2
[0xbafedae5] = gameSapphireEnR1, -- Rev 1
[0x9cc4410e] = gameSapphireEnR1, -- Rev 2
}
function printPartyStatus(game, buffer)

View File

@ -663,7 +663,9 @@ DEFINE_INSTRUCTION_ARM(MRC, ARM_STUB)
// Begin miscellaneous definitions
DEFINE_INSTRUCTION_ARM(BKPT, cpu->irqh.bkpt32(cpu, ((opcode >> 4) & 0xFFF0) | (opcode & 0xF))); // Not strictly in ARMv4T, but here for convenience
DEFINE_INSTRUCTION_ARM(BKPT,
cpu->irqh.bkpt32(cpu, ((opcode >> 4) & 0xFFF0) | (opcode & 0xF));
currentCycles = 0;); // Not strictly in ARMv4T, but here for convenience
DEFINE_INSTRUCTION_ARM(ILL, ARM_ILL) // Illegal opcode
DEFINE_INSTRUCTION_ARM(MSR,

View File

@ -381,7 +381,9 @@ DEFINE_LOAD_STORE_MULTIPLE_THUMB(PUSHR,
cpu->gprs[ARM_SP] = address)
DEFINE_INSTRUCTION_THUMB(ILL, ARM_ILL)
DEFINE_INSTRUCTION_THUMB(BKPT, cpu->irqh.bkpt16(cpu, opcode & 0xFF);)
DEFINE_INSTRUCTION_THUMB(BKPT,
cpu->irqh.bkpt16(cpu, opcode & 0xFF);
currentCycles = 0;) // Not strictly in ARMv4T, but here for convenience
DEFINE_INSTRUCTION_THUMB(B,
int16_t immediate = (opcode & 0x07FF) << 5;
cpu->gprs[ARM_PC] += (((int32_t) immediate) >> 4);
@ -401,11 +403,7 @@ DEFINE_INSTRUCTION_THUMB(BL2,
DEFINE_INSTRUCTION_THUMB(BX,
int rm = (opcode >> 3) & 0xF;
_ARMSetMode(cpu, cpu->gprs[rm] & 0x00000001);
int misalign = 0;
if (rm == ARM_PC) {
misalign = cpu->gprs[rm] & 0x00000002;
}
cpu->gprs[ARM_PC] = (cpu->gprs[rm] & 0xFFFFFFFE) - misalign;
cpu->gprs[ARM_PC] = cpu->gprs[rm] & 0xFFFFFFFE;
if (cpu->executionMode == MODE_THUMB) {
currentCycles += ThumbWritePC(cpu);
} else {

View File

@ -34,12 +34,15 @@ void mCacheSetDeinit(struct mCacheSet* cache) {
for (i = 0; i < mMapCacheSetSize(&cache->maps); ++i) {
mMapCacheDeinit(mMapCacheSetGetPointer(&cache->maps, i));
}
mMapCacheSetDeinit(&cache->maps);
for (i = 0; i < mBitmapCacheSetSize(&cache->bitmaps); ++i) {
mBitmapCacheDeinit(mBitmapCacheSetGetPointer(&cache->bitmaps, i));
}
mBitmapCacheSetDeinit(&cache->bitmaps);
for (i = 0; i < mTileCacheSetSize(&cache->tiles); ++i) {
mTileCacheDeinit(mTileCacheSetGetPointer(&cache->tiles, i));
}
mTileCacheSetDeinit(&cache->tiles);
}
void mCacheSetAssignVRAM(struct mCacheSet* cache, void* vram) {

View File

@ -489,8 +489,10 @@ bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) {
return false;
}
char* name = gbkToUtf8(&cheat[1], end - cheat - 1);
strncpy(cheatName, name, sizeof(cheatName) - 1);
free(name);
if (name) {
strncpy(cheatName, name, sizeof(cheatName) - 1);
free(name);
}
cheatNameLength = strlen(cheatName);
continue;
}
@ -501,7 +503,10 @@ bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) {
}
if (strncmp(cheat, "ON", eq - cheat) != 0) {
char* subname = gbkToUtf8(cheat, eq - cheat);
snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname);
if (subname) {
snprintf(&cheatName[cheatNameLength], sizeof(cheatName) - cheatNameLength - 1, ": %s", subname);
free(subname);
}
}
set = device->createSet(device, cheatName);
set->enabled = false;

View File

@ -169,14 +169,14 @@ void mCoreConfigDeinit(struct mCoreConfig* config) {
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
bool mCoreConfigLoad(struct mCoreConfig* config) {
char path[PATH_MAX];
char path[PATH_MAX + 1];
mCoreConfigDirectory(path, PATH_MAX);
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
return mCoreConfigLoadPath(config, path);
}
bool mCoreConfigSave(const struct mCoreConfig* config) {
char path[PATH_MAX];
char path[PATH_MAX + 1];
mCoreConfigDirectory(path, PATH_MAX);
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
return mCoreConfigSavePath(config, path);
@ -304,7 +304,7 @@ void mCoreConfigPortablePath(char* out, size_t outLength) {
CFRelease(suburl);
}
#endif
strncat(out, PATH_SEP "portable.ini", outLength - strlen(out));
strncat(out, PATH_SEP "portable.ini", outLength - strlen(out) - 1);
#endif
}

View File

@ -127,6 +127,7 @@ struct mCore* mCoreFind(const char* path) {
}
bool mCoreLoadFile(struct mCore* core, const char* path) {
core->unloadROM(core);
#ifdef FIXED_ROM_BUFFER
return mCorePreloadFile(core, path);
#else

View File

@ -50,7 +50,7 @@ const char* mLogCategoryName(int category) {
}
const char* mLogCategoryId(int category) {
if (category < MAX_CATEGORY) {
if (category >= 0 && category < MAX_CATEGORY) {
return _categoryIds[category];
}
return NULL;
@ -88,6 +88,7 @@ void mLogExplicit(struct mLogger* context, int category, enum mLogLevel level, c
if (!context->filter || mLogFilterTest(context->filter, category, level)) {
context->log(context, category, level, format, args);
}
va_end(args);
}
void mLogFilterInit(struct mLogFilter* filter) {

View File

@ -713,33 +713,33 @@ static struct mScriptTextBuffer* _mScriptConsoleCreateBuffer(struct mScriptConso
return buffer;
}
static void mScriptConsoleLog(struct mScriptConsole* console, struct mScriptString* msg) {
static void mScriptConsoleLog(struct mScriptConsole* console, const char* msg) {
if (console->logger) {
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg->buffer);
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg);
} else {
mLog(_mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg->buffer);
mLog(_mLOG_CAT_SCRIPT, mLOG_INFO, "%s", msg);
}
}
static void mScriptConsoleWarn(struct mScriptConsole* console, struct mScriptString* msg) {
static void mScriptConsoleWarn(struct mScriptConsole* console, const char* msg) {
if (console->logger) {
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg);
} else {
mLog(_mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
mLog(_mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg);
}
}
static void mScriptConsoleError(struct mScriptConsole* console, struct mScriptString* msg) {
static void mScriptConsoleError(struct mScriptConsole* console, const char* msg) {
if (console->logger) {
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_ERROR, "%s", msg->buffer);
mLogExplicit(console->logger, _mLOG_CAT_SCRIPT, mLOG_ERROR, "%s", msg);
} else {
mLog(_mLOG_CAT_SCRIPT, mLOG_WARN, "%s", msg->buffer);
mLog(_mLOG_CAT_SCRIPT, mLOG_ERROR, "%s", msg);
}
}
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, log, mScriptConsoleLog, 1, STR, msg);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, warn, mScriptConsoleWarn, 1, STR, msg);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, error, mScriptConsoleError, 1, STR, msg);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, log, mScriptConsoleLog, 1, CHARP, msg);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, warn, mScriptConsoleWarn, 1, CHARP, msg);
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, error, mScriptConsoleError, 1, CHARP, msg);
mSCRIPT_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptConsole, S(mScriptTextBuffer), createBuffer, _mScriptConsoleCreateBuffer, 1, CHARP, name);
mSCRIPT_DEFINE_STRUCT(mScriptConsole)

View File

@ -58,8 +58,19 @@ static void _waitOnInterrupt(struct mCoreThreadInternal* threadContext) {
}
static void _pokeRequest(struct mCoreThreadInternal* threadContext) {
if (threadContext->state == mTHREAD_RUNNING || threadContext->state == mTHREAD_PAUSED) {
switch (threadContext->state) {
case mTHREAD_RUNNING:
case mTHREAD_PAUSED:
case mTHREAD_CRASHED:
threadContext->state = mTHREAD_REQUEST;
break;
case mTHREAD_INITIALIZED:
case mTHREAD_REQUEST:
case mTHREAD_INTERRUPTED:
case mTHREAD_INTERRUPTING:
case mTHREAD_EXITING:
case mTHREAD_SHUTDOWN:
break;
}
}

View File

@ -726,10 +726,14 @@ static void _listWatchpoints(struct CLIDebugger* debugger, struct CLIDebugVector
}
static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
if (!dv || dv->type != CLIDV_INT_TYPE) {
if (!dv) {
debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);
return;
}
if (dv->type != CLIDV_INT_TYPE || dv->intValue < 0) {
debugger->backend->printf(debugger->backend, "%s\n", ERROR_INVALID_ARGS);
return;
}
debugger->traceRemaining = dv->intValue;
if (debugger->traceVf) {

View File

@ -354,6 +354,10 @@ static void _writeGPRs(struct GDBStub* stub, const char* message) {
_sendMessage(stub);
}
static int32_t _readPC(struct ARMCore* cpu) {
return cpu->gprs[ARM_PC] - (cpu->cpsr.t ? WORD_SIZE_THUMB : WORD_SIZE_ARM);
}
static void _readGPRs(struct GDBStub* stub, const char* message) {
struct ARMCore* cpu = stub->d.core->cpu;
UNUSED(message);
@ -367,7 +371,7 @@ static void _readGPRs(struct GDBStub* stub, const char* message) {
}
// Program counter
_int2hex32(cpu->gprs[ARM_PC] - (cpu->cpsr.t ? WORD_SIZE_THUMB : WORD_SIZE_ARM), &stub->outgoing[i]);
_int2hex32(_readPC(cpu), &stub->outgoing[i]);
i += 8;
// CPU status
@ -417,8 +421,10 @@ static void _readRegister(struct GDBStub* stub, const char* message) {
unsigned i = 0;
uint32_t reg = _readHex(readAddress, &i);
uint32_t value;
if (reg < 0x10) {
if (reg < ARM_PC) {
value = cpu->gprs[reg];
} else if (reg == ARM_PC) {
value = _readPC(cpu);
} else if (reg == 0x19) {
value = cpu->cpsr.packed;
} else {
@ -457,7 +463,7 @@ static void _processQSupportedCommand(struct GDBStub* stub, const char* message)
}
message = end + 1;
}
strncpy(stub->outgoing, "swbreak+;hwbreak+;qXfer:features:read+;qXfer:memory-map:read+", GDB_STUB_MAX_LINE - 4);
strncpy(stub->outgoing, "swbreak+;hwbreak+;qXfer:features:read+;qXfer:memory-map:read+;QStartNoAckMode+", GDB_STUB_MAX_LINE - 4);
}
static void _processQXferCommand(struct GDBStub* stub, const char* params, const char* data) {
@ -542,7 +548,7 @@ static void _processQReadCommand(struct GDBStub* stub, const char* message) {
static void _processQWriteCommand(struct GDBStub* stub, const char* message) {
stub->outgoing[0] = '\0';
if (!strncmp("StartNoAckMode#", message, 16)) {
if (!strncmp("StartNoAckMode#", message, 15)) {
stub->lineAck = GDB_ACK_OFF;
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
}
@ -844,6 +850,7 @@ void GDBStubUpdate(struct GDBStub* stub) {
} else {
goto connectionLost;
}
SocketSetTCPPush(stub->connection, 1);
}
while (true) {
if (stub->shouldBlock) {

View File

@ -34,6 +34,10 @@ CXX_GUARD_START
#define FFMPEG_USE_PACKET_UNREF
#endif
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 0)
#define FFMPEG_USE_NEW_CH_LAYOUT
#endif
static inline enum AVPixelFormat mColorFormatToFFmpegPixFmt(enum mColorFormat format) {
switch (format) {
#ifndef USE_LIBAV

View File

@ -62,13 +62,13 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
encoder->audioCodec = NULL;
encoder->videoCodec = NULL;
encoder->containerFormat = NULL;
encoder->isampleRate = PREFERRED_SAMPLE_RATE;
FFmpegEncoderSetAudio(encoder, "flac", 0);
FFmpegEncoderSetVideo(encoder, "libx264", 0, 0);
FFmpegEncoderSetContainer(encoder, "matroska");
FFmpegEncoderSetDimensions(encoder, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS;
encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS;
encoder->isampleRate = PREFERRED_SAMPLE_RATE;
encoder->frameskip = 1;
encoder->skipResidue = 0;
encoder->loop = false;
@ -154,19 +154,35 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un
}
encoder->sampleRate = encoder->isampleRate;
if (codec->supported_samplerates) {
bool gotSampleRate = false;
int highestSampleRate = 0;
for (i = 0; codec->supported_samplerates[i]; ++i) {
if (codec->supported_samplerates[i] > highestSampleRate) {
highestSampleRate = codec->supported_samplerates[i];
}
if (codec->supported_samplerates[i] < encoder->isampleRate) {
continue;
}
if (encoder->sampleRate == encoder->isampleRate || encoder->sampleRate > codec->supported_samplerates[i]) {
if (!gotSampleRate || encoder->sampleRate > codec->supported_samplerates[i]) {
encoder->sampleRate = codec->supported_samplerates[i];
gotSampleRate = true;
}
}
if (!gotSampleRate) {
// There are no available sample rates that are higher than the input sample rate
// Let's use the highest available instead
encoder->sampleRate = highestSampleRate;
}
} else if (codec->id == AV_CODEC_ID_FLAC) {
// HACK: FLAC doesn't support > 65535Hz unless it's divisible by 10
if (encoder->sampleRate >= 65535) {
encoder->sampleRate -= encoder->isampleRate % 10;
}
} else if (codec->id == AV_CODEC_ID_VORBIS) {
// HACK: Vorbis doesn't support > 48000Hz but doesn't tell us
if (encoder->sampleRate > 48000) {
encoder->sampleRate = 48000;
}
} else if (codec->id == AV_CODEC_ID_AAC) {
// HACK: AAC doesn't support 32768Hz (it rounds to 32000), but libfaac doesn't tell us that
encoder->sampleRate = 48000;
@ -304,8 +320,12 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
encoder->audio = encoder->audioStream->codec;
#endif
encoder->audio->bit_rate = encoder->audioBitrate;
#ifdef FFMPEG_USE_NEW_CH_LAYOUT
av_channel_layout_copy(&encoder->audio->ch_layout, &(AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO);
#else
encoder->audio->channels = 2;
encoder->audio->channel_layout = AV_CH_LAYOUT_STEREO;
#endif
encoder->audio->sample_rate = encoder->sampleRate;
encoder->audio->sample_fmt = encoder->sampleFormat;
AVDictionary* opts = 0;
@ -330,7 +350,11 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
encoder->audioFrame->nb_samples = encoder->audio->frame_size;
encoder->audioFrame->format = encoder->audio->sample_fmt;
encoder->audioFrame->pts = 0;
#ifdef FFMPEG_USE_NEW_CH_LAYOUT
av_channel_layout_copy(&encoder->audioFrame->ch_layout, &(AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO);
#else
encoder->audioFrame->channel_layout = AV_CH_LAYOUT_STEREO;
#endif
_ffmpegOpenResampleContext(encoder);
av_frame_get_buffer(encoder->audioFrame, 0);
@ -881,7 +905,7 @@ void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder* encoder, int sampleRa
}
void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder) {
encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 4, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate });
encoder->audioBufferSize = av_rescale_q(encoder->audioFrame->nb_samples, (AVRational) { 1, encoder->sampleRate }, (AVRational) { 1, encoder->isampleRate }) * 4;
encoder->audioBuffer = av_malloc(encoder->audioBufferSize);
#ifdef USE_LIBAVRESAMPLE
encoder->resampleContext = avresample_alloc_context();
@ -892,9 +916,14 @@ void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder) {
av_opt_set_int(encoder->resampleContext, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0);
avresample_open(encoder->resampleContext);
#else
#ifdef FFMPEG_USE_NEW_CH_LAYOUT
swr_alloc_set_opts2(&encoder->resampleContext, &(AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO, encoder->sampleFormat, encoder->sampleRate,
&(AVChannelLayout) AV_CHANNEL_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, encoder->isampleRate, 0, NULL);
#else
encoder->resampleContext = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, encoder->sampleFormat, encoder->sampleRate,
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, encoder->isampleRate, 0, NULL);
#endif
swr_init(encoder->resampleContext);
#endif
}

View File

@ -9,6 +9,7 @@
#include <mgba/core/core.h>
#include "feature/gui/gui-runner.h"
#include <mgba-util/gui/menu.h>
#include <mgba-util/string.h>
enum mGUICheatAction {
CHEAT_BACK = 0,

View File

@ -31,7 +31,7 @@ static bool _biosNamed(const char* name) {
char ext[PATH_MAX + 1] = {};
separatePath(name, NULL, NULL, ext);
if (strstr(name, "bios")) {
if (strcasestr(name, "bios")) {
return true;
}
if (!strncmp(ext, "bin", PATH_MAX)) {

View File

@ -492,6 +492,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
}
mLOG(GUI_RUNNER, INFO, "Game starting");
runner->fps = 0;
bool fastForward = false;
while (running) {
CircleBufferClear(&runner->fpsBuffer);
runner->totalDelta = 0;
@ -500,7 +501,6 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec;
int frame = 0;
bool fastForward = false;
while (running) {
if (runner->running) {
running = runner->running(runner);
@ -648,6 +648,9 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
runner->core->reset(runner->core);
break;
case RUNNER_SAVE_STATE:
// If we are saving state, then the screenshot stored for the state previously should no longer be considered up-to-date.
// Therefore, mark it as stale so that at draw time we load the new save state's screenshot.
((struct mGUIBackground*) stateSaveMenu.background)->screenshotId |= SCREENSHOT_INVALID;
mCoreSaveState(runner->core, item->data.v.u >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA);
break;
case RUNNER_LOAD_STATE:

View File

@ -17,7 +17,7 @@ struct NoIntroDB {
};
struct NoIntroDB* NoIntroDBLoad(const char* path) {
struct NoIntroDB* db = malloc(sizeof(*db));
struct NoIntroDB* db = calloc(1, sizeof(*db));
if (sqlite3_open_v2(path, &db->db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL)) {
goto error;
@ -60,9 +60,6 @@ struct NoIntroDB* NoIntroDBLoad(const char* path) {
return db;
error:
if (db->crc32) {
sqlite3_finalize(db->crc32);
}
NoIntroDBDestroy(db);
return NULL;
@ -285,8 +282,12 @@ bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf) {
}
void NoIntroDBDestroy(struct NoIntroDB* db) {
sqlite3_finalize(db->crc32);
sqlite3_close(db->db);
if (db->crc32) {
sqlite3_finalize(db->crc32);
}
if (db->db) {
sqlite3_close(db->db);
}
free(db);
}

View File

@ -31,6 +31,39 @@
FILE* logfile;
bool rmdirRecursive(struct VDir* dir) {
if (!dir) {
return false;
}
bool ok = true;
struct VDirEntry* vde;
while ((vde = dir->listNext(dir))) {
const char* name = vde->name(vde);
if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0) {
continue;
}
switch (vde->type(vde)) {
case VFS_DIRECTORY:
fprintf(logfile, "cd %s\n", name);
if (!rmdirRecursive(dir->openDir(dir, name))) {
ok = false;
}
fprintf(logfile, "cd ..\n");
// Fall through
case VFS_FILE:
case VFS_UNKNOWN:
fprintf(logfile, "rm %s\n", name);
if (!dir->deleteFile(dir, name)) {
fprintf(logfile, "error\n");
ok = false;
}
break;
}
}
dir->close(dir);
return ok;
}
bool extractArchive(struct VDir* archive, const char* root, bool prefix) {
char path[PATH_MAX] = {0};
struct VDirEntry* vde;
@ -57,11 +90,13 @@ bool extractArchive(struct VDir* archive, const char* root, bool prefix) {
case VFS_DIRECTORY:
fprintf(logfile, "mkdir %s\n", fname);
if (mkdir(path, 0755) < 0 && errno != EEXIST) {
fprintf(logfile, "error %i\n", errno);
return false;
}
if (!prefix) {
struct VDir* subdir = archive->openDir(archive, fname);
if (!subdir) {
fprintf(logfile, "error\n");
return false;
}
if (!extractArchive(subdir, path, false)) {
@ -76,13 +111,28 @@ bool extractArchive(struct VDir* archive, const char* root, bool prefix) {
vfIn = archive->openFile(archive, vde->name(vde), O_RDONLY);
errno = 0;
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
if (!vfOut && errno == EACCES) {
if (!vfOut) {
if (errno == EACCES) {
#ifdef _WIN32
Sleep(1000);
Sleep(1000);
#else
sleep(1);
sleep(1);
#endif
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
} else if (errno == EISDIR) {
fprintf(logfile, "rm -r %s\n", path);
if (!rmdirRecursive(VDirOpen(path))) {
return false;
}
#ifdef _WIN32
wchar_t wpath[MAX_PATH + 1];
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);
RemoveDirectoryW(wpath);
#else
rmdir(path);
#endif
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
}
}
if (!vfOut) {
vfIn->close(vfIn);
@ -114,7 +164,7 @@ int main(int argc, char* argv[]) {
int ok = 1;
mCoreConfigDirectory(bin, sizeof(bin));
strncat(bin, "/updater.log", sizeof(bin));
strncat(bin, "/updater.log", sizeof(bin) - 1);
logfile = fopen(bin, "w");
mCoreConfigInit(&config, "updater");

View File

@ -78,14 +78,7 @@ static void _updateMatch(const char* key, const char* value, void* user) {
return;
}
const char* item = &key[dotLoc + 1];
struct Table* out = user;
struct mUpdate* update = HashTableLookup(out, match->channel);
if (!update) {
update = calloc(1, sizeof(*update));
HashTableInsert(out, match->channel, update);
}
_updateUpdate(update, item, value);
_updateUpdate(match->out, item, value);
}
bool mUpdaterInit(struct mUpdaterContext* context, const char* manifest) {

View File

@ -100,22 +100,24 @@ void GBAudioReset(struct GBAudio* audio) {
audio->ch3 = (struct GBAudioWaveChannel) { .bank = 0 };
audio->ch4 = (struct GBAudioNoiseChannel) { .nSamples = 0 };
// TODO: DMG randomness
audio->ch3.wavedata8[0] = 0x00;
audio->ch3.wavedata8[1] = 0xFF;
audio->ch3.wavedata8[2] = 0x00;
audio->ch3.wavedata8[3] = 0xFF;
audio->ch3.wavedata8[4] = 0x00;
audio->ch3.wavedata8[5] = 0xFF;
audio->ch3.wavedata8[6] = 0x00;
audio->ch3.wavedata8[7] = 0xFF;
audio->ch3.wavedata8[8] = 0x00;
audio->ch3.wavedata8[9] = 0xFF;
audio->ch3.wavedata8[10] = 0x00;
audio->ch3.wavedata8[11] = 0xFF;
audio->ch3.wavedata8[12] = 0x00;
audio->ch3.wavedata8[13] = 0xFF;
audio->ch3.wavedata8[14] = 0x00;
audio->ch3.wavedata8[15] = 0xFF;
if (audio->style != GB_AUDIO_GBA) {
audio->ch3.wavedata8[0] = 0x00;
audio->ch3.wavedata8[1] = 0xFF;
audio->ch3.wavedata8[2] = 0x00;
audio->ch3.wavedata8[3] = 0xFF;
audio->ch3.wavedata8[4] = 0x00;
audio->ch3.wavedata8[5] = 0xFF;
audio->ch3.wavedata8[6] = 0x00;
audio->ch3.wavedata8[7] = 0xFF;
audio->ch3.wavedata8[8] = 0x00;
audio->ch3.wavedata8[9] = 0xFF;
audio->ch3.wavedata8[10] = 0x00;
audio->ch3.wavedata8[11] = 0xFF;
audio->ch3.wavedata8[12] = 0x00;
audio->ch3.wavedata8[13] = 0xFF;
audio->ch3.wavedata8[14] = 0x00;
audio->ch3.wavedata8[15] = 0xFF;
}
audio->ch4 = (struct GBAudioNoiseChannel) { .envelope = { .dead = 2 } };
audio->frame = 0;
audio->sampleInterval = SAMPLE_INTERVAL * GB_MAX_SAMPLES;
@ -138,6 +140,9 @@ void GBAudioReset(struct GBAudio* audio) {
}
void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) {
if (samples > BLIP_BUFFER_SIZE / 2) {
samples = BLIP_BUFFER_SIZE / 2;
}
mCoreSyncLockAudio(audio->p->sync);
audio->samples = samples;
blip_clear(audio->left);
@ -269,6 +274,29 @@ void GBAudioWriteNR31(struct GBAudio* audio, uint8_t value) {
void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
audio->ch3.volume = GBAudioRegisterBankVolumeGetVolumeGB(value);
audio->ch3.sample = audio->ch3.wavedata8[audio->ch3.window >> 1];
if (!(audio->ch3.window & 1)) {
audio->ch3.sample >>= 4;
}
audio->ch3.sample &= 0xF;
int volume;
switch (audio->ch3.volume) {
case 0:
volume = 4;
break;
case 1:
volume = 0;
break;
case 2:
volume = 1;
break;
default:
case 3:
volume = 2;
break;
}
audio->ch3.sample >>= volume;
}
void GBAudioWriteNR33(struct GBAudio* audio, uint8_t value) {
@ -377,13 +405,13 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
}
void GBAudioWriteNR50(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF);
audio->volumeRight = GBRegisterNR50GetVolumeRight(value);
audio->volumeLeft = GBRegisterNR50GetVolumeLeft(value);
}
void GBAudioWriteNR51(struct GBAudio* audio, uint8_t value) {
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x2);
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0xF);
audio->ch1Right = GBRegisterNR51GetCh1Right(value);
audio->ch2Right = GBRegisterNR51GetCh2Right(value);
audio->ch3Right = GBRegisterNR51GetCh3Right(value);
@ -476,11 +504,11 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
if (!audio->enable) {
return;
}
if (audio->p && channels != 0xF && timestamp - audio->lastSample > (int) (SAMPLE_INTERVAL * audio->timingFactor)) {
if (audio->p && channels != 0x1F && timestamp - audio->lastSample > (int) (SAMPLE_INTERVAL * audio->timingFactor)) {
GBAudioSample(audio, timestamp);
}
if (audio->playingCh1 && (channels & 0x1)) {
if ((channels & 0x1) && ((audio->playingCh1 && audio->ch1.envelope.dead != 2) || timestamp - audio->ch1.lastUpdate > 0x40000000 || (channels == 0x1))) {
int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor;
int32_t diff = timestamp - audio->ch1.lastUpdate;
if (diff >= period) {
@ -490,7 +518,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
_updateSquareSample(&audio->ch1);
}
}
if (audio->playingCh2 && (channels & 0x2)) {
if ((channels & 0x2) && ((audio->playingCh2 && audio->ch2.envelope.dead != 2) || timestamp - audio->ch2.lastUpdate > 0x40000000 || (channels == 0x2))) {
int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor;
int32_t diff = timestamp - audio->ch2.lastUpdate;
if (diff >= period) {
@ -621,7 +649,9 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
if (audio->ch1.sweep.enable) {
--audio->ch1.sweep.step;
if (audio->ch1.sweep.step == 0) {
audio->playingCh1 = _updateSweep(&audio->ch1, false);
if (!_updateSweep(&audio->ch1, false)) {
audio->playingCh1 = false;
}
*audio->nr52 &= ~0x0001;
*audio->nr52 |= audio->playingCh1;
}
@ -756,7 +786,7 @@ void GBAudioSample(struct GBAudio* audio, int32_t timestamp) {
for (sample = audio->sampleIndex; timestamp >= interval && sample < GB_MAX_SAMPLES; ++sample, timestamp -= interval) {
int16_t sampleLeft = 0;
int16_t sampleRight = 0;
GBAudioRun(audio, sample * interval + audio->lastSample, 0xF);
GBAudioRun(audio, sample * interval + audio->lastSample, 0x1F);
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
@ -820,9 +850,6 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
bool _resetEnvelope(struct GBAudioEnvelope* envelope) {
envelope->currentVolume = envelope->initialVolume;
_updateEnvelopeDead(envelope);
if (!envelope->dead) {
envelope->nextStep = envelope->stepTime;
}
return envelope->initialVolume || envelope->direction;
}
@ -854,16 +881,29 @@ void _writeDuty(struct GBAudioEnvelope* envelope, uint8_t value) {
}
bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value, enum GBAudioStyle style) {
bool oldDirection = envelope->direction;
envelope->stepTime = GBAudioRegisterSweepGetStepTime(value);
envelope->direction = GBAudioRegisterSweepGetDirection(value);
envelope->initialVolume = GBAudioRegisterSweepGetInitialVolume(value);
if (style == GB_AUDIO_DMG && !envelope->stepTime) {
if (!envelope->stepTime) {
// TODO: Improve "zombie" mode
++envelope->currentVolume;
if (style == GB_AUDIO_DMG) {
++envelope->currentVolume;
} else if (style == GB_AUDIO_CGB) {
if (envelope->direction == oldDirection) {
if (envelope->direction) {
++envelope->currentVolume;
} else {
envelope->currentVolume += 2;
}
} else {
envelope->currentVolume = 0;
}
}
envelope->currentVolume &= 0xF;
}
_updateEnvelopeDead(envelope);
return (envelope->initialVolume || envelope->direction) && envelope->dead != 2;
return envelope->initialVolume || envelope->direction;
}
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
@ -907,6 +947,7 @@ static void _updateEnvelopeDead(struct GBAudioEnvelope* envelope) {
envelope->dead = 1;
} else {
envelope->dead = 0;
envelope->nextStep = envelope->stepTime;
}
}
@ -1066,6 +1107,8 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt
audio->ch4.lastEvent = currentTime + (when & (cycles - 1)) - cycles;
}
}
audio->ch4.nSamples = 0;
audio->ch4.samples = 0;
}
void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state) {

View File

@ -607,16 +607,16 @@ static void _GBCoreReset(struct mCore* core) {
switch (gb->model) {
case GB_MODEL_DMG:
case GB_MODEL_MGB: // TODO
strncat(path, PATH_SEP "gb_bios.bin", PATH_MAX - strlen(path));
strncat(path, PATH_SEP "gb_bios.bin", PATH_MAX - strlen(path) - 1);
break;
case GB_MODEL_SGB:
case GB_MODEL_SGB2: // TODO
strncat(path, PATH_SEP "sgb_bios.bin", PATH_MAX - strlen(path));
strncat(path, PATH_SEP "sgb_bios.bin", PATH_MAX - strlen(path) - 1);
break;
case GB_MODEL_CGB:
case GB_MODEL_AGB:
case GB_MODEL_SCGB:
strncat(path, PATH_SEP "gbc_bios.bin", PATH_MAX - strlen(path));
strncat(path, PATH_SEP "gbc_bios.bin", PATH_MAX - strlen(path) - 1);
break;
default:
break;
@ -644,8 +644,10 @@ static void _GBCoreReset(struct mCore* core) {
size_t i;
for (i = 0; i < sizeof(gbcore->memoryBlocks) / sizeof(*gbcore->memoryBlocks); ++i) {
if (gbcore->memoryBlocks[i].id == GB_REGION_CART_BANK0) {
gbcore->memoryBlocks[i].size = gb->memory.romSize;
gbcore->memoryBlocks[i].maxSegment = gb->memory.romSize / GB_SIZE_CART_BANK0;
} else if (gbcore->memoryBlocks[i].id == GB_REGION_EXTERNAL_RAM) {
gbcore->memoryBlocks[i].size = gb->sramSize;
gbcore->memoryBlocks[i].maxSegment = gb->sramSize / GB_SIZE_EXTERNAL_RAM;
} else {
continue;
@ -1050,7 +1052,7 @@ static void _GBCoreDetachDebugger(struct mCore* core) {
static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) {
core->symbolTable = mDebuggerSymbolTableCreate();
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
if (!vf) {
if (!vf && core->dirs.base) {
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY);
}
#endif

View File

@ -405,7 +405,9 @@ void GBUnloadROM(struct GB* gb) {
if (gb->romVf) {
#ifndef FIXED_ROM_BUFFER
gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->pristineRomSize);
if (gb->isPristine && gb->memory.rom) {
gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->pristineRomSize);
}
#endif
gb->romVf->close(gb->romVf);
gb->romVf = NULL;
@ -453,6 +455,9 @@ void GBApplyPatch(struct GB* gb, struct Patch* patch) {
if (patchedSize > GB_SIZE_CART_MAX) {
patchedSize = GB_SIZE_CART_MAX;
}
const struct GBCartridge* cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
uint8_t type = cart->type;
void* newRom = anonymousMemoryMap(GB_SIZE_CART_MAX);
if (!patch->applyPatch(patch, gb->memory.rom, gb->pristineRomSize, newRom, patchedSize)) {
mappedMemoryFree(newRom, GB_SIZE_CART_MAX);
@ -471,6 +476,12 @@ void GBApplyPatch(struct GB* gb, struct Patch* patch) {
}
gb->memory.rom = newRom;
gb->memory.romSize = patchedSize;
cart = (const struct GBCartridge*) &gb->memory.rom[0x100];
if (cart->type != type) {
gb->memory.mbcType = GB_MBC_AUTODETECT;
GBMBCInit(gb);
}
gb->romCrc32 = doCrc32(gb->memory.rom, gb->memory.romSize);
gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);
}
@ -596,13 +607,13 @@ void GBReset(struct SM83Core* cpu) {
GBVideoReset(&gb->video);
GBTimerReset(&gb->timer);
GBIOReset(gb);
GBAudioReset(&gb->audio);
if (!gb->biosVf && gb->memory.rom) {
GBSkipBIOS(gb);
} else {
mTimingSchedule(&gb->timing, &gb->timer.event, 0);
}
GBAudioReset(&gb->audio);
GBSIOReset(&gb->sio);
cpu->memory.setActiveRegion(cpu, cpu->pc);
@ -744,6 +755,25 @@ void GBSkipBIOS(struct GB* gb) {
GBUnmapBIOS(gb);
}
GBIOWrite(gb, GB_REG_NR52, 0xF1);
GBIOWrite(gb, GB_REG_NR14, 0x3F);
GBIOWrite(gb, GB_REG_NR10, 0x80);
GBIOWrite(gb, GB_REG_NR11, 0xBF);
GBIOWrite(gb, GB_REG_NR12, 0xF3);
GBIOWrite(gb, GB_REG_NR13, 0xF3);
GBIOWrite(gb, GB_REG_NR24, 0x3F);
GBIOWrite(gb, GB_REG_NR21, 0x3F);
GBIOWrite(gb, GB_REG_NR22, 0x00);
GBIOWrite(gb, GB_REG_NR34, 0x3F);
GBIOWrite(gb, GB_REG_NR30, 0x7F);
GBIOWrite(gb, GB_REG_NR31, 0xFF);
GBIOWrite(gb, GB_REG_NR32, 0x9F);
GBIOWrite(gb, GB_REG_NR44, 0x3F);
GBIOWrite(gb, GB_REG_NR41, 0xFF);
GBIOWrite(gb, GB_REG_NR42, 0x00);
GBIOWrite(gb, GB_REG_NR43, 0x00);
GBIOWrite(gb, GB_REG_NR50, 0x77);
GBIOWrite(gb, GB_REG_NR51, 0xF3);
GBIOWrite(gb, GB_REG_LCDC, 0x91);
gb->memory.io[GB_REG_BANK] = 0x1;
GBVideoSkipBIOS(&gb->video);
@ -796,6 +826,7 @@ void GBDetectModel(struct GB* gb) {
gb->model = GB_MODEL_SGB2;
break;
case CGB_BIOS_CHECKSUM:
case CGB0_BIOS_CHECKSUM:
gb->model = GB_MODEL_CGB;
break;
case AGB_BIOS_CHECKSUM:

View File

@ -162,36 +162,7 @@ void GBIOReset(struct GB* gb) {
GBIOWrite(gb, GB_REG_TMA, 0);
GBIOWrite(gb, GB_REG_TAC, 0);
GBIOWrite(gb, GB_REG_IF, 1);
gb->audio.playingCh1 = false;
gb->audio.playingCh2 = false;
gb->audio.playingCh3 = false;
gb->audio.playingCh4 = false;
GBIOWrite(gb, GB_REG_NR52, 0xF1);
GBIOWrite(gb, GB_REG_NR14, 0x3F);
GBIOWrite(gb, GB_REG_NR10, 0x80);
GBIOWrite(gb, GB_REG_NR11, 0xBF);
GBIOWrite(gb, GB_REG_NR12, 0xF3);
GBIOWrite(gb, GB_REG_NR13, 0xF3);
GBIOWrite(gb, GB_REG_NR24, 0x3F);
GBIOWrite(gb, GB_REG_NR21, 0x3F);
GBIOWrite(gb, GB_REG_NR22, 0x00);
GBIOWrite(gb, GB_REG_NR34, 0x3F);
GBIOWrite(gb, GB_REG_NR30, 0x7F);
GBIOWrite(gb, GB_REG_NR31, 0xFF);
GBIOWrite(gb, GB_REG_NR32, 0x9F);
GBIOWrite(gb, GB_REG_NR44, 0x3F);
GBIOWrite(gb, GB_REG_NR41, 0xFF);
GBIOWrite(gb, GB_REG_NR42, 0x00);
GBIOWrite(gb, GB_REG_NR43, 0x00);
GBIOWrite(gb, GB_REG_NR50, 0x77);
GBIOWrite(gb, GB_REG_NR51, 0xF3);
if (!gb->biosVf) {
GBIOWrite(gb, GB_REG_LCDC, 0x91);
gb->memory.io[GB_REG_BANK] = 1;
} else {
GBIOWrite(gb, GB_REG_LCDC, 0x00);
gb->memory.io[GB_REG_BANK] = 0xFF;
}
GBIOWrite(gb, GB_REG_LCDC, 0x00);
GBIOWrite(gb, GB_REG_SCY, 0x00);
GBIOWrite(gb, GB_REG_SCX, 0x00);
GBIOWrite(gb, GB_REG_LYC, 0x00);
@ -203,6 +174,7 @@ void GBIOReset(struct GB* gb) {
}
GBIOWrite(gb, GB_REG_WY, 0x00);
GBIOWrite(gb, GB_REG_WX, 0x00);
gb->memory.io[GB_REG_BANK] = 0xFF;
if (gb->model & GB_MODEL_CGB) {
GBIOWrite(gb, GB_REG_KEY0, 0);
GBIOWrite(gb, GB_REG_JOYP, 0xFF);
@ -519,7 +491,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
return;
case GB_REG_SVBK:
GBMemorySwitchWramBank(&gb->memory, value);
value = gb->memory.wramCurrentBank;
value &= 7;
break;
default:
goto failed;
@ -754,7 +726,7 @@ void GBIODeserialize(struct GB* gb, const struct GBSerializedState* state) {
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_SCX, state->io[GB_REG_SCX]);
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_WY, state->io[GB_REG_WY]);
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_WX, state->io[GB_REG_WX]);
if (gb->model & GB_MODEL_SGB) {
if (gb->model == GB_MODEL_SGB) {
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_BGP, state->io[GB_REG_BGP]);
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP0, state->io[GB_REG_OBP0]);
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP1, state->io[GB_REG_OBP1]);

View File

@ -14,6 +14,7 @@
#include <mgba/internal/sm83/sm83.h>
#include <mgba-util/memory.h>
#include <mgba-util/vfs.h>
mLOG_DEFINE_CATEGORY(GB_MEM, "GB Memory", "gb.memory");
@ -474,13 +475,14 @@ uint8_t GBView8(struct SM83Core* cpu, uint16_t address, int segment) {
if (memory->rtcAccess) {
return memory->rtcRegs[memory->activeRtcReg];
} else if (memory->sramAccess) {
if (segment < 0 && memory->sram) {
return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)];
} else if ((size_t) segment * GB_SIZE_EXTERNAL_RAM < gb->sramSize) {
return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM];
} else {
return 0xFF;
if (memory->sram) {
if (segment < 0) {
return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)];
} else if ((size_t) segment * GB_SIZE_EXTERNAL_RAM < gb->sramSize) {
return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM];
}
}
return 0xFF;
} else if (memory->mbcRead) {
return memory->mbcRead(memory, address);
} else if (memory->mbcType == GB_HuC3) {
@ -558,7 +560,7 @@ uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) {
gb->memory.hdmaDest |= 0x8000;
bool wasHdma = gb->memory.isHdma;
gb->memory.isHdma = value & 0x80;
if ((!wasHdma && !gb->memory.isHdma) || (GBRegisterLCDCIsEnable(gb->memory.io[GB_REG_LCDC]) && gb->video.mode == 0)) {
if ((!wasHdma && !gb->memory.isHdma) || gb->video.mode == 0) {
if (gb->memory.isHdma) {
gb->memory.hdmaRemaining = 0x10;
} else {
@ -566,8 +568,6 @@ uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) {
}
gb->cpuBlocked = true;
mTimingSchedule(&gb->timing, &gb->memory.hdmaEvent, 0);
} else if (gb->memory.isHdma && !GBRegisterLCDCIsEnable(gb->memory.io[GB_REG_LCDC])) {
return 0x80 | ((value + 1) & 0x7F);
}
return value & 0x7F;
}
@ -958,6 +958,11 @@ void _pristineCow(struct GB* gb) {
if (gb->memory.rom == gb->memory.romBase) {
gb->memory.romBase = newRom;
}
if (gb->romVf) {
gb->romVf->unmap(gb->romVf, gb->memory.rom, gb->memory.romSize);
gb->romVf->close(gb->romVf);
gb->romVf = NULL;
}
gb->memory.rom = newRom;
GBMBCSwitchBank(gb, gb->memory.currentBank);
gb->isPristine = false;

View File

@ -91,11 +91,13 @@ void GBSIOWriteSB(struct GBSIO* sio, uint8_t sb) {
void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc) {
sio->period = GBSIOCyclesPerTransfer[GBRegisterSCGetClockSpeed(sc)]; // TODO Shift Clock
if (GBRegisterSCIsEnable(sc)) {
mTimingDeschedule(&sio->p->timing, &sio->event);
if (GBRegisterSCIsShiftClock(sc)) {
mTimingDeschedule(&sio->p->timing, &sio->event);
mTimingSchedule(&sio->p->timing, &sio->event, sio->period * (2 - sio->p->doubleSpeed));
sio->remainingBits = 8;
}
} else {
mTimingDeschedule(&sio->p->timing, &sio->event);
}
if (sio->driver) {
sio->driver->writeSC(sio->driver, sc);

View File

@ -157,7 +157,7 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
}
}
// Tell the other GBs they can continue up to where we were
node->p->d.addCycles(&node->p->d, node->id, node->eventDiff);
node->p->d.addCycles(&node->p->d, 0, node->eventDiff);
#ifndef NDEBUG
node->phase = node->p->d.transferActive;
#endif
@ -169,26 +169,28 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
enum mLockstepPhase transferActive;
int id;
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
ATOMIC_LOAD(id, node->id);
bool signal = false;
switch (transferActive) {
case TRANSFER_IDLE:
node->p->d.addCycles(&node->p->d, node->id, LOCKSTEP_INCREMENT);
node->p->d.addCycles(&node->p->d, id, LOCKSTEP_INCREMENT);
break;
case TRANSFER_STARTING:
case TRANSFER_FINISHING:
break;
case TRANSFER_STARTED:
if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
if (node->p->d.unusedCycles(&node->p->d, id) > node->eventDiff) {
break;
}
node->transferFinished = false;
signal = true;
break;
case TRANSFER_FINISHED:
if (node->p->d.unusedCycles(&node->p->d, node->id) > node->eventDiff) {
if (node->p->d.unusedCycles(&node->p->d, id) > node->eventDiff) {
break;
}
_finishTransfer(node);
@ -199,7 +201,7 @@ static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
node->phase = node->p->d.transferActive;
#endif
if (signal) {
node->p->d.signal(&node->p->d, 1 << node->id);
node->p->d.signal(&node->p->d, 1 << id);
}
return 0;
}
@ -215,11 +217,13 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
int32_t cycles = 0;
node->nextEvent -= cyclesLate;
if (node->nextEvent <= 0) {
if (!node->id) {
int id;
ATOMIC_LOAD(id, node->id);
if (!id) {
cycles = _masterUpdate(node);
} else {
cycles = _slaveUpdate(node);
cycles += node->p->d.useCycles(&node->p->d, node->id, node->eventDiff);
cycles += node->p->d.useCycles(&node->p->d, id, node->eventDiff);
}
node->eventDiff = 0;
} else {
@ -240,7 +244,9 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) {
struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
node->p->pendingSB[node->id] = value;
int id;
ATOMIC_LOAD(id, node->id);
node->p->pendingSB[id] = value;
}
static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t value) {
@ -252,11 +258,17 @@ static uint8_t GBSIOLockstepNodeWriteSC(struct GBSIODriver* driver, uint8_t valu
mLockstepLock(&node->p->d);
bool claimed = false;
if (ATOMIC_CMPXCHG(node->p->masterClaimed, claimed, true)) {
if (node->id != 0) {
int id;
ATOMIC_LOAD(id, node->id);
if (id != 0) {
unsigned sb;
node->p->players[0]->id = 1;
node->p->players[1] = node->p->players[0];
node->p->players[0] = node->p->players[1];
node->id = 0;
node->p->players[1] = node->p->players[0];
node->p->players[0] = node;
sb = node->p->pendingSB[0];
node->p->pendingSB[0] = node->p->pendingSB[1];
node->p->pendingSB[1] = sb;
}
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]);

View File

@ -106,6 +106,9 @@ void GBAAudioDeinit(struct GBAAudio* audio) {
}
void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
if (samples > 0x2000) {
samples = 0x2000;
}
mCoreSyncLockAudio(audio->p->sync);
audio->samples = samples;
blip_clear(audio->psg.left);
@ -128,17 +131,24 @@ void GBAAudioScheduleFifoDma(struct GBAAudio* audio, int number, struct GBADMA*
mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest);
return;
}
uint32_t source = info->source;
uint32_t magic[2] = {
audio->p->cpu->memory.load32(audio->p->cpu, source - 0x350, NULL),
audio->p->cpu->memory.load32(audio->p->cpu, source - 0x980, NULL)
};
if (audio->mixer) {
if (magic[0] - MP2K_MAGIC <= MP2K_LOCK_MAX) {
audio->mixer->engage(audio->mixer, source - 0x350);
} else if (magic[1] - MP2K_MAGIC <= MP2K_LOCK_MAX) {
audio->mixer->engage(audio->mixer, source - 0x980);
} else {
uint32_t source = info->source;
uint32_t offsets[] = { 0x350, 0x980 };
size_t i;
for (i = 0; i < sizeof(offsets) / sizeof(*offsets); ++i) {
if (source < BASE_WORKING_RAM + offsets[i]) {
continue;
}
if (source >= BASE_IO + offsets[i]) {
continue;
}
uint32_t value = GBALoad32(audio->p->cpu, source - offsets[i], NULL);
if (value - MP2K_MAGIC <= MP2K_LOCK_MAX) {
audio->mixer->engage(audio->mixer, source - offsets[i]);
break;
}
}
if (i == sizeof(offsets) / sizeof(*offsets)) {
audio->externalMixing = false;
}
}
@ -231,16 +241,36 @@ void GBAAudioWriteSOUNDCNT_HI(struct GBAAudio* audio, uint16_t value) {
}
void GBAAudioWriteSOUNDCNT_X(struct GBAAudio* audio, uint16_t value) {
GBAAudioSample(audio, mTimingCurrentTime(&audio->p->timing));
audio->enable = GBAudioEnableGetEnable(value);
GBAudioWriteNR52(&audio->psg, value);
if (!audio->enable) {
int i;
for (i = REG_SOUND1CNT_LO; i < REG_SOUNDCNT_HI; i += 2) {
audio->p->memory.io[i >> 1] = 0;
}
audio->psg.ch3.size = 0;
audio->psg.ch3.bank = 0;
audio->psg.ch3.volume = 0;
audio->volume = 0;
audio->volumeChA = 0;
audio->volumeChB = 0;
audio->p->memory.io[REG_SOUNDCNT_HI >> 1] &= 0xFF00;
}
}
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) {
int32_t timestamp = mTimingCurrentTime(&audio->p->timing);
GBAAudioSample(audio, timestamp);
audio->soundbias = value;
int32_t oldSampleInterval = audio->sampleInterval;
audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value);
if (oldSampleInterval != audio->sampleInterval && audio->p->stream && audio->p->stream->audioRateChanged) {
audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval);
if (oldSampleInterval != audio->sampleInterval) {
timestamp -= audio->lastSample;
audio->sampleIndex = timestamp >> (9 - GBARegisterSOUNDBIASGetResolution(value));
if (audio->p->stream && audio->p->stream->audioRateChanged) {
audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval);
}
}
}
@ -506,6 +536,16 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState*
void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) {
GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags);
uint16_t reg;
LOAD_16(reg, REG_SOUND1CNT_X, state->io);
GBAIOWrite(audio->p, REG_SOUND1CNT_X, reg & 0x7FFF);
LOAD_16(reg, REG_SOUND2CNT_HI, state->io);
GBAIOWrite(audio->p, REG_SOUND2CNT_HI, reg & 0x7FFF);
LOAD_16(reg, REG_SOUND3CNT_X, state->io);
GBAIOWrite(audio->p, REG_SOUND3CNT_X, reg & 0x7FFF);
LOAD_16(reg, REG_SOUND4CNT_HI, state->io);
GBAIOWrite(audio->p, REG_SOUND4CNT_HI, reg & 0x7FFF);
LOAD_32(audio->chA.internalSample, 0, &state->audio.internalA);
LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB);
memcpy(audio->chA.samples, state->samples.chA, sizeof(audio->chA.samples));

View File

@ -336,12 +336,14 @@ static int16_t _ArcTan(int32_t i, int32_t* r1, int32_t* r3, uint32_t* cycles) {
static int16_t _ArcTan2(int32_t x, int32_t y, int32_t* r1, uint32_t* cycles) {
if (!y) {
*cycles = 11;
if (x >= 0) {
return 0;
}
return 0x8000;
}
if (!x) {
*cycles = 11;
if (y >= 0) {
return 0x4000;
}

View File

@ -421,8 +421,8 @@ void GBAHardwareTiltWrite(struct GBACartridgeHardware* hw, uint32_t address, uin
int32_t x = rotationSource->readTiltX(rotationSource);
int32_t y = rotationSource->readTiltY(rotationSource);
// Normalize to ~12 bits, focused on 0x3A0
hw->tiltX = (x >> 21) + 0x3A0; // Crop off an extra bit so that we can't go negative
hw->tiltY = (y >> 21) + 0x3A0;
hw->tiltX = 0x3A0 - (x >> 22);
hw->tiltY = 0x3A0 - (y >> 22);
} else {
mLOG(GBA_HW, GAME_ERROR, "Tilt sensor wrote wrong byte to %04x: %02x", address, value);
}

View File

@ -126,13 +126,13 @@ static bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3);
}
rgsaP = GBACheatGameSharkProbability(op1, op1);
rgsaP = GBACheatGameSharkProbability(op1, op2);
if (rgsaP > maxProbability) {
maxProbability = rgsaP;
GBACheatSetGameSharkVersion(set, GBA_GS_GSAV1_RAW);
}
rparP = GBACheatProActionReplayProbability(op1, op1);
rparP = GBACheatProActionReplayProbability(op1, op2);
if (rparP > maxProbability) {
maxProbability = rparP;
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3_RAW);

View File

@ -111,7 +111,7 @@ static const struct mCoreMemoryBlock _GBAMemoryBlocksFlash1M[] = {
{ REGION_CART0, "cart0", "ROM", "Game Pak (32MiB)", BASE_CART0, BASE_CART0 + SIZE_CART0, SIZE_CART0, mCORE_MEMORY_READ | mCORE_MEMORY_WORM | mCORE_MEMORY_MAPPED },
{ REGION_CART1, "cart1", "ROM WS1", "Game Pak (Waitstate 1)", BASE_CART1, BASE_CART1 + SIZE_CART1, SIZE_CART1, mCORE_MEMORY_READ | mCORE_MEMORY_WORM | mCORE_MEMORY_MAPPED },
{ REGION_CART2, "cart2", "ROM WS2", "Game Pak (Waitstate 2)", BASE_CART2, BASE_CART2 + SIZE_CART2, SIZE_CART2, mCORE_MEMORY_READ | mCORE_MEMORY_WORM | mCORE_MEMORY_MAPPED },
{ REGION_CART_SRAM, "sram", "Flash", "Flash Memory (64kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH1M, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 },
{ REGION_CART_SRAM, "sram", "Flash", "Flash Memory (128kiB)", BASE_CART_SRAM, BASE_CART_SRAM + SIZE_CART_FLASH512, SIZE_CART_FLASH1M, mCORE_MEMORY_RW | mCORE_MEMORY_MAPPED, 1 },
};
static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = {
@ -684,7 +684,7 @@ static void _GBACoreReset(struct mCore* core) {
if (!found) {
char path[PATH_MAX];
mCoreConfigDirectory(path, PATH_MAX);
strncat(path, PATH_SEP "gba_bios.bin", PATH_MAX - strlen(path));
strncat(path, PATH_SEP "gba_bios.bin", PATH_MAX - strlen(path) - 1);
bios = VFileOpen(path, O_RDONLY);
if (bios && GBAIsBIOS(bios)) {
found = true;
@ -700,8 +700,8 @@ static void _GBACoreReset(struct mCore* core) {
#endif
ARMReset(core->cpu);
bool forceSkip = gba->romVf && GBAIsMB(gba->romVf);
if (!(forceSkip || core->opts.skipBios) && (gba->romVf || gba->memory.rom) && gba->pristineRomSize >= 0xA0 && gba->biosVf) {
bool forceSkip = gba->mbVf || core->opts.skipBios;
if (!forceSkip && (gba->romVf || gba->memory.rom) && gba->pristineRomSize >= 0xA0 && gba->biosVf) {
uint32_t crc = doCrc32(&gba->memory.rom[1], 0x9C);
if (crc != LOGO_CRC32) {
mLOG(STATUS, WARN, "Invalid logo, skipping BIOS");
@ -709,7 +709,7 @@ static void _GBACoreReset(struct mCore* core) {
}
}
if (forceSkip || (core->opts.skipBios && (gba->romVf || gba->memory.rom))) {
if (forceSkip) {
GBASkipBIOS(core->board);
}
@ -1116,12 +1116,12 @@ static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) {
core->symbolTable = mDebuggerSymbolTableCreate();
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
#ifdef USE_ELF
if (!vf) {
if (!vf && core->dirs.base) {
closeAfter = true;
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".elf", O_RDONLY);
}
#endif
if (!vf) {
if (!vf && core->dirs.base) {
closeAfter = true;
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY);
}

View File

@ -245,9 +245,6 @@ static void _mp2kReload(struct GBAAudioMixer* mixer) {
}
bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address) {
if (address < BASE_WORKING_RAM) {
return false;
}
if (address != mixer->contextAddress) {
mixer->contextAddress = address;
mixer->p->externalMixing = true;

View File

@ -132,6 +132,7 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
}
void GBAUnloadROM(struct GBA* gba) {
GBAMemoryClearAGBPrint(gba);
if (gba->memory.rom && !gba->isPristine) {
if (gba->yankedRomSize) {
gba->yankedRomSize = 0;
@ -213,6 +214,7 @@ void GBAReset(struct ARMCore* cpu) {
gba->earlyExit = false;
gba->dmaPC = 0;
gba->biosStall = 0;
gba->keysLast = 0x400;
if (gba->yankedRomSize) {
gba->memory.romSize = gba->yankedRomSize;
gba->memory.romMask = toPow2(gba->memory.romSize) - 1;
@ -274,8 +276,10 @@ void GBASkipBIOS(struct GBA* gba) {
if (cpu->gprs[ARM_PC] == BASE_RESET + WORD_SIZE_ARM) {
if (gba->memory.rom) {
cpu->gprs[ARM_PC] = BASE_CART0;
} else {
} else if (gba->memory.wram[0x30]) {
cpu->gprs[ARM_PC] = BASE_WORKING_RAM + 0xC0;
} else {
cpu->gprs[ARM_PC] = BASE_WORKING_RAM;
}
gba->video.vcount = 0x7E;
gba->memory.io[REG_VCOUNT >> 1] = 0x7E;

View File

@ -50,13 +50,13 @@ const uint8_t hleBios[SIZE_BIOS] = {
0x0c, 0x80, 0xbd, 0xe8, 0x30, 0x40, 0x2d, 0xe9, 0x02, 0x46, 0xa0, 0xe1,
0x00, 0xc0, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3,
0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a,
0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xbc, 0xe8, 0x04, 0x00, 0x55, 0xe1,
0x08, 0x00, 0xa5, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea,
0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xb0, 0xe8, 0x04, 0x00, 0x51, 0xe1,
0x08, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea,
0x01, 0xc0, 0xcc, 0xe3, 0x01, 0x50, 0xc5, 0xe3, 0xa4, 0x45, 0x85, 0xe0,
0xb0, 0x30, 0xdc, 0xe1, 0x04, 0x00, 0x55, 0xe1, 0xb2, 0x30, 0xc5, 0xb0,
0xfc, 0xff, 0xff, 0xba, 0x0c, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3,
0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1,
0x08, 0x00, 0xbc, 0xb8, 0x08, 0x00, 0xa5, 0xb8, 0xfb, 0xff, 0xff, 0xba,
0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x51, 0xe1,
0x08, 0x00, 0xb0, 0xb8, 0x08, 0x00, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba,
0x04, 0x00, 0x00, 0xea, 0xa4, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1,
0xb2, 0x30, 0xdc, 0xb0, 0xb2, 0x30, 0xc5, 0xb0, 0xfb, 0xff, 0xff, 0xba,
0x17, 0x3e, 0xa0, 0xe3, 0x30, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9,

View File

@ -209,10 +209,10 @@ tst r2, #0x04000000
beq 1f
@ Word
add r4, r5, r4, lsr #10
ldmia r12!, {r3}
ldmia r0!, {r3}
2:
cmp r5, r4
stmltia r5!, {r3}
cmp r1, r4
stmltia r1!, {r3}
blt 2b
b 3f
@ Halfword
@ -233,9 +233,9 @@ beq 1f
@ Word
add r4, r5, r4, lsr #10
2:
cmp r5, r4
ldmltia r12!, {r3}
stmltia r5!, {r3}
cmp r1, r4
ldmltia r0!, {r3}
stmltia r1!, {r3}
blt 2b
b 3f
@ Halfword

View File

@ -296,8 +296,8 @@ static const int _isWSpecialRegister[REG_INTERNAL_MAX >> 1] = {
0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0,
// Audio
1, 1, 1, 0, 1, 0, 1, 0,
1, 0, 1, 0, 1, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 1, 0,
0, 0, 1, 0, 0, 0, 0, 0,
1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 0, 0, 0, 0,

View File

@ -128,6 +128,21 @@ void GBAMemoryReset(struct GBA* gba) {
GBAAdjustWaitstates(gba, 0);
GBAAdjustEWRAMWaitstates(gba, 0x0D00);
GBAMemoryClearAGBPrint(gba);
gba->memory.prefetch = false;
gba->memory.lastPrefetchedPc = 0;
if (!gba->memory.wram || !gba->memory.iwram) {
GBAMemoryDeinit(gba);
mLOG(GBA_MEM, FATAL, "Could not map memory");
}
GBADMAReset(gba);
memset(&gba->memory.matrix, 0, sizeof(gba->memory.matrix));
}
void GBAMemoryClearAGBPrint(struct GBA* gba) {
gba->memory.activeRegion = -1;
gba->memory.agbPrintProtect = 0;
gba->memory.agbPrintBase = 0;
@ -140,17 +155,6 @@ void GBAMemoryReset(struct GBA* gba) {
mappedMemoryFree(gba->memory.agbPrintBufferBackup, SIZE_AGB_PRINT);
gba->memory.agbPrintBufferBackup = NULL;
}
gba->memory.prefetch = false;
gba->memory.lastPrefetchedPc = 0;
if (!gba->memory.wram || !gba->memory.iwram) {
GBAMemoryDeinit(gba);
mLOG(GBA_MEM, FATAL, "Could not map memory");
}
GBADMAReset(gba);
memset(&gba->memory.matrix, 0, sizeof(gba->memory.matrix));
}
static void _analyzeForIdleLoop(struct GBA* gba, struct ARMCore* cpu, uint32_t address) {
@ -397,7 +401,7 @@ static void GBASetActiveRegion(struct ARMCore* cpu, uint32_t address) {
LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \
} \
++wait; \
if (gba->video.shouldStall) { \
if (gba->video.shouldStall && (address & 0x0001FFFF) < ((GBARegisterDISPCNTGetMode(gba->memory.io[REG_DISPCNT >> 1]) >= 3) ? 0x00014000 : 0x00010000)) { \
wait += GBAMemoryStallVRAM(gba, wait, 1); \
}
@ -557,7 +561,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
} else {
LOAD_16(value, address & 0x0001FFFE, gba->video.vram);
}
if (gba->video.shouldStall) {
if (gba->video.shouldStall && (address & 0x0001FFFF) < ((GBARegisterDISPCNTGetMode(gba->memory.io[REG_DISPCNT >> 1]) >= 3) ? 0x00014000 : 0x00010000)) {
wait += GBAMemoryStallVRAM(gba, wait, 0);
}
break;
@ -777,7 +781,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
} \
} \
++wait; \
if (gba->video.shouldStall) { \
if (gba->video.shouldStall && (address & 0x0001FFFF) < ((GBARegisterDISPCNTGetMode(gba->memory.io[REG_DISPCNT >> 1]) >= 3) ? 0x00014000 : 0x00010000)) { \
wait += GBAMemoryStallVRAM(gba, wait, 1); \
}
@ -904,7 +908,7 @@ void GBAStore16(struct ARMCore* cpu, uint32_t address, int16_t value, int* cycle
gba->video.renderer->writeVRAM(gba->video.renderer, address & 0x0001FFFE);
}
}
if (gba->video.shouldStall) {
if (gba->video.shouldStall && (address & 0x0001FFFF) < ((GBARegisterDISPCNTGetMode(gba->memory.io[REG_DISPCNT >> 1]) >= 3) ? 0x00014000 : 0x00010000)) {
wait += GBAMemoryStallVRAM(gba, wait, 0);
}
break;

View File

@ -123,10 +123,8 @@ static const struct GBACartridgeOverride _overrides[] = {
{ "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6, false },
// Pokemon Mystery Dungeon
{ "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
{ "B24E", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
{ "B24P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
{ "B24U", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
// Pokemon FireRed
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },

View File

@ -233,6 +233,29 @@ static const char* const _interpolate =
" aff[2] = transform[start + 2].zw;\n"
" mat[3] = transform[start + 3].xy;\n"
" aff[3] = transform[start + 3].zw;\n"
"}\n"
"ivec2 affineInterpolate() {\n"
" ivec2 mat[4];\n"
" ivec2 offset[4];\n"
" vec2 incoord = texCoord;\n"
" if (mosaic.x > 1) {\n"
" incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n"
" }\n"
" if (mosaic.y > 1) {\n"
" incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n"
" }\n"
" loadAffine(int(incoord.y), mat, offset);\n"
" float y = fract(incoord.y);\n"
" float start = 2. / 3.;\n"
" if (int(incoord.y) - range.x < 4) {\n"
" y = incoord.y - float(range.x);\n"
" start -= 1.;\n"
" }\n"
" float lin = start + y / 3.;\n"
" vec2 mixedTransform = interpolate(mat, lin);\n"
" vec2 mixedOffset = interpolate(offset, lin);\n"
" return ivec2(mixedTransform * incoord.x + mixedOffset);\n"
"}\n";
static const char* const _renderMode2 =
@ -250,8 +273,7 @@ static const char* const _renderMode2 =
"OUT(0) out vec4 color;\n"
"int fetchTile(ivec2 coord);\n"
"vec2 interpolate(ivec2 arr[4], float x);\n"
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
"ivec2 affineInterpolate();\n"
"int renderTile(ivec2 coord) {\n"
" int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n"
@ -278,26 +300,7 @@ static const char* const _renderMode2 =
"}\n"
"void main() {\n"
" ivec2 mat[4];\n"
" ivec2 offset[4];\n"
" vec2 incoord = texCoord;\n"
" if (mosaic.x > 1) {\n"
" incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n"
" }\n"
" if (mosaic.y > 1) {\n"
" incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n"
" }\n"
" loadAffine(int(incoord.y), mat, offset);\n"
" float y = fract(incoord.y);\n"
" float start = 0.75;\n"
" if (int(incoord.y) - range.x < 4) {\n"
" y = incoord.y - float(range.x);\n"
" start = 0.;\n"
" }\n"
" float lin = start + y * 0.25;\n"
" vec2 mixedTransform = interpolate(mat, lin);\n"
" vec2 mixedOffset = interpolate(offset, lin);\n"
" int paletteEntry = fetchTile(ivec2(mixedTransform * incoord.x + mixedOffset));\n"
" int paletteEntry = fetchTile(affineInterpolate());\n"
" color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n"
"}";
@ -325,30 +328,10 @@ static const char* const _renderMode35 =
"uniform ivec2 mosaic;\n"
"OUT(0) out vec4 color;\n"
"vec2 interpolate(ivec2 arr[4], float x);\n"
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
"ivec2 affineInterpolate();\n"
"void main() {\n"
" ivec2 mat[4];\n"
" ivec2 offset[4];\n"
" vec2 incoord = texCoord;\n"
" if (mosaic.x > 1) {\n"
" incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n"
" }\n"
" if (mosaic.y > 1) {\n"
" incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n"
" }\n"
" loadAffine(int(incoord.y), mat, offset);\n"
" float y = fract(incoord.y);\n"
" float start = 0.75;\n"
" if (int(incoord.y) - range.x < 4) {\n"
" y = incoord.y - float(range.x);\n"
" start = 0.;\n"
" }\n"
" float lin = start + y * 0.25;\n"
" vec2 mixedTransform = interpolate(mat, lin);\n"
" vec2 mixedOffset = interpolate(offset, lin);\n"
" ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n"
" ivec2 coord = affineInterpolate();\n"
" if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
" discard;\n"
" }\n"
@ -386,30 +369,10 @@ static const char* const _renderMode4 =
"uniform ivec2 mosaic;\n"
"OUT(0) out vec4 color;\n"
"vec2 interpolate(ivec2 arr[4], float x);\n"
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
"ivec2 affineInterpolate();\n"
"void main() {\n"
" ivec2 mat[4];\n"
" ivec2 offset[4];\n"
" vec2 incoord = texCoord;\n"
" if (mosaic.x > 1) {\n"
" incoord.x = float(MOSAIC(incoord.x, mosaic.x));\n"
" }\n"
" if (mosaic.y > 1) {\n"
" incoord.y = float(MOSAIC(incoord.y, mosaic.y));\n"
" }\n"
" loadAffine(int(incoord.y), mat, offset);\n"
" float y = fract(incoord.y);\n"
" float start = 0.75;\n"
" if (int(incoord.y) - range.x < 4) {\n"
" y = incoord.y - float(range.x);\n"
" start = 0.;\n"
" }\n"
" float lin = start + y * 0.25;\n"
" vec2 mixedTransform = interpolate(mat, lin);\n"
" vec2 mixedOffset = interpolate(offset, lin);\n"
" ivec2 coord = ivec2(mixedTransform * incoord.x + mixedOffset);\n"
" ivec2 coord = affineInterpolate();\n"
" if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
" discard;\n"
" }\n"
@ -664,10 +627,12 @@ static const char* const _finalize =
" if (((topFlags.y & 13) == 5 || topFlags.w > 0) && (bottomFlags.y & 2) == 2) {\n"
" topPixel.rgb *= float(topFlags.z) / 16.;\n"
" topPixel.rgb += bottomPixel.rgb * float(windowFlags.y) / 16.;\n"
" } else if ((topFlags.y & 13) == 9) {\n"
" topPixel.rgb += (1. - topPixel.rgb) * float(windowFlags.z) / 16.;\n"
" } else if ((topFlags.y & 13) == 13) {\n"
" topPixel.rgb -= topPixel.rgb * float(windowFlags.z) / 16.;\n"
" } else if (topFlags.w == 0) { \n"
" if ((topFlags.y & 13) == 9) {\n"
" topPixel.rgb += (1. - topPixel.rgb) * float(windowFlags.z) / 16.;\n"
" } else if ((topFlags.y & 13) == 13) {\n"
" topPixel.rgb -= topPixel.rgb * float(windowFlags.z) / 16.;\n"
" }\n"
" }\n"
" color = topPixel;\n"
"}";

View File

@ -169,10 +169,10 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || objwinSlowPath) {
int target2 = renderer->target2Bd;
target2 |= renderer->bg[0].target2;
target2 |= renderer->bg[1].target2;
target2 |= renderer->bg[2].target2;
target2 |= renderer->bg[3].target2;
target2 |= renderer->bg[0].target2 && renderer->bg[0].enabled;
target2 |= renderer->bg[1].target2 && renderer->bg[1].enabled;
target2 |= renderer->bg[2].target2 && renderer->bg[2].enabled;
target2 |= renderer->bg[3].target2 && renderer->bg[3].enabled;
if (target2) {
renderer->forceTarget1 = true;
flags |= FLAG_REBLEND;

View File

@ -13,6 +13,10 @@
#include <mgba-util/memory.h>
#include <mgba-util/vfs.h>
#ifdef PSP2
#include <psp2/rtc.h>
#endif
#include <errno.h>
#include <fcntl.h>
@ -184,7 +188,7 @@ size_t GBASavedataSize(const struct GBASavedata* savedata) {
bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) {
if (savedata->data) {
if (!in && savedata->type != SAVEDATA_FORCE_NONE) {
if (!in || savedata->type == SAVEDATA_FORCE_NONE) {
return false;
}
ssize_t size = GBASavedataSize(savedata);
@ -598,14 +602,23 @@ void GBASavedataRTCWrite(struct GBASavedata* savedata) {
size_t size = GBASavedataSize(savedata);
savedata->vf->seek(savedata->vf, size & ~0xFF, SEEK_SET);
int bank = 0;
if ((savedata->vf->size(savedata->vf) & 0xFF) != sizeof(buffer)) {
// Writing past the end of the file can invalidate the file mapping
if (savedata->type == SAVEDATA_FLASH1M) {
bank = savedata->currentBank == &savedata->data[0x10000];
}
savedata->vf->unmap(savedata->vf, savedata->data, size);
savedata->data = NULL;
}
savedata->vf->write(savedata->vf, &buffer, sizeof(buffer));
if (!savedata->data) {
savedata->data = savedata->vf->map(savedata->vf, size, MAP_WRITE);
if (savedata->type == SAVEDATA_FLASH1M) {
savedata->currentBank = &savedata->data[bank << 16];
} else if (savedata->type == SAVEDATA_FLASH512) {
savedata->currentBank = savedata->data;
}
}
}
@ -637,6 +650,9 @@ void GBASavedataRTCRead(struct GBASavedata* savedata) {
}
LOAD_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch);
time_t rtcTime;
#ifndef PSP2
struct tm date;
date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100;
date.tm_mon = _unBCD(savedata->gpio->rtc.time[1]) - 1;
@ -645,8 +661,40 @@ void GBASavedataRTCRead(struct GBASavedata* savedata) {
date.tm_min = _unBCD(savedata->gpio->rtc.time[5]);
date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]);
date.tm_isdst = -1;
rtcTime = mktime(&date);
#else
struct SceDateTime date;
date.year = _unBCD(savedata->gpio->rtc.time[0]) + 2000;
date.month = _unBCD(savedata->gpio->rtc.time[1]);
date.day = _unBCD(savedata->gpio->rtc.time[2]);
date.hour = _unBCD(savedata->gpio->rtc.time[4]);
date.minute = _unBCD(savedata->gpio->rtc.time[5]);
date.second = _unBCD(savedata->gpio->rtc.time[6]);
date.microsecond = 0;
savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - mktime(&date);
struct SceRtcTick tick;
int res;
res = sceRtcConvertDateTimeToTick(&date, &tick);
if (res < 0) {
mLOG(GBA_SAVE, ERROR, "sceRtcConvertDateTimeToTick %lx", res);
}
res = sceRtcConvertLocalTimeToUtc(&tick, &tick);
if (res < 0) {
mLOG(GBA_SAVE, ERROR, "sceRtcConvertUtcToLocalTime %lx", res);
}
res = sceRtcConvertTickToDateTime(&tick, &date);
if (res < 0) {
mLOG(GBA_SAVE, ERROR, "sceRtcConvertTickToDateTime %lx", res);
}
res = sceRtcConvertDateTimeToTime_t(&date, &rtcTime);
if (res < 0) {
mLOG(GBA_SAVE, ERROR, "sceRtcConvertDateTimeToTime_t %lx", res);
}
#endif
savedata->gpio->rtc.offset = savedata->gpio->rtc.lastLatch - rtcTime;
mLOG(GBA_SAVE, ERROR, "Savegame time offset set to %li", savedata->gpio->rtc.offset);
}
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {

View File

@ -111,11 +111,27 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
if (node->id) {
node->d.p->rcnt |= 4;
node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt);
int try;
for (try = 0; try < 3; ++try) {
uint16_t masterSiocnt;
ATOMIC_LOAD(masterSiocnt, node->p->players[0]->d.p->siocnt);
if (ATOMIC_CMPXCHG(node->p->players[0]->d.p->siocnt, masterSiocnt, GBASIOMultiplayerClearSlave(masterSiocnt))) {
break;
}
}
} else {
node->d.p->rcnt &= ~4;
node->d.p->siocnt = GBASIOMultiplayerClearSlave(node->d.p->siocnt);
}
break;
case SIO_NORMAL_8:
case SIO_NORMAL_32:
ATOMIC_ADD(node->p->attachedNormal, 1);
if (ATOMIC_ADD(node->p->attachedNormal, 1) > node->id + 1 && node->id > 0) {
node->d.p->siocnt = GBASIONormalSetSi(node->d.p->siocnt, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
} else {
node->d.p->siocnt = GBASIONormalClearSi(node->d.p->siocnt);
}
node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
break;
default:
@ -447,7 +463,7 @@ static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
struct GBASIOLockstepNode* node = user;
mLockstepLock(&node->p->d);
int32_t cycles = cycles = node->nextEvent;
int32_t cycles = node->nextEvent;
node->nextEvent -= cyclesLate;
node->eventDiff += cyclesLate;
if (node->p->d.attached < 2) {
@ -495,11 +511,28 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
if (address == REG_SIOCNT) {
mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value);
int attached;
ATOMIC_LOAD(attached, node->p->attachedNormal);
value &= 0xFF8B;
if (!node->id) {
if (node->id > 0) {
value = GBASIONormalSetSi(value, GBASIONormalGetIdleSo(node->p->players[node->id - 1]->d.p->siocnt));
} else {
value = GBASIONormalClearSi(value);
}
if (value & 0x0080) {
enum mLockstepPhase transferActive;
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
if (node->id < 3 && attached > node->id + 1 && transferActive == TRANSFER_IDLE) {
int try;
for (try = 0; try < 3; ++try) {
GBASIONormal nextSiocnct;
ATOMIC_LOAD(nextSiocnct, node->p->players[node->id + 1]->d.p->siocnt);
if (ATOMIC_CMPXCHG(node->p->players[node->id + 1]->d.p->siocnt, nextSiocnct, GBASIONormalSetSi(nextSiocnct, GBASIONormalGetIdleSo(value)))) {
break;
}
}
}
if ((value & 0x0081) == 0x0081) {
if (!node->id) {
// Frequency
int32_t cycles;
@ -512,9 +545,6 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
cycles *= 4;
}
enum mLockstepPhase transferActive;
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
if (transferActive == TRANSFER_IDLE) {
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
@ -529,7 +559,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
value &= ~0x0080;
}
} else {
// TODO
}
}
} else if (address == REG_SIODATA32_LO) {

View File

@ -34,7 +34,7 @@ static void GBATimerUpdate(struct GBA* gba, int timerId, uint32_t cyclesLate) {
if (timerId < 3) {
struct GBATimer* nextTimer = &gba->timers[timerId + 1];
if (GBATimerFlagsIsCountUp(nextTimer->flags)) { // TODO: Does this increment while disabled?
if (GBATimerFlagsIsCountUp(nextTimer->flags) && GBATimerFlagsIsEnable(nextTimer->flags)) {
++gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1];
if (!gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1] && GBATimerFlagsIsEnable(nextTimer->flags)) {
GBATimerUpdate(gba, timerId + 1, cyclesLate);

View File

@ -617,6 +617,39 @@ void retro_run(void) {
core->desiredVideoDimensions(core, &width, &height);
videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256);
#ifdef M_CORE_GBA
if (core->platform(core) == mPLATFORM_GBA) {
blip_t *audioChannelLeft = core->getAudioChannel(core, 0);
blip_t *audioChannelRight = core->getAudioChannel(core, 1);
int samplesAvail = blip_samples_avail(audioChannelLeft);
if (samplesAvail > 0) {
/* Update 'running average' of number of
* samples per frame.
* Note that this is not a true running
* average, but just a leaky-integrator/
* exponential moving average, used because
* it is simple and fast (i.e. requires no
* window of samples). */
audioSamplesPerFrameAvg = (SAMPLES_PER_FRAME_MOVING_AVG_ALPHA * (float)samplesAvail) +
((1.0f - SAMPLES_PER_FRAME_MOVING_AVG_ALPHA) * audioSamplesPerFrameAvg);
size_t samplesToRead = (size_t)(audioSamplesPerFrameAvg);
/* Resize audio output buffer, if required */
if (audioSampleBufferSize < (samplesToRead * 2)) {
audioSampleBufferSize = (samplesToRead * 2);
audioSampleBuffer = realloc(audioSampleBuffer, audioSampleBufferSize * sizeof(int16_t));
}
int produced = blip_read_samples(audioChannelLeft, audioSampleBuffer, samplesToRead, true);
blip_read_samples(audioChannelRight, audioSampleBuffer + 1, samplesToRead, true);
if (produced > 0) {
if (audioLowPassEnabled) {
_audioLowPassFilter(audioSampleBuffer, produced);
}
audioCallback(audioSampleBuffer, (size_t)produced);
}
}
}
#endif
if (rumbleCallback) {
if (rumbleUp) {
rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleUp * 0xFFFF / (rumbleUp + rumbleDown));

View File

@ -1010,8 +1010,11 @@ bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) {
}
}
u = mGLES2UniformListSize(&uniformVector);
struct mGLES2Uniform* uniformBlock = calloc(u, sizeof(*uniformBlock));
memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u);
struct mGLES2Uniform* uniformBlock;
if (u) {
uniformBlock = calloc(u, sizeof(*uniformBlock));
memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u);
}
mGLES2UniformListDeinit(&uniformVector);
mGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, scaling, uniformBlock, u);
@ -1048,6 +1051,7 @@ bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) {
for (n = 0; n < inShaders; ++n) {
mGLES2ShaderDeinit(&shaderBlock[n]);
}
free(shaderBlock);
}
}
}

View File

@ -28,6 +28,7 @@ set(OS_LIB -lvita2d -l${M_LIBRARY}
-lScePgf_stub
-lScePhotoExport_stub
-lScePower_stub
-lSceRtc_stub
-lSceSysmodule_stub
-lSceTouch_stub)
set(OS_LIB ${OS_LIB} PARENT_SCOPE)

View File

@ -229,7 +229,7 @@ int main() {
},
{ .id = 0 }
},
.nConfigExtra = 2,
.nConfigExtra = 3,
.setup = mPSP2Setup,
.teardown = mPSP2Teardown,
.gameLoaded = mPSP2LoadROM,

View File

@ -74,7 +74,7 @@ AboutScreen::AboutScreen(QWidget* parent)
{
QString copyright = m_ui.copyright->text();
copyright.replace("{year}", QLatin1String("2022"));
copyright.replace("{year}", QLatin1String("2023"));
m_ui.copyright->setText(copyright);
}
}

View File

@ -7,6 +7,7 @@
#include <QDir>
#include <QFileInfo>
#include <QRegularExpression>
#include "ApplicationUpdatePrompt.h"
#include "ConfigController.h"
@ -71,9 +72,10 @@ QStringList ApplicationUpdater::listChannels() {
}
QString ApplicationUpdater::currentChannel() {
QLatin1String version(projectVersion);
QLatin1String branch(gitBranch);
if (branch == QLatin1String("heads/") + version) {
QString version(projectVersion);
QString branch(gitBranch);
QRegularExpression stable("^(?:(?:refs/)?(?:tags|heads)/)?[0-9]+\\.[0-9]+\\.[0-9]+$");
if (branch.contains(stable) || (branch == "(unknown)" && version.contains(stable))) {
return QLatin1String("stable");
} else {
return QLatin1String("dev");
@ -137,7 +139,15 @@ QUrl ApplicationUpdater::parseManifest(const QByteArray& manifest) {
QString ApplicationUpdater::destination() const {
QFileInfo path(updateInfo().url.path());
QDir dir(ConfigController::configDir());
return dir.filePath(QLatin1String("update.") + path.completeSuffix());
// QFileInfo::completeSuffix will eat all .'s in the filename...including
// ones in the version string, turning mGBA-1.0.0-win32.7z into
// 0.0-win32.7z instead of the intended .7z
// As a result, so we have to split out the complete suffix manually.
QString suffix(path.suffix());
if (path.completeBaseName().endsWith(".tar")) {
suffix = "tar." + suffix;
}
return dir.filePath(QLatin1String("update.") + suffix);
}
const char* ApplicationUpdater::platform() {
@ -166,7 +176,8 @@ const char* ApplicationUpdater::platform() {
}
ApplicationUpdater::UpdateInfo::UpdateInfo(const QString& prefix, const mUpdate* update)
: size(update->size)
: rev(-1)
, size(update->size)
, url(prefix + update->path)
{
if (update->rev > 0) {

View File

@ -44,20 +44,22 @@ if(APPLE)
endif()
if(Qt6Widgets_VERSION)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.14")
set(MIN_VER 10.14)
elseif(Qt5Widgets_VERSION MATCHES "^5.15")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.13")
set(MIN_VER 10.13)
elseif(Qt5Widgets_VERSION MATCHES "^5.1[234]")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.12")
set(MIN_VER 10.12)
elseif(Qt5Widgets_VERSION MATCHES "^5.11")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.11")
set(MIN_VER 10.11)
elseif(Qt5Widgets_VERSION MATCHES "^5.(9|10)")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.10")
set(MIN_VER 10.10)
elseif(Qt5Widgets_VERSION MATCHES "^5.8")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.9")
set(MIN_VER 10.9)
else()
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8")
set(MIN_VER 10.8)
endif()
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=${MIN_VER}")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=${MIN_VER}")
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
endif()
@ -118,6 +120,7 @@ set(SOURCE_FILES
MessagePainter.cpp
MultiplayerController.cpp
ObjView.cpp
OpenGLBug.cpp
OverrideView.cpp
PaletteView.cpp
PlacementControl.cpp
@ -300,7 +303,7 @@ if(WIN32)
endif()
if(NOT DEFINED DATADIR)
if(APPLE)
set(DATADIR ${APPDIR}/${PROJECT_NAME}.app/Contents/Resources)
set(DATADIR ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.app/Contents/Resources)
elseif(WIN32 AND NOT WIN32_UNIX_PATHS)
set(DATADIR ".")
else()
@ -413,15 +416,12 @@ if(QT_STATIC)
list(APPEND QT_LIBRARIES "-framework AVFoundation" "-framework CoreMedia" "-framework SystemConfiguration" "-framework Security")
set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE}")
elseif(UNIX)
list(APPEND QT_LIBRARIES ${QT}::FontDatabaseSupport ${QT}::XcbQpa)
list(APPEND QT_LIBRARIES ${QT}::FontDatabaseSupport ${QT}::XcbQpa ${QT}::QWaylandIntegrationPlugin)
endif()
endif()
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE)
install(TARGETS ${BINARY_NAME}-qt
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt
BUNDLE DESTINATION ${APPDIR} COMPONENT ${BINARY_NAME}-qt)
if(UNIX AND NOT APPLE)
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop DESTINATION share/applications RENAME io.mgba.${PROJECT_NAME}.desktop COMPONENT ${BINARY_NAME}-qt)
endif()
@ -447,6 +447,10 @@ if(APPLE)
file(GLOB_RECURSE PLUGINS \"${BUNDLE_PATH}/Contents/PlugIns/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")
fixup_bundle(\"${BUNDLE_PATH}\" \"${PLUGINS}\" \"\")
" COMPONENT ${BINARY_NAME}-qt)
if(CODESIGN_IDENTITY)
install(CODE "execute_process(COMMAND codesign -s \"${CODESIGN_IDENTITY}\" -vf -o runtime --timestamp --entitlements \"${CMAKE_SOURCE_DIR}/res/entitlements.plist\" \"${BUNDLE_PATH}\")"
COMPONENT ${BINARY_NAME}-qt)
endif()
else()
set(DEPLOY_OPTIONS -p platforms/libqcocoa.dylib,audio/libqtaudio_coreaudio.dylib,mediaservice/libqavfcamera.dylib)
if(NOT CMAKE_INSTALL_NAME_TOOL EQUAL "install_name_tool")
@ -458,6 +462,9 @@ if(APPLE)
if(DEFINED CROSS_ROOT)
set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -R "${CROSS_ROOT}")
endif()
if($ENV{CODESIGN_IDENTITY})
set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -s "$ENV{CODESIGN_IDENTITY}" -E "${CMAKE_SOURCE_DIR}/res/entitlements.xml")
endif()
install(CODE "execute_process(COMMAND \"${CMAKE_SOURCE_DIR}/tools/deploy-mac.py\" -v ${DEPLOY_OPTIONS} \"\$ENV{DESTDIR}\${CMAKE_INSTALL_PREFIX}/${APPDIR}/${PROJECT_NAME}.app\")")
endif()
elseif(WIN32)
@ -485,3 +492,7 @@ if(DISTBUILD AND NOT APPLE)
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:${BINARY_NAME}-qt>")
endif()
endif()
install(TARGETS ${BINARY_NAME}-qt
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt
BUNDLE DESTINATION ${APPDIR} COMPONENT ${BINARY_NAME}-qt)

View File

@ -289,13 +289,6 @@ void CoreController::loadConfig(ConfigController* config) {
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
m_preload = config->getOption("preload").toInt();
int playerId = m_multiplayer->playerId(this) + 1;
QVariant savePlayerId = config->getOption("savePlayerId");
if (m_multiplayer->attached() < 2 && savePlayerId.canConvert<int>()) {
playerId = savePlayerId.toInt();
}
mCoreConfigSetOverrideIntValue(&m_threadContext.core->config, "savePlayerId", playerId);
QSize sizeBefore = screenDimensions();
m_activeBuffer.resize(256 * 224 * sizeof(color_t));
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), sizeBefore.width());
@ -518,9 +511,6 @@ void CoreController::setRewinding(bool rewind) {
}
void CoreController::rewind(int states) {
if (!states) {
return;
}
if (!m_threadContext.core->opts.rewindEnable) {
emit statusPosted(tr("Rewinding not currently enabled"));
}
@ -785,10 +775,14 @@ void CoreController::loadSave(const QString& path, bool temporary) {
return;
}
bool ok;
if (temporary) {
m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
ok = m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
} else {
m_threadContext.core->loadSave(m_threadContext.core, vf);
ok = m_threadContext.core->loadSave(m_threadContext.core, vf);
}
if (!ok) {
vf->close(vf);
}
});
if (hasStarted()) {
@ -798,10 +792,14 @@ void CoreController::loadSave(const QString& path, bool temporary) {
void CoreController::loadSave(VFile* vf, bool temporary) {
m_resetActions.append([this, vf, temporary]() {
bool ok;
if (temporary) {
m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
ok = m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
} else {
m_threadContext.core->loadSave(m_threadContext.core, vf);
ok = m_threadContext.core->loadSave(m_threadContext.core, vf);
}
if (!ok) {
vf->close(vf);
}
});
if (hasStarted()) {
@ -914,7 +912,10 @@ void CoreController::scanCard(const QString& path) {
if (!file.open(QIODevice::ReadOnly)) {
return;
}
m_eReaderData = file.read(2912);
QByteArray eReaderData = file.read(2912);
if (eReaderData.isEmpty()) {
return;
}
file.seek(0);
QStringList lines;
@ -936,6 +937,7 @@ void CoreController::scanCard(const QString& path) {
}
}
scanCards(lines);
m_eReaderData = eReaderData;
} else if (image.size() == QSize(989, 44) || image.size() == QSize(639, 44)) {
const uchar* bits = image.constBits();
size_t size;
@ -1215,7 +1217,12 @@ void CoreController::updatePlayerSave() {
int savePlayerId = 0;
mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId);
if (savePlayerId == 0 || m_multiplayer->attached() > 1) {
savePlayerId = m_multiplayer->playerId(this) + 1;
if (savePlayerId == m_multiplayer->playerId(this) + 1) {
// Player 1 is using our save, so let's use theirs, at least for now.
savePlayerId = 1;
} else {
savePlayerId = m_multiplayer->playerId(this) + 1;
}
}
QString saveSuffix;

View File

@ -16,6 +16,7 @@
#endif
#include <mgba/core/core.h>
#include <mgba-util/string.h>
#include <mgba-util/vfs.h>
using namespace QGBA;
@ -163,7 +164,7 @@ CoreController* CoreManager::loadBIOS(int platform, const QString& path) {
mCoreConfigSetOverrideIntValue(&core->config, "skipBios", 0);
QByteArray bytes(info.baseName().toUtf8());
strncpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName));
strlcpy(core->dirs.baseName, bytes.constData(), sizeof(core->dirs.baseName));
bytes = info.dir().canonicalPath().toUtf8();
mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData()));

View File

@ -111,10 +111,11 @@ void QGBA::Display::configure(ConfigController* config) {
filter(opts->resampleVideo);
config->updateOption("showOSD");
config->updateOption("showFrameCounter");
config->updateOption("videoSync");
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (opts->shader) {
if (opts->shader && supportsShaders()) {
struct VDir* shader = VDirOpen(opts->shader);
if (shader && supportsShaders()) {
if (shader) {
setShaders(shader);
shader->close(shader);
}

View File

@ -72,6 +72,7 @@ public slots:
virtual void showOSDMessages(bool enable);
virtual void showFrameCounter(bool enable);
virtual void filter(bool filter);
virtual void swapInterval(int interval) = 0;
virtual void framePosted() = 0;
virtual void setShaders(struct VDir*) = 0;
virtual void clearShaders() = 0;

View File

@ -38,12 +38,29 @@ using QOpenGLFunctions_Baseline = QOpenGLFunctions_3_2_Core;
#endif
#endif
#ifdef _WIN32
#include <windows.h>
#elif defined(Q_OS_MAC)
#include <OpenGL/OpenGL.h>
#endif
#ifdef USE_GLX
#define GLX_GLXEXT_PROTOTYPES
typedef struct _XDisplay Display;
#include <GL/glx.h>
#include <GL/glxext.h>
#endif
#ifdef USE_EGL
#include <EGL/egl.h>
#endif
#ifdef _WIN32
#define OVERHEAD_NSEC 1000000
#else
#define OVERHEAD_NSEC 300000
#endif
#include "OpenGLBug.h"
using namespace QGBA;
QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
@ -63,6 +80,10 @@ mGLWidget::mGLWidget(QWidget* parent)
connect(&m_refresh, &QTimer::timeout, this, static_cast<void (QWidget::*)()>(&QWidget::update));
}
mGLWidget::~mGLWidget() {
// This is needed for unique_ptr<QOpenGLPaintDevice> to work
}
void mGLWidget::initializeGL() {
m_vao = std::make_unique<QOpenGLVertexArrayObject>();
m_vao->create();
@ -92,6 +113,8 @@ void mGLWidget::initializeGL() {
m_vaoDone = false;
m_tex = 0;
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
}
bool mGLWidget::finalizeVAO() {
@ -143,6 +166,23 @@ void mGLWidget::paintGL() {
} else {
m_refresh.start(17);
}
if (m_showOSD && m_messagePainter) {
qreal r = window()->devicePixelRatio();
m_paintDev->setDevicePixelRatio(r);
m_paintDev->setSize(size() * r);
QPainter painter(m_paintDev.get());
m_messagePainter->paint(&painter);
painter.end();
}
}
void mGLWidget::setMessagePainter(MessagePainter* messagePainter) {
m_messagePainter = messagePainter;
}
void mGLWidget::setShowOSD(bool showOSD) {
m_showOSD = showOSD;
}
DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
@ -163,6 +203,7 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
m_gl = new mGLWidget;
m_gl->setAttribute(Qt::WA_NativeWindow);
m_gl->setFormat(format);
m_gl->setMessagePainter(messagePainter());
QBoxLayout* layout = new QVBoxLayout;
layout->addWidget(m_gl);
layout->setContentsMargins(0, 0, 0, 0);
@ -226,7 +267,7 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
messagePainter()->resize(size(), devicePixelRatio());
#endif
CoreController::Interrupter interrupter(controller);
CoreController::Interrupter interrupter(m_context);
QMetaObject::invokeMethod(m_painter.get(), "start");
if (!m_gl) {
if (shouldDisableUpdates()) {
@ -348,6 +389,9 @@ void DisplayGL::interframeBlending(bool enable) {
void DisplayGL::showOSDMessages(bool enable) {
Display::showOSDMessages(enable);
if (m_gl) {
m_gl->setShowOSD(enable);
}
QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
}
@ -361,6 +405,10 @@ void DisplayGL::filter(bool filter) {
QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(bool, filter));
}
void DisplayGL::swapInterval(int interval) {
QMetaObject::invokeMethod(m_painter.get(), "swapInterval", Q_ARG(int, interval));
}
void DisplayGL::framePosted() {
m_painter->enqueue(m_context->drawContext());
QMetaObject::invokeMethod(m_painter.get(), "draw");
@ -477,7 +525,9 @@ void PainterGL::create() {
mGLES2Context* gl2Backend;
#endif
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
if (!m_widget) {
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
}
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (m_supportsShaders) {
@ -503,10 +553,10 @@ void PainterGL::create() {
painter->makeCurrent();
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
if (painter->m_widget && painter->supportsShaders()) {
QOpenGLFunctions_Baseline* fn = painter->m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
QOpenGLFunctions* fn = painter->m_gl->functions();
fn->glFinish();
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]);
painter->m_finalTexIdx ^= 1;
gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx];
@ -540,6 +590,9 @@ void PainterGL::create() {
m_backend->filter = false;
m_backend->lockAspectRatio = false;
m_backend->interframeBlending = false;
m_gl->doneCurrent();
emit created();
}
void PainterGL::destroy() {
@ -591,8 +644,10 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
void PainterGL::resize(const QSize& size) {
qreal r = m_window->devicePixelRatio();
m_size = size;
m_paintDev->setSize(m_size * r);
m_paintDev->setDevicePixelRatio(r);
if (m_paintDev) {
m_paintDev->setSize(m_size * r);
m_paintDev->setDevicePixelRatio(r);
}
if (m_started && !m_active) {
forceDraw();
}
@ -627,8 +682,46 @@ void PainterGL::filter(bool filter) {
}
}
void PainterGL::swapInterval(int interval) {
if (!m_started) {
return;
}
m_swapInterval = interval;
#ifdef Q_OS_WIN
wglSwapIntervalEXT(interval);
#elif defined(Q_OS_MAC)
CGLSetParameter(CGLGetCurrentContext(), kCGLCPSwapInterval, &interval);
#else
#ifdef USE_GLX
if (QGuiApplication::platformName() == "xcb") {
::Display* display = glXGetCurrentDisplay();
GLXDrawable drawable = glXGetCurrentDrawable();
glXSwapIntervalEXT(display, drawable, interval);
}
#endif
#ifdef USE_EGL
if (QGuiApplication::platformName().contains("egl")) {
EGLDisplay display = eglGetCurrentDisplay();
eglSwapInterval(display, interval);
}
#endif
#endif
}
#ifndef GL_DEBUG_OUTPUT_SYNCHRONOUS
#define GL_DEBUG_OUTPUT_SYNCHRONOUS 0x8242
#endif
void PainterGL::start() {
makeCurrent();
#if defined(BUILD_GLES3) && !defined(Q_OS_MAC)
if (glContextHasBug(OpenGLBug::GLTHREAD_BLOCKS_SWAP)) {
// Suggested on Discord as a way to strongly hint that glthread should be disabled
// See https://gitlab.freedesktop.org/mesa/mesa/-/issues/8035
QOpenGLFunctions* fn = m_gl->functions();
fn->glEnable(GL_DEBUG_OUTPUT_SYNCHRONOUS);
}
#endif
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (m_supportsShaders && m_shader.passes) {
@ -640,6 +733,7 @@ void PainterGL::start() {
m_buffer = nullptr;
m_active = true;
m_started = true;
swapInterval(1);
emit started();
}
@ -670,12 +764,16 @@ void PainterGL::draw() {
}
return;
}
int wantSwap = sync->audioWait || sync->videoFrameWait;
if (m_swapInterval != wantSwap) {
swapInterval(wantSwap);
}
dequeue();
bool forceRedraw = !m_videoProxy;
bool forceRedraw = true;
if (!m_delayTimer.isValid()) {
m_delayTimer.start();
} else {
if (sync->audioWait || sync->videoFrameWait) {
if (wantSwap) {
while (m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC < 1000000000 / sync->fpsTarget) {
QThread::usleep(500);
}
@ -725,13 +823,13 @@ void PainterGL::doStop() {
m_videoProxy->processData();
}
}
m_backend->clear(m_backend);
m_backend->swap(m_backend);
if (m_videoProxy) {
m_videoProxy->reset();
m_videoProxy->moveToThread(m_window->thread());
m_videoProxy.reset();
}
m_backend->clear(m_backend);
m_backend->swap(m_backend);
}
void PainterGL::pause() {
@ -751,7 +849,7 @@ void PainterGL::performDraw() {
m_backend->postFrame(m_backend, m_buffer);
}
m_backend->drawFrame(m_backend);
if (m_showOSD && m_messagePainter) {
if (m_showOSD && m_messagePainter && m_paintDev && !glContextHasBug(OpenGLBug::IG4ICD_CRASH)) {
m_painter.begin(m_paintDev.get());
m_messagePainter->paint(&m_painter);
m_painter.end();
@ -777,20 +875,18 @@ void PainterGL::enqueue(const uint32_t* backing) {
void PainterGL::dequeue() {
QMutexLocker locker(&m_mutex);
if (m_queue.isEmpty()) {
return;
if (!m_queue.isEmpty()) {
uint32_t* buffer = m_queue.dequeue();
if (m_buffer) {
m_free.append(m_buffer);
}
m_buffer = buffer;
}
uint32_t* buffer = m_queue.dequeue();
if (m_buffer) {
m_free.append(m_buffer);
m_buffer = nullptr;
}
m_buffer = buffer;
}
void PainterGL::dequeueAll(bool keep) {
QMutexLocker locker(&m_mutex);
uint32_t* buffer = 0;
uint32_t* buffer = nullptr;
while (!m_queue.isEmpty()) {
buffer = m_queue.dequeue();
if (keep) {
@ -821,12 +917,21 @@ void PainterGL::setShaders(struct VDir* dir) {
return;
}
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (!m_started) {
makeCurrent();
}
if (m_shader.passes) {
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
mGLES2ShaderFree(&m_shader);
}
mGLES2ShaderLoad(&m_shader, dir);
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
if (mGLES2ShaderLoad(&m_shader, dir)) {
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
}
if (!m_started) {
m_gl->doneCurrent();
}
#endif
}
@ -835,10 +940,18 @@ void PainterGL::clearShaders() {
return;
}
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
if (!m_started) {
makeCurrent();
}
if (m_shader.passes) {
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
mGLES2ShaderFree(&m_shader);
}
if (!m_started) {
m_gl->doneCurrent();
}
#endif
}
@ -861,4 +974,12 @@ int PainterGL::glTex() {
#endif
}
QOpenGLContext* PainterGL::shareContext() {
if (m_widget) {
return m_widget->context();
} else {
return m_gl.get();
}
}
#endif

View File

@ -51,9 +51,12 @@ Q_OBJECT
public:
mGLWidget(QWidget* parent = nullptr);
~mGLWidget();
void setTex(GLuint tex) { m_tex = tex; }
void setVBO(GLuint vbo) { m_vbo = vbo; }
void setMessagePainter(MessagePainter*);
void setShowOSD(bool showOSD);
bool finalizeVAO();
void reset();
@ -72,6 +75,9 @@ private:
QTimer m_refresh;
int m_refreshResidue = 0;
std::unique_ptr<QOpenGLPaintDevice> m_paintDev;
MessagePainter* m_messagePainter = nullptr;
bool m_showOSD = false;
};
class PainterGL;
@ -102,6 +108,7 @@ public slots:
void showOSDMessages(bool enable) override;
void showFrameCounter(bool enable) override;
void filter(bool filter) override;
void swapInterval(int interval) override;
void framePosted() override;
void setShaders(struct VDir*) override;
void clearShaders() override;
@ -143,6 +150,8 @@ public:
bool supportsShaders() const { return m_supportsShaders; }
int glTex();
QOpenGLContext* shareContext();
void setVideoProxy(std::shared_ptr<VideoProxy>);
void interrupt();
@ -162,6 +171,7 @@ public slots:
void showOSD(bool enable);
void showFrameCounter(bool enable);
void filter(bool filter);
void swapInterval(int interval);
void resizeContext();
void setShaders(struct VDir*);
@ -169,7 +179,9 @@ public slots:
VideoShader* shaders();
signals:
void created();
void started();
void texSwapped();
private slots:
void doStop();
@ -184,6 +196,7 @@ private:
QList<uint32_t*> m_free;
QQueue<uint32_t*> m_queue;
uint32_t* m_buffer = nullptr;
QPainter m_painter;
QMutex m_mutex;
QWindow* m_window;
@ -209,6 +222,7 @@ private:
MessagePainter* m_messagePainter = nullptr;
QElapsedTimer m_delayTimer;
std::shared_ptr<VideoProxy> m_videoProxy;
int m_swapInterval = -1;
};
}

View File

@ -31,6 +31,7 @@ public slots:
void lockAspectRatio(bool lock) override;
void lockIntegerScaling(bool lock) override;
void interframeBlending(bool enable) override;
void swapInterval(int) override {};
void filter(bool filter) override;
void framePosted() override;
void setShaders(struct VDir*) override {}

View File

@ -39,6 +39,7 @@ static const QList<GBMemoryBankControllerType> s_mbcList{
GB_UNL_BBD,
GB_UNL_HITEK,
GB_UNL_SACHEN_MMC1,
GB_UNL_SACHEN_MMC2,
};
static QMap<GBModel, QString> s_gbModelNames;

View File

@ -8,6 +8,7 @@
#include "GamepadAxisEvent.h"
#include "GamepadButtonEvent.h"
#include "ShortcutController.h"
#include "utils.h"
#include <QCoreApplication>
#include <QFontMetrics>
@ -33,35 +34,7 @@ void KeyEditor::setValue(int key) {
if (key < 0) {
setText(tr("---"));
} else {
QKeySequence seq(key);
switch (key) {
#ifndef Q_OS_MAC
case Qt::Key_Shift:
setText(QCoreApplication::translate("QShortcut", "Shift"));
break;
case Qt::Key_Control:
setText(QCoreApplication::translate("QShortcut", "Control"));
break;
case Qt::Key_Alt:
setText(QCoreApplication::translate("QShortcut", "Alt"));
break;
case Qt::Key_Meta:
setText(QCoreApplication::translate("QShortcut", "Meta"));
break;
#endif
case Qt::Key_Super_L:
setText(tr("Super (L)"));
break;
case Qt::Key_Super_R:
setText(tr("Super (R)"));
break;
case Qt::Key_Menu:
setText(tr("Menu"));
break;
default:
setText(QKeySequence(key).toString(QKeySequence::NativeText));
break;
}
setText(keyName(key));
}
}
emit valueChanged(key);

View File

@ -79,8 +79,8 @@ public slots:
private:
mLogFilter m_filter;
bool m_logToFile;
bool m_logToStdout;
bool m_logToFile = false;
bool m_logToStdout = false;
std::unique_ptr<QFile> m_logFile;
std::unique_ptr<QTextStream> m_logStream;

View File

@ -42,7 +42,7 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
m_boundary = 2048;
m_ui.tile->setMaxTile(3096);
m_ui.tile->setMaxTile(3072);
m_addressBase = BASE_VRAM;
m_addressWidth = 8;
m_ui.bgInfo->addCustomProperty("priority", tr("Priority"));
@ -119,6 +119,9 @@ void MapView::selectMap(int map) {
}
m_map = map;
m_mapStatus.fill({});
// Different maps can have different max palette counts; set it to
// 0 immediately to avoid tile lookups with state palette IDs break
m_ui.tile->setPalette(0);
updateTiles(true);
}
@ -184,11 +187,18 @@ void MapView::updateTilesGBA(bool) {
frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]);
}
}
m_boundary = 1024;
m_ui.tile->setMaxTile(1536);
priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]);
if (mode == 0 || (mode == 1 && m_map != 2)) {
offset = QString("%1, %2")
.arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)])
.arg(io[(REG_BG0VOFS >> 1) + (m_map << 1)]);
if (!GBARegisterBGCNTIs256Color(io[(REG_BG0CNT >> 1) + m_map])) {
m_boundary = 2048;
m_ui.tile->setMaxTile(3072);
}
} else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) {
int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)];
refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16;

View File

@ -14,24 +14,46 @@
#include <mgba/internal/gb/gb.h>
#endif
#include <algorithm>
using namespace QGBA;
#ifdef M_CORE_GB
MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* node)
MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* gbNode)
: controller(coreController)
, gbNode(node)
{
node.gb = gbNode;
}
#endif
#ifdef M_CORE_GBA
MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* node)
MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* gbaNode)
: controller(coreController)
, gbaNode(node)
{
node.gba = gbaNode;
}
#endif
int MultiplayerController::Player::id() const {
switch (controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
return node.gba->id;
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB:
return node.gb->id;
#endif
case mPLATFORM_NONE:
break;
}
return -1;
}
bool MultiplayerController::Player::operator<(const MultiplayerController::Player& other) const {
return id() < other.id();
}
MultiplayerController::MultiplayerController() {
mLockstepInit(&m_lockstep);
m_lockstep.context = this;
@ -65,6 +87,7 @@ MultiplayerController::MultiplayerController() {
player->awake = 0;
slept = true;
}
player->controller->setSync(true);
return slept;
};
m_lockstep.addCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
@ -72,44 +95,55 @@ MultiplayerController::MultiplayerController() {
abort();
}
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
if (!id) {
for (int i = 1; i < controller->m_players.count(); ++i) {
Player* player = &controller->m_players[i];
Player* player = controller->player(id);
switch (player->controller->platform()) {
#ifdef M_CORE_GBA
if (player->controller->platform() == mPLATFORM_GBA && player->gbaNode->d.p->mode != controller->m_players[0].gbaNode->d.p->mode) {
player->controller->setSync(true);
continue;
}
#endif
player->controller->setSync(false);
player->cyclesPosted += cycles;
if (player->awake < 1) {
switch (player->controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
player->gbaNode->nextEvent += player->cyclesPosted;
break;
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB:
player->gbNode->nextEvent += player->cyclesPosted;
break;
#endif
default:
break;
case mPLATFORM_GBA:
if (!id) {
for (int i = 1; i < controller->m_players.count(); ++i) {
player = controller->player(i);
if (player->node.gba->d.p->mode > SIO_MULTI) {
player->controller->setSync(true);
continue;
}
player->controller->setSync(false);
player->cyclesPosted += cycles;
if (player->awake < 1) {
player->node.gba->nextEvent += player->cyclesPosted;
}
mCoreThreadStopWaiting(player->controller->thread());
player->awake = 1;
}
} else {
player->controller->setSync(true);
player->cyclesPosted += cycles;
}
} else {
controller->m_players[id].controller->setSync(true);
controller->m_players[id].cyclesPosted += cycles;
break;
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB:
if (!id) {
player = controller->player(1);
player->controller->setSync(false);
player->cyclesPosted += cycles;
if (player->awake < 1) {
player->node.gb->nextEvent += player->cyclesPosted;
}
mCoreThreadStopWaiting(player->controller->thread());
player->awake = 1;
} else {
player->controller->setSync(true);
player->cyclesPosted += cycles;
}
break;
#endif
default:
break;
}
};
m_lockstep.useCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
Player* player = &controller->m_players[id];
Player* player = controller->player(id);
player->cyclesPosted -= cycles;
if (player->cyclesPosted <= 0) {
mCoreThreadWaitFromThread(player->controller->thread());
@ -118,21 +152,21 @@ MultiplayerController::MultiplayerController() {
cycles = player->cyclesPosted;
return cycles;
};
m_lockstep.unusedCycles= [](mLockstep* lockstep, int id) {
m_lockstep.unusedCycles = [](mLockstep* lockstep, int id) {
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
Player* player = &controller->m_players[id];
Player* player = controller->player(id);
auto cycles = player->cyclesPosted;
return cycles;
};
m_lockstep.unload = [](mLockstep* lockstep, int id) {
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
if (id) {
Player* player = &controller->m_players[id];
Player* player = controller->player(id);
player->controller->setSync(true);
player->cyclesPosted = 0;
// release master GBA if it is waiting for this GBA
player = &controller->m_players[0];
player = controller->player(0);
player->waitMask &= ~(1 << id);
if (!player->waitMask && player->awake < 1) {
mCoreThreadStopWaiting(player->controller->thread());
@ -140,7 +174,7 @@ MultiplayerController::MultiplayerController() {
}
} else {
for (int i = 1; i < controller->m_players.count(); ++i) {
Player* player = &controller->m_players[i];
Player* player = controller->player(i);
player->controller->setSync(true);
switch (player->controller->platform()) {
#ifdef M_CORE_GBA
@ -160,12 +194,12 @@ MultiplayerController::MultiplayerController() {
switch (player->controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
player->gbaNode->nextEvent += player->cyclesPosted;
player->node.gba->nextEvent += player->cyclesPosted;
break;
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB:
player->gbNode->nextEvent += player->cyclesPosted;
player->node.gb->nextEvent += player->cyclesPosted;
break;
#endif
default:
@ -184,10 +218,6 @@ MultiplayerController::~MultiplayerController() {
}
bool MultiplayerController::attachGame(CoreController* controller) {
if (m_lockstep.attached == MAX_GBAS) {
return false;
}
if (m_lockstep.attached == 0) {
switch (controller->platform()) {
#ifdef M_CORE_GBA
@ -203,6 +233,9 @@ bool MultiplayerController::attachGame(CoreController* controller) {
default:
return false;
}
m_platform = controller->platform();
} else if (controller->platform() != m_platform) {
return false;
}
mCoreThread* thread = controller->thread();
@ -213,6 +246,10 @@ bool MultiplayerController::attachGame(CoreController* controller) {
switch (controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA: {
if (m_lockstep.attached >= MAX_GBAS) {
return false;
}
GBA* gba = static_cast<GBA*>(thread->core->board);
GBASIOLockstepNode* node = new GBASIOLockstepNode;
@ -229,6 +266,10 @@ bool MultiplayerController::attachGame(CoreController* controller) {
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB: {
if (m_lockstep.attached >= 2) {
return false;
}
GB* gb = static_cast<GB*>(thread->core->board);
GBSIOLockstepNode* node = new GBSIOLockstepNode;
@ -315,3 +356,29 @@ int MultiplayerController::attached() {
num = m_lockstep.attached;
return num;
}
MultiplayerController::Player* MultiplayerController::player(int id) {
Player* player = &m_players[id];
switch (player->controller->platform()) {
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
if (player->node.gba->id != id) {
std::sort(m_players.begin(), m_players.end());
player = &m_players[id];
}
break;
#endif
#ifdef M_CORE_GB
case mPLATFORM_GB:
if (player->node.gb->id != id) {
std::swap(m_players[0], m_players[1]);
player = &m_players[id];
}
break;
#endif
case mPLATFORM_NONE:
break;
}
return player;
}

View File

@ -5,10 +5,11 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#pragma once
#include <QMutex>
#include <QList>
#include <QMutex>
#include <QObject>
#include <mgba/core/core.h>
#include <mgba/core/lockstep.h>
#ifdef M_CORE_GBA
#include <mgba/internal/gba/sio/lockstep.h>
@ -44,6 +45,10 @@ signals:
void gameDetached();
private:
union Node {
GBSIOLockstepNode* gb;
GBASIOLockstepNode* gba;
};
struct Player {
#ifdef M_CORE_GB
Player(CoreController* controller, GBSIOLockstepNode* node);
@ -52,13 +57,18 @@ private:
Player(CoreController* controller, GBASIOLockstepNode* node);
#endif
int id() const;
bool operator<(const Player&) const;
CoreController* controller;
GBSIOLockstepNode* gbNode = nullptr;
GBASIOLockstepNode* gbaNode = nullptr;
Node node = {nullptr};
int awake = 1;
int32_t cyclesPosted = 0;
unsigned waitMask = 0;
};
Player* player(int id);
union {
mLockstep m_lockstep;
#ifdef M_CORE_GB
@ -68,6 +78,8 @@ private:
GBASIOLockstep m_gbaLockstep;
#endif
};
mPlatform m_platform = mPLATFORM_NONE;
QList<Player> m_players;
QMutex m_lock;
};

View File

@ -0,0 +1,50 @@
/* 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 "OpenGLBug.h"
#include <QOpenGLContext>
#include <QOpenGLFunctions>
namespace QGBA {
bool glContextHasBug(OpenGLBug bug) {
QOpenGLContext* context = QOpenGLContext::currentContext();
if (!context) {
abort();
}
QOpenGLFunctions* fn = context->functions();
QString vendor(reinterpret_cast<const char*>(fn->glGetString(GL_VENDOR)));
QString renderer(reinterpret_cast<const char*>(fn->glGetString(GL_RENDERER)));
QString version(reinterpret_cast<const char*>(fn->glGetString(GL_VERSION)));
switch (bug) {
case OpenGLBug::CROSS_THREAD_FLUSH:
#ifndef Q_OS_WIN
return false;
#else
return vendor == "Intel";
#endif
case OpenGLBug::GLTHREAD_BLOCKS_SWAP:
return version.contains(" Mesa ");
case OpenGLBug::IG4ICD_CRASH:
#ifdef Q_OS_WIN
if (vendor != "Intel") {
return false;
}
if (renderer == "Intel Pineview Platform") {
return true;
}
#endif
return false;
default:
return false;
}
}
}

View File

@ -0,0 +1,18 @@
/* 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
namespace QGBA {
enum class OpenGLBug {
CROSS_THREAD_FLUSH, // mgba.io/i/2761
GLTHREAD_BLOCKS_SWAP, // mgba.io/i/2767
IG4ICD_CRASH, // mgba.io/i/2136
};
bool glContextHasBug(OpenGLBug);
}

View File

@ -308,6 +308,7 @@ void ReportView::generateReport() {
deferredBinaries.append(qMakePair(QString("Save %1").arg(winId), save));
}
mStateExtdataDeinit(&extdata);
vf->close(vf);
}
}
} else {

View File

@ -27,9 +27,8 @@
using namespace QGBA;
SaveConverter::SaveConverter(std::shared_ptr<CoreController> controller, QWidget* parent)
SaveConverter::SaveConverter(QWidget* parent)
: QDialog(parent, Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint)
, m_controller(controller)
{
m_ui.setupUi(this);
@ -198,19 +197,24 @@ void SaveConverter::detectFromSize(std::shared_ptr<VFileDevice> vf) {
#ifdef M_CORE_GBA
switch (vf->size()) {
case SIZE_CART_SRAM:
case SIZE_CART_SRAM + 16:
m_validSaves.append(AnnotatedSave{SAVEDATA_SRAM, vf});
break;
case SIZE_CART_FLASH512:
case SIZE_CART_FLASH512 + 16:
m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH512, vf});
break;
case SIZE_CART_FLASH1M:
case SIZE_CART_FLASH1M + 16:
m_validSaves.append(AnnotatedSave{SAVEDATA_FLASH1M, vf});
break;
case SIZE_CART_EEPROM:
case SIZE_CART_EEPROM + 16:
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, vf, Endian::LITTLE});
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM, vf, Endian::BIG});
break;
case SIZE_CART_EEPROM512:
case SIZE_CART_EEPROM512 + 16:
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, vf, Endian::LITTLE});
m_validSaves.append(AnnotatedSave{SAVEDATA_EEPROM512, vf, Endian::BIG});
break;
@ -478,6 +482,9 @@ SaveConverter::AnnotatedSave::operator QString() const {
default:
break;
}
if ((size & 0xFF) == 0x10) {
typeFormat += QCoreApplication::translate("QGBA::SaveConverter", " + RTC");
}
break;
#endif
#ifdef M_CORE_GB
@ -615,9 +622,23 @@ QList<SaveConverter::AnnotatedSave> SaveConverter::AnnotatedSave::possibleConver
}
break;
default:
if (size & 0xFF) {
AnnotatedSave noRtc = same;
noRtc.size &= ~0xFF;
possible.append(noRtc);
}
break;
}
break;
#endif
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
if ((size & 0xFF) == 0x10) {
AnnotatedSave noRtc = same;
noRtc.size &= ~0xFF;
possible.append(noRtc);
}
break;
#endif
default:
break;
@ -650,7 +671,7 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate
}
converted.resize(target.size);
buffer = backing->readAll();
for (int i = 0; i < size; i += 8) {
for (int i = 0; i < (size & ~0xFF); i += 8) {
uint64_t word;
const uint64_t* in = reinterpret_cast<const uint64_t*>(buffer.constData());
uint64_t* out = reinterpret_cast<uint64_t*>(converted.data());
@ -661,6 +682,9 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate
default:
break;
}
if (endianness == target.endianness && size > target.size) {
converted = backing->read(target.size);
}
break;
#endif
#ifdef M_CORE_GB
@ -711,6 +735,9 @@ QByteArray SaveConverter::AnnotatedSave::convertTo(const SaveConverter::Annotate
}
break;
default:
if (endianness == target.endianness && size > target.size) {
converted = backing->read(target.size);
}
break;
}
break;

View File

@ -31,7 +31,7 @@ class SaveConverter : public QDialog {
Q_OBJECT
public:
SaveConverter(std::shared_ptr<CoreController> controller, QWidget* parent = nullptr);
SaveConverter(QWidget* parent = nullptr);
static mPlatform getStatePlatform(VFile*);
static QByteArray getState(VFile*, mPlatform);
@ -100,7 +100,6 @@ private:
Ui::SaveConverter m_ui;
std::shared_ptr<CoreController> m_controller;
QList<AnnotatedSave> m_validSaves;
QList<AnnotatedSave> m_validOutputs;
};

View File

@ -296,9 +296,14 @@ SettingsView::SettingsView(ConfigController* controller, InputController* inputC
}
const GBColorPreset* colorPresets;
QString usedPreset = m_controller->getQtOption("gb.pal").toString();
size_t nPresets = GBColorPresetList(&colorPresets);
for (size_t i = 0; i < nPresets; ++i) {
m_ui.colorPreset->addItem(QString(colorPresets[i].name));
QString presetName(colorPresets[i].name);
m_ui.colorPreset->addItem(presetName);
if (usedPreset == presetName) {
m_ui.colorPreset->setCurrentIndex(i);
}
}
connect(m_ui.colorPreset, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this, colorPresets](int n) {
const GBColorPreset* preset = &colorPresets[n];
@ -640,6 +645,7 @@ void SettingsView::updateConfig() {
m_controller->setOption(color.toUtf8().constData(), m_gbColors[colorId] & ~0xFF000000);
}
m_controller->setQtOption("gb.pal", m_ui.colorPreset->currentText());
int gbColors = GB_COLORS_CGB;
if (m_ui.gbColor->isChecked()) {
@ -693,6 +699,8 @@ void SettingsView::reloadConfig() {
loadSetting("suspendScreensaver", m_ui.suspendScreensaver);
loadSetting("pauseOnFocusLost", m_ui.pauseOnFocusLost);
loadSetting("pauseOnMinimize", m_ui.pauseOnMinimize);
loadSetting("muteOnFocusLost", m_ui.muteOnFocusLost);
loadSetting("muteOnMinimize", m_ui.muteOnMinimize);
loadSetting("savegamePath", m_ui.savegamePath);
loadSetting("savestatePath", m_ui.savestatePath);
loadSetting("screenshotPath", m_ui.screenshotPath);

View File

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>885</width>
<height>797</height>
<width>880</width>
<height>700</height>
</rect>
</property>
<property name="sizePolicy">
@ -95,7 +95,7 @@
<item row="1" column="1">
<widget class="QStackedWidget" name="stackedWidget">
<property name="currentIndex">
<number>9</number>
<number>0</number>
</property>
<widget class="QWidget" name="av">
<layout class="QVBoxLayout" name="formLayout">
@ -1836,8 +1836,8 @@
<widget class="QFrame" name="color0">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -1855,8 +1855,8 @@
<widget class="QFrame" name="color1">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -1874,8 +1874,8 @@
<widget class="QFrame" name="color2">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -1893,8 +1893,8 @@
<widget class="QFrame" name="color3">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -1923,8 +1923,8 @@
<widget class="QFrame" name="color4">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -1942,8 +1942,8 @@
<widget class="QFrame" name="color5">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -1961,8 +1961,8 @@
<widget class="QFrame" name="color6">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -1980,8 +1980,8 @@
<widget class="QFrame" name="color7">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -2010,8 +2010,8 @@
<widget class="QFrame" name="color8">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -2029,8 +2029,8 @@
<widget class="QFrame" name="color9">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -2048,8 +2048,8 @@
<widget class="QFrame" name="color10">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -2067,8 +2067,8 @@
<widget class="QFrame" name="color11">
<property name="minimumSize">
<size>
<width>30</width>
<height>30</height>
<width>24</width>
<height>24</height>
</size>
</property>
<property name="autoFillBackground">
@ -2091,45 +2091,37 @@
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QRadioButton" name="gbColor">
<property name="text">
<string>Default color palette only</string>
</property>
<attribute name="buttonGroup">
<string notr="true">gbColors</string>
</attribute>
</widget>
</item>
<item row="6" column="1">
<widget class="QRadioButton" name="sgbColor">
<property name="text">
<string>SGB color palette if available</string>
</property>
<attribute name="buttonGroup">
<string notr="true">gbColors</string>
</attribute>
</widget>
</item>
<item row="7" column="1">
<widget class="QRadioButton" name="cgbColor">
<property name="text">
<string>GBC color palette if available</string>
</property>
<attribute name="buttonGroup">
<string notr="true">gbColors</string>
</attribute>
</widget>
</item>
<item row="8" column="1">
<widget class="QRadioButton" name="scgbColor">
<property name="text">
<string>SGB (preferred) or GBC color palette if available</string>
</property>
<attribute name="buttonGroup">
<string notr="true">gbColors</string>
</attribute>
</widget>
<item row="5" column="0" colspan="2">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="1">
<widget class="QRadioButton" name="sgbColor">
<property name="text">
<string>SGB color palette if available</string>
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QRadioButton" name="gbColor">
<property name="text">
<string>Default color palette only</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="cgbColor">
<property name="text">
<string>GBC color palette if available</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QRadioButton" name="scgbColor">
<property name="text">
<string>SGB (preferred) or GBC color palette if available</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>

View File

@ -149,7 +149,6 @@ bool ShortcutController::eventFilter(QObject*, QEvent* event) {
Action::BooleanFunction fn = item.value()->action()->booleanAction();
fn(event->type() == QEvent::KeyPress);
event->accept();
return true;
}
}
if (event->type() == GamepadButtonEvent::Down()) {

View File

@ -56,7 +56,7 @@ private:
int m_shortcut = 0;
int m_button = -1;
int m_axis = -1;
GamepadAxisEvent::Direction m_direction;
GamepadAxisEvent::Direction m_direction = GamepadAxisEvent::NEUTRAL;
};
class ShortcutController : public QObject {

View File

@ -6,6 +6,7 @@
#include "ShortcutModel.h"
#include "ShortcutController.h"
#include "utils.h"
using namespace QGBA;
@ -33,7 +34,7 @@ QVariant ShortcutModel::data(const QModelIndex& index, int role) const {
case 0:
return m_controller->visibleName(item->name);
case 1:
return shortcut ? QKeySequence(shortcut->shortcut()).toString(QKeySequence::NativeText) : QVariant();
return shortcut ? keyName(shortcut->shortcut()) : QVariant();
case 2:
if (!shortcut) {
return QVariant();
@ -134,4 +135,4 @@ void ShortcutModel::clearMenu(const QString&) {
// TODO
beginResetModel();
endResetModel();
}
}

View File

@ -51,7 +51,7 @@ TileView::TileView(std::shared_ptr<CoreController> controller, QWidget* parent)
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
m_ui.tile->setBoundary(2048, 0, 2);
m_ui.tile->setMaxTile(3096);
m_ui.tile->setMaxTile(3072);
break;
#endif
#ifdef M_CORE_GB
@ -76,7 +76,7 @@ TileView::TileView(std::shared_ptr<CoreController> controller, QWidget* parent)
#ifdef M_CORE_GBA
case mPLATFORM_GBA:
m_ui.tile->setBoundary(2048 >> selected, selected, selected + 2);
m_ui.tile->setMaxTile(3096 >> selected);
m_ui.tile->setMaxTile(3072 >> selected);
break;
#endif
#ifdef M_CORE_GB

View File

@ -20,6 +20,7 @@ using namespace QGBA;
QMap<QString, QString> VideoView::s_acodecMap;
QMap<QString, QString> VideoView::s_vcodecMap;
QMap<QString, QString> VideoView::s_containerMap;
QMap<QString, QStringList> VideoView::s_extensionMap;
bool VideoView::Preset::compatible(const Preset& other) const {
if (!other.container.isNull() && !container.isNull() && other.container != container) {
@ -71,6 +72,23 @@ VideoView::VideoView(QWidget* parent)
if (s_containerMap.empty()) {
s_containerMap["mkv"] = "matroska";
}
if (s_extensionMap.empty()) {
s_extensionMap["matroska"] += ".mkv";
s_extensionMap["matroska"] += ".mka";
s_extensionMap["webm"] += ".webm";
s_extensionMap["avi"] += ".avi";
s_extensionMap["mp4"] += ".mp4";
s_extensionMap["mp4"] += ".m4v";
s_extensionMap["mp4"] += ".m4a";
s_extensionMap["flac"] += ".flac";
s_extensionMap["mpeg"] += ".mpg";
s_extensionMap["mpeg"] += ".mpeg";
s_extensionMap["mpegts"] += ".ts";
s_extensionMap["mp3"] += ".mp3";
s_extensionMap["ogg"] += ".ogg";
s_extensionMap["ogv"] += ".ogv";
}
connect(m_ui.buttonBox, &QDialogButtonBox::rejected, this, &VideoView::close);
connect(m_ui.start, &QAbstractButton::clicked, this, &VideoView::startRecording);
@ -195,6 +213,9 @@ void VideoView::setController(std::shared_ptr<CoreController> controller) {
}
void VideoView::startRecording() {
if (QFileInfo(m_filename).suffix().isEmpty()) {
changeExtension();
}
if (!validateSettings()) {
return;
}
@ -238,6 +259,7 @@ void VideoView::selectFile() {
QString filename = GBAApp::app()->getSaveFileName(this, tr("Select output file"));
if (!filename.isEmpty()) {
m_ui.filename->setText(filename);
changeExtension();
}
}
@ -289,6 +311,7 @@ void VideoView::setContainer(const QString& container) {
m_containerCstr = nullptr;
m_container = QString();
}
changeExtension();
validateSettings();
uncheckIncompatible();
}
@ -458,6 +481,30 @@ void VideoView::uncheckIncompatible() {
}
}
void VideoView::changeExtension() {
if (m_filename.isEmpty()) {
return;
}
if (!s_extensionMap.contains(m_container)) {
return;
}
QStringList extensions = s_extensionMap.value(m_container);
QString filename = m_filename;
int index = m_filename.lastIndexOf(".");
if (index >= 0) {
if (extensions.contains(filename.mid(index))) {
// This extension is already valid
return;
}
filename.truncate(index);
}
filename += extensions.front();
m_ui.filename->setText(filename);
}
QString VideoView::sanitizeCodec(const QString& codec, const QMap<QString, QString>& mapping) {
QString sanitized = codec.toLower();
sanitized = sanitized.remove(QChar('.'));

View File

@ -7,6 +7,7 @@
#ifdef USE_FFMPEG
#include <QStringList>
#include <QWidget>
#include <memory>
@ -62,6 +63,8 @@ private slots:
void uncheckIncompatible();
void updatePresets();
void changeExtension();
private:
struct Preset {
QString container;
@ -123,6 +126,7 @@ private:
static QMap<QString, QString> s_acodecMap;
static QMap<QString, QString> s_vcodecMap;
static QMap<QString, QString> s_containerMap;
static QMap<QString, QStringList> s_extensionMap;
};
}

View File

@ -247,22 +247,22 @@
</property>
<item>
<property name="text">
<string>MKV</string>
<string notr="true">MKV</string>
</property>
</item>
<item>
<property name="text">
<string>WebM</string>
<string notr="true">WebM</string>
</property>
</item>
<item>
<property name="text">
<string>AVI</string>
<string notr="true">AVI</string>
</property>
</item>
<item>
<property name="text">
<string>MP4</string>
<string notr="true">MP4</string>
</property>
</item>
</widget>
@ -274,42 +274,42 @@
</property>
<item>
<property name="text">
<string>H.264</string>
<string notr="true">H.264</string>
</property>
</item>
<item>
<property name="text">
<string>H.264 (NVENC)</string>
<string notr="true">H.264 (NVENC)</string>
</property>
</item>
<item>
<property name="text">
<string>HEVC</string>
<string notr="true">HEVC</string>
</property>
</item>
<item>
<property name="text">
<string>HEVC (NVENC)</string>
<string notr="true">HEVC (NVENC)</string>
</property>
</item>
<item>
<property name="text">
<string>VP8</string>
<string notr="true">VP8</string>
</property>
</item>
<item>
<property name="text">
<string>VP9</string>
<string notr="true">VP9</string>
</property>
</item>
<item>
<property name="text">
<string>FFV1</string>
<string notr="true">FFV1</string>
</property>
</item>
<item>
<property name="text">
<string>None</string>
<string notr="true">None</string>
</property>
</item>
</widget>
@ -321,42 +321,42 @@
</property>
<item>
<property name="text">
<string>FLAC</string>
<string notr="true">FLAC</string>
</property>
</item>
<item>
<property name="text">
<string>WavPack</string>
<string notr="true">WavPack</string>
</property>
</item>
<item>
<property name="text">
<string>Opus</string>
<string notr="true">Opus</string>
</property>
</item>
<item>
<property name="text">
<string>Vorbis</string>
<string notr="true">Vorbis</string>
</property>
</item>
<item>
<property name="text">
<string>MP3</string>
<string notr="true">MP3</string>
</property>
</item>
<item>
<property name="text">
<string>AAC</string>
<string notr="true">AAC</string>
</property>
</item>
<item>
<property name="text">
<string>Uncompressed</string>
<string notr="true">Uncompressed</string>
</property>
</item>
<item>
<property name="text">
<string>None</string>
<string notr="true">None</string>
</property>
</item>
</widget>

View File

@ -1107,6 +1107,8 @@ void Window::changeRenderer() {
if (!m_controller) {
return;
}
CoreController::Interrupter interrupter(m_controller);
if (m_config->getOption("hwaccelVideo").toInt() && m_display->supportsShaders() && m_controller->supportsFeature(CoreController::Feature::OPENGL)) {
std::shared_ptr<VideoProxy> proxy = m_display->videoProxy();
if (!proxy) {
@ -1169,14 +1171,14 @@ void Window::recordFrame() {
}
void Window::showFPS() {
if (m_frameList.isEmpty()) {
updateTitle();
return;
}
qint64 total = 0;
for (qint64 t : m_frameList) {
total += t;
}
if (!total) {
updateTitle();
return;
}
double fps = (m_frameList.size() * 1e10) / total;
m_frameList.clear();
fps = round(fps) / 10.f;
@ -1294,7 +1296,7 @@ void Window::setupMenu(QMenuBar* menubar) {
m_actions.addSeparator("saves");
m_actions.addAction(tr("Convert save game..."), "convertSave", openControllerTView<SaveConverter>(), "saves");
m_actions.addAction(tr("Convert save game..."), "convertSave", openTView<SaveConverter>(), "saves");
#ifdef M_CORE_GBA
Action* importShark = addGameAction(tr("Import GameShark Save..."), "importShark", this, &Window::importSharkport, "saves");
@ -1595,7 +1597,8 @@ void Window::setupMenu(QMenuBar* menubar) {
m_actions.addSeparator("av");
ConfigOption* mute = m_config->addOption("mute");
mute->addBoolean(tr("Mute"), &m_actions, "av");
Action* muteAction = mute->addBoolean(tr("Mute"), &m_actions, "av");
muteAction->setActive(m_config->getOption("mute").toInt());
mute->connect([this](const QVariant& value) {
m_config->setOption("fastForwardMute", static_cast<bool>(value.toInt()));
reloadConfig();
@ -1721,7 +1724,7 @@ void Window::setupMenu(QMenuBar* menubar) {
if (m_controller) {
mCheatPressButton(m_controller->cheatDevice(), held);
}
}, "tools", QKeySequence(Qt::Key_Apostrophe));
}, "tools");
m_actions.addHiddenMenu(tr("Autofire"), "autofire");
m_actions.addHeldAction(tr("Autofire A"), "autofireA", [this](bool held) {
@ -1785,7 +1788,7 @@ void Window::setupMenu(QMenuBar* menubar) {
void Window::setupOptions() {
ConfigOption* videoSync = m_config->addOption("videoSync");
videoSync->connect([this](const QVariant&) {
videoSync->connect([this](const QVariant& variant) {
reloadConfig();
}, this);
@ -1907,8 +1910,15 @@ void Window::setupOptions() {
}
void Window::attachWidget(QWidget* widget) {
// Fix https://mgba.io/i/2885 -- seems like a Qt bug
if (m_display && widget != m_display.get()) {
m_display->hide();
}
takeCentralWidget();
setCentralWidget(widget);
if (m_display && widget == m_display.get()) {
m_display->show();
}
}
void Window::detachWidget() {

View File

@ -38,6 +38,7 @@ Q_IMPORT_PLUGIN(AVFServicePlugin);
#endif
#elif defined(Q_OS_UNIX)
Q_IMPORT_PLUGIN(QXcbIntegrationPlugin);
Q_IMPORT_PLUGIN(QWaylandIntegrationPlugin);
#endif
#endif
@ -98,6 +99,10 @@ int main(int argc, char* argv[]) {
QApplication::setWindowIcon(QIcon(":/res/mgba-256.png"));
#endif
#ifdef Q_OS_UNIX
QApplication::setDesktopFileName(QString("io.mgba.mGBA"));
#endif
QTranslator qtTranslator;
qtTranslator.load(locale, "qt", "_", QLibraryInfo::location(QLibraryInfo::TranslationsPath));
application.installTranslator(&qtTranslator);

View File

@ -5,6 +5,7 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "scripting/ScriptingController.h"
#include "AudioProcessor.h"
#include "CoreController.h"
#include "scripting/ScriptingTextBuffer.h"
#include "scripting/ScriptingTextBufferModel.h"
@ -73,11 +74,21 @@ bool ScriptingController::load(VFileDevice& vf, const QString& name) {
}
QByteArray utf8 = name.toUtf8();
CoreController::Interrupter interrupter(m_controller);
if (!m_activeEngine->load(m_activeEngine, utf8.constData(), vf) || !m_activeEngine->run(m_activeEngine)) {
emit error(QString::fromUtf8(m_activeEngine->getError(m_activeEngine)));
return false;
if (m_controller) {
m_controller->setSync(false);
m_controller->unpaused();
}
return true;
bool ok = true;
if (!m_activeEngine->load(m_activeEngine, utf8.constData(), vf) || !m_activeEngine->run(m_activeEngine)) {
ok = false;
}
if (m_controller) {
m_controller->setSync(true);
if (m_controller->isPaused()) {
m_controller->paused();
}
}
return ok;
}
void ScriptingController::clearController() {

View File

@ -5,6 +5,8 @@
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "utils.h"
#include <QCoreApplication>
#include <QKeySequence>
#include <QObject>
namespace QGBA {
@ -59,4 +61,27 @@ bool convertAddress(const QHostAddress* input, Address* output) {
return true;
}
QString keyName(int key) {
switch (key) {
#ifndef Q_OS_MAC
case Qt::Key_Shift:
return QCoreApplication::translate("QShortcut", "Shift");
case Qt::Key_Control:
return QCoreApplication::translate("QShortcut", "Control");
case Qt::Key_Alt:
return QCoreApplication::translate("QShortcut", "Alt");
case Qt::Key_Meta:
return QCoreApplication::translate("QShortcut", "Meta");
#endif
case Qt::Key_Super_L:
return QObject::tr("Super (L)");
case Qt::Key_Super_R:
return QObject::tr("Super (R)");
case Qt::Key_Menu:
return QObject::tr("Menu");
default:
return QKeySequence(key).toString(QKeySequence::NativeText);
}
}
}

View File

@ -67,4 +67,6 @@ constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
}
#endif
QString keyName(int key);
}

View File

@ -22,6 +22,9 @@ if (SDL_VERSION EQUAL "2")
endif()
endif()
if(APPLE)
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.7")
endif()
set(SDLMAIN_LIBRARY ${SDL2MAIN_LIBRARY})
endif()
endif()

View File

@ -7,6 +7,7 @@ set(link_flags "-L${DEVKITPRO}/libogc/lib/wii ${arch_flags}")
set(CMAKE_SYSTEM_PROCESSOR powerpc CACHE INTERNAL "processor")
set(CMAKE_LIBRARY_ARCHITECTURE powerpc-none-eabi CACHE INTERNAL "abi")
set(CMAKE_REQUIRED_LIBRARIES ogc)
set(WII ON)
add_definitions(-DGEKKO)

View File

@ -151,7 +151,12 @@ bool _vdwDeleteFile(struct VDir* vd, const char* path) {
MultiByteToWideChar(CP_UTF8, 0, path, -1, pathw, MAX_PATH);
StringCchPrintfW(combined, MAX_PATH, L"%ws\\%ws", dir, pathw);
return DeleteFileW(combined);
DWORD attrs = GetFileAttributesW(combined);
if (attrs & FILE_ATTRIBUTE_DIRECTORY) {
return RemoveDirectoryW(combined);
} else {
return DeleteFileW(combined);
}
}
const char* _vdweName(struct VDirEntry* vde) {
@ -183,4 +188,4 @@ bool VDirCreate(const char* path) {
return true;
}
return false;
}
}

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