diff --git a/src/mame/drivers/scyclone.cpp b/src/mame/drivers/scyclone.cpp index 0e75aa43471..de8a5820e31 100644 --- a/src/mame/drivers/scyclone.cpp +++ b/src/mame/drivers/scyclone.cpp @@ -2,16 +2,37 @@ // copyright-holders:David Haywood /* Space Cyclone - + 3 board stack? - the PCB pictures make it very difficult to figure much out - + seems a bit like 8080bw hardware but with extra sprites and a z80 cpu? maybe an evolution of Polaris etc.? - - + + there's an MB14241 near the Sprite ROM - + 1x 8DSW, 1x 4DSW + + there are 11 HM4716AP RAM chips near the main CPU (1 is unmarked, could be different?) + HM4716AP is 16384 * 1-bit (2048 [0x800] bytes) + + 1bpp of video needs 0x3800 bytes + + 1bpp video would need 4 of these (with 0x400 bytes leftover) + 2bpp video would need 7 of these + 3bpp video would need 11 of these (with 0x400 bytes leftover) + mainram is 0x400 bytes (so the leftover in above calc?) + + + Notes: + + (to check on hardware) + the stars scroll backwards when in cocktail mode, the direction changes when the screen + is flipped but is still reversed compared to upright. We would have no way of knowing + if we're in cocktail mode without checking the dipswitch, so I think this is a game bug + furthermore the game seems to set what I believe to be the 'flipscreen' bit for player 2 + even when in 'upright' mode, so it's possible this romset was only really made for a + cocktail table? */ #include "emu.h" @@ -26,24 +47,70 @@ class scyclone_state : public driver_device public: scyclone_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag), - m_mainram(*this, "mainram"), + //m_vram(*this, "vram"), m_maincpu(*this, "maincpu"), - m_gfxdecode(*this, "gfxdecode") { } + m_gfxdecode(*this, "gfxdecode"), + m_stars(*this, "stars"), + m_gfx1pal(*this, "gfx1pal"), + m_palette(*this, "palette") + { } - /* memory pointers */ - required_shared_ptr m_mainram; + DECLARE_WRITE8_MEMBER(vidctrl_w); - DECLARE_WRITE8_MEMBER(scyclone_port06_w); + DECLARE_WRITE8_MEMBER(sprite_xpos_w); + DECLARE_WRITE8_MEMBER(sprite_ypos_w); + DECLARE_WRITE8_MEMBER(sprite_colour_w); + DECLARE_WRITE8_MEMBER(sprite_tile_w); + DECLARE_WRITE8_MEMBER(starscroll_w); + DECLARE_WRITE8_MEMBER(port0e_w); + DECLARE_WRITE8_MEMBER(port0f_w); - /* video-related */ + DECLARE_WRITE8_MEMBER(port06_w); + DECLARE_WRITE8_MEMBER(videomask1_w); + DECLARE_WRITE8_MEMBER(videomask2_w); + DECLARE_WRITE8_MEMBER(vram_w); + DECLARE_READ8_MEMBER(vram_r); + + uint32_t screen_update_scyclone(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); + + INTERRUPT_GEN_MEMBER(irq); + + CUSTOM_INPUT_MEMBER(collision_r); + +protected: virtual void machine_start() override; virtual void machine_reset() override; virtual void video_start() override; - uint32_t screen_update_scyclone(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); + +private: required_device m_maincpu; required_device m_gfxdecode; + required_region_ptr m_stars; + required_region_ptr m_gfx1pal; + required_device m_palette; - INTERRUPT_GEN_MEMBER(irq); + /* memory pointers */ + std::unique_ptr m_vram; + + uint8_t m_videowritemask; + uint8_t m_videowritemask2; + + uint8_t m_sprite_xpos; + uint8_t m_sprite_ypos; + uint8_t m_sprite_colour; + uint8_t m_sprite_tile; + uint8_t m_starscroll; + uint8_t m_p0e; + uint8_t m_p0f; + uint8_t m_vidctrl; + + uint8_t m_hascollided; + + /* video-related */ + const uint8_t get_sprite_pixel(int x, int y); + const uint8_t get_bitmap_pixel(int x, int y); + uint32_t draw_starfield(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); + uint32_t draw_bitmap_and_sprite(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); }; @@ -56,28 +123,159 @@ public: void scyclone_state::video_start() { + m_vram = std::make_unique(0x3800*3); + m_videowritemask = 0x00; + m_videowritemask2 = 0x00; + + for (int i = 0; i < 8; i++) + m_palette->set_pen_color(i, rgb_t(pal1bit(i >> 0), pal1bit(i >> 1), pal1bit(i >> 2))); + + for (int i = 0; i < 16; i++) + { + uint8_t col = m_gfx1pal[i]; + m_palette->set_pen_color(i+8, rgb_t(pal1bit(col >> 2), pal1bit(col >> 1), pal1bit(col >> 0))); + } } -uint32_t scyclone_state::screen_update_scyclone(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) +const uint8_t scyclone_state::get_sprite_pixel(int x, int y) { - int count = 0; - - for (int y=0;y<256;y++) + int minx = 0xe0-m_sprite_xpos; + int miny = 0xe0-m_sprite_ypos; + int col = m_sprite_colour & 3; + int code = m_sprite_tile & 7; + + int maxx = minx + 32; + int maxy = miny + 32; + + /* if we're not even inside the sprite, return nothing */ + if (x < minx || x >= maxx) + return 0; + + if (y < miny || y >= maxy) + return 0; + + int sprx = x-minx; + int spry = y-miny; + + const uint8_t* srcdata = m_gfxdecode->gfx(0)->get_data(code); + + uint8_t pix = srcdata[spry*32 + sprx] + col*4; + + return pix; +} + + +const uint8_t scyclone_state::get_bitmap_pixel(int x, int y) +{ + const uint8_t video_data0 = m_vram[(y*32+(x/8))]; + const uint8_t video_data1 = m_vram[(y*32+(x/8))+0x3800]; + const uint8_t video_data2 = m_vram[(y*32+(x/8))+0x3800+0x3800]; + + int const bit(x & 0x07); + + const uint8_t plane0(BIT(video_data0, bit)); + const uint8_t plane1(BIT(video_data1, bit)); + const uint8_t plane2(BIT(video_data2, bit)); + + const uint8_t pal = plane0 | (plane1<<1) | (plane2<<2); + + return pal; +} + +uint32_t scyclone_state::draw_starfield(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) +{ + const pen_t *paldata = m_palette->pens(); + + // this is not correct, but the first 0x80 bytes of the PROM are blank suggesting that this + // part is for the non-visible 32 rows at least, so it should be something like this + // blinking and colours are not understood at all + + + + for (int strip=0;strip<16;strip++) + { + for (int x=0;x<256;x++) + { + const uint8_t star = m_stars[((x+m_starscroll) & 0x3f)+(strip*0x40)]; + + for (int y=0;y<16;y++) + { + const int ypos = (y+16*strip)-32; + + if (ypos>=0 && ypos<256) + { + int noclipped = 0; + + if (m_vidctrl & 0x08) + noclipped = x < 64*3; + else + noclipped = x >= 64; + + + if (y == star && star != 0 && noclipped) + bitmap.pix32(ypos, x) = paldata[7]; + else + bitmap.pix32(ypos, x) = paldata[0]; + } + } + } + } + + return 0; +} + +uint32_t scyclone_state::draw_bitmap_and_sprite(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) +{ + const pen_t *paldata = m_palette->pens(); + m_hascollided = 0; + + for (int y=0;y<256-32;y++) { for (int x=0;x<256/8;x++) { - uint8_t video_data = m_mainram[count]; - for (int i=0;i<8;i++) { - pen_t pen = ((video_data>>i) & 0x01) ? rgb_t::white() : rgb_t::black(); - bitmap.pix32(y, x*8+i) = pen; - } + int realx = 0; + int realy = 0; + + if (m_vidctrl & 0x08) + { + realx = 255 - ((x*8)+i); + realy = 255-32-y; + } + else + { + realx = (x*8)+i; + realy = y; + } + + + uint8_t pal = get_bitmap_pixel(realx, realy); + + if (pal) bitmap.pix32(y, (x*8)+i) = paldata[pal]; + + uint8_t pal2 = get_sprite_pixel(realx, realy); + + if (pal2 & 0x3) + { + bitmap.pix32(y, (x*8)+i) = paldata[8+pal2]; + if (pal == 0x7) m_hascollided = 1; + } + } + } + } + + return 0; +} + + +uint32_t scyclone_state::screen_update_scyclone(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) +{ + draw_starfield(screen,bitmap,cliprect); + draw_bitmap_and_sprite(screen,bitmap,cliprect); + + //popmessage("%02x %02x %02x %02x %02x %02x %02x", m_sprite_xpos, m_sprite_ypos, m_sprite_colour, m_sprite_tile, m_starscroll, m_p0e, m_p0f); - count++; - } - } - return 0; } @@ -85,7 +283,9 @@ uint32_t scyclone_state::screen_update_scyclone(screen_device &screen, bitmap_rg static ADDRESS_MAP_START( scyclone_map, AS_PROGRAM, 8, scyclone_state ) AM_RANGE(0x0000, 0x2fff) AM_ROM - AM_RANGE(0x4000, 0x5fff) AM_RAM AM_SHARE("mainram") + AM_RANGE(0x4000, 0x43ff) AM_RAM + AM_RANGE(0x4400, 0x5fff) AM_READWRITE(vram_r,vram_w) + AM_RANGE(0x6000, 0x60ff) AM_NOP // this just seems to be overflow from the VRAM writes, probably goes nowhere ADDRESS_MAP_END @@ -94,17 +294,17 @@ static ADDRESS_MAP_START( scyclone_iomap, AS_IO, 8, scyclone_state ) AM_RANGE(0x00, 0x00) AM_DEVREAD("mb14241", mb14241_device, shift_result_r) AM_DEVWRITE("mb14241", mb14241_device, shift_count_w) AM_RANGE(0x01, 0x01) AM_READ_PORT("IN0") AM_DEVWRITE("mb14241", mb14241_device, shift_data_w) AM_RANGE(0x02, 0x02) AM_READ_PORT("IN1") - AM_RANGE(0x03, 0x03) AM_READ_PORT("DSW0") AM_WRITENOP - AM_RANGE(0x04, 0x04) AM_WRITENOP - AM_RANGE(0x05, 0x05) AM_WRITENOP - AM_RANGE(0x06, 0x06) AM_WRITE(scyclone_port06_w) - AM_RANGE(0x08, 0x08) AM_WRITENOP - AM_RANGE(0x09, 0x09) AM_WRITENOP - AM_RANGE(0x0a, 0x0a) AM_WRITENOP - AM_RANGE(0x0e, 0x0e) AM_WRITENOP - AM_RANGE(0x0f, 0x0f) AM_WRITENOP - AM_RANGE(0x40, 0x40) AM_WRITENOP - AM_RANGE(0x80, 0x80) AM_WRITENOP + AM_RANGE(0x03, 0x03) AM_READ_PORT("DSW0") AM_WRITE(vidctrl_w) + AM_RANGE(0x04, 0x04) AM_WRITE(sprite_xpos_w) + AM_RANGE(0x05, 0x05) AM_WRITE(sprite_ypos_w) + AM_RANGE(0x06, 0x06) AM_WRITE(port06_w) // possible watchdog or star twinkle related + AM_RANGE(0x08, 0x08) AM_WRITE(sprite_colour_w) + AM_RANGE(0x09, 0x09) AM_WRITE(sprite_tile_w) + AM_RANGE(0x0a, 0x0a) AM_WRITE(starscroll_w) + AM_RANGE(0x0e, 0x0e) AM_WRITE(port0e_w) + AM_RANGE(0x0f, 0x0f) AM_WRITE(port0f_w) + AM_RANGE(0x40, 0x40) AM_WRITE(videomask1_w) + AM_RANGE(0x80, 0x80) AM_WRITE(videomask2_w) ADDRESS_MAP_END @@ -121,9 +321,18 @@ static ADDRESS_MAP_START( scyclone_sub_iomap, AS_IO, 8, scyclone_state ) ADDRESS_MAP_END +// appears to be when a white bitmap pixel (col 0x7) collides with a large sprite? +// if you simply set it to 1 and shoot in the left corner, the game gets stuck +// but if you have it set to 0 there are no collisions with large objects +CUSTOM_INPUT_MEMBER(scyclone_state::collision_r) +{ + return m_hascollided; +} + + static INPUT_PORTS_START( scyclone ) PORT_START("IN0") - PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_UNKNOWN ) + PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_UNKNOWN ) PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_TILT ) PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_START2 ) PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_START1 ) @@ -133,11 +342,12 @@ static INPUT_PORTS_START( scyclone ) PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_2WAY PORT_START("IN1") - PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_UNKNOWN ) - PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_UNKNOWN ) - PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_UNKNOWN ) - PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_UNKNOWN ) - PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_UNKNOWN ) + PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_SPECIAL ) PORT_CUSTOM_MEMBER(DEVICE_SELF, scyclone_state,collision_r, nullptr) // hw collision? + // maybe these 4 are the 4xdsw bank? + PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_UNKNOWN ) + PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_UNKNOWN ) + PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_UNKNOWN ) + PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_UNKNOWN ) PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_COCKTAIL PORT_CONDITION("DSW0",0x04,EQUALS,0x00) PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT ) PORT_2WAY PORT_COCKTAIL PORT_CONDITION("DSW0",0x04,EQUALS,0x00) PORT_BIT( 0x80, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_2WAY PORT_COCKTAIL PORT_CONDITION("DSW0",0x04,EQUALS,0x00) @@ -151,18 +361,160 @@ static INPUT_PORTS_START( scyclone ) PORT_DIPNAME( 0x04, 0x04, DEF_STR( Cabinet ) ) PORT_DIPLOCATION("DSW0:3") PORT_DIPSETTING( 0x00, DEF_STR( Cocktail ) ) PORT_DIPSETTING( 0x04, DEF_STR( Upright ) ) - PORT_DIPUNKNOWN_DIPLOC( 0x08, 0x00, "DSW0:4" ) // seems to disable collisions - PORT_DIPUNKNOWN_DIPLOC( 0x10, 0x10, "DSW0:5" ) + PORT_DIPNAME( 0x08, 0x00, "Disable Collision (Buggy!)" ) PORT_DIPLOCATION("DSW0:4") // this causes the game to malfunction, likely leftover debug feature + PORT_DIPSETTING( 0x00, DEF_STR( Off ) ) + PORT_DIPSETTING( 0x08, DEF_STR( On ) ) + PORT_DIPNAME( 0x10, 0x00, "Show Copyright Date" ) PORT_DIPLOCATION("DSW0:5") + PORT_DIPSETTING( 0x10, DEF_STR( Off ) ) + PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_DIPUNKNOWN_DIPLOC( 0x20, 0x20, "DSW0:6" ) PORT_DIPUNKNOWN_DIPLOC( 0x40, 0x40, "DSW0:7" ) PORT_DIPUNKNOWN_DIPLOC( 0x80, 0x80, "DSW0:8" ) INPUT_PORTS_END -WRITE8_MEMBER(scyclone_state::scyclone_port06_w) +READ8_MEMBER(scyclone_state::vram_r) { + uint8_t ret = 0; + // not sure, collisions depend on readback + ret |= m_vram[offset]; + ret |= m_vram[offset+0x3800]; + ret |= m_vram[offset+0x3800+0x3800]; + return ret; } + +WRITE8_MEMBER(scyclone_state::vram_w) +{ + // this seems to give good colours for most things? + // although the lives appear to be solid pink in video? + // CREDIT text looks poor, but seems to be same on real HW? + +#if 0 + // halves are NOT equal in between level cutscenes + // this is used when drawing the pointing stick, need closeup reference + + // 0x40 and 0x80 ports always seem equal tho + if ((m_videowritemask & 0xf) != ((m_videowritemask>>4) & 0xf)) + { + printf("m_videowritemask halves not equal %02x\n", m_videowritemask); + } + + if ((m_videowritemask2 & 0xf) != ((m_videowritemask2>>4) & 0xf)) + { + printf("m_videowritemask2 halves not equal %02x\n", m_videowritemask2); + } + + if ((m_videowritemask) != (m_videowritemask2)) + { + printf("m_videowritemask != m_videowritemask2 %02x %02x\n", m_videowritemask, m_videowritemask2); + } +#endif + + + for (int i=0 ;i<8 ;i++) + { + uint8_t databit = data & (1<=6) videowritemask = (m_videowritemask>>4) & 0x0f; + else if (i>=4) videowritemask = (m_videowritemask>>0) & 0x0f; + if (i>=2) videowritemask = (m_videowritemask2>>4) & 0x0f; + else videowritemask = (m_videowritemask2>>0) & 0x0f; + + if (databit) + { + if (!(videowritemask & 1)) m_vram[offset] |= databit; + if (!(videowritemask & 2)) m_vram[offset+0x3800] |= databit; + if (!(videowritemask & 4)) m_vram[offset+0x3800+0x3800] |= databit; + } + else + { + uint8_t nodatabit = (1<