diff options
Diffstat (limited to 'src/mame/old/ir.cpp')
-rw-r--r-- | src/mame/old/ir.cpp | 591 |
1 files changed, 591 insertions, 0 deletions
diff --git a/src/mame/old/ir.cpp b/src/mame/old/ir.cpp new file mode 100644 index 00000000000..be60850e6f4 --- /dev/null +++ b/src/mame/old/ir.cpp @@ -0,0 +1,591 @@ +// license:BSD-3-Clause +// copyright-holders:Robbbert +/**************************************************************************************** + + Invader's Revenge + + October, 2008 by Robbbert + + No diagram available, so I threw this together + + + Invader's Revenge was made by the English company, Zenitone-Microsec Ltd. + Distributed in Germany by Dutchford. + Also distributed in America by Americade Amusement, Inc., of New Jersey. + + It's basically a hardware hack of the normal Taito Space Invaders machine. + + Changes (start with a Space Invaders with a colour CRT): + + 1. The sound board is replaced with another that makes different sounds. + + 2. A sardine-can-shaped potted black epoxy block containing a 4mhz crystal, + two large 40-pin ICs, at least 2 roms, and a number of TTL-shaped chips. + My assumption is that one of the big chips is a (slave sound) CPU. + + 3. New main roms + + 4. A small card contains a colour PROM. There's no RAM for this, so the colours + must be quite simplistic. + + The main CPU communicates to the slave by sending sound codes via port 3, + while port 5 controls various aspects of the screen display. + + The method of enabling cocktail mode is the same as for Space Invaders, that is, + changing the positions of SW5,6,7; cutting a track; and adding some jumpers. + Obviously this is a once-off change for the table model. + + Known bugs in the original roms: + + 1. If you turn on the machine, start a 1-player game and just let it run to the + end, it will congratulate player 2 for getting a high score. Of course, zero + points is not a high score, and there wasn't a player 2. + + 2. If you die just after killing the last enemy in the 8th level, the docking + stage uses the wrong colour map. I've added a fix for this. + + + Notes about colour proms: + + 1. The proms from sets 'invrvnga' and 'invrvng3' are the correct ones. The prom + from 'invrvng3' is used for the other sets, as their proms haven't shown up yet. + + 2. Real name for 'invrvnga' prom is '3r.bin' ; for 'invrvng3' is 'ir.clr'. + + Todo: + + 1. Identify the slave CPU and make it work. + 2. Get schematic diagrams, and design a discrete sound board. + + Changes: + + 1. Added above documentation (MAME doesn't have any) + 2. Identified all sound codes. (We only need good samples now) + +*******************************************************************************************/ + +#include "emu.h" +#include "cpu/i8085/i8085.h" +#include "machine/mb14241.h" +#include "sound/samples.h" +#include "screen.h" +#include "speaker.h" +#include "cpu/m6800/m6800.h" +#include "sound/ay8910.h" +#include "machine/timer.h" + +#define MW8080BW_MASTER_CLOCK (19968000.0) +#define MW8080BW_CPU_CLOCK (MW8080BW_MASTER_CLOCK / 10) +#define MW8080BW_PIXEL_CLOCK (MW8080BW_MASTER_CLOCK / 4) +#define MW8080BW_HTOTAL (0x140) +#define MW8080BW_HBEND (0x000) +#define MW8080BW_HBSTART (0x100) +#define MW8080BW_VTOTAL (0x106) +#define MW8080BW_VBEND (0x000) +#define MW8080BW_VBSTART (0x0e0) +#define MW8080BW_VCOUNTER_START_NO_VBLANK (0x020) +#define MW8080BW_VCOUNTER_START_VBLANK (0x0da) +#define MW8080BW_INT_TRIGGER_COUNT_1 (0x080) +#define MW8080BW_INT_TRIGGER_VBLANK_1 (0) +#define MW8080BW_INT_TRIGGER_COUNT_2 MW8080BW_VCOUNTER_START_VBLANK +#define MW8080BW_INT_TRIGGER_VBLANK_2 (1) +#define MW8080BW_HPIXCOUNT MW8080BW_HBSTART + +class ir_state : public driver_device +{ +public: + ir_state(const machine_config &mconfig, device_type type, const char *tag) + : driver_device(mconfig, type, tag) + , m_timer_state(0) + , m_maincpu(*this,"maincpu") + , m_audiocpu(*this, "audiocpu") + , m_p_ram(*this, "ram") +// , m_samples(*this, "samples") + , m_screen(*this, "screen") + { } + + void ir(machine_config &config); + void init(); + +private: + + DECLARE_READ8_MEMBER(mw8080bw_shift_result_rev_r); + DECLARE_READ8_MEMBER(mw8080bw_reversable_shift_result_r); + DECLARE_WRITE8_MEMBER(mw8080bw_reversable_shift_count_w); + DECLARE_READ8_MEMBER(port02_r); + DECLARE_WRITE8_MEMBER(port03_w); + DECLARE_WRITE8_MEMBER(port05_w); + DECLARE_MACHINE_START(ir); + DECLARE_MACHINE_RESET(ir); + TIMER_CALLBACK_MEMBER(mw8080bw_interrupt_callback); + TIMER_DEVICE_CALLBACK_MEMBER(nmi_timer); + uint32_t screen_update_ir(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect); + void mem_map(address_map &map); + void io_map(address_map &map); + void sound_map(address_map &map); + bool m_flip_screen; + bool m_screen_red; + emu_timer *m_interrupt_timer; + uint8_t vpos_to_vysnc_chain_counter( int vpos ); + int vysnc_chain_counter_to_vpos( uint8_t counter, int vblank ); + void mw8080bw_create_interrupt_timer( ); + void mw8080bw_start_interrupt_timer( ); + u8 m_sound_data; + u16 m_timer_state; + + required_device<cpu_device> m_maincpu; + required_device<cpu_device> m_audiocpu; + required_shared_ptr<uint8_t> m_p_ram; +// required_device<samples_device> m_samples; + required_device<screen_device> m_screen; +}; + + +READ8_MEMBER(ir_state::port02_r) +{ + uint8_t data = ioport("IN2")->read(); + if (m_flip_screen) return data; + return (data & 0x8f) | (ioport("IN1")->read() & 0x70); +} + +void ir_state::mem_map(address_map &map) { + map.global_mask(0x7fff); + map(0x0000,0x1fff).rom().nopw(); + map(0x2000,0x3fff).mirror(0x4000).ram().share("ram"); + map(0x4000,0x5fff).rom().nopw(); +} + +void ir_state::io_map(address_map &map) { + map(0x00,0x00).portr("IN0"); + map(0x01,0x01).portr("IN1"); + map(0x02,0x02).r(FUNC(ir_state::port02_r)).w("mb14241",FUNC(mb14241_device::shift_count_w)); + map(0x03,0x03).r("mb14241",FUNC(mb14241_device::shift_result_r)).w(FUNC(ir_state::port03_w)); + map(0x04,0x04).w("mb14241",FUNC(mb14241_device::shift_data_w)); + map(0x05,0x05).w(FUNC(ir_state::port05_w)); + map(0x06,0x06).nopw(); //w(m_watchdog, FUNC(watchdog_timer_device::reset_w)); +} + +void ir_state::sound_map(address_map &map) { + map(0x0000,0x007f).ram(); // inside CPU + map(0xa001,0xa001).r("psg",FUNC(ay8910_device::data_r)); + map(0xa002,0xa003).w("psg",FUNC(ay8910_device::data_address_w)); + map(0xc000,0xc7ff).rom(); + map(0xe000,0xe7ff).rom().mirror(0x1800); +} + + +static INPUT_PORTS_START( ir ) + PORT_START("IN0") + PORT_BIT( 0x01, IP_ACTIVE_HIGH, IPT_UNUSED ) + PORT_DIPNAME( 0x06, 0x00, DEF_STR( Difficulty ) ) PORT_DIPLOCATION("SW1:5,6") + PORT_DIPSETTING( 0x00, DEF_STR( Easy ) ) // [code: 0x3b1-3b5] + PORT_DIPSETTING( 0x02, DEF_STR( Hard ) ) + PORT_DIPSETTING( 0x04, DEF_STR( Harder ) ) + PORT_DIPSETTING( 0x06, DEF_STR( Hardest ) ) + PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_UNKNOWN ) // must be low or game won't boot [code: 0x1a9-1af] + PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_UNUSED ) + PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_UNUSED ) + PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_UNUSED ) + PORT_DIPNAME( 0x80, 0x80, "Fuel Destroyed by Comet" ) PORT_DIPLOCATION("SW1:7") + PORT_DIPSETTING( 0x00, "3" ) + PORT_DIPSETTING( 0x80, "6" ) + //PORT_DIPNAME( 0x80, 0x00, "Fuel Usage" ) // [code: 0x1cb0-1cb6] + //PORT_DIPSETTING( 0x80, "Fast" ) + //PORT_DIPSETTING( 0x00, "Slow" ) + + PORT_START("IN1") + PORT_BIT( 0x01, IP_ACTIVE_LOW, IPT_COIN1 ) + PORT_BIT( 0x02, IP_ACTIVE_HIGH, IPT_START2 ) + PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_START1 ) + PORT_BIT( 0x08, IP_ACTIVE_HIGH, IPT_UNUSED ) + PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON1 ) + PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_2WAY + PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT ) PORT_2WAY + PORT_BIT( 0x80, IP_ACTIVE_LOW, IPT_UNUSED ) // [code: 0x750-75B] causes this switch to be ineffective + + PORT_START("IN2") + PORT_DIPNAME( 0x03, 0x00, DEF_STR( Lives ) ) PORT_DIPLOCATION("SW1:1,2") + PORT_DIPSETTING( 0x00, "3" ) + PORT_DIPSETTING( 0x01, "4" ) + PORT_DIPSETTING( 0x02, "5" ) + PORT_DIPSETTING( 0x03, "6" ) + PORT_BIT( 0x04, IP_ACTIVE_HIGH, IPT_TILT ) + PORT_DIPNAME( 0x08, 0x08, DEF_STR( Bonus_Life ) ) PORT_DIPLOCATION("SW1:4") + PORT_DIPSETTING( 0x00, "1500" ) // [code: 0x3b9-3c9] + PORT_DIPSETTING( 0x08, "2000" ) + PORT_BIT( 0x10, IP_ACTIVE_HIGH, IPT_BUTTON1 ) PORT_PLAYER(2) + PORT_BIT( 0x20, IP_ACTIVE_HIGH, IPT_JOYSTICK_LEFT ) PORT_2WAY PORT_PLAYER(2) + PORT_BIT( 0x40, IP_ACTIVE_HIGH, IPT_JOYSTICK_RIGHT ) PORT_2WAY PORT_PLAYER(2) + PORT_DIPNAME( 0x80, 0x00, DEF_STR( Coinage ) ) PORT_DIPLOCATION("SW1:8") + PORT_DIPSETTING( 0x80, DEF_STR( 2C_1C ) ) // 1 play 10p, 2 play 20p, 6 play 50p + PORT_DIPSETTING( 0x00, DEF_STR( 1C_1C ) ) // 1 play 20p, 2 play 40p, 3 play 50p + + PORT_START("CAB") + PORT_CONFNAME( 0x01, 0x00, DEF_STR( Cabinet ) ) + PORT_CONFSETTING( 0x00, DEF_STR( Upright ) ) + PORT_CONFSETTING( 0x01, DEF_STR( Cocktail ) ) +INPUT_PORTS_END + +uint8_t ir_state::vpos_to_vysnc_chain_counter( int vpos ) +{ + /* convert from a vertical position to the actual values on the vertical sync counters */ + uint8_t counter; + int vblank = (vpos >= MW8080BW_VBSTART); + + if (vblank) + counter = vpos - MW8080BW_VBSTART + MW8080BW_VCOUNTER_START_VBLANK; + else + counter = vpos + MW8080BW_VCOUNTER_START_NO_VBLANK; + + return counter; +} + + +int ir_state::vysnc_chain_counter_to_vpos( uint8_t counter, int vblank ) +{ + /* convert from the vertical sync counters to an actual vertical position */ + int vpos; + + if (vblank) + vpos = counter - MW8080BW_VCOUNTER_START_VBLANK + MW8080BW_VBSTART; + else + vpos = counter - MW8080BW_VCOUNTER_START_NO_VBLANK; + + return vpos; +} + + +TIMER_CALLBACK_MEMBER(ir_state::mw8080bw_interrupt_callback) +{ + uint8_t next_counter; + int next_vpos; + int next_vblank; + + /* compute vector and set the interrupt line */ + int vpos = m_screen->vpos(); + uint8_t counter = vpos_to_vysnc_chain_counter(vpos); + uint8_t vector = 0xc7 | ((counter & 0x40) >> 2) | ((~counter & 0x40) >> 3); + m_maincpu->set_input_line_and_vector(0, HOLD_LINE, vector); + + /* set up for next interrupt */ + if (counter == MW8080BW_INT_TRIGGER_COUNT_1) + { + next_counter = MW8080BW_INT_TRIGGER_COUNT_2; + next_vblank = MW8080BW_INT_TRIGGER_VBLANK_2; + } + else + { + next_counter = MW8080BW_INT_TRIGGER_COUNT_1; + next_vblank = MW8080BW_INT_TRIGGER_VBLANK_1; + } + + next_vpos = vysnc_chain_counter_to_vpos(next_counter, next_vblank); + m_interrupt_timer->adjust(m_screen->time_until_pos(next_vpos)); +} + + +void ir_state::mw8080bw_create_interrupt_timer( ) +{ + m_interrupt_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(ir_state::mw8080bw_interrupt_callback),this)); +} + + +void ir_state::mw8080bw_start_interrupt_timer( ) +{ + int vpos = vysnc_chain_counter_to_vpos(MW8080BW_INT_TRIGGER_COUNT_1, MW8080BW_INT_TRIGGER_VBLANK_1); + m_interrupt_timer->adjust(m_screen->time_until_pos(vpos)); +} + + + +/************************************* + * + * Machine setup + * + *************************************/ + +MACHINE_START_MEMBER( ir_state, ir ) +{ + mw8080bw_create_interrupt_timer(); +} + + +/************************************* + * + * Machine reset + * + *************************************/ + +MACHINE_RESET_MEMBER( ir_state, ir ) +{ + mw8080bw_start_interrupt_timer(); +} +#if 0 +static const char *const ir_sample_names[] = +{ + "*invrvnge", + "02", /* insert coin */ + "06", /* you shoot */ + "14", /* hit alien */ + "16", /* hit asteroid */ + "18", /* low fuel */ + "1a", /* successful docking */ + "1e", /* die */ + "22", /* start game */ + "24", /* alien diving */ + "26", /* alien steals fuel */ + "28", /* background sound 1 */ + "2a", /* background sound 2 */ + "2c", /* background sound 3 */ + "2e", /* background sound 4 */ + "30", /* build up fuel */ + "32", /* ufo fires */ + "34", /* high score */ + "36", /* docking stage */ + "38", /* bonus ship */ + "3a", /* thrust */ + 0 +}; +#endif + +/*************************************************************************************/ +/* Notes: */ +/* Init sequence: 0x01 (20 times), 0x40 (20 times), 0x4c, 0x40, 0x44, 0x40 (9 times).*/ +/* Player 1 start sequence: 0x0c, 0x20, 0x22. */ +/* Unknown codes: 0x0A, 0x0c */ +/*************************************************************************************/ +WRITE8_MEMBER(ir_state::port03_w) +{ + m_sound_data = data; +#if 0 + switch (data) + { + case 0x02: /* Coin */ + machine().sound().system_enable(1); /* enable sound so we can hear coin drop */ + m_samples->start(0, 0); + break; + + case 0x04: /* Attract Mode */ + machine().sound().system_enable(0); /* disable sound */ + break; + + case 0x06: + m_samples->start(1, 1); /* Shoot */ + break; + + case 0x14: + m_samples->start(2, 2); /* Hit Alien (0x0a happens at the same time) */ + break; + + case 0x16: + m_samples->start(2, 3); /* Hit Asteroid */ + break; + + case 0x18: /* Fuel Low */ + m_samples->start(5, 4); + break; + + case 0x1a: /* Successful docking */ + m_samples->start(0, 5); + break; + + case 0x1c: /* Finished fuelling */ + m_samples->stop(0); + break; + + case 0x1e: + m_samples->start(0, 6); /* Death */ + break; + + case 0x20: + machine().sound().system_enable(1); /* enable sound for game start */ + case 0x22: + m_samples->start(0, 7); /* Start Game */ + break; + + case 0x24: /* Alien dropping to steal fuel */ + m_samples->start(3, 8); + break; + + case 0x26: /* Alien lifting with fuel */ + m_samples->start(4, 9); + break; + + case 0x28: /* background sound level 1 */ + case 0x2a: /* background sound level 2 */ + case 0x2c: /* background sound level 3 */ + case 0x2e: /* background sound level 4 */ + m_samples->start(0, data/2-10, 1); + break; + + case 0x30: /* Fuel bar filling up */ + m_samples->start(0, 14); + break; + + case 0x32: /* UFO drops a bomb */ + m_samples->start(6, 15); + break; + + case 0x34: /* You got the highest score */ + m_samples->start(0, 16); + break; + + case 0x36: /* Docking stage */ + m_screen_red = 0; /* fix bug (2) in documentation */ + m_samples->start(0, 17); + break; + + case 0x38: /* Bonus ship */ + m_samples->start(8, 18); + break; + + case 0x3a: /* Thrust */ + m_samples->start(7, 19); + break; + + case 0x4c: + m_samples->stop(0); // using this to stop sound if game tilted + case 0x01: /* These 3 play at boot time */ + case 0x40: /* no idea if they are actual sounds */ + case 0x44: + + case 0x0a: /* with 0x14 */ + case 0x0c: /* used a lot but for what? */ + break; + + default: + if (data) printf("Undefined sound code: %X\n",data); + } +#endif +} + +WRITE8_MEMBER(ir_state::port05_w) +{ +/* + 00 - normal play + 0A, 0E - alternate during play/attract at about once per second (invrvngegw only) purpose unknown + 01 - briefly at boot time + 10 - different colour map (or screen red) when you die + 20 - flip screen */ + + m_screen_red = BIT(data, 4); + m_flip_screen = BIT(data, 5) & ioport("CAB")->read(); +} + +TIMER_DEVICE_CALLBACK_MEMBER(ir_state::nmi_timer) +{ + m_timer_state++; + if (m_timer_state < 0x1000) + return; + + m_audiocpu->set_input_line(INPUT_LINE_NMI, BIT(m_timer_state, 0) ? ASSERT_LINE : CLEAR_LINE ); + if (m_timer_state == 0xf000) + m_timer_state = 0x8000; +} + +uint32_t ir_state::screen_update_ir(screen_device &screen, bitmap_rgb32 &bitmap, const rectangle &cliprect) +{ + pen_t pens[8]; + offs_t offs, color_address; + uint8_t *prom; + uint8_t *color_map_base; + uint8_t i, x, y, data, fore_color, color; + + for (i = 0; i < 8; i++) + pens[i] = rgb_t(pal1bit(i >> 0), pal1bit(i >> 2), pal1bit(i >> 1)); + + prom = memregion("proms")->base(); + color_map_base = &prom[0x0000]; + + for (offs = 0; offs < m_p_ram.bytes(); offs++) + { + y = offs >> 5; + x = offs << 3; + + color_address = (offs >> 8 << 5) | (offs & 0x1f); + + data = m_p_ram[offs]; + fore_color = m_screen_red ? 1 : color_map_base[color_address] & 0x07; + + for (i = 0; i < 8; i++) + { + color = BIT(data, i) ? fore_color : 0; + + if (y >= MW8080BW_VCOUNTER_START_NO_VBLANK) + { + if (m_flip_screen) + bitmap.pix32(MW8080BW_VBSTART - 1 - (y - MW8080BW_VCOUNTER_START_NO_VBLANK), MW8080BW_HPIXCOUNT - 1 - x) = pens[color]; + else + bitmap.pix32(y - MW8080BW_VCOUNTER_START_NO_VBLANK, x) = pens[color]; + } + + x++; + } + } + + return 0; +} + +void ir_state::ir(machine_config &config) +{ + /* basic machine hardware */ + I8080(config, m_maincpu, MW8080BW_CPU_CLOCK); + m_maincpu->set_addrmap(AS_PROGRAM, &ir_state::mem_map); + m_maincpu->set_addrmap(AS_IO, &ir_state::io_map); + + MCFG_MACHINE_START_OVERRIDE(ir_state, ir) + MCFG_MACHINE_RESET_OVERRIDE(ir_state, ir) + + /* video hardware */ + SCREEN(config, m_screen, SCREEN_TYPE_RASTER); + m_screen->set_raw(MW8080BW_PIXEL_CLOCK, MW8080BW_HTOTAL, MW8080BW_HBEND, MW8080BW_HPIXCOUNT, MW8080BW_VTOTAL, MW8080BW_VBEND, MW8080BW_VBSTART); + m_screen->set_screen_update(FUNC(ir_state::screen_update_ir)); + + M6802(config, m_audiocpu, 4e6); // divided by 4 internally + m_audiocpu->set_addrmap(AS_PROGRAM, &ir_state::sound_map); + + /* add shifter */ + MB14241(config, "mb14241"); + + /* sound hardware */ + SPEAKER(config, "mono").front_center(); + + ay8910_device &psg(AY8910(config, "psg", 2e6)); + psg.port_a_read_callback().set([this] () { return m_sound_data >> 1; }); + psg.port_b_read_callback().set([this] () { return 0xff; }); + psg.add_route(ALL_OUTPUTS, "mono", 0.75); + + //SAMPLES(config, m_samples); + //m_samples->set_channels(9); + //m_samples->set_samples_names(ir_sample_names); + //m_samples->add_route(ALL_OUTPUTS, "mono", 1.0); + + TIMER(config, "nmi").configure_periodic(FUNC(ir_state::nmi_timer), attotime::from_hz(2e6/512)); +} + +void ir_state::init() +{ + uint8_t *rom = memregion("audiocpu")->base(); + for (offs_t i = 0xc000; i < 0xc800; i++) + rom[i] = bitswap<8>(rom[i], 7, 6, 5, 3, 4, 2, 1, 0); + for (offs_t i = 0xe000; i < 0xe800; i++) + rom[i] = bitswap<8>(rom[i], 7, 6, 5, 3, 4, 2, 1, 0); +} + +ROM_START( ir ) + ROM_REGION( 0x10000, "maincpu", 0 ) + ROM_LOAD( "h.ic36", 0x0000, 0x0800, CRC(0e229b9f) SHA1(617197bf94e9700cbbb2f32487dc47b318d4f2af) ) + ROM_LOAD( "g.ic35", 0x0800, 0x0800, CRC(26b38aa4) SHA1(f281c7ec47ce6ab61bfda2e7aa6a5b8a01f2c11e) ) + ROM_LOAD( "f.ic34", 0x1000, 0x0800, CRC(b3b2749e) SHA1(4f854f981396e2d6a959dd48cff12234074fb69b) ) + ROM_LOAD( "e.ic33", 0x1800, 0x0800, CRC(d8e75102) SHA1(86d5618944265947e3ce60fdf048d8fff4a55744) ) + + ROM_REGION( 0x10000, "audiocpu", 0 ) + ROM_LOAD( "snd.2c", 0xc000, 0x0800, CRC(135f3b16) SHA1(d472a6ca32c4a16cc1faf09f4a4876d75cd4ba24) ) + ROM_LOAD( "snd.1c", 0xe000, 0x0800, CRC(152fc85e) SHA1(df207d6e690287a56e4e330deaa5ee40a179f1fc) ) + + ROM_REGION( 0x0800, "proms", 0 ) + ROM_LOAD( "colour.bin", 0x0000, 0x0800, CRC(7de74988) SHA1(0b8c94b2bfdbc3921d60aad765df8af611f3fdd7) ) +ROM_END + +GAME( 1980?,ir, 0, ir, ir, ir_state, init, ROT270, "Zenitone-Microsec Ltd.", "Invader's Revenge (Extra Sounds)", MACHINE_SUPPORTS_SAVE ) + |