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/**/**.pdb
Stella.xcscheme
src/tools/fonts/*

View File

@ -9,7 +9,7 @@
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
@ -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
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:
* Binaries for Windows Vista/7/8/10 :
Stella-6.1-win32.exe (32-bit EXE installer)
Stella-6.1-x64.exe (64-bit EXE installer)
Stella-6.1-windows.zip (32/64 bit versions)
Stella-6.2.1-win32.exe (32-bit EXE installer)
Stella-6.2.1-x64.exe (64-bit EXE installer)
Stella-6.2.1-windows.zip (32/64 bit versions)
* 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 :
stella_6.1-1_i386.deb
stella_6.1-1_amd64.deb
stella_6.2.1-1_i386.deb
stella_6.2.1-1_amd64.deb
* Binary distribution in 32-bit & 64-bit RPM format :
stella-6.1-2.i386.rpm
stella-6.1-2.x86_64.rpm
stella-6.2.1-2.i386.rpm
stella-6.2.1-2.x86_64.rpm
* Source code distribution for all platforms :
stella-6.1-src.tar.xz
stella-6.2.1-src.tar.xz
Distribution Site

View File

@ -12,7 +12,155 @@
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:
- Because of major event remapping changes, all remappings will be reset
@ -119,7 +267,9 @@
* 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.
* 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,
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 libretro port, and fixed display for OpenGLES renderers.
@ -192,7 +346,8 @@
* 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)

View File

@ -8,14 +8,11 @@
## SS SS tt ee ll ll aa aa
## 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
##
## See the file "License.txt" for information on usage and redistribution of
## 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_USE := stella-pgo$(EXEEXT)
PROFILE_DIR = $(CURDIR)/profile
PROFILE_DIR = $(CURDIR)/test/roms/profile
PROFILE_OUT = $(PROFILE_DIR)/out
PROFILE_STAMP = profile.stamp

View File

@ -10,12 +10,12 @@ environment:
Configuration: Release
SDL2_version: 2.0.10
SDL2_version: 2.0.12
install:
- 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:\"
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
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_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_verc_fail=no
else
cxx_version="$cxx_version, bad"
cxx_verc_fail=yes
fi
;;
'not found')
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
* Version 6.0.2 release

View File

@ -15,7 +15,7 @@
<body>
<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><h4><b>(a work in progress)</b></h4></center>
<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
override automatic code/data determination in the disassembly. For now,
the following directives are supported: CODE, GFX, PGFX, DATA, ROW.
These directives can be entered at the debugger prompt, or (automatically)
the following directives are supported: CODE, GFX, PGFX, COL, PCOL, BCOL, AUD, DATA, ROW.
These directives can be entered at the debugger prompt, or be (automatically)
loaded and saved in configuration files.</li>
<li>Extensive disassembly support, both from the emulation core and with help
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
differentiated between actual code, and 'tentative' code (ie, areas that may
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
<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>
@ -381,12 +382,12 @@ size can be configured e.g. in the
</tr>
<tr>
<td>Backquote (`)</td>
<td>Exit</td>
<td>Run</td>
</tr>
</table>
</p>
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>
<p><img src="graphics/debugger_options.png"></p>
</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,
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
debugger with the Program Counter pointing to the instruction
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> _icycles</td><td> Number of cycles of last instruction</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> _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>
@ -906,7 +908,9 @@ Type "help 'cmd'" to see extended information about the given command.</p>
<pre>
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)
bcol - Mark 'BCOL' range in disassembly
break - Set/clear breakpoint at &lt;address&gt; and &lt;bank&gt;
breakif - Set/clear breakpoint on &lt;condition&gt;
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
cls - Clear prompt area of text
code - Mark 'CODE' range in disassembly
col - Mark 'COL' range in disassembly
colortest - Show value xx as TIA color
d - Decimal Mode Flag: set (0 or 1), or toggle (no arg)
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)
palette - Show current TIA palette
pc - Set Program Counter to address xx
pcol - Mark 'PCOL' range in disassembly
pgfx - Mark 'PGFX' range in disassembly
print - Evaluate/print expression xx in hex/dec/binary
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
s - Set Stack Pointer to value 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)
savedis - Save Distella disassembly (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
the GRP0/1 and playfield registers (remember to double-click).</p>
<p>The group of buttons labelled "Strobes" allows you to write to any
of the strobe registers at any time.</p>
<p>The buttons allow you to write to any of the strobe registers at
any time.</p>
<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
@ -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>
<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,
as illustrated:</p>
@ -1193,6 +1200,8 @@ the reason will be shown as follows:
<li>"WTrap:" for write traps</li>
<li>"RTrapIf:" for conditional read 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>
</p>
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>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.
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
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>
@ -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
first glance. The 'Src Addr' area shows the actual resulting operand/address
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
assembly, it's pretty self-explanatory. If you don't, well, you should
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
decreasing hierarchy:</p>
<table border="1" cellpadding=4>
<tr><td><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
can be edited in either hex or binary. The bitmap is shown as large blocks.</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
can be edited in either hex or binary. The bitmap is shown as small dashes.</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>
<tr>
<td>
<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
can be edited in either hex or binary. The bitmap is shown as large blocks.
</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
can be edited in either hex or binary. The bitmap is shown as small dashes.
</td>
</tr><tr>
<td>
<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>
<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
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>
<ul>
<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
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
disassembled, regardless of whether anything has changed.</li>
<li><b>Disassemble @ current line</b>: Disassemble from the disassembly line where the mouse was clicked.
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>
@ -1445,7 +1513,8 @@ isn't already a defined label).</li>
in either binary or hexidecimal.</li>
<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>
@ -1520,12 +1589,12 @@ the RAM in the DPC scheme is not viewable by the 6507, so its addresses start fr
<br>
<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:
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
several options in this case:</p>
<ol>
<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
will remove that directive from the range.</li>
<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
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
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
@ -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
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:
"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,

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><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>
<br/>

View File

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

View File

@ -213,10 +213,9 @@ void CheatManager::enable(const string& code, bool enable)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void CheatManager::loadCheatDatabase()
{
const string& cheatfile = myOSystem.cheatFile();
ifstream in(cheatfile);
if(!in)
return;
stringstream in;
try { myOSystem.cheatFile().read(in); }
catch(...) { return; }
string line, md5, cheat;
string::size_type one, two, three, four;
@ -253,13 +252,12 @@ void CheatManager::saveCheatDatabase()
if(!myListIsDirty)
return;
const string& cheatfile = myOSystem.cheatFile();
ofstream out(cheatfile);
if(!out)
return;
stringstream out;
for(const auto& iter: myCheatMap)
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);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 AudioSettings::device() const
{
return mySettings.getInt(SETTING_DEVICE);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
bool AudioSettings::enabled() const
{
@ -285,6 +291,14 @@ void AudioSettings::setVolume(uInt32 volume)
normalize(mySettings);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void AudioSettings::setDevice(uInt32 device)
{
if(!myIsPersistent) return;
mySettings.setValue(SETTING_DEVICE, device);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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_STEREO = "audio.stereo";
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_DPC_PITCH = "audio.dpc_pitch";
@ -59,6 +60,7 @@ class AudioSettings
static constexpr ResamplingQuality DEFAULT_RESAMPLING_QUALITY = ResamplingQuality::lanczos_2;
static constexpr bool DEFAULT_STEREO = false;
static constexpr uInt32 DEFAULT_VOLUME = 80;
static constexpr uInt32 DEFAULT_DEVICE = 0;
static constexpr bool DEFAULT_ENABLED = true;
static constexpr uInt32 DEFAULT_DPC_PITCH = 20000;
@ -87,6 +89,8 @@ class AudioSettings
uInt32 volume() const;
uInt32 device() const;
bool enabled() const;
uInt32 dpcPitch() const;
@ -109,6 +113,8 @@ class AudioSettings
void setVolume(uInt32 volume);
void setDevice(uInt32 device);
void setEnabled(bool isEnabled);
void setPersistent(bool isPersistent);

View File

@ -126,6 +126,21 @@ void FilesystemNodeZIP::setFlags(const string& zipfile,
_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
{
@ -181,7 +196,34 @@ size_t FilesystemNodeZIP::read(ByteBuffer& image) const
while(myZipHandler->hasNext() && !found)
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);
bool exists() const override { return _realNode && _realNode->exists(); }
bool exists() const override;
const string& getName() const override { return _name; }
void setName(const string& name) override { _name = name; }
const string& getPath() const override { return _path; }
@ -63,6 +63,9 @@ class FilesystemNodeZIP : public AbstractFSNode
AbstractFSNodePtr getParent() 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:
FilesystemNodeZIP(const string& zipfile, const string& virtualpath,

View File

@ -15,6 +15,8 @@
// this file, and for a DISCLAIMER OF ALL WARRANTIES.
//============================================================================
#include <cmath>
#include "SDL_lib.hxx"
#include "bspf.hxx"
#include "Logger.hxx"
@ -48,8 +50,6 @@ FrameBufferSDL2::FrameBufferSDL2(OSystem& osystem)
// since the structure may be needed before any FBSurface's have
// been created
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);
ostringstream s;
s << "Supported video modes for display " << i << ":";
Logger::debug(s.str());
s << "Supported video modes (" << numModes << ") for display " << i << ":";
string lastRes = "";
for (int m = 0; m < numModes; m++)
{
SDL_DisplayMode mode;
ostringstream res;
SDL_GetDisplayMode(i, m, &mode);
s.str("");
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)
s << " (active)";
res << std::setw(4) << mode.w << "x" << std::setw(4) << mode.h;
if(lastRes != res.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
@ -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;
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)
{
@ -213,31 +233,13 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
if(SDL_WasInit(SDL_INIT_VIDEO) == 0)
return false;
Int32 displayIndex = mode.fsIndex;
if (displayIndex == -1)
{
// 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"));
}
}
const bool fullScreen = mode.fsIndex != -1;
bool forceCreateRenderer = false;
// save and get last windowed window's position
updateWindowedPos();
// Always recreate renderer (some systems need this)
if(myRenderer)
{
SDL_DestroyRenderer(myRenderer);
myRenderer = nullptr;
}
// Get windowed window's last display
Int32 displayIndex = std::min(myNumDisplays, myOSystem.settings().getInt(getDisplayKey()));
// Get windowed window's last position
myWindowedPos = myOSystem.settings().getPoint(getPositionKey());
int posX, posY;
@ -249,7 +251,7 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
posX = myWindowedPos.x;
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;
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);
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
// keep the same handle
// Problem is, doing so on other platforms results in flickering when
// toggling fullscreen windowed mode
// So we have a special case for macOS
#ifndef BSPF_MACOS
// Don't re-create the window if its size hasn't changed, as it's not
// necessary, and causes flashing in fullscreen mode
#ifdef ADAPTABLE_REFRESH_SUPPORT
SDL_DisplayMode adaptedSdlMode;
const bool shouldAdapt = fullScreen && myOSystem.settings().getBool("tia.fs_refresh")
&& gameRefreshRate()
// take care of 59.94 Hz
&& refreshRate() % gameRefreshRate() != 0 && refreshRate() % (gameRefreshRate() - 1) != 0;
const bool adaptRefresh = shouldAdapt && adaptRefreshRate(displayIndex, adaptedSdlMode);
#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)
{
const int d = SDL_GetWindowDisplayIndex(myWindow);
int 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);
myWindow = nullptr;
}
}
if(myWindow)
{
// Even though window size stayed the same, the title may have changed
SDL_SetWindowTitle(myWindow, title.c_str());
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
{
forceCreateRenderer = true;
myWindow = SDL_CreateWindow(title.c_str(), posX, posY,
mode.screen.w, mode.screen.h, flags);
if(myWindow == nullptr)
@ -316,13 +316,114 @@ bool FrameBufferSDL2::setVideoMode(const string& title, const VideoMode& mode)
Logger::error(msg);
return false;
}
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;
if(myOSystem.settings().getBool("vsync")) // V'synced blits option
renderFlags |= SDL_RENDERER_PRESENTVSYNC;
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 != "")
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);
return false;
}
}
clear();
SDL_RendererInfo renderinfo;
if(SDL_GetRendererInfo(myRenderer, &renderinfo) >= 0)
myOSystem.settings().setValue("video", renderinfo.name);
@ -407,6 +510,36 @@ bool FrameBufferSDL2::fullScreen() const
#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()
{
@ -419,10 +552,9 @@ void FrameBufferSDL2::renderToScreen()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void FrameBufferSDL2::setWindowIcon()
{
ASSERT_MAIN_THREAD;
#if !defined(BSPF_MACOS) && !defined(RETRON77)
#include "stella_icon.hxx"
ASSERT_MAIN_THREAD;
SDL_Surface* surface = SDL_CreateRGBSurfaceFrom(stella_icon, 32, 32, 32,
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;
/**
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
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
window is displayed
*/
Int32 getCurrentDisplayIndex() override;
/**
This method is called to preserve the last current windowed position.
*/
void updateWindowedPos() override;
Int32 getCurrentDisplayIndex() const override;
/**
Clear the frame buffer.
@ -137,12 +147,12 @@ class FrameBufferSDL2 : public FrameBuffer
/**
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
*/
int scaleY(int y) const { return (y * myRenderH) / myWindowH; }
int scaleY(int y) const override { return (y * myRenderH) / myWindowH; }
protected:
//////////////////////////////////////////////////////////////////////
@ -171,6 +181,25 @@ class FrameBufferSDL2 : public FrameBuffer
*/
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.
@ -223,6 +252,16 @@ class FrameBufferSDL2 : public FrameBuffer
*/
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:
// The SDL video buffer
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
{
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;
for (auto item : myMap)
for (auto item : sortedMap)
{
if (item.first.mode == mode)
{

View File

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

View File

@ -16,6 +16,7 @@
//============================================================================
#include "KeyMap.hxx"
#include <map>
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
{
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;
for (auto item : myMap)
for (auto item : sortedMap)
{
if (item.first.mode == mode)
{

View File

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

View File

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

View File

@ -124,7 +124,7 @@ MouseControl::MouseControl(Console& console, const string& mode)
int m_range = 100;
if(!(m_axis >> m_range))
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(myModeList.size() == 0)

View File

@ -250,9 +250,9 @@ void PhysicalJoystickHandler::setDefaultAction(int stick,
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
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))
{
if (map.hat == JOY_CTRL_NONE)
@ -687,7 +687,11 @@ void PhysicalJoystickHandler::handleAxisEvent(int stick, int axis, int value)
}
j->axisLastValue[axis] = value;
}
#ifdef GUI_SUPPORT
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
// (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)
if(value != j->axisLastValue[axis])
{
#ifdef GUI_SUPPORT
myHandler.overlay().handleJoyAxisEvent(stick, JoyAxis(axis), convertAxisValue(value), button);
#endif
}
}
j->axisLastValue[axis] = value;
}
}
#endif
}
}

View File

@ -17,13 +17,7 @@
#include "OSystem.hxx"
#include "Console.hxx"
#include "Settings.hxx"
#include "EventHandler.hxx"
#include "Sound.hxx"
#include "StateManager.hxx"
#include "StellaKeys.hxx"
#include "TIASurface.hxx"
#include "PNGLibrary.hxx"
#include "PKeyboardHandler.hxx"
#ifdef DEBUGGER_SUPPORT
@ -45,6 +39,7 @@ PhysicalKeyboardHandler::PhysicalKeyboardHandler(OSystem& system, EventHandler&
myHandler(handler)
{
Int32 version = myOSystem.settings().getInt("event_ver");
bool updateDefaults = false;
// Compare if event list version has changed so that key maps became invalid
if (version == Event::VERSION)
@ -59,11 +54,37 @@ PhysicalKeyboardHandler::PhysicalKeyboardHandler(OSystem& system, EventHandler&
myKeyMap.loadMapping(list, EventMode::kKeypadMode);
list = myOSystem.settings().getString("keymap_ui");
myKeyMap.loadMapping(list, EventMode::kMenuMode);
updateDefaults = true;
}
myKeyMap.enableMod() = myOSystem.settings().getBool("modcombo");
setDefaultMapping(Event::NoType, EventMode::kEmulationMode, true);
setDefaultMapping(Event::NoType, EventMode::kMenuMode, true);
setDefaultMapping(Event::NoType, EventMode::kEmulationMode, updateDefaults);
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 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
if (myKeyMap.getEventMapping(map.event, mode).size() == 0 ||
!myKeyMap.check(mode, map.key, map.mod))
if (myKeyMap.getEventMapping(map.event, mode).size() == 0 &&
!isMappingUsed(mode, map))
{
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::PAUSE:
case EventHandlerState::PLAYBACK:
myHandler.handleEvent(myKeyMap.get(EventMode::kEmulationMode, key, mod), pressed, repeated);
break;
@ -424,7 +446,11 @@ PhysicalKeyboardHandler::EventMappingArray PhysicalKeyboardHandler::DefaultCommo
{Event::LoadState, KBDK_F11},
{Event::LoadAllStates, KBDK_F11, MOD3},
{Event::TakeSnapshot, KBDK_F12},
#ifdef BSPF_MACOS
{Event::TogglePauseMode, KBDK_P, KBDM_SHIFT | MOD3},
#else
{Event::TogglePauseMode, KBDK_PAUSE},
#endif
{Event::OptionsMenuMode, KBDK_TAB},
{Event::CmdMenuMode, KBDK_BACKSLASH},
{Event::TimeMachineMode, KBDK_T, KBDM_SHIFT},
@ -436,45 +462,83 @@ PhysicalKeyboardHandler::EventMappingArray PhysicalKeyboardHandler::DefaultCommo
{Event::Quit, KBDK_Q, KBDM_CTRL},
#endif
{Event::ReloadConsole, KBDK_R, KBDM_CTRL},
{Event::PreviousMultiCartRom, KBDK_R, KBDM_SHIFT | KBDM_CTRL},
{Event::VidmodeDecrease, KBDK_MINUS, MOD3},
{Event::VidmodeIncrease, KBDK_EQUALS, MOD3},
{Event::VCenterDecrease, KBDK_PAGEUP, MOD3},
{Event::VCenterIncrease, KBDK_PAGEDOWN, MOD3},
{Event::ScanlineAdjustDecrease, KBDK_PAGEDOWN, KBDM_SHIFT | MOD3},
{Event::ScanlineAdjustIncrease, KBDK_PAGEUP, KBDM_SHIFT | MOD3},
{Event::VSizeAdjustDecrease, KBDK_PAGEDOWN, KBDM_SHIFT | MOD3},
{Event::VSizeAdjustIncrease, KBDK_PAGEUP, KBDM_SHIFT | MOD3},
{Event::VolumeDecrease, KBDK_LEFTBRACKET, MOD3},
{Event::VolumeIncrease, KBDK_RIGHTBRACKET, MOD3},
{Event::SoundToggle, KBDK_RIGHTBRACKET, KBDM_CTRL},
{Event::ToggleFullScreen, KBDK_RETURN, MOD3},
{Event::ToggleAdaptRefresh, KBDK_R, MOD3},
{Event::OverscanDecrease, KBDK_PAGEDOWN, KBDM_SHIFT},
{Event::OverscanIncrease, KBDK_PAGEUP, KBDM_SHIFT},
{Event::VidmodeStd, KBDK_1, MOD3},
{Event::VidmodeRGB, KBDK_2, MOD3},
{Event::VidmodeSVideo, KBDK_3, MOD3},
{Event::VidModeComposite, KBDK_4, MOD3},
{Event::VidModeBad, KBDK_5, MOD3},
{Event::VidModeCustom, KBDK_6, MOD3},
{Event::PreviousAttribute, KBDK_7, KBDM_SHIFT | MOD3},
{Event::NextAttribute, KBDK_7, MOD3},
{Event::DecreaseAttribute, KBDK_8, KBDM_SHIFT | MOD3},
{Event::IncreaseAttribute, KBDK_8, MOD3},
{Event::PhosphorDecrease, KBDK_9, KBDM_SHIFT | MOD3},
{Event::PhosphorIncrease, KBDK_9, MOD3},
//{Event::VidmodeStd, KBDK_1, MOD3},
//{Event::VidmodeRGB, KBDK_2, MOD3},
//{Event::VidmodeSVideo, KBDK_3, MOD3},
//{Event::VidModeComposite, KBDK_4, MOD3},
//{Event::VidModeBad, KBDK_5, MOD3},
//{Event::VidModeCustom, KBDK_6, MOD3},
{Event::PreviousVideoMode, KBDK_1, KBDM_SHIFT | MOD3},
{Event::NextVideoMode, KBDK_1, MOD3},
{Event::PreviousAttribute, KBDK_2, KBDM_SHIFT | MOD3},
{Event::NextAttribute, KBDK_2, MOD3},
{Event::DecreaseAttribute, KBDK_3, KBDM_SHIFT | MOD3},
{Event::IncreaseAttribute, KBDK_3, MOD3},
{Event::PhosphorDecrease, KBDK_4, KBDM_SHIFT | MOD3},
{Event::PhosphorIncrease, KBDK_4, MOD3},
{Event::TogglePhosphor, KBDK_P, MOD3},
{Event::ScanlinesDecrease, KBDK_0, KBDM_SHIFT | MOD3},
{Event::ScanlinesIncrease, KBDK_0, MOD3},
{Event::ScanlinesDecrease, KBDK_5, KBDM_SHIFT | 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::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::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::ToggleFrameStats, KBDK_L, MOD3},
{Event::ToggleTimeMachine, KBDK_T, MOD3},
#ifdef PNG_SUPPORT
{Event::ToggleContSnapshots, KBDK_S, MOD3},
{Event::ToggleContSnapshotsFrame, KBDK_S, KBDM_SHIFT | MOD3},
#endif
{Event::DecreaseAutoFire, KBDK_A, KBDM_SHIFT | KBDM_CTRL},
{Event::IncreaseAutoFire, KBDK_A, KBDM_CTRL },
{Event::HandleMouseControl, KBDK_0, KBDM_CTRL},
{Event::ToggleGrabMouse, KBDK_G, KBDM_CTRL},
{Event::ToggleSAPortOrder, KBDK_1, KBDM_CTRL},
@ -506,6 +570,7 @@ PhysicalKeyboardHandler::EventMappingArray PhysicalKeyboardHandler::DefaultCommo
{Event::Unwind10Menu, KBDK_RIGHT, KBDM_SHIFT | MOD3},
{Event::UnwindAllMenu, KBDK_UP, MOD3},
{Event::HighScoresMenuMode, KBDK_INSERT},
{Event::TogglePlayBackMode, KBDK_SPACE, KBDM_SHIFT},
#if defined(RETRON77)
{Event::ConsoleColorToggle, KBDK_F4}, // back ("COLOR","B/W")

View File

@ -87,6 +87,9 @@ class PhysicalKeyboardHandler
};
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,
EventMode mode = EventMode::kEmulationMode, bool updateDefaults = false);

View File

@ -29,6 +29,7 @@
#include "TIASurface.hxx"
#include "Version.hxx"
#include "PNGLibrary.hxx"
#include "Rect.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
PNGLibrary::PNGLibrary(OSystem& osystem)
@ -51,7 +52,7 @@ void PNGLibrary::loadImage(const string& filename, FBSurface& surface)
throw runtime_error(s);
};
ifstream in(filename, std::ios_base::binary);
std::ifstream in(filename, std::ios_base::binary);
if(!in.is_open())
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)
{
loadImageERROR("Greyscale PNG images not supported");
png_set_gray_to_rgb(png_ptr);
}
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)
{
ofstream out(filename, std::ios_base::binary);
std::ofstream out(filename, std::ios_base::binary);
if(!out.is_open())
throw runtime_error("ERROR: Couldn't create snapshot file");
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();
// 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,
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())
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_structp png_ptr = nullptr;
@ -290,7 +297,7 @@ void PNGLibrary::takeSnapshot(uInt32 number)
// Figure out the correct snapshot name
string filename;
bool showmessage = number == 0;
string sspath = myOSystem.snapshotSaveDir() +
string sspath = myOSystem.snapshotSaveDir().getPath() +
(myOSystem.settings().getString("snapname") != "int" ?
myOSystem.romFile().getNameWithExt("")
: 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)
{
(static_cast<ifstream*>(png_get_io_ptr(ctx)))->read(
(static_cast<std::ifstream*>(png_get_io_ptr(ctx)))->read(
reinterpret_cast<char *>(area), 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);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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 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,
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() {}
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(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()); }
uInt32 x() const { return left; }

View File

@ -18,6 +18,7 @@
#include <cmath>
#include "OSystem.hxx"
#include "Console.hxx"
#include "Serializer.hxx"
#include "StateManager.hxx"
#include "TIA.hxx"
@ -180,7 +181,8 @@ uInt32 RewindManager::rewindStates(uInt32 numStates)
else
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);
return i;
}
@ -214,7 +216,8 @@ uInt32 RewindManager::unwindStates(uInt32 numStates)
else
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);
return i;
}

View File

@ -18,12 +18,13 @@
#ifndef SOUND_NULL_HXX
#define SOUND_NULL_HXX
class OSystem;
class AudioQueue;
class EmulationTiming;
#include "bspf.hxx"
#include "Logger.hxx"
#include "Sound.hxx"
#include "OSystem.hxx"
#include "AudioQueue.hxx"
#include "EmulationTiming.hxx"
/**
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.
@param direction Increase or decrease the current volume by a predefined
amount based on the direction (1 = increase, -1 =decrease)
@param direction +1 indicates increase, -1 indicates 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.

View File

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

View File

@ -24,12 +24,12 @@ class OSystem;
class AudioQueue;
class EmulationTiming;
class AudioSettings;
class Resampler;
#include "SDL_lib.hxx"
#include "bspf.hxx"
#include "Sound.hxx"
#include "audio/Resampler.hxx"
/**
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.
@param direction Increase or decrease the current volume by a predefined
amount based on the direction (1 = increase, -1 = decrease)
@param direction +1 indicates increase, -1 indicates decrease.
*/
void adjustVolume(Int8 direction) override;
void adjustVolume(int direction = 1) override;
/**
This method is called to provide information about the sound device.
@ -109,6 +108,13 @@ class SoundSDL2 : public Sound
string about() const override;
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.
The stream is 16-bits (even though the callback is 8-bits), since
@ -140,6 +146,8 @@ class SoundSDL2 : public Sound
// Audio specification structure
SDL_AudioSpec myHardwareSpec;
uInt32 myDeviceId{0};
SDL_AudioDeviceID myDevice{0};
shared_ptr<AudioQueue> myAudioQueue;

View File

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

View File

@ -299,15 +299,14 @@ void StateManager::saveState(int slot)
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void StateManager::changeState(int direction)
{
myCurrentSlot += direction;
if (myCurrentSlot < 0)
myCurrentSlot = 9;
else
myCurrentSlot %= 10;
myCurrentSlot = BSPF::clampw(myCurrentSlot + direction, 0, 9);
// Print appropriate message
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());
}

View File

@ -18,7 +18,7 @@
#ifndef STATE_MANAGER_HXX
#define STATE_MANAGER_HXX
#define STATE_HEADER "06000008state"
#define STATE_HEADER "06020100state"
class OSystem;
class RewindManager;
@ -115,8 +115,10 @@ class StateManager
/**
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.

View File

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

View File

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

View File

@ -297,7 +297,7 @@ class ZipHandler
void addToCache();
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
ZipFilePtr myZip;

View File

@ -43,6 +43,7 @@ using uInt64 = uint64_t;
#include <algorithm>
#include <iostream>
#include <fstream>
#include <functional>
#include <iomanip>
#include <memory>
#include <string>
@ -63,8 +64,6 @@ using std::istream;
using std::ostream;
using std::fstream;
using std::iostream;
using std::ifstream;
using std::ofstream;
using std::ostringstream;
using std::istringstream;
using std::stringstream;
@ -84,11 +83,14 @@ using ByteArray = std::vector<uInt8>;
using ShortArray = std::vector<uInt16>;
using StringList = std::vector<std::string>;
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
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("");
@ -97,6 +99,12 @@ static const string EmptyString("");
#undef PAGE_SIZE
#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
{
static constexpr float PI_f = 3.141592653589793238462643383279502884F;
@ -118,20 +126,39 @@ namespace BSPF
static const string ARCH = "NOARCH";
#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
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>;
// Combines 'max' and 'min', and clamps value to the upper/lower value
// 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;
}
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;
}
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
inline const string& toUpperCase(string& s)

View File

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

View File

@ -28,8 +28,8 @@ namespace {
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
KeyValueRepositoryConfigfile::KeyValueRepositoryConfigfile(const string& filename)
: myFilename(filename)
KeyValueRepositoryConfigfile::KeyValueRepositoryConfigfile(const FilesystemNode& file)
: myFile(file)
{
}
@ -41,10 +41,14 @@ std::map<string, Variant> KeyValueRepositoryConfigfile::load()
string line, key, value;
string::size_type equalPos, garbage;
ifstream in(myFilename);
if(!in || !in.is_open()) {
Logger::error("ERROR: Couldn't load from settings file " + myFilename);
stringstream in;
try
{
myFile.read(in);
}
catch(...)
{
Logger::error("ERROR: Couldn't load from settings file " + myFile.getShortPath());
return values;
}
@ -79,13 +83,7 @@ std::map<string, Variant> KeyValueRepositoryConfigfile::load()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void KeyValueRepositoryConfigfile::save(const std::map<string, Variant>& values)
{
ofstream out(myFilename);
if(!out || !out.is_open()) {
Logger::error("ERROR: Couldn't save to settings file " + myFilename);
return;
}
stringstream out;
out << "; Stella configuration file" << endl
<< ";" << 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
for(const auto& pair: values)
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
#define KEY_VALUE_REPOSITORY_CONFIGFILE_HXX
#include "FSNode.hxx"
#include "KeyValueRepository.hxx"
class KeyValueRepositoryConfigfile : public KeyValueRepository
{
public:
explicit KeyValueRepositoryConfigfile(const string& filename);
explicit KeyValueRepositoryConfigfile(const FilesystemNode& file);
std::map<string, Variant> load() override;
@ -34,7 +35,7 @@ class KeyValueRepositoryConfigfile : public KeyValueRepository
private:
const string& myFilename;
FilesystemNode myFile;
};
#endif // KEY_VALUE_REPOSITORY_CONFIGFILE_HXX

View File

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

View File

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

View File

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

View File

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

View File

@ -57,9 +57,15 @@ void AtariNTSC::generateKernels()
const uInt8* ptr = myRGBPalette.data();
for(size_t entry = 0; entry < myRGBPalette.size() / 3; ++entry)
{
#ifdef BLARGG_PALETTE
float r = myImpl.to_float[*ptr++],
g = 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 );
// 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)
{
#ifdef BLARGG_PALETTE
impl.brightness = setup.brightness * (0.5F * rgb_unit) + rgb_offset;
impl.contrast = setup.contrast * (0.5F * rgb_unit) + rgb_unit;
#endif
impl.artifacts = setup.artifacts;
if ( impl.artifacts > 0 )
@ -334,6 +342,7 @@ void AtariNTSC::init(init_t& impl, const Setup& setup)
initFilters(impl, setup);
#ifdef BLARGG_PALETTE
/* generate gamma table */
if (true) /* was (gamma_size > 1) */
{
@ -341,19 +350,22 @@ void AtariNTSC::init(init_t& impl, const Setup& setup)
float const gamma = 1.1333F - setup.gamma * 0.5F;
/* match common PC's 2.2 gamma to TV's 2.65 gamma */
int i;
for ( i = 0; i < gamma_size; i++ )
impl.to_float [i] =
powf( i * to_float, gamma ) * impl.contrast + impl.brightness;
for(i = 0; i < gamma_size; i++)
impl.to_float[i] =
powf(i * to_float, gamma) * impl.contrast + impl.brightness;
}
#endif
/* setup decoder matricies */
{
#ifdef BLARGG_PALETTE
float hue = setup.hue * BSPF::PI_f + BSPF::PI_f / 180 * ext_decoder_hue;
float sat = setup.saturation + 1;
hue += BSPF::PI_f / 180 * (std_decoder_hue - ext_decoder_hue);
float s = sinf( hue ) * sat;
float c = cosf( hue ) * sat;
float s = sinf(hue)*sat;
float c = cosf(hue)*sat;
#endif
float* out = impl.to_rgb.data();
int n;
@ -366,8 +378,13 @@ void AtariNTSC::init(init_t& impl, const Setup& setup)
{
float i = *in++;
float q = *in++;
#ifdef BLARGG_PALETTE
*out++ = i * c - q * s;
*out++ = i * s + q * c;
#else
*out++ = i ;
*out++ = q;
#endif
}
while ( --n2 );
#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 = {
#ifdef BLARGG_PALETTE
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 = {
#ifdef BLARGG_PALETTE
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 = {
#ifdef BLARGG_PALETTE
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 = {
#ifdef BLARGG_PALETTE
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 "bspf.hxx"
//#define BLARGG_PALETTE // also modify contrast, brightness, saturation, gamma and hue when defined
class AtariNTSC
{
public:
@ -57,14 +59,18 @@ class AtariNTSC
struct Setup
{
// Basic parameters
#ifdef BLARGG_PALETTE
float hue{0.F}; // -1 = -180 degrees +1 = +180 degrees
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 brightness{0.F}; // -1 = dark (0.5) +1 = light (1.5)
#endif
float sharpness{0.F}; // edge contrast enhancement/blurring
// Advanced parameters
#ifdef BLARGG_PALETTE
float gamma{0.F}; // -1 = dark (1.5) +1 = light (0.5)
#endif
float resolution{0.F}; // image resolution
float artifacts{0.F}; // artifacts caused by color changes
float fringing{0.F}; // color artifacts caused by brightness changes
@ -127,7 +133,9 @@ class AtariNTSC
burst_size = entry_size / burst_count,
kernel_half = 16,
kernel_size = kernel_half * 2 + 1,
#ifdef BLARGG_PALETTE
gamma_size = 256,
#endif
rgb_builder = ((1 << 21) | (1 << 11) | (1 << 1)),
rgb_kernel_size = burst_size / alignment_count,
@ -162,16 +170,20 @@ class AtariNTSC
struct init_t
{
std::array<float, burst_count * 6> to_rgb{0.F};
#ifdef BLARGG_PALETTE
std::array<float, gamma_size> to_float{0.F};
float contrast{0.F};
float brightness{0.F};
#endif
float artifacts{0.F};
float fringing{0.F};
std::array<float, rescale_out * kernel_size * 2> kernel{0.F};
init_t() {
to_rgb.fill(0.0);
#ifdef BLARGG_PALETTE
to_float.fill(0.0);
#endif
kernel.fill(0.0);
}
};

View File

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

View File

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

View File

@ -32,7 +32,10 @@
#include "CartRamWidget.hxx"
#include "RomWidget.hxx"
#include "Base.hxx"
#include "Device.hxx"
#include "exception/EmulationWarning.hxx"
#include "TIA.hxx"
#include "M6532.hxx"
using Common::Base;
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
// 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
// Banksizes less than 4K use the actual value
size_t banksize = 0;
myConsole.cartridge().getImage(banksize);
// ROM sizes less than 4K use the actual value
size_t romSize = 0;
myConsole.cartridge().getImage(romSize);
BankInfo info;
info.size = std::min<size_t>(banksize, 4_KB);
for(uInt32 i = 0; i < myConsole.cartridge().bankCount(); ++i)
info.size = std::min<size_t>(romSize, 4_KB);
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);
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
myBankInfo[myConsole.cartridge().startBank()].addressList.push_front(
myDebugger.dpeek(0xfffc));
addLabel("Start", myDebugger.dpeek(0xfffc, DATA));
addLabel("Start", myDebugger.dpeek(0xfffc, Device::DATA)); // TOOD: ::CODE???
// Add system equates
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()
{
@ -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
// Also check if the current PC is in the current list
bool bankChanged = myConsole.cartridge().bankChanged();
uInt16 PC = myDebugger.cpuDebug().pc();
int pcline = addressToLine(PC);
bool pcfound = (pcline != -1) && (uInt32(pcline) < myDisassembly.list.size()) &&
(myDisassembly.list[pcline].disasm[0] != '.');
@ -241,8 +283,9 @@ bool CartDebug::disassemble(bool force)
if(changed)
{
// Are we disassembling from ROM or ZP RAM?
BankInfo& info = (PC & 0x1000) ? myBankInfo[getBank(PC)] :
myBankInfo[myBankInfo.size()-1];
BankInfo& info = myBankInfo[bank];
//(PC & 0x1000) ? myBankInfo[getBank(PC)] :
//myBankInfo[myBankInfo.size()-1];
// 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
@ -304,8 +347,8 @@ bool CartDebug::fillDisassemblyList(BankInfo& info, uInt16 search)
const DisassemblyTag& tag = myDisassembly.list[i];
const uInt16 address = tag.address & 0xFFF;
// Exclude 'ROW'; they don't have a valid address
if(tag.type != CartDebug::ROW)
// Exclude 'Device::ROW'; they don't have a valid address
if(tag.type != Device::ROW)
{
// Create a mapping from addresses to line numbers
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
start &= 0xFFF;
@ -346,7 +389,7 @@ string CartDebug::disassemble(uInt16 start, uInt16 lines) const
if((tag.address & 0xfff) >= start)
{
if(begin == list_size) begin = end;
if(tag.type != CartDebug::ROW)
if(tag.type != Device::ROW)
length = std::max(length, uInt32(tag.disasm.length()));
--lines;
@ -357,7 +400,7 @@ string CartDebug::disassemble(uInt16 start, uInt16 lines) const
for(uInt32 i = begin; i < end; ++i)
{
const CartDebug::DisassemblyTag& tag = myDisassembly.list[i];
if(tag.type == CartDebug::NONE)
if(tag.type == Device::NONE)
continue;
else if(tag.address)
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)
{
if(end < start || start == 0 || end == 0)
@ -384,7 +427,7 @@ bool CartDebug::addDirective(CartDebug::DisasmType type,
bank = (myDebugger.cpuDebug().pc() & 0x1000) ?
getBank(myDebugger.cpuDebug().pc()) : int(myBankInfo.size())-1;
bank = std::min(bank, bankCount());
bank = std::min(bank, romBankCount());
BankInfo& info = myBankInfo[bank];
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
// actual ROM filename
if(myListFile == "")
{
FilesystemNode lst(myOSystem.romFile().getPathWithExt("") + ".lst");
if(lst.isFile() && lst.isReadable())
myListFile = lst.getPath();
else
FilesystemNode lst(myOSystem.romFile().getPathWithExt(".lst"));
if(!lst.isReadable())
return DebuggerParser::red("list file \'" + lst.getShortPath() + "\' not found");
}
FilesystemNode node(myListFile);
ifstream in(node.getPath());
if(!in.is_open())
return DebuggerParser::red("list file '" + node.getShortPath() + "' not readable");
stringstream in;
try
{
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())
{
@ -747,7 +791,7 @@ string CartDebug::loadListFile()
}
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
// actual ROM filename
if(mySymbolFile == "")
{
FilesystemNode sym(myOSystem.romFile().getPathWithExt("") + ".sym");
if(sym.isFile() && sym.isReadable())
mySymbolFile = sym.getPath();
else
FilesystemNode sym(myOSystem.romFile().getPathWithExt(".sym"));
if(!sym.isReadable())
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();
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())
{
string label;
@ -807,28 +852,29 @@ string CartDebug::loadSymbolFile()
}
myDebugger.rom().invalidate();
return "symbol file '" + node.getShortPath() + "' loaded OK";
return "symbol file '" + sym.getShortPath() + "' loaded OK";
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::loadConfigFile()
{
// The default naming/location for config files is the ROM dir based on the
// actual ROM filename
// The default naming/location for config files is the CFG dir and based
// on the actual ROM filename
if(myCfgFile == "")
{
FilesystemNode cfg(myOSystem.romFile().getPathWithExt("") + ".cfg");
if(cfg.isFile() && cfg.isReadable())
myCfgFile = cfg.getPath();
else
FilesystemNode romNode(myOSystem.romFile().getPathWithExt(".cfg"));
FilesystemNode cfg = myOSystem.cfgDir(); cfg /= romNode.getName();
if(!cfg.isReadable())
return DebuggerParser::red("config file \'" + cfg.getShortPath() + "\' not found");
}
FilesystemNode node(myCfgFile);
ifstream in(node.getPath());
if(!in.is_open())
return "Unable to load directives from " + node.getPath();
stringstream in;
try
{
cfg.read(in);
}
catch(...)
{
return "Unable to load directives from " + cfg.getPath();
}
// Erase all previous directives
for(auto& bi: myBankInfo)
@ -876,37 +922,57 @@ string CartDebug::loadConfigFile()
else if(BSPF::startsWithIgnoreCase(directive, "CODE"))
{
buf >> hex >> start >> hex >> end;
addDirective(CartDebug::CODE, start, end, currentbank);
addDirective(Device::CODE, start, end, currentbank);
}
else if(BSPF::startsWithIgnoreCase(directive, "GFX"))
{
buf >> hex >> start >> hex >> end;
addDirective(CartDebug::GFX, start, end, currentbank);
addDirective(Device::GFX, start, end, currentbank);
}
else if(BSPF::startsWithIgnoreCase(directive, "PGFX"))
{
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"))
{
buf >> hex >> start >> hex >> end;
addDirective(CartDebug::DATA, start, end, currentbank);
addDirective(Device::DATA, start, end, currentbank);
}
else if(BSPF::startsWithIgnoreCase(directive, "ROW"))
{
buf >> hex >> start;
buf >> hex >> end;
addDirective(CartDebug::ROW, start, end, currentbank);
addDirective(Device::ROW, start, end, currentbank);
}
}
}
myDebugger.rom().invalidate();
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 << "config file '" << node.getShortPath() << "' loaded OK";
retVal << "config file '" << cfg.getShortPath() << "' loaded OK";
return retVal.str();
}
@ -914,69 +980,72 @@ string CartDebug::loadConfigFile()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::saveConfigFile()
{
// The default naming/location for config files is the ROM dir based 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");
}
// The default naming/location for config files is the CFG dir and based
// on the actual ROM filename
const string& name = myConsole.properties().get(PropType::Cart_Name);
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
out << "//Stella.pro: \"" << name << "\"" << endl
<< "//MD5: " << md5 << endl
stringstream out;
out << "// Stella.pro: \"" << name << "\"" << endl
<< "// MD5: " << md5 << endl
<< endl;
for(uInt32 b = 0; b < myConsole.cartridge().bankCount(); ++b)
for(uInt32 b = 0; b < myConsole.cartridge().romBankCount(); ++b)
{
out << "[" << b << "]" << endl;
getBankDirectives(out, myBankInfo[b]);
}
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 << "config file '" << cfg.getShortPath() << "' saved OK";
}
catch(const runtime_error& e)
{
retVal << "Unable to save directives: " << e.what();
}
return retVal.str();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::saveDisassembly()
{
if(myDisasmFile == "")
{
const string& propsname =
myConsole.properties().get(PropType::Cart_Name) + ".asm";
myDisasmFile = FilesystemNode(myOSystem.defaultSaveDir() + propsname).getPath();
}
FilesystemNode node(myDisasmFile);
ofstream out(node.getPath());
if(!out.is_open())
return "Unable to save disassembly to " + node.getShortPath();
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"
};
bool isNTSC = myConsole.timing() == ConsoleTiming::ntsc;
bool isPAL = myConsole.timing() == ConsoleTiming::pal;
#define ALIGN(x) setfill(' ') << left << setw(x)
// We can't print the header to the disassembly until it's actually
// been processed; therefore buffer output to a string first
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
// This will most likely differ from what you see in the debugger
@ -992,13 +1061,34 @@ string CartDebug::saveDisassembly()
Disassembly disasm;
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];
disassembleBank(bank);
// An empty address list means that DiStella can't do a disassembly
if(info.addressList.size() == 0)
continue;
buf << "\n\n;***********************************************************\n"
<< "; Bank " << bank;
if (romBankCount > 1)
buf << " / 0.." << romBankCount - 1;
buf << "\n;***********************************************************\n\n";
// Disassemble bank
disasm.list.clear();
DiStella distella(*this, disasm.list, info, settings,
@ -1007,8 +1097,14 @@ string CartDebug::saveDisassembly()
if (myReserved.breakFound)
addLabel("Break", myDebugger.dpeek(0xfffe));
buf << " SEG CODE\n"
<< " ORG $" << Base::HEX4 << info.offset << "\n\n";
buf << " SEG CODE\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
for(uInt32 i = 0; i < disasm.list.size(); ++i)
@ -1022,53 +1118,66 @@ string CartDebug::saveDisassembly()
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);
if (tag.disasm.find("WSYNC") != std::string::npos)
buf << "\n;---------------------------------------";
break;
}
case CartDebug::ROW:
{
case Device::ROW:
buf << ".byte " << ALIGN(32) << tag.disasm.substr(6, 8*4-1) << "; $" << Base::HEX4 << tag.address << " (*)";
break;
}
case CartDebug::GFX:
{
case Device::GFX:
buf << ".byte " << (settings.gfxFormat == Base::Fmt::_2 ? "%" : "$")
<< tag.bytes << " ; |";
for(int c = 12; c < 20; ++c)
buf << ((tag.disasm[c] == '\x1e') ? "#" : " ");
buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (G)";
break;
}
case CartDebug::PGFX:
{
case Device::PGFX:
buf << ".byte " << (settings.gfxFormat == Base::Fmt::_2 ? "%" : "$")
<< tag.bytes << " ; |";
for(int c = 12; c < 20; ++c)
buf << ((tag.disasm[c] == '\x1f') ? "*" : " ");
buf << ALIGN(13) << "|" << "$" << Base::HEX4 << tag.address << " (P)";
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)";
break;
}
case CartDebug::NONE:
case Device::NONE:
default:
{
break;
}
} // switch
buf << "\n";
}
}
myConsole.cartridge().unlockBank();
myConsole.cartridge().bank(oldBank);
myConsole.cartridge().lockBank();
// Some boilerplate, similar to what DiStella adds
auto timeinfo = BSPF::localTime();
stringstream out;
out << "; Disassembly of " << myOSystem.romFile().getShortPath() << "\n"
<< "; Disassembled " << std::put_time(&timeinfo, "%c\n")
<< "; Using Stella " << STELLA_VERSION << "\n;\n"
@ -1079,19 +1188,45 @@ string CartDebug::saveDisassembly()
<< "; D = DATA directive (referenced in some way)\n"
<< "; G = GFX directive, shown as '#' (stored in player, missile, ball)\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"
<< "; c = used by code executed in RAM\n"
<< "; s = used by stack\n"
<< "; ! = page crossed, 1 cycle penalty\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;
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)
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)
addrUsed = addrUsed || myReserved.IOReadWrite[addr];
if(addrUsed)
{
out << "\n;-----------------------------------------------------------\n"
@ -1103,7 +1238,7 @@ string CartDebug::saveDisassembly()
if(myReserved.TIARead[addr] && ourTIAMnemonicR[addr])
out << ALIGN(16) << ourTIAMnemonicR[addr] << "= $"
<< 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] << "= $"
<< Base::HEX2 << right << addr << " ; (Ri)\n";
out << "\n";
@ -1113,7 +1248,7 @@ string CartDebug::saveDisassembly()
if(myReserved.TIAWrite[addr] && ourTIAMnemonicW[addr])
out << ALIGN(16) << ourTIAMnemonicW[addr] << "= $"
<< 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] << "= $"
<< Base::HEX2 << right << addr << " ; (Wi)\n";
out << "\n";
@ -1128,8 +1263,8 @@ string CartDebug::saveDisassembly()
addrUsed = false;
for(uInt16 addr = 0x80; addr <= 0xFF; ++addr)
addrUsed = addrUsed || myReserved.ZPRAM[addr-0x80]
|| (mySystem.getAccessFlags(addr) & (DATA | WRITE))
|| (mySystem.getAccessFlags(addr|0x100) & (DATA | WRITE));
|| (mySystem.getAccessFlags(addr) & (Device::DATA | Device::WRITE))
|| (mySystem.getAccessFlags(addr|0x100) & (Device::DATA | Device::WRITE));
if(addrUsed)
{
bool addLine = false;
@ -1138,9 +1273,9 @@ string CartDebug::saveDisassembly()
<< ";-----------------------------------------------------------\n\n";
for (uInt16 addr = 0x80; addr <= 0xFF; ++addr) {
bool ramUsed = (mySystem.getAccessFlags(addr) & (DATA | WRITE));
bool codeUsed = (mySystem.getAccessFlags(addr) & CODE);
bool stackUsed = (mySystem.getAccessFlags(addr|0x100) & (DATA | WRITE));
bool ramUsed = (mySystem.getAccessFlags(addr) & (Device::DATA | Device::WRITE));
bool codeUsed = (mySystem.getAccessFlags(addr) & Device::CODE);
bool stackUsed = (mySystem.getAccessFlags(addr|0x100) & (Device::DATA | Device::WRITE));
if (myReserved.ZPRAM[addr - 0x80] &&
myUserLabels.find(addr) == myUserLabels.end()) {
@ -1194,10 +1329,22 @@ string CartDebug::saveDisassembly()
// And finally, output the disassembly
out << buf.str();
const string& propsname =
myConsole.properties().get(PropType::Cart_Name) + ".asm";
FilesystemNode node(myOSystem.defaultSaveDir().getPath() + propsname);
stringstream retVal;
if(myConsole.cartridge().bankCount() > 1)
retVal << DebuggerParser::red("disassembly for multi-bank ROM not fully supported, only currently enabled banks disassembled\n");
try
{
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";
}
catch(...)
{
retVal << "Unable to save disassembly to " << node.getShortPath();
}
return retVal.str();
}
@ -1206,22 +1353,40 @@ string CartDebug::saveRom()
{
const string& rom = myConsole.properties().get(PropType::Cart_Name) + ".a26";
FilesystemNode node(myOSystem.defaultSaveDir() + rom);
ofstream out(node.getPath(), std::ios::binary);
if(out && myConsole.cartridge().saveROM(out))
FilesystemNode node(myOSystem.defaultSaveDir().getPath() + rom);
if(myConsole.cartridge().saveROM(node))
return "saved ROM as " + node.getShortPath();
else
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)
{
if(myConsole.cartridge().bankCount() > 1)
return DebuggerParser::red("config file for multi-bank ROM not yet supported");
uInt32 startbank = 0, endbank = bankCount();
if(bank >= 0 && bank < bankCount())
uInt32 startbank = 0, endbank = romBankCount();
if(bank >= 0 && bank < romBankCount())
{
startbank = bank;
endbank = startbank + 1;
@ -1232,27 +1397,30 @@ string CartDebug::listConfig(int bank)
for(uInt32 b = startbank; b < endbank; ++b)
{
BankInfo& info = myBankInfo[b];
buf << "[" << b << "]" << endl;
buf << "Bank [" << b << "]" << endl;
for(const auto& i: info.directiveList)
{
if(i.type != CartDebug::NONE)
if(i.type != Device::NONE)
{
buf << "(*) ";
disasmTypeAsString(buf, i.type);
AccessTypeAsString(buf, i.type);
buf << " " << Base::HEX4 << i.start << " " << Base::HEX4 << i.end << endl;
}
}
getBankDirectives(buf, info);
}
if(myConsole.cartridge().romBankCount() > 1)
buf << DebuggerParser::red("config file for multi-bank ROM not fully supported") << endl;
return buf.str();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string CartDebug::clearConfig(int bank)
{
uInt32 startbank = 0, endbank = bankCount();
if(bank >= 0 && bank < bankCount())
uInt32 startbank = 0, endbank = romBankCount();
if(bank >= 0 && bank < romBankCount())
{
startbank = bank;
endbank = startbank + 1;
@ -1334,15 +1502,15 @@ void CartDebug::getBankDirectives(ostream& buf, BankInfo& info) const
// Now consider each byte
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)
{
DisasmType currType = disasmTypeAbsolute(mySystem.getAccessFlags(addr));
Device::AccessType currType = accessTypeAbsolute(mySystem.getAccessFlags(addr));
// Have we changed to a new type?
if(currType != prevType)
{
disasmTypeAsString(buf, prevType);
AccessTypeAsString(buf, prevType);
buf << " " << Base::HEX4 << prev << " " << Base::HEX4 << (addr-1) << endl;
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
if(prev != addr)
{
disasmTypeAsString(buf, prevType);
AccessTypeAsString(buf, prevType);
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))
{
@ -1372,70 +1540,88 @@ void CartDebug::addressTypeAsString(ostream& buf, uInt16 addr) const
label = myDisLabels[addr & 0xFFF];
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) << " ";
disasmTypeAsString(buf, debugger);
AccessTypeAsString(buf, debugger);
buf << endl << "tentative: " << Base::toString(label, Base::Fmt::_2_8) << " ";
disasmTypeAsString(buf, label);
AccessTypeAsString(buf, label);
buf << endl;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CartDebug::DisasmType CartDebug::disasmTypeAbsolute(uInt8 flags) const
Device::AccessType CartDebug::accessTypeAbsolute(Device::AccessFlags flags) const
{
if(flags & CartDebug::CODE)
return CartDebug::CODE;
else if(flags & CartDebug::TCODE)
return CartDebug::CODE; // TODO - should this be separate??
else if(flags & CartDebug::GFX)
return CartDebug::GFX;
else if(flags & CartDebug::PGFX)
return CartDebug::PGFX;
else if(flags & CartDebug::DATA)
return CartDebug::DATA;
else if(flags & CartDebug::ROW)
return CartDebug::ROW;
if(flags & Device::CODE)
return Device::CODE;
else if(flags & Device::TCODE)
return Device::CODE; // TODO - should this be separate??
else if(flags & Device::GFX)
return Device::GFX;
else if(flags & Device::PGFX)
return Device::PGFX;
else if(flags & Device::COL)
return Device::COL;
else if(flags & Device::PCOL)
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
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)
{
case CartDebug::CODE: buf << "CODE"; break;
case CartDebug::TCODE: buf << "TCODE"; break;
case CartDebug::GFX: buf << "GFX"; break;
case CartDebug::PGFX: buf << "PGFX"; break;
case CartDebug::DATA: buf << "DATA"; break;
case CartDebug::ROW: buf << "ROW"; break;
case CartDebug::REFERENCED:
case CartDebug::VALID_ENTRY:
case CartDebug::NONE: break;
case Device::CODE: buf << "CODE"; break;
case Device::TCODE: buf << "TCODE"; break;
case Device::GFX: buf << "GFX"; break;
case Device::PGFX: buf << "PGFX"; break;
case Device::COL: buf << "COL"; break;
case Device::PCOL: buf << "PCOL"; break;
case Device::BCOL: buf << "BCOL"; break;
case Device::AUD: buf << "AUD"; 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 & CartDebug::CODE)
if(flags & Device::CODE)
buf << "CODE ";
if(flags & CartDebug::TCODE)
if(flags & Device::TCODE)
buf << "TCODE ";
if(flags & CartDebug::GFX)
if(flags & Device::GFX)
buf << "GFX ";
if(flags & CartDebug::PGFX)
if(flags & Device::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 ";
if(flags & CartDebug::ROW)
if(flags & Device::ROW)
buf << "ROW ";
if(flags & CartDebug::REFERENCED)
if(flags & Device::REFERENCED)
buf << "*REFERENCED ";
if(flags & CartDebug::VALID_ENTRY)
if(flags & Device::VALID_ENTRY)
buf << "*VALID_ENTRY ";
}
else

View File

@ -31,6 +31,7 @@ using CartMethod = int (CartDebug::*)();
#include "bspf.hxx"
#include "DebuggerSystem.hxx"
#include "Device.hxx"
class CartState : public DebuggerState
{
@ -47,30 +48,8 @@ class CartDebug : public DebuggerSystem
friend class DiStella;
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 {
DisasmType type{NONE};
Device::AccessType type{Device::NONE};
uInt16 address{0};
string label;
string disasm;
@ -104,28 +83,55 @@ class CartDebug : public DebuggerSystem
CartDebugWidget* getDebugWidget() const { return myDebugWidget; }
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
int lastReadBaseAddress();
// Return the base (= non-mirrored) address of the last CPU write
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
@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(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()
*/
@ -149,7 +155,7 @@ class CartDebug : public DebuggerSystem
@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
@ -157,14 +163,14 @@ class CartDebug : public DebuggerSystem
things can't be automatically determined. For now, these directives
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 end The end address (inclusive) to mark with the given type
@param bank Bank to which these directive apply (0 indicated current bank)
@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);
// 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.
*/
int bankCount() const;
int romBankCount() const;
/**
Add a label and associated address.
@ -231,6 +237,11 @@ class CartDebug : public DebuggerSystem
string saveDisassembly();
string saveRom();
/**
Save access counters file
*/
string saveAccessFile();
/**
Show Distella directives (both set by the user and determined by Distella)
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;
// Convert given address to corresponding disassembly type and append to buf
void addressTypeAsString(ostream& buf, uInt16 addr) const;
// Convert given address to corresponding access type and append to buf
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:
using AddrToLabel = std::map<uInt16, string>;
using LabelToAddr = std::map<string, uInt16,
std::function<bool(const string&, const string&)>>;
using AddrTypeArray = std::array<uInt8, 0x1000>;
using AddrTypeArray = std::array<uInt16, 0x1000>;
struct DirectiveTag {
DisasmType type{NONE};
Device::AccessType type{Device::NONE};
uInt16 start{0};
uInt16 end{0};
};
@ -290,6 +304,18 @@ class CartDebug : public DebuggerSystem
};
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
// Return whether the search address was actually in the list
bool fillDisassemblyList(BankInfo& bankinfo, uInt16 search);
@ -298,15 +324,12 @@ class CartDebug : public DebuggerSystem
// based on its disassembly
void getBankDirectives(ostream& buf, BankInfo& info) const;
// Get disassembly enum type from 'flags', taking precendence into account
DisasmType disasmTypeAbsolute(uInt8 flags) const;
// Get access enum type from 'flags', taking precendence into account
Device::AccessType accessTypeAbsolute(Device::AccessFlags flags) const;
// Convert disassembly enum type to corresponding string and append to buf
void disasmTypeAsString(ostream& buf, DisasmType type) const;
// Convert all disassembly types in 'flags' to corresponding string and
// Convert all access types in 'flags' to corresponding string and
// append to buf
void disasmTypeAsString(ostream& buf, uInt8 flags) const;
void AccessTypeAsString(ostream& buf, Device::AccessFlags flags) const;
private:
const OSystem& myOSystem;
@ -346,9 +369,6 @@ class CartDebug : public DebuggerSystem
// The maximum length of all labels currently defined
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
static std::array<const char*, 16> ourTIAMnemonicR; // read mode
static std::array<const char*, 64> ourTIAMnemonicW; // write mode

View File

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

View File

@ -31,7 +31,7 @@ class CpuState : public DebuggerState
{
public:
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;
};

View File

@ -115,7 +115,8 @@ void Debugger::initialize()
FBInitStatus Debugger::initializeVideo()
{
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;
// autoexec.script is always run
FilesystemNode autoexec(myOSystem.baseDir() + "autoexec.script");
FilesystemNode autoexec(myOSystem.baseDir().getPath() + "autoexec.script");
buf << "autoExec():" << endl
<< myParser->exec(autoexec, history) << endl;
@ -292,8 +293,9 @@ void Debugger::loadAllStates()
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int Debugger::step()
int Debugger::step(bool save)
{
if(save)
saveOldState();
uInt64 startCycle = mySystem.cycles();
@ -302,6 +304,7 @@ int Debugger::step()
myOSystem.console().tia().updateScanlineByStep().flushLineCache();
lockSystem();
if(save)
addState("step");
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);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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));
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Debugger::poke(uInt16 addr, uInt8 value, uInt8 flags)
void Debugger::poke(uInt16 addr, uInt8 value, Device::AccessFlags 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);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int Debugger::dpeekAsInt(int addr, uInt8 flags)
int Debugger::dpeekAsInt(int addr, Device::AccessFlags flags)
{
return mySystem.peek(uInt16(addr), flags) |
(mySystem.peek(uInt16(addr+1), flags) << 8);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
int Debugger::getAccessFlags(uInt16 addr) const
Device::AccessFlags Debugger::getAccessFlags(uInt16 addr) const
{
return mySystem.getAccessFlags(addr);
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Debugger::setAccessFlags(uInt16 addr, uInt8 flags)
void Debugger::setAccessFlags(uInt16 addr, Device::AccessFlags flags)
{
mySystem.setAccessFlags(addr, flags);
}
@ -688,6 +691,7 @@ void Debugger::setStartState()
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Debugger::setQuitState()
{
myDialog->saveConfig();
saveOldState();
// 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
std::array<Debugger::PseudoRegister, 11> Debugger::ourPseudoRegisters = { {
std::array<Debugger::PseudoRegister, 12> Debugger::ourPseudoRegisters = { {
// Debugger::PseudoRegister Debugger::ourPseudoRegisters[NUM_PSEUDO_REGS] = {
{ "_bank", "Currently selected bank" },
{ "_cclocks", "Color clocks on current scanline" },
@ -883,10 +887,13 @@ std::array<Debugger::PseudoRegister, 11> Debugger::ourPseudoRegisters = { {
{ "_fcycles", "Number of cycles since frame started" },
{ "_icycles", "Number of cycles of last instruction" },
{ "_scan", "Current scanline count" },
{ "_scanend", "Scanline count at end of last frame" },
{ "_scycles", "Number of cycles in current scanline" },
{ "_vblank", "Whether vertical blank is enabled (1 or 0)" },
{ "_vsync", "Whether vertical sync is enabled (1 or 0)" }
// CPU address access functions:
/*{ "__lastread", "last CPU read address" },
{ "__lastwrite", "last CPU write address" },*/
/*{ "_lastread", "last CPU read 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 "DebuggerDialog.hxx"
#include "FrameBufferConstants.hxx"
#include "Cart.hxx"
#include "bspf.hxx"
/**
@ -243,18 +244,18 @@ class Debugger : public DialogContainer
static Debugger& debugger() { return *myStaticDebugger; }
/** Convenience methods to access peek/poke from System */
uInt8 peek(uInt16 addr, uInt8 flags = 0);
uInt16 dpeek(uInt16 addr, uInt8 flags = 0);
void poke(uInt16 addr, uInt8 value, uInt8 flags = 0);
uInt8 peek(uInt16 addr, Device::AccessFlags flags = Device::NONE);
uInt16 dpeek(uInt16 addr, Device::AccessFlags flags = Device::NONE);
void poke(uInt16 addr, uInt8 value, Device::AccessFlags flags = Device::NONE);
/** Convenience method to access the 6502 from System */
M6502& m6502() const;
/** These are now exposed so Expressions can use them. */
int peekAsInt(int addr, uInt8 flags = 0);
int dpeekAsInt(int addr, uInt8 flags = 0);
int getAccessFlags(uInt16 addr) const;
void setAccessFlags(uInt16 addr, uInt8 flags);
int peekAsInt(int addr, Device::AccessFlags flags = Device::NONE);
int dpeekAsInt(int addr, Device::AccessFlags flags = Device::NONE);
Device::AccessFlags getAccessFlags(uInt16 addr) const;
void setAccessFlags(uInt16 addr, Device::AccessFlags flags);
uInt32 getBaseAddress(uInt32 addr, bool read);
@ -305,7 +306,7 @@ class Debugger : public DialogContainer
*/
void setQuitState();
int step();
int step(bool save = true);
int trace();
void nextScanline(int lines);
void nextFrame(int frames);
@ -362,7 +363,7 @@ class Debugger : public DialogContainer
string name, help;
};
static std::array<BuiltinFunction, 18> ourBuiltinFunctions;
static std::array<PseudoRegister, 11> ourPseudoRegisters;
static std::array<PseudoRegister, 12> ourPseudoRegisters;
private:
// rewind/unwind n states

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@ -22,6 +22,7 @@
#include "Base.hxx"
#include "CartDebug.hxx"
#include "Device.hxx"
#include "bspf.hxx"
/**
@ -71,11 +72,26 @@ class DiStella
CartDebug::AddrTypeArray& directives,
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:
// Indicate that a new line of disassembly has been completed
// In the original Distella code, this indicated a new line to be printed
// 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
// Directives are basically the contents of a distella configuration file
@ -87,32 +103,32 @@ class DiStella
void disasmFromAddress(uInt32 distart);
bool check_range(uInt16 start, uInt16 end) const;
int mark(uInt32 address, uInt8 mask, bool directive = false);
bool checkBit(uInt16 address, uInt8 mask, bool useDebugger = true) const;
bool checkBits(uInt16 address, uInt8 mask, uInt8 notMask, bool useDebugger = true) const;
AddressType mark(uInt32 address, uInt16 mask, bool directive = false);
bool checkBit(uInt16 address, uInt16 mask, bool useDebugger = true) const;
bool checkBits(uInt16 address, uInt16 mask, uInt16 notMask, bool useDebugger = true) const;
void outputGraphics();
void outputBytes(CartDebug::DisasmType type);
void outputColors();
void outputBytes(Device::AccessType type);
// 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))
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);
if (labfound == 2)
if (labfound == AddressType::TIA)
{
if(ourLookup[op].rw_mode == RWMode::READ)
myReserved.TIARead[addr & 0x0F] = true;
else
myReserved.TIAWrite[addr & 0x3F] = true;
}
else if (labfound == 3)
else if (labfound == AddressType::RIOT)
myReserved.IOReadWrite[(addr & 0xFF) - 0x80] = true;
else if (labfound == 5)
else if (labfound == AddressType::ZP_RAM)
myReserved.ZPRAM[(addr & 0xFF) - 0x80] = true;
}

View File

@ -16,80 +16,25 @@
//============================================================================
#include "Cart0840.hxx"
#include "PopUpWidget.hxx"
#include "Cart0840Widget.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cartridge0840Widget::Cartridge0840Widget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
int x, int y, int w, int h, Cartridge0840& cart)
: CartDebugWidget(boss, lfont, nfont, x, y, w, h),
myCart(cart)
: CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart)
{
uInt16 size = 2 * 4096;
myHotspotDelta = 0x40;
initialize();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge0840Widget::description()
{
ostringstream info;
info << "0840 ECONObanking, two 4K banks\n"
<< "Startup bank = " << cart.startBank() << "\n";
// Eventually, we should query this from the debugger/disassembler
for(uInt32 i = 0, offset = 0xFFC, spot = 0x800; i < 2;
++i, offset += 0x1000, spot += 0x40)
{
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";
}
info << "0840 ECONObanking, two 4K banks\n";
info << CartridgeEnhancedWidget::description();
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();
return info.str();
}

View File

@ -19,11 +19,10 @@
#define CARTRIDGE0840_WIDGET_HXX
class Cartridge0840;
class PopUpWidget;
#include "CartDebugWidget.hxx"
#include "CartEnhancedWidget.hxx"
class Cartridge0840Widget : public CartDebugWidget
class Cartridge0840Widget : public CartridgeEnhancedWidget
{
public:
Cartridge0840Widget(GuiObject* boss, const GUI::Font& lfont,
@ -33,17 +32,11 @@ class Cartridge0840Widget : public CartDebugWidget
virtual ~Cartridge0840Widget() = default;
private:
Cartridge0840& myCart;
PopUpWidget* myBank{nullptr};
string manufacturer() override { return "Fred X. Quimby"; }
enum { kBankChanged = 'bkCH' };
string description() override;
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
Cartridge0840Widget() = delete;
Cartridge0840Widget(const Cartridge0840Widget&) = delete;

View File

@ -22,15 +22,18 @@
Cartridge2KWidget::Cartridge2KWidget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
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
size_t size = cart.mySize;
uInt16 start = (cart.myImage[size-3] << 8) | cart.myImage[size-4];
start -= start % size;
ostringstream info;
info << "Standard 2K cartridge, non-bankswitched\n"
<< "Accessible @ $" << Common::Base::HEX4 << start << " - " << "$" << (start + size - 1);
addBaseInformation(size, "Atari", info.str());
initialize();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge2KWidget::description()
{
ostringstream info;
info << "Standard 2K cartridge, non-bankswitched\n";
info << CartridgeEnhancedWidget::description();
return info.str();
}

View File

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

View File

@ -18,320 +18,230 @@
#include "Cart3EPlus.hxx"
#include "EditTextWidget.hxx"
#include "PopUpWidget.hxx"
#include "Cart3EPlusWidget.hxx"
#include "CartEnhancedWidget.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cartridge3EPlusWidget::Cartridge3EPlusWidget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
int x, int y, int w, int h, Cartridge3EPlus& cart)
: CartDebugWidget(boss, lfont, nfont, x, y, w, h),
myCart(cart)
: CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart),
myCart3EP(cart)
{
size_t size = cart.mySize;
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;
}
initialize();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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)
myOldState.internalram.push_back(myCart.myRAM[i]);
info << "3E+ cartridge - (4" << ELLIPSIS << "64K ROM + RAM)\n"
<< " " << 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()
{
CartridgeEnhancedWidget::loadConfig();
updateUIState();
CartDebugWidget::loadConfig();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EPlusWidget::handleCommand(CommandSender* sender,
int cmd, int data, int id)
{
uInt16 segment = 0;
const uInt16 segment = id;
switch(cmd)
{
case kBank0Changed:
segment = 0;
break;
case kBank1Changed:
segment = 1;
break;
case kBank2Changed:
segment = 2;
break;
case kBank3Changed:
segment = 3;
break;
default:
case kBankChanged:
case kRomRamChanged:
{
const bool isROM = myBankType[segment]->getSelectedTag() == "ROM";
int bank = myBankWidgets[segment]->getSelected();
myBankCommit[segment]->setEnabled((isROM && bank < myCart.romBankCount())
|| (!isROM && bank < myCart.ramBankCount()));
break;
}
case kChangeBank:
{
// 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)
return;
uInt8 bank = (segment << myCart.BANK_BITS) |
(myBankNumber[segment]->getSelected() & myCart.BIT_BANK_MASK);
uInt8 bank = myBankWidgets[segment]->getSelected();
myCart.unlockBank();
if(myBankType[segment]->getSelectedTag() == "ROM")
myCart.bankROM(bank);
myCart.bank(bank, segment);
else
myCart.bankRAM(bank);
myCart.bank(bank + myCart.romBankCount(), segment);
myCart.lockBank();
invalidate();
updateUIState();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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!";
break;
}
else
{
int bankno = bank & myCart.BIT_BANK_MASK;
if(bank & myCart.BITMASK_ROMRAM) // was RAM mapped here?
buf << " RAM " << bankno;
else
buf << " ROM " << bankno;
default:
break;
}
if(i < 3)
buf << " /";
}
return buf.str();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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)
for(int i = 0; i < 8; ++i)
{
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
for(int seg = 0; seg < myCart3EP.myBankSegs; ++seg)
{
uInt16 bank = myCart.getSegmentBank(seg);
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
{
buf << "RAM " << bankno << " @ $" << Common::Base::HEX4
<< (bankno << myCart.RAM_BANK_TO_POWER) << " (W)";
myBankState[i]->setText(buf.str());
uInt16 ramBank = bank - myCart.romBankCount();
buf << "RAM @ $" << Common::Base::HEX4
<< (ramBank << myCart3EP.myBankShift) << " (R)";
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
{
buf << "RAM " << bankno << " @ $" << Common::Base::HEX4
<< (bankno << myCart.RAM_BANK_TO_POWER) << " (R)";
myBankState[i]->setText(buf.str());
}
buf << "ROM @ $" << Common::Base::HEX4
<< ((bank << myCart3EP.myBankShift));
myBankState[seg * 2]->setText(buf.str());
if(i % 2 == 0)
{
myBankNumber[i/2]->setSelected(bankno);
myBankType[i/2]->setSelected("RAM");
}
}
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());
}
buf.str("");
buf << "ROM @ $" << Common::Base::HEX4
<< ((bank << myCart3EP.myBankShift) + myCart3EP.myBankSize);
myBankState[seg * 2 + 1]->setText(buf.str());
if(i % 2 == 0)
{
myBankNumber[i/2]->setSelected(bankno);
myBankType[i/2]->setSelected("ROM");
myBankWidgets[seg]->setSelectedIndex(bank);
myBankType[seg]->setSelected("ROM");
}
}
}
}
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 Cartridge3EPlusWidget::internalRamSize()
{
return 32*1024;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
uInt32 Cartridge3EPlusWidget::internalRamRPort(int start)
{
return 0x0000 + start;
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge3EPlusWidget::internalRamDescription()
{
ostringstream desc;
desc << "Accessible 512b at a time via:\n"
<< " $f000/$f400/$f800/$fc00 for Read Access\n"
<< " $f200/$f600/$fa00/$fe00 for Write Access (+$200)";
<< " $f000/$f400/$f800/$fc00 for read access\n"
<< " $f200/$f600/$fa00/$fe00 for write access";
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 PopUpWidget;
#include "CartDebugWidget.hxx"
#include "CartEnhancedWidget.hxx"
class Cartridge3EPlusWidget : public CartDebugWidget
class Cartridge3EPlusWidget : public CartridgeEnhancedWidget
{
public:
Cartridge3EPlusWidget(GuiObject* boss, const GUI::Font& lfont,
@ -35,46 +35,33 @@ class Cartridge3EPlusWidget : public CartDebugWidget
virtual ~Cartridge3EPlusWidget() = default;
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();
private:
Cartridge3EPlus& myCart;
void loadConfig() override;
string internalRamDescription() override;
private:
Cartridge3EPlus& myCart3EP;
std::array<PopUpWidget*, 4> myBankNumber{nullptr};
std::array<PopUpWidget*, 4> myBankType{nullptr};
std::array<ButtonWidget*, 4> myBankCommit{nullptr};
std::array<EditTextWidget*, 8> myBankState{nullptr};
struct CartState {
ByteArray internalram;
enum {
kRomRamChanged = 'rrCh',
kChangeBank = 'chBk',
};
CartState myOldState;
enum BankID {
kBank0Changed = 'b0CH',
kBank1Changed = 'b1CH',
kBank2Changed = 'b2CH',
kBank3Changed = 'b3CH'
};
static const std::array<BankID, 4> bankEnum;
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
Cartridge3EPlusWidget() = delete;
Cartridge3EPlusWidget(const Cartridge3EPlusWidget&) = delete;

View File

@ -23,129 +23,134 @@
Cartridge3EWidget::Cartridge3EWidget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
int x, int y, int w, int h, Cartridge3E& cart)
: CartDebugWidget(boss, lfont, nfont, x, y, w, h),
myCart(cart),
myNumRomBanks(uInt32(cart.mySize >> 11)),
myNumRamBanks(32)
: CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart)
{
size_t size = cart.mySize;
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);
initialize();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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()
{
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);
myRAMBank->setSelectedMax(myOldState.bank >= 256);
myBankWidgets[0]->setSelectedIndex(bank, oldBank != bank);
myBankWidgets[1]->setSelectedMax(oldBank >= myCart.romBankCount());
}
else
{
myROMBank->setSelectedMax(myOldState.bank < 256);
myRAMBank->setSelectedIndex(myCart.myCurrentBank - 256, myOldState.bank != myCart.myCurrentBank);
myBankWidgets[0]->setSelectedMax(oldBank < myCart.romBankCount());
myBankWidgets[1]->setSelectedIndex(bank - myCart.romBankCount(), oldBank != bank);
}
CartDebugWidget::loadConfig();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
void Cartridge3EWidget::handleCommand(CommandSender* sender,
int cmd, int data, int id)
void Cartridge3EWidget::handleCommand(CommandSender* sender, int cmd, int data, int id)
{
uInt16 bank = 0;
if(cmd == kROMBankChanged)
if(cmd == kBankChanged)
{
if(myROMBank->getSelected() < int(myNumRomBanks))
if(myBankWidgets[0]->getSelected() < myCart.romBankCount())
{
bank = myROMBank->getSelected();
myRAMBank->setSelectedMax();
bank = myBankWidgets[0]->getSelected();
myBankWidgets[1]->setSelectedMax();
}
else
{
bank = 256; // default to first RAM bank
myRAMBank->setSelectedIndex(0);
bank = myCart.romBankCount(); // default to first RAM bank
myBankWidgets[1]->setSelectedIndex(0);
}
}
else if(cmd == kRAMBankChanged)
{
if(myRAMBank->getSelected() < int(myNumRamBanks))
if(myBankWidgets[1]->getSelected() < myCart.ramBankCount())
{
myROMBank->setSelectedMax();
bank = myRAMBank->getSelected() + 256;
myBankWidgets[0]->setSelectedMax();
bank = myBankWidgets[1]->getSelected() + myCart.romBankCount();
}
else
{
bank = 0; // default to first ROM bank
myROMBank->setSelectedIndex(0);
myBankWidgets[0]->setSelectedIndex(0);
}
}
myCart.unlockBank();
myCart.bank(bank);
myCart.lockBank();
@ -156,65 +161,13 @@ void Cartridge3EWidget::handleCommand(CommandSender* sender,
string Cartridge3EWidget::bankState()
{
ostringstream& buf = buffer();
uInt16 bank = myCart.getBank();
uInt16& bank = myCart.myCurrentBank;
if(bank < 256)
buf << "ROM bank #" << std::dec << bank % myNumRomBanks << ", RAM inactive";
if(bank < myCart.romBankCount())
buf << "ROM bank #" << std::dec << bank % myCart.romBankCount() << ", RAM inactive";
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();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
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
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:
Cartridge3EWidget(GuiObject* boss, const GUI::Font& lfont,
@ -33,39 +34,28 @@ class Cartridge3EWidget : public CartDebugWidget
virtual ~Cartridge3EWidget() = default;
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 {
kROMBankChanged = 'rmCH',
kRAMBankChanged = 'raCH'
};
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 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
private:
// Following constructors and assignment operators not supported
Cartridge3EWidget() = delete;
Cartridge3EWidget(const Cartridge3EWidget&) = delete;

View File

@ -16,79 +16,33 @@
//============================================================================
#include "Cart3F.hxx"
#include "PopUpWidget.hxx"
#include "Cart3FWidget.hxx"
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Cartridge3FWidget::Cartridge3FWidget(
GuiObject* boss, const GUI::Font& lfont, const GUI::Font& nfont,
int x, int y, int w, int h, Cartridge3F& cart)
: CartDebugWidget(boss, lfont, nfont, x, y, w, h),
myCart(cart)
: CartridgeEnhancedWidget(boss, lfont, nfont, x, y, w, h, cart)
{
size_t size = cart.mySize;
myHotspotDelta = 0;
initialize();
}
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
string Cartridge3FWidget::description()
{
ostringstream info;
info << "Tigervision 3F cartridge, 2-256 2K banks\n"
<< "Startup bank = " << cart.startBank() << " or undetermined\n"
<< "First 2K bank selected by writing to $3F\n"
<< "Last 2K always points to last 2K of ROM\n";
size_t size;
const ByteBuffer& image = myCart.getImage(size);
info << "Tigervision 3F cartridge, 2 - 256 2K banks\n"
<< "First 2K bank selected by writing to " << hotspotStr() << "\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
uInt16 start = (cart.myImage[size-3] << 8) | cart.myImage[size-4];
uInt16 start = (image[size-3] << 8) | image[size-4];
start -= start % 0x1000;
info << "Bank RORG" << " = $" << Common::Base::HEX4 << start << "\n";
info << "Bank RORG $" << Common::Base::HEX4 << start << "\n";
int xpos = 2,
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();
return info.str();
}

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