1 // license:LGPL-2.1+
2 // copyright-holders:Angelo Salese
3 /*******************************************************************************************************************
4 
5 Nichibutsu 1414M4 device emulation
6 
7 Written by Angelo Salese, based on researches by Tomasz Slanina with Legion
8 
9 This is some fancy blitter DMA or MCU or even a discrete structure that copies text strings to a 8-bit text layer in
10 various Nihon Bussan games;
11 
12 TODO:
13 - Identify what exactly this "device" is;
14 - The overlying text layer should actually be a base device for this (and used where not applicable like galivan,
15   armedf and bigfightr);
16 - Command triggering condition not understood (and diverges between galivan.cpp and armedf.cpp);
17 - where is the condition that makes "insert coin" text to properly blink?
18 - first byte meaning is completely unknown;
19 - Ninja Emaki triggers unknown commands 0x8000 & 0xff20;
20 
21 Notes:
22 - Just before any string in the "MCU" rom, there's a control byte, this meaning is as follows:
23   0?-- ---- ---- ---- interpret as data?
24   10-- ---- ---- ---- single string transfer
25   11-- ---- ---- ---- src -> dst copy, if destination != 0 fixed src, otherwise do a src -> dst
26   --xx xxxx xxxx xxxx destination offset in the VRAM tilemap
27 
28 ********************************************************************************************************************/
29 
30 #include "emu.h"
31 #include "machine/nb1414m4.h"
32 #include "screen.h"
33 
34 DEFINE_DEVICE_TYPE(NB1414M4, nb1414m4_device, "nb1414m4", "NB1414M4 Mahjong Custom")
35 
nb1414m4_device(const machine_config & mconfig,const char * tag,device_t * owner,uint32_t clock)36 nb1414m4_device::nb1414m4_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
37 	: device_t(mconfig, NB1414M4, tag, owner, clock)
38 	, device_video_interface(mconfig, *this)
39 	, m_data(*this, DEVICE_SELF)
40 {
41 }
42 
43 
44 //-------------------------------------------------
45 //  device_start - device-specific startup
46 //-------------------------------------------------
47 
device_start()48 void nb1414m4_device::device_start()
49 {
50 	m_previous_0200_command = 0xffff;
51 	m_previous_0200_command_frame = 0;
52 	m_in_game = false;
53 	save_item(NAME(m_previous_0200_command));
54 	save_item(NAME(m_previous_0200_command_frame));
55 	save_item(NAME(m_in_game));
56 }
57 
58 //-------------------------------------------------
59 //  device_reset - device-specific startup
60 //-------------------------------------------------
61 
device_reset()62 void nb1414m4_device::device_reset()
63 {
64 	m_previous_0200_command = 0xffff;
65 	m_previous_0200_command_frame = 0;
66 	m_in_game = false;
67 }
68 
69 /*****************************************************************************
70     DEVICE HANDLERS
71 *****************************************************************************/
72 
dma(uint16_t src,uint16_t dst,uint16_t size,uint8_t condition,uint8_t * vram)73 void nb1414m4_device::dma(uint16_t src, uint16_t dst, uint16_t size, uint8_t condition, uint8_t *vram)
74 {
75 	int i;
76 
77 	for(i=0;i<size;i++)
78 	{
79 		if(i+dst+0x000 < 18) //avoid param overwrite
80 			continue;
81 
82 		vram[i+dst+0x000] = (condition) ? (m_data[i+(0)+src] & 0xff) : 0x20;
83 
84 		vram[i+dst+0x400] = (condition) ? (m_data[i+(size)+src] & 0xff) : m_data[0x13];
85 	}
86 }
87 
fill(uint16_t dst,uint8_t tile,uint8_t pal,uint8_t * vram)88 void nb1414m4_device::fill(uint16_t dst, uint8_t tile, uint8_t pal, uint8_t *vram)
89 {
90 	int i;
91 
92 	for(i=0;i<0x400;i++)
93 	{
94 		if(i+dst+0x000 < 18) //avoid param overwrite
95 			continue;
96 
97 		vram[i+dst+0x000] = tile;
98 		vram[i+dst+0x400] = pal;
99 	}
100 }
101 
insert_coin_msg(uint8_t * vram)102 void nb1414m4_device::insert_coin_msg(uint8_t *vram)
103 {
104 	if (m_in_game)
105 		return;
106 
107 	int credit_count = (vram[0xf] & 0xff);
108 	uint8_t fl_cond = screen().frame_number() & 0x10; /* for insert coin "flickering" */
109 	uint16_t dst;
110 
111 	if(credit_count == 0)
112 	{
113 		dst = (m_data[0x01]<<8|m_data[0x02]) & 0x3fff;
114 
115 		dma(0x0003,dst,0x10,fl_cond,vram);
116 	}
117 	else
118 	{
119 		dst = (m_data[0x49]<<8|m_data[0x4a]) & 0x3fff;
120 
121 		dma(0x004b,dst,0x18,1,vram);
122 	}
123 }
124 
credit_msg(uint8_t * vram)125 void nb1414m4_device::credit_msg(uint8_t *vram)
126 {
127 	int credit_count = (vram[0xf] & 0xff);
128 	uint8_t fl_cond = screen().frame_number() & 0x10; /* for insert coin "flickering" */
129 	uint16_t dst;
130 
131 	dst = ((m_data[0x023]<<8)|(m_data[0x024]&0xff)) & 0x3fff;
132 	dma(0x0025,dst,0x10,1,vram); /* credit */
133 
134 	/* credit num */
135 	dst = ((m_data[0x045]<<8)|(m_data[0x046]&0xff)) & 0x3fff;
136 	vram[dst+0x000] = (credit_count & 0xf0) ? (((credit_count & 0xf0)>>4) + 0x30) : 0x20;
137 	vram[dst+0x400] = (m_data[0x47]);
138 	vram[dst+0x001] = ((credit_count & 0x0f) + 0x30);
139 	vram[dst+0x401] = (m_data[0x48]);
140 
141 	if (m_in_game)
142 		return;
143 
144 	if(credit_count == 1) /* ONE PLAYER ONLY */
145 	{
146 		dst = ((m_data[0x07b]<<8)|(m_data[0x07c]&0xff)) & 0x3fff;
147 		dma(0x007d,dst,0x18,fl_cond,vram);
148 	}
149 	else if(credit_count > 1) /* ONE OR TWO PLAYERS */
150 	{
151 		dst = ((m_data[0x0ad]<<8)|(m_data[0x0ae]&0xff)) & 0x3fff;
152 		dma(0x00af,dst,0x18,fl_cond,vram);
153 	}
154 }
155 
kozure_score_msg(uint16_t dst,uint8_t src_base,uint8_t * vram)156 void nb1414m4_device::kozure_score_msg(uint16_t dst, uint8_t src_base, uint8_t *vram)
157 {
158 	int i;
159 	uint8_t first_digit;
160 	uint8_t res;
161 
162 	first_digit = 0;
163 
164 	for(i=0;i<6;i++)
165 	{
166 		res = ((vram[(i/2)+5+src_base*3] >> (!(i & 1) * 4)) & 0xf);
167 
168 		if(first_digit || res)
169 		{
170 			vram[i+dst+0x0000] = res + 0x30;
171 			first_digit = 1;
172 		}
173 		else
174 			vram[i+dst+0x0000] = 0x20;
175 
176 		vram[i+dst+0x0400] = m_data[0x10f+(src_base*0x1c)+i];
177 	}
178 
179 	vram[6+dst+0x0000] = vram[5+dst+0x0000] == 0x20 ? 0x20 : 0x30;
180 	vram[6+dst+0x0400] = m_data[0x10f+(src_base*0x1c)+6];
181 	vram[7+dst+0x0000] = 0x30;
182 	vram[7+dst+0x0400] = m_data[0x10f+(src_base*0x1c)+7];
183 
184 }
185 
_0200(uint16_t mcu_cmd,uint8_t * vram)186 void nb1414m4_device::_0200(uint16_t mcu_cmd, uint8_t *vram)
187 {
188 	uint16_t dst;
189 
190 	// In any game, this bit is set when now playing.
191 	// If it is set, "INSERT COIN" etc. are not displayed.
192 	m_in_game = (mcu_cmd & 0x80) != 0;
193 
194 	// If the same command in an extremely short cycle (1 frame or less), ignored it.
195 	// This is required to displaying continue screen of ninjaemak.
196 	// ninjaemak calls this command to clear the screen, draw the continuation screen, and then immediately call the same command.
197 	// This second command must be ignored in order not to corrupt the continue screen.
198 	if (m_previous_0200_command == mcu_cmd && (screen().frame_number() - m_previous_0200_command_frame) <= 1)
199 		return;
200 
201 	dst = (m_data[0x330+((mcu_cmd & 0xf)*2)]<<8)|(m_data[0x331+((mcu_cmd & 0xf)*2)]&0xff);
202 
203 	dst &= 0x3fff;
204 
205 	if(dst & 0x7ff) // fill
206 		fill(0x0000,m_data[dst],m_data[dst+1],vram);
207 	else // src -> dst
208 		dma(dst,0x0000,0x400,1,vram);
209 
210 	m_previous_0200_command = mcu_cmd;
211 	m_previous_0200_command_frame = screen().frame_number();
212 }
213 
214 /*
215 [0x02] & 0x01 p1 up
216 [0x02] & 0x02 p1 down
217 [0x02] & 0x04 p1 left
218 [0x02] & 0x08 p1 right
219 [0x02] & 0x10 p1 button 1
220 [0x02] & 0x20 p1 button 2
221 [0x02] & 0x40 p1 button 3
222 [0x03] & 0x01 p2 up
223 [0x03] & 0x02 p2 down
224 [0x03] & 0x04 p2 left
225 [0x03] & 0x08 p2 right
226 [0x03] & 0x10 p2 button 1
227 [0x03] & 0x20 p2 button 2
228 [0x03] & 0x40 p2 button 3
229 [0x04] & 0x10 service
230 [0x04] & 0x04 coin A
231 [0x04] & 0x08 coin B
232 [0x04] & 0x01 start 1
233 [0x04] & 0x02 start 2
234 [0x05] DSW1
235 [0x06] DSW2
236 [0x07] & 0x40 demo sounds ON / OFF
237 [0x07] & 0x7 lives setting
238 [0x07] & 0x80 cabinet (upright / table)
239 [0x07] & 0x30 difficulty (easy / normal / hard / hardest)
240 [0x0f] coinage A
241 [0x10] coinage B
242 [0x11] sound test num
243 */
_0600(uint8_t is2p,uint8_t * vram)244 void nb1414m4_device::_0600(uint8_t is2p, uint8_t *vram)
245 {
246 	uint16_t dst;
247 	int i;
248 
249 	dst = ((m_data[0x1f5]<<8)|(m_data[0x1f6]&0xff)) & 0x3fff;
250 	vram[dst] = (vram[7] & 0x7) + 0x30;//m_data[0x1f7];
251 
252 	dst = ((m_data[0x1f8]<<8)|(m_data[0x1f9]&0xff)) & 0x3fff;
253 	dma(0x1fa + (((vram[7] & 0x30) >> 4) * 0x18),dst,12,1,vram);
254 
255 	// 0x25a - 0x261 unknown meaning
256 
257 	dst = ((m_data[0x262]<<8)|(m_data[0x263]&0xff)) & 0x3fff;
258 	dma(0x264 + (((vram[7] & 0x80) >> 7) * 0x18),dst,12,1,vram);
259 
260 	dst = ((m_data[0x294]<<8)|(m_data[0x295]&0xff)) & 0x3fff;
261 	dma(0x296 + (((vram[7] & 0x40) >> 6) * 0x18),dst,12,1,vram);
262 
263 	dst = ((m_data[0x2c6]<<8)|(m_data[0x2c7]&0xff)) & 0x3fff;
264 	vram[dst] = ((vram[0xf] & 0xf0) >> 4) + 0x30;//m_data[0x2c8];
265 
266 	dst = ((m_data[0x2c9]<<8)|(m_data[0x2ca]&0xff)) & 0x3fff;
267 	vram[dst] = ((vram[0xf] & 0x0f) >> 0) + 0x30;//m_data[0x2cb];
268 
269 	dst = ((m_data[0x2cc]<<8)|(m_data[0x2cd]&0xff)) & 0x3fff;
270 	vram[dst] = ((vram[0x10] & 0xf0) >> 4) + 0x30;//m_data[0x2ce];
271 
272 	dst = ((m_data[0x2cf]<<8)|(m_data[0x2d0]&0xff)) & 0x3fff;
273 	vram[dst] = ((vram[0x10] & 0x0f) >> 0) + 0x30;//m_data[0x2d1];
274 
275 	dst = ((m_data[0x2d2]<<8)|(m_data[0x2d3]&0xff)) & 0x3fff;
276 	vram[dst+0] = ((vram[0x11] & 0xf0) >> 4) + 0x30;//m_data[0x2d4];
277 	vram[dst+1] = (vram[0x11] & 0x0f) + 0x30;//m_data[0x2d5];
278 
279 	dst = ((m_data[0x2d6]<<8)|(m_data[0x2d7]&0xff)) & 0x3fff;
280 	dma(0x2d8 + (is2p * 0x18),dst,12,1,vram); // 1p / 2p string
281 
282 	dst = ((m_data[0x308]<<8)|(m_data[0x309]&0xff)) & 0x3fff;
283 	for(i=0;i<5;i++) /* system inputs */
284 		dma(0x310 + (((vram[0x04] >> (4-i)) & 1) * 6),dst + (i * 0x20),0x3,1,vram);
285 
286 	dst = ((m_data[0x30a]<<8)|(m_data[0x30b]&0xff)) & 0x3fff;
287 	for(i=0;i<7;i++) /* 1p / 2p inputs */
288 		dma(0x310 + (((vram[0x02 + is2p] >> (6-i)) & 1) * 6),dst + (i * 0x20),0x3,1,vram);
289 
290 	dst = ((m_data[0x30c]<<8)|(m_data[0x30d]&0xff)) & 0x3fff;
291 	for(i=0;i<8;i++) /* dips */
292 		dma(0x310 + (((vram[0x05] >> (7-i)) & 1) * 6),dst + (i * 0x20),0x3,1,vram);
293 
294 	dst = ((m_data[0x30e]<<8)|(m_data[0x30f]&0xff)) & 0x3fff;
295 	for(i=0;i<8;i++) /* dips */
296 		dma(0x310 + (((vram[0x06] >> (7-i)) & 1) * 6),dst + (i * 0x20),0x3,1,vram);
297 }
298 
_0e00(uint16_t mcu_cmd,uint8_t * vram)299 void nb1414m4_device::_0e00(uint16_t mcu_cmd, uint8_t *vram)
300 {
301 	uint16_t dst;
302 
303 	dst = ((m_data[0xdf]<<8)|(m_data[0xe0]&0xff)) & 0x3fff;
304 	dma(0x00e1,dst,8,1,vram); /* hi-score */
305 
306 	dst = ((m_data[0xfb]<<8)|(m_data[0xfc]&0xff)) & 0x3fff;
307 	dma(0x00fd,dst,8,!(mcu_cmd & 1),vram); /* 1p-msg */
308 	dst = ((m_data[0x10d]<<8)|(m_data[0x10e]&0xff)) & 0x3fff;
309 	kozure_score_msg(dst,0,vram); /* 1p score */
310 
311 	if(mcu_cmd & 0x80)
312 	{
313 		dst = ((m_data[0x117]<<8)|(m_data[0x118]&0xff)) & 0x3fff;
314 		dma(0x0119,dst,8,!(mcu_cmd & 2),vram); /* 2p-msg */
315 		dst = ((m_data[0x129]<<8)|(m_data[0x12a]&0xff)) & 0x3fff;
316 		kozure_score_msg(dst,1,vram); /* 2p score */
317 	}
318 
319 	if((mcu_cmd & 0x04) == 0)
320 	{
321 		dst = ((m_data[0x133]<<8)|(m_data[0x134]&0xff)) & 0x3fff;
322 		dma(0x0135,dst,0x10,1,vram); /* game over */
323 		insert_coin_msg(vram);
324 		if((mcu_cmd & 0x18) == 0) // TODO: either one of these two disables credit display
325 			credit_msg(vram);
326 	}
327 }
328 
329 /*****************************************************************************
330     DEVICE SETTERS
331 *****************************************************************************/
332 
vblank_trigger()333 void nb1414m4_device::vblank_trigger()
334 {
335 	// TODO: use this for frame number synchronization instead of screen().frame_number()
336 	//  real HW references definitely syncs insert coin blinking after POST so whatever is the host actually
337 	//  have an interest over vblank signal.
338 }
339 
exec(uint16_t mcu_cmd,uint8_t * vram,uint16_t & scrollx,uint16_t & scrolly,tilemap_t * tilemap)340 void nb1414m4_device::exec(uint16_t mcu_cmd, uint8_t *vram, uint16_t &scrollx, uint16_t &scrolly, tilemap_t *tilemap)
341 {
342 	/* latch fg scroll values */
343 	scrollx = (vram[0x0d] & 0xff) | ((vram[0x0e] & 0xff) << 8);
344 	scrolly = (vram[0x0b] & 0xff) | ((vram[0x0c] & 0xff) << 8);
345 
346 	/* process the command */
347 	switch(mcu_cmd & 0xff00)
348 	{
349 		/* title screen / continue screens */
350 		case 0x0000: insert_coin_msg(vram); credit_msg(vram); break;
351 
352 		/* direct DMA'ing / fill */
353 		case 0x0200: _0200(mcu_cmd & 0x87,vram); break;
354 
355 		/* service mode */
356 		case 0x0600: _0600(mcu_cmd & 1,vram); break;
357 
358 		/* gameplay */
359 		case 0x0e00: _0e00(mcu_cmd & 0xff,vram); break;
360 
361 		case 0x8000: break; //Ninja Emaki, attract mode
362 		case 0xff00: break; //Ninja Emaki POST, presumably invalid
363 		default:
364 			popmessage("NB 1414M4 executes %04x command, contact MAMEdev",mcu_cmd);
365 			break;
366 	}
367 
368 	/* mark tiles dirty */
369 	tilemap->mark_all_dirty();
370 }
371