Add support for /arcade folder, and MRA xml decoding

* round trip xml launch

* roms load

* removed debug

* added binary data and changed rom format

* changed hex format

* fixed bug

* added structure and start/length

* removed base64 support

* removed base64 support

* fixed parsing bugs

* fixed hex parse bug

* fixed parser

* fixing paths

* fixed rbf parser

* fixed initialization bug

* fixed extension removal for when there are multiple extension options

* fixed core path and cleaned up filelength

* added md5 checks to arcade roms

* fixed rbf search

* added error support

* Simplify error checking code

* fixed arcade error message pop up

* fixed bug in part zip initialization

* removed dtdt

* don't load second rom0 if first works

* fixed directory problem

* added more comments

* added / to zip path

* fixed / bug, truncate mame zip error message

* fixed scrolling RBF

* fixed scrolling name and error message

* added code to remove _date in scrolling text

* remove redundant /
This commit is contained in:
Alan Steremberg
2019-12-11 08:33:27 -08:00
committed by sorgelig
parent aedb73aa12
commit d981092caa
16 changed files with 1313 additions and 54 deletions

View File

@@ -18,11 +18,13 @@ INCLUDE = -I./
INCLUDE += -I./support/minimig
INCLUDE += -I./lib/libco
INCLUDE += -I./lib/miniz
INCLUDE += -I./lib/md5
PRJ = MiSTer
C_SRC = $(wildcard *.c) \
$(wildcard ./lib/miniz/*.c) \
lib/libco/arm.c
$(wildcard ./lib/md5/*.c) \
lib/libco/arm.c
CPP_SRC = $(wildcard *.cpp) \
$(wildcard ./support/minimig/*.cpp) \
@@ -32,6 +34,7 @@ CPP_SRC = $(wildcard *.cpp) \
$(wildcard ./support/x86/*.cpp) \
$(wildcard ./support/snes/*.cpp) \
$(wildcard ./support/neogeo/*.cpp) \
$(wildcard ./support/arcade/*.cpp) \
$(wildcard ./support/megacd/*.cpp) \
lib/lodepng/lodepng.cpp

View File

@@ -238,7 +238,6 @@ void FileClose(fileTYPE *file)
int FileOpenEx(fileTYPE *file, const char *name, int mode, char mute)
{
make_fullpath((char*)name, mode);
FileClose(file);
file->mode = 0;
file->type = 0;

View File

@@ -14,6 +14,7 @@
#include "file_io.h"
#include "input.h"
#include "osd.h"
#include "menu.h"
#include "fpga_base_addr_ac5.h"
#include "fpga_manager.h"
@@ -448,7 +449,7 @@ static int make_env(const char *name, const char *cfg)
return 0;
}
int fpga_load_rbf(const char *name, const char *cfg)
int fpga_load_rbf(const char *name, const char *cfg, const char *xml)
{
OsdDisable();
static char path[1024];
@@ -470,7 +471,10 @@ int fpga_load_rbf(const char *name, const char *cfg)
int rbf = open(path, O_RDONLY);
if (rbf < 0)
{
char error[4096];
snprintf(error,4096,"%s\nNot Found", name);
printf("Couldn't open file %s\n", path);
Info(error,5000);
return -1;
}
else
@@ -524,7 +528,7 @@ int fpga_load_rbf(const char *name, const char *cfg)
}
}
close(rbf);
app_restart(!strcasecmp(name, "menu.rbf") ? "menu.rbf" : path);
app_restart(!strcasecmp(name, "menu.rbf") ? "menu.rbf" : path,xml);
return ret;
}
@@ -637,7 +641,7 @@ char *getappname()
return dest;
}
void app_restart(const char *path)
void app_restart(const char *path, const char *xml)
{
sync();
fpga_core_reset(1);
@@ -647,7 +651,10 @@ void app_restart(const char *path)
char *appname = getappname();
printf("restarting the %s\n", appname);
execl(appname, appname, path, NULL);
if (xml)
execl(appname, appname, path, xml,NULL);
else
execl(appname, appname, path, NULL);
printf("Something went wrong. Rebooting...\n");
reboot(0);

View File

@@ -26,10 +26,10 @@ int is_fpga_ready(int quick);
int fpga_get_fio_size();
int fpga_get_io_version();
int fpga_load_rbf(const char *name, const char *cfg = NULL);
int fpga_load_rbf(const char *name, const char *cfg = NULL, const char *xml=NULL);
void reboot(int cold);
void app_restart(const char *path);
void app_restart(const char *path, const char *xml=NULL);
char *getappname();
#endif

330
lib/md5/md5.c Normal file
View File

@@ -0,0 +1,330 @@
/*
* This code implements the MD5 message-digest algorithm.
* The algorithm is due to Ron Rivest. This code was
* written by Colin Plumb in 1993, no copyright is claimed.
* This code is in the public domain; do with it what you wish.
*
* Equivalent code is available from RSA Data Security, Inc.
* This code has been tested against that, and is equivalent,
* except that you don't need to include two pages of legalese
* with every copy.
*
* To compute the message digest of a chunk of bytes, declare an
* MD5Context structure, pass it to MD5Init, call MD5Update as
* needed on buffers full of bytes, and then call MD5Final, which
* will fill a supplied 16-byte array with the digest.
*/
/* This code was modified in 1997 by Jim Kingdon of Cyclic Software to
not require an integer type which is exactly 32 bits. This work
draws on the changes for the same purpose by Tatu Ylonen
<ylo@cs.hut.fi> as part of SSH, but since I didn't actually use
that code, there is no copyright issue. I hereby disclaim
copyright in any changes I have made; this code remains in the
public domain. */
#define STDC_HEADERS 1
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#if HAVE_STRING_H || STDC_HEADERS
#include <string.h> /* for memcpy() */
#endif
/* Add prototype support. */
#ifndef PROTO
#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
#define PROTO(ARGS) ARGS
#else
#define PROTO(ARGS) ()
#endif
#endif
#include "md5.h"
/* Little-endian byte-swapping routines. Note that these do not
depend on the size of datatypes such as uint32, nor do they require
us to detect the endianness of the machine we are running on. It
is possible they should be macros for speed, but I would be
surprised if they were a performance bottleneck for MD5. */
static uint32
getu32 (addr)
const unsigned char *addr;
{
return (((((unsigned long)addr[3] << 8) | addr[2]) << 8)
| addr[1]) << 8 | addr[0];
}
static void
putu32 (data, addr)
uint32 data;
unsigned char *addr;
{
addr[0] = (unsigned char)data;
addr[1] = (unsigned char)(data >> 8);
addr[2] = (unsigned char)(data >> 16);
addr[3] = (unsigned char)(data >> 24);
}
/*
* Start MD5 accumulation. Set bit count to 0 and buffer to mysterious
* initialization constants.
*/
void
MD5Init(ctx)
struct MD5Context *ctx;
{
ctx->buf[0] = 0x67452301;
ctx->buf[1] = 0xefcdab89;
ctx->buf[2] = 0x98badcfe;
ctx->buf[3] = 0x10325476;
ctx->bits[0] = 0;
ctx->bits[1] = 0;
}
/*
* Update context to reflect the concatenation of another buffer full
* of bytes.
*/
void
MD5Update(ctx, buf, len)
struct MD5Context *ctx;
unsigned char const *buf;
unsigned len;
{
uint32 t;
/* Update bitcount */
t = ctx->bits[0];
if ((ctx->bits[0] = (t + ((uint32)len << 3)) & 0xffffffff) < t)
ctx->bits[1]++; /* Carry from low to high */
ctx->bits[1] += len >> 29;
t = (t >> 3) & 0x3f; /* Bytes already in shsInfo->data */
/* Handle any leading odd-sized chunks */
if ( t ) {
unsigned char *p = ctx->in + t;
t = 64-t;
if (len < t) {
memcpy(p, buf, len);
return;
}
memcpy(p, buf, t);
MD5Transform(ctx->buf, ctx->in);
buf += t;
len -= t;
}
/* Process data in 64-byte chunks */
while (len >= 64) {
memcpy(ctx->in, buf, 64);
MD5Transform(ctx->buf, ctx->in);
buf += 64;
len -= 64;
}
/* Handle any remaining bytes of data. */
memcpy(ctx->in, buf, len);
}
/*
* Final wrapup - pad to 64-byte boundary with the bit pattern
* 1 0* (64-bit count of bits processed, MSB-first)
*/
void
MD5Final(digest, ctx)
unsigned char digest[16];
struct MD5Context *ctx;
{
unsigned count;
unsigned char *p;
/* Compute number of bytes mod 64 */
count = (ctx->bits[0] >> 3) & 0x3F;
/* Set the first char of padding to 0x80. This is safe since there is
always at least one byte free */
p = ctx->in + count;
*p++ = 0x80;
/* Bytes of padding needed to make 64 bytes */
count = 64 - 1 - count;
/* Pad out to 56 mod 64 */
if (count < 8) {
/* Two lots of padding: Pad the first block to 64 bytes */
memset(p, 0, count);
MD5Transform(ctx->buf, ctx->in);
/* Now fill the next block with 56 bytes */
memset(ctx->in, 0, 56);
} else {
/* Pad block to 56 bytes */
memset(p, 0, count-8);
}
/* Append length in bits and transform */
putu32(ctx->bits[0], ctx->in + 56);
putu32(ctx->bits[1], ctx->in + 60);
MD5Transform(ctx->buf, ctx->in);
putu32(ctx->buf[0], digest);
putu32(ctx->buf[1], digest + 4);
putu32(ctx->buf[2], digest + 8);
putu32(ctx->buf[3], digest + 12);
memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */
}
#ifndef ASM_MD5
/* The four core functions - F1 is optimized somewhat */
/* #define F1(x, y, z) (x & y | ~x & z) */
#define F1(x, y, z) (z ^ (x & (y ^ z)))
#define F2(x, y, z) F1(z, x, y)
#define F3(x, y, z) (x ^ y ^ z)
#define F4(x, y, z) (y ^ (x | ~z))
/* This is the central step in the MD5 algorithm. */
#define MD5STEP(f, w, x, y, z, data, s) \
( w += f(x, y, z) + data, w &= 0xffffffff, w = w<<s | w>>(32-s), w += x )
/*
* The core of the MD5 algorithm, this alters an existing MD5 hash to
* reflect the addition of 16 longwords of new data. MD5Update blocks
* the data and converts bytes into longwords for this routine.
*/
void
MD5Transform(buf, inraw)
uint32 buf[4];
const unsigned char inraw[64];
{
register uint32 a, b, c, d;
uint32 in[16];
int i;
for (i = 0; i < 16; ++i)
in[i] = getu32 (inraw + 4 * i);
a = buf[0];
b = buf[1];
c = buf[2];
d = buf[3];
MD5STEP(F1, a, b, c, d, in[ 0]+0xd76aa478, 7);
MD5STEP(F1, d, a, b, c, in[ 1]+0xe8c7b756, 12);
MD5STEP(F1, c, d, a, b, in[ 2]+0x242070db, 17);
MD5STEP(F1, b, c, d, a, in[ 3]+0xc1bdceee, 22);
MD5STEP(F1, a, b, c, d, in[ 4]+0xf57c0faf, 7);
MD5STEP(F1, d, a, b, c, in[ 5]+0x4787c62a, 12);
MD5STEP(F1, c, d, a, b, in[ 6]+0xa8304613, 17);
MD5STEP(F1, b, c, d, a, in[ 7]+0xfd469501, 22);
MD5STEP(F1, a, b, c, d, in[ 8]+0x698098d8, 7);
MD5STEP(F1, d, a, b, c, in[ 9]+0x8b44f7af, 12);
MD5STEP(F1, c, d, a, b, in[10]+0xffff5bb1, 17);
MD5STEP(F1, b, c, d, a, in[11]+0x895cd7be, 22);
MD5STEP(F1, a, b, c, d, in[12]+0x6b901122, 7);
MD5STEP(F1, d, a, b, c, in[13]+0xfd987193, 12);
MD5STEP(F1, c, d, a, b, in[14]+0xa679438e, 17);
MD5STEP(F1, b, c, d, a, in[15]+0x49b40821, 22);
MD5STEP(F2, a, b, c, d, in[ 1]+0xf61e2562, 5);
MD5STEP(F2, d, a, b, c, in[ 6]+0xc040b340, 9);
MD5STEP(F2, c, d, a, b, in[11]+0x265e5a51, 14);
MD5STEP(F2, b, c, d, a, in[ 0]+0xe9b6c7aa, 20);
MD5STEP(F2, a, b, c, d, in[ 5]+0xd62f105d, 5);
MD5STEP(F2, d, a, b, c, in[10]+0x02441453, 9);
MD5STEP(F2, c, d, a, b, in[15]+0xd8a1e681, 14);
MD5STEP(F2, b, c, d, a, in[ 4]+0xe7d3fbc8, 20);
MD5STEP(F2, a, b, c, d, in[ 9]+0x21e1cde6, 5);
MD5STEP(F2, d, a, b, c, in[14]+0xc33707d6, 9);
MD5STEP(F2, c, d, a, b, in[ 3]+0xf4d50d87, 14);
MD5STEP(F2, b, c, d, a, in[ 8]+0x455a14ed, 20);
MD5STEP(F2, a, b, c, d, in[13]+0xa9e3e905, 5);
MD5STEP(F2, d, a, b, c, in[ 2]+0xfcefa3f8, 9);
MD5STEP(F2, c, d, a, b, in[ 7]+0x676f02d9, 14);
MD5STEP(F2, b, c, d, a, in[12]+0x8d2a4c8a, 20);
MD5STEP(F3, a, b, c, d, in[ 5]+0xfffa3942, 4);
MD5STEP(F3, d, a, b, c, in[ 8]+0x8771f681, 11);
MD5STEP(F3, c, d, a, b, in[11]+0x6d9d6122, 16);
MD5STEP(F3, b, c, d, a, in[14]+0xfde5380c, 23);
MD5STEP(F3, a, b, c, d, in[ 1]+0xa4beea44, 4);
MD5STEP(F3, d, a, b, c, in[ 4]+0x4bdecfa9, 11);
MD5STEP(F3, c, d, a, b, in[ 7]+0xf6bb4b60, 16);
MD5STEP(F3, b, c, d, a, in[10]+0xbebfbc70, 23);
MD5STEP(F3, a, b, c, d, in[13]+0x289b7ec6, 4);
MD5STEP(F3, d, a, b, c, in[ 0]+0xeaa127fa, 11);
MD5STEP(F3, c, d, a, b, in[ 3]+0xd4ef3085, 16);
MD5STEP(F3, b, c, d, a, in[ 6]+0x04881d05, 23);
MD5STEP(F3, a, b, c, d, in[ 9]+0xd9d4d039, 4);
MD5STEP(F3, d, a, b, c, in[12]+0xe6db99e5, 11);
MD5STEP(F3, c, d, a, b, in[15]+0x1fa27cf8, 16);
MD5STEP(F3, b, c, d, a, in[ 2]+0xc4ac5665, 23);
MD5STEP(F4, a, b, c, d, in[ 0]+0xf4292244, 6);
MD5STEP(F4, d, a, b, c, in[ 7]+0x432aff97, 10);
MD5STEP(F4, c, d, a, b, in[14]+0xab9423a7, 15);
MD5STEP(F4, b, c, d, a, in[ 5]+0xfc93a039, 21);
MD5STEP(F4, a, b, c, d, in[12]+0x655b59c3, 6);
MD5STEP(F4, d, a, b, c, in[ 3]+0x8f0ccc92, 10);
MD5STEP(F4, c, d, a, b, in[10]+0xffeff47d, 15);
MD5STEP(F4, b, c, d, a, in[ 1]+0x85845dd1, 21);
MD5STEP(F4, a, b, c, d, in[ 8]+0x6fa87e4f, 6);
MD5STEP(F4, d, a, b, c, in[15]+0xfe2ce6e0, 10);
MD5STEP(F4, c, d, a, b, in[ 6]+0xa3014314, 15);
MD5STEP(F4, b, c, d, a, in[13]+0x4e0811a1, 21);
MD5STEP(F4, a, b, c, d, in[ 4]+0xf7537e82, 6);
MD5STEP(F4, d, a, b, c, in[11]+0xbd3af235, 10);
MD5STEP(F4, c, d, a, b, in[ 2]+0x2ad7d2bb, 15);
MD5STEP(F4, b, c, d, a, in[ 9]+0xeb86d391, 21);
buf[0] += a;
buf[1] += b;
buf[2] += c;
buf[3] += d;
}
#endif
#ifdef TEST
/* Simple test program. Can use it to manually run the tests from
RFC1321 for example. */
#include <stdio.h>
int
main (int argc, char **argv)
{
struct MD5Context context;
unsigned char checksum[16];
int i;
int j;
if (argc < 2)
{
fprintf (stderr, "usage: %s string-to-hash\n", argv[0]);
exit (1);
}
for (j = 1; j < argc; ++j)
{
printf ("MD5 (\"%s\") = ", argv[j]);
MD5Init (&context);
MD5Update (&context, argv[j], strlen (argv[j]));
MD5Final (checksum, &context);
for (i = 0; i < 16; i++)
{
printf ("%02x", (unsigned int) checksum[i]);
}
printf ("\n");
}
return 0;
}
#endif /* TEST */

41
lib/md5/md5.h Normal file
View File

@@ -0,0 +1,41 @@
/* See md5.c for explanation and copyright information. */
#ifndef MD5_H
#define MD5_H
#ifdef __cplusplus
extern "C" {
#endif
/* Unlike previous versions of this code, uint32 need not be exactly
32 bits, merely 32 bits or more. Choosing a data type which is 32
bits instead of 64 is not important; speed is considerably more
important. ANSI guarantees that "unsigned long" will be big enough,
and always using it seems to have few disadvantages. */
typedef unsigned long uint32;
struct MD5Context {
uint32 buf[4];
uint32 bits[2];
unsigned char in[64];
};
#ifndef PROTO
#if defined (USE_PROTOTYPES) ? USE_PROTOTYPES : defined (__STDC__)
#define PROTO(ARGS) ARGS
#else
#define PROTO(ARGS) ()
#endif
#endif
void MD5Init PROTO((struct MD5Context *context));
void MD5Update PROTO((struct MD5Context *context, unsigned char const *buf, unsigned len));
void MD5Final PROTO((unsigned char digest[16], struct MD5Context *context));
void MD5Transform PROTO((uint32 buf[4], const unsigned char in[64]));
/*
* This is needed to make RSAREF happy on some MS-DOS compilers.
*/
typedef struct MD5Context MD5_CTX;
#ifdef __cplusplus
}
#endif
#endif /* !MD5_H */

View File

@@ -66,7 +66,7 @@ int main(int argc, char *argv[])
}
FindStorage();
user_io_init((argc > 1) ? argv[1] : "");
user_io_init((argc > 1) ? argv[1] : "",(argc > 2) ? argv[2] : "");
#ifdef USE_SCHEDULER
scheduler_init();

149
menu.cpp
View File

@@ -313,7 +313,7 @@ static void SelectFile(const char* pFileExt, unsigned char Options, unsigned cha
strcat(SelectedPath, "/");
strcat(SelectedPath, get_rbf_name());
}
pFileExt = "RBF";
pFileExt = "RBFMRA";
}
else if (Options & SCANO_TXT)
{
@@ -4481,9 +4481,21 @@ void HandleUI(void)
break;
}
}
// close OSD now as the new core may not even have one
fpga_load_rbf(SelectedRBF);
if (!strcasecmp(".mra",&(SelectedRBF[strlen(SelectedRBF) - 4])))
{
char rbfname[4096];
char rbfpath[4096];
fprintf(stderr,"MRA FILE LOADED - write code\n");
// find the RBF file from the XML
arcade_scan_xml_for_rbf(getFullPath(SelectedRBF),rbfname);
fprintf(stderr,"MRA SelectedRBF: [%s]\n",SelectedRBF);
fprintf(stderr,"MRA rbf: [%s]\n",rbfname);
sprintf(rbfpath,"arcade/%s",rbfname);
fpga_load_rbf(getFullPath(rbfpath),NULL,SelectedRBF);
}
else
// close OSD now as the new core may not even have one
fpga_load_rbf(SelectedRBF);
break;
case MENU_CORE_FILE_SELECTED2:
@@ -4597,6 +4609,84 @@ void open_joystick_setup()
joymap_first = 1;
}
/*
* CalculateFileNameLengthWithoutExtension
*
* This function takes a filename and length, and returns
* the length. It will remove the .rbf or .mra from the length
* based on the fs_pFileExt global
*
* If the fs_pFileExt has multiple extensions, it will look through
* each to try to find a match.
*/
int CalculateFileNameLengthWithoutExtension(char *name,char *possibleextensions)
{
char *ext = possibleextensions;
int found=0;
/* the default length is the whole string */
int len = strlen(name);
/* find the extension on the end of the name*/
char *fext = strrchr(name, '.');
/* we want to push past the period - and just have rbf instead of .rbf*/
if (fext) fext++;
/* walk through each extension and see if it matches */
while (!found && *ext && fext)
{
char e[4];
memcpy(e, ext, 3);
if (e[2] == ' ')
{
e[2] = 0;
if (e[1] == ' ') e[1] = 0;
}
e[3] = 0;
found = 1;
for (int i = 0; i < 4; i++)
{
if (e[i] == '*') break;
if (e[i] == '?' && fext[i]) continue;
if (tolower(e[i]) != tolower(fext[i])) found = 0;
if (!e[i] || !found) break;
}
if (found) break;
if (strlen(ext) < 3) break;
ext += 3;
}
/* if we haven't found a match, then the answer is the full length of the string */
if (!found) return len;
/* we have a match, now we need to handle extensions that are less than 3 characters */
char e[5];
memcpy(e + 1, ext, 3);
/* 0x20 is a space in ascii*/
if (e[3] == 0x20)
{
e[3] = 0;
if (e[2] == 0x20)
{
e[2] = 0;
}
}
e[0] = '.';
e[4] = 0;
int l = strlen(e);
if ((len>l) && !strncasecmp(name + len - l, e, l)) len -= l;
//printf("len: %d l: %d str[%s] e[%s] ext[%s]\n",len,l,name,e,ext);
return len;
}
void ScrollLongName(void)
{
// this function is called periodically when file selection window is displayed
@@ -4608,32 +4698,28 @@ void ScrollLongName(void)
len = strlen(flist_SelectedItem()->altname); // get name length
if (flist_SelectedItem()->de.d_type == DT_REG) // if a file
{
if (fs_ExtLen <= 3)
{
char e[5];
memcpy(e + 1, fs_pFileExt, 3);
if (e[3] == 0x20)
{
e[3] = 0;
if (e[2] == 0x20)
{
e[2] = 0;
}
}
e[0] = '.';
e[4] = 0;
int l = strlen(e);
if ((len>l) && !strncasecmp(flist_SelectedItem()->altname + len - l, e, l)) len -= l;
}
len=CalculateFileNameLengthWithoutExtension(flist_SelectedItem()->altname,fs_pFileExt);
}
max_len = 30; // number of file name characters to display (one more required for scrolling)
if (flist_SelectedItem()->de.d_type == DT_DIR)
max_len = 25; // number of directory name characters to display
// if we are in a core, we might need to resize for the fixed date string at the end
if (!cfg.rbf_hide_datecode && (fs_Options & SCANO_CORES)) {
if (len > 9 && !strncmp(flist_SelectedItem()->altname+ len - 9, "_20", 3)) {
len -= 9;
}
max_len=21; // __.__.__ remove that from the end
}
//printf("ScrollLongName: len %d max_len %d [%s]\n",len,max_len,flist_SelectedItem()->altname);
ScrollText(flist_iSelectedEntry()-flist_iFirstEntry(), flist_SelectedItem()->altname, 0, len, max_len, 1);
}
void PrintFileName(char *name, int row, int maxinv)
{
int len;
@@ -4705,26 +4791,7 @@ void PrintDirectory(void)
if (!(flist_DirItem(k)->de.d_type == DT_DIR)) // if a file
{
if (fs_ExtLen <= 3)
{
char e[5];
memcpy(e + 1, fs_pFileExt, 3);
if (e[3] == 0x20)
{
e[3] = 0;
if (e[2] == 0x20)
{
e[2] = 0;
}
}
e[0] = '.';
e[4] = 0;
int l = strlen(e);
if ((len>l) && !strncasecmp(flist_DirItem(k)->altname + len - l, e, l))
{
len -= l;
}
}
len=CalculateFileNameLengthWithoutExtension(flist_DirItem(k)->altname,fs_pFileExt);
}
char *p = 0;

View File

@@ -23,5 +23,8 @@
// NeoGeo support
#include "support/neogeo/loader.h"
// Arcade support
#include "support/arcade/romutils.h"
// MEGACD support
#include "support/megacd/megacd.h"
#include "support/megacd/megacd.h"

195
support/arcade/buffer.cpp Normal file
View File

@@ -0,0 +1,195 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stddef.h>
#include <limits.h>
#include "buffer.h"
////////////////////////////////////////////////////////////////////////////////
//
// Function buffer_init
//
// Purpose:
//
// This function will return a struct with the values it needs before it
// can be used with buffer_append. The default "expand_length" is
//
// Parameters:
//
// initial_size (IN) This parameter will preallocate the buffer with this
// size in bytes.
//
// Returns:
//
// returns a struct, or NULL on error
//
// Error Definitions:
//
// NULL = Couldn't create the structure
//
////////////////////////////////////////////////////////////////////////////////
buffer_data * buffer_init(unsigned int initial_size) {
buffer_data * buffer = NULL;
// only allow max initial size of SIZE_MAX
if (initial_size > 65535) {
return NULL;
}
// malloc the space for a pointer to a buffer_data
buffer = (buffer_data *)malloc(sizeof(buffer_data));
if (buffer == NULL) {
return NULL;
}
// fill the struct up
buffer->expand_length = initial_size;
buffer->capacity = initial_size;
buffer->length = 0;
// allocate the buffer space
buffer->content = (char *)malloc(sizeof(char) * initial_size);
buffer->content[0]=0;
if (buffer->content == NULL) {
// can't allocate the buffer, therefore we need to clean everything up
free(buffer);
return NULL;
}
// return a pointer to our buffer_data structure
return buffer;
}
////////////////////////////////////////////////////////////////////////////////
//
// Function buffer_append
//
// Purpose:
//
// This function will take data and append it into a buffer in the struct
// given. It will growing as required (or as allowed by realloc) by the
// length desired in the structure element "expand_length" plus the length
// of the data being appended. If additional expansion is not desired, you
// can set the struct element "expand_length" to zero.
//
// Parameters:
//
// buffer_data (IN/OUT) This parameter is a struct (mapped below) to
// keep track of the internal buffer and its working parameters.
//
// typedef struct {
// unsigned int length; // the current length of the content in bytes
// unsigned int capacity; // the total capacity of "content" in bytes
// unsigned int expand_length; // the amount to expand the buffer on resize
// char * content; // the target for appending data
// } buffer_data;
//
//
// append_data (IN) This parameter is a const char * that is the data
// to be appended.
//
// Returns:
//
// This function will return the amount of bytes appended. If the
// return value is <= 0 there was an error.
//
// Error Definitions:
//
// -1 = No data was given to append
// -2 = Additional space could not be allocated for the buffer.
//
////////////////////////////////////////////////////////////////////////////////
int buffer_append(buffer_data * buffer, const char * append_data) {
// ensure there IS something to append..
const unsigned int append_len = strlen(append_data);
if (append_len < 1) {
return -1;
}
// is there enough space, or should we reallocate memory?
if (buffer->length + append_len > buffer->capacity) {
const unsigned int realloc_size =
buffer->capacity + append_len + buffer->expand_length;
// allocate the new buffer
char *new_buffer = (char *)realloc(buffer->content, realloc_size);
// resize success..
if (new_buffer == NULL) {
return -2;
}
// copy the data to append into the new content buffer
strcat(new_buffer, append_data);
// update the struct with the new values
buffer->content = new_buffer; // new buffer content
buffer->capacity = realloc_size; // add the amount to expand beyond our initial needs
buffer->length = buffer->length + append_len;
} else {
// append the string
strcat(buffer->content, append_data);
}
// update the buffer length
buffer->length += append_len;
// return the amount appended
return append_len;
}
////////////////////////////////////////////////////////////////////////////////
//
// Function buffer_destroy
//
// Purpose:
//
// This function will destroy the inner buffer and reset all of the data
// if the struct was dynamically allocated, it is expected that you will
// take care of freeing the data.
//
// Parameters:
//
// buffer_data (IN) This parameter is a struct (mapped below) to
// keep track of the internal buffer and its working parameters.
//
// typedef struct {
// unsigned int length; // the current length of the content in bytes
// unsigned int capacity; // the total capacity of "content" in bytes
// unsigned int expand_length; // the amount to expand the buffer on resize
// char * content; // the target for appending data
// } buffer_data;
//
// Returns:
//
// returns nothing
//
////////////////////////////////////////////////////////////////////////////////
void buffer_destroy(buffer_data * buffer) {
// only work with valid data..
if (buffer != NULL) {
// if the content is not null, free it.
if (buffer->content != NULL) {
free(buffer->content);
}
// free the buffer
free(buffer);
}
}

15
support/arcade/buffer.h Normal file
View File

@@ -0,0 +1,15 @@
#ifndef STRING_UTILS_H_
#define STRING_UTILS_H_
typedef struct {
unsigned int length;
unsigned int capacity;
unsigned int expand_length;
char * content;
} buffer_data;
buffer_data * buffer_init(const unsigned int initial_size);
int buffer_append(buffer_data *buffer, const char *append_data);
void buffer_destroy(buffer_data * buffer);
#endif // STRING_UTILS_H_

470
support/arcade/romutils.cpp Normal file
View File

@@ -0,0 +1,470 @@
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <dirent.h>
#include "sxmlc.h"
#include "../../user_io.h"
#include "../../file_io.h"
#include "../../menu.h"
#include "buffer.h"
#include "md5.h"
/*
* adapted from https://gist.github.com/xsleonard/7341172
*
* hexstr_to_char will take hex strings in two types:
*
* 00 01 02
* 000102
* 00 01 2 03
* 0001 0203
*
* and return an array and a length of binary values
*
* caller must free string that is returned
*
* */
unsigned char* hexstr_to_char(const char* hexstr, size_t *out_len)
{
size_t len = strlen(hexstr);
unsigned char* chrs = (unsigned char*)malloc((len+1) * sizeof(*chrs));
int dest=0;
// point to the beginning of the array
const char *ptr = hexstr;
while (*ptr) {
// check to see if we have a space
while (*ptr=='\n' || *ptr=='\r' || *ptr==' ' || *ptr=='\t' || *ptr==9 /*horiz tab*/) ptr++;
if (*ptr==0) break;
// pull two characters off
int val1= (*ptr % 32 + 9) % 25 * 16;
ptr++;
/* check to odd numbers of characters*/
if (*ptr==0) {
int val= (ptr[-1] % 32 + 9) % 25;
chrs[dest++] = val;
break;
}
int val2= (*ptr % 32 + 9) % 25;
ptr++;
chrs[dest++] = val1+val2;
}
chrs[dest]=0;
*out_len = dest; /* dest is 0 based, so we don't need to subtract 1*/
return chrs;
}
/*
user_io.h:int user_io_file_tx_start(const char *name,unsigned char index=0);
user_io.h:int user_io_file_tx_body(const uint8_t *buf,uint16_t chunk);
user_io.h:int user_io_file_tx_body_filepart(const char *name,int start=0, int len=0);
user_io.h:int user_io_file_tx_finish();
*/
#define kBigTextSize 4096
struct arc_struct {
char md5[kBigTextSize];
char zipname[kBigTextSize];
char partzipname[kBigTextSize];
char partname[kBigTextSize];
char romname[kBigTextSize];
char error_msg[kBigTextSize];
int romindex;
int offset;
int length;
int repeat;
int insiderom;
int validrom0;
buffer_data *data;
struct MD5Context context;
};
char global_error_msg[kBigTextSize];
/*
* xml_send_rom
*
* This is a callback from the XML parser of the MRA file
*
* It parses the MRA, and sends commands to send rom parts to the fpga
* */
static int xml_send_rom(XMLEvent evt, const XMLNode* node, SXML_CHAR* text, const int n, SAX_Data* sd)
{
struct arc_struct *arc_info = (struct arc_struct *)sd->user;
(void)(sd);
switch (evt)
{
case XML_EVENT_START_NODE:
/* initialization */
// initialize things for each tag (node):
buffer_destroy(arc_info->data);
arc_info->data=buffer_init(kBigTextSize);
arc_info->partname[0]=0;
arc_info->offset=0;
arc_info->length=-1;
arc_info->repeat=1;
/* on the beginning of a rom tag, we need to reset the state*/
if (!strcasecmp(node->tag,"rom"))
{
arc_info->insiderom=1;
arc_info->romname[0]=0;
arc_info->romindex=0;
arc_info->md5[0]=0;
MD5Init (&arc_info->context);
}
// for each part tag, we clear the partzipname since it is optional and may not appear in the part tag
if (!strcasecmp(node->tag,"part"))
arc_info->partzipname[0]=0;
//printf("XML_EVENT_START_NODE: tag [%s]\n",node->tag);
// walk the attributes and save them in the data structure as appropriate
for (int i = 0; i < node->n_attributes; i++)
{
//printf("attribute %d name [%s] value [%s]\n",i,node->attributes[i].name,node->attributes[i].value);
if (!strcasecmp(node->attributes[i].name,"zip") && !strcasecmp(node->tag,"rom"))
{
strcpy(arc_info->zipname,node->attributes[i].value);
}
if (!strcasecmp(node->attributes[i].name,"name") && !strcasecmp(node->tag,"rom"))
{
strcpy(arc_info->romname,node->attributes[i].value);
}
if (!strcasecmp(node->attributes[i].name,"md5") && !strcasecmp(node->tag,"rom"))
{
strcpy(arc_info->md5,node->attributes[i].value);
}
if (!strcasecmp(node->attributes[i].name,"index") && !strcasecmp(node->tag,"rom"))
{
arc_info->romindex=atoi(node->attributes[i].value);
}
/* these only exist if we are inside the rom tag, and in a part tag*/
if (arc_info->insiderom) {
if (!strcasecmp(node->attributes[i].name,"zip") && !strcasecmp(node->tag,"part"))
{
strcpy(arc_info->partzipname,node->attributes[i].value);
}
if (!strcasecmp(node->attributes[i].name,"name") && !strcasecmp(node->tag,"part"))
{
strcpy(arc_info->partname,node->attributes[i].value);
}
if (!strcasecmp(node->attributes[i].name,"offset") && !strcasecmp(node->tag,"part"))
{
arc_info->offset=atoi(node->attributes[i].value);
}
if (!strcasecmp(node->attributes[i].name,"length") && !strcasecmp(node->tag,"part"))
{
arc_info->length=atoi(node->attributes[i].value);
}
if (!strcasecmp(node->attributes[i].name,"repeat") && !strcasecmp(node->tag,"part"))
{
arc_info->repeat=atoi(node->attributes[i].value);
}
}
}
/* at the beginning of each rom - tell the user_io to start a new message */
if (!strcasecmp(node->tag,"rom"))
{
// clear an error message if we have a second rom0
// this is kind of a problem - you will never see the
// error from the first rom0?
//
if (arc_info->romindex==0 && strlen(arc_info->zipname))
arc_info->error_msg[0]=0;
user_io_file_tx_start(arc_info->romname,arc_info->romindex);
}
break;
case XML_EVENT_TEXT:
/* the text node is the data between tags, ie: <part>this text</part>
*
* the buffer_append is part of a buffer library that will realloc automatically
*/
buffer_append(arc_info->data, text);
//printf("XML_EVENT_TEXT: text [%s]\n",text);
break;
case XML_EVENT_END_NODE:
//printf("XML_EVENT_END_NODE: tag [%s]\n",node->tag );
// At the end of a rom node (when it is closed) we need to calculate hash values and clean up
if (!strcasecmp(node->tag,"rom")) {
if (arc_info->insiderom)
{
unsigned char checksum[16];
int checksumsame=1;
char *md5=arc_info->md5;
user_io_file_tx_finish();
MD5Final (checksum, &arc_info->context);
printf("md5[%s]\n",arc_info->md5);
printf("md5-calc[");
for (int i = 0; i < 16; i++)
{
char hex[10];
snprintf(hex,10,"%02x", (unsigned int) checksum[i]);
printf ("%02x", (unsigned int) checksum[i]);
if (md5[0]!=hex[0] || md5[1]!=hex[1]) {
checksumsame=0;
}
md5+=2;
}
printf ("]\n");
if (checksumsame==0)
{
printf("mismatch\n");
if (!strlen(arc_info->error_msg))
snprintf(arc_info->error_msg,kBigTextSize,"md5 mismatch for rom %d",arc_info->romindex);
}
else
{
// this code sets the validerom0 and clears the message
// if a rom with index 0 has a correct md5. It supresses
// sending any further rom0 messages
if (arc_info->romindex==0)
{
arc_info->validrom0=1;
arc_info->error_msg[0]=0;
}
}
}
arc_info->insiderom=0;
}
// At the end of a part node, send the rom part if we are inside a rom tag
//int user_io_file_tx_body_filepart(const char *name,int start, int len)
if (!strcasecmp(node->tag,"part") && arc_info->insiderom)
{
// suppress rom0 if we already sent a valid one
// this is useful for merged rom sets - if the first one was valid, use it
// the second might not be
if (arc_info->romindex==0 && arc_info->validrom0==1)
break;
char fname[kBigTextSize*2+16];
int start,length,repeat;
repeat=arc_info->repeat;
start=arc_info->offset;
length=0;
if (arc_info->length>0) length = arc_info->length;
//printf("partname[%s]\n",arc_info->partname);
//printf("zipname [%s]\n",arc_info->zipname);
//printf("offset[%d]\n",arc_info->offset);
//printf("length[%d]\n",arc_info->length);
//printf("repeat[%d]\n",arc_info->repeat);
//
if (strlen(arc_info->partzipname))
{
if (arc_info->partzipname[0]=='/')
sprintf(fname,"arcade%s/%s",arc_info->partzipname,arc_info->partname);
else
sprintf(fname,"arcade/mame/%s/%s",arc_info->partzipname,arc_info->partname);
}
else
{
if (arc_info->zipname[0]=='/')
sprintf(fname,"arcade%s/%s",arc_info->zipname,arc_info->partname);
else
sprintf(fname,"arcade/mame/%s/%s",arc_info->zipname,arc_info->partname);
}
//user_io_file_tx_body_filepart(getFullPath(fname),0,0);
if (strlen(arc_info->partname)) {
printf("user_io_file_tx_body_filepart(const char *name[%s],int start[%d], int len[%d])\n",fname,start,length);
for (int i=0;i<repeat;i++) {
int result=user_io_file_tx_body_filepart(fname,start,length,&arc_info->context);
// we should check file not found error for the zip
if (result==0)
{
int skip=0;
if (!strncasecmp(fname,"arcade/mame/",strlen("arcade/mame/")))
skip=strlen("arcade/mame/");
printf("%s does not exist\n",fname);
snprintf(arc_info->error_msg,kBigTextSize,"%s\n"
"\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\x81\n"
"File Not Found",fname+skip);
}
}
}
else // we have binary data?
{
//printf("we have bin.hex data [%s]\n",arc_info->data->content);
size_t len=0;
unsigned char* binary=hexstr_to_char(arc_info->data->content,&len);
//printf("len %d:\n",len);
//for (size_t i=0;i<len;i++) {
// printf(" %d ",binary[i]);
//}
//printf("\n");
for (int i=0;i<repeat;i++) {
user_io_file_tx_body(binary,len,&arc_info->context);
}
if (binary) free(binary);
}
}
break;
case XML_EVENT_ERROR:
printf("XML parse: %s: ERROR %d\n", text, n);
snprintf(arc_info->error_msg,kBigTextSize,"XML parse: %s: ERROR %d\n", text, n);
break;
default:
break;
}
return true;
}
static int xml_scan_rbf(XMLEvent evt, const XMLNode* node, SXML_CHAR* text, const int n, SAX_Data* sd)
{
static int insiderbf=0;
char bigtext[kBigTextSize];
char *rbf = (char *)sd->user;
switch (evt)
{
case XML_EVENT_START_NODE:
bigtext[0]=0;
if (!strcasecmp(node->tag,"rbf")) {
insiderbf=1;
}
printf("XML_EVENT_START_NODE: tag [%s]\n",node->tag);
for (int i = 0; i < node->n_attributes; i++)
{
printf("attribute %d name [%s] value [%s]\n",i,node->attributes[i].name,node->attributes[i].value);
}
break;
case XML_EVENT_TEXT:
if (insiderbf) {
strncat(bigtext,text,kBigTextSize-strlen(bigtext)-1);
printf("XML_EVENT_TEXT: text [%s]\n",text);
}
break;
case XML_EVENT_END_NODE:
if (!strcasecmp(node->tag,"rbf"))
{
insiderbf=0;
//printf("bigtext [%s]\n",bigtext);
strncpy(rbf,bigtext,kBigTextSize);
//printf("got rbf tag [%s]\n",rbf);
}
printf("XML_EVENT_END_NODE: tag [%s]\n",node->tag );
break;
case XML_EVENT_ERROR:
printf("XML parse: %s: ERROR %d\n", text, n);
break;
default:
break;
}
return true;
}
int arcade_send_rom(const char *xml)
{
SAX_Callbacks sax;
SAX_Callbacks_init(&sax);
sax.all_event = xml_send_rom;
// create the structure we use for the XML parser
struct arc_struct arc_info;
arc_info.data=buffer_init(kBigTextSize);
arc_info.error_msg[0]=0;
arc_info.validrom0=0;
// parse
XMLDoc_parse_file_SAX(xml, &sax, &arc_info);
if (arc_info.validrom0==0 && strlen(arc_info.error_msg))
{
strcpy(global_error_msg,arc_info.error_msg);
printf("arcade_send_rom: pretty error: [%s]\n",global_error_msg);
}
buffer_destroy(arc_info.data);
return 0;
}
int CheckArcadeError(void)
{
if (global_error_msg[0]!=0) {
printf("ERROR: [%s]\n",global_error_msg);
Info(global_error_msg,1000*30);
global_error_msg[0]=0;
}
return 0;
}
int arcade_scan_xml_for_rbf(const char *xml,char *rbfname)
{
char rbfname_fragment[kBigTextSize];
rbfname_fragment[0]=0;
rbfname[0]=0;
SAX_Callbacks sax;
SAX_Callbacks_init(&sax);
sax.all_event = xml_scan_rbf;
XMLDoc_parse_file_SAX(xml, &sax, rbfname_fragment);
strcpy(rbfname,rbfname_fragment);
//printf("arcade_scan_xml_for_rbf [%s]\n",xml);
/* once we have the rbfname fragment from the MRA xml file
* search the arcade folder for the match */
struct dirent *entry;
DIR *dir=NULL;
//printf("opendir(%s)\n",getFullPath("arcade"));
if (!(dir = opendir(getFullPath("arcade"))))
{
printf("arcade directory not found\n");
return 0;
}
while ((entry = readdir(dir)) != NULL) {
if (entry->d_type == DT_DIR) {
}
else {
char newstring[kBigTextSize];
//printf("entry name: %s\n",entry->d_name);
snprintf(newstring,kBigTextSize,"Arcade-%s_",rbfname_fragment);
if (!strncasecmp(newstring,entry->d_name,strlen(newstring))) {
closedir(dir);
strcpy(rbfname,entry->d_name);
return 0;
}
snprintf(newstring,kBigTextSize,"%s_",rbfname_fragment);
if (!strncasecmp(newstring,entry->d_name,strlen(newstring))) {
closedir(dir);
strcpy(rbfname,entry->d_name);
return 0;
}
}
}
if (dir) closedir(dir);
strcpy(rbfname,rbfname_fragment);
return 0;
}

View File

@@ -0,0 +1,5 @@
#include "../../file_io.h"
int arcade_send_rom(const char *xml);
int arcade_scan_xml_for_rbf(const char *xml,char *rbfname);
int CheckArcadeError(void);

View File

@@ -12,6 +12,8 @@
#include <sys/mman.h>
#include "lib/lodepng/lodepng.h"
#include "md5.h"
#include "hardware.h"
#include "osd.h"
#include "user_io.h"
@@ -691,16 +693,26 @@ int user_io_is_dualsdr()
return dual_sdr;
}
void user_io_init(const char *path)
void user_io_init(const char *path, const char *xml)
{
char *name;
static char mainpath[512];
core_name[0] = 0;
disable_osd = 0;
// we need to set the directory to where the XML file (MRA) is
// not the RBF. The RBF will be in arcade, which the user shouldn't
// browse
if (strlen(xml)) {
//printf("USER_IO_INIT got XML: [%s] [%s]\n",path,xml);
strcpy(core_path, getFullPath(xml));
} else {
strcpy(core_path, path);
}
//printf("USER_IO_INIT core_path: [%s] \n",core_path);
memset(sd_image, 0, sizeof(sd_image));
strcpy(core_path, path);
core_type = (fpga_core_id() & 0xFF);
fio_size = fpga_get_fio_size();
io_ver = fpga_get_io_version();
@@ -857,6 +869,14 @@ void user_io_init(const char *path)
if (user_io_use_cheats()) cheats_init("", user_io_get_file_crc());
}
/* AJS -- NOT SURE THIS IS THE BEST PLACE */
if (strlen(xml)) {
//sprintf(mainpath, "%s/%s", user_io_get_core_path(), xml);
printf("USER_IO_INIT got XML: [%s]\n",xml);
//printf("USER_IO_INIT got XML: [%s]\n",mainpath);
arcade_send_rom(getFullPath(xml));
}
if (is_cpc_core())
{
for (int m = 0; m < 3; m++)
@@ -1579,6 +1599,101 @@ static void check_status_change()
}
}
#define DEBUG_ROM_BINARY 0
#if DEBUG_ROM_BINARY
FILE *rombinary;
#endif
int user_io_file_tx_start(const char *name,unsigned char index)
{
// set index byte (0=bios rom, 1-n=OSD entry index)
user_io_set_index(index);
int len = strlen(name);
const char *p = name + len - 4;
EnableFpga();
spi8(UIO_FILE_INFO);
spi_w(toupper(p[0]) << 8 | toupper(p[1]));
spi_w(toupper(p[2]) << 8 | toupper(p[3]));
DisableFpga();
// prepare transmission of new file
EnableFpga();
spi8(UIO_FILE_TX);
spi8(0xff);
DisableFpga();
file_crc = 0;
#if DEBUG_ROM_BINARY
rombinary=fopen("/media/fat/this.rom","wb");
#endif
return 1;
}
int user_io_file_tx_body(const uint8_t *buf,uint16_t chunk,struct MD5Context *md5context)
{
printf(".");
EnableFpga();
spi8(UIO_FILE_TX_DAT);
spi_write(buf, chunk, fio_size);
DisableFpga();
file_crc = crc32(file_crc, buf ,chunk );
if (md5context) MD5Update (md5context, buf,chunk);
#if DEBUG_ROM_BINARY
fwrite(buf,1,chunk,rombinary);
#endif
return 1;
}
int user_io_file_tx_body_filepart(const char *name,int start, int len,struct MD5Context *md5context)
{
char mute=0;
fileTYPE f = {};
static uint8_t buf[4096];
if (!FileOpen(&f, name, mute)) return 0;
if (start) FileSeek(&f, start, SEEK_SET);
unsigned long bytes2send = f.size;
if (len>0 && len < bytes2send) bytes2send=len;
/* transmit the entire file using one transfer */
printf("Selected file %s with %lu bytes to send \n", name, bytes2send);
while (bytes2send)
{
printf(".");
uint16_t chunk = (bytes2send > sizeof(buf)) ? sizeof(buf) : bytes2send;
FileReadAdv(&f, buf, chunk);
user_io_file_tx_body(buf,chunk,md5context);
bytes2send -= chunk;
}
return 1;
}
int user_io_file_tx_finish()
{
// check if core requests some change while downloading
check_status_change();
printf("\n");
printf("CRC32: %08X\n", file_crc);
#if DEBUG_ROM_BINARY
fclose(rombinary);
#endif
// signal end of transmission
EnableFpga();
spi8(UIO_FILE_TX);
spi8(0x00);
DisableFpga();
printf("\n");
return 1;
}
static char pchar[] = { 0x8C, 0x8F, 0x7F };
#define PROGRESS_CNT 28

View File

@@ -199,7 +199,7 @@ typedef struct {
uint8_t fifo_stat; // space in cores input fifo
} __attribute__((packed)) serial_status_t;
void user_io_init(const char *path);
void user_io_init(const char *path, const char *xml);
unsigned char user_io_core_type();
void user_io_poll();
char user_io_menu_button();
@@ -210,6 +210,13 @@ void user_io_read_confstr();
char *user_io_get_confstr(int index);
uint32_t user_io_8bit_set_status(uint32_t, uint32_t, int ex = 0);
int user_io_file_tx(const char* name, unsigned char index = 0, char opensave = 0, char mute = 0, char composite = 0);
int user_io_file_tx_start(const char *name,unsigned char index=0);
int user_io_file_tx_body(const uint8_t *buf,uint16_t chunk,struct MD5Context *md5context=NULL);
int user_io_file_tx_body_filepart(const char *name,int start=0, int len=0,struct MD5Context *md5context=NULL);
int user_io_file_tx_finish();
uint32_t user_io_get_file_crc();
int user_io_file_mount(char *name, unsigned char index = 0, char pre = 0);
char user_io_serial_status(serial_status_t *, uint8_t);

View File

@@ -603,6 +603,8 @@ static uint32_t show_video_info(int force)
Info(str, cfg.video_info * 1000);
}
CheckArcadeError();
uint32_t scrh = v_cur.item[5];
if (height && scrh)
{