Compare commits

...

101 Commits
v109 ... v114

Author SHA1 Message Date
byuu
55e78b03de Point to specific project. 2020-01-08 18:50:07 +09:00
byuu
47dcdc1b4f Added Windows binary release link. 2020-01-08 18:49:40 +09:00
byuu
e13ab011eb v114
Added fast PPU override for Marvelous (fixes text rendering)
Fixed disassembly of SNES CPU opcodes 0x74-0x76 [invertego]
2020-01-08 18:46:53 +09:00
byuu
892f202945 Updated SNES game databases 2020-01-01 18:45:11 +09:00
byuu
e575196abc . 2019-12-31 19:42:11 +09:00
byuu
404caeab50 The input workaround for Taikyoku Igo benefits:
Williams Arcade's Greatest Hits and World Masters Golf;
in very subtle ways, so enable it for those two titles as well.
2019-12-31 19:40:35 +09:00
byuu
dde9b4c2c7 v113.5
It seems auto-joypad poll timing is needed for most games.
So that's back in as before. Instead, I added an override for
Taikyoku Igo - Goliath specifically, until auto-joypad emulation
can be improved further.
2019-12-31 10:22:31 +09:00
byuu
793f2e5bf4 v113.4
Completely disabled auto-joypad timing (happens immediately)
(fixes World Masters until this can be emulated fully)
Disabled fast PPU for Winter Olympic Games
(changes OAM tiledata address mid-frame)
Disabled fast PPU for World Cup Striker
(I'm not sure yet why it's not compatible)
Cleared overscan region when disabling overscan
(fixes World Class Service SNES Tester)
Added override for invalid SNES header in Yuyu no Quiz de Go! Go!
2019-12-30 06:00:17 +09:00
byuu
cc4ab9bc25 Added workaround to reduce auto-joypad polling delays
(until we can emulate the behavior more faithfully)
2019-12-28 13:53:06 +09:00
byuu
2551f20f3a v113.3
Fixed region heuristics for the one Scandanavian SNES game release
2019-12-28 13:41:57 +09:00
byuu
5b29ddbcaa Add hotfix for Nichibutsu Arcade Classics (Japan)
* Frisky Tom hangs sometimes when memory is randomized
2019-12-27 08:58:09 +09:00
byuu
ac4d16c917 Revert gamepak firmware naming to use architecture instead of identifier
* identifier naming interferes with game ROM naming lookup
2019-12-27 08:51:05 +09:00
byuu
01c16dcf4d Fix Taikyoku Igo - Goliath
(clear $4218-421f to 1s instead of 0s at start of auto-joypad polling)
2019-12-27 08:32:18 +09:00
byuu
169c0871c7 Added Super SWIV fast PPU override 2019-12-27 08:17:39 +09:00
byuu
ffee61a1b1 Merge pull request #250 from Sintendo/xcode11-opengl-fix
ruby/CGL: explicitly set current OpenGL context
2019-12-24 18:51:01 +09:00
Sintendo
526df86ee6 ruby/CGL: explicitly set current OpenGL context
On builds made with Xcode 11+ the current OpenGL context wasn't being
properly configured anymore, resulting in shader compilation errors and
a red screen.

Explicitly calling makeCurrentContext fixes this.
2019-12-23 22:30:58 +01:00
byuu
748cf44f35 Added run-ahead support to libretro target [realnc]
Fixed typo in the GUI regarding HD mode 7 +/- hotkeys
2019-12-19 22:04:14 +09:00
byuu
a4f96f0648 Fix link. 2019-12-19 21:16:53 +09:00
byuu
2ca1bab9ed Updated links in readme file. 2019-12-19 21:16:06 +09:00
byuu
1e6a745f19 v113.1
Emergency hotfix for an issue affecting manually created save states.
Still need to determine root cause, but for now, reverting the code.
2019-12-16 01:59:39 +09:00
byuu
90b1350110 v113 2019-12-11 22:02:23 +09:00
byuu
357d054c19 Fix LLE gamepak firmware name lookups (use identifier, not architecture) 2019-12-11 21:56:29 +09:00
byuu
d62e3f3362 v112.14
Finally corrected Super Game Boy 2 audio [LIJI]
2019-12-11 21:40:07 +09:00
byuu
4ec45a7453 Some more testing for Super Game Boy audio mixing. 2019-12-09 09:23:33 +09:00
byuu
f5d40bd1ee Testing: added Super Game Boy audio mixing test function. 2019-12-09 00:08:37 +09:00
byuu
6aa7c944d5 v112.3
Improvements to ruby driver crash detection.
Workaround added for rare crash on close on Windows.
2019-12-08 01:39:46 +09:00
byuu
dafd673177 v112.12
Update to SameBoy-master [2019-12-02]
2019-12-02 20:22:51 +09:00
byuu
a64c1adaa8 v112.11
SFC: Disable color blending for first hires pixel with accuracy PPU
(fixes a green scanline on the left-edge of Jurassic Park)
libco: Don't include <sys/mman.h> when not using mprotect
nall: Detect Windows without invoking uname [Alcaro]
2019-12-02 19:54:03 +09:00
byuu
0d1d6f329d Scanline PPU render position override for Suguro Quest++ 2019-11-12 23:17:25 +09:00
byuu
7cd897b53b . 2019-11-10 10:18:24 +09:00
byuu
011f470b07 Add images to readme 2019-11-10 10:17:42 +09:00
byuu
6edad01fb8 . 2019-11-10 10:05:52 +09:00
byuu
3ecea80ecb v112.10
Fix accuracy PPU mosaic rendering when size!=0 && enable==0
2019-11-10 10:01:13 +09:00
byuu
b7b848eff5 Fix audio balance below 50% 2019-11-10 09:37:56 +09:00
byuu
da7350ac5c Rename functions for consistency. 2019-11-08 16:12:33 +09:00
byuu
ba3fca27ad Fix GUI typos. 2019-11-08 16:00:27 +09:00
byuu
5775155714 v112.9
CPU IRQ improvement to fix Shin Nihon Pro Wrestling Kouhin '95
2019-11-08 15:56:27 +09:00
byuu
f1108408a8 Updated libretro resources file again 2019-11-05 09:01:54 +09:00
byuu
996358da66 v112.8
Made the main window canvas area (program icon) droppable for games
Merged the latest SameBoy core, but disabled it due to an input problem
2019-11-05 08:58:59 +09:00
byuu
c717a0e7bd Mapping fix for RPG Tsukuru 2 2019-11-01 06:26:24 +09:00
byuu
2884cd87d2 v112.7
Added BSC-1A7M-10 board
Corrected BSC-1AxM-xx masking
2019-11-01 05:36:02 +09:00
byuu
454b90be24 v112.6
Fix for Kishin Douji Zenki - Tenchi Meidou
2019-10-31 10:56:16 +09:00
byuu
2b9a22e1d8 Merge IOKit hotplug support patch [Sintendo]
Merge libretro Super Game Boy support improvement patch [fr500]
2019-10-31 09:19:27 +09:00
byuu
1c1cfd086b v112.5
Added game hotfix for Rendering Ranger R2.
2019-10-31 09:13:37 +09:00
byuu
f2978247c1 v112.4
Reverted Kishin Douji Zenki fix, as it seems to have been incorrect.
Disabled supersampling when EXTBG mode is active.
Fixed MSU1 and SGB audio when using run-ahead and overclocking.
macOS: fixed a serious issue with the IOKit joypad driver [kode54]
2019-10-27 13:13:59 +09:00
byuu
4f32551430 . 2019-10-27 01:31:33 +09:00
byuu
c61c3cabc6 . 2019-10-27 01:27:36 +09:00
byuu
819d6dbde4 v112.3
Fixed offset-per-tile regression with accurate PPU renderer.
2019-10-27 00:51:15 +09:00
byuu
f51bc06739 v112.2
Temporarily disabled crash detector to work around Windows issue.
Corrected PPU OAM address latching with the accuracy PPU.
2019-10-26 23:34:24 +09:00
byuu
4f09a3873d v112.1
Add SA1 generic board mapping without RAM.
2019-10-22 12:35:14 +09:00
byuu
55bfe402e7 v112 2019-10-20 02:18:37 +09:00
byuu
30d7fa1923 v111.10
Fixed deterministic serialization on Windows.
2019-10-17 21:42:42 +09:00
byuu
9f86a3be26 v111.9
Cleanups.
2019-10-16 16:17:56 +09:00
byuu
6b7e6e01bb v111.8
Serialize SDD1 decompressor
Major speedup to nall/serializer [Alcaro]
Removed fast PPU tile cache (major speedup for run-ahead mode)
2019-10-16 16:12:28 +09:00
byuu
53f8de6ac3 Merge pull request #174 from Alcaro/master
Optimize serialization stuff a bit
2019-10-16 09:26:50 +09:00
Alcaro
cd18cdb1d6 Optimize serialization stuff a bit 2019-10-15 20:29:04 +02:00
byuu
6cb7d89d64 Update features. 2019-10-15 22:27:18 +09:00
byuu
19f3cdfd5e v111.8
Added fully working deterministic save state support (non-portable.)
Rewind is now 100% deterministic, disk save states are still portable.
Added run-ahead support.
2019-10-15 22:12:10 +09:00
byuu
a32b6fae74 Minor syntax edit. 2019-10-14 23:47:17 +09:00
byuu
03a6e1c7de Added CONTRIBUTING.md 2019-10-14 23:46:21 +09:00
byuu
6b34f134bf More libretro changes. 2019-10-14 23:16:25 +09:00
byuu
2de906ea46 v111.7
Added System/Serialization/Synchronize setting to settings.bml
This option is for experimental deterministic rewind support.
It does not currently work with SuperFX and SA-1 games,
and as such is set to true (force synchronize) for now.
2019-10-14 23:04:38 +09:00
byuu
95addddc46 v111.6
Added support for multiple serialization methods.
Revert to the fast method for games by default.
Default Tales of Phantasia and Star Ocean to the new strict method.
Added new Synchronization/Method settings file override.
Added new pseudo-fullscreen hotkey toggle by request.
Added new preset settings buttons to the driver settings panel.
Merged Super Game Boy support for the libretro target [rtretiakov]
2019-10-13 23:44:53 +09:00
byuu
45e9e0f0ea Language detection fix attempt 1. 2019-10-12 15:47:34 +09:00
byuu
0aea7fd5c5 NHL '94 (Japan) scanline override for fast PPU 2019-10-12 14:38:09 +09:00
byuu
fb95d5b59f v111.5
Updated frame advance to run after first advance when paused previously.
Moved frame events into the CPU core to prevent PPU<>NMI race condition.
Credit to r5 for pointing out there being an issue during frame advance.
2019-10-12 14:28:03 +09:00
byuu
3d646aef73 Added SHVC-2P3B-01 2019-10-10 11:34:45 +09:00
byuu
3fb7ff6bfe v110.4
Save state improvements.
2019-10-09 00:48:59 +09:00
byuu
d8bc2050be v111.4
Serialization improvements.
2019-10-09 00:45:57 +09:00
byuu
e71da4d8c8 Fix detection of ST010 HLE mode when firmware is missing.
Fix display of ST011 missing firmware message.
2019-10-07 16:03:53 +09:00
byuu
e78aca34b9 v111.3
Save state improvements: rewind should be fully stable now.
Before, Star Ocean and Tales of Phantasia would rarely hang with rewind.
2019-10-07 13:32:21 +09:00
byuu
0c82cc325e v111.2
Two sprite fixes for the accurate PPU and Star Ocean.
2019-10-06 18:11:53 +09:00
byuu
78c76962ec v111.1 2019-10-06 10:45:03 +09:00
byuu
e22167cf82 v111.1
More improvements to SameBoy audio interface for Super Game Boy.
Added a fix for a very rare crashing issue with SDL 2.0 joypad support.
2019-10-06 10:14:30 +09:00
byuu
1698533774 v111 2019-10-05 15:09:50 +09:00
byuu
7b66e1c531 libretro Makefile improvements [orbea] 2019-10-05 14:34:09 +09:00
byuu
c6f92b782c Minor touchups. 2019-10-05 14:30:55 +09:00
byuu
e3f2e634c8 Fixed audio crackling in Super Game Boy emulation. 2019-10-05 14:29:31 +09:00
byuu
e598e81ab9 Add compatibility fixes to libretro target. 2019-10-05 13:48:35 +09:00
byuu
d37fb1c12e Added 17 new pixel shaders courtesy of hunterk porting and testing them. 2019-10-05 13:44:51 +09:00
byuu
eaf33cb078 Fix issue with bsnes not remembering user-selected audio frequency. 2019-10-05 13:37:04 +09:00
byuu
07427e4697 v110.7
Improvements to HDMA timing (courtesy of test ROM from undisbeliever]
This fixes flickering in Full Throttle - All-American Racing
2019-10-05 13:24:14 +09:00
byuu
b5301b7ea8 v110.6
Revert AVX2 (see pull request for details)
2019-10-05 10:34:32 +09:00
byuu
57c53a86b4 v110.5
Merged Alcaro's AVX2 mode 7 renderer.
Enable by editing mode7hd.cpp: USE_AVX2=1
2019-10-02 09:56:13 +09:00
byuu
f19f31938b Allow 2 megabit SRAM for SA-1 homebrew. 2019-10-01 06:56:50 +09:00
byuu
4efee7e9f1 Hotfix for Magical Drop (Japan)'s "tokoton mode" 2019-10-01 06:51:17 +09:00
byuu
3701236ca0 v107.4
Shrink Pixel struct for ~1.75% speedup [Alcaro]
2019-10-01 06:41:38 +09:00
byuu
2f684caa7c Merge pull request #132 from Alcaro/pixel4
Shrink struct SuperFamicom::PPUFast::Pixel from 12 to 4 bytes
2019-10-01 06:41:00 +09:00
Alcaro
3a064fc5a3 Shrink struct SuperFamicom::PPUFast::Pixel from 12 to 4 bytes
This gives approximately 3% speedup (118->122fps) on the F-Zero title screen
2019-09-29 13:02:20 +02:00
byuu
5a4b667eae v110.3
Added hotfix for bug in "The Hurricanes" (happens on a real SNES)
Added scanline override for NHL '94
Added fix for entering folder names into BrowserDialog filename box
2019-09-27 10:26:55 +09:00
byuu
62729df2d1 Build fixes [Screwtape] 2019-09-26 03:38:01 +09:00
byuu
1ef227f482 v110.2
Added CRT-Royale [hunterk]
Improved libretro target [rtretiakov]
2019-09-25 15:13:12 +09:00
byuu
6e5542aa20 v110.1
Fixed region detection issue in Hanguk Pro Yagu
Fixed boot hanging issue in Kishin Douji Zenki - Tenchi Meidou
Fixed slowdown issue in Mega Man X2 & X3
Added mute hotkey
Added HD mode 7 hotkeys (likely temporary, we'll see)
2019-09-23 09:50:40 +09:00
byuu
675662e739 v110
Corrections for IOKit joypad driver [Sintendo]
2019-09-21 04:59:29 +09:00
byuu
409dd371b9 v109.5
Added SHVC-4PV5B-01 prototype PCB to database.
Added Firepower 2000 fast PPU render cycle override.
Backported higan's newer accuracy PPU with sprite caching support.
2019-09-21 04:26:27 +09:00
byuu
18d2ab6435 v109.4
Rename hiro::Property to hiro::Attribute
Disable XChaCha20 CSPRNG on Android for now due to compilation issues
Add macOS IOKit joypad support [Sintendo]
2019-09-17 03:37:03 +09:00
byuu
1e626e75ef v109.3
Fixed crash when idling with the snow effect enabled.
Added Android target to libretro port [rtretiakov]
Various nall library improvements.
2019-09-13 22:15:11 +09:00
byuu
c6d90d3ff1 Dyslexia. 2019-09-10 22:35:30 +09:00
byuu
29caf77751 v109.2
Fixed alt-key menu activation on Windows.
Removed 2160p HD mode 7 due to Direct3D limit of 2048x2048 textures.
Reverted to safe ruby drivers when no configuration file is present.
Removed ASIO driver because nobody is interested in improving it.
Added macOS libretro target [rtretiakov]
2019-09-10 22:32:33 +09:00
byuu
29b13083d5 . 2019-09-07 18:22:55 +09:00
byuu
3883172a4e v109.1
Mask A23 for ExLoROM board mappings (fixes Thracia 776 fan translation)
2019-09-07 18:03:12 +09:00
293 changed files with 146369 additions and 5897 deletions

View File

@@ -14,6 +14,7 @@ linux-x86_64-binaries_task:
- mkdir bsnes-nightly/Firmware
- cp -a bsnes/out/bsnes bsnes-nightly/bsnes
- cp -a bsnes/Database/* bsnes-nightly/Database
- cp -a shaders bsnes-nightly/Shaders
- cp -a GPLv3.txt bsnes-nightly
- zip -r bsnes-nightly.zip bsnes-nightly
@@ -36,6 +37,7 @@ freebsd-x86_64-binaries_task:
- mkdir bsnes-nightly/Firmware
- cp -a bsnes/out/bsnes bsnes-nightly/bsnes
- cp -a bsnes/Database/* bsnes-nightly/Database
- cp -a shaders bsnes-nightly/Shaders
- cp -a GPLv3.txt bsnes-nightly
- zip -r bsnes-nightly.zip bsnes-nightly
@@ -58,6 +60,7 @@ windows-x86_64-binaries_task:
- mkdir bsnes-nightly/Firmware
- cp -a bsnes/out/bsnes bsnes-nightly/bsnes.exe
- cp -a bsnes/Database/* bsnes-nightly/Database
- cp -a shaders bsnes-nightly/Shaders
- cp -a GPLv3.txt bsnes-nightly
- zip -r bsnes-nightly.zip bsnes-nightly

2
.gitattributes vendored Normal file
View File

@@ -0,0 +1,2 @@
*.fs linguist-detectable=false
*.vs linguist-detectable=false

89
CONTRIBUTING.md Normal file
View File

@@ -0,0 +1,89 @@
Contributing
============
Code contributions are most welcome and highly appreciated!
But first, please note that although bsnes is licensed under the GPLv3 license,
in order to be merged upstream, any code contributions must be provided under
the ISC source code license.
This is *not* a CLA (community license agreement), no legal contract needs to be
signed, and you will maintain full and exclusive copyright ownership over any
contributed source code.
There are two reasons for this requirement:
GPLv4+
------
bsnes is currently licensed under the GPLv3 license only. I do not license bsnes
under the GPLv3 or later license, because there is no way of knowing what the
GPLv4 and later licenses will change, and if they will be in the best interests
of emulator development and video game preservation.
Although I put a good deal of trust into the FSF, no one is an oracle that can
predict the future. Would *you* agree to a license before being able to read it?
However, the GPLv4 may prove beneficial, and close important holes in the GPLv3
license, just as the GPLv3 license closed the GPLv2's TiVoization loophole. And
so it is important that bsnes retains the option of relicensing to the GPLv4+ in
the future.
As a point of interest, there have been projects with similar concerns about
using a GPLv2 or later clause, that are now permanently stuck on the GPLv2
license. There have also been projects that did use a GPLv2 or later clause,
only to disagree with the changes introduced in the GPLv3.
ISC
---
The more important reason for this requirement is that it is my intention to
release the entirety of bsnes under the ISC license once official upstream
development has ceased.
The reason I would want to relicense bsnes to the ISC license upon its official
discontinuation is because once again, no one is an oracle, and I cannot predict
what future issues bsnes permanently remaining under the GPLv3 license may
cause.
For instance, imagine a world where a certain vendor took over the world, and
the only way to distribute applications was with their approval, and their store
rules forbade GPLv3 software. Or perhaps a world where the GPL was abandoned in
favor of the new OSSv1 license. But GPLv3 software was incompatible with the
OSSv1 license. Other open source developers would not be able to use bsnes in
that scenario.
It would be very disappointing if all of our work ended up unusable 50+ years
into the future because it was permanently bound to the GPLv3 license.
GPLv3
-----
The reason I use the GPLv3 license currently is because it is a balance between
altruism and self-interest. The GPLv3 allows other vendors to sell my own code
without sharing revenue with me, and indeed this has already happened. But the
GPLv3 also prevents other vendors from improving upon bsnes without sharing
their work with everyone else as I have.
While I am actively developing bsnes, I do not wish to compete against myself.
As such, I believe the GPLv3 is the best license during active development, and
the ISC is the best license once bsnes is officially discontinued.
Considerations
--------------
This is the part that should concern you as a contributor: I am not requesting
contributed source code to be released under the ISC so that I personally may
sell GPLv3 commercial license exemptions to your work, but in the future when
bsnes is released under the ISC license, that will open the door for anyone to
sell the work commercially in a closed source form.
If this is not acceptable to you, I wholly understand and I welcome you to
release your work under the GPLv3 in the form of a bsnes fork. And if your work
is not an essential part of the core emulation -- that is to say, it may be
optionally disabled -- then I am still willing to work with you in merging such
work upstream anyway under the full GPLv3 license, but please reach out to me
first before developing under the assumption your work will be merged upstream.
Thank you very much for reading and hopefully for your understanding.

View File

@@ -1,8 +1,10 @@
bsnes
=====
bsnes is a multi-platform Super Nintendo (Super Famicom) emulator that focuses
on performance, features, and ease of use.
![bsnes logo © byuu](https://byuu.org/images/bsnes/github/byuu-bsnes-logo.png)
bsnes is a multi-platform Super Nintendo (Super Famicom) emulator from
[byuu](https://byuu.org) that focuses on performance, features, and ease of use.
bsnes currently enjoys 100% known, bug-free compatibility with the entire SNES
library when configured to its most accurate settings, giving it the same
@@ -32,12 +34,11 @@ Unique Features
- Built-in games database with thousands of game entries
- Built-in cheat code database for hundreds of popular games (by mightymo)
- Built-in save state manager with screenshot previews and naming capabilities
- Support for ASIO low-latency audio
- Customizable per-byte game mappings to support any cartridges, including prototype games
- 7-zip decompression support
- Extensive Satellaview emulation, including BS Memory flash write and wear-leveling emulation
- 30-bit color output support (where supported)
- Optional higan game folder support (standard game ROM files are also fully supported!)
- Advanced mapping system allowing multiple bindings to every emulated input
Standard Features
-----------------
@@ -49,6 +50,7 @@ Standard Features
- Several built-in software filters, including HQ2x (by MaxSt) and snes_ntsc (by blargg)
- Adaptive sync and dynamic rate control for perfect audio/video synchronization
- Just-in-time input polling for minimal input latency
- Run-ahead support for removing internal game engine input latency
- Support for Direct3D exclusive mode video
- Support for WASAPI exclusive mode audio
- Periodic auto-saving of game saves
@@ -56,6 +58,7 @@ Standard Features
- Sprite limit disable support
- Cubic audio interpolation support
- Optional high-level emulation of most SNES coprocessors
- Optional emulation of flaws in older emulators for compatibility with older unofficial software
- CPU, SA1, and SuperFX overclocking support
- Frame advance support
- Screenshot support
@@ -63,14 +66,22 @@ Standard Features
- Movie recording and playback support
- Rewind support
- HiDPI support
- Multi-monitor support
- Turbo support for controller inputs
Links
-----
- [Official website](https://bsnes.byuu.org)
- [Official website](https://byuu.org/bsnes)
- [Official git repository](https://github.com/byuu/bsnes)
- [Developer resources](https://byuu.net)
- [Donations](https://patreon.com/byuu)
Release Builds
--------------
- [Windows binaries](https://byuu.itch.io/bsnes)
Nightly Builds
--------------
@@ -79,3 +90,10 @@ Nightly Builds
- ![Build status](https://api.cirrus-ci.com/github/byuu/bsnes.svg?task=macOS-x86_64-binaries)
- ![Build status](https://api.cirrus-ci.com/github/byuu/bsnes.svg?task=linux-x86_64-binaries)
- ![Build status](https://api.cirrus-ci.com/github/byuu/bsnes.svg?task=freebsd-x86_64-binaries)
Preview
-------
![bsnes user interface © byuu](https://byuu.org/images/bsnes/github/byuu-bsnes-user-interface.png)
![bsnes running Bahamut Lagoon © byuu](https://byuu.org/images/bsnes/github/byuu-bsnes-bahamut-lagoon.png)
![bsnes running Tengai Makyou Zero © byuu](https://byuu.org/images/bsnes/github/byuu-bsnes-tengai-makyou-zero.png)

View File

@@ -1,5 +1,5 @@
database
revision: 2018-09-20
revision: 2020-01-01
//BS Memory (JPN)

View File

@@ -1,5 +1,5 @@
database
revision: 2018-09-20
revision: 2020-01-01
//Sufami Turbo (JPN)

View File

@@ -1,5 +1,5 @@
database
revision: 2018-09-20
revision: 2020-01-01
//Prototypes (JPN)
@@ -125,7 +125,7 @@ game
//Super Famicom (JPN)
database
revision: 2018-09-20
revision: 2020-01-01
game
sha256: 5c4e283efc338958b8dd45ebd6daf133a9eb280420a98e2e1df358ae0242c366
@@ -1277,7 +1277,7 @@ game
size: 0x100
content: Boot
manufacturer: Nintendo
architecture: LR35902
architecture: SM83
identifier: SGB1
game
@@ -1296,7 +1296,7 @@ game
size: 0x100
content: Boot
manufacturer: Nintendo
architecture: LR35902
architecture: SM83
identifier: SGB2
oscillator
frequency: 20971520
@@ -1705,7 +1705,7 @@ game
//Super Nintendo (ESP)
database
revision: 2018-04-14
revision: 2018-09-21
game
sha256: bd5e7a6bc08f64d39c54204b82c6c156f144c03e13c890128588c5faa560659c
@@ -1767,6 +1767,50 @@ game
size: 0x200000
content: Program
game
sha256: bd7e98db82d6b52307be1f3e1fd171e1e7204dc1f8810a95ee2cc64757087e4a
label: The Lost Vikings
name: Lost Vikings, The
region: SNSP-LV-ESP
revision: SESP-LV-0
board: SHVC-1A0N-20
memory
type: ROM
size: 0x100000
content: Program
game
sha256: 6eecabd46305ac95d9cf3a17e1392c24a1b68a7a313173ef0c5b5a3a24cf3353
label: Lufia
name: Lufia
region: SNSP-ANIS-ESP
revision: SPAL-ANIS-0
board: SHVC-1A3M-30
memory
type: ROM
size: 0x300000
content: Program
memory
type: RAM
size: 0x2000
content: Save
game
sha256: d70bc7916ed5132c3b0053f2adbb5004d78ccb986210c9440fedf642cac68554
label: MechWarrior
name: MechWarrior
region: SNSP-WM-ESP
revision: SESP-WM-0
board: SHVC-1A1M-10
memory
type: ROM
size: 0x100000
content: Program
memory
type: RAM
size: 0x800
content: Save
game
sha256: d2233d6310522bbf183b6ca9bbe3e2afaf24de0cc4304bff6d0d547d678aed6f
label: Sonic Blast Man
@@ -1779,6 +1823,18 @@ game
size: 0x100000
content: Program
game
sha256: a20d346da18ddabf70dc43f5095c4189c4a646ca8e6d4ed6c68c20e380f50332
label: Super Battletank 2
name: Super Battletank 2
region: SNSP-2X-ESP
revision: SESP-2X-0
board: SHVC-1A0N-20
memory
type: ROM
size: 0x200000
content: Program
game
sha256: 9eaf1c46d8a068c910d66f582e23b1155882ddfa4b9fd0813819fc5c008167e2
label: Super James Pond
@@ -1803,6 +1859,46 @@ game
size: 0x100000
content: Program
game
sha256: 7f731f4bb620e682132660da39641dda5762211dca4732f8192dd2411211b822
label: Terranigma
name: Terranigma
region: SNSP-AQTS-ESP
revision: SPAL-AQTS-0
board: SHVC-1J3M-20
memory
type: ROM
size: 0x400000
content: Program
memory
type: RAM
size: 0x2000
content: Save
game
sha256: 981128c93f0753dec7af29ec084f13e704cc5d02414be55bb477fc4b2fef5e58
label: Tiny Toon Adventures: Buster Busts Loose!
name: Tiny Toon Adventures - Buster Busts Loose!
region: SNSP-TA-ESP
revision: SESP-TA-0
board: SHVC-1A0N-20
memory
type: ROM
size: 0x100000
content: Program
game
sha256: ce2445ecd0a43f6025dc80857d91dae7c46d33f7821bf98232c2894ca1959da2
label: Turn and Burn: No-Fly Zone
name: Turn and Burn - No-Fly Zone
region: SNSP-ZN-ESP
revision: SESP-ZN-0
board: SHVC-1A0N-20
memory
type: ROM
size: 0x200000
content: Program
game
sha256: 9ed876a632aa699047e9efba8a64ab57abc55086a0aab6b5fa67d87ea4647f3f
label: Whirlo
@@ -1846,7 +1942,7 @@ game
//Super Nintendo (EUR)
database
revision: 2018-05-06
revision: 2018-09-21
game
sha256: ec3e81d628a293514e303b44e3b1ac03461ddd1da32764b10b7fab1e507602df
@@ -2904,6 +3000,18 @@ game
size: 0x100000
content: Program
game
sha256: 4ad736a9e1c7f34740afaa7777b8f1a31da4bb4a021e7ae341d1dafd74fa0acc
label: True Lies
name: True Lies
region: SNSP-ATLP-EUR
revision: SPAL-ATLP-0
board: SHVC-1A0N-30
memory
type: ROM
size: 0x200000
content: Program
game
sha256: dbf11d4c77b9aa3416f687201d57d71a23bb8fb0b8fe5e9e8212db3fac036631
label: Turbo Toons
@@ -2942,7 +3050,7 @@ game
game
sha256: 1217ddf2fe475661a54f50e111864102faf854397ce5aceea4297204ebd6cbb6
label: Val d'isére Championship
label: Val d'isère Championship
name: Val d'isere Championship
region: SNSP-8Z-EUR
revision: SPAL-8Z-0
@@ -3016,7 +3124,7 @@ game
//Super Nintendo (FAH)
database
revision: 2018-04-14
revision: 2018-09-21
game
sha256: 0aafd04a43ae29266e43920a7f9954d4a49f6fe43a5abffecc9c2fd5ad7d6cea
@@ -3110,6 +3218,18 @@ game
size: 0x800
content: Save
game
sha256: 826a328f401cdf5a9ee87aaa7a2784cbb21813b165a6c7ca3f702fe6ba8c0804
label: Eric Cantona: Football Challenge
name: Eric Cantona - Football Challenge
region: SNSP-EC-FAH
revision: SPAL-EC-0
board: SHVC-1A0N-20
memory
type: ROM
size: 0x80000
content: Program
game
sha256: ce09743d44a54f64862d8c53c11c2c84f2f861ec74c778bd8b05b0a3b07708d6
label: FIFA International Soccer
@@ -3423,10 +3543,22 @@ game
size: 0x180000
content: Program
game
sha256: 5a9103b04b9246f63af9018cbbd7934c6b79076dd9b0062887bd16077cd37c81
label: Val d'Isère Championship
name: Val d'Isere Championship
region: SNSP-8V-FAH
revision: SPAL-8V-0
board: SHVC-1A0N-20
memory
type: ROM
size: 0x100000
content: Program
//Super Nintendo (FRA)
database
revision: 2018-04-14
revision: 2018-09-21
game
sha256: 65df600780021f13ced52e7fbc507b7b2e6491b2c5c25fe78d0515dcbe669403
@@ -3660,6 +3792,22 @@ game
size: 0x180000
content: Program
game
sha256: b730adcbb34a19f8fd1c2abe27455cc3256329a9b8a021291e3009ea33004127
label: Secret of Mana
name: Secret of Mana
region: SNSP-K2-FRA
revision: SFRA-K2-1
board: SHVC-1J3M-11
memory
type: ROM
size: 0x200000
content: Program
memory
type: RAM
size: 0x2000
content: Save
game
sha256: f73e6da9e979c839c7c22ec487bea6667d3e65e7d8f9fcc97a2bcdeb4487cddf
label: SimCity
@@ -3676,6 +3824,38 @@ game
size: 0x8000
content: Save
game
sha256: 1f226553ba05fe738d085a88154469bbc9f9058f7dfc320a327259d84ae5f393
label: Soul Blazer
name: Soul Blazer
region: SNSP-SO-FRA
revision: SFRA-SO-0
board: SHVC-1J3M-20
memory
type: ROM
size: 0x100000
content: Program
memory
type: RAM
size: 0x2000
content: Save
game
sha256: 5d0a234a2fcb343d169206d9d7d578507c44f800ead9cc9ccfa0b1d4cb1cc9e5
label: Terranigma
name: Terranigma
region: SNSP-AQTF-FRA
revision: SPAL-AQTF-0
board: SHVC-1J3M-20
memory
type: ROM
size: 0x400000
content: Program
memory
type: RAM
size: 0x2000
content: Save
//Super Nintendo (FRG)
database
@@ -3762,7 +3942,23 @@ game
//Super Nintendo (ITA)
database
revision: 2018-04-14
revision: 2018-09-21
game
sha256: deab7aad7c168423e43eae14e9e31efa29c7341ab84f936be508911ce508b372
label: MechWarrior
name: MechWarrior
region: SNSP-WM-ITA
revision: SITA-WM-0
board: SHVC-1A1M-01
memory
type: ROM
size: 0x100000
content: Program
memory
type: RAM
size: 0x800
content: Save
game
sha256: aafbae4c2a7a5a35c81a183df0470027b4b5690f836592af21c15af6b259328d
@@ -3796,7 +3992,7 @@ game
//Super Nintendo (NOE)
database
revision: 2018-04-14
revision: 2020-01-01
game
sha256: b342d12d71729edebc1911725ea23d58c1a397b27253a5c8cd96cfb58af242a9
@@ -4394,6 +4590,18 @@ game
size: 0x80000
content: Program
game
sha256: 09299d142e485ba2fcdbd9b3a6d1a5acfbc7fc70b06cf22be28479686419a7a9
label: Jimmy Connors Pro Tennis Tour
name: Jimmy Connors Pro Tennis Tour
region: SNSP-JC-NOE
revision: SFRG-JC-0
board: SHVC-1A0N-20
memory
type: ROM
size: 0x80000
content: Program
game
sha256: 74c55ea3c9733bf263628a260df7492fc840d7de1c3fceebb7bcf6d99a8c81d6
label: Joe & Mac: Caveman Ninja
@@ -4470,6 +4678,18 @@ game
size: 0x100000
content: Program
game
sha256: 4a7444780a750f97943d974589586d4cf89d8957e396cc5a7ad565cd4c1b70a7
label: The Legend of the Mystical Ninja
name: Legend of the Mystical Ninja, The
region: SNSP-GG-NOE
revision: SFRG-GG-0
board: SHVC-1A0N-20
memory
type: ROM
size: 0x100000
content: Program
game
sha256: 5ec66298ddb579b35cc5d3df5bfeeee05bdf71347565c7c5f5f3869bf4f1e469
label: Looney Tunes Basketball
@@ -4820,7 +5040,7 @@ game
size: 0x100
content: Boot
manufacturer: Nintendo
architecture: LR35902
architecture: SM83
identifier: SGB1
game
@@ -4975,6 +5195,18 @@ game
size: 0x100000
content: Program
game
sha256: 94e6fe78bb1a1d89ccfd74ad92e2a489f8e2e257d6dfe62404155741763f962f
label: True Lies
name: True Lies
region: SNSP-ATLD-NOE
revision: SPAL-ATLD-0
board: SHVC-1A0N-30
memory
type: ROM
size: 0x200000
content: Program
game
sha256: a1105819d48c04d680c8292bbfa9abbce05224f1bc231afd66af43b7e0a1fd4e
label: Unirally
@@ -5054,7 +5286,7 @@ game
//Super Nintendo (SCN)
database
revision: 2018-04-14
revision: 2018-09-21
game
sha256: beb379ba48f63561c0f939ecd8f623ec06c1b5e06976eef9887e5c62f3df2766
@@ -5080,6 +5312,22 @@ game
size: 0x300000
content: Program
game
sha256: 4fb9eb8fa4d9c3a0b6c24bac5b0a0b0f079f083f5e6dfa937a161c8f4bcde853
label: Shadowrun
name: Shadowrun
region: SNSP-WR-SCN
revision: SSWE-WR-0
board: SHVC-1A3M-20
memory
type: ROM
size: 0x100000
content: Program
memory
type: RAM
size: 0x2000
content: Save
game
sha256: e15247495311e91db9431d61777a264d4b42def011291d512b273fc8acd1cbfa
label: Soul Blazer
@@ -5096,6 +5344,18 @@ game
size: 0x2000
content: Save
game
sha256: 687c4f9a14cc16605f5e92aa0fe33bf083fe8e39ba781676259fadf932480890
label: Tintin i Tibet
name: Tintin i Tibet
region: SNSP-AT6X-SCN
revision: SPAL-AT6X-0
board: SHVC-2A0N-20
memory
type: ROM
size: 0x180000
content: Program
game
sha256: a6297356fb06f1575b432fae463171f53e3b786fd77b841557547a9117fb52fe
label: X-Zone
@@ -5761,7 +6021,7 @@ game
//Super Nintendo (USA)
database
revision: 2018-09-20
revision: 2020-01-01
game
sha256: 2ffe8828480f943056fb1ab5c3c84d48a0bf8cbe3ed7c9960b349b59adb07f3b
@@ -6555,8 +6815,8 @@ game
sha256: 6fa6b8a8804ff6544bdedf94339a86ba64ce0b6dbf059605abb1cd6f102d3483
label: Bill Laimbeer's Combat Basketball
name: Bill Laimbeer's Combat Basketball
region: SNS-C8-USA
revision: SNS-C8-0
region: SNS-CB-USA
revision: SNS-CB-0
board: SHVC-1A3B-12
memory
type: ROM
@@ -13628,7 +13888,7 @@ game
size: 0x100
content: Boot
manufacturer: Nintendo
architecture: LR35902
architecture: SM83
identifier: SGB1
game
@@ -14516,8 +14776,8 @@ game
sha256: 3cdebbd8adc4bb6773a7995f542fdac49adefca71cba583255a1c1bf37ac3946
label: Tetris & Dr. Mario
name: Tetris & Dr. Mario
region: SNS-AFTE-USA
revision: SNS-AFTE-0
region: SNS-ATFE-USA
revision: SNS-ATFE-0
board: SHVC-1A0N-30
memory
type: ROM

View File

@@ -42,8 +42,6 @@ else ifneq ($(filter $(platform),linux bsd),)
flags += -fPIC
options += -shared
endif
else
$(error "unsupported platform")
endif
objects := libco emulator filter lzma

View File

@@ -55,14 +55,16 @@ struct Filter {
struct Stream {
auto reset(uint channels, double inputFrequency, double outputFrequency) -> void;
auto reset() -> void;
auto frequency() const -> double;
auto setFrequency(double inputFrequency, maybe<double> outputFrequency = nothing) -> void;
auto addDCRemovalFilter() -> void;
auto addLowPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void;
auto addHighPassFilter(double cutoffFrequency, Filter::Order order, uint passes = 1) -> void;
auto pending() const -> bool;
auto pending() const -> uint;
auto read(double samples[]) -> uint;
auto write(const double samples[]) -> void;
@@ -71,6 +73,8 @@ struct Stream {
write(samples);
}
auto serialize(serializer&) -> void;
private:
struct Channel {
vector<Filter> filters;

View File

@@ -9,6 +9,16 @@ auto Stream::reset(uint channelCount, double inputFrequency, double outputFreque
setFrequency(inputFrequency, outputFrequency);
}
auto Stream::reset() -> void {
for(auto& channel : channels) {
channel.resampler.reset(this->inputFrequency, this->outputFrequency);
}
}
auto Stream::frequency() const -> double {
return inputFrequency;
}
auto Stream::setFrequency(double inputFrequency, maybe<double> outputFrequency) -> void {
this->inputFrequency = inputFrequency;
if(outputFrequency) this->outputFrequency = outputFrequency();
@@ -77,8 +87,9 @@ auto Stream::addHighPassFilter(double cutoffFrequency, Filter::Order order, uint
}
}
auto Stream::pending() const -> bool {
return channels && channels[0].resampler.pending();
auto Stream::pending() const -> uint {
if(!channels) return 0;
return channels[0].resampler.pending();
}
auto Stream::read(double samples[]) -> uint {
@@ -104,3 +115,11 @@ auto Stream::write(const double samples[]) -> void {
audio.process();
}
auto Stream::serialize(serializer& s) -> void {
for(auto& channel : channels) {
channel.resampler.serialize(s);
}
s.real(inputFrequency);
s.real(outputFrequency);
}

View File

@@ -1,5 +1,7 @@
#pragma once
#include <libco/libco.h>
#include <nall/platform.hpp>
#include <nall/adaptive-array.hpp>
#include <nall/any.hpp>
@@ -20,8 +22,6 @@
#include <nall/hash/sha256.hpp>
using namespace nall;
#include <libco/libco.h>
#include <emulator/types.hpp>
#include <emulator/memory/readable.hpp>
#include <emulator/memory/writable.hpp>
@@ -29,13 +29,13 @@ using namespace nall;
namespace Emulator {
static const string Name = "bsnes";
static const string Version = "109";
static const string Version = "114";
static const string Author = "byuu";
static const string License = "GPLv3";
static const string Website = "https://byuu.org";
//incremented only when serialization format changes
static const string SerializerVersion = "109";
static const string SerializerVersion = "112";
namespace Constants {
namespace Colorburst {

View File

@@ -81,7 +81,7 @@ struct Interface {
virtual auto synchronize(uint64 timestamp = 0) -> void {}
//state functions
virtual auto serialize() -> serializer { return {}; }
virtual auto serialize(bool synchronize = true) -> serializer { return {}; }
virtual auto unserialize(serializer&) -> bool { return false; }
//cheat functions
@@ -101,6 +101,9 @@ struct Interface {
virtual auto frameSkip() -> uint { return 0; }
virtual auto setFrameSkip(uint frameSkip) -> void {}
virtual auto runAhead() -> bool { return false; }
virtual auto setRunAhead(bool runAhead) -> void {}
};
}

View File

@@ -48,6 +48,23 @@ bool GB_apu_is_DAC_enabled(GB_gameboy_t *gb, unsigned index)
return false;
}
static uint8_t agb_bias_for_channel(GB_gameboy_t *gb, unsigned index)
{
if (!gb->apu.is_active[index]) return 0;
switch (index) {
case GB_SQUARE_1:
return gb->apu.square_channels[GB_SQUARE_1].current_volume;
case GB_SQUARE_2:
return gb->apu.square_channels[GB_SQUARE_2].current_volume;
case GB_WAVE:
return 0;
case GB_NOISE:
return gb->apu.noise_channel.current_volume;
}
return 0;
}
static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsigned cycles_offset)
{
if (gb->model >= GB_MODEL_AGB) {
@@ -66,15 +83,17 @@ static void update_sample(GB_gameboy_t *gb, unsigned index, int8_t value, unsign
}
GB_sample_t output;
uint8_t bias = agb_bias_for_channel(gb, index);
if (gb->io_registers[GB_IO_NR51] & (1 << index)) {
output.right = (0xf - value * 2) * right_volume;
output.right = (0xf - value * 2 + bias) * right_volume;
}
else {
output.right = 0xf * right_volume;
}
if (gb->io_registers[GB_IO_NR51] & (0x10 << index)) {
output.left = (0xf - value * 2) * left_volume;
output.left = (0xf - value * 2 + bias) * left_volume;
}
else {
output.left = 0xf * left_volume;
@@ -681,6 +700,21 @@ void GB_apu_write(GB_gameboy_t *gb, uint8_t reg, uint8_t value)
case GB_IO_NR14:
case GB_IO_NR24: {
unsigned index = reg == GB_IO_NR24? GB_SQUARE_2: GB_SQUARE_1;
/* TODO: When the sample length changes right before being updated, the countdown should change to the
old length, but the current sample should not change. Because our write timing isn't accurate to
the T-cycle, we hack around it by stepping the sample index backwards. */
if ((value & 0x80) == 0 && gb->apu.is_active[index]) {
/* On an AGB, as well as on CGB C and earlier (TODO: Tested: 0, B and C), it behaves slightly different on
double speed. */
if (gb->model == GB_MODEL_CGB_E /* || gb->model == GB_MODEL_CGB_D */ || gb->apu.square_channels[index].sample_countdown & 1) {
if (gb->apu.square_channels[index].sample_countdown >> 1 == (gb->apu.square_channels[index].sample_length ^ 0x7FF)) {
gb->apu.square_channels[index].current_sample_index--;
gb->apu.square_channels[index].current_sample_index &= 7;
}
}
}
gb->apu.square_channels[index].sample_length &= 0xFF;
gb->apu.square_channels[index].sample_length |= (value & 7) << 8;
if (index == GB_SQUARE_1) {
@@ -970,9 +1004,23 @@ void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate)
if (sample_rate) {
gb->apu_output.highpass_rate = pow(0.999958, GB_get_clock_rate(gb) / (double)sample_rate);
}
gb->apu_output.rate_set_in_clocks = false;
GB_apu_update_cycles_per_sample(gb);
}
void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample)
{
if (cycles_per_sample == 0) {
GB_set_sample_rate(gb, 0);
return;
}
gb->apu_output.cycles_per_sample = cycles_per_sample;
gb->apu_output.sample_rate = GB_get_clock_rate(gb) / cycles_per_sample * 2;
gb->apu_output.highpass_rate = pow(0.999958, cycles_per_sample);
gb->apu_output.rate_set_in_clocks = true;
}
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback)
{
gb->apu_output.sample_callback = callback;
@@ -985,6 +1033,7 @@ void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode)
void GB_apu_update_cycles_per_sample(GB_gameboy_t *gb)
{
if (gb->apu_output.rate_set_in_clocks) return;
if (gb->apu_output.sample_rate) {
gb->apu_output.cycles_per_sample = 2 * GB_get_clock_rate(gb) / (double)gb->apu_output.sample_rate; /* 2 * because we use 8MHz units */
}

View File

@@ -143,9 +143,12 @@ typedef struct {
GB_double_sample_t highpass_diff;
GB_sample_callback_t sample_callback;
bool rate_set_in_clocks;
} GB_apu_output_t;
void GB_set_sample_rate(GB_gameboy_t *gb, unsigned sample_rate);
void GB_set_sample_rate_by_clocks(GB_gameboy_t *gb, double cycles_per_sample); /* Cycles are in 8MHz units */
void GB_set_highpass_filter_mode(GB_gameboy_t *gb, GB_highpass_mode_t mode);
void GB_apu_set_sample_callback(GB_gameboy_t *gb, GB_sample_callback_t callback);
#ifdef GB_INTERNAL

View File

@@ -127,13 +127,13 @@ static void display_vblank(GB_gameboy_t *gb)
if (GB_is_hle_sgb(gb)) {
GB_sgb_render(gb);
}
if (gb->turbo) {
if (GB_timing_sync_turbo(gb)) {
return;
}
}
if (!gb->disable_rendering && ((!(gb->io_registers[GB_IO_LCDC] & 0x80) || gb->stopped) || gb->frame_skip_state == GB_FRAMESKIP_LCD_TURNED_ON)) {
/* LCD is off, set screen to white or black (if LCD is on in stop mode) */
if (gb->sgb) {
@@ -162,9 +162,20 @@ static inline uint8_t scale_channel(uint8_t x)
static inline uint8_t scale_channel_with_curve(uint8_t x)
{
return (uint8_t[]){0,2,4,7,12,18,25,34,42,52,62,73,85,97,109,121,134,146,158,170,182,193,203,213,221,230,237,243,248,251,253,255,}[x];
return (uint8_t[]){0,2,4,7,12,18,25,34,42,52,62,73,85,97,109,121,134,146,158,170,182,193,203,213,221,230,237,243,248,251,253,255}[x];
}
static inline uint8_t scale_channel_with_curve_agb(uint8_t x)
{
return (uint8_t[]){0,2,5,10,15,20,26,32,38,45,52,60,68,76,84,92,101,110,119,128,138,148,158,168,178,189,199,210,221,232,244,255}[x];
}
static inline uint8_t scale_channel_with_curve_sgb(uint8_t x)
{
return (uint8_t[]){0,2,5,9,15,20,27,34,42,50,58,67,76,85,94,104,114,123,133,143,153,163,173,182,192,202,211,220,229,238,247,255}[x];
}
uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color)
{
uint8_t r = (color) & 0x1F;
@@ -177,13 +188,29 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color)
b = scale_channel(b);
}
else {
r = scale_channel_with_curve(r);
g = scale_channel_with_curve(g);
b = scale_channel_with_curve(b);
if (GB_is_sgb(gb)) {
return gb->rgb_encode_callback(gb,
scale_channel_with_curve_sgb(r),
scale_channel_with_curve_sgb(g),
scale_channel_with_curve_sgb(b));
}
bool agb = gb->model == GB_MODEL_AGB;
r = agb? scale_channel_with_curve_agb(r) : scale_channel_with_curve(r);
g = agb? scale_channel_with_curve_agb(g) : scale_channel_with_curve(g);
b = agb? scale_channel_with_curve_agb(b) : scale_channel_with_curve(b);
if (gb->color_correction_mode != GB_COLOR_CORRECTION_CORRECT_CURVES) {
uint8_t new_g = (g * 3 + b) / 4;
uint8_t new_r = r, new_b = b;
uint8_t new_r, new_g, new_b;
if (agb) {
new_r = (r * 7 + g * 1) / 8;
new_g = (g * 3 + b * 1) / 4;
new_b = (b * 7 + r * 1) / 8;
}
else {
new_g = (g * 3 + b) / 4;
new_r = r;
new_b = b;
}
if (gb->color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) {
uint8_t old_max = MAX(r, MAX(g, b));
uint8_t new_max = MAX(new_r, MAX(new_g, new_b));
@@ -200,7 +227,7 @@ uint32_t GB_convert_rgb15(GB_gameboy_t *gb, uint16_t color)
if (new_min != 0xff) {
new_r = 0xff - (0xff - new_r) * (0xff - old_min) / (0xff - new_min);
new_g = 0xff - (0xff - new_g) * (0xff - old_min) / (0xff - new_min);
new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);;
new_b = 0xff - (0xff - new_b) * (0xff - old_min) / (0xff - new_min);
}
}
r = new_r;
@@ -377,7 +404,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
}
uint8_t icd_pixel = 0;
{
uint8_t pixel = bg_enabled? fifo_item->pixel : 0;
if (pixel && bg_priority) {
@@ -394,7 +420,6 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
else if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
icd_pixel = pixel;
//gb->icd_pixel_callback(gb, pixel);
}
}
else {
@@ -423,7 +448,7 @@ static void render_pixel_if_possible(GB_gameboy_t *gb)
gb->screen[gb->position_in_line + gb->current_line * WIDTH] = gb->sprite_palettes_rgb[oam_fifo_item->palette * 4 + pixel];
}
}
if (gb->model & GB_MODEL_NO_SFC_BIT) {
if (gb->icd_pixel_callback) {
gb->icd_pixel_callback(gb, icd_pixel);
@@ -769,10 +794,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
fifo_push_bg_row(&gb->bg_fifo, 0, 0, 0, false, false);
/* Todo: find out actual access time of SCX */
gb->position_in_line = - (gb->io_registers[GB_IO_SCX] & 7) - 8;
gb->current_lcd_line++; // Todo: unverified timing
if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) {
display_vblank(gb);
}
gb->fetcher_x = ((gb->io_registers[GB_IO_SCX]) / 8) & 0x1f;
gb->extra_penalty_for_sprite_at_0 = (gb->io_registers[GB_IO_SCX] & 7);
@@ -909,6 +931,12 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
}
GB_SLEEP(gb, display, 11, LINE_LENGTH - gb->cycles_for_line);
gb->mode_for_interrupt = 2;
// Todo: unverified timing
gb->current_lcd_line++;
if (gb->current_lcd_line == LINES && GB_is_sgb(gb)) {
display_vblank(gb);
}
if (gb->icd_hreset_callback) {
gb->icd_hreset_callback(gb);
@@ -985,7 +1013,7 @@ void GB_display_run(GB_gameboy_t *gb, uint8_t cycles)
gb->window_disabled_while_active = false;
gb->current_line = 0;
// TODO: not the correct timing
gb->current_lcd_line = -1;
gb->current_lcd_line = 0;
if (gb->icd_vreset_callback) {
gb->icd_vreset_callback(gb);
}

View File

@@ -12,11 +12,19 @@
#include "random.h"
#include "gb.h"
#ifdef DISABLE_REWIND
#define GB_rewind_free(...)
#define GB_rewind_push(...)
#endif
static inline uint32_t state_magic(void)
{
if (sizeof(bool) == 1) return 'SAME';
return 'S4ME';
}
void GB_attributed_logv(GB_gameboy_t *gb, GB_log_attributes attributes, const char *fmt, va_list args)
{
char *string = NULL;
@@ -659,7 +667,7 @@ void GB_disconnect_serial(GB_gameboy_t *gb)
bool GB_is_inited(GB_gameboy_t *gb)
{
return gb->magic == 'SAME';
return gb->magic == state_magic();
}
bool GB_is_cgb(GB_gameboy_t *gb)
@@ -711,7 +719,8 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NO_SFC:
case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
for (unsigned i = 0; i < gb->ram_size; i++) {
gb->ram[i] = GB_random();
if (i & 0x100) {
@@ -757,7 +766,8 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NO_SFC:
case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
for (unsigned i = 0; i < sizeof(gb->hram); i++) {
@@ -782,7 +792,8 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified */
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NO_SFC: /* Unverified */
case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
for (unsigned i = 0; i < 8; i++) {
@@ -810,7 +821,8 @@ static void reset_ram(GB_gameboy_t *gb)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC: /* Unverified*/
case GB_MODEL_SGB_PAL: /* Unverified */
case GB_MODEL_SGB_NO_SFC: /* Unverified */
case GB_MODEL_SGB_NTSC_NO_SFC: /* Unverified */
case GB_MODEL_SGB_PAL_NO_SFC: /* Unverified */
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC: {
uint8_t temp;
@@ -857,7 +869,7 @@ void GB_reset(GB_gameboy_t *gb)
gb->mbc_rom_bank = 1;
gb->last_rtc_second = time(NULL);
gb->cgb_ram_bank = 1;
gb->io_registers[GB_IO_JOYP] = 0xF;
gb->io_registers[GB_IO_JOYP] = 0xCF;
gb->mbc_ram_size = mbc_ram_size;
if (GB_is_cgb(gb)) {
gb->ram_size = 0x1000 * 8;
@@ -924,7 +936,7 @@ void GB_reset(GB_gameboy_t *gb)
gb->nontrivial_jump_state = NULL;
}
gb->magic = (uintptr_t)'SAME';
gb->magic = state_magic();
}
void GB_switch_model_and_reset(GB_gameboy_t *gb, GB_model_t model)
@@ -1016,12 +1028,12 @@ void GB_set_clock_multiplier(GB_gameboy_t *gb, double multiplier)
uint32_t GB_get_clock_rate(GB_gameboy_t *gb)
{
if (gb->model == GB_MODEL_SGB_NTSC) {
return SGB_NTSC_FREQUENCY * gb->clock_multiplier;
}
if (gb->model == GB_MODEL_SGB_PAL) {
if (gb->model & GB_MODEL_PAL_BIT) {
return SGB_PAL_FREQUENCY * gb->clock_multiplier;
}
if ((gb->model & ~GB_MODEL_NO_SFC_BIT) == GB_MODEL_SGB) {
return SGB_NTSC_FREQUENCY * gb->clock_multiplier;
}
return CPU_FREQUENCY * gb->clock_multiplier;
}

View File

@@ -1,7 +1,6 @@
#ifndef GB_h
#define GB_h
#define typeof __typeof__
#define _XOPEN_SOURCE 500
#include <stdbool.h>
#include <stdint.h>
#include <time.h>
@@ -71,7 +70,9 @@ typedef enum {
GB_MODEL_SGB = 0x004,
GB_MODEL_SGB_NTSC = GB_MODEL_SGB,
GB_MODEL_SGB_PAL = GB_MODEL_SGB | GB_MODEL_PAL_BIT,
GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT,
GB_MODEL_SGB_NTSC_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT,
GB_MODEL_SGB_NO_SFC = GB_MODEL_SGB_NTSC_NO_SFC,
GB_MODEL_SGB_PAL_NO_SFC = GB_MODEL_SGB | GB_MODEL_NO_SFC_BIT | GB_MODEL_PAL_BIT,
// GB_MODEL_MGB = 0x100,
GB_MODEL_SGB2 = 0x101,
GB_MODEL_SGB2_NO_SFC = GB_MODEL_SGB2 | GB_MODEL_NO_SFC_BIT,
@@ -277,10 +278,6 @@ typedef struct {
This struct is not packed, but dumped sections exclusively use types that have the same alignment in both 32 and 64
bit platforms. */
/* We make sure bool is 1 for cross-platform save state compatibility. */
/* Todo: We might want to typedef our own bool if this prevents SameBoy from working on specific platforms. */
//_Static_assert(sizeof(bool) == 1, "sizeof(bool) != 1");
#ifdef GB_INTERNAL
struct GB_gameboy_s {
#else
@@ -544,6 +541,7 @@ struct GB_gameboy_internal_s {
GB_icd_pixel_callback_t icd_pixel_callback;
GB_icd_vreset_callback_t icd_hreset_callback;
GB_icd_vreset_callback_t icd_vreset_callback;
GB_read_memory_callback_t read_memory_callback;
/* IR */
long cycles_since_ir_change; // In 8MHz units

View File

@@ -3,7 +3,7 @@
void GB_update_joyp(GB_gameboy_t *gb)
{
if (gb->model & GB_MODEL_SGB_NO_SFC) return;
if (gb->model & GB_MODEL_NO_SFC_BIT) return;
uint8_t key_selection = 0;
uint8_t previous_state = 0;
@@ -12,7 +12,7 @@ void GB_update_joyp(GB_gameboy_t *gb)
previous_state = gb->io_registers[GB_IO_JOYP] & 0xF;
key_selection = (gb->io_registers[GB_IO_JOYP] >> 4) & 3;
gb->io_registers[GB_IO_JOYP] &= 0xF0;
uint8_t current_player = gb->sgb? gb->sgb->current_player : 0;
uint8_t current_player = gb->sgb? (gb->sgb->current_player & (gb->sgb->player_count - 1) & 3) : 0;
switch (key_selection) {
case 3:
if (gb->sgb && gb->sgb->player_count > 1) {
@@ -73,7 +73,7 @@ void GB_icd_set_joyp(GB_gameboy_t *gb, uint8_t value)
if (previous_state & ~(gb->io_registers[GB_IO_JOYP] & 0xF)) {
gb->io_registers[GB_IO_IF] |= 0x10;
}
gb->io_registers[GB_IO_JOYP] |= 0xC0;
}
void GB_set_key_state(GB_gameboy_t *gb, GB_key_t index, bool pressed)

View File

@@ -273,7 +273,8 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NO_SFC:
case GB_MODEL_SGB_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
;
@@ -310,7 +311,6 @@ static uint8_t read_high_memory(GB_gameboy_t *gb, uint16_t addr)
(gb->apu.is_active[GB_WAVE] ? (gb->apu.samples[GB_WAVE]) : 0);
case GB_IO_JOYP:
GB_timing_sync(gb);
return gb->io_registers[addr & 0xFF] | 0xC0;
case GB_IO_TMA:
case GB_IO_LCDC:
case GB_IO_SCY:
@@ -422,11 +422,9 @@ static GB_read_function_t * const read_map[] =
read_ram, read_high_memory, /* EXXX FXXX */
};
static GB_read_memory_callback_t GB_read_memory_callback_v = 0;
void GB_set_read_memory_callback(GB_gameboy_t *gb, GB_read_memory_callback_t callback)
{
GB_read_memory_callback_v = callback;
gb->read_memory_callback = callback;
}
uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
@@ -437,9 +435,9 @@ uint8_t GB_read_memory(GB_gameboy_t *gb, uint16_t addr)
if (is_addr_in_dma_use(gb, addr)) {
addr = gb->dma_current_src;
}
if (GB_read_memory_callback_v) {
if (gb->read_memory_callback) {
uint8_t data = read_map[addr >> 12](gb, addr);
data = GB_read_memory_callback_v(gb, addr, data);
data = gb->read_memory_callback(gb, addr, data);
return data;
}
return read_map[addr >> 12](gb, addr);
@@ -599,7 +597,8 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
case GB_MODEL_DMG_B:
case GB_MODEL_SGB_NTSC:
case GB_MODEL_SGB_PAL:
case GB_MODEL_SGB_NO_SFC:
case GB_MODEL_SGB_NTSC_NO_SFC:
case GB_MODEL_SGB_PAL_NO_SFC:
case GB_MODEL_SGB2:
case GB_MODEL_SGB2_NO_SFC:
case GB_MODEL_CGB_E:
@@ -754,9 +753,15 @@ static void write_high_memory(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
return;
case GB_IO_JOYP:
gb->io_registers[GB_IO_JOYP] = value & 0xF0;
GB_sgb_write(gb, value);
GB_update_joyp(gb);
if (gb->joyp_write_callback) {
gb->joyp_write_callback(gb, value);
GB_update_joyp(gb);
}
else if ((gb->io_registers[GB_IO_JOYP] & 0x30) != (value & 0x30)) {
GB_sgb_write(gb, value);
gb->io_registers[GB_IO_JOYP] = value & 0xF0;
GB_update_joyp(gb);
}
return;
case GB_IO_BIOS:

View File

@@ -72,6 +72,75 @@ static inline void load_attribute_file(GB_gameboy_t *gb, unsigned file_index)
}
}
static const uint16_t built_in_palettes[] =
{
0x67BF, 0x265B, 0x10B5, 0x2866,
0x637B, 0x3AD9, 0x0956, 0x0000,
0x7F1F, 0x2A7D, 0x30F3, 0x4CE7,
0x57FF, 0x2618, 0x001F, 0x006A,
0x5B7F, 0x3F0F, 0x222D, 0x10EB,
0x7FBB, 0x2A3C, 0x0015, 0x0900,
0x2800, 0x7680, 0x01EF, 0x2FFF,
0x73BF, 0x46FF, 0x0110, 0x0066,
0x533E, 0x2638, 0x01E5, 0x0000,
0x7FFF, 0x2BBF, 0x00DF, 0x2C0A,
0x7F1F, 0x463D, 0x74CF, 0x4CA5,
0x53FF, 0x03E0, 0x00DF, 0x2800,
0x433F, 0x72D2, 0x3045, 0x0822,
0x7FFA, 0x2A5F, 0x0014, 0x0003,
0x1EED, 0x215C, 0x42FC, 0x0060,
0x7FFF, 0x5EF7, 0x39CE, 0x0000,
0x4F5F, 0x630E, 0x159F, 0x3126,
0x637B, 0x121C, 0x0140, 0x0840,
0x66BC, 0x3FFF, 0x7EE0, 0x2C84,
0x5FFE, 0x3EBC, 0x0321, 0x0000,
0x63FF, 0x36DC, 0x11F6, 0x392A,
0x65EF, 0x7DBF, 0x035F, 0x2108,
0x2B6C, 0x7FFF, 0x1CD9, 0x0007,
0x53FC, 0x1F2F, 0x0E29, 0x0061,
0x36BE, 0x7EAF, 0x681A, 0x3C00,
0x7BBE, 0x329D, 0x1DE8, 0x0423,
0x739F, 0x6A9B, 0x7293, 0x0001,
0x5FFF, 0x6732, 0x3DA9, 0x2481,
0x577F, 0x3EBC, 0x456F, 0x1880,
0x6B57, 0x6E1B, 0x5010, 0x0007,
0x0F96, 0x2C97, 0x0045, 0x3200,
0x67FF, 0x2F17, 0x2230, 0x1548,
};
static const struct {
char name[16];
unsigned palette_index;
} palette_assignments[] =
{
{"ZELDA", 5},
{"SUPER MARIOLAND", 6},
{"MARIOLAND2", 0x14},
{"SUPERMARIOLAND3", 2},
{"KIRBY DREAM LAND", 0xB},
{"HOSHINOKA-BI", 0xB},
{"KIRBY'S PINBALL", 3},
{"YOSSY NO TAMAGO", 0xC},
{"MARIO & YOSHI", 0xC},
{"YOSSY NO COOKIE", 4},
{"YOSHI'S COOKIE", 4},
{"DR.MARIO", 0x12},
{"TETRIS", 0x11},
{"YAKUMAN", 0x13},
{"METROID2", 0x1F},
{"KAERUNOTAMENI", 9},
{"GOLF", 0x18},
{"ALLEY WAY", 0x16},
{"BASEBALL", 0xF},
{"TENNIS", 0x17},
{"F1RACE", 0x1E},
{"KID ICARUS", 0xE},
{"QIX", 0x19},
{"SOLARSTRIKER", 7},
{"X", 0x1C},
{"GBWARS", 0x15},
};
static void command_ready(GB_gameboy_t *gb)
{
/* SGB header commands are used to send the contents of the header to the SNES CPU.
@@ -81,6 +150,8 @@ static void command_ready(GB_gameboy_t *gb)
0xE content bytes. The last command, FB, is padded with zeros, so information past the header is not sent. */
if ((gb->sgb->command[0] & 0xF1) == 0xF1) {
if(gb->boot_rom_finished) return;
uint8_t checksum = 0;
for (unsigned i = 2; i < 0x10; i++) {
checksum += gb->sgb->command[i];
@@ -90,14 +161,23 @@ static void command_ready(GB_gameboy_t *gb)
gb->sgb->disable_commands = true;
return;
}
if (gb->sgb->command[0] == 0xf9) {
if (gb->sgb->command[0xc] != 3) { // SGB Flag
gb->sgb->disable_commands = true;
}
unsigned index = (gb->sgb->command[0] >> 1) & 7;
if (index > 5) {
return;
}
else if (gb->sgb->command[0] == 0xfb) {
if (gb->sgb->command[0x3] != 0x33) { // Old licensee code
memcpy(&gb->sgb->received_header[index * 14], &gb->sgb->command[2], 14);
if (gb->sgb->command[0] == 0xfb) {
if (gb->sgb->received_header[0x42] != 3 || gb->sgb->received_header[0x47] != 0x33) {
gb->sgb->disable_commands = true;
for (unsigned i = 0; i < sizeof(palette_assignments) / sizeof(palette_assignments[0]); i++) {
if (memcmp(palette_assignments[i].name, &gb->sgb->received_header[0x30], sizeof(palette_assignments[i].name)) == 0) {
gb->sgb->effective_palettes[0] = built_in_palettes[palette_assignments[i].palette_index * 4 - 4];
gb->sgb->effective_palettes[1] = built_in_palettes[palette_assignments[i].palette_index * 4 + 1 - 4];
gb->sgb->effective_palettes[2] = built_in_palettes[palette_assignments[i].palette_index * 4 + 2 - 4];
gb->sgb->effective_palettes[3] = built_in_palettes[palette_assignments[i].palette_index * 4 + 3 - 4];
break;
}
}
}
}
return;
@@ -257,8 +337,15 @@ static void command_ready(GB_gameboy_t *gb)
// Not supported, but used by almost all SGB games for hot patching, so let's mute the warning for this
break;
case MLT_REQ:
gb->sgb->player_count = (uint8_t[]){1, 2, 1, 4}[gb->sgb->command[1] & 3];
gb->sgb->current_player = gb->sgb->player_count - 1;
if (gb->sgb->player_count == 1) {
gb->sgb->current_player = 0;
}
gb->sgb->player_count = (gb->sgb->command[1] & 3) + 1; /* Todo: When breaking save state comaptibility,
fix this to be 0 based. */
if (gb->sgb->player_count == 3) {
gb->sgb->current_player++;
}
gb->sgb->mlt_lock = true;
break;
case CHR_TRN:
gb->sgb->vram_transfer_countdown = 2;
@@ -298,30 +385,33 @@ static void command_ready(GB_gameboy_t *gb)
}
void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
{
if (gb->joyp_write_callback) {
gb->joyp_write_callback(gb, value);
}
{
if (!GB_is_sgb(gb)) return;
if (!GB_is_hle_sgb(gb)) {
/* Notify via callback */
return;
}
if (gb->sgb->disable_commands) return;
if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) return;
if (gb->sgb->command_write_index >= sizeof(gb->sgb->command) * 8) {
return;
}
uint16_t command_size = (gb->sgb->command[0] & 7 ?: 1) * SGB_PACKET_SIZE * 8;
if ((gb->sgb->command[0] & 0xF1) == 0xF1) {
command_size = SGB_PACKET_SIZE * 8;
}
if ((value & 0x20) == 0 && (gb->io_registers[GB_IO_JOYP] & 0x20) != 0) {
gb->sgb->mlt_lock ^= true;
}
switch ((value >> 4) & 3) {
case 3:
gb->sgb->ready_for_pulse = true;
/* TODO: This is the logic used by BGB which *should* work for most/all games, but a proper test ROM is needed */
if (gb->sgb->player_count > 1 && (gb->io_registers[GB_IO_JOYP] & 0x30) == 0x10) {
if ((gb->sgb->player_count & 1) == 0 && !gb->sgb->mlt_lock) {
gb->sgb->current_player++;
gb->sgb->current_player &= gb->sgb->player_count - 1;
gb->sgb->current_player &= 3;
gb->sgb->mlt_lock = true;
}
break;
@@ -382,22 +472,9 @@ void GB_sgb_write(GB_gameboy_t *gb, uint8_t value)
}
}
static inline uint8_t scale_channel(uint8_t x)
{
return (x << 3) | (x >> 2);
}
static uint32_t convert_rgb15(GB_gameboy_t *gb, uint16_t color)
{
uint8_t r = (color) & 0x1F;
uint8_t g = (color >> 5) & 0x1F;
uint8_t b = (color >> 10) & 0x1F;
r = scale_channel(r);
g = scale_channel(g);
b = scale_channel(b);
return gb->rgb_encode_callback(gb, r, g, b);
return GB_convert_rgb15(gb, color);
}
static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_t fade)
@@ -410,11 +487,9 @@ static uint32_t convert_rgb15_with_fade(GB_gameboy_t *gb, uint16_t color, uint8_
if (g >= 0x20) g = 0;
if (b >= 0x20) b = 0;
r = scale_channel(r);
g = scale_channel(g);
b = scale_channel(b);
color = r | (g << 5) | (b << 10);
return gb->rgb_encode_callback(gb, r, g, b);
return GB_convert_rgb15(gb, color);
}
#include <stdio.h>
@@ -679,10 +754,10 @@ void GB_sgb_load_default_data(GB_gameboy_t *gb)
/* Re-center */
memmove(&gb->sgb->border.map[25 * 32 + 1], &gb->sgb->border.map[25 * 32], (32 * 3 - 1) * sizeof(gb->sgb->border.map[0]));
}
gb->sgb->effective_palettes[0] = 0x639E;
gb->sgb->effective_palettes[1] = 0x263A;
gb->sgb->effective_palettes[2] = 0x10D4;
gb->sgb->effective_palettes[3] = 0x2866;
gb->sgb->effective_palettes[0] = built_in_palettes[0];
gb->sgb->effective_palettes[1] = built_in_palettes[1];
gb->sgb->effective_palettes[2] = built_in_palettes[2];
gb->sgb->effective_palettes[3] = built_in_palettes[3];
}
static double fm_synth(double phase)

View File

@@ -49,6 +49,12 @@ struct GB_sgb_s {
/* Intro */
int16_t intro_animation;
/* GB Header */
uint8_t received_header[0x54];
/* Multiplayer (cont) */
bool mlt_lock;
};
void GB_sgb_write(GB_gameboy_t *gb, uint8_t value);

View File

@@ -33,6 +33,7 @@ static const GB_conflict_t cgb_conflict_map[0x80] = {
/* Todo: most values not verified, and probably differ between revisions */
};
/* Todo: verify on an MGB */
static const GB_conflict_t dmg_conflict_map[0x80] = {
[GB_IO_IF] = GB_CONFLICT_WRITE_CPU,
[GB_IO_LYC] = GB_CONFLICT_READ_OLD,
@@ -40,7 +41,6 @@ static const GB_conflict_t dmg_conflict_map[0x80] = {
[GB_IO_SCY] = GB_CONFLICT_READ_NEW,
[GB_IO_STAT] = GB_CONFLICT_STAT_DMG,
/* Todo: these are GB_CONFLICT_READ_NEW on MGB/SGB2 */
[GB_IO_BGP] = GB_CONFLICT_PALETTE_DMG,
[GB_IO_OBP0] = GB_CONFLICT_PALETTE_DMG,
[GB_IO_OBP1] = GB_CONFLICT_PALETTE_DMG,
@@ -51,6 +51,24 @@ static const GB_conflict_t dmg_conflict_map[0x80] = {
[GB_IO_SCX] = GB_CONFLICT_READ_NEW,
};
/* Todo: Verify on an SGB1 */
static const GB_conflict_t sgb_conflict_map[0x80] = {
[GB_IO_IF] = GB_CONFLICT_WRITE_CPU,
[GB_IO_LYC] = GB_CONFLICT_READ_OLD,
[GB_IO_LCDC] = GB_CONFLICT_READ_NEW,
[GB_IO_SCY] = GB_CONFLICT_READ_NEW,
[GB_IO_STAT] = GB_CONFLICT_STAT_DMG,
[GB_IO_BGP] = GB_CONFLICT_READ_NEW,
[GB_IO_OBP0] = GB_CONFLICT_READ_NEW,
[GB_IO_OBP1] = GB_CONFLICT_READ_NEW,
/* Todo: these were not verified at all */
[GB_IO_WY] = GB_CONFLICT_READ_NEW,
[GB_IO_WX] = GB_CONFLICT_READ_NEW,
[GB_IO_SCX] = GB_CONFLICT_READ_NEW,
};
static uint8_t cycle_read(GB_gameboy_t *gb, uint16_t addr)
{
if (gb->pending_cycles) {
@@ -92,7 +110,17 @@ static void cycle_write(GB_gameboy_t *gb, uint16_t addr, uint8_t value)
assert(gb->pending_cycles);
GB_conflict_t conflict = GB_CONFLICT_READ_OLD;
if ((addr & 0xFF80) == 0xFF00) {
conflict = (GB_is_cgb(gb)? cgb_conflict_map : dmg_conflict_map)[addr & 0x7F];
const GB_conflict_t *map = NULL;
if (GB_is_cgb(gb)) {
map = cgb_conflict_map;
}
else if (GB_is_sgb(gb)) {
map = sgb_conflict_map;
}
else {
map = dmg_conflict_map;
}
conflict = map[addr & 0x7F];
}
switch (conflict) {
case GB_CONFLICT_READ_OLD:

5
bsnes/gb/README Normal file
View File

@@ -0,0 +1,5 @@
Core: 2019-10-22 master snapshot
* has issues with SGB2 audio desynchronization
Core-new: 2019-11-05 master snapshot
* Game Boy inputs are not working

View File

@@ -169,7 +169,7 @@ auto SuperFamicom::region() const -> string {
if(D == 'P') region = {"SNSP-", code, "-EUR"};
if(D == 'S') region = {"SNSP-", code, "-ESP"};
if(D == 'U') region = {"SNSP-", code, "-AUS"};
if(D == 'W') region = {"SNSP-", code, "-SCN"};
if(D == 'X') region = {"SNSP-", code, "-SCN"};
}
if(!region) {
@@ -187,6 +187,7 @@ auto SuperFamicom::region() const -> string {
if(E == 0x0f) region = {"CAN"};
if(E == 0x10) region = {"BRA"};
if(E == 0x11) region = {"AUS"};
if(E == 0x12) region = {"SCN"};
}
return region ? region : "NTSC";
@@ -194,7 +195,13 @@ auto SuperFamicom::region() const -> string {
auto SuperFamicom::videoRegion() const -> string {
auto region = data[headerAddress + 0x29];
return (region <= 0x01 || region >= 0x12) ? "NTSC" : "PAL";
if(region == 0x00) return "NTSC"; //JPN
if(region == 0x01) return "NTSC"; //USA
if(region == 0x0b) return "NTSC"; //ROC
if(region == 0x0d) return "NTSC"; //KOR
if(region == 0x0f) return "NTSC"; //CAN
if(region == 0x10) return "NTSC"; //BRA
return "PAL";
}
auto SuperFamicom::revision() const -> string {
@@ -223,7 +230,7 @@ auto SuperFamicom::revision() const -> string {
if(D == 'P') revision = {"SNSP-", code, "-", F};
if(D == 'S') revision = {"SNSP-", code, "-", F};
if(D == 'U') revision = {"SNSP-", code, "-", F};
if(D == 'W') revision = {"SNSP-", code, "-", F};
if(D == 'X') revision = {"SNSP-", code, "-", F};
}
if(!revision) {
@@ -259,6 +266,9 @@ auto SuperFamicom::board() const -> string {
if(headerAddress == 0x40ffb0) mode = "EXHIROM-";
}
//this game's title ovewrites the map mode with '!' (0x21), but is a LOROM game
if(title() == "YUYU NO QUIZ DE GO!GO") mode = "LOROM-";
if(mode == "LOROM-" && headerAddress == 0x407fb0) mode = "EXLOROM-";
bool epsonRTC = false;
@@ -454,15 +464,17 @@ auto SuperFamicom::firmwareRomSize() const -> uint {
}
auto SuperFamicom::ramSize() const -> uint {
auto ramSize = data[headerAddress + 0x28] & 7;
if(ramSize) return 1024 << ramSize;
auto ramSize = data[headerAddress + 0x28] & 15;
if(ramSize > 8) ramSize = 8;
if(ramSize > 0) return 1024 << ramSize;
return 0;
}
auto SuperFamicom::expansionRamSize() const -> uint {
if(data[headerAddress + 0x2a] == 0x33) {
auto ramSize = data[headerAddress + 0x0d] & 7;
if(ramSize) return 1024 << ramSize;
auto ramSize = data[headerAddress + 0x0d] & 15;
if(ramSize > 8) ramSize = 8;
if(ramSize > 0) return 1024 << ramSize;
}
if((data[headerAddress + 0x26] >> 4) == 1) {
//GSU: Starfox / Starwing lacks an extended header; but still has expansion RAM

View File

@@ -84,8 +84,9 @@ auto HG51B::cache() -> bool {
io.cache.address[io.cache.page] = address;
for(uint offset : range(256)) {
step(wait(address)); programRAM[io.cache.page][offset] = read(address++) << 0;
step(wait(address)); programRAM[io.cache.page][offset] |= read(address++) << 8;
step(wait(address));
programRAM[io.cache.page][offset] = read(address++) << 0;
programRAM[io.cache.page][offset] |= read(address++) << 8;
}
return io.cache.enable = 0, true;
}

View File

@@ -250,7 +250,7 @@ auto SPC700::instructionDirectDirectCompare(fpb op) -> void {
uint8 target = fetch();
uint8 lhs = load(target);
lhs = alu(lhs, rhs);
load(target);
idle();
}
auto SPC700::instructionDirectDirectModify(fpb op) -> void {
@@ -274,7 +274,7 @@ auto SPC700::instructionDirectImmediateCompare(fpb op) -> void {
uint8 address = fetch();
uint8 data = load(address);
data = alu(data, immediate);
load(address);
idle();
}
auto SPC700::instructionDirectImmediateModify(fpb op) -> void {
@@ -469,7 +469,7 @@ auto SPC700::instructionIndirectXCompareIndirectY(fpb op) -> void {
uint8 rhs = load(Y);
uint8 lhs = load(X);
lhs = alu(lhs, rhs);
load(X);
idle();
}
auto SPC700::instructionIndirectXWriteIndirectY(fpb op) -> void {

View File

@@ -295,9 +295,9 @@ auto WDC65816::disassemble(uint24 address, bool e, bool m, bool x) -> string {
op(0x71, "adc", indirectIndexedY)
op(0x72, "adc", indirect)
op(0x73, "adc", stackIndirect)
op(0x74, "stz", absoluteX)
op(0x75, "adc", absoluteX)
op(0x76, "ror", absoluteX)
op(0x74, "stz", directX)
op(0x75, "adc", directX)
op(0x76, "ror", directX)
op(0x77, "adc", indirectLongY)
op(0x78, "sei", implied)
op(0x79, "adc", absoluteY)

View File

@@ -530,7 +530,7 @@ auto Cartridge::loaduPD7725(Markup::Node node) -> void {
if(failed) {
//throw an error to the user
platform->open(ID::SuperFamicom, "<DSP1-4>", File::Read, File::Required);
platform->open(ID::SuperFamicom, "DSP3", File::Read, File::Required);
return;
}
@@ -579,7 +579,7 @@ auto Cartridge::loaduPD96050(Markup::Node node) -> void {
if(auto file = game.memory(memory)) {
if(auto fp = platform->open(ID::SuperFamicom, file->name(), File::Read)) {
for(auto n : range(2048)) necdsp.dataROM[n] = fp->readl(2);
} else failed = false;
} else failed = true;
}
}
@@ -598,7 +598,7 @@ auto Cartridge::loaduPD96050(Markup::Node node) -> void {
if(failed) {
//throw an error to the user
platform->open(ID::SuperFamicom, "<ST010-011>", File::Read, File::Required);
platform->open(ID::SuperFamicom, "ST011", File::Read, File::Required);
return;
}

View File

@@ -7,12 +7,15 @@ namespace SuperFamicom {
ArmDSP armdsp;
auto ArmDSP::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto ArmDSP::Enter() -> void {
armdsp.boot();
while(true) scheduler.synchronize(), armdsp.main();
while(true) {
scheduler.synchronize();
armdsp.main();
}
}
auto ArmDSP::boot() -> void {

View File

@@ -8,11 +8,14 @@ namespace SuperFamicom {
EpsonRTC epsonrtc;
auto EpsonRTC::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto EpsonRTC::Enter() -> void {
while(true) scheduler.synchronize(), epsonrtc.main();
while(true) {
scheduler.synchronize();
epsonrtc.main();
}
}
auto EpsonRTC::main() -> void {

View File

@@ -6,11 +6,14 @@ namespace SuperFamicom {
Event event;
auto Event::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto Event::Enter() -> void {
while(true) scheduler.synchronize(), event.main();
while(true) {
scheduler.synchronize();
event.main();
}
}
auto Event::main() -> void {

View File

@@ -9,11 +9,14 @@ namespace SuperFamicom {
HitachiDSP hitachidsp;
auto HitachiDSP::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto HitachiDSP::Enter() -> void {
while(true) scheduler.synchronize(), hitachidsp.main();
while(true) {
scheduler.synchronize();
hitachidsp.main();
}
}
auto HitachiDSP::step(uint clocks) -> void {

View File

@@ -47,7 +47,7 @@ namespace SameBoy {
}
auto ICD::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto ICD::Enter() -> void {
@@ -62,7 +62,7 @@ auto ICD::main() -> void {
auto clocks = GB_run(&sameboy);
step(clocks >> 1);
} else { //DMG halted
stream->sample(float(0.0), float(0.0));
apuWrite(0.0, 0.0);
step(128);
}
synchronizeCPU();
@@ -72,20 +72,24 @@ auto ICD::step(uint clocks) -> void {
clock += clocks * (uint64_t)cpu.frequency;
}
//SGB1 uses the CPU oscillator (~2.4% faster than a real Game Boy)
//SGB2 uses a dedicated oscillator (same speed as a real Game Boy)
auto ICD::clockFrequency() const -> uint {
return Frequency ? Frequency : system.cpuFrequency();
}
auto ICD::load() -> bool {
information = {};
//todo: connect to SFC random enable setting
//GB_random_set_enabled(false);
GB_random_set_enabled(configuration.hacks.entropy != "None");
if(Frequency == 0) {
GB_init(&sameboy, GB_MODEL_SGB_NO_SFC);
GB_load_boot_rom_from_buffer(&sameboy, (const unsigned char*)&SGB1BootROM[0], 256);
GB_set_sample_rate(&sameboy, uint(system.cpuFrequency() / 5.0 / 128.0));
} else {
GB_init(&sameboy, GB_MODEL_SGB2_NO_SFC);
GB_load_boot_rom_from_buffer(&sameboy, (const unsigned char*)&SGB2BootROM[0], 256);
GB_set_sample_rate(&sameboy, uint(Frequency / 5.0 / 128.0));
}
GB_set_sample_rate_by_clocks(&sameboy, 256);
GB_set_highpass_filter_mode(&sameboy, GB_HIGHPASS_ACCURATE);
GB_set_icd_hreset_callback(&sameboy, &SameBoy::hreset);
GB_set_icd_vreset_callback(&sameboy, &SameBoy::vreset);
@@ -138,11 +142,9 @@ auto ICD::unload() -> void {
}
auto ICD::power(bool reset) -> void {
//SGB1 uses CPU oscillator; SGB2 uses dedicated oscillator
create(ICD::Enter, (Frequency ? Frequency : system.cpuFrequency()) / 5.0);
if(!reset) {
stream = Emulator::audio.createStream(2, uint((Frequency ? Frequency : system.cpuFrequency()) / 5.0 / 128.0));
}
auto frequency = clockFrequency() / 5;
create(ICD::Enter, frequency);
if(!reset) stream = Emulator::audio.createStream(2, frequency / 128);
for(auto& packet : this->packet) packet = {};
packetSize = 0;

View File

@@ -8,6 +8,7 @@ struct ICD : Emulator::Platform, Thread {
static auto Enter() -> void;
auto main() -> void;
auto step(uint clocks) -> void;
auto clockFrequency() const -> uint;
auto load() -> bool;
auto save() -> void;

View File

@@ -21,7 +21,7 @@ auto ICD::ppuWrite(uint2 color) -> void {
auto ICD::apuWrite(float left, float right) -> void {
float samples[] = {left, right};
stream->write(samples);
if(!system.runAhead) stream->write(samples);
}
auto ICD::joypWrite(bool p14, bool p15) -> void {

View File

@@ -55,13 +55,14 @@ auto ICD::writeIO(uint addr, uint8 data) -> void {
if((r6003 & 0x80) == 0x00 && (data & 0x80) == 0x80) {
power(true); //soft reset
}
auto frequency = system.cpuFrequency();
auto frequency = clockFrequency();
switch(data & 3) {
case 0: this->frequency = frequency / 4; break; //fast (glitchy, even on real hardware)
case 1: this->frequency = frequency / 5; break; //normal
case 2: this->frequency = frequency / 7; break; //slow
case 3: this->frequency = frequency / 9; break; //very slow
}
stream->setFrequency(this->frequency / 128);
r6003 = data;
return;
}

View File

@@ -7,12 +7,15 @@ MSU1 msu1;
#include "serialization.cpp"
auto MSU1::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto MSU1::Enter() -> void {
while(true) scheduler.synchronize(), msu1.main();
while(true) {
scheduler.synchronize();
msu1.main();
}
}
auto MSU1::main() -> void {
@@ -39,7 +42,7 @@ auto MSU1::main() -> void {
}
}
stream->sample(float(left), float(right));
if(!system.runAhead) stream->sample(float(left), float(right));
step(1);
synchronizeCPU();
}

View File

@@ -7,11 +7,14 @@ namespace SuperFamicom {
NECDSP necdsp;
auto NECDSP::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto NECDSP::Enter() -> void {
while(true) scheduler.synchronize(), necdsp.main();
while(true) {
scheduler.synchronize();
necdsp.main();
}
}
auto NECDSP::main() -> void {

View File

@@ -12,11 +12,14 @@ namespace SuperFamicom {
SA1 sa1;
auto SA1::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto SA1::Enter() -> void {
while(true) scheduler.synchronize(), sa1.main();
while(true) {
scheduler.synchronize();
sa1.main();
}
}
auto SA1::main() -> void {

View File

@@ -1,7 +1,7 @@
//Super Accelerator (SA-1)
struct SA1 : Processor::WDC65816, Thread {
inline auto synchronizing() const -> bool override { return scheduler.mode == Scheduler::Mode::SynchronizeAll; }
inline auto synchronizing() const -> bool override { return scheduler.synchronizing(); }
//sa1.cpp
auto synchronizeCPU() -> void;

View File

@@ -3,6 +3,7 @@ struct Decompressor {
IM(SDD1::Decompressor& self) : self(self) {}
auto init(uint offset) -> void;
auto getCodeWord(uint8 codeLength) -> uint8;
auto serialize(serializer&) -> void;
private:
Decompressor& self;
@@ -13,6 +14,7 @@ struct Decompressor {
struct GCD { //golomb-code decoder
GCD(SDD1::Decompressor& self) : self(self) {}
auto getRunCount(uint8 codeNumber, uint8& mpsCount, bool& lpsIndex) -> void;
auto serialize(serializer&) -> void;
private:
Decompressor& self;
@@ -23,6 +25,7 @@ struct Decompressor {
BG(SDD1::Decompressor& self, uint8 codeNumber) : self(self), codeNumber(codeNumber) {}
auto init() -> void;
auto getBit(bool& endOfRun) -> uint8;
auto serialize(serializer&) -> void;
private:
Decompressor& self;
@@ -35,6 +38,7 @@ struct Decompressor {
PEM(SDD1::Decompressor& self) : self(self) {}
auto init() -> void;
auto getBit(uint8 context) -> uint8;
auto serialize(serializer&) -> void;
private:
Decompressor& self;
@@ -54,6 +58,7 @@ struct Decompressor {
CM(SDD1::Decompressor& self) : self(self) {}
auto init(uint offset) -> void;
auto getBit() -> uint8;
auto serialize(serializer&) -> void;
private:
Decompressor& self;
@@ -68,6 +73,7 @@ struct Decompressor {
OL(SDD1::Decompressor& self) : self(self) {}
auto init(uint offset) -> void;
auto decompress() -> uint8;
auto serialize(serializer&) -> void;
private:
Decompressor& self;
@@ -78,6 +84,7 @@ struct Decompressor {
Decompressor();
auto init(uint offset) -> void;
auto read() -> uint8;
auto serialize(serializer&) -> void;
IM im;
GCD gcd;

View File

@@ -11,4 +11,56 @@ auto SDD1::serialize(serializer& s) -> void {
s.integer(dma[n].size);
}
s.integer(dmaReady);
decompressor.serialize(s);
}
auto SDD1::Decompressor::serialize(serializer& s) -> void {
im.serialize(s);
gcd.serialize(s);
bg0.serialize(s);
bg1.serialize(s);
bg2.serialize(s);
bg3.serialize(s);
bg4.serialize(s);
bg5.serialize(s);
bg6.serialize(s);
bg7.serialize(s);
cm.serialize(s);
ol.serialize(s);
}
auto SDD1::Decompressor::IM::serialize(serializer& s) -> void {
s.integer(offset);
s.integer(bitCount);
}
auto SDD1::Decompressor::GCD::serialize(serializer& s) -> void {
}
auto SDD1::Decompressor::BG::serialize(serializer& s) -> void {
s.integer(mpsCount);
s.integer(lpsIndex);
}
auto SDD1::Decompressor::PEM::serialize(serializer& s) -> void {
for(auto& info : contextInfo) {
s.integer(info.status);
s.integer(info.mps);
}
}
auto SDD1::Decompressor::CM::serialize(serializer& s) -> void {
s.integer(bitplanesInfo);
s.integer(contextBitsInfo);
s.integer(bitNumber);
s.integer(currentBitplane);
s.array(previousBitplaneBits);
}
auto SDD1::Decompressor::OL::serialize(serializer& s) -> void {
s.integer(bitplanesInfo);
s.integer(r0);
s.integer(r1);
s.integer(r2);
}

View File

@@ -8,11 +8,14 @@ namespace SuperFamicom {
SharpRTC sharprtc;
auto SharpRTC::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto SharpRTC::Enter() -> void {
while(true) scheduler.synchronize(), sharprtc.main();
while(true) {
scheduler.synchronize();
sharprtc.main();
}
}
auto SharpRTC::main() -> void {

View File

@@ -17,11 +17,14 @@ SPC7110::~SPC7110() {
}
auto SPC7110::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto SPC7110::Enter() -> void {
while(true) scheduler.synchronize(), spc7110.main();
while(true) {
scheduler.synchronize();
spc7110.main();
}
}
auto SPC7110::main() -> void {

View File

@@ -12,11 +12,14 @@ namespace SuperFamicom {
SuperFX superfx;
auto SuperFX::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto SuperFX::Enter() -> void {
while(true) scheduler.synchronize(), superfx.main();
while(true) {
scheduler.synchronize();
superfx.main();
}
}
auto SuperFX::main() -> void {

View File

@@ -2,7 +2,7 @@ struct SuperFX : Processor::GSU, Thread {
ReadableMemory rom;
WritableMemory ram;
inline auto synchronizing() const -> bool { return scheduler.mode == Scheduler::Mode::SynchronizeAll; }
inline auto synchronizing() const -> bool { return scheduler.synchronizing(); }
//superfx.cpp
auto synchronizeCPU() -> void;

View File

@@ -11,24 +11,22 @@ CPU cpu;
#include "serialization.cpp"
auto CPU::synchronizeSMP() -> void {
if(smp.clock < 0) co_switch(smp.thread);
if(smp.clock < 0) scheduler.resume(smp.thread);
}
auto CPU::synchronizePPU() -> void {
if(ppu.clock < 0) co_switch(ppu.thread);
if(ppu.clock < 0) scheduler.resume(ppu.thread);
}
auto CPU::synchronizeCoprocessors() -> void {
for(auto coprocessor : coprocessors) {
if(coprocessor->clock < 0) co_switch(coprocessor->thread);
if(coprocessor->clock < 0) scheduler.resume(coprocessor->thread);
}
}
auto CPU::Enter() -> void {
while(true) {
if(scheduler.mode == Scheduler::Mode::SynchronizeCPU) {
scheduler.leave(Scheduler::Event::Synchronize);
}
scheduler.synchronize();
cpu.main();
}
}
@@ -69,7 +67,7 @@ auto CPU::load() -> bool {
auto CPU::power(bool reset) -> void {
WDC65816::power();
create(Enter, system.cpuFrequency());
Thread::create(Enter, system.cpuFrequency());
coprocessors.reset();
PPUcounter::reset();
PPUcounter::scanline = {&CPU::scanline, this};

View File

@@ -2,7 +2,7 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
inline auto interruptPending() const -> bool override { return status.interruptPending; }
inline auto pio() const -> uint8 { return io.pio; }
inline auto refresh() const -> bool { return status.dramRefresh == 1; }
inline auto synchronizing() const -> bool override { return scheduler.mode == Scheduler::Mode::SynchronizeCPU; }
inline auto synchronizing() const -> bool override { return scheduler.synchronizing(); }
//cpu.cpp
auto synchronizeSMP() -> void;
@@ -52,7 +52,8 @@ struct CPU : Processor::WDC65816, Thread, PPUcounter {
alwaysinline auto dmaEdge() -> void;
//irq.cpp
alwaysinline auto pollInterrupts() -> void;
alwaysinline auto nmiPoll() -> void;
alwaysinline auto irqPoll() -> void;
auto nmitimenUpdate(uint8 data) -> void;
auto rdnmi() -> bool;
auto timeup() -> bool;

View File

@@ -191,20 +191,24 @@ auto CPU::writeCPU(uint addr, uint8 data) -> void {
io.htime = (io.htime >> 2) - 1;
io.htime = io.htime & 0x100 | data << 0;
io.htime = (io.htime + 1) << 2;
irqPoll(); //unverified
return;
case 0x4208: //HTIMEH
io.htime = (io.htime >> 2) - 1;
io.htime = io.htime & 0x0ff | (data & 1) << 8;
io.htime = (io.htime + 1) << 2;
irqPoll(); //unverified
return;
case 0x4209: //VTIMEL
io.vtime = io.vtime & 0x100 | data << 0;
irqPoll(); //unverified
return;
case 0x420a: //VTIMEH
io.vtime = io.vtime & 0x0ff | (data & 1) << 8;
irqPoll(); //unverified
return;
case 0x420b: //DMAEN

View File

@@ -1,9 +1,10 @@
//called once every four clock cycles;
//nmiPoll() and irqPoll() are called once every four clock cycles;
//as NMI steps by scanlines (divisible by 4) and IRQ by PPU 4-cycle dots.
//
//ppu.(vh)counter(n) returns the value of said counters n-clocks before current time;
//it is used to emulate hardware communication delay between opcode and interrupt units.
auto CPU::pollInterrupts() -> void {
auto CPU::nmiPoll() -> void {
//NMI hold
if(status.nmiHold.lower() && io.nmiEnable) {
status.nmiTransition = 1;
@@ -13,7 +14,9 @@ auto CPU::pollInterrupts() -> void {
if(status.nmiValid.flip(vcounter(2) >= ppu.vdisp())) {
if(status.nmiLine = status.nmiValid) status.nmiHold = 1; //hold /NMI for four cycles
}
}
auto CPU::irqPoll() -> void {
//IRQ hold
status.irqHold = 0;
if(status.irqLine && io.irqEnable) {

View File

@@ -11,7 +11,7 @@ auto CPU::joypadCounter() const -> uint {
auto CPU::stepOnce() -> void {
counter.cpu += 2;
tick();
if(hcounter() & 2) pollInterrupts();
if(hcounter() & 2) nmiPoll(), irqPoll();
if(joypadCounter() == 0) joypadEdge();
}
@@ -20,6 +20,7 @@ auto CPU::step() -> void {
static_assert(Clocks == 2 || Clocks == 4 || Clocks == 6 || Clocks == 8 || Clocks == 10 || Clocks == 12);
for(auto coprocessor : coprocessors) {
if(coprocessor == &icd || coprocessor == &msu1) continue;
coprocessor->clock -= Clocks * (uint64)coprocessor->frequency;
}
@@ -43,6 +44,10 @@ auto CPU::step() -> void {
smp.clock -= Clocks * (uint64)smp.frequency;
ppu.clock -= Clocks;
for(auto coprocessor : coprocessors) {
if(coprocessor != &icd && coprocessor != &msu1) continue;
coprocessor->clock -= Clocks * (uint64)coprocessor->frequency;
}
if(!status.dramRefresh && hcounter() >= status.dramRefreshPosition) {
//note: pattern should technically be 5-3, 5-3, 5-3, 5-3, 5-3 per logic analyzer
@@ -54,6 +59,23 @@ auto CPU::step() -> void {
status.dramRefresh = 1; step<6,0>(); status.dramRefresh = 2; step<2,0>(); aluEdge();
}
if(!status.hdmaSetupTriggered && hcounter() >= status.hdmaSetupPosition) {
status.hdmaSetupTriggered = true;
hdmaReset();
if(hdmaEnable()) {
status.hdmaPending = true;
status.hdmaMode = 0;
}
}
if(!status.hdmaTriggered && hcounter() >= status.hdmaPosition) {
status.hdmaTriggered = true;
if(hdmaActive()) {
status.hdmaPending = true;
status.hdmaMode = 1;
}
}
if constexpr(Synchronize) {
if(configuration.hacks.coprocessor.delayedSync) return;
synchronizeCoprocessors();
@@ -106,6 +128,15 @@ auto CPU::scanline() -> void {
overclocking.target = clocks * overclock - clocks;
}
}
//handle video frame events from the CPU core to prevent a race condition between
//games polling inputs during NMI and the PPU thread not being 100% synchronized.
if(vcounter() == ppu.vdisp()) {
if(auto device = controllerPort2.device) device->latch(); //light guns
synchronizePPU();
if(system.fastPPU()) PPUfast::Line::flush();
scheduler.leave(Scheduler::Event::Frame);
}
}
auto CPU::aluEdge() -> void {
@@ -162,23 +193,6 @@ auto CPU::dmaEdge() -> void {
}
}
if(!status.hdmaSetupTriggered && hcounter() >= status.hdmaSetupPosition) {
status.hdmaSetupTriggered = true;
hdmaReset();
if(hdmaEnable()) {
status.hdmaPending = true;
status.hdmaMode = 0;
}
}
if(!status.hdmaTriggered && hcounter() >= status.hdmaPosition) {
status.hdmaTriggered = true;
if(hdmaActive()) {
status.hdmaPending = true;
status.hdmaMode = 1;
}
}
if(!status.dmaActive) {
if(status.dmaPending || status.hdmaPending) {
status.dmaActive = true;
@@ -188,34 +202,73 @@ auto CPU::dmaEdge() -> void {
//called every 256 clocks; see CPU::step()
auto CPU::joypadEdge() -> void {
if(vcounter() >= ppu.vdisp()) {
//cache enable state at first iteration
if(status.autoJoypadCounter == 0) status.autoJoypadLatch = io.autoJoypadPoll;
status.autoJoypadActive = status.autoJoypadCounter <= 15;
//fast joypad polling is a hack to work around edge cases not currently emulated in auto-joypad polling.
//below is a list of games that have had input issues over the years.
//Nuke (PD): inputs do not work
//Super Conflict: sends random inputs even with no buttons pressed
//Super Star Wars: Start button auto-unpauses
//Taikyoku Igo - Goliath: start button not acknowledged
//Tatakae Genshijin 2: attract sequence ends early
//Williams Arcade's Greatest Hits: inputs fire on their own; or menu items sometimes skipped
//World Masters Golf: inputs fail to register; or holding D-pad should only move the cursor once, not continuously
if(status.autoJoypadActive && status.autoJoypadLatch) {
if(status.autoJoypadCounter == 0) {
controllerPort1.device->latch(1);
controllerPort2.device->latch(1);
controllerPort1.device->latch(0);
controllerPort2.device->latch(0);
if(configuration.hacks.cpu.fastJoypadPolling) {
//Taikyoku Igo - Goliath
//Williams Arcade's Greatest Hits
//World Masters Golf
if(!status.autoJoypadCounter && vcounter() >= ppu.vdisp()) {
controllerPort1.device->latch(1);
controllerPort2.device->latch(1);
controllerPort1.device->latch(0);
controllerPort2.device->latch(0);
//shift registers are cleared at start of auto joypad polling
io.joy1 = 0;
io.joy2 = 0;
io.joy3 = 0;
io.joy4 = 0;
io.joy1 = 0;
io.joy2 = 0;
io.joy3 = 0;
io.joy4 = 0;
for(uint index : range(16)) {
uint2 port0 = controllerPort1.device->data();
uint2 port1 = controllerPort2.device->data();
io.joy1 = io.joy1 << 1 | port0.bit(0);
io.joy2 = io.joy2 << 1 | port1.bit(0);
io.joy3 = io.joy3 << 1 | port0.bit(1);
io.joy4 = io.joy4 << 1 | port1.bit(1);
}
uint2 port0 = controllerPort1.device->data();
uint2 port1 = controllerPort2.device->data();
io.joy1 = io.joy1 << 1 | port0.bit(0);
io.joy2 = io.joy2 << 1 | port1.bit(0);
io.joy3 = io.joy3 << 1 | port0.bit(1);
io.joy4 = io.joy4 << 1 | port1.bit(1);
status.autoJoypadCounter = 16;
}
} else {
if(vcounter() >= ppu.vdisp()) {
//cache enable state at first iteration
if(status.autoJoypadCounter == 0) status.autoJoypadLatch = io.autoJoypadPoll;
status.autoJoypadActive = status.autoJoypadCounter <= 15;
status.autoJoypadCounter++;
if(status.autoJoypadActive && status.autoJoypadLatch) {
if(status.autoJoypadCounter == 0) {
controllerPort1.device->latch(1);
controllerPort2.device->latch(1);
controllerPort1.device->latch(0);
controllerPort2.device->latch(0);
//shift registers are cleared at start of auto joypad polling
io.joy1 = 0;
io.joy2 = 0;
io.joy3 = 0;
io.joy4 = 0;
}
uint2 port0 = controllerPort1.device->data();
uint2 port1 = controllerPort2.device->data();
io.joy1 = io.joy1 << 1 | port0.bit(0);
io.joy2 = io.joy2 << 1 | port1.bit(0);
io.joy3 = io.joy3 << 1 | port0.bit(1);
io.joy4 = io.joy4 << 1 | port1.bit(1);
}
status.autoJoypadCounter++;
}
}
}

View File

@@ -18,8 +18,11 @@ auto DSP::main() -> void {
int count = spc_dsp.sample_count();
if(count > 0) {
if(!system.runAhead)
for(uint n = 0; n < count; n += 2) {
stream->sample(samplebuffer[n + 0] / 32768.0f, samplebuffer[n + 1] / 32768.0f);
float left = samplebuffer[n + 0] / 32768.0f;
float right = samplebuffer[n + 1] / 32768.0f;
stream->sample(left, right);
}
spc_dsp.set_output(samplebuffer, 8192);
}
@@ -60,6 +63,14 @@ auto DSP::power(bool reset) -> void {
spc_dsp.soft_reset();
spc_dsp.set_output(samplebuffer, 8192);
}
if(configuration.hacks.hotfixes) {
//Magical Drop (Japan) does not initialize the DSP registers at startup:
//tokoton mode will hang forever in some instances even on real hardware.
if(cartridge.headerTitle() == "MAGICAL DROP") {
for(uint address : range(0x80)) spc_dsp.write(address, 0xff);
}
}
}
auto DSP::mute() -> bool {

View File

@@ -12,6 +12,7 @@ auto Configuration::process(Markup::Node document, bool load) -> void {
bind(natural, "System/PPU1/Version", system.ppu1.version);
bind(natural, "System/PPU1/VRAM/Size", system.ppu1.vram.size);
bind(natural, "System/PPU2/Version", system.ppu2.version);
bind(text, "System/Serialization/Method", system.serialization.method);
bind(boolean, "Video/BlurEmulation", video.blurEmulation);
bind(boolean, "Video/ColorEmulation", video.colorEmulation);
@@ -20,6 +21,7 @@ auto Configuration::process(Markup::Node document, bool load) -> void {
bind(text, "Hacks/Entropy", hacks.entropy);
bind(natural, "Hacks/CPU/Overclock", hacks.cpu.overclock);
bind(boolean, "Hacks/CPU/FastMath", hacks.cpu.fastMath);
bind(boolean, "Hacks/CPU/FastJoypadPolling", hacks.cpu.fastJoypadPolling);
bind(boolean, "Hacks/PPU/Fast", hacks.ppu.fast);
bind(boolean, "Hacks/PPU/Deinterlace", hacks.ppu.deinterlace);
bind(natural, "Hacks/PPU/RenderCycle", hacks.ppu.renderCycle);

View File

@@ -17,6 +17,9 @@ struct Configuration {
struct PPU2 {
uint version = 3;
} ppu2;
struct Serialization {
string method = "Fast";
} serialization;
} system;
struct Video {
@@ -30,6 +33,7 @@ struct Configuration {
struct CPU {
uint overclock = 100;
bool fastMath = false;
bool fastJoypadPolling = false;
} cpu;
struct PPU {
bool fast = true;

View File

@@ -239,9 +239,8 @@ auto Interface::synchronize(uint64 timestamp) -> void {
if(cartridge.has.SharpRTC) sharprtc.synchronize(timestamp);
}
auto Interface::serialize() -> serializer {
system.runToSave();
return system.serialize();
auto Interface::serialize(bool synchronize) -> serializer {
return system.serialize(synchronize);
}
auto Interface::unserialize(serializer& s) -> bool {
@@ -331,7 +330,15 @@ auto Interface::frameSkip() -> uint {
auto Interface::setFrameSkip(uint frameSkip) -> void {
system.frameSkip = frameSkip;
system.frameCounter = 0;
system.frameCounter = frameSkip;
}
auto Interface::runAhead() -> bool {
return system.runAhead;
}
auto Interface::setRunAhead(bool runAhead) -> void {
system.runAhead = runAhead;
}
}

View File

@@ -58,7 +58,7 @@ struct Interface : Emulator::Interface {
auto rtc() -> bool override;
auto synchronize(uint64 timestamp) -> void override;
auto serialize() -> serializer override;
auto serialize(bool synchronize = true) -> serializer override;
auto unserialize(serializer&) -> bool override;
auto read(uint24 address) -> uint8 override;
@@ -71,6 +71,9 @@ struct Interface : Emulator::Interface {
auto frameSkip() -> uint override;
auto setFrameSkip(uint frameSkip) -> void override;
auto runAhead() -> bool override;
auto setRunAhead(bool runAhead) -> void override;
};
#include "configuration.hpp"

View File

@@ -61,6 +61,7 @@ auto Bus::map(
}
uint offset = reduce(bank << 16 | addr, mask);
if(size) base = mirror(base, size);
if(size) offset = base + mirror(offset, size - base);
lookup[bank << 16 | addr] = id;
target[bank << 16 | addr] = offset;

View File

@@ -6,9 +6,13 @@ struct ProtectableMemory : Memory {
}
inline auto allocate(uint size, uint8 fill = 0xff) -> void override {
delete[] self.data;
self.data = new uint8[self.size = size];
for(uint address : range(size)) self.data[address] = fill;
if(self.size != size) {
delete[] self.data;
self.data = new uint8[self.size = size];
}
for(uint address : range(size)) {
self.data[address] = fill;
}
}
inline auto data() -> uint8* override {

View File

@@ -6,9 +6,13 @@ struct ReadableMemory : Memory {
}
inline auto allocate(uint size, uint8 fill = 0xff) -> void override {
delete[] self.data;
self.data = new uint8[self.size = size];
for(uint address : range(size)) self.data[address] = fill;
if(self.size != size) {
delete[] self.data;
self.data = new uint8[self.size = size];
}
for(uint address : range(size)) {
self.data[address] = fill;
}
}
inline auto data() -> uint8* override {

View File

@@ -6,9 +6,13 @@ struct WritableMemory : Memory {
}
inline auto allocate(uint size, uint8 fill = 0xff) -> void override {
delete[] self.data;
self.data = new uint8[self.size = size];
for(uint address : range(size)) self.data[address] = fill;
if(self.size != size) {
delete[] self.data;
self.data = new uint8[self.size = size];
}
for(uint address : range(size)) {
self.data[address] = fill;
}
}
inline auto data() -> uint8* override {

View File

@@ -10,7 +10,7 @@ auto PPU::Line::cacheBackground(PPU::IO::Background& bg) -> void {
}
//parallelized
auto PPU::Line::renderBackground(PPU::IO::Background& self, uint source) -> void {
auto PPU::Line::renderBackground(PPU::IO::Background& self, uint8 source) -> void {
if(!self.aboveEnable && !self.belowEnable) return;
if(self.tileMode == TileMode::Mode7) return renderMode7(self, source);
if(self.tileMode == TileMode::Inactive) return;
@@ -23,6 +23,7 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint source) -> void
bool hires = io.bgMode == 5 || io.bgMode == 6;
bool offsetPerTileMode = io.bgMode == 2 || io.bgMode == 4 || io.bgMode == 6;
bool directColorMode = io.col.directColor && source == Source::BG1 && (io.bgMode == 3 || io.bgMode == 4);
uint colorShift = 3 + self.tileMode;
int width = 256 << hires;
uint tileHeight = 3 + self.tileSize;
@@ -46,8 +47,8 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint source) -> void
uint mosaicCounter = 1;
uint mosaicPalette = 0;
uint mosaicPriority = 0;
uint mosaicColor = 0;
uint8 mosaicPriority = 0;
uint16 mosaicColor = 0;
int x = 0 - (hscroll & 7);
while(x < width) {
@@ -83,7 +84,7 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint source) -> void
uint tileNumber = getTile(self, hoffset, voffset);
uint mirrorY = tileNumber & 0x8000 ? 7 : 0;
uint mirrorX = tileNumber & 0x4000 ? 7 : 0;
uint tilePriority = self.priority[bool(tileNumber & 0x2000)];
uint8 tilePriority = self.priority[bool(tileNumber & 0x2000)];
uint paletteNumber = tileNumber >> 10 & 7;
uint paletteIndex = paletteBase + (paletteNumber << paletteShift) & 0xff;
@@ -91,14 +92,36 @@ auto PPU::Line::renderBackground(PPU::IO::Background& self, uint source) -> void
if(tileHeight == 4 && (bool(voffset & 8) ^ bool(mirrorY))) tileNumber += 16;
tileNumber = (tileNumber & 0x03ff) + tiledataIndex & tileMask;
auto tiledata = ppu.tilecache[self.tileMode] + (tileNumber << 6);
tiledata += (voffset & 7 ^ mirrorY) << 3;
uint16 address;
address = (tileNumber << colorShift) + (voffset & 7 ^ mirrorY) & 0x7fff;
uint64 data;
data = (uint64)ppu.vram[address + 0] << 0;
data |= (uint64)ppu.vram[address + 8] << 16;
data |= (uint64)ppu.vram[address + 16] << 32;
data |= (uint64)ppu.vram[address + 24] << 48;
for(uint tileX = 0; tileX < 8; tileX++, x++) {
if(x & width) continue; //x < 0 || x >= width
if(!self.mosaicEnable || --mosaicCounter == 0) {
uint color, shift = mirrorX ? tileX : 7 - tileX;
/*if(self.tileMode >= TileMode::BPP2)*/ {
color = data >> shift + 0 & 1;
color += data >> shift + 7 & 2;
}
if(self.tileMode >= TileMode::BPP4) {
color += data >> shift + 14 & 4;
color += data >> shift + 21 & 8;
}
if(self.tileMode >= TileMode::BPP8) {
color += data >> shift + 28 & 16;
color += data >> shift + 35 & 32;
color += data >> shift + 42 & 64;
color += data >> shift + 49 & 128;
}
mosaicCounter = 1 + io.mosaicSize;
mosaicPalette = tiledata[tileX ^ mirrorX];
mosaicPalette = color;
mosaicPriority = tilePriority;
if(directColorMode) {
mosaicColor = directColor(paletteNumber, mosaicPalette);

View File

@@ -38,21 +38,6 @@ auto PPU::writeVRAM(uint8 data) -> void {
if constexpr(Byte == 1) {
vram[address] = vram[address] & 0x00ff | data << 8;
}
updateTiledata(address);
}
auto PPU::updateTiledata(uint address) -> void {
auto word = vram[address & 0x7fff];
auto line2bpp = tilecache[TileMode::BPP2] + (address << 3 & 0x3fff8);
auto line4bpp = tilecache[TileMode::BPP4] + (address << 2 & 0x1ffc0) + (address << 3 & 0x38);
auto line8bpp = tilecache[TileMode::BPP8] + (address << 1 & 0x0ffc0) + (address << 3 & 0x38);
uint plane4bpp = address >> 2 & 2;
uint plane8bpp = address >> 2 & 6;
for(uint x : range(8)) {
line2bpp[7 - x] = word >> x & 1 | word >> x + 7 & 2;
line4bpp[7 - x] = line4bpp[7 - x] & ~(3 << plane4bpp) | (word >> x & 1) << plane4bpp | (word >> x + 7 & 2) << plane4bpp;
line8bpp[7 - x] = line8bpp[7 - x] & ~(3 << plane8bpp) | (word >> x & 1) << plane8bpp | (word >> x + 7 & 2) << plane8bpp;
}
}
auto PPU::readOAM(uint10 address) -> uint8 {

View File

@@ -63,8 +63,8 @@ auto PPU::Line::render(bool fieldID) -> void {
}
bool hires = io.pseudoHires || io.bgMode == 5 || io.bgMode == 6;
auto aboveColor = cgram[0];
auto belowColor = hires ? cgram[0] : io.col.fixedColor;
uint16 aboveColor = cgram[0];
uint16 belowColor = hires ? cgram[0] : io.col.fixedColor;
uint xa = (hd || ss) && ppu.interlace() && field() ? 256 * scale * scale / 2 : 0;
uint xb = !(hd || ss) ? 256 : ppu.interlace() && !field() ? 256 * scale * scale / 2 : 256 * scale * scale;
for(uint x = xa; x < xb; x++) {
@@ -145,18 +145,18 @@ auto PPU::Line::directColor(uint paletteIndex, uint paletteColor) const -> uint1
+ (paletteColor << 7 & 0x6000) + (paletteIndex << 10 & 0x1000); //B
}
auto PPU::Line::plotAbove(uint x, uint source, uint priority, uint color) -> void {
auto PPU::Line::plotAbove(uint x, uint8 source, uint8 priority, uint16 color) -> void {
if(ppu.hd()) return plotHD(above, x, source, priority, color, false, false);
if(priority > above[x].priority) above[x] = {source, priority, color};
}
auto PPU::Line::plotBelow(uint x, uint source, uint priority, uint color) -> void {
auto PPU::Line::plotBelow(uint x, uint8 source, uint8 priority, uint16 color) -> void {
if(ppu.hd()) return plotHD(below, x, source, priority, color, false, false);
if(priority > below[x].priority) below[x] = {source, priority, color};
}
//todo: name these variables more clearly ...
auto PPU::Line::plotHD(Pixel* pixel, uint x, uint source, uint priority, uint color, bool hires, bool subpixel) -> void {
auto PPU::Line::plotHD(Pixel* pixel, uint x, uint8 source, uint8 priority, uint16 color, bool hires, bool subpixel) -> void {
auto scale = ppu.hdScale();
int xss = hires && subpixel ? scale / 2 : 0;
int ys = ppu.interlace() && field() ? scale / 2 : 0;

View File

@@ -1,4 +1,4 @@
auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void {
auto PPU::Line::renderMode7(PPU::IO::Background& self, uint8 source) -> void {
//HD mode 7 support
if(!ppu.hdMosaic() || !self.mosaicEnable || !io.mosaicSize) {
if(ppu.hdScale() > 1) return renderMode7HD(self, source);
@@ -18,8 +18,8 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void {
uint mosaicCounter = 1;
uint mosaicPalette = 0;
uint mosaicPriority = 0;
uint mosaicColor = 0;
uint8 mosaicPriority = 0;
uint16 mosaicColor = 0;
auto clip = [](int n) -> int { return n & 0x2000 ? (n | ~1023) : (n & 1023); };
int originX = (a * clip(hoffset - hcenter) & ~63) + (b * clip(voffset - vcenter) & ~63) + (b * y & ~63) + (hcenter << 8);
@@ -42,7 +42,7 @@ auto PPU::Line::renderMode7(PPU::IO::Background& self, uint source) -> void {
uint8 tile = io.mode7.repeat == 3 && outOfBounds ? 0 : ppu.vram[tileAddress] >> 0;
uint8 palette = io.mode7.repeat == 2 && outOfBounds ? 0 : ppu.vram[tile << 6 | paletteAddress] >> 8;
uint priority;
uint8 priority;
if(source == Source::BG1) {
priority = self.priority[0];
} else if(source == Source::BG2) {

View File

@@ -95,7 +95,7 @@ auto PPU::Line::cacheMode7HD() -> void {
}
}
auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint source) -> void {
auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint8 source) -> void {
const bool extbg = source == Source::BG2;
const uint scale = ppu.hdScale();
@@ -168,8 +168,8 @@ auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint source) -> void {
int ht = (hoffset - hcenter) % 1024;
float vty = ((voffset - vcenter) % 1024) + yf;
float originX = (a * ht) + (b * vty) + (hcenter << 8);
float originY = (c * ht) + (d * vty) + (vcenter << 8);
float originY = (c * ht) + (d * vty) + (vcenter << 8);
int pixelXp = INT_MIN;
for(int x : range(256)) {
bool doAbove = self.aboveEnable && !windowAbove[x];
@@ -190,7 +190,7 @@ auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint source) -> void {
uint tile = io.mode7.repeat == 3 && ((pixelX | pixelY) & ~1023) ? 0 : (ppu.vram[(pixelY >> 3 & 127) * 128 + (pixelX >> 3 & 127)] & 0xff);
uint palette = io.mode7.repeat == 2 && ((pixelX | pixelY) & ~1023) ? 0 : (ppu.vram[(((pixelY & 7) << 3) + (pixelX & 7)) + (tile << 6)] >> 8);
uint priority;
uint8 priority;
if(!extbg) {
priority = self.priority[0];
} else {
@@ -199,7 +199,7 @@ auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint source) -> void {
}
if(!palette) continue;
uint color;
uint16 color;
if(io.col.directColor && !extbg) {
color = directColor(0, palette);
} else {
@@ -215,7 +215,7 @@ auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint source) -> void {
if(doBelow && (!extbg || pixel.priority > below->priority)) *below = pixel;
}
}
}
}
if(ppu.ss()) {
uint divisor = scale * scale;
@@ -228,7 +228,7 @@ auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint source) -> void {
auto below = &this->below[p * scale];
for(uint x : range(scale)) {
uint a = above[x].color;
uint b = below[x].color;
uint b = below[x].color;
ab += a >> 0 & 31;
ag += a >> 5 & 31;
ar += a >> 10 & 31;
@@ -237,8 +237,8 @@ auto PPU::Line::renderMode7HD(PPU::IO::Background& self, uint source) -> void {
br += b >> 10 & 31;
}
}
uint aboveColor = ab / divisor << 0 | ag / divisor << 5 | ar / divisor << 10;
uint belowColor = bb / divisor << 0 | bg / divisor << 5 | br / divisor << 10;
uint16 aboveColor = ab / divisor << 0 | ag / divisor << 5 | ar / divisor << 10;
uint16 belowColor = bb / divisor << 0 | bg / divisor << 5 | br / divisor << 10;
this->above[p] = {source, this->above[p * scale].priority, aboveColor};
this->below[p] = {source, this->below[p * scale].priority, belowColor};
}

View File

@@ -83,7 +83,9 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void {
uint mirrorX = !object.hflip ? tileX : tileWidth - 1 - tileX;
uint address = tiledataAddress + ((characterY + (characterX + mirrorX & 15)) << 4);
tile.number = address >> 4 & 0x7ff;
address = (address & 0x7ff0) + (y & 7);
tile.data = ppu.vram[address + 0] << 0;
tile.data |= ppu.vram[address + 8] << 16;
if(tileCount++ >= ppu.TileLimit) break;
tiles[tileCount - 1] = tile;
@@ -97,16 +99,19 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void {
uint8_t priority[256] = {};
for(uint n : range(ppu.TileLimit)) {
const auto& tile = tiles[n];
auto& tile = tiles[n];
if(!tile.valid) continue;
auto tiledata = ppu.tilecache[TileMode::BPP4] + (tile.number << 6) + ((tile.y & 7) << 3);
uint tileX = tile.x;
uint mirrorX = tile.hflip ? 7 : 0;
for(uint x : range(8)) {
tileX &= 511;
if(tileX < 256) {
if(uint color = tiledata[x ^ mirrorX]) {
uint color, shift = tile.hflip ? x : 7 - x;
color = tile.data >> shift + 0 & 1;
color += tile.data >> shift + 7 & 2;
color += tile.data >> shift + 14 & 4;
color += tile.data >> shift + 21 & 8;
if(color) {
palette[tileX] = tile.palette + color;
priority[tileX] = self.priority[tile.priority];
}
@@ -117,7 +122,7 @@ auto PPU::Line::renderObject(PPU::IO::Object& self) -> void {
for(uint x : range(256)) {
if(!priority[x]) continue;
uint source = palette[x] < 192 ? Source::OBJ1 : Source::OBJ2;
uint8 source = palette[x] < 192 ? Source::OBJ1 : Source::OBJ2;
if(self.aboveEnable && !windowAbove[x]) plotAbove(x, source, priority[x], cgram[palette[x]]);
if(self.belowEnable && !windowBelow[x]) plotBelow(x, source, priority[x], cgram[palette[x]]);
}

View File

@@ -51,10 +51,6 @@ PPU::PPU() {
}
}
tilecache[TileMode::BPP2] = new uint8_t[4096 * 8 * 8]();
tilecache[TileMode::BPP4] = new uint8_t[2048 * 8 * 8]();
tilecache[TileMode::BPP8] = new uint8_t[1024 * 8 * 8]();
for(uint y : range(240)) {
lines[y].y = y;
}
@@ -63,13 +59,10 @@ PPU::PPU() {
PPU::~PPU() {
delete[] output;
for(uint l : range(16)) delete[] lightTable[l];
delete[] tilecache[TileMode::BPP2];
delete[] tilecache[TileMode::BPP4];
delete[] tilecache[TileMode::BPP8];
}
auto PPU::synchronizeCPU() -> void {
if(ppubase.clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(ppubase.clock >= 0) scheduler.resume(cpu.thread);
}
auto PPU::Enter() -> void {
@@ -88,7 +81,7 @@ auto PPU::step(uint clocks) -> void {
auto PPU::main() -> void {
scanline();
if(system.frameCounter == 0) {
if(system.frameCounter == 0 && !system.runAhead) {
uint y = vcounter();
if(y >= 1 && y <= 239) {
step(renderCycle());
@@ -101,6 +94,15 @@ auto PPU::main() -> void {
auto PPU::scanline() -> void {
if(vcounter() == 0) {
if(latch.overscan && !io.overscan) {
//when disabling overscan, clear the overscan area that won't be rendered to:
for(uint y = 1; y <= 240; y++) {
if(y >= 8 && y <= 231) continue;
auto output = ppu.output + y * 1024;
memory::fill<uint16>(output, 1024);
}
}
ppubase.display.interlace = io.interlace;
ppubase.display.overscan = io.overscan;
latch.overscan = io.overscan;
@@ -113,23 +115,22 @@ auto PPU::scanline() -> void {
if(vcounter() > 0 && vcounter() < vdisp()) {
latch.hires |= io.pseudoHires || io.bgMode == 5 || io.bgMode == 6;
latch.hd |= io.bgMode == 7 && hdScale() > 1 && hdSupersample() == 0;
latch.ss |= io.bgMode == 7 && hdScale() > 1 && hdSupersample() == 1;
//supersampling and EXTBG mode are not compatible, so disable supersampling in EXTBG mode
latch.hd |= io.bgMode == 7 && hdScale() > 1 && (hdSupersample() == 0 || io.extbg == 1);
latch.ss |= io.bgMode == 7 && hdScale() > 1 && (hdSupersample() == 1 && io.extbg == 0);
}
if(vcounter() == vdisp()) {
if(auto device = controllerPort2.device) device->latch(); //light guns
if(!io.displayDisable) oamAddressReset();
}
if(vcounter() == 240) {
Line::flush();
scheduler.leave(Scheduler::Event::Frame);
}
}
auto PPU::refresh() -> void {
if(system.frameCounter == 0) {
if(system.frameCounter == 0 && !system.runAhead) {
auto output = this->output;
uint pitch, width, height;
if(!hd()) {
@@ -176,10 +177,7 @@ auto PPU::power(bool reset) -> void {
bus.map(reader, writer, "00-3f,80-bf:2100-213f");
if(!reset) {
for(uint address : range(32768)) {
vram[address] = 0x0000;
updateTiledata(address);
}
for(auto& word : vram) word = 0x0000;
for(auto& color : cgram) color = 0x0000;
for(auto& object : objects) object = {};
}

View File

@@ -39,9 +39,9 @@ struct PPU : PPUcounter {
auto serialize(serializer&) -> void;
public:
struct Source { enum : uint { BG1, BG2, BG3, BG4, OBJ1, OBJ2, COL }; };
struct TileMode { enum : uint { BPP2, BPP4, BPP8, Mode7, Inactive }; };
struct ScreenMode { enum : uint { Above, Below }; };
struct Source { enum : uint8 { BG1, BG2, BG3, BG4, OBJ1, OBJ2, COL }; };
struct TileMode { enum : uint8 { BPP2, BPP4, BPP8, Mode7, Inactive }; };
struct ScreenMode { enum : uint8 { Above, Below }; };
struct Latch {
//serialization.cpp
@@ -235,13 +235,13 @@ public:
uint8 priority = 0;
uint8 palette = 0;
bool hflip = 0;
uint16 number = 0;
uint32 data = 0;
};
struct Pixel {
uint source;
uint priority;
uint color;
uint8 source = 0;
uint8 priority = 0;
uint16 color = 0;
};
//io.cpp
@@ -250,7 +250,6 @@ public:
alwaysinline auto vramAddress() const -> uint;
alwaysinline auto readVRAM() -> uint16;
template<bool Byte> alwaysinline auto writeVRAM(uint8 data) -> void;
alwaysinline auto updateTiledata(uint address) -> void;
alwaysinline auto readOAM(uint10 address) -> uint8;
alwaysinline auto writeOAM(uint10 address, uint8 data) -> void;
template<bool Byte> alwaysinline auto readCGRAM(uint8 address) -> uint8;
@@ -276,7 +275,6 @@ public:
//[unserialized]
uint16* output = {};
uint16* lightTable[16] = {};
uint8* tilecache[3] = {}; //bitplane -> bitmap tiledata
uint ItemLimit = 0;
uint TileLimit = 0;
@@ -290,23 +288,32 @@ public:
auto pixel(uint x, Pixel above, Pixel below) const -> uint16;
auto blend(uint x, uint y, bool halve) const -> uint16;
alwaysinline auto directColor(uint paletteIndex, uint paletteColor) const -> uint16;
alwaysinline auto plotAbove(uint x, uint source, uint priority, uint color) -> void;
alwaysinline auto plotBelow(uint x, uint source, uint priority, uint color) -> void;
alwaysinline auto plotHD(Pixel*, uint x, uint source, uint priority, uint color, bool hires, bool subpixel) -> void;
alwaysinline auto plotAbove(uint x, uint8 source, uint8 priority, uint16 color) -> void;
alwaysinline auto plotBelow(uint x, uint8 source, uint8 priority, uint16 color) -> void;
alwaysinline auto plotHD(Pixel*, uint x, uint8 source, uint8 priority, uint16 color, bool hires, bool subpixel) -> void;
//background.cpp
auto cacheBackground(PPU::IO::Background&) -> void;
auto renderBackground(PPU::IO::Background&, uint source) -> void;
auto renderBackground(PPU::IO::Background&, uint8 source) -> void;
auto getTile(PPU::IO::Background&, uint hoffset, uint voffset) -> uint;
//mode7.cpp
auto renderMode7(PPU::IO::Background&, uint source) -> void;
auto renderMode7(PPU::IO::Background&, uint8 source) -> void;
//mode7hd.cpp
static auto cacheMode7HD() -> void;
auto renderMode7HD(PPU::IO::Background&, uint source) -> void;
auto renderMode7HD(PPU::IO::Background&, uint8 source) -> void;
alwaysinline auto lerp(float pa, float va, float pb, float vb, float pr) -> float;
//mode7hd-avx2.cpp
auto renderMode7HD_AVX2(
PPU::IO::Background&, uint8 source,
Pixel* above, Pixel* below,
bool* windowAbove, bool* windowBelow,
float originX, float a,
float originY, float c
) -> void;
//object.cpp
auto renderObject(PPU::IO::Object&) -> void;

View File

@@ -8,7 +8,6 @@ auto PPU::serialize(serializer& s) -> void {
s.array(cgram);
for(auto& object : objects) object.serialize(s);
for(auto address : range(32768)) updateTiledata(address);
Line::start = 0;
Line::count = 0;
}

View File

@@ -5,31 +5,13 @@ auto PPU::Background::hires() const -> bool {
return ppu.io.bgMode == 5 || ppu.io.bgMode == 6;
}
auto PPU::Background::voffset() const -> uint16 {
return mosaic.enable ? latch.voffset : io.voffset;
}
auto PPU::Background::hoffset() const -> uint16 {
return mosaic.enable ? latch.hoffset : io.hoffset;
}
//V = 0, H = 0
auto PPU::Background::frame() -> void {
}
//H = 0
auto PPU::Background::scanline() -> void {
}
//H = 28
auto PPU::Background::begin() -> void {
x = -7;
y = ppu.vcounter();
tileCounter = 7 - (io.hoffset & 7) << hires();
for(auto& word : data) word = 0;
if(y == 1) {
if(ppu.vcounter() == 1) {
mosaic.vcounter = mosaic.size + 1;
mosaic.voffset = 1;
latch.hoffset = io.hoffset;
@@ -40,7 +22,6 @@ auto PPU::Background::begin() -> void {
latch.hoffset = io.hoffset;
latch.voffset = io.voffset;
}
latch.screenAddress = io.screenAddress;
mosaic.hcounter = mosaic.size + 1;
mosaic.hoffset = 0;
@@ -50,140 +31,161 @@ auto PPU::Background::begin() -> void {
latch.hoffset = io.hoffset;
latch.voffset = io.voffset;
}
nameTableIndex = 0;
characterIndex = 0;
renderingIndex = 0;
opt.hoffset = 0;
opt.voffset = 0;
}
auto PPU::Background::getTile() -> void {
uint paletteOffset = ppu.io.bgMode == 0 ? id << 5 : 0;
uint paletteSize = 2 << io.mode;
uint tileMask = ppu.vram.mask >> 3 + io.mode;
uint tiledataIndex = io.tiledataAddress >> 3 + io.mode;
//H = 56
auto PPU::Background::begin() -> void {
//remove partial tile columns that have been scrolled offscreen
pixelCounter = io.hoffset & 7;
for(auto& data : tiles[0].data) data >>= pixelCounter << 1;
}
uint tileHeight = 3 + io.tileSize;
uint tileWidth = !hires() ? tileHeight : 4;
auto PPU::Background::fetchNameTable() -> void {
if(ppu.vcounter() == 0) return;
uint width = 256 << hires();
int x = (ppu.hcounter() & ~31) >> 2;
uint hpixel = x << hires();
uint vpixel = mosaic.enable ? (uint)mosaic.voffset : ppu.vcounter();
uint hmask = (width << io.tileSize << io.screenSize.bit(0)) - 1;
uint vmask = (width << io.tileSize << io.screenSize.bit(1)) - 1;
uint px = x << hires();
uint py = mosaic.enable ? (uint)mosaic.voffset : y;
uint hscroll = hoffset();
uint vscroll = voffset();
uint hscroll = mosaic.enable ? latch.hoffset : io.hoffset;
uint vscroll = mosaic.enable ? latch.voffset : io.voffset;
if(hires()) {
hscroll <<= 1;
if(ppu.io.interlace) py = py << 1 | (ppu.field() & !mosaic.enable); //todo: temporary vmosaic hack
if(ppu.io.interlace) vpixel = vpixel << 1 | (ppu.field() && !mosaic.enable);
}
uint hoffset = hscroll + px;
uint voffset = vscroll + py;
bool repeated = false;
repeat:
uint hoffset = hpixel + hscroll;
uint voffset = vpixel + vscroll;
if(ppu.io.bgMode == 2 || ppu.io.bgMode == 4 || ppu.io.bgMode == 6) {
uint16 offsetX = px + (hscroll & 7);
auto hlookup = ppu.bg3.opt.hoffset;
auto vlookup = ppu.bg3.opt.voffset;
uint valid = 1 << 13 + id;
if(offsetX >= 8) {
auto hlookup = ppu.bg3.getTile((offsetX - 8) + (ppu.bg3.hoffset() & ~7), ppu.bg3.voffset() + 0);
auto vlookup = ppu.bg3.getTile((offsetX - 8) + (ppu.bg3.hoffset() & ~7), ppu.bg3.voffset() + 8);
uint valid = 1 << 13 + id;
if(ppu.io.bgMode == 4) {
if(hlookup & valid) {
if(!(hlookup & 0x8000)) {
hoffset = offsetX + (hlookup & ~7);
} else {
voffset = py + hlookup;
}
if(ppu.io.bgMode == 4) {
if(hlookup & valid) {
if(!(hlookup & 0x8000)) {
hoffset = hpixel + (hlookup & ~7) + (hscroll & 7);
} else {
voffset = vpixel + (vlookup);
}
} else {
if(hlookup & valid) hoffset = offsetX + (hlookup & ~7);
if(vlookup & valid) voffset = py + vlookup;
}
} else {
if(hlookup & valid) hoffset = hpixel + (hlookup & ~7) + (hscroll & 7);
if(vlookup & valid) voffset = vpixel + (vlookup);
}
}
hoffset &= hmask;
voffset &= vmask;
uint width = 256 << hires();
uint hsize = width << io.tileSize << io.screenSize.bit(0);
uint vsize = width << io.tileSize << io.screenSize.bit(1);
uint screenX = io.screenSize.bit(0) ? 32 << 5 : 0;
uint screenY = io.screenSize.bit(1) ? 32 << 5 + io.screenSize.bit(0) : 0;
hoffset &= hsize - 1;
voffset &= vsize - 1;
uint tileX = hoffset >> tileWidth;
uint tileY = voffset >> tileHeight;
uint vtiles = 3 + io.tileSize;
uint htiles = !hires() ? vtiles : 4;
uint16 offset = (tileY & 0x1f) << 5 | (tileX & 0x1f);
if(tileX & 0x20) offset += screenX;
if(tileY & 0x20) offset += screenY;
uint htile = hoffset >> htiles;
uint vtile = voffset >> vtiles;
uint16 address = latch.screenAddress + offset;
tile = ppu.vram[address];
bool mirrorY = tile & 0x8000;
bool mirrorX = tile & 0x4000;
priority = io.priority[bool(tile & 0x2000)];
paletteNumber = tile >> 10 & 7;
paletteIndex = paletteOffset + (paletteNumber << paletteSize);
uint hscreen = io.screenSize.bit(0) ? 32 << 5 : 0;
uint vscreen = io.screenSize.bit(1) ? 32 << 5 + io.screenSize.bit(0) : 0;
if(tileWidth == 4 && bool(hoffset & 8) != mirrorX) tile += 1;
if(tileHeight == 4 && bool(voffset & 8) != mirrorY) tile += 16;
uint16 character = (tile & 0x03ff) + tiledataIndex & tileMask;
uint16 offset = (uint5)htile << 0 | (uint5)vtile << 5;
if(htile & 0x20) offset += hscreen;
if(vtile & 0x20) offset += vscreen;
if(mirrorY) voffset ^= 7;
offset = (character << 3 + io.mode) + (voffset & 7);
uint16 address = io.screenAddress + offset;
uint16 attributes = ppu.vram[address];
switch(io.mode) {
case Mode::BPP8:
data[0] = ppu.vram[offset + 0] << 0 | ppu.vram[offset + 8] << 16;
data[1] = ppu.vram[offset + 16] << 0 | ppu.vram[offset + 24] << 16;
break;
case Mode::BPP4:
data[0] = ppu.vram[offset + 0] << 0 | ppu.vram[offset + 8] << 16;
break;
case Mode::BPP2:
data[0] = ppu.vram[offset + 0] << 0;
break;
}
auto& tile = tiles[nameTableIndex];
tile.character = attributes & 0x03ff;
tile.paletteGroup = attributes >> 10 & 7;
tile.priority = io.priority[attributes >> 13 & 1];
tile.hmirror = bool(attributes & 0x4000);
tile.vmirror = bool(attributes & 0x8000);
if(mirrorX) for(auto n : range(2)) {
data[n] = (data[n] >> 4 & 0x0f0f0f0f) | (data[n] << 4 & 0xf0f0f0f0);
data[n] = (data[n] >> 2 & 0x33333333) | (data[n] << 2 & 0xcccccccc);
data[n] = (data[n] >> 1 & 0x55555555) | (data[n] << 1 & 0xaaaaaaaa);
if(htiles == 4 && bool(hoffset & 8) != tile.hmirror) tile.character += 1;
if(vtiles == 4 && bool(voffset & 8) != tile.vmirror) tile.character += 16;
uint characterMask = ppu.vram.mask >> 3 + io.mode;
uint characterIndex = io.tiledataAddress >> 3 + io.mode;
uint16 origin = tile.character + characterIndex & characterMask;
if(tile.vmirror) voffset ^= 7;
tile.address = (origin << 3 + io.mode) + (voffset & 7);
uint paletteOffset = ppu.io.bgMode == 0 ? id << 5 : 0;
uint paletteSize = 2 << io.mode;
tile.palette = paletteOffset + (tile.paletteGroup << paletteSize);
nameTableIndex++;
if(hires() && !repeated) {
repeated = true;
hpixel += 8;
goto repeat;
}
}
auto PPU::Background::getTile(uint x, uint y) -> uint16 {
uint tileHeight = 3 + io.tileSize;
uint tileWidth = !hires() ? tileHeight : 4;
uint screenX = io.screenSize.bit(0) ? 32 << 5 : 0;
uint screenY = io.screenSize.bit(1) ? 32 << 5 + io.screenSize.bit(0) : 0;
uint tileX = x >> tileWidth;
uint tileY = y >> tileHeight;
uint16 offset = (tileY & 0x1f) << 5 | (tileX & 0x1f);
if(tileX & 0x20) offset += screenX;
if(tileY & 0x20) offset += screenY;
uint16 address = latch.screenAddress + offset;
return ppu.vram[address];
auto PPU::Background::fetchOffset(uint y) -> void {
if(ppu.vcounter() == 0) return;
uint x = characterIndex << 3;
uint hoffset = x + (io.hoffset & ~7);
uint voffset = y + (io.voffset);
uint vtiles = 3 + io.tileSize;
uint htiles = !hires() ? vtiles : 4;
uint htile = hoffset >> htiles;
uint vtile = voffset >> vtiles;
uint hscreen = io.screenSize.bit(0) ? 32 << 5 : 0;
uint vscreen = io.screenSize.bit(1) ? 32 << 5 + io.screenSize.bit(0) : 0;
uint16 offset = (uint5)htile << 0 | (uint5)vtile << 5;
if(htile & 0x20) offset += hscreen;
if(vtile & 0x20) offset += vscreen;
uint16 address = io.screenAddress + offset;
if(y == 0) opt.hoffset = ppu.vram[address];
if(y == 8) opt.voffset = ppu.vram[address];
if(y == 0) characterIndex++;
}
auto PPU::Background::getTileColor() -> uint {
uint color = 0;
auto PPU::Background::fetchCharacter(uint index) -> void {
if(ppu.vcounter() == 0) return;
switch(io.mode) {
case Mode::BPP8:
color += data[1] >> 24 & 0x80;
color += data[1] >> 17 & 0x40;
color += data[1] >> 10 & 0x20;
color += data[1] >> 3 & 0x10;
data[1] <<= 1;
case Mode::BPP4:
color += data[0] >> 28 & 0x08;
color += data[0] >> 21 & 0x04;
case Mode::BPP2:
color += data[0] >> 14 & 0x02;
color += data[0] >> 7 & 0x01;
data[0] <<= 1;
auto& tile = tiles[characterIndex];
uint16 data = ppu.vram[tile.address + (index << 3)];
//reverse bits so that the lowest bit is the left-most pixel
if(!tile.hmirror) {
data = data >> 4 & 0x0f0f | data << 4 & 0xf0f0;
data = data >> 2 & 0x3333 | data << 2 & 0xcccc;
data = data >> 1 & 0x5555 | data << 1 & 0xaaaa;
}
return color;
//interleave two bitplanes for faster planar decoding later
tile.data[index] = (
((uint8(data >> 0) * 0x0101010101010101ull & 0x8040201008040201ull) * 0x0102040810204081ull >> 49) & 0x5555
| ((uint8(data >> 8) * 0x0101010101010101ull & 0x8040201008040201ull) * 0x0102040810204081ull >> 48) & 0xaaaa
);
if(index == 0) characterIndex++;
}
auto PPU::Background::run(bool screen) -> void {
@@ -195,19 +197,22 @@ auto PPU::Background::run(bool screen) -> void {
if(!hires()) return;
}
if(tileCounter-- == 0) {
tileCounter = 7;
getTile();
}
if(io.mode == Mode::Mode7) return runMode7();
uint8 color = getTileColor();
Pixel pixel;
pixel.priority = priority;
pixel.palette = color ? uint(paletteIndex + color) : 0;
pixel.tile = tile;
auto& tile = tiles[renderingIndex];
uint8 color = 0;
if(io.mode >= Mode::BPP2) color |= (tile.data[0] & 3) << 0; tile.data[0] >>= 2;
if(io.mode >= Mode::BPP4) color |= (tile.data[1] & 3) << 2; tile.data[1] >>= 2;
if(io.mode >= Mode::BPP8) color |= (tile.data[2] & 3) << 4; tile.data[2] >>= 2;
if(io.mode >= Mode::BPP8) color |= (tile.data[3] & 3) << 6; tile.data[3] >>= 2;
Pixel pixel;
pixel.priority = tile.priority;
pixel.palette = color ? uint(tile.palette + color) : 0;
pixel.paletteGroup = tile.paletteGroup;
if(++pixelCounter == 0) renderingIndex++;
uint x = ppu.hcounter() - 56 >> 2;
if(x == 0) {
mosaic.hcounter = mosaic.size + 1;
mosaic.pixel = pixel;
@@ -243,14 +248,4 @@ auto PPU::Background::power() -> void {
mosaic = {};
mosaic.size = random();
mosaic.enable = random();
x = 0;
y = 0;
tileCounter = 0;
tile = 0;
priority = 0;
paletteNumber = 0;
paletteIndex = 0;
for(auto& word : data) word = 0;
}

View File

@@ -2,19 +2,18 @@ struct Background {
Background(uint id) : id(id) {}
alwaysinline auto hires() const -> bool;
alwaysinline auto hoffset() const -> uint16;
alwaysinline auto voffset() const -> uint16;
//background.cpp
auto frame() -> void;
auto scanline() -> void;
auto begin() -> void;
auto fetchNameTable() -> void;
auto fetchOffset(uint y) -> void;
auto fetchCharacter(uint index) -> void;
auto run(bool screen) -> void;
auto power() -> void;
auto getTile() -> void;
auto getTile(uint x, uint y) -> uint16;
auto getTileColor() -> uint;
//mode7.cpp
alwaysinline auto clip(int n) -> int;
auto beginMode7() -> void;
auto runMode7() -> void;
@@ -32,14 +31,14 @@ struct Background {
struct IO {
uint16 tiledataAddress;
uint16 screenAddress;
uint2 screenSize;
uint1 tileSize;
uint2 screenSize;
uint1 tileSize;
uint8 mode;
uint8 priority[2];
uint8 mode;
uint8 priority[2];
uint1 aboveEnable;
uint1 belowEnable;
uint1 aboveEnable;
uint1 belowEnable;
uint16 hoffset;
uint16 voffset;
@@ -48,13 +47,12 @@ struct Background {
struct Latch {
uint16 hoffset;
uint16 voffset;
uint16 screenAddress;
} latch;
struct Pixel {
uint8 priority; //0 = none (transparent)
uint8 palette;
uint16 tile;
uint3 paletteGroup;
} above, below;
struct Output {
@@ -64,7 +62,7 @@ struct Background {
struct Mosaic {
static uint4 size;
uint1 enable;
uint1 enable;
uint16 vcounter;
uint16 hcounter;
@@ -72,18 +70,30 @@ struct Background {
uint16 voffset;
uint16 hoffset;
Pixel pixel;
Pixel pixel;
} mosaic;
int x;
int y;
struct OffsetPerTile {
//set in BG3 only; used by BG1 and BG2
uint16 hoffset;
uint16 voffset;
} opt;
uint3 tileCounter;
uint16 tile;
uint8 priority;
uint3 paletteNumber;
uint8 paletteIndex;
uint32 data[2];
struct Tile {
uint16 address;
uint10 character;
uint8 palette;
uint3 paletteGroup;
uint8 priority;
uint1 hmirror;
uint1 vmirror;
uint16 data[4];
} tiles[66];
uint7 nameTableIndex;
uint7 characterIndex;
uint7 renderingIndex;
uint3 pixelCounter;
friend class PPU;
};

194
bsnes/sfc/ppu/main.cpp Normal file
View File

@@ -0,0 +1,194 @@
auto PPU::main() -> void {
if(vcounter() == 0) {
if(display.overscan && !io.overscan) {
//when disabling overscan, clear the overscan area that won't be rendered to:
for(uint y = 1; y <= 240; y++) {
if(y >= 8 && y <= 231) continue;
auto output = ppu.output + y * 1024;
memory::fill<uint16>(output, 1024);
}
}
display.interlace = io.interlace;
display.overscan = io.overscan;
bg1.frame();
bg2.frame();
bg3.frame();
bg4.frame();
obj.frame();
}
bg1.scanline();
bg2.scanline();
bg3.scanline();
bg4.scanline();
obj.scanline();
window.scanline();
screen.scanline();
if(vcounter() > 240) {
step(hperiod());
return;
}
#define cycles02(index) cycle<index>()
#define cycles04(index) cycles02(index); cycles02(index + 2)
#define cycles08(index) cycles04(index); cycles04(index + 4)
#define cycles16(index) cycles08(index); cycles08(index + 8)
#define cycles32(index) cycles16(index); cycles16(index + 16)
#define cycles64(index) cycles32(index); cycles32(index + 32)
cycles16( 0);
cycles04( 16);
//H = 20
cycles04( 20);
cycles04( 24);
//H = 28
cycles04( 28);
cycles32( 32);
cycles64( 64);
cycles64( 128);
cycles64( 192);
cycles64( 256);
cycles64( 320);
cycles64( 384);
cycles64( 448);
cycles64( 512);
cycles64( 576);
cycles64( 640);
cycles64( 704);
cycles64( 768);
cycles64( 832);
cycles64( 896);
cycles64( 960);
cycles32(1024);
cycles16(1056);
cycles08(1072);
//H = 1080
obj.fetch();
//H = 1352 (max)
step(hperiod() - hcounter());
}
//it would be lovely if we could put these functions inside cycle(),
//but due to the multiple template instantiations, that destroys L1 cache.
//it's a performance penalty of about 25% for the entire(!!) emulator.
auto PPU::cycleObjectEvaluate() -> void {
obj.evaluate(hcounter() >> 3);
}
template<uint Cycle>
auto PPU::cycleBackgroundFetch() -> void {
switch(io.bgMode) {
case 0:
if constexpr(Cycle == 0) bg4.fetchNameTable();
if constexpr(Cycle == 1) bg3.fetchNameTable();
if constexpr(Cycle == 2) bg2.fetchNameTable();
if constexpr(Cycle == 3) bg1.fetchNameTable();
if constexpr(Cycle == 4) bg4.fetchCharacter(0);
if constexpr(Cycle == 5) bg3.fetchCharacter(0);
if constexpr(Cycle == 6) bg2.fetchCharacter(0);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
break;
case 1:
if constexpr(Cycle == 0) bg3.fetchNameTable();
if constexpr(Cycle == 1) bg2.fetchNameTable();
if constexpr(Cycle == 2) bg1.fetchNameTable();
if constexpr(Cycle == 3) bg3.fetchCharacter(0);
if constexpr(Cycle == 4) bg2.fetchCharacter(1);
if constexpr(Cycle == 5) bg2.fetchCharacter(0);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
break;
case 2:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg3.fetchOffset(8);
if constexpr(Cycle == 3) bg3.fetchOffset(0);
if constexpr(Cycle == 4) bg2.fetchCharacter(1);
if constexpr(Cycle == 5) bg2.fetchCharacter(0);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
break;
case 3:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg2.fetchCharacter(1);
if constexpr(Cycle == 3) bg2.fetchCharacter(0);
if constexpr(Cycle == 4) bg1.fetchCharacter(3);
if constexpr(Cycle == 5) bg1.fetchCharacter(2);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
break;
case 4:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg3.fetchOffset(0);
if constexpr(Cycle == 3) bg2.fetchCharacter(0);
if constexpr(Cycle == 4) bg1.fetchCharacter(3);
if constexpr(Cycle == 5) bg1.fetchCharacter(2);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
break;
case 5:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg2.fetchCharacter(0);
if constexpr(Cycle == 3) bg2.fetchCharacter(0);
if constexpr(Cycle == 4) bg1.fetchCharacter(1);
if constexpr(Cycle == 5) bg1.fetchCharacter(0);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
break;
case 6:
if constexpr(Cycle == 0) bg2.fetchNameTable();
if constexpr(Cycle == 1) bg1.fetchNameTable();
if constexpr(Cycle == 2) bg3.fetchOffset(8);
if constexpr(Cycle == 3) bg3.fetchOffset(0);
if constexpr(Cycle == 4) bg1.fetchCharacter(1);
if constexpr(Cycle == 5) bg1.fetchCharacter(0);
if constexpr(Cycle == 6) bg1.fetchCharacter(1);
if constexpr(Cycle == 7) bg1.fetchCharacter(0);
break;
case 7:
//handled separately by mode7.cpp
break;
}
}
auto PPU::cycleBackgroundBegin() -> void {
bg1.begin();
bg2.begin();
bg3.begin();
bg4.begin();
}
auto PPU::cycleBackgroundBelow() -> void {
bg1.run(1);
bg2.run(1);
bg3.run(1);
bg4.run(1);
}
auto PPU::cycleBackgroundAbove() -> void {
bg1.run(0);
bg2.run(0);
bg3.run(0);
bg4.run(0);
}
auto PPU::cycleRenderPixel() -> void {
obj.run();
window.run();
screen.run();
}
template<uint Cycle>
auto PPU::cycle() -> void {
if constexpr(Cycle >= 0 && Cycle <= 1016 && (Cycle - 0) % 8 == 0) cycleObjectEvaluate();
if constexpr(Cycle >= 0 && Cycle <= 1054 && (Cycle - 0) % 4 == 0) cycleBackgroundFetch<(Cycle - 0) / 4 & 7>();
if constexpr(Cycle == 56 ) cycleBackgroundBegin();
if constexpr(Cycle >= 56 && Cycle <= 1078 && (Cycle - 56) % 4 == 0) cycleBackgroundBelow();
if constexpr(Cycle >= 56 && Cycle <= 1078 && (Cycle - 56) % 4 == 2) cycleBackgroundAbove();
if constexpr(Cycle >= 56 && Cycle <= 1078 && (Cycle - 56) % 4 == 2) cycleRenderPixel();
step();
}

View File

@@ -3,7 +3,7 @@ auto PPU::Background::clip(int n) -> int {
return n & 0x2000 ? (n | ~1023) : (n & 1023);
}
//H = 28
//H = 0
auto PPU::Background::beginMode7() -> void {
latch.hoffset = ppu.io.hoffsetMode7;
latch.voffset = ppu.io.voffsetMode7;
@@ -20,11 +20,12 @@ auto PPU::Background::runMode7() -> void {
int hoffset = (int13)latch.hoffset;
int voffset = (int13)latch.voffset;
if(Background::x++ & ~255) return;
uint x = mosaic.hoffset;
uint y = ppu.bg1.mosaic.voffset; //BG2 vertical mosaic uses BG1 mosaic size
uint y = !mosaic.enable ? ppu.vcounter() : ppu.bg1.mosaic.voffset; //BG2 vertical mosaic uses BG1 mosaic size
if(--mosaic.hcounter == 0) {
if(!mosaic.enable) {
mosaic.hoffset += 1;
} else if(--mosaic.hcounter == 0) {
mosaic.hcounter = mosaic.size + 1;
mosaic.hoffset += mosaic.size + 1;
}
@@ -37,36 +38,36 @@ auto PPU::Background::runMode7() -> void {
int pixelX = originX + a * x >> 8;
int pixelY = originY + c * x >> 8;
uint16 paletteAddress = (pixelY & 7) << 3 | (pixelX & 7);
uint16 paletteAddress = (uint3)pixelY << 3 | (uint3)pixelX;
int tileX = pixelX >> 3;
int tileY = pixelY >> 3;
uint16 tileAddress = (tileY & 127) << 7 | (tileX & 127);
uint7 tileX = pixelX >> 3;
uint7 tileY = pixelY >> 3;
uint16 tileAddress = tileY << 7 | tileX;
bool outOfBounds = (pixelX | pixelY) & ~1023;
uint8 tile = ppu.io.repeatMode7 == 3 & outOfBounds ? 0 : ppu.vram[tileAddress] >> 0;
uint8 tile = ppu.io.repeatMode7 == 3 && outOfBounds ? 0 : ppu.vram[tileAddress] >> 0;
uint8 palette = ppu.io.repeatMode7 == 2 && outOfBounds ? 0 : ppu.vram[tile << 6 | paletteAddress] >> 8;
uint priority;
if(id == ID::BG1) {
priority = io.priority[0];
} else if(id == ID::BG2) {
priority = io.priority[bool(palette & 0x80)];
priority = io.priority[palette >> 7];
palette &= 0x7f;
}
if(palette == 0) return;
if(io.aboveEnable) {
output.above.palette = palette;
output.above.priority = priority;
output.above.tile = 0;
output.above.palette = palette;
output.above.paletteGroup = 0;
}
if(io.belowEnable) {
output.below.palette = palette;
output.below.priority = priority;
output.below.tile = 0;
output.below.palette = palette;
output.below.paletteGroup = 0;
}
}

View File

@@ -15,9 +15,10 @@ auto PPU::Object::frame() -> void {
}
auto PPU::Object::scanline() -> void {
latch.firstSprite = io.firstSprite;
t.x = 0;
t.y = ppu.vcounter();
t.itemCount = 0;
t.tileCount = 0;
@@ -25,22 +26,27 @@ auto PPU::Object::scanline() -> void {
auto oamItem = t.item[t.active];
auto oamTile = t.tile[t.active];
for(uint n : range(32)) oamItem[n].valid = false;
for(uint n : range(34)) oamTile[n].valid = false;
if(t.y == ppu.vdisp() && !ppu.io.displayDisable) addressReset();
if(t.y >= ppu.vdisp() - 1 || ppu.io.displayDisable) return;
}
for(auto n : range(32)) oamItem[n].valid = false;
for(auto n : range(34)) oamTile[n].valid = false;
auto PPU::Object::evaluate(uint7 index) -> void {
if(ppu.io.displayDisable) return;
if(t.itemCount > 32) return;
for(auto n : range(128)) {
uint7 sprite = io.firstSprite + n;
if(!onScanline(oam.object[sprite])) continue;
if(t.itemCount++ >= 32) break;
auto oamItem = t.item[t.active];
auto oamTile = t.tile[t.active];
uint7 sprite = latch.firstSprite + index;
if(!onScanline(oam.object[sprite])) return;
ppu.latch.oamAddress = sprite;
if(t.itemCount++ < 32) {
oamItem[t.itemCount - 1] = {true, sprite};
}
if(t.itemCount > 0 && oamItem[t.itemCount - 1].valid) {
ppu.latch.oamAddress = 0x0200 + (oamItem[t.itemCount - 1].index >> 2);
}
}
auto PPU::Object::onScanline(PPU::OAM::Object& sprite) -> bool {
@@ -58,7 +64,7 @@ auto PPU::Object::run() -> void {
auto oamTile = t.tile[!t.active];
uint x = t.x++;
for(auto n : range(34)) {
for(uint n : range(34)) {
const auto& tile = oamTile[n];
if(!tile.valid) break;
@@ -66,10 +72,10 @@ auto PPU::Object::run() -> void {
if(px & ~7) continue;
uint color = 0, shift = tile.hflip ? px : 7 - px;
color += tile.data >> (shift + 0) & 1;
color += tile.data >> (shift + 7) & 2;
color += tile.data >> (shift + 14) & 4;
color += tile.data >> (shift + 21) & 8;
color += tile.data >> shift + 0 & 1;
color += tile.data >> shift + 7 & 2;
color += tile.data >> shift + 14 & 4;
color += tile.data >> shift + 21 & 8;
if(color) {
if(io.aboveEnable) {
@@ -85,12 +91,19 @@ auto PPU::Object::run() -> void {
}
}
auto PPU::Object::tilefetch() -> void {
auto PPU::Object::fetch() -> void {
auto oamItem = t.item[t.active];
auto oamTile = t.tile[t.active];
for(int i = 31; i >= 0; i--) {
for(uint i : reverse(range(32))) {
if(!oamItem[i].valid) continue;
if(ppu.io.displayDisable || ppu.vcounter() >= ppu.vdisp() - 1) {
ppu.step(8);
continue;
}
ppu.latch.oamAddress = 0x0200 + (oamItem[i].index >> 2);
const auto& sprite = oam.object[oamItem[i].index];
uint tileWidth = sprite.width() >> 3;
@@ -117,8 +130,8 @@ auto PPU::Object::tilefetch() -> void {
uint16 tiledataAddress = io.tiledataAddress;
if(sprite.nameselect) tiledataAddress += 1 + io.nameselect << 12;
uint16 chrx = (sprite.character >> 0 & 15);
uint16 chry = ((sprite.character >> 4 & 15) + (y >> 3) & 15) << 4;
uint16 chrx = (sprite.character & 15);
uint16 chry = ((sprite.character >> 4) + (y >> 3) & 15) << 4;
for(uint tx : range(tileWidth)) {
uint sx = x + (tx << 3) & 511;
@@ -134,14 +147,18 @@ auto PPU::Object::tilefetch() -> void {
uint mx = !sprite.hflip ? tx : tileWidth - 1 - tx;
uint pos = tiledataAddress + ((chry + (chrx + mx & 15)) << 4);
uint16 addr = (pos & 0xfff0) + (y & 7);
uint16 address = (pos & 0xfff0) + (y & 7);
oamTile[n].data = ppu.vram[addr + 0] << 0 | ppu.vram[addr + 8] << 16;
ppu.step(2);
if(!ppu.io.displayDisable)
oamTile[n].data = ppu.vram[address + 0] << 0;
ppu.step(4);
if(!ppu.io.displayDisable)
oamTile[n].data |= ppu.vram[address + 8] << 16;
ppu.step(4);
}
}
if(t.tileCount < 34) ppu.step((34 - t.tileCount) * 4);
io.timeOver |= (t.tileCount > 34);
io.rangeOver |= (t.itemCount > 32);
}
@@ -166,12 +183,12 @@ auto PPU::Object::power() -> void {
t.tileCount = 0;
t.active = 0;
for(auto p : range(2)) {
for(auto n : range(32)) {
for(uint p : range(2)) {
for(uint n : range(32)) {
t.item[p][n].valid = false;
t.item[p][n].index = 0;
}
for(auto n : range(34)) {
for(uint n : range(34)) {
t.tile[p][n].valid = false;
t.tile[p][n].x = 0;
t.tile[p][n].priority = 0;
@@ -195,6 +212,8 @@ auto PPU::Object::power() -> void {
io.timeOver = false;
io.rangeOver = false;
latch = {};
output.above.palette = 0;
output.above.priority = 0;
output.below.palette = 0;

View File

@@ -23,8 +23,9 @@ struct Object {
alwaysinline auto setFirstSprite() -> void;
auto frame() -> void;
auto scanline() -> void;
auto evaluate(uint7 index) -> void;
auto run() -> void;
auto tilefetch() -> void;
auto fetch() -> void;
auto power() -> void;
auto onScanline(PPU::OAM::Object&) -> bool;
@@ -34,32 +35,36 @@ struct Object {
OAM oam;
struct IO {
bool aboveEnable;
bool belowEnable;
bool interlace;
uint1 aboveEnable;
uint1 belowEnable;
uint1 interlace;
uint3 baseSize;
uint2 nameselect;
uint3 baseSize;
uint2 nameselect;
uint16 tiledataAddress;
uint7 firstSprite;
uint7 firstSprite;
uint priority[4];
uint8 priority[4];
bool timeOver;
bool rangeOver;
uint1 timeOver;
uint1 rangeOver;
} io;
struct Latch {
uint7 firstSprite;
} latch;
struct Item {
bool valid;
uint7 index;
uint1 valid;
uint7 index;
};
struct Tile {
bool valid;
uint9 x;
uint2 priority;
uint8 palette;
uint1 hflip;
uint1 valid;
uint9 x;
uint2 priority;
uint8 palette;
uint1 hflip;
uint32 data;
};
@@ -77,7 +82,7 @@ struct Object {
struct Output {
struct Pixel {
uint priority; //0 = none (transparent)
uint8 priority; //0 = none (transparent)
uint8 palette;
} above, below;
} output;

View File

@@ -3,7 +3,7 @@
namespace SuperFamicom {
PPU ppu;
#include "main.cpp"
#include "io.cpp"
#include "background.cpp"
#include "object.cpp"
@@ -39,7 +39,13 @@ PPU::~PPU() {
}
auto PPU::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto PPU::step() -> void {
tick(2);
clock += 2;
synchronizeCPU();
}
auto PPU::step(uint clocks) -> void {
@@ -58,41 +64,6 @@ auto PPU::Enter() -> void {
}
}
auto PPU::main() -> void {
scanline();
step(28);
bg4.begin();
bg3.begin();
bg2.begin();
bg1.begin();
if(vcounter() < vdisp()) {
for(int pixel = -7; pixel <= 255; pixel++) {
bg4.run(1);
bg3.run(1);
bg2.run(1);
bg1.run(1);
step(2);
bg4.run(0);
bg3.run(0);
bg2.run(0);
bg1.run(0);
if(pixel >= 0) {
obj.run();
window.run();
screen.run();
}
step(2);
}
step(14 + 34 * 2);
obj.tilefetch();
}
step(hperiod() - hcounter());
}
auto PPU::load() -> bool {
ppu1.version = max(1, min(1, configuration.system.ppu1.version));
ppu2.version = max(1, min(3, configuration.system.ppu2.version));
@@ -213,39 +184,13 @@ auto PPU::power(bool reset) -> void {
updateVideoMode();
}
auto PPU::scanline() -> void {
if(vcounter() == 0) {
display.interlace = io.interlace;
display.overscan = io.overscan;
bg1.frame();
bg2.frame();
bg3.frame();
bg4.frame();
obj.frame();
}
bg1.scanline();
bg2.scanline();
bg3.scanline();
bg4.scanline();
obj.scanline();
window.scanline();
screen.scanline();
if(vcounter() == vdisp()) {
if(auto device = controllerPort2.device) device->latch(); //light guns
}
if(vcounter() == 240) {
scheduler.leave(Scheduler::Event::Frame);
}
}
auto PPU::refresh() -> void {
if(system.fastPPU()) {
return ppufast.refresh();
}
if(system.runAhead) return;
auto output = this->output;
auto pitch = 512;
auto width = 512;

View File

@@ -9,10 +9,19 @@ struct PPU : Thread, PPUcounter {
auto synchronizeCPU() -> void;
static auto Enter() -> void;
auto main() -> void;
auto load() -> bool;
auto power(bool reset) -> void;
//main.cpp
auto main() -> void;
noinline auto cycleObjectEvaluate() -> void;
template<uint Cycle> noinline auto cycleBackgroundFetch() -> void;
noinline auto cycleBackgroundBegin() -> void;
noinline auto cycleBackgroundBelow() -> void;
noinline auto cycleBackgroundAbove() -> void;
noinline auto cycleRenderPixel() -> void;
template<uint> auto cycle() -> void;
//io.cpp
auto latchCounters(uint hcounter, uint vcounter) -> void;
auto latchCounters() -> void;
@@ -22,6 +31,7 @@ struct PPU : Thread, PPUcounter {
private:
//ppu.cpp
alwaysinline auto step() -> void;
alwaysinline auto step(uint clocks) -> void;
//io.cpp
@@ -51,7 +61,6 @@ private:
uint vdisp;
} display;
auto scanline() -> void;
auto refresh() -> void;
struct {

View File

@@ -10,8 +10,8 @@ auto PPU::Screen::scanline() -> void {
math.above.color = paletteColor(0);
math.below.color = math.above.color;
math.above.colorEnable = !(ppu.window.io.col.aboveMask & 1);
math.below.colorEnable = !(ppu.window.io.col.belowMask & 1) && io.back.colorEnable;
math.above.colorEnable = false;
math.below.colorEnable = false;
math.transparent = true;
math.blendMode = false;
@@ -25,8 +25,8 @@ auto PPU::Screen::run() -> void {
auto belowColor = below(hires);
auto aboveColor = above();
*lineA++ = *lineB++ = ppu.lightTable[ppu.io.displayBrightness][(hires ? belowColor : aboveColor)];
*lineA++ = *lineB++ = ppu.lightTable[ppu.io.displayBrightness][(aboveColor)];
*lineA++ = *lineB++ = ppu.lightTable[ppu.io.displayBrightness][hires ? belowColor : aboveColor];
*lineA++ = *lineB++ = ppu.lightTable[ppu.io.displayBrightness][aboveColor];
}
auto PPU::Screen::below(bool hires) -> uint16 {
@@ -36,7 +36,7 @@ auto PPU::Screen::below(bool hires) -> uint16 {
if(ppu.bg1.output.below.priority) {
priority = ppu.bg1.output.below.priority;
if(io.directColor && (ppu.io.bgMode == 3 || ppu.io.bgMode == 4 || ppu.io.bgMode == 7)) {
math.below.color = directColor(ppu.bg1.output.below.palette, ppu.bg1.output.below.tile);
math.below.color = directColor(ppu.bg1.output.below.palette, ppu.bg1.output.below.paletteGroup);
} else {
math.below.color = paletteColor(ppu.bg1.output.below.palette);
}
@@ -75,7 +75,7 @@ auto PPU::Screen::above() -> uint16 {
if(ppu.bg1.output.above.priority) {
priority = ppu.bg1.output.above.priority;
if(io.directColor && (ppu.io.bgMode == 3 || ppu.io.bgMode == 4 || ppu.io.bgMode == 7)) {
math.above.color = directColor(ppu.bg1.output.above.palette, ppu.bg1.output.above.tile);
math.above.color = directColor(ppu.bg1.output.above.palette, ppu.bg1.output.above.paletteGroup);
} else {
math.above.color = paletteColor(ppu.bg1.output.above.palette);
}
@@ -149,13 +149,13 @@ auto PPU::Screen::paletteColor(uint8 palette) const -> uint15 {
return cgram[palette];
}
auto PPU::Screen::directColor(uint palette, uint tile) const -> uint15 {
auto PPU::Screen::directColor(uint8 palette, uint3 paletteGroup) const -> uint15 {
//palette = -------- BBGGGRRR
//tile = ---bgr-- --------
//group = -------- -----bgr
//output = 0BBb00GG Gg0RRRr0
return (palette << 7 & 0x6000) + (tile >> 0 & 0x1000)
+ (palette << 4 & 0x0380) + (tile >> 5 & 0x0040)
+ (palette << 2 & 0x001c) + (tile >> 9 & 0x0002);
return (palette << 7 & 0x6000) + (paletteGroup << 10 & 0x1000)
+ (palette << 4 & 0x0380) + (paletteGroup << 5 & 0x0040)
+ (palette << 2 & 0x001c) + (paletteGroup << 1 & 0x0002);
}
auto PPU::Screen::fixedColor() const -> uint15 {

View File

@@ -1,6 +1,6 @@
struct Screen {
auto scanline() -> void;
alwaysinline auto run() -> void;
auto run() -> void;
auto power() -> void;
auto below(bool hires) -> uint16;
@@ -8,7 +8,7 @@ struct Screen {
auto blend(uint x, uint y) const -> uint15;
alwaysinline auto paletteColor(uint8 palette) const -> uint15;
alwaysinline auto directColor(uint palette, uint tile) const -> uint15;
alwaysinline auto directColor(uint8 palette, uint3 paletteGroup) const -> uint15;
alwaysinline auto fixedColor() const -> uint15;
auto serialize(serializer&) -> void;
@@ -19,13 +19,13 @@ struct Screen {
uint15 cgram[256];
struct IO {
bool blendMode;
bool directColor;
uint1 blendMode;
uint1 directColor;
bool colorMode;
bool colorHalve;
uint1 colorMode;
uint1 colorHalve;
struct Layer {
bool colorEnable;
uint1 colorEnable;
} bg1, bg2, bg3, bg4, obj, back;
uint5 colorBlue;
@@ -36,11 +36,11 @@ struct Screen {
struct Math {
struct Screen {
uint15 color;
bool colorEnable;
uint1 colorEnable;
} above, below;
bool transparent;
bool blendMode;
bool colorHalve;
uint1 transparent;
uint1 blendMode;
uint1 colorHalve;
} math;
friend class PPU;

View File

@@ -39,8 +39,8 @@ auto PPU::serialize(serializer& s) -> void {
s.integer(io.oamAddress);
s.integer(io.oamPriority);
s.integer(io.bgPriority);
s.integer(io.bgMode);
s.integer(io.bgPriority);
s.integer(io.hoffsetMode7);
s.integer(io.voffsetMode7);
@@ -96,14 +96,14 @@ auto PPU::Background::serialize(serializer& s) -> void {
s.integer(latch.hoffset);
s.integer(latch.voffset);
s.integer(latch.screenAddress);
s.integer(output.above.priority);
s.integer(output.above.palette);
s.integer(output.above.tile);
s.integer(output.above.paletteGroup);
s.integer(output.below.priority);
s.integer(output.below.palette);
s.integer(output.below.tile);
s.integer(output.below.paletteGroup);
s.integer(mosaic.size);
s.integer(mosaic.enable);
@@ -111,18 +111,29 @@ auto PPU::Background::serialize(serializer& s) -> void {
s.integer(mosaic.hcounter);
s.integer(mosaic.voffset);
s.integer(mosaic.hoffset);
s.integer(mosaic.pixel.priority);
s.integer(mosaic.pixel.palette);
s.integer(mosaic.pixel.tile);
s.integer(mosaic.pixel.paletteGroup);
s.integer(x);
s.integer(y);
s.integer(tileCounter);
s.integer(tile);
s.integer(priority);
s.integer(paletteNumber);
s.integer(paletteIndex);
s.array(data);
s.integer(opt.hoffset);
s.integer(opt.voffset);
for(auto& tile : tiles) {
s.integer(tile.address);
s.integer(tile.character);
s.integer(tile.palette);
s.integer(tile.paletteGroup);
s.integer(tile.priority);
s.integer(tile.hmirror);
s.integer(tile.vmirror);
s.array(tile.data);
}
s.integer(nameTableIndex);
s.integer(characterIndex);
s.integer(renderingIndex);
s.integer(pixelCounter);
}
auto PPU::Object::serialize(serializer& s) -> void {
@@ -152,6 +163,8 @@ auto PPU::Object::serialize(serializer& s) -> void {
s.integer(io.timeOver);
s.integer(io.rangeOver);
s.integer(latch.firstSprite);
s.integer(t.x);
s.integer(t.y);

View File

@@ -28,11 +28,12 @@ namespace SuperFamicom {
extern Cheat cheat;
struct Scheduler {
enum class Mode : uint { Run, SynchronizeCPU, SynchronizeAll } mode;
enum class Event : uint { Frame, Synchronize } event;
enum class Mode : uint { Run, Synchronize } mode;
enum class Event : uint { Frame, Synchronized, Desynchronized } event;
cothread_t host = nullptr;
cothread_t active = nullptr;
bool desynchronized = false;
auto enter() -> void {
host = co_active();
@@ -45,16 +46,41 @@ namespace SuperFamicom {
co_switch(host);
}
auto synchronize() -> void {
if(mode == Mode::SynchronizeAll) leave(Event::Synchronize);
auto resume(cothread_t thread) -> void {
if(mode == Mode::Synchronize) desynchronized = true;
co_switch(thread);
}
inline auto synchronizing() const -> bool {
return mode == Mode::Synchronize;
}
inline auto synchronize() -> void {
if(mode == Mode::Synchronize) {
if(desynchronized) {
desynchronized = false;
leave(Event::Desynchronized);
} else {
leave(Event::Synchronized);
}
}
}
inline auto desynchronize() -> void {
desynchronized = true;
}
};
extern Scheduler scheduler;
struct Thread {
enum : uint { Size = 4_KiB * sizeof(void*) };
auto create(auto (*entrypoint)() -> void, uint frequency_) -> void {
if(thread) co_delete(thread);
thread = co_create(65536 * sizeof(void*), entrypoint);
if(!thread) {
thread = co_create(Thread::Size, entrypoint);
} else {
thread = co_derive(thread, Thread::Size, entrypoint);
}
frequency = frequency_;
clock = 0;
}
@@ -68,6 +94,29 @@ namespace SuperFamicom {
s.integer(clock);
}
auto serializeStack(serializer& s) -> void {
static uint8_t stack[Thread::Size];
bool active = co_active() == thread;
if(s.mode() == serializer::Size) {
s.array(stack, Thread::Size);
s.boolean(active);
}
if(s.mode() == serializer::Load) {
s.array(stack, Thread::Size);
s.boolean(active);
memory::copy(thread, stack, Thread::Size);
if(active) scheduler.active = thread;
}
if(s.mode() == serializer::Save) {
memory::copy(stack, thread, Thread::Size);
s.array(stack, Thread::Size);
s.boolean(active);
}
}
cothread_t thread = nullptr;
uint32_t frequency = 0;
int64_t clock = 0;

View File

@@ -13,11 +13,14 @@ BSMemory::BSMemory() {
}
auto BSMemory::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto BSMemory::Enter() -> void {
while(true) scheduler.synchronize(), bsmemory.main();
while(true) {
scheduler.synchronize();
bsmemory.main();
}
}
auto BSMemory::main() -> void {

View File

@@ -14,10 +14,20 @@ auto SMP::idle() -> void {
}
auto SMP::read(uint16 address) -> uint8 {
wait(address);
uint8 data = readRAM(address);
if((address & 0xfff0) == 0x00f0) data = readIO(address);
return data;
//Kishin Douji Zenki - Tenchi Meidou requires bus hold delays on CPU I/O reads.
//smp_mem_access_times requires no bus hold delays on APU RAM reads.
if((address & 0xfffc) == 0x00f4) {
wait(address, 1);
uint8 data = readRAM(address);
if((address & 0xfff0) == 0x00f0) data = readIO(address);
wait(address, 1);
return data;
} else {
wait(address, 0);
uint8 data = readRAM(address);
if((address & 0xfff0) == 0x00f0) data = readIO(address);
return data;
}
}
auto SMP::write(uint16 address, uint8 data) -> void {

View File

@@ -9,7 +9,7 @@ SMP smp;
#include "serialization.cpp"
auto SMP::synchronizeCPU() -> void {
if(clock >= 0 && scheduler.mode != Scheduler::Mode::SynchronizeAll) co_switch(cpu.thread);
if(clock >= 0) scheduler.resume(cpu.thread);
}
auto SMP::synchronizeDSP() -> void {

View File

@@ -1,7 +1,7 @@
//Sony CXP1100Q-1
struct SMP : Processor::SPC700, Thread {
inline auto synchronizing() const -> bool override { return scheduler.mode == Scheduler::Mode::SynchronizeAll; }
inline auto synchronizing() const -> bool override { return scheduler.synchronizing(); }
//io.cpp
auto portRead(uint2 port) const -> uint8;
@@ -90,8 +90,8 @@ private:
Timer<128> timer1;
Timer< 16> timer2;
inline auto wait(maybe<uint16> address = nothing) -> void;
inline auto waitIdle(maybe<uint16> address = nothing) -> void;
inline auto wait(maybe<uint16> address = nothing, bool half = false) -> void;
inline auto waitIdle(maybe<uint16> address = nothing, bool half = false) -> void;
inline auto step(uint clocks) -> void;
inline auto stepIdle(uint clocks) -> void;
inline auto stepTimers(uint clocks) -> void;

View File

@@ -6,7 +6,7 @@
//sometimes the SMP will run far slower than expected
//other times (and more likely), the SMP will deadlock until the system is reset
//the timers are not affected by this and advance by their expected values
auto SMP::wait(maybe<uint16> addr) -> void {
auto SMP::wait(maybe<uint16> addr, bool half) -> void {
static const uint cycleWaitStates[4] = {2, 4, 10, 20};
static const uint timerWaitStates[4] = {2, 4, 8, 16};
@@ -15,11 +15,11 @@ auto SMP::wait(maybe<uint16> addr) -> void {
else if((*addr & 0xfff0) == 0x00f0) waitStates = io.internalWaitStates; //IO registers
else if(*addr >= 0xffc0 && io.iplromEnable) waitStates = io.internalWaitStates; //IPLROM
step(cycleWaitStates[waitStates]);
stepTimers(timerWaitStates[waitStates]);
step(cycleWaitStates[waitStates] >> half);
stepTimers(timerWaitStates[waitStates] >> half);
}
auto SMP::waitIdle(maybe<uint16> addr) -> void {
auto SMP::waitIdle(maybe<uint16> addr, bool half) -> void {
static const uint cycleWaitStates[4] = {2, 4, 10, 20};
static const uint timerWaitStates[4] = {2, 4, 8, 16};
@@ -28,22 +28,16 @@ auto SMP::waitIdle(maybe<uint16> addr) -> void {
else if((*addr & 0xfff0) == 0x00f0) waitStates = io.internalWaitStates; //IO registers
else if(*addr >= 0xffc0 && io.iplromEnable) waitStates = io.internalWaitStates; //IPLROM
stepIdle(cycleWaitStates[waitStates]);
stepTimers(timerWaitStates[waitStates]);
stepIdle(cycleWaitStates[waitStates] >> half);
stepTimers(timerWaitStates[waitStates] >> half);
}
auto SMP::step(uint clocks) -> void {
clock += clocks * (uint64_t)cpu.frequency;
dsp.clock -= clocks;
synchronizeDSP();
#if defined(DEBUGGER)
synchronizeCPU();
#else
//forcefully sync S-SMP to S-CPU in case chips are not communicating
//sync if S-SMP is more than 24 samples ahead of S-CPU
if(clock > +(768 * 24 * (int64_t)24'000'000)) synchronizeCPU();
#endif
//forcefully sync SMP to CPU in case chips are not communicating
if(clock > 768 * 24 * (int64_t)24'000'000) synchronizeCPU();
}
auto SMP::stepIdle(uint clocks) -> void {

View File

@@ -1,48 +1,55 @@
auto System::serialize() -> serializer {
serializer s(serializeSize);
auto System::serialize(bool synchronize) -> serializer {
//deterministic serialization (synchronize=false) is only possible with select libco methods
if(!co_serializable()) synchronize = true;
if(!information.serializeSize[synchronize]) return {}; //should never occur
if(synchronize) runToSave();
uint signature = 0x31545342;
uint serializeSize = information.serializeSize[synchronize];
char version[16] = {};
char description[512] = {};
memory::copy(&version, (const char*)Emulator::SerializerVersion, Emulator::SerializerVersion.size());
serializer s(serializeSize);
s.integer(signature);
s.integer(serializeSize);
s.array(version);
s.array(description);
s.boolean(synchronize);
s.boolean(hacks.fastPPU);
serializeAll(s);
serializeAll(s, synchronize);
return s;
}
auto System::unserialize(serializer& s) -> bool {
uint signature = 0;
uint serializeSize = 0;
char version[16] = {};
char description[512] = {};
bool synchronize = false;
bool fastPPU = false;
s.integer(signature);
s.integer(serializeSize);
s.array(version);
s.array(description);
s.boolean(synchronize);
s.boolean(fastPPU);
if(signature != 0x31545342) return false;
if(serializeSize != information.serializeSize[synchronize]) return false;
if(string{version} != Emulator::SerializerVersion) return false;
if(fastPPU != hacks.fastPPU) return false;
s.boolean(hacks.fastPPU);
power(/* reset = */ false);
serializeAll(s);
serializeInit(); //hacks.fastPPU setting changes serializeSize
if(synchronize) power(/* reset = */ false);
serializeAll(s, synchronize);
return true;
}
//internal
auto System::serialize(serializer& s) -> void {
}
auto System::serializeAll(serializer& s) -> void {
system.serialize(s);
auto System::serializeAll(serializer& s, bool synchronize) -> void {
random.serialize(s);
cartridge.serialize(s);
cpu.serialize(s);
@@ -79,24 +86,34 @@ auto System::serializeAll(serializer& s) -> void {
controllerPort1.serialize(s);
controllerPort2.serialize(s);
expansionPort.serialize(s);
if(!synchronize) {
cpu.serializeStack(s);
smp.serializeStack(s);
ppu.serializeStack(s);
for(auto coprocessor : cpu.coprocessors) {
coprocessor->serializeStack(s);
}
}
}
//perform dry-run state save:
//determines exactly how many bytes are needed to save state for this cartridge,
//as amount varies per game (eg different RAM sizes, special chips, etc.)
auto System::serializeInit() -> void {
auto System::serializeInit(bool synchronize) -> uint {
serializer s;
uint signature = 0;
uint serializeSize = 0;
char version[16] = {};
char description[512] = {};
s.integer(signature);
s.integer(serializeSize);
s.array(version);
s.array(description);
s.boolean(synchronize);
s.boolean(hacks.fastPPU);
serializeAll(s);
serializeSize = s.size();
serializeAll(s, synchronize);
return s.size();
}

View File

@@ -11,37 +11,100 @@ Cheat cheat;
auto System::run() -> void {
scheduler.mode = Scheduler::Mode::Run;
scheduler.enter();
if(scheduler.event == Scheduler::Event::Frame) {
ppu.refresh();
//refresh all cheat codes once per frame
Memory::GlobalWriteEnable = true;
for(auto& code : cheat.codes) {
if(code.enable) {
bus.write(code.address, code.data);
}
}
Memory::GlobalWriteEnable = false;
}
if(scheduler.event == Scheduler::Event::Frame) frameEvent();
}
auto System::runToSave() -> void {
scheduler.mode = Scheduler::Mode::SynchronizeCPU;
while(true) { scheduler.enter(); if(scheduler.event == Scheduler::Event::Synchronize) break; }
auto method = configuration.system.serialization.method;
scheduler.mode = Scheduler::Mode::SynchronizeAll;
scheduler.active = smp.thread;
while(true) { scheduler.enter(); if(scheduler.event == Scheduler::Event::Synchronize) break; }
//these games will periodically deadlock when using "Fast" synchronization
if(cartridge.headerTitle() == "Star Ocean") method = "Strict";
if(cartridge.headerTitle() == "TALES OF PHANTASIA") method = "Strict";
scheduler.mode = Scheduler::Mode::SynchronizeAll;
scheduler.active = ppu.thread;
while(true) { scheduler.enter(); if(scheduler.event == Scheduler::Event::Synchronize) break; }
//fallback in case of unrecognized method specified
if(method != "Fast" && method != "Strict") method = "Fast";
for(auto coprocessor : cpu.coprocessors) {
scheduler.mode = Scheduler::Mode::SynchronizeAll;
scheduler.active = coprocessor->thread;
while(true) { scheduler.enter(); if(scheduler.event == Scheduler::Event::Synchronize) break; }
scheduler.mode = Scheduler::Mode::Synchronize;
if(method == "Fast") runToSaveFast();
if(method == "Strict") runToSaveStrict();
scheduler.mode = Scheduler::Mode::Run;
scheduler.active = cpu.thread;
}
auto System::runToSaveFast() -> void {
//run the emulator normally until the CPU thread naturally hits a synchronization point
while(true) {
scheduler.enter();
if(scheduler.event == Scheduler::Event::Frame) frameEvent();
if(scheduler.event == Scheduler::Event::Synchronized) {
if(scheduler.active != cpu.thread) continue;
break;
}
if(scheduler.event == Scheduler::Event::Desynchronized) continue;
}
//ignore any desynchronization events to force all other threads to their synchronization points
auto synchronize = [&](cothread_t thread) -> void {
scheduler.active = thread;
while(true) {
scheduler.enter();
if(scheduler.event == Scheduler::Event::Frame) frameEvent();
if(scheduler.event == Scheduler::Event::Synchronized) break;
if(scheduler.event == Scheduler::Event::Desynchronized) continue;
}
};
synchronize(smp.thread);
synchronize(ppu.thread);
for(auto coprocessor : cpu.coprocessors) {
synchronize(coprocessor->thread);
}
}
auto System::runToSaveStrict() -> void {
//run every thread until it cleanly hits a synchronization point
//if it fails, start resynchronizing every thread again
auto synchronize = [&](cothread_t thread) -> bool {
scheduler.active = thread;
while(true) {
scheduler.enter();
if(scheduler.event == Scheduler::Event::Frame) frameEvent();
if(scheduler.event == Scheduler::Event::Synchronized) break;
if(scheduler.event == Scheduler::Event::Desynchronized) return false;
}
return true;
};
while(true) {
//SMP thread is synchronized twice to ensure the CPU and SMP are closely aligned:
//this is extremely critical for Tales of Phantasia and Star Ocean.
if(!synchronize(smp.thread)) continue;
if(!synchronize(cpu.thread)) continue;
if(!synchronize(smp.thread)) continue;
if(!synchronize(ppu.thread)) continue;
bool synchronized = true;
for(auto coprocessor : cpu.coprocessors) {
if(!synchronize(coprocessor->thread)) { synchronized = false; break; }
}
if(!synchronized) continue;
break;
}
}
auto System::frameEvent() -> void {
ppu.refresh();
//refresh all cheat codes once per frame
Memory::GlobalWriteEnable = true;
for(auto& code : cheat.codes) {
if(code.enable) {
bus.write(code.address, code.data);
}
}
Memory::GlobalWriteEnable = false;
}
auto System::load(Emulator::Interface* interface) -> bool {
@@ -63,12 +126,18 @@ auto System::load(Emulator::Interface* interface) -> bool {
information.cpuFrequency = Emulator::Constants::Colorburst::PAL * 4.8;
}
if(configuration.hacks.hotfixes) {
//due to poor programming, Rendering Ranger R2 will rarely lock up at 32040 * 768hz.
if(cartridge.headerTitle() == "RENDERING RANGER R2") {
information.apuFrequency = 32000.0 * 768.0;
}
}
if(cartridge.has.ICD) {
if(!icd.load()) return false;
}
if(cartridge.has.BSMemorySlot) bsmemory.load();
serializeInit();
this->interface = interface;
return information.loaded = true;
}
@@ -165,6 +234,9 @@ auto System::power(bool reset) -> void {
controllerPort1.connect(settings.controllerPort1);
controllerPort2.connect(settings.controllerPort2);
expansionPort.connect(settings.expansionPort);
information.serializeSize[0] = serializeInit(0);
information.serializeSize[1] = serializeInit(1);
}
}

View File

@@ -10,6 +10,9 @@ struct System {
auto run() -> void;
auto runToSave() -> void;
auto runToSaveFast() -> void;
auto runToSaveStrict() -> void;
auto frameEvent() -> void;
auto load(Emulator::Interface*) -> bool;
auto save() -> void;
@@ -17,11 +20,12 @@ struct System {
auto power(bool reset) -> void;
//serialization.cpp
auto serialize() -> serializer;
auto serialize(bool synchronize) -> serializer;
auto unserialize(serializer&) -> bool;
uint frameSkip = 0;
uint frameCounter = 0;
bool runAhead = 0;
private:
Emulator::Interface* interface = nullptr;
@@ -31,17 +35,15 @@ private:
Region region = Region::NTSC;
double cpuFrequency = Emulator::Constants::Colorburst::NTSC * 6.0;
double apuFrequency = 32040.0 * 768.0;
uint serializeSize[2] = {0, 0};
} information;
struct Hacks {
bool fastPPU = false;
} hacks;
uint serializeSize = 0;
auto serialize(serializer&) -> void;
auto serializeAll(serializer&) -> void;
auto serializeInit() -> void;
auto serializeAll(serializer&, bool synchronize) -> void;
auto serializeInit(bool synchronize) -> uint;
friend class Cartridge;
};

View File

@@ -27,9 +27,11 @@ ifeq ($(platform),macos)
mkdir -p out/$(name).app/Contents/MacOS/
mkdir -p out/$(name).app/Contents/MacOS/Database/
mkdir -p out/$(name).app/Contents/MacOS/Firmware/
mkdir -p out/$(name).app/Contents/MacOS/Shaders/
mkdir -p out/$(name).app/Contents/Resources/
mv out/$(name) out/$(name).app/Contents/MacOS/$(name)
cp Database/* out/$(name).app/Contents/MacOS/Database/
cp -r ../shaders/* out/$(name).app/Contents/macOS/Shaders/
cp $(ui)/resource/$(name).plist out/$(name).app/Contents/Info.plist
sips -s format icns $(ui)/resource/$(name).png --out out/$(name).app/Contents/Resources/$(name).icns
endif
@@ -37,13 +39,14 @@ endif
verbose: hiro.verbose ruby.verbose nall.verbose all;
install: all
ifeq ($(shell id -un),root)
ifeq ($(platform),windows)
else ifeq ($(shell id -un),root)
$(error "make install should not be run as root")
else ifeq ($(platform),windows)
else ifeq ($(platform),macos)
mkdir -p ~/Library/Application\ Support/$(name)/
mkdir -p ~/Library/Application\ Support/$(name)/Database/
mkdir -p ~/Library/Application\ Support/$(name)/Firmware/
mkdir -p ~/Library/Application\ Support/$(name)/Shaders/
cp -R out/$(name).app /Applications/$(name).app
else ifneq ($(filter $(platform),linux bsd),)
mkdir -p $(prefix)/bin/
@@ -52,18 +55,18 @@ else ifneq ($(filter $(platform),linux bsd),)
mkdir -p $(prefix)/share/$(name)/
mkdir -p $(prefix)/share/$(name)/Database/
mkdir -p $(prefix)/share/$(name)/Firmware/
mkdir -p $(prefix)/share/$(name)/Locale/
mkdir -p $(prefix)/share/$(name)/Shaders/
cp out/$(name) $(prefix)/bin/$(name)
cp $(ui)/resource/$(name).desktop $(prefix)/share/applications/$(name).desktop
cp $(ui)/resource/$(name).png $(prefix)/share/icons/$(name).png
cp Database/* $(prefix)/share/$(name)/Database/
cp Locale/* $(prefix)/share/$(name)/Locale/
cp -r ../shaders/* $(prefix)/share/$(name)/Shaders/
endif
uninstall:
ifeq ($(shell id -un),root)
ifeq ($(platform),windows)
else ifeq ($(shell id -un),root)
$(error "make uninstall should not be run as root")
else ifeq ($(platform),windows)
else ifeq ($(platform),macos)
rm -rf /Applications/$(name).app
else ifneq ($(filter $(platform),linux bsd),)

View File

@@ -52,7 +52,7 @@ auto nall::main(Arguments arguments) -> void {
emulator = new SuperFamicom::Interface;
program.create();
if(Emulator::Version.find(".") && settings.general.betaWarning) {
if(Emulator::Version.find(".") && settings.general.betaWarning && 0) {
MessageDialog dialog;
dialog.setTitle(Emulator::Name);
dialog.setText(

View File

@@ -8,13 +8,15 @@ auto InputManager::bindHotkeys() -> void {
static int stateSlot = 1;
static double frequency = 48000.0;
static double volume = 0.0;
static bool fastForwarding = false;
static bool rewinding = false;
hotkeys.append(InputHotkey("Toggle Fullscreen Mode").onPress([] {
hotkeys.append(InputHotkey("Toggle Fullscreen").onPress([] {
program.toggleVideoFullScreen();
}));
hotkeys.append(InputHotkey("Toggle Pseudo-Fullscreen").onPress([] {
program.toggleVideoPseudoFullScreen();
}));
hotkeys.append(InputHotkey("Toggle Mouse Capture").onPress([] {
input.acquired() ? input.release() : input.acquire();
}));
@@ -23,9 +25,13 @@ auto InputManager::bindHotkeys() -> void {
cheatEditor.enableCheats.setChecked(!cheatEditor.enableCheats.checked()).doToggle();
}));
hotkeys.append(InputHotkey("Toggle Mute").onPress([] {
presentation.muteAudio.setChecked(!presentation.muteAudio.checked()).doToggle();
}));
hotkeys.append(InputHotkey("Rewind").onPress([&] {
if(!emulator->loaded() || fastForwarding) return;
rewinding = true;
if(!emulator->loaded() || program.fastForwarding) return;
program.rewinding = true;
if(program.rewind.frequency == 0) {
program.showMessage("Please enable rewind support in Settings->Emulator first");
} else {
@@ -38,7 +44,7 @@ auto InputManager::bindHotkeys() -> void {
Emulator::audio.setVolume(volume * 0.65);
}
}).onRelease([&] {
rewinding = false;
program.rewinding = false;
if(!emulator->loaded()) return;
program.rewindMode(Program::Rewind::Mode::Playing);
program.mute &= ~Program::Mute::Rewind;
@@ -61,12 +67,12 @@ auto InputManager::bindHotkeys() -> void {
program.loadState("Quick/Redo");
}));
hotkeys.append(InputHotkey("Increment State Slot").onPress([&] {
hotkeys.append(InputHotkey("Decrement State Slot").onPress([&] {
if(--stateSlot < 1) stateSlot = 9;
program.showMessage({"Selected state slot ", stateSlot});
}));
hotkeys.append(InputHotkey("Decrement State Slot").onPress([&] {
hotkeys.append(InputHotkey("Increment State Slot").onPress([&] {
if(++stateSlot > 9) stateSlot = 1;
program.showMessage({"Selected state slot ", stateSlot});
}));
@@ -76,8 +82,8 @@ auto InputManager::bindHotkeys() -> void {
}));
hotkeys.append(InputHotkey("Fast Forward").onPress([] {
if(!emulator->loaded() || rewinding) return;
fastForwarding = true;
if(!emulator->loaded() || program.rewinding) return;
program.fastForwarding = true;
emulator->setFrameSkip(emulator->configuration("Hacks/PPU/Fast") == "true" ? settings.fastForward.frameSkip : 0);
video.setBlocking(false);
audio.setBlocking(settings.fastForward.limiter != 0);
@@ -93,7 +99,7 @@ auto InputManager::bindHotkeys() -> void {
Emulator::audio.setVolume(volume * 0.65);
}
}).onRelease([] {
fastForwarding = false;
program.fastForwarding = false;
if(!emulator->loaded()) return;
emulator->setFrameSkip(0);
video.setBlocking(settings.video.blocking);
@@ -119,10 +125,27 @@ auto InputManager::bindHotkeys() -> void {
if(!presentation.frameAdvance.checked()) {
//start frame advance if not currently frame advancing
presentation.frameAdvance.setChecked().doActivate();
} else {
//advance to the next video frame otherwise
program.frameAdvanceLock = false;
}
//advance one frame, even if we were currently paused when starting frame advance mode
program.frameAdvanceLock = false;
}));
hotkeys.append(InputHotkey("Decrease HD Mode 7").onPress([] {
int index = enhancementSettings.mode7Scale.selected().offset() - 1;
if(index < 0) return;
enhancementSettings.mode7Scale.item(index).setSelected();
enhancementSettings.mode7Scale.doChange();
}));
hotkeys.append(InputHotkey("Increase HD Mode 7").onPress([] {
int index = enhancementSettings.mode7Scale.selected().offset() + 1;
if(index >= enhancementSettings.mode7Scale.itemCount()) return;
enhancementSettings.mode7Scale.item(index).setSelected();
enhancementSettings.mode7Scale.doChange();
}));
hotkeys.append(InputHotkey("Toggle Supersampling").onPress([] {
enhancementSettings.mode7Supersample.setChecked(!enhancementSettings.mode7Supersample.checked()).doToggle();
}));
hotkeys.append(InputHotkey("Reset Emulation").onPress([] {

View File

@@ -170,13 +170,23 @@ auto InputMapping::Binding::icon() -> image {
}
auto InputMapping::Binding::name() -> string {
if(device && device->isKeyboard()) {
//if ruby drivers cannot report accurate vendor/product IDs (eg SDL),
//and the user closes the emulator, changes gamepads, and restarts it,
//it is possible the wrong device will now be mapped to the input IDs.
//that could potentially make the group/input IDs go out of bounds.
if(!device) return {};
if(group >= device->size()) return {};
if(input >= device->group(group).size()) return {};
if(device->isKeyboard()) {
return device->group(group).input(input).name();
}
if(device && device->isMouse()) {
if(device->isMouse()) {
return device->group(group).input(input).name();
}
if(device && device->isJoypad()) {
if(device->isJoypad()) {
string name{Hash::CRC16(string{device->id()}).digest().upcase()};
name.append(" ", device->group(group).name());
name.append(" ", device->group(group).input(input).name());
@@ -185,6 +195,7 @@ auto InputMapping::Binding::name() -> string {
if(qualifier == Qualifier::Rumble) name.append(" Rumble");
return name;
}
return {};
}

View File

@@ -48,7 +48,7 @@ auto Presentation::create() -> void {
settings.video.overscan = showOverscanArea.checked();
resizeWindow();
});
blurEmulation.setText("Blur Emulation").setChecked(settings.video.blur).onToggle([&] {
blurEmulation.setText("Hires Blur Emulation").setChecked(settings.video.blur).onToggle([&] {
settings.video.blur = blurEmulation.checked();
emulator->configure("Video/BlurEmulation", settings.video.blur);
}).doToggle();
@@ -115,29 +115,29 @@ auto Presentation::create() -> void {
saveState.setIcon(Icon::Media::Record).setText("Save State");
for(uint index : range(QuickStates)) {
MenuItem item{&saveState};
item.setProperty("name", {"Quick/Slot ", 1 + index});
item.setProperty("title", {"Slot ", 1 + index});
item.setAttribute("name", {"Quick/Slot ", 1 + index});
item.setAttribute("title", {"Slot ", 1 + index});
item.setText({"Slot ", 1 + index});
item.onActivate([=] { program.saveState({"Quick/Slot ", 1 + index}); });
}
loadState.setIcon(Icon::Media::Rewind).setText("Load State");
for(uint index : range(QuickStates)) {
MenuItem item{&loadState};
item.setProperty("name", {"Quick/Slot ", 1 + index});
item.setProperty("title", {"Slot ", 1 + index});
item.setAttribute("name", {"Quick/Slot ", 1 + index});
item.setAttribute("title", {"Slot ", 1 + index});
item.setText({"Slot ", 1 + index});
item.onActivate([=] { program.loadState({"Quick/Slot ", 1 + index}); });
}
loadState.append(MenuSeparator());
loadState.append(MenuItem()
.setProperty("name", "Quick/Undo")
.setProperty("title", "Undo Last Save")
.setAttribute("name", "Quick/Undo")
.setAttribute("title", "Undo Last Save")
.setIcon(Icon::Edit::Undo).setText("Undo Last Save").onActivate([&] {
program.loadState("Quick/Undo");
}));
loadState.append(MenuItem()
.setProperty("name", "Quick/Redo")
.setProperty("title", "Redo Last Undo")
.setAttribute("name", "Quick/Redo")
.setAttribute("title", "Redo Last Undo")
.setIcon(Icon::Edit::Redo).setText("Redo Last Undo").onActivate([&] {
program.loadState("Quick/Redo");
}));
@@ -149,11 +149,11 @@ auto Presentation::create() -> void {
}
}));
speedMenu.setIcon(Icon::Device::Clock).setText("Speed").setEnabled(!settings.video.blocking && settings.audio.blocking);
speedSlowest.setText("50% (Slowest)").setProperty("multiplier", "2.0").onActivate([&] { program.updateAudioFrequency(); });
speedSlow.setText("75% (Slow)").setProperty("multiplier", "1.333").onActivate([&] { program.updateAudioFrequency(); });
speedNormal.setText("100% (Normal)").setProperty("multiplier", "1.0").onActivate([&] { program.updateAudioFrequency(); });
speedFast.setText("150% (Fast)").setProperty("multiplier", "0.667").onActivate([&] { program.updateAudioFrequency(); });
speedFastest.setText("200% (Fastest)").setProperty("multiplier", "0.5").onActivate([&] { program.updateAudioFrequency(); });
speedSlowest.setText("50% (Slowest)").setAttribute("multiplier", "2.0").onActivate([&] { program.updateAudioFrequency(); });
speedSlow.setText("75% (Slow)").setAttribute("multiplier", "1.333").onActivate([&] { program.updateAudioFrequency(); });
speedNormal.setText("100% (Normal)").setAttribute("multiplier", "1.0").onActivate([&] { program.updateAudioFrequency(); });
speedFast.setText("150% (Fast)").setAttribute("multiplier", "0.667").onActivate([&] { program.updateAudioFrequency(); });
speedFastest.setText("200% (Fastest)").setAttribute("multiplier", "0.5").onActivate([&] { program.updateAudioFrequency(); });
runMenu.setIcon(Icon::Media::Play).setText("Run Mode");
runEmulation.setText("Normal").onActivate([&] {
});
@@ -179,7 +179,7 @@ auto Presentation::create() -> void {
helpMenu.setText(tr("Help"));
documentation.setIcon(Icon::Application::Browser).setText({tr("Documentation"), " ..."}).onActivate([&] {
invoke("https://doc.byuu.org/bsnes");
invoke("https://byuu.org/doc/bsnes");
});
aboutSameBoy.setIcon(Icon::Prompt::Question).setText({tr("About SameBoy"), " ..."}).onActivate([&] {
AboutDialog()
@@ -206,20 +206,20 @@ auto Presentation::create() -> void {
.show();
});
viewport.setFocusable();
viewport.setFocusable(false); //true would also capture Alt, which breaks keyboard menu navigation
viewport.setDroppable();
viewport.onDrop([&](vector<string> locations) {
if(!locations) return;
program.gameQueue = {};
program.gameQueue.append({"Auto;", locations.first()});
program.load();
setFocused();
});
viewport.onDrop([&](auto locations) { onDrop(locations); });
iconSpacer.setColor({0, 0, 0});
iconSpacer.setDroppable();
iconSpacer.onDrop([&](auto locations) { onDrop(locations); });
iconLayout.setAlignment(0.0).setCollapsible();
image icon{Resource::Icon};
icon.alphaBlend(0x000000);
iconCanvas.setIcon(icon);
iconCanvas.setDroppable();
iconCanvas.onDrop([&](auto locations) { onDrop(locations); });
if(!settings.general.statusBar) layout.remove(statusLayout);
@@ -265,6 +265,14 @@ auto Presentation::create() -> void {
#endif
}
auto Presentation::onDrop(vector<string> locations) -> void {
if(!locations) return;
program.gameQueue = {};
program.gameQueue.append({"Auto;", locations.first()});
program.load();
setFocused();
}
auto Presentation::updateProgramIcon() -> void {
presentation.iconLayout.setVisible(!emulator->loaded() && !settings.video.snow);
presentation.layout.resize();
@@ -335,7 +343,7 @@ auto Presentation::updateDeviceMenu() -> void {
if(port.name == "Expansion Port" && device.name == "21fx") continue;
MenuRadioItem item{menu};
item.setProperty("deviceID", device.id);
item.setAttribute("deviceID", device.id);
item.setText(tr(device.name));
item.onActivate([=] {
settings(path).setValue(device.name);
@@ -363,7 +371,7 @@ auto Presentation::updateDeviceSelections() -> void {
auto deviceID = emulator->connected(port.id);
for(auto& action : menu->actions()) {
if(auto item = action.cast<MenuRadioItem>()) {
if(item.property("deviceID").natural() == deviceID) {
if(item.attribute("deviceID").natural() == deviceID) {
item.setChecked();
break;
}
@@ -385,7 +393,7 @@ auto Presentation::updateSizeMenu() -> void {
uint multipliers = max(1, height / 240);
for(uint multiplier : range(1, multipliers + 1)) {
MenuRadioItem item{&sizeMenu};
item.setProperty("multiplier", multiplier);
item.setAttribute("multiplier", multiplier);
item.setText({multiplier, "x (", 240 * multiplier, "p)"});
item.onActivate([=] {
settings.video.multiplier = multiplier;
@@ -394,7 +402,7 @@ auto Presentation::updateSizeMenu() -> void {
sizeGroup.append(item);
}
for(auto item : sizeGroup.objects<MenuRadioItem>()) {
if(settings.video.multiplier == item.property("multiplier").natural()) {
if(settings.video.multiplier == item.attribute("multiplier").natural()) {
item.setChecked();
}
}
@@ -413,11 +421,11 @@ auto Presentation::updateStateMenus() -> void {
for(auto& action : saveState.actions()) {
if(auto item = action.cast<MenuItem>()) {
if(auto name = item.property("name")) {
if(auto name = item.attribute("name")) {
if(auto offset = states.find([&](auto& state) { return state.name == name; })) {
item.setText({item.property("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
item.setText({item.attribute("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
} else {
item.setText({item.property("title"), " (empty)"});
item.setText({item.attribute("title"), " (empty)"});
}
}
}
@@ -425,13 +433,13 @@ auto Presentation::updateStateMenus() -> void {
for(auto& action : loadState.actions()) {
if(auto item = action.cast<MenuItem>()) {
if(auto name = item.property("name")) {
if(auto name = item.attribute("name")) {
if(auto offset = states.find([&](auto& state) { return state.name == name; })) {
item.setEnabled(true);
item.setText({item.property("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
item.setText({item.attribute("title"), " (", chrono::local::datetime(states[*offset].date), ")"});
} else {
item.setEnabled(false);
item.setText({item.property("title"), " (empty)"});
item.setText({item.attribute("title"), " (empty)"});
}
}
}
@@ -530,7 +538,7 @@ auto Presentation::updateShaders() -> void {
});
shaders.append(blur);
auto location = locate("shaders/");
auto location = locate("Shaders/");
if(settings.video.driver == "OpenGL 3.2") {
for(auto shader : directory::folders(location, "*.shader")) {

View File

@@ -5,6 +5,7 @@ struct Presentation : Window {
enum : uint { RecentGames = 9, QuickStates = 9 };
enum : uint { StatusHeight = 24 };
auto onDrop(vector<string> locations) -> void;
auto updateProgramIcon() -> void;
auto updateStatusIcon() -> void;
auto resizeWindow() -> void;
@@ -126,7 +127,7 @@ struct Presentation : Window {
HorizontalLayout viewportLayout{&layout, Size{~0, ~0}, 0};
Viewport viewport{&viewportLayout, Size{~0, ~0}, 0};
VerticalLayout iconLayout{&viewportLayout, Size{0, ~0}, 0};
Widget iconSpacer{&iconLayout, Size{144, ~0}, 0};
Canvas iconSpacer{&iconLayout, Size{144, ~0}, 0};
Canvas iconCanvas{&iconLayout, Size{128, 128}, 0};
HorizontalLayout statusLayout{&layout, Size{~0, StatusHeight}, 0};
Label spacerIcon{&statusLayout, Size{8, ~0}, 0};

View File

@@ -56,7 +56,7 @@ auto Program::updateAudioFrequency() -> void {
double frequency = settings.audio.frequency + settings.audio.skew;
if(!settings.video.blocking && settings.audio.blocking) {
for(auto item : presentation.speedGroup.objects<MenuRadioItem>()) {
if(item.checked()) frequency *= item.property("multiplier").real();
if(item.checked()) frequency *= item.attribute("multiplier").real();
}
}
Emulator::audio.setFrequency(frequency);
@@ -74,6 +74,6 @@ auto Program::updateAudioEffects() -> void {
double volume = settings.audio.volume * 0.01;
Emulator::audio.setVolume(volume);
double balance = max(-1.0, min(+1.0, (settings.audio.balance - 50) / 50.0));
double balance = max(-1.0, min(+1.0, ((int)settings.audio.balance - 50) / 50.0));
Emulator::audio.setBalance(balance);
}

View File

@@ -1,6 +1,7 @@
auto Program::load() -> void {
unload();
emulator->configure("System/Serialization/Method", settings.emulator.serialization.method);
emulator->configure("Hacks/Hotfixes", settings.emulator.hack.hotfixes);
emulator->configure("Hacks/Entropy", settings.emulator.hack.entropy);
emulator->configure("Hacks/CPU/Overclock", settings.emulator.hack.cpu.overclock);
@@ -127,14 +128,16 @@ auto Program::loadSuperFamicom(string location) -> bool {
if(!superFamicom.patched) superFamicom.patched = applyPatchBPS(rom, location);
auto heuristics = Heuristics::SuperFamicom(rom, location);
auto sha256 = Hash::SHA256(rom).digest();
superFamicom.title = heuristics.title();
superFamicom.region = heuristics.videoRegion();
if(auto document = BML::unserialize(string::read(locate("Database/Super Famicom.bml")))) {
if(auto game = document[{"game(sha256=", sha256, ")"}]) {
manifest = BML::serialize(game);
//the internal ROM header title is not present in the database, but is needed for internal core overrides
manifest.append(" title: ", superFamicom.title, "\n");
superFamicom.verified = true;
}
}
superFamicom.title = heuristics.title();
superFamicom.region = heuristics.videoRegion();
superFamicom.manifest = manifest ? manifest : heuristics.manifest();
hackPatchMemory(rom);
superFamicom.document = BML::unserialize(superFamicom.manifest);

View File

@@ -1,4 +1,6 @@
auto Program::hackCompatibility() -> void {
string entropy = settings.emulator.hack.entropy;
bool fastJoypadPolling = false;
bool fastPPU = settings.emulator.hack.ppu.fast;
bool fastPPUNoSpriteLimit = settings.emulator.hack.ppu.noSpriteLimit;
bool fastDSP = settings.emulator.hack.dsp.fast;
@@ -8,12 +10,31 @@ auto Program::hackCompatibility() -> void {
auto title = superFamicom.title;
auto region = superFamicom.region;
//sometimes menu options are skipped over in the main menu with cycle-based joypad polling
if(title == "Arcades Greatest Hits") fastJoypadPolling = true;
//the start button doesn't work in this game with cycle-based joypad polling
if(title == "TAIKYOKU-IGO Goliath") fastJoypadPolling = true;
//holding up or down on the menu quickly cycles through options instead of stopping after each button press
if(title == "WORLD MASTERS GOLF") fastJoypadPolling = true;
//relies on mid-scanline rendering techniques
if(title == "AIR STRIKE PATROL" || title == "DESERT FIGHTER") fastPPU = false;
//the dialogue text is blurry due to an issue in the scanline-based renderer's color math support
if(title == "マーヴェラス") fastPPU = false;
//stage 2 uses pseudo-hires in a way that's not compatible with the scanline-based renderer
if(title == "SFC クレヨンシンチャン") fastPPU = false;
//title screen game select (after choosing a game) changes OAM tiledata address mid-frame
//this is only supported by the cycle-based PPU renderer
if(title == "Winter olympics") fastPPU = false;
//title screen shows remnants of the flag after choosing a language with the scanline-based renderer
if(title == "WORLD CUP STRIKER") fastPPU = false;
//relies on cycle-accurate writes to the echo buffer
if(title == "KOUSHIEN_2") fastDSP = false;
@@ -26,6 +47,27 @@ auto Program::hackCompatibility() -> void {
//fixes an errant scanline on the title screen due to writing to PPU registers too late
if(title == "ADVENTURES OF FRANKEN" && region == "PAL") renderCycle = 32;
//fixes an errant scanline on the title screen due to writing to PPU registers too late
if(title == "FIREPOWER 2000" || title == "SUPER SWIV") renderCycle = 32;
//fixes an errant scanline on the title screen due to writing to PPU registers too late
if(title == "NHL '94" || title == "NHL PROHOCKEY'94") renderCycle = 32;
//fixes an errant scanline on the title screen due to writing to PPU registers too late
if(title == "Sugoro Quest++") renderCycle = 128;
if(settings.emulator.hack.hotfixes) {
//this game transfers uninitialized memory into video RAM: this can cause a row of invalid tiles
//to appear in the background of stage 12. this one is a bug in the original game, so only enable
//it if the hotfixes option has been enabled.
if(title == "The Hurricanes") entropy = "None";
//Frisky Tom attract sequence sometimes hangs when WRAM is initialized to pseudo-random patterns
if(title == "ニチブツ・アーケード・クラシックス") entropy = "None";
}
emulator->configure("Hacks/Entropy", entropy);
emulator->configure("Hacks/CPU/FastJoypadPolling", fastJoypadPolling);
emulator->configure("Hacks/PPU/Fast", fastPPU);
emulator->configure("Hacks/PPU/NoSpriteLimit", fastPPUNoSpriteLimit);
emulator->configure("Hacks/PPU/RenderCycle", renderCycle);

View File

@@ -88,12 +88,27 @@ auto Program::main() -> void {
if(inactive()) {
audio.clear();
usleep(20 * 1000);
viewportRefresh();
if(settings.emulator.runAhead.frames == 0) viewportRefresh();
return;
}
rewindRun();
emulator->run();
if(!settings.emulator.runAhead.frames || fastForwarding || rewinding) {
emulator->run();
} else {
emulator->setRunAhead(true);
emulator->run();
auto state = emulator->serialize(0);
if(settings.emulator.runAhead.frames >= 2) emulator->run();
if(settings.emulator.runAhead.frames >= 3) emulator->run();
if(settings.emulator.runAhead.frames >= 4) emulator->run();
emulator->setRunAhead(false);
emulator->run();
state.setMode(serializer::Mode::Load);
emulator->unserialize(state);
}
if(emulatorSettings.autoSaveMemory.checked()) {
auto currentTime = chrono::timestamp();
if(currentTime - autoSaveTime >= settings.emulator.autoSaveMemory.interval) {
@@ -108,10 +123,22 @@ auto Program::quit() -> void {
presentation.setVisible(false);
Application::processEvents();
//in case the emulator was closed prior to initialization completing:
settings.general.crashed = false;
unload();
settings.save();
video.reset();
audio.reset();
input.reset();
#if defined(PLATFORM_WINDOWS)
//in rare cases, when Application::exit() calls exit(0), a crash will occur.
//this seems to be due to the internal state of certain ruby drivers.
auto processID = GetCurrentProcessId();
auto handle = OpenProcess(SYNCHRONIZE | PROCESS_TERMINATE, true, processID);
TerminateProcess(handle, 0);
#endif
Application::exit();
}

View File

@@ -98,6 +98,7 @@ struct Program : Lock, Emulator::Platform {
auto updateVideoPalette() -> void;
auto updateVideoEffects() -> void;
auto toggleVideoFullScreen() -> void;
auto toggleVideoPseudoFullScreen() -> void;
//audio.cpp
auto updateAudioDriver(Window parent) -> void;
@@ -199,6 +200,9 @@ public:
Rewind = 1 << 4,
};};
uint mute = 0;
bool fastForwarding = false;
bool rewinding = false;
};
extern Program program;

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