Merge branch 'master' into feature-highscores

This commit is contained in:
thrust26 2020-07-31 10:47:49 +02:00
commit 5a7f1fb914
889 changed files with 50552 additions and 23102 deletions

2
.gitignore vendored
View File

@ -31,3 +31,5 @@ src/**/*.psess
src/**/*.vspx src/**/*.vspx
src/**/**.pdb src/**/**.pdb
Stella.xcscheme Stella.xcscheme
src/tools/fonts/*

View File

@ -9,7 +9,7 @@
SSSS ttt eeeee llll llll aaaaa SSSS ttt eeeee llll llll aaaaa
=========================================================================== ===========================================================================
Release 6.1 for Linux, macOS and Windows Release 6.2.1 for Linux, macOS and Windows
=========================================================================== ===========================================================================
The Atari 2600 Video Computer System (VCS), introduced in 1977, was the The Atari 2600 Video Computer System (VCS), introduced in 1977, was the
@ -21,27 +21,27 @@ all of your favourite Atari 2600 games again! Stella was originally
developed for Linux by Bradford W. Mott, however, it has been ported to a developed for Linux by Bradford W. Mott, however, it has been ported to a
number of other platforms and is currently maintained by Stephen Anthony. number of other platforms and is currently maintained by Stephen Anthony.
This is the 6.1 release of Stella for Linux, macOS and Windows. The This is the 6.2.1 release of Stella for Linux, macOS and Windows. The
distributions currently available are: distributions currently available are:
* Binaries for Windows Vista/7/8/10 : * Binaries for Windows Vista/7/8/10 :
Stella-6.1-win32.exe (32-bit EXE installer) Stella-6.2.1-win32.exe (32-bit EXE installer)
Stella-6.1-x64.exe (64-bit EXE installer) Stella-6.2.1-x64.exe (64-bit EXE installer)
Stella-6.1-windows.zip (32/64 bit versions) Stella-6.2.1-windows.zip (32/64 bit versions)
* Binary distribution for macOS 10.7 and above : * Binary distribution for macOS 10.7 and above :
Stella-6.1-macos.dmg (64-bit Intel) Stella-6.2.1-macos.dmg (64-bit Intel)
* Binary distribution in 32-bit & 64-bit Ubuntu DEB format : * Binary distribution in 32-bit & 64-bit Ubuntu DEB format :
stella_6.1-1_i386.deb stella_6.2.1-1_i386.deb
stella_6.1-1_amd64.deb stella_6.2.1-1_amd64.deb
* Binary distribution in 32-bit & 64-bit RPM format : * Binary distribution in 32-bit & 64-bit RPM format :
stella-6.1-2.i386.rpm stella-6.2.1-2.i386.rpm
stella-6.1-2.x86_64.rpm stella-6.2.1-2.x86_64.rpm
* Source code distribution for all platforms : * Source code distribution for all platforms :
stella-6.1-src.tar.xz stella-6.2.1-src.tar.xz
Distribution Site Distribution Site

View File

@ -12,7 +12,155 @@
Release History Release History
=========================================================================== ===========================================================================
6.0.2 to 6.1: (MM dd, 2020) 6.2.1 to 6.3 (XXXX XX, 2020)
* Added adjustable autofire.
* Added 'Dark' UI theme. (TODO: DOC)
* Extended global hotkeys for debug options.
* Added option to playback a game using the Time Machine.
* Allow taking snapshots from within the Time Machine dialog.
* Added the ability to access most files that Stella uses from within a
ZIP file. This includes the following:
- Per-ROM properties file (so one can distribute a ROM and its
associated properties).
- Debugger symbol (.sym) and list (.lst) files, etc.
- Several others, as we extend the support.
Basically, you are now able to put many files that Stella uses inside
one ZIP file, and distribute just that file.
* Added option to select the audio device.
* Added option to display detected settings info when a ROM is loaded.
* Replaced "Re-disassemble" with "Disassemble @ current line" in debugger.
* Fixed bug when taking fullscreen snapshots; the dimensions were
sometimes cut off.
-Have fun!
6.2 to 6.2.1: (June 20, 2020)
* Fixed Pitfall II ROM not working correctly.
* Fixed crashes when using some combinations of bankswitching schemes on
incorrect ROMs, or when using invalid ROM file sizes, etc.
* Fixed RIOT timer behaviour on reading/writing at the wraparound cycle.
* Fixed incorrectly setting D6 bit on TIA reads in some cases. Related
to this, improve 'tiadriven' option to randomize only D5..D0 bits.
* Fixed custom palette and TV effects adjustable slider rounding issue.
* Fixed some bugs in 3E+ scheme when using non-standard ROM sizes.
* Fixed crash in Audio & Video dialog when opened from debugger, and the
debugger window sometimes being resized when using the Options dialog.
* Make NTSC custom phase shift not affect Yellow anymore.
* Fixed '1x' snapshot mode; TV effects are now disabled. This mode
now generates a clean, pixel-exact image.
* Fixed mappings sometimes not being saved in the Retron77 port.
* A ROM properties file may now be placed next to the ROM (with the same
name as the ROM, except ending in .pro), and Stella will automatically
apply the properties to the ROM. [NOTE: this was present in 6.2, but
was mistakenly left out of the changelog]
* Added button to Game Info dialog to save properties of the currently
loaded ROM to a separate properties file (in the default save directory).
This is useful in conjunction with the previous item.
* Allow changing custom palette and TV effects adjustables in 1% steps
again.
* Updated documentation for changes in ROM properties key names.
* The codebase now compiles under gcc6 again. Future versions will
require gcc7, though.
6.1.2 to 6.2: (June 7, 2020)
* Added interactive palette to Video & Audio settings.
* Added 'Custom' palette, generated from user controlled phase shifts.
* Added that adjustable audio & video settings are displayed as gauge bars.
* Added four global hotkeys which allow selecting and changing numerous
audio & video settings without having to remember the dedicated hotkeys.
* Added 'Turbo' mode, runs the game as fast as the computer allows.
* Added that paddle centering (per ROM) and sensitivity can be adjusted.
* Added that mouse sensitivity for Driving controller can be adjusted.
* Added paddle filtering in UI to avoid unwanted navigation events.
* Added selectable dialog fonts.
* Added separate positioning of launcher, emulator and debugger.
* Added optional display to game refresh rate adaption in fullscreen mode.
* Added option which lets default ROM path follow launcher navigation.
* Added debugger 'saveaccess' function, which saves memory access counts to
a CSV file.
* Added displaying last write address in the debugger.
* Added debugger pseudo-register '_scanend', which gives the number of
scanlines at the end of the last frame.
* Added detection of color and audio data in DiStella.
* Restored 'cfg' directory for Distella config files.
* Added TV Boy and 3EX bank switching types.
* Removed unused CV+ and DASH bank switching types.
* Added support for loading grayscale PNG images in the ROM launcher.
6.1.1 to 6.1.2: (April 25, 2020)
* Fixed bug with remapped events not being reloaded in certain cases.
* Fixed bug in debugger for 3E scheme when displaying active RAM bank.
* Fixed bug in "Dragon Defender" ROM being misconfigured for Mindlink
controller.
6.1 to 6.1.1: (April 4, 2020)
* Fixed crash in 3E bankswitching scheme when writing to ROM addresses.
* Fix snapshots on Retina HiDPI displays capturing only the top-left
corner.
* Fixed wrong color for BK (background) swatch in the debugger.
* Fixed 'Right Diff' button in Command menu changing left difficulty
instead.
* Fixed compilation of libretro port on Debian Buster.
6.0.2 to 6.1: (March 22, 2020)
* IMPORTANT NOTES: * IMPORTANT NOTES:
- Because of major event remapping changes, all remappings will be reset - Because of major event remapping changes, all remappings will be reset
@ -119,7 +267,9 @@
* Added option to change pitch of Pitfall II music. * Added option to change pitch of Pitfall II music.
* ROM Info Launcher can now display multiple lines per property and * ROM Info Viewer size is not limited to fixed zoom steps anymore.
* ROM Info Viewer can now display multiple lines per property and the
bank switching type. bank switching type.
* In file listings, you can now select directories by holding 'Shift' on * In file listings, you can now select directories by holding 'Shift' on
@ -179,6 +329,10 @@
* Fixed bug in DPC+ scheme; 'fast fetch mode' was enabled at startup, * Fixed bug in DPC+ scheme; 'fast fetch mode' was enabled at startup,
when it should be disabled by default. when it should be disabled by default.
* Some more work on DPC+ playfield 'jitter' effect for certain older DPC+
driver versions; more ROMs are now detected properly. Special thanks
to SpiceWare for his research in this area.
* Added proper Retron77 port. * Added proper Retron77 port.
* Added proper libretro port, and fixed display for OpenGLES renderers. * Added proper libretro port, and fixed display for OpenGLES renderers.
@ -192,7 +346,8 @@
* Updated included PNG library to latest stable version. * Updated included PNG library to latest stable version.
-Have fun! * Updated UNIX configure script to work with the gcc version 10 and
above.
6.0.1 to 6.0.2: (October 11, 2019) 6.0.1 to 6.0.2: (October 11, 2019)

View File

@ -8,14 +8,11 @@
## SS SS tt ee ll ll aa aa ## SS SS tt ee ll ll aa aa
## SSSS ttt eeeee llll llll aaaaa ## SSSS ttt eeeee llll llll aaaaa
## ##
## Copyright (c) 1995-2016 by Bradford W. Mott, Stephen Anthony ## Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony
## and the Stella Team ## and the Stella Team
## ##
## See the file "License.txt" for information on usage and redistribution of ## See the file "License.txt" for information on usage and redistribution of
## this file, and for a DISCLAIMER OF ALL WARRANTIES. ## this file, and for a DISCLAIMER OF ALL WARRANTIES.
##
## Based on code from ScummVM - Scumm Interpreter
## Copyright (C) 2002-2004 The ScummVM project
##============================================================================ ##============================================================================
####################################################################### #######################################################################
@ -99,7 +96,7 @@ EXECUTABLE := stella$(EXEEXT)
EXECUTABLE_PROFILE_GENERATE := stella-pgo-generate$(EXEEXT) EXECUTABLE_PROFILE_GENERATE := stella-pgo-generate$(EXEEXT)
EXECUTABLE_PROFILE_USE := stella-pgo$(EXEEXT) EXECUTABLE_PROFILE_USE := stella-pgo$(EXEEXT)
PROFILE_DIR = $(CURDIR)/profile PROFILE_DIR = $(CURDIR)/test/roms/profile
PROFILE_OUT = $(PROFILE_DIR)/out PROFILE_OUT = $(PROFILE_DIR)/out
PROFILE_STAMP = profile.stamp PROFILE_STAMP = profile.stamp

View File

@ -10,12 +10,12 @@ environment:
Configuration: Release Configuration: Release
SDL2_version: 2.0.10 SDL2_version: 2.0.12
install: install:
- cmd: | - cmd: |
curl -o "C:\SDL2-devel.zip" https://www.libsdl.org/release/SDL2-devel-2.0.10-VC.zip curl -o "C:\SDL2-devel.zip" "https://www.libsdl.org/release/SDL2-devel-%SDL2_version%-VC.zip"
7z x "C:\SDL2-devel.zip" -o"C:\" 7z x "C:\SDL2-devel.zip" -o"C:\"
xcopy /S "C:\SDL2-%SDL2_version%\include" src\common xcopy /S "C:\SDL2-%SDL2_version%\include" src\common

8
configure vendored
View File

@ -458,11 +458,17 @@ elif test "$have_gcc" = yes; then
fi fi
case $cxx_version in case $cxx_version in
4.[7-9]|4.[7-9].[0-9]|4.[7-9].[0-9][-.]*|[5-9]|[5-9].[0-9]|[5-9].[0-9].[0-9]|[5-9].[0-9].[0-9][-.]*) [1-9]*)
_cxx_major=`echo $cxx_version | cut -d '.' -f 1` _cxx_major=`echo $cxx_version | cut -d '.' -f 1`
_cxx_minor=`echo $cxx_version | cut -d '.' -f 2` _cxx_minor=`echo $cxx_version | cut -d '.' -f 2`
# Need at least version 4.7
if [ $_cxx_major -ge 5 ] || [ $_cxx_major -eq 4 -a $_cxx_minor -ge 7 ]; then
cxx_version="$cxx_version, ok" cxx_version="$cxx_version, ok"
cxx_verc_fail=no cxx_verc_fail=no
else
cxx_version="$cxx_version, bad"
cxx_verc_fail=yes
fi
;; ;;
'not found') 'not found')
cxx_verc_fail=yes cxx_verc_fail=yes

35
debian/changelog vendored
View File

@ -1,3 +1,38 @@
stella (6.2.1-1) stable; urgency=high
* Version 6.2.1 release
-- Stephen Anthony <sa666666@gmail.com> Sat, 20 Jun 2020 17:09:59 -0230
stella (6.2-1) stable; urgency=high
* Version 6.2 release
-- Stephen Anthony <sa666666@gmail.com> Sun, 7 Jun 2020 17:09:59 -0230
stella (6.1.2-1) stable; urgency=high
* Version 6.1.2 release
-- Stephen Anthony <sa666666@gmail.com> Sat, 25 Apr 2020 17:09:59 -0230
stella (6.1.1-1) stable; urgency=high
* Version 6.1.1 release
-- Stephen Anthony <sa666666@gmail.com> Sat, 04 Apr 2020 17:09:59 -0230
stella (6.1-1) stable; urgency=high
* Version 6.1 release
-- Stephen Anthony <sa666666@gmail.com> Sun, 22 Mar 2020 17:09:59 -0230
stella (6.0.2-1) stable; urgency=high stella (6.0.2-1) stable; urgency=high
* Version 6.0.2 release * Version 6.0.2 release

View File

@ -15,7 +15,7 @@
<body> <body>
<center><b><font size="7">Stella</font></b></center> <center><b><font size="7">Stella</font></b></center>
<center><h4><b>Release 6.1</b></h4></center> <center><h4><b>Release 6.2.1</b></h4></center>
<center><h1><b>Integrated Debugger</b></h1></center> <center><h1><b>Integrated Debugger</b></h1></center>
<center><h4><b>(a work in progress)</b></h4></center> <center><h4><b>(a work in progress)</b></h4></center>
<br> <br>
@ -121,13 +121,14 @@ feature that no other 2600 debugger has; it's <b>completely</b> cross-platform.<
<li>Supports Distella 'configuration directives', which may be used to <li>Supports Distella 'configuration directives', which may be used to
override automatic code/data determination in the disassembly. For now, override automatic code/data determination in the disassembly. For now,
the following directives are supported: CODE, GFX, PGFX, DATA, ROW. the following directives are supported: CODE, GFX, PGFX, COL, PCOL, BCOL, AUD, DATA, ROW.
These directives can be entered at the debugger prompt, or (automatically) These directives can be entered at the debugger prompt, or be (automatically)
loaded and saved in configuration files.</li> loaded and saved in configuration files.</li>
<li>Extensive disassembly support, both from the emulation core and with help <li>Extensive disassembly support, both from the emulation core and with help
from Distella. Where possible, the disassembly differentiates between code, from Distella. Where possible, the disassembly differentiates between code,
player graphics and playfield graphics (ie, addresses stored in GRPx and PFx) player and playfield graphics, colors and audio (ie, addresses stored in
GRPx, PFx, COLUxx, AUDxx)
and data (addresses used as an operand of a command). Code sections are also and data (addresses used as an operand of a command). Code sections are also
differentiated between actual code, and 'tentative' code (ie, areas that may differentiated between actual code, and 'tentative' code (ie, areas that may
represent code sections, but haven't actually been executed yet). Such represent code sections, but haven't actually been executed yet). Such
@ -330,7 +331,7 @@ previous rewind operation. The rewind buffer is 100 levels deep by default, the
size can be configured e.g. in the size can be configured e.g. in the
<b><a href="index.html#Debugger">Developer Settings</a> - Time Machine</b> dialog.<p> <b><a href="index.html#Debugger">Developer Settings</a> - Time Machine</b> dialog.<p>
<p>The other operations are Step, Trace, Scan+1, Frame+1 and Exit (debugger).</p> <p>The other operations are Step, Trace, Scan+1, Frame+1 and Run.</p>
<p>You can also use the buttons from anywhere in the GUI via hotkeys.</p> <p>You can also use the buttons from anywhere in the GUI via hotkeys.</p>
<p> <p>
@ -381,12 +382,12 @@ size can be configured e.g. in the
</tr> </tr>
<tr> <tr>
<td>Backquote (`)</td> <td>Backquote (`)</td>
<td>Exit</td> <td>Run</td>
</tr> </tr>
</table> </table>
</p> </p>
For MacOS use 'Cmd' instead of &nbsp;'Alt' key. For MacOS use 'Cmd' instead of &nbsp;'Alt' key.
<p>To the left of the global buttons, you find the "Options..." button.</p> <p>To the left of the global buttons, you find the 'Options...' button.</p>
<ul> <ul>
<p><img src="graphics/debugger_options.png"></p> <p><img src="graphics/debugger_options.png"></p>
</ul> </ul>
@ -607,7 +608,7 @@ created a symbol file, you can use labels for the expression.</p>
<p>Example: You have got a label called "kernel". To break there, <p>Example: You have got a label called "kernel". To break there,
the command is "break kernel". After you've set the breakpoint, the command is "break kernel". After you've set the breakpoint,
exit the debugger ("quit" or click the Exit button). The emulator exit the debugger (enter "run" or click the 'Run' button). The emulator
will run until it gets to the breakpoint, then it will enter the will run until it gets to the breakpoint, then it will enter the
debugger with the Program Counter pointing to the instruction debugger with the Program Counter pointing to the instruction
at the breakpoint.</p> at the breakpoint.</p>
@ -754,6 +755,7 @@ that holds 'number of scanlines' on an actual console).</p>
<tr><td> _fcycles</td><td> Number of cycles since frame started</td></tr> <tr><td> _fcycles</td><td> Number of cycles since frame started</td></tr>
<tr><td> _icycles</td><td> Number of cycles of last instruction</td></tr> <tr><td> _icycles</td><td> Number of cycles of last instruction</td></tr>
<tr><td> _scan</td><td> Current scanline count</td></tr> <tr><td> _scan</td><td> Current scanline count</td></tr>
<tr><td> _scanend</td><td> Scanline count at end of last frame</td></tr>
<tr><td> _scycles</td><td> Number of cycles in current scanline</td></tr> <tr><td> _scycles</td><td> Number of cycles in current scanline</td></tr>
<tr><td> _vblank</td><td> Whether vertical blank is enabled (1 or 0)</td></tr> <tr><td> _vblank</td><td> Whether vertical blank is enabled (1 or 0)</td></tr>
<tr><td> _vsync</td><td> Whether vertical sync is enabled (1 or 0)</td></tr> <tr><td> _vsync</td><td> Whether vertical sync is enabled (1 or 0)</td></tr>
@ -906,7 +908,9 @@ Type "help 'cmd'" to see extended information about the given command.</p>
<pre> <pre>
a - Set Accumulator to &lt;value&gt; a - Set Accumulator to &lt;value&gt;
aud - Mark 'AUD' range in disassembly
base - Set default number base to &lt;base&gt; (bin, dec, hex) base - Set default number base to &lt;base&gt; (bin, dec, hex)
bcol - Mark 'BCOL' range in disassembly
break - Set/clear breakpoint at &lt;address&gt; and &lt;bank&gt; break - Set/clear breakpoint at &lt;address&gt; and &lt;bank&gt;
breakif - Set/clear breakpoint on &lt;condition&gt; breakif - Set/clear breakpoint on &lt;condition&gt;
breaklabel - Set/clear breakpoint on &lt;address&gt; (no mirrors, all banks) breaklabel - Set/clear breakpoint on &lt;address&gt; (no mirrors, all banks)
@ -919,6 +923,7 @@ clearsavestateifs - Clear all savestate points
clearwatches - Clear all watches clearwatches - Clear all watches
cls - Clear prompt area of text cls - Clear prompt area of text
code - Mark 'CODE' range in disassembly code - Mark 'CODE' range in disassembly
col - Mark 'COL' range in disassembly
colortest - Show value xx as TIA color colortest - Show value xx as TIA color
d - Decimal Mode Flag: set (0 or 1), or toggle (no arg) d - Decimal Mode Flag: set (0 or 1), or toggle (no arg)
data - Mark 'DATA' range in disassembly data - Mark 'DATA' range in disassembly
@ -959,6 +964,7 @@ clearsavestateifs - Clear all savestate points
n - Negative Flag: set (0 or 1), or toggle (no arg) n - Negative Flag: set (0 or 1), or toggle (no arg)
palette - Show current TIA palette palette - Show current TIA palette
pc - Set Program Counter to address xx pc - Set Program Counter to address xx
pcol - Mark 'PCOL' range in disassembly
pgfx - Mark 'PGFX' range in disassembly pgfx - Mark 'PGFX' range in disassembly
print - Evaluate/print expression xx in hex/dec/binary print - Evaluate/print expression xx in hex/dec/binary
ram - Show ZP RAM, or set address xx to yy1 [yy2 ...] ram - Show ZP RAM, or set address xx to yy1 [yy2 ...]
@ -972,6 +978,7 @@ clearsavestateifs - Clear all savestate points
runtopc - Run until PC is set to value xx runtopc - Run until PC is set to value xx
s - Set Stack Pointer to value xx s - Set Stack Pointer to value xx
save - Save breaks, watches, traps and functions to file xx save - Save breaks, watches, traps and functions to file xx
saveaccess - Save access counters to CSV file
saveconfig - Save Distella config file (with default name) saveconfig - Save Distella config file (with default name)
savedis - Save Distella disassembly (with default name) savedis - Save Distella disassembly (with default name)
saverom - Save (possibly patched) ROM (with default name) saverom - Save (possibly patched) ROM (with default name)
@ -1022,8 +1029,8 @@ graphics and positions, and the playfield.</p>
of the displays are editable. You can even toggle individual bits in of the displays are editable. You can even toggle individual bits in
the GRP0/1 and playfield registers (remember to double-click).</p> the GRP0/1 and playfield registers (remember to double-click).</p>
<p>The group of buttons labelled "Strobes" allows you to write to any <p>The buttons allow you to write to any of the strobe registers at
of the strobe registers at any time.</p> any time.</p>
<p>The collision registers are displayed in decoded format, in a table. <p>The collision registers are displayed in decoded format, in a table.
You can see exactly which objects have hit what. These are read-only You can see exactly which objects have hit what. These are read-only
@ -1098,7 +1105,7 @@ or TV effects are enabled, you won't see the effects here; this shows the
<b>raw</b> TIA image only.</p> <b>raw</b> TIA image only.</p>
<p>To e.g. watch the TIA draw the frame one scanline at a time, you can <p>To e.g. watch the TIA draw the frame one scanline at a time, you can
use the "Scan+1" button, the prompt "scan" command or the Control-L key.</p> use the 'Scan+1' button, the prompt "scan" command or the Control-L key.</p>
<p>You can also right-click anywhere in this window to show a context menu, <p>You can also right-click anywhere in this window to show a context menu,
as illustrated:</p> as illustrated:</p>
@ -1193,6 +1200,8 @@ the reason will be shown as follows:
<li>"WTrap:" for write traps</li> <li>"WTrap:" for write traps</li>
<li>"RTrapIf:" for conditional read traps</li> <li>"RTrapIf:" for conditional read traps</li>
<li>"WTrapIf:" for conditional write traps</li> <li>"WTrapIf:" for conditional write traps</li>
<li>"RWP:" for reads from write ports</li>
<li>"WRP:" for writes to read ports</li>
</ul> </ul>
</p> </p>
See the <a href="#BreakpointsEtc"><b>Breakpoints, watches and traps...</b></a> See the <a href="#BreakpointsEtc"><b>Breakpoints, watches and traps...</b></a>
@ -1206,7 +1215,7 @@ section for details.</p>
<p><img src="graphics/debugger_cpuregs.png"></p> <p><img src="graphics/debugger_cpuregs.png"></p>
<p>All the registers and flags are displayed, and can be changed by <p>All the registers and flags are displayed, and can be changed by
double-clicking on them (to the left). Flags are toggled on double-click. double-clicking on them (to the left). Flags are toggled on double-click.
Selected registers here can also be changed by using the "Data Operations" buttons, Selected registers here can also be changed by using the 'Data Operations' buttons,
further described in (J). All items are shown in hex. Any label defined for the further described in (J). All items are shown in hex. Any label defined for the
current PC value is shown to the right. Decimal and binary equivalents current PC value is shown to the right. Decimal and binary equivalents
are shown for SP/A/X/Y to the right (first decimal, then binary).</p> are shown for SP/A/X/Y to the right (first decimal, then binary).</p>
@ -1215,6 +1224,8 @@ registers. For example, consider the command 'LDA ($80),Y'. The operand of
the command resolves to some address, which isn't always easy to determine at the command resolves to some address, which isn't always easy to determine at
first glance. The 'Src Addr' area shows the actual resulting operand/address first glance. The 'Src Addr' area shows the actual resulting operand/address
being used with the given opcode.</p> being used with the given opcode.</p>
<p>The destination address of the last write is shown besides 'Dest'.</p>
<p>There's not much else to say about the CPU Registers widget: if you know 6502 <p>There's not much else to say about the CPU Registers widget: if you know 6502
assembly, it's pretty self-explanatory. If you don't, well, you should assembly, it's pretty self-explanatory. If you don't, well, you should
learn :)</p> learn :)</p>
@ -1369,21 +1380,78 @@ can use. These are known as 'directives', and partly correspond to configuration
options from the standalone Distella program. They are listed in order of options from the standalone Distella program. They are listed in order of
decreasing hierarchy:</p> decreasing hierarchy:</p>
<table border="1" cellpadding=4> <table border="1" cellpadding=4>
<tr><td><b>CODE</b></td><td>Addresses which have appeared in the program counter, or <tr>
which tentatively can appear in the program counter. These can be edited in hex.</td></tr> <td>
<tr><td><b>GFX</b></td><td>Addresses which contain data stored in the player graphics registers <b>CODE</b>
</td><td>
Addresses which have appeared in the program counter, or
which tentatively can appear in the program counter. These can be edited in hex.
</td>
</tr><tr>
<td>
<b>GFX</b>
</td><td>
Addresses which contain data stored in the player graphics registers
(GRP0/GRP1). These addresses are shown with a bitmap of the graphics, which (GRP0/GRP1). These addresses are shown with a bitmap of the graphics, which
can be edited in either hex or binary. The bitmap is shown as large blocks.</td></tr> can be edited in either hex or binary. The bitmap is shown as large blocks.
<tr><td><b>PGFX</b></td><td>Addresses which contain data stored in the playfield graphics registers </td>
</tr><tr>
<td>
<b>PGFX</b>
</td><td>
Addresses which contain data stored in the playfield graphics registers
(PF0/PF1/PF2). These addresses are shown with a bitmap of the graphics, which (PF0/PF1/PF2). These addresses are shown with a bitmap of the graphics, which
can be edited in either hex or binary. The bitmap is shown as small dashes.</td></tr> can be edited in either hex or binary. The bitmap is shown as small dashes.
<tr><td><b>DATA</b></td><td>Addresses used as an operand for some opcode. These can be edited </td>
in hex.</td></tr> </tr><tr>
<tr><td><b>ROW</b></td><td>Addresses not used as any of the above. These are shown up <td>
to 8 per line, and cannot be edited.</td></tr> <b>COL</b>
</td><td>
Addresses which contain data stored in the player color registers
(COLUP0/COLUP1). These addresses are shown as color constants, which
can be edited in hex. The color constant names are depending on the ROM's TV type.
</td>
</tr><tr>
<td>
<b>PCOL</b>
</td><td>
Addresses which contain data stored in the playfield color register
(COLUPF). These addresses are shown as color constants, which
can be edited in hex. The color constant names are depending on the ROM's TV type.
</td>
</tr><tr>
<td>
<b>BCOL</b>
</td><td>
Addresses which contain data stored in the background color register
(COLUBK). These addresses are shown as color constants, which
can be edited in hex. The color constant names are depending on the ROM's TV type.
</td>
</tr><tr>
<td>
<b>AUD</b>
</td><td>
Addresses which contain data stored in the audio registers
(AUDC0/AUDC1/AUDF0/AUDF1/AUDV0/AUDV1). These can be edited
in hex.
</td>
</tr><tr>
<td>
<b>DATA</b>
</td><td>
Addresses used as an operand for some opcode. These can be edited
in hex.
</td>
</tr><tr>
<td>
<b>ROW</b>
</td><td>
Addresses not used as any of the above. These are shown up
to 8 per line and cannot be edited.
</td>
</tr>
</table> </table>
<p>For code sections, the 6502 mnemonic will be UPPERCASE for all standard instructions, <p>For code sections, the 6502 mnemonic will be UPPERCASE for all standard instructions,
or lowercase for "illegal" 6502 instructions (like "dcp"). If automatic resolving or lowercase for "illegal" 6502 instructions (like "dcp"). If automatic resolving
of code sections has been disabled for any reason, you'll likely see a lot of code sections has been disabled for any reason, you'll likely see a lot
@ -1428,13 +1496,13 @@ anywhere in the listing:</p>
<p>The following options are available:</p> <p>The following options are available:</p>
<ul> <ul>
<li><b>Set PC @ current line</b>: Set the Program Counter to the address of the <li><b>Set PC @ current line</b>: Set the Program Counter to the address of the
disassembly line where the mouse was clicked (highlighted in yellow).</li> disassembly line where the mouse was clicked.</li>
<li><b>RunTo PC @ current line</b>: Single-step through code until the Program Counter <li><b>RunTo PC @ current line</b>: Single-step through code until the Program Counter
matches the address of the disassembly line where the mouse was clicked (highlighted in yellow)</li> matches the address of the disassembly line where the mouse was clicked.</li>
<li><b>Re-disassemble</b>: Self-explanatory; force the current bank to be <li><b>Disassemble @ current line</b>: Disassemble from the disassembly line where the mouse was clicked.
disassembled, regardless of whether anything has changed.</li> This allows to fill gaps in the disassembly and is most useful for bankswitched ROMs.</li>
<li><b>Show tentative code</b>: Allow Distella to do a static analysis/disassembly.</li> <li><b>Show tentative code</b>: Allow Distella to do a static analysis/disassembly.</li>
@ -1445,7 +1513,8 @@ isn't already a defined label).</li>
in either binary or hexidecimal.</li> in either binary or hexidecimal.</li>
<li><b>Use address relocation</b>: Corresponds to the Distella '-r' option <li><b>Use address relocation</b>: Corresponds to the Distella '-r' option
(Relocate calls out of address range).</li> (Relocate calls out of address range).</br>
Note: The code will continue to run fine, but the ROM image will be altered.</li>
</ul> </ul>
@ -1520,12 +1589,12 @@ the RAM in the DPC scheme is not viewable by the 6507, so its addresses start fr
<br> <br>
<h1><a name="DistellaConfiguration">Distella Configuration Files</a></h1> <h1><a name="DistellaConfiguration">Distella Configuration Files</a></h1>
<p>As mentioned in <a href="#Disassembly"><b>ROM Disassembly</b></a>, Stella supports the following directives: <p>As mentioned in <a href="#Disassembly"><b>ROM Disassembly</b></a>, Stella supports the following directives:
CODE/GFX/PGFX/DATA/ROW. While the debugger will try to automatically mark address CODE, GFX, PGFX, COL, PCOL, BCOL, AUD, DATA, ROW. While the debugger will try to automatically mark address
space with the appropriate directive, there are times when it will fail. There are space with the appropriate directive, there are times when it will fail. There are
several options in this case:</p> several options in this case:</p>
<ol> <ol>
<li><b>Manually set the directives</b>: Directives can be set in the debugger <li><b>Manually set the directives</b>: Directives can be set in the debugger
prompt with the code/gfx/pgfx/data/row commands. These accept an address range prompt with the aud/code/col/bcol/gfx/pcol/pgfx/data/row commands. These accept an address range
for the given directive type. Setting a range with the same type a second time for the given directive type. Setting a range with the same type a second time
will remove that directive from the range.</li> will remove that directive from the range.</li>
<li><b>Use configuration files</b>: Configuration files can be used to automatically <li><b>Use configuration files</b>: Configuration files can be used to automatically
@ -1564,7 +1633,7 @@ but it helps to know at least a little about 6502 programming.</p>
<li>Enter the debugger by pressing the ` (backquote) key. Don't get <li>Enter the debugger by pressing the ` (backquote) key. Don't get
killed before you do this, though. You should still have all 5 lives.</li> killed before you do this, though. You should still have all 5 lives.</li>
<li>In the RAM display, click the "Search" button and enter "5" for input. <li>In the RAM display, click the 'Search' button and enter "5" for input.
This searches RAM for your value and highlights all addresses that match This searches RAM for your value and highlights all addresses that match
the input. You should see two addresses highlighted: "00a5" and "00ba". the input. You should see two addresses highlighted: "00a5" and "00ba".
These are the only two addresses that currently have the value 5, so they're These are the only two addresses that currently have the value 5, so they're
@ -1580,7 +1649,7 @@ but it helps to know at least a little about 6502 programming.</p>
<li>Get killed! Ram an enemy tank, or let him shoot you. Wait for <li>Get killed! Ram an enemy tank, or let him shoot you. Wait for
the explosion to finish. You will now have 4 lives.</li> the explosion to finish. You will now have 4 lives.</li>
<li>Enter the debugger again. Click the "Compare" button in RAM widget and enter <li>Enter the debugger again. Click the 'Compare...' button in RAM widget and enter
a value of 4. Now the RAM widget should only show one highlighted address: a value of 4. Now the RAM widget should only show one highlighted address:
"00ba". What we did was search within our previous results (the ones that "00ba". What we did was search within our previous results (the ones that
were 5 before) for the new value 4. Address $00ba used to have the value 5, were 5 before) for the new value 4. Address $00ba used to have the value 5,

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.5 KiB

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.7 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.7 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.7 KiB

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.5 KiB

File diff suppressed because it is too large Load Diff

View File

@ -58,7 +58,7 @@
<center><h1>Stella for RetroN 77</h1></center> <center><h1>Stella for RetroN 77</h1></center>
<center><h2>Atari 2600 VCS emulator</h2></center> <center><h2>Atari 2600 VCS emulator</h2></center>
<center>Release 6.1 Beta 1</center> <center>Release 6.2.1</center>
<center><h2>Quick Navigation Guide</h2></center> <center><h2>Quick Navigation Guide</h2></center>
<br/> <br/>

View File

@ -36,45 +36,48 @@ CheatCodeDialog::CheatCodeDialog(OSystem& osystem, DialogContainer& parent,
: Dialog(osystem, parent, font, "Cheat codes") : Dialog(osystem, parent, font, "Cheat codes")
{ {
const int lineHeight = font.getLineHeight(), const int lineHeight = font.getLineHeight(),
fontHeight = font.getFontHeight(),
fontWidth = font.getMaxCharWidth(), fontWidth = font.getMaxCharWidth(),
buttonWidth = font.getStringWidth("Defaults") + 20, buttonWidth = font.getStringWidth("One shot ") + fontWidth * 2.5,
buttonHeight = font.getLineHeight() + 4; buttonHeight = font.getLineHeight() * 1.25;
const int HBORDER = 10; const int VBORDER = fontHeight / 2;
const int VBORDER = 10 + _th; const int HBORDER = fontWidth * 1.25;
const int VGAP = fontHeight / 4;
int xpos, ypos; int xpos, ypos;
WidgetArray wid; WidgetArray wid;
ButtonWidget* b; ButtonWidget* b;
// Set real dimensions // Set real dimensions
_w = 45 * fontWidth + HBORDER * 2; _w = 45 * fontWidth + HBORDER * 2;
_h = 11 * (lineHeight + 4) + VBORDER; _h = _th + 11 * (lineHeight + 4) + VBORDER * 2;
// List of cheats, with checkboxes to enable/disable // List of cheats, with checkboxes to enable/disable
xpos = HBORDER; ypos = VBORDER; xpos = HBORDER; ypos = _th + VBORDER;
myCheatList = myCheatList =
new CheckListWidget(this, font, xpos, ypos, _w - buttonWidth - HBORDER * 2 - 8, new CheckListWidget(this, font, xpos, ypos, _w - buttonWidth - HBORDER * 2 - fontWidth,
_h - 2*buttonHeight - VBORDER); _h - _th - buttonHeight - VBORDER * 3);
myCheatList->setEditable(false); myCheatList->setEditable(false);
wid.push_back(myCheatList); wid.push_back(myCheatList);
xpos += myCheatList->getWidth() + 8; ypos = VBORDER; xpos += myCheatList->getWidth() + fontWidth; ypos = _th + VBORDER;
b = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, b = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
"Add" + ELLIPSIS, kAddCheatCmd); "Add" + ELLIPSIS, kAddCheatCmd);
wid.push_back(b); wid.push_back(b);
ypos += lineHeight + 8; ypos += lineHeight + VGAP * 2;
myEditButton = myEditButton =
new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
"Edit" + ELLIPSIS, kEditCheatCmd); "Edit" + ELLIPSIS, kEditCheatCmd);
wid.push_back(myEditButton); wid.push_back(myEditButton);
ypos += lineHeight + 8; ypos += lineHeight + VGAP * 2;
myRemoveButton = myRemoveButton =
new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
"Remove", kRemCheatCmd); "Remove", kRemCheatCmd);
wid.push_back(myRemoveButton); wid.push_back(myRemoveButton);
ypos += lineHeight + 8 * 3; ypos += lineHeight + VGAP * 2 * 3;
b = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight, b = new ButtonWidget(this, font, xpos, ypos, buttonWidth, buttonHeight,
"One shot" + ELLIPSIS, kAddOneShotCmd); "One shot" + ELLIPSIS, kAddOneShotCmd);

View File

@ -213,10 +213,9 @@ void CheatManager::enable(const string& code, bool enable)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CheatManager::loadCheatDatabase() void CheatManager::loadCheatDatabase()
{ {
const string& cheatfile = myOSystem.cheatFile(); stringstream in;
ifstream in(cheatfile); try { myOSystem.cheatFile().read(in); }
if(!in) catch(...) { return; }
return;
string line, md5, cheat; string line, md5, cheat;
string::size_type one, two, three, four; string::size_type one, two, three, four;
@ -253,13 +252,12 @@ void CheatManager::saveCheatDatabase()
if(!myListIsDirty) if(!myListIsDirty)
return; return;
const string& cheatfile = myOSystem.cheatFile(); stringstream out;
ofstream out(cheatfile);
if(!out)
return;
for(const auto& iter: myCheatMap) for(const auto& iter: myCheatMap)
out << "\"" << iter.first << "\" " << "\"" << iter.second << "\"" << endl; out << "\"" << iter.first << "\" " << "\"" << iter.second << "\"" << endl;
try { myOSystem.cheatFile().write(out); }
catch(...) { return; }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -154,6 +154,12 @@ uInt32 AudioSettings::volume() const
return lboundInt(mySettings.getInt(SETTING_VOLUME), 0); return lboundInt(mySettings.getInt(SETTING_VOLUME), 0);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioSettings::device() const
{
return mySettings.getInt(SETTING_DEVICE);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AudioSettings::enabled() const bool AudioSettings::enabled() const
{ {
@ -285,6 +291,14 @@ void AudioSettings::setVolume(uInt32 volume)
normalize(mySettings); normalize(mySettings);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setDevice(uInt32 device)
{
if(!myIsPersistent) return;
mySettings.setValue(SETTING_DEVICE, device);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setEnabled(bool isEnabled) void AudioSettings::setEnabled(bool isEnabled)
{ {

View File

@ -48,6 +48,7 @@ class AudioSettings
static constexpr const char* SETTING_RESAMPLING_QUALITY = "audio.resampling_quality"; static constexpr const char* SETTING_RESAMPLING_QUALITY = "audio.resampling_quality";
static constexpr const char* SETTING_STEREO = "audio.stereo"; static constexpr const char* SETTING_STEREO = "audio.stereo";
static constexpr const char* SETTING_VOLUME = "audio.volume"; static constexpr const char* SETTING_VOLUME = "audio.volume";
static constexpr const char* SETTING_DEVICE = "audio.device";
static constexpr const char* SETTING_ENABLED = "audio.enabled"; static constexpr const char* SETTING_ENABLED = "audio.enabled";
static constexpr const char* SETTING_DPC_PITCH = "audio.dpc_pitch"; static constexpr const char* SETTING_DPC_PITCH = "audio.dpc_pitch";
@ -59,6 +60,7 @@ class AudioSettings
static constexpr ResamplingQuality DEFAULT_RESAMPLING_QUALITY = ResamplingQuality::lanczos_2; static constexpr ResamplingQuality DEFAULT_RESAMPLING_QUALITY = ResamplingQuality::lanczos_2;
static constexpr bool DEFAULT_STEREO = false; static constexpr bool DEFAULT_STEREO = false;
static constexpr uInt32 DEFAULT_VOLUME = 80; static constexpr uInt32 DEFAULT_VOLUME = 80;
static constexpr uInt32 DEFAULT_DEVICE = 0;
static constexpr bool DEFAULT_ENABLED = true; static constexpr bool DEFAULT_ENABLED = true;
static constexpr uInt32 DEFAULT_DPC_PITCH = 20000; static constexpr uInt32 DEFAULT_DPC_PITCH = 20000;
@ -87,6 +89,8 @@ class AudioSettings
uInt32 volume() const; uInt32 volume() const;
uInt32 device() const;
bool enabled() const; bool enabled() const;
uInt32 dpcPitch() const; uInt32 dpcPitch() const;
@ -109,6 +113,8 @@ class AudioSettings
void setVolume(uInt32 volume); void setVolume(uInt32 volume);
void setDevice(uInt32 device);
void setEnabled(bool isEnabled); void setEnabled(bool isEnabled);
void setPersistent(bool isPersistent); void setPersistent(bool isPersistent);

View File

@ -126,6 +126,21 @@ void FilesystemNodeZIP::setFlags(const string& zipfile,
_error = zip_error::NOT_READABLE; _error = zip_error::NOT_READABLE;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FilesystemNodeZIP::exists() const
{
if(_realNode && _realNode->exists())
{
// We need to inspect the actual path, not just the ZIP file itself
myZipHandler->open(_zipFile);
while(myZipHandler->hasNext())
if(BSPF::startsWithIgnoreCase(myZipHandler->next(), _virtualPath))
return true;
}
return false;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FilesystemNodeZIP::getChildren(AbstractFSList& myList, ListMode mode) const bool FilesystemNodeZIP::getChildren(AbstractFSList& myList, ListMode mode) const
{ {
@ -181,7 +196,34 @@ size_t FilesystemNodeZIP::read(ByteBuffer& image) const
while(myZipHandler->hasNext() && !found) while(myZipHandler->hasNext() && !found)
found = myZipHandler->next() == _virtualPath; found = myZipHandler->next() == _virtualPath;
return found ? uInt32(myZipHandler->decompress(image)) : 0; // TODO: 64bit return found ? myZipHandler->decompress(image) : 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNodeZIP::read(stringstream& image) const
{
// For now, we just read into a buffer and store in the stream
// TODO: maybe there's a more efficient way to do this?
ByteBuffer buffer;
size_t size = read(buffer);
if(size > 0)
image.write(reinterpret_cast<char*>(buffer.get()), size);
return size;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNodeZIP::write(const ByteBuffer& buffer, size_t size) const
{
// TODO: Not yet implemented
throw runtime_error("ZIP file not writable");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
size_t FilesystemNodeZIP::write(const stringstream& buffer) const
{
// TODO: Not yet implemented
throw runtime_error("ZIP file not writable");
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -42,7 +42,7 @@ class FilesystemNodeZIP : public AbstractFSNode
*/ */
explicit FilesystemNodeZIP(const string& path); explicit FilesystemNodeZIP(const string& path);
bool exists() const override { return _realNode && _realNode->exists(); } bool exists() const override;
const string& getName() const override { return _name; } const string& getName() const override { return _name; }
void setName(const string& name) override { _name = name; } void setName(const string& name) override { _name = name; }
const string& getPath() const override { return _path; } const string& getPath() const override { return _path; }
@ -63,6 +63,9 @@ class FilesystemNodeZIP : public AbstractFSNode
AbstractFSNodePtr getParent() const override; AbstractFSNodePtr getParent() const override;
size_t read(ByteBuffer& image) const override; size_t read(ByteBuffer& image) const override;
size_t read(stringstream& image) const override;
size_t write(const ByteBuffer& buffer, size_t size) const override;
size_t write(const stringstream& buffer) const override;
private: private:
FilesystemNodeZIP(const string& zipfile, const string& virtualpath, FilesystemNodeZIP(const string& zipfile, const string& virtualpath,

View File

@ -15,6 +15,8 @@
// this file, and for a DISCLAIMER OF ALL WARRANTIES. // this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================ //============================================================================
#include <cmath>
#include "SDL_lib.hxx" #include "SDL_lib.hxx"
#include "bspf.hxx" #include "bspf.hxx"
#include "Logger.hxx" #include "Logger.hxx"
@ -48,8 +50,6 @@ FrameBufferSDL2::FrameBufferSDL2(OSystem& osystem)
// since the structure may be needed before any FBSurface's have // since the structure may be needed before any FBSurface's have
// been created // been created
myPixelFormat = SDL_AllocFormat(SDL_PIXELFORMAT_ARGB8888); myPixelFormat = SDL_AllocFormat(SDL_PIXELFORMAT_ARGB8888);
myWindowedPos = myOSystem.settings().getPoint("windowedpos");
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -101,19 +101,32 @@ void FrameBufferSDL2::queryHardware(vector<Common::Size>& fullscreenRes,
int numModes = SDL_GetNumDisplayModes(i); int numModes = SDL_GetNumDisplayModes(i);
ostringstream s; ostringstream s;
s << "Supported video modes for display " << i << ":"; s << "Supported video modes (" << numModes << ") for display " << i << ":";
Logger::debug(s.str());
string lastRes = "";
for (int m = 0; m < numModes; m++) for (int m = 0; m < numModes; m++)
{ {
SDL_DisplayMode mode; SDL_DisplayMode mode;
ostringstream res;
SDL_GetDisplayMode(i, m, &mode); SDL_GetDisplayMode(i, m, &mode);
s.str(""); res << std::setw(4) << mode.w << "x" << std::setw(4) << mode.h;
s << " " << m << ": " << mode.w << "x" << mode.h << "@" << mode.refresh_rate << "Hz";
if (mode.w == display.w && mode.h == display.h && mode.refresh_rate == display.refresh_rate) if(lastRes != res.str())
s << " (active)"; {
Logger::debug(s.str()); Logger::debug(s.str());
s.str("");
lastRes = res.str();
s << " " << lastRes << ": ";
} }
s << mode.refresh_rate << "Hz";
if(mode.w == display.w && mode.h == display.h && mode.refresh_rate == display.refresh_rate)
s << "* ";
else
s << " ";
}
Logger::debug(s.str());
} }
// Now get the maximum windowed desktop resolution // Now get the maximum windowed desktop resolution
@ -183,27 +196,34 @@ void FrameBufferSDL2::queryHardware(vector<Common::Size>& fullscreenRes,
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int32 FrameBufferSDL2::getCurrentDisplayIndex() bool FrameBufferSDL2::isCurrentWindowPositioned() const
{
ASSERT_MAIN_THREAD;
return !myCenter
&& myWindow && !(SDL_GetWindowFlags(myWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Common::Point FrameBufferSDL2::getCurrentWindowPos() const
{
ASSERT_MAIN_THREAD;
Common::Point pos;
SDL_GetWindowPosition(myWindow, &pos.x, &pos.y);
return pos;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Int32 FrameBufferSDL2::getCurrentDisplayIndex() const
{ {
ASSERT_MAIN_THREAD; ASSERT_MAIN_THREAD;
return SDL_GetWindowDisplayIndex(myWindow); return SDL_GetWindowDisplayIndex(myWindow);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBufferSDL2::updateWindowedPos()
{
ASSERT_MAIN_THREAD;
// only save if the window is not centered and not in full screen mode
if (!myCenter && myWindow && !(SDL_GetWindowFlags(myWindow) & SDL_WINDOW_FULLSCREEN_DESKTOP))
{
// save current windowed position
SDL_GetWindowPosition(myWindow, &myWindowedPos.x, &myWindowedPos.y);
myOSystem.settings().setValue("windowedpos", myWindowedPos);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode) bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
{ {
@ -213,31 +233,13 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
if(SDL_WasInit(SDL_INIT_VIDEO) == 0) if(SDL_WasInit(SDL_INIT_VIDEO) == 0)
return false; return false;
Int32 displayIndex = mode.fsIndex; const bool fullScreen = mode.fsIndex != -1;
if (displayIndex == -1) bool forceCreateRenderer = false;
{
// windowed mode
if (myWindow)
{
// Show it on same screen as the previous window
displayIndex = SDL_GetWindowDisplayIndex(myWindow);
}
if (displayIndex < 0)
{
// fallback to the last used screen if still existing
displayIndex = std::min(myNumDisplays, myOSystem.settings().getInt("display"));
}
}
// save and get last windowed window's position // Get windowed window's last display
updateWindowedPos(); Int32 displayIndex = std::min(myNumDisplays, myOSystem.settings().getInt(getDisplayKey()));
// Get windowed window's last position
// Always recreate renderer (some systems need this) myWindowedPos = myOSystem.settings().getPoint(getPositionKey());
if(myRenderer)
{
SDL_DestroyRenderer(myRenderer);
myRenderer = nullptr;
}
int posX, posY; int posX, posY;
@ -249,7 +251,7 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
posX = myWindowedPos.x; posX = myWindowedPos.x;
posY = myWindowedPos.y; posY = myWindowedPos.y;
// make sure the window is at least partially visibile // Make sure the window is at least partially visibile
int x0 = 0, y0 = 0, x1 = 0, y1 = 0; int x0 = 0, y0 = 0, x1 = 0, y1 = 0;
for (int display = SDL_GetNumVideoDisplays() - 1; display >= 0; display--) for (int display = SDL_GetNumVideoDisplays() - 1; display >= 0; display--)
@ -267,47 +269,45 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
posX = BSPF::clamp(posX, x0 - Int32(mode.screen.w) + 50, x1 - 50); posX = BSPF::clamp(posX, x0 - Int32(mode.screen.w) + 50, x1 - 50);
posY = BSPF::clamp(posY, y0 + 50, y1 - 50); posY = BSPF::clamp(posY, y0 + 50, y1 - 50);
} }
uInt32 flags = mode.fsIndex != -1 ? SDL_WINDOW_FULLSCREEN_DESKTOP : 0;
flags |= SDL_WINDOW_ALLOW_HIGHDPI;
// macOS seems to have issues with destroying the window, and wants to #ifdef ADAPTABLE_REFRESH_SUPPORT
// keep the same handle SDL_DisplayMode adaptedSdlMode;
// Problem is, doing so on other platforms results in flickering when const bool shouldAdapt = fullScreen && myOSystem.settings().getBool("tia.fs_refresh")
// toggling fullscreen windowed mode && gameRefreshRate()
// So we have a special case for macOS // take care of 59.94 Hz
#ifndef BSPF_MACOS && refreshRate() % gameRefreshRate() != 0 && refreshRate() % (gameRefreshRate() - 1) != 0;
// Don't re-create the window if its size hasn't changed, as it's not const bool adaptRefresh = shouldAdapt && adaptRefreshRate(displayIndex, adaptedSdlMode);
// necessary, and causes flashing in fullscreen mode #else
const bool adaptRefresh = false;
#endif
const uInt32 flags = SDL_WINDOW_ALLOW_HIGHDPI
| (fullScreen ? adaptRefresh ? SDL_WINDOW_FULLSCREEN : SDL_WINDOW_FULLSCREEN_DESKTOP : 0);
// Don't re-create the window if its display and size hasn't changed,
// as it's not necessary, and causes flashing in fullscreen mode
if(myWindow) if(myWindow)
{ {
const int d = SDL_GetWindowDisplayIndex(myWindow);
int w, h; int w, h;
SDL_GetWindowSize(myWindow, &w, &h); SDL_GetWindowSize(myWindow, &w, &h);
if(uInt32(w) != mode.screen.w || uInt32(h) != mode.screen.h) if(d != displayIndex || uInt32(w) != mode.screen.w || uInt32(h) != mode.screen.h
|| adaptRefresh)
{ {
SDL_DestroyWindow(myWindow); SDL_DestroyWindow(myWindow);
myWindow = nullptr; myWindow = nullptr;
} }
} }
if(myWindow) if(myWindow)
{ {
// Even though window size stayed the same, the title may have changed // Even though window size stayed the same, the title may have changed
SDL_SetWindowTitle(myWindow, title.c_str()); SDL_SetWindowTitle(myWindow, title.c_str());
SDL_SetWindowPosition(myWindow, posX, posY); SDL_SetWindowPosition(myWindow, posX, posY);
} }
#else
// macOS wants to *never* re-create the window
// This sometimes results in the window being resized *after* it's displayed,
// but at least the code works and doesn't crash
if(myWindow)
{
SDL_SetWindowFullscreen(myWindow, flags);
SDL_SetWindowSize(myWindow, mode.screen.w, mode.screen.h);
SDL_SetWindowPosition(myWindow, posX, posY);
SDL_SetWindowTitle(myWindow, title.c_str());
}
#endif
else else
{ {
forceCreateRenderer = true;
myWindow = SDL_CreateWindow(title.c_str(), posX, posY, myWindow = SDL_CreateWindow(title.c_str(), posX, posY,
mode.screen.w, mode.screen.h, flags); mode.screen.w, mode.screen.h, flags);
if(myWindow == nullptr) if(myWindow == nullptr)
@ -316,13 +316,114 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
Logger::error(msg); Logger::error(msg);
return false; return false;
} }
setWindowIcon(); setWindowIcon();
} }
#ifdef ADAPTABLE_REFRESH_SUPPORT
if(adaptRefresh)
{
// Switch to mode for adapted refresh rate
if(SDL_SetWindowDisplayMode(myWindow, &adaptedSdlMode) != 0)
{
Logger::error("ERROR: Display refresh rate change failed");
}
else
{
ostringstream msg;
msg << "Display refresh rate changed to " << adaptedSdlMode.refresh_rate << " Hz";
Logger::info(msg.str());
}
}
#endif
return createRenderer(forceCreateRenderer);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FrameBufferSDL2::adaptRefreshRate(Int32 displayIndex, SDL_DisplayMode& adaptedSdlMode)
{
SDL_DisplayMode sdlMode;
if(SDL_GetCurrentDisplayMode(displayIndex, &sdlMode) != 0)
{
Logger::error("ERROR: Display mode could not be retrieved");
return false;
}
const int currentRefreshRate = sdlMode.refresh_rate;
const int wantedRefreshRate = gameRefreshRate();
// Take care of rounded refresh rates (e.g. 59.94 Hz)
float factor = std::min(float(currentRefreshRate) / wantedRefreshRate,
float(currentRefreshRate) / (wantedRefreshRate - 1));
// Calculate difference taking care of integer factors (e.g. 100/120)
float bestDiff = std::abs(factor - std::round(factor)) / factor;
bool adapt = false;
// Display refresh rate should be an integer factor of the game's refresh rate
// Note: Modes are scanned with size being first priority,
// therefore the size will never change.
// Check for integer factors 1 (60/50 Hz) and 2 (120/100 Hz)
for(int m = 1; m <= 2; ++m)
{
SDL_DisplayMode closestSdlMode;
sdlMode.refresh_rate = wantedRefreshRate * m;
if(SDL_GetClosestDisplayMode(displayIndex, &sdlMode, &closestSdlMode) == nullptr)
{
Logger::error("ERROR: Closest display mode could not be retrieved");
return adapt;
}
factor = std::min(float(sdlMode.refresh_rate) / sdlMode.refresh_rate,
float(sdlMode.refresh_rate) / (sdlMode.refresh_rate - 1));
const float diff = std::abs(factor - std::round(factor)) / factor;
if(diff < bestDiff)
{
bestDiff = diff;
adaptedSdlMode = closestSdlMode;
adapt = true;
}
}
//cerr << "refresh rate adapt ";
//if(adapt)
// cerr << "required (" << currentRefreshRate << " Hz -> " << adaptedSdlMode.refresh_rate << " Hz)";
//else
// cerr << "not required/possible";
//cerr << endl;
// Only change if the display supports a better refresh rate
return adapt;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool FrameBufferSDL2::createRenderer(bool force)
{
// A new renderer is only created when necessary:
// - new myWindow (force = true)
// - no renderer existing
// - different renderer flags
// - different renderer name
bool recreate = force || myRenderer == nullptr;
uInt32 renderFlags = SDL_RENDERER_ACCELERATED; uInt32 renderFlags = SDL_RENDERER_ACCELERATED;
if(myOSystem.settings().getBool("vsync")) // V'synced blits option
renderFlags |= SDL_RENDERER_PRESENTVSYNC;
const string& video = myOSystem.settings().getString("video"); // Render hint const string& video = myOSystem.settings().getString("video"); // Render hint
SDL_RendererInfo renderInfo;
if(myOSystem.settings().getBool("vsync")
&& !myOSystem.settings().getBool("turbo")) // V'synced blits option
renderFlags |= SDL_RENDERER_PRESENTVSYNC;
// check renderer flags and name
recreate |= (SDL_GetRendererInfo(myRenderer, &renderInfo) != 0)
|| ((renderInfo.flags & (SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC)) != renderFlags
|| (video != renderInfo.name));
if(recreate)
{
//cerr << "Create new renderer for buffer type #" << int(myBufferType) << endl;
if(myRenderer)
SDL_DestroyRenderer(myRenderer);
if(video != "") if(video != "")
SDL_SetHint(SDL_HINT_RENDER_DRIVER, video.c_str()); SDL_SetHint(SDL_HINT_RENDER_DRIVER, video.c_str());
@ -337,9 +438,11 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
Logger::error(msg); Logger::error(msg);
return false; return false;
} }
}
clear(); clear();
SDL_RendererInfo renderinfo; SDL_RendererInfo renderinfo;
if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0) if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0)
myOSystem.settings().setValue("video", renderinfo.name); myOSystem.settings().setValue("video", renderinfo.name);
@ -407,6 +510,36 @@ bool FrameBufferSDL2::fullScreen() const
#endif #endif
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int FrameBufferSDL2::refreshRate() const
{
ASSERT_MAIN_THREAD;
const uInt32 displayIndex = SDL_GetWindowDisplayIndex(myWindow);
SDL_DisplayMode sdlMode;
if(SDL_GetCurrentDisplayMode(displayIndex, &sdlMode) == 0)
return sdlMode.refresh_rate;
if(myWindow != nullptr)
Logger::error("Could not retrieve current display mode");
return 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int FrameBufferSDL2::gameRefreshRate() const
{
if(myOSystem.hasConsole())
{
const string format = myOSystem.console().getFormatString();
const bool isNtsc = format == "NTSC" || format == "PAL60" || format == "SECAM60";
return isNtsc ? 60 : 50; // The code will take care of 59/49 Hz
}
return 0;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBufferSDL2::renderToScreen() void FrameBufferSDL2::renderToScreen()
{ {
@ -419,10 +552,9 @@ void FrameBufferSDL2::renderToScreen()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBufferSDL2::setWindowIcon() void FrameBufferSDL2::setWindowIcon()
{ {
ASSERT_MAIN_THREAD;
#if !defined(BSPF_MACOS) && !defined(RETRON77) #if !defined(BSPF_MACOS) && !defined(RETRON77)
#include "stella_icon.hxx" #include "stella_icon.hxx"
ASSERT_MAIN_THREAD;
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(stella_icon, 32, 32, 32, SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(stella_icon, 32, 32, 32,
32 * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000); 32 * 4, 0xFF0000, 0x00FF00, 0x0000FF, 0xFF000000);

View File

@ -95,6 +95,21 @@ class FrameBufferSDL2 : public FrameBuffer
*/ */
void readPixels(uInt8* buffer, uInt32 pitch, const Common::Rect& rect) const override; void readPixels(uInt8* buffer, uInt32 pitch, const Common::Rect& rect) const override;
/**
This method is called to query if the current window is not centered
or fullscreen.
@return True, if the current window is positioned
*/
bool isCurrentWindowPositioned() const override;
/**
This method is called to query the video hardware for position of
the current window
@return The position of the currently displayed window
*/
Common::Point getCurrentWindowPos() const override;
/** /**
This method is called to query the video hardware for the index This method is called to query the video hardware for the index
of the display the current window is displayed on of the display the current window is displayed on
@ -102,12 +117,7 @@ class FrameBufferSDL2 : public FrameBuffer
@return the current display index or a negative value if no @return the current display index or a negative value if no
window is displayed window is displayed
*/ */
Int32 getCurrentDisplayIndex() override; Int32 getCurrentDisplayIndex() const override;
/**
This method is called to preserve the last current windowed position.
*/
void updateWindowedPos() override;
/** /**
Clear the frame buffer. Clear the frame buffer.
@ -137,12 +147,12 @@ class FrameBufferSDL2 : public FrameBuffer
/** /**
Transform from window to renderer coordinates, x direction Transform from window to renderer coordinates, x direction
*/ */
int scaleX(int x) const { return (x * myRenderW) / myWindowW; } int scaleX(int x) const override { return (x * myRenderW) / myWindowW; }
/** /**
Transform from window to renderer coordinates, y direction Transform from window to renderer coordinates, y direction
*/ */
int scaleY(int y) const { return (y * myRenderH) / myWindowH; } int scaleY(int y) const override { return (y * myRenderH) / myWindowH; }
protected: protected:
////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////
@ -171,6 +181,25 @@ class FrameBufferSDL2 : public FrameBuffer
*/ */
bool setVideoMode(const string& title, const VideoMode& mode) override; bool setVideoMode(const string& title, const VideoMode& mode) override;
/**
Checks if the display refresh rate should be adapted to game refresh rate in (real) fullscreen mode
@param displayIndex The display which should be checked
@param adaptedSdlMode The best matching mode if the refresh rate should be changed
@return True if the refresh rate should be changed
*/
bool adaptRefreshRate(Int32 displayIndex, SDL_DisplayMode& adaptedSdlMode);
/**
Create a new renderer if required
@param force If true, force new renderer creation
@return False on any errors, else true
*/
bool createRenderer(bool force);
/** /**
This method is called to create a surface with the given attributes. This method is called to create a surface with the given attributes.
@ -223,6 +252,16 @@ class FrameBufferSDL2 : public FrameBuffer
*/ */
void determineDimensions(); void determineDimensions();
/**
Retrieve the current display's refresh rate, or 0 if no window
*/
int refreshRate() const override;
/**
Retrieve the current game's refresh rate, or 60 if no game
*/
int gameRefreshRate() const;
private: private:
// The SDL video buffer // The SDL video buffer
SDL_Window* myWindow{nullptr}; SDL_Window* myWindow{nullptr};

View File

@ -185,9 +185,35 @@ JoyMap::JoyMappingArray JoyMap::getEventMapping(const Event::Type event, const E
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string JoyMap::saveMapping(const EventMode mode) const string JoyMap::saveMapping(const EventMode mode) const
{ {
using MapType = std::pair<JoyMapping, Event::Type>;
std::vector<MapType> sortedMap(myMap.begin(), myMap.end());
std::sort(sortedMap.begin(), sortedMap.end(),
[](const MapType& a, const MapType& b)
{
// Event::Type first
if(a.second != b.second)
return a.second < b.second;
if(a.first.button != b.first.button)
return a.first.button < b.first.button;
if(a.first.axis != b.first.axis)
return a.first.axis < b.first.axis;
if(a.first.adir != b.first.adir)
return a.first.adir < b.first.adir;
if(a.first.hat != b.first.hat)
return a.first.hat < b.first.hat;
return a.first.hdir < b.first.hdir;
}
);
ostringstream buf; ostringstream buf;
for (auto item : myMap) for (auto item : sortedMap)
{ {
if (item.first.mode == mode) if (item.first.mode == mode)
{ {

View File

@ -19,6 +19,7 @@
#define CONTROLLERMAP_HXX #define CONTROLLERMAP_HXX
#include <unordered_map> #include <unordered_map>
#include "Event.hxx" #include "Event.hxx"
#include "EventHandlerConstants.hxx" #include "EventHandlerConstants.hxx"

View File

@ -16,6 +16,7 @@
//============================================================================ //============================================================================
#include "KeyMap.hxx" #include "KeyMap.hxx"
#include <map>
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyMap::add(const Event::Type event, const Mapping& mapping) void KeyMap::add(const Event::Type event, const Mapping& mapping)
@ -170,9 +171,26 @@ KeyMap::MappingArray KeyMap::getEventMapping(const Event::Type event, const Even
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string KeyMap::saveMapping(const EventMode mode) const string KeyMap::saveMapping(const EventMode mode) const
{ {
using MapType = std::pair<Mapping, Event::Type>;
std::vector<MapType> sortedMap(myMap.begin(), myMap.end());
std::sort(sortedMap.begin(), sortedMap.end(),
[](const MapType& a, const MapType& b)
{
// Event::Type first
if(a.second != b.second)
return a.second < b.second;
if(a.first.key != b.first.key)
return a.first.key < b.first.key;
return a.first.mod < b.first.mod;
}
);
ostringstream buf; ostringstream buf;
for (auto item : myMap) for (auto item : sortedMap)
{ {
if (item.first.mode == mode) if (item.first.mode == mode)
{ {

View File

@ -21,6 +21,7 @@
#include <unordered_map> #include <unordered_map>
#include "Event.hxx" #include "Event.hxx"
#include "EventHandlerConstants.hxx" #include "EventHandlerConstants.hxx"
#include "StellaKeys.hxx"
/** /**
This class handles keyboard mappings in Stella. This class handles keyboard mappings in Stella.

View File

@ -49,7 +49,7 @@
*/ */
namespace Common { namespace Common {
template <class T, uInt32 CAPACITY = 100> template <typename T, uInt32 CAPACITY = 100>
class LinkedObjectPool class LinkedObjectPool
{ {
public: public:

View File

@ -124,7 +124,7 @@ MouseControl::MouseControl(Console& console, const string& mode)
int m_range = 100; int m_range = 100;
if(!(m_axis >> m_range)) if(!(m_axis >> m_range))
m_range = 100; m_range = 100;
Paddles::setPaddleRange(m_range); Paddles::setDigitalPaddleRange(m_range);
// If the mouse isn't used at all, we still need one item in the list // If the mouse isn't used at all, we still need one item in the list
if(myModeList.size() == 0) if(myModeList.size() == 0)

View File

@ -250,9 +250,9 @@ void PhysicalJoystickHandler::setDefaultAction(int stick,
if(updateDefaults) if(updateDefaults)
{ {
// if there is no existing mapping for the event or // if there is no existing mapping for the event and
// the default mapping for the event is unused, set default key for event // the default mapping for the event is unused, set default key for event
if(j->joyMap.getEventMapping(map.event, mode).size() == 0 || if(j->joyMap.getEventMapping(map.event, mode).size() == 0 &&
!j->joyMap.check(mode, map.button, map.axis, map.adir, map.hat, map.hdir)) !j->joyMap.check(mode, map.button, map.axis, map.adir, map.hat, map.hdir))
{ {
if (map.hat == JOY_CTRL_NONE) if (map.hat == JOY_CTRL_NONE)
@ -687,7 +687,11 @@ void PhysicalJoystickHandler::handleAxisEvent(int stick, int axis, int value)
} }
j->axisLastValue[axis] = value; j->axisLastValue[axis] = value;
} }
#ifdef GUI_SUPPORT
else if(myHandler.hasOverlay()) else if(myHandler.hasOverlay())
{
// A value change lower than Joystick::deadzone indicates analog input which is ignored
if((abs(j->axisLastValue[axis] - value) > Joystick::deadzone()))
{ {
// First, clamp the values to simulate digital input // First, clamp the values to simulate digital input
// (the only thing that the underlying code understands) // (the only thing that the underlying code understands)
@ -702,12 +706,13 @@ void PhysicalJoystickHandler::handleAxisEvent(int stick, int axis, int value)
// (only pass on the event if the state has changed) // (only pass on the event if the state has changed)
if(value != j->axisLastValue[axis]) if(value != j->axisLastValue[axis])
{ {
#ifdef GUI_SUPPORT
myHandler.overlay().handleJoyAxisEvent(stick, JoyAxis(axis), convertAxisValue(value), button); myHandler.overlay().handleJoyAxisEvent(stick, JoyAxis(axis), convertAxisValue(value), button);
#endif
}
}
j->axisLastValue[axis] = value; j->axisLastValue[axis] = value;
} }
} #endif
} }
} }

View File

@ -17,13 +17,7 @@
#include "OSystem.hxx" #include "OSystem.hxx"
#include "Console.hxx" #include "Console.hxx"
#include "Settings.hxx"
#include "EventHandler.hxx" #include "EventHandler.hxx"
#include "Sound.hxx"
#include "StateManager.hxx"
#include "StellaKeys.hxx"
#include "TIASurface.hxx"
#include "PNGLibrary.hxx"
#include "PKeyboardHandler.hxx" #include "PKeyboardHandler.hxx"
#ifdef DEBUGGER_SUPPORT #ifdef DEBUGGER_SUPPORT
@ -45,6 +39,7 @@ PhysicalKeyboardHandler::PhysicalKeyboardHandler(OSystem& system, EventHandler&
myHandler(handler) myHandler(handler)
{ {
Int32 version = myOSystem.settings().getInt("event_ver"); Int32 version = myOSystem.settings().getInt("event_ver");
bool updateDefaults = false;
// Compare if event list version has changed so that key maps became invalid // Compare if event list version has changed so that key maps became invalid
if (version == Event::VERSION) if (version == Event::VERSION)
@ -59,11 +54,37 @@ PhysicalKeyboardHandler::PhysicalKeyboardHandler(OSystem& system, EventHandler&
myKeyMap.loadMapping(list, EventMode::kKeypadMode); myKeyMap.loadMapping(list, EventMode::kKeypadMode);
list = myOSystem.settings().getString("keymap_ui"); list = myOSystem.settings().getString("keymap_ui");
myKeyMap.loadMapping(list, EventMode::kMenuMode); myKeyMap.loadMapping(list, EventMode::kMenuMode);
updateDefaults = true;
} }
myKeyMap.enableMod() = myOSystem.settings().getBool("modcombo"); myKeyMap.enableMod() = myOSystem.settings().getBool("modcombo");
setDefaultMapping(Event::NoType, EventMode::kEmulationMode, true); setDefaultMapping(Event::NoType, EventMode::kEmulationMode, updateDefaults);
setDefaultMapping(Event::NoType, EventMode::kMenuMode, true); setDefaultMapping(Event::NoType, EventMode::kMenuMode, updateDefaults);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool PhysicalKeyboardHandler::isMappingUsed(EventMode mode, const EventMapping& map) const
{
// Menu events can only interfere with
// - other menu events
if(mode == EventMode::kMenuMode)
return myKeyMap.check(EventMode::kMenuMode, map.key, map.mod);
// Controller events can interfere with
// - other events of the same controller
// - and common emulation events
if(mode != EventMode::kCommonMode)
return myKeyMap.check(mode, map.key, map.mod)
|| myKeyMap.check(EventMode::kCommonMode, map.key, map.mod);
// Common emulation events can interfere with
// - other common emulation events
// - and all controller events
return myKeyMap.check(EventMode::kCommonMode, map.key, map.mod)
|| myKeyMap.check(EventMode::kJoystickMode, map.key, map.mod)
|| myKeyMap.check(EventMode::kPaddlesMode, map.key, map.mod)
|| myKeyMap.check(EventMode::kKeypadMode, map.key, map.mod)
|| myKeyMap.check(EventMode::kCompuMateMode, map.key, map.mod);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -80,10 +101,10 @@ void PhysicalKeyboardHandler::setDefaultKey(EventMapping map, Event::Type event,
if (updateDefaults) if (updateDefaults)
{ {
// if there is no existing mapping for the event or // if there is no existing mapping for the event and
// the default mapping for the event is unused, set default key for event // the default mapping for the event is unused, set default key for event
if (myKeyMap.getEventMapping(map.event, mode).size() == 0 || if (myKeyMap.getEventMapping(map.event, mode).size() == 0 &&
!myKeyMap.check(mode, map.key, map.mod)) !isMappingUsed(mode, map))
{ {
addMapping(map.event, mode, map.key, StellaMod(map.mod)); addMapping(map.event, mode, map.key, StellaMod(map.mod));
} }
@ -395,6 +416,7 @@ void PhysicalKeyboardHandler::handleEvent(StellaKey key, StellaMod mod, bool pre
{ {
case EventHandlerState::EMULATION: case EventHandlerState::EMULATION:
case EventHandlerState::PAUSE: case EventHandlerState::PAUSE:
case EventHandlerState::PLAYBACK:
myHandler.handleEvent(myKeyMap.get(EventMode::kEmulationMode, key, mod), pressed, repeated); myHandler.handleEvent(myKeyMap.get(EventMode::kEmulationMode, key, mod), pressed, repeated);
break; break;
@ -424,7 +446,11 @@ PhysicalKeyboardHandler::EventMappingArray PhysicalKeyboardHandler::DefaultCommo
{Event::LoadState, KBDK_F11}, {Event::LoadState, KBDK_F11},
{Event::LoadAllStates, KBDK_F11, MOD3}, {Event::LoadAllStates, KBDK_F11, MOD3},
{Event::TakeSnapshot, KBDK_F12}, {Event::TakeSnapshot, KBDK_F12},
#ifdef BSPF_MACOS
{Event::TogglePauseMode, KBDK_P, KBDM_SHIFT | MOD3},
#else
{Event::TogglePauseMode, KBDK_PAUSE}, {Event::TogglePauseMode, KBDK_PAUSE},
#endif
{Event::OptionsMenuMode, KBDK_TAB}, {Event::OptionsMenuMode, KBDK_TAB},
{Event::CmdMenuMode, KBDK_BACKSLASH}, {Event::CmdMenuMode, KBDK_BACKSLASH},
{Event::TimeMachineMode, KBDK_T, KBDM_SHIFT}, {Event::TimeMachineMode, KBDK_T, KBDM_SHIFT},
@ -436,45 +462,83 @@ PhysicalKeyboardHandler::EventMappingArray PhysicalKeyboardHandler::DefaultCommo
{Event::Quit, KBDK_Q, KBDM_CTRL}, {Event::Quit, KBDK_Q, KBDM_CTRL},
#endif #endif
{Event::ReloadConsole, KBDK_R, KBDM_CTRL}, {Event::ReloadConsole, KBDK_R, KBDM_CTRL},
{Event::PreviousMultiCartRom, KBDK_R, KBDM_SHIFT | KBDM_CTRL},
{Event::VidmodeDecrease, KBDK_MINUS, MOD3}, {Event::VidmodeDecrease, KBDK_MINUS, MOD3},
{Event::VidmodeIncrease, KBDK_EQUALS, MOD3}, {Event::VidmodeIncrease, KBDK_EQUALS, MOD3},
{Event::VCenterDecrease, KBDK_PAGEUP, MOD3}, {Event::VCenterDecrease, KBDK_PAGEUP, MOD3},
{Event::VCenterIncrease, KBDK_PAGEDOWN, MOD3}, {Event::VCenterIncrease, KBDK_PAGEDOWN, MOD3},
{Event::ScanlineAdjustDecrease, KBDK_PAGEDOWN, KBDM_SHIFT | MOD3}, {Event::VSizeAdjustDecrease, KBDK_PAGEDOWN, KBDM_SHIFT | MOD3},
{Event::ScanlineAdjustIncrease, KBDK_PAGEUP, KBDM_SHIFT | MOD3}, {Event::VSizeAdjustIncrease, KBDK_PAGEUP, KBDM_SHIFT | MOD3},
{Event::VolumeDecrease, KBDK_LEFTBRACKET, MOD3}, {Event::VolumeDecrease, KBDK_LEFTBRACKET, MOD3},
{Event::VolumeIncrease, KBDK_RIGHTBRACKET, MOD3}, {Event::VolumeIncrease, KBDK_RIGHTBRACKET, MOD3},
{Event::SoundToggle, KBDK_RIGHTBRACKET, KBDM_CTRL}, {Event::SoundToggle, KBDK_RIGHTBRACKET, KBDM_CTRL},
{Event::ToggleFullScreen, KBDK_RETURN, MOD3}, {Event::ToggleFullScreen, KBDK_RETURN, MOD3},
{Event::ToggleAdaptRefresh, KBDK_R, MOD3},
{Event::OverscanDecrease, KBDK_PAGEDOWN, KBDM_SHIFT}, {Event::OverscanDecrease, KBDK_PAGEDOWN, KBDM_SHIFT},
{Event::OverscanIncrease, KBDK_PAGEUP, KBDM_SHIFT}, {Event::OverscanIncrease, KBDK_PAGEUP, KBDM_SHIFT},
{Event::VidmodeStd, KBDK_1, MOD3}, //{Event::VidmodeStd, KBDK_1, MOD3},
{Event::VidmodeRGB, KBDK_2, MOD3}, //{Event::VidmodeRGB, KBDK_2, MOD3},
{Event::VidmodeSVideo, KBDK_3, MOD3}, //{Event::VidmodeSVideo, KBDK_3, MOD3},
{Event::VidModeComposite, KBDK_4, MOD3}, //{Event::VidModeComposite, KBDK_4, MOD3},
{Event::VidModeBad, KBDK_5, MOD3}, //{Event::VidModeBad, KBDK_5, MOD3},
{Event::VidModeCustom, KBDK_6, MOD3}, //{Event::VidModeCustom, KBDK_6, MOD3},
{Event::PreviousAttribute, KBDK_7, KBDM_SHIFT | MOD3}, {Event::PreviousVideoMode, KBDK_1, KBDM_SHIFT | MOD3},
{Event::NextAttribute, KBDK_7, MOD3}, {Event::NextVideoMode, KBDK_1, MOD3},
{Event::DecreaseAttribute, KBDK_8, KBDM_SHIFT | MOD3}, {Event::PreviousAttribute, KBDK_2, KBDM_SHIFT | MOD3},
{Event::IncreaseAttribute, KBDK_8, MOD3}, {Event::NextAttribute, KBDK_2, MOD3},
{Event::PhosphorDecrease, KBDK_9, KBDM_SHIFT | MOD3}, {Event::DecreaseAttribute, KBDK_3, KBDM_SHIFT | MOD3},
{Event::PhosphorIncrease, KBDK_9, MOD3}, {Event::IncreaseAttribute, KBDK_3, MOD3},
{Event::PhosphorDecrease, KBDK_4, KBDM_SHIFT | MOD3},
{Event::PhosphorIncrease, KBDK_4, MOD3},
{Event::TogglePhosphor, KBDK_P, MOD3}, {Event::TogglePhosphor, KBDK_P, MOD3},
{Event::ScanlinesDecrease, KBDK_0, KBDM_SHIFT | MOD3}, {Event::ScanlinesDecrease, KBDK_5, KBDM_SHIFT | MOD3},
{Event::ScanlinesIncrease, KBDK_0, MOD3}, {Event::ScanlinesIncrease, KBDK_5, MOD3},
{Event::PreviousPaletteAttribute, KBDK_9, KBDM_SHIFT | MOD3},
{Event::NextPaletteAttribute, KBDK_9, MOD3},
{Event::PaletteAttributeDecrease, KBDK_0, KBDM_SHIFT | MOD3},
{Event::PaletteAttributeIncrease, KBDK_0, MOD3},
{Event::ToggleColorLoss, KBDK_L, KBDM_CTRL}, {Event::ToggleColorLoss, KBDK_L, KBDM_CTRL},
{Event::TogglePalette, KBDK_P, KBDM_CTRL}, {Event::PaletteDecrease, KBDK_P, KBDM_SHIFT | KBDM_CTRL},
{Event::PaletteIncrease, KBDK_P, KBDM_CTRL},
#ifndef BSPF_MACOS
{Event::PreviousSetting, KBDK_END},
{Event::NextSetting, KBDK_HOME},
{Event::PreviousSettingGroup, KBDK_END, KBDM_CTRL},
{Event::NextSettingGroup, KBDK_HOME, KBDM_CTRL},
#else
// HOME & END keys are swapped on Mac keyboards
{Event::PreviousSetting, KBDK_HOME},
{Event::NextSetting, KBDK_END},
{Event::PreviousSettingGroup, KBDK_HOME, KBDM_CTRL},
{Event::NextSettingGroup, KBDK_END, KBDM_CTRL},
#endif
{Event::PreviousSetting, KBDK_KP_1},
{Event::NextSetting, KBDK_KP_7},
{Event::PreviousSettingGroup, KBDK_KP_1, KBDM_CTRL},
{Event::NextSettingGroup, KBDK_KP_7, KBDM_CTRL},
{Event::SettingDecrease, KBDK_PAGEDOWN},
{Event::SettingDecrease, KBDK_KP_3, KBDM_CTRL},
{Event::SettingIncrease, KBDK_PAGEUP},
{Event::SettingIncrease, KBDK_KP_9, KBDM_CTRL},
{Event::ToggleInter, KBDK_I, KBDM_CTRL}, {Event::ToggleInter, KBDK_I, KBDM_CTRL},
{Event::DecreaseSpeed, KBDK_S, KBDM_SHIFT | KBDM_CTRL},
{Event::IncreaseSpeed, KBDK_S, KBDM_CTRL },
{Event::ToggleTurbo, KBDK_T, KBDM_CTRL},
{Event::ToggleJitter, KBDK_J, MOD3}, {Event::ToggleJitter, KBDK_J, MOD3},
{Event::ToggleFrameStats, KBDK_L, MOD3}, {Event::ToggleFrameStats, KBDK_L, MOD3},
{Event::ToggleTimeMachine, KBDK_T, MOD3}, {Event::ToggleTimeMachine, KBDK_T, MOD3},
#ifdef PNG_SUPPORT #ifdef PNG_SUPPORT
{Event::ToggleContSnapshots, KBDK_S, MOD3}, {Event::ToggleContSnapshots, KBDK_S, MOD3},
{Event::ToggleContSnapshotsFrame, KBDK_S, KBDM_SHIFT | MOD3}, {Event::ToggleContSnapshotsFrame, KBDK_S, KBDM_SHIFT | MOD3},
#endif #endif
{Event::DecreaseAutoFire, KBDK_A, KBDM_SHIFT | KBDM_CTRL},
{Event::IncreaseAutoFire, KBDK_A, KBDM_CTRL },
{Event::HandleMouseControl, KBDK_0, KBDM_CTRL}, {Event::HandleMouseControl, KBDK_0, KBDM_CTRL},
{Event::ToggleGrabMouse, KBDK_G, KBDM_CTRL}, {Event::ToggleGrabMouse, KBDK_G, KBDM_CTRL},
{Event::ToggleSAPortOrder, KBDK_1, KBDM_CTRL}, {Event::ToggleSAPortOrder, KBDK_1, KBDM_CTRL},
@ -506,6 +570,7 @@ PhysicalKeyboardHandler::EventMappingArray PhysicalKeyboardHandler::DefaultCommo
{Event::Unwind10Menu, KBDK_RIGHT, KBDM_SHIFT | MOD3}, {Event::Unwind10Menu, KBDK_RIGHT, KBDM_SHIFT | MOD3},
{Event::UnwindAllMenu, KBDK_UP, MOD3}, {Event::UnwindAllMenu, KBDK_UP, MOD3},
{Event::HighScoresMenuMode, KBDK_INSERT}, {Event::HighScoresMenuMode, KBDK_INSERT},
{Event::TogglePlayBackMode, KBDK_SPACE, KBDM_SHIFT},
#if defined(RETRON77) #if defined(RETRON77)
{Event::ConsoleColorToggle, KBDK_F4}, // back ("COLOR","B/W") {Event::ConsoleColorToggle, KBDK_F4}, // back ("COLOR","B/W")

View File

@ -87,6 +87,9 @@ class PhysicalKeyboardHandler
}; };
using EventMappingArray = std::vector<EventMapping>; using EventMappingArray = std::vector<EventMapping>;
// Checks if the given mapping is used by any event mode
bool isMappingUsed(EventMode mode, const EventMapping& map) const;
void setDefaultKey(EventMapping map, Event::Type event = Event::NoType, void setDefaultKey(EventMapping map, Event::Type event = Event::NoType,
EventMode mode = EventMode::kEmulationMode, bool updateDefaults = false); EventMode mode = EventMode::kEmulationMode, bool updateDefaults = false);

View File

@ -29,6 +29,7 @@
#include "TIASurface.hxx" #include "TIASurface.hxx"
#include "Version.hxx" #include "Version.hxx"
#include "PNGLibrary.hxx" #include "PNGLibrary.hxx"
#include "Rect.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PNGLibrary::PNGLibrary(OSystem& osystem) PNGLibrary::PNGLibrary(OSystem& osystem)
@ -51,7 +52,7 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface)
throw runtime_error(s); throw runtime_error(s);
}; };
ifstream in(filename, std::ios_base::binary); std::ifstream in(filename, std::ios_base::binary);
if(!in.is_open()) if(!in.is_open())
loadImageERROR("No snapshot found"); loadImageERROR("No snapshot found");
@ -88,7 +89,7 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface)
} }
else if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) else if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
{ {
loadImageERROR("Greyscale PNG images not supported"); png_set_gray_to_rgb(png_ptr);
} }
else if(color_type == PNG_COLOR_TYPE_PALETTE) else if(color_type == PNG_COLOR_TYPE_PALETTE)
{ {
@ -124,12 +125,18 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::saveImage(const string& filename, const VariantList& comments) void PNGLibrary::saveImage(const string& filename, const VariantList& comments)
{ {
ofstream out(filename, std::ios_base::binary); std::ofstream out(filename, std::ios_base::binary);
if(!out.is_open()) if(!out.is_open())
throw runtime_error("ERROR: Couldn't create snapshot file"); throw runtime_error("ERROR: Couldn't create snapshot file");
const FrameBuffer& fb = myOSystem.frameBuffer(); const FrameBuffer& fb = myOSystem.frameBuffer();
const Common::Rect& rect = fb.imageRect();
const Common::Rect& rectUnscaled = fb.imageRect();
const Common::Rect rect(
Common::Point(fb.scaleX(rectUnscaled.x()), fb.scaleY(rectUnscaled.y())),
fb.scaleX(rectUnscaled.w()), fb.scaleY(rectUnscaled.h())
);
png_uint_32 width = rect.w(), height = rect.h(); png_uint_32 width = rect.w(), height = rect.h();
// Get framebuffer pixel data (we get ABGR format) // Get framebuffer pixel data (we get ABGR format)
@ -149,7 +156,7 @@ void PNGLibrary::saveImage(const string& filename, const VariantList& comments)
void PNGLibrary::saveImage(const string& filename, const FBSurface& surface, void PNGLibrary::saveImage(const string& filename, const FBSurface& surface,
const Common::Rect& rect, const VariantList& comments) const Common::Rect& rect, const VariantList& comments)
{ {
ofstream out(filename, std::ios_base::binary); std::ofstream out(filename, std::ios_base::binary);
if(!out.is_open()) if(!out.is_open())
throw runtime_error("ERROR: Couldn't create snapshot file"); throw runtime_error("ERROR: Couldn't create snapshot file");
@ -175,7 +182,7 @@ void PNGLibrary::saveImage(const string& filename, const FBSurface& surface,
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::saveImageToDisk(ofstream& out, const vector<png_bytep>& rows, void PNGLibrary::saveImageToDisk(std::ofstream& out, const vector<png_bytep>& rows,
png_uint_32 width, png_uint_32 height, const VariantList& comments) png_uint_32 width, png_uint_32 height, const VariantList& comments)
{ {
png_structp png_ptr = nullptr; png_structp png_ptr = nullptr;
@ -290,7 +297,7 @@ void PNGLibrary::takeSnapshot(uInt32 number)
// Figure out the correct snapshot name // Figure out the correct snapshot name
string filename; string filename;
bool showmessage = number == 0; bool showmessage = number == 0;
string sspath = myOSystem.snapshotSaveDir() + string sspath = myOSystem.snapshotSaveDir().getPath() +
(myOSystem.settings().getString("snapname") != "int" ? (myOSystem.settings().getString("snapname") != "int" ?
myOSystem.romFile().getNameWithExt("") myOSystem.romFile().getNameWithExt("")
: myOSystem.console().properties().get(PropType::Cart_Name)); : myOSystem.console().properties().get(PropType::Cart_Name));
@ -449,21 +456,21 @@ void PNGLibrary::writeComments(png_structp png_ptr, png_infop info_ptr,
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::png_read_data(png_structp ctx, png_bytep area, png_size_t size) void PNGLibrary::png_read_data(png_structp ctx, png_bytep area, png_size_t size)
{ {
(static_cast<ifstream*>(png_get_io_ptr(ctx)))->read( (static_cast<std::ifstream*>(png_get_io_ptr(ctx)))->read(
reinterpret_cast<char *>(area), size); reinterpret_cast<char *>(area), size);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::png_write_data(png_structp ctx, png_bytep area, png_size_t size) void PNGLibrary::png_write_data(png_structp ctx, png_bytep area, png_size_t size)
{ {
(static_cast<ofstream*>(png_get_io_ptr(ctx)))->write( (static_cast<std::ofstream*>(png_get_io_ptr(ctx)))->write(
reinterpret_cast<const char *>(area), size); reinterpret_cast<const char *>(area), size);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PNGLibrary::png_io_flush(png_structp ctx) void PNGLibrary::png_io_flush(png_structp ctx)
{ {
(static_cast<ofstream*>(png_get_io_ptr(ctx)))->flush(); (static_cast<std::ofstream*>(png_get_io_ptr(ctx)))->flush();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -165,7 +165,7 @@ class PNGLibrary
@param height The height of the PNG image @param height The height of the PNG image
@param comments The text comments to add to the PNG image @param comments The text comments to add to the PNG image
*/ */
void saveImageToDisk(ofstream& out, const vector<png_bytep>& rows, void saveImageToDisk(std::ofstream& out, const vector<png_bytep>& rows,
png_uint_32 width, png_uint_32 height, png_uint_32 width, png_uint_32 height,
const VariantList& comments); const VariantList& comments);

View File

@ -0,0 +1,723 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <cmath>
#include "Console.hxx"
#include "FrameBuffer.hxx"
#include "PaletteHandler.hxx"
PaletteHandler::PaletteHandler(OSystem& system)
: myOSystem(system)
{
// Load user-defined palette for this ROM
loadUserPalette();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PaletteHandler::PaletteType PaletteHandler::toPaletteType(const string& name) const
{
if(name == SETTING_Z26)
return PaletteType::Z26;
if(name == SETTING_USER && myUserPaletteDefined)
return PaletteType::User;
if(name == SETTING_CUSTOM)
return PaletteType::Custom;
return PaletteType::Standard;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string PaletteHandler::toPaletteName(PaletteType type) const
{
const string SETTING_NAMES[int(PaletteType::NumTypes)] = {
SETTING_STANDARD, SETTING_Z26, SETTING_USER, SETTING_CUSTOM
};
return SETTING_NAMES[type];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::cyclePalette(int direction)
{
const string MESSAGES[PaletteType::NumTypes] = {
"Standard Stella", "Z26", "User-defined", "Custom"
};
int type = toPaletteType(myOSystem.settings().getString("palette"));
do {
type = BSPF::clampw(type + direction, int(PaletteType::MinType), int(PaletteType::MaxType));
} while(type == PaletteType::User && !myUserPaletteDefined);
const string palette = toPaletteName(PaletteType(type));
const string message = MESSAGES[type] + " palette";
myOSystem.frameBuffer().showMessage(message);
setPalette(palette);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::showAdjustableMessage()
{
const bool isPhaseShift = myAdjustables[myCurrentAdjustable].value == nullptr;
ostringstream msg, buf;
msg << "Palette " << myAdjustables[myCurrentAdjustable].name;
if(isPhaseShift)
{
const ConsoleTiming timing = myOSystem.console().timing();
const bool isNTSC = timing == ConsoleTiming::ntsc;
const float value =
myOSystem.console().timing() == ConsoleTiming::pal ? myPhasePAL : myPhaseNTSC;
buf << std::fixed << std::setprecision(1) << value << DEGREE;
myOSystem.frameBuffer().showMessage(
"Palette phase shift", buf.str(), value,
(isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) - MAX_SHIFT,
(isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT) + MAX_SHIFT);
}
else
{
const int value = scaleTo100(*myAdjustables[myCurrentAdjustable].value);
buf << value << "%";
myOSystem.frameBuffer().showMessage(
msg.str(), buf.str(), value);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::cycleAdjustable(int direction)
{
const bool isCustomPalette = SETTING_CUSTOM == myOSystem.settings().getString("palette");
bool isPhaseShift;
do {
myCurrentAdjustable = BSPF::clampw(int(myCurrentAdjustable + direction), 0, NUM_ADJUSTABLES - 1);
isPhaseShift = myAdjustables[myCurrentAdjustable].value == nullptr;
// skip phase shift when 'Custom' palette is not selected
if(!direction && isPhaseShift && !isCustomPalette)
myCurrentAdjustable++;
} while(isPhaseShift && !isCustomPalette);
showAdjustableMessage();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::changeAdjustable(int adjustable, int direction)
{
const bool isCustomPalette = SETTING_CUSTOM == myOSystem.settings().getString("palette");
const bool isPhaseShift = myAdjustables[adjustable].value == nullptr;
myCurrentAdjustable = adjustable;
if(isPhaseShift && !isCustomPalette)
myCurrentAdjustable++;
changeCurrentAdjustable(direction);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::changeCurrentAdjustable(int direction)
{
if(myAdjustables[myCurrentAdjustable].value == nullptr)
changeColorPhaseShift(direction);
else
{
int newVal = scaleTo100(*myAdjustables[myCurrentAdjustable].value);
newVal = BSPF::clamp(newVal + direction * 1, 0, 100);
*myAdjustables[myCurrentAdjustable].value = scaleFrom100(newVal);
showAdjustableMessage();
setPalette();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::changeColorPhaseShift(int direction)
{
const ConsoleTiming timing = myOSystem.console().timing();
// SECAM is not supported
if(timing != ConsoleTiming::secam)
{
const bool isNTSC = timing == ConsoleTiming::ntsc;
const float shift = isNTSC ? DEF_NTSC_SHIFT : DEF_PAL_SHIFT;
float newPhase = isNTSC ? myPhaseNTSC : myPhasePAL;
newPhase = BSPF::clamp(newPhase + direction * 0.3F, shift - MAX_SHIFT, shift + MAX_SHIFT);
if(isNTSC)
myPhaseNTSC = newPhase;
else
myPhasePAL = newPhase;
generateCustomPalette(timing);
setPalette(SETTING_CUSTOM);
showAdjustableMessage();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::loadConfig(const Settings& settings)
{
// Load adjustables
myPhaseNTSC = BSPF::clamp(settings.getFloat("pal.phase_ntsc"),
DEF_NTSC_SHIFT - MAX_SHIFT, DEF_NTSC_SHIFT + MAX_SHIFT);
myPhasePAL = BSPF::clamp(settings.getFloat("pal.phase_pal"),
DEF_PAL_SHIFT - MAX_SHIFT, DEF_PAL_SHIFT + MAX_SHIFT);
myHue = BSPF::clamp(settings.getFloat("pal.hue"), -1.0F, 1.0F);
mySaturation = BSPF::clamp(settings.getFloat("pal.saturation"), -1.0F, 1.0F);
myContrast = BSPF::clamp(settings.getFloat("pal.contrast"), -1.0F, 1.0F);
myBrightness = BSPF::clamp(settings.getFloat("pal.brightness"), -1.0F, 1.0F);
myGamma = BSPF::clamp(settings.getFloat("pal.gamma"), -1.0F, 1.0F);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::saveConfig(Settings& settings) const
{
// Save adjustables
settings.setValue("pal.phase_ntsc", myPhaseNTSC);
settings.setValue("pal.phase_pal", myPhasePAL);
settings.setValue("pal.hue", myHue);
settings.setValue("pal.saturation", mySaturation);
settings.setValue("pal.contrast", myContrast);
settings.setValue("pal.brightness", myBrightness);
settings.setValue("pal.gamma", myGamma);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::setAdjustables(const Adjustable& adjustable)
{
myPhaseNTSC = adjustable.phaseNtsc / 10.F;
myPhasePAL = adjustable.phasePal / 10.F;
myHue = scaleFrom100(adjustable.hue);
mySaturation = scaleFrom100(adjustable.saturation);
myContrast = scaleFrom100(adjustable.contrast);
myBrightness = scaleFrom100(adjustable.brightness);
myGamma = scaleFrom100(adjustable.gamma);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::getAdjustables(Adjustable& adjustable) const
{
adjustable.phaseNtsc = myPhaseNTSC * 10.F;
adjustable.phasePal = myPhasePAL * 10.F;
adjustable.hue = scaleTo100(myHue);
adjustable.saturation = scaleTo100(mySaturation);
adjustable.contrast = scaleTo100(myContrast);
adjustable.brightness = scaleTo100(myBrightness);
adjustable.gamma = scaleTo100(myGamma);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::setPalette(const string& name)
{
myOSystem.settings().setValue("palette", name);
setPalette();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::setPalette()
{
if(myOSystem.hasConsole())
{
const string& name = myOSystem.settings().getString("palette");
// Look at all the palettes, since we don't know which one is
// currently active
static constexpr BSPF::array2D<const PaletteArray*, PaletteType::NumTypes, int(ConsoleTiming::numTimings)> palettes = {{
{ &ourNTSCPalette, &ourPALPalette, &ourSECAMPalette },
{ &ourNTSCPaletteZ26, &ourPALPaletteZ26, &ourSECAMPaletteZ26 },
{ &ourUserNTSCPalette, &ourUserPALPalette, &ourUserSECAMPalette },
{ &ourCustomNTSCPalette, &ourCustomPALPalette, &ourSECAMPalette }
}};
// See which format we should be using
const ConsoleTiming timing = myOSystem.console().timing();
const PaletteType paletteType = toPaletteType(name);
// Now consider the current display format
const PaletteArray* palette = palettes[paletteType][int(timing)];
if(paletteType == PaletteType::Custom)
generateCustomPalette(timing);
myOSystem.frameBuffer().setTIAPalette(adjustedPalette(*palette));
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PaletteArray PaletteHandler::adjustedPalette(const PaletteArray& palette)
{
PaletteArray destPalette;
// Constants for saturation and gray scale calculation
constexpr float PR = .2989F;
constexpr float PG = .5870F;
constexpr float PB = .1140F;
// Generate adjust table
constexpr int ADJUST_SIZE = 256;
constexpr int RGB_UNIT = 1 << 8;
constexpr float RGB_OFFSET = 0.5F;
const float hue = myHue;
const float brightness = myBrightness * (0.5F * RGB_UNIT) + RGB_OFFSET;
const float contrast = myContrast * (0.5F * RGB_UNIT) + RGB_UNIT;
const float saturation = mySaturation + 1;
const float gamma = 1.1333F - myGamma * 0.5F;
/* match common PC's 2.2 gamma to TV's 2.65 gamma */
constexpr float toFloat = 1.F / (ADJUST_SIZE - 1);
std::array<float, ADJUST_SIZE> adjust;
for(int i = 0; i < ADJUST_SIZE; i++)
adjust[i] = powf(i * toFloat, gamma) * contrast + brightness;
// Transform original palette into destination palette
for(size_t i = 0; i < destPalette.size(); i += 2)
{
const uInt32 pixel = palette[i];
int r = (pixel >> 16) & 0xff;
int g = (pixel >> 8) & 0xff;
int b = (pixel >> 0) & 0xff;
// adjust hue (different for NTSC and PAL?) and saturation
adjustHueSaturation(r, g, b, hue, saturation);
// adjust contrast, brightness, gamma
r = adjust[r];
g = adjust[g];
b = adjust[b];
r = BSPF::clamp(r, 0, 255);
g = BSPF::clamp(g, 0, 255);
b = BSPF::clamp(b, 0, 255);
destPalette[i] = (r << 16) + (g << 8) + b;
// Fill the odd numbered palette entries with gray values (calculated
// using the standard RGB -> grayscale conversion formula)
// Used for PAL color-loss data and 'greying out' the frame in the debugger.
const uInt8 lum = static_cast<uInt8>((r * PR) + (g * PG) + (b * PB));
destPalette[i + 1] = (lum << 16) + (lum << 8) + lum;
}
return destPalette;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::loadUserPalette()
{
if (!myOSystem.checkUserPalette(true))
return;
ByteBuffer in;
try { myOSystem.paletteFile().read(in); }
catch(...) { return; }
uInt8* pixbuf = in.get();
for(int i = 0; i < 128; i++, pixbuf += 3) // NTSC palette
{
const uInt32 pixel = (int(pixbuf[0]) << 16) + (int(pixbuf[1]) << 8) + int(pixbuf[2]);
ourUserNTSCPalette[(i<<1)] = pixel;
}
for(int i = 0; i < 128; i++, pixbuf += 3) // PAL palette
{
const uInt32 pixel = (int(pixbuf[0]) << 16) + (int(pixbuf[1]) << 8) + int(pixbuf[2]);
ourUserPALPalette[(i<<1)] = pixel;
}
std::array<uInt32, 16> secam; // All 8 24-bit pixels, plus 8 colorloss pixels
for(int i = 0; i < 8; i++, pixbuf += 3) // SECAM palette
{
const uInt32 pixel = (int(pixbuf[0]) << 16) + (int(pixbuf[1]) << 8) + int(pixbuf[2]);
secam[(i<<1)] = pixel;
secam[(i<<1)+1] = 0;
}
uInt32* ptr = ourUserSECAMPalette.data();
for(int i = 0; i < 16; ++i)
{
const uInt32* s = secam.data();
for(int j = 0; j < 16; ++j)
*ptr++ = *s++;
}
myUserPaletteDefined = true;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::generateCustomPalette(ConsoleTiming timing)
{
constexpr int NUM_CHROMA = 16;
constexpr int NUM_LUMA = 8;
constexpr float SATURATION = 0.25F; // default saturation
float color[NUM_CHROMA][2] = {{0.0F}};
if(timing == ConsoleTiming::ntsc)
{
// YIQ is YUV shifted by 33°
constexpr float offset = 33 * BSPF::PI_f / 180;
const float shift = myPhaseNTSC * BSPF::PI_f / 180;
// color 0 is grayscale
for(int chroma = 1; chroma < NUM_CHROMA; chroma++)
{
color[chroma][0] = SATURATION * sinf(offset + shift * (chroma - 1));
color[chroma][1] = SATURATION * cosf(offset + shift * (chroma - 1) - BSPF::PI_f);
}
for(int chroma = 0; chroma < NUM_CHROMA; chroma++)
{
const float I = color[chroma][0];
const float Q = color[chroma][1];
for(int luma = 0; luma < NUM_LUMA; luma++)
{
const float Y = 0.05F + luma / 8.24F; // 0.05..~0.90
float R = Y + 0.956F * I + 0.621F * Q;
float G = Y - 0.272F * I - 0.647F * Q;
float B = Y - 1.106F * I + 1.703F * Q;
if(R < 0) R = 0;
if(G < 0) G = 0;
if(B < 0) B = 0;
R = powf(R, 0.9F);
G = powf(G, 0.9F);
B = powf(B, 0.9F);
int r = BSPF::clamp(R * 255.F, 0.F, 255.F);
int g = BSPF::clamp(G * 255.F, 0.F, 255.F);
int b = BSPF::clamp(B * 255.F, 0.F, 255.F);
ourCustomNTSCPalette[(chroma * NUM_LUMA + luma) << 1] = (r << 16) + (g << 8) + b;
}
}
}
else if(timing == ConsoleTiming::pal)
{
constexpr float offset = BSPF::PI_f;
const float shift = myPhasePAL * BSPF::PI_f / 180;
constexpr float fixedShift = 22.5F * BSPF::PI_f / 180;
// colors 0, 1, 14 and 15 are grayscale
for(int chroma = 2; chroma < NUM_CHROMA - 2; chroma++)
{
int idx = NUM_CHROMA - 1 - chroma;
color[idx][0] = SATURATION * sinf(offset - fixedShift * chroma);
if ((idx & 1) == 0)
color[idx][1] = SATURATION * sinf(offset - shift * (chroma - 3.5F) / 2.F);
else
color[idx][1] = SATURATION * -sinf(offset - shift * chroma / 2.F);
}
for(int chroma = 0; chroma < NUM_CHROMA; chroma++)
{
const float U = color[chroma][0];
const float V = color[chroma][1];
for(int luma = 0; luma < NUM_LUMA; luma++)
{
const float Y = 0.05F + luma / 8.24F; // 0.05..~0.90
// Most sources
float R = Y + 1.403F * V;
float G = Y - 0.344F * U - 0.714F * V;
float B = Y + 1.770F * U;
// German Wikipedia, huh???
//float B = Y + 1 / 0.493 * U;
//float R = Y + 1 / 0.877 * V;
//float G = 1.704 * Y - 0.590 * R - 0.194 * B;
if(R < 0) R = 0.0;
if(G < 0) G = 0.0;
if(B < 0) B = 0.0;
R = powf(R, 1.2F);
G = powf(G, 1.2F);
B = powf(B, 1.2F);
int r = BSPF::clamp(R * 255.F, 0.F, 255.F);
int g = BSPF::clamp(G * 255.F, 0.F, 255.F);
int b = BSPF::clamp(B * 255.F, 0.F, 255.F);
ourCustomPALPalette[(chroma * NUM_LUMA + luma) << 1] = (r << 16) + (g << 8) + b;
}
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void PaletteHandler::adjustHueSaturation(int& R, int& G, int& B, float H, float S)
{
// Adapted from http://beesbuzz.biz/code/16-hsv-color-transforms
// (C) J. “Fluffy” Shagam
// License: CC BY-SA 4.0
const float su = S * cosf(-H * BSPF::PI_f);
const float sw = S * sinf(-H * BSPF::PI_f);
const float r = (.299F + .701F * su + .168F * sw) * R
+ (.587F - .587F * su + .330F * sw) * G
+ (.114F - .114F * su - .497F * sw) * B;
const float g = (.299F - .299F * su - .328F * sw) * R
+ (.587F + .413F * su + .035F * sw) * G
+ (.114F - .114F * su + .292F * sw) * B;
const float b = (.299F - .300F * su + 1.25F * sw) * R
+ (.587F - .588F * su - 1.05F * sw) * G
+ (.114F + .886F * su - .203F * sw) * B;
R = BSPF::clamp(r, 0.F, 255.F);
G = BSPF::clamp(g, 0.F, 255.F);
B = BSPF::clamp(b, 0.F, 255.F);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const PaletteArray PaletteHandler::ourNTSCPalette = {
0x000000, 0, 0x4a4a4a, 0, 0x6f6f6f, 0, 0x8e8e8e, 0,
0xaaaaaa, 0, 0xc0c0c0, 0, 0xd6d6d6, 0, 0xececec, 0,
0x484800, 0, 0x69690f, 0, 0x86861d, 0, 0xa2a22a, 0,
0xbbbb35, 0, 0xd2d240, 0, 0xe8e84a, 0, 0xfcfc54, 0,
0x7c2c00, 0, 0x904811, 0, 0xa26221, 0, 0xb47a30, 0,
0xc3903d, 0, 0xd2a44a, 0, 0xdfb755, 0, 0xecc860, 0,
0x901c00, 0, 0xa33915, 0, 0xb55328, 0, 0xc66c3a, 0,
0xd5824a, 0, 0xe39759, 0, 0xf0aa67, 0, 0xfcbc74, 0,
0x940000, 0, 0xa71a1a, 0, 0xb83232, 0, 0xc84848, 0,
0xd65c5c, 0, 0xe46f6f, 0, 0xf08080, 0, 0xfc9090, 0,
0x840064, 0, 0x97197a, 0, 0xa8308f, 0, 0xb846a2, 0,
0xc659b3, 0, 0xd46cc3, 0, 0xe07cd2, 0, 0xec8ce0, 0,
0x500084, 0, 0x68199a, 0, 0x7d30ad, 0, 0x9246c0, 0,
0xa459d0, 0, 0xb56ce0, 0, 0xc57cee, 0, 0xd48cfc, 0,
0x140090, 0, 0x331aa3, 0, 0x4e32b5, 0, 0x6848c6, 0,
0x7f5cd5, 0, 0x956fe3, 0, 0xa980f0, 0, 0xbc90fc, 0,
0x000094, 0, 0x181aa7, 0, 0x2d32b8, 0, 0x4248c8, 0,
0x545cd6, 0, 0x656fe4, 0, 0x7580f0, 0, 0x8490fc, 0,
0x001c88, 0, 0x183b9d, 0, 0x2d57b0, 0, 0x4272c2, 0,
0x548ad2, 0, 0x65a0e1, 0, 0x75b5ef, 0, 0x84c8fc, 0,
0x003064, 0, 0x185080, 0, 0x2d6d98, 0, 0x4288b0, 0,
0x54a0c5, 0, 0x65b7d9, 0, 0x75cceb, 0, 0x84e0fc, 0,
0x004030, 0, 0x18624e, 0, 0x2d8169, 0, 0x429e82, 0,
0x54b899, 0, 0x65d1ae, 0, 0x75e7c2, 0, 0x84fcd4, 0,
0x004400, 0, 0x1a661a, 0, 0x328432, 0, 0x48a048, 0,
0x5cba5c, 0, 0x6fd26f, 0, 0x80e880, 0, 0x90fc90, 0,
0x143c00, 0, 0x355f18, 0, 0x527e2d, 0, 0x6e9c42, 0,
0x87b754, 0, 0x9ed065, 0, 0xb4e775, 0, 0xc8fc84, 0,
0x303800, 0, 0x505916, 0, 0x6d762b, 0, 0x88923e, 0,
0xa0ab4f, 0, 0xb7c25f, 0, 0xccd86e, 0, 0xe0ec7c, 0,
0x482c00, 0, 0x694d14, 0, 0x866a26, 0, 0xa28638, 0,
0xbb9f47, 0, 0xd2b656, 0, 0xe8cc63, 0, 0xfce070, 0
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const PaletteArray PaletteHandler::ourPALPalette = {
0x000000, 0, 0x121212, 0, 0x242424, 0, 0x484848, 0, // 180 0
0x6c6c6c, 0, 0x909090, 0, 0xb4b4b4, 0, 0xd8d8d8, 0, // was 0x111111..0xcccccc
0x000000, 0, 0x121212, 0, 0x242424, 0, 0x484848, 0, // 198 1
0x6c6c6c, 0, 0x909090, 0, 0xb4b4b4, 0, 0xd8d8d8, 0,
0x1d0f00, 0, 0x3f2700, 0, 0x614900, 0, 0x836b01, 0, // 1b0 2
0xa58d23, 0, 0xc7af45, 0, 0xe9d167, 0, 0xffe789, 0, // was ..0xfff389
0x002400, 0, 0x004600, 0, 0x216800, 0, 0x438a07, 0, // 1c8 3
0x65ac29, 0, 0x87ce4b, 0, 0xa9f06d, 0, 0xcbff8f, 0,
0x340000, 0, 0x561400, 0, 0x783602, 0, 0x9a5824, 0, // 1e0 4
0xbc7a46, 0, 0xde9c68, 0, 0xffbe8a, 0, 0xffd0ad, 0, // was ..0xffe0ac
0x002700, 0, 0x004900, 0, 0x0c6b0c, 0, 0x2e8d2e, 0, // 1f8 5
0x50af50, 0, 0x72d172, 0, 0x94f394, 0, 0xb6ffb6, 0,
0x3d0008, 0, 0x610511, 0, 0x832733, 0, 0xa54955, 0, // 210 6
0xc76b77, 0, 0xe98d99, 0, 0xffafbb, 0, 0xffd1d7, 0, // was 0x3f0000..0xffd1dd
0x001e12, 0, 0x004228, 0, 0x046540, 0, 0x268762, 0, // 228 7
0x48a984, 0, 0x6acba6, 0, 0x8cedc8, 0, 0xafffe0, 0, // was 0x002100, 0x00431e..0xaeffff
0x300025, 0, 0x5f0047, 0, 0x811e69, 0, 0xa3408b, 0, // 240 8
0xc562ad, 0, 0xe784cf, 0, 0xffa8ea, 0, 0xffc9f2, 0, // was ..0xffa6f1, 0xffc8ff
0x001431, 0, 0x003653, 0, 0x0a5875, 0, 0x2c7a97, 0, // 258 9
0x4e9cb9, 0, 0x70bedb, 0, 0x92e0fd, 0, 0xb4ffff, 0,
0x2c0052, 0, 0x4e0074, 0, 0x701d96, 0, 0x923fb8, 0, // 270 a
0xb461da, 0, 0xd683fc, 0, 0xe2a5ff, 0, 0xeec9ff, 0, // was ..0xf8a5ff, 0xffc7ff
0x001759, 0, 0x00247c, 0, 0x1d469e, 0, 0x3f68c0, 0, // 288 b
0x618ae2, 0, 0x83acff, 0, 0xa5ceff, 0, 0xc7f0ff, 0,
0x12006d, 0, 0x34038f, 0, 0x5625b1, 0, 0x7847d3, 0, // 2a0 c
0x9a69f5, 0, 0xb48cff, 0, 0xc9adff, 0, 0xe1d1ff, 0, // was ..0xbc8bff, 0xdeadff, 0xffcfff,
0x000070, 0, 0x161292, 0, 0x3834b4, 0, 0x5a56d6, 0, // 2b8 d
0x7c78f8, 0, 0x9e9aff, 0, 0xc0bcff, 0, 0xe2deff, 0,
0x000000, 0, 0x121212, 0, 0x242424, 0, 0x484848, 0, // 2d0 e
0x6c6c6c, 0, 0x909090, 0, 0xb4b4b4, 0, 0xd8d8d8, 0,
0x000000, 0, 0x121212, 0, 0x242424, 0, 0x484848, 0, // 2e8 f
0x6c6c6c, 0, 0x909090, 0, 0xb4b4b4, 0, 0xd8d8d8, 0,
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const PaletteArray PaletteHandler::ourSECAMPalette = {
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff50ff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const PaletteArray PaletteHandler::ourNTSCPaletteZ26 = {
0x000000, 0, 0x505050, 0, 0x646464, 0, 0x787878, 0,
0x8c8c8c, 0, 0xa0a0a0, 0, 0xb4b4b4, 0, 0xc8c8c8, 0,
0x445400, 0, 0x586800, 0, 0x6c7c00, 0, 0x809000, 0,
0x94a414, 0, 0xa8b828, 0, 0xbccc3c, 0, 0xd0e050, 0,
0x673900, 0, 0x7b4d00, 0, 0x8f6100, 0, 0xa37513, 0,
0xb78927, 0, 0xcb9d3b, 0, 0xdfb14f, 0, 0xf3c563, 0,
0x7b2504, 0, 0x8f3918, 0, 0xa34d2c, 0, 0xb76140, 0,
0xcb7554, 0, 0xdf8968, 0, 0xf39d7c, 0, 0xffb190, 0,
0x7d122c, 0, 0x912640, 0, 0xa53a54, 0, 0xb94e68, 0,
0xcd627c, 0, 0xe17690, 0, 0xf58aa4, 0, 0xff9eb8, 0,
0x730871, 0, 0x871c85, 0, 0x9b3099, 0, 0xaf44ad, 0,
0xc358c1, 0, 0xd76cd5, 0, 0xeb80e9, 0, 0xff94fd, 0,
0x5d0b92, 0, 0x711fa6, 0, 0x8533ba, 0, 0x9947ce, 0,
0xad5be2, 0, 0xc16ff6, 0, 0xd583ff, 0, 0xe997ff, 0,
0x401599, 0, 0x5429ad, 0, 0x683dc1, 0, 0x7c51d5, 0,
0x9065e9, 0, 0xa479fd, 0, 0xb88dff, 0, 0xcca1ff, 0,
0x252593, 0, 0x3939a7, 0, 0x4d4dbb, 0, 0x6161cf, 0,
0x7575e3, 0, 0x8989f7, 0, 0x9d9dff, 0, 0xb1b1ff, 0,
0x0f3480, 0, 0x234894, 0, 0x375ca8, 0, 0x4b70bc, 0,
0x5f84d0, 0, 0x7398e4, 0, 0x87acf8, 0, 0x9bc0ff, 0,
0x04425a, 0, 0x18566e, 0, 0x2c6a82, 0, 0x407e96, 0,
0x5492aa, 0, 0x68a6be, 0, 0x7cbad2, 0, 0x90cee6, 0,
0x044f30, 0, 0x186344, 0, 0x2c7758, 0, 0x408b6c, 0,
0x549f80, 0, 0x68b394, 0, 0x7cc7a8, 0, 0x90dbbc, 0,
0x0f550a, 0, 0x23691e, 0, 0x377d32, 0, 0x4b9146, 0,
0x5fa55a, 0, 0x73b96e, 0, 0x87cd82, 0, 0x9be196, 0,
0x1f5100, 0, 0x336505, 0, 0x477919, 0, 0x5b8d2d, 0,
0x6fa141, 0, 0x83b555, 0, 0x97c969, 0, 0xabdd7d, 0,
0x344600, 0, 0x485a00, 0, 0x5c6e14, 0, 0x708228, 0,
0x84963c, 0, 0x98aa50, 0, 0xacbe64, 0, 0xc0d278, 0,
0x463e00, 0, 0x5a5205, 0, 0x6e6619, 0, 0x827a2d, 0,
0x968e41, 0, 0xaaa255, 0, 0xbeb669, 0, 0xd2ca7d, 0
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const PaletteArray PaletteHandler::ourPALPaletteZ26 = {
0x000000, 0, 0x4c4c4c, 0, 0x606060, 0, 0x747474, 0,
0x888888, 0, 0x9c9c9c, 0, 0xb0b0b0, 0, 0xc4c4c4, 0,
0x000000, 0, 0x4c4c4c, 0, 0x606060, 0, 0x747474, 0,
0x888888, 0, 0x9c9c9c, 0, 0xb0b0b0, 0, 0xc4c4c4, 0,
0x533a00, 0, 0x674e00, 0, 0x7b6203, 0, 0x8f7617, 0,
0xa38a2b, 0, 0xb79e3f, 0, 0xcbb253, 0, 0xdfc667, 0,
0x1b5800, 0, 0x2f6c00, 0, 0x438001, 0, 0x579415, 0,
0x6ba829, 0, 0x7fbc3d, 0, 0x93d051, 0, 0xa7e465, 0,
0x6a2900, 0, 0x7e3d12, 0, 0x925126, 0, 0xa6653a, 0,
0xba794e, 0, 0xce8d62, 0, 0xe2a176, 0, 0xf6b58a, 0,
0x075b00, 0, 0x1b6f11, 0, 0x2f8325, 0, 0x439739, 0,
0x57ab4d, 0, 0x6bbf61, 0, 0x7fd375, 0, 0x93e789, 0,
0x741b2f, 0, 0x882f43, 0, 0x9c4357, 0, 0xb0576b, 0,
0xc46b7f, 0, 0xd87f93, 0, 0xec93a7, 0, 0xffa7bb, 0,
0x00572e, 0, 0x106b42, 0, 0x247f56, 0, 0x38936a, 0,
0x4ca77e, 0, 0x60bb92, 0, 0x74cfa6, 0, 0x88e3ba, 0,
0x6d165f, 0, 0x812a73, 0, 0x953e87, 0, 0xa9529b, 0,
0xbd66af, 0, 0xd17ac3, 0, 0xe58ed7, 0, 0xf9a2eb, 0,
0x014c5e, 0, 0x156072, 0, 0x297486, 0, 0x3d889a, 0,
0x519cae, 0, 0x65b0c2, 0, 0x79c4d6, 0, 0x8dd8ea, 0,
0x5f1588, 0, 0x73299c, 0, 0x873db0, 0, 0x9b51c4, 0,
0xaf65d8, 0, 0xc379ec, 0, 0xd78dff, 0, 0xeba1ff, 0,
0x123b87, 0, 0x264f9b, 0, 0x3a63af, 0, 0x4e77c3, 0,
0x628bd7, 0, 0x769feb, 0, 0x8ab3ff, 0, 0x9ec7ff, 0,
0x451e9d, 0, 0x5932b1, 0, 0x6d46c5, 0, 0x815ad9, 0,
0x956eed, 0, 0xa982ff, 0, 0xbd96ff, 0, 0xd1aaff, 0,
0x2a2b9e, 0, 0x3e3fb2, 0, 0x5253c6, 0, 0x6667da, 0,
0x7a7bee, 0, 0x8e8fff, 0, 0xa2a3ff, 0, 0xb6b7ff, 0,
0x000000, 0, 0x4c4c4c, 0, 0x606060, 0, 0x747474, 0,
0x888888, 0, 0x9c9c9c, 0, 0xb0b0b0, 0, 0xc4c4c4, 0,
0x000000, 0, 0x4c4c4c, 0, 0x606060, 0, 0x747474, 0,
0x888888, 0, 0x9c9c9c, 0, 0xb0b0b0, 0, 0xc4c4c4, 0
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const PaletteArray PaletteHandler::ourSECAMPaletteZ26 = {
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0,
0x000000, 0, 0x2121ff, 0, 0xf03c79, 0, 0xff3cff, 0,
0x7fff00, 0, 0x7fffff, 0, 0xffff3f, 0, 0xffffff, 0
};
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PaletteArray PaletteHandler::ourUserNTSCPalette = { 0 }; // filled from external file
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PaletteArray PaletteHandler::ourUserPALPalette = { 0 }; // filled from external file
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PaletteArray PaletteHandler::ourUserSECAMPalette = { 0 }; // filled from external file
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PaletteArray PaletteHandler::ourCustomNTSCPalette = { 0 }; // filled by function
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PaletteArray PaletteHandler::ourCustomPALPalette = { 0 }; // filled by function

View File

@ -0,0 +1,261 @@
//============================================================================
//
// SSSS tt lll lll
// SS SS tt ll ll
// SS tttttt eeee ll ll aaaa
// SSSS tt ee ee ll ll aa
// SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
// SS SS tt ee ll ll aa aa
// SSSS ttt eeeee llll llll aaaaa
//
// Copyright (c) 1995-2020 by Bradford W. Mott, Stephen Anthony
// and the Stella Team
//
// See the file "License.txt" for information on usage and redistribution of
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#ifndef PALETTE_HANDLER_HXX
#define PALETTE_HANDLER_HXX
#include "bspf.hxx"
#include "OSystem.hxx"
#include "ConsoleTiming.hxx"
#include "EventHandlerConstants.hxx"
class PaletteHandler
{
public:
// Setting names of palette types
static constexpr const char* SETTING_STANDARD = "standard";
static constexpr const char* SETTING_Z26 = "z26";
static constexpr const char* SETTING_USER = "user";
static constexpr const char* SETTING_CUSTOM = "custom";
// Phase shift default and limits
static constexpr float DEF_NTSC_SHIFT = 26.2F;
static constexpr float DEF_PAL_SHIFT = 31.3F; // ~= 360 / 11.5
static constexpr float MAX_SHIFT = 4.5F;
enum Adjustables {
PHASE_SHIFT,
HUE,
SATURATION,
CONTRAST,
BRIGHTNESS,
GAMMA
};
// Externally used adjustment parameters
struct Adjustable {
float phaseNtsc{0.F}, phasePal{0.F};
uInt32 hue{0}, saturation{0}, contrast{0}, brightness{0}, gamma{0};
};
public:
PaletteHandler(OSystem& system);
virtual ~PaletteHandler() = default;
/**
Cycle through available palettes.
@param direction +1 indicates increase, -1 indicates decrease.
*/
void cyclePalette(int direction = +1);
/*
Cycle through each palette adjustable.
@param direction +1 indicates increase, -1 indicates decrease.
*/
void cycleAdjustable(int direction = +1);
/*
Increase or decrease given palette adjustable.
@param adjustable The adjustable to change
@param direction +1 indicates increase, -1 indicates decrease.
*/
void changeAdjustable(int adjustable, int direction);
/*
Increase or decrease current palette adjustable.
@param direction +1 indicates increase, -1 indicates decrease.
*/
void changeCurrentAdjustable(int direction = +1);
// Load adjustables from settings
void loadConfig(const Settings& settings);
// Save adjustables to settings
void saveConfig(Settings& settings) const;
// Set adjustables
void setAdjustables(const Adjustable& adjustable);
// Retrieve current adjustables
void getAdjustables(Adjustable& adjustable) const;
/**
Sets the palette according to the given palette name.
@param name The palette to switch to
*/
void setPalette(const string& name);
/**
Sets the palette from current settings.
*/
void setPalette();
private:
static constexpr char DEGREE = 0x1c;
enum PaletteType {
Standard,
Z26,
User,
Custom,
NumTypes,
MinType = Standard,
MaxType = Custom
};
/**
Convert adjustables from/to 100% scale
*/
static constexpr float scaleFrom100(float x) { return (x / 50.F) - 1.F; }
static constexpr uInt32 scaleTo100(float x) { return uInt32(50.0001F * (x + 1.F)); }
/**
Convert palette settings name to enumeration.
@param name The given palette's settings name
@return The palette type
*/
PaletteType toPaletteType(const string& name) const;
/**
Convert enumeration to palette settings name.
@param type The given palette type
@return The palette's settings name
*/
string toPaletteName(PaletteType type) const;
/**
Display current adjustable with gauge bar message
*/
void showAdjustableMessage();
/**
Change the "phase shift" variable.
Note that there are two of these (NTSC and PAL). The currently
active mode will determine which one is used.
@param direction +1 indicates increase, -1 indicates decrease.
*/
void changeColorPhaseShift(int direction = +1);
/**
Generates a custom palette, based on user defined phase shifts.
@param timing Use NTSC or PAL phase shift and generate according palette
*/
void generateCustomPalette(ConsoleTiming timing);
/**
Create new palette by applying palette adjustments on given palette.
@param source The palette which should be adjusted
@return An adjusted palette
*/
PaletteArray adjustedPalette(const PaletteArray& source);
/**
Adjust hue and saturation for given RGB values.
@param R The red value to adjust
@param G The green value to adjust
@param B The blue value to adjust
@param H The hue adjustment value
@param S The saturation
*/
void adjustHueSaturation(int& R, int& G, int& B, float H, float S);
/**
Loads a user-defined palette file (from OSystem::paletteFile), filling the
appropriate user-defined palette arrays.
*/
void loadUserPalette();
private:
static constexpr int NUM_ADJUSTABLES = 6;
OSystem& myOSystem;
// The currently selected adjustable
uInt32 myCurrentAdjustable{0};
struct AdjustableTag {
const char* const name{nullptr};
float* value{nullptr};
};
const std::array<AdjustableTag, NUM_ADJUSTABLES> myAdjustables =
{ {
{ "phase shift", nullptr },
{ "hue", &myHue },
{ "saturation", &mySaturation },
{ "contrast", &myContrast },
{ "brightness", &myBrightness },
{ "gamma", &myGamma },
} };
// NTSC and PAL color phase shifts
float myPhaseNTSC{DEF_NTSC_SHIFT};
float myPhasePAL{DEF_PAL_SHIFT};
// range -1.0 to +1.0 (as in AtariNTSC)
// Basic parameters
float myHue{0.0F}; // -1 = -180 degrees +1 = +180 degrees
float mySaturation{0.0F}; // -1 = grayscale (0.0) +1 = oversaturated colors (2.0)
float myContrast{0.0F}; // -1 = dark (0.5) +1 = light (1.5)
float myBrightness{0.0F}; // -1 = dark (0.5) +1 = light (1.5)
// Advanced parameters
float myGamma{0.0F}; // -1 = dark (1.5) +1 = light (0.5)
// Indicates whether an external palette was found and
// successfully loaded
bool myUserPaletteDefined{false};
// Table of RGB values for NTSC, PAL and SECAM
static const PaletteArray ourNTSCPalette;
static const PaletteArray ourPALPalette;
static const PaletteArray ourSECAMPalette;
// Table of RGB values for NTSC, PAL and SECAM - Z26 version
static const PaletteArray ourNTSCPaletteZ26;
static const PaletteArray ourPALPaletteZ26;
static const PaletteArray ourSECAMPaletteZ26;
// Table of RGB values for NTSC, PAL and SECAM - user-defined
static PaletteArray ourUserNTSCPalette;
static PaletteArray ourUserPALPalette;
static PaletteArray ourUserSECAMPalette;
// Table of RGB values for NTSC, PAL - custom-defined and generated
static PaletteArray ourCustomNTSCPalette;
static PaletteArray ourCustomPALPalette;
private:
PaletteHandler() = delete;
PaletteHandler(const PaletteHandler&) = delete;
PaletteHandler(PaletteHandler&&) = delete;
PaletteHandler& operator=(const PaletteHandler&) = delete;
PaletteHandler& operator=(const PaletteHandler&&) = delete;
};
#endif // PALETTE_HANDLER_HXX

View File

@ -111,7 +111,8 @@ struct Rect
Rect() {} Rect() {}
explicit Rect(const Size& s) : bottom(s.h), right(s.w) { assert(valid()); } explicit Rect(const Size& s) : bottom(s.h), right(s.w) { assert(valid()); }
Rect(uInt32 w, uInt32 h) : bottom(h), right(w) { assert(valid()); } Rect(uInt32 w, uInt32 h) : bottom(h), right(w) { assert(valid()); }
Rect(const Point& p, uInt32 w, uInt32 h) : top(p.y), left(p.x), bottom(h), right(w) { assert(valid()); } Rect(const Point& p, uInt32 w, uInt32 h)
: top(p.y), left(p.x), bottom(p.y + h), right(p.x + w) { assert(valid()); }
Rect(uInt32 x1, uInt32 y1, uInt32 x2, uInt32 y2) : top(y1), left(x1), bottom(y2), right(x2) { assert(valid()); } Rect(uInt32 x1, uInt32 y1, uInt32 x2, uInt32 y2) : top(y1), left(x1), bottom(y2), right(x2) { assert(valid()); }
uInt32 x() const { return left; } uInt32 x() const { return left; }

View File

@ -18,6 +18,7 @@
#include <cmath> #include <cmath>
#include "OSystem.hxx" #include "OSystem.hxx"
#include "Console.hxx"
#include "Serializer.hxx" #include "Serializer.hxx"
#include "StateManager.hxx" #include "StateManager.hxx"
#include "TIA.hxx" #include "TIA.hxx"
@ -180,7 +181,8 @@ uInt32 RewindManager::rewindStates(uInt32 numStates)
else else
message = "Rewind not possible"; message = "Rewind not possible";
if(myOSystem.eventHandler().state() != EventHandlerState::TIMEMACHINE) if(myOSystem.eventHandler().state() != EventHandlerState::TIMEMACHINE
&& myOSystem.eventHandler().state() != EventHandlerState::PLAYBACK)
myOSystem.frameBuffer().showMessage(message); myOSystem.frameBuffer().showMessage(message);
return i; return i;
} }
@ -214,7 +216,8 @@ uInt32 RewindManager::unwindStates(uInt32 numStates)
else else
message = "Unwind not possible"; message = "Unwind not possible";
if(myOSystem.eventHandler().state() != EventHandlerState::TIMEMACHINE) if(myOSystem.eventHandler().state() != EventHandlerState::TIMEMACHINE
&& myOSystem.eventHandler().state() != EventHandlerState::PLAYBACK)
myOSystem.frameBuffer().showMessage(message); myOSystem.frameBuffer().showMessage(message);
return i; return i;
} }

View File

@ -18,12 +18,13 @@
#ifndef SOUND_NULL_HXX #ifndef SOUND_NULL_HXX
#define SOUND_NULL_HXX #define SOUND_NULL_HXX
class OSystem;
class AudioQueue;
class EmulationTiming;
#include "bspf.hxx" #include "bspf.hxx"
#include "Logger.hxx" #include "Logger.hxx"
#include "Sound.hxx" #include "Sound.hxx"
#include "OSystem.hxx"
#include "AudioQueue.hxx"
#include "EmulationTiming.hxx"
/** /**
This class implements a Null sound object, where-by sound generation This class implements a Null sound object, where-by sound generation
@ -96,10 +97,16 @@ class SoundNull : public Sound
/** /**
Adjusts the volume of the sound device based on the given direction. Adjusts the volume of the sound device based on the given direction.
@param direction Increase or decrease the current volume by a predefined @param direction +1 indicates increase, -1 indicates decrease.
amount based on the direction (1 = increase, -1 =decrease)
*/ */
void adjustVolume(Int8 direction) override { } void adjustVolume(int direction = 1) override { }
/**
Sets the audio device.
@param device The number of the device to select (0 = default).
*/
void setDevice(uInt32 device) override { };
/** /**
This method is called to provide information about the sound device. This method is called to provide information about the sound device.

View File

@ -56,6 +56,8 @@ SoundSDL2::SoundSDL2(OSystem& osystem, AudioSettings& audioSettings)
return; return;
} }
queryHardware(myDevices);
SDL_zero(myHardwareSpec); SDL_zero(myHardwareSpec);
if(!openDevice()) if(!openDevice())
return; return;
@ -76,6 +78,29 @@ SoundSDL2::~SoundSDL2()
SDL_QuitSubSystem(SDL_INIT_AUDIO); SDL_QuitSubSystem(SDL_INIT_AUDIO);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::queryHardware(VariantList& devices)
{
ASSERT_MAIN_THREAD;
int numDevices = SDL_GetNumAudioDevices(0);
// log the available audio devices
ostringstream s;
s << "Supported audio devices (" << numDevices << "):";
Logger::debug(s.str());
VarList::push_back(devices, "Default", 0);
for(int i = 0; i < numDevices; ++i) {
ostringstream ss;
ss << " " << i + 1 << ": " << SDL_GetAudioDeviceName(i, 0);
Logger::debug(ss.str());
VarList::push_back(devices, SDL_GetAudioDeviceName(i, 0), i + 1);
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool SoundSDL2::openDevice() bool SoundSDL2::openDevice()
{ {
@ -91,7 +116,11 @@ bool SoundSDL2::openDevice()
if(myIsInitializedFlag) if(myIsInitializedFlag)
SDL_CloseAudioDevice(myDevice); SDL_CloseAudioDevice(myDevice);
myDevice = SDL_OpenAudioDevice(nullptr, 0, &desired, &myHardwareSpec,
myDeviceId = BSPF::clamp(myAudioSettings.device(), 0u, uInt32(myDevices.size() - 1));
const char* device = myDeviceId ? myDevices.at(myDeviceId).first.c_str() : nullptr;
myDevice = SDL_OpenAudioDevice(device, 0, &desired, &myHardwareSpec,
SDL_AUDIO_ALLOW_FREQUENCY_CHANGE); SDL_AUDIO_ALLOW_FREQUENCY_CHANGE);
if(myDevice == 0) if(myDevice == 0)
@ -126,7 +155,8 @@ void SoundSDL2::open(shared_ptr<AudioQueue> audioQueue,
// Do we need to re-open the sound device? // Do we need to re-open the sound device?
// Only do this when absolutely necessary // Only do this when absolutely necessary
if(myAudioSettings.sampleRate() != uInt32(myHardwareSpec.freq) || if(myAudioSettings.sampleRate() != uInt32(myHardwareSpec.freq) ||
myAudioSettings.fragmentSize() != uInt32(myHardwareSpec.samples)) myAudioSettings.fragmentSize() != uInt32(myHardwareSpec.samples) ||
myAudioSettings.device() != myDeviceId)
openDevice(); openDevice();
myEmulationTiming = emulationTiming; myEmulationTiming = emulationTiming;
@ -186,16 +216,31 @@ bool SoundSDL2::mute(bool state)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool SoundSDL2::toggleMute() bool SoundSDL2::toggleMute()
{ {
bool enabled = myAudioSettings.enabled(); bool enabled = !myAudioSettings.enabled();
setEnabled(!enabled); setEnabled(enabled);
myOSystem.console().initializeAudio(); myOSystem.console().initializeAudio();
string message = "Sound "; string message = "Sound ";
message += !enabled ? "unmuted" : "muted"; message += enabled ? "unmuted" : "muted";
myOSystem.frameBuffer().showMessage(message); myOSystem.frameBuffer().showMessage(message);
//ostringstream strval;
//uInt32 volume;
//// Now show an onscreen message
//if(enabled)
//{
// volume = myVolume;
// strval << volume << "%";
//}
//else
//{
// volume = 0;
// strval << "Muted";
//}
//myOSystem.frameBuffer().showMessage("Volume", strval.str(), volume);
return enabled; return enabled;
} }
@ -214,20 +259,12 @@ void SoundSDL2::setVolume(uInt32 percent)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void SoundSDL2::adjustVolume(Int8 direction) void SoundSDL2::adjustVolume(int direction)
{ {
ostringstream strval; ostringstream strval;
string message;
Int32 percent = myVolume; Int32 percent = myVolume;
if(direction == -1) percent = BSPF::clamp(percent + direction * 2, 0, 100);
percent -= 2;
else if(direction == 1)
percent += 2;
if((percent < 0) || (percent > 100))
return;
setVolume(percent); setVolume(percent);
@ -241,11 +278,11 @@ void SoundSDL2::adjustVolume(Int8 direction)
} }
// Now show an onscreen message // Now show an onscreen message
strval << percent; if(percent)
message = "Volume set to "; strval << percent << "%";
message += strval.str(); else
strval << "Off";
myOSystem.frameBuffer().showMessage(message); myOSystem.frameBuffer().showMessage("Volume", strval.str(), percent);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -254,6 +291,7 @@ string SoundSDL2::about() const
ostringstream buf; ostringstream buf;
buf << "Sound enabled:" << endl buf << "Sound enabled:" << endl
<< " Volume: " << myVolume << "%" << endl << " Volume: " << myVolume << "%" << endl
<< " Device: " << myDevices.at(myDeviceId).first << endl
<< " Channels: " << uInt32(myHardwareSpec.channels) << " Channels: " << uInt32(myHardwareSpec.channels)
<< (myAudioQueue->isStereo() ? " (Stereo)" : " (Mono)") << endl << (myAudioQueue->isStereo() ? " (Stereo)" : " (Mono)") << endl
<< " Preset: "; << " Preset: ";

View File

@ -24,12 +24,12 @@ class OSystem;
class AudioQueue; class AudioQueue;
class EmulationTiming; class EmulationTiming;
class AudioSettings; class AudioSettings;
class Resampler;
#include "SDL_lib.hxx" #include "SDL_lib.hxx"
#include "bspf.hxx" #include "bspf.hxx"
#include "Sound.hxx" #include "Sound.hxx"
#include "audio/Resampler.hxx"
/** /**
This class implements the sound API for SDL. This class implements the sound API for SDL.
@ -98,10 +98,9 @@ class SoundSDL2 : public Sound
/** /**
Adjusts the volume of the sound device based on the given direction. Adjusts the volume of the sound device based on the given direction.
@param direction Increase or decrease the current volume by a predefined @param direction +1 indicates increase, -1 indicates decrease.
amount based on the direction (1 = increase, -1 = decrease)
*/ */
void adjustVolume(Int8 direction) override; void adjustVolume(int direction = 1) override;
/** /**
This method is called to provide information about the sound device. This method is called to provide information about the sound device.
@ -109,6 +108,13 @@ class SoundSDL2 : public Sound
string about() const override; string about() const override;
protected: protected:
/**
This method is called to query the audio devices.
@param devices List of device names
*/
void queryHardware(VariantList& devices) override;
/** /**
Invoked by the sound callback to process the next sound fragment. Invoked by the sound callback to process the next sound fragment.
The stream is 16-bits (even though the callback is 8-bits), since The stream is 16-bits (even though the callback is 8-bits), since
@ -140,6 +146,8 @@ class SoundSDL2 : public Sound
// Audio specification structure // Audio specification structure
SDL_AudioSpec myHardwareSpec; SDL_AudioSpec myHardwareSpec;
uInt32 myDeviceId{0};
SDL_AudioDeviceID myDevice{0}; SDL_AudioDeviceID myDevice{0};
shared_ptr<AudioQueue> myAudioQueue; shared_ptr<AudioQueue> myAudioQueue;

View File

@ -27,7 +27,7 @@
*/ */
namespace Common { namespace Common {
template <class T, uInt32 CAPACITY = 50> template <typename T, uInt32 CAPACITY = 50>
class FixedStack class FixedStack
{ {
private: private:

View File

@ -299,15 +299,14 @@ void StateManager::saveState(int slot)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StateManager::changeState(int direction) void StateManager::changeState(int direction)
{ {
myCurrentSlot += direction; myCurrentSlot = BSPF::clampw(myCurrentSlot + direction, 0, 9);
if (myCurrentSlot < 0)
myCurrentSlot = 9;
else
myCurrentSlot %= 10;
// Print appropriate message // Print appropriate message
ostringstream buf; ostringstream buf;
buf << "Changed to slot " << myCurrentSlot; if(direction)
buf << "Changed to state slot " << myCurrentSlot;
else
buf << "State slot " << myCurrentSlot;
myOSystem.frameBuffer().showMessage(buf.str()); myOSystem.frameBuffer().showMessage(buf.str());
} }

View File

@ -18,7 +18,7 @@
#ifndef STATE_MANAGER_HXX #ifndef STATE_MANAGER_HXX
#define STATE_MANAGER_HXX #define STATE_MANAGER_HXX
#define STATE_HEADER "06000008state" #define STATE_HEADER "06020100state"
class OSystem; class OSystem;
class RewindManager; class RewindManager;
@ -115,8 +115,10 @@ class StateManager
/** /**
Switches to the next higher or lower state slot (circular queue style). Switches to the next higher or lower state slot (circular queue style).
@param direction +1 indicates increase, -1 indicates decrease.
*/ */
void changeState(int direction); void changeState(int direction = +1);
/** /**
Toggles auto slot mode. Toggles auto slot mode.

View File

@ -22,19 +22,19 @@
namespace Vec { namespace Vec {
template<class T> template<typename T>
void append(vector<T>& dst, const vector<T>& src) void append(vector<T>& dst, const vector<T>& src)
{ {
dst.insert(dst.cend(), src.cbegin(), src.cend()); dst.insert(dst.cend(), src.cbegin(), src.cend());
} }
template<class T> template<typename T>
void insertAt(vector<T>& dst, uInt32 idx, const T& element) void insertAt(vector<T>& dst, uInt32 idx, const T& element)
{ {
dst.insert(dst.cbegin()+idx, element); dst.insert(dst.cbegin()+idx, element);
} }
template<class T> template<typename T>
void removeAt(vector<T>& dst, uInt32 idx) void removeAt(vector<T>& dst, uInt32 idx)
{ {
dst.erase(dst.cbegin()+idx); dst.erase(dst.cbegin()+idx);

View File

@ -18,7 +18,7 @@
#ifndef VERSION_HXX #ifndef VERSION_HXX
#define VERSION_HXX #define VERSION_HXX
#define STELLA_VERSION "6.1_rc1" #define STELLA_VERSION "6.3_pre"
#define STELLA_BUILD "5657" #define STELLA_BUILD "6091"
#endif #endif

View File

@ -297,7 +297,7 @@ class ZipHandler
void addToCache(); void addToCache();
private: private:
static constexpr uInt32 DECOMPRESS_BUFSIZE = 16_KB; static constexpr size_t DECOMPRESS_BUFSIZE = 16_KB;
static constexpr uInt32 CACHE_SIZE = 8; // number of open files to cache static constexpr uInt32 CACHE_SIZE = 8; // number of open files to cache
ZipFilePtr myZip; ZipFilePtr myZip;

View File

@ -43,6 +43,7 @@ using uInt64 = uint64_t;
#include <algorithm> #include <algorithm>
#include <iostream> #include <iostream>
#include <fstream> #include <fstream>
#include <functional>
#include <iomanip> #include <iomanip>
#include <memory> #include <memory>
#include <string> #include <string>
@ -63,8 +64,6 @@ using std::istream;
using std::ostream; using std::ostream;
using std::fstream; using std::fstream;
using std::iostream; using std::iostream;
using std::ifstream;
using std::ofstream;
using std::ostringstream; using std::ostringstream;
using std::istringstream; using std::istringstream;
using std::stringstream; using std::stringstream;
@ -84,11 +83,14 @@ using ByteArray = std::vector<uInt8>;
using ShortArray = std::vector<uInt16>; using ShortArray = std::vector<uInt16>;
using StringList = std::vector<std::string>; using StringList = std::vector<std::string>;
using ByteBuffer = std::unique_ptr<uInt8[]>; // NOLINT using ByteBuffer = std::unique_ptr<uInt8[]>; // NOLINT
using DWordBuffer = std::unique_ptr<uInt32[]>; // NOLINT
using AdjustFunction = std::function<void(int)>;
// We use KB a lot; let's make a literal for it // We use KB a lot; let's make a literal for it
constexpr uInt32 operator "" _KB(unsigned long long size) constexpr size_t operator "" _KB(unsigned long long size)
{ {
return static_cast<uInt32>(size * 1024); return static_cast<size_t>(size * 1024);
} }
static const string EmptyString(""); static const string EmptyString("");
@ -97,6 +99,12 @@ static const string EmptyString("");
#undef PAGE_SIZE #undef PAGE_SIZE
#undef PAGE_MASK #undef PAGE_MASK
// Adaptable refresh is currently not available on MacOS
// In the future, this may expand to other systems
#if !defined(BSPF_MACOS)
#define ADAPTABLE_REFRESH_SUPPORT
#endif
namespace BSPF namespace BSPF
{ {
static constexpr float PI_f = 3.141592653589793238462643383279502884F; static constexpr float PI_f = 3.141592653589793238462643383279502884F;
@ -118,20 +126,39 @@ namespace BSPF
static const string ARCH = "NOARCH"; static const string ARCH = "NOARCH";
#endif #endif
// Get next power of two greater than or equal to the given value
inline size_t nextPowerOfTwo(size_t size) {
if(size < 2) return 1;
size_t power2 = 1;
while(power2 < size)
power2 <<= 1;
return power2;
}
// Get next multiple of the given value
// Note that this only works when multiple is a power of two
inline size_t nextMultipleOf(size_t size, size_t multiple) {
return (size + multiple - 1) & ~(multiple - 1);
}
// Make 2D-arrays using std::array less verbose // Make 2D-arrays using std::array less verbose
template<class T, size_t ROW, size_t COL> template<typename T, size_t ROW, size_t COL>
using array2D = std::array<std::array<T, COL>, ROW>; using array2D = std::array<std::array<T, COL>, ROW>;
// Combines 'max' and 'min', and clamps value to the upper/lower value // Combines 'max' and 'min', and clamps value to the upper/lower value
// if it is outside the specified range // if it is outside the specified range
template<class T> inline T clamp(T val, T lower, T upper) template<typename T> inline T clamp(T val, T lower, T upper)
{ {
return (val < lower) ? lower : (val > upper) ? upper : val; return (val < lower) ? lower : (val > upper) ? upper : val;
} }
template<class T> inline void clamp(T& val, T lower, T upper, T setVal) template<typename T> inline void clamp(T& val, T lower, T upper, T setVal)
{ {
if(val < lower || val > upper) val = setVal; if(val < lower || val > upper) val = setVal;
} }
template<typename T> inline T clampw(T val, T lower, T upper)
{
return (val < lower) ? upper : (val > upper) ? lower : val;
}
// Convert string to given case // Convert string to given case
inline const string& toUpperCase(string& s) inline const string& toUpperCase(string& s)

View File

@ -12,6 +12,7 @@ MODULE_OBJS := \
src/common/Logger.o \ src/common/Logger.o \
src/common/main.o \ src/common/main.o \
src/common/MouseControl.o \ src/common/MouseControl.o \
src/common/PaletteHandler.o \
src/common/PhosphorHandler.o \ src/common/PhosphorHandler.o \
src/common/PhysicalJoystick.o \ src/common/PhysicalJoystick.o \
src/common/PJoystickHandler.o \ src/common/PJoystickHandler.o \

View File

@ -28,8 +28,8 @@ namespace {
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyValueRepositoryConfigfile::KeyValueRepositoryConfigfile(const string& filename) KeyValueRepositoryConfigfile::KeyValueRepositoryConfigfile(const FilesystemNode& file)
: myFilename(filename) : myFile(file)
{ {
} }
@ -41,10 +41,14 @@ std::map<string, Variant> KeyValueRepositoryConfigfile::load()
string line, key, value; string line, key, value;
string::size_type equalPos, garbage; string::size_type equalPos, garbage;
ifstream in(myFilename); stringstream in;
if(!in || !in.is_open()) { try
Logger::error("ERROR: Couldn't load from settings file " + myFilename); {
myFile.read(in);
}
catch(...)
{
Logger::error("ERROR: Couldn't load from settings file " + myFile.getShortPath());
return values; return values;
} }
@ -79,13 +83,7 @@ std::map<string, Variant> KeyValueRepositoryConfigfile::load()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyValueRepositoryConfigfile::save(const std::map<string, Variant>& values) void KeyValueRepositoryConfigfile::save(const std::map<string, Variant>& values)
{ {
ofstream out(myFilename); stringstream out;
if(!out || !out.is_open()) {
Logger::error("ERROR: Couldn't save to settings file " + myFilename);
return;
}
out << "; Stella configuration file" << endl out << "; Stella configuration file" << endl
<< ";" << endl << ";" << endl
<< "; Lines starting with ';' are comments and are ignored." << endl << "; Lines starting with ';' are comments and are ignored." << endl
@ -104,4 +102,13 @@ void KeyValueRepositoryConfigfile::save(const std::map<string, Variant>& values)
// Write out each of the key and value pairs // Write out each of the key and value pairs
for(const auto& pair: values) for(const auto& pair: values)
out << pair.first << " = " << pair.second << endl; out << pair.first << " = " << pair.second << endl;
try
{
myFile.write(out);
}
catch(...)
{
Logger::error("ERROR: Couldn't save to settings file " + myFile.getShortPath());
}
} }

View File

@ -18,13 +18,14 @@
#ifndef KEY_VALUE_REPOSITORY_CONFIGFILE_HXX #ifndef KEY_VALUE_REPOSITORY_CONFIGFILE_HXX
#define KEY_VALUE_REPOSITORY_CONFIGFILE_HXX #define KEY_VALUE_REPOSITORY_CONFIGFILE_HXX
#include "FSNode.hxx"
#include "KeyValueRepository.hxx" #include "KeyValueRepository.hxx"
class KeyValueRepositoryConfigfile : public KeyValueRepository class KeyValueRepositoryConfigfile : public KeyValueRepository
{ {
public: public:
explicit KeyValueRepositoryConfigfile(const string& filename); explicit KeyValueRepositoryConfigfile(const FilesystemNode& file);
std::map<string, Variant> load() override; std::map<string, Variant> load() override;
@ -34,7 +35,7 @@ class KeyValueRepositoryConfigfile : public KeyValueRepository
private: private:
const string& myFilename; FilesystemNode myFile;
}; };
#endif // KEY_VALUE_REPOSITORY_CONFIGFILE_HXX #endif // KEY_VALUE_REPOSITORY_CONFIGFILE_HXX

View File

@ -15,9 +15,9 @@
// this file, and for a DISCLAIMER OF ALL WARRANTIES. // this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================ //============================================================================
#include "BilinearBlitter.hxx" #include "FrameBufferSDL2.hxx"
#include "ThreadDebugging.hxx" #include "ThreadDebugging.hxx"
#include "BilinearBlitter.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
BilinearBlitter::BilinearBlitter(FrameBufferSDL2& fb, bool interpolate) BilinearBlitter::BilinearBlitter(FrameBufferSDL2& fb, bool interpolate)

View File

@ -18,8 +18,9 @@
#ifndef BILINEAR_BLITTER_HXX #ifndef BILINEAR_BLITTER_HXX
#define BILINEAR_BLITTER_HXX #define BILINEAR_BLITTER_HXX
class FrameBufferSDL2;
#include "Blitter.hxx" #include "Blitter.hxx"
#include "FrameBufferSDL2.hxx"
#include "SDL_lib.hxx" #include "SDL_lib.hxx"
class BilinearBlitter : public Blitter { class BilinearBlitter : public Blitter {

View File

@ -15,9 +15,9 @@
// this file, and for a DISCLAIMER OF ALL WARRANTIES. // this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================ //============================================================================
#include "QisBlitter.hxx" #include "FrameBufferSDL2.hxx"
#include "ThreadDebugging.hxx" #include "ThreadDebugging.hxx"
#include "QisBlitter.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
QisBlitter::QisBlitter(FrameBufferSDL2& fb) QisBlitter::QisBlitter(FrameBufferSDL2& fb)

View File

@ -18,8 +18,9 @@
#ifndef QIS_BLITTER_HXX #ifndef QIS_BLITTER_HXX
#define QIS_BLITTER_HXX #define QIS_BLITTER_HXX
class FrameBufferSDL2;
#include "Blitter.hxx" #include "Blitter.hxx"
#include "FrameBufferSDL2.hxx"
#include "SDL_lib.hxx" #include "SDL_lib.hxx"
class QisBlitter : public Blitter { class QisBlitter : public Blitter {

View File

@ -57,9 +57,15 @@ void AtariNTSC::generateKernels()
const uInt8* ptr = myRGBPalette.data(); const uInt8* ptr = myRGBPalette.data();
for(size_t entry = 0; entry < myRGBPalette.size() / 3; ++entry) for(size_t entry = 0; entry < myRGBPalette.size() / 3; ++entry)
{ {
#ifdef BLARGG_PALETTE
float r = myImpl.to_float[*ptr++], float r = myImpl.to_float[*ptr++],
g = myImpl.to_float[*ptr++], g = myImpl.to_float[*ptr++],
b = myImpl.to_float[*ptr++]; b = myImpl.to_float[*ptr++];
#else
float r = (*ptr++) / 255.F * rgb_unit + rgb_offset,
g = (*ptr++) / 255.F * rgb_unit + rgb_offset,
b = (*ptr++) / 255.F * rgb_unit + rgb_offset;
#endif
float y, i, q; RGB_TO_YIQ( r, g, b, y, i, q ); float y, i, q; RGB_TO_YIQ( r, g, b, y, i, q );
// Generate kernel // Generate kernel
@ -319,8 +325,10 @@ void AtariNTSC::renderWithPhosphorThread(const uInt8* atari_in, const uInt32 in_
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AtariNTSC::init(init_t& impl, const Setup& setup) void AtariNTSC::init(init_t& impl, const Setup& setup)
{ {
#ifdef BLARGG_PALETTE
impl.brightness = setup.brightness * (0.5F * rgb_unit) + rgb_offset; impl.brightness = setup.brightness * (0.5F * rgb_unit) + rgb_offset;
impl.contrast = setup.contrast * (0.5F * rgb_unit) + rgb_unit; impl.contrast = setup.contrast * (0.5F * rgb_unit) + rgb_unit;
#endif
impl.artifacts = setup.artifacts; impl.artifacts = setup.artifacts;
if ( impl.artifacts > 0 ) if ( impl.artifacts > 0 )
@ -334,6 +342,7 @@ void AtariNTSC::init(init_t& impl, const Setup& setup)
initFilters(impl, setup); initFilters(impl, setup);
#ifdef BLARGG_PALETTE
/* generate gamma table */ /* generate gamma table */
if (true) /* was (gamma_size > 1) */ if (true) /* was (gamma_size > 1) */
{ {
@ -345,15 +354,18 @@ void AtariNTSC::init(init_t& impl, const Setup& setup)
impl.to_float[i] = impl.to_float[i] =
powf(i * to_float, gamma) * impl.contrast + impl.brightness; powf(i * to_float, gamma) * impl.contrast + impl.brightness;
} }
#endif
/* setup decoder matricies */ /* setup decoder matricies */
{ {
#ifdef BLARGG_PALETTE
float hue = setup.hue * BSPF::PI_f + BSPF::PI_f / 180 * ext_decoder_hue; float hue = setup.hue * BSPF::PI_f + BSPF::PI_f / 180 * ext_decoder_hue;
float sat = setup.saturation + 1; float sat = setup.saturation + 1;
hue += BSPF::PI_f / 180 * (std_decoder_hue - ext_decoder_hue); hue += BSPF::PI_f / 180 * (std_decoder_hue - ext_decoder_hue);
float s = sinf(hue)*sat; float s = sinf(hue)*sat;
float c = cosf(hue)*sat; float c = cosf(hue)*sat;
#endif
float* out = impl.to_rgb.data(); float* out = impl.to_rgb.data();
int n; int n;
@ -366,8 +378,13 @@ void AtariNTSC::init(init_t& impl, const Setup& setup)
{ {
float i = *in++; float i = *in++;
float q = *in++; float q = *in++;
#ifdef BLARGG_PALETTE
*out++ = i * c - q * s; *out++ = i * c - q * s;
*out++ = i * s + q * c; *out++ = i * s + q * c;
#else
*out++ = i ;
*out++ = q;
#endif
} }
while ( --n2 ); while ( --n2 );
#if 0 // burst_count is always 0 #if 0 // burst_count is always 0
@ -544,16 +561,32 @@ void AtariNTSC::genKernel(init_t& impl, float y, float i, float q, uInt32* out)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const AtariNTSC::Setup AtariNTSC::TV_Composite = { const AtariNTSC::Setup AtariNTSC::TV_Composite = {
#ifdef BLARGG_PALETTE
0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.15F, 0.0F, 0.0F, 0.0F 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.15F, 0.0F, 0.0F, 0.0F
#else
0.0F, 0.15F, 0.0F, 0.0F, 0.0F
#endif
}; };
const AtariNTSC::Setup AtariNTSC::TV_SVideo = { const AtariNTSC::Setup AtariNTSC::TV_SVideo = {
#ifdef BLARGG_PALETTE
0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.45F, -1.0F, -1.0F, 0.0F 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.0F, 0.45F, -1.0F, -1.0F, 0.0F
#else
0.0F, 0.45F, -1.0F, -1.0F, 0.0F
#endif
}; };
const AtariNTSC::Setup AtariNTSC::TV_RGB = { const AtariNTSC::Setup AtariNTSC::TV_RGB = {
#ifdef BLARGG_PALETTE
0.0F, 0.0F, 0.0F, 0.0F, 0.2F, 0.0F, 0.70F, -1.0F, -1.0F, -1.0F 0.0F, 0.0F, 0.0F, 0.0F, 0.2F, 0.0F, 0.70F, -1.0F, -1.0F, -1.0F
#else
0.2F, 0.70F, -1.0F, -1.0F, -1.0F
#endif
}; };
const AtariNTSC::Setup AtariNTSC::TV_Bad = { const AtariNTSC::Setup AtariNTSC::TV_Bad = {
#ifdef BLARGG_PALETTE
0.1F, -0.3F, 0.3F, 0.25F, 0.2F, 0.0F, 0.1F, 0.5F, 0.5F, 0.5F 0.1F, -0.3F, 0.3F, 0.25F, 0.2F, 0.0F, 0.1F, 0.5F, 0.5F, 0.5F
#else
0.2F, 0.1F, 0.5F, 0.5F, 0.5F
#endif
}; };
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -46,6 +46,8 @@
#include "FrameBufferConstants.hxx" #include "FrameBufferConstants.hxx"
#include "bspf.hxx" #include "bspf.hxx"
//#define BLARGG_PALETTE // also modify contrast, brightness, saturation, gamma and hue when defined
class AtariNTSC class AtariNTSC
{ {
public: public:
@ -57,14 +59,18 @@ class AtariNTSC
struct Setup struct Setup
{ {
// Basic parameters // Basic parameters
#ifdef BLARGG_PALETTE
float hue{0.F}; // -1 = -180 degrees +1 = +180 degrees float hue{0.F}; // -1 = -180 degrees +1 = +180 degrees
float saturation{0.F}; // -1 = grayscale (0.0) +1 = oversaturated colors (2.0) float saturation{0.F}; // -1 = grayscale (0.0) +1 = oversaturated colors (2.0)
float contrast{0.F}; // -1 = dark (0.5) +1 = light (1.5) float contrast{0.F}; // -1 = dark (0.5) +1 = light (1.5)
float brightness{0.F}; // -1 = dark (0.5) +1 = light (1.5) float brightness{0.F}; // -1 = dark (0.5) +1 = light (1.5)
#endif
float sharpness{0.F}; // edge contrast enhancement/blurring float sharpness{0.F}; // edge contrast enhancement/blurring
// Advanced parameters // Advanced parameters
#ifdef BLARGG_PALETTE
float gamma{0.F}; // -1 = dark (1.5) +1 = light (0.5) float gamma{0.F}; // -1 = dark (1.5) +1 = light (0.5)
#endif
float resolution{0.F}; // image resolution float resolution{0.F}; // image resolution
float artifacts{0.F}; // artifacts caused by color changes float artifacts{0.F}; // artifacts caused by color changes
float fringing{0.F}; // color artifacts caused by brightness changes float fringing{0.F}; // color artifacts caused by brightness changes
@ -127,7 +133,9 @@ class AtariNTSC
burst_size = entry_size / burst_count, burst_size = entry_size / burst_count,
kernel_half = 16, kernel_half = 16,
kernel_size = kernel_half * 2 + 1, kernel_size = kernel_half * 2 + 1,
#ifdef BLARGG_PALETTE
gamma_size = 256, gamma_size = 256,
#endif
rgb_builder = ((1 << 21) | (1 << 11) | (1 << 1)), rgb_builder = ((1 << 21) | (1 << 11) | (1 << 1)),
rgb_kernel_size = burst_size / alignment_count, rgb_kernel_size = burst_size / alignment_count,
@ -162,16 +170,20 @@ class AtariNTSC
struct init_t struct init_t
{ {
std::array<float, burst_count * 6> to_rgb{0.F}; std::array<float, burst_count * 6> to_rgb{0.F};
#ifdef BLARGG_PALETTE
std::array<float, gamma_size> to_float{0.F}; std::array<float, gamma_size> to_float{0.F};
float contrast{0.F}; float contrast{0.F};
float brightness{0.F}; float brightness{0.F};
#endif
float artifacts{0.F}; float artifacts{0.F};
float fringing{0.F}; float fringing{0.F};
std::array<float, rescale_out * kernel_size * 2> kernel{0.F}; std::array<float, rescale_out * kernel_size * 2> kernel{0.F};
init_t() { init_t() {
to_rgb.fill(0.0); to_rgb.fill(0.0);
#ifdef BLARGG_PALETTE
to_float.fill(0.0); to_float.fill(0.0);
#endif
kernel.fill(0.0); kernel.fill(0.0);
} }
}; };

View File

@ -21,7 +21,7 @@
#include "NTSCFilter.hxx" #include "NTSCFilter.hxx"
constexpr float scaleFrom100(float x) { return (x / 50.F) - 1.F; } constexpr float scaleFrom100(float x) { return (x / 50.F) - 1.F; }
constexpr uInt32 scaleTo100(float x) { return uInt32(50*(x+1.F)); } constexpr uInt32 scaleTo100(float x) { return uInt32(50.0001F * (x + 1.F)); }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string NTSCFilter::setPreset(Preset preset) string NTSCFilter::setPreset(Preset preset)
@ -62,9 +62,9 @@ string NTSCFilter::getPreset() const
{ {
switch(myPreset) switch(myPreset)
{ {
case Preset::COMPOSITE: return "COMPOSITE";
case Preset::SVIDEO: return "S-VIDEO";
case Preset::RGB: return "RGB"; case Preset::RGB: return "RGB";
case Preset::SVIDEO: return "S-VIDEO";
case Preset::COMPOSITE: return "COMPOSITE";
case Preset::BAD: return "BAD ADJUST"; case Preset::BAD: return "BAD ADJUST";
case Preset::CUSTOM: return "CUSTOM"; case Preset::CUSTOM: return "CUSTOM";
default: return "Disabled"; default: return "Disabled";
@ -72,81 +72,81 @@ string NTSCFilter::getPreset() const
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string NTSCFilter::setNextAdjustable() void NTSCFilter::selectAdjustable(int direction,
string& text, string& valueText, Int32& value)
{ {
if(myPreset != Preset::CUSTOM) if(direction == +1)
return "'Custom' TV mode not selected"; {
#ifdef BLARGG_PALETTE
myCurrentAdjustable = (myCurrentAdjustable + 1) % 10; myCurrentAdjustable = (myCurrentAdjustable + 1) % 10;
ostringstream buf; #else
buf << "Custom adjustable '" << ourCustomAdjustables[myCurrentAdjustable].type myCurrentAdjustable = (myCurrentAdjustable + 1) % 5;
<< "' selected"; #endif
return buf.str();
} }
else if(direction == -1)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string NTSCFilter::setPreviousAdjustable()
{ {
if(myPreset != Preset::CUSTOM) #ifdef BLARGG_PALETTE
return "'Custom' TV mode not selected";
if(myCurrentAdjustable == 0) myCurrentAdjustable = 9; if(myCurrentAdjustable == 0) myCurrentAdjustable = 9;
#else
if(myCurrentAdjustable == 0) myCurrentAdjustable = 4;
#endif
else --myCurrentAdjustable; else --myCurrentAdjustable;
ostringstream buf; }
buf << "Custom adjustable '" << ourCustomAdjustables[myCurrentAdjustable].type
<< "' selected";
return buf.str(); ostringstream msg, val;
value = scaleTo100(*ourCustomAdjustables[myCurrentAdjustable].value);
msg << "Custom " << ourCustomAdjustables[myCurrentAdjustable].type;
val << value << "%";
text = msg.str();
valueText = val.str();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string NTSCFilter::increaseAdjustable() void NTSCFilter::changeAdjustable(int adjustable, int direction,
string& text, string& valueText, Int32& newValue)
{ {
if(myPreset != Preset::CUSTOM) myCurrentAdjustable = adjustable;
return "'Custom' TV mode not selected"; changeCurrentAdjustable(direction, text, valueText, newValue);
uInt32 newval = scaleTo100(*ourCustomAdjustables[myCurrentAdjustable].value);
newval += 2; if(newval > 100) newval = 100;
*ourCustomAdjustables[myCurrentAdjustable].value = scaleFrom100(newval);
ostringstream buf;
buf << "Custom '" << ourCustomAdjustables[myCurrentAdjustable].type
<< "' set to " << newval;
setPreset(myPreset);
return buf.str();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string NTSCFilter::decreaseAdjustable() void NTSCFilter::changeCurrentAdjustable(int direction,
string& text, string& valueText, Int32& newValue)
{ {
if(myPreset != Preset::CUSTOM) //if(myPreset != Preset::CUSTOM)
return "'Custom' TV mode not selected"; // return "'Custom' TV mode not selected";
uInt32 newval = scaleTo100(*ourCustomAdjustables[myCurrentAdjustable].value); newValue = scaleTo100(*ourCustomAdjustables[myCurrentAdjustable].value);
if(newval < 2) newval = 0; newValue = BSPF::clamp(newValue + direction * 1, 0, 100);
else newval -= 2;
*ourCustomAdjustables[myCurrentAdjustable].value = scaleFrom100(newval);
ostringstream buf; *ourCustomAdjustables[myCurrentAdjustable].value = scaleFrom100(newValue);
buf << "Custom '" << ourCustomAdjustables[myCurrentAdjustable].type
<< "' set to " << newval;
setPreset(myPreset); setPreset(myPreset);
return buf.str();
ostringstream msg, val;
msg << "Custom " << ourCustomAdjustables[myCurrentAdjustable].type;
val << newValue << "%";
text = msg.str();
valueText = val.str();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void NTSCFilter::loadConfig(const Settings& settings) void NTSCFilter::loadConfig(const Settings& settings)
{ {
// Load adjustables for custom mode // Load adjustables for custom mode
#ifdef BLARGG_PALETTE
myCustomSetup.hue = BSPF::clamp(settings.getFloat("tv.hue"), -1.0F, 1.0F); myCustomSetup.hue = BSPF::clamp(settings.getFloat("tv.hue"), -1.0F, 1.0F);
myCustomSetup.saturation = BSPF::clamp(settings.getFloat("tv.saturation"), -1.0F, 1.0F); myCustomSetup.saturation = BSPF::clamp(settings.getFloat("tv.saturation"), -1.0F, 1.0F);
myCustomSetup.contrast = BSPF::clamp(settings.getFloat("tv.contrast"), -1.0F, 1.0F); myCustomSetup.contrast = BSPF::clamp(settings.getFloat("tv.contrast"), -1.0F, 1.0F);
myCustomSetup.brightness = BSPF::clamp(settings.getFloat("tv.brightness"), -1.0F, 1.0F); myCustomSetup.brightness = BSPF::clamp(settings.getFloat("tv.brightness"), -1.0F, 1.0F);
myCustomSetup.sharpness = BSPF::clamp(settings.getFloat("tv.sharpness"), -1.0F, 1.0F);
myCustomSetup.gamma = BSPF::clamp(settings.getFloat("tv.gamma"), -1.0F, 1.0F); myCustomSetup.gamma = BSPF::clamp(settings.getFloat("tv.gamma"), -1.0F, 1.0F);
#endif
myCustomSetup.sharpness = BSPF::clamp(settings.getFloat("tv.sharpness"), -1.0F, 1.0F);
myCustomSetup.resolution = BSPF::clamp(settings.getFloat("tv.resolution"), -1.0F, 1.0F); myCustomSetup.resolution = BSPF::clamp(settings.getFloat("tv.resolution"), -1.0F, 1.0F);
myCustomSetup.artifacts = BSPF::clamp(settings.getFloat("tv.artifacts"), -1.0F, 1.0F); myCustomSetup.artifacts = BSPF::clamp(settings.getFloat("tv.artifacts"), -1.0F, 1.0F);
myCustomSetup.fringing = BSPF::clamp(settings.getFloat("tv.fringing"), -1.0F, 1.0F); myCustomSetup.fringing = BSPF::clamp(settings.getFloat("tv.fringing"), -1.0F, 1.0F);
@ -157,12 +157,14 @@ void NTSCFilter::loadConfig(const Settings& settings)
void NTSCFilter::saveConfig(Settings& settings) const void NTSCFilter::saveConfig(Settings& settings) const
{ {
// Save adjustables for custom mode // Save adjustables for custom mode
#ifdef BLARGG_PALETTE
settings.setValue("tv.hue", myCustomSetup.hue); settings.setValue("tv.hue", myCustomSetup.hue);
settings.setValue("tv.saturation", myCustomSetup.saturation); settings.setValue("tv.saturation", myCustomSetup.saturation);
settings.setValue("tv.contrast", myCustomSetup.contrast); settings.setValue("tv.contrast", myCustomSetup.contrast);
settings.setValue("tv.brightness", myCustomSetup.brightness); settings.setValue("tv.brightness", myCustomSetup.brightness);
settings.setValue("tv.sharpness", myCustomSetup.sharpness);
settings.setValue("tv.gamma", myCustomSetup.gamma); settings.setValue("tv.gamma", myCustomSetup.gamma);
#endif
settings.setValue("tv.sharpness", myCustomSetup.sharpness);
settings.setValue("tv.resolution", myCustomSetup.resolution); settings.setValue("tv.resolution", myCustomSetup.resolution);
settings.setValue("tv.artifacts", myCustomSetup.artifacts); settings.setValue("tv.artifacts", myCustomSetup.artifacts);
settings.setValue("tv.fringing", myCustomSetup.fringing); settings.setValue("tv.fringing", myCustomSetup.fringing);
@ -174,12 +176,12 @@ void NTSCFilter::getAdjustables(Adjustable& adjustable, Preset preset) const
{ {
switch(preset) switch(preset)
{ {
case Preset::COMPOSITE:
convertToAdjustable(adjustable, AtariNTSC::TV_Composite); break;
case Preset::SVIDEO:
convertToAdjustable(adjustable, AtariNTSC::TV_SVideo); break;
case Preset::RGB: case Preset::RGB:
convertToAdjustable(adjustable, AtariNTSC::TV_RGB); break; convertToAdjustable(adjustable, AtariNTSC::TV_RGB); break;
case Preset::SVIDEO:
convertToAdjustable(adjustable, AtariNTSC::TV_SVideo); break;
case Preset::COMPOSITE:
convertToAdjustable(adjustable, AtariNTSC::TV_Composite); break;
case Preset::BAD: case Preset::BAD:
convertToAdjustable(adjustable, AtariNTSC::TV_Bad); break; convertToAdjustable(adjustable, AtariNTSC::TV_Bad); break;
case Preset::CUSTOM: case Preset::CUSTOM:
@ -192,12 +194,14 @@ void NTSCFilter::getAdjustables(Adjustable& adjustable, Preset preset) const
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void NTSCFilter::setCustomAdjustables(Adjustable& adjustable) void NTSCFilter::setCustomAdjustables(Adjustable& adjustable)
{ {
myCustomSetup.hue = scaleFrom100(adjustable.hue); #ifdef BLARGG_PALETTE
myCustomSetup.saturation = scaleFrom100(adjustable.saturation); //myCustomSetup.hue = scaleFrom100(adjustable.hue);
myCustomSetup.contrast = scaleFrom100(adjustable.contrast); //myCustomSetup.saturation = scaleFrom100(adjustable.saturation);
myCustomSetup.brightness = scaleFrom100(adjustable.brightness); //myCustomSetup.contrast = scaleFrom100(adjustable.contrast);
//myCustomSetup.brightness = scaleFrom100(adjustable.brightness);
//myCustomSetup.gamma = scaleFrom100(adjustable.gamma);
#endif
myCustomSetup.sharpness = scaleFrom100(adjustable.sharpness); myCustomSetup.sharpness = scaleFrom100(adjustable.sharpness);
myCustomSetup.gamma = scaleFrom100(adjustable.gamma);
myCustomSetup.resolution = scaleFrom100(adjustable.resolution); myCustomSetup.resolution = scaleFrom100(adjustable.resolution);
myCustomSetup.artifacts = scaleFrom100(adjustable.artifacts); myCustomSetup.artifacts = scaleFrom100(adjustable.artifacts);
myCustomSetup.fringing = scaleFrom100(adjustable.fringing); myCustomSetup.fringing = scaleFrom100(adjustable.fringing);
@ -208,12 +212,14 @@ void NTSCFilter::setCustomAdjustables(Adjustable& adjustable)
void NTSCFilter::convertToAdjustable(Adjustable& adjustable, void NTSCFilter::convertToAdjustable(Adjustable& adjustable,
const AtariNTSC::Setup& setup) const const AtariNTSC::Setup& setup) const
{ {
adjustable.hue = scaleTo100(setup.hue); #ifdef BLARGG_PALETTE
adjustable.saturation = scaleTo100(setup.saturation); //adjustable.hue = scaleTo100(setup.hue);
adjustable.contrast = scaleTo100(setup.contrast); //adjustable.saturation = scaleTo100(setup.saturation);
adjustable.brightness = scaleTo100(setup.brightness); //adjustable.contrast = scaleTo100(setup.contrast);
//adjustable.brightness = scaleTo100(setup.brightness);
//adjustable.gamma = scaleTo100(setup.gamma);
#endif
adjustable.sharpness = scaleTo100(setup.sharpness); adjustable.sharpness = scaleTo100(setup.sharpness);
adjustable.gamma = scaleTo100(setup.gamma);
adjustable.resolution = scaleTo100(setup.resolution); adjustable.resolution = scaleTo100(setup.resolution);
adjustable.artifacts = scaleTo100(setup.artifacts); adjustable.artifacts = scaleTo100(setup.artifacts);
adjustable.fringing = scaleTo100(setup.fringing); adjustable.fringing = scaleTo100(setup.fringing);
@ -224,12 +230,16 @@ void NTSCFilter::convertToAdjustable(Adjustable& adjustable,
AtariNTSC::Setup NTSCFilter::myCustomSetup = AtariNTSC::TV_Composite; AtariNTSC::Setup NTSCFilter::myCustomSetup = AtariNTSC::TV_Composite;
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
#ifdef BLARGG_PALETTE
const std::array<NTSCFilter::AdjustableTag, 10> NTSCFilter::ourCustomAdjustables = { { const std::array<NTSCFilter::AdjustableTag, 10> NTSCFilter::ourCustomAdjustables = { {
{ "contrast", &myCustomSetup.contrast }, { "contrast", &myCustomSetup.contrast },
{ "brightness", &myCustomSetup.brightness }, { "brightness", &myCustomSetup.brightness },
{ "hue", &myCustomSetup.hue }, { "hue", &myCustomSetup.hue },
{ "saturation", &myCustomSetup.saturation }, { "saturation", &myCustomSetup.saturation },
{ "gamma", &myCustomSetup.gamma }, { "gamma", &myCustomSetup.gamma },
#else
const std::array<NTSCFilter::AdjustableTag, int(NTSCFilter::Adjustables::NUM_ADJUSTABLES)> NTSCFilter::ourCustomAdjustables = { {
#endif
{ "sharpness", &myCustomSetup.sharpness }, { "sharpness", &myCustomSetup.sharpness },
{ "resolution", &myCustomSetup.resolution }, { "resolution", &myCustomSetup.resolution },
{ "artifacts", &myCustomSetup.artifacts }, { "artifacts", &myCustomSetup.artifacts },

View File

@ -47,12 +47,24 @@ class NTSCFilter
BAD, BAD,
CUSTOM CUSTOM
}; };
enum class Adjustables {
SHARPNESS,
RESOLUTION,
ARTIFACTS,
FRINGING,
BLEEDING,
NUM_ADJUSTABLES
};
/* Normally used in conjunction with custom mode, contains all /* Normally used in conjunction with custom mode, contains all
aspects currently adjustable in NTSC TV emulation. */ aspects currently adjustable in NTSC TV emulation. */
struct Adjustable { struct Adjustable {
#ifdef BLARGG_PALETTE
uInt32 hue, saturation, contrast, brightness, gamma, uInt32 hue, saturation, contrast, brightness, gamma,
sharpness, resolution, artifacts, fringing, bleed; sharpness, resolution, artifacts, fringing, bleed;
#else
uInt32 sharpness, resolution, artifacts, fringing, bleed;
#endif
}; };
public: public:
@ -86,10 +98,12 @@ class NTSCFilter
// Changes are made this way since otherwise 20 key-combinations // Changes are made this way since otherwise 20 key-combinations
// would be needed to dynamically change each setting, and now // would be needed to dynamically change each setting, and now
// only 4 combinations are necessary // only 4 combinations are necessary
string setNextAdjustable(); void selectAdjustable(int direction,
string setPreviousAdjustable(); string& text, string& valueText, Int32& value);
string increaseAdjustable(); void changeAdjustable(int adjustable, int direction,
string decreaseAdjustable(); string& text, string& valueText, Int32& newValue);
void changeCurrentAdjustable(int direction,
string& text, string& valueText, Int32& newValue);
// Load and save NTSC-related settings // Load and save NTSC-related settings
void loadConfig(const Settings& settings); void loadConfig(const Settings& settings);
@ -139,7 +153,11 @@ class NTSCFilter
float* value{nullptr}; float* value{nullptr};
}; };
uInt32 myCurrentAdjustable{0}; uInt32 myCurrentAdjustable{0};
#ifdef BLARGG_PALETTE
static const std::array<AdjustableTag, 10> ourCustomAdjustables; static const std::array<AdjustableTag, 10> ourCustomAdjustables;
#else
static const std::array<AdjustableTag, 5> ourCustomAdjustables;
#endif
private: private:
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported

View File

@ -32,7 +32,10 @@
#include "CartRamWidget.hxx" #include "CartRamWidget.hxx"
#include "RomWidget.hxx" #include "RomWidget.hxx"
#include "Base.hxx" #include "Base.hxx"
#include "Device.hxx"
#include "exception/EmulationWarning.hxx" #include "exception/EmulationWarning.hxx"
#include "TIA.hxx"
#include "M6532.hxx"
using Common::Base; using Common::Base;
using std::hex; using std::hex;
@ -68,15 +71,18 @@ CartDebug::CartDebug(Debugger& dbg, Console& console, const OSystem& osystem)
} }
// Create bank information for each potential bank, and an extra one for ZP RAM // Create bank information for each potential bank, and an extra one for ZP RAM
// Banksizes greater than 4096 indicate multi-bank ROMs, but we handle only // ROM sizes greater than 4096 indicate multi-bank ROMs, but we handle only
// 4K pieces at a time // 4K pieces at a time
// Banksizes less than 4K use the actual value // ROM sizes less than 4K use the actual value
size_t banksize = 0; size_t romSize = 0;
myConsole.cartridge().getImage(banksize); myConsole.cartridge().getImage(romSize);
BankInfo info; BankInfo info;
info.size = std::min<size_t>(banksize, 4_KB); info.size = std::min<size_t>(romSize, 4_KB);
for(uInt32 i = 0; i < myConsole.cartridge().bankCount(); ++i) for(uInt32 i = 0; i < myConsole.cartridge().romBankCount(); ++i)
myBankInfo.push_back(info);
for(uInt32 i = 0; i < myConsole.cartridge().ramBankCount(); ++i)
myBankInfo.push_back(info); myBankInfo.push_back(info);
info.size = 128; // ZP RAM info.size = 128; // ZP RAM
@ -85,7 +91,7 @@ CartDebug::CartDebug(Debugger& dbg, Console& console, const OSystem& osystem)
// We know the address for the startup bank right now // We know the address for the startup bank right now
myBankInfo[myConsole.cartridge().startBank()].addressList.push_front( myBankInfo[myConsole.cartridge().startBank()].addressList.push_front(
myDebugger.dpeek(0xfffc)); myDebugger.dpeek(0xfffc));
addLabel("Start", myDebugger.dpeek(0xfffc, DATA)); addLabel("Start", myDebugger.dpeek(0xfffc, Device::DATA)); // TOOD: ::CODE???
// Add system equates // Add system equates
for(uInt16 addr = 0x00; addr <= 0x0F; ++addr) for(uInt16 addr = 0x00; addr <= 0x0F; ++addr)
@ -156,6 +162,18 @@ void CartDebug::saveOldState()
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int CartDebug::lastReadAddress()
{
return mySystem.m6502().lastReadAddress();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int CartDebug::lastWriteAddress()
{
return mySystem.m6502().lastWriteAddress();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int CartDebug::lastReadBaseAddress() int CartDebug::lastReadBaseAddress()
{ {
@ -224,12 +242,36 @@ string CartDebug::toString()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartDebug::disassemble(bool force) bool CartDebug::disassembleAddr(uInt16 address, bool force)
{
// ROM/RAM bank or ZP-RAM?
int bank = (address & 0x1000) ? getBank(address) : int(myBankInfo.size()) - 1;
return disassemble(bank, address, force);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartDebug::disassemblePC(bool force)
{
return (disassembleAddr(myDebugger.cpuDebug().pc()));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartDebug::disassembleBank(int bank)
{
BankInfo& info = myBankInfo[bank];
info.offset = myConsole.cartridge().bankOrigin(bank);
return disassemble(bank, info.offset, true);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartDebug::disassemble(int bank, uInt16 PC, bool force)
{ {
// Test current disassembly; don't re-disassemble if it hasn't changed // Test current disassembly; don't re-disassemble if it hasn't changed
// Also check if the current PC is in the current list // Also check if the current PC is in the current list
bool bankChanged = myConsole.cartridge().bankChanged(); bool bankChanged = myConsole.cartridge().bankChanged();
uInt16 PC = myDebugger.cpuDebug().pc();
int pcline = addressToLine(PC); int pcline = addressToLine(PC);
bool pcfound = (pcline != -1) && (uInt32(pcline) < myDisassembly.list.size()) && bool pcfound = (pcline != -1) && (uInt32(pcline) < myDisassembly.list.size()) &&
(myDisassembly.list[pcline].disasm[0] != '.'); (myDisassembly.list[pcline].disasm[0] != '.');
@ -241,8 +283,9 @@ bool CartDebug::disassemble(bool force)
if(changed) if(changed)
{ {
// Are we disassembling from ROM or ZP RAM? // Are we disassembling from ROM or ZP RAM?
BankInfo& info = (PC & 0x1000) ? myBankInfo[getBank(PC)] : BankInfo& info = myBankInfo[bank];
myBankInfo[myBankInfo.size()-1]; //(PC & 0x1000) ? myBankInfo[getBank(PC)] :
//myBankInfo[myBankInfo.size()-1];
// If the offset has changed, all old addresses must be 'converted' // If the offset has changed, all old addresses must be 'converted'
// For example, if the list contains any $fxxx and the address space is now // For example, if the list contains any $fxxx and the address space is now
@ -304,8 +347,8 @@ bool CartDebug::fillDisassemblyList(BankInfo& info, uInt16 search)
const DisassemblyTag& tag = myDisassembly.list[i]; const DisassemblyTag& tag = myDisassembly.list[i];
const uInt16 address = tag.address & 0xFFF; const uInt16 address = tag.address & 0xFFF;
// Exclude 'ROW'; they don't have a valid address // Exclude 'Device::ROW'; they don't have a valid address
if(tag.type != CartDebug::ROW) if(tag.type != Device::ROW)
{ {
// Create a mapping from addresses to line numbers // Create a mapping from addresses to line numbers
myAddrToLineList.emplace(address, i); myAddrToLineList.emplace(address, i);
@ -331,7 +374,7 @@ int CartDebug::addressToLine(uInt16 address) const
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::disassemble(uInt16 start, uInt16 lines) const string CartDebug::disassembleLines(uInt16 start, uInt16 lines) const
{ {
// Fill the string with disassembled data // Fill the string with disassembled data
start &= 0xFFF; start &= 0xFFF;
@ -346,7 +389,7 @@ string CartDebug::disassemble(uInt16 start, uInt16 lines) const
if((tag.address & 0xfff) >= start) if((tag.address & 0xfff) >= start)
{ {
if(begin == list_size) begin = end; if(begin == list_size) begin = end;
if(tag.type != CartDebug::ROW) if(tag.type != Device::ROW)
length = std::max(length, uInt32(tag.disasm.length())); length = std::max(length, uInt32(tag.disasm.length()));
--lines; --lines;
@ -357,7 +400,7 @@ string CartDebug::disassemble(uInt16 start, uInt16 lines) const
for(uInt32 i = begin; i < end; ++i) for(uInt32 i = begin; i < end; ++i)
{ {
const CartDebug::DisassemblyTag& tag = myDisassembly.list[i]; const CartDebug::DisassemblyTag& tag = myDisassembly.list[i];
if(tag.type == CartDebug::NONE) if(tag.type == Device::NONE)
continue; continue;
else if(tag.address) else if(tag.address)
buffer << std::uppercase << std::hex << std::setw(4) buffer << std::uppercase << std::hex << std::setw(4)
@ -374,7 +417,7 @@ string CartDebug::disassemble(uInt16 start, uInt16 lines) const
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool CartDebug::addDirective(CartDebug::DisasmType type, bool CartDebug::addDirective(Device::AccessType type,
uInt16 start, uInt16 end, int bank) uInt16 start, uInt16 end, int bank)
{ {
if(end < start || start == 0 || end == 0) if(end < start || start == 0 || end == 0)
@ -384,7 +427,7 @@ bool CartDebug::addDirective(CartDebug::DisasmType type,
bank = (myDebugger.cpuDebug().pc() & 0x1000) ? bank = (myDebugger.cpuDebug().pc() & 0x1000) ?
getBank(myDebugger.cpuDebug().pc()) : int(myBankInfo.size())-1; getBank(myDebugger.cpuDebug().pc()) : int(myBankInfo.size())-1;
bank = std::min(bank, bankCount()); bank = std::min(bank, romBankCount());
BankInfo& info = myBankInfo[bank]; BankInfo& info = myBankInfo[bank];
DirectiveList& list = info.directiveList; DirectiveList& list = info.directiveList;
@ -516,9 +559,9 @@ int CartDebug::getPCBank()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int CartDebug::bankCount() const int CartDebug::romBankCount() const
{ {
return myConsole.cartridge().bankCount(); return myConsole.cartridge().romBankCount();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -696,19 +739,20 @@ string CartDebug::loadListFile()
// The default naming/location for list files is the ROM dir based on the // The default naming/location for list files is the ROM dir based on the
// actual ROM filename // actual ROM filename
if(myListFile == "") FilesystemNode lst(myOSystem.romFile().getPathWithExt(".lst"));
{ if(!lst.isReadable())
FilesystemNode lst(myOSystem.romFile().getPathWithExt("") + ".lst");
if(lst.isFile() && lst.isReadable())
myListFile = lst.getPath();
else
return DebuggerParser::red("list file \'" + lst.getShortPath() + "\' not found"); return DebuggerParser::red("list file \'" + lst.getShortPath() + "\' not found");
}
FilesystemNode node(myListFile); stringstream in;
ifstream in(node.getPath()); try
if(!in.is_open()) {
return DebuggerParser::red("list file '" + node.getShortPath() + "' not readable"); if(lst.read(in) == 0)
return DebuggerParser::red("list file '" + lst.getShortPath() + "' not found");
}
catch(...)
{
return DebuggerParser::red("list file '" + lst.getShortPath() + "' not readable");
}
while(!in.eof()) while(!in.eof())
{ {
@ -747,7 +791,7 @@ string CartDebug::loadListFile()
} }
myDebugger.rom().invalidate(); myDebugger.rom().invalidate();
return "list file '" + node.getShortPath() + "' loaded OK"; return "list file '" + lst.getShortPath() + "' loaded OK";
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -756,23 +800,24 @@ string CartDebug::loadSymbolFile()
// The default naming/location for symbol files is the ROM dir based on the // The default naming/location for symbol files is the ROM dir based on the
// actual ROM filename // actual ROM filename
if(mySymbolFile == "") FilesystemNode sym(myOSystem.romFile().getPathWithExt(".sym"));
{ if(!sym.isReadable())
FilesystemNode sym(myOSystem.romFile().getPathWithExt("") + ".sym");
if(sym.isFile() && sym.isReadable())
mySymbolFile = sym.getPath();
else
return DebuggerParser::red("symbol file \'" + sym.getShortPath() + "\' not found"); return DebuggerParser::red("symbol file \'" + sym.getShortPath() + "\' not found");
}
FilesystemNode node(mySymbolFile);
ifstream in(node.getPath());
if(!in.is_open())
return DebuggerParser::red("symbol file '" + node.getShortPath() + "' not readable");
myUserAddresses.clear(); myUserAddresses.clear();
myUserLabels.clear(); myUserLabels.clear();
stringstream in;
try
{
if(sym.read(in) == 0)
return DebuggerParser::red("symbol file '" + sym.getShortPath() + "' not found");
}
catch(...)
{
return DebuggerParser::red("symbol file '" + sym.getShortPath() + "' not readable");
}
while(!in.eof()) while(!in.eof())
{ {
string label; string label;
@ -807,28 +852,29 @@ string CartDebug::loadSymbolFile()
} }
myDebugger.rom().invalidate(); myDebugger.rom().invalidate();
return "symbol file '" + node.getShortPath() + "' loaded OK"; return "symbol file '" + sym.getShortPath() + "' loaded OK";
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::loadConfigFile() string CartDebug::loadConfigFile()
{ {
// The default naming/location for config files is the ROM dir based on the // The default naming/location for config files is the CFG dir and based
// actual ROM filename // on the actual ROM filename
if(myCfgFile == "") FilesystemNode romNode(myOSystem.romFile().getPathWithExt(".cfg"));
{ FilesystemNode cfg = myOSystem.cfgDir(); cfg /= romNode.getName();
FilesystemNode cfg(myOSystem.romFile().getPathWithExt("") + ".cfg"); if(!cfg.isReadable())
if(cfg.isFile() && cfg.isReadable())
myCfgFile = cfg.getPath();
else
return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not found"); return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not found");
}
FilesystemNode node(myCfgFile); stringstream in;
ifstream in(node.getPath()); try
if(!in.is_open()) {
return "Unable to load directives from " + node.getPath(); cfg.read(in);
}
catch(...)
{
return "Unable to load directives from " + cfg.getPath();
}
// Erase all previous directives // Erase all previous directives
for(auto& bi: myBankInfo) for(auto& bi: myBankInfo)
@ -876,37 +922,57 @@ string CartDebug::loadConfigFile()
else if(BSPF::startsWithIgnoreCase(directive, "CODE")) else if(BSPF::startsWithIgnoreCase(directive, "CODE"))
{ {
buf >> hex >> start >> hex >> end; buf >> hex >> start >> hex >> end;
addDirective(CartDebug::CODE, start, end, currentbank); addDirective(Device::CODE, start, end, currentbank);
} }
else if(BSPF::startsWithIgnoreCase(directive, "GFX")) else if(BSPF::startsWithIgnoreCase(directive, "GFX"))
{ {
buf >> hex >> start >> hex >> end; buf >> hex >> start >> hex >> end;
addDirective(CartDebug::GFX, start, end, currentbank); addDirective(Device::GFX, start, end, currentbank);
} }
else if(BSPF::startsWithIgnoreCase(directive, "PGFX")) else if(BSPF::startsWithIgnoreCase(directive, "PGFX"))
{ {
buf >> hex >> start >> hex >> end; buf >> hex >> start >> hex >> end;
addDirective(CartDebug::PGFX, start, end, currentbank); addDirective(Device::PGFX, start, end, currentbank);
}
else if(BSPF::startsWithIgnoreCase(directive, "COL"))
{
buf >> hex >> start >> hex >> end;
addDirective(Device::COL, start, end, currentbank);
}
else if(BSPF::startsWithIgnoreCase(directive, "PCOL"))
{
buf >> hex >> start >> hex >> end;
addDirective(Device::PCOL, start, end, currentbank);
}
else if(BSPF::startsWithIgnoreCase(directive, "BCOL"))
{
buf >> hex >> start >> hex >> end;
addDirective(Device::BCOL, start, end, currentbank);
}
else if(BSPF::startsWithIgnoreCase(directive, "AUD"))
{
buf >> hex >> start >> hex >> end;
addDirective(Device::AUD, start, end, currentbank);
} }
else if(BSPF::startsWithIgnoreCase(directive, "DATA")) else if(BSPF::startsWithIgnoreCase(directive, "DATA"))
{ {
buf >> hex >> start >> hex >> end; buf >> hex >> start >> hex >> end;
addDirective(CartDebug::DATA, start, end, currentbank); addDirective(Device::DATA, start, end, currentbank);
} }
else if(BSPF::startsWithIgnoreCase(directive, "ROW")) else if(BSPF::startsWithIgnoreCase(directive, "ROW"))
{ {
buf >> hex >> start; buf >> hex >> start;
buf >> hex >> end; buf >> hex >> end;
addDirective(CartDebug::ROW, start, end, currentbank); addDirective(Device::ROW, start, end, currentbank);
} }
} }
} }
myDebugger.rom().invalidate(); myDebugger.rom().invalidate();
stringstream retVal; stringstream retVal;
if(myConsole.cartridge().bankCount() > 1) if(myConsole.cartridge().romBankCount() > 1)
retVal << DebuggerParser::red("config file for multi-bank ROM not fully supported\n"); retVal << DebuggerParser::red("config file for multi-bank ROM not fully supported\n");
retVal << "config file '" << node.getShortPath() << "' loaded OK"; retVal << "config file '" << cfg.getShortPath() << "' loaded OK";
return retVal.str(); return retVal.str();
} }
@ -914,69 +980,72 @@ string CartDebug::loadConfigFile()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::saveConfigFile() string CartDebug::saveConfigFile()
{ {
// The default naming/location for config files is the ROM dir based on the // The default naming/location for config files is the CFG dir and based
// actual ROM filename // on the actual ROM filename
FilesystemNode cfg;
if(myCfgFile == "")
{
cfg = FilesystemNode(myOSystem.romFile().getPathWithExt("") + ".cfg");
if(cfg.isFile() && cfg.isWritable())
myCfgFile = cfg.getPath();
else
return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not writable");
}
const string& name = myConsole.properties().get(PropType::Cart_Name); const string& name = myConsole.properties().get(PropType::Cart_Name);
const string& md5 = myConsole.properties().get(PropType::Cart_MD5); const string& md5 = myConsole.properties().get(PropType::Cart_MD5);
ofstream out(cfg.getPath());
if(!out.is_open())
return "Unable to save directives to " + cfg.getShortPath();
// Store all bank information // Store all bank information
stringstream out;
out << "// Stella.pro: \"" << name << "\"" << endl out << "// Stella.pro: \"" << name << "\"" << endl
<< "// MD5: " << md5 << endl << "// MD5: " << md5 << endl
<< endl; << endl;
for(uInt32 b = 0; b < myConsole.cartridge().bankCount(); ++b) for(uInt32 b = 0; b < myConsole.cartridge().romBankCount(); ++b)
{ {
out << "[" << b << "]" << endl; out << "[" << b << "]" << endl;
getBankDirectives(out, myBankInfo[b]); getBankDirectives(out, myBankInfo[b]);
} }
stringstream retVal; stringstream retVal;
if(myConsole.cartridge().bankCount() > 1) try
{
FilesystemNode romNode(myOSystem.romFile().getPathWithExt(".cfg"));
FilesystemNode cfg = myOSystem.cfgDir(); cfg /= romNode.getName();
if(!cfg.getParent().isWritable())
return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not writable");
if(cfg.write(out) == 0)
return "Unable to save directives to " + cfg.getShortPath();
if(myConsole.cartridge().romBankCount() > 1)
retVal << DebuggerParser::red("config file for multi-bank ROM not fully supported\n"); retVal << DebuggerParser::red("config file for multi-bank ROM not fully supported\n");
retVal << "config file '" << cfg.getShortPath() << "' saved OK"; retVal << "config file '" << cfg.getShortPath() << "' saved OK";
}
catch(const runtime_error& e)
{
retVal << "Unable to save directives: " << e.what();
}
return retVal.str(); return retVal.str();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::saveDisassembly() string CartDebug::saveDisassembly()
{ {
if(myDisasmFile == "") string NTSC_COLOR[16] = {
{ "BLACK", "YELLOW", "BROWN", "ORANGE",
const string& propsname = "RED", "MAUVE", "VIOLET", "PURPLE",
myConsole.properties().get(PropType::Cart_Name) + ".asm"; "BLUE", "BLUE_CYAN", "CYAN", "CYAN_GREEN",
"GREEN", "GREEN_YELLOW", "GREEN_BEIGE", "BEIGE"
myDisasmFile = FilesystemNode(myOSystem.defaultSaveDir() + propsname).getPath(); };
} string PAL_COLOR[16] = {
"BLACK0", "BLACK1", "YELLOW", "GREEN_YELLOW",
FilesystemNode node(myDisasmFile); "ORANGE", "GREEN", "RED", "CYAN_GREEN",
ofstream out(node.getPath()); "MAUVE", "CYAN", "VIOLET", "BLUE_CYAN",
if(!out.is_open()) "PURPLE", "BLUE", "BLACKE", "BLACKF"
return "Unable to save disassembly to " + node.getShortPath(); };
string SECAM_COLOR[8] = {
"BLACK", "BLUE", "RED", "PURPLE",
"GREEN", "CYAN", "YELLOW", "WHITE"
};
bool isNTSC = myConsole.timing() == ConsoleTiming::ntsc;
bool isPAL = myConsole.timing() == ConsoleTiming::pal;
#define ALIGN(x) setfill(' ') << left << setw(x) #define ALIGN(x) setfill(' ') << left << setw(x)
// We can't print the header to the disassembly until it's actually // We can't print the header to the disassembly until it's actually
// been processed; therefore buffer output to a string first // been processed; therefore buffer output to a string first
ostringstream buf; ostringstream buf;
buf << "\n\n;***********************************************************\n"
<< "; Bank " << myConsole.cartridge().getBank();
if (myConsole.cartridge().bankCount() > 1)
buf << " / 0.." << myConsole.cartridge().bankCount() - 1;
buf << "\n;***********************************************************\n\n";
// Use specific settings for disassembly output // Use specific settings for disassembly output
// This will most likely differ from what you see in the debugger // This will most likely differ from what you see in the debugger
@ -992,13 +1061,34 @@ string CartDebug::saveDisassembly()
Disassembly disasm; Disassembly disasm;
disasm.list.reserve(2048); disasm.list.reserve(2048);
for(int bank = 0; bank < myConsole.cartridge().bankCount(); ++bank) uInt16 romBankCount = myConsole.cartridge().romBankCount();
uInt16 oldBank = myConsole.cartridge().getBank();
// prepare for switching banks
myConsole.cartridge().unlockBank();
uInt32 origin = 0;
for(int bank = 0; bank < romBankCount; ++bank)
{ {
// TODO: not every CartDebugWidget does it like that, we need a method
myConsole.cartridge().unlockBank();
myConsole.cartridge().bank(bank);
myConsole.cartridge().lockBank();
BankInfo& info = myBankInfo[bank]; BankInfo& info = myBankInfo[bank];
disassembleBank(bank);
// An empty address list means that DiStella can't do a disassembly // An empty address list means that DiStella can't do a disassembly
if(info.addressList.size() == 0) if(info.addressList.size() == 0)
continue; continue;
buf << "\n\n;***********************************************************\n"
<< "; Bank " << bank;
if (romBankCount > 1)
buf << " / 0.." << romBankCount - 1;
buf << "\n;***********************************************************\n\n";
// Disassemble bank // Disassemble bank
disasm.list.clear(); disasm.list.clear();
DiStella distella(*this, disasm.list, info, settings, DiStella distella(*this, disasm.list, info, settings,
@ -1007,8 +1097,14 @@ string CartDebug::saveDisassembly()
if (myReserved.breakFound) if (myReserved.breakFound)
addLabel("Break", myDebugger.dpeek(0xfffe)); addLabel("Break", myDebugger.dpeek(0xfffe));
buf << " SEG CODE\n" buf << " SEG CODE\n";
<< " ORG $" << Base::HEX4 << info.offset << "\n\n";
if(romBankCount == 1)
buf << " ORG $" << Base::HEX4 << info.offset << "\n\n";
else
buf << " ORG $" << Base::HEX4 << origin << "\n"
<< " RORG $" << Base::HEX4 << info.offset << "\n\n";
origin += uInt32(info.size);
// Format in 'distella' style // Format in 'distella' style
for(uInt32 i = 0; i < disasm.list.size(); ++i) for(uInt32 i = 0; i < disasm.list.size(); ++i)
@ -1022,53 +1118,66 @@ string CartDebug::saveDisassembly()
switch(tag.type) switch(tag.type)
{ {
case CartDebug::CODE: case Device::CODE:
{
buf << ALIGN(32) << tag.disasm << tag.ccount.substr(0, 5) << tag.ctotal << tag.ccount.substr(5, 2); buf << ALIGN(32) << tag.disasm << tag.ccount.substr(0, 5) << tag.ctotal << tag.ccount.substr(5, 2);
if (tag.disasm.find("WSYNC") != std::string::npos) if (tag.disasm.find("WSYNC") != std::string::npos)
buf << "\n;---------------------------------------"; buf << "\n;---------------------------------------";
break; break;
}
case CartDebug::ROW: case Device::ROW:
{
buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8*4-1) << "; $" << Base::HEX4 << tag.address << " (*)"; buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8*4-1) << "; $" << Base::HEX4 << tag.address << " (*)";
break; break;
}
case CartDebug::GFX: case Device::GFX:
{
buf << ".byte " << (settings.gfxFormat == Base::Fmt::_2 ? "%" : "$") buf << ".byte " << (settings.gfxFormat == Base::Fmt::_2 ? "%" : "$")
<< tag.bytes << " ; |"; << tag.bytes << " ; |";
for(int c = 12; c < 20; ++c) for(int c = 12; c < 20; ++c)
buf << ((tag.disasm[c] == '\x1e') ? "#" : " "); buf << ((tag.disasm[c] == '\x1e') ? "#" : " ");
buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (G)"; buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (G)";
break; break;
}
case CartDebug::PGFX: case Device::PGFX:
{
buf << ".byte " << (settings.gfxFormat == Base::Fmt::_2 ? "%" : "$") buf << ".byte " << (settings.gfxFormat == Base::Fmt::_2 ? "%" : "$")
<< tag.bytes << " ; |"; << tag.bytes << " ; |";
for(int c = 12; c < 20; ++c) for(int c = 12; c < 20; ++c)
buf << ((tag.disasm[c] == '\x1f') ? "*" : " "); buf << ((tag.disasm[c] == '\x1f') ? "*" : " ");
buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (P)"; buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (P)";
break; break;
}
case CartDebug::DATA: case Device::COL:
{ buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 15) << "; $" << Base::HEX4 << tag.address << " (C)";
break;
case Device::PCOL:
buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 15) << "; $" << Base::HEX4 << tag.address << " (CP)";
break;
case Device::BCOL:
buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 15) << "; $" << Base::HEX4 << tag.address << " (CB)";
break;
case Device::AUD:
buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8 * 4 - 1) << "; $" << Base::HEX4 << tag.address << " (A)";
break;
case Device::DATA:
buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8 * 4 - 1) << "; $" << Base::HEX4 << tag.address << " (D)"; buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8 * 4 - 1) << "; $" << Base::HEX4 << tag.address << " (D)";
break; break;
}
case CartDebug::NONE: case Device::NONE:
default: default:
{
break; break;
}
} // switch } // switch
buf << "\n"; buf << "\n";
} }
} }
myConsole.cartridge().unlockBank();
myConsole.cartridge().bank(oldBank);
myConsole.cartridge().lockBank();
// Some boilerplate, similar to what DiStella adds // Some boilerplate, similar to what DiStella adds
auto timeinfo = BSPF::localTime(); auto timeinfo = BSPF::localTime();
stringstream out;
out << "; Disassembly of " << myOSystem.romFile().getShortPath() << "\n" out << "; Disassembly of " << myOSystem.romFile().getShortPath() << "\n"
<< "; Disassembled " << std::put_time(&timeinfo, "%c\n") << "; Disassembled " << std::put_time(&timeinfo, "%c\n")
<< "; Using Stella " << STELLA_VERSION << "\n;\n" << "; Using Stella " << STELLA_VERSION << "\n;\n"
@ -1079,19 +1188,45 @@ string CartDebug::saveDisassembly()
<< "; D = DATA directive (referenced in some way)\n" << "; D = DATA directive (referenced in some way)\n"
<< "; G = GFX directive, shown as '#' (stored in player, missile, ball)\n" << "; G = GFX directive, shown as '#' (stored in player, missile, ball)\n"
<< "; P = PGFX directive, shown as '*' (stored in playfield)\n" << "; P = PGFX directive, shown as '*' (stored in playfield)\n"
<< "; C = COL directive, shown as color constants (stored in player color)\n"
<< "; CP = PCOL directive, shown as color constants (stored in playfield color)\n"
<< "; CB = BCOL directive, shown as color constants (stored in background color)\n"
<< "; A = AUD directive (stored in audio registers)\n"
<< "; i = indexed accessed only\n" << "; i = indexed accessed only\n"
<< "; c = used by code executed in RAM\n" << "; c = used by code executed in RAM\n"
<< "; s = used by stack\n" << "; s = used by stack\n"
<< "; ! = page crossed, 1 cycle penalty\n" << "; ! = page crossed, 1 cycle penalty\n"
<< "\n processor 6502\n\n"; << "\n processor 6502\n\n";
out << "\n;-----------------------------------------------------------\n"
<< "; Color constants\n"
<< ";-----------------------------------------------------------\n\n";
if(isNTSC)
{
for(int i = 0; i < 16; ++i)
out << ALIGN(16) << NTSC_COLOR[i] << " = $" << Base::HEX2 << (i << 4) << "\n";
}
else if(isPAL)
{
for(int i = 0; i < 16; ++i)
out << ALIGN(16) << PAL_COLOR[i] << " = $" << Base::HEX2 << (i << 4) << "\n";
}
else
{
for(int i = 0; i < 8; ++i)
out << ALIGN(16) << SECAM_COLOR[i] << " = $" << Base::HEX1 << (i << 1) << "\n";
}
out << "\n";
bool addrUsed = false; bool addrUsed = false;
for(uInt16 addr = 0x00; addr <= 0x0F; ++addr) for(uInt16 addr = 0x00; addr <= 0x0F; ++addr)
addrUsed = addrUsed || myReserved.TIARead[addr] || (mySystem.getAccessFlags(addr) & WRITE); addrUsed = addrUsed || myReserved.TIARead[addr] || (mySystem.getAccessFlags(addr) & Device::WRITE);
for(uInt16 addr = 0x00; addr <= 0x3F; ++addr) for(uInt16 addr = 0x00; addr <= 0x3F; ++addr)
addrUsed = addrUsed || myReserved.TIAWrite[addr] || (mySystem.getAccessFlags(addr) & DATA); addrUsed = addrUsed || myReserved.TIAWrite[addr] || (mySystem.getAccessFlags(addr) & Device::DATA);
for(uInt16 addr = 0x00; addr <= 0x17; ++addr) for(uInt16 addr = 0x00; addr <= 0x17; ++addr)
addrUsed = addrUsed || myReserved.IOReadWrite[addr]; addrUsed = addrUsed || myReserved.IOReadWrite[addr];
if(addrUsed) if(addrUsed)
{ {
out << "\n;-----------------------------------------------------------\n" out << "\n;-----------------------------------------------------------\n"
@ -1103,7 +1238,7 @@ string CartDebug::saveDisassembly()
if(myReserved.TIARead[addr] && ourTIAMnemonicR[addr]) if(myReserved.TIARead[addr] && ourTIAMnemonicR[addr])
out << ALIGN(16) << ourTIAMnemonicR[addr] << "= $" out << ALIGN(16) << ourTIAMnemonicR[addr] << "= $"
<< Base::HEX2 << right << addr << " ; (R)\n"; << Base::HEX2 << right << addr << " ; (R)\n";
else if (mySystem.getAccessFlags(addr) & DATA) else if (mySystem.getAccessFlags(addr) & Device::DATA)
out << ";" << ALIGN(16-1) << ourTIAMnemonicR[addr] << "= $" out << ";" << ALIGN(16-1) << ourTIAMnemonicR[addr] << "= $"
<< Base::HEX2 << right << addr << " ; (Ri)\n"; << Base::HEX2 << right << addr << " ; (Ri)\n";
out << "\n"; out << "\n";
@ -1113,7 +1248,7 @@ string CartDebug::saveDisassembly()
if(myReserved.TIAWrite[addr] && ourTIAMnemonicW[addr]) if(myReserved.TIAWrite[addr] && ourTIAMnemonicW[addr])
out << ALIGN(16) << ourTIAMnemonicW[addr] << "= $" out << ALIGN(16) << ourTIAMnemonicW[addr] << "= $"
<< Base::HEX2 << right << addr << " ; (W)\n"; << Base::HEX2 << right << addr << " ; (W)\n";
else if (mySystem.getAccessFlags(addr) & WRITE) else if (mySystem.getAccessFlags(addr) & Device::WRITE)
out << ";" << ALIGN(16-1) << ourTIAMnemonicW[addr] << "= $" out << ";" << ALIGN(16-1) << ourTIAMnemonicW[addr] << "= $"
<< Base::HEX2 << right << addr << " ; (Wi)\n"; << Base::HEX2 << right << addr << " ; (Wi)\n";
out << "\n"; out << "\n";
@ -1128,8 +1263,8 @@ string CartDebug::saveDisassembly()
addrUsed = false; addrUsed = false;
for(uInt16 addr = 0x80; addr <= 0xFF; ++addr) for(uInt16 addr = 0x80; addr <= 0xFF; ++addr)
addrUsed = addrUsed || myReserved.ZPRAM[addr-0x80] addrUsed = addrUsed || myReserved.ZPRAM[addr-0x80]
|| (mySystem.getAccessFlags(addr) & (DATA | WRITE)) || (mySystem.getAccessFlags(addr) & (Device::DATA | Device::WRITE))
|| (mySystem.getAccessFlags(addr|0x100) & (DATA | WRITE)); || (mySystem.getAccessFlags(addr|0x100) & (Device::DATA | Device::WRITE));
if(addrUsed) if(addrUsed)
{ {
bool addLine = false; bool addLine = false;
@ -1138,9 +1273,9 @@ string CartDebug::saveDisassembly()
<< ";-----------------------------------------------------------\n\n"; << ";-----------------------------------------------------------\n\n";
for (uInt16 addr = 0x80; addr <= 0xFF; ++addr) { for (uInt16 addr = 0x80; addr <= 0xFF; ++addr) {
bool ramUsed = (mySystem.getAccessFlags(addr) & (DATA | WRITE)); bool ramUsed = (mySystem.getAccessFlags(addr) & (Device::DATA | Device::WRITE));
bool codeUsed = (mySystem.getAccessFlags(addr) & CODE); bool codeUsed = (mySystem.getAccessFlags(addr) & Device::CODE);
bool stackUsed = (mySystem.getAccessFlags(addr|0x100) & (DATA | WRITE)); bool stackUsed = (mySystem.getAccessFlags(addr|0x100) & (Device::DATA | Device::WRITE));
if (myReserved.ZPRAM[addr - 0x80] && if (myReserved.ZPRAM[addr - 0x80] &&
myUserLabels.find(addr) == myUserLabels.end()) { myUserLabels.find(addr) == myUserLabels.end()) {
@ -1194,10 +1329,22 @@ string CartDebug::saveDisassembly()
// And finally, output the disassembly // And finally, output the disassembly
out << buf.str(); out << buf.str();
const string& propsname =
myConsole.properties().get(PropType::Cart_Name) + ".asm";
FilesystemNode node(myOSystem.defaultSaveDir().getPath() + propsname);
stringstream retVal; stringstream retVal;
if(myConsole.cartridge().bankCount() > 1) try
retVal << DebuggerParser::red("disassembly for multi-bank ROM not fully supported, only currently enabled banks disassembled\n"); {
node.write(out);
if(myConsole.cartridge().romBankCount() > 1)
retVal << DebuggerParser::red("disassembly for multi-bank ROM not fully supported\n");
retVal << "saved " << node.getShortPath() << " OK"; retVal << "saved " << node.getShortPath() << " OK";
}
catch(...)
{
retVal << "Unable to save disassembly to " << node.getShortPath();
}
return retVal.str(); return retVal.str();
} }
@ -1206,22 +1353,40 @@ string CartDebug::saveRom()
{ {
const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".a26"; const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".a26";
FilesystemNode node(myOSystem.defaultSaveDir() + rom); FilesystemNode node(myOSystem.defaultSaveDir().getPath() + rom);
ofstream out(node.getPath(), std::ios::binary); if(myConsole.cartridge().saveROM(node))
if(out && myConsole.cartridge().saveROM(out))
return "saved ROM as " + node.getShortPath(); return "saved ROM as " + node.getShortPath();
else else
return DebuggerParser::red("failed to save ROM"); return DebuggerParser::red("failed to save ROM");
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::saveAccessFile()
{
stringstream out;
out << myConsole.tia().getAccessCounters();
out << myConsole.riot().getAccessCounters();
out << myConsole.cartridge().getAccessCounters();
try
{
const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".csv";
FilesystemNode node(myOSystem.defaultSaveDir().getPath() + rom);
node.write(out);
return "saved access counters as " + node.getShortPath();
}
catch(...)
{
}
return DebuggerParser::red("failed to save access counters file");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::listConfig(int bank) string CartDebug::listConfig(int bank)
{ {
if(myConsole.cartridge().bankCount() > 1) uInt32 startbank = 0, endbank = romBankCount();
return DebuggerParser::red("config file for multi-bank ROM not yet supported"); if(bank >= 0 && bank < romBankCount())
uInt32 startbank = 0, endbank = bankCount();
if(bank >= 0 && bank < bankCount())
{ {
startbank = bank; startbank = bank;
endbank = startbank + 1; endbank = startbank + 1;
@ -1232,27 +1397,30 @@ string CartDebug::listConfig(int bank)
for(uInt32 b = startbank; b < endbank; ++b) for(uInt32 b = startbank; b < endbank; ++b)
{ {
BankInfo& info = myBankInfo[b]; BankInfo& info = myBankInfo[b];
buf << "[" << b << "]" << endl; buf << "Bank [" << b << "]" << endl;
for(const auto& i: info.directiveList) for(const auto& i: info.directiveList)
{ {
if(i.type != CartDebug::NONE) if(i.type != Device::NONE)
{ {
buf << "(*) "; buf << "(*) ";
disasmTypeAsString(buf, i.type); AccessTypeAsString(buf, i.type);
buf << " " << Base::HEX4 << i.start << " " << Base::HEX4 << i.end << endl; buf << " " << Base::HEX4 << i.start << " " << Base::HEX4 << i.end << endl;
} }
} }
getBankDirectives(buf, info); getBankDirectives(buf, info);
} }
if(myConsole.cartridge().romBankCount() > 1)
buf << DebuggerParser::red("config file for multi-bank ROM not fully supported") << endl;
return buf.str(); return buf.str();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::clearConfig(int bank) string CartDebug::clearConfig(int bank)
{ {
uInt32 startbank = 0, endbank = bankCount(); uInt32 startbank = 0, endbank = romBankCount();
if(bank >= 0 && bank < bankCount()) if(bank >= 0 && bank < romBankCount())
{ {
startbank = bank; startbank = bank;
endbank = startbank + 1; endbank = startbank + 1;
@ -1334,15 +1502,15 @@ void CartDebug::getBankDirectives(ostream& buf, BankInfo& info) const
// Now consider each byte // Now consider each byte
uInt32 prev = info.offset, addr = prev + 1; uInt32 prev = info.offset, addr = prev + 1;
DisasmType prevType = disasmTypeAbsolute(mySystem.getAccessFlags(prev)); Device::AccessType prevType = accessTypeAbsolute(mySystem.getAccessFlags(prev));
for( ; addr < info.offset + info.size; ++addr) for( ; addr < info.offset + info.size; ++addr)
{ {
DisasmType currType = disasmTypeAbsolute(mySystem.getAccessFlags(addr)); Device::AccessType currType = accessTypeAbsolute(mySystem.getAccessFlags(addr));
// Have we changed to a new type? // Have we changed to a new type?
if(currType != prevType) if(currType != prevType)
{ {
disasmTypeAsString(buf, prevType); AccessTypeAsString(buf, prevType);
buf << " " << Base::HEX4 << prev << " " << Base::HEX4 << (addr-1) << endl; buf << " " << Base::HEX4 << prev << " " << Base::HEX4 << (addr-1) << endl;
prev = addr; prev = addr;
@ -1353,13 +1521,13 @@ void CartDebug::getBankDirectives(ostream& buf, BankInfo& info) const
// Grab the last directive, making sure it accounts for all remaining space // Grab the last directive, making sure it accounts for all remaining space
if(prev != addr) if(prev != addr)
{ {
disasmTypeAsString(buf, prevType); AccessTypeAsString(buf, prevType);
buf << " " << Base::HEX4 << prev << " " << Base::HEX4 << (addr-1) << endl; buf << " " << Base::HEX4 << prev << " " << Base::HEX4 << (addr-1) << endl;
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartDebug::addressTypeAsString(ostream& buf, uInt16 addr) const void CartDebug::accessTypeAsString(ostream& buf, uInt16 addr) const
{ {
if(!(addr & 0x1000)) if(!(addr & 0x1000))
{ {
@ -1372,70 +1540,88 @@ void CartDebug::addressTypeAsString(ostream& buf, uInt16 addr) const
label = myDisLabels[addr & 0xFFF]; label = myDisLabels[addr & 0xFFF];
buf << endl << "directive: " << Base::toString(directive, Base::Fmt::_2_8) << " "; buf << endl << "directive: " << Base::toString(directive, Base::Fmt::_2_8) << " ";
disasmTypeAsString(buf, directive); AccessTypeAsString(buf, directive);
buf << endl << "emulation: " << Base::toString(debugger, Base::Fmt::_2_8) << " "; buf << endl << "emulation: " << Base::toString(debugger, Base::Fmt::_2_8) << " ";
disasmTypeAsString(buf, debugger); AccessTypeAsString(buf, debugger);
buf << endl << "tentative: " << Base::toString(label, Base::Fmt::_2_8) << " "; buf << endl << "tentative: " << Base::toString(label, Base::Fmt::_2_8) << " ";
disasmTypeAsString(buf, label); AccessTypeAsString(buf, label);
buf << endl; buf << endl;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartDebug::DisasmType CartDebug::disasmTypeAbsolute(uInt8 flags) const Device::AccessType CartDebug::accessTypeAbsolute(Device::AccessFlags flags) const
{ {
if(flags & CartDebug::CODE) if(flags & Device::CODE)
return CartDebug::CODE; return Device::CODE;
else if(flags & CartDebug::TCODE) else if(flags & Device::TCODE)
return CartDebug::CODE; // TODO - should this be separate?? return Device::CODE; // TODO - should this be separate??
else if(flags & CartDebug::GFX) else if(flags & Device::GFX)
return CartDebug::GFX; return Device::GFX;
else if(flags & CartDebug::PGFX) else if(flags & Device::PGFX)
return CartDebug::PGFX; return Device::PGFX;
else if(flags & CartDebug::DATA) else if(flags & Device::COL)
return CartDebug::DATA; return Device::COL;
else if(flags & CartDebug::ROW) else if(flags & Device::PCOL)
return CartDebug::ROW; return Device::PCOL;
else if(flags & Device::BCOL)
return Device::BCOL;
else if(flags & Device::AUD)
return Device::AUD;
else if(flags & Device::DATA)
return Device::DATA;
else if(flags & Device::ROW)
return Device::ROW;
else else
return CartDebug::NONE; return Device::NONE;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartDebug::disasmTypeAsString(ostream& buf, DisasmType type) const void CartDebug::AccessTypeAsString(ostream& buf, Device::AccessType type) const
{ {
switch(type) switch(type)
{ {
case CartDebug::CODE: buf << "CODE"; break; case Device::CODE: buf << "CODE"; break;
case CartDebug::TCODE: buf << "TCODE"; break; case Device::TCODE: buf << "TCODE"; break;
case CartDebug::GFX: buf << "GFX"; break; case Device::GFX: buf << "GFX"; break;
case CartDebug::PGFX: buf << "PGFX"; break; case Device::PGFX: buf << "PGFX"; break;
case CartDebug::DATA: buf << "DATA"; break; case Device::COL: buf << "COL"; break;
case CartDebug::ROW: buf << "ROW"; break; case Device::PCOL: buf << "PCOL"; break;
case CartDebug::REFERENCED: case Device::BCOL: buf << "BCOL"; break;
case CartDebug::VALID_ENTRY: case Device::AUD: buf << "AUD"; break;
case CartDebug::NONE: break; case Device::DATA: buf << "DATA"; break;
case Device::ROW: buf << "ROW"; break;
default: break;
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CartDebug::disasmTypeAsString(ostream& buf, uInt8 flags) const void CartDebug::AccessTypeAsString(ostream& buf, Device::AccessFlags flags) const
{ {
if(flags) if(flags)
{ {
if(flags & CartDebug::CODE) if(flags & Device::CODE)
buf << "CODE "; buf << "CODE ";
if(flags & CartDebug::TCODE) if(flags & Device::TCODE)
buf << "TCODE "; buf << "TCODE ";
if(flags & CartDebug::GFX) if(flags & Device::GFX)
buf << "GFX "; buf << "GFX ";
if(flags & CartDebug::PGFX) if(flags & Device::PGFX)
buf << "PGFX "; buf << "PGFX ";
if(flags & CartDebug::DATA) if(flags & Device::COL)
buf << "COL ";
if(flags & Device::PCOL)
buf << "PCOL ";
if(flags & Device::BCOL)
buf << "BCOL ";
if(flags & Device::AUD)
buf << "AUD ";
if(flags & Device::DATA)
buf << "DATA "; buf << "DATA ";
if(flags & CartDebug::ROW) if(flags & Device::ROW)
buf << "ROW "; buf << "ROW ";
if(flags & CartDebug::REFERENCED) if(flags & Device::REFERENCED)
buf << "*REFERENCED "; buf << "*REFERENCED ";
if(flags & CartDebug::VALID_ENTRY) if(flags & Device::VALID_ENTRY)
buf << "*VALID_ENTRY "; buf << "*VALID_ENTRY ";
} }
else else

View File

@ -31,6 +31,7 @@ using CartMethod = int (CartDebug::*)();
#include "bspf.hxx" #include "bspf.hxx"
#include "DebuggerSystem.hxx" #include "DebuggerSystem.hxx"
#include "Device.hxx"
class CartState : public DebuggerState class CartState : public DebuggerState
{ {
@ -47,30 +48,8 @@ class CartDebug : public DebuggerSystem
friend class DiStella; friend class DiStella;
public: public:
enum DisasmType {
NONE = 0,
REFERENCED = 1 << 0, /* 0x01, code somewhere in the program references it,
i.e. LDA $F372 referenced $F372 */
VALID_ENTRY = 1 << 1, /* 0x02, addresses that can have a label placed in front of it.
A good counterexample would be "FF00: LDA $FE00"; $FF01
would be in the middle of a multi-byte instruction, and
therefore cannot be labelled. */
// The following correspond to specific types that can be set within the
// debugger, or specified in a Distella cfg file, and are listed in order
// of decreasing hierarchy
//
CODE = 1 << 7, // 0x80, disassemble-able code segments
TCODE = 1 << 6, // 0x40, (tentative) disassemble-able code segments
GFX = 1 << 5, // 0x20, addresses loaded into GRPx registers
PGFX = 1 << 4, // 0x10, addresses loaded into PFx registers
DATA = 1 << 3, // 0x08, addresses loaded into registers other than GRPx / PFx
ROW = 1 << 2, // 0x04, all other addresses
// special type for poke()
WRITE = TCODE // 0x40, address written to
};
struct DisassemblyTag { struct DisassemblyTag {
DisasmType type{NONE}; Device::AccessType type{Device::NONE};
uInt16 address{0}; uInt16 address{0};
string label; string label;
string disasm; string disasm;
@ -104,28 +83,55 @@ class CartDebug : public DebuggerSystem
CartDebugWidget* getDebugWidget() const { return myDebugWidget; } CartDebugWidget* getDebugWidget() const { return myDebugWidget; }
void setDebugWidget(CartDebugWidget* w) { myDebugWidget = w; } void setDebugWidget(CartDebugWidget* w) { myDebugWidget = w; }
// Return the address of the last CPU read
int lastReadAddress();
// Return the address of the last CPU write
int lastWriteAddress();
// Return the base (= non-mirrored) address of the last CPU read // Return the base (= non-mirrored) address of the last CPU read
int lastReadBaseAddress(); int lastReadBaseAddress();
// Return the base (= non-mirrored) address of the last CPU write // Return the base (= non-mirrored) address of the last CPU write
int lastWriteBaseAddress(); int lastWriteBaseAddress();
// The following two methods are meant to be used together
// First, a call is made to disassemble(), which updates the disassembly
// list; it will figure out when an actual complete disassembly is
// required, and when the previous results can be used
//
// Later, successive calls to disassemblyList() simply return the
// previous results; no disassembly is done in this case
/** /**
Disassemble from the given address using the Distella disassembler Disassemble from the given address and its bank using the Distella disassembler
Address-to-label mappings (and vice-versa) are also determined here
@param address The address to start with
@param force Force a re-disassembly, even if the state hasn't changed
@return True if disassembly changed from previous call, else false
*/
bool disassembleAddr(uInt16 address, bool force = false);
/**
Disassemble from the current PC and its bank using the Distella disassembler
Address-to-label mappings (and vice-versa) are also determined here Address-to-label mappings (and vice-versa) are also determined here
@param force Force a re-disassembly, even if the state hasn't changed @param force Force a re-disassembly, even if the state hasn't changed
@return True if disassembly changed from previous call, else false @return True if disassembly changed from previous call, else false
*/ */
bool disassemble(bool force = false); bool disassemblePC(bool force = false);
/**
Disassemble the given bank using the Distella disassembler
Address-to-label mappings (and vice-versa) are also determined here
@param bank The bank to disassemble
@return True if disassembly changed from previous call, else false
*/
bool disassembleBank(int bank);
// First, a call is made to disassemble(), which updates the disassembly
// list, is required; it will figure out when an actual complete
// disassembly is required, and when the previous results can be used
//
// Later, successive calls to disassembly() simply return the
// previous results; no disassembly is done in this case
/** /**
Get the results from the most recent call to disassemble() Get the results from the most recent call to disassemble()
*/ */
@ -149,7 +155,7 @@ class CartDebug : public DebuggerSystem
@return The disassembly represented as a string @return The disassembly represented as a string
*/ */
string disassemble(uInt16 start, uInt16 lines) const; string disassembleLines(uInt16 start, uInt16 lines) const;
/** /**
Add a directive to the disassembler. Directives are basically overrides Add a directive to the disassembler. Directives are basically overrides
@ -157,14 +163,14 @@ class CartDebug : public DebuggerSystem
things can't be automatically determined. For now, these directives things can't be automatically determined. For now, these directives
have exactly the same syntax as in a distella configuration file. have exactly the same syntax as in a distella configuration file.
@param type Currently, CODE/DATA/GFX are supported @param type Currently, CODE/DATA/GFX/PGFX/COL/PCOL/BCOL/AUD/ROW are supported
@param start The start address (inclusive) to mark with the given type @param start The start address (inclusive) to mark with the given type
@param end The end address (inclusive) to mark with the given type @param end The end address (inclusive) to mark with the given type
@param bank Bank to which these directive apply (0 indicated current bank) @param bank Bank to which these directive apply (0 indicated current bank)
@return True if directive was added, else false if it was removed @return True if directive was added, else false if it was removed
*/ */
bool addDirective(CartDebug::DisasmType type, uInt16 start, uInt16 end, bool addDirective(Device::AccessType type, uInt16 start, uInt16 end,
int bank = -1); int bank = -1);
// The following are convenience methods that query the cartridge object // The following are convenience methods that query the cartridge object
@ -181,7 +187,7 @@ class CartDebug : public DebuggerSystem
/** /**
Get the total number of banks supported by the cartridge. Get the total number of banks supported by the cartridge.
*/ */
int bankCount() const; int romBankCount() const;
/** /**
Add a label and associated address. Add a label and associated address.
@ -231,6 +237,11 @@ class CartDebug : public DebuggerSystem
string saveDisassembly(); string saveDisassembly();
string saveRom(); string saveRom();
/**
Save access counters file
*/
string saveAccessFile();
/** /**
Show Distella directives (both set by the user and determined by Distella) Show Distella directives (both set by the user and determined by Distella)
for the given bank (or all banks, if no bank is specified). for the given bank (or all banks, if no bank is specified).
@ -249,18 +260,21 @@ class CartDebug : public DebuggerSystem
*/ */
void getCompletions(const char* in, StringList& list) const; void getCompletions(const char* in, StringList& list) const;
// Convert given address to corresponding disassembly type and append to buf // Convert given address to corresponding access type and append to buf
void addressTypeAsString(ostream& buf, uInt16 addr) const; void accessTypeAsString(ostream& buf, uInt16 addr) const;
// Convert access enum type to corresponding string and append to buf
void AccessTypeAsString(ostream& buf, Device::AccessType type) const;
private: private:
using AddrToLabel = std::map<uInt16, string>; using AddrToLabel = std::map<uInt16, string>;
using LabelToAddr = std::map<string, uInt16, using LabelToAddr = std::map<string, uInt16,
std::function<bool(const string&, const string&)>>; std::function<bool(const string&, const string&)>>;
using AddrTypeArray = std::array<uInt8, 0x1000>; using AddrTypeArray = std::array<uInt16, 0x1000>;
struct DirectiveTag { struct DirectiveTag {
DisasmType type{NONE}; Device::AccessType type{Device::NONE};
uInt16 start{0}; uInt16 start{0};
uInt16 end{0}; uInt16 end{0};
}; };
@ -290,6 +304,18 @@ class CartDebug : public DebuggerSystem
}; };
ReservedEquates myReserved; ReservedEquates myReserved;
/**
Disassemble from the given address using the Distella disassembler
Address-to-label mappings (and vice-versa) are also determined here
@param bank The current bank to disassemble
@param PC A program counter to start with
@param force Force a re-disassembly, even if the state hasn't changed
@return True if disassembly changed from previous call, else false
*/
bool disassemble(int bank, uInt16 PC, bool force = false);
// Actually call DiStella to fill the DisassemblyList structure // Actually call DiStella to fill the DisassemblyList structure
// Return whether the search address was actually in the list // Return whether the search address was actually in the list
bool fillDisassemblyList(BankInfo& bankinfo, uInt16 search); bool fillDisassemblyList(BankInfo& bankinfo, uInt16 search);
@ -298,15 +324,12 @@ class CartDebug : public DebuggerSystem
// based on its disassembly // based on its disassembly
void getBankDirectives(ostream& buf, BankInfo& info) const; void getBankDirectives(ostream& buf, BankInfo& info) const;
// Get disassembly enum type from 'flags', taking precendence into account // Get access enum type from 'flags', taking precendence into account
DisasmType disasmTypeAbsolute(uInt8 flags) const; Device::AccessType accessTypeAbsolute(Device::AccessFlags flags) const;
// Convert disassembly enum type to corresponding string and append to buf // Convert all access types in 'flags' to corresponding string and
void disasmTypeAsString(ostream& buf, DisasmType type) const;
// Convert all disassembly types in 'flags' to corresponding string and
// append to buf // append to buf
void disasmTypeAsString(ostream& buf, uInt8 flags) const; void AccessTypeAsString(ostream& buf, Device::AccessFlags flags) const;
private: private:
const OSystem& myOSystem; const OSystem& myOSystem;
@ -346,9 +369,6 @@ class CartDebug : public DebuggerSystem
// The maximum length of all labels currently defined // The maximum length of all labels currently defined
uInt16 myLabelLength{8}; // longest pre-defined label uInt16 myLabelLength{8}; // longest pre-defined label
// Filenames to use for various I/O (currently these are hardcoded)
string myListFile, mySymbolFile, myCfgFile, myDisasmFile, myRomFile;
/// Table of instruction mnemonics /// Table of instruction mnemonics
static std::array<const char*, 16> ourTIAMnemonicR; // read mode static std::array<const char*, 16> ourTIAMnemonicR; // read mode
static std::array<const char*, 64> ourTIAMnemonicW; // write mode static std::array<const char*, 64> ourTIAMnemonicW; // write mode

View File

@ -20,7 +20,6 @@
#include "M6502.hxx" #include "M6502.hxx"
#include "System.hxx" #include "System.hxx"
#include "Debugger.hxx" #include "Debugger.hxx"
#include "CartDebug.hxx"
#include "TIADebug.hxx" #include "TIADebug.hxx"
#include "CpuDebug.hxx" #include "CpuDebug.hxx"
@ -46,6 +45,7 @@ const DebuggerState& CpuDebug::getState()
myState.srcA = my6502.lastSrcAddressA(); myState.srcA = my6502.lastSrcAddressA();
myState.srcX = my6502.lastSrcAddressX(); myState.srcX = my6502.lastSrcAddressX();
myState.srcY = my6502.lastSrcAddressY(); myState.srcY = my6502.lastSrcAddressY();
myState.dest = my6502.lastWriteAddress();
Debugger::set_bits(myState.PS, myState.PSbits); Debugger::set_bits(myState.PS, myState.PSbits);
@ -66,6 +66,7 @@ void CpuDebug::saveOldState()
myOldState.srcA = my6502.lastSrcAddressA(); myOldState.srcA = my6502.lastSrcAddressA();
myOldState.srcX = my6502.lastSrcAddressX(); myOldState.srcX = my6502.lastSrcAddressX();
myOldState.srcY = my6502.lastSrcAddressY(); myOldState.srcY = my6502.lastSrcAddressY();
myOldState.dest = my6502.lastWriteAddress();
Debugger::set_bits(myOldState.PS, myOldState.PSbits); Debugger::set_bits(myOldState.PS, myOldState.PSbits);
} }

View File

@ -31,7 +31,7 @@ class CpuState : public DebuggerState
{ {
public: public:
int PC{0}, SP{0}, PS{0}, A{0}, X{0}, Y{0}; int PC{0}, SP{0}, PS{0}, A{0}, X{0}, Y{0};
int srcS{0}, srcA{0}, srcX{0}, srcY{0}; int srcS{0}, srcA{0}, srcX{0}, srcY{0}, dest{0};
BoolArray PSbits; BoolArray PSbits;
}; };

View File

@ -115,7 +115,8 @@ void Debugger::initialize()
FBInitStatus Debugger::initializeVideo() FBInitStatus Debugger::initializeVideo()
{ {
string title = string("Stella ") + STELLA_VERSION + ": Debugger mode"; string title = string("Stella ") + STELLA_VERSION + ": Debugger mode";
return myOSystem.frameBuffer().createDisplay(title, myWidth, myHeight); return myOSystem.frameBuffer().createDisplay(title, FrameBuffer::BufferType::Debugger,
myWidth, myHeight);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -166,7 +167,7 @@ string Debugger::autoExec(StringList* history)
ostringstream buf; ostringstream buf;
// autoexec.script is always run // autoexec.script is always run
FilesystemNode autoexec(myOSystem.baseDir() + "autoexec.script"); FilesystemNode autoexec(myOSystem.baseDir().getPath() + "autoexec.script");
buf << "autoExec():" << endl buf << "autoExec():" << endl
<< myParser->exec(autoexec, history) << endl; << myParser->exec(autoexec, history) << endl;
@ -292,8 +293,9 @@ void Debugger::loadAllStates()
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int Debugger::step() int Debugger::step(bool save)
{ {
if(save)
saveOldState(); saveOldState();
uInt64 startCycle = mySystem.cycles(); uInt64 startCycle = mySystem.cycles();
@ -302,6 +304,7 @@ int Debugger::step()
myOSystem.console().tia().updateScanlineByStep().flushLineCache(); myOSystem.console().tia().updateScanlineByStep().flushLineCache();
lockSystem(); lockSystem();
if(save)
addState("step"); addState("step");
return int(mySystem.cycles() - startCycle); return int(mySystem.cycles() - startCycle);
} }
@ -440,19 +443,19 @@ bool Debugger::writeTrap(uInt16 t)
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 Debugger::peek(uInt16 addr, uInt8 flags) uInt8 Debugger::peek(uInt16 addr, Device::AccessFlags flags)
{ {
return mySystem.peek(addr, flags); return mySystem.peek(addr, flags);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt16 Debugger::dpeek(uInt16 addr, uInt8 flags) uInt16 Debugger::dpeek(uInt16 addr, Device::AccessFlags flags)
{ {
return uInt16(mySystem.peek(addr, flags) | (mySystem.peek(addr+1, flags) << 8)); return uInt16(mySystem.peek(addr, flags) | (mySystem.peek(addr+1, flags) << 8));
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Debugger::poke(uInt16 addr, uInt8 value, uInt8 flags) void Debugger::poke(uInt16 addr, uInt8 value, Device::AccessFlags flags)
{ {
mySystem.poke(addr, value, flags); mySystem.poke(addr, value, flags);
} }
@ -464,26 +467,26 @@ M6502& Debugger::m6502() const
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int Debugger::peekAsInt(int addr, uInt8 flags) int Debugger::peekAsInt(int addr, Device::AccessFlags flags)
{ {
return mySystem.peek(uInt16(addr), flags); return mySystem.peek(uInt16(addr), flags);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int Debugger::dpeekAsInt(int addr, uInt8 flags) int Debugger::dpeekAsInt(int addr, Device::AccessFlags flags)
{ {
return mySystem.peek(uInt16(addr), flags) | return mySystem.peek(uInt16(addr), flags) |
(mySystem.peek(uInt16(addr+1), flags) << 8); (mySystem.peek(uInt16(addr+1), flags) << 8);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int Debugger::getAccessFlags(uInt16 addr) const Device::AccessFlags Debugger::getAccessFlags(uInt16 addr) const
{ {
return mySystem.getAccessFlags(addr); return mySystem.getAccessFlags(addr);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Debugger::setAccessFlags(uInt16 addr, uInt8 flags) void Debugger::setAccessFlags(uInt16 addr, Device::AccessFlags flags)
{ {
mySystem.setAccessFlags(addr, flags); mySystem.setAccessFlags(addr, flags);
} }
@ -688,6 +691,7 @@ void Debugger::setStartState()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Debugger::setQuitState() void Debugger::setQuitState()
{ {
myDialog->saveConfig();
saveOldState(); saveOldState();
// Bus must be unlocked for normal operation when leaving debugger mode // Bus must be unlocked for normal operation when leaving debugger mode
@ -873,7 +877,7 @@ std::array<Debugger::BuiltinFunction, 18> Debugger::ourBuiltinFunctions = { {
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// Names are defined here, but processed in YaccParser // Names are defined here, but processed in YaccParser
std::array<Debugger::PseudoRegister, 11> Debugger::ourPseudoRegisters = { { std::array<Debugger::PseudoRegister, 12> Debugger::ourPseudoRegisters = { {
// Debugger::PseudoRegister Debugger::ourPseudoRegisters[NUM_PSEUDO_REGS] = { // Debugger::PseudoRegister Debugger::ourPseudoRegisters[NUM_PSEUDO_REGS] = {
{ "_bank", "Currently selected bank" }, { "_bank", "Currently selected bank" },
{ "_cclocks", "Color clocks on current scanline" }, { "_cclocks", "Color clocks on current scanline" },
@ -883,10 +887,13 @@ std::array<Debugger::PseudoRegister, 11> Debugger::ourPseudoRegisters = { {
{ "_fcycles", "Number of cycles since frame started" }, { "_fcycles", "Number of cycles since frame started" },
{ "_icycles", "Number of cycles of last instruction" }, { "_icycles", "Number of cycles of last instruction" },
{ "_scan", "Current scanline count" }, { "_scan", "Current scanline count" },
{ "_scanend", "Scanline count at end of last frame" },
{ "_scycles", "Number of cycles in current scanline" }, { "_scycles", "Number of cycles in current scanline" },
{ "_vblank", "Whether vertical blank is enabled (1 or 0)" }, { "_vblank", "Whether vertical blank is enabled (1 or 0)" },
{ "_vsync", "Whether vertical sync is enabled (1 or 0)" } { "_vsync", "Whether vertical sync is enabled (1 or 0)" }
// CPU address access functions: // CPU address access functions:
/*{ "__lastread", "last CPU read address" }, /*{ "_lastread", "last CPU read address" },
{ "__lastwrite", "last CPU write address" },*/ { "_lastwrite", "last CPU write address" },
{ "__lastbaseread", "last CPU read base address" },
{ "__lastbasewrite", "last CPU write base address" }*/
} }; } };

View File

@ -47,6 +47,7 @@ class RewindManager;
#include "DialogContainer.hxx" #include "DialogContainer.hxx"
#include "DebuggerDialog.hxx" #include "DebuggerDialog.hxx"
#include "FrameBufferConstants.hxx" #include "FrameBufferConstants.hxx"
#include "Cart.hxx"
#include "bspf.hxx" #include "bspf.hxx"
/** /**
@ -243,18 +244,18 @@ class Debugger : public DialogContainer
static Debugger& debugger() { return *myStaticDebugger; } static Debugger& debugger() { return *myStaticDebugger; }
/** Convenience methods to access peek/poke from System */ /** Convenience methods to access peek/poke from System */
uInt8 peek(uInt16 addr, uInt8 flags = 0); uInt8 peek(uInt16 addr, Device::AccessFlags flags = Device::NONE);
uInt16 dpeek(uInt16 addr, uInt8 flags = 0); uInt16 dpeek(uInt16 addr, Device::AccessFlags flags = Device::NONE);
void poke(uInt16 addr, uInt8 value, uInt8 flags = 0); void poke(uInt16 addr, uInt8 value, Device::AccessFlags flags = Device::NONE);
/** Convenience method to access the 6502 from System */ /** Convenience method to access the 6502 from System */
M6502& m6502() const; M6502& m6502() const;
/** These are now exposed so Expressions can use them. */ /** These are now exposed so Expressions can use them. */
int peekAsInt(int addr, uInt8 flags = 0); int peekAsInt(int addr, Device::AccessFlags flags = Device::NONE);
int dpeekAsInt(int addr, uInt8 flags = 0); int dpeekAsInt(int addr, Device::AccessFlags flags = Device::NONE);
int getAccessFlags(uInt16 addr) const; Device::AccessFlags getAccessFlags(uInt16 addr) const;
void setAccessFlags(uInt16 addr, uInt8 flags); void setAccessFlags(uInt16 addr, Device::AccessFlags flags);
uInt32 getBaseAddress(uInt32 addr, bool read); uInt32 getBaseAddress(uInt32 addr, bool read);
@ -305,7 +306,7 @@ class Debugger : public DialogContainer
*/ */
void setQuitState(); void setQuitState();
int step(); int step(bool save = true);
int trace(); int trace();
void nextScanline(int lines); void nextScanline(int lines);
void nextFrame(int frames); void nextFrame(int frames);
@ -362,7 +363,7 @@ class Debugger : public DialogContainer
string name, help; string name, help;
}; };
static std::array<BuiltinFunction, 18> ourBuiltinFunctions; static std::array<BuiltinFunction, 18> ourBuiltinFunctions;
static std::array<PseudoRegister, 11> ourPseudoRegisters; static std::array<PseudoRegister, 12> ourPseudoRegisters;
private: private:
// rewind/unwind n states // rewind/unwind n states

View File

@ -30,6 +30,7 @@
#include "M6502.hxx" #include "M6502.hxx"
#include "Expression.hxx" #include "Expression.hxx"
#include "FSNode.hxx" #include "FSNode.hxx"
#include "OSystem.hxx"
#include "Settings.hxx" #include "Settings.hxx"
#include "PromptWidget.hxx" #include "PromptWidget.hxx"
#include "RomWidget.hxx" #include "RomWidget.hxx"
@ -111,6 +112,8 @@ string DebuggerParser::run(const string& command)
if(validateArgs(i)) if(validateArgs(i))
{ {
myCommand = i; myCommand = i;
if(commands[i].refreshRequired)
debugger.baseDialog()->saveConfig();
commands[i].executor(this); commands[i].executor(this);
} }
@ -129,9 +132,9 @@ string DebuggerParser::exec(const FilesystemNode& file, StringList* history)
{ {
if(file.exists()) if(file.exists())
{ {
ifstream in(file.getPath()); stringstream in;
if(!in.is_open()) try { file.read(in); }
return red("script file \'" + file.getShortPath() + "\' not found"); catch(...) { return red("script file \'" + file.getShortPath() + "\' not found"); }
ostringstream buf; ostringstream buf;
int count = 0; int count = 0;
@ -630,11 +633,7 @@ string DebuggerParser::saveScriptFile(string file)
if(file.find_last_of('.') == string::npos) if(file.find_last_of('.') == string::npos)
file += ".script"; file += ".script";
FilesystemNode node(debugger.myOSystem.defaultSaveDir() + file); stringstream out;
ofstream out(node.getPath());
if(!out.is_open())
return "Unable to save script to " + node.getShortPath();
Debugger::FunctionDefMap funcs = debugger.getFunctionDefMap(); Debugger::FunctionDefMap funcs = debugger.getFunctionDefMap();
for(const auto& f: funcs) for(const auto& f: funcs)
if (!debugger.isBuiltinFunction(f.first)) if (!debugger.isBuiltinFunction(f.first))
@ -675,9 +674,42 @@ string DebuggerParser::saveScriptFile(string file)
out << endl; out << endl;
} }
FilesystemNode node(debugger.myOSystem.defaultSaveDir().getPath() + file);
try
{
node.write(out);
}
catch(...)
{
return "Unable to save script to " + node.getShortPath();
}
return "saved " + node.getShortPath() + " OK"; return "saved " + node.getShortPath() + " OK";
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DebuggerParser::executeDirective(Device::AccessType type)
{
if(argCount != 2)
{
outputCommandError("specify start and end of range only", myCommand);
return;
}
else if(args[1] < args[0])
{
commandResult << red("start address must be <= end address");
return;
}
bool result = debugger.cartDebug().addDirective(type, args[0], args[1]);
commandResult << (result ? "added " : "removed ");
debugger.cartDebug().AccessTypeAsString(commandResult, type);
commandResult << " directive on range $"
<< hex << args[0] << " $" << hex << args[1];
debugger.rom().invalidate();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// executor methods for commands[] array. All are void, no args. // executor methods for commands[] array. All are void, no args.
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -689,6 +721,13 @@ void DebuggerParser::executeA()
debugger.cpuDebug().setA(uInt8(args[0])); debugger.cpuDebug().setA(uInt8(args[0]));
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// "aud"
void DebuggerParser::executeAud()
{
executeDirective(Device::AUD);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// "base" // "base"
void DebuggerParser::executeBase() void DebuggerParser::executeBase()
@ -720,13 +759,20 @@ void DebuggerParser::executeBase()
} }
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// "bcol"
void DebuggerParser::executeBCol()
{
executeDirective(Device::BCOL);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// "break" // "break"
void DebuggerParser::executeBreak() void DebuggerParser::executeBreak()
{ {
uInt16 addr; uInt16 addr;
uInt8 bank; uInt8 bank;
uInt32 bankCount = debugger.cartDebug().bankCount(); uInt32 romBankCount = debugger.cartDebug().romBankCount();
if(argCount == 0) if(argCount == 0)
addr = debugger.cpuDebug().pc(); addr = debugger.cpuDebug().pc();
@ -738,7 +784,7 @@ void DebuggerParser::executeBreak()
else else
{ {
bank = args[1]; bank = args[1];
if(bank >= bankCount && bank != 0xff) if(bank >= romBankCount && bank != 0xff)
{ {
commandResult << red("invalid bank"); commandResult << red("invalid bank");
return; return;
@ -754,12 +800,12 @@ void DebuggerParser::executeBreak()
commandResult << "cleared"; commandResult << "cleared";
commandResult << " breakpoint at $" << Base::HEX4 << addr << " + mirrors"; commandResult << " breakpoint at $" << Base::HEX4 << addr << " + mirrors";
if(bankCount > 1) if(romBankCount > 1)
commandResult << " in bank #" << std::dec << int(bank); commandResult << " in bank #" << std::dec << int(bank);
} }
else else
{ {
for(int i = 0; i < debugger.cartDebug().bankCount(); ++i) for(int i = 0; i < debugger.cartDebug().romBankCount(); ++i)
{ {
bool set = debugger.toggleBreakPoint(addr, i); bool set = debugger.toggleBreakPoint(addr, i);
@ -772,7 +818,7 @@ void DebuggerParser::executeBreak()
commandResult << "cleared"; commandResult << "cleared";
commandResult << " breakpoint at $" << Base::HEX4 << addr << " + mirrors"; commandResult << " breakpoint at $" << Base::HEX4 << addr << " + mirrors";
if(bankCount > 1) if(romBankCount > 1)
commandResult << " in bank #" << std::dec << int(bank); commandResult << " in bank #" << std::dec << int(bank);
} }
} }
@ -912,22 +958,14 @@ void DebuggerParser::executeCls()
// "code" // "code"
void DebuggerParser::executeCode() void DebuggerParser::executeCode()
{ {
if(argCount != 2) executeDirective(Device::CODE);
{
outputCommandError("specify start and end of range only", myCommand);
return;
}
else if(args[1] < args[0])
{
commandResult << red("start address must be <= end address");
return;
} }
bool result = debugger.cartDebug().addDirective( // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartDebug::CODE, args[0], args[1]); // "col"
commandResult << (result ? "added" : "removed") << " CODE directive on range $" void DebuggerParser::executeCol()
<< hex << args[0] << " $" << hex << args[1]; {
debugger.rom().invalidate(); executeDirective(Device::COL);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -953,22 +991,7 @@ void DebuggerParser::executeD()
// "data" // "data"
void DebuggerParser::executeData() void DebuggerParser::executeData()
{ {
if(argCount != 2) executeDirective(Device::DATA);
{
outputCommandError("specify start and end of range only", myCommand);
return;
}
else if(args[1] < args[0])
{
commandResult << red("start address must be <= end address");
return;
}
bool result = debugger.cartDebug().addDirective(
CartDebug::DATA, args[0], args[1]);
commandResult << (result ? "added" : "removed") << " DATA directive on range $"
<< hex << args[0] << " $" << hex << args[1];
debugger.rom().invalidate();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1067,7 +1090,7 @@ void DebuggerParser::executeDisasm()
return; return;
} }
commandResult << debugger.cartDebug().disassemble(start, lines); commandResult << debugger.cartDebug().disassembleLines(start, lines);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1123,7 +1146,7 @@ void DebuggerParser::executeDump()
file << ".dump"; file << ".dump";
FilesystemNode node(file.str()); FilesystemNode node(file.str());
// cout << "dump " << args[0] << "-" << args[1] << " to " << file.str() << endl; // cout << "dump " << args[0] << "-" << args[1] << " to " << file.str() << endl;
ofstream ofs(node.getPath(), ofstream::out | ofstream::app); std::ofstream ofs(node.getPath(), std::ofstream::out | std::ofstream::app);
if(!ofs.is_open()) if(!ofs.is_open())
{ {
outputCommandError("Unable to append dump to file " + node.getShortPath(), myCommand); outputCommandError("Unable to append dump to file " + node.getShortPath(), myCommand);
@ -1204,9 +1227,8 @@ void DebuggerParser::executeExec()
if(file.find_last_of('.') == string::npos) if(file.find_last_of('.') == string::npos)
file += ".script"; file += ".script";
FilesystemNode node(file); FilesystemNode node(file);
if (!node.exists()) { if (!node.exists())
node = FilesystemNode(debugger.myOSystem.defaultSaveDir() + file); node = FilesystemNode(debugger.myOSystem.defaultSaveDir().getPath() + file);
}
if (argCount == 2) { if (argCount == 2) {
execPrefix = argStrings[1]; execPrefix = argStrings[1];
@ -1270,22 +1292,7 @@ void DebuggerParser::executeFunction()
// "gfx" // "gfx"
void DebuggerParser::executeGfx() void DebuggerParser::executeGfx()
{ {
if(argCount != 2) executeDirective(Device::GFX);
{
outputCommandError("specify start and end of range only", myCommand);
return;
}
else if(args[1] < args[0])
{
commandResult << red("start address must be <= end address");
return;
}
bool result = debugger.cartDebug().addDirective(
CartDebug::GFX, args[0], args[1]);
commandResult << (result ? "added" : "removed") << " GFX directive on range $"
<< hex << args[0] << " $" << hex << args[1];
debugger.rom().invalidate();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1460,11 +1467,11 @@ void DebuggerParser::executeListbreaks()
{ {
stringstream buf; stringstream buf;
int count = 0; int count = 0;
uInt32 bankCount = debugger.cartDebug().bankCount(); uInt32 romBankCount = debugger.cartDebug().romBankCount();
for(const auto& bp : debugger.breakPoints().getBreakpoints()) for(const auto& bp : debugger.breakPoints().getBreakpoints())
{ {
if(bankCount == 1) if(romBankCount == 1)
{ {
buf << debugger.cartDebug().getLabel(bp.addr, true, 4) << " "; buf << debugger.cartDebug().getLabel(bp.addr, true, 4) << " ";
if(!(++count % 8)) buf << endl; if(!(++count % 8)) buf << endl;
@ -1626,26 +1633,18 @@ void DebuggerParser::executePc()
debugger.cpuDebug().setPC(args[0]); debugger.cpuDebug().setPC(args[0]);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// "pcol"
void DebuggerParser::executePCol()
{
executeDirective(Device::PCOL);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// "pgfx" // "pgfx"
void DebuggerParser::executePGfx() void DebuggerParser::executePGfx()
{ {
if(argCount != 2) executeDirective(Device::PGFX);
{
outputCommandError("specify start and end of range only", myCommand);
return;
}
else if(args[1] < args[0])
{
commandResult << red("start address must be <= end address");
return;
}
bool result = debugger.cartDebug().addDirective(
CartDebug::PGFX, args[0], args[1]);
commandResult << (result ? "added" : "removed") << " PGFX directive on range $"
<< hex << args[0] << " $" << hex << args[1];
debugger.rom().invalidate();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1722,22 +1721,7 @@ void DebuggerParser::executeRom()
// "row" // "row"
void DebuggerParser::executeRow() void DebuggerParser::executeRow()
{ {
if(argCount != 2) executeDirective(Device::ROW);
{
outputCommandError("specify start and end of range only", myCommand);
return;
}
else if(args[1] < args[0])
{
commandResult << red("start address must be <= end address");
return;
}
bool result = debugger.cartDebug().addDirective(
CartDebug::ROW, args[0], args[1]);
commandResult << (result ? "added" : "removed") << " ROW directive on range $"
<< hex << args[0] << " $" << hex << args[1];
debugger.rom().invalidate();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1756,6 +1740,8 @@ void DebuggerParser::executeRunTo()
const CartDebug& cartdbg = debugger.cartDebug(); const CartDebug& cartdbg = debugger.cartDebug();
const CartDebug::DisassemblyList& list = cartdbg.disassembly().list; const CartDebug::DisassemblyList& list = cartdbg.disassembly().list;
debugger.saveOldState();
uInt32 count = 0, max_iterations = uInt32(list.size()); uInt32 count = 0, max_iterations = uInt32(list.size());
// Create a progress dialog box to show the progress searching through the // Create a progress dialog box to show the progress searching through the
@ -1767,7 +1753,7 @@ void DebuggerParser::executeRunTo()
bool done = false; bool done = false;
do { do {
debugger.step(); debugger.step(false);
// Update romlist to point to current PC // Update romlist to point to current PC
int pcline = cartdbg.addressToLine(debugger.cpuDebug().pc()); int pcline = cartdbg.addressToLine(debugger.cpuDebug().pc());
@ -1799,24 +1785,36 @@ void DebuggerParser::executeRunToPc()
const CartDebug& cartdbg = debugger.cartDebug(); const CartDebug& cartdbg = debugger.cartDebug();
const CartDebug::DisassemblyList& list = cartdbg.disassembly().list; const CartDebug::DisassemblyList& list = cartdbg.disassembly().list;
debugger.saveOldState();
uInt32 count = 0; uInt32 count = 0;
bool done = false; bool done = false;
constexpr uInt32 max_iterations = 1000000;
// Create a progress dialog box to show the progress searching through the
// disassembly, since this may be a time-consuming operation
ostringstream buf;
buf << "RunTo PC searching through " << max_iterations << " instructions";
ProgressDialog progress(debugger.baseDialog(), debugger.lfont(), buf.str());
progress.setRange(0, max_iterations, 5);
do { do {
debugger.step(); debugger.step(false);
// Update romlist to point to current PC // Update romlist to point to current PC
int pcline = cartdbg.addressToLine(debugger.cpuDebug().pc()); int pcline = cartdbg.addressToLine(debugger.cpuDebug().pc());
done = (pcline >= 0) && (list[pcline].address == args[0]); done = (pcline >= 0) && (list[pcline].address == args[0]);
} while(!done && ++count < list.size()); progress.setProgress(count);
} while(!done && ++count < max_iterations/*list.size()*/);
progress.close();
if(done) if(done)
commandResult commandResult
<< "set PC to " << Base::HEX4 << args[0] << " in " << "Set PC to $" << Base::HEX4 << args[0] << " in "
<< dec << count << " disassembled instructions"; << dec << count << " instructions";
else else
commandResult commandResult
<< "PC " << Base::HEX4 << args[0] << " not reached or found in " << "PC $" << Base::HEX4 << args[0] << " not reached or found in "
<< dec << count << " disassembled instructions"; << dec << count << " instructions";
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
@ -1833,6 +1831,13 @@ void DebuggerParser::executeSave()
commandResult << saveScriptFile(argStrings[0]); commandResult << saveScriptFile(argStrings[0]);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// "saveaccess"
void DebuggerParser::executeSaveAccess()
{
commandResult << debugger.cartDebug().saveAccessFile();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// "saveconfig" // "saveconfig"
void DebuggerParser::executeSaveconfig() void DebuggerParser::executeSaveconfig()
@ -1947,9 +1952,23 @@ void DebuggerParser::executeStepwhile()
} }
Expression* expr = YaccParser::getResult(); Expression* expr = YaccParser::getResult();
int ncycles = 0; int ncycles = 0;
uInt32 count = 0;
constexpr uInt32 max_iterations = 1000000;
// Create a progress dialog box to show the progress searching through the
// disassembly, since this may be a time-consuming operation
ostringstream buf;
buf << "stepwhile running through " << max_iterations << " disassembled instructions";
ProgressDialog progress(debugger.baseDialog(), debugger.lfont(), buf.str());
progress.setRange(0, max_iterations, 5);
do { do {
ncycles += debugger.step(); ncycles += debugger.step(false);
} while (expr->evaluate());
progress.setProgress(count);
} while (expr->evaluate() && ++count < max_iterations);
progress.close();
commandResult << "executed " << ncycles << " cycles"; commandResult << "executed " << ncycles << " cycles";
} }
@ -2054,18 +2073,18 @@ void DebuggerParser::executeTraps(bool read, bool write, const string& command,
if(read) if(read)
{ {
if(beginRead != endRead) if(beginRead != endRead)
conditionBuf << "__lastread>=" << Base::toString(beginRead) << "&&__lastread<=" << Base::toString(endRead); conditionBuf << "__lastbaseread>=" << Base::toString(beginRead) << "&&__lastbaseread<=" << Base::toString(endRead);
else else
conditionBuf << "__lastread==" << Base::toString(beginRead); conditionBuf << "__lastbaseread==" << Base::toString(beginRead);
} }
if(read && write) if(read && write)
conditionBuf << "||"; conditionBuf << "||";
if(write) if(write)
{ {
if(beginWrite != endWrite) if(beginWrite != endWrite)
conditionBuf << "__lastwrite>=" << Base::toString(beginWrite) << "&&__lastwrite<=" << Base::toString(endWrite); conditionBuf << "__lastbasewrite>=" << Base::toString(beginWrite) << "&&__lastbasewrite<=" << Base::toString(endWrite);
else else
conditionBuf << "__lastwrite==" << Base::toString(beginWrite); conditionBuf << "__lastbasewrite==" << Base::toString(beginWrite);
} }
// parenthesize provided condition (end) // parenthesize provided condition (end)
if(hasCond) if(hasCond)
@ -2194,7 +2213,7 @@ void DebuggerParser::executeType()
for(uInt32 i = beg; i <= end; ++i) for(uInt32 i = beg; i <= end; ++i)
{ {
commandResult << Base::HEX4 << i << ": "; commandResult << Base::HEX4 << i << ": ";
debugger.cartDebug().addressTypeAsString(commandResult, i); debugger.cartDebug().accessTypeAsString(commandResult, i);
commandResult << endl; commandResult << endl;
} }
} }
@ -2300,7 +2319,7 @@ void DebuggerParser::executeZ()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// List of all commands available to the parser // List of all commands available to the parser
std::array<DebuggerParser::Command, 95> DebuggerParser::commands = { { std::array<DebuggerParser::Command, 100> DebuggerParser::commands = { {
{ {
"a", "a",
"Set Accumulator to <value>", "Set Accumulator to <value>",
@ -2311,6 +2330,16 @@ std::array<DebuggerParser::Command, 95> DebuggerParser::commands = { {
std::mem_fn(&DebuggerParser::executeA) std::mem_fn(&DebuggerParser::executeA)
}, },
{
"aud",
"Mark 'AUD' range in disassembly",
"Start and end of range required\nExample: aud f000 f010",
true,
false,
{ Parameters::ARG_WORD, Parameters::ARG_MULTI_BYTE },
std::mem_fn(&DebuggerParser::executeAud)
},
{ {
"base", "base",
"Set default number base to <base>", "Set default number base to <base>",
@ -2321,6 +2350,17 @@ std::array<DebuggerParser::Command, 95> DebuggerParser::commands = { {
std::mem_fn(&DebuggerParser::executeBase) std::mem_fn(&DebuggerParser::executeBase)
}, },
{
"bcol",
"Mark 'BCOL' range in disassembly",
"Start and end of range required\nExample: bcol f000 f010",
true,
false,
{ Parameters::ARG_WORD, Parameters::ARG_MULTI_BYTE },
std::mem_fn(&DebuggerParser::executeBCol)
},
{ {
"break", "break",
"Break at <address> and <bank>", "Break at <address> and <bank>",
@ -2442,6 +2482,16 @@ std::array<DebuggerParser::Command, 95> DebuggerParser::commands = { {
std::mem_fn(&DebuggerParser::executeCode) std::mem_fn(&DebuggerParser::executeCode)
}, },
{
"col",
"Mark 'COL' range in disassembly",
"Start and end of range required\nExample: col f000 f010",
true,
false,
{ Parameters::ARG_WORD, Parameters::ARG_MULTI_BYTE },
std::mem_fn(&DebuggerParser::executeCol)
},
{ {
"colortest", "colortest",
"Show value xx as TIA color", "Show value xx as TIA color",
@ -2847,6 +2897,16 @@ std::array<DebuggerParser::Command, 95> DebuggerParser::commands = { {
std::mem_fn(&DebuggerParser::executePc) std::mem_fn(&DebuggerParser::executePc)
}, },
{
"pcol",
"Mark 'PCOL' range in disassembly",
"Start and end of range required\nExample: col f000 f010",
true,
false,
{ Parameters::ARG_WORD, Parameters::ARG_MULTI_BYTE },
std::mem_fn(&DebuggerParser::executePCol)
},
{ {
"pgfx", "pgfx",
"Mark 'PGFX' range in disassembly", "Mark 'PGFX' range in disassembly",
@ -2980,6 +3040,16 @@ std::array<DebuggerParser::Command, 95> DebuggerParser::commands = { {
std::mem_fn(&DebuggerParser::executeSave) std::mem_fn(&DebuggerParser::executeSave)
}, },
{
"saveaccess",
"Save the access counters to CSV file",
"Example: saveaccess (no parameters)",
false,
false,
{ Parameters::ARG_END_ARGS },
std::mem_fn(&DebuggerParser::executeSaveAccess)
},
{ {
"saveconfig", "saveconfig",
"Save Distella config file (with default name)", "Save Distella config file (with default name)",
@ -3182,7 +3252,7 @@ std::array<DebuggerParser::Command, 95> DebuggerParser::commands = { {
{ {
"type", "type",
"Show disassembly type for address xx [yy]", "Show access type for address xx [yy]",
"Example: type f000, type f000 f010", "Example: type f000, type f000 f010",
true, true,
false, false,

View File

@ -27,6 +27,7 @@ class FilesystemNode;
struct Command; struct Command;
#include "bspf.hxx" #include "bspf.hxx"
#include "Device.hxx"
class DebuggerParser class DebuggerParser
{ {
@ -97,7 +98,7 @@ class DebuggerParser
std::array<Parameters, 10> parms; std::array<Parameters, 10> parms;
std::function<void (DebuggerParser*)> executor; std::function<void (DebuggerParser*)> executor;
}; };
static std::array<Command, 95> commands; static std::array<Command, 100> commands;
struct Trap struct Trap
{ {
@ -140,9 +141,13 @@ class DebuggerParser
// output the error with the example provided for the command // output the error with the example provided for the command
void outputCommandError(const string& errorMsg, int command); void outputCommandError(const string& errorMsg, int command);
void executeDirective(Device::AccessType type);
// List of available command methods // List of available command methods
void executeA(); void executeA();
void executeAud();
void executeBase(); void executeBase();
void executeBCol();
void executeBreak(); void executeBreak();
void executeBreakif(); void executeBreakif();
void executeBreaklabel(); void executeBreaklabel();
@ -155,6 +160,7 @@ class DebuggerParser
void executeClearwatches(); void executeClearwatches();
void executeCls(); void executeCls();
void executeCode(); void executeCode();
void executeCol();
void executeColortest(); void executeColortest();
void executeD(); void executeD();
void executeData(); void executeData();
@ -195,6 +201,7 @@ class DebuggerParser
void executeN(); void executeN();
void executePalette(); void executePalette();
void executePc(); void executePc();
void executePCol();
void executePGfx(); void executePGfx();
void executePrint(); void executePrint();
void executeRam(); void executeRam();
@ -208,6 +215,7 @@ class DebuggerParser
void executeRunToPc(); void executeRunToPc();
void executeS(); void executeS();
void executeSave(); void executeSave();
void executeSaveAccess();
void executeSaveallstates(); void executeSaveallstates();
void executeSaveconfig(); void executeSaveconfig();
void executeSavedisassembly(); void executeSavedisassembly();

View File

@ -17,6 +17,7 @@
#include "bspf.hxx" #include "bspf.hxx"
#include "Debugger.hxx" #include "Debugger.hxx"
#include "Device.hxx"
#include "DiStella.hxx" #include "DiStella.hxx"
using Common::Base; using Common::Base;
@ -73,7 +74,7 @@ DiStella::DiStella(const CartDebug& dbg, CartDebug::DisassemblyList& list,
// Add reserved line equates // Add reserved line equates
ostringstream reservedLabel; ostringstream reservedLabel;
for (int k = 0; k <= myAppData.end; k++) { for (int k = 0; k <= myAppData.end; k++) {
if ((myLabels[k] & (CartDebug::REFERENCED | CartDebug::VALID_ENTRY)) == CartDebug::REFERENCED) { if ((myLabels[k] & (Device::REFERENCED | Device::VALID_ENTRY)) == Device::REFERENCED) {
// If we have a piece of code referenced somewhere else, but cannot // If we have a piece of code referenced somewhere else, but cannot
// locate the label in code (i.e because the address is inside of a // locate the label in code (i.e because the address is inside of a
// multi-byte instruction, then we make note of that address for reference // multi-byte instruction, then we make note of that address for reference
@ -107,62 +108,91 @@ void DiStella::disasm(uInt32 distart, int pass)
uInt16 ad; uInt16 ad;
Int32 cycles = 0; Int32 cycles = 0;
AddressingMode addrMode; AddressingMode addrMode;
int labelFound = 0; AddressType labelFound = AddressType::INVALID;
stringstream nextLine, nextLineBytes; stringstream nextLine, nextLineBytes;
mySegType = CartDebug::NONE; // create extra lines between code and data mySegType = Device::NONE; // create extra lines between code and data
myDisasmBuf.str(""); myDisasmBuf.str("");
/* pc=myAppData.start; */ /* pc=myAppData.start; */
myPC = distart - myOffset; myPC = distart - myOffset;
while (myPC <= myAppData.end) { while(myPC <= myAppData.end)
{
// since -1 is used in m6502.m4 for clearing the last peek // since -1 is used in m6502.m4 for clearing the last peek
// and this results into an access at e.g. 0xffff, // and this results into an access at e.g. 0xffff,
// we have to fix the consequences here (ugly!). // we have to fix the consequences here (ugly!).
if(myPC == myAppData.end) if(myPC == myAppData.end)
goto FIX_LAST; goto FIX_LAST;
if (checkBits(myPC, CartDebug::GFX | CartDebug::PGFX, if(checkBits(myPC, Device::GFX | Device::PGFX,
CartDebug::CODE)) { Device::CODE))
{
if(pass == 2) if(pass == 2)
mark(myPC + myOffset, CartDebug::VALID_ENTRY); mark(myPC + myOffset, Device::VALID_ENTRY);
if(pass == 3) if(pass == 3)
outputGraphics(); outputGraphics();
++myPC; ++myPC;
} else if (checkBits(myPC, CartDebug::DATA, }
CartDebug::CODE | CartDebug::GFX | CartDebug::PGFX)) { else if(checkBits(myPC, Device::COL | Device::PCOL | Device::BCOL,
Device::CODE | Device::GFX | Device::PGFX))
{
if(pass == 2) if(pass == 2)
mark(myPC + myOffset, CartDebug::VALID_ENTRY); mark(myPC + myOffset, Device::VALID_ENTRY);
if(pass == 3) if(pass == 3)
outputBytes(CartDebug::DATA); outputColors();
++myPC;
}
else if(checkBits(myPC, Device::AUD,
Device::CODE | Device::GFX | Device::PGFX |
Device::COL | Device::PCOL | Device::BCOL))
{
if(pass == 2)
mark(myPC + myOffset, Device::VALID_ENTRY);
if(pass == 3)
outputBytes(Device::AUD);
else else
++myPC; ++myPC;
} else if (checkBits(myPC, CartDebug::ROW, }
CartDebug::CODE | CartDebug::DATA | CartDebug::GFX | CartDebug::PGFX)) { else if(checkBits(myPC, Device::DATA,
Device::CODE | Device::GFX | Device::PGFX |
Device::COL | Device::PCOL | Device::BCOL |
Device::AUD))
{
if(pass == 2)
mark(myPC + myOffset, Device::VALID_ENTRY);
if(pass == 3)
outputBytes(Device::DATA);
else
++myPC;
}
else if(checkBits(myPC, Device::ROW,
Device::CODE | Device::GFX | Device::PGFX |
Device::COL | Device::PCOL | Device::BCOL |
Device::AUD | Device::DATA)) {
FIX_LAST: FIX_LAST:
if(pass == 2) if(pass == 2)
mark(myPC + myOffset, CartDebug::VALID_ENTRY); mark(myPC + myOffset, Device::VALID_ENTRY);
if(pass == 3) if(pass == 3)
outputBytes(CartDebug::ROW); outputBytes(Device::ROW);
else else
++myPC; ++myPC;
} else { }
else {
// The following sections must be CODE // The following sections must be CODE
// add extra spacing line when switching from non-code to code // add extra spacing line when switching from non-code to code
if (pass == 3 && mySegType != CartDebug::CODE && mySegType != CartDebug::NONE) { if(pass == 3 && mySegType != Device::CODE && mySegType != Device::NONE) {
myDisasmBuf << " ' ' "; myDisasmBuf << " ' ' ";
addEntry(CartDebug::NONE); addEntry(Device::NONE);
mark(myPC + myOffset, CartDebug::REFERENCED); // add label when switching mark(myPC + myOffset, Device::REFERENCED); // add label when switching
} }
mySegType = CartDebug::CODE; mySegType = Device::CODE;
/* version 2.1 bug fix */ /* version 2.1 bug fix */
if(pass == 2) if(pass == 2)
mark(myPC + myOffset, CartDebug::VALID_ENTRY); mark(myPC + myOffset, Device::VALID_ENTRY);
// get opcode // get opcode
opcode = Debugger::debugger().peek(myPC + myOffset); opcode = Debugger::debugger().peek(myPC + myOffset);
@ -170,7 +200,7 @@ FIX_LAST:
addrMode = ourLookup[opcode].addr_mode; addrMode = ourLookup[opcode].addr_mode;
if(pass == 3) { if(pass == 3) {
if (checkBit(myPC, CartDebug::REFERENCED)) if(checkBit(myPC, Device::REFERENCED))
myDisasmBuf << Base::HEX4 << myPC + myOffset << "'L" << Base::HEX4 << myPC + myOffset << "'"; myDisasmBuf << Base::HEX4 << myPC + myOffset << "'L" << Base::HEX4 << myPC + myOffset << "'";
else else
myDisasmBuf << Base::HEX4 << myPC + myOffset << "' '"; myDisasmBuf << Base::HEX4 << myPC + myOffset << "' '";
@ -178,14 +208,14 @@ FIX_LAST:
++myPC; ++myPC;
// detect labels inside instructions (e.g. BIT masks) // detect labels inside instructions (e.g. BIT masks)
labelFound = false; labelFound = AddressType::INVALID;
for(Uint8 i = 0; i < ourLookup[opcode].bytes - 1; i++) { for(Uint8 i = 0; i < ourLookup[opcode].bytes - 1; i++) {
if (checkBit(myPC + i, CartDebug::REFERENCED)) { if(checkBit(myPC + i, Device::REFERENCED)) {
labelFound = true; labelFound = AddressType::ROM;
break; break;
} }
} }
if (labelFound) { if(labelFound != AddressType::INVALID) {
if(myOffset >= 0x1000) { if(myOffset >= 0x1000) {
// the opcode's operand address matches a label address // the opcode's operand address matches a label address
if(pass == 3) { if(pass == 3) {
@ -203,11 +233,12 @@ FIX_LAST:
nextLine.str(""); nextLine.str("");
cycles = 0; cycles = 0;
addEntry(CartDebug::CODE); // add the new found CODE entry addEntry(Device::CODE); // add the new found CODE entry
} }
// continue with the label's opcode // continue with the label's opcode
continue; continue;
} else { }
else {
if(pass == 3) { if(pass == 3) {
// TODO // TODO
} }
@ -242,10 +273,10 @@ FIX_LAST:
myDisasmBuf << ".byte $" << Base::HEX2 << int(opcode) << " $" myDisasmBuf << ".byte $" << Base::HEX2 << int(opcode) << " $"
<< Base::HEX4 << myPC + myOffset << "'" << Base::HEX4 << myPC + myOffset << "'"
<< Base::HEX2 << int(opcode); << Base::HEX2 << int(opcode);
addEntry(CartDebug::DATA); addEntry(Device::DATA);
if(myPC == myAppData.end) { if(myPC == myAppData.end) {
if (checkBit(myPC, CartDebug::REFERENCED)) if(checkBit(myPC, Device::REFERENCED))
myDisasmBuf << Base::HEX4 << myPC + myOffset << "'L" << Base::HEX4 << myPC + myOffset << "'"; myDisasmBuf << Base::HEX4 << myPC + myOffset << "'L" << Base::HEX4 << myPC + myOffset << "'";
else else
myDisasmBuf << Base::HEX4 << myPC + myOffset << "' '"; myDisasmBuf << Base::HEX4 << myPC + myOffset << "' '";
@ -254,7 +285,7 @@ FIX_LAST:
myDisasmBuf << ".byte $" << Base::HEX2 << int(opcode) << " $" myDisasmBuf << ".byte $" << Base::HEX2 << int(opcode) << " $"
<< Base::HEX4 << myPC + myOffset << "'" << Base::HEX4 << myPC + myOffset << "'"
<< Base::HEX2 << int(opcode); << Base::HEX2 << int(opcode);
addEntry(CartDebug::DATA); addEntry(Device::DATA);
} }
} }
myPCEnd = myAppData.end + myOffset; myPCEnd = myAppData.end + myOffset;
@ -271,7 +302,7 @@ FIX_LAST:
/* Line information is already printed, but we can remove the /* Line information is already printed, but we can remove the
Instruction (i.e. BMI) by simply clearing the buffer to print */ Instruction (i.e. BMI) by simply clearing the buffer to print */
myDisasmBuf << ".byte $" << Base::HEX2 << int(opcode); myDisasmBuf << ".byte $" << Base::HEX2 << int(opcode);
addEntry(CartDebug::ROW); addEntry(Device::ROW);
nextLine.str(""); nextLine.str("");
nextLineBytes.str(""); nextLineBytes.str("");
} }
@ -299,26 +330,29 @@ FIX_LAST:
case AddressingMode::ABSOLUTE: case AddressingMode::ABSOLUTE:
{ {
ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2; ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2;
labelFound = mark(ad, CartDebug::REFERENCED); labelFound = mark(ad, Device::REFERENCED);
if(pass == 3) { if(pass == 3) {
if(ad < 0x100 && mySettings.fFlag) if(ad < 0x100 && mySettings.fFlag)
nextLine << ".w "; nextLine << ".w ";
else else
nextLine << " "; nextLine << " ";
if (labelFound == 1) { if(labelFound == AddressType::ROM) {
LABEL_A12_HIGH(ad); LABEL_A12_HIGH(ad);
nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8); nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8);
} else if (labelFound == 4) { }
else if(labelFound == AddressType::ROM_MIRROR) {
if(mySettings.rFlag) { if(mySettings.rFlag) {
int tmp = (ad & myAppData.end) + myOffset; int tmp = (ad & myAppData.end) + myOffset;
LABEL_A12_HIGH(tmp); LABEL_A12_HIGH(tmp);
nextLineBytes << Base::HEX2 << int(tmp & 0xff) << " " << Base::HEX2 << int(tmp >> 8); nextLineBytes << Base::HEX2 << int(tmp & 0xff) << " " << Base::HEX2 << int(tmp >> 8);
} else { }
else {
nextLine << "$" << Base::HEX4 << ad; nextLine << "$" << Base::HEX4 << ad;
nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8); nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8);
} }
} else { }
else {
LABEL_A12_LOW(ad); LABEL_A12_LOW(ad);
nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8); nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8);
} }
@ -329,7 +363,7 @@ FIX_LAST:
case AddressingMode::ZERO_PAGE: case AddressingMode::ZERO_PAGE:
{ {
d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC; d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC;
labelFound = mark(d1, CartDebug::REFERENCED); labelFound = mark(d1, Device::REFERENCED);
if(pass == 3) { if(pass == 3) {
nextLine << " "; nextLine << " ";
LABEL_A12_LOW(int(d1)); LABEL_A12_LOW(int(d1));
@ -351,34 +385,38 @@ FIX_LAST:
case AddressingMode::ABSOLUTE_X: case AddressingMode::ABSOLUTE_X:
{ {
ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2; ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2;
labelFound = mark(ad, CartDebug::REFERENCED); labelFound = mark(ad, Device::REFERENCED);
if (pass == 2 && !checkBit(ad & myAppData.end, CartDebug::CODE)) { if(pass == 2 && !checkBit(ad & myAppData.end, Device::CODE)) {
// Since we can't know what address is being accessed unless we also // Since we can't know what address is being accessed unless we also
// know the current X value, this is marked as ROW instead of DATA // know the current X value, this is marked as ROW instead of DATA
// The processing is left here, however, in case future versions of // The processing is left here, however, in case future versions of
// the code can somehow track access to CPU registers // the code can somehow track access to CPU registers
mark(ad, CartDebug::ROW); mark(ad, Device::ROW);
} else if (pass == 3) { }
else if(pass == 3) {
if(ad < 0x100 && mySettings.fFlag) if(ad < 0x100 && mySettings.fFlag)
nextLine << ".wx "; nextLine << ".wx ";
else else
nextLine << " "; nextLine << " ";
if (labelFound == 1) { if(labelFound == AddressType::ROM) {
LABEL_A12_HIGH(ad); LABEL_A12_HIGH(ad);
nextLine << ",x"; nextLine << ",x";
nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8); nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8);
} else if (labelFound == 4) { }
else if(labelFound == AddressType::ROM_MIRROR) {
if(mySettings.rFlag) { if(mySettings.rFlag) {
int tmp = (ad & myAppData.end) + myOffset; int tmp = (ad & myAppData.end) + myOffset;
LABEL_A12_HIGH(tmp); LABEL_A12_HIGH(tmp);
nextLine << ",x"; nextLine << ",x";
nextLineBytes << Base::HEX2 << int(tmp & 0xff) << " " << Base::HEX2 << int(tmp >> 8); nextLineBytes << Base::HEX2 << int(tmp & 0xff) << " " << Base::HEX2 << int(tmp >> 8);
} else { }
else {
nextLine << "$" << Base::HEX4 << ad << ",x"; nextLine << "$" << Base::HEX4 << ad << ",x";
nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8); nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8);
} }
} else { }
else {
LABEL_A12_LOW(ad); LABEL_A12_LOW(ad);
nextLine << ",x"; nextLine << ",x";
nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8); nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8);
@ -390,34 +428,38 @@ FIX_LAST:
case AddressingMode::ABSOLUTE_Y: case AddressingMode::ABSOLUTE_Y:
{ {
ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2; ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2;
labelFound = mark(ad, CartDebug::REFERENCED); labelFound = mark(ad, Device::REFERENCED);
if (pass == 2 && !checkBit(ad & myAppData.end, CartDebug::CODE)) { if(pass == 2 && !checkBit(ad & myAppData.end, Device::CODE)) {
// Since we can't know what address is being accessed unless we also // Since we can't know what address is being accessed unless we also
// know the current Y value, this is marked as ROW instead of DATA // know the current Y value, this is marked as ROW instead of DATA
// The processing is left here, however, in case future versions of // The processing is left here, however, in case future versions of
// the code can somehow track access to CPU registers // the code can somehow track access to CPU registers
mark(ad, CartDebug::ROW); mark(ad, Device::ROW);
} else if (pass == 3) { }
else if(pass == 3) {
if(ad < 0x100 && mySettings.fFlag) if(ad < 0x100 && mySettings.fFlag)
nextLine << ".wy "; nextLine << ".wy ";
else else
nextLine << " "; nextLine << " ";
if (labelFound == 1) { if(labelFound == AddressType::ROM) {
LABEL_A12_HIGH(ad); LABEL_A12_HIGH(ad);
nextLine << ",y"; nextLine << ",y";
nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8); nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8);
} else if (labelFound == 4) { }
else if(labelFound == AddressType::ROM_MIRROR) {
if(mySettings.rFlag) { if(mySettings.rFlag) {
int tmp = (ad & myAppData.end) + myOffset; int tmp = (ad & myAppData.end) + myOffset;
LABEL_A12_HIGH(tmp); LABEL_A12_HIGH(tmp);
nextLine << ",y"; nextLine << ",y";
nextLineBytes << Base::HEX2 << int(tmp & 0xff) << " " << Base::HEX2 << int(tmp >> 8); nextLineBytes << Base::HEX2 << int(tmp & 0xff) << " " << Base::HEX2 << int(tmp >> 8);
} else { }
else {
nextLine << "$" << Base::HEX4 << ad << ",y"; nextLine << "$" << Base::HEX4 << ad << ",y";
nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8); nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8);
} }
} else { }
else {
LABEL_A12_LOW(ad); LABEL_A12_LOW(ad);
nextLine << ",y"; nextLine << ",y";
nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8); nextLineBytes << Base::HEX2 << int(ad & 0xff) << " " << Base::HEX2 << int(ad >> 8);
@ -455,7 +497,7 @@ FIX_LAST:
case AddressingMode::ZERO_PAGE_X: case AddressingMode::ZERO_PAGE_X:
{ {
d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC; d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC;
labelFound = mark(d1, CartDebug::REFERENCED); labelFound = mark(d1, Device::REFERENCED);
if(pass == 3) { if(pass == 3) {
nextLine << " "; nextLine << " ";
LABEL_A12_LOW(d1); LABEL_A12_LOW(d1);
@ -468,7 +510,7 @@ FIX_LAST:
case AddressingMode::ZERO_PAGE_Y: case AddressingMode::ZERO_PAGE_Y:
{ {
d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC; d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC;
labelFound = mark(d1, CartDebug::REFERENCED); labelFound = mark(d1, Device::REFERENCED);
if(pass == 3) { if(pass == 3) {
nextLine << " "; nextLine << " ";
LABEL_A12_LOW(d1); LABEL_A12_LOW(d1);
@ -486,12 +528,13 @@ FIX_LAST:
d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC; d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC;
ad = ((myPC + Int8(d1)) & 0xfff) + myOffset; ad = ((myPC + Int8(d1)) & 0xfff) + myOffset;
labelFound = mark(ad, CartDebug::REFERENCED); labelFound = mark(ad, Device::REFERENCED);
if(pass == 3) { if(pass == 3) {
if (labelFound == 1) { if(labelFound == AddressType::ROM) {
nextLine << " "; nextLine << " ";
LABEL_A12_HIGH(ad); LABEL_A12_HIGH(ad);
} else }
else
nextLine << " $" << Base::HEX4 << ad; nextLine << " $" << Base::HEX4 << ad;
nextLineBytes << Base::HEX2 << int(d1); nextLineBytes << Base::HEX2 << int(d1);
@ -502,25 +545,36 @@ FIX_LAST:
case AddressingMode::ABS_INDIRECT: case AddressingMode::ABS_INDIRECT:
{ {
ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2; ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2;
labelFound = mark(ad, CartDebug::REFERENCED); labelFound = mark(ad, Device::REFERENCED);
if (pass == 2 && !checkBit(ad & myAppData.end, CartDebug::CODE)) { if(pass == 2 && !checkBit(ad & myAppData.end, Device::CODE)) {
// Since we can't know what address is being accessed unless we also // Since we can't know what address is being accessed unless we also
// know the current X value, this is marked as ROW instead of DATA // know the current X value, this is marked as ROW instead of DATA
// The processing is left here, however, in case future versions of // The processing is left here, however, in case future versions of
// the code can somehow track access to CPU registers // the code can somehow track access to CPU registers
mark(ad, CartDebug::ROW); mark(ad, Device::ROW);
} else if (pass == 3) { }
else if(pass == 3) {
if(ad < 0x100 && mySettings.fFlag) if(ad < 0x100 && mySettings.fFlag)
nextLine << ".ind "; nextLine << ".ind ";
else else
nextLine << " "; nextLine << " ";
} }
if (labelFound == 1) { if(labelFound == AddressType::ROM) {
nextLine << "("; nextLine << "(";
LABEL_A12_HIGH(ad); LABEL_A12_HIGH(ad);
nextLine << ")"; nextLine << ")";
} }
// TODO - should we consider case 4?? else if(labelFound == AddressType::ROM_MIRROR) {
nextLine << "(";
if(mySettings.rFlag) {
int tmp = (ad & myAppData.end) + myOffset;
LABEL_A12_HIGH(tmp);
}
else {
LABEL_A12_LOW(ad);
}
nextLine << ")";
}
else { else {
nextLine << "("; nextLine << "(";
LABEL_A12_LOW(ad); LABEL_A12_LOW(ad);
@ -542,28 +596,29 @@ FIX_LAST:
<< ";" << std::dec << int(ourLookup[opcode].cycles) << ";" << std::dec << int(ourLookup[opcode].cycles)
<< (addrMode == AddressingMode::RELATIVE ? (ad & 0xf00) != ((myPC + myOffset) & 0xf00) ? "/3!" : "/3 " : " "); << (addrMode == AddressingMode::RELATIVE ? (ad & 0xf00) != ((myPC + myOffset) & 0xf00) ? "/3!" : "/3 " : " ");
if((opcode == 0x40 || opcode == 0x60 || opcode == 0x4c || opcode == 0x00 // code block end if((opcode == 0x40 || opcode == 0x60 || opcode == 0x4c || opcode == 0x00 // code block end
|| checkBit(myPC, CartDebug::REFERENCED) // referenced address || checkBit(myPC, Device::REFERENCED) // referenced address
|| (ourLookup[opcode].rw_mode == RWMode::WRITE && d1 == WSYNC)) // strobe WSYNC || (ourLookup[opcode].rw_mode == RWMode::WRITE && d1 == WSYNC)) // strobe WSYNC
&& cycles > 0) { && cycles > 0) {
// output cycles for previous code block // output cycles for previous code block
myDisasmBuf << "'= " << std::setw(3) << std::setfill(' ') << std::dec << cycles; myDisasmBuf << "'= " << std::setw(3) << std::setfill(' ') << std::dec << cycles;
cycles = 0; cycles = 0;
} else { }
else {
myDisasmBuf << "' "; myDisasmBuf << "' ";
} }
myDisasmBuf << "'" << nextLineBytes.str(); myDisasmBuf << "'" << nextLineBytes.str();
addEntry(CartDebug::CODE); addEntry(Device::CODE);
if(opcode == 0x40 || opcode == 0x60 || opcode == 0x4c || opcode == 0x00) { if(opcode == 0x40 || opcode == 0x60 || opcode == 0x4c || opcode == 0x00) {
myDisasmBuf << " ' ' "; myDisasmBuf << " ' ' ";
addEntry(CartDebug::NONE); addEntry(Device::NONE);
mySegType = CartDebug::NONE; // prevent extra lines if data follows mySegType = Device::NONE; // prevent extra lines if data follows
} }
nextLine.str(""); nextLine.str("");
nextLineBytes.str(""); nextLineBytes.str("");
} }
} } // CODE
} /* while loop */ } /* while loop */
/* Just in case we are disassembling outside of the address range, force the myPCEnd to EOF */ /* Just in case we are disassembling outside of the address range, force the myPCEnd to EOF */
@ -602,20 +657,21 @@ void DiStella::disasmPass1(CartDebug::AddressList& debuggerAddresses)
// Note that this is a 'best-effort' approach, since // Note that this is a 'best-effort' approach, since
// Distella will normally keep going until the end of the // Distella will normally keep going until the end of the
// range or branch is encountered // range or branch is encountered
// However, addresses *specifically* marked as DATA/GFX/PGFX // However, addresses *specifically* marked as DATA/GFX/PGFX/COL/PCOL/BCOL/AUD
// in the emulation core indicate that the CODE range has finished // in the emulation core indicate that the CODE range has finished
// Therefore, we stop at the first such address encountered // Therefore, we stop at the first such address encountered
for (uInt32 k = pcBeg; k <= myPCEnd; ++k) { for (uInt32 k = pcBeg; k <= myPCEnd; ++k) {
if (checkBits(k, CartDebug::CartDebug::DATA | CartDebug::GFX | CartDebug::PGFX, if (checkBits(k, Device::Device::DATA | Device::GFX | Device::PGFX |
CartDebug::CODE)) { Device::COL | Device::PCOL | Device::BCOL | Device::AUD,
Device::CODE)) {
//if (Debugger::debugger().getAccessFlags(k) & //if (Debugger::debugger().getAccessFlags(k) &
// (CartDebug::DATA | CartDebug::GFX | CartDebug::PGFX)) { // (Device::DATA | Device::GFX | Device::PGFX)) {
// TODO: this should never happen, remove when we are sure // TODO: this should never happen, remove when we are sure
// TODO: NOT USED: uInt8 flags = Debugger::debugger().getAccessFlags(k); // TODO: NOT USED: uInt16 flags = Debugger::debugger().getAccessFlags(k);
myPCEnd = k - 1; myPCEnd = k - 1;
break; break;
} }
mark(k, CartDebug::CODE); mark(k, Device::CODE);
} }
} }
@ -637,7 +693,7 @@ void DiStella::disasmPass1(CartDebug::AddressList& debuggerAddresses)
while (myAddressQueue.empty() && it != debuggerAddresses.end()) { while (myAddressQueue.empty() && it != debuggerAddresses.end()) {
uInt16 addr = *it; uInt16 addr = *it;
if (!checkBit(addr - myOffset, CartDebug::CODE)) { if (!checkBit(addr - myOffset, Device::CODE)) {
myAddressQueue.push(addr); myAddressQueue.push(addr);
++it; ++it;
} else // remove this address, it is redundant } else // remove this address, it is redundant
@ -647,8 +703,8 @@ void DiStella::disasmPass1(CartDebug::AddressList& debuggerAddresses)
// Stella itself can provide hints on whether an address has ever // Stella itself can provide hints on whether an address has ever
// been referenced as CODE // been referenced as CODE
while (myAddressQueue.empty() && codeAccessPoint <= myAppData.end) { while (myAddressQueue.empty() && codeAccessPoint <= myAppData.end) {
if ((Debugger::debugger().getAccessFlags(codeAccessPoint + myOffset) & CartDebug::CODE) if ((Debugger::debugger().getAccessFlags(codeAccessPoint + myOffset) & Device::CODE)
&& !(myLabels[codeAccessPoint & myAppData.end] & CartDebug::CODE)) { && !(myLabels[codeAccessPoint & myAppData.end] & Device::CODE)) {
myAddressQueue.push(codeAccessPoint + myOffset); myAddressQueue.push(codeAccessPoint + myOffset);
++codeAccessPoint; ++codeAccessPoint;
break; break;
@ -660,16 +716,17 @@ void DiStella::disasmPass1(CartDebug::AddressList& debuggerAddresses)
for (int k = 0; k <= myAppData.end; k++) { for (int k = 0; k <= myAppData.end; k++) {
// Let the emulation core know about tentative code // Let the emulation core know about tentative code
if (checkBit(k, CartDebug::CODE) && if (checkBit(k, Device::CODE) &&
!(Debugger::debugger().getAccessFlags(k + myOffset) & CartDebug::CODE) !(Debugger::debugger().getAccessFlags(k + myOffset) & Device::CODE)
&& myOffset != 0) { && myOffset != 0) {
Debugger::debugger().setAccessFlags(k + myOffset, CartDebug::TCODE); Debugger::debugger().setAccessFlags(k + myOffset, Device::TCODE);
} }
// Must be ROW / unused bytes // Must be ROW / unused bytes
if (!checkBit(k, CartDebug::CODE | CartDebug::GFX | if (!checkBit(k, Device::CODE | Device::GFX | Device::PGFX |
CartDebug::PGFX | CartDebug::DATA)) Device::COL | Device::PCOL | Device::BCOL | Device::AUD |
mark(k + myOffset, CartDebug::ROW); Device::DATA))
mark(k + myOffset, Device::ROW);
} }
} }
@ -685,7 +742,9 @@ void DiStella::disasmFromAddress(uInt32 distart)
while (myPC <= myAppData.end) { while (myPC <= myAppData.end) {
// abort when we reach non-code areas // abort when we reach non-code areas
if (checkBits(myPC, CartDebug::CartDebug::DATA | CartDebug::GFX | CartDebug::PGFX, CartDebug::CODE)) { if (checkBits(myPC, Device::Device::DATA | Device::GFX | Device::PGFX |
Device::COL | Device::PCOL | Device::BCOL | Device::AUD,
Device::CODE)) {
myPCEnd = (myPC - 1) + myOffset; myPCEnd = (myPC - 1) + myOffset;
return; return;
} }
@ -729,22 +788,22 @@ void DiStella::disasmFromAddress(uInt32 distart)
switch (addrMode) { switch (addrMode) {
case AddressingMode::ABSOLUTE: case AddressingMode::ABSOLUTE:
ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2; ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2;
mark(ad, CartDebug::REFERENCED); mark(ad, Device::REFERENCED);
// handle JMP/JSR // handle JMP/JSR
if (ourLookup[opcode].source == AccessMode::ADDR) { if (ourLookup[opcode].source == AccessMode::ADDR) {
// do NOT use flags set by debugger, else known CODE will not analyzed statically. // do NOT use flags set by debugger, else known CODE will not analyzed statically.
if (!checkBit(ad & myAppData.end, CartDebug::CODE, false)) { if (!checkBit(ad & myAppData.end, Device::CODE, false)) {
if (ad > 0xfff) if (ad > 0xfff)
myAddressQueue.push((ad & myAppData.end) + myOffset); myAddressQueue.push((ad & myAppData.end) + myOffset);
mark(ad, CartDebug::CODE); mark(ad, Device::CODE);
} }
} else } else
mark(ad, CartDebug::DATA); mark(ad, Device::DATA);
break; break;
case AddressingMode::ZERO_PAGE: case AddressingMode::ZERO_PAGE:
d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC; d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC;
mark(d1, CartDebug::REFERENCED); mark(d1, Device::REFERENCED);
break; break;
case AddressingMode::IMMEDIATE: case AddressingMode::IMMEDIATE:
@ -753,12 +812,12 @@ void DiStella::disasmFromAddress(uInt32 distart)
case AddressingMode::ABSOLUTE_X: case AddressingMode::ABSOLUTE_X:
ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2; ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2;
mark(ad, CartDebug::REFERENCED); mark(ad, Device::REFERENCED);
break; break;
case AddressingMode::ABSOLUTE_Y: case AddressingMode::ABSOLUTE_Y:
ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2; ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2;
mark(ad, CartDebug::REFERENCED); mark(ad, Device::REFERENCED);
break; break;
case AddressingMode::INDIRECT_X: case AddressingMode::INDIRECT_X:
@ -771,12 +830,12 @@ void DiStella::disasmFromAddress(uInt32 distart)
case AddressingMode::ZERO_PAGE_X: case AddressingMode::ZERO_PAGE_X:
d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC; d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC;
mark(d1, CartDebug::REFERENCED); mark(d1, Device::REFERENCED);
break; break;
case AddressingMode::ZERO_PAGE_Y: case AddressingMode::ZERO_PAGE_Y:
d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC; d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC;
mark(d1, CartDebug::REFERENCED); mark(d1, Device::REFERENCED);
break; break;
case AddressingMode::RELATIVE: case AddressingMode::RELATIVE:
@ -785,17 +844,17 @@ void DiStella::disasmFromAddress(uInt32 distart)
// indexing into the labels array caused a crash // indexing into the labels array caused a crash
d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC; d1 = Debugger::debugger().peek(myPC + myOffset); ++myPC;
ad = ((myPC + Int8(d1)) & 0xfff) + myOffset; ad = ((myPC + Int8(d1)) & 0xfff) + myOffset;
mark(ad, CartDebug::REFERENCED); mark(ad, Device::REFERENCED);
// do NOT use flags set by debugger, else known CODE will not analyzed statically. // do NOT use flags set by debugger, else known CODE will not analyzed statically.
if (!checkBit(ad - myOffset, CartDebug::CODE, false)) { if (!checkBit(ad - myOffset, Device::CODE, false)) {
myAddressQueue.push(ad); myAddressQueue.push(ad);
mark(ad, CartDebug::CODE); mark(ad, Device::CODE);
} }
break; break;
case AddressingMode::ABS_INDIRECT: case AddressingMode::ABS_INDIRECT:
ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2; ad = Debugger::debugger().dpeek(myPC + myOffset); myPC += 2;
mark(ad, CartDebug::REFERENCED); mark(ad, Device::REFERENCED);
break; break;
default: default:
@ -804,10 +863,10 @@ void DiStella::disasmFromAddress(uInt32 distart)
// mark BRK vector // mark BRK vector
if (opcode == 0x00) { if (opcode == 0x00) {
ad = Debugger::debugger().dpeek(0xfffe, CartDebug::DATA); ad = Debugger::debugger().dpeek(0xfffe, Device::DATA);
if (!myReserved.breakFound) { if (!myReserved.breakFound) {
myAddressQueue.push(ad); myAddressQueue.push(ad);
mark(ad, CartDebug::CODE); mark(ad, Device::CODE);
myReserved.breakFound = true; myReserved.breakFound = true;
} }
} }
@ -823,7 +882,7 @@ void DiStella::disasmFromAddress(uInt32 distart)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int DiStella::mark(uInt32 address, uInt8 mask, bool directive) DiStella::AddressType DiStella::mark(uInt32 address, uInt16 mask, bool directive)
{ {
/*----------------------------------------------------------------------- /*-----------------------------------------------------------------------
For any given offset and code range... For any given offset and code range...
@ -831,44 +890,44 @@ int DiStella::mark(uInt32 address, uInt8 mask, bool directive)
If we're between the offset and the end of the code range, we mark If we're between the offset and the end of the code range, we mark
the bit in the labels array for that data. The labels array is an the bit in the labels array for that data. The labels array is an
array of label info for each code address. If this is the case, array of label info for each code address. If this is the case,
return "1", else... return "ROM", else...
We sweep for hardware/system equates, which are valid addresses, We sweep for hardware/system equates, which are valid addresses,
outside the scope of the code/data range. For these, we mark its outside the scope of the code/data range. For these, we mark its
corresponding hardware/system array element, and return "2" or "3" corresponding hardware/system array element, and return "TIA" or "RIOT"
(depending on which system/hardware element was accessed). (depending on which system/hardware element was accessed).
If this was not the case... If this was not the case...
Next we check if it is a code "mirror". For the 2600, address ranges Next we check if it is a code "mirror". For the 2600, address ranges
are limited with 13 bits, so other addresses can exist outside of the are limited with 13 bits, so other addresses can exist outside of the
standard code/data range. For these, we mark the element in the "labels" standard code/data range. For these, we mark the element in the "labels"
array that corresponds to the mirrored address, and return "4" array that corresponds to the mirrored address, and return "ROM_MIRROR"
If all else fails, it's not a valid address, so return 0. If all else fails, it's not a valid address, so return INVALID.
A quick example breakdown for a 2600 4K cart: A quick example breakdown for a 2600 4K cart:
=========================================================== ===========================================================
$00-$3d = system equates (WSYNC, etc...); return 2. $00-$3d = system equates (WSYNC, etc...); return TIA.
$80-$ff = zero-page RAM (ram_80, etc...); return 5. $80-$ff = zero-page RAM (ram_80, etc...); return ZP_RAM.
$0280-$0297 = system equates (INPT0, etc...); mark the array's element $0280-$0297 = system equates (INPT0, etc...); mark the array's element
with the appropriate bit; return 3. with the appropriate bit; return RIOT.
$1000-$1FFF = mark the code/data array for the mirrored address $1000-$1FFF = mark the code/data array for the mirrored address
with the appropriate bit; return 4. with the appropriate bit; return ROM_MIRROR.
$3000-$3FFF = mark the code/data array for the mirrored address $3000-$3FFF = mark the code/data array for the mirrored address
with the appropriate bit; return 4. with the appropriate bit; return ROM_MIRROR.
$5000-$5FFF = mark the code/data array for the mirrored address $5000-$5FFF = mark the code/data array for the mirrored address
with the appropriate bit; return 4. with the appropriate bit; return ROM_MIRROR.
$7000-$7FFF = mark the code/data array for the mirrored address $7000-$7FFF = mark the code/data array for the mirrored address
with the appropriate bit; return 4. with the appropriate bit; return ROM_MIRROR.
$9000-$9FFF = mark the code/data array for the mirrored address $9000-$9FFF = mark the code/data array for the mirrored address
with the appropriate bit; return 4. with the appropriate bit; return ROM_MIRROR.
$B000-$BFFF = mark the code/data array for the mirrored address $B000-$BFFF = mark the code/data array for the mirrored address
with the appropriate bit; return 4. with the appropriate bit; return ROM_MIRROR.
$D000-$DFFF = mark the code/data array for the mirrored address $D000-$DFFF = mark the code/data array for the mirrored address
with the appropriate bit; return 4. with the appropriate bit; return ROM_MIRROR.
$F000-$FFFF = mark the code/data array for the address $F000-$FFFF = mark the code/data array for the address
with the appropriate bit; return 1. with the appropriate bit; return ROM.
Anything else = invalid, return 0. Anything else = invalid, return INVALID.
=========================================================== ===========================================================
-----------------------------------------------------------------------*/ -----------------------------------------------------------------------*/
@ -876,36 +935,43 @@ int DiStella::mark(uInt32 address, uInt8 mask, bool directive)
// of Distella assumed either equates or ROM; it didn't take ZP-RAM into account // of Distella assumed either equates or ROM; it didn't take ZP-RAM into account
CartDebug::AddrType type = myDbg.addressType(address); CartDebug::AddrType type = myDbg.addressType(address);
if(type == CartDebug::AddrType::TIA) { if(type == CartDebug::AddrType::TIA) {
return 2; return AddressType::TIA;
} else if (type == CartDebug::AddrType::IO) { }
return 3; else if(type == CartDebug::AddrType::IO) {
} else if (type == CartDebug::AddrType::ZPRAM && myOffset != 0) { return AddressType::RIOT;
return 5; }
} else if (address >= uInt32(myOffset) && address <= uInt32(myAppData.end + myOffset)) { else if(type == CartDebug::AddrType::ZPRAM && myOffset != 0) {
return AddressType::ZP_RAM;
}
else if(address >= uInt32(myOffset) && address <= uInt32(myAppData.end + myOffset)) {
myLabels[address - myOffset] = myLabels[address - myOffset] | mask; myLabels[address - myOffset] = myLabels[address - myOffset] | mask;
if (directive) myDirectives[address - myOffset] = mask; if(directive)
return 1; myDirectives[address - myOffset] = mask;
} else if (address > 0x1000 && myOffset != 0) // Exclude zero-page accesses return AddressType::ROM;
}
else if(address > 0x1000 && myOffset != 0) // Exclude zero-page accesses
{ {
/* 2K & 4K case */ /* 2K & 4K case */
myLabels[address & myAppData.end] = myLabels[address & myAppData.end] | mask; myLabels[address & myAppData.end] = myLabels[address & myAppData.end] | mask;
if (directive) myDirectives[address & myAppData.end] = mask; if(directive)
return 4; myDirectives[address & myAppData.end] = mask;
} else return AddressType::ROM_MIRROR;
return 0; }
else
return AddressType::INVALID;
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool DiStella::checkBit(uInt16 address, uInt8 mask, bool useDebugger) const bool DiStella::checkBit(uInt16 address, uInt16 mask, bool useDebugger) const
{ {
// The REFERENCED and VALID_ENTRY flags are needed for any inspection of // The REFERENCED and VALID_ENTRY flags are needed for any inspection of
// an address // an address
// Since they're set only in the labels array (as the lower two bits), // Since they're set only in the labels array (as the lower two bits),
// they must be included in the other bitfields // they must be included in the other bitfields
uInt8 label = myLabels[address & myAppData.end], uInt16 label = myLabels[address & myAppData.end],
lastbits = label & 0x03, lastbits = label & (Device::REFERENCED | Device::VALID_ENTRY),
directive = myDirectives[address & myAppData.end] & 0xFC, directive = myDirectives[address & myAppData.end] & ~(Device::REFERENCED | Device::VALID_ENTRY),
debugger = Debugger::debugger().getAccessFlags(address | myOffset) & 0xFC; debugger = Debugger::debugger().getAccessFlags(address | myOffset) & ~(Device::REFERENCED | Device::VALID_ENTRY);
// Any address marked by a manual directive always takes priority // Any address marked by a manual directive always takes priority
if (directive) if (directive)
@ -918,7 +984,8 @@ bool DiStella::checkBit(uInt16 address, uInt8 mask, bool useDebugger) const
return label & mask; return label & mask;
} }
bool DiStella::checkBits(uInt16 address, uInt8 mask, uInt8 notMask, bool useDebugger) const // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool DiStella::checkBits(uInt16 address, uInt16 mask, uInt16 notMask, bool useDebugger) const
{ {
return checkBit(address, mask, useDebugger) && !checkBit(address, notMask, useDebugger); return checkBit(address, mask, useDebugger) && !checkBit(address, notMask, useDebugger);
} }
@ -943,7 +1010,7 @@ bool DiStella::check_range(uInt16 beg, uInt16 end) const
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DiStella::addEntry(CartDebug::DisasmType type) void DiStella::addEntry(Device::AccessType type)
{ {
CartDebug::DisassemblyTag tag; CartDebug::DisassemblyTag tag;
@ -969,7 +1036,7 @@ void DiStella::addEntry(CartDebug::DisasmType type)
if (tag.label == EmptyString) { if (tag.label == EmptyString) {
if (myDisasmBuf.peek() != ' ') if (myDisasmBuf.peek() != ' ')
getline(myDisasmBuf, tag.label, '\''); getline(myDisasmBuf, tag.label, '\'');
else if (mySettings.showAddresses && tag.type == CartDebug::CODE) { else if (mySettings.showAddresses && tag.type == Device::CODE) {
// Have addresses indented, to differentiate from actual labels // Have addresses indented, to differentiate from actual labels
tag.label = " " + Base::toString(tag.address, Base::Fmt::_16_4); tag.label = " " + Base::toString(tag.address, Base::Fmt::_16_4);
tag.hllabel = false; tag.hllabel = false;
@ -982,7 +1049,7 @@ void DiStella::addEntry(CartDebug::DisasmType type)
// variable length labels, cycle counts, etc // variable length labels, cycle counts, etc
myDisasmBuf.seekg(11, std::ios::beg); myDisasmBuf.seekg(11, std::ios::beg);
switch (tag.type) { switch (tag.type) {
case CartDebug::CODE: case Device::CODE:
getline(myDisasmBuf, tag.disasm, '\''); getline(myDisasmBuf, tag.disasm, '\'');
getline(myDisasmBuf, tag.ccount, '\''); getline(myDisasmBuf, tag.ccount, '\'');
getline(myDisasmBuf, tag.ctotal, '\''); getline(myDisasmBuf, tag.ctotal, '\'');
@ -993,25 +1060,29 @@ void DiStella::addEntry(CartDebug::DisasmType type)
// but it could also indicate that code will *never* be accessed // but it could also indicate that code will *never* be accessed
// Since it is impossible to tell the difference, marking the address // Since it is impossible to tell the difference, marking the address
// in the disassembly at least tells the user about it // in the disassembly at least tells the user about it
if (!(Debugger::debugger().getAccessFlags(tag.address) & CartDebug::CODE) if (!(Debugger::debugger().getAccessFlags(tag.address) & Device::CODE)
&& myOffset != 0) { && myOffset != 0) {
tag.ccount += " *"; tag.ccount += " *";
Debugger::debugger().setAccessFlags(tag.address, CartDebug::TCODE); Debugger::debugger().setAccessFlags(tag.address, Device::TCODE);
} }
break; break;
case CartDebug::GFX:
case CartDebug::PGFX: case Device::GFX:
case Device::PGFX:
case Device::COL:
case Device::PCOL:
case Device::BCOL:
case Device::DATA:
case Device::AUD:
getline(myDisasmBuf, tag.disasm, '\''); getline(myDisasmBuf, tag.disasm, '\'');
getline(myDisasmBuf, tag.bytes); getline(myDisasmBuf, tag.bytes);
break; break;
case CartDebug::DATA:
getline(myDisasmBuf, tag.disasm, '\''); case Device::ROW:
getline(myDisasmBuf, tag.bytes);
break;
case CartDebug::ROW:
getline(myDisasmBuf, tag.disasm); getline(myDisasmBuf, tag.disasm);
break; break;
case CartDebug::NONE:
case Device::NONE:
default: // should never happen default: // should never happen
tag.disasm = " "; tag.disasm = " ";
break; break;
@ -1026,18 +1097,18 @@ DONE_WITH_ADD:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DiStella::outputGraphics() void DiStella::outputGraphics()
{ {
bool isPGfx = checkBit(myPC, CartDebug::PGFX); bool isPGfx = checkBit(myPC, Device::PGFX);
const string& bitString = isPGfx ? "\x1f" : "\x1e"; const string& bitString = isPGfx ? "\x1f" : "\x1e";
uInt8 byte = Debugger::debugger().peek(myPC + myOffset); uInt8 byte = Debugger::debugger().peek(myPC + myOffset);
// add extra spacing line when switching from non-graphics to graphics // add extra spacing line when switching from non-graphics to graphics
if (mySegType != CartDebug::GFX && mySegType != CartDebug::NONE) { if (mySegType != Device::GFX && mySegType != Device::NONE) {
myDisasmBuf << " ' ' "; myDisasmBuf << " ' ' ";
addEntry(CartDebug::NONE); addEntry(Device::NONE);
} }
mySegType = CartDebug::GFX; mySegType = Device::GFX;
if (checkBit(myPC, CartDebug::REFERENCED)) if (checkBit(myPC, Device::REFERENCED))
myDisasmBuf << Base::HEX4 << myPC + myOffset << "'L" << Base::HEX4 << myPC + myOffset << "'"; myDisasmBuf << Base::HEX4 << myPC + myOffset << "'L" << Base::HEX4 << myPC + myOffset << "'";
else else
myDisasmBuf << Base::HEX4 << myPC + myOffset << "' '"; myDisasmBuf << Base::HEX4 << myPC + myOffset << "' '";
@ -1050,23 +1121,91 @@ void DiStella::outputGraphics()
else else
myDisasmBuf << Base::HEX2 << int(byte); myDisasmBuf << Base::HEX2 << int(byte);
addEntry(isPGfx ? CartDebug::PGFX : CartDebug::GFX); addEntry(isPGfx ? Device::PGFX : Device::GFX);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DiStella::outputBytes(CartDebug::DisasmType type) void DiStella::outputColors()
{
string NTSC_COLOR[16] = {
"BLACK", "YELLOW", "BROWN", "ORANGE",
"RED", "MAUVE", "VIOLET", "PURPLE",
"BLUE", "BLUE_CYAN", "CYAN", "CYAN_GREEN",
"GREEN", "GREEN_YELLOW", "GREEN_BEIGE", "BEIGE"
};
string PAL_COLOR[16] = {
"BLACK0", "BLACK1", "YELLOW", "GREEN_YELLOW",
"ORANGE", "GREEN", "RED", "CYAN_GREEN",
"MAUVE", "CYAN", "VIOLET", "BLUE_CYAN",
"PURPLE", "BLUE", "BLACKE", "BLACKF"
};
string SECAM_COLOR[8] = {
"BLACK", "BLUE", "RED", "PURPLE",
"GREEN", "CYAN", "YELLOW", "WHITE"
};
uInt8 byte = Debugger::debugger().peek(myPC + myOffset);
// add extra spacing line when switching from non-colors to colors
if(mySegType != Device::COL && mySegType != Device::NONE)
{
myDisasmBuf << " ' ' ";
addEntry(Device::NONE);
}
mySegType = Device::COL;
// output label/address
if(checkBit(myPC, Device::REFERENCED))
myDisasmBuf << Base::HEX4 << myPC + myOffset << "'L" << Base::HEX4 << myPC + myOffset << "'";
else
myDisasmBuf << Base::HEX4 << myPC + myOffset << "' '";
// output color
string color;
myDisasmBuf << ".byte ";
if(myDbg.myConsole.timing() == ConsoleTiming::ntsc)
{
color = NTSC_COLOR[byte >> 4];
myDisasmBuf << color << "|$" << Base::HEX1 << (byte & 0xf);
}
else if(myDbg.myConsole.timing() == ConsoleTiming::pal)
{
color = PAL_COLOR[byte >> 4];
myDisasmBuf << color << "|$" << Base::HEX1 << (byte & 0xf);
}
else
{
color = SECAM_COLOR[(byte >> 1) & 0x7];
myDisasmBuf << "$" << Base::HEX1 << (byte >> 4) << "|" << color;
}
myDisasmBuf << std::setw(int(16 - color.length())) << std::setfill(' ');
// output address
myDisasmBuf << "; $" << Base::HEX4 << myPC + myOffset << " "
<< (checkBit(myPC, Device::COL) ? "(Px)" : checkBit(myPC, Device::PCOL) ? "(PF)" : "(BK)");
// output color value
myDisasmBuf << "'" << Base::HEX2 << int(byte);
addEntry(checkBit(myPC, Device::COL) ? Device::COL :
checkBit(myPC, Device::PCOL) ? Device::PCOL : Device::BCOL);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void DiStella::outputBytes(Device::AccessType type)
{ {
bool isType = true; bool isType = true;
bool referenced = checkBit(myPC, CartDebug::REFERENCED); bool referenced = checkBit(myPC, Device::REFERENCED);
bool lineEmpty = true; bool lineEmpty = true;
int numBytes = 0; int numBytes = 0;
// add extra spacing line when switching from non-data to data // add extra spacing line when switching from non-data to data
if (mySegType != CartDebug::DATA && mySegType != CartDebug::NONE) { if (mySegType != Device::DATA && mySegType != Device::NONE) {
myDisasmBuf << " ' ' "; myDisasmBuf << " ' ' ";
addEntry(CartDebug::NONE); addEntry(Device::NONE);
} }
mySegType = CartDebug::DATA; mySegType = Device::DATA;
while (isType && myPC <= myAppData.end) { while (isType && myPC <= myAppData.end) {
if (referenced) { if (referenced) {
@ -1097,13 +1236,15 @@ void DiStella::outputBytes(CartDebug::DisasmType type)
++myPC; ++myPC;
} }
isType = checkBits(myPC, type, isType = checkBits(myPC, type,
CartDebug::CODE | (type != CartDebug::DATA ? CartDebug::DATA : 0) | CartDebug::GFX | CartDebug::PGFX); Device::CODE | (type != Device::DATA ? Device::DATA : 0) |
referenced = checkBit(myPC, CartDebug::REFERENCED); Device::GFX | Device::PGFX |
Device::COL | Device::PCOL | Device::BCOL | Device::AUD);
referenced = checkBit(myPC, Device::REFERENCED);
} }
if (!lineEmpty) if (!lineEmpty)
addEntry(type); addEntry(type);
/*myDisasmBuf << " ' ' "; /*myDisasmBuf << " ' ' ";
addEntry(CartDebug::NONE);*/ addEntry(Device::NONE);*/
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

View File

@ -22,6 +22,7 @@
#include "Base.hxx" #include "Base.hxx"
#include "CartDebug.hxx" #include "CartDebug.hxx"
#include "Device.hxx"
#include "bspf.hxx" #include "bspf.hxx"
/** /**
@ -71,11 +72,26 @@ class DiStella
CartDebug::AddrTypeArray& directives, CartDebug::AddrTypeArray& directives,
CartDebug::ReservedEquates& reserved); CartDebug::ReservedEquates& reserved);
private:
/**
Enumeration of the addressing type (RAM, ROM, RIOT, TIA...)
*/
enum class AddressType : int
{
INVALID,
ROM,
TIA,
RIOT,
ROM_MIRROR,
ZP_RAM
};
private: private:
// Indicate that a new line of disassembly has been completed // Indicate that a new line of disassembly has been completed
// In the original Distella code, this indicated a new line to be printed // In the original Distella code, this indicated a new line to be printed
// Here, we add a new entry to the DisassemblyList // Here, we add a new entry to the DisassemblyList
void addEntry(CartDebug::DisasmType type); void addEntry(Device::AccessType type);
// Process directives given in the list // Process directives given in the list
// Directives are basically the contents of a distella configuration file // Directives are basically the contents of a distella configuration file
@ -87,32 +103,32 @@ class DiStella
void disasmFromAddress(uInt32 distart); void disasmFromAddress(uInt32 distart);
bool check_range(uInt16 start, uInt16 end) const; bool check_range(uInt16 start, uInt16 end) const;
int mark(uInt32 address, uInt8 mask, bool directive = false); AddressType mark(uInt32 address, uInt16 mask, bool directive = false);
bool checkBit(uInt16 address, uInt8 mask, bool useDebugger = true) const; bool checkBit(uInt16 address, uInt16 mask, bool useDebugger = true) const;
bool checkBits(uInt16 address, uInt16 mask, uInt16 notMask, bool useDebugger = true) const;
bool checkBits(uInt16 address, uInt8 mask, uInt8 notMask, bool useDebugger = true) const;
void outputGraphics(); void outputGraphics();
void outputBytes(CartDebug::DisasmType type); void outputColors();
void outputBytes(Device::AccessType type);
// Convenience methods to generate appropriate labels // Convenience methods to generate appropriate labels
inline void labelA12High(stringstream& buf, uInt8 op, uInt16 addr, int labfound) inline void labelA12High(stringstream& buf, uInt8 op, uInt16 addr, AddressType labfound)
{ {
if(!myDbg.getLabel(buf, addr, true)) if(!myDbg.getLabel(buf, addr, true))
buf << "L" << Common::Base::HEX4 << addr; buf << "L" << Common::Base::HEX4 << addr;
} }
inline void labelA12Low(stringstream& buf, uInt8 op, uInt16 addr, int labfound) inline void labelA12Low(stringstream& buf, uInt8 op, uInt16 addr, AddressType labfound)
{ {
myDbg.getLabel(buf, addr, ourLookup[op].rw_mode == RWMode::READ, 2); myDbg.getLabel(buf, addr, ourLookup[op].rw_mode == RWMode::READ, 2);
if (labfound == 2) if (labfound == AddressType::TIA)
{ {
if(ourLookup[op].rw_mode == RWMode::READ) if(ourLookup[op].rw_mode == RWMode::READ)
myReserved.TIARead[addr & 0x0F] = true; myReserved.TIARead[addr & 0x0F] = true;
else else
myReserved.TIAWrite[addr & 0x3F] = true; myReserved.TIAWrite[addr & 0x3F] = true;
} }
else if (labfound == 3) else if (labfound == AddressType::RIOT)
myReserved.IOReadWrite[(addr & 0xFF) - 0x80] = true; myReserved.IOReadWrite[(addr & 0xFF) - 0x80] = true;
else if (labfound == 5) else if (labfound == AddressType::ZP_RAM)
myReserved.ZPRAM[(addr & 0xFF) - 0x80] = true; myReserved.ZPRAM[(addr & 0xFF) - 0x80] = true;
} }

View File

@ -16,80 +16,25 @@
//============================================================================ //============================================================================
#include "Cart0840.hxx" #include "Cart0840.hxx"
#include "PopUpWidget.hxx"
#include "Cart0840Widget.hxx" #include "Cart0840Widget.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cartridge0840Widget::Cartridge0840Widget( Cartridge0840Widget::Cartridge0840Widget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont, GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
int x, int y, int w, int h, Cartridge0840& cart) int x, int y, int w, int h, Cartridge0840& cart)
: CartDebugWidget(boss, lfont, nfont, x, y, w, h), : CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart)
myCart(cart)
{ {
uInt16 size = 2 * 4096; myHotspotDelta = 0x40;
initialize();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge0840Widget::description()
{
ostringstream info; ostringstream info;
info << "0840 ECONObanking, two 4K banks\n"
<< "Startup bank = " << cart.startBank() << "\n";
// Eventually, we should query this from the debugger/disassembler info << "0840 ECONObanking, two 4K banks\n";
for(uInt32 i = 0, offset = 0xFFC, spot = 0x800; i < 2; info << CartridgeEnhancedWidget::description();
++i, offset += 0x1000, spot += 0x40)
{ return info.str();
uInt16 start = uInt16((cart.myImage[offset+1] << 8) | cart.myImage[offset]);
start -= start % 0x1000;
info << "Bank " << i << " @ $" << Common::Base::HEX4 << start << " - "
<< "$" << (start + 0xFFF) << " (hotspot = $" << spot << ")\n";
}
int xpos = 2,
ypos = addBaseInformation(size, "Fred X. Quimby", info.str()) + myLineHeight;
VariantList items;
VarList::push_back(items, "0 ($800)");
VarList::push_back(items, "1 ($840)");
myBank =
new PopUpWidget(boss, _font, xpos, ypos-2, _font.getStringWidth("0 ($800)"),
myLineHeight, items, "Set bank ",
0, kBankChanged);
myBank->setTarget(this);
addFocusWidget(myBank);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge0840Widget::loadConfig()
{
Debugger& dbg = instance().debugger();
CartDebug& cart = dbg.cartDebug();
const CartState& state = static_cast<const CartState&>(cart.getState());
const CartState& oldstate = static_cast<const CartState&>(cart.getOldState());
myBank->setSelectedIndex(myCart.getBank(), state.bank != oldstate.bank);
CartDebugWidget::loadConfig();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge0840Widget::handleCommand(CommandSender* sender,
int cmd, int data, int id)
{
if(cmd == kBankChanged)
{
myCart.unlockBank();
myCart.bank(myBank->getSelected());
myCart.lockBank();
invalidate();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge0840Widget::bankState()
{
ostringstream& buf = buffer();
static const std::array<string, 2> spot = { "$800", "$840" };
buf << "Bank = " << std::dec << myCart.getBank()
<< ", hotspot = " << spot[myCart.getBank()];
return buf.str();
} }

View File

@ -19,11 +19,10 @@
#define CARTRIDGE0840_WIDGET_HXX #define CARTRIDGE0840_WIDGET_HXX
class Cartridge0840; class Cartridge0840;
class PopUpWidget;
#include "CartDebugWidget.hxx" #include "CartEnhancedWidget.hxx"
class Cartridge0840Widget : public CartDebugWidget class Cartridge0840Widget : public CartridgeEnhancedWidget
{ {
public: public:
Cartridge0840Widget(GuiObject* boss, const GUI::Font& lfont, Cartridge0840Widget(GuiObject* boss, const GUI::Font& lfont,
@ -33,17 +32,11 @@ class Cartridge0840Widget : public CartDebugWidget
virtual ~Cartridge0840Widget() = default; virtual ~Cartridge0840Widget() = default;
private: private:
Cartridge0840& myCart; string manufacturer() override { return "Fred X. Quimby"; }
PopUpWidget* myBank{nullptr};
enum { kBankChanged = 'bkCH' }; string description() override;
private: private:
void loadConfig() override;
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
string bankState() override;
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
Cartridge0840Widget() = delete; Cartridge0840Widget() = delete;
Cartridge0840Widget(const Cartridge0840Widget&) = delete; Cartridge0840Widget(const Cartridge0840Widget&) = delete;

View File

@ -22,15 +22,18 @@
Cartridge2KWidget::Cartridge2KWidget( Cartridge2KWidget::Cartridge2KWidget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont, GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
int x, int y, int w, int h, Cartridge2K& cart) int x, int y, int w, int h, Cartridge2K& cart)
: CartDebugWidget(boss, lfont, nfont, x, y, w, h) : CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart)
{ {
// Eventually, we should query this from the debugger/disassembler initialize();
size_t size = cart.mySize; }
uInt16 start = (cart.myImage[size-3] << 8) | cart.myImage[size-4];
start -= start % size; // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge2KWidget::description()
ostringstream info; {
info << "Standard 2K cartridge, non-bankswitched\n" ostringstream info;
<< "Accessible @ $" << Common::Base::HEX4 << start << " - " << "$" << (start + size - 1);
addBaseInformation(size, "Atari", info.str()); info << "Standard 2K cartridge, non-bankswitched\n";
info << CartridgeEnhancedWidget::description();
return info.str();
} }

View File

@ -20,9 +20,9 @@
class Cartridge2K; class Cartridge2K;
#include "CartDebugWidget.hxx" #include "CartEnhancedWidget.hxx"
class Cartridge2KWidget : public CartDebugWidget class Cartridge2KWidget : public CartridgeEnhancedWidget
{ {
public: public:
Cartridge2KWidget(GuiObject* boss, const GUI::Font& lfont, Cartridge2KWidget(GuiObject* boss, const GUI::Font& lfont,
@ -32,10 +32,11 @@ class Cartridge2KWidget : public CartDebugWidget
virtual ~Cartridge2KWidget() = default; virtual ~Cartridge2KWidget() = default;
private: private:
// No implementation for non-bankswitched ROMs string manufacturer() override { return "Atari"; }
void loadConfig() override { }
void handleCommand(CommandSender* sender, int cmd, int data, int id) override { }
string description() override;
private:
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
Cartridge2KWidget() = delete; Cartridge2KWidget() = delete;
Cartridge2KWidget(const Cartridge2KWidget&) = delete; Cartridge2KWidget(const Cartridge2KWidget&) = delete;

View File

@ -18,320 +18,230 @@
#include "Cart3EPlus.hxx" #include "Cart3EPlus.hxx"
#include "EditTextWidget.hxx" #include "EditTextWidget.hxx"
#include "PopUpWidget.hxx" #include "PopUpWidget.hxx"
#include "Cart3EPlusWidget.hxx" #include "CartEnhancedWidget.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cartridge3EPlusWidget::Cartridge3EPlusWidget( Cartridge3EPlusWidget::Cartridge3EPlusWidget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont, GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
int x, int y, int w, int h, Cartridge3EPlus& cart) int x, int y, int w, int h, Cartridge3EPlus& cart)
: CartDebugWidget(boss, lfont, nfont, x, y, w, h), : CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart),
myCart(cart) myCart3EP(cart)
{ {
size_t size = cart.mySize; initialize();
ostringstream info;
info << "3EPlus cartridge - (64K ROM + RAM)\n"
<< " 4-64K ROM (1K banks), 32K RAM (512b banks)\n"
<< "Each 1K ROM selected by writing to $3F\n"
"Each 512b RAM selected by writing to $3E\n"
" Lower 512b of bank x (R)\n"
" Upper 512b of bank x (+$200) (W)\n"
<< "Startup bank = 0/-1/-1/0 (ROM)\n";
// Eventually, we should query this from the debugger/disassembler
//uInt16 start = (cart.myImage[size-3] << 8) | cart.myImage[size-4];
// Currently the cart starts at bank 0. If we change that, we have to change this too.
uInt16 start = (cart.myImage[0x400-3] << 8) | cart.myImage[0x400 - 4];
start -= start % 0x1000;
info << "Bank RORG" << " = $" << Common::Base::HEX4 << start << "\n";
int xpos = 2,
ypos = addBaseInformation(size, "T. Jentzsch", info.str()) +
myLineHeight;
VariantList bankno;
for(uInt32 i = 0; i < myCart.ROM_BANK_COUNT; ++i)
VarList::push_back(bankno, i, i);
VariantList banktype;
VarList::push_back(banktype, "ROM", "ROM");
VarList::push_back(banktype, "RAM", "RAM");
for(uInt32 i = 0; i < 4; ++i)
{
int xpos_s, ypos_s = ypos;
ostringstream label;
label << "Set segment " << i << " as ";
new StaticTextWidget(boss, _font, xpos, ypos, _font.getStringWidth(label.str()),
myFontHeight, label.str(), TextAlign::Left);
ypos += myLineHeight + 8;
xpos += 20;
myBankNumber[i] =
new PopUpWidget(boss, _font, xpos, ypos-2, _font.getStringWidth("Slot "),
myLineHeight, bankno, "Slot ",
6*_font.getMaxCharWidth());
addFocusWidget(myBankNumber[i]);
xpos += myBankNumber[i]->getWidth();
myBankType[i] =
new PopUpWidget(boss, _font, xpos, ypos-2, 5*_font.getMaxCharWidth(),
myLineHeight, banktype, " of ", _font.getStringWidth(" of "));
addFocusWidget(myBankType[i]);
xpos += myBankType[i]->getWidth() + 10;
myBankCommit[i] = new ButtonWidget(boss, _font, xpos, ypos-4,
_font.getStringWidth(" Commit "), myButtonHeight,
"Commit", bankEnum[i]);
myBankCommit[i]->setTarget(this);
addFocusWidget(myBankCommit[i]);
xpos_s = xpos + myBankCommit[i]->getWidth() + 20;
StaticTextWidget* t;
int addr1 = start + (i*0x400), addr2 = addr1 + 0x1FF;
label.str("");
label << Common::Base::HEX4 << addr1 << "-" << Common::Base::HEX4 << addr2;
t = new StaticTextWidget(boss, _font, xpos_s, ypos_s+2,
_font.getStringWidth(label.str()), myFontHeight, label.str(), TextAlign::Left);
int xoffset = xpos_s+t->getWidth() + 10;
myBankState[2*i] = new EditTextWidget(boss, _font, xoffset, ypos_s,
w - xoffset - 10, myLineHeight, "");
myBankState[2*i]->setEditable(false, true);
ypos_s += myLineHeight + 4;
label.str("");
label << Common::Base::HEX4 << (addr2 + 1) << "-" << Common::Base::HEX4 << (addr2 + 1 + 0x1FF);
new StaticTextWidget(boss, _font, xpos_s, ypos_s+2,
_font.getStringWidth(label.str()), myFontHeight, label.str(), TextAlign::Left);
myBankState[2*i+1] = new EditTextWidget(boss, _font, xoffset, ypos_s,
w - xoffset - 10, myLineHeight, "");
myBankState[2*i+1]->setEditable(false, true);
xpos = 10;
ypos+= 2 * myLineHeight;
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EPlusWidget::saveOldState() string Cartridge3EPlusWidget::description()
{ {
myOldState.internalram.clear(); ostringstream info;
size_t size;
const ByteBuffer& image = myCart.getImage(size);
uInt16 numRomBanks = myCart.romBankCount();
uInt16 numRamBanks = myCart.ramBankCount();
for(uInt32 i = 0; i < internalRamSize(); ++i) info << "3E+ cartridge - (4" << ELLIPSIS << "64K ROM + RAM)\n"
myOldState.internalram.push_back(myCart.myRAM[i]); << " " << numRomBanks << " 1K ROM banks + " << numRamBanks << " 512b RAM banks\n"
<< " mapped into four segments\n"
"ROM bank & segment selected by writing to $3F\n"
"RAM bank & segment selected by writing to $3E\n"
" Lower 512b of segment for read access\n"
" Upper 512b of segment for write access\n"
"Startup bank = -1/-1/-1/0 (ROM)\n";
// Eventually, we should query this from the debugger/disassembler
uInt16 start = (image[0x400 - 3] << 8) | image[0x400 - 4];
start -= start % 0x1000;
info << "Bank RORG" << " = $" << Common::Base::HEX4 << start << "\n";
return info.str();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EPlusWidget::bankSelect(int& ypos)
{
size_t size;
const ByteBuffer& image = myCart.getImage(size);
const int VGAP = myFontHeight / 4;
VariantList banktype;
VarList::push_back(banktype, "ROM", "ROM");
VarList::push_back(banktype, "RAM", "RAM");
myBankWidgets = make_unique<PopUpWidget* []>(bankSegs());
ypos -= VGAP * 2;
for(uInt32 seg = 0; seg < bankSegs(); ++seg)
{
int xpos = 2, xpos_s, ypos_s = ypos + 1, width;
ostringstream label;
VariantList items;
label << "Set segment " << seg << " as ";
new StaticTextWidget(_boss, _font, xpos, ypos, label.str());
ypos += myLineHeight + VGAP * 2;
xpos += _font.getMaxCharWidth() * 2;
CartridgeEnhancedWidget::bankList(std::max(myCart.romBankCount(), myCart.ramBankCount()),
seg, items, width);
myBankWidgets[seg] =
new PopUpWidget(_boss, _font, xpos, ypos - 2, width,
myLineHeight, items, "Bank ", 0, kBankChanged);
myBankWidgets[seg]->setID(seg);
myBankWidgets[seg]->setTarget(this);
addFocusWidget(myBankWidgets[seg]);
xpos += myBankWidgets[seg]->getWidth();
myBankType[seg] =
new PopUpWidget(_boss, _font, xpos, ypos - 2, 3 * _font.getMaxCharWidth(),
myLineHeight, banktype, " of ", 0, kRomRamChanged);
myBankType[seg]->setID(seg);
myBankType[seg]->setTarget(this);
addFocusWidget(myBankType[seg]);
xpos = myBankType[seg]->getRight() + _font.getMaxCharWidth();
// add "Commit" button (why required?)
myBankCommit[seg] = new ButtonWidget(_boss, _font, xpos, ypos - 4,
_font.getStringWidth(" Commit "), myButtonHeight,
"Commit", kChangeBank);
myBankCommit[seg]->setID(seg);
myBankCommit[seg]->setTarget(this);
addFocusWidget(myBankCommit[seg]);
xpos_s = myBankCommit[seg]->getRight() + _font.getMaxCharWidth() * 2;
StaticTextWidget* t;
uInt16 start = (image[0x400 - 3] << 8) | image[0x400 - 4];
start -= start % 0x1000;
int addr1 = start + (seg * 0x400), addr2 = addr1 + 0x200;
label.str("");
label << "$" << Common::Base::HEX4 << addr1 << "-$" << Common::Base::HEX4 << (addr1 + 0x1FF);
t = new StaticTextWidget(_boss, _font, xpos_s, ypos_s + 2, label.str());
int xoffset = t->getRight() + _font.getMaxCharWidth();
myBankState[2 * seg] = new EditTextWidget(_boss, _font, xoffset, ypos_s,
_w - xoffset - 10, myLineHeight, "");
myBankState[2 * seg]->setEditable(false, true);
ypos_s += myLineHeight + VGAP;
label.str("");
label << "$" << Common::Base::HEX4 << addr2 << "-$" << Common::Base::HEX4 << (addr2 + 0x1FF);
new StaticTextWidget(_boss, _font, xpos_s, ypos_s + 2, label.str());
myBankState[2 * seg + 1] = new EditTextWidget(_boss, _font, xoffset, ypos_s,
_w - xoffset - 10, myLineHeight, "");
myBankState[2 * seg + 1]->setEditable(false, true);
ypos += myLineHeight + VGAP * 4;
}
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EPlusWidget::loadConfig() void Cartridge3EPlusWidget::loadConfig()
{ {
CartridgeEnhancedWidget::loadConfig();
updateUIState(); updateUIState();
CartDebugWidget::loadConfig();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EPlusWidget::handleCommand(CommandSender* sender, void Cartridge3EPlusWidget::handleCommand(CommandSender* sender,
int cmd, int data, int id) int cmd, int data, int id)
{ {
uInt16 segment = 0; const uInt16 segment = id;
switch(cmd) switch(cmd)
{ {
case kBank0Changed: case kBankChanged:
segment = 0; case kRomRamChanged:
break; {
case kBank1Changed: const bool isROM = myBankType[segment]->getSelectedTag() == "ROM";
segment = 1; int bank = myBankWidgets[segment]->getSelected();
break;
case kBank2Changed: myBankCommit[segment]->setEnabled((isROM && bank < myCart.romBankCount())
segment = 2; || (!isROM && bank < myCart.ramBankCount()));
break;
case kBank3Changed:
segment = 3;
break;
default:
break; break;
} }
case kChangeBank:
{
// Ignore bank if either number or type hasn't been selected // Ignore bank if either number or type hasn't been selected
if(myBankNumber[segment]->getSelected() < 0 || if(myBankWidgets[segment]->getSelected() < 0 ||
myBankType[segment]->getSelected() < 0) myBankType[segment]->getSelected() < 0)
return; return;
uInt8 bank = (segment << myCart.BANK_BITS) | uInt8 bank = myBankWidgets[segment]->getSelected();
(myBankNumber[segment]->getSelected() & myCart.BIT_BANK_MASK);
myCart.unlockBank(); myCart.unlockBank();
if(myBankType[segment]->getSelectedTag() == "ROM") if(myBankType[segment]->getSelectedTag() == "ROM")
myCart.bankROM(bank); myCart.bank(bank, segment);
else else
myCart.bankRAM(bank); myCart.bank(bank + myCart.romBankCount(), segment);
myCart.lockBank(); myCart.lockBank();
invalidate(); invalidate();
updateUIState(); updateUIState();
break;
} }
default:
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - break;
string Cartridge3EPlusWidget::bankState()
{
ostringstream& buf = buffer();
// In this scheme, consecutive 512b segments are either both ROM or both RAM;
// we only need to look at the lower segment to determine what the 1K bank is
for(int i = 0; i < 4; ++i)
{
uInt16 bank = myCart.bankInUse[i*2];
if(bank == myCart.BANK_UNDEFINED) // never accessed
{
buf << " U!";
} }
else
{
int bankno = bank & myCart.BIT_BANK_MASK;
if(bank & myCart.BITMASK_ROMRAM) // was RAM mapped here?
buf << " RAM " << bankno;
else
buf << " ROM " << bankno;
}
if(i < 3)
buf << " /";
}
return buf.str();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EPlusWidget::updateUIState() void Cartridge3EPlusWidget::updateUIState()
{ {
// Set description for each 512b bank state (@ each index) // Set description for each 1K segment state (@ each index)
// Set contents for actual banks number and type (@ each even index) // Set contents for actual banks number and type (@ each even index)
for(int i = 0; i < 8; ++i) for(int seg = 0; seg < myCart3EP.myBankSegs; ++seg)
{
uInt16 bank = myCart.bankInUse[i];
if(bank == myCart.BANK_UNDEFINED) // never accessed
{
myBankState[i]->setText("Undefined");
if(i % 2 == 0)
{
myBankNumber[i/2]->clearSelection();
myBankType[i/2]->clearSelection();
}
}
else
{ {
uInt16 bank = myCart.getSegmentBank(seg);
ostringstream buf; ostringstream buf;
int bankno = bank & myCart.BIT_BANK_MASK;
if(bank & myCart.BITMASK_ROMRAM) // was RAM mapped here? if(bank >= myCart.romBankCount()) // was RAM mapped here?
{ {
if(bank & myCart.BITMASK_LOWERUPPER) // upper is write port uInt16 ramBank = bank - myCart.romBankCount();
{
buf << "RAM " << bankno << " @ $" << Common::Base::HEX4 buf << "RAM @ $" << Common::Base::HEX4
<< (bankno << myCart.RAM_BANK_TO_POWER) << " (W)"; << (ramBank << myCart3EP.myBankShift) << " (R)";
myBankState[i]->setText(buf.str()); myBankState[seg * 2]->setText(buf.str());
buf.str("");
buf << "RAM @ $" << Common::Base::HEX4
<< ((ramBank << myCart3EP.myBankShift) + myCart3EP.myBankSize) << " (W)";
myBankState[seg * 2 + 1]->setText(buf.str());
myBankWidgets[seg]->setSelectedIndex(ramBank);
myBankType[seg]->setSelected("RAM");
} }
else else
{ {
buf << "RAM " << bankno << " @ $" << Common::Base::HEX4 buf << "ROM @ $" << Common::Base::HEX4
<< (bankno << myCart.RAM_BANK_TO_POWER) << " (R)"; << ((bank << myCart3EP.myBankShift));
myBankState[i]->setText(buf.str()); myBankState[seg * 2]->setText(buf.str());
}
if(i % 2 == 0) buf.str("");
{ buf << "ROM @ $" << Common::Base::HEX4
myBankNumber[i/2]->setSelected(bankno); << ((bank << myCart3EP.myBankShift) + myCart3EP.myBankSize);
myBankType[i/2]->setSelected("RAM"); myBankState[seg * 2 + 1]->setText(buf.str());
}
}
else
{
if(bank & myCart.BITMASK_LOWERUPPER) // upper is high 512b
{
buf << "ROM " << bankno << " @ $" << Common::Base::HEX4
<< ((bankno << myCart.RAM_BANK_TO_POWER) + myCart.RAM_BANK_SIZE);
myBankState[i]->setText(buf.str());
}
else
{
buf << "ROM " << bankno << " @ $" << Common::Base::HEX4
<< (bankno << myCart.RAM_BANK_TO_POWER);
myBankState[i]->setText(buf.str());
}
if(i % 2 == 0) myBankWidgets[seg]->setSelectedIndex(bank);
{ myBankType[seg]->setSelected("ROM");
myBankNumber[i/2]->setSelected(bankno);
myBankType[i/2]->setSelected("ROM");
} }
} }
} }
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 Cartridge3EPlusWidget::internalRamSize()
{
return 32*1024;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 Cartridge3EPlusWidget::internalRamRPort(int start)
{
return 0x0000 + start;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge3EPlusWidget::internalRamDescription() string Cartridge3EPlusWidget::internalRamDescription()
{ {
ostringstream desc; ostringstream desc;
desc << "Accessible 512b at a time via:\n" desc << "Accessible 512b at a time via:\n"
<< " $f000/$f400/$f800/$fc00 for Read Access\n" << " $f000/$f400/$f800/$fc00 for read access\n"
<< " $f200/$f600/$fa00/$fe00 for Write Access (+$200)"; << " $f200/$f600/$fa00/$fe00 for write access";
return desc.str(); return desc.str();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const ByteArray& Cartridge3EPlusWidget::internalRamOld(int start, int count)
{
myRamOld.clear();
for(int i = 0; i < count; i++)
myRamOld.push_back(myOldState.internalram[start + i]);
return myRamOld;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const ByteArray& Cartridge3EPlusWidget::internalRamCurrent(int start, int count)
{
myRamCurrent.clear();
for(int i = 0; i < count; i++)
myRamCurrent.push_back(myCart.myRAM[start + i]);
return myRamCurrent;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EPlusWidget::internalRamSetValue(int addr, uInt8 value)
{
myCart.myRAM[addr] = value;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 Cartridge3EPlusWidget::internalRamGetValue(int addr)
{
return myCart.myRAM[addr];
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const std::array<Cartridge3EPlusWidget::BankID, 4> Cartridge3EPlusWidget::bankEnum = {
kBank0Changed, kBank1Changed, kBank2Changed, kBank3Changed
};

View File

@ -23,9 +23,9 @@ class ButtonWidget;
class EditTextWidget; class EditTextWidget;
class PopUpWidget; class PopUpWidget;
#include "CartDebugWidget.hxx" #include "CartEnhancedWidget.hxx"
class Cartridge3EPlusWidget : public CartDebugWidget class Cartridge3EPlusWidget : public CartridgeEnhancedWidget
{ {
public: public:
Cartridge3EPlusWidget(GuiObject* boss, const GUI::Font& lfont, Cartridge3EPlusWidget(GuiObject* boss, const GUI::Font& lfont,
@ -35,46 +35,33 @@ class Cartridge3EPlusWidget : public CartDebugWidget
virtual ~Cartridge3EPlusWidget() = default; virtual ~Cartridge3EPlusWidget() = default;
private: private:
string manufacturer() override { return "Thomas Jentzsch"; }
string description() override;
void bankSelect(int& ypos) override;
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
void updateUIState(); void updateUIState();
private: void loadConfig() override;
Cartridge3EPlus& myCart;
string internalRamDescription() override;
private:
Cartridge3EPlus& myCart3EP;
std::array<PopUpWidget*, 4> myBankNumber{nullptr};
std::array<PopUpWidget*, 4> myBankType{nullptr}; std::array<PopUpWidget*, 4> myBankType{nullptr};
std::array<ButtonWidget*, 4> myBankCommit{nullptr}; std::array<ButtonWidget*, 4> myBankCommit{nullptr};
std::array<EditTextWidget*, 8> myBankState{nullptr}; std::array<EditTextWidget*, 8> myBankState{nullptr};
struct CartState { enum {
ByteArray internalram; kRomRamChanged = 'rrCh',
kChangeBank = 'chBk',
}; };
CartState myOldState;
enum BankID {
kBank0Changed = 'b0CH',
kBank1Changed = 'b1CH',
kBank2Changed = 'b2CH',
kBank3Changed = 'b3CH'
};
static const std::array<BankID, 4> bankEnum;
private: private:
void saveOldState() override;
void loadConfig() override;
void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
string bankState() override;
// start of functions for Cartridge RAM tab
uInt32 internalRamSize() override;
uInt32 internalRamRPort(int start) override;
string internalRamDescription() override;
const ByteArray& internalRamOld(int start, int count) override;
const ByteArray& internalRamCurrent(int start, int count) override;
void internalRamSetValue(int addr, uInt8 value) override;
uInt8 internalRamGetValue(int addr) override;
// end of functions for Cartridge RAM tab
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
Cartridge3EPlusWidget() = delete; Cartridge3EPlusWidget() = delete;
Cartridge3EPlusWidget(const Cartridge3EPlusWidget&) = delete; Cartridge3EPlusWidget(const Cartridge3EPlusWidget&) = delete;

View File

@ -23,129 +23,134 @@
Cartridge3EWidget::Cartridge3EWidget( Cartridge3EWidget::Cartridge3EWidget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont, GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
int x, int y, int w, int h, Cartridge3E& cart) int x, int y, int w, int h, Cartridge3E& cart)
: CartDebugWidget(boss, lfont, nfont, x, y, w, h), : CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart)
myCart(cart),
myNumRomBanks(uInt32(cart.mySize >> 11)),
myNumRamBanks(32)
{ {
size_t size = cart.mySize; initialize();
ostringstream info;
info << "3E cartridge - (3F + RAM)\n"
<< " 2-256 2K ROM (currently " << myNumRomBanks << "), 32 1K RAM\n"
<< "First 2K (ROM) selected by writing to $3F\n"
"First 2K (RAM) selected by writing to $3E\n"
" $F000 - $F3FF (R), $F400 - $F7FF (W)\n"
"Last 2K always points to last 2K of ROM\n";
if(cart.startBank() < myNumRomBanks)
info << "Startup bank = " << cart.startBank() << " (ROM)\n";
else
info << "Startup bank = " << (cart.startBank()-myNumRomBanks) << " (RAM)\n";
// Eventually, we should query this from the debugger/disassembler
uInt16 start = (cart.myImage[size-3] << 8) | cart.myImage[size-4];
start -= start % 0x1000;
info << "Bank RORG" << " = $" << Common::Base::HEX4 << start << "\n";
int xpos = 2,
ypos = addBaseInformation(size, "TigerVision", info.str()) + myLineHeight;
VariantList romitems;
for(uInt32 i = 0; i < myNumRomBanks; ++i)
VarList::push_back(romitems, i);
VarList::push_back(romitems, "Inactive", "");
VariantList ramitems;
for(uInt32 i = 0; i < myNumRamBanks; ++i)
VarList::push_back(ramitems, i);
VarList::push_back(ramitems, "Inactive", "");
ostringstream label;
label << "Set bank ($" << Common::Base::HEX4 << start << " - $"
<< (start+0x7FF) << "): ";
new StaticTextWidget(_boss, _font, xpos, ypos, _font.getStringWidth(label.str()),
myFontHeight, label.str(), TextAlign::Left);
ypos += myLineHeight + 8;
xpos += 40;
myROMBank =
new PopUpWidget(boss, _font, xpos, ypos-2, _font.getStringWidth("0 ($3E) "),
myLineHeight, romitems, "ROM ($3F) ",
_font.getStringWidth("ROM ($3F) "), kROMBankChanged);
myROMBank->setTarget(this);
addFocusWidget(myROMBank);
xpos += myROMBank->getWidth() + 20;
myRAMBank =
new PopUpWidget(boss, _font, xpos, ypos-2, _font.getStringWidth("0 ($3E) "),
myLineHeight, ramitems, "RAM ($3E) ",
_font.getStringWidth("RAM ($3E) "), kRAMBankChanged);
myRAMBank->setTarget(this);
addFocusWidget(myRAMBank);
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EWidget::saveOldState() string Cartridge3EWidget::description()
{ {
myOldState.internalram.clear(); ostringstream info;
size_t size;
const ByteBuffer& image = myCart.getImage(size);
uInt16 numRomBanks = myCart.romBankCount();
uInt16 numRamBanks = myCart.ramBankCount();
for(uInt32 i = 0; i < internalRamSize(); ++i)
myOldState.internalram.push_back(myCart.myRAM[i]);
myOldState.bank = myCart.myCurrentBank; info << "3E cartridge (3F + RAM),\n"
<< " " << numRomBanks << " 2K ROM banks, " << numRamBanks << " 1K RAM banks\n"
<< "First 2K (ROM) selected by writing to $3F\n"
"First 2K (RAM) selected by writing to $3E\n";
info << CartridgeEnhancedWidget::ramDescription();
info << "Last 2K always points to last 2K of ROM\n";
if(myCart.startBank() < numRomBanks)
info << "Startup bank = " << myCart.startBank() << " (ROM)\n";
else
info << "Startup bank = " << (myCart.startBank() - numRomBanks) << " (RAM)\n";
// Eventually, we should query this from the debugger/disassembler
uInt16 start = (image[size-3] << 8) | image[size-4];
start -= start % 0x1000;
info << "Bank RORG" << " = $" << Common::Base::HEX4 << start << "\n";
return info.str();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EWidget::bankList(uInt16 bankCount, int seg, VariantList& items, int& width)
{
CartridgeEnhancedWidget::bankList(bankCount, seg, items, width);
VarList::push_back(items, "Inactive", "");
width = _font.getStringWidth("Inactive");
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EWidget::bankSelect(int& ypos)
{
int xpos = 2;
VariantList items;
int pw;
myBankWidgets = make_unique<PopUpWidget* []>(2);
bankList(myCart.romBankCount(), 0, items, pw);
myBankWidgets[0] =
new PopUpWidget(_boss, _font, xpos, ypos - 2, pw,
myLineHeight, items, "Set bank ",
_font.getStringWidth("Set bank "), kBankChanged);
myBankWidgets[0]->setTarget(this);
myBankWidgets[0]->setID(0);
addFocusWidget(myBankWidgets[0]);
StaticTextWidget* t = new StaticTextWidget(_boss, _font, myBankWidgets[0]->getRight(), ypos - 1, " (ROM)");
xpos = t->getRight() + 20;
items.clear();
bankList(myCart.ramBankCount(), 0, items, pw);
myBankWidgets[1] =
new PopUpWidget(_boss, _font, xpos, ypos - 2, pw,
myLineHeight, items, "", 0, kRAMBankChanged);
myBankWidgets[1]->setTarget(this);
myBankWidgets[1]->setID(1);
addFocusWidget(myBankWidgets[1]);
new StaticTextWidget(_boss, _font, myBankWidgets[1]->getRight(), ypos - 1, " (RAM)");
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EWidget::loadConfig() void Cartridge3EWidget::loadConfig()
{ {
if(myCart.myCurrentBank < 256) uInt16 oldBank = myOldState.banks[0];
uInt16 bank = myCart.getBank();
if(myCart.getBank() < myCart.romBankCount())
{ {
myROMBank->setSelectedIndex(myCart.myCurrentBank % myNumRomBanks, myOldState.bank != myCart.myCurrentBank); myBankWidgets[0]->setSelectedIndex(bank, oldBank != bank);
myRAMBank->setSelectedMax(myOldState.bank >= 256); myBankWidgets[1]->setSelectedMax(oldBank >= myCart.romBankCount());
} }
else else
{ {
myROMBank->setSelectedMax(myOldState.bank < 256); myBankWidgets[0]->setSelectedMax(oldBank < myCart.romBankCount());
myRAMBank->setSelectedIndex(myCart.myCurrentBank - 256, myOldState.bank != myCart.myCurrentBank); myBankWidgets[1]->setSelectedIndex(bank - myCart.romBankCount(), oldBank != bank);
} }
CartDebugWidget::loadConfig(); CartDebugWidget::loadConfig();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EWidget::handleCommand(CommandSender* sender, void Cartridge3EWidget::handleCommand(CommandSender* sender, int cmd, int data, int id)
int cmd, int data, int id)
{ {
uInt16 bank = 0; uInt16 bank = 0;
if(cmd == kROMBankChanged) if(cmd == kBankChanged)
{ {
if(myROMBank->getSelected() < int(myNumRomBanks)) if(myBankWidgets[0]->getSelected() < myCart.romBankCount())
{ {
bank = myROMBank->getSelected(); bank = myBankWidgets[0]->getSelected();
myRAMBank->setSelectedMax(); myBankWidgets[1]->setSelectedMax();
} }
else else
{ {
bank = 256; // default to first RAM bank bank = myCart.romBankCount(); // default to first RAM bank
myRAMBank->setSelectedIndex(0); myBankWidgets[1]->setSelectedIndex(0);
} }
} }
else if(cmd == kRAMBankChanged) else if(cmd == kRAMBankChanged)
{ {
if(myRAMBank->getSelected() < int(myNumRamBanks)) if(myBankWidgets[1]->getSelected() < myCart.ramBankCount())
{ {
myROMBank->setSelectedMax(); myBankWidgets[0]->setSelectedMax();
bank = myRAMBank->getSelected() + 256; bank = myBankWidgets[1]->getSelected() + myCart.romBankCount();
} }
else else
{ {
bank = 0; // default to first ROM bank bank = 0; // default to first ROM bank
myROMBank->setSelectedIndex(0); myBankWidgets[0]->setSelectedIndex(0);
} }
} }
myCart.unlockBank(); myCart.unlockBank();
myCart.bank(bank); myCart.bank(bank);
myCart.lockBank(); myCart.lockBank();
@ -156,65 +161,13 @@ void Cartridge3EWidget::handleCommand(CommandSender* sender,
string Cartridge3EWidget::bankState() string Cartridge3EWidget::bankState()
{ {
ostringstream& buf = buffer(); ostringstream& buf = buffer();
uInt16 bank = myCart.getBank();
uInt16& bank = myCart.myCurrentBank; if(bank < myCart.romBankCount())
if(bank < 256) buf << "ROM bank #" << std::dec << bank % myCart.romBankCount() << ", RAM inactive";
buf << "ROM bank #" << std::dec << bank % myNumRomBanks << ", RAM inactive";
else else
buf << "ROM inactive, RAM bank #" << std::dec << bank % myNumRomBanks; buf << "ROM inactive, RAM bank #"
<< std::dec << (bank - myCart.romBankCount()) % myCart.ramBankCount();
return buf.str(); return buf.str();
} }
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 Cartridge3EWidget::internalRamSize()
{
return 32*1024;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 Cartridge3EWidget::internalRamRPort(int start)
{
return 0x0000 + start;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge3EWidget::internalRamDescription()
{
ostringstream desc;
desc << "Accessible 1K at a time via:\n"
<< " $F000 - $F3FF used for Read Access\n"
<< " $F400 - $F7FF used for Write Access";
return desc.str();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const ByteArray& Cartridge3EWidget::internalRamOld(int start, int count)
{
myRamOld.clear();
for(int i = 0; i < count; i++)
myRamOld.push_back(myOldState.internalram[start + i]);
return myRamOld;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
const ByteArray& Cartridge3EWidget::internalRamCurrent(int start, int count)
{
myRamCurrent.clear();
for(int i = 0; i < count; i++)
myRamCurrent.push_back(myCart.myRAM[start + i]);
return myRamCurrent;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EWidget::internalRamSetValue(int addr, uInt8 value)
{
myCart.myRAM[addr] = value;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt8 Cartridge3EWidget::internalRamGetValue(int addr)
{
return myCart.myRAM[addr];
}

View File

@ -19,11 +19,12 @@
#define CARTRIDGE3E_WIDGET_HXX #define CARTRIDGE3E_WIDGET_HXX
class Cartridge3E; class Cartridge3E;
class PopUpWidget;
#include "CartDebugWidget.hxx" #include "CartEnhancedWidget.hxx"
class Cartridge3EWidget : public CartDebugWidget // Note: This class supports 3EX too
class Cartridge3EWidget : public CartridgeEnhancedWidget
{ {
public: public:
Cartridge3EWidget(GuiObject* boss, const GUI::Font& lfont, Cartridge3EWidget(GuiObject* boss, const GUI::Font& lfont,
@ -33,39 +34,28 @@ class Cartridge3EWidget : public CartDebugWidget
virtual ~Cartridge3EWidget() = default; virtual ~Cartridge3EWidget() = default;
private: private:
Cartridge3E& myCart;
const uInt32 myNumRomBanks{0};
const uInt32 myNumRamBanks{0};
PopUpWidget *myROMBank{nullptr}, *myRAMBank{nullptr};
struct CartState {
ByteArray internalram;
uInt16 bank;
};
CartState myOldState;
enum { enum {
kROMBankChanged = 'rmCH',
kRAMBankChanged = 'raCH' kRAMBankChanged = 'raCH'
}; };
private: private:
void saveOldState() override; string manufacturer() override { return "Andrew Davie & Thomas Jentzsch"; }
string description() override;
void bankList(uInt16 bankCount, int seg, VariantList& items, int& width) override;
void bankSelect(int& ypos) override;
uInt16 bankSegs() override { return 1; }
void loadConfig() override; void loadConfig() override;
void handleCommand(CommandSender* sender, int cmd, int data, int id) override; void handleCommand(CommandSender* sender, int cmd, int data, int id) override;
string bankState() override; string bankState() override;
// start of functions for Cartridge RAM tab private:
uInt32 internalRamSize() override;
uInt32 internalRamRPort(int start) override;
string internalRamDescription() override;
const ByteArray& internalRamOld(int start, int count) override;
const ByteArray& internalRamCurrent(int start, int count) override;
void internalRamSetValue(int addr, uInt8 value) override;
uInt8 internalRamGetValue(int addr) override;
// end of functions for Cartridge RAM tab
// Following constructors and assignment operators not supported // Following constructors and assignment operators not supported
Cartridge3EWidget() = delete; Cartridge3EWidget() = delete;
Cartridge3EWidget(const Cartridge3EWidget&) = delete; Cartridge3EWidget(const Cartridge3EWidget&) = delete;

View File

@ -16,79 +16,33 @@
//============================================================================ //============================================================================
#include "Cart3F.hxx" #include "Cart3F.hxx"
#include "PopUpWidget.hxx"
#include "Cart3FWidget.hxx" #include "Cart3FWidget.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cartridge3FWidget::Cartridge3FWidget( Cartridge3FWidget::Cartridge3FWidget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont, GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
int x, int y, int w, int h, Cartridge3F& cart) int x, int y, int w, int h, Cartridge3F& cart)
: CartDebugWidget(boss, lfont, nfont, x, y, w, h), : CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart)
myCart(cart)
{ {
size_t size = cart.mySize; myHotspotDelta = 0;
initialize();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge3FWidget::description()
{
ostringstream info; ostringstream info;
size_t size;
const ByteBuffer& image = myCart.getImage(size);
info << "Tigervision 3F cartridge, 2 - 256 2K banks\n" info << "Tigervision 3F cartridge, 2 - 256 2K banks\n"
<< "Startup bank = " << cart.startBank() << " or undetermined\n" << "First 2K bank selected by writing to " << hotspotStr() << "\n"
<< "First 2K bank selected by writing to $3F\n" << "Last 2K always points to last 2K of ROM\n"
<< "Last 2K always points to last 2K of ROM\n"; << "Startup bank = " << myCart.startBank() << " or undetermined\n";
// Eventually, we should query this from the debugger/disassembler // Eventually, we should query this from the debugger/disassembler
uInt16 start = (cart.myImage[size-3] << 8) | cart.myImage[size-4]; uInt16 start = (image[size-3] << 8) | image[size-4];
start -= start % 0x1000; start -= start % 0x1000;
info << "Bank RORG" << " = $" << Common::Base::HEX4 << start << "\n"; info << "Bank RORG $" << Common::Base::HEX4 << start << "\n";
int xpos = 2, return info.str();
ypos = addBaseInformation(size, "TigerVision", info.str()) + myLineHeight;
VariantList items;
for(uInt16 i = 0; i < cart.bankCount(); ++i)
VarList::push_back(items, Variant(i).toString() + " ($3F)");
ostringstream label;
label << "Set bank ($" << Common::Base::HEX4 << start << " - $" <<
(start+0x7FF) << ") ";
myBank =
new PopUpWidget(boss, _font, xpos, ypos-2, _font.getStringWidth("0 ($3F) "),
myLineHeight, items, label.str(),
_font.getStringWidth(label.str()), kBankChanged);
myBank->setTarget(this);
addFocusWidget(myBank);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3FWidget::loadConfig()
{
Debugger& dbg = instance().debugger();
CartDebug& cart = dbg.cartDebug();
const CartState& state = static_cast<const CartState&>(cart.getState());
const CartState& oldstate = static_cast<const CartState&>(cart.getOldState());
myBank->setSelectedIndex(myCart.getBank(0), state.bank != oldstate.bank);
CartDebugWidget::loadConfig();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3FWidget::handleCommand(CommandSender* sender,
int cmd, int data, int id)
{
if(cmd == kBankChanged)
{
myCart.unlockBank();
myCart.bank(myBank->getSelected());
myCart.lockBank();
invalidate();
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge3FWidget::bankState()
{
ostringstream& buf = buffer();
buf << "Bank = #" << std::dec << myCart.myCurrentBank << ", hotspot = $3F";
return buf.str();
} }

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