diff options
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 |
commit | 7b77f1218624ea26dbb2efd85a19f795f5d4e02e (patch) | |
tree | 19209304095572b4fd61c2a2d6a5aa75c4e471ad /src/tools | |
parent | 3da7f476068b3ffef713218ba2fc1bd5030f2c38 (diff) |
Initial checkin of MAME 0.121.mame0121
Diffstat (limited to 'src/tools')
-rw-r--r-- | src/tools/chdcd.c | 283 | ||||
-rw-r--r-- | src/tools/chdcd.h | 28 | ||||
-rw-r--r-- | src/tools/chdman.c | 2629 | ||||
-rw-r--r-- | src/tools/jedutil.c | 289 | ||||
-rw-r--r-- | src/tools/makemeta.c | 587 | ||||
-rw-r--r-- | src/tools/regrep.c | 1181 | ||||
-rw-r--r-- | src/tools/romcmp.c | 748 | ||||
-rw-r--r-- | src/tools/runtest.cmd | 156 | ||||
-rw-r--r-- | src/tools/src2html.c | 860 | ||||
-rw-r--r-- | src/tools/srcclean.c | 182 | ||||
-rw-r--r-- | src/tools/tools.mak | 124 |
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) ? " " : 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™ 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 = "<!--"; + comment_end = "-->"; + comment_end_esc = "-->"; + 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, "&"); + else if (ch == '<') + dstptr += sprintf(dstptr, "<"); + else if (ch == '>') + dstptr += sprintf(dstptr, ">"); + 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) ? " " : 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\"><root></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 $@ |