Floating bus for ZX Spectrum +2A/+3 and alternative palettes (#153)

* Floating bus support for ZX Spectrum +2A/+3

- Add floating bus support for ZX Spectrum +2A/+3
- Games tested:
   - A Yankee in Irak 1.3.3
   - Mr. Kung Fu 1.3 +2a/+3 version
   - MONJAS 1.6 - Spanish
   - MONJAS 1.6 - English
   - Hell Yeah! v210131
   - Sidewize  +2A:+3 fix by Ast. A. Moore

* Alternative palettes

- Add alternative palettes:
   - Fuse original
   - ZX Spectrum (Wikipedia)
   - Black and White TV
   - Green monochrome monitor (from ZXSpin)
   - Ambar monochrome monitor (from ZXSpin)
   - C64-like
   - CGA 4 colours
   - CGA 8 colours
   - CGA 15 colours
   - Inverted colours
This commit is contained in:
jfroco authored and GitHub committed 2024-11-24 02:44:14 -08:00
1 parent 99df66eac0
commit cad85b7b1b
12 files changed
+305 -22

No files matched your search

+4
View File
@@ -34,7 +34,11 @@
#include "peripherals/specdrum.h"
#include "spectrum.h"
#ifdef __LIBRETRO__
typedef libspectrum_byte (*spectrum_unattached_port_fn)( libspectrum_word port );
#else
typedef libspectrum_byte (*spectrum_unattached_port_fn)( void );
#endif
/* How long do things take to happen; fields are pulled from libspectrum
via the libspectrum_timings_* functions */
+4 -1
View File
@@ -86,8 +86,11 @@ pentagon_select_ff_read( libspectrum_word port, libspectrum_byte *attached )
data = beta_sp_read( port, &tmpattached );
if( !tmpattached )
#ifdef __LIBRETRO__
data = spectrum_unattached_port(port);
#else
data = spectrum_unattached_port();
#endif
*attached = 0xff; /* TODO: check this */
return data;
}
+4 -1
View File
@@ -50,8 +50,11 @@ specplus2a_init( fuse_machine_info *machine )
machine->ram.contend_delay_no_mreq = spectrum_contend_delay_none;
machine->ram.valid_pages = 8;
#ifdef __LIBRETRO__
machine->unattached_port = spectrum_unattached_port_amstrad;
#else
machine->unattached_port = spectrum_unattached_port_none;
#endif
machine->shutdown = NULL;
machine->memory_map = specplus3_memory_map;
+4
View File
@@ -121,7 +121,11 @@ int specplus3_init( fuse_machine_info *machine )
machine->ram.contend_delay_no_mreq = spectrum_contend_delay_none;
machine->ram.valid_pages = 8;
#ifdef __LIBRETRO__
machine->unattached_port = spectrum_unattached_port_amstrad;
#else
machine->unattached_port = spectrum_unattached_port_none;
#endif
specplus3_765_init();
specplus3_menu_items();
+4
View File
@@ -49,7 +49,11 @@ specplus3e_init( fuse_machine_info *machine )
machine->ram.contend_delay_no_mreq = spectrum_contend_delay_none;
machine->ram.valid_pages = 8;
#ifdef __LIBRETRO__
machine->unattached_port = spectrum_unattached_port_amstrad;
#else
machine->unattached_port = spectrum_unattached_port_none;
#endif
machine->shutdown = specplus3_shutdown;
+4 -1
View File
@@ -346,8 +346,11 @@ readport_internal( libspectrum_word port )
if( callback_info.attached != 0xff )
callback_info.value =
periph_merge_floating_bus( callback_info.value, callback_info.attached,
#ifdef __LIBRETRO__
machine_current->unattached_port(port) );
#else
machine_current->unattached_port() );
#endif
/* If we're RZX recording, store this byte */
if( rzx_recording ) rzx_store_byte( callback_info.value );
+129 -1
View File
@@ -72,6 +72,9 @@ static const char * const frame_count_name = "frames";
/* Count of frames since last reset */
static libspectrum_dword frames_since_reset;
#ifdef __LIBRETRO__
static libspectrum_word last_floating_bus_amstrad_value = 0xff;
#endif
static void
spectrum_reset( int hard_reset )
@@ -228,7 +231,11 @@ spectrum_contend_delay_76543210( libspectrum_dword time )
/* What happens if we read from an unattached port? */
libspectrum_byte
#ifdef __LIBRETRO__
spectrum_unattached_port(libspectrum_word port)
#else
spectrum_unattached_port( void )
#endif
{
int line, tstates_through_line, column;
@@ -263,7 +270,7 @@ spectrum_unattached_port( void )
column = ( ( tstates_through_line -
machine_current->timings.left_border ) / 8 ) * 2;
switch( tstates_through_line % 8 ) {
switch( tstates_through_line % 8) {
/* The pattern of bytes returned here is the same as documented by
Ramsoft in their 'Floating bus technical guide' at
@@ -296,7 +303,128 @@ spectrum_unattached_port( void )
}
libspectrum_byte
#ifdef __LIBRETRO__
spectrum_unattached_port_none( libspectrum_word port )
#else
spectrum_unattached_port_none( void )
#endif
{
#ifdef __LIBRETRO__
/* Make compiler happy */
(void) port;
#endif
return 0xff;
}
#ifdef __LIBRETRO__
/**
* References:
* http://sky.relative-path.com/zx/floating_bus.html
* https://sourceforge.net/p/fuse-emulator/bugs/360/
* https://www.usebox.net/jjm/blog/zx-spectrum-floating-bus/
* https://softspectrum48.weebly.com/notes/category/floating-bus
* https://sinclair.wiki.zxnet.co.uk/wiki/Floating_bus
*
* Not implemented and not really necessary to run the games:
* - Check if 128k paging is enabled
* - Return value = 0xff or last value that was written to, or read from, contended memory duringle idle intervals
*
* Tested with all known games:
*
* - A Yankee in Irak 1.3.3
* - Mr. Kung Fu 1.3 +2a/+3 version
* - MONJAS 1.6 - Spanish
* - MONJAS 1.6 - English
* - Hell Yeah! v210131
* - Sidewize +2A:+3 fix by Ast. A. Moore
*/
libspectrum_byte
spectrum_unattached_port_amstrad( libspectrum_word port )
{
int game = 1;
int line, tstates_through_line, column;
/* Check port pattern 1 + (4 * n) */
if (port <= 1 || port > 0x1000 || ((port - 1) & 3) != 0)
{
last_floating_bus_amstrad_value = 0xff;
return 0xff;
}
/* Return 0xff (idle bus) if we're in the top border */
if( tstates < machine_current->line_times[ DISPLAY_BORDER_HEIGHT ] )
{
last_floating_bus_amstrad_value = 0xff;
return last_floating_bus_amstrad_value;
}
/* Work out which line we're on, relative to the top of the screen */
line = ( (libspectrum_signed_dword)tstates -
machine_current->line_times[ DISPLAY_BORDER_HEIGHT ] ) /
machine_current->timings.tstates_per_line;
/* Idle bus if we're in the lower border */
if( line >= DISPLAY_HEIGHT )
{
last_floating_bus_amstrad_value = 0xff;
return (game) ? 0xff : last_floating_bus_amstrad_value;
}
/* Work out where we are in this line, remembering that line_times[] holds
the first pixel we display, not the start of where the Spectrum produced
the left border */
tstates_through_line = tstates -
machine_current->line_times[ DISPLAY_BORDER_HEIGHT + line ] +
( machine_current->timings.left_border - DISPLAY_BORDER_WIDTH_COLS * 4 );
/* Idle bus if we're in the left border */
if( tstates_through_line < machine_current->timings.left_border )
return (game) ? 0xff : last_floating_bus_amstrad_value;
/* Or the right border or retrace */
if( tstates_through_line >= machine_current->timings.left_border +
machine_current->timings.horizontal_screen )
return (game) ? 0xff : last_floating_bus_amstrad_value;
column = ( ( tstates_through_line -
machine_current->timings.left_border ) / 8 ) * 2;
switch( (tstates_through_line - game) % 8 ) {
/* The pattern of bytes returned here is the same as documented by
Ramsoft in their 'Floating bus technical guide' at
http://web.archive.org/web/20080509193736/http://www.ramsoft.bbk.org/floatingbus.html
However, the timings used are based on the first byte being
returned at 14338 (48K) and 14364 (128K) respectively, not
14347 and 14368 as used by Ramsoft.
In contrast to previous versions of this code, Arkanoid and
Sidewize now work. */
/* Attribute bytes */
case 5: column++;
case 3:
last_floating_bus_amstrad_value = RAM[ memory_current_screen ][ display_attr_start[line] + column ] | 0x01;
return last_floating_bus_amstrad_value;
/* Screen data */
case 4: column++;
case 2:
last_floating_bus_amstrad_value = RAM[ memory_current_screen ][ display_line_start[line] + column ] | 0x01;
return last_floating_bus_amstrad_value;
/* Idle bus */
case 0: case 1: case 6: case 7:
return (game) ? 0xff : last_floating_bus_amstrad_value;
}
return (game) ? 0xff : last_floating_bus_amstrad_value;
}
#endif
+6
View File
@@ -72,8 +72,14 @@ libspectrum_byte spectrum_contend_delay_none( libspectrum_dword time );
libspectrum_byte spectrum_contend_delay_65432100( libspectrum_dword time );
libspectrum_byte spectrum_contend_delay_76543210( libspectrum_dword time );
#ifdef __LIBRETRO__
libspectrum_byte spectrum_unattached_port( libspectrum_word port );
libspectrum_byte spectrum_unattached_port_none( libspectrum_word port );
libspectrum_byte spectrum_unattached_port_amstrad( libspectrum_word port );
#else
libspectrum_byte spectrum_unattached_port( void );
libspectrum_byte spectrum_unattached_port_none( void );
#endif
/* Miscellaneous stuff */
+4 -1
View File
@@ -154,8 +154,11 @@ floating_bus_test( void )
RAM[ memory_current_screen ][ offset ] = offset % 0x100;
for( tstates = 0; tstates < ULA_CONTENTION_SIZE; tstates++ )
#ifdef __LIBRETRO__
checksum += machine_current->unattached_port((libspectrum_word) 0xff) * ( tstates + 1 );
#else
checksum += machine_current->unattached_port() * ( tstates + 1 );
#endif
if( settings_current.late_timings ) {
switch( machine_current->machine ) {
case LIBSPECTRUM_MACHINE_16:
-17
View File
@@ -4,23 +4,6 @@
#include <externs.h>
#include <machine.h>
static const uint16_t palette[16] = {
0x0000, 0x0018, 0xc000, 0xc018,
0x0600, 0x0618, 0xc600, 0xc618,
0x0000, 0x001f, 0xf800, 0xf81f,
0x07e0, 0x07ff, 0xffe0, 0xffff,
};
/*
// B&W TV palette
static const uint16_t greys[16] = {
0x0000, 0x10a2, 0x39c7, 0x4a69,
0x738e, 0x8430, 0xad55, 0xbdf7,
0x0000, 0x18e3, 0x4a69, 0x6b4d,
0x94b2, 0xb596, 0xe71c, 0xffff,
};
*/
int uidisplay_init(int width, int height)
{
log_cb(RETRO_LOG_DEBUG, "%s(%d, %d)\n", __FUNCTION__, width, height);
+1
View File
@@ -54,6 +54,7 @@ extern void* tape_data;
extern size_t tape_size;
extern int joymap[16];
extern keysyms_map_t keysyms_map[];
extern uint16_t *palette;
int update_variables(int);
int fuse_ui_error_specific(ui_error_level, const char*);
+141
View File
@@ -77,6 +77,131 @@ static const machine_t machine_list[] =
{ LIBSPECTRUM_MACHINE_SCORP, "scorpion", 0 },
};
#define BGR16(color) rgb32_to_bgr16(color)
#define rgb32_to_bgr16(color) rgbc32_to_bgr16((color & 0xFF0000) >> 16, (color & 0x00FF00) >> 8, (color & 0x0000FF))
#define rgbc32_to_bgr16(red, green, blue) ((blue & 0b11111000) << 8) | ((green & 0b11111100) << 3) | (red >> 3)
#define RGB16(color) rgb32_to_rgb16(color)
#define rgb32_to_rgb16(color) rgbc32_to_rgb16((color & 0xFF0000) >> 16, (color & 0x00FF00) >> 8, (color & 0x0000FF))
#define rgbc32_to_rgb16(red, green, blue) ((red & 0b11111000) << 8) | ((green & 0b11111100) << 3) | (blue >> 3)
#define G3R3B2_TO_RGB565(color) \
((((color >> 5) & 0x7) << 13) & 0x7C00) | \
((((color >> 2) & 0x7) << 8) & 0x03E0) | \
(((((color & 0x3) << 1) | (((color & 0x3) & 0x1) | (((color & 0x3) >> 1) & 0x1))) << 2) & 0x001F)
enum PALETTES {
PALETTE_FUSE = 0,
PALETTE_ZX_SPECTRUM_WIKIPEDIA,
PALETTE_BLACK_AND_WHITE_TV,
PALETTE_GREEN_MONOCHROME,
PALETTE_AMBAR_MONOCHROME,
PALETTE_C64,
PALETTE_CGA_4,
PALETTE_CGA_8,
PALETTE_CGA_16,
PALETTE_INVERTED,
PALETTE_COUNT
};
static uint16_t palettes[PALETTE_COUNT][16] = {
[PALETTE_FUSE] = {
0x0000, 0x0018, 0xc000, 0xc018,
0x0600, 0x0618, 0xc600, 0xc618,
0x0000, 0x001f, 0xf800, 0xf81f,
0x07e0, 0x07ff, 0xffe0, 0xffff,
},
[PALETTE_ZX_SPECTRUM_WIKIPEDIA] = {
RGB16(0x000000),RGB16(0x0000D7),RGB16(0xD70000),RGB16(0xD700D7),
RGB16(0x00D700),RGB16(0x00D7D7),RGB16(0xD7D700),RGB16(0xD7D7D7),
RGB16(0x000000),RGB16(0x0000FF),RGB16(0xFF0000),RGB16(0xFF00FF),
RGB16(0x00FF00),RGB16(0x00FFFF),RGB16(0xFFFF00),RGB16(0xffffff),
},
[PALETTE_BLACK_AND_WHITE_TV] = {
0x0000, 0x10a2, 0x39c7, 0x4a69,
0x738e, 0x8430, 0xad55, 0xbdf7,
0x0000, 0x18e3, 0x4a69, 0x6b4d,
0x94b2, 0xb596, 0xe71c, 0xffff,
},
[PALETTE_GREEN_MONOCHROME] = { /* From ZX Spin */
rgbc32_to_rgb16(0,0,0), /* black */
rgbc32_to_rgb16(0,33,0), /* blue */
rgbc32_to_rgb16(0,62,0), /* red */
rgbc32_to_rgb16(0,85,0), /* magenta*/
rgbc32_to_rgb16(0,115,0), /* green*/
rgbc32_to_rgb16(0,136,0), /* cyan*/
rgbc32_to_rgb16(0,168,0), /* yellow*/
rgbc32_to_rgb16(0,181,0), /* white */
rgbc32_to_rgb16(0,0,0), /* bright black */
rgbc32_to_rgb16(0,52,0), /* bright blue */
rgbc32_to_rgb16(0,81,0), /* bright red */
rgbc32_to_rgb16(0,113,0), /* bright magenta*/
rgbc32_to_rgb16(0,154,0), /* bright green*/
rgbc32_to_rgb16(0,185,0), /* bright cyan*/
rgbc32_to_rgb16(0,237,0), /* bright yellow*/
rgbc32_to_rgb16(0,255,0) /* bright white*/
},
[PALETTE_AMBAR_MONOCHROME] = { /* From ZX Spin */
rgbc32_to_rgb16(0,0,0), /* black */
rgbc32_to_rgb16(34,24,0), /* blue */
rgbc32_to_rgb16(62,44,0), /* red */
rgbc32_to_rgb16(86,61,0), /* magenta*/
rgbc32_to_rgb16(116,82,0), /* green*/
rgbc32_to_rgb16(136,96,0), /* cyan*/
rgbc32_to_rgb16(168,119,0), /* yellow*/
rgbc32_to_rgb16(182,128,0), /* white */
rgbc32_to_rgb16(0,0,0), /* bright black */
rgbc32_to_rgb16(52,37,0), /* bright blue */
rgbc32_to_rgb16(82,58,0), /* bright red */
rgbc32_to_rgb16(114,80,0), /* bright magenta*/
rgbc32_to_rgb16(154,109,0), /* bright green*/
rgbc32_to_rgb16(186,131,0), /* bright cyan*/
rgbc32_to_rgb16(238,168,0), /* bright yellow*/
rgbc32_to_rgb16(255,180,1) /* bright white*/
},
[PALETTE_C64] = {
RGB16(0x000000), /* black */
RGB16(0x40318D), /* blue */
RGB16(0x883932), /* red */
RGB16(0x8B5429), /* magenta*/
RGB16(0x55A049), /* green*/
RGB16(0x67B6BD), /* cyan*/
RGB16(0x574200), /* yellow*/
RGB16(0x9F9F9F), /* white */
RGB16(0x000000), /* bright black */
RGB16(0x7869C4), /* bright blue */
RGB16(0xB86962), /* bright red */
RGB16(0x8B5429), /* bright magenta*/
RGB16(0x94E089), /* bright green*/
RGB16(0x9F9F9F), /* bright cyan*/
RGB16(0xBFCE72), /* bright yellow*/
RGB16(0xFFFFFF), /* bright white*/
},
[PALETTE_CGA_4] = {
RGB16(0x000000),RGB16(0x55ffff),RGB16(0xff55ff),RGB16(0xff55ff),
RGB16(0x55ffff),RGB16(0x55ffff),RGB16(0xffffff),RGB16(0xffffff),
RGB16(0x000000),RGB16(0x55ffff),RGB16(0xff55ff),RGB16(0xff55ff),
RGB16(0x55ffff),RGB16(0x55ffff),RGB16(0xffffff),RGB16(0xffffff),
},
[PALETTE_CGA_8] = {
RGB16(0x000000), RGB16(0xAAAA), RGB16(0xAA00AA), RGB16(0xAA00AA),
RGB16(0xAAAA),RGB16(0xAAAA),RGB16(0xAAAAAA),RGB16(0xAAAAAA),
RGB16(0x000000),RGB16(0x55ffff),RGB16(0xff55ff),RGB16(0xff55ff),
RGB16(0x55ffff),RGB16(0x55ffff),RGB16(0xffffff),RGB16(0xffffff),
},
[PALETTE_CGA_16] = {
RGB16(0x000000), RGB16(0x0000AA), RGB16(0xAA0000), RGB16(0xAA00AA),
RGB16(0x00AA00),RGB16(0x00AAAA),RGB16(0xAA5500),RGB16(0xAAAAAA),
RGB16(0x000000),RGB16(0x5555FF),RGB16(0xFF5555),RGB16(0xFF55FF),
RGB16(0x55FF55),RGB16(0x55FFFF),RGB16(0xFFFF55),RGB16(0xffffff),
},
[PALETTE_INVERTED] = {
RGB16(0xffffff),RGB16(0xFEFF31),RGB16(0x30FEFF),RGB16(0x30FE31),
RGB16(0xFF30EA),RGB16(0xFE3030),RGB16(0x3030EA),RGB16(0x303030),
RGB16(0xffffff),RGB16(0xFDFF02),RGB16(0x00FDFE),RGB16(0x00FD02),
RGB16(0xFF00E3),RGB16(0xFD0000),RGB16(0xFD0000),RGB16(0x000000),
},
};
static int fuse_init_called = 0;
static int forced_machine_at_init = 0;
static int forced_machine_idx = 0;
@@ -97,6 +222,7 @@ static int keyb_transparent;
static const machine_t* machine;
static double frame_time;
static cheat_t* active_cheats;
static int current_palette = PALETTE_FUSE;
// allow access to variables declared here
double total_time_ms;
@@ -123,6 +249,7 @@ size_t snapshot_size;
void* tape_data;
size_t tape_size;
int joymap[16];
uint16_t *palette = palettes[PALETTE_FUSE];
static const struct { unsigned x; unsigned y; } keyb_positions[4] = {
{ 32, 40 }, { 40, 88 }, { 48, 136 }, { 32, 184 }
@@ -281,6 +408,7 @@ static const struct retro_variable core_vars[] =
{ "fuse_machine", "Model (needs content load); Spectrum 48K|Spectrum 48K (NTSC)|Spectrum 128K|Spectrum +2|Spectrum +2A|Spectrum +3|Spectrum +3e|Spectrum SE|Timex TC2048|Timex TC2068|Timex TS2068|Spectrum 16K|Pentagon 128K|Pentagon 512K|Pentagon 1024|Scorpion 256K" },
{ "fuse_emulation_speed", "Emulation speed percentage (needs content load); 100|150|200|300|50"},
{ "fuse_size_border", "Size Video Border; full|medium|small|minimum|none" },
{ "fuse_palette", "Colour Palette; Fuse Standard|ZX Standard|B&W TV|Green Monochrome|Ambar Monochrome|C64|CGA 4 colours|CGA 8 colours|CGA 16 colours|Inverted colours"},
{ "fuse_auto_load", "Tape Auto Load; enabled|disabled" },
{ "fuse_fast_load", "Tape Fast Load; enabled|disabled" },
{ "fuse_load_sound", "Tape Load Sound; enabled|disabled" },
@@ -456,6 +584,19 @@ int update_variables(int force)
settings_current.emulation_speed = option >= 0 ? atoi(value) : 100;
}
{
int option = coreopt(env_cb, core_vars, "fuse_palette", NULL);
option += option < 0;
if (option>=0 && option<=PALETTE_COUNT-1 && current_palette!=option)
{
current_palette = option;
palette = palettes[current_palette];
display_refresh_all();
}
}
settings_current.auto_load = coreopt(env_cb, core_vars, "fuse_auto_load", NULL) != 1;
if (coreopt(env_cb, core_vars, "fuse_fast_load", NULL) == 0)