// license:BSD-3-Clause // copyright-holders:David Haywood /* program rom contains the following details COMPANY:FUN TECH CORPORATION PRODUCT-NAME:SUPER TWO IN ONE PROJECTOR:TIEN YUAN CHIEN,NOMA HARDWARE-DESIGNER:EN YU CHENG SOFTWARE-DESIGNER:RANG CHANG LI,CHIH HNI HUANG,WEN CHANG LIN PROGRAM-VERSION:1.0 PROGRAM-DATE:09/23/1993 8x8 tiles and 8x32 reels, likely going to be very similar to skylncr.cpp or goldstar.cpp (which are both very similar anyway) palette addresses are the same as unkch in goldstar.cpp, but the io stuff is definitely different here board has an M5255 for sound and an unpopulated position for a YM2413 or UM3567 */ #include "emu.h" #include "cpu/z80/z80.h" #include "sound/ay8910.h" #include "machine/nvram.h" #include "machine/ticket.h" #include "screen.h" #include "speaker.h" #include "fts2in1.lh" class fun_tech_corp_state : public driver_device { public: fun_tech_corp_state(const machine_config &mconfig, device_type type, const char *tag) : driver_device(mconfig, type, tag), m_fgram(*this, "fgram"), m_reel1_ram(*this, "reel1ram"), m_reel2_ram(*this, "reel2ram"), m_reel3_ram(*this, "reel3ram"), m_reel1_scroll(*this, "reel1_scroll"), m_reel2_scroll(*this, "reel2_scroll"), m_reel3_scroll(*this, "reel3_scroll"), m_reel1_alt_scroll(*this, "reel1_alt_scroll"), m_maincpu(*this, "maincpu"), m_hopper(*this, "hopper"), m_gfxdecode(*this, "gfxdecode"), m_lamps(*this, "lamp%u", 0U) { } void funtech(machine_config &config); protected: virtual void machine_start() override; virtual void video_start() override; private: required_shared_ptr m_fgram; required_shared_ptr m_reel1_ram; required_shared_ptr m_reel2_ram; required_shared_ptr m_reel3_ram; required_shared_ptr m_reel1_scroll; required_shared_ptr m_reel2_scroll; required_shared_ptr m_reel3_scroll; required_shared_ptr m_reel1_alt_scroll; required_device m_maincpu; required_device m_hopper; required_device m_gfxdecode; output_finder<8> m_lamps; uint8_t m_vreg; tilemap_t *m_fg_tilemap; INTERRUPT_GEN_MEMBER(vblank_interrupt); DECLARE_WRITE8_MEMBER(lamps_w); DECLARE_WRITE8_MEMBER(coins_w); DECLARE_WRITE8_MEMBER(vreg_w); DECLARE_WRITE8_MEMBER(fgram_w); TILE_GET_INFO_MEMBER(get_fg_tile_info); tilemap_t *m_reel1_tilemap; tilemap_t *m_reel2_tilemap; tilemap_t *m_reel3_tilemap; DECLARE_WRITE8_MEMBER(reel1_ram_w); DECLARE_WRITE8_MEMBER(reel2_ram_w); DECLARE_WRITE8_MEMBER(reel3_ram_w); TILE_GET_INFO_MEMBER(get_reel1_tile_info); TILE_GET_INFO_MEMBER(get_reel2_tile_info); TILE_GET_INFO_MEMBER(get_reel3_tile_info); uint32_t screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); void funtech_io_map(address_map &map); void funtech_map(address_map &map); }; TILE_GET_INFO_MEMBER(fun_tech_corp_state::get_fg_tile_info) { int code = m_fgram[tile_index]; int attr = m_fgram[tile_index+0x800]; code |= (attr & 0x0f) << 8; if (m_vreg&1) code |= 0x1000; SET_TILE_INFO_MEMBER(0, code, attr>>4, 0); } TILE_GET_INFO_MEMBER(fun_tech_corp_state::get_reel1_tile_info) { int code = m_reel1_ram[tile_index]; if (m_vreg & 0x4) code |= 0x100; if (m_vreg & 0x8) code |= 0x200; SET_TILE_INFO_MEMBER(1, code, 0, 0); } TILE_GET_INFO_MEMBER(fun_tech_corp_state::get_reel2_tile_info) { int code = m_reel2_ram[tile_index]; if (m_vreg & 0x4) code |= 0x100; if (m_vreg & 0x8) code |= 0x200; SET_TILE_INFO_MEMBER(1, code, 0, 0); } TILE_GET_INFO_MEMBER(fun_tech_corp_state::get_reel3_tile_info) { int code = m_reel3_ram[tile_index]; if (m_vreg & 0x4) code |= 0x100; if (m_vreg & 0x8) code |= 0x200; SET_TILE_INFO_MEMBER(1, code, 0, 0); } WRITE8_MEMBER(fun_tech_corp_state::reel1_ram_w) { m_reel1_ram[offset] = data; m_reel1_tilemap->mark_tile_dirty(offset); } WRITE8_MEMBER(fun_tech_corp_state::reel2_ram_w) { m_reel2_ram[offset] = data; m_reel2_tilemap->mark_tile_dirty(offset); } WRITE8_MEMBER(fun_tech_corp_state::reel3_ram_w) { m_reel3_ram[offset] = data; m_reel3_tilemap->mark_tile_dirty(offset); } void fun_tech_corp_state::video_start() { m_fg_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(fun_tech_corp_state::get_fg_tile_info), this), TILEMAP_SCAN_ROWS, 8, 8, 64, 32); m_fg_tilemap->set_transparent_pen(0); m_reel1_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(fun_tech_corp_state::get_reel1_tile_info), this), TILEMAP_SCAN_ROWS, 8, 32, 64, 8); m_reel2_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(fun_tech_corp_state::get_reel2_tile_info), this), TILEMAP_SCAN_ROWS, 8, 32, 64, 8); m_reel3_tilemap = &machine().tilemap().create(*m_gfxdecode, tilemap_get_info_delegate(FUNC(fun_tech_corp_state::get_reel3_tile_info), this), TILEMAP_SCAN_ROWS, 8, 32, 64, 8); m_reel1_tilemap->set_scroll_cols(64); m_reel2_tilemap->set_scroll_cols(64); m_reel3_tilemap->set_scroll_cols(64); } WRITE8_MEMBER(fun_tech_corp_state::fgram_w) { m_fgram[offset] = data; m_fg_tilemap->mark_tile_dirty(offset&0x7ff); } uint32_t fun_tech_corp_state::screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect) { bitmap.fill(0, cliprect); if (!(m_vreg & 0x40)) { for (int i = 0; i < 64; i++) { m_reel1_tilemap->set_scrolly(i, m_reel1_scroll[i]); m_reel2_tilemap->set_scrolly(i, m_reel2_scroll[i]); m_reel3_tilemap->set_scrolly(i, m_reel3_scroll[i]); } const rectangle visible1(0 * 8, (14 + 48) * 8 - 1, 4 * 8, (4 + 7) * 8 - 1); const rectangle visible2(0 * 8, (14 + 48) * 8 - 1, 12 * 8, (12 + 7) * 8 - 1); const rectangle visible3(0 * 8, (14 + 48) * 8 - 1, 18 * 8, (18 + 7) * 8 - 1); m_reel1_tilemap->draw(screen, bitmap, visible1, 0, 0); m_reel2_tilemap->draw(screen, bitmap, visible2, 0, 0); m_reel3_tilemap->draw(screen, bitmap, visible3, 0, 0); } else { // this mode seems to draw reel1 as fullscreen using a different set of scroll regs for (int i = 0; i < 64; i++) { m_reel1_tilemap->set_scrolly(i, m_reel1_alt_scroll[i]); } m_reel1_tilemap->draw(screen, bitmap, cliprect, 0, 0); } m_fg_tilemap->draw(screen, bitmap, cliprect, 0, 0); return 0; } INTERRUPT_GEN_MEMBER(fun_tech_corp_state::vblank_interrupt) { // if (m_nmi_enable) device.execute().pulse_input_line(INPUT_LINE_NMI, attotime::zero); } void fun_tech_corp_state::funtech_map(address_map &map) { map(0x0000, 0xbfff).rom(); map(0xc000, 0xc1ff).ram().w("palette", FUNC(palette_device::write8)).share("palette"); map(0xc800, 0xc9ff).ram().w("palette", FUNC(palette_device::write8_ext)).share("palette_ext"); map(0xd000, 0xd7ff).rom(); // maybe map(0xd800, 0xdfff).ram().share("nvram"); map(0xe000, 0xefff).ram().w(this, FUNC(fun_tech_corp_state::fgram_w)).share("fgram"); map(0xf000, 0xf1ff).ram().w(this, FUNC(fun_tech_corp_state::reel1_ram_w)).share("reel1ram"); map(0xf200, 0xf3ff).ram().w(this, FUNC(fun_tech_corp_state::reel2_ram_w)).share("reel2ram"); map(0xf400, 0xf5ff).ram().w(this, FUNC(fun_tech_corp_state::reel3_ram_w)).share("reel3ram"); map(0xf600, 0xf7ff).ram(); map(0xf840, 0xf87f).ram().share("reel1_scroll"); map(0xf880, 0xf8bf).ram().share("reel2_scroll"); map(0xf900, 0xf93f).ram().share("reel3_scroll"); map(0xf9c0, 0xf9ff).ram().share("reel1_alt_scroll"); // or a mirror, gets used in 'full screen' mode. } WRITE8_MEMBER(fun_tech_corp_state::lamps_w) { for (int i = 0; i < 8; i++) m_lamps[i] = BIT(data, i); } WRITE8_MEMBER(fun_tech_corp_state::coins_w) { if (data & 0x01) printf("coins_w %02x\n", data); // 80 = hopper motor? m_hopper->motor_w(BIT(data, 7)); // 40 = ? sometimes // 20 = coin 3 counter machine().bookkeeping().coin_counter_w(2, data & 0x20 ); // 10 = coin 2 counter machine().bookkeeping().coin_counter_w(1, data & 0x10 ); // 08 = coin 4 counter machine().bookkeeping().coin_counter_w(3, data & 0x08 ); // 04 = coin 1 counter machine().bookkeeping().coin_counter_w(0, data & 0x04 ); // 02 = used when hopper is used (coin out counter?) } WRITE8_MEMBER(fun_tech_corp_state::vreg_w) { if (data & 0xb2) printf("vreg_w %02x\n", data); // -x-- rr-t // t = text tile bank // r = reel tile bank // x = show reel 1 full screen? m_vreg = data; m_fg_tilemap->mark_all_dirty(); m_reel1_tilemap->mark_all_dirty(); } void fun_tech_corp_state::funtech_io_map(address_map &map) { map.global_mask(0xff); // lamps? map(0x00, 0x00).w(this, FUNC(fun_tech_corp_state::lamps_w)); map(0x01, 0x01).w(this, FUNC(fun_tech_corp_state::coins_w)); map(0x03, 0x03).w(this, FUNC(fun_tech_corp_state::vreg_w)); map(0x04, 0x04).portr("IN0"); map(0x05, 0x05).portr("IN1"); map(0x06, 0x06).portr("DSW1"); map(0x07, 0x07).portr("DSW2"); map(0x10, 0x10).portr("IN4"); map(0x11, 0x11).w("aysnd", FUNC(ay8910_device::data_w)); map(0x12, 0x12).w("aysnd", FUNC(ay8910_device::address_w)); } static INPUT_PORTS_START( funtech ) PORT_START("IN0") // the buttons are all multi-purpose as it's a 2-in-1. PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_BUTTON5 ) PORT_NAME("Hold 5, Bet") PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_COIN1 ) PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_COIN2 ) PORT_BIT( 0x08, IP_ACTIVE_LOW, IPT_COIN3 ) PORT_BIT( 0x10, IP_ACTIVE_LOW, IPT_BUTTON1 ) PORT_NAME("Hold 1, Take, Odds, Stop 1") PORT_BIT( 0x20, IP_ACTIVE_LOW, IPT_BUTTON4 ) PORT_NAME("Hold 4, Double") PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_BUTTON3 ) PORT_NAME("Hold 3, Small, Stop 3") PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_BUTTON2 ) PORT_NAME("Hold 2, Big, Stop 2") PORT_START("IN1") PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_GAMBLE_BOOK ) PORT_BIT( 0x02, IP_ACTIVE_LOW, IPT_GAMBLE_PAYOUT ) PORT_BIT( 0x04, IP_ACTIVE_LOW, IPT_CUSTOM ) PORT_READ_LINE_DEVICE_MEMBER("hopper", ticket_dispenser_device, line_r) PORT_DIPNAME( 0x08, 0x08, "IN1-08" ) // some kind of key-out? reduces credits to 0 PORT_DIPSETTING( 0x08, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_DIPNAME( 0x10, 0x10, "IN1-10" ) // prevents start from working in the poker game at least PORT_DIPSETTING( 0x10, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_SERVICE_NO_TOGGLE( 0x20, IP_ACTIVE_LOW ) PORT_BIT( 0x40, IP_ACTIVE_LOW, IPT_START1 ) PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_COIN4 ) // note? // the board contains 4 banks of 8 dipswitches, the code does not appear to read that many. PORT_START("DSW1") PORT_DIPNAME( 0x01, 0x01, "Reel Speed" ) PORT_DIPSETTING( 0x01, "Low" ) PORT_DIPSETTING( 0x00, "High" ) PORT_DIPNAME( 0x06, 0x06, "Max Bet" ) PORT_DIPSETTING( 0x00, "8" ) PORT_DIPSETTING( 0x04, "16" ) PORT_DIPSETTING( 0x02, "32" ) PORT_DIPSETTING( 0x06, "64" ) PORT_DIPNAME( 0x08, 0x08, "Min Bet for Bonus" ) PORT_DIPSETTING( 0x00, "8" ) PORT_DIPSETTING( 0x08, "16" ) PORT_DIPNAME( 0x10, 0x10, DEF_STR( Unknown ) ) // unused? PORT_DIPSETTING( 0x10, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_DIPNAME( 0x20, 0x20, DEF_STR( Unknown ) ) // unused? PORT_DIPSETTING( 0x20, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_DIPNAME( 0x40, 0x40, DEF_STR( Unknown ) ) // unused? PORT_DIPSETTING( 0x40, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_DIPNAME( 0x80, 0x80, DEF_STR( Unknown ) ) // unused? PORT_DIPSETTING( 0x80, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_START("DSW2") PORT_DIPNAME( 0x0f, 0x0f, "Main Game Percent" ) PORT_DIPSETTING( 0x00, "50" ) PORT_DIPSETTING( 0x08, "53" ) PORT_DIPSETTING( 0x04, "56" ) PORT_DIPSETTING( 0x0c, "59" ) PORT_DIPSETTING( 0x02, "62" ) PORT_DIPSETTING( 0x0a, "65" ) PORT_DIPSETTING( 0x06, "68" ) PORT_DIPSETTING( 0x0e, "71" ) PORT_DIPSETTING( 0x01, "74" ) PORT_DIPSETTING( 0x09, "77" ) PORT_DIPSETTING( 0x05, "80" ) PORT_DIPSETTING( 0x0d, "83" ) PORT_DIPSETTING( 0x03, "86" ) PORT_DIPSETTING( 0x0b, "89" ) PORT_DIPSETTING( 0x07, "92" ) PORT_DIPSETTING( 0x0f, "95" ) PORT_DIPNAME( 0x10, 0x10, "Double Up Game Enable" ) PORT_DIPSETTING( 0x00, "Disabled" ) PORT_DIPSETTING( 0x10, "Enabled" ) PORT_DIPNAME( 0x20, 0x20, "Double Up Percent" ) PORT_DIPSETTING( 0x00, "80" ) PORT_DIPSETTING( 0x20, "90" ) PORT_DIPNAME( 0x40, 0x40, DEF_STR( Unknown ) ) // unused? PORT_DIPSETTING( 0x40, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_DIPNAME( 0x80, 0x80, DEF_STR( Unknown ) ) // unused? PORT_DIPSETTING( 0x80, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_START("IN4") // the dipswitch screen doesn't list these, maybe determined by the hopper? PORT_DIPNAME( 0x07, 0x07, "Coin 4 Value" ) PORT_DIPSETTING( 0x00, "1" ) PORT_DIPSETTING( 0x04, "2" ) PORT_DIPSETTING( 0x02, "5" ) PORT_DIPSETTING( 0x06, "10" ) PORT_DIPSETTING( 0x01, "20" ) PORT_DIPSETTING( 0x05, "25" ) PORT_DIPSETTING( 0x03, "50" ) PORT_DIPSETTING( 0x07, "100" ) PORT_DIPNAME( 0x08, 0x08, DEF_STR( Unknown ) ) PORT_DIPSETTING( 0x08, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_DIPNAME( 0x10, 0x10, DEF_STR( Unknown ) ) PORT_DIPSETTING( 0x10, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) PORT_DIPNAME( 0x60, 0x00, "Payout Value" ) PORT_DIPSETTING( 0x00, "1" ) PORT_DIPSETTING( 0x40, "2" ) PORT_DIPSETTING( 0x20, "10" ) PORT_DIPSETTING( 0x60, "50" ) PORT_DIPNAME( 0x80, 0x80, DEF_STR( Unknown ) ) PORT_DIPSETTING( 0x80, DEF_STR( Off ) ) PORT_DIPSETTING( 0x00, DEF_STR( On ) ) INPUT_PORTS_END static const gfx_layout tiles8x32_layout = { 8,32, RGN_FRAC(1,1), 8, { 0, 1, 2, 3, 4 ,5, 6, 7 }, { 0, 8, 16, 24, 32, 40, 48, 56 }, { 0*64, 1*64, 2*64, 3*64, 4*64, 5*64, 6*64, 7*64, 8*64, 9*64, 10*64, 11*64, 12*64, 13*64, 14*64, 15*64, 16*64, 17*64, 18*64, 19*64, 20*64, 21*64, 22*64, 23*64, 24*64, 25*64, 26*64, 27*64, 28*64, 29*64, 30*64, 31*64 }, 32*64 }; static const gfx_layout tiles8x8_layout = { 8,8, RGN_FRAC(1,1), 4, { 0, 1, 2, 3 }, { 4, 0, 12, 8, 20, 16, 28, 24 }, { 0*32, 1*32, 2*32, 3*32, 4*32, 5*32, 6*32, 7*32 }, 32*8 }; static GFXDECODE_START( gfx_funtech ) GFXDECODE_ENTRY( "gfx1", 0, tiles8x8_layout, 0, 16 ) GFXDECODE_ENTRY( "gfx2", 0, tiles8x32_layout, 0x100, 1 ) GFXDECODE_END void fun_tech_corp_state::machine_start() { m_lamps.resolve(); } MACHINE_CONFIG_START(fun_tech_corp_state::funtech) /* basic machine hardware */ MCFG_DEVICE_ADD("maincpu", Z80, 4000000) /* ? MHz */ MCFG_DEVICE_PROGRAM_MAP(funtech_map) MCFG_DEVICE_IO_MAP(funtech_io_map) MCFG_DEVICE_VBLANK_INT_DRIVER("screen", fun_tech_corp_state, vblank_interrupt) /* video hardware */ MCFG_SCREEN_ADD("screen", RASTER) MCFG_SCREEN_REFRESH_RATE(60) MCFG_SCREEN_VBLANK_TIME(ATTOSECONDS_IN_USEC(0)) MCFG_SCREEN_SIZE(512, 256) MCFG_SCREEN_VISIBLE_AREA(0, 512-1, 8, 256-8-1) MCFG_SCREEN_UPDATE_DRIVER(fun_tech_corp_state, screen_update) MCFG_SCREEN_PALETTE("palette") MCFG_DEVICE_ADD("gfxdecode", GFXDECODE, "palette", gfx_funtech) MCFG_PALETTE_ADD("palette", 0x200) MCFG_PALETTE_FORMAT(xBBBBBGGGGGRRRRR) MCFG_NVRAM_ADD_1FILL("nvram") MCFG_TICKET_DISPENSER_ADD("hopper", attotime::from_msec(50), TICKET_MOTOR_ACTIVE_HIGH, TICKET_STATUS_ACTIVE_HIGH) /* sound hardware */ SPEAKER(config, "mono").front_center(); MCFG_DEVICE_ADD("aysnd", AY8910, 1500000) /* M5255, ? MHz */ MCFG_SOUND_ROUTE(ALL_OUTPUTS, "mono", 1.00) MACHINE_CONFIG_END ROM_START( fts2in1 ) ROM_REGION( 0x10000, "maincpu", 0 ) ROM_LOAD( "u5.bin", 0x00000, 0x10000, CRC(ab19fd28) SHA1(a65ff732e0aaaec256cc63beff5f24419e691645) ) ROM_REGION( 0x80000, "gfx1", 0 ) // crc printed on label matches half the data, even if chip was double size ROM_LOAD( "u18.bin", 0x00000, 0x80000, CRC(d1154aac) SHA1(dc03c4b7a4dfda2a30bfabaeb0ce053660961663) ) // 1ST AND 2ND HALF IDENTICAL ROM_REGION( 0x40000, "gfx2", 0 ) ROM_LOAD16_BYTE( "u29.bin", 0x00000, 0x20000, CRC(ed6a1e2f) SHA1(2c72e764c7c8091a8fa1dfc257a84d61e2da0e4b) ) ROM_LOAD16_BYTE( "u30.bin", 0x00001, 0x20000, CRC(d572bddc) SHA1(06499aeb47085a02af9eb4987ed987f9a3a397f7) ) ROM_END GAMEL( 1993, fts2in1, 0, funtech, funtech, fun_tech_corp_state, empty_init, ROT0, "Fun Tech Corporation", "Super Two In One", 0, layout_fts2in1 )