1 // license:BSD-3-Clause
2 // copyright-holders:MetalliC
3 
4 /*
5   Gambling hardware based on "Specialist MX" computer
6   (c) 199? Dinaris
7 
8   Main components:
9    Z80 CPU,
10    27256 ROM,
11    2x KR580VV55(i8255 PPI),
12    KR580VI53(i8253 PIT),
13    8x KR565RU5(4164) DRAM,
14    3x KR573RU10(HM6516) SRAM
15 
16    How to enter statistics mode:
17     - press Test button, "CODE" text will appear
18     - repeat 4x times
19       - press Test button (code_digit - 1) times
20       - press and hold Test button until "INPUT" text appears
21       - release Test button
22     Currently we use 0000 hardware code, in such case game will accept any code sequence.
23    Now you may press "Clear stats" key few times to clear all the non-volatile play statistics.
24    Or press "Clear stats" once, then hold for a several seconds, and then press again - game will clear NVRAM and also raise few flags, effect is not known.
25 
26 */
27 
28 #include "emu.h"
29 #include "cpu/z80/z80.h"
30 #include "audio/special.h"
31 #include "machine/i8255.h"
32 #include "machine/pit8253.h"
33 #include "machine/nvram.h"
34 #include "machine/ticket.h"
35 #include "emupal.h"
36 #include "screen.h"
37 #include "speaker.h"
38 
39 class dinaris_state : public driver_device
40 {
41 public:
dinaris_state(const machine_config & mconfig,device_type type,const char * tag)42 	dinaris_state(const machine_config &mconfig, device_type type, const char *tag) :
43 		driver_device(mconfig, type, tag)
44 		, m_maincpu(*this, "maincpu")
45 		, m_ppi(*this, "ppi8255")
46 		, m_ppi2(*this, "ppi82552")
47 		, m_pit(*this, "pit8253")
48 		, m_palette(*this, "palette")
49 		, m_nvram(*this, "nvram")
50 		, m_vram(*this, "vram")
51 		, m_hopper(*this, "hopper")
52 		, m_lamps(*this, "lamp%u", 0U)
53 		, m_sram_en(false)
54 		, m_cold_boot(0)
55 	{ }
56 
57 	void dice(machine_config &config);
58 
DECLARE_CUSTOM_INPUT_MEMBER(boot_r)59 	DECLARE_CUSTOM_INPUT_MEMBER(boot_r) { return m_cold_boot; }
DECLARE_INPUT_CHANGED_MEMBER(ram_test)60 	DECLARE_INPUT_CHANGED_MEMBER(ram_test) { m_maincpu->set_input_line(INPUT_LINE_NMI, newval ? ASSERT_LINE : CLEAR_LINE); }
61 
62 protected:
63 	enum
64 	{
65 		TIMER_COLD_BOOT
66 	};
67 
68 	void machine_start() override;
69 	virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) override;
70 
71 	void dice_palette(palette_device &palette) const;
72 	u32 screen_update_dice(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
73 
74 	void dice_mem(address_map &map);
75 	void dice_io(address_map &map);
76 
77 	required_device<cpu_device> m_maincpu;
78 	required_device<i8255_device> m_ppi;
79 	required_device<i8255_device> m_ppi2;
80 	required_device<pit8253_device> m_pit;
81 	required_device<palette_device> m_palette;
82 	required_device<nvram_device> m_nvram;
83 	required_shared_ptr<u8> m_vram;
84 	required_device<hopper_device> m_hopper;
85 	output_finder<8> m_lamps;
86 
87 	void lamps_w(u8 data);
88 	u8 ppi2a_r();
89 	void ppi2c_w(u8 data);
90 	u8 code_r(offs_t offset);
91 
92 	std::unique_ptr<u8[]> m_sram;
93 	bool m_sram_en;
94 	int m_cold_boot;
95 };
96 
97 
98 static INPUT_PORTS_START(dice)
99 	PORT_START("P0")
100 	PORT_BIT(0x01, IP_ACTIVE_LOW, IPT_GAMBLE_LOW)    // 6 or less / double less
101 	PORT_BIT(0x02, IP_ACTIVE_LOW, IPT_GAMBLE_D_UP)   // 12/11
102 	PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_GAMBLE_BET)    // bet / field
103 	PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_GAMBLE_HALF)   // point/7
104 	PORT_BIT(0x10, IP_ACTIVE_LOW, IPT_GAMBLE_HIGH)   // 8 or more / double more
105 	PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_GAMBLE_DEAL)   // start / double
106 	PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_GAMBLE_PAYOUT) // payout / take
PORT_CUSTOM_MEMBER(dinaris_state,boot_r)107 	PORT_BIT(0x80, IP_ACTIVE_HIGH, IPT_CUSTOM) PORT_CUSTOM_MEMBER(dinaris_state, boot_r)
108 
109 	PORT_START("P1")
110 	PORT_DIPNAME(0x01, 0x00, DEF_STR(Language))
111 	PORT_DIPSETTING(0x00, DEF_STR(English))
112 	PORT_DIPSETTING(0x01, "Russian")
113 	PORT_SERVICE_NO_TOGGLE(0x02, IP_ACTIVE_LOW)
114 	PORT_BIT(0x04, IP_ACTIVE_LOW, IPT_SERVICE1) PORT_NAME("Clear stats")
115 	PORT_BIT(0x08, IP_ACTIVE_LOW, IPT_COIN1)
116 	PORT_BIT(0x10, IP_ACTIVE_HIGH, IPT_CUSTOM) PORT_READ_LINE_DEVICE_MEMBER("hopper", hopper_device, line_r)
117 	PORT_BIT(0x20, IP_ACTIVE_LOW, IPT_SERVICE2) PORT_NAME("Reset")
118 	PORT_BIT(0x40, IP_ACTIVE_LOW, IPT_UNUSED)
119 	PORT_BIT(0x80, IP_ACTIVE_LOW, IPT_UNUSED)
120 
121 	PORT_START("NMI")
122 	PORT_BIT(0x01, IP_ACTIVE_HIGH, IPT_SERVICE3) PORT_NAME("RAM Test") PORT_CHANGED_MEMBER(DEVICE_SELF, dinaris_state, ram_test, 0)
123 INPUT_PORTS_END
124 
125 void dinaris_state::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr)
126 {
127 	switch (id)
128 	{
129 	case TIMER_COLD_BOOT:
130 		m_cold_boot = 1;
131 		break;
132 	default:
133 		throw emu_fatalerror("Unknown id in dinaris_state::device_timer");
134 	}
135 }
136 
machine_start()137 void dinaris_state::machine_start()
138 {
139 	m_lamps.resolve();
140 
141 	constexpr int size = 0x800; // actually used only 256 bytes
142 	m_sram = std::make_unique<u8[]>(size);
143 	m_nvram->set_base(&m_sram[0], size);
144 
145 	save_pointer(NAME(m_sram), size);
146 	save_item(NAME(m_sram_en));
147 	save_item(NAME(m_cold_boot));
148 
149 	timer_set(attotime::from_msec(100), TIMER_COLD_BOOT);
150 }
151 
dice_mem(address_map & map)152 void dinaris_state::dice_mem(address_map &map)
153 {
154 	map(0x0000, 0x7fff).rom();
155 	map(0x8000, 0x8fff).ram(); // SRAM
156 	map(0x9000, 0xffff).ram().share("vram"); // DRAM
157 }
158 
dice_io(address_map & map)159 void dinaris_state::dice_io(address_map &map)
160 {
161 	map.global_mask(0x000f);
162 	map(0x00, 0x03).rw(m_ppi, FUNC(i8255_device::read), FUNC(i8255_device::write));
163 	map(0x04, 0x07).rw(m_ppi2, FUNC(i8255_device::read), FUNC(i8255_device::write));
164 	map(0x08, 0x0b).rw(m_pit, FUNC(pit8253_device::read), FUNC(pit8253_device::write));
165 	map(0x0c, 0x0d).r(FUNC(dinaris_state::code_r)).mirror(0x02);
166 }
167 
168 // borrowed from Specialist MX, probably wrong
169 static constexpr rgb_t specimx_pens[16] = {
170 	{ 0x00, 0x00, 0x00 }, // 0
171 	{ 0x00, 0x00, 0xaa }, // 1
172 	{ 0x00, 0xaa, 0x00 }, // 2
173 	{ 0x00, 0xaa, 0xaa }, // 3
174 	{ 0xaa, 0x00, 0x00 }, // 4
175 	{ 0xaa, 0x00, 0xaa }, // 5
176 	{ 0xaa, 0xaa, 0x00 }, // 6
177 	{ 0xaa, 0xaa, 0xaa }, // 7
178 	{ 0x55, 0x55, 0x55 }, // 8
179 	{ 0x55, 0x55, 0xff }, // 9
180 	{ 0x55, 0xff, 0x55 }, // A
181 	{ 0x55, 0xff, 0xff }, // B
182 	{ 0xff, 0x55, 0x55 }, // C
183 	{ 0xff, 0x55, 0xff }, // D
184 	{ 0xff, 0xff, 0x55 }, // E
185 	{ 0xff, 0xff, 0xff }  // F
186 };
187 
dice_palette(palette_device & palette) const188 void dinaris_state::dice_palette(palette_device &palette) const
189 {
190 	palette.set_pen_colors(0, specimx_pens);
191 }
192 
screen_update_dice(screen_device & screen,bitmap_ind16 & bitmap,const rectangle & cliprect)193 u32 dinaris_state::screen_update_dice(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect)
194 {
195 	// note: attribute area bytes set colors for 4x2 pix screen elements, not 8x1 like Specialist MX.
196 	for (int x = 0; x < 48; x++)
197 	{
198 		for (int y = 0; y < 256; y++)
199 		{
200 			u8 const code = m_vram[0x0000 + y + x * 256];
201 			u8 const color1 = m_vram[0x4000 + (y & 0xfe) + x * 256];
202 			u8 const color2 = m_vram[0x4000 + (y | 0x01) + x * 256];
203 			for (int b = 7; b >= 4; b--)
204 				bitmap.pix(y, x * 8 + (7 - b)) = BIT(code, b) ? (color1 & 0xf) : (color1 >> 4);
205 			for (int b = 3; b >= 0; b--)
206 				bitmap.pix(y, x * 8 + (7 - b)) = BIT(code, b) ? (color2 & 0xf) : (color2 >> 4);
207 		}
208 	}
209 	return 0;
210 }
211 
lamps_w(u8 data)212 void dinaris_state::lamps_w(u8 data)
213 {
214 	// TODO identify each lamp
215 	for (int i = 0; i < 8; i++)
216 		m_lamps[i] = BIT(data, i);
217 }
218 
ppi2a_r()219 u8 dinaris_state::ppi2a_r()
220 {
221 	return m_sram_en ? m_sram[m_ppi2->pb_r()] : 0xff;
222 }
223 
ppi2c_w(u8 data)224 void dinaris_state::ppi2c_w(u8 data)
225 {
226 	if (!BIT(data, 2))
227 		m_sram_en = true;
228 
229 	if (!BIT(data, 1))
230 		m_sram_en = false;
231 
232 	if (!BIT(data, 0) && m_sram_en)
233 		m_sram[m_ppi2->pb_r()] = m_ppi2->pa_r();
234 
235 	m_hopper->motor_w(((data & 0x68) == 0x60) ? 1 : 0);
236 }
237 
code_r(offs_t offset)238 u8 dinaris_state::code_r(offs_t offset)
239 {
240 	// it is not clear how statistics mode code was set, related components was removed from PCB
241 	// currently we return 0 so game will accept any code sequence
242 	return 0x0000 >> (offset * 8);
243 }
244 
dice(machine_config & config)245 void dinaris_state::dice(machine_config &config)
246 {
247 	// Basic machine hardware
248 	Z80(config, m_maincpu, XTAL(8'000'000) / 4); // not confirmed
249 	m_maincpu->set_addrmap(AS_PROGRAM, &dinaris_state::dice_mem);
250 	m_maincpu->set_addrmap(AS_IO, &dinaris_state::dice_io);
251 	m_maincpu->set_vblank_int("screen", FUNC(dinaris_state::irq0_line_hold));
252 
253 	// Video
254 	screen_device &screen(SCREEN(config, "screen", SCREEN_TYPE_RASTER));
255 	screen.set_raw(XTAL(8'000'000), 512, 0, 384, 312, 0, 256);
256 	screen.set_screen_update(FUNC(dinaris_state::screen_update_dice));
257 	screen.set_palette(m_palette);
258 
259 	PALETTE(config, m_palette, FUNC(dinaris_state::dice_palette), 16);
260 
261 	// Sound
262 	SPEAKER(config, "speaker").front_center();
263 	SPECIMX_SND(config, "custom", 0).add_route(ALL_OUTPUTS, "speaker", 1.0);
264 
265 	PIT8253(config, m_pit, 0);
266 	m_pit->set_clk<0>(XTAL(8'000'000) / 4);
267 	m_pit->out_handler<0>().set("custom", FUNC(specimx_sound_device::set_input_ch0));
268 	m_pit->set_clk<1>(XTAL(8'000'000) / 4);
269 	m_pit->out_handler<1>().set("custom", FUNC(specimx_sound_device::set_input_ch1));
270 	m_pit->set_clk<2>(XTAL(8'000'000) / 4);
271 	m_pit->out_handler<2>().set("custom", FUNC(specimx_sound_device::set_input_ch2));
272 
273 	// Devices
274 	I8255(config, m_ppi);
275 	m_ppi->in_pa_callback().set_ioport("P0");
276 	m_ppi->in_pb_callback().set_ioport("P1");
277 	m_ppi->out_pc_callback().set(FUNC(dinaris_state::lamps_w));
278 
279 	I8255(config, m_ppi2);
280 	m_ppi2->in_pa_callback().set(FUNC(dinaris_state::ppi2a_r));
281 	m_ppi2->out_pc_callback().set(FUNC(dinaris_state::ppi2c_w));
282 
283 	NVRAM(config, m_nvram, nvram_device::DEFAULT_ALL_0);
284 	HOPPER(config, m_hopper, attotime::from_msec(100), TICKET_MOTOR_ACTIVE_HIGH, TICKET_STATUS_ACTIVE_LOW);
285 }
286 
287 ROM_START(dindice)
288 	ROM_REGION( 0x8000, "maincpu", 0)
289 	ROM_LOAD( "27256.bin", 0x0000, 0x8000, CRC(511f8ba8) SHA1(e75a2cab80ac6b08a19d1adb8ba9bb321aa5e7a8))
290 ROM_END
291 
292 GAME( 199?, dindice, 0,    dice,     dice,     dinaris_state, empty_init, ROT0,  "Dinaris",   "Dice game", MACHINE_SUPPORTS_SAVE|MACHINE_IMPERFECT_COLORS)
293