mirror of https://github.com/mgba-emu/mgba.git
Compare commits
266 Commits
Author | SHA1 | Date |
---|---|---|
![]() |
26b7884bc2 | |
![]() |
b9b090c566 | |
![]() |
5e8c47ecb1 | |
![]() |
406a202157 | |
![]() |
332b1b8bc3 | |
![]() |
7cfe792559 | |
![]() |
e19f35e7e4 | |
![]() |
56b59c767b | |
![]() |
0670687b86 | |
![]() |
196adf233c | |
![]() |
dbae6e1eef | |
![]() |
5e4b91c94f | |
![]() |
afdf3b0b88 | |
![]() |
f40ea09127 | |
![]() |
7e1e7d2253 | |
![]() |
c9083715e6 | |
![]() |
eecdfd0c77 | |
![]() |
20da804e65 | |
![]() |
e4dc41df31 | |
![]() |
9a1d2442a3 | |
![]() |
6c2da7b621 | |
![]() |
9a38ba4913 | |
![]() |
2f880b17ca | |
![]() |
bbf601da02 | |
![]() |
b51e58640f | |
![]() |
30897fee62 | |
![]() |
c143851916 | |
![]() |
10eb8c57d2 | |
![]() |
952c883755 | |
![]() |
7ae9117930 | |
![]() |
07ac520a9a | |
![]() |
f778cf4749 | |
![]() |
9271198f64 | |
![]() |
16f7a05b1a | |
![]() |
a66ad9c2c8 | |
![]() |
3ae88949a3 | |
![]() |
ffe539343e | |
![]() |
00426ad6d1 | |
![]() |
7d8b029282 | |
![]() |
67b0a27d88 | |
![]() |
d2a8b43ae0 | |
![]() |
ebad7a380b | |
![]() |
a0f6255a0b | |
![]() |
154fe6b344 | |
![]() |
5ac23cfe4f | |
![]() |
2c2eab2b94 | |
![]() |
ee2533831d | |
![]() |
b205537558 | |
![]() |
e0b0e45821 | |
![]() |
832c0aed27 | |
![]() |
f8ece1ed04 | |
![]() |
95c2e2fbe6 | |
![]() |
40dcea36c0 | |
![]() |
21d76adf66 | |
![]() |
8f648b6808 | |
![]() |
b342ecb76f | |
![]() |
63c0bedf44 | |
![]() |
ff00fed81c | |
![]() |
a2493a7bf3 | |
![]() |
b1c7c6d14a | |
![]() |
16a565dc6b | |
![]() |
0ca0ea47d4 | |
![]() |
455d424685 | |
![]() |
99bae2dc06 | |
![]() |
34b1f0b3a4 | |
![]() |
c2907e8286 | |
![]() |
557a3b67db | |
![]() |
950e576f09 | |
![]() |
2ab7500e3e | |
![]() |
5aa7c955f1 | |
![]() |
32149f0e24 | |
![]() |
1c058b596e | |
![]() |
3253c5dd32 | |
![]() |
858c94e5ee | |
![]() |
a2b7b19d89 | |
![]() |
48350b1fd3 | |
![]() |
1c61b54208 | |
![]() |
15ec8eb795 | |
![]() |
383dcda998 | |
![]() |
e1a5329f30 | |
![]() |
0227d2db39 | |
![]() |
3f4cb4dbcb | |
![]() |
960d98a554 | |
![]() |
9a45bf94aa | |
![]() |
f8112aee59 | |
![]() |
68e5a0aa47 | |
![]() |
7be38eaed5 | |
![]() |
3c5caf7c53 | |
![]() |
f4a5d86f88 | |
![]() |
3520607ebc | |
![]() |
4f50fe04fe | |
![]() |
c6afa29215 | |
![]() |
ba83c4d9d3 | |
![]() |
0e3d55c2ac | |
![]() |
ef9c5bd096 | |
![]() |
5ecce03fba | |
![]() |
0b003fe230 | |
![]() |
a362317f46 | |
![]() |
73ac3dad4f | |
![]() |
1d4bf372b8 | |
![]() |
da8f4f28f3 | |
![]() |
35cdd5d242 | |
![]() |
d2205d3475 | |
![]() |
404e84b045 | |
![]() |
31235a6b2c | |
![]() |
0fb8b782be | |
![]() |
c5dc97a432 | |
![]() |
9adb1faef2 | |
![]() |
9b98a19a37 | |
![]() |
ddee0af216 | |
![]() |
35bbc74cc2 | |
![]() |
510d1afa4a | |
![]() |
d6291bcc05 | |
![]() |
e86e9d0c76 | |
![]() |
cda008eef5 | |
![]() |
fcb2f11464 | |
![]() |
eb2233eaaa | |
![]() |
1d37620c8c | |
![]() |
1ec45502fb | |
![]() |
1dc94c999a | |
![]() |
afb614ad03 | |
![]() |
a51cb3f921 | |
![]() |
1acb7dfa9e | |
![]() |
b06c22a408 | |
![]() |
2669e0f772 | |
![]() |
87c9d3851e | |
![]() |
0d353a740f | |
![]() |
72379168cb | |
![]() |
76fe63a254 | |
![]() |
24f3d8808f | |
![]() |
6c4ca7724f | |
![]() |
276e911e38 | |
![]() |
e49fe46c26 | |
![]() |
724b79b035 | |
![]() |
c37949fee7 | |
![]() |
d99f7b9912 | |
![]() |
97162280f9 | |
![]() |
84d600cba6 | |
![]() |
21cfb15f99 | |
![]() |
ff3c68bbd6 | |
![]() |
5f762e68b8 | |
![]() |
5a94780871 | |
![]() |
078ce85774 | |
![]() |
2fb5545061 | |
![]() |
c5743e7931 | |
![]() |
b876218060 | |
![]() |
76acad90a6 | |
![]() |
c0e3db2bb4 | |
![]() |
cbe87be222 | |
![]() |
e15cca9623 | |
![]() |
b0441cffd0 | |
![]() |
2f2287683a | |
![]() |
70bbe06bfb | |
![]() |
d79a83321c | |
![]() |
8344efdccd | |
![]() |
7517cce0af | |
![]() |
5a642ae1f1 | |
![]() |
9d7f47413c | |
![]() |
6806850441 | |
![]() |
121376ccb0 | |
![]() |
7d9d71ef54 | |
![]() |
7da48a7c7f | |
![]() |
ff3f2a9d84 | |
![]() |
87295370f1 | |
![]() |
e01b56fbd5 | |
![]() |
5b52f0f277 | |
![]() |
e7f56bd5c9 | |
![]() |
40e7d2aa4d | |
![]() |
603ab67926 | |
![]() |
5f6244556e | |
![]() |
06b1b04c7d | |
![]() |
853b028892 | |
![]() |
1ed40a1931 | |
![]() |
b8248327a2 | |
![]() |
b6e34f084f | |
![]() |
8b67243780 | |
![]() |
b06d178c41 | |
![]() |
43f2eb78d7 | |
![]() |
2e4732f1ff | |
![]() |
71174659f2 | |
![]() |
d099a0d07e | |
![]() |
3357fa6a03 | |
![]() |
72817df4cf | |
![]() |
9da638f0c0 | |
![]() |
1bbbbc745d | |
![]() |
18b798ec41 | |
![]() |
bd805dd720 | |
![]() |
c13e2a822e | |
![]() |
9132859b6f | |
![]() |
feb9c4a48b | |
![]() |
081fe27e3a | |
![]() |
11d38a8317 | |
![]() |
766c6625c4 | |
![]() |
1d01a7585c | |
![]() |
a473aeff2c | |
![]() |
3f5fec4e4d | |
![]() |
8c5ad62dbc | |
![]() |
5558e469e0 | |
![]() |
1164d5b470 | |
![]() |
b9a950fee7 | |
![]() |
7e624c6857 | |
![]() |
395fa2da4c | |
![]() |
4738e558f2 | |
![]() |
1f2df26e22 | |
![]() |
4f8763ad0b | |
![]() |
1331788b6e | |
![]() |
7c4a7796f8 | |
![]() |
5183edc3c6 | |
![]() |
5d1fdf3b24 | |
![]() |
b00a04099f | |
![]() |
d29e782789 | |
![]() |
17322e50b8 | |
![]() |
31755ebb19 | |
![]() |
f69853e8ba | |
![]() |
df6ae1883c | |
![]() |
21f441953f | |
![]() |
3415dfa1b7 | |
![]() |
71d1f122f9 | |
![]() |
9c69b86d71 | |
![]() |
b5b4271145 | |
![]() |
a4b63e1db9 | |
![]() |
86061dbc3b | |
![]() |
456ac2222b | |
![]() |
a413dda335 | |
![]() |
c8d7a7d1a3 | |
![]() |
9d6d2c9c6b | |
![]() |
f7acda6d64 | |
![]() |
0da3498ee8 | |
![]() |
d17b1da1b7 | |
![]() |
0d52995877 | |
![]() |
63a019b749 | |
![]() |
fac26cda3c | |
![]() |
245f4b961a | |
![]() |
3d723d5cf4 | |
![]() |
1a4dc70731 | |
![]() |
411b7dccb4 | |
![]() |
947a1c8f5c | |
![]() |
576f607c81 | |
![]() |
ab790f4c6d | |
![]() |
94277cffbb | |
![]() |
7ba3b40977 | |
![]() |
3dbb90e574 | |
![]() |
c323ab384c | |
![]() |
411741fba3 | |
![]() |
9f057e2719 | |
![]() |
0fc6b02691 | |
![]() |
3866d74707 | |
![]() |
aeaa40e373 | |
![]() |
8e58dcadb8 | |
![]() |
f632b85338 | |
![]() |
3cd63cb71c | |
![]() |
4914954c67 | |
![]() |
20c16bc8e7 | |
![]() |
838e4a182e | |
![]() |
e3605d291c | |
![]() |
12f7168e9f | |
![]() |
455525e3b6 | |
![]() |
3f39bd2536 | |
![]() |
838122c234 | |
![]() |
c6d7c8f601 | |
![]() |
9a3ec792dd | |
![]() |
c4718d6907 | |
![]() |
64255a716b | |
![]() |
277aa36d12 | |
![]() |
81cb4c3bf6 | |
![]() |
df599a1fb6 |
|
@ -12,7 +12,11 @@ install:
|
||||||
- vcpkg --no-dry-run upgrade
|
- vcpkg --no-dry-run upgrade
|
||||||
- rd /Q /S C:\Tools\vcpkg\buildtrees
|
- rd /Q /S C:\Tools\vcpkg\buildtrees
|
||||||
before_build:
|
before_build:
|
||||||
- cmake . -DCMAKE_PREFIX_PATH=C:\Qt\5.15\msvc2019_64 -DCMAKE_TOOLCHAIN_FILE=C:\Tools\vcpkg\scripts\buildsystems\vcpkg.cmake -DVCPKG_TARGET_TRIPLET=x64-windows-release -DCMAKE_CONFIGURATION_TYPES=Release
|
- cmake . -DCMAKE_PREFIX_PATH=C:\Qt\5.15\msvc2019_64 \
|
||||||
|
-DCMAKE_TOOLCHAIN_FILE=C:\Tools\vcpkg\scripts\buildsystems\vcpkg.cmake \
|
||||||
|
-DVCPKG_TARGET_TRIPLET=x64-windows-release \
|
||||||
|
-DCMAKE_CONFIGURATION_TYPES=Release \
|
||||||
|
-DCMAKE_SYSTEM_VERSION=10.0.22000.1
|
||||||
build:
|
build:
|
||||||
parallel: true
|
parallel: true
|
||||||
project: mGBA.sln
|
project: mGBA.sln
|
||||||
|
|
146
CHANGES
146
CHANGES
|
@ -1,3 +1,139 @@
|
||||||
|
0.10.5: (2025-03-08)
|
||||||
|
Other fixes:
|
||||||
|
- ARM Debugger: Fix disassembly of ror r0 barrel shift (fixes mgba.io/i/3412)
|
||||||
|
- FFmpeg: Fix failing to record videos with CRF video (fixes mgba.io/i/3368)
|
||||||
|
- GB Core: Fix cloning savedata when backing file is outdated (fixes mgba.io/i/3388)
|
||||||
|
- GBA Cheats: Let VBA-style codes patch ROM (fixes mgba.io/i/3423)
|
||||||
|
- GBA Core: Fix booting into BIOS when skip BIOS is enabled
|
||||||
|
- GBA Hardware: Fix loading states unconditionally overwriting GPIO memory
|
||||||
|
- Updater: Fix rewriting folders and files on Windows (fixes mgba.io/i/3384)
|
||||||
|
- Wii: Fix crash on loading large ZIP files (fixes mgba.io/i/3404)
|
||||||
|
Misc:
|
||||||
|
- GB: Allow use of CGB-E and AGB-0 BIOS versions (closes mgba.io/i/3427)
|
||||||
|
|
||||||
|
0.10.4: (2024-12-07)
|
||||||
|
Emulation fixes:
|
||||||
|
- GB Audio: Fix audio envelope timing resetting too often (fixes mgba.io/i/3164)
|
||||||
|
- GB I/O: Fix STAT writing IRQ trigger conditions (fixes mgba.io/i/2501)
|
||||||
|
- GBA GPIO: Fix gyro read-out start (fixes mgba.io/i/3141)
|
||||||
|
- GBA I/O: Fix HALTCNT access behavior (fixes mgba.io/i/2309)
|
||||||
|
- GBA I/O: Fix audio register 8-bit write behavior (fixes mgba.io/i/3086)
|
||||||
|
- GBA Serialize: Properly restore GPIO register state (fixes mgba.io/i/3294)
|
||||||
|
- GBA SIO: Fix MULTI mode SIOCNT bit 7 writes on secondary GBAs (fixes mgba.io/i/3110)
|
||||||
|
Other fixes:
|
||||||
|
- Core: Fix patch autoloading leaking the file handle
|
||||||
|
- GB: Fix uninitialized save data when loading undersized temporary saves
|
||||||
|
- GB, GBA Core: Fix memory leak if reloading debug symbols
|
||||||
|
- GB Serialize: Prevent loading invalid states where LY >= 144 in modes other than 1
|
||||||
|
- GBA Audio: Fix crash if audio FIFOs and timers get out of sync
|
||||||
|
- GBA Audio: Fix crash in audio subsampling if timing lockstep breaks
|
||||||
|
- GBA Core: Fix loading symbols from ELF files if the file doesn't end with .elf
|
||||||
|
- GBA Memory: Let raw access read high MMIO addresses
|
||||||
|
- Qt: Fix crash when applying changes to GB I/O registers in I/O view
|
||||||
|
- Qt: Fix LCDC background priority/enable bit being mis-mapped in I/O view
|
||||||
|
- Qt: Fix saving named states breaking when screenshot states disabled (fixes mgba.io/i/3320)
|
||||||
|
- Qt: Fix potential crash on Wayland with OpenGL (fixes mgba.io/i/3276)
|
||||||
|
- Qt: Fix installer updates if a version number is in the filename (fixes mgba.io/i/3109)
|
||||||
|
- Updater: Fix updating appimage across filesystems
|
||||||
|
Misc:
|
||||||
|
- Qt: Make window corners square on Windows 11 (fixes mgba.io/i/3285)
|
||||||
|
- Switch: Add bilinear filtering option (closes mgba.io/i/3111)
|
||||||
|
- Vita: Add imc0 and xmc0 mount point support
|
||||||
|
|
||||||
|
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)
|
0.10.0: (2022-10-11)
|
||||||
Features:
|
Features:
|
||||||
- Preliminary Lua scripting support
|
- Preliminary Lua scripting support
|
||||||
|
@ -6,7 +142,7 @@ Features:
|
||||||
- Tool for converting scanned pictures of e-Reader cards to raw dotcode data
|
- 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
|
- Options for muting when inactive, minimized, or for different players in multiplayer
|
||||||
- Cheat code support in homebrew ports
|
- 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
|
- Support for combo "Super Game Boy Color" SGB + GBC ROM hacks
|
||||||
- Improved support for HuC-3 mapper, including RTC
|
- Improved support for HuC-3 mapper, including RTC
|
||||||
- Support for 64 kiB SRAM saves used in some bootlegs
|
- Support for 64 kiB SRAM saves used in some bootlegs
|
||||||
|
@ -20,7 +156,7 @@ Emulation fixes:
|
||||||
- ARM7: Fix unsigned multiply timing
|
- ARM7: Fix unsigned multiply timing
|
||||||
- GB: Copy logo from ROM if not running the BIOS intro (fixes mgba.io/i/2378)
|
- 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: 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: Properly apply per-model audio differences
|
||||||
- GB Audio: Revamp channel rendering
|
- GB Audio: Revamp channel rendering
|
||||||
- GB Audio: Fix APU re-enable timing glitch
|
- GB Audio: Fix APU re-enable timing glitch
|
||||||
|
@ -130,7 +266,7 @@ Emulation fixes:
|
||||||
Other fixes:
|
Other fixes:
|
||||||
- ARM Decoder: Fix decoding of lsl r0 (fixes mgba.io/i/2349)
|
- 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)
|
- 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 out of bounds ROM accesses on patched ROMs smaller than 32 MiB
|
||||||
- GBA: Fix maximum tile ID in caching for 256-color modes
|
- GBA: Fix maximum tile ID in caching for 256-color modes
|
||||||
- GBA Video: Fix cache updating with proxy and GL renderers
|
- GBA Video: Fix cache updating with proxy and GL renderers
|
||||||
|
@ -241,7 +377,7 @@ Emulation fixes:
|
||||||
- GBA BIOS: Implement dummy sound driver calls
|
- GBA BIOS: Implement dummy sound driver calls
|
||||||
- GBA BIOS: Improve HLE BIOS timing
|
- GBA BIOS: Improve HLE BIOS timing
|
||||||
- GBA BIOS: Fix reloading video registers after reset (fixes mgba.io/i/1808)
|
- 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 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: Linger last DMA on bus (fixes mgba.io/i/301 and mgba.io/i/1320)
|
||||||
- GBA DMA: Fix ordering and timing of overlapping DMAs
|
- GBA DMA: Fix ordering and timing of overlapping DMAs
|
||||||
|
@ -257,7 +393,7 @@ Emulation fixes:
|
||||||
- GBA Serialize: Fix alignment check when loading states
|
- GBA Serialize: Fix alignment check when loading states
|
||||||
- GBA SIO: Fix copying Normal mode transfer values
|
- GBA SIO: Fix copying Normal mode transfer values
|
||||||
- GBA SIO: Fix Normal mode being totally broken (fixes mgba.io/i/1800)
|
- 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 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 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)
|
- GBA Timers: Fix toggling timer cascading while timer is active (fixes mgba.io/i/2043)
|
||||||
|
|
|
@ -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)
|
source_group("Generated sources" FILES ${CMAKE_CURRENT_BINARY_DIR}/version.c)
|
||||||
|
|
||||||
# Advanced settings
|
# 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)
|
set(DEFAULT_LTO ON)
|
||||||
else()
|
else()
|
||||||
set(DEFAULT_LTO OFF)
|
set(DEFAULT_LTO OFF)
|
||||||
|
@ -236,10 +236,14 @@ endif()
|
||||||
|
|
||||||
if(APPLE)
|
if(APPLE)
|
||||||
execute_process(COMMAND xcrun --show-sdk-version OUTPUT_VARIABLE MACOSX_SDK)
|
execute_process(COMMAND xcrun --show-sdk-version OUTPUT_VARIABLE MACOSX_SDK)
|
||||||
|
if(NOT MACOSX_SDK)
|
||||||
|
message(WARNING "Could not detect SDK version; defaulting to system version. Is SDKROOT set?")
|
||||||
|
set(MACOSX_SDK ${CMAKE_SYSTEM_VERSION})
|
||||||
|
endif()
|
||||||
add_definitions(-D_DARWIN_C_SOURCE)
|
add_definitions(-D_DARWIN_C_SOURCE)
|
||||||
list(APPEND OS_LIB "-framework Foundation")
|
list(APPEND OS_LIB "-framework Foundation")
|
||||||
if(NOT CMAKE_SYSTEM_VERSION VERSION_LESS "10.0") # Darwin 10.x is Mac OS X 10.6
|
if(NOT CMAKE_SYSTEM_VERSION VERSION_LESS "10.0") # Darwin 10.x is Mac OS X 10.6
|
||||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -mmacosx-version-min=10.6")
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.6")
|
||||||
endif()
|
endif()
|
||||||
# Not supported until Xcode 9
|
# Not supported until Xcode 9
|
||||||
if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "9")
|
if(CMAKE_C_COMPILER_ID STREQUAL "AppleClang" AND CMAKE_C_COMPILER_VERSION VERSION_LESS "9")
|
||||||
|
@ -336,16 +340,12 @@ find_function(popcount32)
|
||||||
|
|
||||||
find_function(futimens)
|
find_function(futimens)
|
||||||
find_function(futimes)
|
find_function(futimes)
|
||||||
|
find_function(localtime_r)
|
||||||
|
|
||||||
if(ANDROID AND ANDROID_NDK_MAJOR GREATER 13)
|
if(ANDROID AND ANDROID_NDK_MAJOR GREATER 13)
|
||||||
find_function(localtime_r)
|
list(APPEND FUNCTION_DEFINES HAVE_STRTOF_L)
|
||||||
set(HAVE_STRTOF_L ON)
|
elseif(NOT CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
||||||
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|
||||||
find_function(localtime_r)
|
|
||||||
# The strtof_l on Linux not actually exposed nor actually strtof_l
|
# The strtof_l on Linux not actually exposed nor actually strtof_l
|
||||||
set(HAVE_STRTOF_L OFF)
|
|
||||||
else()
|
|
||||||
find_function(localtime_r)
|
|
||||||
find_function(strtof_l)
|
find_function(strtof_l)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
@ -419,6 +419,13 @@ if(BUILD_GL)
|
||||||
elseif(UNIX AND NOT APPLE AND TARGET OpenGL::GL)
|
elseif(UNIX AND NOT APPLE AND TARGET OpenGL::GL)
|
||||||
set(OPENGL_LIBRARY OpenGL::GL)
|
set(OPENGL_LIBRARY OpenGL::GL)
|
||||||
endif()
|
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()
|
endif()
|
||||||
if(BUILD_GL)
|
if(BUILD_GL)
|
||||||
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c)
|
list(APPEND FEATURE_SRC ${CMAKE_CURRENT_SOURCE_DIR}/src/platform/opengl/gl.c)
|
||||||
|
@ -718,8 +725,12 @@ if (USE_LZMA)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(USE_EPOXY)
|
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)
|
if(NOT APPLE OR NOT MACOSX_SDK VERSION_GREATER 10.14)
|
||||||
list(APPEND FEATURE_DEFINES BUILD_GL BUILD_GLES2 BUILD_GLES3)
|
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)
|
list(APPEND FEATURES EPOXY)
|
||||||
include_directories(AFTER ${EPOXY_INCLUDE_DIRS})
|
include_directories(AFTER ${EPOXY_INCLUDE_DIRS})
|
||||||
link_directories(${EPOXY_LIBRARY_DIRS})
|
link_directories(${EPOXY_LIBRARY_DIRS})
|
||||||
|
@ -754,6 +765,7 @@ if(USE_ELF)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if (USE_DISCORD_RPC)
|
if (USE_DISCORD_RPC)
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET "10.7")
|
||||||
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/discord-rpc discord-rpc EXCLUDE_FROM_ALL)
|
add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/discord-rpc discord-rpc EXCLUDE_FROM_ALL)
|
||||||
list(APPEND FEATURES DISCORD_RPC)
|
list(APPEND FEATURES DISCORD_RPC)
|
||||||
include_directories(AFTER ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/discord-rpc/include)
|
include_directories(AFTER ${CMAKE_CURRENT_SOURCE_DIR}/src/third-party/discord-rpc/include)
|
||||||
|
|
|
@ -50,6 +50,7 @@ The following mappers are fully supported:
|
||||||
- MBC2
|
- MBC2
|
||||||
- MBC3
|
- MBC3
|
||||||
- MBC3+RTC
|
- MBC3+RTC
|
||||||
|
- MBC30
|
||||||
- MBC5
|
- MBC5
|
||||||
- MBC5+Rumble
|
- MBC5+Rumble
|
||||||
- MBC7
|
- MBC7
|
||||||
|
|
Binary file not shown.
After Width: | Height: | Size: 2.1 KiB |
|
@ -0,0 +1,3 @@
|
||||||
|
[testinfo]
|
||||||
|
skip=10
|
||||||
|
frames=1
|
Binary file not shown.
|
@ -12,6 +12,7 @@
|
||||||
#include <malloc.h>
|
#include <malloc.h>
|
||||||
|
|
||||||
#define THREAD_ENTRY void
|
#define THREAD_ENTRY void
|
||||||
|
#define THREAD_EXIT(RES) return
|
||||||
typedef ThreadFunc ThreadEntry;
|
typedef ThreadFunc ThreadEntry;
|
||||||
|
|
||||||
typedef LightLock Mutex;
|
typedef LightLock Mutex;
|
||||||
|
|
|
@ -20,6 +20,7 @@ CXX_GUARD_START
|
||||||
|
|
||||||
#define THREAD_ENTRY void*
|
#define THREAD_ENTRY void*
|
||||||
typedef THREAD_ENTRY (*ThreadEntry)(void*);
|
typedef THREAD_ENTRY (*ThreadEntry)(void*);
|
||||||
|
#define THREAD_EXIT(RES) return RES
|
||||||
|
|
||||||
typedef pthread_t Thread;
|
typedef pthread_t Thread;
|
||||||
typedef pthread_mutex_t Mutex;
|
typedef pthread_mutex_t Mutex;
|
||||||
|
|
|
@ -17,6 +17,7 @@ typedef struct {
|
||||||
} Condition;
|
} Condition;
|
||||||
#define THREAD_ENTRY int
|
#define THREAD_ENTRY int
|
||||||
typedef THREAD_ENTRY (*ThreadEntry)(void*);
|
typedef THREAD_ENTRY (*ThreadEntry)(void*);
|
||||||
|
#define THREAD_EXIT(RES) return RES
|
||||||
|
|
||||||
static inline int MutexInit(Mutex* mutex) {
|
static inline int MutexInit(Mutex* mutex) {
|
||||||
Mutex id = sceKernelCreateMutex("mutex", 0, 0, 0);
|
Mutex id = sceKernelCreateMutex("mutex", 0, 0, 0);
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include <switch.h>
|
#include <switch.h>
|
||||||
|
|
||||||
#define THREAD_ENTRY void
|
#define THREAD_ENTRY void
|
||||||
|
#define THREAD_EXIT(RES) return
|
||||||
typedef ThreadFunc ThreadEntry;
|
typedef ThreadFunc ThreadEntry;
|
||||||
typedef CondVar Condition;
|
typedef CondVar Condition;
|
||||||
|
|
||||||
|
|
|
@ -27,6 +27,8 @@
|
||||||
extern "C" {
|
extern "C" {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct option;
|
||||||
|
|
||||||
#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */
|
#define REPLACE_GETOPT /* use this getopt as the system getopt(3) */
|
||||||
|
|
||||||
#ifdef REPLACE_GETOPT
|
#ifdef REPLACE_GETOPT
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#define THREAD_ENTRY DWORD WINAPI
|
#define THREAD_ENTRY DWORD WINAPI
|
||||||
typedef THREAD_ENTRY ThreadEntry(LPVOID);
|
typedef THREAD_ENTRY ThreadEntry(LPVOID);
|
||||||
|
#define THREAD_EXIT(RES) return RES
|
||||||
|
|
||||||
typedef HANDLE Thread;
|
typedef HANDLE Thread;
|
||||||
typedef CRITICAL_SECTION Mutex;
|
typedef CRITICAL_SECTION Mutex;
|
||||||
|
|
|
@ -204,6 +204,7 @@ static inline Socket SocketOpenTCP(int port, const struct Address* bindAddress)
|
||||||
err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
|
err = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &enable, sizeof(enable));
|
||||||
#endif
|
#endif
|
||||||
if (err) {
|
if (err) {
|
||||||
|
SocketCloseQuiet(sock);
|
||||||
return INVALID_SOCKET;
|
return INVALID_SOCKET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,7 @@ void GBAMemoryInit(struct GBA* gba);
|
||||||
void GBAMemoryDeinit(struct GBA* gba);
|
void GBAMemoryDeinit(struct GBA* gba);
|
||||||
|
|
||||||
void GBAMemoryReset(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 GBALoad32(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||||
uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter);
|
||||||
|
|
|
@ -97,7 +97,7 @@ CXX_GUARD_START
|
||||||
#define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT)
|
#define mSCRIPT_TYPE_MS_CS(STRUCT) (&mSTStructConst_ ## STRUCT)
|
||||||
#define mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME) (&_mSTStructBindingType_ ## STRUCT ## _ ## NAME)
|
#define mSCRIPT_TYPE_MS_S_METHOD(STRUCT, NAME) (&_mSTStructBindingType_ ## STRUCT ## _ ## NAME)
|
||||||
#define mSCRIPT_TYPE_MS_PS(STRUCT) (&mSTStructPtr_ ## STRUCT)
|
#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_WSTR (&mSTStringWrapper)
|
||||||
#define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper)
|
#define mSCRIPT_TYPE_MS_WLIST (&mSTListWrapper)
|
||||||
#define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE)
|
#define mSCRIPT_TYPE_MS_W(TYPE) (&mSTWrapper_ ## TYPE)
|
||||||
|
|
|
@ -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>
|
|
@ -32,6 +32,8 @@
|
||||||
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
<string>${MACOSX_BUNDLE_COPYRIGHT}</string>
|
||||||
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
<key>NSSupportsAutomaticGraphicsSwitching</key>
|
||||||
<true/>
|
<true/>
|
||||||
|
<key>NSCameraUsageDescription</key>
|
||||||
|
<string>Only used when Game Boy Camera is selected and a physical camera is set</string>
|
||||||
<key>CFBundleDocumentTypes</key>
|
<key>CFBundleDocumentTypes</key>
|
||||||
<array>
|
<array>
|
||||||
<dict>
|
<dict>
|
||||||
|
@ -56,5 +58,7 @@
|
||||||
<string>Viewer</string>
|
<string>Viewer</string>
|
||||||
</dict>
|
</dict>
|
||||||
</array>
|
</array>
|
||||||
|
<key>LSApplicationCategoryType</key>
|
||||||
|
<string>public.app-category.games</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
|
@ -8,5 +8,6 @@ Name=mGBA
|
||||||
GenericName=Game Boy Advance Emulator
|
GenericName=Game Boy Advance Emulator
|
||||||
Comment=Nintendo Game Boy Advance Emulator
|
Comment=Nintendo Game Boy Advance Emulator
|
||||||
Categories=Game;Emulator;
|
Categories=Game;Emulator;
|
||||||
MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom;
|
MimeType=application/x-gameboy-advance-rom;application/x-agb-rom;application/x-gba-rom;application/x-gameboy-rom;application/x-gameboy-color-rom;
|
||||||
Keywords=emulator;Nintendo;advance;gba;Game Boy Advance;
|
Keywords=emulator;Nintendo;advance;gba;gbc;gb;Game Boy Advance;Game Boy Color; Game Boy;
|
||||||
|
StartupWMClass=mGBA
|
||||||
|
|
8753
res/nointro.dat
8753
res/nointro.dat
File diff suppressed because it is too large
Load Diff
|
@ -1,16 +1,15 @@
|
||||||
Akatsuki
|
Akatsuki
|
||||||
AVjoyu__Chan
|
|
||||||
Benedikt Feih
|
Benedikt Feih
|
||||||
Brandon
|
Brandon
|
||||||
Emily A. Bellows
|
Emily A. Bellows
|
||||||
G
|
EmuDeck
|
||||||
gocha
|
gocha
|
||||||
Jaime J. Denizard
|
Jaime J. Denizard
|
||||||
MichaelK__
|
MichaelK__
|
||||||
Miras Absar
|
Miras Absar
|
||||||
|
Molly Howell
|
||||||
|
Nic Losby (blurbdust)
|
||||||
Petru-Sebastian Toader
|
Petru-Sebastian Toader
|
||||||
Stevoisiak
|
Stevoisiak
|
||||||
Tyler Jenkins
|
|
||||||
William K. Leung
|
William K. Leung
|
||||||
Zach
|
|
||||||
Zhongchao Qian
|
Zhongchao Qian
|
||||||
|
|
|
@ -412,6 +412,13 @@ local gameRubyEn = Generation3En:new{
|
||||||
_speciesNameTable=0x1f716c,
|
_speciesNameTable=0x1f716c,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local gameRubyEnR1 = Generation3En:new{
|
||||||
|
name="Ruby (USA)",
|
||||||
|
_party=0x3004360,
|
||||||
|
_partyCount=0x3004350,
|
||||||
|
_speciesNameTable=0x1f7184,
|
||||||
|
}
|
||||||
|
|
||||||
local gameSapphireEn = Generation3En:new{
|
local gameSapphireEn = Generation3En:new{
|
||||||
name="Sapphire (USA)",
|
name="Sapphire (USA)",
|
||||||
_party=0x3004360,
|
_party=0x3004360,
|
||||||
|
@ -419,6 +426,13 @@ local gameSapphireEn = Generation3En:new{
|
||||||
_speciesNameTable=0x1f70fc,
|
_speciesNameTable=0x1f70fc,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
local gameSapphireEnR1 = Generation3En:new{
|
||||||
|
name="Sapphire (USA)",
|
||||||
|
_party=0x3004360,
|
||||||
|
_partyCount=0x3004350,
|
||||||
|
_speciesNameTable=0x1f7114,
|
||||||
|
}
|
||||||
|
|
||||||
local gameEmeraldEn = Generation3En:new{
|
local gameEmeraldEn = Generation3En:new{
|
||||||
name="Emerald (USA)",
|
name="Emerald (USA)",
|
||||||
_party=0x20244ec,
|
_party=0x20244ec,
|
||||||
|
@ -471,6 +485,10 @@ gameCrc32 = {
|
||||||
[0x7d527d62] = gameYellowEn,
|
[0x7d527d62] = gameYellowEn,
|
||||||
[0x84ee4776] = gameFireRedEnR1,
|
[0x84ee4776] = gameFireRedEnR1,
|
||||||
[0xdaffecec] = gameLeafGreenEnR1,
|
[0xdaffecec] = gameLeafGreenEnR1,
|
||||||
|
[0x61641576] = gameRubyEnR1, -- Rev 1
|
||||||
|
[0xaeac73e6] = gameRubyEnR1, -- Rev 2
|
||||||
|
[0xbafedae5] = gameSapphireEnR1, -- Rev 1
|
||||||
|
[0x9cc4410e] = gameSapphireEnR1, -- Rev 2
|
||||||
}
|
}
|
||||||
|
|
||||||
function printPartyStatus(game, buffer)
|
function printPartyStatus(game, buffer)
|
||||||
|
|
|
@ -36,7 +36,7 @@
|
||||||
#define ADDR_MODE_1_ASR ADDR_MODE_1_SHIFT(ASR)
|
#define ADDR_MODE_1_ASR ADDR_MODE_1_SHIFT(ASR)
|
||||||
#define ADDR_MODE_1_ROR \
|
#define ADDR_MODE_1_ROR \
|
||||||
ADDR_MODE_1_SHIFT(ROR) \
|
ADDR_MODE_1_SHIFT(ROR) \
|
||||||
if (!info->op3.shifterImm) { \
|
if ((info->operandFormat & ARM_OPERAND_SHIFT_IMMEDIATE_3) && !info->op3.shifterImm) { \
|
||||||
info->op3.shifterOp = ARM_SHIFT_RRX; \
|
info->op3.shifterOp = ARM_SHIFT_RRX; \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -663,7 +663,9 @@ DEFINE_INSTRUCTION_ARM(MRC, ARM_STUB)
|
||||||
|
|
||||||
// Begin miscellaneous definitions
|
// 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(ILL, ARM_ILL) // Illegal opcode
|
||||||
|
|
||||||
DEFINE_INSTRUCTION_ARM(MSR,
|
DEFINE_INSTRUCTION_ARM(MSR,
|
||||||
|
|
|
@ -381,7 +381,9 @@ DEFINE_LOAD_STORE_MULTIPLE_THUMB(PUSHR,
|
||||||
cpu->gprs[ARM_SP] = address)
|
cpu->gprs[ARM_SP] = address)
|
||||||
|
|
||||||
DEFINE_INSTRUCTION_THUMB(ILL, ARM_ILL)
|
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,
|
DEFINE_INSTRUCTION_THUMB(B,
|
||||||
int16_t immediate = (opcode & 0x07FF) << 5;
|
int16_t immediate = (opcode & 0x07FF) << 5;
|
||||||
cpu->gprs[ARM_PC] += (((int32_t) immediate) >> 4);
|
cpu->gprs[ARM_PC] += (((int32_t) immediate) >> 4);
|
||||||
|
@ -401,11 +403,7 @@ DEFINE_INSTRUCTION_THUMB(BL2,
|
||||||
DEFINE_INSTRUCTION_THUMB(BX,
|
DEFINE_INSTRUCTION_THUMB(BX,
|
||||||
int rm = (opcode >> 3) & 0xF;
|
int rm = (opcode >> 3) & 0xF;
|
||||||
_ARMSetMode(cpu, cpu->gprs[rm] & 0x00000001);
|
_ARMSetMode(cpu, cpu->gprs[rm] & 0x00000001);
|
||||||
int misalign = 0;
|
cpu->gprs[ARM_PC] = cpu->gprs[rm] & 0xFFFFFFFE;
|
||||||
if (rm == ARM_PC) {
|
|
||||||
misalign = cpu->gprs[rm] & 0x00000002;
|
|
||||||
}
|
|
||||||
cpu->gprs[ARM_PC] = (cpu->gprs[rm] & 0xFFFFFFFE) - misalign;
|
|
||||||
if (cpu->executionMode == MODE_THUMB) {
|
if (cpu->executionMode == MODE_THUMB) {
|
||||||
currentCycles += ThumbWritePC(cpu);
|
currentCycles += ThumbWritePC(cpu);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -34,12 +34,15 @@ void mCacheSetDeinit(struct mCacheSet* cache) {
|
||||||
for (i = 0; i < mMapCacheSetSize(&cache->maps); ++i) {
|
for (i = 0; i < mMapCacheSetSize(&cache->maps); ++i) {
|
||||||
mMapCacheDeinit(mMapCacheSetGetPointer(&cache->maps, i));
|
mMapCacheDeinit(mMapCacheSetGetPointer(&cache->maps, i));
|
||||||
}
|
}
|
||||||
|
mMapCacheSetDeinit(&cache->maps);
|
||||||
for (i = 0; i < mBitmapCacheSetSize(&cache->bitmaps); ++i) {
|
for (i = 0; i < mBitmapCacheSetSize(&cache->bitmaps); ++i) {
|
||||||
mBitmapCacheDeinit(mBitmapCacheSetGetPointer(&cache->bitmaps, i));
|
mBitmapCacheDeinit(mBitmapCacheSetGetPointer(&cache->bitmaps, i));
|
||||||
}
|
}
|
||||||
|
mBitmapCacheSetDeinit(&cache->bitmaps);
|
||||||
for (i = 0; i < mTileCacheSetSize(&cache->tiles); ++i) {
|
for (i = 0; i < mTileCacheSetSize(&cache->tiles); ++i) {
|
||||||
mTileCacheDeinit(mTileCacheSetGetPointer(&cache->tiles, i));
|
mTileCacheDeinit(mTileCacheSetGetPointer(&cache->tiles, i));
|
||||||
}
|
}
|
||||||
|
mTileCacheSetDeinit(&cache->tiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mCacheSetAssignVRAM(struct mCacheSet* cache, void* vram) {
|
void mCacheSetAssignVRAM(struct mCacheSet* cache, void* vram) {
|
||||||
|
|
|
@ -489,8 +489,10 @@ bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
char* name = gbkToUtf8(&cheat[1], end - cheat - 1);
|
char* name = gbkToUtf8(&cheat[1], end - cheat - 1);
|
||||||
strncpy(cheatName, name, sizeof(cheatName) - 1);
|
if (name) {
|
||||||
free(name);
|
strncpy(cheatName, name, sizeof(cheatName) - 1);
|
||||||
|
free(name);
|
||||||
|
}
|
||||||
cheatNameLength = strlen(cheatName);
|
cheatNameLength = strlen(cheatName);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
@ -501,7 +503,10 @@ bool mCheatParseEZFChtFile(struct mCheatDevice* device, struct VFile* vf) {
|
||||||
}
|
}
|
||||||
if (strncmp(cheat, "ON", eq - cheat) != 0) {
|
if (strncmp(cheat, "ON", eq - cheat) != 0) {
|
||||||
char* subname = gbkToUtf8(cheat, eq - cheat);
|
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 = device->createSet(device, cheatName);
|
||||||
set->enabled = false;
|
set->enabled = false;
|
||||||
|
@ -622,6 +627,9 @@ void mCheatAutosave(struct mCheatDevice* device) {
|
||||||
if (!device->autosave) {
|
if (!device->autosave) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (!device->p->dirs.cheats) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC);
|
struct VFile* vf = mDirectorySetOpenSuffix(&device->p->dirs, device->p->dirs.cheats, ".cheats", O_WRONLY | O_CREAT | O_TRUNC);
|
||||||
if (!vf) {
|
if (!vf) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -169,14 +169,14 @@ void mCoreConfigDeinit(struct mCoreConfig* config) {
|
||||||
|
|
||||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||||
bool mCoreConfigLoad(struct mCoreConfig* config) {
|
bool mCoreConfigLoad(struct mCoreConfig* config) {
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX + 1];
|
||||||
mCoreConfigDirectory(path, PATH_MAX);
|
mCoreConfigDirectory(path, PATH_MAX);
|
||||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||||
return mCoreConfigLoadPath(config, path);
|
return mCoreConfigLoadPath(config, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mCoreConfigSave(const struct mCoreConfig* config) {
|
bool mCoreConfigSave(const struct mCoreConfig* config) {
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX + 1];
|
||||||
mCoreConfigDirectory(path, PATH_MAX);
|
mCoreConfigDirectory(path, PATH_MAX);
|
||||||
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
strncat(path, PATH_SEP "config.ini", PATH_MAX - strlen(path));
|
||||||
return mCoreConfigSavePath(config, path);
|
return mCoreConfigSavePath(config, path);
|
||||||
|
@ -304,7 +304,7 @@ void mCoreConfigPortablePath(char* out, size_t outLength) {
|
||||||
CFRelease(suburl);
|
CFRelease(suburl);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
strncat(out, PATH_SEP "portable.ini", outLength - strlen(out));
|
strncat(out, PATH_SEP "portable.ini", outLength - strlen(out) - 1);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -127,6 +127,7 @@ struct mCore* mCoreFind(const char* path) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mCoreLoadFile(struct mCore* core, const char* path) {
|
bool mCoreLoadFile(struct mCore* core, const char* path) {
|
||||||
|
core->unloadROM(core);
|
||||||
#ifdef FIXED_ROM_BUFFER
|
#ifdef FIXED_ROM_BUFFER
|
||||||
return mCorePreloadFile(core, path);
|
return mCorePreloadFile(core, path);
|
||||||
#else
|
#else
|
||||||
|
@ -236,20 +237,35 @@ bool mCoreAutoloadPatch(struct mCore* core) {
|
||||||
if (!core->dirs.patch) {
|
if (!core->dirs.patch) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY)) ||
|
struct VFile* vf = NULL;
|
||||||
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY)) ||
|
if (!vf) {
|
||||||
core->loadPatch(core, mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY));
|
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".bps", O_RDONLY);
|
||||||
|
}
|
||||||
|
if (!vf) {
|
||||||
|
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ups", O_RDONLY);
|
||||||
|
}
|
||||||
|
if (!vf) {
|
||||||
|
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.patch, ".ips", O_RDONLY);
|
||||||
|
}
|
||||||
|
if (!vf) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
bool result = core->loadPatch(core, vf);
|
||||||
|
vf->close(vf);
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mCoreAutoloadCheats(struct mCore* core) {
|
bool mCoreAutoloadCheats(struct mCore* core) {
|
||||||
bool success = true;
|
bool success = !!core->dirs.cheats;
|
||||||
int cheatAuto;
|
int cheatAuto;
|
||||||
if (!mCoreConfigGetIntValue(&core->config, "cheatAutoload", &cheatAuto) || cheatAuto) {
|
if (success && (!mCoreConfigGetIntValue(&core->config, "cheatAutoload", &cheatAuto) || cheatAuto)) {
|
||||||
struct VFile* vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.cheats, ".cheats", O_RDONLY);
|
struct VFile* vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.cheats, ".cheats", O_RDONLY);
|
||||||
if (vf) {
|
if (vf) {
|
||||||
struct mCheatDevice* device = core->cheatDevice(core);
|
struct mCheatDevice* device = core->cheatDevice(core);
|
||||||
success = mCheatParseFile(device, vf);
|
success = mCheatParseFile(device, vf);
|
||||||
vf->close(vf);
|
vf->close(vf);
|
||||||
|
} else {
|
||||||
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!mCoreConfigGetIntValue(&core->config, "cheatAutosave", &cheatAuto) || cheatAuto) {
|
if (!mCoreConfigGetIntValue(&core->config, "cheatAutosave", &cheatAuto) || cheatAuto) {
|
||||||
|
|
|
@ -110,6 +110,9 @@ struct VFile* mDirectorySetOpenPath(struct mDirectorySet* dirs, const char* path
|
||||||
}
|
}
|
||||||
|
|
||||||
struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* dir, const char* suffix, int mode) {
|
struct VFile* mDirectorySetOpenSuffix(struct mDirectorySet* dirs, struct VDir* dir, const char* suffix, int mode) {
|
||||||
|
if (!dir) {
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
char name[PATH_MAX + 1] = "";
|
char name[PATH_MAX + 1] = "";
|
||||||
snprintf(name, sizeof(name) - 1, "%s%s", dirs->baseName, suffix);
|
snprintf(name, sizeof(name) - 1, "%s%s", dirs->baseName, suffix);
|
||||||
return dir->openFile(dir, name, mode);
|
return dir->openFile(dir, name, mode);
|
||||||
|
|
|
@ -95,6 +95,10 @@
|
||||||
#cmakedefine USE_LIBZIP
|
#cmakedefine USE_LIBZIP
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef USE_LUA
|
||||||
|
#cmakedefine USE_LUA
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef USE_LZMA
|
#ifndef USE_LZMA
|
||||||
#cmakedefine USE_LZMA
|
#cmakedefine USE_LZMA
|
||||||
#endif
|
#endif
|
||||||
|
@ -121,8 +125,44 @@
|
||||||
|
|
||||||
// HAVE flags
|
// HAVE flags
|
||||||
|
|
||||||
|
#ifndef HAVE_CRC32
|
||||||
|
#cmakedefine HAVE_CRC32
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_LOCALTIME_R
|
||||||
|
#cmakedefine HAVE_LOCALTIME_R
|
||||||
|
#endif
|
||||||
|
|
||||||
#ifndef HAVE_POPCOUNT32
|
#ifndef HAVE_POPCOUNT32
|
||||||
#cmakedefine HAVE_POPCOUNT32
|
#cmakedefine HAVE_POPCOUNT32
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_PTHREAD_NP_H
|
||||||
|
#cmakedefine HAVE_PTHREAD_NP_H
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_PTHREAD_SETNAME_NP
|
||||||
|
#cmakedefine HAVE_PTHREAD_SETNAME_NP
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_STRDUP
|
||||||
|
#cmakedefine HAVE_STRDUP
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_STRLCPY
|
||||||
|
#cmakedefine HAVE_STRLCPY
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_STRDUP
|
||||||
|
#cmakedefine HAVE_STRDUP
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_STRTOF_L
|
||||||
|
#cmakedefine HAVE_STRTOF_L
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifndef HAVE_XLOCALE
|
||||||
|
#cmakedefine HAVE_XLOCALE
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -50,7 +50,7 @@ const char* mLogCategoryName(int category) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* mLogCategoryId(int category) {
|
const char* mLogCategoryId(int category) {
|
||||||
if (category < MAX_CATEGORY) {
|
if (category >= 0 && category < MAX_CATEGORY) {
|
||||||
return _categoryIds[category];
|
return _categoryIds[category];
|
||||||
}
|
}
|
||||||
return NULL;
|
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)) {
|
if (!context->filter || mLogFilterTest(context->filter, category, level)) {
|
||||||
context->log(context, category, level, format, args);
|
context->log(context, category, level, format, args);
|
||||||
}
|
}
|
||||||
|
va_end(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
void mLogFilterInit(struct mLogFilter* filter) {
|
void mLogFilterInit(struct mLogFilter* filter) {
|
||||||
|
|
|
@ -175,7 +175,7 @@ THREAD_ENTRY _rewindThread(void* context) {
|
||||||
rewindContext->ready = false;
|
rewindContext->ready = false;
|
||||||
}
|
}
|
||||||
MutexUnlock(&rewindContext->mutex);
|
MutexUnlock(&rewindContext->mutex);
|
||||||
return 0;
|
THREAD_EXIT(0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
|
|
@ -713,33 +713,33 @@ static struct mScriptTextBuffer* _mScriptConsoleCreateBuffer(struct mScriptConso
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void mScriptConsoleLog(struct mScriptConsole* console, struct mScriptString* msg) {
|
static void mScriptConsoleLog(struct mScriptConsole* console, const char* msg) {
|
||||||
if (console->logger) {
|
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 {
|
} 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) {
|
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 {
|
} 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) {
|
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 {
|
} 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, log, mScriptConsoleLog, 1, CHARP, msg);
|
||||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, warn, mScriptConsoleWarn, 1, STR, msg);
|
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, warn, mScriptConsoleWarn, 1, CHARP, msg);
|
||||||
mSCRIPT_DECLARE_STRUCT_VOID_METHOD(mScriptConsole, error, mScriptConsoleError, 1, STR, 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_DECLARE_STRUCT_METHOD_WITH_DEFAULTS(mScriptConsole, S(mScriptTextBuffer), createBuffer, _mScriptConsoleCreateBuffer, 1, CHARP, name);
|
||||||
|
|
||||||
mSCRIPT_DEFINE_STRUCT(mScriptConsole)
|
mSCRIPT_DEFINE_STRUCT(mScriptConsole)
|
||||||
|
|
|
@ -131,6 +131,9 @@ bool mStateExtdataDeserialize(struct mStateExtdata* extdata, struct VFile* vf) {
|
||||||
if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
|
if (vf->seek(vf, header.offset, SEEK_SET) < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (header.size <= 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
struct mStateExtdataItem item = {
|
struct mStateExtdataItem item = {
|
||||||
.data = malloc(header.size),
|
.data = malloc(header.size),
|
||||||
.size = header.size,
|
.size = header.size,
|
||||||
|
|
|
@ -58,8 +58,19 @@ static void _waitOnInterrupt(struct mCoreThreadInternal* threadContext) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _pokeRequest(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;
|
threadContext->state = mTHREAD_REQUEST;
|
||||||
|
break;
|
||||||
|
case mTHREAD_INITIALIZED:
|
||||||
|
case mTHREAD_REQUEST:
|
||||||
|
case mTHREAD_INTERRUPTED:
|
||||||
|
case mTHREAD_INTERRUPTING:
|
||||||
|
case mTHREAD_EXITING:
|
||||||
|
case mTHREAD_SHUTDOWN:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -439,7 +450,7 @@ static THREAD_ENTRY _mCoreThreadRun(void* context) {
|
||||||
}
|
}
|
||||||
logger->filter = NULL;
|
logger->filter = NULL;
|
||||||
|
|
||||||
return 0;
|
THREAD_EXIT(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mCoreThreadStart(struct mCoreThread* threadContext) {
|
bool mCoreThreadStart(struct mCoreThread* threadContext) {
|
||||||
|
|
|
@ -597,7 +597,7 @@ static struct ParseTree* _parseTree(const char** string) {
|
||||||
struct ParseTree* tree = NULL;
|
struct ParseTree* tree = NULL;
|
||||||
if (!error) {
|
if (!error) {
|
||||||
tree = parseTreeCreate();
|
tree = parseTreeCreate();
|
||||||
parseLexedExpression(tree, &lv);
|
error = !parseLexedExpression(tree, &lv);
|
||||||
}
|
}
|
||||||
lexFree(&lv);
|
lexFree(&lv);
|
||||||
LexVectorClear(&lv);
|
LexVectorClear(&lv);
|
||||||
|
@ -726,10 +726,14 @@ static void _listWatchpoints(struct CLIDebugger* debugger, struct CLIDebugVector
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _trace(struct CLIDebugger* debugger, struct CLIDebugVector* dv) {
|
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);
|
debugger->backend->printf(debugger->backend, "%s\n", ERROR_MISSING_ARGS);
|
||||||
return;
|
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;
|
debugger->traceRemaining = dv->intValue;
|
||||||
if (debugger->traceVf) {
|
if (debugger->traceVf) {
|
||||||
|
|
|
@ -53,7 +53,11 @@ struct mDebugger* mDebuggerCreate(enum mDebuggerType type, struct mCore* core) {
|
||||||
case DEBUGGER_GDB:
|
case DEBUGGER_GDB:
|
||||||
#ifdef USE_GDB_STUB
|
#ifdef USE_GDB_STUB
|
||||||
GDBStubCreate(&debugger->gdb);
|
GDBStubCreate(&debugger->gdb);
|
||||||
GDBStubListen(&debugger->gdb, 2345, 0, GDB_WATCHPOINT_STANDARD_LOGIC);
|
struct Address localHost = {
|
||||||
|
.version = IPV4,
|
||||||
|
.ipv4 = 0x7F000001
|
||||||
|
};
|
||||||
|
GDBStubListen(&debugger->gdb, 2345, &localHost, GDB_WATCHPOINT_STANDARD_LOGIC);
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
case DEBUGGER_NONE:
|
case DEBUGGER_NONE:
|
||||||
|
|
|
@ -354,6 +354,10 @@ static void _writeGPRs(struct GDBStub* stub, const char* message) {
|
||||||
_sendMessage(stub);
|
_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) {
|
static void _readGPRs(struct GDBStub* stub, const char* message) {
|
||||||
struct ARMCore* cpu = stub->d.core->cpu;
|
struct ARMCore* cpu = stub->d.core->cpu;
|
||||||
UNUSED(message);
|
UNUSED(message);
|
||||||
|
@ -367,7 +371,7 @@ static void _readGPRs(struct GDBStub* stub, const char* message) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Program counter
|
// 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;
|
i += 8;
|
||||||
|
|
||||||
// CPU status
|
// CPU status
|
||||||
|
@ -417,8 +421,10 @@ static void _readRegister(struct GDBStub* stub, const char* message) {
|
||||||
unsigned i = 0;
|
unsigned i = 0;
|
||||||
uint32_t reg = _readHex(readAddress, &i);
|
uint32_t reg = _readHex(readAddress, &i);
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
if (reg < 0x10) {
|
if (reg < ARM_PC) {
|
||||||
value = cpu->gprs[reg];
|
value = cpu->gprs[reg];
|
||||||
|
} else if (reg == ARM_PC) {
|
||||||
|
value = _readPC(cpu);
|
||||||
} else if (reg == 0x19) {
|
} else if (reg == 0x19) {
|
||||||
value = cpu->cpsr.packed;
|
value = cpu->cpsr.packed;
|
||||||
} else {
|
} else {
|
||||||
|
@ -457,7 +463,7 @@ static void _processQSupportedCommand(struct GDBStub* stub, const char* message)
|
||||||
}
|
}
|
||||||
message = end + 1;
|
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) {
|
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) {
|
static void _processQWriteCommand(struct GDBStub* stub, const char* message) {
|
||||||
stub->outgoing[0] = '\0';
|
stub->outgoing[0] = '\0';
|
||||||
if (!strncmp("StartNoAckMode#", message, 16)) {
|
if (!strncmp("StartNoAckMode#", message, 15)) {
|
||||||
stub->lineAck = GDB_ACK_OFF;
|
stub->lineAck = GDB_ACK_OFF;
|
||||||
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
strncpy(stub->outgoing, "OK", GDB_STUB_MAX_LINE - 4);
|
||||||
}
|
}
|
||||||
|
@ -844,6 +850,7 @@ void GDBStubUpdate(struct GDBStub* stub) {
|
||||||
} else {
|
} else {
|
||||||
goto connectionLost;
|
goto connectionLost;
|
||||||
}
|
}
|
||||||
|
SocketSetTCPPush(stub->connection, 1);
|
||||||
}
|
}
|
||||||
while (true) {
|
while (true) {
|
||||||
if (stub->shouldBlock) {
|
if (stub->shouldBlock) {
|
||||||
|
|
|
@ -34,6 +34,14 @@ CXX_GUARD_START
|
||||||
#define FFMPEG_USE_PACKET_UNREF
|
#define FFMPEG_USE_PACKET_UNREF
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(59, 24, 0)
|
||||||
|
#define FFMPEG_USE_NEW_CH_LAYOUT
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if LIBAVCODEC_VERSION_INT >= AV_VERSION_INT(61, 13, 100)
|
||||||
|
#define FFMPEG_USE_GET_SUPPORTED_CONFIG
|
||||||
|
#endif
|
||||||
|
|
||||||
static inline enum AVPixelFormat mColorFormatToFFmpegPixFmt(enum mColorFormat format) {
|
static inline enum AVPixelFormat mColorFormatToFFmpegPixFmt(enum mColorFormat format) {
|
||||||
switch (format) {
|
switch (format) {
|
||||||
#ifndef USE_LIBAV
|
#ifndef USE_LIBAV
|
||||||
|
|
|
@ -62,13 +62,13 @@ void FFmpegEncoderInit(struct FFmpegEncoder* encoder) {
|
||||||
encoder->audioCodec = NULL;
|
encoder->audioCodec = NULL;
|
||||||
encoder->videoCodec = NULL;
|
encoder->videoCodec = NULL;
|
||||||
encoder->containerFormat = NULL;
|
encoder->containerFormat = NULL;
|
||||||
|
encoder->isampleRate = PREFERRED_SAMPLE_RATE;
|
||||||
FFmpegEncoderSetAudio(encoder, "flac", 0);
|
FFmpegEncoderSetAudio(encoder, "flac", 0);
|
||||||
FFmpegEncoderSetVideo(encoder, "libx264", 0, 0);
|
FFmpegEncoderSetVideo(encoder, "libx264", 0, 0);
|
||||||
FFmpegEncoderSetContainer(encoder, "matroska");
|
FFmpegEncoderSetContainer(encoder, "matroska");
|
||||||
FFmpegEncoderSetDimensions(encoder, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
|
FFmpegEncoderSetDimensions(encoder, GBA_VIDEO_HORIZONTAL_PIXELS, GBA_VIDEO_VERTICAL_PIXELS);
|
||||||
encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS;
|
encoder->iwidth = GBA_VIDEO_HORIZONTAL_PIXELS;
|
||||||
encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS;
|
encoder->iheight = GBA_VIDEO_VERTICAL_PIXELS;
|
||||||
encoder->isampleRate = PREFERRED_SAMPLE_RATE;
|
|
||||||
encoder->frameskip = 1;
|
encoder->frameskip = 1;
|
||||||
encoder->skipResidue = 0;
|
encoder->skipResidue = 0;
|
||||||
encoder->loop = false;
|
encoder->loop = false;
|
||||||
|
@ -134,18 +134,28 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!codec->sample_fmts) {
|
const enum AVSampleFormat* formats = NULL;
|
||||||
|
#ifdef FFMPEG_USE_GET_SUPPORTED_CONFIG
|
||||||
|
if (avcodec_get_supported_config(NULL, codec, AV_CODEC_CONFIG_SAMPLE_FORMAT, 0, (const void**) &formats, NULL) < 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
#else
|
||||||
|
formats = codec->sample_fmts;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
if (!formats) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
size_t i;
|
size_t i;
|
||||||
size_t j;
|
size_t j;
|
||||||
int priority = INT_MAX;
|
int priority = INT_MAX;
|
||||||
encoder->sampleFormat = AV_SAMPLE_FMT_NONE;
|
encoder->sampleFormat = AV_SAMPLE_FMT_NONE;
|
||||||
for (i = 0; codec->sample_fmts[i] != AV_SAMPLE_FMT_NONE; ++i) {
|
for (i = 0; formats[i] != AV_SAMPLE_FMT_NONE; ++i) {
|
||||||
for (j = 0; j < sizeof(priorities) / sizeof(*priorities); ++j) {
|
for (j = 0; j < sizeof(priorities) / sizeof(*priorities); ++j) {
|
||||||
if (codec->sample_fmts[i] == priorities[j].format && priority > priorities[j].priority) {
|
if (formats[i] == priorities[j].format && priority > priorities[j].priority) {
|
||||||
priority = priorities[j].priority;
|
priority = priorities[j].priority;
|
||||||
encoder->sampleFormat = codec->sample_fmts[i];
|
encoder->sampleFormat = formats[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,20 +163,47 @@ bool FFmpegEncoderSetAudio(struct FFmpegEncoder* encoder, const char* acodec, un
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
encoder->sampleRate = encoder->isampleRate;
|
encoder->sampleRate = encoder->isampleRate;
|
||||||
if (codec->supported_samplerates) {
|
|
||||||
for (i = 0; codec->supported_samplerates[i]; ++i) {
|
|
||||||
if (codec->supported_samplerates[i] < encoder->isampleRate) {
|
|
||||||
|
const int* sampleRates = NULL;
|
||||||
|
#ifdef FFMPEG_USE_GET_SUPPORTED_CONFIG
|
||||||
|
if (avcodec_get_supported_config(NULL, codec, AV_CODEC_CONFIG_SAMPLE_RATE, 0, (const void**) &sampleRates, NULL) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
sampleRates = codec->supported_samplerates;
|
||||||
|
#endif
|
||||||
|
if (sampleRates) {
|
||||||
|
bool gotSampleRate = false;
|
||||||
|
int highestSampleRate = 0;
|
||||||
|
for (i = 0; sampleRates[i]; ++i) {
|
||||||
|
if (sampleRates[i] > highestSampleRate) {
|
||||||
|
highestSampleRate = sampleRates[i];
|
||||||
|
}
|
||||||
|
if (sampleRates[i] < encoder->isampleRate) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (encoder->sampleRate == encoder->isampleRate || encoder->sampleRate > codec->supported_samplerates[i]) {
|
if (!gotSampleRate || encoder->sampleRate > sampleRates[i]) {
|
||||||
encoder->sampleRate = codec->supported_samplerates[i];
|
encoder->sampleRate = 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) {
|
} else if (codec->id == AV_CODEC_ID_FLAC) {
|
||||||
// HACK: FLAC doesn't support > 65535Hz unless it's divisible by 10
|
// HACK: FLAC doesn't support > 65535Hz unless it's divisible by 10
|
||||||
if (encoder->sampleRate >= 65535) {
|
if (encoder->sampleRate >= 65535) {
|
||||||
encoder->sampleRate -= encoder->isampleRate % 10;
|
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) {
|
} 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
|
// HACK: AAC doesn't support 32768Hz (it rounds to 32000), but libfaac doesn't tell us that
|
||||||
encoder->sampleRate = 48000;
|
encoder->sampleRate = 48000;
|
||||||
|
@ -215,11 +252,19 @@ bool FFmpegEncoderSetVideo(struct FFmpegEncoder* encoder, const char* vcodec, in
|
||||||
size_t j;
|
size_t j;
|
||||||
int priority = INT_MAX;
|
int priority = INT_MAX;
|
||||||
encoder->pixFormat = AV_PIX_FMT_NONE;
|
encoder->pixFormat = AV_PIX_FMT_NONE;
|
||||||
for (i = 0; codec->pix_fmts[i] != AV_PIX_FMT_NONE; ++i) {
|
const enum AVPixelFormat* formats;
|
||||||
|
#ifdef FFMPEG_USE_GET_SUPPORTED_CONFIG
|
||||||
|
if (avcodec_get_supported_config(NULL, codec, AV_CODEC_CONFIG_PIX_FORMAT, 0, (const void**) &formats, NULL) < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
formats = codec->pix_fmts;
|
||||||
|
#endif
|
||||||
|
for (i = 0; formats[i] != AV_PIX_FMT_NONE; ++i) {
|
||||||
for (j = 0; j < sizeof(priorities) / sizeof(*priorities); ++j) {
|
for (j = 0; j < sizeof(priorities) / sizeof(*priorities); ++j) {
|
||||||
if (codec->pix_fmts[i] == priorities[j].format && priority > priorities[j].priority) {
|
if (formats[i] == priorities[j].format && priority > priorities[j].priority) {
|
||||||
priority = priorities[j].priority;
|
priority = priorities[j].priority;
|
||||||
encoder->pixFormat = codec->pix_fmts[i];
|
encoder->pixFormat = formats[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -304,8 +349,12 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
||||||
encoder->audio = encoder->audioStream->codec;
|
encoder->audio = encoder->audioStream->codec;
|
||||||
#endif
|
#endif
|
||||||
encoder->audio->bit_rate = encoder->audioBitrate;
|
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->channels = 2;
|
||||||
encoder->audio->channel_layout = AV_CH_LAYOUT_STEREO;
|
encoder->audio->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||||
|
#endif
|
||||||
encoder->audio->sample_rate = encoder->sampleRate;
|
encoder->audio->sample_rate = encoder->sampleRate;
|
||||||
encoder->audio->sample_fmt = encoder->sampleFormat;
|
encoder->audio->sample_fmt = encoder->sampleFormat;
|
||||||
AVDictionary* opts = 0;
|
AVDictionary* opts = 0;
|
||||||
|
@ -330,7 +379,11 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
||||||
encoder->audioFrame->nb_samples = encoder->audio->frame_size;
|
encoder->audioFrame->nb_samples = encoder->audio->frame_size;
|
||||||
encoder->audioFrame->format = encoder->audio->sample_fmt;
|
encoder->audioFrame->format = encoder->audio->sample_fmt;
|
||||||
encoder->audioFrame->pts = 0;
|
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;
|
encoder->audioFrame->channel_layout = AV_CH_LAYOUT_STEREO;
|
||||||
|
#endif
|
||||||
_ffmpegOpenResampleContext(encoder);
|
_ffmpegOpenResampleContext(encoder);
|
||||||
av_frame_get_buffer(encoder->audioFrame, 0);
|
av_frame_get_buffer(encoder->audioFrame, 0);
|
||||||
|
|
||||||
|
@ -360,7 +413,9 @@ bool FFmpegEncoderOpen(struct FFmpegEncoder* encoder, const char* outfile) {
|
||||||
encoder->videoStream = avformat_new_stream(encoder->context, vcodec);
|
encoder->videoStream = avformat_new_stream(encoder->context, vcodec);
|
||||||
encoder->video = encoder->videoStream->codec;
|
encoder->video = encoder->videoStream->codec;
|
||||||
#endif
|
#endif
|
||||||
encoder->video->bit_rate = encoder->videoBitrate;
|
if (encoder->videoBitrate >= 0) {
|
||||||
|
encoder->video->bit_rate = encoder->videoBitrate;
|
||||||
|
}
|
||||||
encoder->video->width = encoder->width;
|
encoder->video->width = encoder->width;
|
||||||
encoder->video->height = encoder->height;
|
encoder->video->height = encoder->height;
|
||||||
encoder->video->time_base = (AVRational) { encoder->frameCycles * encoder->frameskip, encoder->cycles };
|
encoder->video->time_base = (AVRational) { encoder->frameCycles * encoder->frameskip, encoder->cycles };
|
||||||
|
@ -881,7 +936,7 @@ void FFmpegEncoderSetInputSampleRate(struct FFmpegEncoder* encoder, int sampleRa
|
||||||
}
|
}
|
||||||
|
|
||||||
void _ffmpegOpenResampleContext(struct FFmpegEncoder* encoder) {
|
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);
|
encoder->audioBuffer = av_malloc(encoder->audioBufferSize);
|
||||||
#ifdef USE_LIBAVRESAMPLE
|
#ifdef USE_LIBAVRESAMPLE
|
||||||
encoder->resampleContext = avresample_alloc_context();
|
encoder->resampleContext = avresample_alloc_context();
|
||||||
|
@ -892,9 +947,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, "in_sample_fmt", AV_SAMPLE_FMT_S16, 0);
|
||||||
av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0);
|
av_opt_set_int(encoder->resampleContext, "out_sample_fmt", encoder->sampleFormat, 0);
|
||||||
avresample_open(encoder->resampleContext);
|
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
|
#else
|
||||||
encoder->resampleContext = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, encoder->sampleFormat, encoder->sampleRate,
|
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);
|
AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16, encoder->isampleRate, 0, NULL);
|
||||||
|
#endif
|
||||||
swr_init(encoder->resampleContext);
|
swr_init(encoder->resampleContext);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
#include <mgba/core/core.h>
|
#include <mgba/core/core.h>
|
||||||
#include "feature/gui/gui-runner.h"
|
#include "feature/gui/gui-runner.h"
|
||||||
#include <mgba-util/gui/menu.h>
|
#include <mgba-util/gui/menu.h>
|
||||||
|
#include <mgba-util/string.h>
|
||||||
|
|
||||||
enum mGUICheatAction {
|
enum mGUICheatAction {
|
||||||
CHEAT_BACK = 0,
|
CHEAT_BACK = 0,
|
||||||
|
|
|
@ -31,7 +31,7 @@ static bool _biosNamed(const char* name) {
|
||||||
char ext[PATH_MAX + 1] = {};
|
char ext[PATH_MAX + 1] = {};
|
||||||
separatePath(name, NULL, NULL, ext);
|
separatePath(name, NULL, NULL, ext);
|
||||||
|
|
||||||
if (strstr(name, "bios")) {
|
if (strcasestr(name, "bios")) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (!strncmp(ext, "bin", PATH_MAX)) {
|
if (!strncmp(ext, "bin", PATH_MAX)) {
|
||||||
|
|
|
@ -492,6 +492,7 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
|
||||||
}
|
}
|
||||||
mLOG(GUI_RUNNER, INFO, "Game starting");
|
mLOG(GUI_RUNNER, INFO, "Game starting");
|
||||||
runner->fps = 0;
|
runner->fps = 0;
|
||||||
|
bool fastForward = false;
|
||||||
while (running) {
|
while (running) {
|
||||||
CircleBufferClear(&runner->fpsBuffer);
|
CircleBufferClear(&runner->fpsBuffer);
|
||||||
runner->totalDelta = 0;
|
runner->totalDelta = 0;
|
||||||
|
@ -500,7 +501,6 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
|
||||||
runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec;
|
runner->lastFpsCheck = 1000000LL * tv.tv_sec + tv.tv_usec;
|
||||||
|
|
||||||
int frame = 0;
|
int frame = 0;
|
||||||
bool fastForward = false;
|
|
||||||
while (running) {
|
while (running) {
|
||||||
if (runner->running) {
|
if (runner->running) {
|
||||||
running = runner->running(runner);
|
running = runner->running(runner);
|
||||||
|
@ -648,6 +648,9 @@ void mGUIRun(struct mGUIRunner* runner, const char* path) {
|
||||||
runner->core->reset(runner->core);
|
runner->core->reset(runner->core);
|
||||||
break;
|
break;
|
||||||
case RUNNER_SAVE_STATE:
|
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);
|
mCoreSaveState(runner->core, item->data.v.u >> 16, SAVESTATE_SCREENSHOT | SAVESTATE_SAVEDATA | SAVESTATE_RTC | SAVESTATE_METADATA);
|
||||||
break;
|
break;
|
||||||
case RUNNER_LOAD_STATE:
|
case RUNNER_LOAD_STATE:
|
||||||
|
@ -791,5 +794,6 @@ THREAD_ENTRY mGUIAutosaveThread(void* context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MutexUnlock(&autosave->mutex);
|
MutexUnlock(&autosave->mutex);
|
||||||
|
THREAD_EXIT(0);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -17,7 +17,7 @@ struct NoIntroDB {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct NoIntroDB* NoIntroDBLoad(const char* path) {
|
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)) {
|
if (sqlite3_open_v2(path, &db->db, SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, NULL)) {
|
||||||
goto error;
|
goto error;
|
||||||
|
@ -60,9 +60,6 @@ struct NoIntroDB* NoIntroDBLoad(const char* path) {
|
||||||
return db;
|
return db;
|
||||||
|
|
||||||
error:
|
error:
|
||||||
if (db->crc32) {
|
|
||||||
sqlite3_finalize(db->crc32);
|
|
||||||
}
|
|
||||||
NoIntroDBDestroy(db);
|
NoIntroDBDestroy(db);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
|
@ -73,6 +70,7 @@ bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf) {
|
||||||
|
|
||||||
sqlite3_stmt* gamedbTable = NULL;
|
sqlite3_stmt* gamedbTable = NULL;
|
||||||
sqlite3_stmt* gamedbDrop = NULL;
|
sqlite3_stmt* gamedbDrop = NULL;
|
||||||
|
sqlite3_stmt* gamedbSelect = NULL;
|
||||||
sqlite3_stmt* gameTable = NULL;
|
sqlite3_stmt* gameTable = NULL;
|
||||||
sqlite3_stmt* romTable = NULL;
|
sqlite3_stmt* romTable = NULL;
|
||||||
char* fieldName = NULL;
|
char* fieldName = NULL;
|
||||||
|
@ -92,6 +90,11 @@ bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static const char selectGamedb[] = "SELECT * FROM gamedb WHERE name = ? AND version >= ?;";
|
||||||
|
if (sqlite3_prepare_v2(db->db, selectGamedb, -1, &gamedbSelect, NULL)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
static const char insertGame[] = "INSERT INTO games (dbid, name) VALUES (?, ?);";
|
static const char insertGame[] = "INSERT INTO games (dbid, name) VALUES (?, ?);";
|
||||||
if (sqlite3_prepare_v2(db->db, insertGame, -1, &gameTable, NULL)) {
|
if (sqlite3_prepare_v2(db->db, insertGame, -1, &gameTable, NULL)) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -162,18 +165,24 @@ bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf) {
|
||||||
break;
|
break;
|
||||||
case ')':
|
case ')':
|
||||||
if (currentDb < 0 && dbType && dbVersion) {
|
if (currentDb < 0 && dbType && dbVersion) {
|
||||||
sqlite3_clear_bindings(gamedbDrop);
|
sqlite3_clear_bindings(gamedbSelect);
|
||||||
sqlite3_reset(gamedbDrop);
|
sqlite3_reset(gamedbSelect);
|
||||||
sqlite3_bind_text(gamedbDrop, 1, dbType, -1, SQLITE_TRANSIENT);
|
sqlite3_bind_text(gamedbSelect, 1, dbType, -1, SQLITE_TRANSIENT);
|
||||||
sqlite3_bind_text(gamedbDrop, 2, dbVersion, -1, SQLITE_TRANSIENT);
|
sqlite3_bind_text(gamedbSelect, 2, dbVersion, -1, SQLITE_TRANSIENT);
|
||||||
sqlite3_step(gamedbDrop);
|
if (sqlite3_step(gamedbSelect) != SQLITE_ROW) {
|
||||||
|
sqlite3_clear_bindings(gamedbDrop);
|
||||||
|
sqlite3_reset(gamedbDrop);
|
||||||
|
sqlite3_bind_text(gamedbDrop, 1, dbType, -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_bind_text(gamedbDrop, 2, dbVersion, -1, SQLITE_TRANSIENT);
|
||||||
|
sqlite3_step(gamedbDrop);
|
||||||
|
|
||||||
sqlite3_clear_bindings(gamedbTable);
|
sqlite3_clear_bindings(gamedbTable);
|
||||||
sqlite3_reset(gamedbTable);
|
sqlite3_reset(gamedbTable);
|
||||||
sqlite3_bind_text(gamedbTable, 1, dbType, -1, SQLITE_TRANSIENT);
|
sqlite3_bind_text(gamedbTable, 1, dbType, -1, SQLITE_TRANSIENT);
|
||||||
sqlite3_bind_text(gamedbTable, 2, dbVersion, -1, SQLITE_TRANSIENT);
|
sqlite3_bind_text(gamedbTable, 2, dbVersion, -1, SQLITE_TRANSIENT);
|
||||||
if (sqlite3_step(gamedbTable) == SQLITE_DONE) {
|
if (sqlite3_step(gamedbTable) == SQLITE_DONE) {
|
||||||
currentDb = sqlite3_last_insert_rowid(db->db);
|
currentDb = sqlite3_last_insert_rowid(db->db);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
free((void*) dbType);
|
free((void*) dbType);
|
||||||
free((void*) dbVersion);
|
free((void*) dbVersion);
|
||||||
|
@ -273,6 +282,7 @@ bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf) {
|
||||||
|
|
||||||
sqlite3_finalize(gamedbTable);
|
sqlite3_finalize(gamedbTable);
|
||||||
sqlite3_finalize(gamedbDrop);
|
sqlite3_finalize(gamedbDrop);
|
||||||
|
sqlite3_finalize(gamedbSelect);
|
||||||
sqlite3_finalize(gameTable);
|
sqlite3_finalize(gameTable);
|
||||||
sqlite3_finalize(romTable);
|
sqlite3_finalize(romTable);
|
||||||
|
|
||||||
|
@ -285,8 +295,12 @@ bool NoIntroDBLoadClrMamePro(struct NoIntroDB* db, struct VFile* vf) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void NoIntroDBDestroy(struct NoIntroDB* db) {
|
void NoIntroDBDestroy(struct NoIntroDB* db) {
|
||||||
sqlite3_finalize(db->crc32);
|
if (db->crc32) {
|
||||||
sqlite3_close(db->db);
|
sqlite3_finalize(db->crc32);
|
||||||
|
}
|
||||||
|
if (db->db) {
|
||||||
|
sqlite3_close(db->db);
|
||||||
|
}
|
||||||
free(db);
|
free(db);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -207,7 +207,7 @@ static THREAD_ENTRY _proxyThread(void* logger) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
MutexUnlock(&proxyRenderer->mutex);
|
MutexUnlock(&proxyRenderer->mutex);
|
||||||
return 0;
|
THREAD_EXIT(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -21,6 +21,9 @@
|
||||||
#include <synchapi.h>
|
#include <synchapi.h>
|
||||||
|
|
||||||
#define mkdir(X, Y) _mkdir(X)
|
#define mkdir(X, Y) _mkdir(X)
|
||||||
|
#ifndef S_ISDIR
|
||||||
|
#define S_ISDIR(MODE) (((MODE) & _S_IFMT) == _S_IFDIR)
|
||||||
|
#endif
|
||||||
#elif defined(_POSIX_C_SOURCE)
|
#elif defined(_POSIX_C_SOURCE)
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#endif
|
#endif
|
||||||
|
@ -31,6 +34,39 @@
|
||||||
|
|
||||||
FILE* logfile;
|
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) {
|
bool extractArchive(struct VDir* archive, const char* root, bool prefix) {
|
||||||
char path[PATH_MAX] = {0};
|
char path[PATH_MAX] = {0};
|
||||||
struct VDirEntry* vde;
|
struct VDirEntry* vde;
|
||||||
|
@ -56,12 +92,32 @@ bool extractArchive(struct VDir* archive, const char* root, bool prefix) {
|
||||||
switch (vde->type(vde)) {
|
switch (vde->type(vde)) {
|
||||||
case VFS_DIRECTORY:
|
case VFS_DIRECTORY:
|
||||||
fprintf(logfile, "mkdir %s\n", fname);
|
fprintf(logfile, "mkdir %s\n", fname);
|
||||||
if (mkdir(path, 0755) < 0 && errno != EEXIST) {
|
if (mkdir(path, 0755) < 0) {
|
||||||
return false;
|
bool redo = true;
|
||||||
|
struct stat st;
|
||||||
|
if (errno != EEXIST || stat(path, &st) < 0) {
|
||||||
|
redo = false;
|
||||||
|
} else if (!S_ISDIR(st.st_mode)) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
wchar_t wpath[MAX_PATH + 1];
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);
|
||||||
|
DeleteFileW(wpath);
|
||||||
|
#else
|
||||||
|
unlink(path);
|
||||||
|
#endif
|
||||||
|
if (mkdir(path, 0755) < 0) {
|
||||||
|
redo = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!redo) {
|
||||||
|
fprintf(logfile, "error %i\n", errno);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!prefix) {
|
if (!prefix) {
|
||||||
struct VDir* subdir = archive->openDir(archive, fname);
|
struct VDir* subdir = archive->openDir(archive, fname);
|
||||||
if (!subdir) {
|
if (!subdir) {
|
||||||
|
fprintf(logfile, "error\n");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (!extractArchive(subdir, path, false)) {
|
if (!extractArchive(subdir, path, false)) {
|
||||||
|
@ -76,13 +132,32 @@ bool extractArchive(struct VDir* archive, const char* root, bool prefix) {
|
||||||
vfIn = archive->openFile(archive, vde->name(vde), O_RDONLY);
|
vfIn = archive->openFile(archive, vde->name(vde), O_RDONLY);
|
||||||
errno = 0;
|
errno = 0;
|
||||||
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
|
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||||
if (!vfOut && errno == EACCES) {
|
if (!vfOut) {
|
||||||
|
int error = errno;
|
||||||
|
struct stat st;
|
||||||
|
if (error == EISDIR || (stat(path, &st) >= 0 && S_ISDIR(st.st_mode))) {
|
||||||
|
// Windows maps STATUS_FILE_IS_A_DIRECTORY to ERROR_ACCESS_DENIED,
|
||||||
|
// which then gets mapped to EACCESS, because everything is awful
|
||||||
|
fprintf(logfile, "rm -r %s\n", path);
|
||||||
|
if (!rmdirRecursive(VDirOpen(path))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
Sleep(1000);
|
wchar_t wpath[MAX_PATH + 1];
|
||||||
|
MultiByteToWideChar(CP_UTF8, 0, path, -1, wpath, MAX_PATH);
|
||||||
|
RemoveDirectoryW(wpath);
|
||||||
#else
|
#else
|
||||||
sleep(1);
|
rmdir(path);
|
||||||
#endif
|
#endif
|
||||||
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
|
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||||
|
} else if (error == EACCES || error == ETXTBSY) {
|
||||||
|
#ifdef _WIN32
|
||||||
|
Sleep(1000);
|
||||||
|
#else
|
||||||
|
sleep(1);
|
||||||
|
#endif
|
||||||
|
vfOut = VFileOpen(path, O_WRONLY | O_CREAT | O_TRUNC);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (!vfOut) {
|
if (!vfOut) {
|
||||||
vfIn->close(vfIn);
|
vfIn->close(vfIn);
|
||||||
|
@ -114,7 +189,7 @@ int main(int argc, char* argv[]) {
|
||||||
int ok = 1;
|
int ok = 1;
|
||||||
|
|
||||||
mCoreConfigDirectory(bin, sizeof(bin));
|
mCoreConfigDirectory(bin, sizeof(bin));
|
||||||
strncat(bin, "/updater.log", sizeof(bin));
|
strncat(bin, "/updater.log", sizeof(bin) - 1);
|
||||||
logfile = fopen(bin, "w");
|
logfile = fopen(bin, "w");
|
||||||
|
|
||||||
mCoreConfigInit(&config, "updater");
|
mCoreConfigInit(&config, "updater");
|
||||||
|
@ -200,7 +275,7 @@ int main(int argc, char* argv[]) {
|
||||||
// Cross-dev, need to copy manually
|
// Cross-dev, need to copy manually
|
||||||
int infd = open(updateArchive, O_RDONLY);
|
int infd = open(updateArchive, O_RDONLY);
|
||||||
int outfd = -1;
|
int outfd = -1;
|
||||||
if (infd >= 0) {
|
if (infd < 0) {
|
||||||
ok = 2;
|
ok = 2;
|
||||||
} else {
|
} else {
|
||||||
outfd = open(bin, O_CREAT | O_WRONLY | O_TRUNC, 0755);
|
outfd = open(bin, O_CREAT | O_WRONLY | O_TRUNC, 0755);
|
||||||
|
@ -274,7 +349,7 @@ int main(int argc, char* argv[]) {
|
||||||
const char* argv[] = { qbin, NULL };
|
const char* argv[] = { qbin, NULL };
|
||||||
_execv(bin, argv);
|
_execv(bin, argv);
|
||||||
#elif defined(_POSIX_C_SOURCE) || defined(__APPLE__)
|
#elif defined(_POSIX_C_SOURCE) || defined(__APPLE__)
|
||||||
const char* argv[] = { bin, NULL };
|
char* const argv[] = { bin, NULL };
|
||||||
execv(bin, argv);
|
execv(bin, argv);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,14 +78,7 @@ static void _updateMatch(const char* key, const char* value, void* user) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const char* item = &key[dotLoc + 1];
|
const char* item = &key[dotLoc + 1];
|
||||||
|
_updateUpdate(match->out, item, value);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mUpdaterInit(struct mUpdaterContext* context, const char* manifest) {
|
bool mUpdaterInit(struct mUpdaterContext* context, const char* manifest) {
|
||||||
|
@ -155,7 +148,7 @@ bool mUpdateLoad(const struct mCoreConfig* config, const char* prefix, struct mU
|
||||||
snprintf(key, sizeof(key), "%s.path", prefix);
|
snprintf(key, sizeof(key), "%s.path", prefix);
|
||||||
update->path = mCoreConfigGetValue(config, key);
|
update->path = mCoreConfigGetValue(config, key);
|
||||||
snprintf(key, sizeof(key), "%s.size", prefix);
|
snprintf(key, sizeof(key), "%s.size", prefix);
|
||||||
uint32_t size = 0;
|
unsigned size = 0;
|
||||||
mCoreConfigGetUIntValue(config, key, &size);
|
mCoreConfigGetUIntValue(config, key, &size);
|
||||||
if (!update->path && !size) {
|
if (!update->path && !size) {
|
||||||
return false;
|
return false;
|
||||||
|
|
126
src/gb/audio.c
126
src/gb/audio.c
|
@ -33,10 +33,10 @@ static void _writeDuty(struct GBAudioEnvelope* envelope, uint8_t value);
|
||||||
static bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value, enum GBAudioStyle style);
|
static bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value, enum GBAudioStyle style);
|
||||||
|
|
||||||
static void _resetSweep(struct GBAudioSweep* sweep);
|
static void _resetSweep(struct GBAudioSweep* sweep);
|
||||||
static bool _resetEnvelope(struct GBAudioEnvelope* sweep);
|
static bool _resetEnvelope(struct GBAudioEnvelope* sweep, enum GBAudioStyle style);
|
||||||
|
|
||||||
static void _updateEnvelope(struct GBAudioEnvelope* envelope);
|
static void _updateEnvelope(struct GBAudioEnvelope* envelope);
|
||||||
static void _updateEnvelopeDead(struct GBAudioEnvelope* envelope);
|
static void _updateEnvelopeDead(struct GBAudioEnvelope* envelope, enum GBAudioStyle style);
|
||||||
static bool _updateSweep(struct GBAudioSquareChannel* sweep, bool initial);
|
static bool _updateSweep(struct GBAudioSquareChannel* sweep, bool initial);
|
||||||
|
|
||||||
static void _updateSquareSample(struct GBAudioSquareChannel* ch);
|
static void _updateSquareSample(struct GBAudioSquareChannel* ch);
|
||||||
|
@ -100,22 +100,24 @@ void GBAudioReset(struct GBAudio* audio) {
|
||||||
audio->ch3 = (struct GBAudioWaveChannel) { .bank = 0 };
|
audio->ch3 = (struct GBAudioWaveChannel) { .bank = 0 };
|
||||||
audio->ch4 = (struct GBAudioNoiseChannel) { .nSamples = 0 };
|
audio->ch4 = (struct GBAudioNoiseChannel) { .nSamples = 0 };
|
||||||
// TODO: DMG randomness
|
// TODO: DMG randomness
|
||||||
audio->ch3.wavedata8[0] = 0x00;
|
if (audio->style != GB_AUDIO_GBA) {
|
||||||
audio->ch3.wavedata8[1] = 0xFF;
|
audio->ch3.wavedata8[0] = 0x00;
|
||||||
audio->ch3.wavedata8[2] = 0x00;
|
audio->ch3.wavedata8[1] = 0xFF;
|
||||||
audio->ch3.wavedata8[3] = 0xFF;
|
audio->ch3.wavedata8[2] = 0x00;
|
||||||
audio->ch3.wavedata8[4] = 0x00;
|
audio->ch3.wavedata8[3] = 0xFF;
|
||||||
audio->ch3.wavedata8[5] = 0xFF;
|
audio->ch3.wavedata8[4] = 0x00;
|
||||||
audio->ch3.wavedata8[6] = 0x00;
|
audio->ch3.wavedata8[5] = 0xFF;
|
||||||
audio->ch3.wavedata8[7] = 0xFF;
|
audio->ch3.wavedata8[6] = 0x00;
|
||||||
audio->ch3.wavedata8[8] = 0x00;
|
audio->ch3.wavedata8[7] = 0xFF;
|
||||||
audio->ch3.wavedata8[9] = 0xFF;
|
audio->ch3.wavedata8[8] = 0x00;
|
||||||
audio->ch3.wavedata8[10] = 0x00;
|
audio->ch3.wavedata8[9] = 0xFF;
|
||||||
audio->ch3.wavedata8[11] = 0xFF;
|
audio->ch3.wavedata8[10] = 0x00;
|
||||||
audio->ch3.wavedata8[12] = 0x00;
|
audio->ch3.wavedata8[11] = 0xFF;
|
||||||
audio->ch3.wavedata8[13] = 0xFF;
|
audio->ch3.wavedata8[12] = 0x00;
|
||||||
audio->ch3.wavedata8[14] = 0x00;
|
audio->ch3.wavedata8[13] = 0xFF;
|
||||||
audio->ch3.wavedata8[15] = 0xFF;
|
audio->ch3.wavedata8[14] = 0x00;
|
||||||
|
audio->ch3.wavedata8[15] = 0xFF;
|
||||||
|
}
|
||||||
audio->ch4 = (struct GBAudioNoiseChannel) { .envelope = { .dead = 2 } };
|
audio->ch4 = (struct GBAudioNoiseChannel) { .envelope = { .dead = 2 } };
|
||||||
audio->frame = 0;
|
audio->frame = 0;
|
||||||
audio->sampleInterval = SAMPLE_INTERVAL * GB_MAX_SAMPLES;
|
audio->sampleInterval = SAMPLE_INTERVAL * GB_MAX_SAMPLES;
|
||||||
|
@ -138,6 +140,9 @@ void GBAudioReset(struct GBAudio* audio) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) {
|
void GBAudioResizeBuffer(struct GBAudio* audio, size_t samples) {
|
||||||
|
if (samples > BLIP_BUFFER_SIZE / 2) {
|
||||||
|
samples = BLIP_BUFFER_SIZE / 2;
|
||||||
|
}
|
||||||
mCoreSyncLockAudio(audio->p->sync);
|
mCoreSyncLockAudio(audio->p->sync);
|
||||||
audio->samples = samples;
|
audio->samples = samples;
|
||||||
blip_clear(audio->left);
|
blip_clear(audio->left);
|
||||||
|
@ -187,7 +192,7 @@ void GBAudioWriteNR14(struct GBAudio* audio, uint8_t value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (GBAudioRegisterControlIsRestart(value << 8)) {
|
if (GBAudioRegisterControlIsRestart(value << 8)) {
|
||||||
audio->playingCh1 = _resetEnvelope(&audio->ch1.envelope);
|
audio->playingCh1 = _resetEnvelope(&audio->ch1.envelope, audio->style);
|
||||||
audio->ch1.sweep.realFrequency = audio->ch1.control.frequency;
|
audio->ch1.sweep.realFrequency = audio->ch1.control.frequency;
|
||||||
_resetSweep(&audio->ch1.sweep);
|
_resetSweep(&audio->ch1.sweep);
|
||||||
if (audio->playingCh1 && audio->ch1.sweep.shift) {
|
if (audio->playingCh1 && audio->ch1.sweep.shift) {
|
||||||
|
@ -238,7 +243,7 @@ void GBAudioWriteNR24(struct GBAudio* audio, uint8_t value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (GBAudioRegisterControlIsRestart(value << 8)) {
|
if (GBAudioRegisterControlIsRestart(value << 8)) {
|
||||||
audio->playingCh2 = _resetEnvelope(&audio->ch2.envelope);
|
audio->playingCh2 = _resetEnvelope(&audio->ch2.envelope, audio->style);
|
||||||
|
|
||||||
if (!audio->ch2.control.length) {
|
if (!audio->ch2.control.length) {
|
||||||
audio->ch2.control.length = 64;
|
audio->ch2.control.length = 64;
|
||||||
|
@ -269,6 +274,29 @@ void GBAudioWriteNR31(struct GBAudio* audio, uint8_t value) {
|
||||||
void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) {
|
void GBAudioWriteNR32(struct GBAudio* audio, uint8_t value) {
|
||||||
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
|
GBAudioRun(audio, mTimingCurrentTime(audio->timing), 0x4);
|
||||||
audio->ch3.volume = GBAudioRegisterBankVolumeGetVolumeGB(value);
|
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) {
|
void GBAudioWriteNR33(struct GBAudio* audio, uint8_t value) {
|
||||||
|
@ -355,7 +383,7 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (GBAudioRegisterNoiseControlIsRestart(value)) {
|
if (GBAudioRegisterNoiseControlIsRestart(value)) {
|
||||||
audio->playingCh4 = _resetEnvelope(&audio->ch4.envelope);
|
audio->playingCh4 = _resetEnvelope(&audio->ch4.envelope, audio->style);
|
||||||
|
|
||||||
if (audio->ch4.power) {
|
if (audio->ch4.power) {
|
||||||
audio->ch4.lfsr = 0x7F;
|
audio->ch4.lfsr = 0x7F;
|
||||||
|
@ -377,13 +405,13 @@ void GBAudioWriteNR44(struct GBAudio* audio, uint8_t value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAudioWriteNR50(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->volumeRight = GBRegisterNR50GetVolumeRight(value);
|
||||||
audio->volumeLeft = GBRegisterNR50GetVolumeLeft(value);
|
audio->volumeLeft = GBRegisterNR50GetVolumeLeft(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAudioWriteNR51(struct GBAudio* audio, uint8_t 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->ch1Right = GBRegisterNR51GetCh1Right(value);
|
||||||
audio->ch2Right = GBRegisterNR51GetCh2Right(value);
|
audio->ch2Right = GBRegisterNR51GetCh2Right(value);
|
||||||
audio->ch3Right = GBRegisterNR51GetCh3Right(value);
|
audio->ch3Right = GBRegisterNR51GetCh3Right(value);
|
||||||
|
@ -476,11 +504,11 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
|
||||||
if (!audio->enable) {
|
if (!audio->enable) {
|
||||||
return;
|
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);
|
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;
|
int period = 4 * (2048 - audio->ch1.control.frequency) * audio->timingFactor;
|
||||||
int32_t diff = timestamp - audio->ch1.lastUpdate;
|
int32_t diff = timestamp - audio->ch1.lastUpdate;
|
||||||
if (diff >= period) {
|
if (diff >= period) {
|
||||||
|
@ -490,7 +518,7 @@ void GBAudioRun(struct GBAudio* audio, int32_t timestamp, int channels) {
|
||||||
_updateSquareSample(&audio->ch1);
|
_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;
|
int period = 4 * (2048 - audio->ch2.control.frequency) * audio->timingFactor;
|
||||||
int32_t diff = timestamp - audio->ch2.lastUpdate;
|
int32_t diff = timestamp - audio->ch2.lastUpdate;
|
||||||
if (diff >= period) {
|
if (diff >= period) {
|
||||||
|
@ -621,7 +649,9 @@ void GBAudioUpdateFrame(struct GBAudio* audio) {
|
||||||
if (audio->ch1.sweep.enable) {
|
if (audio->ch1.sweep.enable) {
|
||||||
--audio->ch1.sweep.step;
|
--audio->ch1.sweep.step;
|
||||||
if (audio->ch1.sweep.step == 0) {
|
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 &= ~0x0001;
|
||||||
*audio->nr52 |= audio->playingCh1;
|
*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) {
|
for (sample = audio->sampleIndex; timestamp >= interval && sample < GB_MAX_SAMPLES; ++sample, timestamp -= interval) {
|
||||||
int16_t sampleLeft = 0;
|
int16_t sampleLeft = 0;
|
||||||
int16_t sampleRight = 0;
|
int16_t sampleRight = 0;
|
||||||
GBAudioRun(audio, sample * interval + audio->lastSample, 0xF);
|
GBAudioRun(audio, sample * interval + audio->lastSample, 0x1F);
|
||||||
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
|
GBAudioSamplePSG(audio, &sampleLeft, &sampleRight);
|
||||||
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
|
sampleLeft = (sampleLeft * audio->masterVolume * 6) >> 7;
|
||||||
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
|
sampleRight = (sampleRight * audio->masterVolume * 6) >> 7;
|
||||||
|
@ -817,12 +847,10 @@ static void _sample(struct mTiming* timing, void* user, uint32_t cyclesLate) {
|
||||||
mTimingSchedule(timing, &audio->sampleEvent, audio->sampleInterval * audio->timingFactor - cyclesLate);
|
mTimingSchedule(timing, &audio->sampleEvent, audio->sampleInterval * audio->timingFactor - cyclesLate);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _resetEnvelope(struct GBAudioEnvelope* envelope) {
|
bool _resetEnvelope(struct GBAudioEnvelope* envelope, enum GBAudioStyle style) {
|
||||||
envelope->currentVolume = envelope->initialVolume;
|
envelope->currentVolume = envelope->initialVolume;
|
||||||
_updateEnvelopeDead(envelope);
|
envelope->nextStep = envelope->stepTime;
|
||||||
if (!envelope->dead) {
|
_updateEnvelopeDead(envelope, style);
|
||||||
envelope->nextStep = envelope->stepTime;
|
|
||||||
}
|
|
||||||
return envelope->initialVolume || envelope->direction;
|
return envelope->initialVolume || envelope->direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -854,16 +882,29 @@ void _writeDuty(struct GBAudioEnvelope* envelope, uint8_t value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value, enum GBAudioStyle style) {
|
bool _writeEnvelope(struct GBAudioEnvelope* envelope, uint8_t value, enum GBAudioStyle style) {
|
||||||
|
bool oldDirection = envelope->direction;
|
||||||
envelope->stepTime = GBAudioRegisterSweepGetStepTime(value);
|
envelope->stepTime = GBAudioRegisterSweepGetStepTime(value);
|
||||||
envelope->direction = GBAudioRegisterSweepGetDirection(value);
|
envelope->direction = GBAudioRegisterSweepGetDirection(value);
|
||||||
envelope->initialVolume = GBAudioRegisterSweepGetInitialVolume(value);
|
envelope->initialVolume = GBAudioRegisterSweepGetInitialVolume(value);
|
||||||
if (style == GB_AUDIO_DMG && !envelope->stepTime) {
|
if (!envelope->stepTime) {
|
||||||
// TODO: Improve "zombie" mode
|
// 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;
|
envelope->currentVolume &= 0xF;
|
||||||
}
|
}
|
||||||
_updateEnvelopeDead(envelope);
|
_updateEnvelopeDead(envelope, style);
|
||||||
return (envelope->initialVolume || envelope->direction) && envelope->dead != 2;
|
return envelope->initialVolume || envelope->direction;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
|
static void _updateSquareSample(struct GBAudioSquareChannel* ch) {
|
||||||
|
@ -898,14 +939,19 @@ static void _updateEnvelope(struct GBAudioEnvelope* envelope) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _updateEnvelopeDead(struct GBAudioEnvelope* envelope) {
|
static void _updateEnvelopeDead(struct GBAudioEnvelope* envelope, enum GBAudioStyle style) {
|
||||||
if (!envelope->stepTime) {
|
if (!envelope->stepTime) {
|
||||||
envelope->dead = envelope->currentVolume ? 1 : 2;
|
envelope->dead = envelope->currentVolume ? 1 : 2;
|
||||||
} else if (!envelope->direction && !envelope->currentVolume) {
|
} else if (!envelope->direction && !envelope->currentVolume) {
|
||||||
envelope->dead = 2;
|
envelope->dead = 2;
|
||||||
} else if (envelope->direction && envelope->currentVolume == 0xF) {
|
} else if (envelope->direction && envelope->currentVolume == 0xF) {
|
||||||
envelope->dead = 1;
|
envelope->dead = 1;
|
||||||
} else {
|
} else if (envelope->dead) {
|
||||||
|
// TODO: Figure out if this happens on DMG/CGB or just AGB
|
||||||
|
// TODO: Figure out the exact circumstances that lead to reloading the step
|
||||||
|
if (style == GB_AUDIO_GBA) {
|
||||||
|
envelope->nextStep = envelope->stepTime;
|
||||||
|
}
|
||||||
envelope->dead = 0;
|
envelope->dead = 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1066,6 +1112,8 @@ void GBAudioPSGDeserialize(struct GBAudio* audio, const struct GBSerializedPSGSt
|
||||||
audio->ch4.lastEvent = currentTime + (when & (cycles - 1)) - cycles;
|
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) {
|
void GBAudioSerialize(const struct GBAudio* audio, struct GBSerializedState* state) {
|
||||||
|
|
|
@ -607,16 +607,16 @@ static void _GBCoreReset(struct mCore* core) {
|
||||||
switch (gb->model) {
|
switch (gb->model) {
|
||||||
case GB_MODEL_DMG:
|
case GB_MODEL_DMG:
|
||||||
case GB_MODEL_MGB: // TODO
|
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;
|
break;
|
||||||
case GB_MODEL_SGB:
|
case GB_MODEL_SGB:
|
||||||
case GB_MODEL_SGB2: // TODO
|
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;
|
break;
|
||||||
case GB_MODEL_CGB:
|
case GB_MODEL_CGB:
|
||||||
case GB_MODEL_AGB:
|
case GB_MODEL_AGB:
|
||||||
case GB_MODEL_SCGB:
|
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;
|
break;
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
|
@ -644,8 +644,10 @@ static void _GBCoreReset(struct mCore* core) {
|
||||||
size_t i;
|
size_t i;
|
||||||
for (i = 0; i < sizeof(gbcore->memoryBlocks) / sizeof(*gbcore->memoryBlocks); ++i) {
|
for (i = 0; i < sizeof(gbcore->memoryBlocks) / sizeof(*gbcore->memoryBlocks); ++i) {
|
||||||
if (gbcore->memoryBlocks[i].id == GB_REGION_CART_BANK0) {
|
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;
|
gbcore->memoryBlocks[i].maxSegment = gb->memory.romSize / GB_SIZE_CART_BANK0;
|
||||||
} else if (gbcore->memoryBlocks[i].id == GB_REGION_EXTERNAL_RAM) {
|
} 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;
|
gbcore->memoryBlocks[i].maxSegment = gb->sramSize / GB_SIZE_EXTERNAL_RAM;
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
@ -1048,9 +1050,11 @@ static void _GBCoreDetachDebugger(struct mCore* core) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) {
|
static void _GBCoreLoadSymbols(struct mCore* core, struct VFile* vf) {
|
||||||
core->symbolTable = mDebuggerSymbolTableCreate();
|
if (!core->symbolTable) {
|
||||||
|
core->symbolTable = mDebuggerSymbolTableCreate();
|
||||||
|
}
|
||||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||||
if (!vf) {
|
if (!vf && core->dirs.base) {
|
||||||
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY);
|
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -1088,19 +1092,32 @@ static struct mCheatDevice* _GBCoreCheatDevice(struct mCore* core) {
|
||||||
|
|
||||||
static size_t _GBCoreSavedataClone(struct mCore* core, void** sram) {
|
static size_t _GBCoreSavedataClone(struct mCore* core, void** sram) {
|
||||||
struct GB* gb = core->board;
|
struct GB* gb = core->board;
|
||||||
struct VFile* vf = gb->sramVf;
|
size_t sramSize = gb->sramSize;
|
||||||
if (vf) {
|
size_t vfSize = 0;
|
||||||
*sram = malloc(vf->size(vf));
|
size_t size = sramSize;
|
||||||
vf->seek(vf, 0, SEEK_SET);
|
uint8_t* view = NULL;
|
||||||
return vf->read(vf, *sram, vf->size(vf));
|
|
||||||
|
if (gb->sramVf) {
|
||||||
|
vfSize = gb->sramVf->size(gb->sramVf);
|
||||||
|
if (vfSize > size) {
|
||||||
|
size = vfSize;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (gb->sramSize) {
|
if (!size) {
|
||||||
*sram = malloc(gb->sramSize);
|
*sram = NULL;
|
||||||
memcpy(*sram, gb->memory.sram, gb->sramSize);
|
return 0;
|
||||||
return gb->sramSize;
|
|
||||||
}
|
}
|
||||||
*sram = NULL;
|
|
||||||
return 0;
|
view = malloc(size);
|
||||||
|
if (sramSize) {
|
||||||
|
memcpy(view, gb->memory.sram, gb->sramSize);
|
||||||
|
}
|
||||||
|
if (vfSize > sramSize) {
|
||||||
|
gb->sramVf->seek(gb->sramVf, sramSize, SEEK_SET);
|
||||||
|
gb->sramVf->read(gb->sramVf, &view[sramSize], vfSize - sramSize);
|
||||||
|
}
|
||||||
|
*sram = view;
|
||||||
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
static bool _GBCoreSavedataRestore(struct mCore* core, const void* sram, size_t size, bool writeback) {
|
static bool _GBCoreSavedataRestore(struct mCore* core, const void* sram, size_t size, bool writeback) {
|
||||||
|
|
76
src/gb/gb.c
76
src/gb/gb.c
|
@ -34,7 +34,9 @@ static const uint8_t _registeredTrademark[] = {0x3C, 0x42, 0xB9, 0xA5, 0xB9, 0xA
|
||||||
#define SGB2_BIOS_CHECKSUM 0X53D0DD63
|
#define SGB2_BIOS_CHECKSUM 0X53D0DD63
|
||||||
#define CGB_BIOS_CHECKSUM 0x41884E46
|
#define CGB_BIOS_CHECKSUM 0x41884E46
|
||||||
#define CGB0_BIOS_CHECKSUM 0xE8EF5318
|
#define CGB0_BIOS_CHECKSUM 0xE8EF5318
|
||||||
|
#define CGBE_BIOS_CHECKSUM 0xE95DC95D
|
||||||
#define AGB_BIOS_CHECKSUM 0xFFD6B0F1
|
#define AGB_BIOS_CHECKSUM 0xFFD6B0F1
|
||||||
|
#define AGB0_BIOS_CHECKSUM 0x570337EA
|
||||||
|
|
||||||
mLOG_DEFINE_CATEGORY(GB, "GB", "gb");
|
mLOG_DEFINE_CATEGORY(GB, "GB", "gb");
|
||||||
|
|
||||||
|
@ -260,9 +262,13 @@ void GBResizeSram(struct GB* gb, size_t size) {
|
||||||
}
|
}
|
||||||
struct VFile* vf = gb->sramVf;
|
struct VFile* vf = gb->sramVf;
|
||||||
if (vf) {
|
if (vf) {
|
||||||
|
// We have a vf
|
||||||
|
ssize_t vfSize = vf->size(vf);
|
||||||
if (vf == gb->sramRealVf) {
|
if (vf == gb->sramRealVf) {
|
||||||
ssize_t vfSize = vf->size(vf);
|
// This is the real save file, not a masked one
|
||||||
if (vfSize >= 0 && (size_t) vfSize < size) {
|
if (vfSize >= 0 && (size_t) vfSize < size) {
|
||||||
|
// We need to grow the file
|
||||||
|
// Make sure to copy the footer data, if any
|
||||||
uint8_t extdataBuffer[0x100];
|
uint8_t extdataBuffer[0x100];
|
||||||
if (vfSize & 0xFF) {
|
if (vfSize & 0xFF) {
|
||||||
vf->seek(vf, -(vfSize & 0xFF), SEEK_END);
|
vf->seek(vf, -(vfSize & 0xFF), SEEK_END);
|
||||||
|
@ -270,6 +276,7 @@ void GBResizeSram(struct GB* gb, size_t size) {
|
||||||
}
|
}
|
||||||
if (gb->memory.sram) {
|
if (gb->memory.sram) {
|
||||||
vf->unmap(vf, gb->memory.sram, gb->sramSize);
|
vf->unmap(vf, gb->memory.sram, gb->sramSize);
|
||||||
|
gb->memory.sram = NULL;
|
||||||
}
|
}
|
||||||
vf->truncate(vf, size + (vfSize & 0xFF));
|
vf->truncate(vf, size + (vfSize & 0xFF));
|
||||||
if (vfSize & 0xFF) {
|
if (vfSize & 0xFF) {
|
||||||
|
@ -281,24 +288,36 @@ void GBResizeSram(struct GB* gb, size_t size) {
|
||||||
memset(&gb->memory.sram[vfSize], 0xFF, size - vfSize);
|
memset(&gb->memory.sram[vfSize], 0xFF, size - vfSize);
|
||||||
}
|
}
|
||||||
} else if (size > gb->sramSize || !gb->memory.sram) {
|
} else if (size > gb->sramSize || !gb->memory.sram) {
|
||||||
|
// We aren't growing the file, but we are changing our mapping of it
|
||||||
if (gb->memory.sram) {
|
if (gb->memory.sram) {
|
||||||
vf->unmap(vf, gb->memory.sram, gb->sramSize);
|
vf->unmap(vf, gb->memory.sram, gb->sramSize);
|
||||||
|
gb->memory.sram = NULL;
|
||||||
}
|
}
|
||||||
if (size) {
|
if (size) {
|
||||||
gb->memory.sram = vf->map(vf, size, MAP_WRITE);
|
gb->memory.sram = vf->map(vf, size, MAP_WRITE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// This is a masked save file
|
||||||
if (gb->memory.sram) {
|
if (gb->memory.sram) {
|
||||||
vf->unmap(vf, gb->memory.sram, gb->sramSize);
|
vf->unmap(vf, gb->memory.sram, gb->sramSize);
|
||||||
}
|
}
|
||||||
if (vf->size(vf) < gb->sramSize) {
|
if ((vfSize <= 0 && size) || (size_t) vfSize < size) {
|
||||||
void* sram = vf->map(vf, vf->size(vf), MAP_READ);
|
// The loaded mask file is too small. Since these can be read-only,
|
||||||
struct VFile* newVf = VFileMemChunk(sram, vf->size(vf));
|
// we need to make a new one of the right size
|
||||||
vf->unmap(vf, sram,vf->size(vf));
|
if (vfSize < 0) {
|
||||||
vf = newVf;
|
vfSize = 0;
|
||||||
gb->sramVf = newVf;
|
}
|
||||||
vf->truncate(vf, size);
|
gb->sramVf = VFileMemChunk(NULL, size);
|
||||||
|
uint8_t* sram = gb->sramVf->map(gb->sramVf, size, MAP_WRITE);
|
||||||
|
if (vfSize > 0) {
|
||||||
|
vf->seek(vf, 0, SEEK_SET);
|
||||||
|
vf->read(vf, sram, vfSize);
|
||||||
|
}
|
||||||
|
memset(&sram[vfSize], 0xFF, size - vfSize);
|
||||||
|
gb->sramVf->unmap(gb->sramVf, sram, size);
|
||||||
|
vf->close(vf);
|
||||||
|
vf = gb->sramVf;
|
||||||
}
|
}
|
||||||
if (size) {
|
if (size) {
|
||||||
gb->memory.sram = vf->map(vf, size, MAP_READ);
|
gb->memory.sram = vf->map(vf, size, MAP_READ);
|
||||||
|
@ -308,6 +327,8 @@ void GBResizeSram(struct GB* gb, size_t size) {
|
||||||
gb->memory.sram = NULL;
|
gb->memory.sram = NULL;
|
||||||
}
|
}
|
||||||
} else if (size) {
|
} else if (size) {
|
||||||
|
// There's no vf, so let's make it only memory-backed
|
||||||
|
// TODO: Investigate just using a VFileMemChunk instead of this hybrid approach
|
||||||
uint8_t* newSram = anonymousMemoryMap(size);
|
uint8_t* newSram = anonymousMemoryMap(size);
|
||||||
if (gb->memory.sram) {
|
if (gb->memory.sram) {
|
||||||
if (size > gb->sramSize) {
|
if (size > gb->sramSize) {
|
||||||
|
@ -405,7 +426,9 @@ void GBUnloadROM(struct GB* gb) {
|
||||||
|
|
||||||
if (gb->romVf) {
|
if (gb->romVf) {
|
||||||
#ifndef FIXED_ROM_BUFFER
|
#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
|
#endif
|
||||||
gb->romVf->close(gb->romVf);
|
gb->romVf->close(gb->romVf);
|
||||||
gb->romVf = NULL;
|
gb->romVf = NULL;
|
||||||
|
@ -453,6 +476,9 @@ void GBApplyPatch(struct GB* gb, struct Patch* patch) {
|
||||||
if (patchedSize > GB_SIZE_CART_MAX) {
|
if (patchedSize > GB_SIZE_CART_MAX) {
|
||||||
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);
|
void* newRom = anonymousMemoryMap(GB_SIZE_CART_MAX);
|
||||||
if (!patch->applyPatch(patch, gb->memory.rom, gb->pristineRomSize, newRom, patchedSize)) {
|
if (!patch->applyPatch(patch, gb->memory.rom, gb->pristineRomSize, newRom, patchedSize)) {
|
||||||
mappedMemoryFree(newRom, GB_SIZE_CART_MAX);
|
mappedMemoryFree(newRom, GB_SIZE_CART_MAX);
|
||||||
|
@ -471,6 +497,12 @@ void GBApplyPatch(struct GB* gb, struct Patch* patch) {
|
||||||
}
|
}
|
||||||
gb->memory.rom = newRom;
|
gb->memory.rom = newRom;
|
||||||
gb->memory.romSize = patchedSize;
|
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->romCrc32 = doCrc32(gb->memory.rom, gb->memory.romSize);
|
||||||
gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);
|
gb->cpu->memory.setActiveRegion(gb->cpu, gb->cpu->pc);
|
||||||
}
|
}
|
||||||
|
@ -521,7 +553,9 @@ bool GBIsBIOS(struct VFile* vf) {
|
||||||
case SGB2_BIOS_CHECKSUM:
|
case SGB2_BIOS_CHECKSUM:
|
||||||
case CGB_BIOS_CHECKSUM:
|
case CGB_BIOS_CHECKSUM:
|
||||||
case CGB0_BIOS_CHECKSUM:
|
case CGB0_BIOS_CHECKSUM:
|
||||||
|
case CGBE_BIOS_CHECKSUM:
|
||||||
case AGB_BIOS_CHECKSUM:
|
case AGB_BIOS_CHECKSUM:
|
||||||
|
case AGB0_BIOS_CHECKSUM:
|
||||||
return true;
|
return true;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
@ -596,13 +630,13 @@ void GBReset(struct SM83Core* cpu) {
|
||||||
GBVideoReset(&gb->video);
|
GBVideoReset(&gb->video);
|
||||||
GBTimerReset(&gb->timer);
|
GBTimerReset(&gb->timer);
|
||||||
GBIOReset(gb);
|
GBIOReset(gb);
|
||||||
|
GBAudioReset(&gb->audio);
|
||||||
if (!gb->biosVf && gb->memory.rom) {
|
if (!gb->biosVf && gb->memory.rom) {
|
||||||
GBSkipBIOS(gb);
|
GBSkipBIOS(gb);
|
||||||
} else {
|
} else {
|
||||||
mTimingSchedule(&gb->timing, &gb->timer.event, 0);
|
mTimingSchedule(&gb->timing, &gb->timer.event, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
GBAudioReset(&gb->audio);
|
|
||||||
GBSIOReset(&gb->sio);
|
GBSIOReset(&gb->sio);
|
||||||
|
|
||||||
cpu->memory.setActiveRegion(cpu, cpu->pc);
|
cpu->memory.setActiveRegion(cpu, cpu->pc);
|
||||||
|
@ -744,6 +778,25 @@ void GBSkipBIOS(struct GB* gb) {
|
||||||
GBUnmapBIOS(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);
|
GBIOWrite(gb, GB_REG_LCDC, 0x91);
|
||||||
gb->memory.io[GB_REG_BANK] = 0x1;
|
gb->memory.io[GB_REG_BANK] = 0x1;
|
||||||
GBVideoSkipBIOS(&gb->video);
|
GBVideoSkipBIOS(&gb->video);
|
||||||
|
@ -796,9 +849,12 @@ void GBDetectModel(struct GB* gb) {
|
||||||
gb->model = GB_MODEL_SGB2;
|
gb->model = GB_MODEL_SGB2;
|
||||||
break;
|
break;
|
||||||
case CGB_BIOS_CHECKSUM:
|
case CGB_BIOS_CHECKSUM:
|
||||||
|
case CGB0_BIOS_CHECKSUM:
|
||||||
|
case CGBE_BIOS_CHECKSUM:
|
||||||
gb->model = GB_MODEL_CGB;
|
gb->model = GB_MODEL_CGB;
|
||||||
break;
|
break;
|
||||||
case AGB_BIOS_CHECKSUM:
|
case AGB_BIOS_CHECKSUM:
|
||||||
|
case AGB0_BIOS_CHECKSUM:
|
||||||
gb->model = GB_MODEL_AGB;
|
gb->model = GB_MODEL_AGB;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
|
36
src/gb/io.c
36
src/gb/io.c
|
@ -162,36 +162,7 @@ void GBIOReset(struct GB* gb) {
|
||||||
GBIOWrite(gb, GB_REG_TMA, 0);
|
GBIOWrite(gb, GB_REG_TMA, 0);
|
||||||
GBIOWrite(gb, GB_REG_TAC, 0);
|
GBIOWrite(gb, GB_REG_TAC, 0);
|
||||||
GBIOWrite(gb, GB_REG_IF, 1);
|
GBIOWrite(gb, GB_REG_IF, 1);
|
||||||
gb->audio.playingCh1 = false;
|
GBIOWrite(gb, GB_REG_LCDC, 0x00);
|
||||||
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_SCY, 0x00);
|
GBIOWrite(gb, GB_REG_SCY, 0x00);
|
||||||
GBIOWrite(gb, GB_REG_SCX, 0x00);
|
GBIOWrite(gb, GB_REG_SCX, 0x00);
|
||||||
GBIOWrite(gb, GB_REG_LYC, 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_WY, 0x00);
|
||||||
GBIOWrite(gb, GB_REG_WX, 0x00);
|
GBIOWrite(gb, GB_REG_WX, 0x00);
|
||||||
|
gb->memory.io[GB_REG_BANK] = 0xFF;
|
||||||
if (gb->model & GB_MODEL_CGB) {
|
if (gb->model & GB_MODEL_CGB) {
|
||||||
GBIOWrite(gb, GB_REG_KEY0, 0);
|
GBIOWrite(gb, GB_REG_KEY0, 0);
|
||||||
GBIOWrite(gb, GB_REG_JOYP, 0xFF);
|
GBIOWrite(gb, GB_REG_JOYP, 0xFF);
|
||||||
|
@ -519,7 +491,7 @@ void GBIOWrite(struct GB* gb, unsigned address, uint8_t value) {
|
||||||
return;
|
return;
|
||||||
case GB_REG_SVBK:
|
case GB_REG_SVBK:
|
||||||
GBMemorySwitchWramBank(&gb->memory, value);
|
GBMemorySwitchWramBank(&gb->memory, value);
|
||||||
value = gb->memory.wramCurrentBank;
|
value &= 7;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
goto failed;
|
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_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_WY, state->io[GB_REG_WY]);
|
||||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_WX, state->io[GB_REG_WX]);
|
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_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_OBP0, state->io[GB_REG_OBP0]);
|
||||||
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP1, state->io[GB_REG_OBP1]);
|
gb->video.renderer->writeVideoRegister(gb->video.renderer, GB_REG_OBP1, state->io[GB_REG_OBP1]);
|
||||||
|
|
|
@ -14,6 +14,7 @@
|
||||||
#include <mgba/internal/sm83/sm83.h>
|
#include <mgba/internal/sm83/sm83.h>
|
||||||
|
|
||||||
#include <mgba-util/memory.h>
|
#include <mgba-util/memory.h>
|
||||||
|
#include <mgba-util/vfs.h>
|
||||||
|
|
||||||
mLOG_DEFINE_CATEGORY(GB_MEM, "GB Memory", "gb.memory");
|
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) {
|
if (memory->rtcAccess) {
|
||||||
return memory->rtcRegs[memory->activeRtcReg];
|
return memory->rtcRegs[memory->activeRtcReg];
|
||||||
} else if (memory->sramAccess) {
|
} else if (memory->sramAccess) {
|
||||||
if (segment < 0 && memory->sram) {
|
if (memory->sram) {
|
||||||
return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)];
|
if (segment < 0) {
|
||||||
} else if ((size_t) segment * GB_SIZE_EXTERNAL_RAM < gb->sramSize) {
|
return memory->sramBank[address & (GB_SIZE_EXTERNAL_RAM - 1)];
|
||||||
return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM];
|
} else if ((size_t) segment * GB_SIZE_EXTERNAL_RAM < gb->sramSize) {
|
||||||
} else {
|
return memory->sram[(address & (GB_SIZE_EXTERNAL_RAM - 1)) + segment *GB_SIZE_EXTERNAL_RAM];
|
||||||
return 0xFF;
|
}
|
||||||
}
|
}
|
||||||
|
return 0xFF;
|
||||||
} else if (memory->mbcRead) {
|
} else if (memory->mbcRead) {
|
||||||
return memory->mbcRead(memory, address);
|
return memory->mbcRead(memory, address);
|
||||||
} else if (memory->mbcType == GB_HuC3) {
|
} else if (memory->mbcType == GB_HuC3) {
|
||||||
|
@ -558,7 +560,7 @@ uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) {
|
||||||
gb->memory.hdmaDest |= 0x8000;
|
gb->memory.hdmaDest |= 0x8000;
|
||||||
bool wasHdma = gb->memory.isHdma;
|
bool wasHdma = gb->memory.isHdma;
|
||||||
gb->memory.isHdma = value & 0x80;
|
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) {
|
if (gb->memory.isHdma) {
|
||||||
gb->memory.hdmaRemaining = 0x10;
|
gb->memory.hdmaRemaining = 0x10;
|
||||||
} else {
|
} else {
|
||||||
|
@ -566,8 +568,6 @@ uint8_t GBMemoryWriteHDMA5(struct GB* gb, uint8_t value) {
|
||||||
}
|
}
|
||||||
gb->cpuBlocked = true;
|
gb->cpuBlocked = true;
|
||||||
mTimingSchedule(&gb->timing, &gb->memory.hdmaEvent, 0);
|
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;
|
return value & 0x7F;
|
||||||
}
|
}
|
||||||
|
@ -958,6 +958,11 @@ void _pristineCow(struct GB* gb) {
|
||||||
if (gb->memory.rom == gb->memory.romBase) {
|
if (gb->memory.rom == gb->memory.romBase) {
|
||||||
gb->memory.romBase = newRom;
|
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;
|
gb->memory.rom = newRom;
|
||||||
GBMBCSwitchBank(gb, gb->memory.currentBank);
|
GBMBCSwitchBank(gb, gb->memory.currentBank);
|
||||||
gb->isPristine = false;
|
gb->isPristine = false;
|
||||||
|
|
|
@ -122,6 +122,13 @@ bool GBDeserialize(struct GB* gb, const struct GBSerializedState* state) {
|
||||||
mLOG(GB_STATE, WARN, "Savestate is corrupted: video y is out of range");
|
mLOG(GB_STATE, WARN, "Savestate is corrupted: video y is out of range");
|
||||||
error = true;
|
error = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GBSerializedVideoFlags videoFlags = state->video.flags;
|
||||||
|
if (check16 >= GB_VIDEO_VERTICAL_PIXELS && GBSerializedVideoFlagsGetMode(videoFlags) != 1) {
|
||||||
|
mLOG(GB_STATE, WARN, "Savestate is corrupted: video y is in vblank but mode is not vblank");
|
||||||
|
error = true;
|
||||||
|
}
|
||||||
|
|
||||||
LOAD_16LE(ucheck16, 0, &state->memory.dmaDest);
|
LOAD_16LE(ucheck16, 0, &state->memory.dmaDest);
|
||||||
if (ucheck16 + state->memory.dmaRemaining > GB_SIZE_OAM) {
|
if (ucheck16 + state->memory.dmaRemaining > GB_SIZE_OAM) {
|
||||||
mLOG(GB_STATE, WARN, "Savestate is corrupted: DMA destination is out of range");
|
mLOG(GB_STATE, WARN, "Savestate is corrupted: DMA destination is out of range");
|
||||||
|
|
|
@ -91,11 +91,13 @@ void GBSIOWriteSB(struct GBSIO* sio, uint8_t sb) {
|
||||||
void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc) {
|
void GBSIOWriteSC(struct GBSIO* sio, uint8_t sc) {
|
||||||
sio->period = GBSIOCyclesPerTransfer[GBRegisterSCGetClockSpeed(sc)]; // TODO Shift Clock
|
sio->period = GBSIOCyclesPerTransfer[GBRegisterSCGetClockSpeed(sc)]; // TODO Shift Clock
|
||||||
if (GBRegisterSCIsEnable(sc)) {
|
if (GBRegisterSCIsEnable(sc)) {
|
||||||
mTimingDeschedule(&sio->p->timing, &sio->event);
|
|
||||||
if (GBRegisterSCIsShiftClock(sc)) {
|
if (GBRegisterSCIsShiftClock(sc)) {
|
||||||
|
mTimingDeschedule(&sio->p->timing, &sio->event);
|
||||||
mTimingSchedule(&sio->p->timing, &sio->event, sio->period * (2 - sio->p->doubleSpeed));
|
mTimingSchedule(&sio->p->timing, &sio->event, sio->period * (2 - sio->p->doubleSpeed));
|
||||||
sio->remainingBits = 8;
|
sio->remainingBits = 8;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
mTimingDeschedule(&sio->p->timing, &sio->event);
|
||||||
}
|
}
|
||||||
if (sio->driver) {
|
if (sio->driver) {
|
||||||
sio->driver->writeSC(sio->driver, sc);
|
sio->driver->writeSC(sio->driver, sc);
|
||||||
|
|
|
@ -157,7 +157,7 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Tell the other GBs they can continue up to where we were
|
// 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
|
#ifndef NDEBUG
|
||||||
node->phase = node->p->d.transferActive;
|
node->phase = node->p->d.transferActive;
|
||||||
#endif
|
#endif
|
||||||
|
@ -169,26 +169,28 @@ static int32_t _masterUpdate(struct GBSIOLockstepNode* node) {
|
||||||
|
|
||||||
static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
|
static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
|
||||||
enum mLockstepPhase transferActive;
|
enum mLockstepPhase transferActive;
|
||||||
|
int id;
|
||||||
|
|
||||||
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
|
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
|
||||||
|
ATOMIC_LOAD(id, node->id);
|
||||||
|
|
||||||
bool signal = false;
|
bool signal = false;
|
||||||
switch (transferActive) {
|
switch (transferActive) {
|
||||||
case TRANSFER_IDLE:
|
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;
|
break;
|
||||||
case TRANSFER_STARTING:
|
case TRANSFER_STARTING:
|
||||||
case TRANSFER_FINISHING:
|
case TRANSFER_FINISHING:
|
||||||
break;
|
break;
|
||||||
case TRANSFER_STARTED:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
node->transferFinished = false;
|
node->transferFinished = false;
|
||||||
signal = true;
|
signal = true;
|
||||||
break;
|
break;
|
||||||
case TRANSFER_FINISHED:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
_finishTransfer(node);
|
_finishTransfer(node);
|
||||||
|
@ -199,7 +201,7 @@ static uint32_t _slaveUpdate(struct GBSIOLockstepNode* node) {
|
||||||
node->phase = node->p->d.transferActive;
|
node->phase = node->p->d.transferActive;
|
||||||
#endif
|
#endif
|
||||||
if (signal) {
|
if (signal) {
|
||||||
node->p->d.signal(&node->p->d, 1 << node->id);
|
node->p->d.signal(&node->p->d, 1 << id);
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -215,11 +217,13 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
|
||||||
int32_t cycles = 0;
|
int32_t cycles = 0;
|
||||||
node->nextEvent -= cyclesLate;
|
node->nextEvent -= cyclesLate;
|
||||||
if (node->nextEvent <= 0) {
|
if (node->nextEvent <= 0) {
|
||||||
if (!node->id) {
|
int id;
|
||||||
|
ATOMIC_LOAD(id, node->id);
|
||||||
|
if (!id) {
|
||||||
cycles = _masterUpdate(node);
|
cycles = _masterUpdate(node);
|
||||||
} else {
|
} else {
|
||||||
cycles = _slaveUpdate(node);
|
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;
|
node->eventDiff = 0;
|
||||||
} else {
|
} else {
|
||||||
|
@ -240,7 +244,9 @@ static void _GBSIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
|
||||||
|
|
||||||
static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) {
|
static void GBSIOLockstepNodeWriteSB(struct GBSIODriver* driver, uint8_t value) {
|
||||||
struct GBSIOLockstepNode* node = (struct GBSIOLockstepNode*) driver;
|
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) {
|
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);
|
mLockstepLock(&node->p->d);
|
||||||
bool claimed = false;
|
bool claimed = false;
|
||||||
if (ATOMIC_CMPXCHG(node->p->masterClaimed, claimed, true)) {
|
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[0]->id = 1;
|
||||||
node->p->players[1] = node->p->players[0];
|
|
||||||
node->p->players[0] = node->p->players[1];
|
|
||||||
node->id = 0;
|
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.transferActive, TRANSFER_STARTING);
|
||||||
ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]);
|
ATOMIC_STORE(node->p->d.transferCycles, GBSIOCyclesPerTransfer[(value >> 1) & 1]);
|
||||||
|
|
|
@ -770,7 +770,10 @@ void GBVideoWriteSTAT(struct GBVideo* video, GBRegisterSTAT value) {
|
||||||
if (!GBRegisterLCDCIsEnable(video->p->memory.io[GB_REG_LCDC]) || video->p->model >= GB_MODEL_CGB) {
|
if (!GBRegisterLCDCIsEnable(video->p->memory.io[GB_REG_LCDC]) || video->p->model >= GB_MODEL_CGB) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!_statIRQAsserted(oldStat) && video->mode < 3) {
|
// Writing to STAT on a DMG selects all STAT IRQ types for one cycle.
|
||||||
|
// However, the signal that the mode 2 IRQ relies on is only high for
|
||||||
|
// one cycle, which we don't handle yet. TODO: Handle it.
|
||||||
|
if (!_statIRQAsserted(oldStat) && (video->mode < 2 || GBRegisterSTATIsLYC(video->stat))) {
|
||||||
// TODO: variable for the IRQ line value?
|
// TODO: variable for the IRQ line value?
|
||||||
video->p->memory.io[GB_REG_IF] |= (1 << GB_IRQ_LCDSTAT);
|
video->p->memory.io[GB_REG_IF] |= (1 << GB_IRQ_LCDSTAT);
|
||||||
GBUpdateIRQs(video->p);
|
GBUpdateIRQs(video->p);
|
||||||
|
|
|
@ -106,6 +106,9 @@ void GBAAudioDeinit(struct GBAAudio* audio) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
|
void GBAAudioResizeBuffer(struct GBAAudio* audio, size_t samples) {
|
||||||
|
if (samples > 0x2000) {
|
||||||
|
samples = 0x2000;
|
||||||
|
}
|
||||||
mCoreSyncLockAudio(audio->p->sync);
|
mCoreSyncLockAudio(audio->p->sync);
|
||||||
audio->samples = samples;
|
audio->samples = samples;
|
||||||
blip_clear(audio->psg.left);
|
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);
|
mLOG(GBA_AUDIO, GAME_ERROR, "Invalid FIFO destination: 0x%08X", info->dest);
|
||||||
return;
|
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 (audio->mixer) {
|
||||||
if (magic[0] - MP2K_MAGIC <= MP2K_LOCK_MAX) {
|
uint32_t source = info->source;
|
||||||
audio->mixer->engage(audio->mixer, source - 0x350);
|
uint32_t offsets[] = { 0x350, 0x980 };
|
||||||
} else if (magic[1] - MP2K_MAGIC <= MP2K_LOCK_MAX) {
|
size_t i;
|
||||||
audio->mixer->engage(audio->mixer, source - 0x980);
|
for (i = 0; i < sizeof(offsets) / sizeof(*offsets); ++i) {
|
||||||
} else {
|
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;
|
audio->externalMixing = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -231,16 +241,39 @@ void GBAAudioWriteSOUNDCNT_HI(struct GBAAudio* audio, uint16_t value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAAudioWriteSOUNDCNT_X(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);
|
audio->enable = GBAudioEnableGetEnable(value);
|
||||||
GBAudioWriteNR52(&audio->psg, 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) {
|
void GBAAudioWriteSOUNDBIAS(struct GBAAudio* audio, uint16_t value) {
|
||||||
|
int32_t timestamp = mTimingCurrentTime(&audio->p->timing);
|
||||||
|
GBAAudioSample(audio, timestamp);
|
||||||
audio->soundbias = value;
|
audio->soundbias = value;
|
||||||
int32_t oldSampleInterval = audio->sampleInterval;
|
int32_t oldSampleInterval = audio->sampleInterval;
|
||||||
audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value);
|
audio->sampleInterval = 0x200 >> GBARegisterSOUNDBIASGetResolution(value);
|
||||||
if (oldSampleInterval != audio->sampleInterval && audio->p->stream && audio->p->stream->audioRateChanged) {
|
if (oldSampleInterval != audio->sampleInterval) {
|
||||||
audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval);
|
timestamp -= audio->lastSample;
|
||||||
|
audio->sampleIndex = timestamp >> (9 - GBARegisterSOUNDBIASGetResolution(value));
|
||||||
|
if (audio->sampleIndex < 0 || audio->sampleIndex >= GBA_MAX_SAMPLES) {
|
||||||
|
audio->sampleIndex = 0;
|
||||||
|
}
|
||||||
|
if (audio->p->stream && audio->p->stream->audioRateChanged) {
|
||||||
|
audio->p->stream->audioRateChanged(audio->p->stream, GBA_ARM7TDMI_FREQUENCY / audio->sampleInterval);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -327,6 +360,9 @@ void GBAAudioSampleFIFO(struct GBAAudio* audio, int fifoId, int32_t cycles) {
|
||||||
int bits = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
|
int bits = 2 << GBARegisterSOUNDBIASGetResolution(audio->soundbias);
|
||||||
until += 1 << (9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias));
|
until += 1 << (9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias));
|
||||||
until >>= 9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias);
|
until >>= 9 - GBARegisterSOUNDBIASGetResolution(audio->soundbias);
|
||||||
|
if (UNLIKELY(bits < until)) {
|
||||||
|
until = bits;
|
||||||
|
}
|
||||||
int i;
|
int i;
|
||||||
for (i = bits - until; i < bits; ++i) {
|
for (i = bits - until; i < bits; ++i) {
|
||||||
channel->samples[i] = channel->internalSample;
|
channel->samples[i] = channel->internalSample;
|
||||||
|
@ -506,6 +542,16 @@ void GBAAudioSerialize(const struct GBAAudio* audio, struct GBASerializedState*
|
||||||
void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) {
|
void GBAAudioDeserialize(struct GBAAudio* audio, const struct GBASerializedState* state) {
|
||||||
GBAudioPSGDeserialize(&audio->psg, &state->audio.psg, &state->audio.flags);
|
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->chA.internalSample, 0, &state->audio.internalA);
|
||||||
LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB);
|
LOAD_32(audio->chB.internalSample, 0, &state->audio.internalB);
|
||||||
memcpy(audio->chA.samples, state->samples.chA, sizeof(audio->chA.samples));
|
memcpy(audio->chA.samples, state->samples.chA, sizeof(audio->chA.samples));
|
||||||
|
|
|
@ -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) {
|
static int16_t _ArcTan2(int32_t x, int32_t y, int32_t* r1, uint32_t* cycles) {
|
||||||
if (!y) {
|
if (!y) {
|
||||||
|
*cycles = 11;
|
||||||
if (x >= 0) {
|
if (x >= 0) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
return 0x8000;
|
return 0x8000;
|
||||||
}
|
}
|
||||||
if (!x) {
|
if (!x) {
|
||||||
|
*cycles = 11;
|
||||||
if (y >= 0) {
|
if (y >= 0) {
|
||||||
return 0x4000;
|
return 0x4000;
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,18 +320,20 @@ void _gyroReadPins(struct GBACartridgeHardware* hw) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Write bit on falling edge
|
||||||
|
bool doOutput = hw->gyroEdge && !(hw->pinState & 2);
|
||||||
if (hw->pinState & 1) {
|
if (hw->pinState & 1) {
|
||||||
if (gyro->sample) {
|
if (gyro->sample) {
|
||||||
gyro->sample(gyro);
|
gyro->sample(gyro);
|
||||||
}
|
}
|
||||||
int32_t sample = gyro->readGyroZ(gyro);
|
int32_t sample = gyro->readGyroZ(gyro);
|
||||||
|
|
||||||
// Normalize to ~12 bits, focused on 0x6C0
|
// Normalize to ~12 bits, focused on 0x700
|
||||||
hw->gyroSample = (sample >> 21) + 0x6C0; // Crop off an extra bit so that we can't go negative
|
hw->gyroSample = (sample >> 21) + 0x700; // Crop off an extra bit so that we can't go negative
|
||||||
|
doOutput = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hw->gyroEdge && !(hw->pinState & 2)) {
|
if (doOutput) {
|
||||||
// Write bit on falling edge
|
|
||||||
unsigned bit = hw->gyroSample >> 15;
|
unsigned bit = hw->gyroSample >> 15;
|
||||||
hw->gyroSample <<= 1;
|
hw->gyroSample <<= 1;
|
||||||
_outputPins(hw, bit << 2);
|
_outputPins(hw, bit << 2);
|
||||||
|
@ -421,8 +423,8 @@ void GBAHardwareTiltWrite(struct GBACartridgeHardware* hw, uint32_t address, uin
|
||||||
int32_t x = rotationSource->readTiltX(rotationSource);
|
int32_t x = rotationSource->readTiltX(rotationSource);
|
||||||
int32_t y = rotationSource->readTiltY(rotationSource);
|
int32_t y = rotationSource->readTiltY(rotationSource);
|
||||||
// Normalize to ~12 bits, focused on 0x3A0
|
// 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->tiltX = 0x3A0 - (x >> 22);
|
||||||
hw->tiltY = (y >> 21) + 0x3A0;
|
hw->tiltY = 0x3A0 - (y >> 22);
|
||||||
} else {
|
} else {
|
||||||
mLOG(GBA_HW, GAME_ERROR, "Tilt sensor wrote wrong byte to %04x: %02x", address, value);
|
mLOG(GBA_HW, GAME_ERROR, "Tilt sensor wrote wrong byte to %04x: %02x", address, value);
|
||||||
}
|
}
|
||||||
|
@ -496,6 +498,18 @@ void GBAHardwareDeserialize(struct GBACartridgeHardware* hw, const struct GBASer
|
||||||
LOAD_16(hw->direction, 0, &state->hw.pinDirection);
|
LOAD_16(hw->direction, 0, &state->hw.pinDirection);
|
||||||
hw->devices = state->hw.devices;
|
hw->devices = state->hw.devices;
|
||||||
|
|
||||||
|
if ((hw->devices & (HW_RTC | HW_RUMBLE | HW_LIGHT_SENSOR | HW_GYRO | HW_TILT)) && hw->gpioBase) {
|
||||||
|
if (hw->readWrite) {
|
||||||
|
STORE_16(hw->pinState, 0, hw->gpioBase);
|
||||||
|
STORE_16(hw->direction, 2, hw->gpioBase);
|
||||||
|
STORE_16(hw->readWrite, 4, hw->gpioBase);
|
||||||
|
} else {
|
||||||
|
hw->gpioBase[0] = 0;
|
||||||
|
hw->gpioBase[1] = 0;
|
||||||
|
hw->gpioBase[2] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
LOAD_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining);
|
LOAD_32(hw->rtc.bytesRemaining, 0, &state->hw.rtcBytesRemaining);
|
||||||
LOAD_32(hw->rtc.transferStep, 0, &state->hw.rtcTransferStep);
|
LOAD_32(hw->rtc.transferStep, 0, &state->hw.rtcTransferStep);
|
||||||
LOAD_32(hw->rtc.bitsRead, 0, &state->hw.rtcBitsRead);
|
LOAD_32(hw->rtc.bitsRead, 0, &state->hw.rtcBitsRead);
|
||||||
|
|
|
@ -126,13 +126,13 @@ static bool GBACheatAddAutodetect(struct GBACheatSet* set, uint32_t op1, uint32_
|
||||||
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3);
|
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3);
|
||||||
}
|
}
|
||||||
|
|
||||||
rgsaP = GBACheatGameSharkProbability(op1, op1);
|
rgsaP = GBACheatGameSharkProbability(op1, op2);
|
||||||
if (rgsaP > maxProbability) {
|
if (rgsaP > maxProbability) {
|
||||||
maxProbability = rgsaP;
|
maxProbability = rgsaP;
|
||||||
GBACheatSetGameSharkVersion(set, GBA_GS_GSAV1_RAW);
|
GBACheatSetGameSharkVersion(set, GBA_GS_GSAV1_RAW);
|
||||||
}
|
}
|
||||||
|
|
||||||
rparP = GBACheatProActionReplayProbability(op1, op1);
|
rparP = GBACheatProActionReplayProbability(op1, op2);
|
||||||
if (rparP > maxProbability) {
|
if (rparP > maxProbability) {
|
||||||
maxProbability = rparP;
|
maxProbability = rparP;
|
||||||
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3_RAW);
|
GBACheatSetGameSharkVersion(set, GBA_GS_PARV3_RAW);
|
||||||
|
@ -180,14 +180,25 @@ bool GBACheatAddVBALine(struct GBACheatSet* cheats, const char* line) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
struct mCheat* cheat = mCheatListAppend(&cheats->d.list);
|
if (address < BASE_CART0 || address >= BASE_CART_SRAM) {
|
||||||
cheat->address = address;
|
struct mCheat* cheat = mCheatListAppend(&cheats->d.list);
|
||||||
cheat->operandOffset = 0;
|
memset(cheat, 0, sizeof(*cheat));
|
||||||
cheat->addressOffset = 0;
|
cheat->address = address;
|
||||||
cheat->repeat = 1;
|
cheat->operandOffset = 0;
|
||||||
cheat->type = CHEAT_ASSIGN;
|
cheat->addressOffset = 0;
|
||||||
cheat->width = width;
|
cheat->repeat = 1;
|
||||||
cheat->operand = value;
|
cheat->type = CHEAT_ASSIGN;
|
||||||
|
cheat->width = width;
|
||||||
|
cheat->operand = value;
|
||||||
|
} else {
|
||||||
|
struct mCheatPatch* patch = mCheatPatchListAppend(&cheats->d.romPatches);
|
||||||
|
memset(patch, 0, sizeof(*patch));
|
||||||
|
patch->width = width;
|
||||||
|
patch->address = address;
|
||||||
|
patch->segment = 0;
|
||||||
|
patch->value = value;
|
||||||
|
patch->check = false;
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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_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_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_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[] = {
|
static const struct mCoreMemoryBlock _GBAMemoryBlocksEEPROM[] = {
|
||||||
|
@ -684,7 +684,7 @@ static void _GBACoreReset(struct mCore* core) {
|
||||||
if (!found) {
|
if (!found) {
|
||||||
char path[PATH_MAX];
|
char path[PATH_MAX];
|
||||||
mCoreConfigDirectory(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);
|
bios = VFileOpen(path, O_RDONLY);
|
||||||
if (bios && GBAIsBIOS(bios)) {
|
if (bios && GBAIsBIOS(bios)) {
|
||||||
found = true;
|
found = true;
|
||||||
|
@ -700,8 +700,8 @@ static void _GBACoreReset(struct mCore* core) {
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
ARMReset(core->cpu);
|
ARMReset(core->cpu);
|
||||||
bool forceSkip = gba->romVf && GBAIsMB(gba->romVf);
|
bool forceSkip = gba->mbVf || (core->opts.skipBios && (gba->romVf || gba->memory.rom));
|
||||||
if (!(forceSkip || core->opts.skipBios) && (gba->romVf || gba->memory.rom) && gba->pristineRomSize >= 0xA0 && gba->biosVf) {
|
if (!forceSkip && (gba->romVf || gba->memory.rom) && gba->pristineRomSize >= 0xA0 && gba->biosVf) {
|
||||||
uint32_t crc = doCrc32(&gba->memory.rom[1], 0x9C);
|
uint32_t crc = doCrc32(&gba->memory.rom[1], 0x9C);
|
||||||
if (crc != LOGO_CRC32) {
|
if (crc != LOGO_CRC32) {
|
||||||
mLOG(STATUS, WARN, "Invalid logo, skipping BIOS");
|
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);
|
GBASkipBIOS(core->board);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1112,20 +1112,44 @@ static void _GBACoreDetachDebugger(struct mCore* core) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) {
|
static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) {
|
||||||
|
struct GBA* gba = core->board;
|
||||||
bool closeAfter = false;
|
bool closeAfter = false;
|
||||||
core->symbolTable = mDebuggerSymbolTableCreate();
|
if (!core->symbolTable) {
|
||||||
|
core->symbolTable = mDebuggerSymbolTableCreate();
|
||||||
|
}
|
||||||
|
off_t seek;
|
||||||
|
if (vf) {
|
||||||
|
seek = vf->seek(vf, 0, SEEK_CUR);
|
||||||
|
vf->seek(vf, 0, SEEK_SET);
|
||||||
|
}
|
||||||
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
#if !defined(MINIMAL_CORE) || MINIMAL_CORE < 2
|
||||||
#ifdef USE_ELF
|
#ifdef USE_ELF
|
||||||
if (!vf) {
|
if (!vf && core->dirs.base) {
|
||||||
closeAfter = true;
|
closeAfter = true;
|
||||||
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".elf", O_RDONLY);
|
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".elf", O_RDONLY);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (!vf) {
|
if (!vf && core->dirs.base) {
|
||||||
closeAfter = true;
|
|
||||||
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY);
|
vf = mDirectorySetOpenSuffix(&core->dirs, core->dirs.base, ".sym", O_RDONLY);
|
||||||
|
if (vf) {
|
||||||
|
mDebuggerLoadARMIPSSymbols(core->symbolTable, vf);
|
||||||
|
vf->close(vf);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
if (!vf && gba->mbVf) {
|
||||||
|
closeAfter = false;
|
||||||
|
vf = gba->mbVf;
|
||||||
|
seek = vf->seek(vf, 0, SEEK_CUR);
|
||||||
|
vf->seek(vf, 0, SEEK_SET);
|
||||||
|
}
|
||||||
|
if (!vf && gba->romVf) {
|
||||||
|
closeAfter = false;
|
||||||
|
vf = gba->romVf;
|
||||||
|
seek = vf->seek(vf, 0, SEEK_CUR);
|
||||||
|
vf->seek(vf, 0, SEEK_SET);
|
||||||
|
}
|
||||||
if (!vf) {
|
if (!vf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -1136,13 +1160,12 @@ static void _GBACoreLoadSymbols(struct mCore* core, struct VFile* vf) {
|
||||||
mCoreLoadELFSymbols(core->symbolTable, elf);
|
mCoreLoadELFSymbols(core->symbolTable, elf);
|
||||||
#endif
|
#endif
|
||||||
ELFClose(elf);
|
ELFClose(elf);
|
||||||
} else
|
|
||||||
#endif
|
|
||||||
{
|
|
||||||
mDebuggerLoadARMIPSSymbols(core->symbolTable, vf);
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
if (closeAfter) {
|
if (closeAfter) {
|
||||||
vf->close(vf);
|
vf->close(vf);
|
||||||
|
} else {
|
||||||
|
vf->seek(vf, seek, SEEK_SET);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -245,9 +245,6 @@ static void _mp2kReload(struct GBAAudioMixer* mixer) {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address) {
|
bool _mp2kEngage(struct GBAAudioMixer* mixer, uint32_t address) {
|
||||||
if (address < BASE_WORKING_RAM) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (address != mixer->contextAddress) {
|
if (address != mixer->contextAddress) {
|
||||||
mixer->contextAddress = address;
|
mixer->contextAddress = address;
|
||||||
mixer->p->externalMixing = true;
|
mixer->p->externalMixing = true;
|
||||||
|
|
|
@ -132,6 +132,7 @@ static void GBAInit(void* cpu, struct mCPUComponent* component) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAUnloadROM(struct GBA* gba) {
|
void GBAUnloadROM(struct GBA* gba) {
|
||||||
|
GBAMemoryClearAGBPrint(gba);
|
||||||
if (gba->memory.rom && !gba->isPristine) {
|
if (gba->memory.rom && !gba->isPristine) {
|
||||||
if (gba->yankedRomSize) {
|
if (gba->yankedRomSize) {
|
||||||
gba->yankedRomSize = 0;
|
gba->yankedRomSize = 0;
|
||||||
|
@ -213,6 +214,7 @@ void GBAReset(struct ARMCore* cpu) {
|
||||||
gba->earlyExit = false;
|
gba->earlyExit = false;
|
||||||
gba->dmaPC = 0;
|
gba->dmaPC = 0;
|
||||||
gba->biosStall = 0;
|
gba->biosStall = 0;
|
||||||
|
gba->keysLast = 0x400;
|
||||||
if (gba->yankedRomSize) {
|
if (gba->yankedRomSize) {
|
||||||
gba->memory.romSize = gba->yankedRomSize;
|
gba->memory.romSize = gba->yankedRomSize;
|
||||||
gba->memory.romMask = toPow2(gba->memory.romSize) - 1;
|
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 (cpu->gprs[ARM_PC] == BASE_RESET + WORD_SIZE_ARM) {
|
||||||
if (gba->memory.rom) {
|
if (gba->memory.rom) {
|
||||||
cpu->gprs[ARM_PC] = BASE_CART0;
|
cpu->gprs[ARM_PC] = BASE_CART0;
|
||||||
} else {
|
} else if (gba->memory.wram[0x30]) {
|
||||||
cpu->gprs[ARM_PC] = BASE_WORKING_RAM + 0xC0;
|
cpu->gprs[ARM_PC] = BASE_WORKING_RAM + 0xC0;
|
||||||
|
} else {
|
||||||
|
cpu->gprs[ARM_PC] = BASE_WORKING_RAM;
|
||||||
}
|
}
|
||||||
gba->video.vcount = 0x7E;
|
gba->video.vcount = 0x7E;
|
||||||
gba->memory.io[REG_VCOUNT >> 1] = 0x7E;
|
gba->memory.io[REG_VCOUNT >> 1] = 0x7E;
|
||||||
|
|
|
@ -50,13 +50,13 @@ const uint8_t hleBios[SIZE_BIOS] = {
|
||||||
0x0c, 0x80, 0xbd, 0xe8, 0x30, 0x40, 0x2d, 0xe9, 0x02, 0x46, 0xa0, 0xe1,
|
0x0c, 0x80, 0xbd, 0xe8, 0x30, 0x40, 0x2d, 0xe9, 0x02, 0x46, 0xa0, 0xe1,
|
||||||
0x00, 0xc0, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3,
|
0x00, 0xc0, 0xa0, 0xe1, 0x01, 0x50, 0xa0, 0xe1, 0x01, 0x04, 0x12, 0xe3,
|
||||||
0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a,
|
0x0f, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x12, 0xe3, 0x05, 0x00, 0x00, 0x0a,
|
||||||
0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xbc, 0xe8, 0x04, 0x00, 0x55, 0xe1,
|
0x24, 0x45, 0x85, 0xe0, 0x08, 0x00, 0xb0, 0xe8, 0x04, 0x00, 0x51, 0xe1,
|
||||||
0x08, 0x00, 0xa5, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea,
|
0x08, 0x00, 0xa1, 0xb8, 0xfc, 0xff, 0xff, 0xba, 0x14, 0x00, 0x00, 0xea,
|
||||||
0x01, 0xc0, 0xcc, 0xe3, 0x01, 0x50, 0xc5, 0xe3, 0xa4, 0x45, 0x85, 0xe0,
|
0x01, 0xc0, 0xcc, 0xe3, 0x01, 0x50, 0xc5, 0xe3, 0xa4, 0x45, 0x85, 0xe0,
|
||||||
0xb0, 0x30, 0xdc, 0xe1, 0x04, 0x00, 0x55, 0xe1, 0xb2, 0x30, 0xc5, 0xb0,
|
0xb0, 0x30, 0xdc, 0xe1, 0x04, 0x00, 0x55, 0xe1, 0xb2, 0x30, 0xc5, 0xb0,
|
||||||
0xfc, 0xff, 0xff, 0xba, 0x0c, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3,
|
0xfc, 0xff, 0xff, 0xba, 0x0c, 0x00, 0x00, 0xea, 0x01, 0x03, 0x12, 0xe3,
|
||||||
0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1,
|
0x05, 0x00, 0x00, 0x0a, 0x24, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x51, 0xe1,
|
||||||
0x08, 0x00, 0xbc, 0xb8, 0x08, 0x00, 0xa5, 0xb8, 0xfb, 0xff, 0xff, 0xba,
|
0x08, 0x00, 0xb0, 0xb8, 0x08, 0x00, 0xa1, 0xb8, 0xfb, 0xff, 0xff, 0xba,
|
||||||
0x04, 0x00, 0x00, 0xea, 0xa4, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1,
|
0x04, 0x00, 0x00, 0xea, 0xa4, 0x45, 0x85, 0xe0, 0x04, 0x00, 0x55, 0xe1,
|
||||||
0xb2, 0x30, 0xdc, 0xb0, 0xb2, 0x30, 0xc5, 0xb0, 0xfb, 0xff, 0xff, 0xba,
|
0xb2, 0x30, 0xdc, 0xb0, 0xb2, 0x30, 0xc5, 0xb0, 0xfb, 0xff, 0xff, 0xba,
|
||||||
0x17, 0x3e, 0xa0, 0xe3, 0x30, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9,
|
0x17, 0x3e, 0xa0, 0xe3, 0x30, 0x80, 0xbd, 0xe8, 0xf0, 0x47, 0x2d, 0xe9,
|
||||||
|
|
|
@ -209,10 +209,10 @@ tst r2, #0x04000000
|
||||||
beq 1f
|
beq 1f
|
||||||
@ Word
|
@ Word
|
||||||
add r4, r5, r4, lsr #10
|
add r4, r5, r4, lsr #10
|
||||||
ldmia r12!, {r3}
|
ldmia r0!, {r3}
|
||||||
2:
|
2:
|
||||||
cmp r5, r4
|
cmp r1, r4
|
||||||
stmltia r5!, {r3}
|
stmltia r1!, {r3}
|
||||||
blt 2b
|
blt 2b
|
||||||
b 3f
|
b 3f
|
||||||
@ Halfword
|
@ Halfword
|
||||||
|
@ -233,9 +233,9 @@ beq 1f
|
||||||
@ Word
|
@ Word
|
||||||
add r4, r5, r4, lsr #10
|
add r4, r5, r4, lsr #10
|
||||||
2:
|
2:
|
||||||
cmp r5, r4
|
cmp r1, r4
|
||||||
ldmltia r12!, {r3}
|
ldmltia r0!, {r3}
|
||||||
stmltia r5!, {r3}
|
stmltia r1!, {r3}
|
||||||
blt 2b
|
blt 2b
|
||||||
b 3f
|
b 3f
|
||||||
@ Halfword
|
@ Halfword
|
||||||
|
|
144
src/gba/io.c
144
src/gba/io.c
|
@ -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,
|
||||||
0, 0, 0, 0, 0, 0, 0, 0,
|
0, 0, 0, 0, 0, 0, 0, 0,
|
||||||
// Audio
|
// Audio
|
||||||
1, 1, 1, 0, 1, 0, 1, 0,
|
0, 0, 1, 0, 0, 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, 0, 0,
|
0, 0, 1, 0, 0, 0, 0, 0,
|
||||||
1, 1, 1, 1, 1, 1, 1, 1,
|
1, 1, 1, 1, 1, 1, 1, 1,
|
||||||
1, 1, 1, 1, 0, 0, 0, 0,
|
1, 1, 1, 1, 0, 0, 0, 0,
|
||||||
|
@ -378,17 +378,19 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
||||||
break;
|
break;
|
||||||
case REG_SOUND1CNT_HI:
|
case REG_SOUND1CNT_HI:
|
||||||
GBAAudioWriteSOUND1CNT_HI(&gba->audio, value);
|
GBAAudioWriteSOUND1CNT_HI(&gba->audio, value);
|
||||||
|
value &= 0xFFC0;
|
||||||
break;
|
break;
|
||||||
case REG_SOUND1CNT_X:
|
case REG_SOUND1CNT_X:
|
||||||
GBAAudioWriteSOUND1CNT_X(&gba->audio, value);
|
GBAAudioWriteSOUND1CNT_X(&gba->audio, value);
|
||||||
value &= 0x47FF;
|
value &= 0x4000;
|
||||||
break;
|
break;
|
||||||
case REG_SOUND2CNT_LO:
|
case REG_SOUND2CNT_LO:
|
||||||
GBAAudioWriteSOUND2CNT_LO(&gba->audio, value);
|
GBAAudioWriteSOUND2CNT_LO(&gba->audio, value);
|
||||||
|
value &= 0xFFC0;
|
||||||
break;
|
break;
|
||||||
case REG_SOUND2CNT_HI:
|
case REG_SOUND2CNT_HI:
|
||||||
GBAAudioWriteSOUND2CNT_HI(&gba->audio, value);
|
GBAAudioWriteSOUND2CNT_HI(&gba->audio, value);
|
||||||
value &= 0x47FF;
|
value &= 0x4000;
|
||||||
break;
|
break;
|
||||||
case REG_SOUND3CNT_LO:
|
case REG_SOUND3CNT_LO:
|
||||||
GBAAudioWriteSOUND3CNT_LO(&gba->audio, value);
|
GBAAudioWriteSOUND3CNT_LO(&gba->audio, value);
|
||||||
|
@ -396,16 +398,15 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
||||||
break;
|
break;
|
||||||
case REG_SOUND3CNT_HI:
|
case REG_SOUND3CNT_HI:
|
||||||
GBAAudioWriteSOUND3CNT_HI(&gba->audio, value);
|
GBAAudioWriteSOUND3CNT_HI(&gba->audio, value);
|
||||||
value &= 0xE03F;
|
value &= 0xE000;
|
||||||
break;
|
break;
|
||||||
case REG_SOUND3CNT_X:
|
case REG_SOUND3CNT_X:
|
||||||
GBAAudioWriteSOUND3CNT_X(&gba->audio, value);
|
GBAAudioWriteSOUND3CNT_X(&gba->audio, value);
|
||||||
// TODO: The low bits need to not be readable, but still 8-bit writable
|
value &= 0x4000;
|
||||||
value &= 0x47FF;
|
|
||||||
break;
|
break;
|
||||||
case REG_SOUND4CNT_LO:
|
case REG_SOUND4CNT_LO:
|
||||||
GBAAudioWriteSOUND4CNT_LO(&gba->audio, value);
|
GBAAudioWriteSOUND4CNT_LO(&gba->audio, value);
|
||||||
value &= 0xFF3F;
|
value &= 0xFF00;
|
||||||
break;
|
break;
|
||||||
case REG_SOUND4CNT_HI:
|
case REG_SOUND4CNT_HI:
|
||||||
GBAAudioWriteSOUND4CNT_HI(&gba->audio, value);
|
GBAAudioWriteSOUND4CNT_HI(&gba->audio, value);
|
||||||
|
@ -583,6 +584,21 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
||||||
case REG_MAX:
|
case REG_MAX:
|
||||||
// Some bad interrupt libraries will write to this
|
// Some bad interrupt libraries will write to this
|
||||||
break;
|
break;
|
||||||
|
case REG_POSTFLG:
|
||||||
|
if (gba->memory.activeRegion == REGION_BIOS) {
|
||||||
|
if (gba->memory.io[address >> 1]) {
|
||||||
|
if (value & 0x8000) {
|
||||||
|
GBAStop(gba);
|
||||||
|
} else {
|
||||||
|
GBAHalt(gba);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
value &= ~0x8000;
|
||||||
|
} else {
|
||||||
|
mLOG(GBA_IO, GAME_ERROR, "Write to BIOS-only I/O register: %03X", address);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
break;
|
||||||
case REG_EXWAITCNT_HI:
|
case REG_EXWAITCNT_HI:
|
||||||
// This register sits outside of the normal I/O block, so we need to stash it somewhere unused
|
// This register sits outside of the normal I/O block, so we need to stash it somewhere unused
|
||||||
address = REG_INTERNAL_EXWAITCNT_HI;
|
address = REG_INTERNAL_EXWAITCNT_HI;
|
||||||
|
@ -615,19 +631,6 @@ void GBAIOWrite(struct GBA* gba, uint32_t address, uint16_t value) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) {
|
void GBAIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) {
|
||||||
if (address == REG_HALTCNT) {
|
|
||||||
value &= 0x80;
|
|
||||||
if (!value) {
|
|
||||||
GBAHalt(gba);
|
|
||||||
} else {
|
|
||||||
GBAStop(gba);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (address == REG_POSTFLG) {
|
|
||||||
gba->memory.io[(address & (SIZE_IO - 1)) >> 1] = value;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (address >= REG_DEBUG_STRING && address - REG_DEBUG_STRING < sizeof(gba->debugString)) {
|
if (address >= REG_DEBUG_STRING && address - REG_DEBUG_STRING < sizeof(gba->debugString)) {
|
||||||
gba->debugString[address - REG_DEBUG_STRING] = value;
|
gba->debugString[address - REG_DEBUG_STRING] = value;
|
||||||
return;
|
return;
|
||||||
|
@ -635,9 +638,96 @@ void GBAIOWrite8(struct GBA* gba, uint32_t address, uint8_t value) {
|
||||||
if (address > SIZE_IO) {
|
if (address > SIZE_IO) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
uint16_t value16 = value << (8 * (address & 1));
|
uint16_t value16;
|
||||||
value16 |= (gba->memory.io[(address & (SIZE_IO - 1)) >> 1]) & ~(0xFF << (8 * (address & 1)));
|
|
||||||
GBAIOWrite(gba, address & 0xFFFFFFFE, value16);
|
switch (address) {
|
||||||
|
case REG_SOUND1CNT_HI:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR11(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND1CNT_HI >> 1] &= 0xFF00;
|
||||||
|
gba->memory.io[REG_SOUND1CNT_HI >> 1] |= value & 0xC0;
|
||||||
|
break;
|
||||||
|
case REG_SOUND1CNT_HI + 1:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR12(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND1CNT_HI >> 1] &= 0x00C0;
|
||||||
|
gba->memory.io[REG_SOUND1CNT_HI >> 1] |= value << 8;
|
||||||
|
break;
|
||||||
|
case REG_SOUND1CNT_X:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR13(&gba->audio.psg, value);
|
||||||
|
break;
|
||||||
|
case REG_SOUND1CNT_X + 1:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR14(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND1CNT_X >> 1] = (value & 0x40) << 8;
|
||||||
|
break;
|
||||||
|
case REG_SOUND2CNT_LO:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR21(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND2CNT_LO >> 1] &= 0xFF00;
|
||||||
|
gba->memory.io[REG_SOUND2CNT_LO >> 1] |= value & 0xC0;
|
||||||
|
break;
|
||||||
|
case REG_SOUND2CNT_LO + 1:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR22(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND2CNT_LO >> 1] &= 0x00C0;
|
||||||
|
gba->memory.io[REG_SOUND2CNT_LO >> 1] |= value << 8;
|
||||||
|
break;
|
||||||
|
case REG_SOUND2CNT_HI:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR23(&gba->audio.psg, value);
|
||||||
|
break;
|
||||||
|
case REG_SOUND2CNT_HI + 1:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR24(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND2CNT_HI >> 1] = (value & 0x40) << 8;
|
||||||
|
break;
|
||||||
|
case REG_SOUND3CNT_HI:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR31(&gba->audio.psg, value);
|
||||||
|
break;
|
||||||
|
case REG_SOUND3CNT_HI + 1:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
gba->audio.psg.ch3.volume = GBAudioRegisterBankVolumeGetVolumeGBA(value);
|
||||||
|
gba->memory.io[REG_SOUND3CNT_HI >> 1] = (value & 0xE0) << 8;
|
||||||
|
break;
|
||||||
|
case REG_SOUND3CNT_X:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR33(&gba->audio.psg, value);
|
||||||
|
break;
|
||||||
|
case REG_SOUND3CNT_X + 1:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR34(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND3CNT_X >> 1] = (value & 0x40) << 8;
|
||||||
|
break;
|
||||||
|
case REG_SOUND4CNT_LO:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR41(&gba->audio.psg, value);
|
||||||
|
break;
|
||||||
|
case REG_SOUND4CNT_LO + 1:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR42(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND4CNT_LO >> 1] = value << 8;
|
||||||
|
break;
|
||||||
|
case REG_SOUND4CNT_HI:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR43(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND4CNT_HI >> 1] &= 0x4000;
|
||||||
|
gba->memory.io[REG_SOUND4CNT_HI >> 1] |= value;
|
||||||
|
break;
|
||||||
|
case REG_SOUND4CNT_HI + 1:
|
||||||
|
GBAAudioSample(&gba->audio, mTimingCurrentTime(&gba->timing));
|
||||||
|
GBAudioWriteNR44(&gba->audio.psg, value);
|
||||||
|
gba->memory.io[REG_SOUND4CNT_HI >> 1] &= 0x00FF;
|
||||||
|
gba->memory.io[REG_SOUND4CNT_HI >> 1] |= (value & 0x40) << 8;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
value16 = value << (8 * (address & 1));
|
||||||
|
value16 |= (gba->memory.io[(address & (SIZE_IO - 1)) >> 1]) & ~(0xFF << (8 * (address & 1)));
|
||||||
|
GBAIOWrite(gba, address & 0xFFFFFFFE, value16);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) {
|
void GBAIOWrite32(struct GBA* gba, uint32_t address, uint32_t value) {
|
||||||
|
@ -858,10 +948,6 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
||||||
gba->memory.io[REG_JOYSTAT >> 1] &= ~JOYSTAT_RECV;
|
gba->memory.io[REG_JOYSTAT >> 1] &= ~JOYSTAT_RECV;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case REG_POSTFLG:
|
|
||||||
mLOG(GBA_IO, STUB, "Stub I/O register read: %03x", address);
|
|
||||||
break;
|
|
||||||
|
|
||||||
// Wave RAM can be written and read even if the audio hardware is disabled.
|
// Wave RAM can be written and read even if the audio hardware is disabled.
|
||||||
// However, it is not possible to switch between the two banks because it
|
// However, it is not possible to switch between the two banks because it
|
||||||
// isn't possible to write to register SOUND3CNT_LO.
|
// isn't possible to write to register SOUND3CNT_LO.
|
||||||
|
@ -935,6 +1021,7 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
||||||
case REG_IF:
|
case REG_IF:
|
||||||
case REG_WAITCNT:
|
case REG_WAITCNT:
|
||||||
case REG_IME:
|
case REG_IME:
|
||||||
|
case REG_POSTFLG:
|
||||||
// Handled transparently by registers
|
// Handled transparently by registers
|
||||||
break;
|
break;
|
||||||
case 0x066:
|
case 0x066:
|
||||||
|
@ -949,6 +1036,7 @@ uint16_t GBAIORead(struct GBA* gba, uint32_t address) {
|
||||||
case 0x142:
|
case 0x142:
|
||||||
case 0x15A:
|
case 0x15A:
|
||||||
case 0x206:
|
case 0x206:
|
||||||
|
case 0x302:
|
||||||
mLOG(GBA_IO, GAME_ERROR, "Read from unused I/O register: %03X", address);
|
mLOG(GBA_IO, GAME_ERROR, "Read from unused I/O register: %03X", address);
|
||||||
return 0;
|
return 0;
|
||||||
// These registers sit outside of the normal I/O block, so we need to stash them somewhere unused
|
// These registers sit outside of the normal I/O block, so we need to stash them somewhere unused
|
||||||
|
|
|
@ -128,6 +128,21 @@ void GBAMemoryReset(struct GBA* gba) {
|
||||||
GBAAdjustWaitstates(gba, 0);
|
GBAAdjustWaitstates(gba, 0);
|
||||||
GBAAdjustEWRAMWaitstates(gba, 0x0D00);
|
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.activeRegion = -1;
|
||||||
gba->memory.agbPrintProtect = 0;
|
gba->memory.agbPrintProtect = 0;
|
||||||
gba->memory.agbPrintBase = 0;
|
gba->memory.agbPrintBase = 0;
|
||||||
|
@ -140,17 +155,6 @@ void GBAMemoryReset(struct GBA* gba) {
|
||||||
mappedMemoryFree(gba->memory.agbPrintBufferBackup, SIZE_AGB_PRINT);
|
mappedMemoryFree(gba->memory.agbPrintBufferBackup, SIZE_AGB_PRINT);
|
||||||
gba->memory.agbPrintBufferBackup = NULL;
|
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) {
|
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); \
|
LOAD_32(value, address & 0x0001FFFC, gba->video.vram); \
|
||||||
} \
|
} \
|
||||||
++wait; \
|
++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); \
|
wait += GBAMemoryStallVRAM(gba, wait, 1); \
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -557,7 +561,7 @@ uint32_t GBALoad16(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||||
} else {
|
} else {
|
||||||
LOAD_16(value, address & 0x0001FFFE, gba->video.vram);
|
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);
|
wait += GBAMemoryStallVRAM(gba, wait, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -777,7 +781,7 @@ uint32_t GBALoad8(struct ARMCore* cpu, uint32_t address, int* cycleCounter) {
|
||||||
} \
|
} \
|
||||||
} \
|
} \
|
||||||
++wait; \
|
++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); \
|
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);
|
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);
|
wait += GBAMemoryStallVRAM(gba, wait, 0);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -1114,10 +1118,8 @@ uint32_t GBAView32(struct ARMCore* cpu, uint32_t address) {
|
||||||
value = GBALoad32(cpu, address, 0);
|
value = GBALoad32(cpu, address, 0);
|
||||||
break;
|
break;
|
||||||
case REGION_IO:
|
case REGION_IO:
|
||||||
if ((address & OFFSET_MASK) < REG_MAX) {
|
value = GBAView16(cpu, address);
|
||||||
value = gba->memory.io[(address & OFFSET_MASK) >> 1];
|
value |= GBAView16(cpu, address + 2) << 16;
|
||||||
value |= gba->memory.io[((address & OFFSET_MASK) >> 1) + 1] << 16;
|
|
||||||
}
|
|
||||||
break;
|
break;
|
||||||
case REGION_CART_SRAM:
|
case REGION_CART_SRAM:
|
||||||
value = GBALoad8(cpu, address, 0);
|
value = GBALoad8(cpu, address, 0);
|
||||||
|
@ -1155,7 +1157,10 @@ uint16_t GBAView16(struct ARMCore* cpu, uint32_t address) {
|
||||||
value = GBALoad16(cpu, address, 0);
|
value = GBALoad16(cpu, address, 0);
|
||||||
break;
|
break;
|
||||||
case REGION_IO:
|
case REGION_IO:
|
||||||
if ((address & OFFSET_MASK) < REG_MAX) {
|
if ((address & OFFSET_MASK) < REG_MAX || (address & OFFSET_MASK) == REG_POSTFLG) {
|
||||||
|
value = gba->memory.io[(address & OFFSET_MASK) >> 1];
|
||||||
|
} else if ((address & OFFSET_MASK) == REG_EXWAITCNT_LO || (address & OFFSET_MASK) == REG_EXWAITCNT_HI) {
|
||||||
|
address += REG_INTERNAL_EXWAITCNT_LO - REG_EXWAITCNT_LO;
|
||||||
value = gba->memory.io[(address & OFFSET_MASK) >> 1];
|
value = gba->memory.io[(address & OFFSET_MASK) >> 1];
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -123,10 +123,8 @@ static const struct GBACartridgeOverride _overrides[] = {
|
||||||
{ "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6, false },
|
{ "BPEF", SAVEDATA_FLASH1M, HW_RTC, 0x80008C6, false },
|
||||||
|
|
||||||
// Pokemon Mystery Dungeon
|
// Pokemon Mystery Dungeon
|
||||||
{ "B24J", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
|
|
||||||
{ "B24E", 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 },
|
{ "B24P", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
|
||||||
{ "B24U", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
|
|
||||||
|
|
||||||
// Pokemon FireRed
|
// Pokemon FireRed
|
||||||
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
|
{ "BPRJ", SAVEDATA_FLASH1M, HW_NONE, IDLE_LOOP_NONE, false },
|
||||||
|
|
|
@ -233,6 +233,29 @@ static const char* const _interpolate =
|
||||||
" aff[2] = transform[start + 2].zw;\n"
|
" aff[2] = transform[start + 2].zw;\n"
|
||||||
" mat[3] = transform[start + 3].xy;\n"
|
" mat[3] = transform[start + 3].xy;\n"
|
||||||
" aff[3] = transform[start + 3].zw;\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";
|
"}\n";
|
||||||
|
|
||||||
static const char* const _renderMode2 =
|
static const char* const _renderMode2 =
|
||||||
|
@ -250,8 +273,7 @@ static const char* const _renderMode2 =
|
||||||
"OUT(0) out vec4 color;\n"
|
"OUT(0) out vec4 color;\n"
|
||||||
|
|
||||||
"int fetchTile(ivec2 coord);\n"
|
"int fetchTile(ivec2 coord);\n"
|
||||||
"vec2 interpolate(ivec2 arr[4], float x);\n"
|
"ivec2 affineInterpolate();\n"
|
||||||
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
|
|
||||||
|
|
||||||
"int renderTile(ivec2 coord) {\n"
|
"int renderTile(ivec2 coord) {\n"
|
||||||
" int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n"
|
" int map = (coord.x >> 11) + (((coord.y >> 7) & 0x7F0) << size);\n"
|
||||||
|
@ -278,26 +300,7 @@ static const char* const _renderMode2 =
|
||||||
"}\n"
|
"}\n"
|
||||||
|
|
||||||
"void main() {\n"
|
"void main() {\n"
|
||||||
" ivec2 mat[4];\n"
|
" int paletteEntry = fetchTile(affineInterpolate());\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"
|
|
||||||
" color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n"
|
" color = texelFetch(palette, ivec2(paletteEntry, int(texCoord.y)), 0);\n"
|
||||||
"}";
|
"}";
|
||||||
|
|
||||||
|
@ -325,30 +328,10 @@ static const char* const _renderMode35 =
|
||||||
"uniform ivec2 mosaic;\n"
|
"uniform ivec2 mosaic;\n"
|
||||||
"OUT(0) out vec4 color;\n"
|
"OUT(0) out vec4 color;\n"
|
||||||
|
|
||||||
"vec2 interpolate(ivec2 arr[4], float x);\n"
|
"ivec2 affineInterpolate();\n"
|
||||||
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
|
|
||||||
|
|
||||||
"void main() {\n"
|
"void main() {\n"
|
||||||
" ivec2 mat[4];\n"
|
" ivec2 coord = affineInterpolate();\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"
|
|
||||||
" if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
|
" if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
|
||||||
" discard;\n"
|
" discard;\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
|
@ -386,30 +369,10 @@ static const char* const _renderMode4 =
|
||||||
"uniform ivec2 mosaic;\n"
|
"uniform ivec2 mosaic;\n"
|
||||||
"OUT(0) out vec4 color;\n"
|
"OUT(0) out vec4 color;\n"
|
||||||
|
|
||||||
"vec2 interpolate(ivec2 arr[4], float x);\n"
|
"ivec2 affineInterpolate();\n"
|
||||||
"void loadAffine(int y, out ivec2 mat[4], out ivec2 aff[4]);\n"
|
|
||||||
|
|
||||||
"void main() {\n"
|
"void main() {\n"
|
||||||
" ivec2 mat[4];\n"
|
" ivec2 coord = affineInterpolate();\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"
|
|
||||||
" if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
|
" if (coord.x < 0 || coord.x >= (size.x << 8)) {\n"
|
||||||
" discard;\n"
|
" discard;\n"
|
||||||
" }\n"
|
" }\n"
|
||||||
|
@ -664,10 +627,12 @@ static const char* const _finalize =
|
||||||
" if (((topFlags.y & 13) == 5 || topFlags.w > 0) && (bottomFlags.y & 2) == 2) {\n"
|
" if (((topFlags.y & 13) == 5 || topFlags.w > 0) && (bottomFlags.y & 2) == 2) {\n"
|
||||||
" topPixel.rgb *= float(topFlags.z) / 16.;\n"
|
" topPixel.rgb *= float(topFlags.z) / 16.;\n"
|
||||||
" topPixel.rgb += bottomPixel.rgb * float(windowFlags.y) / 16.;\n"
|
" topPixel.rgb += bottomPixel.rgb * float(windowFlags.y) / 16.;\n"
|
||||||
" } else if ((topFlags.y & 13) == 9) {\n"
|
" } else if (topFlags.w == 0) { \n"
|
||||||
" topPixel.rgb += (1. - topPixel.rgb) * float(windowFlags.z) / 16.;\n"
|
" if ((topFlags.y & 13) == 9) {\n"
|
||||||
" } else if ((topFlags.y & 13) == 13) {\n"
|
" topPixel.rgb += (1. - topPixel.rgb) * float(windowFlags.z) / 16.;\n"
|
||||||
" topPixel.rgb -= 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"
|
" }\n"
|
||||||
" color = topPixel;\n"
|
" color = topPixel;\n"
|
||||||
"}";
|
"}";
|
||||||
|
|
|
@ -169,10 +169,10 @@ int GBAVideoSoftwareRendererPreprocessSprite(struct GBAVideoSoftwareRenderer* re
|
||||||
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
(renderer->blendEffect == BLEND_BRIGHTEN || renderer->blendEffect == BLEND_DARKEN);
|
||||||
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || objwinSlowPath) {
|
if (GBAObjAttributesAGetMode(sprite->a) == OBJ_MODE_SEMITRANSPARENT || (renderer->target1Obj && renderer->blendEffect == BLEND_ALPHA) || objwinSlowPath) {
|
||||||
int target2 = renderer->target2Bd;
|
int target2 = renderer->target2Bd;
|
||||||
target2 |= renderer->bg[0].target2;
|
target2 |= renderer->bg[0].target2 && renderer->bg[0].enabled;
|
||||||
target2 |= renderer->bg[1].target2;
|
target2 |= renderer->bg[1].target2 && renderer->bg[1].enabled;
|
||||||
target2 |= renderer->bg[2].target2;
|
target2 |= renderer->bg[2].target2 && renderer->bg[2].enabled;
|
||||||
target2 |= renderer->bg[3].target2;
|
target2 |= renderer->bg[3].target2 && renderer->bg[3].enabled;
|
||||||
if (target2) {
|
if (target2) {
|
||||||
renderer->forceTarget1 = true;
|
renderer->forceTarget1 = true;
|
||||||
flags |= FLAG_REBLEND;
|
flags |= FLAG_REBLEND;
|
||||||
|
|
|
@ -13,6 +13,10 @@
|
||||||
#include <mgba-util/memory.h>
|
#include <mgba-util/memory.h>
|
||||||
#include <mgba-util/vfs.h>
|
#include <mgba-util/vfs.h>
|
||||||
|
|
||||||
|
#ifdef PSP2
|
||||||
|
#include <psp2/rtc.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <fcntl.h>
|
#include <fcntl.h>
|
||||||
|
|
||||||
|
@ -184,7 +188,7 @@ size_t GBASavedataSize(const struct GBASavedata* savedata) {
|
||||||
|
|
||||||
bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) {
|
bool GBASavedataLoad(struct GBASavedata* savedata, struct VFile* in) {
|
||||||
if (savedata->data) {
|
if (savedata->data) {
|
||||||
if (!in && savedata->type != SAVEDATA_FORCE_NONE) {
|
if (!in || savedata->type == SAVEDATA_FORCE_NONE) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ssize_t size = GBASavedataSize(savedata);
|
ssize_t size = GBASavedataSize(savedata);
|
||||||
|
@ -598,14 +602,23 @@ void GBASavedataRTCWrite(struct GBASavedata* savedata) {
|
||||||
size_t size = GBASavedataSize(savedata);
|
size_t size = GBASavedataSize(savedata);
|
||||||
savedata->vf->seek(savedata->vf, size & ~0xFF, SEEK_SET);
|
savedata->vf->seek(savedata->vf, size & ~0xFF, SEEK_SET);
|
||||||
|
|
||||||
|
int bank = 0;
|
||||||
if ((savedata->vf->size(savedata->vf) & 0xFF) != sizeof(buffer)) {
|
if ((savedata->vf->size(savedata->vf) & 0xFF) != sizeof(buffer)) {
|
||||||
// Writing past the end of the file can invalidate the file mapping
|
// 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->vf->unmap(savedata->vf, savedata->data, size);
|
||||||
savedata->data = NULL;
|
savedata->data = NULL;
|
||||||
}
|
}
|
||||||
savedata->vf->write(savedata->vf, &buffer, sizeof(buffer));
|
savedata->vf->write(savedata->vf, &buffer, sizeof(buffer));
|
||||||
if (!savedata->data) {
|
if (!savedata->data) {
|
||||||
savedata->data = savedata->vf->map(savedata->vf, size, MAP_WRITE);
|
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);
|
LOAD_64LE(savedata->gpio->rtc.lastLatch, 0, &buffer.lastLatch);
|
||||||
|
|
||||||
|
time_t rtcTime;
|
||||||
|
|
||||||
|
#ifndef PSP2
|
||||||
struct tm date;
|
struct tm date;
|
||||||
date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100;
|
date.tm_year = _unBCD(savedata->gpio->rtc.time[0]) + 100;
|
||||||
date.tm_mon = _unBCD(savedata->gpio->rtc.time[1]) - 1;
|
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_min = _unBCD(savedata->gpio->rtc.time[5]);
|
||||||
date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]);
|
date.tm_sec = _unBCD(savedata->gpio->rtc.time[6]);
|
||||||
date.tm_isdst = -1;
|
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, DEBUG, "Savegame time offset set to %li", savedata->gpio->rtc.offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {
|
void GBASavedataSerialize(const struct GBASavedata* savedata, struct GBASerializedState* state) {
|
||||||
|
|
|
@ -105,17 +105,32 @@ bool GBASIOLockstepNodeLoad(struct GBASIODriver* driver) {
|
||||||
switch (node->mode) {
|
switch (node->mode) {
|
||||||
case SIO_MULTI:
|
case SIO_MULTI:
|
||||||
node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
|
node->d.writeRegister = GBASIOLockstepNodeMultiWriteRegister;
|
||||||
node->d.p->rcnt |= 3;
|
|
||||||
ATOMIC_ADD(node->p->attachedMulti, 1);
|
ATOMIC_ADD(node->p->attachedMulti, 1);
|
||||||
node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached);
|
node->d.p->siocnt = GBASIOMultiplayerSetReady(node->d.p->siocnt, node->p->attachedMulti == node->p->d.attached);
|
||||||
if (node->id) {
|
if (node->id) {
|
||||||
node->d.p->rcnt |= 4;
|
node->d.p->rcnt |= 4;
|
||||||
node->d.p->siocnt = GBASIOMultiplayerFillSlave(node->d.p->siocnt);
|
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;
|
break;
|
||||||
case SIO_NORMAL_8:
|
case SIO_NORMAL_8:
|
||||||
case SIO_NORMAL_32:
|
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;
|
node->d.writeRegister = GBASIOLockstepNodeNormalWriteRegister;
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -197,8 +212,6 @@ static uint16_t GBASIOLockstepNodeMultiWriteRegister(struct GBASIODriver* driver
|
||||||
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
mTimingDeschedule(&driver->p->p->timing, &node->event);
|
||||||
}
|
}
|
||||||
mTimingSchedule(&driver->p->p->timing, &node->event, 0);
|
mTimingSchedule(&driver->p->p->timing, &node->event, 0);
|
||||||
} else {
|
|
||||||
value &= ~0x0080;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
value &= 0xFF83;
|
value &= 0xFF83;
|
||||||
|
@ -447,7 +460,7 @@ static void _GBASIOLockstepNodeProcessEvents(struct mTiming* timing, void* user,
|
||||||
struct GBASIOLockstepNode* node = user;
|
struct GBASIOLockstepNode* node = user;
|
||||||
mLockstepLock(&node->p->d);
|
mLockstepLock(&node->p->d);
|
||||||
|
|
||||||
int32_t cycles = cycles = node->nextEvent;
|
int32_t cycles = node->nextEvent;
|
||||||
node->nextEvent -= cyclesLate;
|
node->nextEvent -= cyclesLate;
|
||||||
node->eventDiff += cyclesLate;
|
node->eventDiff += cyclesLate;
|
||||||
if (node->p->d.attached < 2) {
|
if (node->p->d.attached < 2) {
|
||||||
|
@ -495,11 +508,28 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
|
||||||
|
|
||||||
if (address == REG_SIOCNT) {
|
if (address == REG_SIOCNT) {
|
||||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value);
|
mLOG(GBA_SIO, DEBUG, "Lockstep %i: SIOCNT <- %04X", node->id, value);
|
||||||
|
int attached;
|
||||||
|
ATOMIC_LOAD(attached, node->p->attachedNormal);
|
||||||
value &= 0xFF8B;
|
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);
|
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) {
|
if (!node->id) {
|
||||||
// Frequency
|
// Frequency
|
||||||
int32_t cycles;
|
int32_t cycles;
|
||||||
|
@ -512,9 +542,6 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
|
||||||
cycles *= 4;
|
cycles *= 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum mLockstepPhase transferActive;
|
|
||||||
ATOMIC_LOAD(transferActive, node->p->d.transferActive);
|
|
||||||
|
|
||||||
if (transferActive == TRANSFER_IDLE) {
|
if (transferActive == TRANSFER_IDLE) {
|
||||||
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
|
mLOG(GBA_SIO, DEBUG, "Lockstep %i: Transfer initiated", node->id);
|
||||||
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
|
ATOMIC_STORE(node->p->d.transferActive, TRANSFER_STARTING);
|
||||||
|
@ -529,7 +556,7 @@ static uint16_t GBASIOLockstepNodeNormalWriteRegister(struct GBASIODriver* drive
|
||||||
value &= ~0x0080;
|
value &= ~0x0080;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
// TODO
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (address == REG_SIODATA32_LO) {
|
} else if (address == REG_SIODATA32_LO) {
|
||||||
|
|
|
@ -34,7 +34,7 @@ static void GBATimerUpdate(struct GBA* gba, int timerId, uint32_t cyclesLate) {
|
||||||
|
|
||||||
if (timerId < 3) {
|
if (timerId < 3) {
|
||||||
struct GBATimer* nextTimer = &gba->timers[timerId + 1];
|
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];
|
++gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1];
|
||||||
if (!gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1] && GBATimerFlagsIsEnable(nextTimer->flags)) {
|
if (!gba->memory.io[REG_TMCNT_LO(timerId + 1) >> 1] && GBATimerFlagsIsEnable(nextTimer->flags)) {
|
||||||
GBATimerUpdate(gba, timerId + 1, cyclesLate);
|
GBATimerUpdate(gba, timerId + 1, cyclesLate);
|
||||||
|
|
|
@ -702,7 +702,7 @@ static int32_t _readTiltY(struct mRotationSource* source) {
|
||||||
|
|
||||||
static int32_t _readGyroZ(struct mRotationSource* source) {
|
static int32_t _readGyroZ(struct mRotationSource* source) {
|
||||||
struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
|
struct m3DSRotationSource* rotation = (struct m3DSRotationSource*) source;
|
||||||
return rotation->gyro.y << 18L; // Yes, y
|
return rotation->gyro.y << 17L; // Yes, y
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
|
static void _startRequestImage(struct mImageSource* source, unsigned w, unsigned h, int colorFormats) {
|
||||||
|
|
|
@ -617,6 +617,39 @@ void retro_run(void) {
|
||||||
core->desiredVideoDimensions(core, &width, &height);
|
core->desiredVideoDimensions(core, &width, &height);
|
||||||
videoCallback(outputBuffer, width, height, BYTES_PER_PIXEL * 256);
|
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 (rumbleCallback) {
|
||||||
if (rumbleUp) {
|
if (rumbleUp) {
|
||||||
rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleUp * 0xFFFF / (rumbleUp + rumbleDown));
|
rumbleCallback(0, RETRO_RUMBLE_STRONG, rumbleUp * 0xFFFF / (rumbleUp + rumbleDown));
|
||||||
|
@ -1363,7 +1396,7 @@ static void _updateRotation(struct mRotationSource* source) {
|
||||||
tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * 2e8f;
|
tiltY = sensorGetCallback(0, RETRO_SENSOR_ACCELEROMETER_Y) * 2e8f;
|
||||||
}
|
}
|
||||||
if (gyroEnabled) {
|
if (gyroEnabled) {
|
||||||
gyroZ = sensorGetCallback(0, RETRO_SENSOR_GYROSCOPE_Z) * -1.1e9f;
|
gyroZ = sensorGetCallback(0, RETRO_SENSOR_GYROSCOPE_Z) * -5.5e8f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1010,8 +1010,11 @@ bool mGLES2ShaderLoad(struct VideoShader* shader, struct VDir* dir) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
u = mGLES2UniformListSize(&uniformVector);
|
u = mGLES2UniformListSize(&uniformVector);
|
||||||
struct mGLES2Uniform* uniformBlock = calloc(u, sizeof(*uniformBlock));
|
struct mGLES2Uniform* uniformBlock;
|
||||||
memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u);
|
if (u) {
|
||||||
|
uniformBlock = calloc(u, sizeof(*uniformBlock));
|
||||||
|
memcpy(uniformBlock, mGLES2UniformListGetPointer(&uniformVector, 0), sizeof(*uniformBlock) * u);
|
||||||
|
}
|
||||||
mGLES2UniformListDeinit(&uniformVector);
|
mGLES2UniformListDeinit(&uniformVector);
|
||||||
|
|
||||||
mGLES2ShaderInit(&shaderBlock[n], vssrc, fssrc, width, height, scaling, uniformBlock, u);
|
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) {
|
for (n = 0; n < inShaders; ++n) {
|
||||||
mGLES2ShaderDeinit(&shaderBlock[n]);
|
mGLES2ShaderDeinit(&shaderBlock[n]);
|
||||||
}
|
}
|
||||||
|
free(shaderBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,7 @@ set(OS_LIB -lvita2d -l${M_LIBRARY}
|
||||||
-lScePgf_stub
|
-lScePgf_stub
|
||||||
-lScePhotoExport_stub
|
-lScePhotoExport_stub
|
||||||
-lScePower_stub
|
-lScePower_stub
|
||||||
|
-lSceRtc_stub
|
||||||
-lSceSysmodule_stub
|
-lSceSysmodule_stub
|
||||||
-lSceTouch_stub)
|
-lSceTouch_stub)
|
||||||
set(OS_LIB ${OS_LIB} PARENT_SCOPE)
|
set(OS_LIB ${OS_LIB} PARENT_SCOPE)
|
||||||
|
|
|
@ -229,7 +229,7 @@ int main() {
|
||||||
},
|
},
|
||||||
{ .id = 0 }
|
{ .id = 0 }
|
||||||
},
|
},
|
||||||
.nConfigExtra = 2,
|
.nConfigExtra = 3,
|
||||||
.setup = mPSP2Setup,
|
.setup = mPSP2Setup,
|
||||||
.teardown = mPSP2Teardown,
|
.teardown = mPSP2Teardown,
|
||||||
.gameLoaded = mPSP2LoadROM,
|
.gameLoaded = mPSP2LoadROM,
|
||||||
|
|
|
@ -129,7 +129,7 @@ static THREAD_ENTRY _audioThread(void* context) {
|
||||||
sceAudioOutOutput(audioPort, buffer);
|
sceAudioOutOutput(audioPort, buffer);
|
||||||
}
|
}
|
||||||
sceAudioOutReleasePort(audioPort);
|
sceAudioOutReleasePort(audioPort);
|
||||||
return 0;
|
THREAD_EXIT(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _sampleRotation(struct mRotationSource* source) {
|
static void _sampleRotation(struct mRotationSource* source) {
|
||||||
|
@ -149,7 +149,7 @@ static int32_t _readTiltY(struct mRotationSource* source) {
|
||||||
|
|
||||||
static int32_t _readGyroZ(struct mRotationSource* source) {
|
static int32_t _readGyroZ(struct mRotationSource* source) {
|
||||||
struct mSceRotationSource* rotation = (struct mSceRotationSource*) source;
|
struct mSceRotationSource* rotation = (struct mSceRotationSource*) source;
|
||||||
return rotation->state.gyro.z * -0x10000000;
|
return rotation->state.gyro.z * -0x8000000;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void _setRumble(struct mRumble* source, int enable) {
|
static void _setRumble(struct mRumble* source, int enable) {
|
||||||
|
|
|
@ -284,7 +284,10 @@ struct VDirSceDevList {
|
||||||
static const char* _devs[] = {
|
static const char* _devs[] = {
|
||||||
"ux0:",
|
"ux0:",
|
||||||
"ur0:",
|
"ur0:",
|
||||||
"uma0:"
|
"uma0:",
|
||||||
|
"imc0:",
|
||||||
|
"xmc0:",
|
||||||
|
NULL
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VDir* VDeviceList() {
|
struct VDir* VDeviceList() {
|
||||||
|
@ -322,7 +325,7 @@ static void _vdlsceRewind(struct VDir* vd) {
|
||||||
|
|
||||||
static struct VDirEntry* _vdlsceListNext(struct VDir* vd) {
|
static struct VDirEntry* _vdlsceListNext(struct VDir* vd) {
|
||||||
struct VDirSceDevList* vdl = (struct VDirSceDevList*) vd;
|
struct VDirSceDevList* vdl = (struct VDirSceDevList*) vd;
|
||||||
while (vdl->vde.index < 3) {
|
while (vdl->vde.index < 0 || _devs[vdl->vde.index]) {
|
||||||
++vdl->vde.index;
|
++vdl->vde.index;
|
||||||
vdl->vde.name = _devs[vdl->vde.index];
|
vdl->vde.name = _devs[vdl->vde.index];
|
||||||
SceUID dir = sceIoDopen(vdl->vde.name);
|
SceUID dir = sceIoDopen(vdl->vde.name);
|
||||||
|
|
|
@ -74,7 +74,7 @@ AboutScreen::AboutScreen(QWidget* parent)
|
||||||
|
|
||||||
{
|
{
|
||||||
QString copyright = m_ui.copyright->text();
|
QString copyright = m_ui.copyright->text();
|
||||||
copyright.replace("{year}", QLatin1String("2022"));
|
copyright.replace("{year}", QLatin1String("2023"));
|
||||||
m_ui.copyright->setText(copyright);
|
m_ui.copyright->setText(copyright);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@
|
||||||
|
|
||||||
#include <QDir>
|
#include <QDir>
|
||||||
#include <QFileInfo>
|
#include <QFileInfo>
|
||||||
|
#include <QRegularExpression>
|
||||||
|
|
||||||
#include "ApplicationUpdatePrompt.h"
|
#include "ApplicationUpdatePrompt.h"
|
||||||
#include "ConfigController.h"
|
#include "ConfigController.h"
|
||||||
|
@ -71,9 +72,10 @@ QStringList ApplicationUpdater::listChannels() {
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ApplicationUpdater::currentChannel() {
|
QString ApplicationUpdater::currentChannel() {
|
||||||
QLatin1String version(projectVersion);
|
QString version(projectVersion);
|
||||||
QLatin1String branch(gitBranch);
|
QString branch(gitBranch);
|
||||||
if (branch == QLatin1String("heads/") + version) {
|
QRegularExpression stable("^(?:(?:refs/)?(?:tags|heads)/)?[0-9]+\\.[0-9]+\\.[0-9]+$");
|
||||||
|
if (branch.contains(stable) || (branch == "(unknown)" && version.contains(stable))) {
|
||||||
return QLatin1String("stable");
|
return QLatin1String("stable");
|
||||||
} else {
|
} else {
|
||||||
return QLatin1String("dev");
|
return QLatin1String("dev");
|
||||||
|
@ -137,7 +139,15 @@ QUrl ApplicationUpdater::parseManifest(const QByteArray& manifest) {
|
||||||
QString ApplicationUpdater::destination() const {
|
QString ApplicationUpdater::destination() const {
|
||||||
QFileInfo path(updateInfo().url.path());
|
QFileInfo path(updateInfo().url.path());
|
||||||
QDir dir(ConfigController::configDir());
|
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() {
|
const char* ApplicationUpdater::platform() {
|
||||||
|
@ -166,7 +176,8 @@ const char* ApplicationUpdater::platform() {
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplicationUpdater::UpdateInfo::UpdateInfo(const QString& prefix, const mUpdate* update)
|
ApplicationUpdater::UpdateInfo::UpdateInfo(const QString& prefix, const mUpdate* update)
|
||||||
: size(update->size)
|
: rev(-1)
|
||||||
|
, size(update->size)
|
||||||
, url(prefix + update->path)
|
, url(prefix + update->path)
|
||||||
{
|
{
|
||||||
if (update->rev > 0) {
|
if (update->rev > 0) {
|
||||||
|
|
|
@ -44,20 +44,21 @@ if(APPLE)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if(Qt6Widgets_VERSION)
|
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")
|
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]")
|
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")
|
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)")
|
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")
|
elseif(Qt5Widgets_VERSION MATCHES "^5.8")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.9")
|
set(MIN_VER 10.9)
|
||||||
else()
|
else()
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -mmacosx-version-min=10.8")
|
set(MIN_VER 10.8)
|
||||||
endif()
|
endif()
|
||||||
|
set(CMAKE_OSX_DEPLOYMENT_TARGET ${MIN_VER})
|
||||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++")
|
||||||
endif()
|
endif()
|
||||||
|
@ -118,6 +119,7 @@ set(SOURCE_FILES
|
||||||
MessagePainter.cpp
|
MessagePainter.cpp
|
||||||
MultiplayerController.cpp
|
MultiplayerController.cpp
|
||||||
ObjView.cpp
|
ObjView.cpp
|
||||||
|
OpenGLBug.cpp
|
||||||
OverrideView.cpp
|
OverrideView.cpp
|
||||||
PaletteView.cpp
|
PaletteView.cpp
|
||||||
PlacementControl.cpp
|
PlacementControl.cpp
|
||||||
|
@ -300,7 +302,7 @@ if(WIN32)
|
||||||
endif()
|
endif()
|
||||||
if(NOT DEFINED DATADIR)
|
if(NOT DEFINED DATADIR)
|
||||||
if(APPLE)
|
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)
|
elseif(WIN32 AND NOT WIN32_UNIX_PATHS)
|
||||||
set(DATADIR ".")
|
set(DATADIR ".")
|
||||||
else()
|
else()
|
||||||
|
@ -384,6 +386,7 @@ set_target_properties(${BINARY_NAME}-qt PROPERTIES MACOSX_BUNDLE_INFO_PLIST ${CM
|
||||||
|
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
set_target_properties(${BINARY_NAME}-qt PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
|
set_target_properties(${BINARY_NAME}-qt PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||||
|
target_link_libraries(${BINARY_NAME}-qt dwmapi)
|
||||||
if(NOT MSVC)
|
if(NOT MSVC)
|
||||||
target_link_libraries(${BINARY_NAME}-qt -municode)
|
target_link_libraries(${BINARY_NAME}-qt -municode)
|
||||||
endif()
|
endif()
|
||||||
|
@ -399,7 +402,7 @@ if(QT_STATIC)
|
||||||
if(CMAKE_CROSSCOMPILING)
|
if(CMAKE_CROSSCOMPILING)
|
||||||
set(QWINDOWS_DEPS ${QT}EventDispatcherSupport ${QT}FontDatabaseSupport ${QT}ThemeSupport ${QT}WindowsUIAutomationSupport)
|
set(QWINDOWS_DEPS ${QT}EventDispatcherSupport ${QT}FontDatabaseSupport ${QT}ThemeSupport ${QT}WindowsUIAutomationSupport)
|
||||||
endif()
|
endif()
|
||||||
list(APPEND QT_LIBRARIES ${QT}::QWindowsIntegrationPlugin ${QWINDOWS_DEPS} amstrmid dwmapi uxtheme imm32 -static-libgcc -static-libstdc++)
|
list(APPEND QT_LIBRARIES ${QT}::QWindowsIntegrationPlugin ${QWINDOWS_DEPS} amstrmid uxtheme imm32 -static-libgcc -static-libstdc++)
|
||||||
set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE};version;winmm;ssl;crypto;ws2_32;iphlpapi;crypt32;userenv;netapi32;wtsapi32")
|
set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE};version;winmm;ssl;crypto;ws2_32;iphlpapi;crypt32;userenv;netapi32;wtsapi32")
|
||||||
set_target_properties(${QT}::Gui PROPERTIES INTERFACE_LINK_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
|
set_target_properties(${QT}::Gui PROPERTIES INTERFACE_LINK_LIBRARIES ${OPENGL_LIBRARY} ${OPENGLES2_LIBRARY})
|
||||||
elseif(APPLE)
|
elseif(APPLE)
|
||||||
|
@ -413,15 +416,12 @@ if(QT_STATIC)
|
||||||
list(APPEND QT_LIBRARIES "-framework AVFoundation" "-framework CoreMedia" "-framework SystemConfiguration" "-framework Security")
|
list(APPEND QT_LIBRARIES "-framework AVFoundation" "-framework CoreMedia" "-framework SystemConfiguration" "-framework Security")
|
||||||
set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE}")
|
set_target_properties(${QT}::Core PROPERTIES INTERFACE_LINK_LIBRARIES "${QTPCRE}")
|
||||||
elseif(UNIX)
|
elseif(UNIX)
|
||||||
list(APPEND QT_LIBRARIES ${QT}::FontDatabaseSupport ${QT}::XcbQpa)
|
list(APPEND QT_LIBRARIES ${QT}::FontDatabaseSupport ${QT}::XcbQpa ${QT}::QWaylandIntegrationPlugin)
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
|
target_link_libraries(${BINARY_NAME}-qt ${PLATFORM_LIBRARY} ${BINARY_NAME} ${QT_LIBRARIES})
|
||||||
set(CPACK_DEBIAN_PACKAGE_DEPENDS "${CPACK_DEBIAN_PACKAGE_DEPENDS}" PARENT_SCOPE)
|
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)
|
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)
|
install(FILES ${CMAKE_SOURCE_DIR}/res/mgba-qt.desktop DESTINATION share/applications RENAME io.mgba.${PROJECT_NAME}.desktop COMPONENT ${BINARY_NAME}-qt)
|
||||||
endif()
|
endif()
|
||||||
|
@ -447,6 +447,10 @@ if(APPLE)
|
||||||
file(GLOB_RECURSE PLUGINS \"${BUNDLE_PATH}/Contents/PlugIns/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")
|
file(GLOB_RECURSE PLUGINS \"${BUNDLE_PATH}/Contents/PlugIns/*${CMAKE_SHARED_LIBRARY_SUFFIX}\")
|
||||||
fixup_bundle(\"${BUNDLE_PATH}\" \"${PLUGINS}\" \"\")
|
fixup_bundle(\"${BUNDLE_PATH}\" \"${PLUGINS}\" \"\")
|
||||||
" COMPONENT ${BINARY_NAME}-qt)
|
" 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()
|
else()
|
||||||
set(DEPLOY_OPTIONS -p platforms/libqcocoa.dylib,audio/libqtaudio_coreaudio.dylib,mediaservice/libqavfcamera.dylib)
|
set(DEPLOY_OPTIONS -p platforms/libqcocoa.dylib,audio/libqtaudio_coreaudio.dylib,mediaservice/libqavfcamera.dylib)
|
||||||
if(NOT CMAKE_INSTALL_NAME_TOOL EQUAL "install_name_tool")
|
if(NOT CMAKE_INSTALL_NAME_TOOL EQUAL "install_name_tool")
|
||||||
|
@ -458,6 +462,9 @@ if(APPLE)
|
||||||
if(DEFINED CROSS_ROOT)
|
if(DEFINED CROSS_ROOT)
|
||||||
set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -R "${CROSS_ROOT}")
|
set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -R "${CROSS_ROOT}")
|
||||||
endif()
|
endif()
|
||||||
|
if($ENV{CODESIGN_IDENTITY})
|
||||||
|
set(DEPLOY_OPTIONS ${DEPLOY_OPTIONS} -s "$ENV{CODESIGN_IDENTITY}" -E "${CMAKE_SOURCE_DIR}/res/entitlements.plist")
|
||||||
|
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\")")
|
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()
|
endif()
|
||||||
elseif(WIN32)
|
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>")
|
add_custom_command(TARGET ${BINARY_NAME}-qt POST_BUILD COMMAND "${STRIP}" "$<TARGET_FILE:${BINARY_NAME}-qt>")
|
||||||
endif()
|
endif()
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
|
install(TARGETS ${BINARY_NAME}-qt
|
||||||
|
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} COMPONENT ${BINARY_NAME}-qt
|
||||||
|
BUNDLE DESTINATION ${APPDIR} COMPONENT ${BINARY_NAME}-qt)
|
||||||
|
|
|
@ -289,13 +289,6 @@ void CoreController::loadConfig(ConfigController* config) {
|
||||||
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
|
mCoreConfigCopyValue(&m_threadContext.core->config, config->config(), "mute");
|
||||||
m_preload = config->getOption("preload").toInt();
|
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();
|
QSize sizeBefore = screenDimensions();
|
||||||
m_activeBuffer.resize(256 * 224 * sizeof(color_t));
|
m_activeBuffer.resize(256 * 224 * sizeof(color_t));
|
||||||
m_threadContext.core->setVideoBuffer(m_threadContext.core, reinterpret_cast<color_t*>(m_activeBuffer.data()), sizeBefore.width());
|
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) {
|
void CoreController::rewind(int states) {
|
||||||
if (!states) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!m_threadContext.core->opts.rewindEnable) {
|
if (!m_threadContext.core->opts.rewindEnable) {
|
||||||
emit statusPosted(tr("Rewinding not currently enabled"));
|
emit statusPosted(tr("Rewinding not currently enabled"));
|
||||||
}
|
}
|
||||||
|
@ -712,7 +702,7 @@ void CoreController::saveState(const QString& path, int flags) {
|
||||||
vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
|
vf->read(vf, controller->m_backupSaveState.data(), controller->m_backupSaveState.size());
|
||||||
vf->close(vf);
|
vf->close(vf);
|
||||||
}
|
}
|
||||||
vf = VFileDevice::open(controller->m_statePath, O_WRONLY | O_CREAT | O_TRUNC);
|
vf = VFileDevice::open(controller->m_statePath, O_RDWR | O_CREAT | O_TRUNC);
|
||||||
if (!vf) {
|
if (!vf) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -785,10 +775,14 @@ void CoreController::loadSave(const QString& path, bool temporary) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ok;
|
||||||
if (temporary) {
|
if (temporary) {
|
||||||
m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
|
ok = m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
|
||||||
} else {
|
} 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()) {
|
if (hasStarted()) {
|
||||||
|
@ -798,10 +792,14 @@ void CoreController::loadSave(const QString& path, bool temporary) {
|
||||||
|
|
||||||
void CoreController::loadSave(VFile* vf, bool temporary) {
|
void CoreController::loadSave(VFile* vf, bool temporary) {
|
||||||
m_resetActions.append([this, vf, temporary]() {
|
m_resetActions.append([this, vf, temporary]() {
|
||||||
|
bool ok;
|
||||||
if (temporary) {
|
if (temporary) {
|
||||||
m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
|
ok = m_threadContext.core->loadTemporarySave(m_threadContext.core, vf);
|
||||||
} else {
|
} 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()) {
|
if (hasStarted()) {
|
||||||
|
@ -914,7 +912,10 @@ void CoreController::scanCard(const QString& path) {
|
||||||
if (!file.open(QIODevice::ReadOnly)) {
|
if (!file.open(QIODevice::ReadOnly)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
m_eReaderData = file.read(2912);
|
QByteArray eReaderData = file.read(2912);
|
||||||
|
if (eReaderData.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
file.seek(0);
|
file.seek(0);
|
||||||
QStringList lines;
|
QStringList lines;
|
||||||
|
@ -936,6 +937,7 @@ void CoreController::scanCard(const QString& path) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
scanCards(lines);
|
scanCards(lines);
|
||||||
|
m_eReaderData = eReaderData;
|
||||||
} else if (image.size() == QSize(989, 44) || image.size() == QSize(639, 44)) {
|
} else if (image.size() == QSize(989, 44) || image.size() == QSize(639, 44)) {
|
||||||
const uchar* bits = image.constBits();
|
const uchar* bits = image.constBits();
|
||||||
size_t size;
|
size_t size;
|
||||||
|
@ -1215,7 +1217,12 @@ void CoreController::updatePlayerSave() {
|
||||||
int savePlayerId = 0;
|
int savePlayerId = 0;
|
||||||
mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId);
|
mCoreConfigGetIntValue(&m_threadContext.core->config, "savePlayerId", &savePlayerId);
|
||||||
if (savePlayerId == 0 || m_multiplayer->attached() > 1) {
|
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;
|
QString saveSuffix;
|
||||||
|
|
|
@ -16,6 +16,7 @@
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#include <mgba/core/core.h>
|
#include <mgba/core/core.h>
|
||||||
|
#include <mgba-util/string.h>
|
||||||
#include <mgba-util/vfs.h>
|
#include <mgba-util/vfs.h>
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
@ -163,7 +164,7 @@ CoreController* CoreManager::loadBIOS(int platform, const QString& path) {
|
||||||
mCoreConfigSetOverrideIntValue(&core->config, "skipBios", 0);
|
mCoreConfigSetOverrideIntValue(&core->config, "skipBios", 0);
|
||||||
|
|
||||||
QByteArray bytes(info.baseName().toUtf8());
|
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();
|
bytes = info.dir().canonicalPath().toUtf8();
|
||||||
mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData()));
|
mDirectorySetAttachBase(&core->dirs, VDirOpen(bytes.constData()));
|
||||||
|
|
|
@ -25,7 +25,7 @@ protected:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Ui::DebuggerConsole m_ui;
|
Ui::DebuggerConsole m_ui;
|
||||||
int m_historyOffset;
|
int m_historyOffset = 0;
|
||||||
|
|
||||||
DebuggerConsoleController* m_consoleController;
|
DebuggerConsoleController* m_consoleController;
|
||||||
};
|
};
|
||||||
|
|
|
@ -111,10 +111,11 @@ void QGBA::Display::configure(ConfigController* config) {
|
||||||
filter(opts->resampleVideo);
|
filter(opts->resampleVideo);
|
||||||
config->updateOption("showOSD");
|
config->updateOption("showOSD");
|
||||||
config->updateOption("showFrameCounter");
|
config->updateOption("showFrameCounter");
|
||||||
|
config->updateOption("videoSync");
|
||||||
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GL) || defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
if (opts->shader) {
|
if (opts->shader && supportsShaders()) {
|
||||||
struct VDir* shader = VDirOpen(opts->shader);
|
struct VDir* shader = VDirOpen(opts->shader);
|
||||||
if (shader && supportsShaders()) {
|
if (shader) {
|
||||||
setShaders(shader);
|
setShaders(shader);
|
||||||
shader->close(shader);
|
shader->close(shader);
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,7 @@ public slots:
|
||||||
virtual void showOSDMessages(bool enable);
|
virtual void showOSDMessages(bool enable);
|
||||||
virtual void showFrameCounter(bool enable);
|
virtual void showFrameCounter(bool enable);
|
||||||
virtual void filter(bool filter);
|
virtual void filter(bool filter);
|
||||||
|
virtual void swapInterval(int interval) = 0;
|
||||||
virtual void framePosted() = 0;
|
virtual void framePosted() = 0;
|
||||||
virtual void setShaders(struct VDir*) = 0;
|
virtual void setShaders(struct VDir*) = 0;
|
||||||
virtual void clearShaders() = 0;
|
virtual void clearShaders() = 0;
|
||||||
|
|
|
@ -38,12 +38,29 @@ using QOpenGLFunctions_Baseline = QOpenGLFunctions_3_2_Core;
|
||||||
#endif
|
#endif
|
||||||
#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
|
#ifdef _WIN32
|
||||||
#define OVERHEAD_NSEC 1000000
|
#define OVERHEAD_NSEC 1000000
|
||||||
#else
|
#else
|
||||||
#define OVERHEAD_NSEC 300000
|
#define OVERHEAD_NSEC 300000
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include "OpenGLBug.h"
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
|
||||||
QHash<QSurfaceFormat, bool> DisplayGL::s_supports;
|
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));
|
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() {
|
void mGLWidget::initializeGL() {
|
||||||
m_vao = std::make_unique<QOpenGLVertexArrayObject>();
|
m_vao = std::make_unique<QOpenGLVertexArrayObject>();
|
||||||
m_vao->create();
|
m_vao->create();
|
||||||
|
@ -92,6 +113,8 @@ void mGLWidget::initializeGL() {
|
||||||
|
|
||||||
m_vaoDone = false;
|
m_vaoDone = false;
|
||||||
m_tex = 0;
|
m_tex = 0;
|
||||||
|
|
||||||
|
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool mGLWidget::finalizeVAO() {
|
bool mGLWidget::finalizeVAO() {
|
||||||
|
@ -143,6 +166,23 @@ void mGLWidget::paintGL() {
|
||||||
} else {
|
} else {
|
||||||
m_refresh.start(17);
|
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)
|
DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
||||||
|
@ -150,6 +190,8 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
||||||
{
|
{
|
||||||
setAttribute(Qt::WA_NativeWindow);
|
setAttribute(Qt::WA_NativeWindow);
|
||||||
window()->windowHandle()->setFormat(format);
|
window()->windowHandle()->setFormat(format);
|
||||||
|
windowHandle()->setSurfaceType(QSurface::OpenGLSurface);
|
||||||
|
windowHandle()->destroy();
|
||||||
windowHandle()->create();
|
windowHandle()->create();
|
||||||
|
|
||||||
#ifdef USE_SHARE_WIDGET
|
#ifdef USE_SHARE_WIDGET
|
||||||
|
@ -163,6 +205,7 @@ DisplayGL::DisplayGL(const QSurfaceFormat& format, QWidget* parent)
|
||||||
m_gl = new mGLWidget;
|
m_gl = new mGLWidget;
|
||||||
m_gl->setAttribute(Qt::WA_NativeWindow);
|
m_gl->setAttribute(Qt::WA_NativeWindow);
|
||||||
m_gl->setFormat(format);
|
m_gl->setFormat(format);
|
||||||
|
m_gl->setMessagePainter(messagePainter());
|
||||||
QBoxLayout* layout = new QVBoxLayout;
|
QBoxLayout* layout = new QVBoxLayout;
|
||||||
layout->addWidget(m_gl);
|
layout->addWidget(m_gl);
|
||||||
layout->setContentsMargins(0, 0, 0, 0);
|
layout->setContentsMargins(0, 0, 0, 0);
|
||||||
|
@ -226,7 +269,7 @@ void DisplayGL::startDrawing(std::shared_ptr<CoreController> controller) {
|
||||||
messagePainter()->resize(size(), devicePixelRatio());
|
messagePainter()->resize(size(), devicePixelRatio());
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
CoreController::Interrupter interrupter(controller);
|
CoreController::Interrupter interrupter(m_context);
|
||||||
QMetaObject::invokeMethod(m_painter.get(), "start");
|
QMetaObject::invokeMethod(m_painter.get(), "start");
|
||||||
if (!m_gl) {
|
if (!m_gl) {
|
||||||
if (shouldDisableUpdates()) {
|
if (shouldDisableUpdates()) {
|
||||||
|
@ -348,6 +391,9 @@ void DisplayGL::interframeBlending(bool enable) {
|
||||||
|
|
||||||
void DisplayGL::showOSDMessages(bool enable) {
|
void DisplayGL::showOSDMessages(bool enable) {
|
||||||
Display::showOSDMessages(enable);
|
Display::showOSDMessages(enable);
|
||||||
|
if (m_gl) {
|
||||||
|
m_gl->setShowOSD(enable);
|
||||||
|
}
|
||||||
QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
|
QMetaObject::invokeMethod(m_painter.get(), "showOSD", Q_ARG(bool, enable));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -361,6 +407,10 @@ void DisplayGL::filter(bool filter) {
|
||||||
QMetaObject::invokeMethod(m_painter.get(), "filter", Q_ARG(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() {
|
void DisplayGL::framePosted() {
|
||||||
m_painter->enqueue(m_context->drawContext());
|
m_painter->enqueue(m_context->drawContext());
|
||||||
QMetaObject::invokeMethod(m_painter.get(), "draw");
|
QMetaObject::invokeMethod(m_painter.get(), "draw");
|
||||||
|
@ -477,7 +527,9 @@ void PainterGL::create() {
|
||||||
mGLES2Context* gl2Backend;
|
mGLES2Context* gl2Backend;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
|
if (!m_widget) {
|
||||||
|
m_paintDev = std::make_unique<QOpenGLPaintDevice>();
|
||||||
|
}
|
||||||
|
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
if (m_supportsShaders) {
|
if (m_supportsShaders) {
|
||||||
|
@ -503,10 +555,10 @@ void PainterGL::create() {
|
||||||
painter->makeCurrent();
|
painter->makeCurrent();
|
||||||
|
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
|
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
|
||||||
if (painter->m_widget && painter->supportsShaders()) {
|
if (painter->m_widget && painter->supportsShaders()) {
|
||||||
QOpenGLFunctions_Baseline* fn = painter->m_gl->versionFunctions<QOpenGLFunctions_Baseline>();
|
QOpenGLFunctions* fn = painter->m_gl->functions();
|
||||||
fn->glFinish();
|
fn->glFinish();
|
||||||
mGLES2Context* gl2Backend = reinterpret_cast<mGLES2Context*>(painter->m_backend);
|
|
||||||
painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]);
|
painter->m_widget->setTex(painter->m_finalTex[painter->m_finalTexIdx]);
|
||||||
painter->m_finalTexIdx ^= 1;
|
painter->m_finalTexIdx ^= 1;
|
||||||
gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx];
|
gl2Backend->finalShader.tex = painter->m_finalTex[painter->m_finalTexIdx];
|
||||||
|
@ -540,6 +592,9 @@ void PainterGL::create() {
|
||||||
m_backend->filter = false;
|
m_backend->filter = false;
|
||||||
m_backend->lockAspectRatio = false;
|
m_backend->lockAspectRatio = false;
|
||||||
m_backend->interframeBlending = false;
|
m_backend->interframeBlending = false;
|
||||||
|
m_gl->doneCurrent();
|
||||||
|
|
||||||
|
emit created();
|
||||||
}
|
}
|
||||||
|
|
||||||
void PainterGL::destroy() {
|
void PainterGL::destroy() {
|
||||||
|
@ -591,8 +646,10 @@ void PainterGL::setMessagePainter(MessagePainter* messagePainter) {
|
||||||
void PainterGL::resize(const QSize& size) {
|
void PainterGL::resize(const QSize& size) {
|
||||||
qreal r = m_window->devicePixelRatio();
|
qreal r = m_window->devicePixelRatio();
|
||||||
m_size = size;
|
m_size = size;
|
||||||
m_paintDev->setSize(m_size * r);
|
if (m_paintDev) {
|
||||||
m_paintDev->setDevicePixelRatio(r);
|
m_paintDev->setSize(m_size * r);
|
||||||
|
m_paintDev->setDevicePixelRatio(r);
|
||||||
|
}
|
||||||
if (m_started && !m_active) {
|
if (m_started && !m_active) {
|
||||||
forceDraw();
|
forceDraw();
|
||||||
}
|
}
|
||||||
|
@ -627,8 +684,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() {
|
void PainterGL::start() {
|
||||||
makeCurrent();
|
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 defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
if (m_supportsShaders && m_shader.passes) {
|
if (m_supportsShaders && m_shader.passes) {
|
||||||
|
@ -640,6 +735,7 @@ void PainterGL::start() {
|
||||||
m_buffer = nullptr;
|
m_buffer = nullptr;
|
||||||
m_active = true;
|
m_active = true;
|
||||||
m_started = true;
|
m_started = true;
|
||||||
|
swapInterval(1);
|
||||||
emit started();
|
emit started();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -670,12 +766,16 @@ void PainterGL::draw() {
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
int wantSwap = sync->audioWait || sync->videoFrameWait;
|
||||||
|
if (m_swapInterval != wantSwap) {
|
||||||
|
swapInterval(wantSwap);
|
||||||
|
}
|
||||||
dequeue();
|
dequeue();
|
||||||
bool forceRedraw = !m_videoProxy;
|
bool forceRedraw = true;
|
||||||
if (!m_delayTimer.isValid()) {
|
if (!m_delayTimer.isValid()) {
|
||||||
m_delayTimer.start();
|
m_delayTimer.start();
|
||||||
} else {
|
} else {
|
||||||
if (sync->audioWait || sync->videoFrameWait) {
|
if (wantSwap) {
|
||||||
while (m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC < 1000000000 / sync->fpsTarget) {
|
while (m_delayTimer.nsecsElapsed() + OVERHEAD_NSEC < 1000000000 / sync->fpsTarget) {
|
||||||
QThread::usleep(500);
|
QThread::usleep(500);
|
||||||
}
|
}
|
||||||
|
@ -725,13 +825,13 @@ void PainterGL::doStop() {
|
||||||
m_videoProxy->processData();
|
m_videoProxy->processData();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m_backend->clear(m_backend);
|
||||||
|
m_backend->swap(m_backend);
|
||||||
if (m_videoProxy) {
|
if (m_videoProxy) {
|
||||||
m_videoProxy->reset();
|
m_videoProxy->reset();
|
||||||
m_videoProxy->moveToThread(m_window->thread());
|
m_videoProxy->moveToThread(m_window->thread());
|
||||||
m_videoProxy.reset();
|
m_videoProxy.reset();
|
||||||
}
|
}
|
||||||
m_backend->clear(m_backend);
|
|
||||||
m_backend->swap(m_backend);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void PainterGL::pause() {
|
void PainterGL::pause() {
|
||||||
|
@ -751,7 +851,7 @@ void PainterGL::performDraw() {
|
||||||
m_backend->postFrame(m_backend, m_buffer);
|
m_backend->postFrame(m_backend, m_buffer);
|
||||||
}
|
}
|
||||||
m_backend->drawFrame(m_backend);
|
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_painter.begin(m_paintDev.get());
|
||||||
m_messagePainter->paint(&m_painter);
|
m_messagePainter->paint(&m_painter);
|
||||||
m_painter.end();
|
m_painter.end();
|
||||||
|
@ -777,20 +877,18 @@ void PainterGL::enqueue(const uint32_t* backing) {
|
||||||
|
|
||||||
void PainterGL::dequeue() {
|
void PainterGL::dequeue() {
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
if (m_queue.isEmpty()) {
|
if (!m_queue.isEmpty()) {
|
||||||
return;
|
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) {
|
void PainterGL::dequeueAll(bool keep) {
|
||||||
QMutexLocker locker(&m_mutex);
|
QMutexLocker locker(&m_mutex);
|
||||||
uint32_t* buffer = 0;
|
uint32_t* buffer = nullptr;
|
||||||
while (!m_queue.isEmpty()) {
|
while (!m_queue.isEmpty()) {
|
||||||
buffer = m_queue.dequeue();
|
buffer = m_queue.dequeue();
|
||||||
if (keep) {
|
if (keep) {
|
||||||
|
@ -821,12 +919,21 @@ void PainterGL::setShaders(struct VDir* dir) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
|
if (!m_started) {
|
||||||
|
makeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
if (m_shader.passes) {
|
if (m_shader.passes) {
|
||||||
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
||||||
mGLES2ShaderFree(&m_shader);
|
mGLES2ShaderFree(&m_shader);
|
||||||
}
|
}
|
||||||
mGLES2ShaderLoad(&m_shader, dir);
|
if (mGLES2ShaderLoad(&m_shader, dir)) {
|
||||||
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
|
mGLES2ShaderAttach(reinterpret_cast<mGLES2Context*>(m_backend), static_cast<mGLES2Shader*>(m_shader.passes), m_shader.nPasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!m_started) {
|
||||||
|
m_gl->doneCurrent();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -835,10 +942,18 @@ void PainterGL::clearShaders() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
#if defined(BUILD_GLES2) || defined(BUILD_GLES3)
|
||||||
|
if (!m_started) {
|
||||||
|
makeCurrent();
|
||||||
|
}
|
||||||
|
|
||||||
if (m_shader.passes) {
|
if (m_shader.passes) {
|
||||||
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
mGLES2ShaderDetach(reinterpret_cast<mGLES2Context*>(m_backend));
|
||||||
mGLES2ShaderFree(&m_shader);
|
mGLES2ShaderFree(&m_shader);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!m_started) {
|
||||||
|
m_gl->doneCurrent();
|
||||||
|
}
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -861,4 +976,12 @@ int PainterGL::glTex() {
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
QOpenGLContext* PainterGL::shareContext() {
|
||||||
|
if (m_widget) {
|
||||||
|
return m_widget->context();
|
||||||
|
} else {
|
||||||
|
return m_gl.get();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
|
@ -51,9 +51,12 @@ Q_OBJECT
|
||||||
|
|
||||||
public:
|
public:
|
||||||
mGLWidget(QWidget* parent = nullptr);
|
mGLWidget(QWidget* parent = nullptr);
|
||||||
|
~mGLWidget();
|
||||||
|
|
||||||
void setTex(GLuint tex) { m_tex = tex; }
|
void setTex(GLuint tex) { m_tex = tex; }
|
||||||
void setVBO(GLuint vbo) { m_vbo = vbo; }
|
void setVBO(GLuint vbo) { m_vbo = vbo; }
|
||||||
|
void setMessagePainter(MessagePainter*);
|
||||||
|
void setShowOSD(bool showOSD);
|
||||||
bool finalizeVAO();
|
bool finalizeVAO();
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
@ -72,6 +75,9 @@ private:
|
||||||
|
|
||||||
QTimer m_refresh;
|
QTimer m_refresh;
|
||||||
int m_refreshResidue = 0;
|
int m_refreshResidue = 0;
|
||||||
|
std::unique_ptr<QOpenGLPaintDevice> m_paintDev;
|
||||||
|
MessagePainter* m_messagePainter = nullptr;
|
||||||
|
bool m_showOSD = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
class PainterGL;
|
class PainterGL;
|
||||||
|
@ -102,6 +108,7 @@ public slots:
|
||||||
void showOSDMessages(bool enable) override;
|
void showOSDMessages(bool enable) override;
|
||||||
void showFrameCounter(bool enable) override;
|
void showFrameCounter(bool enable) override;
|
||||||
void filter(bool filter) override;
|
void filter(bool filter) override;
|
||||||
|
void swapInterval(int interval) override;
|
||||||
void framePosted() override;
|
void framePosted() override;
|
||||||
void setShaders(struct VDir*) override;
|
void setShaders(struct VDir*) override;
|
||||||
void clearShaders() override;
|
void clearShaders() override;
|
||||||
|
@ -143,6 +150,8 @@ public:
|
||||||
bool supportsShaders() const { return m_supportsShaders; }
|
bool supportsShaders() const { return m_supportsShaders; }
|
||||||
int glTex();
|
int glTex();
|
||||||
|
|
||||||
|
QOpenGLContext* shareContext();
|
||||||
|
|
||||||
void setVideoProxy(std::shared_ptr<VideoProxy>);
|
void setVideoProxy(std::shared_ptr<VideoProxy>);
|
||||||
void interrupt();
|
void interrupt();
|
||||||
|
|
||||||
|
@ -162,6 +171,7 @@ public slots:
|
||||||
void showOSD(bool enable);
|
void showOSD(bool enable);
|
||||||
void showFrameCounter(bool enable);
|
void showFrameCounter(bool enable);
|
||||||
void filter(bool filter);
|
void filter(bool filter);
|
||||||
|
void swapInterval(int interval);
|
||||||
void resizeContext();
|
void resizeContext();
|
||||||
|
|
||||||
void setShaders(struct VDir*);
|
void setShaders(struct VDir*);
|
||||||
|
@ -169,7 +179,9 @@ public slots:
|
||||||
VideoShader* shaders();
|
VideoShader* shaders();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
|
void created();
|
||||||
void started();
|
void started();
|
||||||
|
void texSwapped();
|
||||||
|
|
||||||
private slots:
|
private slots:
|
||||||
void doStop();
|
void doStop();
|
||||||
|
@ -184,6 +196,7 @@ private:
|
||||||
QList<uint32_t*> m_free;
|
QList<uint32_t*> m_free;
|
||||||
QQueue<uint32_t*> m_queue;
|
QQueue<uint32_t*> m_queue;
|
||||||
uint32_t* m_buffer = nullptr;
|
uint32_t* m_buffer = nullptr;
|
||||||
|
|
||||||
QPainter m_painter;
|
QPainter m_painter;
|
||||||
QMutex m_mutex;
|
QMutex m_mutex;
|
||||||
QWindow* m_window;
|
QWindow* m_window;
|
||||||
|
@ -209,6 +222,7 @@ private:
|
||||||
MessagePainter* m_messagePainter = nullptr;
|
MessagePainter* m_messagePainter = nullptr;
|
||||||
QElapsedTimer m_delayTimer;
|
QElapsedTimer m_delayTimer;
|
||||||
std::shared_ptr<VideoProxy> m_videoProxy;
|
std::shared_ptr<VideoProxy> m_videoProxy;
|
||||||
|
int m_swapInterval = -1;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,6 +31,7 @@ public slots:
|
||||||
void lockAspectRatio(bool lock) override;
|
void lockAspectRatio(bool lock) override;
|
||||||
void lockIntegerScaling(bool lock) override;
|
void lockIntegerScaling(bool lock) override;
|
||||||
void interframeBlending(bool enable) override;
|
void interframeBlending(bool enable) override;
|
||||||
|
void swapInterval(int) override {};
|
||||||
void filter(bool filter) override;
|
void filter(bool filter) override;
|
||||||
void framePosted() override;
|
void framePosted() override;
|
||||||
void setShaders(struct VDir*) override {}
|
void setShaders(struct VDir*) override {}
|
||||||
|
|
|
@ -316,7 +316,7 @@ void GBAApp::cleanupAfterUpdate() {
|
||||||
void GBAApp::restartForUpdate() {
|
void GBAApp::restartForUpdate() {
|
||||||
QFileInfo updaterPath(m_updater.updateInfo().url.path());
|
QFileInfo updaterPath(m_updater.updateInfo().url.path());
|
||||||
QDir configDir(ConfigController::configDir());
|
QDir configDir(ConfigController::configDir());
|
||||||
if (updaterPath.completeSuffix() == "exe") {
|
if (updaterPath.suffix() == "exe") {
|
||||||
m_invokeOnExit = configDir.filePath(QLatin1String("update.exe"));
|
m_invokeOnExit = configDir.filePath(QLatin1String("update.exe"));
|
||||||
} else {
|
} else {
|
||||||
QFile updater(":/updater");
|
QFile updater(":/updater");
|
||||||
|
|
|
@ -44,7 +44,7 @@ GDBWindow::GDBWindow(GDBController* controller, QWidget* parent)
|
||||||
connect(m_portEdit, &QLineEdit::textChanged, this, &GDBWindow::portChanged);
|
connect(m_portEdit, &QLineEdit::textChanged, this, &GDBWindow::portChanged);
|
||||||
settingsGrid->addWidget(m_portEdit, 0, 1, Qt::AlignLeft);
|
settingsGrid->addWidget(m_portEdit, 0, 1, Qt::AlignLeft);
|
||||||
|
|
||||||
m_bindAddressEdit = new QLineEdit("0.0.0.0");
|
m_bindAddressEdit = new QLineEdit("127.0.0.1");
|
||||||
m_bindAddressEdit->setMaxLength(15);
|
m_bindAddressEdit->setMaxLength(15);
|
||||||
connect(m_bindAddressEdit, &QLineEdit::textChanged, this, &GDBWindow::bindAddressChanged);
|
connect(m_bindAddressEdit, &QLineEdit::textChanged, this, &GDBWindow::bindAddressChanged);
|
||||||
settingsGrid->addWidget(m_bindAddressEdit, 1, 1, Qt::AlignLeft);
|
settingsGrid->addWidget(m_bindAddressEdit, 1, 1, Qt::AlignLeft);
|
||||||
|
|
|
@ -39,6 +39,7 @@ static const QList<GBMemoryBankControllerType> s_mbcList{
|
||||||
GB_UNL_BBD,
|
GB_UNL_BBD,
|
||||||
GB_UNL_HITEK,
|
GB_UNL_HITEK,
|
||||||
GB_UNL_SACHEN_MMC1,
|
GB_UNL_SACHEN_MMC1,
|
||||||
|
GB_UNL_SACHEN_MMC2,
|
||||||
};
|
};
|
||||||
|
|
||||||
static QMap<GBModel, QString> s_gbModelNames;
|
static QMap<GBModel, QString> s_gbModelNames;
|
||||||
|
|
|
@ -1323,7 +1323,7 @@ const QList<IOViewer::RegisterDescription>& IOViewer::registerDescriptions(mPlat
|
||||||
});
|
});
|
||||||
// 0xFF40: LCDC
|
// 0xFF40: LCDC
|
||||||
regGB.append({
|
regGB.append({
|
||||||
{ tr("Background enable/priority"), 1 },
|
{ tr("Background enable/priority"), 0 },
|
||||||
{ tr("Enable sprites"), 1 },
|
{ tr("Enable sprites"), 1 },
|
||||||
{ tr("Double-height sprites"), 2 },
|
{ tr("Double-height sprites"), 2 },
|
||||||
{ tr("Background tile map"), 3, 1, {
|
{ tr("Background tile map"), 3, 1, {
|
||||||
|
@ -1689,7 +1689,15 @@ void IOViewer::bitFlipped() {
|
||||||
void IOViewer::writeback() {
|
void IOViewer::writeback() {
|
||||||
{
|
{
|
||||||
CoreController::Interrupter interrupter(m_controller);
|
CoreController::Interrupter interrupter(m_controller);
|
||||||
GBAIOWrite(static_cast<GBA*>(m_controller->thread()->core->board), m_register, m_value);
|
mCore* core = m_controller->thread()->core;
|
||||||
|
switch (m_width) {
|
||||||
|
case 0:
|
||||||
|
core->busWrite8(core, m_base + m_register, m_value);
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
core->busWrite16(core, m_base + m_register, m_value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
updateRegister();
|
updateRegister();
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@
|
||||||
#include "GamepadAxisEvent.h"
|
#include "GamepadAxisEvent.h"
|
||||||
#include "GamepadButtonEvent.h"
|
#include "GamepadButtonEvent.h"
|
||||||
#include "ShortcutController.h"
|
#include "ShortcutController.h"
|
||||||
|
#include "utils.h"
|
||||||
|
|
||||||
#include <QCoreApplication>
|
#include <QCoreApplication>
|
||||||
#include <QFontMetrics>
|
#include <QFontMetrics>
|
||||||
|
@ -33,35 +34,7 @@ void KeyEditor::setValue(int key) {
|
||||||
if (key < 0) {
|
if (key < 0) {
|
||||||
setText(tr("---"));
|
setText(tr("---"));
|
||||||
} else {
|
} else {
|
||||||
QKeySequence seq(key);
|
setText(keyName(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
emit valueChanged(key);
|
emit valueChanged(key);
|
||||||
|
|
|
@ -79,8 +79,8 @@ public slots:
|
||||||
|
|
||||||
private:
|
private:
|
||||||
mLogFilter m_filter;
|
mLogFilter m_filter;
|
||||||
bool m_logToFile;
|
bool m_logToFile = false;
|
||||||
bool m_logToStdout;
|
bool m_logToStdout = false;
|
||||||
std::unique_ptr<QFile> m_logFile;
|
std::unique_ptr<QFile> m_logFile;
|
||||||
std::unique_ptr<QTextStream> m_logStream;
|
std::unique_ptr<QTextStream> m_logStream;
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ MapView::MapView(std::shared_ptr<CoreController> controller, QWidget* parent)
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
case mPLATFORM_GBA:
|
case mPLATFORM_GBA:
|
||||||
m_boundary = 2048;
|
m_boundary = 2048;
|
||||||
m_ui.tile->setMaxTile(3096);
|
m_ui.tile->setMaxTile(3072);
|
||||||
m_addressBase = BASE_VRAM;
|
m_addressBase = BASE_VRAM;
|
||||||
m_addressWidth = 8;
|
m_addressWidth = 8;
|
||||||
m_ui.bgInfo->addCustomProperty("priority", tr("Priority"));
|
m_ui.bgInfo->addCustomProperty("priority", tr("Priority"));
|
||||||
|
@ -119,6 +119,9 @@ void MapView::selectMap(int map) {
|
||||||
}
|
}
|
||||||
m_map = map;
|
m_map = map;
|
||||||
m_mapStatus.fill({});
|
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);
|
updateTiles(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,11 +187,18 @@ void MapView::updateTilesGBA(bool) {
|
||||||
frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]);
|
frame = GBARegisterDISPCNTGetFrameSelect(io[REG_DISPCNT >> 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
m_boundary = 1024;
|
||||||
|
m_ui.tile->setMaxTile(1536);
|
||||||
priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]);
|
priority = GBARegisterBGCNTGetPriority(io[(REG_BG0CNT >> 1) + m_map]);
|
||||||
if (mode == 0 || (mode == 1 && m_map != 2)) {
|
if (mode == 0 || (mode == 1 && m_map != 2)) {
|
||||||
offset = QString("%1, %2")
|
offset = QString("%1, %2")
|
||||||
.arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)])
|
.arg(io[(REG_BG0HOFS >> 1) + (m_map << 1)])
|
||||||
.arg(io[(REG_BG0VOFS >> 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)) {
|
} else if ((mode > 0 && m_map == 2) || (mode == 2 && m_map == 3)) {
|
||||||
int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)];
|
int32_t refX = io[(REG_BG2X_LO >> 1) + ((m_map - 2) << 2)];
|
||||||
refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16;
|
refX |= io[(REG_BG2X_HI >> 1) + ((m_map - 2) << 2)] << 16;
|
||||||
|
|
|
@ -14,24 +14,46 @@
|
||||||
#include <mgba/internal/gb/gb.h>
|
#include <mgba/internal/gb/gb.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
using namespace QGBA;
|
using namespace QGBA;
|
||||||
|
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* node)
|
MultiplayerController::Player::Player(CoreController* coreController, GBSIOLockstepNode* gbNode)
|
||||||
: controller(coreController)
|
: controller(coreController)
|
||||||
, gbNode(node)
|
|
||||||
{
|
{
|
||||||
|
node.gb = gbNode;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* node)
|
MultiplayerController::Player::Player(CoreController* coreController, GBASIOLockstepNode* gbaNode)
|
||||||
: controller(coreController)
|
: controller(coreController)
|
||||||
, gbaNode(node)
|
|
||||||
{
|
{
|
||||||
|
node.gba = gbaNode;
|
||||||
}
|
}
|
||||||
#endif
|
#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() {
|
MultiplayerController::MultiplayerController() {
|
||||||
mLockstepInit(&m_lockstep);
|
mLockstepInit(&m_lockstep);
|
||||||
m_lockstep.context = this;
|
m_lockstep.context = this;
|
||||||
|
@ -65,6 +87,7 @@ MultiplayerController::MultiplayerController() {
|
||||||
player->awake = 0;
|
player->awake = 0;
|
||||||
slept = true;
|
slept = true;
|
||||||
}
|
}
|
||||||
|
player->controller->setSync(true);
|
||||||
return slept;
|
return slept;
|
||||||
};
|
};
|
||||||
m_lockstep.addCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
m_lockstep.addCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
||||||
|
@ -72,44 +95,55 @@ MultiplayerController::MultiplayerController() {
|
||||||
abort();
|
abort();
|
||||||
}
|
}
|
||||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||||
if (!id) {
|
Player* player = controller->player(id);
|
||||||
for (int i = 1; i < controller->m_players.count(); ++i) {
|
switch (player->controller->platform()) {
|
||||||
Player* player = &controller->m_players[i];
|
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
if (player->controller->platform() == mPLATFORM_GBA && player->gbaNode->d.p->mode != controller->m_players[0].gbaNode->d.p->mode) {
|
case mPLATFORM_GBA:
|
||||||
player->controller->setSync(true);
|
if (!id) {
|
||||||
continue;
|
for (int i = 1; i < controller->m_players.count(); ++i) {
|
||||||
}
|
player = controller->player(i);
|
||||||
#endif
|
if (player->node.gba->d.p->mode > SIO_MULTI) {
|
||||||
player->controller->setSync(false);
|
player->controller->setSync(true);
|
||||||
player->cyclesPosted += cycles;
|
continue;
|
||||||
if (player->awake < 1) {
|
}
|
||||||
switch (player->controller->platform()) {
|
player->controller->setSync(false);
|
||||||
#ifdef M_CORE_GBA
|
player->cyclesPosted += cycles;
|
||||||
case mPLATFORM_GBA:
|
if (player->awake < 1) {
|
||||||
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;
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
mCoreThreadStopWaiting(player->controller->thread());
|
mCoreThreadStopWaiting(player->controller->thread());
|
||||||
player->awake = 1;
|
player->awake = 1;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
player->controller->setSync(true);
|
||||||
|
player->cyclesPosted += cycles;
|
||||||
}
|
}
|
||||||
} else {
|
break;
|
||||||
controller->m_players[id].controller->setSync(true);
|
#endif
|
||||||
controller->m_players[id].cyclesPosted += cycles;
|
#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) {
|
m_lockstep.useCycles = [](mLockstep* lockstep, int id, int32_t cycles) {
|
||||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||||
Player* player = &controller->m_players[id];
|
Player* player = controller->player(id);
|
||||||
player->cyclesPosted -= cycles;
|
player->cyclesPosted -= cycles;
|
||||||
if (player->cyclesPosted <= 0) {
|
if (player->cyclesPosted <= 0) {
|
||||||
mCoreThreadWaitFromThread(player->controller->thread());
|
mCoreThreadWaitFromThread(player->controller->thread());
|
||||||
|
@ -118,21 +152,21 @@ MultiplayerController::MultiplayerController() {
|
||||||
cycles = player->cyclesPosted;
|
cycles = player->cyclesPosted;
|
||||||
return cycles;
|
return cycles;
|
||||||
};
|
};
|
||||||
m_lockstep.unusedCycles= [](mLockstep* lockstep, int id) {
|
m_lockstep.unusedCycles = [](mLockstep* lockstep, int id) {
|
||||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||||
Player* player = &controller->m_players[id];
|
Player* player = controller->player(id);
|
||||||
auto cycles = player->cyclesPosted;
|
auto cycles = player->cyclesPosted;
|
||||||
return cycles;
|
return cycles;
|
||||||
};
|
};
|
||||||
m_lockstep.unload = [](mLockstep* lockstep, int id) {
|
m_lockstep.unload = [](mLockstep* lockstep, int id) {
|
||||||
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
MultiplayerController* controller = static_cast<MultiplayerController*>(lockstep->context);
|
||||||
if (id) {
|
if (id) {
|
||||||
Player* player = &controller->m_players[id];
|
Player* player = controller->player(id);
|
||||||
player->controller->setSync(true);
|
player->controller->setSync(true);
|
||||||
player->cyclesPosted = 0;
|
player->cyclesPosted = 0;
|
||||||
|
|
||||||
// release master GBA if it is waiting for this GBA
|
// release master GBA if it is waiting for this GBA
|
||||||
player = &controller->m_players[0];
|
player = controller->player(0);
|
||||||
player->waitMask &= ~(1 << id);
|
player->waitMask &= ~(1 << id);
|
||||||
if (!player->waitMask && player->awake < 1) {
|
if (!player->waitMask && player->awake < 1) {
|
||||||
mCoreThreadStopWaiting(player->controller->thread());
|
mCoreThreadStopWaiting(player->controller->thread());
|
||||||
|
@ -140,7 +174,7 @@ MultiplayerController::MultiplayerController() {
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int i = 1; i < controller->m_players.count(); ++i) {
|
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);
|
player->controller->setSync(true);
|
||||||
switch (player->controller->platform()) {
|
switch (player->controller->platform()) {
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
|
@ -160,12 +194,12 @@ MultiplayerController::MultiplayerController() {
|
||||||
switch (player->controller->platform()) {
|
switch (player->controller->platform()) {
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
case mPLATFORM_GBA:
|
case mPLATFORM_GBA:
|
||||||
player->gbaNode->nextEvent += player->cyclesPosted;
|
player->node.gba->nextEvent += player->cyclesPosted;
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
case mPLATFORM_GB:
|
case mPLATFORM_GB:
|
||||||
player->gbNode->nextEvent += player->cyclesPosted;
|
player->node.gb->nextEvent += player->cyclesPosted;
|
||||||
break;
|
break;
|
||||||
#endif
|
#endif
|
||||||
default:
|
default:
|
||||||
|
@ -184,10 +218,6 @@ MultiplayerController::~MultiplayerController() {
|
||||||
}
|
}
|
||||||
|
|
||||||
bool MultiplayerController::attachGame(CoreController* controller) {
|
bool MultiplayerController::attachGame(CoreController* controller) {
|
||||||
if (m_lockstep.attached == MAX_GBAS) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (m_lockstep.attached == 0) {
|
if (m_lockstep.attached == 0) {
|
||||||
switch (controller->platform()) {
|
switch (controller->platform()) {
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
|
@ -203,6 +233,9 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
m_platform = controller->platform();
|
||||||
|
} else if (controller->platform() != m_platform) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
mCoreThread* thread = controller->thread();
|
mCoreThread* thread = controller->thread();
|
||||||
|
@ -213,6 +246,10 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
||||||
switch (controller->platform()) {
|
switch (controller->platform()) {
|
||||||
#ifdef M_CORE_GBA
|
#ifdef M_CORE_GBA
|
||||||
case mPLATFORM_GBA: {
|
case mPLATFORM_GBA: {
|
||||||
|
if (m_lockstep.attached >= MAX_GBAS) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
GBA* gba = static_cast<GBA*>(thread->core->board);
|
GBA* gba = static_cast<GBA*>(thread->core->board);
|
||||||
|
|
||||||
GBASIOLockstepNode* node = new GBASIOLockstepNode;
|
GBASIOLockstepNode* node = new GBASIOLockstepNode;
|
||||||
|
@ -229,6 +266,10 @@ bool MultiplayerController::attachGame(CoreController* controller) {
|
||||||
#endif
|
#endif
|
||||||
#ifdef M_CORE_GB
|
#ifdef M_CORE_GB
|
||||||
case mPLATFORM_GB: {
|
case mPLATFORM_GB: {
|
||||||
|
if (m_lockstep.attached >= 2) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
GB* gb = static_cast<GB*>(thread->core->board);
|
GB* gb = static_cast<GB*>(thread->core->board);
|
||||||
|
|
||||||
GBSIOLockstepNode* node = new GBSIOLockstepNode;
|
GBSIOLockstepNode* node = new GBSIOLockstepNode;
|
||||||
|
@ -315,3 +356,29 @@ int MultiplayerController::attached() {
|
||||||
num = m_lockstep.attached;
|
num = m_lockstep.attached;
|
||||||
return num;
|
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;
|
||||||
|
}
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue