mirror of
https://github.com/bsnes-emu/bsnes.git
synced 2025-09-21 01:21:45 +02:00
Compare commits
101 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
55e78b03de | ||
|
47dcdc1b4f | ||
|
e13ab011eb | ||
|
892f202945 | ||
|
e575196abc | ||
|
404caeab50 | ||
|
dde9b4c2c7 | ||
|
793f2e5bf4 | ||
|
cc4ab9bc25 | ||
|
2551f20f3a | ||
|
5b29ddbcaa | ||
|
ac4d16c917 | ||
|
01c16dcf4d | ||
|
169c0871c7 | ||
|
ffee61a1b1 | ||
|
526df86ee6 | ||
|
748cf44f35 | ||
|
a4f96f0648 | ||
|
2ca1bab9ed | ||
|
1e6a745f19 | ||
|
90b1350110 | ||
|
357d054c19 | ||
|
d62e3f3362 | ||
|
4ec45a7453 | ||
|
f5d40bd1ee | ||
|
6aa7c944d5 | ||
|
dafd673177 | ||
|
a64c1adaa8 | ||
|
0d1d6f329d | ||
|
7cd897b53b | ||
|
011f470b07 | ||
|
6edad01fb8 | ||
|
3ecea80ecb | ||
|
b7b848eff5 | ||
|
da7350ac5c | ||
|
ba3fca27ad | ||
|
5775155714 | ||
|
f1108408a8 | ||
|
996358da66 | ||
|
c717a0e7bd | ||
|
2884cd87d2 | ||
|
454b90be24 | ||
|
2b9a22e1d8 | ||
|
1c1cfd086b | ||
|
f2978247c1 | ||
|
4f32551430 | ||
|
c61c3cabc6 | ||
|
819d6dbde4 | ||
|
f51bc06739 | ||
|
4f09a3873d | ||
|
55bfe402e7 | ||
|
30d7fa1923 | ||
|
9f86a3be26 | ||
|
6b7e6e01bb | ||
|
53f8de6ac3 | ||
|
cd18cdb1d6 | ||
|
6cb7d89d64 | ||
|
19f3cdfd5e | ||
|
a32b6fae74 | ||
|
03a6e1c7de | ||
|
6b34f134bf | ||
|
2de906ea46 | ||
|
95addddc46 | ||
|
45e9e0f0ea | ||
|
0aea7fd5c5 | ||
|
fb95d5b59f | ||
|
3d646aef73 | ||
|
3fb7ff6bfe | ||
|
d8bc2050be | ||
|
e71da4d8c8 | ||
|
e78aca34b9 | ||
|
0c82cc325e | ||
|
78c76962ec | ||
|
e22167cf82 | ||
|
1698533774 | ||
|
7b66e1c531 | ||
|
c6f92b782c | ||
|
e3f2e634c8 | ||
|
e598e81ab9 | ||
|
d37fb1c12e | ||
|
eaf33cb078 | ||
|
07427e4697 | ||
|
b5301b7ea8 | ||
|
57c53a86b4 | ||
|
f19f31938b | ||
|
4efee7e9f1 | ||
|
3701236ca0 | ||
|
2f684caa7c | ||
|
3a064fc5a3 | ||
|
5a4b667eae | ||
|
62729df2d1 | ||
|
1ef227f482 | ||
|
6e5542aa20 | ||
|
675662e739 | ||
|
409dd371b9 | ||
|
18d2ab6435 | ||
|
1e626e75ef | ||
|
c6d90d3ff1 | ||
|
29caf77751 | ||
|
29b13083d5 | ||
|
3883172a4e |
@@ -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
2
.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.fs linguist-detectable=false
|
||||
*.vs linguist-detectable=false
|
89
CONTRIBUTING.md
Normal file
89
CONTRIBUTING.md
Normal 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.
|
28
README.md
28
README.md
@@ -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 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
|
||||
- 
|
||||
- 
|
||||
- 
|
||||
|
||||
Preview
|
||||
-------
|
||||
|
||||

|
||||

|
||||

|
||||
|
@@ -1,5 +1,5 @@
|
||||
database
|
||||
revision: 2018-09-20
|
||||
revision: 2020-01-01
|
||||
|
||||
//BS Memory (JPN)
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
database
|
||||
revision: 2018-09-20
|
||||
revision: 2020-01-01
|
||||
|
||||
//Sufami Turbo (JPN)
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {}
|
||||
};
|
||||
|
||||
}
|
||||
|
@@ -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 */
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -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)
|
||||
|
@@ -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:
|
||||
|
@@ -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)
|
||||
|
@@ -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);
|
||||
|
@@ -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
5
bsnes/gb/README
Normal 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
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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)
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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};
|
||||
|
@@ -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;
|
||||
|
@@ -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
|
||||
|
@@ -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) {
|
||||
|
@@ -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++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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"
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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);
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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) {
|
||||
|
@@ -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};
|
||||
}
|
||||
|
@@ -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]]);
|
||||
}
|
||||
|
@@ -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 = {};
|
||||
}
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
194
bsnes/sfc/ppu/main.cpp
Normal 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();
|
||||
}
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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;
|
||||
|
@@ -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 {
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -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;
|
||||
};
|
||||
|
@@ -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),)
|
||||
|
@@ -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(
|
||||
|
@@ -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([] {
|
||||
|
@@ -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 {};
|
||||
}
|
||||
|
||||
|
@@ -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")) {
|
||||
|
@@ -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};
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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);
|
||||
|
@@ -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);
|
||||
|
@@ -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();
|
||||
}
|
||||
|
@@ -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
Reference in New Issue
Block a user