From 7b77f1218624ea26dbb2efd85a19f795f5d4e02e Mon Sep 17 00:00:00 2001 From: Aaron Giles Date: Mon, 17 Dec 2007 15:19:59 +0000 Subject: Initial checkin of MAME 0.121. --- src/tools/chdcd.c | 283 ++++++ src/tools/chdcd.h | 28 + src/tools/chdman.c | 2629 +++++++++++++++++++++++++++++++++++++++++++++++++ src/tools/jedutil.c | 289 ++++++ src/tools/makemeta.c | 587 +++++++++++ src/tools/regrep.c | 1181 ++++++++++++++++++++++ src/tools/romcmp.c | 748 ++++++++++++++ src/tools/runtest.cmd | 156 +++ src/tools/src2html.c | 860 ++++++++++++++++ src/tools/srcclean.c | 182 ++++ src/tools/tools.mak | 124 +++ 11 files changed, 7067 insertions(+) create mode 100644 src/tools/chdcd.c create mode 100644 src/tools/chdcd.h create mode 100644 src/tools/chdman.c create mode 100644 src/tools/jedutil.c create mode 100644 src/tools/makemeta.c create mode 100644 src/tools/regrep.c create mode 100644 src/tools/romcmp.c create mode 100644 src/tools/runtest.cmd create mode 100644 src/tools/src2html.c create mode 100644 src/tools/srcclean.c create mode 100644 src/tools/tools.mak (limited to 'src/tools') 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 + + + +/*************************************************************************** + 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 +#include +#include +#include + + +/*************************************************************************** + 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 +#include +#include +#include +#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 [fuses] -- convert JED to binary form\n" + " jedutil -- 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 +#include +#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] [