// license:BSD-3-Clause
// copyright-holders:Tomasz Slanina, David Haywood
/* ST0016 - CPU (z80) + Sound + Video */
#include "emu.h"
#include "st0016.h"
#include "speaker.h"
DEFINE_DEVICE_TYPE(ST0016_CPU, st0016_cpu_device, "st0016_cpu", "ST0016")
void st0016_cpu_device::st0016_cpu_internal_map(address_map &map)
{
map(0xc000, 0xcfff).r(this, FUNC(st0016_cpu_device::st0016_sprite_ram_r)).w(this, FUNC(st0016_cpu_device::st0016_sprite_ram_w));
map(0xd000, 0xdfff).r(this, FUNC(st0016_cpu_device::st0016_sprite2_ram_r)).w(this, FUNC(st0016_cpu_device::st0016_sprite2_ram_w));
map(0xea00, 0xebff).r(this, FUNC(st0016_cpu_device::st0016_palette_ram_r)).w(this, FUNC(st0016_cpu_device::st0016_palette_ram_w));
map(0xec00, 0xec1f).r(this, FUNC(st0016_cpu_device::st0016_character_ram_r)).w(this, FUNC(st0016_cpu_device::st0016_character_ram_w));
map(0xe900, 0xe9ff).rw("stsnd", FUNC(st0016_device::st0016_snd_r), FUNC(st0016_device::st0016_snd_w)); /* sound regs 8 x $20 bytes, see notes */
}
void st0016_cpu_device::st0016_cpu_internal_io_map(address_map &map)
{
map.global_mask(0xff);
map(0x00, 0xbf).r(this, FUNC(st0016_cpu_device::st0016_vregs_r)).w(this, FUNC(st0016_cpu_device::st0016_vregs_w)); /* video/crt regs ? */
map(0xe2, 0xe2).w(this, FUNC(st0016_cpu_device::st0016_sprite_bank_w));
map(0xe3, 0xe4).w(this, FUNC(st0016_cpu_device::st0016_character_bank_w));
map(0xe5, 0xe5).w(this, FUNC(st0016_cpu_device::st0016_palette_bank_w));
map(0xf0, 0xf0).r(this, FUNC(st0016_cpu_device::st0016_dma_r));
}
// note: a lot of bits are left uninitialized by the games, the default values are uncertain
st0016_cpu_device::st0016_cpu_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: z80_device(mconfig, ST0016_CPU, tag, owner, clock),
device_gfx_interface(mconfig, *this, nullptr, "palette"),
st0016_spr_bank(0),
st0016_spr2_bank(0),
st0016_pal_bank(0),
st0016_char_bank(0),
spr_dx(0),
spr_dy(0),
st0016_ramgfx(0),
m_io_space_config("io", ENDIANNESS_LITTLE, 8, 16, 0, address_map_constructor(FUNC(st0016_cpu_device::st0016_cpu_internal_io_map), this)),
m_space_config("regs", ENDIANNESS_LITTLE, 8, 16, 0, address_map_constructor(FUNC(st0016_cpu_device::st0016_cpu_internal_map), this)),
m_screen(*this, ":screen"),
m_rom(*this, DEVICE_SELF),
m_game_flag(-1)
{
for (auto & elem : st0016_vregs)
elem = 0;
}
device_memory_interface::space_config_vector st0016_cpu_device::memory_space_config() const
{
return space_config_vector {
std::make_pair(AS_PROGRAM, &m_space_config),
std::make_pair(AS_IO, &m_io_space_config)
};
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void st0016_cpu_device::device_start()
{
z80_device::device_start();
startup();
m_dma_offs_cb.bind_relative_to(*owner());
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void st0016_cpu_device::device_reset()
{
z80_device::device_reset();
switch (m_game_flag & 0x3f)
{
case 0: //renju kizoku
m_screen->set_visible_area(0, 40*8-1, 0, 30*8-1);
spr_dx=0;
spr_dy=0;
break;
case 1: //neratte chu!
m_screen->set_visible_area(8,41*8-1,0,30*8-1);
spr_dx=0;
spr_dy=8;
break;
case 4: //mayjinsen 1&2
m_screen->set_visible_area(0,32*8-1,0,28*8-1);
break;
case 10:
m_screen->set_visible_area(0,383,0,255);
break;
case 11:
m_screen->set_visible_area(0,383,0,383);
break;
}
}
READ8_MEMBER(st0016_cpu_device::soundram_read)
{
return m_charram[offset];
}
/* CPU interface */
MACHINE_CONFIG_START(st0016_cpu_device::device_add_mconfig)
SPEAKER(config, "lspeaker").front_left();
SPEAKER(config, "rspeaker").front_right();
MCFG_PALETTE_ADD("palette", 16*16*4+1)
MCFG_DEVICE_ADD("stsnd", ST0016, 0)
MCFG_ST0016_SOUNDRAM_READ_CB(READ8(*this, st0016_cpu_device, soundram_read))
MCFG_SOUND_ROUTE(0, "lspeaker", 1.0)
MCFG_SOUND_ROUTE(1, "rspeaker", 1.0)
MACHINE_CONFIG_END
static const gfx_layout charlayout =
{
8,8,
0x10000,
4,
{ 0, 1, 2, 3 },
{ 1*4, 0*4, 3*4, 2*4, 5*4, 4*4, 7*4, 6*4 },
{ 0*32, 1*32, 2*32, 3*32, 4*32, 5*32, 6*32, 7*32 },
8*8*4
};
WRITE8_MEMBER(st0016_cpu_device::st0016_sprite_bank_w)
{
/*
76543210
xxxx - spriteram bank1
xxxx - spriteram bank2
*/
st0016_spr_bank=data&SPR_BANK_MASK;
st0016_spr2_bank=(data>>4)&SPR_BANK_MASK;
}
WRITE8_MEMBER(st0016_cpu_device::st0016_palette_bank_w)
{
/*
76543210
xx - palram bank
xxxxxx - unknown/unused
*/
st0016_pal_bank=data&PAL_BANK_MASK;
}
WRITE8_MEMBER(st0016_cpu_device::st0016_character_bank_w)
{
/*
fedcba9876543210
xxxxxxxxxxxxxxxx - character (bank )
*/
if(offset&1)
st0016_char_bank=(st0016_char_bank&0xff)|(data<<8);
else
st0016_char_bank=(st0016_char_bank&0xff00)|data;
st0016_char_bank&=CHAR_BANK_MASK;
}
READ8_MEMBER(st0016_cpu_device::st0016_sprite_ram_r)
{
return st0016_spriteram[SPR_BANK_SIZE*st0016_spr_bank+offset];
}
WRITE8_MEMBER(st0016_cpu_device::st0016_sprite_ram_w)
{
st0016_spriteram[SPR_BANK_SIZE*st0016_spr_bank+offset]=data;
}
READ8_MEMBER(st0016_cpu_device::st0016_sprite2_ram_r)
{
return st0016_spriteram[SPR_BANK_SIZE*st0016_spr2_bank+offset];
}
WRITE8_MEMBER(st0016_cpu_device::st0016_sprite2_ram_w)
{
st0016_spriteram[SPR_BANK_SIZE*st0016_spr2_bank+offset]=data;
}
READ8_MEMBER(st0016_cpu_device::st0016_palette_ram_r)
{
return st0016_paletteram[PAL_BANK_SIZE*st0016_pal_bank+offset];
}
WRITE8_MEMBER(st0016_cpu_device::st0016_palette_ram_w)
{
int color=(PAL_BANK_SIZE*st0016_pal_bank+offset)/2;
int val;
st0016_paletteram[PAL_BANK_SIZE*st0016_pal_bank+offset]=data;
val=st0016_paletteram[color*2]+(st0016_paletteram[color*2+1]<<8);
if(!color)
palette().set_pen_color(UNUSED_PEN,pal5bit(val >> 0),pal5bit(val >> 5),pal5bit(val >> 10)); /* same as color 0 - bg ? */
palette().set_pen_color(color,pal5bit(val >> 0),pal5bit(val >> 5),pal5bit(val >> 10));
}
READ8_MEMBER(st0016_cpu_device::st0016_character_ram_r)
{
return m_charram[CHAR_BANK_SIZE*st0016_char_bank+offset];
}
WRITE8_MEMBER(st0016_cpu_device::st0016_character_ram_w)
{
m_charram[CHAR_BANK_SIZE*st0016_char_bank+offset]=data;
gfx(st0016_ramgfx)->mark_dirty(st0016_char_bank);
}
READ8_MEMBER(st0016_cpu_device::st0016_vregs_r)
{
/*
$0, $1 = max scanline(including vblank)/timer? ($3e7)
$8-$40 = bg tilemaps (8 bytes each) :
0 - ? = usually 0/20/ba*
1 - 0 = disabled , !zero = address of tilemap in spriteram /$1000 (for example: 3 -> tilemap at $3000 )
2 - ? = usually ff/1f/af*
3 - priority ? = 0 - under sprites , $ff - over sprites \
4 - ? = $7f/$ff
5 - ? = $29/$20 (29 when tilemap must be drawn over sprites . maybe this is real priority ?)
6 - ? = 0
7 - ? =$20/$10/$12*
$40-$60 = scroll registers , X.w, Y.w
*/
switch (offset)
{
case 0:
case 1:
return machine().rand();
}
return st0016_vregs[offset];
}
READ8_MEMBER(st0016_cpu_device::st0016_dma_r)
{
/* bits 0 and 1 = 0 -> DMA transfer complete */
if(ismacs())
return 0;
else
return 0;
}
WRITE8_MEMBER(st0016_cpu_device::st0016_vregs_w)
{
/*
I/O ports:
$74 x--- ---- global flip screen
-xx- ---- individual flip screen x/y
i.e. Mayjinsen sets 0x80, other ST0016 games 0x60.
TODO: Might also be paired with $70 & $75 (setted up by Mayjinsen).
$a0 \
$a1 - source address >> 1
$a2 /
$a3 \
$a4 - destination address >> 1 (inside character ram)
$a5 /
$a6 \
&a7 - (length inbytes - 1 ) >> 1
$a8 - 76543210
??faaaaa
a - most sign. bits of length
f - DMA start latch
*/
st0016_vregs[offset]=data;
if(offset==0xa8 && (data&0x20))
{
uint32_t srcadr=(st0016_vregs[0xa0]|(st0016_vregs[0xa1]<<8)|(st0016_vregs[0xa2]<<16))<<1;
uint32_t dstadr=(st0016_vregs[0xa3]|(st0016_vregs[0xa4]<<8)|(st0016_vregs[0xa5]<<16))<<1;
uint32_t length=((st0016_vregs[0xa6]|(st0016_vregs[0xa7]<<8)|((st0016_vregs[0xa8]&0x1f)<<16))+1)<<1;
uint32_t srclen = (m_rom->bytes());
uint8_t *mem = m_rom->base();
int xfer_offs = m_dma_offset;
if (!m_dma_offs_cb.isnull())
xfer_offs = m_dma_offs_cb() * 0x400000;
srcadr += xfer_offs;
while(length>0)
{
if( srcadr < srclen && (dstadr < MAX_CHAR_BANK*CHAR_BANK_SIZE))
{
st0016_char_bank=dstadr>>5;
st0016_character_ram_w(space,dstadr&0x1f,mem[srcadr]);
srcadr++;
dstadr++;
length--;
}
else
{
/* samples ? sound dma ? */
// speaglsht: unknown DMA copy : src - 2B6740, dst - 4400, len - 1E400
logerror("%s unknown DMA copy : src - %X, dst - %X, len - %X\n", machine().describe_context(), srcadr, dstadr, length);
break;
}
}
}
}
void st0016_cpu_device::draw_sprites(bitmap_ind16 &bitmap, const rectangle &cliprect)
{
/*
object ram :
each entry is 8 bytes:
76543210 (bit)
0 llllllll
1 ---gSSSl
2 oooooooo
3 fooooooo
4 xxxxxxxx
5 ----XXxx
6 yyyyyyyy
7 ----XYyy
1 - always(?) set
S - scroll index ? (ports $40-$60, X(word),Y(word) )
l - sublist length (8 byte entries -1)
o - sublist offset (*8 to get real offset)
f - end of list flag
x,y - sprite coords
(complete guess)
g - use tile sizes specified in list (global / fixed ones if not set?) (gostop)
X = global X size?
Y = global Y size?
sublist format (8 bytes/entry):
76543210
0 cccccccc
1 cccccccc
2 --kkkkkk
3 QW------
4 xxxxxxxx
5 -B--XX-x
6 yyyyyyyy
7 ----YY-y
c - character code
k - palette
QW - flips
x,y - coords
XX,YY - size (1<<size)
B - merge pixel data with prevoius one (8bpp mode - neratte: seta logo and title screen)
*/
gfx_element *gfx = this->gfx(st0016_ramgfx);
int i, j, lx, ly, x, y, code, offset, length, sx, sy, color, flipx, flipy, scrollx, scrolly/*,plx,ply*/;
for (i = 0; i < SPR_BANK_SIZE*MAX_SPR_BANK; i += 8)
{
x = st0016_spriteram[i + 4] + ((st0016_spriteram[i + 5] & 3) << 8);
y = st0016_spriteram[i + 6] + ((st0016_spriteram[i + 7] & 3) << 8);
int use_sizes = (st0016_spriteram[i + 1] & 0x10);
int globalx = (st0016_spriteram[i + 5] & 0x0c)>>2;
int globaly = (st0016_spriteram[i + 7] & 0x0c)>>2;
scrollx = (st0016_vregs[(((st0016_spriteram[i + 1] & 0x0f) >> 1) << 2) + 0x40] + 256 * st0016_vregs[(((st0016_spriteram[i + 1] & 0x0f) >> 1) << 2) + 1 + 0x40]) & 0x3ff;
scrolly = (st0016_vregs[(((st0016_spriteram[i + 1] & 0x0f) >> 1) << 2) + 2 + 0x40] + 256 * st0016_vregs[(((st0016_spriteram[i + 1] & 0x0f) >> 1) << 2) + 3 + 0x40]) & 0x3ff;
if (!ismacs())
{
if (x & 0x200) x -= 0x400; //sign
if (y & 0x200) y -= 0x400;
if (scrollx & 0x200) scrollx -= 0x400; //sign
if (scrolly & 0x200) scrolly -= 0x400;
}
if (ismacs1())
{
if (x & 0x200) x -= 0x400; //sign
if (y & 0x200) y -= 0x2b0;//0x400;
if (scrollx & 0x200) scrollx -= 0x400; //sign
if (scrolly & 0x200) scrolly -= 0x400;
}
x += scrollx;
y += scrolly;
if (ismacs())
{
y += 0x20;
}
if (st0016_spriteram[i + 3] & 0x80) /* end of list */
break;
offset = st0016_spriteram[i + 2] + 256 * (st0016_spriteram[i + 3]);
offset <<= 3;
length = st0016_spriteram[i + 0] + 1 + 256 * (st0016_spriteram[i + 1] & 1);
//plx=(st0016_spriteram[i+5]>>2)&0x3;
//ply=(st0016_spriteram[i+7]>>2)&0x3;
if (offset < SPR_BANK_SIZE*MAX_SPR_BANK)
{
for (j = 0; j < length; j++)
{
code = st0016_spriteram[offset] + 256 * st0016_spriteram[offset + 1];
sx = st0016_spriteram[offset + 4] + ((st0016_spriteram[offset + 5] & 1) << 8);
sy = st0016_spriteram[offset + 6] + ((st0016_spriteram[offset + 7] & 1) << 8);
if (ismacs() && !ismacs1())
{
if (sy & 0x100) sy -= 0x200; //yuka & yujan
}
if (ismacs())
{
sy = 0xe0 - sy;
}
sx += x;
sy += y;
color = st0016_spriteram[offset + 2] & 0x3f;
if (use_sizes)
{
lx = (st0016_spriteram[offset + 5] >> 2) & 3;
ly = (st0016_spriteram[offset + 7] >> 2) & 3;
}
else
{
lx = globalx;
ly = globaly;
}
/*
if(plx |ply) //parent
{
lx=plx;
ly=ply;
}
*/
flipx = st0016_spriteram[offset + 3] & 0x80;
flipy = st0016_spriteram[offset + 3] & 0x40;
if (ismacs())
sy -= (1 << ly) * 8;
{
int x0, y0, i0 = 0;
for (x0 = (flipx ? ((1 << lx) - 1) : 0); x0 != (flipx ? -1 : (1 << lx)); x0 += (flipx ? -1 : 1))
for (y0 = (flipy ? ((1 << ly) - 1) : 0); y0 != (flipy ? -1 : (1 << ly)); y0 += (flipy ? -1 : 1))
{
/* custom draw */
uint16_t *destline;
int yloop, xloop;
int ypos, xpos;
int tileno;
const uint8_t *srcgfx;
int gfxoffs;
ypos = sy + y0 * 8 + spr_dy;
xpos = sx + x0 * 8 + spr_dx;
tileno = (code + i0++)&CHAR_BANK_MASK;
gfxoffs = 0;
srcgfx = gfx->get_data(tileno);
for (yloop = 0; yloop < 8; yloop++)
{
uint16_t drawypos;
if (!flipy) { drawypos = ypos + yloop; }
else { drawypos = (ypos + 8 - 1) - yloop; }
destline = &bitmap.pix16(drawypos);
for (xloop = 0; xloop<8; xloop++)
{
uint16_t drawxpos;
int pixdata;
pixdata = srcgfx[gfxoffs];
if (!flipx) { drawxpos = xpos + xloop; }
else { drawxpos = (xpos + 8 - 1) - xloop; }
if (drawxpos > cliprect.max_x)
drawxpos -= 512; // wrap around
if (cliprect.contains(drawxpos, drawypos))
{
if (st0016_spriteram[offset + 5] & 0x40)
{
destline[drawxpos] = (destline[drawxpos] | pixdata << 4) & 0x3ff;
}
else
{
if (ismacs2())
{
if (pixdata)//|| destline[drawxpos]==UNUSED_PEN)
{
destline[drawxpos] = pixdata + (color * 16);
}
}
else
{
if (pixdata || destline[drawxpos] == UNUSED_PEN)
{
destline[drawxpos] = pixdata + (color * 16);
}
}
}
}
gfxoffs++;
}
}
}
}
offset += 8;
if (offset >= SPR_BANK_SIZE*MAX_SPR_BANK)
break;
}
}
}
}
void st0016_cpu_device::st0016_save_init()
{
save_item(NAME(st0016_spr_bank));
save_item(NAME(st0016_spr2_bank));
save_item(NAME(st0016_pal_bank));
save_item(NAME(st0016_char_bank));
save_item(NAME(m_dma_offset));
//save_item(NAME(st0016_rom_bank));
save_item(NAME(st0016_vregs));
save_pointer(NAME(m_charram.get()), MAX_CHAR_BANK*CHAR_BANK_SIZE);
save_pointer(NAME(st0016_paletteram.get()), MAX_PAL_BANK*PAL_BANK_SIZE);
save_pointer(NAME(st0016_spriteram.get()), MAX_SPR_BANK*SPR_BANK_SIZE);
}
void st0016_cpu_device::startup()
{
int gfx_index=0;
m_dma_offset = 0;
m_charram=make_unique_clear<uint8_t[]>(MAX_CHAR_BANK*CHAR_BANK_SIZE);
st0016_spriteram=make_unique_clear<uint8_t[]>(MAX_SPR_BANK*SPR_BANK_SIZE);
st0016_paletteram=make_unique_clear<uint8_t[]>(MAX_PAL_BANK*PAL_BANK_SIZE);
/* find first empty slot to decode gfx */
for (gfx_index = 0; gfx_index < MAX_GFX_ELEMENTS; gfx_index++)
if (gfx(gfx_index) == nullptr)
break;
assert(gfx_index != MAX_GFX_ELEMENTS);
/* create the char set (gfx will then be updated dynamically from RAM) */
set_gfx(gfx_index, std::make_unique<gfx_element>(&palette(), charlayout, m_charram.get(), 0, 0x40, 0));
st0016_ramgfx = gfx_index;
spr_dx=0;
spr_dy=0;
st0016_save_init();
}
void st0016_cpu_device::draw_bgmap(bitmap_ind16 &bitmap, const rectangle &cliprect, int priority)
{
gfx_element *gfx = this->gfx(st0016_ramgfx);
int j;
//for(j=0x40-8;j>=0;j-=8)
for (j = 0; j < 0x40; j += 8)
{
if (st0016_vregs[j + 1] && ((priority && (st0016_vregs[j + 3] == 0xff)) || ((!priority) && (st0016_vregs[j + 3] != 0xff))))
{
int x, y, code, color, flipx, flipy;
int i = st0016_vregs[j + 1] * 0x1000;
for (x = 0; x < 32 * 2; x++)
{
for (y = 0; y < 8 * 4; y++)
{
code = st0016_spriteram[i] + 256 * st0016_spriteram[i + 1];
color = st0016_spriteram[i + 2] & 0x3f;
flipx = st0016_spriteram[i + 3] & 0x80;
flipy = st0016_spriteram[i + 3] & 0x40;
if (priority)
{
gfx->transpen(bitmap, cliprect,
code,
color,
flipx, flipy,
x * 8 + spr_dx, y * 8 + spr_dy, 0);
}
else
{
uint16_t *destline;
int yloop, xloop;
int ypos, xpos;
const uint8_t *srcgfx;
int gfxoffs;
ypos = y * 8 + spr_dy;//+((st0016_vregs[j+2]==0xaf)?0x50:0);//hack for mayjinsen title screen
xpos = x * 8 + spr_dx;
gfxoffs = 0;
srcgfx = gfx->get_data(code);
for (yloop = 0; yloop < 8; yloop++)
{
uint16_t drawypos;
if (!flipy) { drawypos = ypos + yloop; }
else { drawypos = (ypos + 8 - 1) - yloop; }
destline = &bitmap.pix16(drawypos);
for (xloop = 0; xloop<8; xloop++)
{
uint16_t drawxpos;
int pixdata;
pixdata = srcgfx[gfxoffs];
if (!flipx) { drawxpos = xpos + xloop; }
else { drawxpos = (xpos + 8 - 1) - xloop; }
if (drawxpos > cliprect.max_x)
drawxpos -= 512; // wrap around
if (cliprect.contains(drawxpos, drawypos))
{
if (st0016_vregs[j + 7] == 0x12)
destline[drawxpos] = (destline[drawxpos] | (pixdata << 4)) & 0x3ff;
else
{
if (ismacs2())
{
if (pixdata)// || destline[drawxpos]==UNUSED_PEN)
{
destline[drawxpos] = pixdata + (color * 16);
}
}
else
{
if (pixdata || destline[drawxpos] == UNUSED_PEN)
{
destline[drawxpos] = pixdata + (color * 16);
}
}
}
}
gfxoffs++;
}
}
}
i += 4;
}
}
}
}
}
void st0016_cpu_device::st0016_draw_screen(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
draw_bgmap(bitmap,cliprect,0);
draw_sprites(bitmap,cliprect);
draw_bgmap(bitmap,cliprect,1);
}
uint32_t st0016_cpu_device::update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
{
#ifdef MAME_DEBUG
if(machine().input().code_pressed_once(KEYCODE_Z))
{
int h,j;
FILE *p=fopen("vram.bin","wb");
fwrite(st0016_spriteram.get(),1,0x1000*MAX_SPR_BANK,p);
fclose(p);
p=fopen("vram.txt","wt");
for(h=0;h<0xc0;h++)
fprintf(p,"VREG %.4x - %.4x\n",h,st0016_vregs[h]);
for(h=0;h<0x1000*MAX_SPR_BANK;h+=8)
{
fprintf(p,"%.4x - %.4x - ",h,h>>3);
for(j=0;j<8;j++)
fprintf(p,"%.2x ",st0016_spriteram[h+j]);
fprintf(p,"\n");
}
fclose(p);
}
#endif
bitmap.fill(UNUSED_PEN, cliprect);
st0016_draw_screen(screen, bitmap, cliprect);
return 0;
}