1 /*
2 * Copyright (C) 2005-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
7 */
8
9 #include "DVDDemuxSPU.h"
10
11 #include "DVDCodecs/Overlay/DVDOverlaySpu.h"
12 #include "cores/VideoPlayer/Interface/TimingConstants.h"
13 #include "utils/log.h"
14
15 #include <locale.h>
16 #include <stdlib.h>
17
18 #undef ALIGN
19 #define ALIGN(value, alignment) (((value)+((alignment)-1))&~((alignment)-1))
20
21 // #define SPU_DEBUG
22
DebugLog(const char * format,...)23 void DebugLog(const char *format, ...)
24 {
25 #ifdef SPU_DEBUG
26 static char temp_spubuffer[1024];
27 va_list va;
28
29 va_start(va, format);
30 _vsnprintf(temp_spubuffer, 1024, format, va);
31 va_end(va);
32
33 CLog::Log(LOGDEBUG,temp_spubuffer);
34 #endif
35 }
36
CDVDDemuxSPU()37 CDVDDemuxSPU::CDVDDemuxSPU()
38 {
39 memset(&m_spuData, 0, sizeof(m_spuData));
40 memset(m_clut, 0, sizeof(m_clut));
41 m_bHasClut = false;
42 }
43
~CDVDDemuxSPU()44 CDVDDemuxSPU::~CDVDDemuxSPU()
45 {
46 free(m_spuData.data);
47 }
48
Reset()49 void CDVDDemuxSPU::Reset()
50 {
51 FlushCurrentPacket();
52
53 // We can't reset this during playback, cause we don't always
54 // get a new clut from libdvdnav leading to invalid colors
55 // so let's just never reset it. It will only be reset
56 // when VideoPlayer is destructed and constructed
57 // m_bHasClut = false;
58 // memset(m_clut, 0, sizeof(m_clut));
59 }
60
FlushCurrentPacket()61 void CDVDDemuxSPU::FlushCurrentPacket()
62 {
63 free(m_spuData.data);
64 memset(&m_spuData, 0, sizeof(m_spuData));
65 }
66
AddData(uint8_t * data,int iSize,double pts)67 CDVDOverlaySpu* CDVDDemuxSPU::AddData(uint8_t* data, int iSize, double pts)
68 {
69 SPUData* pSPUData = &m_spuData;
70
71 if (pSPUData->iNeededSize > 0 &&
72 (pSPUData->iSize != pSPUData->iNeededSize) &&
73 ((pSPUData->iSize + iSize) > pSPUData->iNeededSize))
74 {
75 DebugLog("corrupt spu data: packet does not fit");
76 m_spuData.iNeededSize = 0;
77 m_spuData.iSize = 0;
78 return NULL;
79 }
80
81 // check if we are about to start a new packet
82 if (pSPUData->iSize == pSPUData->iNeededSize)
83 {
84 // for now we don't delete the memory associated with m_spuData.data
85 pSPUData->iSize = 0;
86
87 // check spu data length, only needed / possible in the first spu packet
88 uint16_t length = data[0] << 8 | data[1];
89 if (length == 0)
90 {
91 DebugLog("corrupt spu data: zero packet");
92 m_spuData.iNeededSize = 0;
93 m_spuData.iSize = 0;
94 return NULL;
95 }
96 if (length > iSize) pSPUData->iNeededSize = length;
97 else pSPUData->iNeededSize = iSize;
98
99 // set presentation time stamp
100 pSPUData->pts = pts;
101 }
102
103 // allocate data if not already done ( done in blocks off 16384 bytes )
104 // or allocate some more if 16384 bytes is not enough
105 if ((pSPUData->iSize + iSize) > pSPUData->iAllocatedSize)
106 {
107 uint8_t* tmpptr = (uint8_t*)realloc(pSPUData->data, ALIGN(pSPUData->iSize + iSize, 0x4000));
108 if (!tmpptr)
109 {
110 free(pSPUData->data);
111 return NULL;
112 }
113 pSPUData->data = tmpptr;
114 }
115
116 if(!pSPUData->data)
117 return NULL; // crap realloc failed, this will have leaked some memory due to odd realloc
118
119 // add new data
120 memcpy(pSPUData->data + pSPUData->iSize, data, iSize);
121 pSPUData->iSize += iSize;
122
123 if (pSPUData->iNeededSize - pSPUData->iSize == 1) // to make it even
124 {
125 DebugLog("missing 1 byte to complete packet, adding 0xff");
126
127 pSPUData->data[pSPUData->iSize] = 0xff;
128 pSPUData->iSize++;
129 }
130
131 if (pSPUData->iSize == pSPUData->iNeededSize)
132 {
133 DebugLog("got complete spu packet\n length: %i bytes\n stream: %i\n", pSPUData->iSize);
134
135 return ParsePacket(pSPUData);
136 }
137
138 return NULL;
139 }
140
141 #define CMD_END 0xFF
142 #define FSTA_DSP 0x00
143 #define STA_DSP 0x01
144 #define STP_DSP 0x02
145 #define SET_COLOR 0x03
146 #define SET_CONTR 0x04
147 #define SET_DAREA 0x05
148 #define SET_DSPXA 0x06
149 #define CHG_COLCON 0x07
150
ParsePacket(SPUData * pSPUData)151 CDVDOverlaySpu* CDVDDemuxSPU::ParsePacket(SPUData* pSPUData)
152 {
153 unsigned int alpha[4];
154 uint8_t* pUnparsedData = NULL;
155
156 if (pSPUData->iNeededSize != pSPUData->iSize)
157 {
158 DebugLog("GetPacket, packet is incomplete, missing: %i bytes", (pSPUData->iNeededSize - pSPUData->iSize));
159 }
160
161 if (pSPUData->data[pSPUData->iSize - 1] != 0xff)
162 {
163 DebugLog("GetPacket, missing end of data 0xff");
164 }
165
166 CDVDOverlaySpu* pSPUInfo = new CDVDOverlaySpu();
167 uint8_t* p = pSPUData->data; // pointer to walk through all data
168
169 // get data length
170 uint16_t datalength = p[2] << 8 | p[3]; // datalength + 4 control bytes
171
172 pUnparsedData = pSPUData->data + 4;
173
174 // if it is set to 0 it means it's a menu overlay by default
175 // this is not what we want too, cause you get strange results on a parse error
176 pSPUInfo->iPTSStartTime = -1;
177
178 //skip data packet and goto control sequence
179 p += datalength;
180
181 bool bHasNewDCSQ = true;
182 while (bHasNewDCSQ)
183 {
184 DebugLog(" starting new SP_DCSQT");
185 // p is beginning of first SP_DCSQT now
186 uint16_t delay = p[0] << 8 | p[1];
187 uint16_t next_DCSQ = p[2] << 8 | p[3];
188
189 //offset within the Sub-Picture Unit to the next SP_DCSQ. If this is the last SP_DCSQ, it points to itself.
190 bHasNewDCSQ = ((pSPUData->data + next_DCSQ) != p);
191 // skip 4 bytes
192 p += 4;
193
194 while (*p != CMD_END && (unsigned int)(p - pSPUData->data) <= pSPUData->iSize)
195 {
196 switch (*p)
197 {
198 case FSTA_DSP:
199 p++;
200 DebugLog(" GetPacket, FSTA_DSP: Forced Start Display, no arguments");
201 pSPUInfo->iPTSStartTime = pSPUData->pts;
202 pSPUInfo->iPTSStopTime = 0x9000000000000LL;
203 pSPUInfo->bForced = true;
204 // delay is always 0, the VideoPlayer should decide when to display the packet (menu highlight)
205 break;
206 case STA_DSP:
207 {
208 p++;
209 pSPUInfo->iPTSStartTime = pSPUData->pts;
210 pSPUInfo->iPTSStartTime += (double)delay * 1024 * DVD_TIME_BASE / 90000;
211 DebugLog(" GetPacket, STA_DSP: Start Display, delay: %i", ((delay * 1024) / 90000));
212 }
213 break;
214 case STP_DSP:
215 {
216 p++;
217 pSPUInfo->iPTSStopTime = pSPUData->pts;
218 pSPUInfo->iPTSStopTime += (double)delay * 1024 * DVD_TIME_BASE / 90000;
219 DebugLog(" GetPacket, STP_DSP: Stop Display, delay: %i", ((delay * 1024) / 90000));
220 }
221 break;
222 case SET_COLOR:
223 {
224 p++;
225
226 if (m_bHasClut)
227 {
228 pSPUInfo->bHasColor = true;
229
230 unsigned int idx[4];
231 // 0, 1, 2, 3
232 idx[0] = (p[0] >> 4) & 0x0f;
233 idx[1] = (p[0]) & 0x0f;
234 idx[2] = (p[1] >> 4) & 0x0f;
235 idx[3] = (p[1]) & 0x0f;
236
237 for (int i = 0; i < 4 ; i++) // emphasis 1, emphasis 2, pattern, back ground
238 {
239 uint8_t* iColor = m_clut[idx[i]];
240
241 pSPUInfo->color[3 - i][0] = iColor[0]; // Y
242 pSPUInfo->color[3 - i][1] = iColor[1]; // Cr
243 pSPUInfo->color[3 - i][2] = iColor[2]; // Cb
244 }
245 }
246
247 DebugLog(" GetPacket, SET_COLOR:");
248 p += 2;
249 }
250 break;
251 case SET_CONTR: // alpha
252 {
253 p++;
254 // 3, 2, 1, 0
255 alpha[0] = (p[0] >> 4) & 0x0f;
256 alpha[1] = (p[0]) & 0x0f;
257 alpha[2] = (p[1] >> 4) & 0x0f;
258 alpha[3] = (p[1]) & 0x0f;
259
260 // Ignore blank alpha palette.
261 if (alpha[0] | alpha[1] | alpha[2] | alpha[3])
262 {
263 pSPUInfo->bHasAlpha = true;
264
265 // 0, 1, 2, 3
266 pSPUInfo->alpha[0] = alpha[3]; //0 // background, should be hidden
267 pSPUInfo->alpha[1] = alpha[2]; //1
268 pSPUInfo->alpha[2] = alpha[1]; //2 // wm button overlay
269 pSPUInfo->alpha[3] = alpha[0]; //3
270 }
271
272 DebugLog(" GetPacket, SET_CONTR:");
273 p += 2;
274 }
275 break;
276 case SET_DAREA:
277 {
278 p++;
279 pSPUInfo->x = (p[0] << 4) | (p[1] >> 4);
280 pSPUInfo->y = (p[3] << 4) | (p[4] >> 4);
281 pSPUInfo->width = (((p[1] & 0x0f) << 8) | p[2]) - pSPUInfo->x + 1;
282 pSPUInfo->height = (((p[4] & 0x0f) << 8) | p[5]) - pSPUInfo->y + 1;
283 DebugLog(" GetPacket, SET_DAREA: x,y:%i,%i width,height:%i,%i",
284 pSPUInfo->x, pSPUInfo->y, pSPUInfo->width, pSPUInfo->height);
285 p += 6;
286 }
287 break;
288 case SET_DSPXA:
289 {
290 p++;
291 uint16_t tfaddr = (p[0] << 8 | p[1]); // offset in packet
292 uint16_t bfaddr = (p[2] << 8 | p[3]); // offset in packet
293 pSPUInfo->pTFData = (tfaddr - 4); //pSPUInfo->pData + (tfaddr - 4); // pSPUData->data = packet startaddr - 4
294 pSPUInfo->pBFData = (bfaddr - 4); //pSPUInfo->pData + (bfaddr - 4); // pSPUData->data = packet startaddr - 4
295 p += 4;
296 DebugLog(" GetPacket, SET_DSPXA: tf: %i bf: %i ", tfaddr, bfaddr);
297 }
298 break;
299 case CHG_COLCON:
300 {
301 p++;
302 uint16_t paramlength = p[0] << 8 | p[1];
303 DebugLog("GetPacket, CHG_COLCON, skippin %i bytes", paramlength);
304 p += paramlength;
305 }
306 break;
307
308 default:
309 DebugLog("GetPacket, error parsing control sequence");
310 delete pSPUInfo;
311 return NULL;
312 break;
313 }
314 }
315 DebugLog(" end off SP_DCSQT");
316 if (*p == CMD_END) p++;
317 else
318 {
319 DebugLog("GetPacket, end off SP_DCSQT, but did not found 0xff (CMD_END)");
320 }
321 }
322
323 // parse the rle.
324 // this should be changed so it get's converted to a yuv overlay
325 return ParseRLE(pSPUInfo, pUnparsedData);
326 }
327
328 /*****************************************************************************
329 * AddNibble: read a nibble from a source packet and add it to our integer.
330 *****************************************************************************/
AddNibble(unsigned int i_code,uint8_t * p_src,unsigned int * pi_index)331 inline unsigned int AddNibble( unsigned int i_code, uint8_t* p_src, unsigned int* pi_index )
332 {
333 if ( *pi_index & 0x1 )
334 {
335 return ( i_code << 4 | ( p_src[(*pi_index)++ >> 1] & 0xf ) );
336 }
337 else
338 {
339 return ( i_code << 4 | p_src[(*pi_index)++ >> 1] >> 4 );
340 }
341 }
342
343 /*****************************************************************************
344 * ParseRLE: parse the RLE part of the subtitle
345 *****************************************************************************
346 * This part parses the subtitle graphical data and stores it in a more
347 * convenient structure for later decoding. For more information on the
348 * subtitles format, see http://sam.zoy.org/doc/dvd/subtitles/index.html
349 *****************************************************************************/
ParseRLE(CDVDOverlaySpu * pSPU,uint8_t * pUnparsedData)350 CDVDOverlaySpu* CDVDDemuxSPU::ParseRLE(CDVDOverlaySpu* pSPU, uint8_t* pUnparsedData)
351 {
352 uint8_t* p_src = pUnparsedData;
353
354 unsigned int i_code = 0;
355
356 unsigned int i_width = pSPU->width;
357 unsigned int i_height = pSPU->height;
358 unsigned int i_x, i_y;
359
360 // allocate a buffer for the result
361 uint16_t* p_dest = (uint16_t*)pSPU->result;
362
363 /* The subtitles are interlaced, we need two offsets */
364 unsigned int i_id = 0; /* Start on the even SPU layer */
365 unsigned int pi_table[2];
366
367 /* Colormap statistics */
368 int i_border = -1;
369 int stats[4]; stats[0] = stats[1] = stats[2] = stats[3] = 0;
370
371 pi_table[ 0 ] = pSPU->pTFData << 1;
372 pi_table[ 1 ] = pSPU->pBFData << 1;
373
374 for ( i_y = 0 ; i_y < i_height ; i_y++ )
375 {
376 unsigned int *pi_offset = pi_table + i_id;
377
378 for ( i_x = 0 ; i_x < i_width ; i_x += i_code >> 2 )
379 {
380 i_code = AddNibble( 0, p_src, pi_offset );
381
382 if ( i_code < 0x04 )
383 {
384 i_code = AddNibble( i_code, p_src, pi_offset );
385
386 if ( i_code < 0x10 )
387 {
388 i_code = AddNibble( i_code, p_src, pi_offset );
389
390 if ( i_code < 0x040 )
391 {
392 i_code = AddNibble( i_code, p_src, pi_offset );
393
394 if ( i_code < 0x0100 )
395 {
396 /* If the 14 first bits are set to 0, then it's a
397 * new line. We emulate it. */
398 if ( i_code < 0x0004 )
399 {
400 i_code |= ( i_width - i_x ) << 2;
401 }
402 else
403 {
404 /* We have a boo boo ! */
405 CLog::Log(LOGERROR, "ParseRLE: unknown RLE code 0x%.4x", i_code);
406 pSPU->Release();
407 return NULL;
408 }
409 }
410 }
411 }
412 }
413
414 if ( ( (i_code >> 2) + i_x + i_y * i_width ) > i_height * i_width )
415 {
416 CLog::Log(LOGERROR, "ParseRLE: out of bounds, %i at (%i,%i) is out of %ix%i",
417 i_code >> 2, i_x, i_y, i_width, i_height );
418 pSPU->Release();
419 return NULL;
420 }
421
422 // keep trace of all occurring pixels, even keeping the background in mind
423 stats[i_code & 0x3] += i_code >> 2;
424
425 // count the number of pixels for every occurring parts, without background
426 if (pSPU->alpha[i_code & 0x3] != 0x00)
427 {
428 // the last non background pixel is probably the border color
429 i_border = i_code & 0x3;
430 stats[i_border] += i_code >> 2;
431 }
432
433 /* Check we aren't overwriting our data range
434 This occurs on "The Triplets of BelleVille" region 4 disk (NTSC)"
435 where we use around 96k rather than 64k + 20bytes */
436 if ((uint8_t *)p_dest >= pSPU->result + sizeof(pSPU->result))
437 {
438 CLog::Log(LOGERROR, "ParseRLE: Overrunning our data range. Need %li bytes", (long)((uint8_t *)p_dest - pSPU->result));
439 pSPU->Release();
440 return NULL;
441 }
442 *p_dest++ = i_code;
443 }
444
445 /* Check that we didn't go too far */
446 if ( i_x > i_width )
447 {
448 CLog::Log(LOGERROR, "ParseRLE: i_x overflowed, %i > %i", i_x, i_width );
449 pSPU->Release();
450 return NULL;
451 }
452
453 /* Byte-align the stream */
454 if ( *pi_offset & 0x1 )
455 {
456 (*pi_offset)++;
457 }
458
459 /* Swap fields */
460 i_id = ~i_id & 0x1;
461 }
462
463 /* We shouldn't get any padding bytes */
464 if ( i_y < i_height )
465 {
466 DebugLog("ParseRLE: padding bytes found in RLE sequence" );
467 DebugLog("ParseRLE: send mail to <sam@zoy.org> if you want to help debugging this" );
468
469 /* Skip them just in case */
470 while ( i_y < i_height )
471 {
472 /* Check we aren't overwriting our data range
473 This occurs on "The Triplets of BelleVille" region 4 disk (NTSC)"
474 where we use around 96k rather than 64k + 20bytes */
475 if ((uint8_t *)p_dest >= pSPU->result + sizeof(pSPU->result))
476 {
477 CLog::Log(LOGERROR, "ParseRLE: Overrunning our data range. Need %li bytes", (long)((uint8_t *)p_dest - pSPU->result));
478 pSPU->Release();
479 return NULL;
480 }
481 *p_dest++ = i_width << 2;
482 i_y++;
483 }
484
485 pSPU->Release();
486 return NULL;
487 }
488
489 DebugLog("ParseRLE: valid subtitle, size: %ix%i, position: %i,%i",
490 pSPU->width, pSPU->height, pSPU->x, pSPU->y );
491
492 // forced spu's (menu overlays) retrieve their alpha/color information from InputStreamNavigator::GetCurrentButtonInfo
493 // also they may contain completely covering data wich is supposed to be hidden normally
494 // since whole spu is drawn, if this is done for forced, that may be displayed
495 // so we must trust what is given
496 if( !pSPU->bForced )
497 {
498 // Handle color if no palette was found.
499 // we only set it if there is a valid i_border color
500 if (!pSPU->bHasColor)
501 {
502 CLog::Log(LOGINFO, "%s - no color palette found, using default", __FUNCTION__);
503 FindSubtitleColor(i_border, stats, pSPU);
504 }
505
506 // check alpha values, for non forced spu's we use a default value
507 if (pSPU->bHasAlpha)
508 {
509 // check alpha values
510 // the array stats represents the nr of pixels for each color channel
511 // thus if there are no pixels to display, we assume the alphas are incorrect.
512 if (!CanDisplayWithAlphas(pSPU->alpha, stats))
513 {
514 CLog::Log(LOGINFO, "%s - no matching color and alpha found, resetting alpha", __FUNCTION__);
515
516 pSPU->alpha[0] = 0x00; // back ground
517 pSPU->alpha[1] = 0x0f;
518 pSPU->alpha[2] = 0x0f;
519 pSPU->alpha[3] = 0x0f;
520 }
521 }
522 else
523 {
524 CLog::Log(LOGINFO, "%s - ignoring blank alpha palette, using default", __FUNCTION__);
525
526 pSPU->alpha[0] = 0x00; // back ground
527 pSPU->alpha[1] = 0x0f;
528 pSPU->alpha[2] = 0x0f;
529 pSPU->alpha[3] = 0x0f;
530 }
531
532 }
533
534 return pSPU;
535 }
536
FindSubtitleColor(int last_color,int stats[4],CDVDOverlaySpu * pSPU)537 void CDVDDemuxSPU::FindSubtitleColor(int last_color, int stats[4], CDVDOverlaySpu* pSPU)
538 {
539 const int COLOR_INNER = 0;
540 const int COLOR_SHADE = 1;
541 const int COLOR_BORDER = 2;
542
543 //uint8_t custom_subtitle_color[4][3] = { // blue, yellow and something else (xine)
544 // { 0x80, 0x90, 0x80 }, // inner color
545 // { 0x00, 0x90, 0x00 }, // shade color
546 // { 0x00, 0x90, 0xff } // border color
547 //};
548
549 uint8_t custom_subtitle_color[4][3] = { // inner color white, gray shading and a black border
550 { 0xff, 0x80, 0x80 }, // inner color, white
551 { 0x80, 0x80, 0x80 }, // shade color, gray
552 { 0x00, 0x80, 0x80 } // border color, black
553 };
554
555 //uint8_t custom_subtitle_color[4][3] = { // completely white and a black border
556 // { 0xff, 0x80, 0x80 }, // inner color, white
557 // { 0xff, 0x80, 0x80 }, // shade color, white
558 // { 0x00, 0x80, 0x80 } // border color, black
559 //};
560
561
562 int nrOfUsedColors = 0;
563 for (int alpha : pSPU->alpha)
564 {
565 if (alpha > 0) nrOfUsedColors++;
566 }
567
568 if (nrOfUsedColors == 0)
569 {
570 // nothing todo
571 DebugLog("FindSubtitleColor: all 4 alpha channels are 0, nothing todo");
572 }
573 else if (nrOfUsedColors == 1)
574 {
575 // only one color is used, probably the inner color
576 for (int i = 0; i < 4; i++) // find the position that is used
577 {
578 if (pSPU->alpha[i] > 0)
579 {
580 pSPU->color[i][0] = custom_subtitle_color[COLOR_INNER][0]; // Y
581 pSPU->color[i][1] = custom_subtitle_color[COLOR_INNER][1]; // Cr ?
582 pSPU->color[i][2] = custom_subtitle_color[COLOR_INNER][2]; // Cb ?
583 return;
584 }
585 }
586
587 }
588 else
589 {
590 // old code
591
592 if (last_color >= 0 && last_color < 4)
593 {
594 int i, i_inner = -1, i_shade = -1;
595 // Set the border color, the last color is probably the border color
596 pSPU->color[last_color][0] = custom_subtitle_color[COLOR_BORDER][0];
597 pSPU->color[last_color][1] = custom_subtitle_color[COLOR_BORDER][1];
598 pSPU->color[last_color][2] = custom_subtitle_color[COLOR_BORDER][2];
599 stats[last_color] = 0;
600
601 // find the inner colors
602 for ( i = 0 ; i < 4 && i_inner == -1 ; i++ )
603 {
604 if ( stats[i] )
605 {
606 i_inner = i;
607 }
608 }
609
610 // try to find the shade color
611 for ( ; i < 4 && i_shade == -1 ; i++)
612 {
613 if ( stats[i] )
614 {
615 if ( stats[i] > stats[i_inner] )
616 {
617 i_shade = i_inner;
618 i_inner = i;
619 }
620 else
621 {
622 i_shade = i;
623 }
624 }
625 }
626
627 /* Set the inner color */
628 if ( i_inner != -1 )
629 {
630 // white color
631 pSPU->color[i_inner][0] = custom_subtitle_color[COLOR_INNER][0]; // Y
632 pSPU->color[i_inner][1] = custom_subtitle_color[COLOR_INNER][1]; // Cr ?
633 pSPU->color[i_inner][2] = custom_subtitle_color[COLOR_INNER][2]; // Cb ?
634 }
635
636 /* Set the anti-aliasing color */
637 if ( i_shade != -1 )
638 {
639 // gray
640 pSPU->color[i_shade][0] = custom_subtitle_color[COLOR_SHADE][0];
641 pSPU->color[i_shade][1] = custom_subtitle_color[COLOR_SHADE][1];
642 pSPU->color[i_shade][2] = custom_subtitle_color[COLOR_SHADE][2];
643 }
644
645 DebugLog("ParseRLE: using custom palette (border %i, inner %i, shade %i)", last_color, i_inner, i_shade);
646 }
647 }
648 }
649
CanDisplayWithAlphas(int a[4],int stats[4])650 bool CDVDDemuxSPU::CanDisplayWithAlphas(int a[4], int stats[4])
651 {
652 return(
653 a[0] * stats[0] > 0 ||
654 a[1] * stats[1] > 0 ||
655 a[2] * stats[2] > 0 ||
656 a[3] * stats[3] > 0);
657 }
658