summaryrefslogtreecommitdiffstatshomepage
path: root/src/tools
diff options
context:
space:
mode:
author Aaron Giles <aaron@aarongiles.com>2007-12-17 15:19:59 +0000
committer Aaron Giles <aaron@aarongiles.com>2007-12-17 15:19:59 +0000
commit7b77f1218624ea26dbb2efd85a19f795f5d4e02e (patch)
tree19209304095572b4fd61c2a2d6a5aa75c4e471ad /src/tools
parent3da7f476068b3ffef713218ba2fc1bd5030f2c38 (diff)
Initial checkin of MAME 0.121.mame0121
Diffstat (limited to 'src/tools')
-rw-r--r--src/tools/chdcd.c283
-rw-r--r--src/tools/chdcd.h28
-rw-r--r--src/tools/chdman.c2629
-rw-r--r--src/tools/jedutil.c289
-rw-r--r--src/tools/makemeta.c587
-rw-r--r--src/tools/regrep.c1181
-rw-r--r--src/tools/romcmp.c748
-rw-r--r--src/tools/runtest.cmd156
-rw-r--r--src/tools/src2html.c860
-rw-r--r--src/tools/srcclean.c182
-rw-r--r--src/tools/tools.mak124
11 files changed, 7067 insertions, 0 deletions
diff --git a/src/tools/chdcd.c b/src/tools/chdcd.c
new file mode 100644
index 00000000000..d205f4d1bc8
--- /dev/null
+++ b/src/tools/chdcd.c
@@ -0,0 +1,283 @@
+/***************************************************************************
+
+ CDRDAO TOC parser for CHD compression frontend
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#include "osdcore.h"
+#include "chd.h"
+#include "chdcd.h"
+#include <ctype.h>
+
+
+
+/***************************************************************************
+ CONSTANTS & DEFINES
+***************************************************************************/
+
+#define TOKENIZE i = tokenize( linebuffer, i, sizeof(linebuffer), token, sizeof(token) );
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+static char linebuffer[512];
+
+
+
+/***************************************************************************
+ IMPLEMENTATION
+***************************************************************************/
+
+/*-------------------------------------------------
+ get_file_size - get the size of a file
+-------------------------------------------------*/
+
+static UINT64 get_file_size(const char *filename)
+{
+ osd_file *file;
+ UINT64 filesize = 0;
+ file_error filerr;
+
+ filerr = osd_open(filename, OPEN_FLAG_READ, &file, &filesize);
+ if (filerr == FILERR_NONE)
+ osd_close(file);
+ return filesize;
+}
+
+
+/*-------------------------------------------------
+ tokenize - get a token from the line buffer
+-------------------------------------------------*/
+
+static int tokenize( const char *linebuffer, int i, int linebuffersize, char *token, int tokensize )
+{
+ int j = 0;
+ int singlequote = 0;
+ int doublequote = 0;
+
+ while ((i < linebuffersize) && isspace(linebuffer[i]))
+ {
+ i++;
+ }
+
+ while ((i < linebuffersize) && (j < tokensize))
+ {
+ if (!singlequote && linebuffer[i] == '"' )
+ {
+ doublequote = !doublequote;
+ }
+ else if (!doublequote && linebuffer[i] == '\'')
+ {
+ singlequote = !singlequote;
+ }
+ else if (!singlequote && !doublequote && isspace(linebuffer[i]))
+ {
+ break;
+ }
+ else
+ {
+ token[j] = linebuffer[i];
+ j++;
+ }
+
+ i++;
+ }
+
+ token[j] = '\0';
+
+ return i;
+}
+
+
+/*-------------------------------------------------
+ msf_to_frames - convert m:s:f into a number of frames
+-------------------------------------------------*/
+
+static int msf_to_frames( char *token )
+{
+ int m = 0;
+ int s = 0;
+ int f = 0;
+
+ if( sscanf( token, "%d:%d:%d", &m, &s, &f ) == 1 )
+ {
+ f = m;
+ }
+ else
+ {
+ /* convert to just frames */
+ s += (m * 60);
+ f += (s * 75);
+ }
+
+ return f;
+}
+
+
+/*-------------------------------------------------
+ chdcd_parse_toc - parse a CDRDAO format TOC file
+-------------------------------------------------*/
+
+chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_input_info *outinfo)
+{
+ FILE *infile;
+ int i, trknum;
+ static char token[128];
+
+ infile = fopen(tocfname, "rt");
+
+ if (infile == (FILE *)NULL)
+ {
+ return CHDERR_FILE_NOT_FOUND;
+ }
+
+ /* clear structures */
+ memset(outtoc, 0, sizeof(cdrom_toc));
+ memset(outinfo, 0, sizeof(chdcd_track_input_info));
+
+ trknum = -1;
+
+ while (!feof(infile))
+ {
+ /* get the next line */
+ fgets(linebuffer, 511, infile);
+
+ /* if EOF didn't hit, keep going */
+ if (!feof(infile))
+ {
+ i = 0;
+
+ TOKENIZE
+
+ if ((!strcmp(token, "DATAFILE")) || (!strcmp(token, "AUDIOFILE")) || (!strcmp(token, "FILE")))
+ {
+ int f;
+
+ /* found the data file for a track */
+ TOKENIZE
+
+ /* keep the filename */
+ strncpy(&outinfo->fname[trknum][0], token, strlen(token));
+
+ /* get either the offset or the length */
+ TOKENIZE
+
+ if (!strcmp(token, "SWAP"))
+ {
+ TOKENIZE
+
+ outinfo->swap[trknum] = 1;
+ }
+ else
+ {
+ outinfo->swap[trknum] = 0;
+ }
+
+ if (token[0] == '#')
+ {
+ /* it's a decimal offset, use it */
+ f = strtoul(&token[1], NULL, 10);
+ }
+ else if (isdigit(token[0]))
+ {
+ /* convert the time to an offset */
+ f = msf_to_frames( token );
+
+ f *= (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize);
+ }
+ else
+ {
+ f = 0;
+ }
+
+ outinfo->offset[trknum] = f;
+
+ TOKENIZE
+
+ if (isdigit(token[0]))
+ {
+ // this could be the length or an offset from the previous field.
+ f = msf_to_frames( token );
+
+ TOKENIZE
+
+ if (isdigit(token[0]))
+ {
+ // it was an offset.
+ f *= (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize);
+
+ outinfo->offset[trknum] += f;
+
+ // this is the length.
+ f = msf_to_frames( token );
+ }
+ }
+ else if( trknum == 0 && outinfo->offset[trknum] != 0 )
+ {
+ /* the 1st track might have a length with no offset */
+ f = outinfo->offset[trknum] / (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize);
+ outinfo->offset[trknum] = 0;
+ }
+ else
+ {
+ /* guesstimate the track length */
+ UINT64 tlen;
+ printf("Warning: Estimating length of track %d. If this is not the final or only track\n on the disc, the estimate may be wrong.\n", trknum+1);
+
+ tlen = get_file_size(outinfo->fname[trknum]) - outinfo->offset[trknum];
+
+ tlen /= (outtoc->tracks[trknum].datasize + outtoc->tracks[trknum].subsize);
+
+ f = tlen;
+ }
+
+ outtoc->tracks[trknum].frames = f;
+ }
+ else if (!strcmp(token, "TRACK"))
+ {
+ /* found a new track */
+ trknum++;
+
+ /* next token on the line is the track type */
+ TOKENIZE
+
+ outtoc->tracks[trknum].trktype = CD_TRACK_MODE1;
+ outtoc->tracks[trknum].datasize = 0;
+ outtoc->tracks[trknum].subtype = CD_SUB_NONE;
+ outtoc->tracks[trknum].subsize = 0;
+
+ cdrom_convert_type_string_to_track_info(token, &outtoc->tracks[trknum]);
+ if (outtoc->tracks[trknum].datasize == 0)
+ {
+ printf("ERROR: Unknown track type [%s]. Contact MAMEDEV.\n", token);
+ }
+ else if (outtoc->tracks[trknum].trktype != CD_TRACK_MODE1_RAW &&
+ outtoc->tracks[trknum].trktype != CD_TRACK_MODE2_RAW &&
+ outtoc->tracks[trknum].trktype != CD_TRACK_AUDIO)
+ {
+ printf("Note: MAME now prefers and can accept RAW format images.\n");
+ printf("At least one track of this CDRDAO rip is not either RAW or AUDIO.\n");
+ }
+
+ /* next (optional) token on the line is the subcode type */
+ TOKENIZE
+
+ cdrom_convert_subtype_string_to_track_info(token, &outtoc->tracks[trknum]);
+ }
+ }
+ }
+
+ /* close the input TOC */
+ fclose(infile);
+
+ /* store the number of tracks found */
+ outtoc->numtrks = trknum + 1;
+
+ return CHDERR_NONE;
+}
diff --git a/src/tools/chdcd.h b/src/tools/chdcd.h
new file mode 100644
index 00000000000..b783ab00acc
--- /dev/null
+++ b/src/tools/chdcd.h
@@ -0,0 +1,28 @@
+/***************************************************************************
+
+ CDRDAO TOC parser for CHD compression frontend
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#pragma once
+
+#ifndef __CHDCD_H__
+#define __CHDCD_H__
+
+#include "cdrom.h"
+
+typedef struct _chdcd_track_input_info chdcd_track_input_info;
+struct _chdcd_track_input_info /* used only at compression time */
+{
+ char fname[CD_MAX_TRACKS][256]; /* filename for each track */
+ UINT32 offset[CD_MAX_TRACKS]; /* offset in the data file for each track */
+ int swap[CD_MAX_TRACKS]; /* data needs to be byte swapped */
+};
+
+
+chd_error chdcd_parse_toc(const char *tocfname, cdrom_toc *outtoc, chdcd_track_input_info *outinfo);
+
+#endif /* __CHDCD_H__ */
diff --git a/src/tools/chdman.c b/src/tools/chdman.c
new file mode 100644
index 00000000000..fe276d3ba90
--- /dev/null
+++ b/src/tools/chdman.c
@@ -0,0 +1,2629 @@
+/***************************************************************************
+
+ CHD compression frontend
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#include "osdcore.h"
+#include "corefile.h"
+#include "chdcd.h"
+#include "aviio.h"
+#include "bitmap.h"
+#include "md5.h"
+#include "sha1.h"
+#include <stdarg.h>
+#include <stdio.h>
+#include <time.h>
+#include <ctype.h>
+
+
+/***************************************************************************
+ CONSTANTS & DEFINES
+***************************************************************************/
+
+#define IDE_SECTOR_SIZE 512
+
+#define ENABLE_CUSTOM_CHOMP 0
+
+#define OPERATION_UPDATE 0
+#define OPERATION_MERGE 1
+#define OPERATION_CHOMP 2
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+struct _chd_interface_file
+{
+ osd_file *file;
+ UINT64 length;
+};
+
+
+
+/***************************************************************************
+ FUNCTION PROTOTYPES
+***************************************************************************/
+
+static chd_error chdman_compress_file(chd_file *chd, const char *rawfile, UINT32 offset);
+static chd_error chdman_compress_chd(chd_file *chd, chd_file *source, UINT32 totalhunks);
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+static const char *error_strings[] =
+{
+ "no error",
+ "no drive interface",
+ "out of memory",
+ "invalid file",
+ "invalid parameter",
+ "invalid data",
+ "file not found",
+ "requires parent",
+ "file not writeable",
+ "read error",
+ "write error",
+ "codec error",
+ "invalid parent",
+ "hunk out of range",
+ "decompression error",
+ "compression error",
+ "can't create file",
+ "can't verify file",
+ "operation not supported",
+ "can't find metadata",
+ "invalid metadata size",
+ "unsupported CHD version",
+ "incomplete verify"
+};
+
+static clock_t lastprogress = 0;
+
+
+
+/***************************************************************************
+ IMPLEMENTATION
+***************************************************************************/
+
+/*-------------------------------------------------
+ put_bigendian_uint32 - write a UINT32 in big-endian order to memory
+-------------------------------------------------*/
+
+INLINE void put_bigendian_uint32(UINT8 *base, UINT32 value)
+{
+ base[0] = value >> 24;
+ base[1] = value >> 16;
+ base[2] = value >> 8;
+ base[3] = value;
+}
+
+
+/*-------------------------------------------------
+ print_big_int - 64-bit int printing with commas
+-------------------------------------------------*/
+
+void print_big_int(UINT64 intvalue, char *output)
+{
+ int chunk;
+
+ chunk = intvalue % 1000;
+ intvalue /= 1000;
+ if (intvalue != 0)
+ {
+ print_big_int(intvalue, output);
+ strcat(output, ",");
+ sprintf(&output[strlen(output)], "%03d", chunk);
+ }
+ else
+ sprintf(&output[strlen(output)], "%d", chunk);
+}
+
+
+/*-------------------------------------------------
+ big_int_string - return a string for a big int
+-------------------------------------------------*/
+
+char *big_int_string(UINT64 intvalue)
+{
+ static char buffer[256];
+ buffer[0] = 0;
+ print_big_int(intvalue, buffer);
+ return buffer;
+}
+
+
+/*-------------------------------------------------
+ progress - generic progress callback
+-------------------------------------------------*/
+
+static void progress(int forceit, const char *fmt, ...)
+{
+ clock_t curtime = clock();
+ va_list arg;
+
+ /* skip if it hasn't been long enough */
+ if (!forceit && curtime - lastprogress < CLOCKS_PER_SEC / 2)
+ return;
+ lastprogress = curtime;
+
+ /* standard vfprintf stuff here */
+ va_start(arg, fmt);
+ vprintf(fmt, arg);
+ fflush(stdout);
+ va_end(arg);
+}
+
+
+/*-------------------------------------------------
+ error_string - return an error sting
+-------------------------------------------------*/
+
+static const char *error_string(int err)
+{
+ static char temp_buffer[100];
+
+ if (err < sizeof(error_strings) / sizeof(error_strings[0]))
+ return error_strings[err];
+
+ sprintf(temp_buffer, "unknown error %d", err);
+ return temp_buffer;
+}
+
+
+/*-------------------------------------------------
+ usage - generic usage error display
+-------------------------------------------------*/
+
+static int usage(void)
+{
+ printf("usage: chdman -info input.chd\n");
+ printf(" or: chdman -createraw inputhd.raw output.chd [inputoffs [hunksize]]\n");
+ printf(" or: chdman -createhd inputhd.raw output.chd [inputoffs [cylinders heads sectors [sectorsize [hunksize]]]]\n");
+ printf(" or: chdman -createblankhd output.chd cylinders heads sectors [sectorsize [hunksize]]\n");
+ printf(" or: chdman -createcd input.toc output.chd\n");
+ printf(" or: chdman -createav input.avi inputmeta.txt output.chd [firstframe [numframes]]\n");
+ printf(" or: chdman -copydata input.chd output.chd\n");
+ printf(" or: chdman -extract input.chd output.raw\n");
+ printf(" or: chdman -extractcd input.chd output.toc output.bin\n");
+ printf(" or: chdman -extractav input.chd output.avi outputmeta.txt [firstframe [numframes]]\n");
+ printf(" or: chdman -verify input.chd\n");
+ printf(" or: chdman -verifyfix input.chd\n");
+ printf(" or: chdman -update input.chd output.chd\n");
+ printf(" or: chdman -chomp inout.chd output.chd maxhunk\n");
+ printf(" or: chdman -merge parent.chd diff.chd output.chd\n");
+ printf(" or: chdman -diff parent.chd compare.chd diff.chd\n");
+ printf(" or: chdman -setchs inout.chd cylinders heads sectors\n");
+ return 1;
+}
+
+
+/*-------------------------------------------------
+ get_file_size - get the size of a file
+-------------------------------------------------*/
+
+static UINT64 get_file_size(const char *filename)
+{
+ osd_file *file;
+ UINT64 filesize = 0;
+ file_error filerr;
+
+ filerr = osd_open(filename, OPEN_FLAG_READ, &file, &filesize);
+ if (filerr == FILERR_NONE)
+ osd_close(file);
+ return filesize;
+}
+
+
+/*-------------------------------------------------
+ guess_chs - given a file and an offset,
+ compute a best guess CHS value set
+-------------------------------------------------*/
+
+static chd_error guess_chs(const char *filename, int offset, int sectorsize, UINT32 *cylinders, UINT32 *heads, UINT32 *sectors, UINT32 *bps)
+{
+ UINT32 totalsecs, hds, secs;
+ UINT64 filesize;
+
+ /* if this is a direct physical drive read, handle it specially */
+ if (osd_get_physical_drive_geometry(filename, cylinders, heads, sectors, bps))
+ return CHDERR_NONE;
+
+ /* compute the filesize */
+ filesize = get_file_size(filename);
+ if (filesize <= offset)
+ {
+ fprintf(stderr, "Invalid file '%s'\n", filename);
+ return CHDERR_INVALID_FILE;
+ }
+ filesize -= offset;
+
+ /* validate the size */
+ if (filesize % sectorsize != 0)
+ {
+ fprintf(stderr, "Can't guess CHS values because data size is not divisible by the sector size\n");
+ return CHDERR_INVALID_FILE;
+ }
+ totalsecs = filesize / sectorsize;
+
+ /* now find a valid value */
+ for (secs = 63; secs > 1; secs--)
+ if (totalsecs % secs == 0)
+ {
+ size_t totalhds = totalsecs / secs;
+ for (hds = 16; hds > 1; hds--)
+ if (totalhds % hds == 0)
+ {
+ *cylinders = totalhds / hds;
+ *heads = hds;
+ *sectors = secs;
+ *bps = sectorsize;
+ return CHDERR_NONE;
+ }
+ }
+
+ /* ack, it didn't work! */
+ fprintf(stderr, "Can't guess CHS values because no logical combination works!\n");
+ return CHDERR_INVALID_FILE;
+}
+
+
+/*-------------------------------------------------
+ do_createhd - create a new compressed hard
+ disk image from a raw file
+-------------------------------------------------*/
+
+static int do_createhd(int argc, char *argv[], int param)
+{
+ UINT32 guess_cylinders = 0, guess_heads = 0, guess_sectors = 0, guess_sectorsize = 0;
+ UINT32 cylinders, heads, sectors, sectorsize, hunksize, totalsectors, offset;
+ const char *inputfile, *outputfile;
+ chd_file *chd = NULL;
+ char metadata[256];
+ chd_error err;
+
+ /* require 4-5, or 8-10 args total */
+ if (argc != 4 && argc != 5 && argc != 8 && argc != 9 && argc != 10)
+ return usage();
+
+ /* extract the first few parameters */
+ inputfile = argv[2];
+ outputfile = argv[3];
+ offset = (argc >= 5) ? atoi(argv[4]) : (get_file_size(inputfile) % IDE_SECTOR_SIZE);
+
+ /* if less than 8 parameters, we need to guess the CHS values */
+ if (argc < 8)
+ {
+ err = guess_chs(inputfile, offset, IDE_SECTOR_SIZE, &guess_cylinders, &guess_heads, &guess_sectors, &guess_sectorsize);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+ }
+
+ /* parse the remaining parameters */
+ cylinders = (argc >= 6) ? atoi(argv[5]) : guess_cylinders;
+ heads = (argc >= 7) ? atoi(argv[6]) : guess_heads;
+ sectors = (argc >= 8) ? atoi(argv[7]) : guess_sectors;
+ sectorsize = (argc >= 9) ? atoi(argv[8]) : guess_sectorsize;
+ if (sectorsize == 0) sectorsize = IDE_SECTOR_SIZE;
+ hunksize = (argc >= 10) ? atoi(argv[9]) : (sectorsize > 4096) ? sectorsize : ((4096 / sectorsize) * sectorsize);
+ totalsectors = cylinders * heads * sectors;
+
+ /* print some info */
+ printf("Input file: %s\n", inputfile);
+ printf("Output file: %s\n", outputfile);
+ printf("Input offset: %d\n", offset);
+ printf("Cylinders: %d\n", cylinders);
+ printf("Heads: %d\n", heads);
+ printf("Sectors: %d\n", sectors);
+ printf("Bytes/sector: %d\n", sectorsize);
+ printf("Sectors/hunk: %d\n", hunksize / sectorsize);
+ printf("Logical size: %s\n", big_int_string((UINT64)totalsectors * (UINT64)sectorsize));
+
+ /* create the new hard drive */
+ err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_ZLIB_PLUS, NULL);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error creating CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* open the new hard drive */
+ err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening new CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* write the metadata */
+ sprintf(metadata, HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize);
+ err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error adding hard disk metadata: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* compress the hard drive */
+ err = chdman_compress_file(chd, inputfile, offset);
+ if (err != CHDERR_NONE)
+ fprintf(stderr, "Error during compression: %s\n", error_string(err));
+
+cleanup:
+ /* close everything down */
+ if (chd != NULL)
+ chd_close(chd);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_createraw - create a new compressed raw
+ image from a raw file
+-------------------------------------------------*/
+
+static int do_createraw(int argc, char *argv[], int param)
+{
+ const char *inputfile, *outputfile;
+ UINT32 hunksize, offset;
+ UINT64 logicalbytes;
+ chd_file *chd = NULL;
+ chd_error err;
+
+ /* require 4, 5, or 6 args total */
+ if (argc != 4 && argc != 5 && argc != 6)
+ return usage();
+
+ /* extract the first few parameters */
+ inputfile = argv[2];
+ outputfile = argv[3];
+ offset = (argc >= 5) ? atoi(argv[4]) : 0;
+ hunksize = (argc >= 6) ? atoi(argv[5]) : 4096;
+ logicalbytes = get_file_size(inputfile) - offset;
+
+ /* print some info */
+ printf("Input file: %s\n", inputfile);
+ printf("Output file: %s\n", outputfile);
+ printf("Input offset: %d\n", offset);
+ printf("Bytes/hunk: %d\n", hunksize);
+ printf("Logical size: %s\n", big_int_string(logicalbytes));
+
+ /* create the new CHD */
+ err = chd_create(outputfile, logicalbytes, hunksize, CHDCOMPRESSION_ZLIB_PLUS, NULL);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error creating CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* open the new CHD */
+ err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening new CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* compress the CHD */
+ err = chdman_compress_file(chd, inputfile, offset);
+ if (err != CHDERR_NONE)
+ fprintf(stderr, "Error during compression: %s\n", error_string(err));
+
+cleanup:
+ /* close everything down */
+ if (chd != NULL)
+ chd_close(chd);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_createcd - create a new compressed CD
+ image from a raw file
+-------------------------------------------------*/
+
+static int do_createcd(int argc, char *argv[], int param)
+{
+ static chdcd_track_input_info track_info;
+ static cdrom_toc toc;
+ UINT32 hunksize = CD_FRAME_SIZE * CD_FRAMES_PER_HUNK;
+ UINT32 sectorsize = CD_FRAME_SIZE;
+ const char *inputfile, *outputfile;
+ core_file *srcfile = NULL;
+ UINT32 origtotalsectors;
+ chd_file *chd = NULL;
+ UINT8 *cache = NULL;
+ UINT32 totalsectors;
+ double ratio = 1.0;
+ UINT32 totalhunks;
+ file_error filerr;
+ chd_error err;
+ int i;
+
+ /* require 4 args total */
+ if (argc != 4)
+ return usage();
+
+ /* extract the data */
+ inputfile = argv[2];
+ outputfile = argv[3];
+
+ /* allocate a cache */
+ cache = malloc(hunksize);
+ if (cache == NULL)
+ {
+ fprintf(stderr, "Out of memory allocating temporary buffer\n");
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+
+ /* setup the CDROM module and get the disc info */
+ err = chdcd_parse_toc(inputfile, &toc, &track_info);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error reading input file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* pad each track to a hunk boundry. cdrom.c will deal with this on the read side */
+ for (i = 0; i < toc.numtrks; i++)
+ {
+ int hunks = (toc.tracks[i].frames + CD_FRAMES_PER_HUNK - 1) / CD_FRAMES_PER_HUNK;
+ toc.tracks[i].extraframes = hunks * CD_FRAMES_PER_HUNK - toc.tracks[i].frames;
+ }
+
+ /* count up the total number of frames */
+ origtotalsectors = totalsectors = 0;
+ for (i = 0; i < toc.numtrks; i++)
+ {
+ origtotalsectors += toc.tracks[i].frames;
+ totalsectors += toc.tracks[i].frames + toc.tracks[i].extraframes;
+ }
+ printf("\nCD-ROM %s has %d tracks and %d total frames\n", inputfile, toc.numtrks, origtotalsectors);
+
+ /* create the new CHD file */
+ err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_ZLIB_PLUS, NULL);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error creating CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* open the new CHD file */
+ err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening new CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* write the metadata */
+ for (i = 0; i < toc.numtrks; i++)
+ {
+ char metadata[256];
+ sprintf(metadata, CDROM_TRACK_METADATA_FORMAT, i + 1, cdrom_get_type_string(&toc.tracks[i]),
+ cdrom_get_subtype_string(&toc.tracks[i]), toc.tracks[i].frames);
+
+ err = chd_set_metadata(chd, CDROM_TRACK_METADATA_TAG, i, metadata, strlen(metadata) + 1);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error adding CD-ROM metadata: %s\n", error_string(err));
+ goto cleanup;
+ }
+ }
+
+ /* begin state for writing */
+ err = chd_compress_begin(chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error compressing: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* loop over tracks */
+ totalhunks = 0;
+ for (i = 0; i < toc.numtrks; i++)
+ {
+ int frames = 0;
+ int bytespersector = toc.tracks[i].datasize + toc.tracks[i].subsize;
+ int trackhunks = (toc.tracks[i].frames + toc.tracks[i].extraframes) / CD_FRAMES_PER_HUNK;
+ UINT64 sourcefileoffset = track_info.offset[i];
+ int curhunk;
+
+ /* open the input file for this track */
+ filerr = core_fopen(track_info.fname[i], OPEN_FLAG_READ, &srcfile);
+ if (filerr != FILERR_NONE)
+ {
+ fprintf(stderr, "Unable to open file: %s\n", track_info.fname[i]);
+ err = CHDERR_FILE_NOT_FOUND;
+ goto cleanup;
+ }
+
+ printf("Track %d/%d (%s:%d,%d frames,%d hunks,swap %d)\n", i+1, toc.numtrks, track_info.fname[i], track_info.offset[i], toc.tracks[i].frames, trackhunks, track_info.swap[i]);
+
+ /* loop over hunks */
+ for (curhunk = 0; curhunk < trackhunks; curhunk++, totalhunks++)
+ {
+ int secnum;
+
+ progress(FALSE, "Compressing hunk %d/%d... (ratio=%d%%) \r", totalhunks, chd_get_header(chd)->totalhunks, (int)(ratio * 100));
+
+ /* loop over sectors in this hunk, reading the source data into a fixed start location */
+ /* relative to the start; we zero out the buffer ahead of time to ensure that unpopulated */
+ /* areas are cleared */
+ memset(cache, 0, hunksize);
+ for (secnum = 0; secnum < CD_FRAMES_PER_HUNK; secnum++)
+ {
+ if (frames < toc.tracks[i].frames)
+ {
+ core_fseek(srcfile, sourcefileoffset, SEEK_SET);
+ core_fread(srcfile, &cache[secnum * CD_FRAME_SIZE], bytespersector);
+
+ if (track_info.swap[i])
+ {
+ int swapindex;
+
+ for (swapindex = 0; swapindex < 2352; swapindex += 2 )
+ {
+ int swapoffset = ( secnum * CD_FRAME_SIZE ) + swapindex;
+
+ int swaptemp = cache[ swapoffset ];
+ cache[ swapoffset ] = cache[ swapoffset + 1 ];
+ cache[ swapoffset + 1 ] = swaptemp;
+ }
+ }
+ }
+
+ sourcefileoffset += bytespersector;
+ frames++;
+ }
+
+ /* compress the current hunk */
+ err = chd_compress_hunk(chd, cache, &ratio);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error during compression: %s\n", error_string(err));
+ goto cleanup;
+ }
+ }
+
+ /* close the file */
+ core_fclose(srcfile);
+ srcfile = NULL;
+ }
+
+ /* cleanup */
+ err = chd_compress_finish(chd);
+ if (err != CHDERR_NONE)
+ fprintf(stderr, "Error during compression finalization: %s\n", error_string(err));
+ else
+ progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio));
+
+cleanup:
+ if (cache != NULL)
+ free(cache);
+ if (srcfile != NULL)
+ core_fclose(srcfile);
+ if (chd != NULL)
+ chd_close(chd);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ read_avi_frame - read an AVI frame
+-------------------------------------------------*/
+
+static avi_error read_avi_frame(avi_file *avi, UINT32 framenum, UINT8 *cache, bitmap_t *bitmap, int interlaced, UINT32 hunkbytes)
+{
+ const avi_movie_info *info = avi_get_movie_info(avi);
+ int interlace_factor = interlaced ? 2 : 1;
+ UINT32 first_sample, num_samples;
+ avi_error avierr = AVIERR_NONE;
+ int chnum, sampnum, x, y;
+ UINT8 *dest = cache;
+ INT16 *temp;
+
+ /* compute the number of samples in this frame */
+ first_sample = avi_first_sample_in_frame(avi, framenum / interlace_factor);
+ num_samples = avi_first_sample_in_frame(avi, framenum / interlace_factor + 1) - first_sample;
+ if (interlaced)
+ {
+ if (framenum % 2 == 0)
+ num_samples = (num_samples + 1) / 2;
+ else
+ {
+ first_sample += (num_samples + 1) / 2;
+ num_samples -= (num_samples + 1) / 2;
+ }
+ }
+
+ /* allocate a temporary buffer */
+ temp = malloc(num_samples * 2);
+ if (temp == NULL)
+ return AVIERR_NO_MEMORY;
+
+ /* update the header with the actual number of samples in the frame */
+ dest[6] = num_samples >> 8;
+ dest[7] = num_samples;
+ dest += 12 + dest[4];
+
+ /* loop over channels and read the samples */
+ for (chnum = 0; chnum < info->audio_channels; chnum++)
+ {
+ /* read the sound samples */
+ avierr = avi_read_sound_samples(avi, chnum, first_sample, num_samples, temp);
+ if (avierr != AVIERR_NONE)
+ goto cleanup;
+
+ /* store them big endian at the destination */
+ for (sampnum = 0; sampnum < num_samples; sampnum++)
+ {
+ INT16 sample = temp[sampnum];
+ *dest++ = sample >> 8;
+ *dest++ = sample;
+ }
+ }
+
+ /* read the video data when we hit a new frame */
+ if (framenum % interlace_factor == 0)
+ {
+ avierr = avi_read_video_frame_yuy16(avi, framenum / interlace_factor, bitmap);
+ if (avierr != AVIERR_NONE)
+ goto cleanup;
+ }
+
+ /* loop over the data and copy it to the cache */
+ for (y = framenum % interlace_factor; y < info->video_height; y += interlace_factor)
+ {
+ UINT16 *source = (UINT16 *)bitmap->base + y * bitmap->rowpixels;
+
+ for (x = 0; x < info->video_width; x++)
+ {
+ UINT16 pixel = *source++;
+ *dest++ = pixel;
+ *dest++ = pixel >> 8;
+ }
+ }
+
+ /* fill the rest with 0 */
+ while (dest < &cache[hunkbytes])
+ *dest++ = 0;
+
+cleanup:
+ free(temp);
+ return avierr;
+}
+
+
+/*-------------------------------------------------
+ do_createav - create a new A/V file from an
+ input AVI file and metadata
+-------------------------------------------------*/
+
+static int do_createav(int argc, char *argv[], int param)
+{
+ UINT32 fps_times_1million, width, height, interlaced, channels, rate, metabytes = 0, totalframes;
+ UINT32 max_samples_per_frame, bytes_per_frame, firstframe, numframes;
+ const char *inputfile, *metafile, *outputfile;
+ bitmap_t videobitmap = { 0 };
+ const avi_movie_info *info;
+ const chd_header *header;
+ char metadata[256];
+ chd_file *chd = NULL;
+ avi_file *avi = NULL;
+ UINT8 *cache = NULL;
+ double ratio = 1.0;
+ FILE *meta = NULL;
+ avi_error avierr;
+ chd_error err;
+ UINT32 framenum;
+
+ /* require 5-7 args total */
+ if (argc < 5 || argc > 7)
+ return usage();
+
+ /* extract the first few parameters */
+ inputfile = argv[2];
+ metafile = argv[3];
+ outputfile = argv[4];
+ firstframe = (argc > 5) ? atoi(argv[5]) : 0;
+ numframes = (argc > 6) ? atoi(argv[6]) : 1000000;
+
+ /* print some info */
+ printf("Input file: %s\n", inputfile);
+ printf("Meta file: %s\n", (metafile == NULL) ? "(none)" : metafile);
+ printf("Output file: %s\n", outputfile);
+
+ /* open the meta file */
+ if (metafile != NULL)
+ {
+ meta = fopen(metafile, "r");
+ if (meta == NULL)
+ {
+ fprintf(stderr, "Error opening meta file\n");
+ err = CHDERR_INVALID_FILE;
+ goto cleanup;
+ }
+ if (fgets(metadata, sizeof(metadata), meta) == NULL || sscanf(metadata, "chdmeta %d\n", &metabytes) != 1)
+ {
+ fprintf(stderr, "Invalid data header in metafile\n");
+ err = CHDERR_INVALID_FILE;
+ goto cleanup;
+ }
+ if (metabytes > 255)
+ {
+ fprintf(stderr, "Metadata too large (255 bytes maximum)\n");
+ err = CHDERR_INVALID_FILE;
+ goto cleanup;
+ }
+ }
+
+ /* open the source file */
+ avierr = avi_open(inputfile, &avi);
+ if (avierr != AVIERR_NONE)
+ {
+ fprintf(stderr, "Error opening AVI file: %s\n", avi_error_string(avierr));
+ err = CHDERR_INVALID_FILE;
+ goto cleanup;
+ }
+
+ /* get the movie information */
+ info = avi_get_movie_info(avi);
+ fps_times_1million = (UINT64)info->video_timescale * 1000000 / info->video_sampletime;
+ width = info->video_width;
+ height = info->video_height;
+ interlaced = ((fps_times_1million / 1000000) <= 30) && (height % 2 == 0) && (height > 288);
+ channels = info->audio_channels;
+ rate = info->audio_samplerate;
+ totalframes = info->video_numsamples;
+ numframes = MIN(totalframes - firstframe, numframes);
+
+ /* allocate a video buffer */
+ videobitmap.base = malloc(width * height * 2);
+ if (videobitmap.base == NULL)
+ {
+ fprintf(stderr, "Out of memory allocating temporary bitmap\n");
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+ videobitmap.format = BITMAP_FORMAT_YUY16;
+ videobitmap.width = width;
+ videobitmap.height = height;
+ videobitmap.bpp = 16;
+ videobitmap.rowpixels = width;
+
+ /* print some of it */
+ printf("Use frames: %d-%d\n", firstframe, firstframe + numframes - 1);
+ printf("Frame rate: %d.%06d\n", fps_times_1million / 1000000, fps_times_1million % 1000000);
+ printf("Frame size: %d x %d %s\n", width, height, interlaced ? "interlaced" : "non-interlaced");
+ printf("Audio: %d channels at %d Hz\n", channels, rate);
+ printf("Metadata: %d bytes/frame\n", metabytes);
+ printf("Total frames: %d (%02d:%02d:%02d)\n", totalframes,
+ (UINT32)((UINT64)totalframes * 1000000 / fps_times_1million / 60 / 60),
+ (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million / 60) % 60),
+ (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million) % 60));
+
+ /* adjust for interlacing */
+ if (interlaced)
+ {
+ fps_times_1million *= 2;
+ totalframes *= 2;
+ height /= 2;
+ firstframe *= 2;
+ numframes *= 2;
+ }
+
+ /* determine the number of bytes per frame */
+ max_samples_per_frame = ((UINT64)rate * 1000000 + fps_times_1million - 1) / fps_times_1million;
+ bytes_per_frame = 12 + metabytes + channels * max_samples_per_frame * 2 + width * height * 2;
+
+ /* create the new CHD */
+ err = chd_create(outputfile, (UINT64)numframes * (UINT64)bytes_per_frame, bytes_per_frame, CHDCOMPRESSION_AV, NULL);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error creating CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* open the new CHD */
+ err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening new CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+ header = chd_get_header(chd);
+
+ /* write the metadata */
+ sprintf(metadata, AV_METADATA_FORMAT, fps_times_1million / 1000000, fps_times_1million % 1000000, width, height, interlaced, channels, rate, metabytes);
+ err = chd_set_metadata(chd, AV_METADATA_TAG, 0, metadata, strlen(metadata) + 1);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error adding AV metadata: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* allocate a cache */
+ cache = malloc(bytes_per_frame);
+ if (cache == NULL)
+ {
+ fprintf(stderr, "Out of memory allocating temporary buffer\n");
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+
+ /* fill in the basic values */
+ cache[0] = 'c';
+ cache[1] = 'h';
+ cache[2] = 'a';
+ cache[3] = 'v';
+ cache[4] = metabytes;
+ cache[5] = channels;
+ cache[6] = max_samples_per_frame >> 8;
+ cache[7] = max_samples_per_frame;
+ cache[8] = width >> 8;
+ cache[9] = width;
+ cache[10] = (interlaced << 7) | (height >> 8);
+ cache[11] = height;
+
+ /* begin compressing */
+ err = chd_compress_begin(chd);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+
+ /* loop over source hunks until we run out */
+ for (framenum = 0; framenum < numframes; framenum++)
+ {
+ /* progress */
+ progress(framenum == 0, "Compressing hunk %d/%d... (ratio=%d%%) \r", framenum, header->totalhunks, (int)(100.0 * ratio));
+
+ /* read the metadata */
+ if (metabytes > 0)
+ {
+ memset(&cache[12], 0, metabytes);
+ if (meta != NULL && fgets(metadata, sizeof(metadata), meta) != NULL)
+ {
+ int metaoffs, stroffs, length = strlen(metadata);
+
+ for (metaoffs = stroffs = 0; metaoffs < metabytes && stroffs < length; metaoffs++, stroffs += 2)
+ {
+ int data;
+ if (sscanf(&metadata[stroffs], "%02X", &data) != 1)
+ break;
+ cache[12 + metaoffs] = data;
+ }
+ }
+ }
+
+ /* read the frame into its proper format in the cache */
+ avierr = read_avi_frame(avi, firstframe + framenum, cache, &videobitmap, interlaced, bytes_per_frame);
+ if (avierr != AVIERR_NONE)
+ {
+ fprintf(stderr, "Error reading frame %d from AVI file: %s\n", firstframe + framenum, avi_error_string(avierr));
+ err = CHDERR_COMPRESSION_ERROR;
+ }
+
+ /* append the data */
+ err = chd_compress_hunk(chd, cache, &ratio);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+ }
+
+ /* finish compression */
+ err = chd_compress_finish(chd);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+ else
+ progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio));
+
+cleanup:
+ /* close everything down */
+ if (avi != NULL)
+ avi_close(avi);
+ if (chd != NULL)
+ chd_close(chd);
+ if (meta != NULL)
+ fclose(meta);
+ if (cache != NULL)
+ free(cache);
+ if (videobitmap.base != NULL)
+ free(videobitmap.base);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_createblankhd - create a new non-compressed
+ hard disk image, with all hunks filled with 0s
+-------------------------------------------------*/
+
+static int do_createblankhd(int argc, char *argv[], int param)
+{
+ UINT32 cylinders, heads, sectors, sectorsize, hunksize, totalsectors, hunknum;
+ const char *outputfile;
+ chd_file *chd = NULL;
+ UINT8 *cache = NULL;
+ char metadata[256];
+ chd_error err;
+
+ /* require 6, 7, or 8 args total */
+ if (argc != 6 && argc != 7 && argc != 8)
+ return usage();
+
+ /* extract the data */
+ outputfile = argv[2];
+ cylinders = atoi(argv[3]);
+ heads = atoi(argv[4]);
+ sectors = atoi(argv[5]);
+ sectorsize = (argc >= 7) ? atoi(argv[6]) : IDE_SECTOR_SIZE;
+ if (sectorsize == 0) sectorsize = IDE_SECTOR_SIZE;
+ hunksize = (argc >= 8) ? atoi(argv[7]) : (sectorsize > 4096) ? sectorsize : ((4096 / sectorsize) * sectorsize);
+ totalsectors = cylinders * heads * sectors;
+
+ /* print some info */
+ printf("Output file: %s\n", outputfile);
+ printf("Cylinders: %d\n", cylinders);
+ printf("Heads: %d\n", heads);
+ printf("Sectors: %d\n", sectors);
+ printf("Bytes/sector: %d\n", sectorsize);
+ printf("Sectors/hunk: %d\n", hunksize / sectorsize);
+ printf("Logical size: %s\n", big_int_string((UINT64)totalsectors * (UINT64)sectorsize));
+
+ /* create the new hard drive */
+ err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_NONE, NULL);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error creating CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* open the new hard drive */
+ err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening new CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* write the metadata */
+ sprintf(metadata, HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize);
+ err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error adding hard disk metadata: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* alloc and zero buffer*/
+ cache = malloc(hunksize);
+ if (cache == NULL)
+ {
+ fprintf(stderr, "Error allocating memory buffer\n");
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+ memset(cache, 0, hunksize);
+
+ /* Zero every hunk */
+ for (hunknum = 0; hunknum < chd_get_header(chd)->totalhunks; hunknum++)
+ {
+ /* progress */
+ progress(hunknum == 0, "Zeroing hunk %d/%d... \r", hunknum, chd_get_header(chd)->totalhunks);
+
+ /* write out the data */
+ err = chd_write(chd, hunknum, cache);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error writing CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+ }
+ progress(TRUE, "Creation complete! \n");
+
+cleanup:
+ /* close everything down */
+ if (cache != NULL)
+ free(cache);
+ if (chd != NULL)
+ chd_close(chd);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_copydata - copy all hunks of data from one
+ CHD file to another. The hunk sizes do not
+ need to match. If the source is shorter than
+ the destination, the source data will be
+ padded with 0s.
+-------------------------------------------------*/
+
+static int do_copydata(int argc, char *argv[], int param)
+{
+ const char *inputfile, *outputfile;
+ chd_file *outputchd = NULL;
+ chd_file *inputchd = NULL;
+ chd_error err;
+
+ /* require 4 args total */
+ if (argc != 4)
+ return usage();
+
+ /* extract the data */
+ inputfile = argv[2];
+ outputfile = argv[3];
+
+ /* print some info */
+ printf("Input file: %s\n", inputfile);
+ printf("Output file: %s\n", outputfile);
+
+ /* open the src hard drive */
+ err = chd_open(inputfile, CHD_OPEN_READ, NULL, &inputchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening src CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* open the dest hard drive */
+ err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &outputchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening dest CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* compress the source into the dest */
+ err = chdman_compress_chd(outputchd, inputchd, 0);
+ if (err != CHDERR_NONE)
+ fprintf(stderr, "Error during compression: %s\n", error_string(err));
+
+cleanup:
+ /* close everything down */
+ if (outputchd != NULL)
+ chd_close(outputchd);
+ if (inputchd != NULL)
+ chd_close(inputchd);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_extract - extract a raw file from a
+ CHD image
+-------------------------------------------------*/
+
+static int do_extract(int argc, char *argv[], int param)
+{
+ const char *inputfile, *outputfile;
+ core_file *outfile = NULL;
+ chd_file *infile = NULL;
+ const chd_header *header;
+ UINT64 bytesremaining;
+ void *hunk = NULL;
+ file_error filerr;
+ chd_error err;
+ int hunknum;
+
+ /* require 4 args total */
+ if (argc != 4)
+ return usage();
+
+ /* extract the data */
+ inputfile = argv[2];
+ outputfile = argv[3];
+
+ /* print some info */
+ printf("Input file: %s\n", inputfile);
+ printf("Output file: %s\n", outputfile);
+
+ /* get the header */
+ err = chd_open(inputfile, CHD_OPEN_READ, NULL, &infile);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, error_string(err));
+ goto cleanup;
+ }
+ header = chd_get_header(infile);
+
+ /* allocate memory to hold a hunk */
+ hunk = malloc(header->hunkbytes);
+ if (hunk == NULL)
+ {
+ fprintf(stderr, "Out of memory allocating hunk buffer!\n");
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+
+ /* create the output file */
+ filerr = core_fopen(outputfile, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &outfile);
+ if (filerr != FILERR_NONE)
+ {
+ fprintf(stderr, "Error opening output file '%s'\n", outputfile);
+ err = CHDERR_CANT_CREATE_FILE;
+ goto cleanup;
+ }
+
+ /* loop over hunks, reading and writing */
+ bytesremaining = header->logicalbytes;
+ for (hunknum = 0; hunknum < header->totalhunks; hunknum++)
+ {
+ UINT32 byteswritten, bytes_to_write;
+
+ /* progress */
+ progress(hunknum == 0, "Extracting hunk %d/%d... \r", hunknum, header->totalhunks);
+
+ /* read the hunk into a buffer */
+ err = chd_read(infile, hunknum, hunk);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error reading hunk %d from CHD file: %s\n", hunknum, error_string(err));
+ goto cleanup;
+ }
+
+ /* write the hunk to the file */
+ bytes_to_write = MIN(bytesremaining, header->hunkbytes);
+ core_fseek(outfile, (UINT64)hunknum * (UINT64)header->hunkbytes, SEEK_SET);
+ byteswritten = core_fwrite(outfile, hunk, bytes_to_write);
+ if (byteswritten != bytes_to_write)
+ {
+ fprintf(stderr, "Error writing hunk %d to output file: %s\n", hunknum, error_string(CHDERR_WRITE_ERROR));
+ err = CHDERR_WRITE_ERROR;
+ goto cleanup;
+ }
+ }
+ progress(TRUE, "Extraction complete! \n");
+
+cleanup:
+ /* clean up our mess */
+ if (outfile != NULL)
+ core_fclose(outfile);
+ if (hunk != NULL)
+ free(hunk);
+ if (infile != NULL)
+ chd_close(infile);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_extractcd - extract a CDRDAO .toc/.bin
+ file from a CHD-CD image
+-------------------------------------------------*/
+
+static int do_extractcd(int argc, char *argv[], int param)
+{
+ const char *inputfile, *outputfile, *outputfile2;
+ core_file *outfile2 = NULL;
+ chd_file *inputchd = NULL;
+ cdrom_file *cdrom = NULL;
+ FILE *outfile = NULL;
+ const cdrom_toc *toc;
+ UINT64 out2offs;
+ file_error filerr;
+ chd_error err;
+ int track;
+
+ /* require 5 args total */
+ if (argc != 5)
+ return usage();
+
+ /* extract the data */
+ inputfile = argv[2];
+ outputfile = argv[3];
+ outputfile2 = argv[4];
+
+ /* print some info */
+ printf("Input file: %s\n", inputfile);
+ printf("Output files: %s (toc) and %s (bin)\n", outputfile, outputfile2);
+
+ /* get the header */
+ err = chd_open(inputfile, CHD_OPEN_READ, NULL, &inputchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, error_string(err));
+ goto cleanup;
+ }
+
+ /* open the CD */
+ cdrom = cdrom_open(inputchd);
+ if (cdrom == NULL)
+ {
+ fprintf(stderr, "Error opening CHD-CD '%s'\n", inputfile);
+ err = CHDERR_INVALID_FILE;
+ goto cleanup;
+ }
+
+ /* get the TOC data */
+ toc = cdrom_get_toc(cdrom);
+
+ /* create the output files */
+ outfile = fopen(outputfile, "w");
+ if (outfile == NULL)
+ {
+ fprintf(stderr, "Error opening output file '%s'\n", outputfile);
+ err = CHDERR_CANT_CREATE_FILE;
+ goto cleanup;
+ }
+ fprintf(outfile, "CD_ROM\n\n\n");
+
+ filerr = core_fopen(outputfile2, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &outfile2);
+ if (filerr != FILERR_NONE)
+ {
+ fprintf(stderr, "Error opening output file '%s'\n", outputfile2);
+ err = CHDERR_CANT_CREATE_FILE;
+ goto cleanup;
+ }
+
+ /* process away */
+ out2offs = 0;
+ for (track = 0; track < toc->numtrks; track++)
+ {
+ UINT32 m, s, f, frame, trackframes;
+
+ progress(TRUE, "Extracting track %d... \r", track+1);
+
+ fprintf(outfile, "// Track %d\n", track+1);
+
+ /* write out the track type */
+ if (toc->tracks[track].subtype != CD_SUB_NONE)
+ fprintf(outfile, "TRACK %s %s\n", cdrom_get_type_string(&toc->tracks[track]), cdrom_get_subtype_string(&toc->tracks[track]));
+ else
+ fprintf(outfile, "TRACK %s\n", cdrom_get_type_string(&toc->tracks[track]));
+
+ /* write out the attributes */
+ fprintf(outfile, "NO COPY\n");
+ if (toc->tracks[track].trktype == CD_TRACK_AUDIO)
+ {
+ fprintf(outfile, "NO PRE_EMPHASIS\n");
+ fprintf(outfile, "TWO_CHANNEL_AUDIO\n");
+
+ /* the first audio track on a mixed-track disc always has a 2 second pad */
+ if (track == 1)
+ {
+ if (toc->tracks[track].subtype != CD_SUB_NONE)
+ fprintf(outfile, "ZERO AUDIO %s 00:02:00\n", cdrom_get_subtype_string(&toc->tracks[track]));
+ else
+ fprintf(outfile, "ZERO AUDIO 00:02:00\n");
+ }
+ }
+
+ /* convert to minutes/seconds/frames */
+ trackframes = toc->tracks[track].frames;
+ f = trackframes;
+ s = f / 75;
+ f %= 75;
+ m = s / 60;
+ s %= 60;
+
+ /* all tracks but the first one have a file offset */
+ if (track > 0)
+ fprintf(outfile, "DATAFILE \"%s\" #%d %02d:%02d:%02d // length in bytes: %d\n", outputfile2, (UINT32)out2offs, m, s, f, trackframes*(toc->tracks[track].datasize+toc->tracks[track].subsize));
+ else
+ fprintf(outfile, "DATAFILE \"%s\" %02d:%02d:%02d // length in bytes: %d\n", outputfile2, m, s, f, trackframes*(toc->tracks[track].datasize+toc->tracks[track].subsize));
+
+ if ((toc->tracks[track].trktype == CD_TRACK_AUDIO) && (track == 1))
+ fprintf(outfile, "START 00:02:00\n");
+
+ fprintf(outfile, "\n\n");
+
+ /* now write the actual data */
+ for (frame = 0; frame < trackframes; frame++)
+ {
+ UINT8 sector[CD_MAX_SECTOR_DATA + CD_MAX_SUBCODE_DATA];
+ UINT32 byteswritten;
+
+ progress(frame == 0, "Extracting track %d... %d/%d... \r", track+1, frame, trackframes);
+
+ /* read the raw data */
+ cdrom_read_data(cdrom, cdrom_get_track_start(cdrom, track) + frame, sector, toc->tracks[track].trktype);
+
+ /* write it out */
+ core_fseek(outfile2, out2offs, SEEK_SET);
+ byteswritten = core_fwrite(outfile2, sector, toc->tracks[track].datasize);
+ if (byteswritten != toc->tracks[track].datasize)
+ {
+ fprintf(stderr, "Error writing frame %d to output file: %s\n", frame, error_string(CHDERR_WRITE_ERROR));
+ err = CHDERR_WRITE_ERROR;
+ goto cleanup;
+ }
+ out2offs += toc->tracks[track].datasize;
+
+ /* read the subcode data */
+ cdrom_read_subcode(cdrom, cdrom_get_track_start(cdrom, track) + frame, sector);
+
+ /* write it out */
+ core_fseek(outfile2, out2offs, SEEK_SET);
+ byteswritten = core_fwrite(outfile2, sector, toc->tracks[track].subsize);
+ if (byteswritten != toc->tracks[track].subsize)
+ {
+ fprintf(stderr, "Error writing frame %d to output file: %s\n", frame, error_string(CHDERR_WRITE_ERROR));
+ err = CHDERR_WRITE_ERROR;
+ goto cleanup;
+ }
+ out2offs += toc->tracks[track].subsize;
+ }
+ progress(TRUE, "Extracting track %d... complete \n", track+1);
+ }
+ progress(TRUE, "Completed!\n");
+
+cleanup:
+ /* close everything down */
+ if (outfile != NULL)
+ fclose(outfile);
+ if (outfile2 != NULL)
+ core_fclose(outfile2);
+ if (cdrom != NULL)
+ cdrom_close(cdrom);
+ if (inputchd != NULL)
+ chd_close(inputchd);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ write_avi_frame - write an AVI frame
+-------------------------------------------------*/
+
+static avi_error write_avi_frame(avi_file *avi, UINT32 framenum, const UINT8 *buffer, bitmap_t *bitmap)
+{
+ const avi_movie_info *info = avi_get_movie_info(avi);
+ UINT32 channels, samples, width, height;
+ avi_error avierr = AVIERR_NONE;
+ int chnum, sampnum, x, y;
+ int interlace_factor;
+ INT16 *temp;
+
+ /* extract core data */
+ channels = buffer[5];
+ samples = (buffer[6] << 8) | buffer[7];
+ width = (buffer[8] << 8) | buffer[9];
+ height = (buffer[10] << 8) | buffer[11];
+ interlace_factor = (height & 0x8000) ? 2 : 1;
+ height &= 0x7fff;
+ height *= interlace_factor;
+ buffer += 12 + buffer[4];
+
+ /* make sure it makes sense */
+ if (width != info->video_width || height != info->video_height)
+ return AVIERR_INVALID_DATA;
+
+ /* allocate a temporary buffer */
+ temp = malloc(samples * 2);
+ if (temp == NULL)
+ return AVIERR_NO_MEMORY;
+
+ /* loop over audio channels */
+ for (chnum = 0; chnum < channels; chnum++)
+ {
+ /* extract samples */
+ for (sampnum = 0; sampnum < samples; sampnum++)
+ {
+ INT16 sample = *buffer++ << 8;
+ temp[sampnum] = sample | *buffer++;
+ }
+
+ /* write the samples */
+ avierr = avi_append_sound_samples(avi, chnum, temp, samples);
+ if (avierr != AVIERR_NONE)
+ goto cleanup;
+ }
+
+ /* loop over the data and copy it to the bitmap */
+ for (y = framenum % interlace_factor; y < height; y += interlace_factor)
+ {
+ UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels;
+
+ for (x = 0; x < width; x++)
+ {
+ UINT16 pixel = *buffer++;
+ *dest++ = pixel | *buffer++ << 8;
+ }
+ }
+
+ /* write the video data */
+ if (interlace_factor == 1 || framenum % 2 == 1)
+ {
+ avierr = avi_append_video_frame_yuy16(avi, bitmap);
+ if (avierr != AVIERR_NONE)
+ goto cleanup;
+ }
+
+cleanup:
+ free(temp);
+ return avierr;
+}
+
+
+/*-------------------------------------------------
+ do_extractav - extract an AVI file from a
+ CHD image
+-------------------------------------------------*/
+
+static int do_extractav(int argc, char *argv[], int param)
+{
+ int fps, fpsfrac, width, height, interlaced, channels, rate, metabytes, totalframes;
+ const char *inputfile, *metafile, *outputfile;
+ int firstframe, numframes;
+ bitmap_t videobitmap = { 0 };
+ UINT32 fps_times_1million;
+ const chd_header *header;
+ chd_file *chd = NULL;
+ avi_file *avi = NULL;
+ avi_movie_info info;
+ char metadata[256];
+ void *hunk = NULL;
+ FILE *meta = NULL;
+ avi_error avierr;
+ chd_error err;
+ int framenum;
+
+ /* require 5-7 args total */
+ if (argc < 5 || argc > 7)
+ return usage();
+
+ /* extract the data */
+ inputfile = argv[2];
+ outputfile = argv[3];
+ metafile = argv[4];
+ firstframe = (argc > 5) ? atoi(argv[5]) : 0;
+ numframes = (argc > 6) ? atoi(argv[6]) : 1000000;
+
+ /* print some info */
+ printf("Input file: %s\n", inputfile);
+ printf("Output file: %s\n", outputfile);
+ printf("Meta file: %s\n", (metafile == NULL) ? "(none)" : metafile);
+
+ /* get the header */
+ err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, error_string(err));
+ goto cleanup;
+ }
+ header = chd_get_header(chd);
+
+ /* get the metadata */
+ err = chd_get_metadata(chd, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error getting A/V metadata: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* extract the info */
+ if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate, &metabytes) != 8)
+ {
+ fprintf(stderr, "Improperly formatted metadata\n");
+ err = CHDERR_INVALID_METADATA;
+ goto cleanup;
+ }
+ fps_times_1million = fps * 1000000 + fpsfrac;
+ totalframes = header->totalhunks;
+
+ /* adjust for interlacing */
+ if (interlaced)
+ {
+ fps_times_1million /= 2;
+ height *= 2;
+ }
+ numframes = MIN(totalframes - firstframe, numframes);
+
+ /* allocate a video buffer */
+ videobitmap.base = malloc(width * height * 2);
+ if (videobitmap.base == NULL)
+ {
+ fprintf(stderr, "Out of memory allocating temporary bitmap\n");
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+ videobitmap.format = BITMAP_FORMAT_YUY16;
+ videobitmap.width = width;
+ videobitmap.height = height;
+ videobitmap.bpp = 16;
+ videobitmap.rowpixels = width;
+
+ /* print some of it */
+ printf("Use frames: %d-%d\n", firstframe, firstframe + numframes - 1);
+ printf("Frame rate: %d.%06d\n", fps_times_1million / 1000000, fps_times_1million % 1000000);
+ printf("Frame size: %d x %d %s\n", width, height, interlaced ? "interlaced" : "non-interlaced");
+ printf("Audio: %d channels at %d Hz\n", channels, rate);
+ printf("Metadata: %d bytes/frame\n", metabytes);
+ printf("Total frames: %d (%02d:%02d:%02d)\n", totalframes,
+ (UINT32)((UINT64)totalframes * 1000000 / fps_times_1million / 60 / 60),
+ (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million / 60) % 60),
+ (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million) % 60));
+
+ if (metabytes > 0 && metafile == NULL)
+ fprintf(stderr, "Warning: per-frame metadata included but not extracted\n");
+
+ /* allocate memory to hold a hunk */
+ hunk = malloc(header->hunkbytes);
+ if (hunk == NULL)
+ {
+ fprintf(stderr, "Out of memory allocating hunk buffer!\n");
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+
+ /* build up the movie info */
+ info.video_format = FORMAT_YUY2;
+ info.video_timescale = fps_times_1million;
+ info.video_sampletime = 1000000;
+ info.video_width = width;
+ info.video_height = height;
+ info.video_depth = 16;
+ info.audio_format = 0;
+ info.audio_timescale = rate;
+ info.audio_sampletime = 1;
+ info.audio_channels = channels;
+ info.audio_samplebits = 16;
+ info.audio_samplerate = rate;
+
+ /* create the output file */
+ avierr = avi_create(outputfile, &info, &avi);
+ if (avierr != AVIERR_NONE)
+ {
+ fprintf(stderr, "Error opening output file '%s': %s\n", outputfile, avi_error_string(avierr));
+ err = CHDERR_CANT_CREATE_FILE;
+ goto cleanup;
+ }
+
+ /* create the metadata file */
+ if (metafile != NULL && metabytes > 0)
+ {
+ meta = fopen(metafile, "w");
+ if (meta == NULL)
+ {
+ fprintf(stderr, "Error opening meta file '%s': %s\n", metafile, avi_error_string(avierr));
+ err = CHDERR_CANT_CREATE_FILE;
+ goto cleanup;
+ }
+ fprintf(meta, "chdmeta %d\n", metabytes);
+ }
+
+ /* loop over hunks, reading and writing */
+ for (framenum = 0; framenum < numframes; framenum++)
+ {
+ /* progress */
+ progress(framenum == 0, "Extracting hunk %d/%d... \r", framenum, numframes);
+
+ /* read the hunk into a buffer */
+ err = chd_read(chd, firstframe + framenum, hunk);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error reading hunk %d from CHD file: %s\n", firstframe + framenum, error_string(err));
+ goto cleanup;
+ }
+
+ /* write the metadata */
+ if (meta != NULL)
+ {
+ int metaoffs;
+ for (metaoffs = 0; metaoffs < metabytes; metaoffs++)
+ fprintf(meta, "%02X", ((UINT8 *)hunk)[12 + metaoffs]);
+ fprintf(meta, "\n");
+ }
+
+ /* write the hunk to the file */
+ avierr = write_avi_frame(avi, framenum, hunk, &videobitmap);
+ if (avierr != AVIERR_NONE)
+ {
+ fprintf(stderr, "Error writing AVI frame: %s\n", avi_error_string(avierr));
+ err = CHDERR_DECOMPRESSION_ERROR;
+ goto cleanup;
+ }
+ }
+ progress(TRUE, "Extraction complete! \n");
+
+cleanup:
+ /* clean up our mess */
+ if (avi != NULL)
+ avi_close(avi);
+ if (hunk != NULL)
+ free(hunk);
+ if (videobitmap.base != NULL)
+ free(videobitmap.base);
+ if (chd != NULL)
+ chd_close(chd);
+ if (meta != NULL)
+ fclose(meta);
+ if (err != CHDERR_NONE)
+ {
+ osd_rmfile(outputfile);
+ osd_rmfile(metafile);
+ }
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_verify - validate the MD5/SHA1 on a drive
+ image
+-------------------------------------------------*/
+
+static int do_verify(int argc, char *argv[], int param)
+{
+ UINT8 actualmd5[CHD_MD5_BYTES], actualsha1[CHD_SHA1_BYTES];
+ const char *inputfile;
+ chd_file *chd = NULL;
+ chd_header header;
+ int fixed = FALSE;
+ chd_error err;
+ int i;
+
+ /* require 3 args total */
+ if (argc != 3)
+ return usage();
+
+ /* extract the data */
+ inputfile = argv[2];
+
+ /* print some info */
+ printf("Input file: %s\n", inputfile);
+
+ /* open the CHD file */
+ err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+ header = *chd_get_header(chd);
+
+ /* verify the CHD data */
+ err = chd_verify_begin(chd);
+ if (err == CHDERR_NONE)
+ {
+ UINT32 hunknum;
+ for (hunknum = 0; hunknum < header.totalhunks; hunknum++)
+ {
+ /* progress */
+ progress(FALSE, "Verifying hunk %d/%d... \r", hunknum, header.totalhunks);
+
+ /* verify the data */
+ err = chd_verify_hunk(chd);
+ if (err != CHDERR_NONE)
+ break;
+ }
+
+ /* finish it */
+ if (err == CHDERR_NONE)
+ err = chd_verify_finish(chd, actualmd5, actualsha1);
+ }
+
+ /* handle errors */
+ if (err != CHDERR_NONE)
+ {
+ if (err == CHDERR_CANT_VERIFY)
+ fprintf(stderr, "Can't verify this type of image (probably writeable)\n");
+ else
+ fprintf(stderr, "\nError during verify: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* verify the MD5 */
+ if (memcmp(header.md5, actualmd5, sizeof(header.md5)) == 0)
+ printf("MD5 verification successful!\n");
+ else
+ {
+ fprintf(stderr, "Error: MD5 in header = ");
+ for (i = 0; i < CHD_MD5_BYTES; i++)
+ fprintf(stderr, "%02x", header.md5[i]);
+ fprintf(stderr, "\n");
+ fprintf(stderr, " actual MD5 = ");
+ for (i = 0; i < CHD_MD5_BYTES; i++)
+ fprintf(stderr, "%02x", actualmd5[i]);
+ fprintf(stderr, "\n");
+
+ /* fix it */
+ if (param)
+ {
+ memcpy(header.md5, actualmd5, sizeof(header.md5));
+ fixed = TRUE;
+ }
+ }
+
+ /* verify the SHA1 */
+ if (header.version >= 3)
+ {
+ if (memcmp(header.sha1, actualsha1, sizeof(header.sha1)) == 0)
+ printf("SHA1 verification successful!\n");
+ else
+ {
+ fprintf(stderr, "Error: SHA1 in header = ");
+ for (i = 0; i < CHD_SHA1_BYTES; i++)
+ fprintf(stderr, "%02x", header.sha1[i]);
+ fprintf(stderr, "\n");
+ fprintf(stderr, " actual SHA1 = ");
+ for (i = 0; i < CHD_SHA1_BYTES; i++)
+ fprintf(stderr, "%02x", actualsha1[i]);
+ fprintf(stderr, "\n");
+
+ /* fix it */
+ if (param)
+ {
+ memcpy(header.sha1, actualsha1, sizeof(header.sha1));
+ fixed = TRUE;
+ }
+ }
+ }
+
+ /* close the drive */
+ chd_close(chd);
+ chd = NULL;
+
+ /* update the header */
+ if (fixed)
+ {
+ err = chd_set_header(inputfile, &header);
+ if (err != CHDERR_NONE)
+ fprintf(stderr, "Error writing new header: %s\n", error_string(err));
+ else
+ printf("Updated header successfully\n");
+ }
+
+cleanup:
+ /* close everything down */
+ if (chd != NULL)
+ chd_close(chd);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_info - dump the header information from
+ a drive image
+-------------------------------------------------*/
+
+static int do_info(int argc, char *argv[], int param)
+{
+ const char *inputfile;
+ chd_file *chd = NULL;
+ UINT8 metadata[256];
+ chd_header header;
+ chd_error err;
+ int i, j;
+
+ /* require 3 args total */
+ if (argc != 3)
+ return usage();
+
+ /* extract the data */
+ inputfile = argv[2];
+
+ /* print some info */
+ printf("Input file: %s\n", inputfile);
+
+ /* get the header */
+ err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, error_string(err));
+ goto cleanup;
+ }
+ header = *chd_get_header(chd);
+
+ /* print the info */
+ printf("Header Size: %d bytes\n", header.length);
+ printf("File Version: %d\n", header.version);
+ printf("Flags: %s, %s\n",
+ (header.flags & CHDFLAGS_HAS_PARENT) ? "HAS_PARENT" : "NO_PARENT",
+ (header.flags & CHDFLAGS_IS_WRITEABLE) ? "WRITEABLE" : "READ_ONLY");
+ printf("Compression: %s\n", chd_get_codec_name(header.compression));
+ printf("Hunk Size: %d bytes\n", header.hunkbytes);
+ printf("Total Hunks: %d\n", header.totalhunks);
+ printf("Logical size: %s bytes\n", big_int_string(header.logicalbytes));
+ if (!(header.flags & CHDFLAGS_IS_WRITEABLE))
+ printf("MD5: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+ header.md5[0], header.md5[1], header.md5[2], header.md5[3],
+ header.md5[4], header.md5[5], header.md5[6], header.md5[7],
+ header.md5[8], header.md5[9], header.md5[10], header.md5[11],
+ header.md5[12], header.md5[13], header.md5[14], header.md5[15]);
+ if (!(header.flags & CHDFLAGS_IS_WRITEABLE) && header.version >= 3)
+ printf("SHA1: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+ header.sha1[0], header.sha1[1], header.sha1[2], header.sha1[3],
+ header.sha1[4], header.sha1[5], header.sha1[6], header.sha1[7],
+ header.sha1[8], header.sha1[9], header.sha1[10], header.sha1[11],
+ header.sha1[12], header.sha1[13], header.sha1[14], header.sha1[15],
+ header.sha1[16], header.sha1[17], header.sha1[18], header.sha1[19]);
+ if (header.flags & CHDFLAGS_HAS_PARENT)
+ {
+ printf("Parent MD5: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+ header.parentmd5[0], header.parentmd5[1], header.parentmd5[2], header.parentmd5[3],
+ header.parentmd5[4], header.parentmd5[5], header.parentmd5[6], header.parentmd5[7],
+ header.parentmd5[8], header.parentmd5[9], header.parentmd5[10], header.parentmd5[11],
+ header.parentmd5[12], header.parentmd5[13], header.parentmd5[14], header.parentmd5[15]);
+ if (header.version >= 3)
+ printf("Parent SHA1: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n",
+ header.parentsha1[0], header.parentsha1[1], header.parentsha1[2], header.parentsha1[3],
+ header.parentsha1[4], header.parentsha1[5], header.parentsha1[6], header.parentsha1[7],
+ header.parentsha1[8], header.parentsha1[9], header.parentsha1[10], header.parentsha1[11],
+ header.parentsha1[12], header.parentsha1[13], header.parentsha1[14], header.parentsha1[15],
+ header.parentsha1[16], header.parentsha1[17], header.parentsha1[18], header.parentsha1[19]);
+ }
+
+ /* print out metadata */
+ for (i = 0; ; i++)
+ {
+ UINT32 metatag, metasize;
+
+ /* get the indexed metadata item; stop when we hit an error */
+ err = chd_get_metadata(chd, CHDMETATAG_WILDCARD, i, metadata, sizeof(metadata), &metasize, &metatag);
+ if (err != CHDERR_NONE)
+ break;
+
+ /* print either a string representation or a hex representation of the tag */
+ if (isprint((metatag >> 24) & 0xff) && isprint((metatag >> 16) & 0xff) && isprint((metatag >> 8) & 0xff) && isprint(metatag & 0xff))
+ printf("Metadata: Tag='%c%c%c%c' Length=%d\n", (metatag >> 24) & 0xff, (metatag >> 16) & 0xff, (metatag >> 8) & 0xff, metatag & 0xff, metasize);
+ else
+ printf("Metadata: Tag=%08x Length=%d\n", metatag, metasize);
+ printf(" ");
+
+ /* print up to 60 characters of metadata */
+ metasize = MIN(60, metasize);
+ for (j = 0; j < metasize; j++)
+ printf("%c", isprint(metadata[j]) ? metadata[j] : '.');
+ printf("\n");
+ }
+
+cleanup:
+ /* close everything down */
+ if (chd != NULL)
+ chd_close(chd);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ handle_custom_chomp - custom chomp a file
+-------------------------------------------------*/
+
+#if ENABLE_CUSTOM_CHOMP
+static chd_error handle_custom_chomp(const char *name, chd_file *chd, UINT32 *maxhunk)
+{
+ const chd_header *header = chd_get_header(chd);
+ int sectors_per_hunk = (header->hunkbytes / IDE_SECTOR_SIZE);
+ chd_error err = CHDERR_INVALID_DATA;
+ UINT8 *temp = NULL;
+
+ /* allocate memory to hold a hunk */
+ temp = malloc(header->hunkbytes);
+ if (temp == NULL)
+ {
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+
+ /* check for midway */
+ if (strcmp(name, "midway") == 0)
+ {
+ UINT32 maxsector = 0;
+ UINT32 numparts;
+ chd_error err;
+ int i;
+
+ /* read sector 0 */
+ err = chd_read(chd, 0, temp);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error reading hunk 0\n");
+ goto cleanup;
+ }
+
+ /* look for the signature */
+ if (temp[0] != 0x54 || temp[1] != 0x52 || temp[2] != 0x41 || temp[3] != 0x50)
+ goto cleanup;
+
+ /* determine the number of partitions */
+ numparts = temp[4] | (temp[5] << 8) | (temp[6] << 16) | (temp[7] << 24);
+ printf("%d partitions\n", numparts);
+
+ /* get the partition information for each one and track the maximum referenced sector */
+ for (i = 0; i < numparts; i++)
+ {
+ UINT32 pstart = temp[i*12 + 8] | (temp[i*12 + 9] << 8) | (temp[i*12 + 10] << 16) | (temp[i*12 + 11] << 24);
+ UINT32 psize = temp[i*12 + 12] | (temp[i*12 + 13] << 8) | (temp[i*12 + 14] << 16) | (temp[i*12 + 15] << 24);
+ UINT32 pflags = temp[i*12 + 16] | (temp[i*12 + 17] << 8) | (temp[i*12 + 18] << 16) | (temp[i*12 + 19] << 24);
+ printf(" %2d. %7d - %7d (%X)\n", i, pstart, pstart + psize - 1, pflags);
+ if (i != 0 && pstart + psize > maxsector)
+ maxsector = pstart + psize;
+ }
+
+ /* the maximum hunk is the one that contains the last sector */
+ *maxhunk = (maxsector + sectors_per_hunk - 1) / sectors_per_hunk;
+ printf("Maximum hunk: %d\n", *maxhunk);
+
+ /* warn if there will be no effect */
+ if (*maxhunk >= header->totalhunks)
+ {
+ printf("Warning: chomp will have no effect\n");
+ *maxhunk = header->totalhunks;
+ }
+ }
+
+ /* check for atari */
+ if (strcmp(name, "atari") == 0)
+ {
+ UINT32 sectors[4];
+ UINT8 *data;
+ int i, maxdiff;
+ chd_error err;
+
+ /* read the second sector */
+ err = chd_read(chd, 0x200 / header->hunkbytes, temp);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error reading sector 1\n");
+ goto cleanup;
+ }
+ data = &temp[0x200 % header->hunkbytes];
+
+ /* look for the signature */
+ if (data[0] != 0x0d || data[1] != 0xf0 || data[2] != 0xed || data[3] != 0xfe)
+ goto cleanup;
+
+ /* loop over 4 partitions and compute the number of sectors in each */
+ for (i = 0; i < 4; i++)
+ sectors[i] = data[i*4+0x40] | (data[i*4+0x41] << 8) | (data[i*4+0x42] << 16) | (data[i*4+0x43] << 24);
+ maxdiff = sectors[2] - sectors[1];
+ if (sectors[3] - sectors[2] > maxdiff)
+ maxdiff = sectors[3] - sectors[2];
+ if (sectors[0] != 8)
+ goto cleanup;
+
+ /* the maximum hunk is the end of the fourth copy of the data */
+ *maxhunk = (sectors[3] + maxdiff + sectors_per_hunk - 1) / sectors_per_hunk;
+ printf("Maximum hunk: %d\n", *maxhunk);
+
+ /* warn if there will be no effect */
+ if (*maxhunk >= header->totalhunks)
+ {
+ fprintf(stderr, "Warning: chomp will have no effect\n");
+ *maxhunk = header->totalhunks;
+ }
+ }
+
+ /* if we fall through, there was no error */
+ err = CHDERR_NONE;
+
+cleanup:
+ if (temp != NULL)
+ free(temp);
+ if (err == CHDERR_INVALID_DATA)
+ fprintf(stderr, "Error: unable to identify file or compute chomping size.\n");
+ return err;
+}
+#endif
+
+
+/*-------------------------------------------------
+ do_merge_update_chomp - merge a parent and its
+ child together (also works for update & chomp)
+-------------------------------------------------*/
+
+static int do_merge_update_chomp(int argc, char *argv[], int param)
+{
+ const char *parentfile, *inputfile, *outputfile;
+ const chd_header *inputheader;
+ chd_file *parentchd = NULL;
+ chd_file *outputchd = NULL;
+ chd_file *inputchd = NULL;
+ UINT32 maxhunk = ~0;
+ chd_error err;
+
+ /* require 4-5 args total */
+ if (param == OPERATION_UPDATE && argc != 4)
+ return usage();
+ if ((param == OPERATION_MERGE || param == OPERATION_CHOMP) && argc != 5)
+ return usage();
+
+ /* extract the data */
+ if (param == OPERATION_MERGE)
+ {
+ parentfile = argv[2];
+ inputfile = argv[3];
+ outputfile = argv[4];
+ }
+ else
+ {
+ parentfile = NULL;
+ inputfile = argv[2];
+ outputfile = argv[3];
+ if (param == OPERATION_CHOMP)
+ maxhunk = atoi(argv[4]);
+ }
+
+ /* print some info */
+ if (parentfile != NULL)
+ {
+ printf("Parent file: %s\n", parentfile);
+ printf("Diff file: %s\n", inputfile);
+ }
+ else
+ printf("Input file: %s\n", inputfile);
+ printf("Output file: %s\n", outputfile);
+ if (param == OPERATION_CHOMP)
+ printf("Maximum hunk: %d\n", maxhunk);
+
+ /* open the parent CHD */
+ if (parentfile != NULL)
+ {
+ err = chd_open(parentfile, CHD_OPEN_READ, NULL, &parentchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s': %s\n", parentfile, error_string(err));
+ goto cleanup;
+ }
+ }
+
+ /* open the diff CHD */
+ err = chd_open(inputfile, CHD_OPEN_READ, parentchd, &inputchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, error_string(err));
+ goto cleanup;
+ }
+ inputheader = chd_get_header(inputchd);
+
+#if ENABLE_CUSTOM_CHOMP
+ /* if we're chomping with a auto parameter, now is the time to figure it out */
+ if (param == OPERATION_CHOMP && maxhunk == 0)
+ if (handle_custom_chomp(argv[4], inputchd, &maxhunk) != CHDERR_NONE)
+ return 1;
+#endif
+
+ /* create the new merged CHD */
+ err = chd_create(outputfile, inputheader->logicalbytes, inputheader->hunkbytes, inputheader->compression, NULL);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error creating CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* open the new CHD */
+ err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &outputchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening new CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* clone the metadata from the input file (which should have inherited from the parent) */
+ err = chd_clone_metadata(inputchd, outputchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error cloning metadata: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* do the compression; our interface will route reads for us */
+ err = chdman_compress_chd(outputchd, inputchd, (param == OPERATION_CHOMP) ? (maxhunk + 1) : 0);
+ if (err != CHDERR_NONE)
+ fprintf(stderr, "Error during compression: %s\n", error_string(err));
+
+cleanup:
+ /* close everything down */
+ if (outputchd != NULL)
+ chd_close(outputchd);
+ if (inputchd != NULL)
+ chd_close(inputchd);
+ if (parentchd != NULL)
+ chd_close(parentchd);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_diff - generate a difference between two
+ CHD files
+-------------------------------------------------*/
+
+static int do_diff(int argc, char *argv[], int param)
+{
+ const char *parentfile = NULL, *inputfile = NULL, *outputfile = NULL;
+ chd_file *parentchd = NULL;
+ chd_file *outputchd = NULL;
+ chd_file *inputchd = NULL;
+ chd_error err;
+
+ /* require 5 args total */
+ if (argc != 5)
+ return usage();
+
+ /* extract the data */
+ parentfile = argv[2];
+ inputfile = argv[3];
+ outputfile = argv[4];
+
+ /* print some info */
+ printf("Parent file: %s\n", parentfile);
+ printf("Input file: %s\n", inputfile);
+ printf("Diff file: %s\n", outputfile);
+
+ /* open the soon-to-be-parent CHD */
+ err = chd_open(parentfile, CHD_OPEN_READ, NULL, &parentchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s': %s\n", parentfile, error_string(err));
+ goto cleanup;
+ }
+
+ /* open the input CHD */
+ err = chd_open(inputfile, CHD_OPEN_READ, NULL, &inputchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, error_string(err));
+ goto cleanup;
+ }
+
+ /* create the new CHD as a diff against the parent */
+ err = chd_create(outputfile, 0, 0, chd_get_header(parentchd)->compression, parentchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error creating CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* open the new CHD */
+ err = chd_open(outputfile, CHD_OPEN_READWRITE, parentchd, &outputchd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening new CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* do the compression; our interface will route reads for us */
+ err = chdman_compress_chd(outputchd, inputchd, 0);
+ if (err != CHDERR_NONE)
+ fprintf(stderr, "Error during compression: %s\n", error_string(err));
+
+cleanup:
+ /* close everything down */
+ if (outputchd != NULL)
+ chd_close(outputchd);
+ if (inputchd != NULL)
+ chd_close(inputchd);
+ if (parentchd != NULL)
+ chd_close(parentchd);
+ if (err != CHDERR_NONE)
+ osd_rmfile(outputfile);
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ do_setchs - change the CHS values on a hard
+ disk image
+-------------------------------------------------*/
+
+static int do_setchs(int argc, char *argv[], int param)
+{
+ int oldcyls, oldhds, oldsecs, oldsecsize;
+ UINT8 was_readonly = FALSE;
+ UINT64 old_logicalbytes;
+ const char *inoutfile;
+ chd_file *chd = NULL;
+ int cyls, hds, secs;
+ char metadata[256];
+ chd_header header;
+ chd_error err;
+
+ /* require 6 args total */
+ if (argc != 6)
+ return usage();
+
+ /* extract the data */
+ inoutfile = argv[2];
+ cyls = atoi(argv[3]);
+ hds = atoi(argv[4]);
+ secs = atoi(argv[5]);
+
+ /* print some info */
+ printf("Input file: %s\n", inoutfile);
+ printf("Cylinders: %d\n", cyls);
+ printf("Heads: %d\n", hds);
+ printf("Sectors: %d\n", secs);
+
+ /* open the file read-only and get the header */
+ err = chd_open(inoutfile, CHD_OPEN_READ, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s' read-only: %s\n", inoutfile, error_string(err));
+ goto cleanup;
+ }
+ header = *chd_get_header(chd);
+ chd_close(chd);
+ chd = NULL;
+
+ /* if the drive is not writeable, note that, and make it so */
+ if (!(header.flags & CHDFLAGS_IS_WRITEABLE))
+ {
+ was_readonly = TRUE;
+ header.flags |= CHDFLAGS_IS_WRITEABLE;
+
+ /* write the new header */
+ err = chd_set_header(inoutfile, &header);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error making CHD file writeable: %s\n", error_string(err));
+ goto cleanup;
+ }
+ }
+
+ /* open the file read/write */
+ err = chd_open(inoutfile, CHD_OPEN_READWRITE, NULL, &chd);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error opening CHD file '%s' read/write: %s\n", inoutfile, error_string(err));
+ goto cleanup;
+ }
+
+ /* get the hard disk metadata */
+ err = chd_get_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL);
+ if (err != CHDERR_NONE || sscanf(metadata, HARD_DISK_METADATA_FORMAT, &oldcyls, &oldhds, &oldsecs, &oldsecsize) != 4)
+ {
+ fprintf(stderr, "CHD file '%s' is not a hard disk!\n", inoutfile);
+ err = CHDERR_INVALID_FILE;
+ goto cleanup;
+ }
+
+ /* write our own */
+ sprintf(metadata, HARD_DISK_METADATA_FORMAT, cyls, hds, secs, oldsecsize);
+ err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1);
+ if (err != CHDERR_NONE)
+ {
+ fprintf(stderr, "Error writing new metadata to CHD file: %s\n", error_string(err));
+ goto cleanup;
+ }
+
+ /* get the header and compute the new logical size */
+ header = *chd_get_header(chd);
+ old_logicalbytes = header.logicalbytes;
+ header.logicalbytes = (UINT64)cyls * (UINT64)hds * (UINT64)secs * (UINT64)oldsecsize;
+
+ /* close the file */
+ chd_close(chd);
+ chd = NULL;
+
+ /* restore the read-only state */
+ if (was_readonly)
+ header.flags &= ~CHDFLAGS_IS_WRITEABLE;
+
+ /* set the new logical size */
+ if (header.logicalbytes != old_logicalbytes || was_readonly)
+ {
+ err = chd_set_header(inoutfile, &header);
+ if (err != CHDERR_NONE)
+ fprintf(stderr, "Error writing new header to CHD file: %s\n", error_string(err));
+ }
+
+ /* print a warning if the size is different */
+ if (header.logicalbytes < old_logicalbytes)
+ fprintf(stderr, "WARNING: new size is smaller; run chdman -update to reclaim empty space\n");
+ else if (header.logicalbytes > old_logicalbytes)
+ fprintf(stderr, "WARNING: new size is larger; run chdman -update to account for new empty space\n");
+
+cleanup:
+ if (chd != NULL)
+ chd_close(chd);
+ if (err != CHDERR_NONE && was_readonly)
+ {
+ header.flags &= ~CHDFLAGS_IS_WRITEABLE;
+ chd_set_header(inoutfile, &header);
+ }
+ return (err != CHDERR_NONE);
+}
+
+
+/*-------------------------------------------------
+ chdman_compress_file - compress a regular
+ file via the compression interfaces
+-------------------------------------------------*/
+
+static chd_error chdman_compress_file(chd_file *chd, const char *rawfile, UINT32 offset)
+{
+ core_file *sourcefile;
+ const chd_header *header;
+ UINT64 sourceoffset = 0;
+ UINT8 *cache = NULL;
+ double ratio = 1.0;
+ file_error filerr;
+ chd_error err;
+ int hunknum;
+
+ /* open the raw file */
+ filerr = core_fopen(rawfile, OPEN_FLAG_READ, &sourcefile);
+ if (filerr != FILERR_NONE)
+ {
+ err = CHDERR_FILE_NOT_FOUND;
+ goto cleanup;
+ }
+
+ /* get the header */
+ header = chd_get_header(chd);
+ cache = malloc(header->hunkbytes);
+ if (cache == NULL)
+ {
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+
+ /* begin compressing */
+ err = chd_compress_begin(chd);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+
+ /* loop over source hunks until we run out */
+ for (hunknum = 0; hunknum < header->totalhunks; hunknum++)
+ {
+ UINT32 bytesread;
+
+ /* progress */
+ progress(hunknum == 0, "Compressing hunk %d/%d... (ratio=%d%%) \r", hunknum, header->totalhunks, (int)(100.0 * ratio));
+
+ /* read the data */
+ core_fseek(sourcefile, sourceoffset + offset, SEEK_SET);
+ bytesread = core_fread(sourcefile, cache, header->hunkbytes);
+ if (bytesread < header->hunkbytes)
+ memset(&cache[bytesread], 0, header->hunkbytes - bytesread);
+
+ /* append the data */
+ err = chd_compress_hunk(chd, cache, &ratio);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+
+ /* prepare for the next hunk */
+ sourceoffset += header->hunkbytes;
+ }
+
+ /* finish compression */
+ err = chd_compress_finish(chd);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+
+ /* final progress update */
+ progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio));
+
+cleanup:
+ if (sourcefile != NULL)
+ core_fclose(sourcefile);
+ if (cache != NULL)
+ free(cache);
+ return err;
+}
+
+
+/*-------------------------------------------------
+ chdman_compress_chd - (re)compress a CHD file
+ via the compression interfaces
+-------------------------------------------------*/
+
+static chd_error chdman_compress_chd(chd_file *chd, chd_file *source, UINT32 totalhunks)
+{
+ const chd_header *source_header;
+ const chd_header *header;
+ UINT8 *source_cache = NULL;
+ UINT64 source_offset = 0;
+ UINT32 source_bytes = 0;
+ UINT8 *cache = NULL;
+ double ratio = 1.0;
+ chd_error err, verifyerr;
+ int hunknum;
+
+ /* get the header */
+ header = chd_get_header(chd);
+ cache = malloc(header->hunkbytes);
+ if (cache == NULL)
+ {
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+
+ /* get the source CHD header */
+ source_header = chd_get_header(source);
+ source_cache = malloc(source_header->hunkbytes);
+ if (source_cache == NULL)
+ {
+ err = CHDERR_OUT_OF_MEMORY;
+ goto cleanup;
+ }
+
+ /* begin compressing */
+ err = chd_compress_begin(chd);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+
+ /* also begin verifying the source driver */
+ verifyerr = chd_verify_begin(source);
+
+ /* a zero count means the natural number */
+ if (totalhunks == 0)
+ totalhunks = source_header->totalhunks;
+
+ /* loop over source hunks until we run out */
+ for (hunknum = 0; hunknum < totalhunks; hunknum++)
+ {
+ UINT32 bytesremaining = header->hunkbytes;
+ UINT8 *dest = cache;
+
+ /* progress */
+ progress(hunknum == 0, "Compressing hunk %d/%d... (ratio=%d%%) \r", hunknum, totalhunks, (int)(100.0 * ratio));
+
+ /* read the data */
+ while (bytesremaining > 0)
+ {
+ /* if we have data in the buffer, copy it */
+ if (source_bytes > 0)
+ {
+ UINT32 bytestocopy = MIN(bytesremaining, source_bytes);
+ memcpy(dest, &source_cache[source_header->hunkbytes - source_bytes], bytestocopy);
+ dest += bytestocopy;
+ source_bytes -= bytestocopy;
+ bytesremaining -= bytestocopy;
+ }
+
+ /* otherwise, read in another hunk of the source */
+ else
+ {
+ /* verify the next hunk */
+ if (verifyerr == CHDERR_NONE)
+ err = chd_verify_hunk(source);
+
+ /* then read it (should be the same) */
+ err = chd_read(source, source_offset / source_header->hunkbytes, source_cache);
+ if (err != CHDERR_NONE)
+ memset(source_cache, 0, source_header->hunkbytes);
+ source_bytes = source_header->hunkbytes;
+ source_offset += source_bytes;
+ }
+ }
+
+ /* append the data */
+ err = chd_compress_hunk(chd, cache, &ratio);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+ }
+
+ /* if we read all the source data, verify the checksums */
+ if (verifyerr == CHDERR_NONE && source_offset >= source_header->logicalbytes)
+ {
+ static const UINT8 empty_checksum[CHD_SHA1_BYTES] = { 0 };
+ UINT8 md5[CHD_MD5_BYTES];
+ UINT8 sha1[CHD_SHA1_BYTES];
+ int i;
+
+ /* get the final values */
+ err = chd_verify_finish(source, md5, sha1);
+
+ /* check the MD5 */
+ if (memcmp(source_header->md5, empty_checksum, CHD_MD5_BYTES) != 0)
+ {
+ if (memcmp(source_header->md5, md5, CHD_MD5_BYTES) != 0)
+ {
+ progress(TRUE, "WARNING: expected input MD5 = ");
+ for (i = 0; i < CHD_MD5_BYTES; i++)
+ progress(TRUE, "%02x", source_header->md5[i]);
+ progress(TRUE, "\n");
+
+ progress(TRUE, " actual MD5 = ");
+ for (i = 0; i < CHD_MD5_BYTES; i++)
+ progress(TRUE, "%02x", md5[i]);
+ progress(TRUE, "\n");
+ }
+ else
+ progress(TRUE, "Input MD5 verified \n");
+ }
+
+ /* check the SHA1 */
+ if (memcmp(source_header->sha1, empty_checksum, CHD_SHA1_BYTES) != 0)
+ {
+ if (memcmp(source_header->sha1, sha1, CHD_SHA1_BYTES) != 0)
+ {
+ progress(TRUE, "WARNING: expected input SHA1 = ");
+ for (i = 0; i < CHD_SHA1_BYTES; i++)
+ progress(TRUE, "%02x", source_header->sha1[i]);
+ progress(TRUE, "\n");
+
+ progress(TRUE, " actual SHA1 = ");
+ for (i = 0; i < CHD_SHA1_BYTES; i++)
+ progress(TRUE, "%02x", sha1[i]);
+ progress(TRUE, "\n");
+ }
+ else
+ progress(TRUE, "Input SHA1 verified \n");
+ }
+ }
+
+ /* finish compression */
+ err = chd_compress_finish(chd);
+ if (err != CHDERR_NONE)
+ goto cleanup;
+
+ /* final progress update */
+ progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio));
+
+cleanup:
+ if (source_cache != NULL)
+ free(source_cache);
+ if (cache != NULL)
+ free(cache);
+ return err;
+}
+
+
+/*-------------------------------------------------
+ main - entry point
+-------------------------------------------------*/
+
+int CLIB_DECL main(int argc, char **argv)
+{
+ static const struct
+ {
+ const char * option;
+ int (*callback)(int argc, char *argv[], int param);
+ int param;
+ } option_list[] =
+ {
+ { "-createhd", do_createhd, 0 },
+ { "-createraw", do_createraw, 0 },
+ { "-createcd", do_createcd, 0 },
+ { "-createblankhd", do_createblankhd, 0 },
+ { "-createav", do_createav, 0 },
+ { "-copydata", do_copydata, 0 },
+ { "-extract", do_extract, 0 },
+ { "-extractcd", do_extractcd, 0 },
+ { "-extractav", do_extractav, 0 },
+ { "-verify", do_verify, 0 },
+ { "-verifyfix", do_verify, 1 },
+ { "-update", do_merge_update_chomp, OPERATION_UPDATE },
+ { "-chomp", do_merge_update_chomp, OPERATION_CHOMP },
+ { "-info", do_info, 0 },
+ { "-merge", do_merge_update_chomp, OPERATION_MERGE },
+ { "-diff", do_diff, 0 },
+ { "-setchs", do_setchs, 0 }
+ };
+ extern char build_version[];
+ int i;
+
+ /* print the header */
+ printf("chdman - MAME Compressed Hunks of Data (CHD) manager %s\n", build_version);
+
+ /* require at least 1 argument */
+ if (argc < 2)
+ return usage();
+
+ /* handle the appropriate command */
+ for (i = 0; i < ARRAY_LENGTH(option_list); i++)
+ if (strcmp(argv[1], option_list[i].option) == 0)
+ return (*option_list[i].callback)(argc, argv, option_list[i].param);
+
+ return usage();
+}
diff --git a/src/tools/jedutil.c b/src/tools/jedutil.c
new file mode 100644
index 00000000000..3c3f057a880
--- /dev/null
+++ b/src/tools/jedutil.c
@@ -0,0 +1,289 @@
+/***************************************************************************
+
+ jedutil.c
+
+ JEDEC file utilities.
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+****************************************************************************
+
+ Binary file format:
+
+ Offset
+ 0 = Total number of fuses (32 bits)
+ 4 = Raw fuse data, packed 8 bits at a time, LSB to MSB
+
+****************************************************************************
+
+ Known types:
+
+ 20-pin devices:
+ PAL10H8 = QP20 QF0320
+ PAL12H6 = QP20 QF0320
+ PAL14H4 = QP20
+ PAL16H2 = QP20
+ PAL16C1 = QP20
+ PAL10L8 = QP20 QF0320
+ PAL12L6 = QP20
+ PAL14L4 = QP20
+ PAL16L2 = QP20
+
+ 15S8 = QP20 QF0448
+
+ PLS153 = QP20 QF1842
+
+ PAL16L8 = QP20 QF2048
+
+ PAL16RA8 = QP20 QF2056
+
+ PAL16V8R = QP20 QF2194
+ PALCE16V8 = QP20 QF2194
+ GAL16V8A = QP20 QF2194
+
+ 18CV8 = QP20 QF2696
+
+ 24-pin devices:
+ GAL20V8A = QP24 QF2706
+ GAL22V10 = QP24 QF5892
+
+ 28-pin devices:
+ PLS100 = QP28 QF1928
+
+***************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "jedparse.h"
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+static UINT8 *srcbuf;
+static size_t srcbuflen;
+
+static UINT8 *dstbuf;
+static size_t dstbuflen;
+
+
+
+/***************************************************************************
+ CORE IMPLEMENTATION
+***************************************************************************/
+
+/*-------------------------------------------------
+ read_source_file - read a raw source file
+ into an allocated memory buffer
+-------------------------------------------------*/
+
+static int read_source_file(const char *srcfile)
+{
+ size_t bytes;
+ FILE *file;
+
+ /* open the source file */
+ file = fopen(srcfile, "rb");
+ if (!file)
+ {
+ fprintf(stderr, "Unable to open source file '%s'!\n", srcfile);
+ return 1;
+ }
+
+ /* allocate memory for the data */
+ fseek(file, 0, SEEK_END);
+ srcbuflen = ftell(file);
+ fseek(file, 0, SEEK_SET);
+ srcbuf = malloc(srcbuflen);
+ if (!srcbuf)
+ {
+ fprintf(stderr, "Unable to allocate %d bytes for the source!\n", (int)srcbuflen);
+ fclose(file);
+ return 1;
+ }
+
+ /* read the data */
+ bytes = fread(srcbuf, 1, srcbuflen, file);
+ if (bytes != srcbuflen)
+ {
+ fprintf(stderr, "Error reading %d bytes from the source!\n", (int)srcbuflen);
+ free(srcbuf);
+ fclose(file);
+ return 1;
+ }
+
+ /* close up shop */
+ fclose(file);
+ return 0;
+}
+
+
+
+/*-------------------------------------------------
+ write_dest_file - write a memory buffer raw
+ into a desintation file
+-------------------------------------------------*/
+
+static int write_dest_file(const char *dstfile)
+{
+ size_t bytes;
+ FILE *file;
+
+ /* open the source file */
+ file = fopen(dstfile, "wb");
+ if (!file)
+ {
+ fprintf(stderr, "Unable to open target file '%s'!\n", dstfile);
+ return 1;
+ }
+
+ /* write the data */
+ bytes = fwrite(dstbuf, 1, dstbuflen, file);
+ if (bytes != dstbuflen)
+ {
+ fprintf(stderr, "Error writing %d bytes to the target!\n", (int)dstbuflen);
+ fclose(file);
+ return 1;
+ }
+
+ /* close up shop */
+ fclose(file);
+ return 0;
+}
+
+
+
+/*-------------------------------------------------
+ main - primary entry point
+-------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+ const char *srcfile, *dstfile;
+ int src_is_jed, dst_is_jed;
+ int numfuses = 0;
+ jed_data jed;
+ int len;
+ int err;
+
+ /* needs at least two arguments */
+ if (argc < 3)
+ {
+ fprintf(stderr,
+ "Usage:\n"
+ " jedutil <source.jed> <target.bin> [fuses] -- convert JED to binary form\n"
+ " jedutil <source.bin> <target.jed> -- convert binary to JED form\n"
+ );
+ return 0;
+ }
+
+ /* extract arguments */
+ srcfile = argv[1];
+ dstfile = argv[2];
+ if (argc >= 4)
+ numfuses = atoi(argv[3]);
+
+ /* does the source end in '.jed'? */
+ len = strlen(srcfile);
+ src_is_jed = (srcfile[len - 4] == '.' &&
+ tolower(srcfile[len - 3]) == 'j' &&
+ tolower(srcfile[len - 2]) == 'e' &&
+ tolower(srcfile[len - 1]) == 'd');
+
+ /* does the destination end in '.jed'? */
+ len = strlen(dstfile);
+ dst_is_jed = (dstfile[len - 4] == '.' &&
+ tolower(dstfile[len - 3]) == 'j' &&
+ tolower(dstfile[len - 2]) == 'e' &&
+ tolower(dstfile[len - 1]) == 'd');
+
+ /* error if neither or both are .jed */
+ if (!src_is_jed && !dst_is_jed)
+ {
+ fprintf(stderr, "At least one of the filenames must end in .jed!\n");
+ return 1;
+ }
+ if (src_is_jed && dst_is_jed)
+ {
+ fprintf(stderr, "Both filenames cannot end in .jed!\n");
+ return 1;
+ }
+
+ /* read the source file */
+ err = read_source_file(srcfile);
+ if (err != 0)
+ return 1;
+
+ /* if the source is JED, convert to binary */
+ if (src_is_jed)
+ {
+ printf("Converting '%s' to binary form '%s'\n", srcfile, dstfile);
+
+ /* read the JEDEC data */
+ err = jed_parse(srcbuf, srcbuflen, &jed);
+ switch (err)
+ {
+ case JEDERR_INVALID_DATA: fprintf(stderr, "Fatal error: Invalid .JED file\n"); return 1;
+ case JEDERR_BAD_XMIT_SUM: fprintf(stderr, "Fatal error: Bad transmission checksum\n"); return 1;
+ case JEDERR_BAD_FUSE_SUM: fprintf(stderr, "Fatal error: Bad fusemap checksum\n"); return 1;
+ }
+
+ /* override the number of fuses */
+ if (numfuses != 0)
+ jed.numfuses = numfuses;
+
+ /* print out data */
+ printf("Source file read successfully\n");
+ printf(" Total fuses = %d\n", jed.numfuses);
+
+ /* generate the output */
+ dstbuflen = jedbin_output(&jed, NULL, 0);
+ dstbuf = malloc(dstbuflen);
+ if (!dstbuf)
+ {
+ fprintf(stderr, "Unable to allocate %d bytes for the target buffer!\n", (int)dstbuflen);
+ return 1;
+ }
+ dstbuflen = jedbin_output(&jed, dstbuf, dstbuflen);
+ }
+
+ /* if the source is binary, convert to JED */
+ else
+ {
+ printf("Converting '%s' to JED form '%s'\n", srcfile, dstfile);
+
+ /* read the binary data */
+ err = jedbin_parse(srcbuf, srcbuflen, &jed);
+ switch (err)
+ {
+ case JEDERR_INVALID_DATA: fprintf(stderr, "Fatal error: Invalid binary JEDEC file\n"); return 1;
+ }
+
+ /* print out data */
+ printf("Source file read successfully\n");
+ printf(" Total fuses = %d\n", jed.numfuses);
+
+ /* generate the output */
+ dstbuflen = jed_output(&jed, NULL, 0);
+ dstbuf = malloc(dstbuflen);
+ if (!dstbuf)
+ {
+ fprintf(stderr, "Unable to allocate %d bytes for the target buffer!\n", (int)dstbuflen);
+ return 1;
+ }
+ dstbuflen = jed_output(&jed, dstbuf, dstbuflen);
+ }
+
+ /* write the destination file */
+ err = write_dest_file(dstfile);
+ if (err != 0)
+ return 1;
+
+ printf("Target file written succesfully\n");
+ return 0;
+}
diff --git a/src/tools/makemeta.c b/src/tools/makemeta.c
new file mode 100644
index 00000000000..3b01a5ab786
--- /dev/null
+++ b/src/tools/makemeta.c
@@ -0,0 +1,587 @@
+/***************************************************************************
+
+ makemeta.c
+
+ Laserdisc metadata generator.
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+****************************************************************************
+
+ Metadata format (12 bytes/field):
+
+ Offset Description
+ ------ ------------------------------------------
+ 0 = version (currently 2)
+ 1 = internal flags:
+ bit 0 = previous field is the same frame
+ bit 1 = next field is the same frame
+ 2 = white flag
+ 3-5 = line 16 Philips code
+ 6-8 = line 17 Philips code
+ 9-11 = line 18 Philips code
+
+***************************************************************************/
+
+#include <stdio.h>
+#include <ctype.h>
+#include "aviio.h"
+#include "bitmap.h"
+
+
+
+/***************************************************************************
+ CONSTANTS
+***************************************************************************/
+
+#define INSERT_FRAME_CODE 0xff000000
+#define INSERT_FRAME_CODE_INC 0xff000001
+#define INVALID_CODE 0xffffffff
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+typedef struct _pattern_data pattern_data;
+struct _pattern_data
+{
+ pattern_data *next;
+ UINT32 line16, line17, line18;
+ int white;
+};
+
+
+
+/***************************************************************************
+ IMPLEMENTATION
+***************************************************************************/
+
+/*-------------------------------------------------
+ parse_line - parse a Philips code from a
+ line of video data
+-------------------------------------------------*/
+
+static int parse_line(bitmap_t *bitmap, int line, int expected_bits, UINT8 *result)
+{
+ const UINT16 *source = BITMAP_ADDR16(bitmap, line, 0);
+ int x, edges = 0, bits = 0;
+ int minwidth = 1000;
+ UINT8 bit[720];
+ int edge[720];
+ int error = 0;
+
+ /* clamp expected bits */
+ expected_bits *= 2;
+ if (expected_bits > ARRAY_LENGTH(edge) - 1)
+ expected_bits = ARRAY_LENGTH(edge) - 1;
+
+ /* find the edges in the line */
+ for (x = 1; x < bitmap->width && edges < ARRAY_LENGTH(edge); x++)
+ if (source[x] >= 0xc000 && source[x - 1] < 0xc000)
+ edge[edges++] = x;
+ else if (source[x] <= 0x4000 && source[x - 1] > 0x4000)
+ edge[edges++] = x;
+
+ /* find the minimum width */
+ for (x = 1; x < edges; x++)
+ {
+ int width = edge[x] - edge[x - 1];
+ if (width > 3 && width < minwidth)
+ minwidth = edge[x] - edge[x - 1];
+ }
+
+ /* now generate the bits */
+ for (x = 1; x < edges; x++)
+ {
+ int width = edge[x] - edge[x - 1];
+ if (width > 3)
+ {
+ int count = (width > 3 * minwidth / 2) ? 2 : 1;
+ while (count--)
+ bit[bits++] = (source[edge[x - 1]] >= 0x8000) ? 1 : 0;
+ }
+ }
+
+ /* look for improperly paired bits in the sequence */
+ if (bits < expected_bits)
+ {
+ /* look for two bits in a row of the same type on an even boundary */
+ for (x = 0; x < bits; x += 2)
+ if (bit[x] == bit[x + 1])
+ break;
+
+ /* if we got something wrong, assume we're missing an opening 0 bit */
+ if (x < bits)
+ {
+ memmove(&bit[1], &bit[0], bits);
+ bit[0] = 0;
+ bits++;
+ }
+ }
+
+ /* trailing bits are 0 */
+ while (bits < expected_bits)
+ bit[bits++] = 0;
+
+ /* output */
+ for (x = 0; x < MIN(bits, expected_bits); x += 2)
+ {
+ static const UINT8 trans[4] = { 0x80, 1, 0, 0xff };
+ result[x/2] = trans[(bit[x] << 1) | bit[x + 1]];
+ if (result[x/2] > 1)
+ error++;
+ }
+ return error ? -(bits / 2) : (bits / 2);
+}
+
+
+/*-------------------------------------------------
+ output_meta - output a line of metadata
+-------------------------------------------------*/
+
+static void output_meta(UINT8 flags, UINT8 white, UINT32 line12, UINT32 line13, UINT32 line14, UINT32 framenum, UINT32 chapternum)
+{
+ /* start with the raw metadata, followed by a comment */
+ printf("02%02X%02X%06X%06X%06X ; ",
+ flags, white, line12, line13, line14);
+
+ /* separate comments for leadin/leadout */
+ if (line13 == 0x88ffff)
+ printf("leadin\n");
+ else if (line13 == 0x80eeee)
+ printf("leadout\n");
+
+ /* otherwise, display the frame and chapter, and indicate white flag/stop code */
+ else
+ {
+ printf("frame %05x ch %02x", framenum, chapternum);
+ if (white)
+ printf(" (white)");
+ if (line12 == 0x82cfff)
+ printf(" (stop)");
+ printf("\n");
+ }
+}
+
+
+/*-------------------------------------------------
+ generate_from_avi - generate the data from
+ an AVI file
+-------------------------------------------------*/
+
+static int generate_from_avi(const char *aviname)
+{
+ UINT32 line12 = 0, line13 = 0, line14 = 0, framenum = 0, chapternum = 0;
+ const avi_movie_info *info;
+ bitmap_t *bitmap;
+ avi_error avierr;
+ avi_file *avi;
+ int white = 0;
+ int frame;
+
+ /* open the file */
+ avierr = avi_open(aviname, &avi);
+ if (avierr != AVIERR_NONE)
+ {
+ fprintf(stderr, "Error opening AVI file: %s\n", avi_error_string(avierr));
+ return 1;
+ }
+
+ /* extract movie info */
+ info = avi_get_movie_info(avi);
+ fprintf(stderr, "%dx%d - %d frames total\n", info->video_width, info->video_height, info->video_numsamples);
+ if (info->video_height != 39)
+ {
+ fprintf(stderr, "Unknown VANC capture format: expected 39 rows\n");
+ return 1;
+ }
+
+ /* allocate a bitmap to hold it */
+ bitmap = bitmap_alloc(info->video_width, info->video_height, BITMAP_FORMAT_YUY16);
+ if (bitmap == NULL)
+ {
+ fprintf(stderr, "Out of memory allocating %dx%d bitmap\n", info->video_width, info->video_height);
+ return 1;
+ }
+
+ /* loop over frames */
+ for (frame = 0; frame < info->video_numsamples; frame++)
+ {
+ int field;
+ UINT8 bits[24];
+
+ /* read the frame */
+ avierr = avi_read_video_frame_yuy16(avi, frame, bitmap);
+ if (avierr != AVIERR_NONE)
+ {
+ fprintf(stderr, "Error reading AVI frame %d: %s\n", frame, avi_error_string(avierr));
+ break;
+ }
+
+ /* loop over two fields */
+ for (field = 0; field < 2; field++)
+ {
+ int prevwhite = white;
+ int i;
+
+ /* line 7 contains the white flag */
+ white = 0;
+ if (*BITMAP_ADDR16(bitmap, 20 * field + 7, bitmap->width / 2) > 0x8000)
+ white = 1;
+
+ /* output metadata for *previous* field */
+ if (frame > 0 || field > 0)
+ {
+ int flags = 0;
+
+ if (!prevwhite) flags |= 0x01;
+ if (!white) flags |= 0x02;
+ output_meta(flags, prevwhite, line12, line13, line14, framenum, chapternum);
+ }
+
+ /* line 12 contains stop code and other interesting bits */
+ line12 = 0;
+ if (parse_line(bitmap, 20 * field + 12, 24, bits) == 24)
+ for (i = 0; i < 24; i++)
+ line12 = (line12 << 1) | bits[i];
+
+ /* line 13 and 14 contain frame/chapter/lead in/out encodings */
+ line13 = 0;
+ if (parse_line(bitmap, 20 * field + 13, 24, bits) == 24)
+ for (i = 0; i < 24; i++)
+ line13 = (line13 << 1) | bits[i];
+
+ line14 = 0;
+ if (parse_line(bitmap, 20 * field + 14, 24, bits) == 24)
+ for (i = 0; i < 24; i++)
+ line14 = (line14 << 1) | bits[i];
+
+ /* the two lines must match */
+// if (line13 != 0 && line14 != 0 && line13 != line14)
+// line13 = line14 = 0;
+
+ /* is this a frame number? */
+ if ((line13 & 0xf00000) == 0xf00000)
+ framenum = line13 & 0x7ffff;
+ if ((line13 & 0xf00fff) == 0x800ddd)
+ chapternum = (line13 >> 12) & 0x7f;
+ }
+ }
+
+ /* output metadata for *previous* field */
+ {
+ int flags = 0;
+
+ if (!white) flags |= 0x01;
+ output_meta(flags, white, line12, line13, line14, framenum, chapternum);
+ }
+
+ bitmap_free(bitmap);
+ return 0;
+}
+
+
+/*-------------------------------------------------
+ parse_philips_code - parse a single Philips
+ code from a string, stopping at the given
+ end characters
+-------------------------------------------------*/
+
+static UINT32 parse_philips_code(char **argptr, const char *endchars)
+{
+ char *arg = *argptr;
+ UINT32 value = 0;
+
+ /* look for special chars first */
+ if (*arg == '+')
+ {
+ *argptr = arg + 1;
+ return INSERT_FRAME_CODE_INC;
+ }
+ else if (*arg == '@')
+ {
+ *argptr = arg + 1;
+ return INSERT_FRAME_CODE;
+ }
+
+ /* parse the rest as hex digits */
+ for ( ; *arg != 0 && strchr(endchars, *arg) == NULL; arg++)
+ {
+ if (*arg >= '0' && *arg <= '9')
+ value = (value << 4) + (*arg - '0');
+ else if (*arg >= 'a' && *arg <= 'f')
+ value = (value << 4) + 10 + (*arg - 'a');
+ else if (*arg >= 'A' && *arg <= 'F')
+ value = (value << 4) + 10 + (*arg - 'A');
+ else
+ return INVALID_CODE;
+ }
+
+ /* if we're too big, we're invalid */
+ if (value > 0xffffff)
+ return INVALID_CODE;
+
+ *argptr = arg;
+ return value;
+}
+
+
+/*-------------------------------------------------
+ parse_pattern - parse a pattern into a series
+ of pattern_data structs
+-------------------------------------------------*/
+
+pattern_data *parse_pattern(char *arg, int *countptr)
+{
+ pattern_data *head = NULL;
+ pattern_data **tailptr = &head;
+ int count = 0;
+
+ /* first parse the count */
+ for ( ; *arg != 0 && *arg != '*'; arg++)
+ {
+ if (!isdigit(*arg))
+ return NULL;
+ count = count * 10 + (*arg - '0');
+ }
+ if (*arg == 0)
+ return NULL;
+ arg++;
+ *countptr = count;
+
+ /* loop until we hit the end */
+ while (*arg != 0)
+ {
+ pattern_data *pat;
+
+ /* allocate a new structure */
+ pat = malloc(sizeof(*pat));
+ if (pat == NULL)
+ return NULL;
+ memset(pat, 0, sizeof(*pat));
+
+ /* bang at the beginning means white flag */
+ if (*arg == '!')
+ {
+ arg++;
+ pat->white = 1;
+ }
+
+ /* parse line16 until we hit a period or comma */
+ pat->line16 = parse_philips_code(&arg, ".,");
+ if (pat->line16 == INVALID_CODE)
+ return NULL;
+ if (*arg != '.' && *arg != ',' && *arg != 0)
+ return NULL;
+ if (*arg == '.')
+ arg++;
+
+ /* parse line17 until we hit a period */
+ pat->line17 = parse_philips_code(&arg, ".,");
+ if (pat->line17 == INVALID_CODE)
+ return NULL;
+ if (*arg != '.' && *arg != ',' && *arg != 0)
+ return NULL;
+ if (*arg == '.')
+ arg++;
+
+ /* parse line18 until we hit a comma */
+ pat->line18 = parse_philips_code(&arg, ",");
+ if (pat->line18 == INVALID_CODE)
+ return NULL;
+ if (*arg != ',' && *arg != 0)
+ return NULL;
+ if (*arg == ',')
+ arg++;
+
+ /* append to the end */
+ *tailptr = pat;
+ tailptr = &pat->next;
+ }
+
+ return head;
+}
+
+
+/*-------------------------------------------------
+ generate_from_pattern - generate metadata
+ from a pattern
+-------------------------------------------------*/
+
+static int generate_from_pattern(pattern_data *pattern, int patcount)
+{
+ pattern_data *curpat = pattern;
+ int framenum = 0, bcdframenum = 0;
+
+ /* loop until we exceed the pattern frame count */
+ while (1)
+ {
+ UINT32 line16, line17, line18;
+ int flags = 0;
+
+ /* handle special codes for line 16 */
+ line16 = curpat->line16;
+ if (line16 == INSERT_FRAME_CODE_INC)
+ {
+ framenum++;
+ bcdframenum = (((framenum / 10000) % 10) << 16) | (((framenum / 1000) % 10) << 12) | (((framenum / 100) % 10) << 8) | (((framenum / 10) % 10) << 4) | (framenum % 10);
+ }
+ if (line16 == INSERT_FRAME_CODE || line16 == INSERT_FRAME_CODE_INC)
+ line16 = 0xf80000 | bcdframenum;
+
+ /* handle special codes for line 17 */
+ line17 = curpat->line17;
+ if (line17 == INSERT_FRAME_CODE_INC)
+ {
+ framenum++;
+ bcdframenum = (((framenum / 10000) % 10) << 16) | (((framenum / 1000) % 10) << 12) | (((framenum / 100) % 10) << 8) | (((framenum / 10) % 10) << 4) | (framenum % 10);
+ }
+ if (line17 == INSERT_FRAME_CODE || line17 == INSERT_FRAME_CODE_INC)
+ line17 = 0xf80000 | bcdframenum;
+
+ /* handle special codes for line 18 */
+ line18 = curpat->line18;
+ if (line18 == INSERT_FRAME_CODE_INC)
+ {
+ framenum++;
+ bcdframenum = (((framenum / 10000) % 10) << 16) | (((framenum / 1000) % 10) << 12) | (((framenum / 100) % 10) << 8) | (((framenum / 10) % 10) << 4) | (framenum % 10);
+ }
+ if (line18 == INSERT_FRAME_CODE || line18 == INSERT_FRAME_CODE_INC)
+ line18 = 0xf80000 | bcdframenum;
+
+ /* bail if we passed the end */
+ if (framenum > patcount)
+ return 0;
+
+ /* if we don't have a white flag, the previous frame must match us */
+ if (!curpat->white) flags |= 0x01;
+
+ /* advance to the next pattern piece */
+ curpat = curpat->next;
+ if (curpat == NULL)
+ curpat = pattern;
+
+ /* if the new field doesn't have a white flag, it must match our current frame */
+ if (!curpat->white) flags |= 0x02;
+
+ /* output the result */
+ output_meta(flags, curpat->white, line16, line17, line18, bcdframenum, 0);
+ }
+
+ return 0;
+}
+
+
+/*-------------------------------------------------
+ usage - display program usage
+-------------------------------------------------*/
+
+static int usage(void)
+{
+ fprintf(stderr, "Usage: \n");
+ fprintf(stderr, " makemeta [avifile.avi] [<option> [<option> [...]]]\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, "Options:\n");
+ fprintf(stderr, " -leadin <count> -- prepend <count> fields of leadin codes\n");
+ fprintf(stderr, " -leadout <count> -- append <count> fields of leadout codes\n");
+ fprintf(stderr, " -pattern <frames>*<pat0>[,<pat1>[,...]] -- repeat Philips code pattern\n");
+ fprintf(stderr, " in place of AVI. <pat0>, <pat1>, etc. are of the form: \n");
+ fprintf(stderr, " [!]A[.B[.C]][,<pattern>] where A,B,C are either hexadecimal\n");
+ fprintf(stderr, " values one of these special codes:\n");
+ fprintf(stderr, " ! -- at the start of a pattern sets the white flag\n");
+ fprintf(stderr, " + -- increments frame number and inserts frame code\n");
+ fprintf(stderr, " @ -- inserts repeat of most recent frame code\n");
+ fprintf(stderr, " non-present values are assumed to be 0\n");
+ fprintf(stderr, "\n");
+ fprintf(stderr, "Examples:\n");
+ fprintf(stderr, " makemeta -pattern 54000*!8da01e.+.@,8da01e,!8da01e.+.@,8da01e,8da01e\n");
+ fprintf(stderr, " generates the following pattern of Philips codes:\n");
+ fprintf(stderr, " 8da01e f80001 f80001 (white)\n");
+ fprintf(stderr, " 8da01e 000000 000000\n");
+ fprintf(stderr, " 8da01e f80002 f80002 (white)\n");
+ fprintf(stderr, " 8da01e 000000 000000\n");
+ fprintf(stderr, " 8da01e 000000 000000\n");
+ fprintf(stderr, " <repeat until frame is 54000>\n");
+ return 1;
+}
+
+
+/*-------------------------------------------------
+ main - main entry point
+-------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+ int leadin = 0, leadout = 0;
+ const char *avifile = NULL;
+ pattern_data *pattern = NULL;
+ int patcount = 0;
+ int arg;
+ int i;
+
+ /* iterate over arguments */
+ for (arg = 1; arg < argc; arg++)
+ {
+ /* assume anything without a - is a filename */
+ if (argv[arg][0] != '-')
+ {
+ if (avifile != NULL || pattern != NULL)
+ return usage();
+ avifile = argv[arg];
+ }
+
+ /* look for options */
+ else if (strcmp(argv[arg], "-leadin") == 0)
+ {
+ if (++arg >= argc)
+ return usage();
+ leadin = atoi(argv[arg]);
+ }
+ else if (strcmp(argv[arg], "-leadout") == 0)
+ {
+ if (++arg >= argc)
+ return usage();
+ leadout = atoi(argv[arg]);
+ }
+ else if (strcmp(argv[arg], "-pattern") == 0)
+ {
+ if (avifile != NULL || pattern != NULL)
+ return usage();
+ if (++arg >= argc)
+ return usage();
+ pattern = parse_pattern(argv[arg], &patcount);
+ if (pattern == NULL)
+ return usage();
+ }
+ }
+
+ /* must have an AVI file or a pattern */
+ if (avifile == NULL && pattern == NULL)
+ return usage();
+
+ /* output header and leadin */
+ printf("chdmeta 12\n");
+ for (i = 0; i < leadin; i++)
+ {
+ int flags = (i == 0) ? 0x02 : (i == leadin - 1) ? 0x01 : 0x03;
+ printf("02%02X%02X00000088FFFF88FFFF\n", flags, (i % 2));
+ }
+
+ /* if we got a file, output it */
+ if (avifile != NULL)
+ generate_from_avi(avifile);
+ else if (pattern != NULL)
+ generate_from_pattern(pattern, patcount);
+
+ /* output leadout */
+ for (i = 0; i < leadout; i++)
+ {
+ int flags = (i == 0) ? 0x02 : (i == leadout - 1) ? 0x01 : 0x03;
+ printf("02%02X%02X00000080EEEE80EEEE\n", flags, (i % 2));
+ }
+
+ return 0;
+}
diff --git a/src/tools/regrep.c b/src/tools/regrep.c
new file mode 100644
index 00000000000..97bd2b9c17b
--- /dev/null
+++ b/src/tools/regrep.c
@@ -0,0 +1,1181 @@
+/***************************************************************************
+
+ Regression test report generator
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "osdcore.h"
+#include "png.h"
+
+
+/***************************************************************************
+ CONSTANTS & DEFINES
+***************************************************************************/
+
+#define MAX_COMPARES 16
+#define BITMAP_SPACE 4
+
+enum
+{
+ STATUS_NOT_PRESENT = 0,
+ STATUS_SUCCESS,
+ STATUS_SUCCESS_DIFFERENT,
+ STATUS_MISSING_FILES,
+ STATUS_EXCEPTION,
+ STATUS_FATAL_ERROR,
+ STATUS_FAILED_VALIDITY,
+ STATUS_OTHER,
+ STATUS_COUNT
+};
+
+enum
+{
+ BUCKET_UNKNOWN = 0,
+ BUCKET_IMPROVED,
+ BUCKET_REGRESSED,
+ BUCKET_CHANGED,
+ BUCKET_MULTI_ERROR,
+ BUCKET_CONSISTENT_ERROR,
+ BUCKET_GOOD,
+ BUCKET_GOOD_BUT_CHANGED,
+ BUCKET_GOOD_BUT_CHANGED_SCREENSHOTS,
+ BUCKET_COUNT
+};
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+typedef struct _summary_file summary_file;
+struct _summary_file
+{
+ summary_file * next;
+ char name[10];
+ char source[100];
+ UINT8 status[MAX_COMPARES];
+ UINT8 matchbitmap[MAX_COMPARES];
+ char * text[MAX_COMPARES];
+ UINT32 textsize[MAX_COMPARES];
+ UINT32 textalloc[MAX_COMPARES];
+};
+
+
+typedef struct _summary_list summary_list;
+struct _summary_list
+{
+ summary_list * next;
+ summary_file * files;
+ char * dir;
+ char version[40];
+};
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+static summary_file *filehash[128][128];
+static summary_list lists[MAX_COMPARES];
+static int list_count;
+
+static const char *bucket_name[] =
+{
+ "Unknown",
+ "Games That Have Improved",
+ "Games That Have Regressed",
+ "Games With Changed Screenshots",
+ "Games With Multiple Errors",
+ "Games With Consistent Errors",
+ "Games That Are Consistently Good",
+ "Games That Regressed But Improved",
+ "Games With Changed Screenshots",
+};
+
+static const int bucket_output_order[] =
+{
+ BUCKET_REGRESSED,
+ BUCKET_IMPROVED,
+ BUCKET_CHANGED,
+ BUCKET_GOOD_BUT_CHANGED_SCREENSHOTS,
+ BUCKET_GOOD_BUT_CHANGED,
+ BUCKET_MULTI_ERROR,
+ BUCKET_CONSISTENT_ERROR
+};
+
+static const char *status_text[] =
+{
+ "",
+ "Success",
+ "Changed",
+ "Missing Files",
+ "Exception",
+ "Fatal Error",
+ "Failed Validity Check",
+ "Other Unknown Error"
+};
+
+static const char *status_color[] =
+{
+ "",
+ "bgcolor=\"#00A000\"",
+ "bgcolor=\"#E0E000\"",
+ "bgcolor=\"#8000C0\"",
+ "bgcolor=\"#C00000\"",
+ "bgcolor=\"#C00000\"",
+ "bgcolor=\"#C06000\"",
+ "bgcolor=\"#C00000\"",
+ "bgcolor=\"#C00000\"",
+};
+
+
+
+/***************************************************************************
+ PROTOTYPES
+***************************************************************************/
+
+/* summary parsing */
+static int read_summary_log(const char *filename, int index);
+static summary_file *parse_driver_tag(char *linestart, int index);
+static summary_file *get_file(const char *filename);
+static int CLIB_DECL compare_file(const void *file0ptr, const void *file1ptr);
+static summary_file *sort_file_list(void);
+
+/* HTML helpers */
+static FILE *create_file_and_output_header(const char *filename, const char *title, const char *subtitle);
+static void output_footer_and_close_file(FILE *file);
+
+/* report generators */
+static void output_report(const char *dirname, summary_file *filelist);
+static int compare_screenshots(summary_file *curfile);
+static int generate_png_diff(const summary_file *curfile, const char *destdir, const char *destname);
+static void create_linked_file(const char *dirname, const summary_file *curfile, const summary_file *prevfile, const summary_file *nextfile, const char *pngfile);
+static void append_driver_list_table(const char *header, const char *dirname, FILE *indexfile, const summary_file *listhead);
+
+
+
+/***************************************************************************
+ INLINE FUNCTIONS
+***************************************************************************/
+
+/*-------------------------------------------------
+ alloc_filename - combine directory name
+ and remainder into full path, living in
+ allocated memory
+-------------------------------------------------*/
+
+INLINE const char *alloc_filename(const char *dirname, const char *remainder)
+{
+ int count = strlen(dirname) + 1 + strlen(remainder) + 1;
+ char *result;
+
+ result = malloc(count);
+ if (result == NULL)
+ return NULL;
+ sprintf(result, "%s" PATH_SEPARATOR "%s", dirname, remainder);
+ return result;
+}
+
+
+/*-------------------------------------------------
+ trim_string - trim leading/trailing spaces
+ from a string
+-------------------------------------------------*/
+
+INLINE char *trim_string(char *string)
+{
+ int length;
+
+ /* trim leading spaces */
+ while (*string != 0 && isspace(*string))
+ string++;
+
+ /* trim trailing spaces */
+ length = strlen(string);
+ while (length > 0 && isspace(string[length - 1]))
+ string[--length] = 0;
+
+ return string;
+}
+
+
+/*-------------------------------------------------
+ get_unique_index - get the unique bitmap
+ index for a given entry
+-------------------------------------------------*/
+
+INLINE int get_unique_index(const summary_file *curfile, int index)
+{
+ int listnum, curindex = 0;
+
+ /* if we're invalid, just return that */
+ if (curfile->matchbitmap[index] == 0xff)
+ return -1;
+
+ /* count unique elements up to us */
+ for (listnum = 0; listnum < curfile->matchbitmap[index]; listnum++)
+ if (curfile->matchbitmap[listnum] == listnum)
+ curindex++;
+ return curindex;
+}
+
+
+
+/***************************************************************************
+ MAIN
+***************************************************************************/
+
+/*-------------------------------------------------
+ main - main entry point
+-------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+ const char *dirname;
+ int listnum;
+ int result;
+
+ /* first argument is the directory */
+ if (argc < 3)
+ {
+ fprintf(stderr, "Usage:\nsummcomp <outputdir> <summary1> [<summary2> [<summary3> ...]]\n");
+ return 1;
+ }
+ dirname = argv[1];
+ list_count = argc - 2;
+
+ /* loop over arguments and read the files */
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ result = read_summary_log(argv[listnum + 2], listnum);
+ if (result != 0)
+ return result;
+ }
+
+ /* output the summary */
+ output_report(dirname, sort_file_list());
+ return 0;
+}
+
+
+
+/***************************************************************************
+ SUMMARY PARSING
+***************************************************************************/
+
+/*-------------------------------------------------
+ get_file - lookup a driver name in the hash
+ table and return a pointer to it; if none
+ found, allocate a new entry
+-------------------------------------------------*/
+
+static summary_file *get_file(const char *filename)
+{
+ summary_file *file;
+
+ /* use the first two characters as a lookup */
+ for (file = filehash[filename[0] & 0x7f][filename[1] & 0x7f]; file != NULL; file = file->next)
+ if (strcmp(filename, file->name) == 0)
+ return file;
+
+ /* didn't find one -- allocate */
+ file = malloc(sizeof(*file));
+ if (file == NULL)
+ return NULL;
+ memset(file, 0, sizeof(*file));
+
+ /* set the name so we find it in the future */
+ strcpy(file->name, filename);
+
+ /* add to the head of the list */
+ file->next = filehash[filename[0] & 0x7f][filename[1] & 0x7f];
+ filehash[filename[0] & 0x7f][filename[1] & 0x7f] = file;
+ return file;
+}
+
+
+/*-------------------------------------------------
+ read_summary_log - read a summary.log file
+ and build entries for its data
+-------------------------------------------------*/
+
+static int read_summary_log(const char *filename, int index)
+{
+ summary_file *curfile = NULL;
+ char linebuffer[1024];
+ char *linestart;
+ int drivers = 0;
+ FILE *file;
+
+ /* open the logfile */
+ file = fopen(filename, "r");
+ if (file == NULL)
+ {
+ fprintf(stderr, "Error: file '%s' not found\n", filename);
+ return 1;
+ }
+
+ /* parse it */
+ while (fgets(linebuffer, sizeof(linebuffer), file) != NULL)
+ {
+ /* trim the leading/trailing spaces */
+ linestart = trim_string(linebuffer);
+
+ /* is this one of our specials? */
+ if (strncmp(linestart, "@@@@@", 5) == 0)
+ {
+ /* advance past the signature */
+ linestart += 5;
+
+ /* look for the driver= tag */
+ if (strncmp(linestart, "driver=", 7) == 0)
+ {
+ curfile = parse_driver_tag(linestart + 7, index);
+ if (curfile == NULL)
+ goto error;
+ drivers++;
+ }
+
+ /* look for the source= tag */
+ else if (strncmp(linestart, "source=", 7) == 0)
+ {
+ /* error if no driver yet */
+ if (curfile == NULL)
+ {
+ fprintf(stderr, "Unexpected @@@@@source= tag\n");
+ goto error;
+ }
+
+ /* copy the string */
+ strcpy(curfile->source, trim_string(linestart + 7));
+ }
+
+ /* look for the dir= tag */
+ else if (strncmp(linestart, "dir=", 4) == 0)
+ {
+ char *dirname = trim_string(linestart + 4);
+
+ /* allocate a copy of the string */
+ lists[index].dir = malloc(strlen(dirname) + 1);
+ if (lists[index].dir == NULL)
+ goto error;
+ strcpy(lists[index].dir, dirname);
+ fprintf(stderr, "Directory %s\n", lists[index].dir);
+ }
+ }
+
+ /* if not, consider other options */
+ else if (curfile != NULL)
+ {
+ int foundchars = 0;
+ char *curptr;
+
+ /* look for the pngcrc= tag */
+ if (strncmp(linestart, "pngcrc: ", 7) == 0)
+ {
+ }
+
+ /* otherwise, accumulate the text */
+ else
+ {
+ /* find the end of the line and normalize it with a CR */
+ for (curptr = linestart; *curptr != 0 && *curptr != '\n' && *curptr != '\r'; curptr++)
+ if (!isspace(*curptr))
+ foundchars = 1;
+ *curptr++ = '\n';
+ *curptr = 0;
+
+ /* ignore blank lines */
+ if (!foundchars)
+ continue;
+
+ /* see if we have enough room */
+ if (curfile->textsize[index] + (curptr - linestart) + 1 >= curfile->textalloc[index])
+ {
+ curfile->textalloc[index] = curfile->textsize[index] + (curptr - linestart) + 256;
+ curfile->text[index] = realloc(curfile->text[index], curfile->textalloc[index]);
+ if (curfile->text[index] == NULL)
+ {
+ fprintf(stderr, "Unable to allocate memory for text\n");
+ goto error;
+ }
+ }
+
+ /* append our text */
+ strcpy(curfile->text[index] + curfile->textsize[index], linestart);
+ curfile->textsize[index] += curptr - linestart;
+ }
+ }
+
+ /* look for the M.A.M.E. header */
+ else if (strncmp(linestart, "M.A.M.E. v", 10) == 0)
+ {
+ char *start = linestart + 10;
+ char *end;
+
+ /* find the end */
+ for (end = start; !isspace(*end); end++) ;
+ *end = 0;
+ strcpy(lists[index].version, start);
+ fprintf(stderr, "Parsing results from version %s\n", lists[index].version);
+ }
+ }
+
+ fclose(file);
+ fprintf(stderr, "Parsed %d drivers\n", drivers);
+ return 0;
+
+error:
+ fclose(file);
+ return 1;
+}
+
+
+/*-------------------------------------------------
+ parse_driver_tag - parse the status info
+ from a driver tag
+-------------------------------------------------*/
+
+static summary_file *parse_driver_tag(char *linestart, int index)
+{
+ summary_file *curfile;
+ char *colon;
+
+ /* find the colon separating name from status */
+ colon = strchr(linestart, ':');
+ if (colon == NULL)
+ {
+ fprintf(stderr, "Unexpected text after @@@@@driver=\n");
+ return NULL;
+ }
+
+ /* NULL terminate at the colon and look up the file */
+ *colon = 0;
+ curfile = get_file(trim_string(linestart));
+ if (curfile == NULL)
+ {
+ fprintf(stderr, "Unable to allocate memory for driver\n");
+ return NULL;
+ }
+
+ /* clear out any old status for this file */
+ curfile->status[index] = STATUS_NOT_PRESENT;
+ if (curfile->text[index] != NULL)
+ free(curfile->text[index]);
+ curfile->text[index] = NULL;
+ curfile->textsize[index] = 0;
+ curfile->textalloc[index] = 0;
+
+ /* strip leading/trailing spaces from the status */
+ colon = trim_string(colon + 1);
+
+ /* convert status into statistics */
+ if (strcmp(colon, "Success") == 0)
+ curfile->status[index] = STATUS_SUCCESS;
+ else if (strcmp(colon, "Missing files") == 0)
+ curfile->status[index] = STATUS_MISSING_FILES;
+ else if (strcmp(colon, "Exception") == 0)
+ curfile->status[index] = STATUS_EXCEPTION;
+ else if (strcmp(colon, "Fatal error") == 0)
+ curfile->status[index] = STATUS_FATAL_ERROR;
+ else if (strcmp(colon, "Failed validity check") == 0)
+ curfile->status[index] = STATUS_FAILED_VALIDITY;
+ else
+ curfile->status[index] = STATUS_OTHER;
+
+ return curfile;
+}
+
+
+/*-------------------------------------------------
+ compare_file - compare two files, sorting
+ first by source filename, then by driver name
+-------------------------------------------------*/
+
+static int CLIB_DECL compare_file(const void *file0ptr, const void *file1ptr)
+{
+ summary_file *file0 = *(summary_file **)file0ptr;
+ summary_file *file1 = *(summary_file **)file1ptr;
+ int result = strcmp(file0->source, file1->source);
+ if (result == 0)
+ result = strcmp(file0->name, file1->name);
+ return result;
+}
+
+
+/*-------------------------------------------------
+ sort_file_list - convert the hashed lists
+ into a single, sorted list
+-------------------------------------------------*/
+
+static summary_file *sort_file_list(void)
+{
+ summary_file *listhead, **tailptr, *curfile, **filearray;
+ int numfiles, filenum;
+ int c0, c1;
+
+ /* count the total number of files */
+ numfiles = 0;
+ for (c0 = 0; c0 < 128; c0++)
+ for (c1 = 0; c1 < 128; c1++)
+ for (curfile = filehash[c0][c1]; curfile != NULL; curfile = curfile->next)
+ numfiles++;
+
+ /* allocate an array of files */
+ filearray = malloc(numfiles * sizeof(*filearray));
+ if (filearray == NULL)
+ {
+ fprintf(stderr, "Out of memory!\n");
+ return NULL;
+ }
+
+ /* populate the array */
+ numfiles = 0;
+ for (c0 = 0; c0 < 128; c0++)
+ for (c1 = 0; c1 < 128; c1++)
+ for (curfile = filehash[c0][c1]; curfile != NULL; curfile = curfile->next)
+ filearray[numfiles++] = curfile;
+
+ /* sort the array */
+ qsort(filearray, numfiles, sizeof(filearray[0]), compare_file);
+
+ /* now regenerate a single list */
+ listhead = NULL;
+ tailptr = &listhead;
+ for (filenum = 0; filenum < numfiles; filenum++)
+ {
+ *tailptr = filearray[filenum];
+ tailptr = &(*tailptr)->next;
+ }
+ *tailptr = NULL;
+ free(filearray);
+
+ return listhead;
+}
+
+
+
+/***************************************************************************
+ HTML OUTPUT HELPERS
+***************************************************************************/
+
+/*-------------------------------------------------
+ create_file_and_output_header - create a new
+ HTML file with a standard header
+-------------------------------------------------*/
+
+static FILE *create_file_and_output_header(const char *filename, const char *title, const char *subtitle)
+{
+ FILE *file;
+
+ /* create the indexfile */
+ file = fopen(filename, "w");
+ if (file == NULL)
+ return NULL;
+
+ /* print a header */
+ fprintf(file,
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ "<head>\n"
+ "\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"
+ "\t<title>%s</title>\n"
+ "\t<link rel=\"stylesheet\" href=\"http://mamedev.org/styles-site.css\" type=\"text/css\" />\n"
+ "</head>\n"
+ "\n"
+ "<body>\n"
+ "\t<div id=\"outer\">\n"
+ "\n"
+ "\t<div id=\"banner\">\n"
+ "\t<h1>%s</h1>\n"
+ "\t<h2>%s</h2>\n"
+ "\t</div>\n"
+ "\n"
+ "\t<div id=\"left\">\n"
+ "\t<div class=\"sidebar\">\n"
+ "\t<!--#include virtual=\"/links.txt\" -->\n"
+ "\t</div>\n"
+ "\t</div>\n"
+ "\n"
+ "\t<div id=\"center\">\n"
+ "\t<div class=\"content\">\n"
+ "\n",
+ title, title, (subtitle == NULL) ? "&nbsp;" : subtitle
+ );
+
+ /* return the file */
+ return file;
+}
+
+
+/*-------------------------------------------------
+ output_footer_and_close_file - write a
+ standard footer to an HTML file and close it
+-------------------------------------------------*/
+
+static void output_footer_and_close_file(FILE *file)
+{
+ fprintf(file,
+ "\n"
+ "\t</div>\n"
+ "\t</div>\n"
+ "\t</div>\n"
+ "</body>"
+ "\n"
+ "</html>\n"
+ );
+ fclose(file);
+}
+
+
+
+/***************************************************************************
+ REPORT GENERATORS
+***************************************************************************/
+
+/*-------------------------------------------------
+ output_report - generate the summary
+ report HTML files
+-------------------------------------------------*/
+
+static void output_report(const char *dirname, summary_file *filelist)
+{
+ summary_file *buckethead[BUCKET_COUNT], **buckettailptr[BUCKET_COUNT];
+ summary_file *curfile;
+ const char *indexname;
+ int listnum, bucknum;
+ FILE *indexfile;
+ int count = 0, total;
+
+ /* initialize the lists */
+ for (bucknum = 0; bucknum < BUCKET_COUNT; bucknum++)
+ {
+ buckethead[bucknum] = NULL;
+ buckettailptr[bucknum] = &buckethead[bucknum];
+ }
+
+ /* compute the total number of files */
+ total = 0;
+ for (curfile = filelist; curfile != NULL; curfile = curfile->next)
+ total++;
+
+ /* first bucketize the games */
+ for (curfile = filelist; curfile != NULL; curfile = curfile->next)
+ {
+ int statcount[STATUS_COUNT] = { 0 };
+ int bucket = BUCKET_UNKNOWN;
+ int unique_codes = 0;
+ int first_valid;
+
+ /* print status */
+ if (++count % 100 == 0)
+ fprintf(stderr, "Processing file %d/%d\n", count, total);
+
+ /* find the first valid entry */
+ for (first_valid = 0; curfile->status[first_valid] == STATUS_NOT_PRESENT; first_valid++) ;
+
+ /* do we need to output anything? */
+ for (listnum = first_valid; listnum < list_count; listnum++)
+ if (statcount[curfile->status[listnum]]++ == 0)
+ unique_codes++;
+
+ /* were we consistent? */
+ if (unique_codes == 1)
+ {
+ /* were we consistently ok? */
+ if (curfile->status[first_valid] == STATUS_SUCCESS)
+ bucket = compare_screenshots(curfile);
+
+ /* must have been consistently erroring */
+ else
+ bucket = BUCKET_CONSISTENT_ERROR;
+ }
+
+ /* ok, we're not consistent; could be a number of things */
+ else
+ {
+ /* were we ok at the start and end but not in the middle? */
+ if (curfile->status[first_valid] == STATUS_SUCCESS && curfile->status[list_count - 1] == STATUS_SUCCESS)
+ bucket = BUCKET_GOOD_BUT_CHANGED;
+
+ /* did we go from good to bad? */
+ else if (curfile->status[first_valid] == STATUS_SUCCESS)
+ bucket = BUCKET_REGRESSED;
+
+ /* did we go from bad to good? */
+ else if (curfile->status[list_count - 1] == STATUS_SUCCESS)
+ bucket = BUCKET_IMPROVED;
+
+ /* must have had multiple errors */
+ else
+ bucket = BUCKET_MULTI_ERROR;
+ }
+
+ /* add us to the appropriate list */
+ *buckettailptr[bucket] = curfile;
+ buckettailptr[bucket] = &curfile->next;
+ }
+
+ /* terminate all the lists */
+ for (bucknum = 0; bucknum < BUCKET_COUNT; bucknum++)
+ *buckettailptr[bucknum] = NULL;
+
+ /* output header */
+ indexname = alloc_filename(dirname, "index.html");
+ indexfile = create_file_and_output_header(indexname, "MAME&trade; Regressions", NULL);
+ free((void *)indexname);
+ if (indexfile == NULL)
+ {
+ fprintf(stderr, "Error creating file '%s'\n", indexname);
+ return;
+ }
+
+ /* iterate over buckets and output them */
+ for (bucknum = 0; bucknum < ARRAY_LENGTH(bucket_output_order); bucknum++)
+ {
+ int curbucket = bucket_output_order[bucknum];
+
+ if (buckethead[curbucket] != NULL)
+ {
+ fprintf(stderr, "Outputting bucket: %s\n", bucket_name[curbucket]);
+ append_driver_list_table(bucket_name[curbucket], dirname, indexfile, buckethead[curbucket]);
+ }
+ }
+
+ /* output footer */
+ output_footer_and_close_file(indexfile);
+}
+
+
+/*-------------------------------------------------
+ compare_screenshots - compare the screenshots
+ for all the games in a file
+-------------------------------------------------*/
+
+static int compare_screenshots(summary_file *curfile)
+{
+ bitmap_t *bitmaps[MAX_COMPARES];
+ int unique[MAX_COMPARES];
+ int numunique = 0;
+ int listnum;
+
+ /* iterate over all files and load their bitmaps */
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ bitmaps[listnum] = NULL;
+ if (curfile->status[listnum] == STATUS_SUCCESS)
+ {
+ file_error filerr;
+ const char *fullname;
+ char imgname[100];
+ core_file *file;
+
+ /* get the filename for the image */
+ sprintf(imgname, "snap" PATH_SEPARATOR "%s" PATH_SEPARATOR "final.png", curfile->name);
+ fullname = alloc_filename(lists[listnum].dir, imgname);
+
+ /* open the file */
+ filerr = core_fopen(fullname, OPEN_FLAG_READ, &file);
+ free((void *)fullname);
+
+ /* if that failed, look in the old location */
+ if (filerr != FILERR_NONE)
+ {
+ /* get the filename for the image */
+ sprintf(imgname, "snap" PATH_SEPARATOR "_%s.png", curfile->name);
+ fullname = alloc_filename(lists[listnum].dir, imgname);
+
+ /* open the file */
+ filerr = core_fopen(fullname, OPEN_FLAG_READ, &file);
+ free((void *)fullname);
+ }
+
+ /* if that worked, load the file */
+ if (filerr == FILERR_NONE)
+ {
+ png_read_bitmap(file, &bitmaps[listnum]);
+ core_fclose(file);
+ }
+ }
+ }
+
+ /* now find all the different bitmap types */
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ curfile->matchbitmap[listnum] = 0xff;
+ if (bitmaps[listnum] != NULL)
+ {
+ bitmap_t *this_bitmap = bitmaps[listnum];
+ int compnum;
+
+ /* compare against all unique bitmaps */
+ for (compnum = 0; compnum < numunique; compnum++)
+ {
+ bitmap_t *base_bitmap = bitmaps[unique[compnum]];
+ int bitmaps_differ;
+ int x, y;
+
+ /* if the sizes are different, we differ; otherwise start off assuming we are the same */
+ bitmaps_differ = (this_bitmap->width != base_bitmap->width || this_bitmap->height != base_bitmap->height);
+
+ /* compare scanline by scanline */
+ for (y = 0; y < this_bitmap->height && !bitmaps_differ; y++)
+ {
+ UINT32 *base = BITMAP_ADDR32(base_bitmap, y, 0);
+ UINT32 *curr = BITMAP_ADDR32(this_bitmap, y, 0);
+
+ /* scan the scanline */
+ for (x = 0; x < this_bitmap->width; x++)
+ if (*base++ != *curr++)
+ break;
+ bitmaps_differ = (x != this_bitmap->width);
+ }
+
+ /* if we matched, remember which listnum index we matched, and stop */
+ if (!bitmaps_differ)
+ {
+ curfile->matchbitmap[listnum] = unique[compnum];
+ break;
+ }
+
+ /* if different from the first unique entry, adjust the status */
+ if (bitmaps_differ && compnum == 0)
+ curfile->status[listnum] = STATUS_SUCCESS_DIFFERENT;
+ }
+
+ /* if we're unique, add ourselves to the list */
+ if (compnum >= numunique)
+ {
+ unique[numunique++] = listnum;
+ curfile->matchbitmap[listnum] = listnum;
+ continue;
+ }
+ }
+ }
+
+ /* free the bitmaps */
+ for (listnum = 0; listnum < list_count; listnum++)
+ if (bitmaps[listnum] != NULL)
+ bitmap_free(bitmaps[listnum]);
+
+ /* if all screenshots matched, we're good */
+ if (numunique == 1)
+ return BUCKET_GOOD;
+
+ /* if the last screenshot matched the first unique one, we're good but changed */
+ if (curfile->matchbitmap[listnum - 1] == unique[0])
+ return BUCKET_GOOD_BUT_CHANGED_SCREENSHOTS;
+
+ /* otherwise we're just changed */
+ return BUCKET_CHANGED;
+}
+
+
+/*-------------------------------------------------
+ generate_png_diff - create a new PNG file
+ that shows multiple differing PNGs side by
+ side with a third set of differences
+-------------------------------------------------*/
+
+static int generate_png_diff(const summary_file *curfile, const char *destdir, const char *destname)
+{
+ const char *dstfilename = alloc_filename(destdir, destname);
+ bitmap_t *bitmaps[MAX_COMPARES] = { NULL };
+ bitmap_t *finalbitmap = NULL;
+ int width, height, maxwidth;
+ int bitmapcount = 0;
+ int listnum, bmnum;
+ core_file *file = NULL;
+ char srcimgname[50];
+ file_error filerr;
+ png_error pngerr;
+ int error = -1;
+ int starty;
+
+ /* generate the common source filename */
+ sprintf(srcimgname, "snap" PATH_SEPARATOR "%s" PATH_SEPARATOR "final.png", curfile->name);
+
+ /* open and load all unique bitmaps */
+ for (listnum = 0; listnum < list_count; listnum++)
+ if (curfile->matchbitmap[listnum] == listnum)
+ {
+ const char *filename = alloc_filename(lists[listnum].dir, srcimgname);
+
+ /* open the source image */
+ filerr = core_fopen(filename, OPEN_FLAG_READ, &file);
+ free((void *)filename);
+ if (filerr != FILERR_NONE)
+ goto error;
+
+ /* load the source image */
+ pngerr = png_read_bitmap(file, &bitmaps[bitmapcount++]);
+ core_fclose(file);
+ if (pngerr != FILERR_NONE)
+ goto error;
+ }
+
+ /* if there's only one unique bitmap, skip it */
+ if (bitmapcount <= 1)
+ goto error;
+
+ /* determine the size of the final bitmap */
+ height = width = 0;
+ maxwidth = bitmaps[0]->width;
+ for (bmnum = 1; bmnum < bitmapcount; bmnum++)
+ {
+ int curwidth;
+
+ /* determine the maximal width */
+ maxwidth = MAX(maxwidth, bitmaps[bmnum]->width);
+ curwidth = bitmaps[0]->width + BITMAP_SPACE + maxwidth + BITMAP_SPACE + maxwidth;
+ width = MAX(width, curwidth);
+
+ /* add to the height */
+ height += MAX(bitmaps[0]->height, bitmaps[bmnum]->height);
+ if (bmnum != 1)
+ height += BITMAP_SPACE;
+ }
+
+ /* allocate the final bitmap */
+ finalbitmap = bitmap_alloc(width, height, BITMAP_FORMAT_ARGB32);
+ if (finalbitmap == NULL)
+ goto error;
+
+ /* now copy and compare each set of bitmaps */
+ starty = 0;
+ for (bmnum = 1; bmnum < bitmapcount; bmnum++)
+ {
+ bitmap_t *bitmap1 = bitmaps[0];
+ bitmap_t *bitmap2 = bitmaps[bmnum];
+ int curheight = MAX(bitmap1->height, bitmap2->height);
+ int x, y;
+
+ /* iterate over rows in these bitmaps */
+ for (y = 0; y < curheight; y++)
+ {
+ UINT32 *src1 = (y < bitmap1->height) ? BITMAP_ADDR32(bitmap1, y, 0) : NULL;
+ UINT32 *src2 = (y < bitmap2->height) ? BITMAP_ADDR32(bitmap2, y, 0) : NULL;
+ UINT32 *dst1 = BITMAP_ADDR32(finalbitmap, starty + y, 0);
+ UINT32 *dst2 = BITMAP_ADDR32(finalbitmap, starty + y, bitmap1->width + BITMAP_SPACE);
+ UINT32 *dstdiff = BITMAP_ADDR32(finalbitmap, starty + y, bitmap1->width + BITMAP_SPACE + maxwidth + BITMAP_SPACE);
+
+ /* now iterate over columns */
+ for (x = 0; x < maxwidth; x++)
+ {
+ int pix1 = -1, pix2 = -2;
+
+ if (src1 != NULL && x < bitmap1->width)
+ pix1 = dst1[x] = src1[x];
+ if (src2 != NULL && x < bitmap2->width)
+ pix2 = dst2[x] = src2[x];
+ dstdiff[x] = (pix1 != pix2) ? 0xffffffff : 0xff000000;
+ }
+ }
+
+ /* update the starting Y position */
+ starty += BITMAP_SPACE + MAX(bitmap1->height, bitmap2->height);
+ }
+
+ /* write the final PNG */
+ filerr = core_fopen(dstfilename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file);
+ if (filerr != FILERR_NONE)
+ goto error;
+ pngerr = png_write_bitmap(file, NULL, finalbitmap, 0, NULL);
+ core_fclose(file);
+ if (pngerr != FILERR_NONE)
+ goto error;
+
+ /* if we get here, we are error free */
+ error = 0;
+
+error:
+ if (dstfilename != NULL)
+ free((void *)dstfilename);
+ if (finalbitmap != NULL)
+ bitmap_free(finalbitmap);
+ for (bmnum = 0; bmnum < bitmapcount; bmnum++)
+ if (bitmaps[bmnum] != NULL)
+ bitmap_free(bitmaps[bmnum]);
+ if (error)
+ osd_rmfile(dstfilename);
+ return error;
+}
+
+
+/*-------------------------------------------------
+ create_linked_file - create a comparison
+ file between differing versions
+-------------------------------------------------*/
+
+static void create_linked_file(const char *dirname, const summary_file *curfile, const summary_file *prevfile, const summary_file *nextfile, const char *pngfile)
+{
+ const char *linkname;
+ char filename[100];
+ char title[100];
+ FILE *linkfile;
+ int listnum;
+
+ /* create the filename */
+ sprintf(filename, "%s.html", curfile->name);
+
+ /* output header */
+ sprintf(title, "%s Regressions", curfile->name);
+ linkname = alloc_filename(dirname, filename);
+ linkfile = create_file_and_output_header(linkname, title, NULL);
+ free((void *)linkname);
+ if (linkfile == NULL)
+ {
+ fprintf(stderr, "Error creating file '%s'\n", filename);
+ return;
+ }
+
+ /* link to the previous/next entries */
+ fprintf(linkfile, "\t<p>\n");
+ fprintf(linkfile, "\t<table width=\"100%%\">\n");
+ fprintf(linkfile, "\t\t<td align=\"left\" width=\"40%%\" style=\"border:none\">");
+ if (prevfile != NULL)
+ fprintf(linkfile, "<a href=\"%s.html\"><< %s (%s)</a>", prevfile->name, prevfile->name, prevfile->source);
+ fprintf(linkfile, "</td>\n");
+ fprintf(linkfile, "\t\t<td align=\"center\" width=\"20%%\" style=\"border:none\"><a href=\"index.html\">Home</a></td>\n");
+ fprintf(linkfile, "\t\t<td align=\"right\" width=\"40%%\" style=\"border:none\">");
+ if (nextfile != NULL)
+ fprintf(linkfile, "<a href=\"%s.html\">%s (%s) >></a>", nextfile->name, nextfile->name, nextfile->source);
+ fprintf(linkfile, "</td>\n");
+ fprintf(linkfile, "\t</table>\n");
+ fprintf(linkfile, "\t</p>\n");
+
+ /* output data for each one */
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ int imageindex = -1;
+
+ /* generate the HTML */
+ fprintf(linkfile, "\n\t<h2>%s</h2>\n", lists[listnum].version);
+ fprintf(linkfile, "\t<p>\n");
+ fprintf(linkfile, "\t<b>Status:</b> %s\n", status_text[curfile->status[listnum]]);
+ if (pngfile != NULL)
+ imageindex = get_unique_index(curfile, listnum);
+ if (imageindex != -1)
+ fprintf(linkfile, " [%d]", imageindex);
+ fprintf(linkfile, "\t</p>\n");
+ if (curfile->text[listnum] != NULL)
+ {
+ fprintf(linkfile, "\t<p>\n");
+ fprintf(linkfile, "\t<b>Errors:</b>\n");
+ fprintf(linkfile, "\t<pre>%s</pre>\n", curfile->text[listnum]);
+ fprintf(linkfile, "\t</p>\n");
+ }
+ }
+
+ /* output link to the image */
+ if (pngfile != NULL)
+ {
+ fprintf(linkfile, "\n\t<h2>Screenshot Comparisons</h2>\n");
+ fprintf(linkfile, "\t<p>\n");
+ fprintf(linkfile, "\t<img src=\"%s\" />\n", pngfile);
+ fprintf(linkfile, "\t</p>\n");
+ }
+
+ /* output footer */
+ output_footer_and_close_file(linkfile);
+}
+
+
+/*-------------------------------------------------
+ append_driver_list_table - append a table
+ of drivers from a list to an HTML file
+-------------------------------------------------*/
+
+static void append_driver_list_table(const char *header, const char *dirname, FILE *indexfile, const summary_file *listhead)
+{
+ const summary_file *curfile, *prevfile;
+ int width = 100 / (2 + list_count);
+ int listnum;
+
+ /* output a header */
+ fprintf(indexfile, "\t<h2>%s</h2>\n", header);
+ fprintf(indexfile, "\t<div style=\"padding-left:20px;\">\n");
+
+ /* start the table */
+ fprintf(indexfile, "\t<p><table width=\"90%%\">\n");
+ fprintf(indexfile, "\t\t<tr>\n\t\t\t<th width=\"%d%%\">Source</th><th width=\"%d%%\">Driver</th>", width, width);
+ for (listnum = 0; listnum < list_count; listnum++)
+ fprintf(indexfile, "<th width=\"%d%%\">%s</th>", width, lists[listnum].version);
+ fprintf(indexfile, "\n\t\t</tr>\n");
+
+ /* if nothing, print a default message */
+ if (listhead == NULL)
+ {
+ fprintf(indexfile, "\t\t<tr>\n\t\t\t");
+ fprintf(indexfile, "<td colspan=\"%d\" align=\"center\">(No regressions detected)</td>", list_count + 2);
+ fprintf(indexfile, "\n\t\t</tr>\n");
+ }
+
+ /* iterate over files */
+ for (prevfile = NULL, curfile = listhead; curfile != NULL; prevfile = curfile, curfile = curfile->next)
+ {
+ int rowspan = 0, uniqueshots = 0;
+ char pngdiffname[40];
+
+ /* if this is the first entry in this source file, count how many rows we need to span */
+ if (prevfile == NULL || strcmp(prevfile->source, curfile->source) != 0)
+ {
+ const summary_file *cur;
+ for (cur = curfile; cur != NULL; cur = cur->next)
+ if (strcmp(cur->source, curfile->source) == 0)
+ rowspan++;
+ else
+ break;
+ }
+
+ /* create screenshots if necessary */
+ pngdiffname[0] = 0;
+ for (listnum = 0; listnum < list_count; listnum++)
+ if (curfile->matchbitmap[listnum] == listnum)
+ uniqueshots++;
+ if (uniqueshots > 1)
+ {
+ sprintf(pngdiffname, "compare_%s.png", curfile->name);
+ if (generate_png_diff(curfile, dirname, pngdiffname) != 0)
+ pngdiffname[0] = 0;
+ }
+
+ /* create a linked file */
+ create_linked_file(dirname, curfile, prevfile, curfile->next, (pngdiffname[0] == 0) ? NULL : pngdiffname);
+
+ /* create a row */
+ fprintf(indexfile, "\t\t<tr>\n\t\t\t");
+ if (rowspan > 0)
+ fprintf(indexfile, "<td rowspan=\"%d\">%s</td>", rowspan, curfile->source);
+ fprintf(indexfile, "<td><a href=\"%s.html\">%s</a></td>", curfile->name, curfile->name);
+ for (listnum = 0; listnum < list_count; listnum++)
+ {
+ int unique_index = -1;
+
+ if (pngdiffname[0] != 0)
+ unique_index = get_unique_index(curfile, listnum);
+ if (unique_index != -1)
+ fprintf(indexfile, "<td %s>%s [<a href=\"%s\" target=\"blank\">%d</a>]</td>", status_color[curfile->status[listnum]], status_text[curfile->status[listnum]], pngdiffname, unique_index);
+ else
+ fprintf(indexfile, "<td %s>%s</td>", status_color[curfile->status[listnum]], status_text[curfile->status[listnum]]);
+ }
+ fprintf(indexfile, "\n\t\t</tr>\n");
+
+ /* also print the name and source file */
+ printf("%s %s\n", curfile->name, curfile->source);
+ }
+
+ /* end of table */
+ fprintf(indexfile, "</table></p>\n");
+ fprintf(indexfile, "</div>\n");
+}
diff --git a/src/tools/romcmp.c b/src/tools/romcmp.c
new file mode 100644
index 00000000000..dbef0effa7c
--- /dev/null
+++ b/src/tools/romcmp.c
@@ -0,0 +1,748 @@
+/***************************************************************************
+
+ romcmp.c
+
+ ROM comparison utility program.
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#include "unzip.h"
+#include "osdepend.h" /* for CLIB_DECL */
+#include "osdcore.h"
+
+#include <stdarg.h>
+
+
+#define MAX_FILES 100
+
+#ifndef MAX_FILENAME_LEN
+#define MAX_FILENAME_LEN 12 /* increase this if you are using a real OS... */
+#endif
+
+#ifndef PATH_DELIM
+#define PATH_DELIM '/'
+#endif
+
+
+/* for unzip.c */
+void CLIB_DECL logerror(const char *text,...)
+{
+}
+
+
+
+/* compare modes when one file is twice as long as the other */
+/* A = All file */
+/* 12 = 1st half */
+/* 22 = 2nd half */
+/* E = Even bytes */
+/* O = Odd bytes */
+/* E1 = Even bytes 1st half */
+/* O1 = Odd bytes 1st half */
+/* E2 = Even bytes 2nd half */
+/* O2 = Odd bytes 2nd half */
+enum { MODE_A,
+ MODE_NIB1,MODE_NIB2,
+ MODE_12, MODE_22,
+ MODE_14, MODE_24, MODE_34, MODE_44,
+ MODE_E, MODE_O,
+ MODE_E12, MODE_O12, MODE_E22, MODE_O22,
+ TOTAL_MODES };
+const char *modenames[] =
+{
+ " ",
+ "[bits 0-3]",
+ "[bits 4-7]",
+ "[1/2] ",
+ "[2/2] ",
+ "[1/4] ",
+ "[2/4] ",
+ "[3/4] ",
+ "[4/4] ",
+ "[even] ",
+ "[odd] ",
+ "[even 1/2]",
+ "[odd 1/2] ",
+ "[even 2/2]",
+ "[odd 2/2] ",
+};
+
+static void compatiblemodes(int mode,int *start,int *end)
+{
+ if (mode == MODE_A)
+ {
+ *start = MODE_A;
+ *end = MODE_A;
+ }
+ if (mode >= MODE_NIB1 && mode <= MODE_NIB2)
+ {
+ *start = MODE_NIB1;
+ *end = MODE_NIB2;
+ }
+ if (mode >= MODE_12 && mode <= MODE_22)
+ {
+ *start = MODE_12;
+ *end = MODE_22;
+ }
+ if (mode >= MODE_14 && mode <= MODE_44)
+ {
+ *start = MODE_14;
+ *end = MODE_44;
+ }
+ if (mode >= MODE_E && mode <= MODE_O)
+ {
+ *start = MODE_E;
+ *end = MODE_O;
+ }
+ if (mode >= MODE_E12 && mode <= MODE_O22)
+ {
+ *start = MODE_E12;
+ *end = MODE_O22;
+ }
+}
+
+struct _fileinfo
+{
+ char name[MAX_FILENAME_LEN+1];
+ int size;
+ unsigned char *buf; /* file is read in here */
+ int listed;
+};
+typedef struct _fileinfo fileinfo;
+
+fileinfo files[2][MAX_FILES];
+float matchscore[MAX_FILES][MAX_FILES][TOTAL_MODES][TOTAL_MODES];
+
+
+static void checkintegrity(const fileinfo *file,int side)
+{
+ int i;
+ int mask0,mask1;
+ int addrbit;
+
+ if (file->buf == 0) return;
+
+ /* check for bad data lines */
+ mask0 = 0x0000;
+ mask1 = 0xffff;
+
+ for (i = 0;i < file->size;i+=2)
+ {
+ mask0 |= ((file->buf[i] << 8) | file->buf[i+1]);
+ mask1 &= ((file->buf[i] << 8) | file->buf[i+1]);
+ if (mask0 == 0xffff && mask1 == 0x0000) break;
+ }
+
+ if (mask0 != 0xffff || mask1 != 0x0000)
+ {
+ int fixedmask;
+ int bits;
+
+
+ fixedmask = (~mask0 | mask1) & 0xffff;
+
+ if (((mask0 >> 8) & 0xff) == (mask0 & 0xff) && ((mask1 >> 8) & 0xff) == (mask1 & 0xff))
+ bits = 8;
+ else bits = 16;
+
+ printf("%-23s %-23s FIXED BITS (",side ? "" : file->name,side ? file->name : "");
+ for (i = 0;i < bits;i++)
+ {
+ if (~mask0 & 0x8000) printf("0");
+ else if (mask1 & 0x8000) printf("1");
+ else printf("x");
+
+ mask0 <<= 1;
+ mask1 <<= 1;
+ }
+ printf(")\n");
+
+ /* if the file contains a fixed value, we don't need to do the other */
+ /* validity checks */
+ if (fixedmask == 0xffff || fixedmask == 0x00ff || fixedmask == 0xff00)
+ return;
+ }
+
+
+ addrbit = 1;
+ mask0 = 0;
+ while (addrbit <= file->size/2)
+ {
+ for (i = 0;i < file->size;i++)
+ {
+ if (file->buf[i] != file->buf[i ^ addrbit]) break;
+ }
+
+ if (i == file->size)
+ mask0 |= addrbit;
+
+ addrbit <<= 1;
+ }
+
+ if (mask0)
+ {
+ if (mask0 == file->size/2)
+ printf("%-23s %-23s 1ST AND 2ND HALF IDENTICAL\n",side ? "" : file->name,side ? file->name : "");
+ else
+ {
+ printf("%-23s %-23s BADADDR",side ? "" : file->name,side ? file->name : "");
+ for (i = 0;i < 24;i++)
+ {
+ if (file->size <= (1<<(23-i))) printf(" ");
+ else if (mask0 & 0x800000) printf("-");
+ else printf("x");
+ mask0 <<= 1;
+ }
+ printf("\n");
+ }
+ return;
+ }
+
+ mask0 = 0x000000;
+ mask1 = file->size-1;
+ for (i = 0;i < file->size;i++)
+ {
+ if (file->buf[i] != 0xff)
+ {
+ mask0 |= i;
+ mask1 &= i;
+ if (mask0 == file->size-1 && mask1 == 0x00) break;
+ }
+ }
+
+ if (mask0 != file->size-1 || mask1 != 0x00)
+ {
+ printf("%-23s %-23s ",side ? "" : file->name,side ? file->name : "");
+ for (i = 0;i < 24;i++)
+ {
+ if (file->size <= (1<<(23-i))) printf(" ");
+ else if (~mask0 & 0x800000) printf("1");
+ else if (mask1 & 0x800000) printf("0");
+ else printf("x");
+ mask0 <<= 1;
+ mask1 <<= 1;
+ }
+ printf(" = 0xFF\n");
+
+ return;
+ }
+
+
+ mask0 = 0x000000;
+ mask1 = file->size-1;
+ for (i = 0;i < file->size;i++)
+ {
+ if (file->buf[i] != 0x00)
+ {
+ mask0 |= i;
+ mask1 &= i;
+ if (mask0 == file->size-1 && mask1 == 0x00) break;
+ }
+ }
+
+ if (mask0 != file->size-1 || mask1 != 0x00)
+ {
+ printf("%-23s %-23s ",side ? "" : file->name,side ? file->name : "");
+ for (i = 0;i < 24;i++)
+ {
+ if (file->size <= (1<<(23-i))) printf(" ");
+ else if ((mask0 & 0x800000) == 0) printf("1");
+ else if (mask1 & 0x800000) printf("0");
+ else printf("x");
+ mask0 <<= 1;
+ mask1 <<= 1;
+ }
+ printf(" = 0x00\n");
+
+ return;
+ }
+
+
+ mask0 = 0xff;
+ for (i = 0;i < file->size/4 && mask0;i++)
+ {
+ if (file->buf[ 2*i ] != 0x00) mask0 &= ~0x01;
+ if (file->buf[ 2*i ] != 0xff) mask0 &= ~0x02;
+ if (file->buf[ 2*i+1] != 0x00) mask0 &= ~0x04;
+ if (file->buf[ 2*i+1] != 0xff) mask0 &= ~0x08;
+ if (file->buf[file->size/2 + 2*i ] != 0x00) mask0 &= ~0x10;
+ if (file->buf[file->size/2 + 2*i ] != 0xff) mask0 &= ~0x20;
+ if (file->buf[file->size/2 + 2*i+1] != 0x00) mask0 &= ~0x40;
+ if (file->buf[file->size/2 + 2*i+1] != 0xff) mask0 &= ~0x80;
+ }
+
+ if (mask0 & 0x01) printf("%-23s %-23s 1ST HALF = 00xx\n",side ? "" : file->name,side ? file->name : "");
+ if (mask0 & 0x02) printf("%-23s %-23s 1ST HALF = FFxx\n",side ? "" : file->name,side ? file->name : "");
+ if (mask0 & 0x04) printf("%-23s %-23s 1ST HALF = xx00\n",side ? "" : file->name,side ? file->name : "");
+ if (mask0 & 0x08) printf("%-23s %-23s 1ST HALF = xxFF\n",side ? "" : file->name,side ? file->name : "");
+ if (mask0 & 0x10) printf("%-23s %-23s 2ND HALF = 00xx\n",side ? "" : file->name,side ? file->name : "");
+ if (mask0 & 0x20) printf("%-23s %-23s 2ND HALF = FFxx\n",side ? "" : file->name,side ? file->name : "");
+ if (mask0 & 0x40) printf("%-23s %-23s 2ND HALF = xx00\n",side ? "" : file->name,side ? file->name : "");
+ if (mask0 & 0x80) printf("%-23s %-23s 2ND HALF = xxFF\n",side ? "" : file->name,side ? file->name : "");
+}
+
+
+static int usedbytes(const fileinfo *file,int mode)
+{
+ switch (mode)
+ {
+ case MODE_A:
+ case MODE_NIB1:
+ case MODE_NIB2:
+ return file->size;
+ case MODE_12:
+ case MODE_22:
+ case MODE_E:
+ case MODE_O:
+ return file->size / 2;
+ case MODE_14:
+ case MODE_24:
+ case MODE_34:
+ case MODE_44:
+ case MODE_E12:
+ case MODE_O12:
+ case MODE_E22:
+ case MODE_O22:
+ return file->size / 4;
+ default:
+ return 0;
+ }
+}
+
+static void basemultmask(const fileinfo *file,int mode,int *base,int *mult,int *mask)
+{
+ *mult = 1;
+ if (mode >= MODE_E) *mult = 2;
+
+ switch (mode)
+ {
+ case MODE_A:
+ case MODE_12:
+ case MODE_14:
+ case MODE_E:
+ case MODE_E12:
+ *base = 0; *mask = 0xff; break;
+ case MODE_NIB1:
+ *base = 0; *mask = 0x0f; break;
+ case MODE_NIB2:
+ *base = 0; *mask = 0xf0; break;
+ case MODE_O:
+ case MODE_O12:
+ *base = 1; *mask = 0xff; break;
+ case MODE_22:
+ case MODE_E22:
+ *base = file->size / 2; *mask = 0xff; break;
+ case MODE_O22:
+ *base = 1 + file->size / 2; *mask = 0xff; break;
+ case MODE_24:
+ *base = file->size / 4; *mask = 0xff; break;
+ case MODE_34:
+ *base = 2*file->size / 4; *mask = 0xff; break;
+ case MODE_44:
+ *base = 3*file->size / 4; *mask = 0xff; break;
+ }
+}
+
+static float filecompare(const fileinfo *file1,const fileinfo *file2,int mode1,int mode2)
+{
+ int i;
+ int match = 0;
+ int size1,size2;
+ int base1=0,base2=0,mult1=0,mult2=0,mask1=0,mask2=0;
+
+
+ if (file1->buf == 0 || file2->buf == 0) return 0.0;
+
+ size1 = usedbytes(file1,mode1);
+ size2 = usedbytes(file2,mode2);
+
+ if (size1 != size2) return 0.0;
+
+ basemultmask(file1,mode1,&base1,&mult1,&mask1);
+ basemultmask(file2,mode2,&base2,&mult2,&mask2);
+
+ if (mask1 == mask2)
+ {
+ if (mask1 == 0xff)
+ {
+ /* normal compare */
+ for (i = 0;i < size1;i++)
+ if (file1->buf[base1 + mult1 * i] == file2->buf[base2 + mult2 * i]) match++;
+ }
+ else
+ {
+ /* nibble compare, abort if other half is not empty */
+ for (i = 0;i < size1;i++)
+ {
+ if (((file1->buf[base1 + mult1 * i] & ~mask1) != (0x00 & ~mask1) &&
+ (file1->buf[base1 + mult1 * i] & ~mask1) != (0xff & ~mask1)) ||
+ ((file2->buf[base1 + mult1 * i] & ~mask2) != (0x00 & ~mask2) &&
+ (file2->buf[base1 + mult1 * i] & ~mask2) != (0xff & ~mask2)))
+ {
+ match = 0;
+ break;
+ }
+ if ((file1->buf[base1 + mult1 * i] & mask1) == (file2->buf[base2 + mult2 * i] & mask2)) match++;
+ }
+ }
+ }
+
+ return (float)match / size1;
+}
+
+
+static void readfile(const char *path,fileinfo *file)
+{
+ file_error filerr;
+ UINT64 filesize;
+ UINT32 actual;
+ char fullname[256];
+ osd_file *f = 0;
+
+ if (path)
+ {
+ char delim[2] = { PATH_DELIM, '\0' };
+ strcpy(fullname,path);
+ strcat(fullname,delim);
+ }
+ else fullname[0] = 0;
+ strcat(fullname,file->name);
+
+ if ((file->buf = malloc(file->size)) == 0)
+ {
+ printf("%s: out of memory!\n",file->name);
+ return;
+ }
+
+ filerr = osd_open(fullname, OPEN_FLAG_READ, &f, &filesize);
+ if (filerr != FILERR_NONE)
+ {
+ printf("%s: error %d\n", fullname, filerr);
+ return;
+ }
+
+ filerr = osd_read(f, file->buf, 0, file->size, &actual);
+ if (filerr != FILERR_NONE)
+ {
+ printf("%s: error %d\n", fullname, filerr);
+ osd_close(f);
+ return;
+ }
+
+ osd_close(f);
+}
+
+
+static void freefile(fileinfo *file)
+{
+ free(file->buf);
+ file->buf = 0;
+}
+
+
+static void printname(const fileinfo *file1,const fileinfo *file2,float score,int mode1,int mode2)
+{
+ printf("%-12s %s %-12s %s ",file1 ? file1->name : "",modenames[mode1],file2 ? file2->name : "",modenames[mode2]);
+ if (score == 0.0) printf("NO MATCH\n");
+ else if (score == 1.0) printf("IDENTICAL\n");
+ else printf("%3.6f%%\n",score*100);
+}
+
+
+static int load_files(int i, int *found, const char *path)
+{
+ osd_directory *dir;
+
+ /* attempt to open as a directory first */
+ dir = osd_opendir(path);
+ if (dir != NULL)
+ {
+ const osd_directory_entry *d;
+
+ /* load all files in directory */
+ while ((d = osd_readdir(dir)) != NULL)
+ {
+ const char *d_name = d->name;
+ char buf[255+1];
+
+ sprintf(buf, "%s%c%s", path, PATH_DELIM, d_name);
+ if (d->type == ENTTYPE_FILE)
+ {
+ UINT64 size = d->size;
+ while (size && (size & 1) == 0) size >>= 1;
+ if (size & ~1)
+ printf("%-23s %-23s ignored (not a ROM)\n",i ? "" : d_name,i ? d_name : "");
+ else
+ {
+ strcpy(files[i][found[i]].name,d_name);
+ files[i][found[i]].size = d->size;
+ readfile(path,&files[i][found[i]]);
+ files[i][found[i]].listed = 0;
+ if (found[i] >= MAX_FILES)
+ {
+ printf("%s: max of %d files exceeded\n",path,MAX_FILES);
+ break;
+ }
+ found[i]++;
+ }
+ }
+ }
+ osd_closedir(dir);
+ }
+
+ /* if not, try to open as a ZIP file */
+ else
+ {
+ zip_file *zip;
+ const zip_file_header* zipent;
+ zip_error ziperr;
+
+ /* wasn't a directory, so try to open it as a zip file */
+ ziperr = zip_file_open(path, &zip);
+ if (ziperr != ZIPERR_NONE)
+ {
+ printf("Error, cannot open zip file '%s' !\n", path);
+ return 1;
+ }
+
+ /* load all files in zip file */
+ for (zipent = zip_file_first_file(zip); zipent != NULL; zipent = zip_file_next_file(zip))
+ {
+ int size;
+
+ size = zipent->uncompressed_length;
+ while (size && (size & 1) == 0) size >>= 1;
+ if (zipent->uncompressed_length == 0 || (size & ~1))
+ printf("%-23s %-23s ignored (not a ROM)\n",
+ i ? "" : zipent->filename, i ? zipent->filename : "");
+ else
+ {
+ fileinfo *file = &files[i][found[i]];
+ const char *delim = strrchr(zipent->filename,'/');
+
+ if (delim)
+ strcpy (file->name,delim+1);
+ else
+ strcpy(file->name,zipent->filename);
+ file->size = zipent->uncompressed_length;
+ if ((file->buf = malloc(file->size)) == 0)
+ printf("%s: out of memory!\n",file->name);
+ else
+ {
+ if (zip_file_decompress(zip, (char *)file->buf, file->size) != ZIPERR_NONE)
+ {
+ free(file->buf);
+ file->buf = 0;
+ }
+ }
+
+ file->listed = 0;
+ if (found[i] >= MAX_FILES)
+ {
+ printf("%s: max of %d files exceeded\n",path,MAX_FILES);
+ break;
+ }
+ found[i]++;
+ }
+ }
+ zip_file_close(zip);
+ }
+ return 0;
+}
+
+
+int CLIB_DECL main(int argc,char **argv)
+{
+ int err;
+ int total_modes = MODE_NIB2; /* by default, use only MODE_A, MODE_NIB1 and MODE_NIB2 */
+
+ if (argc >= 2 && strcmp(argv[1],"-d") == 0)
+ {
+ argc--;
+ argv++;
+ total_modes = TOTAL_MODES;
+ }
+
+ if (argc < 2)
+ {
+ printf("usage: romcmp [-d] [dir1 | zip1] [dir2 | zip2]\n");
+ printf("-d enables a slower, more comprehensive comparison.\n");
+ return 0;
+ }
+
+ {
+ int found[2];
+ int i,j,mode1,mode2;
+ int besti,bestj;
+
+
+ found[0] = found[1] = 0;
+ for (i = 0;i < 2;i++)
+ {
+ if (argc > i+1)
+ {
+ err = load_files (i, found, argv[i+1]);
+ if (err != 0)
+ return err;
+ }
+ }
+
+ if (argc >= 3)
+ printf("%d and %d files\n",found[0],found[1]);
+ else
+ printf("%d files\n",found[0]);
+
+ for (i = 0;i < found[0];i++)
+ {
+ checkintegrity(&files[0][i],0);
+ }
+
+ for (j = 0;j < found[1];j++)
+ {
+ checkintegrity(&files[1][j],1);
+ }
+
+ if (argc < 3)
+ {
+ /* find duplicates in one dir */
+ for (i = 0;i < found[0];i++)
+ {
+ for (j = i+1;j < found[0];j++)
+ {
+ for (mode1 = 0;mode1 < total_modes;mode1++)
+ {
+ for (mode2 = 0;mode2 < total_modes;mode2++)
+ {
+ if (filecompare(&files[0][i],&files[0][j],mode1,mode2) == 1.0)
+ printname(&files[0][i],&files[0][j],1.0,mode1,mode2);
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ /* compare two dirs */
+ for (i = 0;i < found[0];i++)
+ {
+ for (j = 0;j < found[1];j++)
+ {
+ fprintf(stderr,"%2d%%\r",100*(i*found[1]+j)/(found[0]*found[1]));
+ for (mode1 = 0;mode1 < total_modes;mode1++)
+ {
+ for (mode2 = 0;mode2 < total_modes;mode2++)
+ {
+ matchscore[i][j][mode1][mode2] = filecompare(&files[0][i],&files[1][j],mode1,mode2);
+ }
+ }
+ }
+ }
+ fprintf(stderr," \r");
+
+ do
+ {
+ float bestscore;
+ int bestmode1,bestmode2;
+
+ besti = -1;
+ bestj = -1;
+ bestscore = 0.0;
+ bestmode1 = bestmode2 = -1;
+
+ for (mode1 = 0;mode1 < total_modes;mode1++)
+ {
+ for (mode2 = 0;mode2 < total_modes;mode2++)
+ {
+ for (i = 0;i < found[0];i++)
+ {
+ for (j = 0;j < found[1];j++)
+ {
+ if (matchscore[i][j][mode1][mode2] > bestscore
+ || (matchscore[i][j][mode1][mode2] == 1.0 && mode2 == 0 && bestmode2 > 0))
+ {
+ bestscore = matchscore[i][j][mode1][mode2];
+ besti = i;
+ bestj = j;
+ bestmode1 = mode1;
+ bestmode2 = mode2;
+ }
+ }
+ }
+ }
+ }
+
+ if (besti != -1)
+ {
+ int start=0,end=0;
+
+ printname(&files[0][besti],&files[1][bestj],bestscore,bestmode1,bestmode2);
+ files[0][besti].listed = 1;
+ files[1][bestj].listed = 1;
+
+ matchscore[besti][bestj][bestmode1][bestmode2] = 0.0;
+
+ /* remove all matches using the same sections with a worse score */
+ for (j = 0;j < found[1];j++)
+ {
+ for (mode2 = 0;mode2 < total_modes;mode2++)
+ {
+ if (matchscore[besti][j][bestmode1][mode2] < bestscore)
+ matchscore[besti][j][bestmode1][mode2] = 0.0;
+ }
+ }
+ for (i = 0;i < found[0];i++)
+ {
+ for (mode1 = 0;mode1 < total_modes;mode1++)
+ {
+ if (matchscore[i][bestj][mode1][bestmode2] < bestscore)
+ matchscore[i][bestj][mode1][bestmode2] = 0.0;
+ }
+ }
+
+ /* remove all matches using incompatible sections */
+ compatiblemodes(bestmode1,&start,&end);
+ for (j = 0;j < found[1];j++)
+ {
+ for (mode2 = 0;mode2 < total_modes;mode2++)
+ {
+ for (mode1 = 0;mode1 < start;mode1++)
+ matchscore[besti][j][mode1][mode2] = 0.0;
+ for (mode1 = end+1;mode1 < total_modes;mode1++)
+ matchscore[besti][j][mode1][mode2] = 0.0;
+ }
+ }
+ compatiblemodes(bestmode2,&start,&end);
+ for (i = 0;i < found[0];i++)
+ {
+ for (mode1 = 0;mode1 < total_modes;mode1++)
+ {
+ for (mode2 = 0;mode2 < start;mode2++)
+ matchscore[i][bestj][mode1][mode2] = 0.0;
+ for (mode2 = end+1;mode2 < total_modes;mode2++)
+ matchscore[i][bestj][mode1][mode2] = 0.0;
+ }
+ }
+ }
+ } while (besti != -1);
+
+
+ for (i = 0;i < found[0];i++)
+ {
+ if (files[0][i].listed == 0) printname(&files[0][i],0,0.0,0,0);
+ freefile(&files[0][i]);
+ }
+ for (i = 0;i < found[1];i++)
+ {
+ if (files[1][i].listed == 0) printname(0,&files[1][i],0.0,0,0);
+ freefile(&files[1][i]);
+ }
+ }
+ }
+
+ zip_file_cache_clear();
+ return 0;
+}
+
diff --git a/src/tools/runtest.cmd b/src/tools/runtest.cmd
new file mode 100644
index 00000000000..65f63c2ad7e
--- /dev/null
+++ b/src/tools/runtest.cmd
@@ -0,0 +1,156 @@
+@rem ----------------------------------------------------
+@rem MAME Testing script
+@rem (Windows only at the moment, sorry!)
+@rem
+@rem Initial setup of the script:
+@rem
+@rem 1. Create a fresh directory mametest/
+@rem 2. Copy this script into it (mametest/runtest.cmd)
+@rem 3. Copy a mame.ini with your ROM paths into it
+@rem (mametest/mame.ini)
+@rem 4. Copy a transparent crosshair cursor into it
+@rem (mametest/cross.png)
+@rem
+@rem How to run a test:
+@rem
+@rem 1. Create a new subdirectory mametest/version/
+@rem 2. Copy mame.exe into it (mametest/version/mame.exe)
+@rem 3. Open a command prompt to mametest/version
+@rem 4. Run "..\runtest"
+@rem 5. Wait for all the tests to complete
+@rem
+@rem How to generate a report:
+@rem
+@rem 1. Open a command prompt to mametest.
+@rem 2. Make sure you have run tests for at least two
+@rem versions (mametest/ver1 and mametest/ver2)
+@rem 3. Create an output directory (mametest/report)
+@rem 4. Run "regrep report ver1\summary.log ver2\summary.log"
+@rem 5. Upload the report directory to mamedev.org :)
+@rem 6. Differing files are printed to stdout; redirect
+@rem to create a list that can be run again via
+@rem this script
+@rem ----------------------------------------------------
+
+@echo off
+
+@rem ----------------------------------------------------
+@rem We require mame.exe to be present
+@rem ----------------------------------------------------
+
+if not exist mame.exe (
+@echo Missing mame.exe!
+@goto :eof
+)
+
+@rem ----------------------------------------------------
+@rem By default we generate our own list; however, a list
+@rem can be specified by an alternate parameter. If a
+@rem parameter is given, we leave the existing log and
+@rem snap directories intact; otherwise, we delete them
+@rem and start fresh.
+@rem ----------------------------------------------------
+
+set LIST=gamelist.txt
+if "%1"=="" (
+@echo Generating full list
+mame -ls >%LIST%
+@echo Deleting old data
+if exist log rmdir /s/q log
+if exist snap rmdir /s/q snap
+if exist summary.log del summary.log
+) else (
+set LIST=%1
+@echo Re-testing %1
+)
+
+@rem ----------------------------------------------------
+@rem Always delete all cfg, nvram, and diff files.
+@rem ----------------------------------------------------
+
+if exist cfg rmdir /s/q cfg
+if exist nvram rmdir /s/q nvram
+if exist diff rmdir /s/q diff
+
+@rem ----------------------------------------------------
+@rem Make sure we use transparent crosshairs.
+@rem ----------------------------------------------------
+
+if not exist artwork mkdir artwork
+copy /y ..\cross.png artwork\cross0.png
+copy /y ..\cross.png artwork\cross1.png
+copy /y ..\cross.png artwork\cross2.png
+copy /y ..\cross.png artwork\cross3.png
+
+@rem ----------------------------------------------------
+@rem If we don't yet have a summary.log, create a new one.
+@rem ----------------------------------------------------
+
+if not exist summary.log (
+mame -help >summary.log
+echo @@@@@dir=%CD%>>summary.log
+)
+
+@rem ----------------------------------------------------
+@rem Create the log directory and a starting timestamp.
+@rem ----------------------------------------------------
+
+if not exist log mkdir log
+echo @@@@@start=%TIME%>>summary.log
+
+@rem ----------------------------------------------------
+@rem Iterate over drivers in the log, extracting the
+@rem source filename as well, and passing both to runone.
+@rem ----------------------------------------------------
+
+for /f "tokens=1-5 delims=/ " %%i in (%LIST%) do (
+if not "%%m"=="" (
+call :runone %%i %%m
+) else if not "%%l"=="" (
+call :runone %%i %%l
+) else if not "%%k"=="" (
+call :runone %%i %%k
+) else (
+call :runone %%i %%j
+)
+)
+
+@rem ----------------------------------------------------
+@rem Add a final timestamp and we're done.
+@rem ----------------------------------------------------
+
+echo @@@@@stop=%TIME%>>summary.log
+goto :eof
+
+
+
+@rem ----------------------------------------------------
+@rem runone: Execute a single game for 30 seconds and
+@rem output the results to the summary.log.
+@rem ----------------------------------------------------
+
+:runone
+@echo Testing %1 (%2)...
+echo.>>summary.log
+mame %1 -str 30 -nodebug -nothrottle -inipath .. -video none -nosound 1>log\%1.txt 2>log\%1.err
+if %errorlevel% equ 100 (
+echo @@@@@driver=%1: Exception>>summary.log
+type log\%1.err >>summary.log
+) else if %errorlevel% equ 5 (
+@rem Do nothing -- game does not exist in this build
+) else if %errorlevel% equ 3 (
+echo @@@@@driver=%1: Fatal error>>summary.log
+type log\%1.err >>summary.log
+) else if %errorlevel% equ 2 (
+echo @@@@@driver=%1: Missing files>>summary.log
+type log\%1.err >>summary.log
+) else if %errorlevel% equ 1 (
+echo @@@@@driver=%1: Failed validity check>>summary.log
+type log\%1.err >>summary.log
+) else if %errorlevel% equ 0 (
+echo @@@@@driver=%1: Success>>summary.log
+) else (
+echo @@@@@driver=%1: Unknown error %errorlevel%>>summary.log
+)
+echo @@@@@source=%2>>summary.log
+goto :eof
diff --git a/src/tools/src2html.c b/src/tools/src2html.c
new file mode 100644
index 00000000000..cce46c3e354
--- /dev/null
+++ b/src/tools/src2html.c
@@ -0,0 +1,860 @@
+/***************************************************************************
+
+ MAME source code to HTML converter
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <ctype.h>
+#include "osdcore.h"
+#include "astring.h"
+#include "corefile.h"
+
+
+/***************************************************************************
+ CONSTANTS & DEFINES
+***************************************************************************/
+
+#define COMMENT_STYLE "color:#b30000"
+#define STRING_STYLE "color:#666"
+#define PREPROCESSOR_STYLE "color:#0000b3"
+#define KEYWORD_STYLE "color:#0000b3"
+#define MAMEWORD_STYLE "color:#7f007f"
+
+
+
+/***************************************************************************
+ TYPE DEFINITIONS
+***************************************************************************/
+
+enum _file_type
+{
+ FILE_TYPE_INVALID,
+ FILE_TYPE_C,
+ FILE_TYPE_MAKE,
+ FILE_TYPE_XML,
+ FILE_TYPE_TEXT
+};
+typedef enum _file_type file_type;
+
+
+typedef struct _ext_to_type ext_to_type;
+struct _ext_to_type
+{
+ const char * extension;
+ file_type type;
+};
+
+
+typedef struct _token_entry token_entry;
+struct _token_entry
+{
+ const char * token;
+ const char * color;
+};
+
+
+typedef struct _include_path include_path;
+struct _include_path
+{
+ include_path * next;
+ const astring * path;
+};
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+static include_path *incpaths;
+static include_path **incpathhead = &incpaths;
+
+static const ext_to_type extension_lookup[] =
+{
+ { ".c", FILE_TYPE_C },
+ { ".h", FILE_TYPE_C },
+ { ".mak", FILE_TYPE_MAKE },
+ { "makefile", FILE_TYPE_MAKE },
+ { ".lay", FILE_TYPE_XML },
+ { ".txt", FILE_TYPE_TEXT },
+};
+
+
+static const token_entry dummy_token_table[] =
+{
+ { NULL, KEYWORD_STYLE }
+};
+
+
+static const token_entry c_token_table[] =
+{
+ { "#define", PREPROCESSOR_STYLE },
+ { "#elif", PREPROCESSOR_STYLE },
+ { "#else", PREPROCESSOR_STYLE },
+ { "#endif", PREPROCESSOR_STYLE },
+ { "#error", PREPROCESSOR_STYLE },
+ { "#if", PREPROCESSOR_STYLE },
+ { "#ifdef", PREPROCESSOR_STYLE },
+ { "#ifndef", PREPROCESSOR_STYLE },
+ { "#include", PREPROCESSOR_STYLE },
+ { "#line", PREPROCESSOR_STYLE },
+ { "#pragma", PREPROCESSOR_STYLE },
+ { "#undef", PREPROCESSOR_STYLE },
+
+ { "auto", KEYWORD_STYLE },
+ { "break", KEYWORD_STYLE },
+ { "case", KEYWORD_STYLE },
+ { "char", KEYWORD_STYLE },
+ { "const", KEYWORD_STYLE },
+ { "continue", KEYWORD_STYLE },
+ { "default", KEYWORD_STYLE },
+ { "do", KEYWORD_STYLE },
+ { "double", KEYWORD_STYLE },
+ { "else", KEYWORD_STYLE },
+ { "enum", KEYWORD_STYLE },
+ { "extern", KEYWORD_STYLE },
+ { "float", KEYWORD_STYLE },
+ { "for", KEYWORD_STYLE },
+ { "goto", KEYWORD_STYLE },
+ { "if", KEYWORD_STYLE },
+ { "int", KEYWORD_STYLE },
+ { "long", KEYWORD_STYLE },
+ { "register", KEYWORD_STYLE },
+ { "return", KEYWORD_STYLE },
+ { "short", KEYWORD_STYLE },
+ { "signed", KEYWORD_STYLE },
+ { "sizeof", KEYWORD_STYLE },
+ { "static", KEYWORD_STYLE },
+ { "struct", KEYWORD_STYLE },
+ { "switch", KEYWORD_STYLE },
+ { "typedef", KEYWORD_STYLE },
+ { "union", KEYWORD_STYLE },
+ { "unsigned", KEYWORD_STYLE },
+ { "void", KEYWORD_STYLE },
+ { "volatile", KEYWORD_STYLE },
+ { "while", KEYWORD_STYLE },
+
+/*
+ { "INLINE", MAMEWORD_STYLE },
+ { "INT8", MAMEWORD_STYLE },
+ { "UINT8", MAMEWORD_STYLE },
+ { "INT16", MAMEWORD_STYLE },
+ { "UINT16", MAMEWORD_STYLE },
+ { "INT32", MAMEWORD_STYLE },
+ { "UINT32", MAMEWORD_STYLE },
+ { "INT64", MAMEWORD_STYLE },
+ { "UINT64", MAMEWORD_STYLE },
+ { "ARRAY_LENGTH", MAMEWORD_STYLE },
+*/
+ { NULL, KEYWORD_STYLE }
+};
+
+
+
+/***************************************************************************
+ PROTOTYPES
+***************************************************************************/
+
+/* core output functions */
+static int recurse_dir(int srcrootlen, int dstrootlen, const astring *srcdir, const astring *dstdir);
+static int output_file(file_type type, int srcrootlen, int dstrootlen, const astring *srcfile, const astring *dstfile);
+
+/* HTML helpers */
+static core_file *create_file_and_output_header(const astring *filename, const char *title, const char *subtitle);
+static void output_footer_and_close_file(core_file *file);
+
+/* path helpers */
+static const astring *normalized_subpath(const astring *path, int start);
+static void output_path_as_links(core_file *file, const astring *path, int end_is_directory);
+static astring *find_include_file(int srcrootlen, int dstrootlen, const astring *srcfile, const astring *dstfile, const astring *filename);
+
+
+
+/***************************************************************************
+ MAIN
+***************************************************************************/
+
+/*-------------------------------------------------
+ main - main entry point
+-------------------------------------------------*/
+
+int main(int argc, char *argv[])
+{
+ astring *srcdir = NULL, *dstdir = NULL;
+ int unadorned = 0;
+ int result;
+ int argnum;
+
+ /* loop over arguments */
+ for (argnum = 1; argnum < argc; argnum++)
+ {
+ char *arg = argv[argnum];
+
+ /* include path? */
+ if (arg[0] == '-' && arg[1] == 'I')
+ {
+ *incpathhead = malloc(sizeof(**incpathhead));
+ if (*incpathhead != NULL)
+ {
+ (*incpathhead)->next = NULL;
+ (*incpathhead)->path = astring_replacechr(astring_dupc(&arg[2]), '/', PATH_SEPARATOR[0]);
+ incpathhead = &(*incpathhead)->next;
+ }
+ }
+
+ /* other parameter */
+ else if (arg[0] != '-' && unadorned == 0)
+ {
+ srcdir = astring_dupc(arg);
+ unadorned++;
+ }
+ else if (arg[0] != '-' && unadorned == 1)
+ {
+ dstdir = astring_dupc(arg);
+ unadorned++;
+ }
+ else
+ goto usage;
+ }
+
+ /* make sure we got 2 parameters */
+ if (srcdir == NULL || dstdir == NULL)
+ goto usage;
+
+ /* recurse over subdirectories */
+ result = recurse_dir(astring_len(srcdir), astring_len(dstdir), srcdir, dstdir);
+
+ /* free source and destination directories */
+ astring_free(srcdir);
+ astring_free(dstdir);
+ return result;
+
+usage:
+ fprintf(stderr, "Usage:\n%s <srcroot> <destroot> [-Iincpath [-Iincpath [...]]]\n", argv[0]);
+ return 1;
+}
+
+
+
+/***************************************************************************
+ CORE OUTPUT FUNCTIONS
+***************************************************************************/
+
+/*-------------------------------------------------
+ recurse_dir - recurse through a directory
+-------------------------------------------------*/
+
+static int recurse_dir(int srcrootlen, int dstrootlen, const astring *srcdir, const astring *dstdir)
+{
+ static const osd_dir_entry_type typelist[] = { ENTTYPE_DIR, ENTTYPE_FILE };
+ const astring *srcdir_subpath;
+ core_file *indexfile = NULL;
+ astring *indexname;
+ int result = 0;
+ int entindex;
+
+ /* extract a normalized subpath */
+ srcdir_subpath = normalized_subpath(srcdir, srcrootlen + 1);
+ if (srcdir_subpath == NULL)
+ return 1;
+
+ /* create an index file */
+ indexname = astring_alloc();
+ astring_printf(indexname, "%s%c%s", astring_c(dstdir), PATH_SEPARATOR[0], "index.html");
+ indexfile = create_file_and_output_header(indexname, "MAME Source Code", astring_c(srcdir_subpath));
+ astring_free(indexname);
+
+ /* output the directory navigation */
+ core_fprintf(indexfile, "<h3>Viewing Directory: ");
+ output_path_as_links(indexfile, srcdir_subpath, TRUE);
+ core_fprintf(indexfile, "</h3>");
+ astring_free((astring *)srcdir_subpath);
+
+ /* iterate first over directories, then over files */
+ for (entindex = 0; entindex < ARRAY_LENGTH(typelist) && result == 0; entindex++)
+ {
+ osd_dir_entry_type entry_type = typelist[entindex];
+ const osd_directory_entry *entry;
+ osd_directory *dir;
+ int found = 0;
+
+ /* open the directory and iterate through it */
+ dir = osd_opendir(astring_c(srcdir));
+ if (dir == NULL)
+ {
+ result = 1;
+ goto error;
+ }
+
+ /* iterate through each file */
+ while ((entry = osd_readdir(dir)) != NULL && result == 0)
+ if (entry->type == entry_type && entry->name[0] != '.')
+ {
+ astring *srcfile, *dstfile;
+
+ /* add a header */
+ if (++found == 1)
+ core_fprintf(indexfile, "\t<h2>%s</h2>\n\t<ul>\n", (entry_type == ENTTYPE_DIR) ? "Directories" : "Files");
+
+ /* build the source filename */
+ srcfile = astring_alloc();
+ astring_printf(srcfile, "%s%c%s", astring_c(srcdir), PATH_SEPARATOR[0], entry->name);
+
+ /* if we have a file, output it */
+ dstfile = astring_alloc();
+ if (entry->type == ENTTYPE_FILE)
+ {
+ file_type type = FILE_TYPE_INVALID;
+ int extnum;
+
+ /* make sure we care, first */
+ for (extnum = 0; extnum < ARRAY_LENGTH(extension_lookup); extnum++)
+ if (core_filename_ends_with(entry->name, extension_lookup[extnum].extension))
+ {
+ type = extension_lookup[extnum].type;
+ break;
+ }
+
+ /* if we got a valid file, process it */
+ if (type != FILE_TYPE_INVALID)
+ {
+ astring_printf(dstfile, "%s%c%s.html", astring_c(dstdir), PATH_SEPARATOR[0], entry->name);
+ if (indexfile != NULL)
+ core_fprintf(indexfile, "\t<li><a href=\"%s.html\">%s</a></li>\n", entry->name, entry->name);
+ result = output_file(type, srcrootlen, dstrootlen, srcfile, dstfile);
+ }
+ }
+
+ /* if we have a directory, recurse */
+ else
+ {
+ astring_printf(dstfile, "%s%c%s", astring_c(dstdir), PATH_SEPARATOR[0], entry->name);
+ if (indexfile != NULL)
+ core_fprintf(indexfile, "\t<li><a href=\"%s/index.html\">%s/</a></li>\n", entry->name, entry->name);
+ result = recurse_dir(srcrootlen, dstrootlen, srcfile, dstfile);
+ }
+
+ /* free memory for the names */
+ astring_free(srcfile);
+ astring_free(dstfile);
+ }
+
+ /* close the list if we found some stuff */
+ if (found > 0)
+ core_fprintf(indexfile, "\t</ul>\n");
+
+ /* close the directory */
+ osd_closedir(dir);
+ }
+
+error:
+ if (indexfile != NULL)
+ output_footer_and_close_file(indexfile);
+ return result;
+}
+
+
+/*-------------------------------------------------
+ output_file - output a file, converting to
+ HTML
+-------------------------------------------------*/
+
+static int output_file(file_type type, int srcrootlen, int dstrootlen, const astring *srcfile, const astring *dstfile)
+{
+ const char *comment_start, *comment_end, *comment_inline, *token_chars;
+ const char *comment_start_esc, *comment_end_esc, *comment_inline_esc;
+ const token_entry *token_table;
+ const astring *srcfile_subpath;
+ char srcline[4096], *srcptr;
+ int in_comment = FALSE;
+ UINT8 is_token[256];
+ int color_quotes;
+ core_file *src;
+ core_file *dst;
+ int toknum;
+
+ /* extract a normalized subpath */
+ srcfile_subpath = normalized_subpath(srcfile, srcrootlen + 1);
+ if (srcfile_subpath == NULL)
+ return 1;
+
+ fprintf(stderr, "Processing %s\n", astring_c(srcfile_subpath));
+
+ /* set some defaults */
+ color_quotes = FALSE;
+ comment_start = comment_start_esc = "";
+ comment_end = comment_end_esc = "";
+ comment_inline = comment_inline_esc = "";
+ token_chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_#";
+ token_table = dummy_token_table;
+
+ /* based on the file type, set the comment info */
+ switch (type)
+ {
+ case FILE_TYPE_C:
+ color_quotes = TRUE;
+ comment_start = comment_start_esc = "/*";
+ comment_end = comment_end_esc = "*/";
+ comment_inline = comment_inline_esc = "//";
+ token_table = c_token_table;
+ break;
+
+ case FILE_TYPE_MAKE:
+ color_quotes = TRUE;
+ comment_inline = comment_inline_esc = "#";
+ break;
+
+ case FILE_TYPE_XML:
+ color_quotes = TRUE;
+ comment_start = "<!--";
+ comment_start_esc = "&lt;!--";
+ comment_end = "-->";
+ comment_end_esc = "--&gt;";
+ break;
+
+ default:
+ case FILE_TYPE_TEXT:
+ break;
+ }
+
+ /* make the token lookup table */
+ memset(is_token, 0, sizeof(is_token));
+ for (toknum = 0; token_chars[toknum] != 0; toknum++)
+ is_token[(UINT8)token_chars[toknum]] = TRUE;
+
+ /* open the source file */
+ if (core_fopen(astring_c(srcfile), OPEN_FLAG_READ, &src) != FILERR_NONE)
+ {
+ fprintf(stderr, "Unable to read file '%s'\n", astring_c(srcfile));
+ return 1;
+ }
+
+ /* open the output file */
+ dst = create_file_and_output_header(dstfile, "MAME Source Code", astring_c(srcfile_subpath));
+ if (dst == NULL)
+ {
+ fprintf(stderr, "Unable to write file '%s'\n", astring_c(dstfile));
+ core_fclose(src);
+ return 1;
+ }
+
+ /* output the directory navigation */
+ core_fprintf(dst, "<h3>Viewing File: ");
+ output_path_as_links(dst, srcfile_subpath, FALSE);
+ core_fprintf(dst, "</h3>");
+ astring_free((astring *)srcfile_subpath);
+
+ /* start with some tags */
+ core_fprintf(dst, "\t<pre style=\"font-family:'Courier New','Courier',monospace; font-size:12px;\">\n");
+
+ /* iterate over lines in the source file */
+ while (core_fgets(srcline, ARRAY_LENGTH(srcline), src) != NULL)
+ {
+ char dstline[4096], *dstptr = dstline;
+ int in_inline_comment = FALSE;
+ int last_token_was_include = FALSE;
+ int last_was_token = FALSE;
+ int quotes_are_linked = FALSE;
+ char in_quotes = 0;
+ int curcol = 0;
+
+ /* iterate over characters in the source line */
+ for (srcptr = srcline; *srcptr != 0; )
+ {
+ UINT8 ch = *srcptr++;
+
+ /* track whether or not we are within an extended (C-style) comment */
+ if (!in_quotes && !in_inline_comment)
+ {
+ if (!in_comment && ch == comment_start[0] && strncmp(srcptr - 1, comment_start, strlen(comment_start)) == 0)
+ {
+ dstptr += sprintf(dstptr, "<span style=\"" COMMENT_STYLE "\">%s", comment_start_esc);
+ curcol += strlen(comment_start);
+ srcptr += strlen(comment_start) - 1;
+ ch = 0;
+ in_comment = TRUE;
+ }
+ else if (in_comment && ch == comment_end[0] && strncmp(srcptr - 1, comment_end, strlen(comment_end)) == 0)
+ {
+ dstptr += sprintf(dstptr, "%s</span>", comment_end_esc);
+ curcol += strlen(comment_end);
+ srcptr += strlen(comment_end) - 1;
+ ch = 0;
+ in_comment = FALSE;
+ }
+ }
+
+ /* track whether or not we are within an inline (C++-style) comment */
+ if (!in_quotes && !in_comment && !in_inline_comment && ch == comment_inline[0] && strncmp(srcptr - 1, comment_inline, strlen(comment_inline)) == 0)
+ {
+ dstptr += sprintf(dstptr, "<span style=\"" COMMENT_STYLE "\">%s", comment_inline_esc);
+ curcol += strlen(comment_inline);
+ srcptr += strlen(comment_inline) - 1;
+ ch = 0;
+ in_inline_comment = TRUE;
+ }
+
+ /* if this is the start of a new token, see if we want to color it */
+ if (!in_quotes && !in_comment && !in_inline_comment && !last_was_token && is_token[ch])
+ {
+ const token_entry *curtoken;
+ char *temp = srcptr;
+ int toklength;
+
+ /* find the end of the token */
+ while (*temp != 0 && is_token[(UINT8)*temp])
+ temp++;
+ toklength = temp - (srcptr - 1);
+
+ /* scan the token table */
+ last_token_was_include = FALSE;
+ for (curtoken = token_table; curtoken->token != NULL; curtoken++)
+ if (strncmp(srcptr - 1, curtoken->token, toklength) == 0 && strlen(curtoken->token) == toklength)
+ {
+ dstptr += sprintf(dstptr, "<span style=\"%s\">%s</span>", curtoken->color, curtoken->token);
+ curcol += strlen(curtoken->token);
+ srcptr += strlen(curtoken->token) - 1;
+ ch = 0;
+
+ /* look for include tokens specially */
+ if (type == FILE_TYPE_C && strcmp(curtoken->token, "#include") == 0)
+ last_token_was_include = TRUE;
+ break;
+ }
+ }
+ last_was_token = is_token[ch];
+
+ /* if we hit a tab, expand it */
+ if (ch == 0x09)
+ {
+ /* compute how many spaces */
+ int spaces = 4 - curcol % 4;
+ while (spaces--)
+ {
+ *dstptr++ = ' ';
+ curcol++;
+ }
+ }
+
+ /* otherwise, copy the source character */
+ else if (ch != 0x0a && ch != 0x0d && ch != 0)
+ {
+ /* track opening quotes */
+ if (!in_comment && !in_inline_comment && !in_quotes && (ch == '"' || ch == '\''))
+ {
+ if (color_quotes)
+ dstptr += sprintf(dstptr, "<span style=\"" STRING_STYLE "\">%c", ch);
+ else
+ *dstptr++ = ch;
+ in_quotes = ch;
+
+ /* handle includes */
+ if (last_token_was_include)
+ {
+ char *endquote = strchr(srcptr, ch);
+ if (endquote != NULL)
+ {
+ astring *filename = astring_dupch(srcptr, endquote - srcptr);
+ astring *target = find_include_file(srcrootlen, dstrootlen, srcfile, dstfile, filename);
+ if (target != NULL)
+ {
+ dstptr += sprintf(dstptr, "<a href=\"%s\">", astring_c(target));
+ quotes_are_linked = TRUE;
+ astring_free(target);
+ }
+ astring_free(filename);
+ }
+ }
+ }
+
+ /* track closing quotes */
+ else if (!in_comment && !in_inline_comment && in_quotes && ch == in_quotes && (type != FILE_TYPE_C || srcptr[-2] != '\\' || srcptr[-3] == '\\'))
+ {
+ if (quotes_are_linked)
+ dstptr += sprintf(dstptr, "</a>");
+ if (color_quotes)
+ dstptr += sprintf(dstptr, "%c</span>", ch);
+ else
+ *dstptr++ = ch;
+ in_quotes = 0;
+ quotes_are_linked = FALSE;
+ }
+
+ /* else just output the current character */
+ else if (ch == '&')
+ dstptr += sprintf(dstptr, "&amp;");
+ else if (ch == '<')
+ dstptr += sprintf(dstptr, "&lt;");
+ else if (ch == '>')
+ dstptr += sprintf(dstptr, "&gt;");
+ else
+ *dstptr++ = ch;
+ curcol++;
+ }
+ }
+
+ /* finish inline comments */
+ if (in_inline_comment)
+ {
+ dstptr += sprintf(dstptr, "</span>");
+ in_inline_comment = FALSE;
+ }
+
+ /* append a break and move on */
+ dstptr += sprintf(dstptr, "\n");
+ core_fputs(dst, dstline);
+ }
+
+ /* close tags */
+ core_fprintf(dst, "\t</pre>\n");
+
+ /* close the file */
+ output_footer_and_close_file(dst);
+ core_fclose(src);
+ return 0;
+}
+
+
+
+/***************************************************************************
+ HTML OUTPUT HELPERS
+***************************************************************************/
+
+/*-------------------------------------------------
+ create_file_and_output_header - create a new
+ HTML file with a standard header
+-------------------------------------------------*/
+
+static core_file *create_file_and_output_header(const astring *filename, const char *title, const char *subtitle)
+{
+ core_file *file;
+
+ /* create the indexfile */
+ if (core_fopen(astring_c(filename), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS | OPEN_FLAG_NO_BOM, &file) != FILERR_NONE)
+ return NULL;
+
+ /* print a header */
+ core_fprintf(file,
+ "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n"
+ "\n"
+ "<html xmlns=\"http://www.w3.org/1999/xhtml\">\n"
+ "<head>\n"
+ "\t<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" />\n"
+ "\t<title>%s</title>\n"
+ "\t<link rel=\"stylesheet\" href=\"http://mamedev.org/styles-site.css\" type=\"text/css\" />\n"
+ "</head>\n"
+ "\n"
+ "<body>\n"
+ "\t<div id=\"outer\">\n"
+ "\n"
+ "\t<div id=\"banner\">\n"
+ "\t<h1>%s</h1>\n"
+ "\t<h2>%s</h2>\n"
+ "\t</div>\n"
+ "\n"
+ "\t<div id=\"left\">\n"
+ "\t<div class=\"sidebar\">\n"
+ "\t<!--#include virtual=\"/links.txt\" -->\n"
+ "\t</div>\n"
+ "\t</div>\n"
+ "\n"
+ "\t<div id=\"center\">\n"
+ "\t<div class=\"content\">\n"
+ "\n",
+ title, title, (subtitle == NULL) ? "&nbsp;" : subtitle
+ );
+
+ /* return the file */
+ return file;
+}
+
+
+/*-------------------------------------------------
+ output_footer_and_close_file - write a
+ standard footer to an HTML file and close it
+-------------------------------------------------*/
+
+static void output_footer_and_close_file(core_file *file)
+{
+ core_fprintf(file,
+ "\n"
+ "\t</div>\n"
+ "\t</div>\n"
+ "\t</div>\n"
+ "</body>"
+ "\n"
+ "</html>\n"
+ );
+ core_fclose(file);
+}
+
+
+
+/***************************************************************************
+ HTML OUTPUT HELPERS
+***************************************************************************/
+
+/*-------------------------------------------------
+ normalized_subpath - normalize a path to
+ forward slashes and extract a subpath
+-------------------------------------------------*/
+
+static const astring *normalized_subpath(const astring *path, int start)
+{
+ astring *result = astring_dupsubstr(path, start, -1);
+ if (result != NULL)
+ astring_replacechr(result, PATH_SEPARATOR[0], '/');
+ return result;
+}
+
+
+/*-------------------------------------------------
+ output_path_as_links - output a path as a
+ series of links
+-------------------------------------------------*/
+
+static void output_path_as_links(core_file *file, const astring *path, int end_is_directory)
+{
+ astring *substr = astring_alloc();
+ int srcdepth, curdepth, depth;
+ int slashindex, lastslash;
+
+ /* first count how deep we are */
+ srcdepth = 0;
+ for (slashindex = astring_chr(path, 0, '/'); slashindex != -1; slashindex = astring_chr(path, slashindex + 1, '/'))
+ srcdepth++;
+ if (end_is_directory)
+ srcdepth++;
+
+ /* output a link to the root */
+ core_fprintf(file, "<a href=\"");
+ for (depth = 0; depth < srcdepth; depth++)
+ core_fprintf(file, "../");
+ core_fprintf(file, "index.html\">&lt;root&gt;</a>/");
+
+ /* now output links to each path up the chain */
+ curdepth = 0;
+ lastslash = 0;
+ for (slashindex = astring_chr(path, lastslash, '/'); slashindex != -1; slashindex = astring_chr(path, lastslash, '/'))
+ {
+ astring_cpysubstr(substr, path, lastslash, slashindex - lastslash);
+
+ curdepth++;
+ core_fprintf(file, "<a href=\"");
+ for (depth = curdepth; depth < srcdepth; depth++)
+ core_fprintf(file, "../");
+ core_fprintf(file, "index.html\">%s</a>/", astring_c(substr));
+
+ lastslash = slashindex + 1;
+ }
+
+ /* and a final link to the current directory */
+ astring_cpysubstr(substr, path, lastslash, -1);
+ if (end_is_directory)
+ core_fprintf(file, "<a href=\"index.html\">%s</a>", astring_c(substr));
+ else
+ core_fprintf(file, "<a href=\"%s.html\">%s</a>", astring_c(substr), astring_c(substr));
+
+ astring_free(substr);
+}
+
+
+/*-------------------------------------------------
+ find_include_file - find an include file
+-------------------------------------------------*/
+
+static astring *find_include_file(int srcrootlen, int dstrootlen, const astring *srcfile, const astring *dstfile, const astring *filename)
+{
+ include_path *curpath;
+
+ /* iterate over include paths and find the file */
+ for (curpath = incpaths; curpath != NULL; curpath = curpath->next)
+ {
+ astring *srcincpath = astring_cat(astring_dupsubstr(srcfile, 0, srcrootlen + 1), curpath->path);
+ core_file *testfile;
+ int lastsepindex = 0;
+ int sepindex;
+
+ /* a '.' include path is specially treated */
+ if (astring_cmpc(curpath->path, ".") == 0)
+ astring_cpysubstr(srcincpath, srcfile, 0, astring_rchr(srcfile, 0, PATH_SEPARATOR[0]));
+
+ /* append the filename piecemeal to account for directories */
+ while ((sepindex = astring_chr(filename, lastsepindex, '/')) != -1)
+ {
+ astring *pathpart = astring_dupsubstr(filename, lastsepindex, sepindex - lastsepindex);
+
+ /* handle .. by removing a chunk from the incpath */
+ if (astring_cmpc(pathpart, "..") == 0)
+ {
+ sepindex = astring_rchr(srcincpath, 0, PATH_SEPARATOR[0]);
+ if (sepindex != -1)
+ astring_substr(srcincpath, 0, sepindex);
+ }
+
+ /* otherwise, append a path separator and the pathpart */
+ else
+ astring_cat(astring_catc(srcincpath, PATH_SEPARATOR), pathpart);
+
+ /* advance past the previous index */
+ lastsepindex = sepindex + 1;
+
+ /* free the path part we extracted */
+ astring_free(pathpart);
+ }
+
+ /* now append the filename */
+ astring_catsubstr(astring_catc(srcincpath, PATH_SEPARATOR), filename, lastsepindex, -1);
+
+ /* see if we can open it */
+ if (core_fopen(astring_c(srcincpath), OPEN_FLAG_READ, &testfile) == FILERR_NONE)
+ {
+ astring *tempfile = astring_alloc();
+ astring *tempinc = astring_alloc();
+
+ /* close the file */
+ core_fclose(testfile);
+
+ /* find the longest matching directory substring between the include and source file */
+ lastsepindex = 0;
+ while ((sepindex = astring_chr(srcincpath, lastsepindex, PATH_SEPARATOR[0])) != -1)
+ {
+ /* get substrings up to the current directory */
+ astring_cpysubstr(tempfile, srcfile, 0, sepindex);
+ astring_cpysubstr(tempinc, srcincpath, 0, sepindex);
+
+ /* if we don't match, stop */
+ if (astring_cmp(tempfile, tempinc) != 0)
+ break;
+ lastsepindex = sepindex + 1;
+ }
+
+ /* chop off the common parts of the paths */
+ astring_cpysubstr(tempfile, srcfile, lastsepindex, -1);
+ astring_replacechr(astring_substr(srcincpath, lastsepindex, -1), PATH_SEPARATOR[0], '/');
+
+ /* for each directory left in the filename, we need to prepend a "../" */
+ while ((sepindex = astring_chr(tempfile, 0, PATH_SEPARATOR[0])) != -1)
+ {
+ astring_substr(tempfile, sepindex + 1, -1);
+ astring_insc(srcincpath, 0, "../");
+ }
+ astring_catc(srcincpath, ".html");
+
+ /* free the strings and return the include path */
+ astring_free(tempfile);
+ astring_free(tempinc);
+ return srcincpath;
+ }
+
+ /* free our include path */
+ astring_free(srcincpath);
+ }
+ return NULL;
+}
diff --git a/src/tools/srcclean.c b/src/tools/srcclean.c
new file mode 100644
index 00000000000..a0e137b6419
--- /dev/null
+++ b/src/tools/srcclean.c
@@ -0,0 +1,182 @@
+/***************************************************************************
+
+ srcclean.c
+
+ Basic source code cleanear.
+
+ Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+ Visit http://mamedev.org for licensing and usage restrictions.
+
+***************************************************************************/
+
+#include <stdio.h>
+#include <string.h>
+
+#include "corestr.h"
+#include "osdcore.h"
+
+
+/***************************************************************************
+ CONSTANTS & DEFINES
+***************************************************************************/
+
+#define MAX_FILE_SIZE (10 * 1024 * 1024)
+
+
+
+/***************************************************************************
+ GLOBAL VARIABLES
+***************************************************************************/
+
+static UINT8 original[MAX_FILE_SIZE];
+static UINT8 modified[MAX_FILE_SIZE];
+
+
+
+/***************************************************************************
+ MAIN
+***************************************************************************/
+
+int main(int argc, char *argv[])
+{
+ int removed_tabs = 0, removed_spaces = 0, fixed_mac_style = 0, fixed_nix_style = 0;
+ int src = 0, dst = 0, in_c_comment = FALSE, in_cpp_comment = FALSE;
+ int hichars = 0;
+ int is_c_file;
+ const char *ext;
+ FILE *file;
+ int bytes;
+
+ /* print usage info */
+ if (argc != 2)
+ {
+ printf("Usage:\nsrcclean <file>\n");
+ return 0;
+ }
+
+ /* read the file */
+ file = fopen(argv[1], "rb");
+ if (file == NULL)
+ {
+ fprintf(stderr, "Can't open %s\n", argv[1]);
+ return 1;
+ }
+ bytes = fread(original, 1, MAX_FILE_SIZE, file);
+ fclose(file);
+
+ /* determine if we are a C file */
+ ext = strrchr(argv[1], '.');
+ is_c_file = (ext && (core_stricmp(ext, ".c") == 0 || core_stricmp(ext, ".h") == 0 || core_stricmp(ext, ".cpp") == 0));
+
+ /* rip through it */
+ for (src = 0; src < bytes; )
+ {
+ UINT8 ch = original[src++];
+
+ /* check for invalid upper-ASCII chars */
+ if (ch != 13 && ch != 10 && ch != 9 && (ch > 127 || ch < 32))
+ {
+ ch = '?';
+ hichars++;
+ }
+
+ /* track whether or not we are within a C-style comment */
+ if (is_c_file && !in_cpp_comment)
+ {
+ if (!in_c_comment && ch == '/' && original[src] == '*')
+ in_c_comment = TRUE;
+ else if (in_c_comment && ch == '*' && original[src] == '/')
+ in_c_comment = FALSE;
+ }
+
+ /* track whether or not we are within a C++-style comment */
+ if (is_c_file && !in_c_comment && ch == '/' && original[src] == '/')
+ in_cpp_comment = TRUE;
+
+ /* if we hit a LF without a CR, back up and act like we hit a CR */
+ if (ch == 0x0a)
+ {
+ src--;
+ ch = 0x0d;
+ fixed_nix_style = 1;
+ }
+
+ /* if we hit a CR, clean up from there */
+ if (ch == 0x0d)
+ {
+ /* remove all extra spaces/tabs at the end */
+ while (dst > 0 && (modified[dst-1] == ' ' || modified[dst-1] == 0x09))
+ {
+ removed_spaces++;
+ dst--;
+ }
+
+ /* insert a proper CR/LF */
+ modified[dst++] = 0x0d;
+ modified[dst++] = 0x0a;
+
+ /* skip over any LF in the source file */
+ if (original[src] == 0x0a)
+ src++;
+ else
+ fixed_mac_style = 1;
+
+ /* we are no longer in a C++-style comment */
+ in_cpp_comment = FALSE;
+ }
+
+ /* if we hit a tab within a comment, expand it */
+ else if (ch == 0x09 && (in_c_comment || in_cpp_comment))
+ {
+ int temp, col;
+
+ /* scan backwards to find the start of line */
+ for (temp = dst; temp >= 0; temp--)
+ if (modified[temp] == 0x0a)
+ break;
+
+ /* scan forwards to compute the current column */
+ for (temp++, col = 0; temp < dst; temp++)
+ if (modified[temp] == 0x09)
+ col += 4 - col % 4;
+ else
+ col++;
+
+ /* compute how many spaces */
+ col = 4 - col % 4;
+ while (col--) modified[dst++] = ' ';
+ removed_tabs++;
+ }
+
+ /* otherwise, copy the source character */
+ else
+ modified[dst++] = ch;
+ }
+
+ /* if we didn't find an end of comment, we screwed up */
+ if (in_c_comment)
+ {
+ printf("Error: unmatched C-style comment (%s)!\n", argv[1]);
+ return 1;
+ }
+
+ /* if the result == original, skip it */
+ if (dst != bytes || memcmp(original, modified, bytes))
+ {
+ /* explain what we did */
+ printf("Cleaned up %s:", argv[1]);
+ if (removed_spaces) printf(" removed %d spaces", removed_spaces);
+ if (removed_tabs) printf(" removed %d tabs", removed_tabs);
+ if (hichars) printf(" fixed %d high-ASCII chars", hichars);
+ if (fixed_nix_style) printf(" fixed *nix-style line-ends");
+ if (fixed_mac_style) printf(" fixed Mac-style line-ends");
+ printf("\n");
+
+ /* write the file */
+ file = fopen(argv[1], "wb");
+ fwrite(modified, 1, dst, file);
+ fclose(file);
+ }
+
+ return 0;
+}
diff --git a/src/tools/tools.mak b/src/tools/tools.mak
new file mode 100644
index 00000000000..4afe12a1625
--- /dev/null
+++ b/src/tools/tools.mak
@@ -0,0 +1,124 @@
+###########################################################################
+#
+# tools.mak
+#
+# MAME tools makefile
+#
+# Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team.
+# Visit http://mamedev.org for licensing and usage restrictions.
+#
+###########################################################################
+
+
+TOOLSSRC = $(SRC)/tools
+TOOLSOBJ = $(OBJ)/tools
+
+OBJDIRS += \
+ $(TOOLSOBJ) \
+
+
+
+#-------------------------------------------------
+# set of tool targets
+#-------------------------------------------------
+
+TOOLS += \
+ romcmp$(EXE) \
+ chdman$(EXE) \
+ jedutil$(EXE) \
+ makemeta$(EXE) \
+ regrep$(EXE) \
+ srcclean$(EXE) \
+ src2html$(EXE) \
+
+
+
+#-------------------------------------------------
+# romcmp
+#-------------------------------------------------
+
+ROMCMPOBJS = \
+ $(TOOLSOBJ)/romcmp.o \
+
+romcmp$(EXE): $(ROMCMPOBJS) $(LIBUTIL) $(ZLIB) $(EXPAT) $(LIBOCORE)
+ @echo Linking $@...
+ $(LD) $(LDFLAGS) $^ $(LIBS) -o $@
+
+
+
+#-------------------------------------------------
+# chdman
+#-------------------------------------------------
+
+CHDMANOBJS = \
+ $(TOOLSOBJ)/chdman.o \
+ $(TOOLSOBJ)/chdcd.o \
+
+chdman$(EXE): $(VERSIONOBJ) $(CHDMANOBJS) $(LIBUTIL) $(ZLIB) $(EXPAT) $(LIBOCORE)
+ @echo Linking $@...
+ $(LD) $(LDFLAGS) $^ $(LIBS) -o $@
+
+
+
+#-------------------------------------------------
+# jedutil
+#-------------------------------------------------
+
+JEDUTILOBJS = \
+ $(TOOLSOBJ)/jedutil.o \
+
+jedutil$(EXE): $(JEDUTILOBJS) $(LIBUTIL) $(LIBOCORE) $(ZLIB) $(EXPAT)
+ @echo Linking $@...
+ $(LD) $(LDFLAGS) $^ $(LIBS) -o $@
+
+
+
+#-------------------------------------------------
+# makemeta
+#-------------------------------------------------
+
+MAKEMETAOBJS = \
+ $(TOOLSOBJ)/makemeta.o \
+
+makemeta$(EXE): $(MAKEMETAOBJS) $(LIBUTIL) $(LIBOCORE) $(ZLIB) $(EXPAT)
+ @echo Linking $@...
+ $(LD) $(LDFLAGS) $^ $(LIBS) -o $@
+
+
+
+#-------------------------------------------------
+# regrep
+#-------------------------------------------------
+
+REGREPOBJS = \
+ $(TOOLSOBJ)/regrep.o \
+
+regrep$(EXE): $(REGREPOBJS) $(LIBUTIL) $(LIBOCORE) $(ZLIB) $(EXPAT)
+ @echo Linking $@...
+ $(LD) $(LDFLAGS) $^ $(LIBS) -o $@
+
+
+
+#-------------------------------------------------
+# srcclean
+#-------------------------------------------------
+
+SRCCLEANOBJS = \
+ $(TOOLSOBJ)/srcclean.o \
+
+srcclean$(EXE): $(SRCCLEANOBJS) $(LIBUTIL) $(LIBOCORE) $(ZLIB) $(EXPAT)
+ @echo Linking $@...
+ $(LD) $(LDFLAGS) $^ $(LIBS) -o $@
+
+
+
+#-------------------------------------------------
+# src2html
+#-------------------------------------------------
+
+SRC2HTMLOBJS = \
+ $(TOOLSOBJ)/src2html.o \
+
+src2html$(EXE): $(SRC2HTMLOBJS) $(LIBUTIL) $(LIBOCORE) $(ZLIB) $(EXPAT)
+ @echo Linking $@...
+ $(LD) $(LDFLAGS) $^ $(LIBS) -o $@