src/tools/chdman.c
| r14481 | r14482 | |
| 39 | 39 | #include "corefile.h" |
| 40 | 40 | #include "chdcd.h" |
| 41 | 41 | #include "aviio.h" |
| 42 | | #include "avcomp.h" |
| 42 | #include "avhuff.h" |
| 43 | 43 | #include "bitmap.h" |
| 44 | 44 | #include "md5.h" |
| 45 | 45 | #include "sha1.h" |
| 46 | 46 | #include "vbiparse.h" |
| 47 | #include "tagmap.h" |
| 47 | 48 | #include <stdarg.h> |
| 48 | 49 | #include <stdlib.h> |
| 49 | 50 | #include <stdio.h> |
| r14481 | r14482 | |
| 51 | 52 | #include <ctype.h> |
| 52 | 53 | #include <new> |
| 53 | 54 | |
| 54 | | |
| 55 | | /*************************************************************************** |
| 56 | | CONSTANTS & DEFINES |
| 57 | | ***************************************************************************/ |
| 58 | | |
| 59 | | #define IDE_SECTOR_SIZE 512 |
| 60 | | |
| 61 | | #define ENABLE_CUSTOM_CHOMP 0 |
| 62 | | |
| 63 | | #define OPERATION_UPDATE 0 |
| 64 | | #define OPERATION_MERGE 1 |
| 65 | | #define OPERATION_CHOMP 2 |
| 66 | | |
| 67 | | #ifdef PTR64 |
| 68 | | #define IS_FAKE_AVI_FILE(a) ((UINT64)(a) <= 2) |
| 55 | #ifdef _WIN32 |
| 56 | #define NUM_PROCESSORS_SUPPORTED 1 |
| 69 | 57 | #else |
| 70 | | #define IS_FAKE_AVI_FILE(a) ((UINT32)(a) <= 2) |
| 58 | #define NUM_PROCESSORS_SUPPORTED 0 |
| 71 | 59 | #endif |
| 72 | | #define AVI_FAKE_FILE_22 ((avi_file *)1) |
| 73 | | #define AVI_FAKE_FILE_32 ((avi_file *)2) |
| 74 | 60 | |
| 75 | | #define AVI_FAKE_FRAMERATE 29970000 |
| 76 | | #define AVI_FAKE_FRAMES 54004 |
| 77 | | #define AVI_FAKE_WIDTH 720 |
| 78 | | #define AVI_FAKE_HEIGHT 524 |
| 79 | | #define AVI_FAKE_CHANNELS 2 |
| 80 | | #define AVI_FAKE_SAMPLERATE 48000 |
| 81 | 61 | |
| 62 | //************************************************************************** |
| 63 | // CONSTANTS & DEFINES |
| 64 | //************************************************************************** |
| 82 | 65 | |
| 66 | // default hard disk sector size |
| 67 | const UINT32 IDE_SECTOR_SIZE = 512; |
| 83 | 68 | |
| 84 | | /*************************************************************************** |
| 85 | | TYPE DEFINITIONS |
| 86 | | ***************************************************************************/ |
| 69 | // temporary input buffer size |
| 70 | const UINT32 TEMP_BUFFER_SIZE = 32 * 1024 * 1024; |
| 87 | 71 | |
| 88 | | struct _chd_interface_file |
| 89 | | { |
| 90 | | osd_file *file; |
| 91 | | UINT64 length; |
| 92 | | }; |
| 72 | // command modifier |
| 73 | #define REQUIRED "~" |
| 93 | 74 | |
| 75 | // command strings |
| 76 | #define COMMAND_HELP "help" |
| 77 | #define COMMAND_INFO "info" |
| 78 | #define COMMAND_VERIFY "verify" |
| 79 | #define COMMAND_CREATE_RAW "createraw" |
| 80 | #define COMMAND_CREATE_HD "createhd" |
| 81 | #define COMMAND_CREATE_CD "createcd" |
| 82 | #define COMMAND_CREATE_LD "createld" |
| 83 | #define COMMAND_EXTRACT_RAW "extractraw" |
| 84 | #define COMMAND_EXTRACT_HD "extracthd" |
| 85 | #define COMMAND_EXTRACT_CD "extractcd" |
| 86 | #define COMMAND_EXTRACT_LD "extractld" |
| 87 | #define COMMAND_COPY "copy" |
| 88 | #define COMMAND_ADD_METADATA "addmeta" |
| 89 | #define COMMAND_DEL_METADATA "delmeta" |
| 94 | 90 | |
| 91 | // option strings |
| 92 | #define OPTION_INPUT "input" |
| 93 | #define OPTION_OUTPUT "output" |
| 94 | #define OPTION_OUTPUT_BIN "outputbin" |
| 95 | #define OPTION_OUTPUT_FORCE "force" |
| 96 | #define OPTION_INPUT_START_BYTE "inputstartbyte" |
| 97 | #define OPTION_INPUT_START_HUNK "inputstarthunk" |
| 98 | #define OPTION_INPUT_START_FRAME "inputstartframe" |
| 99 | #define OPTION_INPUT_LENGTH_BYTES "inputbytes" |
| 100 | #define OPTION_INPUT_LENGTH_HUNKS "inputhunks" |
| 101 | #define OPTION_INPUT_LENGTH_FRAMES "inputframes" |
| 102 | #define OPTION_HUNK_SIZE "hunksize" |
| 103 | #define OPTION_UNIT_SIZE "unitsize" |
| 104 | #define OPTION_COMPRESSION "compression" |
| 105 | #define OPTION_INPUT_PARENT "inputparent" |
| 106 | #define OPTION_OUTPUT_PARENT "outputparent" |
| 107 | #define OPTION_IDENT "ident" |
| 108 | #define OPTION_CHS "chs" |
| 109 | #define OPTION_SECTOR_SIZE "sectorsize" |
| 110 | #define OPTION_TAG "tag" |
| 111 | #define OPTION_INDEX "index" |
| 112 | #define OPTION_VALUE_TEXT "valuetext" |
| 113 | #define OPTION_VALUE_FILE "valuefile" |
| 114 | #define OPTION_NO_CHECKSUM "nochecksum" |
| 115 | #define OPTION_VERBOSE "verbose" |
| 116 | #define OPTION_FIX "fix" |
| 117 | #define OPTION_NUMPROCESSORS "numprocessors" |
| 95 | 118 | |
| 96 | | /*************************************************************************** |
| 97 | | FUNCTION PROTOTYPES |
| 98 | | ***************************************************************************/ |
| 99 | 119 | |
| 100 | | static chd_error chdman_compress_file(chd_file *chd, const char *rawfile, UINT32 offset); |
| 101 | | static chd_error chdman_compress_chd(chd_file *chd, chd_file *source, UINT32 totalhunks); |
| 102 | | static chd_error chdman_clone_metadata(chd_file *source, chd_file *dest); |
| 103 | 120 | |
| 121 | //************************************************************************** |
| 122 | // FUNCTION PROTOTYPES |
| 123 | //************************************************************************** |
| 104 | 124 | |
| 125 | typedef tagmap_t<astring *> parameters_t; |
| 105 | 126 | |
| 106 | | /*************************************************************************** |
| 107 | | GLOBAL VARIABLES |
| 108 | | ***************************************************************************/ |
| 127 | static void report_error(int error, const char *format, ...); |
| 128 | static void do_info(parameters_t ¶ms); |
| 129 | static void do_verify(parameters_t ¶ms); |
| 130 | static void do_create_raw(parameters_t ¶ms); |
| 131 | static void do_create_hd(parameters_t ¶ms); |
| 132 | static void do_create_cd(parameters_t ¶ms); |
| 133 | static void do_create_ld(parameters_t ¶ms); |
| 134 | static void do_copy(parameters_t ¶ms); |
| 135 | static void do_extract_raw(parameters_t ¶ms); |
| 136 | static void do_extract_cd(parameters_t ¶ms); |
| 137 | static void do_extract_ld(parameters_t ¶ms); |
| 138 | static void do_add_metadata(parameters_t ¶ms); |
| 139 | static void do_del_metadata(parameters_t ¶ms); |
| 109 | 140 | |
| 110 | | static clock_t lastprogress = 0; |
| 111 | 141 | |
| 112 | 142 | |
| 143 | //************************************************************************** |
| 144 | // TYPE DEFINITIONS |
| 145 | //************************************************************************** |
| 113 | 146 | |
| 114 | | /*************************************************************************** |
| 115 | | IMPLEMENTATION |
| 116 | | ***************************************************************************/ |
| 147 | // ======================> option_description |
| 117 | 148 | |
| 118 | | /*------------------------------------------------- |
| 119 | | print_big_int - 64-bit int printing with |
| 120 | | commas |
| 121 | | -------------------------------------------------*/ |
| 122 | | |
| 123 | | static void print_big_int(UINT64 intvalue, char *output) |
| 149 | struct option_description |
| 124 | 150 | { |
| 125 | | int chunk; |
| 151 | const char *name; |
| 152 | const char *shortname; |
| 153 | bool parameter; |
| 154 | const char *description; |
| 155 | }; |
| 126 | 156 | |
| 127 | | chunk = intvalue % 1000; |
| 128 | | intvalue /= 1000; |
| 129 | | if (intvalue != 0) |
| 130 | | { |
| 131 | | print_big_int(intvalue, output); |
| 132 | | strcat(output, ","); |
| 133 | | sprintf(&output[strlen(output)], "%03d", chunk); |
| 134 | | } |
| 135 | | else |
| 136 | | sprintf(&output[strlen(output)], "%d", chunk); |
| 137 | | } |
| 138 | 157 | |
| 158 | // ======================> command_description |
| 139 | 159 | |
| 140 | | /*------------------------------------------------- |
| 141 | | big_int_string - return a string for a big |
| 142 | | integer |
| 143 | | -------------------------------------------------*/ |
| 144 | | |
| 145 | | static char *big_int_string(UINT64 intvalue) |
| 160 | struct command_description |
| 146 | 161 | { |
| 147 | | static char buffer[256]; |
| 148 | | buffer[0] = 0; |
| 149 | | print_big_int(intvalue, buffer); |
| 150 | | return buffer; |
| 151 | | } |
| 162 | const char *name; |
| 163 | void (*handler)(parameters_t &); |
| 164 | const char *description; |
| 165 | const char *valid_options[16]; |
| 166 | }; |
| 152 | 167 | |
| 153 | 168 | |
| 154 | | /*------------------------------------------------- |
| 155 | | progress - generic progress callback |
| 156 | | -------------------------------------------------*/ |
| 169 | // ======================> avi_info |
| 157 | 170 | |
| 158 | | static void ATTR_PRINTF(2,3) progress(int forceit, const char *fmt, ...) |
| 171 | struct avi_info |
| 159 | 172 | { |
| 160 | | clock_t curtime = clock(); |
| 161 | | va_list arg; |
| 173 | UINT32 fps_times_1million; |
| 174 | UINT32 width; |
| 175 | UINT32 height; |
| 176 | bool interlaced; |
| 177 | UINT32 channels; |
| 178 | UINT32 rate; |
| 179 | UINT32 max_samples_per_frame; |
| 180 | UINT32 bytes_per_frame; |
| 181 | }; |
| 162 | 182 | |
| 163 | | /* skip if it hasn't been long enough */ |
| 164 | | if (!forceit && curtime - lastprogress < CLOCKS_PER_SEC / 2) |
| 165 | | return; |
| 166 | | lastprogress = curtime; |
| 167 | 183 | |
| 168 | | /* standard vfprintf stuff here */ |
| 169 | | va_start(arg, fmt); |
| 170 | | vfprintf(stderr, fmt, arg); |
| 171 | | fflush(stderr); |
| 172 | | va_end(arg); |
| 173 | | } |
| 184 | // ======================> metadata_index_info |
| 174 | 185 | |
| 175 | | |
| 176 | | /*------------------------------------------------- |
| 177 | | usage - generic usage error display |
| 178 | | -------------------------------------------------*/ |
| 179 | | |
| 180 | | static int usage(void) |
| 186 | struct metadata_index_info |
| 181 | 187 | { |
| 182 | | printf("usage: chdman -info input.chd\n"); |
| 183 | | printf(" or: chdman -createraw inputhd.raw output.chd [inputoffs [hunksize]]\n"); |
| 184 | | printf(" or: chdman -createhd inputhd.raw output.chd [ident.bin] [inputoffs [cylinders heads sectors [sectorsize [hunksize]]]]\n"); |
| 185 | | printf(" or: chdman -createuncomphd inputhd.raw output.chd [ident.bin] [inputoffs [cylinders heads sectors [sectorsize [hunksize]]]]\n"); |
| 186 | | printf(" or: chdman -createblankhd output.chd cylinders heads sectors [sectorsize [hunksize]]\n"); |
| 187 | | printf(" or: chdman -createcd input.(toc/cue/nrg/gdi) output.chd\n"); |
| 188 | | printf(" or: chdman -createav input.avi output.chd [firstframe [numframes]]\n"); |
| 189 | | printf(" or: chdman -copydata input.chd output.chd\n"); |
| 190 | | printf(" or: chdman -extract input.chd output.raw\n"); |
| 191 | | printf(" or: chdman -extractcd input.chd output.(toc/cue) output.bin (if toc, no extension if cue)\n"); |
| 192 | | printf(" or: chdman -extractav input.chd output.avi [firstframe [numframes]]\n"); |
| 193 | | printf(" or: chdman -verify input.chd\n"); |
| 194 | | printf(" or: chdman -verifyfix input.chd\n"); |
| 195 | | printf(" or: chdman -update input.chd output.chd\n"); |
| 196 | | printf(" or: chdman -chomp inout.chd output.chd maxhunk\n"); |
| 197 | | printf(" or: chdman -merge parent.chd diff.chd output.chd\n"); |
| 198 | | printf(" or: chdman -diff parent.chd compare.chd diff.chd\n"); |
| 199 | | printf(" or: chdman -setchs inout.chd cylinders heads sectors\n"); |
| 200 | | printf(" or: chdman -fixavdata inout.chd\n"); |
| 201 | | printf(" or: chdman -addmetabin inout.chd tag [index] sourcefile\n"); |
| 202 | | printf(" or: chdman -addmetatext inout.chd tag [index] sourcefile\n"); |
| 203 | | return 1; |
| 204 | | } |
| 188 | chd_metadata_tag tag; |
| 189 | UINT32 index; |
| 190 | }; |
| 205 | 191 | |
| 206 | 192 | |
| 207 | | /*------------------------------------------------- |
| 208 | | get_file_size - get the size of a file |
| 209 | | -------------------------------------------------*/ |
| 193 | // ======================> fatal_error |
| 210 | 194 | |
| 211 | | static UINT64 get_file_size(const char *filename) |
| 195 | class fatal_error : public std::exception |
| 212 | 196 | { |
| 213 | | osd_file *file; |
| 214 | | UINT64 filesize = 0; |
| 215 | | file_error filerr; |
| 197 | public: |
| 198 | fatal_error(int error) |
| 199 | : m_error(error) { } |
| 216 | 200 | |
| 217 | | filerr = osd_open(filename, OPEN_FLAG_READ, &file, &filesize); |
| 218 | | if (filerr == FILERR_NONE) |
| 219 | | osd_close(file); |
| 220 | | return filesize; |
| 221 | | } |
| 201 | int error() const { return m_error; } |
| 222 | 202 | |
| 203 | private: |
| 204 | int m_error; |
| 205 | }; |
| 223 | 206 | |
| 224 | | /*------------------------------------------------- |
| 225 | | guess_chs - given a file and an offset, |
| 226 | | compute a best guess CHS value set |
| 227 | | -------------------------------------------------*/ |
| 228 | 207 | |
| 229 | | static chd_error guess_chs(const char *filename, int offset, int sectorsize, UINT32 *cylinders, UINT32 *heads, UINT32 *sectors, UINT32 *bps) |
| 230 | | { |
| 231 | | UINT32 totalsecs, hds, secs; |
| 232 | | UINT64 filesize; |
| 208 | // ======================> chd_rawfile_compressor |
| 233 | 209 | |
| 234 | | /* if this is a direct physical drive read, handle it specially */ |
| 235 | | if (osd_get_physical_drive_geometry(filename, cylinders, heads, sectors, bps)) |
| 236 | | return CHDERR_NONE; |
| 237 | | |
| 238 | | /* compute the filesize */ |
| 239 | | filesize = get_file_size(filename); |
| 240 | | if (filesize <= offset) |
| 241 | | { |
| 242 | | fprintf(stderr, "Invalid file '%s'\n", filename); |
| 243 | | return CHDERR_INVALID_FILE; |
| 244 | | } |
| 245 | | filesize -= offset; |
| 246 | | |
| 247 | | /* validate the size */ |
| 248 | | if (filesize % sectorsize != 0) |
| 249 | | { |
| 250 | | fprintf(stderr, "Can't guess CHS values because data size is not divisible by the sector size\n"); |
| 251 | | return CHDERR_INVALID_FILE; |
| 252 | | } |
| 253 | | totalsecs = filesize / sectorsize; |
| 254 | | |
| 255 | | /* now find a valid value */ |
| 256 | | for (secs = 63; secs > 1; secs--) |
| 257 | | if (totalsecs % secs == 0) |
| 258 | | { |
| 259 | | size_t totalhds = totalsecs / secs; |
| 260 | | for (hds = 16; hds > 1; hds--) |
| 261 | | if (totalhds % hds == 0) |
| 262 | | { |
| 263 | | *cylinders = totalhds / hds; |
| 264 | | *heads = hds; |
| 265 | | *sectors = secs; |
| 266 | | *bps = sectorsize; |
| 267 | | return CHDERR_NONE; |
| 268 | | } |
| 269 | | } |
| 270 | | |
| 271 | | /* ack, it didn't work! */ |
| 272 | | fprintf(stderr, "Can't guess CHS values because no logical combination works!\n"); |
| 273 | | return CHDERR_INVALID_FILE; |
| 274 | | } |
| 275 | | |
| 276 | | |
| 277 | | /*------------------------------------------------- |
| 278 | | get_chs_from_ident - extract chs from an ident |
| 279 | | information, validate it with the file size |
| 280 | | |
| 281 | | Note: limited to IDE for now |
| 282 | | -------------------------------------------------*/ |
| 283 | | |
| 284 | | static chd_error get_chs_from_ident(const char *filename, int offset, const UINT8 *ident, UINT32 identsize, UINT32 *cylinders, UINT32 *heads, UINT32 *sectors, UINT32 *bps) |
| 210 | class chd_rawfile_compressor : public chd_file_compressor |
| 285 | 211 | { |
| 286 | | UINT64 filesize, expected_size; |
| 287 | | |
| 288 | | filesize = get_file_size(filename); |
| 289 | | if (filesize <= offset) |
| 212 | public: |
| 213 | // construction/destruction |
| 214 | chd_rawfile_compressor(core_file *file, UINT64 offset = 0, UINT64 maxoffset = ~0) |
| 215 | : m_file(file), |
| 216 | m_offset(offset), |
| 217 | m_maxoffset(MIN(maxoffset, (file != NULL) ? core_fsize(file) : 0)) { } |
| 218 | |
| 219 | // read interface |
| 220 | virtual UINT32 read_data(void *dest, UINT64 offset, UINT32 length) |
| 290 | 221 | { |
| 291 | | fprintf(stderr, "Invalid file '%s'\n", filename); |
| 292 | | return CHDERR_INVALID_FILE; |
| 222 | offset += m_offset; |
| 223 | if (offset >= m_maxoffset) |
| 224 | return 0; |
| 225 | if (offset + length > m_maxoffset) |
| 226 | length = m_maxoffset - offset; |
| 227 | core_fseek(m_file, offset, SEEK_SET); |
| 228 | return core_fread(m_file, dest, length); |
| 293 | 229 | } |
| 294 | 230 | |
| 295 | | if (filesize % 512 != 0) |
| 296 | | { |
| 297 | | fprintf(stderr, "Can't validate CHS values because data size is not divisible by the sector size\n"); |
| 298 | | return CHDERR_INVALID_FILE; |
| 299 | | } |
| 231 | private: |
| 232 | // internal state |
| 233 | core_file * m_file; |
| 234 | UINT64 m_offset; |
| 235 | UINT64 m_maxoffset; |
| 236 | }; |
| 300 | 237 | |
| 301 | | if (identsize < 14) |
| 302 | | { |
| 303 | | fprintf(stderr, "Error: the ident metadata is too short to include geometry information\n"); |
| 304 | | return CHDERR_INVALID_FILE; |
| 305 | | } |
| 306 | 238 | |
| 307 | | *bps = 512; |
| 308 | | *cylinders = ((unsigned char)ident[ 2]) | (((unsigned char )ident[ 3]) << 8); |
| 309 | | *heads = ((unsigned char)ident[ 6]) | (((unsigned char )ident[ 7]) << 8); |
| 310 | | *sectors = ((unsigned char)ident[12]) | (((unsigned char )ident[13]) << 8); |
| 239 | // ======================> chd_chdfile_compressor |
| 311 | 240 | |
| 312 | | expected_size = (UINT64)*cylinders * (UINT64)*heads * (UINT64)*sectors * 512; |
| 313 | | if (expected_size != filesize - offset) |
| 314 | | { |
| 315 | | fprintf(stderr, "Error: Mismatch between the ident CHS data (%u/%u/%u, %u sectors) and the file size (%u sectors)\n", |
| 316 | | *cylinders, *heads, *sectors, *cylinders * *heads * *sectors, |
| 317 | | (UINT32)((filesize-offset)/512)); |
| 318 | | return CHDERR_INVALID_FILE; |
| 319 | | } |
| 320 | | return CHDERR_NONE; |
| 321 | | } |
| 322 | | |
| 323 | | |
| 324 | | /*------------------------------------------------- |
| 325 | | do_createhd - create a new compressed hard |
| 326 | | disk image from a raw file |
| 327 | | -------------------------------------------------*/ |
| 328 | | |
| 329 | | static int do_createhd(int argc, char *argv[], int param) |
| 241 | class chd_chdfile_compressor : public chd_file_compressor |
| 330 | 242 | { |
| 331 | | UINT32 guess_cylinders = 0, guess_heads = 0, guess_sectors = 0, guess_sectorsize = 0; |
| 332 | | UINT32 cylinders, heads, sectors, sectorsize, hunksize, totalsectors, offset; |
| 333 | | const char *inputfile, *outputfile = NULL; |
| 334 | | chd_error err = CHDERR_NONE; |
| 335 | | UINT32 identdatasize = 0; |
| 336 | | UINT8 *identdata = NULL; |
| 337 | | chd_file *chd = NULL; |
| 338 | | char metadata[256]; |
| 243 | public: |
| 244 | // construction/destruction |
| 245 | chd_chdfile_compressor(chd_file &file, UINT64 offset = 0, UINT64 maxoffset = ~0) |
| 246 | : m_file(file), |
| 247 | m_offset(offset), |
| 248 | m_maxoffset(MIN(maxoffset, file.logical_bytes())) { } |
| 339 | 249 | |
| 340 | | /* if a file is provided for argument 4 (ident filename), then shift the remaining arguments down */ |
| 341 | | if (argc >= 5) |
| 250 | // read interface |
| 251 | virtual UINT32 read_data(void *dest, UINT64 offset, UINT32 length) |
| 342 | 252 | { |
| 343 | | char *scan; |
| 344 | | |
| 345 | | /* if there are any non-digits in the 'offset', then treat it as a ident file */ |
| 346 | | for (scan = argv[4]; *scan != 0; scan++) |
| 347 | | if (!isdigit((UINT8)*scan)) |
| 348 | | break; |
| 349 | | if (*scan != 0) |
| 350 | | { |
| 351 | | /* attempt to load the file */ |
| 352 | | file_error filerr = core_fload(argv[4], (void **)&identdata, &identdatasize); |
| 353 | | if (filerr != FILERR_NONE) |
| 354 | | { |
| 355 | | fprintf(stderr, "Error opening ident file '%s'\n", argv[4]); |
| 356 | | return 1; |
| 357 | | } |
| 358 | | |
| 359 | | /* shift the remaining arguments down */ |
| 360 | | if (argc > 5) |
| 361 | | memmove(&argv[4], &argv[5], (argc - 5) * sizeof(argv[0])); |
| 362 | | argc--; |
| 363 | | } |
| 364 | | } |
| 365 | | |
| 366 | | /* require 4-5, or 8-10 args total */ |
| 367 | | if (argc != 4 && argc != 5 && argc != 8 && argc != 9 && argc != 10) |
| 368 | | return usage(); |
| 369 | | |
| 370 | | /* extract the first few parameters */ |
| 371 | | inputfile = argv[2]; |
| 372 | | outputfile = argv[3]; |
| 373 | | offset = (argc >= 5) ? atoi(argv[4]) : (get_file_size(inputfile) % IDE_SECTOR_SIZE); |
| 374 | | |
| 375 | | /* if less than 8 parameters, we need to guess the CHS values */ |
| 376 | | if (argc < 8) |
| 377 | | { |
| 378 | | if (identdata != NULL) |
| 379 | | err = get_chs_from_ident(inputfile, offset, identdata, identdatasize, &guess_cylinders, &guess_heads, &guess_sectors, &guess_sectorsize); |
| 380 | | else |
| 381 | | err = guess_chs(inputfile, offset, IDE_SECTOR_SIZE, &guess_cylinders, &guess_heads, &guess_sectors, &guess_sectorsize); |
| 253 | offset += m_offset; |
| 254 | if (offset >= m_maxoffset) |
| 255 | return 0; |
| 256 | if (offset + length > m_maxoffset) |
| 257 | length = m_maxoffset - offset; |
| 258 | chd_error err = m_file.read_bytes(offset, dest, length); |
| 382 | 259 | if (err != CHDERR_NONE) |
| 383 | | goto cleanup; |
| 260 | throw err; |
| 261 | return length; |
| 384 | 262 | } |
| 385 | 263 | |
| 386 | | /* parse the remaining parameters */ |
| 387 | | cylinders = (argc >= 6) ? atoi(argv[5]) : guess_cylinders; |
| 388 | | heads = (argc >= 7) ? atoi(argv[6]) : guess_heads; |
| 389 | | sectors = (argc >= 8) ? atoi(argv[7]) : guess_sectors; |
| 390 | | sectorsize = (argc >= 9) ? atoi(argv[8]) : guess_sectorsize; |
| 391 | | if (sectorsize == 0) sectorsize = IDE_SECTOR_SIZE; |
| 392 | | hunksize = (argc >= 10) ? atoi(argv[9]) : (sectorsize > 4096) ? sectorsize : ((4096 / sectorsize) * sectorsize); |
| 393 | | totalsectors = cylinders * heads * sectors; |
| 264 | private: |
| 265 | // internal state |
| 266 | chd_file & m_file; |
| 267 | UINT64 m_offset; |
| 268 | UINT64 m_maxoffset; |
| 269 | }; |
| 394 | 270 | |
| 395 | | /* print some info */ |
| 396 | | printf("Input file: %s\n", inputfile); |
| 397 | | printf("Output file: %s\n", outputfile); |
| 398 | | printf("Input offset: %d\n", offset); |
| 399 | | printf("Cylinders: %d\n", cylinders); |
| 400 | | printf("Heads: %d\n", heads); |
| 401 | | printf("Sectors: %d\n", sectors); |
| 402 | | printf("Bytes/sector: %d\n", sectorsize); |
| 403 | | printf("Sectors/hunk: %d\n", hunksize / sectorsize); |
| 404 | | printf("Logical size: %s\n", big_int_string((UINT64)totalsectors * (UINT64)sectorsize)); |
| 405 | 271 | |
| 406 | | /* create the new hard drive */ |
| 407 | | err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_ZLIB_PLUS, NULL); |
| 408 | | if (err != CHDERR_NONE) |
| 409 | | { |
| 410 | | fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); |
| 411 | | goto cleanup; |
| 412 | | } |
| 272 | // ======================> chd_cd_compressor |
| 413 | 273 | |
| 414 | | /* open the new hard drive */ |
| 415 | | err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); |
| 416 | | if (err != CHDERR_NONE) |
| 274 | class chd_cd_compressor : public chd_file_compressor |
| 275 | { |
| 276 | public: |
| 277 | // construction/destruction |
| 278 | chd_cd_compressor(cdrom_toc &toc, chdcd_track_input_info &info) |
| 279 | : m_file(NULL), |
| 280 | m_toc(toc), |
| 281 | m_info(info) { } |
| 282 | |
| 283 | ~chd_cd_compressor() |
| 417 | 284 | { |
| 418 | | fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); |
| 419 | | goto cleanup; |
| 285 | if (m_file != NULL) |
| 286 | core_fclose(m_file); |
| 420 | 287 | } |
| 421 | 288 | |
| 422 | | /* write the metadata */ |
| 423 | | sprintf(metadata, HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize); |
| 424 | | err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); |
| 425 | | if (err != CHDERR_NONE) |
| 289 | // read interface |
| 290 | virtual UINT32 read_data(void *_dest, UINT64 offset, UINT32 length) |
| 426 | 291 | { |
| 427 | | fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); |
| 428 | | goto cleanup; |
| 429 | | } |
| 430 | | |
| 431 | | /* write the ident if present */ |
| 432 | | if (identdata != NULL) |
| 433 | | { |
| 434 | | err = chd_set_metadata(chd, HARD_DISK_IDENT_METADATA_TAG, 0, identdata, identdatasize, CHD_MDFLAGS_CHECKSUM); |
| 435 | | if (err != CHDERR_NONE) |
| 292 | // verify assumptions made below |
| 293 | assert(offset % CD_FRAME_SIZE == 0); |
| 294 | assert(length % CD_FRAME_SIZE == 0); |
| 295 | |
| 296 | // initialize destination to 0 so that unused areas are filled |
| 297 | UINT8 *dest = reinterpret_cast<UINT8 *>(_dest); |
| 298 | memset(dest, 0, length); |
| 299 | |
| 300 | // find out which track we're starting in |
| 301 | UINT64 startoffs = 0; |
| 302 | UINT32 length_remaining = length; |
| 303 | for (int tracknum = 0; tracknum < m_toc.numtrks; tracknum++) |
| 436 | 304 | { |
| 437 | | fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); |
| 438 | | goto cleanup; |
| 439 | | } |
| 440 | | } |
| 441 | | |
| 442 | | /* compress the hard drive */ |
| 443 | | err = chdman_compress_file(chd, inputfile, offset); |
| 444 | | if (err != CHDERR_NONE) |
| 445 | | fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); |
| 446 | | |
| 447 | | cleanup: |
| 448 | | /* close everything down */ |
| 449 | | if (chd != NULL) |
| 450 | | chd_close(chd); |
| 451 | | if (err != CHDERR_NONE) |
| 452 | | osd_rmfile(outputfile); |
| 453 | | if (identdata != NULL) |
| 454 | | osd_free(identdata); |
| 455 | | return (err != CHDERR_NONE); |
| 456 | | } |
| 457 | | |
| 458 | | /*------------------------------------------------- |
| 459 | | do_createhd_uncomp - create a new uncompressed hard |
| 460 | | disk image from a raw file |
| 461 | | -------------------------------------------------*/ |
| 462 | | |
| 463 | | static int do_createhd_uncomp(int argc, char *argv[], int param) |
| 464 | | { |
| 465 | | UINT32 guess_cylinders = 0, guess_heads = 0, guess_sectors = 0, guess_sectorsize = 0; |
| 466 | | UINT32 cylinders, heads, sectors, sectorsize, hunksize, totalsectors, offset; |
| 467 | | const char *inputfile, *outputfile = NULL; |
| 468 | | chd_error err = CHDERR_NONE; |
| 469 | | UINT32 identdatasize = 0; |
| 470 | | UINT8 *identdata = NULL; |
| 471 | | chd_file *chd = NULL; |
| 472 | | char metadata[256]; |
| 473 | | chd_header header; |
| 474 | | |
| 475 | | /* if a file is provided for argument 4 (ident filename), then shift the remaining arguments down */ |
| 476 | | if (argc >= 5) |
| 477 | | { |
| 478 | | char *scan; |
| 479 | | |
| 480 | | /* if there are any non-digits in the 'offset', then treat it as a ident file */ |
| 481 | | for (scan = argv[4]; *scan != 0; scan++) |
| 482 | | if (!isdigit((UINT8)*scan)) |
| 483 | | break; |
| 484 | | if (*scan != 0) |
| 485 | | { |
| 486 | | /* attempt to load the file */ |
| 487 | | file_error filerr = core_fload(argv[4], (void **)&identdata, &identdatasize); |
| 488 | | if (filerr != FILERR_NONE) |
| 305 | const cdrom_track_info &trackinfo = m_toc.tracks[tracknum]; |
| 306 | UINT64 endoffs = startoffs + (trackinfo.frames + trackinfo.extraframes) * CD_FRAME_SIZE; |
| 307 | if (offset >= startoffs && offset < endoffs) |
| 489 | 308 | { |
| 490 | | fprintf(stderr, "Error opening ident file '%s'\n", argv[4]); |
| 491 | | return 1; |
| 309 | // if we don't already have this file open, open it now |
| 310 | if (m_file == NULL || m_lastfile != m_info.track[tracknum].fname) |
| 311 | { |
| 312 | if (m_file != NULL) |
| 313 | core_fclose(m_file); |
| 314 | m_lastfile = m_info.track[tracknum].fname; |
| 315 | file_error filerr = core_fopen(m_lastfile, OPEN_FLAG_READ, &m_file); |
| 316 | if (filerr != FILERR_NONE) |
| 317 | report_error(1, "Error opening input file (%s)'", m_lastfile.cstr()); |
| 318 | } |
| 319 | |
| 320 | // iterate over frames |
| 321 | UINT32 bytesperframe = trackinfo.datasize + trackinfo.subsize; |
| 322 | UINT64 src_track_start = m_info.track[tracknum].offset; |
| 323 | UINT64 src_track_end = src_track_start + bytesperframe * trackinfo.frames; |
| 324 | while (length_remaining != 0 && offset < endoffs) |
| 325 | { |
| 326 | // determine start of current frame |
| 327 | UINT64 src_frame_start = src_track_start + ((offset - startoffs) / CD_FRAME_SIZE) * bytesperframe; |
| 328 | if (src_frame_start < src_track_end) |
| 329 | { |
| 330 | // read it in |
| 331 | core_fseek(m_file, src_frame_start, SEEK_SET); |
| 332 | UINT32 count = core_fread(m_file, dest, bytesperframe); |
| 333 | if (count != bytesperframe) |
| 334 | report_error(1, "Error reading input file (%s)'", m_lastfile.cstr()); |
| 335 | |
| 336 | // swap if appropriate |
| 337 | if (m_info.track[tracknum].swap) |
| 338 | for (UINT32 swapindex = 0; swapindex < 2352; swapindex += 2) |
| 339 | { |
| 340 | UINT8 temp = dest[swapindex]; |
| 341 | dest[swapindex] = dest[swapindex + 1]; |
| 342 | dest[swapindex + 1] = temp; |
| 343 | } |
| 344 | } |
| 345 | |
| 346 | // advance |
| 347 | offset += CD_FRAME_SIZE; |
| 348 | dest += CD_FRAME_SIZE; |
| 349 | length_remaining -= CD_FRAME_SIZE; |
| 350 | if (length_remaining == 0) |
| 351 | break; |
| 352 | } |
| 492 | 353 | } |
| 493 | | |
| 494 | | /* shift the remaining arguments down */ |
| 495 | | if (argc > 5) |
| 496 | | memmove(&argv[4], &argv[5], (argc - 5) * sizeof(argv[0])); |
| 497 | | argc--; |
| 354 | |
| 355 | // next track starts after the previous one |
| 356 | startoffs = endoffs; |
| 498 | 357 | } |
| 358 | return length - length_remaining; |
| 499 | 359 | } |
| 500 | 360 | |
| 501 | | /* require 4-5, or 8-10 args total */ |
| 502 | | if (argc != 4 && argc != 5 && argc != 8 && argc != 9 && argc != 10) |
| 503 | | return usage(); |
| 361 | private: |
| 362 | // internal state |
| 363 | astring m_lastfile; |
| 364 | core_file * m_file; |
| 365 | cdrom_toc & m_toc; |
| 366 | chdcd_track_input_info & m_info; |
| 367 | }; |
| 504 | 368 | |
| 505 | | /* extract the first few parameters */ |
| 506 | | inputfile = argv[2]; |
| 507 | | outputfile = argv[3]; |
| 508 | | offset = (argc >= 5) ? atoi(argv[4]) : (get_file_size(inputfile) % IDE_SECTOR_SIZE); |
| 509 | 369 | |
| 510 | | /* if less than 8 parameters, we need to guess the CHS values */ |
| 511 | | if (argc < 8) |
| 512 | | { |
| 513 | | if (identdata != NULL) |
| 514 | | err = get_chs_from_ident(inputfile, offset, identdata, identdatasize, &guess_cylinders, &guess_heads, &guess_sectors, &guess_sectorsize); |
| 515 | | else |
| 516 | | err = guess_chs(inputfile, offset, IDE_SECTOR_SIZE, &guess_cylinders, &guess_heads, &guess_sectors, &guess_sectorsize); |
| 517 | | if (err != CHDERR_NONE) |
| 518 | | goto cleanup; |
| 519 | | } |
| 370 | // ======================> chd_avi_compressor |
| 520 | 371 | |
| 521 | | /* parse the remaining parameters */ |
| 522 | | cylinders = (argc >= 6) ? atoi(argv[5]) : guess_cylinders; |
| 523 | | heads = (argc >= 7) ? atoi(argv[6]) : guess_heads; |
| 524 | | sectors = (argc >= 8) ? atoi(argv[7]) : guess_sectors; |
| 525 | | sectorsize = (argc >= 9) ? atoi(argv[8]) : guess_sectorsize; |
| 526 | | if (sectorsize == 0) sectorsize = IDE_SECTOR_SIZE; |
| 527 | | hunksize = (argc >= 10) ? atoi(argv[9]) : (sectorsize > 4096) ? sectorsize : ((4096 / sectorsize) * sectorsize); |
| 528 | | totalsectors = cylinders * heads * sectors; |
| 529 | | |
| 530 | | /* print some info */ |
| 531 | | printf("Input file: %s\n", inputfile); |
| 532 | | printf("Output file: %s\n", outputfile); |
| 533 | | printf("Input offset: %d\n", offset); |
| 534 | | printf("Cylinders: %d\n", cylinders); |
| 535 | | printf("Heads: %d\n", heads); |
| 536 | | printf("Sectors: %d\n", sectors); |
| 537 | | printf("Bytes/sector: %d\n", sectorsize); |
| 538 | | printf("Sectors/hunk: %d\n", hunksize / sectorsize); |
| 539 | | printf("Logical size: %s\n", big_int_string((UINT64)totalsectors * (UINT64)sectorsize)); |
| 540 | | |
| 541 | | /* create the new hard drive */ |
| 542 | | err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_NONE, NULL); |
| 543 | | if (err != CHDERR_NONE) |
| 544 | | { |
| 545 | | fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); |
| 546 | | goto cleanup; |
| 547 | | } |
| 548 | | |
| 549 | | /* open the new hard drive */ |
| 550 | | err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); |
| 551 | | if (err != CHDERR_NONE) |
| 552 | | { |
| 553 | | fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); |
| 554 | | goto cleanup; |
| 555 | | } |
| 556 | | |
| 557 | | /* write the metadata */ |
| 558 | | sprintf(metadata, HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize); |
| 559 | | err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); |
| 560 | | if (err != CHDERR_NONE) |
| 561 | | { |
| 562 | | fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); |
| 563 | | goto cleanup; |
| 564 | | } |
| 565 | | |
| 566 | | /* write the ident if present */ |
| 567 | | if (identdata != NULL) |
| 568 | | { |
| 569 | | err = chd_set_metadata(chd, HARD_DISK_IDENT_METADATA_TAG, 0, identdata, identdatasize, CHD_MDFLAGS_CHECKSUM); |
| 570 | | if (err != CHDERR_NONE) |
| 571 | | { |
| 572 | | fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); |
| 573 | | goto cleanup; |
| 574 | | } |
| 575 | | } |
| 576 | | |
| 577 | | /* compress the hard drive */ |
| 578 | | err = chdman_compress_file(chd, inputfile, offset); |
| 579 | | if (err != CHDERR_NONE) |
| 580 | | fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); |
| 581 | | |
| 582 | | /* make it writeable */ |
| 583 | | header = *chd_get_header(chd); |
| 584 | | header.flags |= CHDFLAGS_IS_WRITEABLE; |
| 585 | | err = chd_set_header_file(chd_core_file(chd), &header); |
| 586 | | if (err != CHDERR_NONE) |
| 587 | | fprintf(stderr, "Error writing new header: %s\n", chd_error_string(err)); |
| 588 | | |
| 589 | | cleanup: |
| 590 | | /* close everything down */ |
| 591 | | if (chd != NULL) |
| 592 | | chd_close(chd); |
| 593 | | if (err != CHDERR_NONE) |
| 594 | | osd_rmfile(outputfile); |
| 595 | | if (identdata != NULL) |
| 596 | | osd_free(identdata); |
| 597 | | return (err != CHDERR_NONE); |
| 598 | | } |
| 599 | | |
| 600 | | /*------------------------------------------------- |
| 601 | | do_createraw - create a new compressed raw |
| 602 | | image from a raw file |
| 603 | | -------------------------------------------------*/ |
| 604 | | |
| 605 | | static int do_createraw(int argc, char *argv[], int param) |
| 372 | class chd_avi_compressor : public chd_file_compressor |
| 606 | 373 | { |
| 607 | | const char *inputfile, *outputfile; |
| 608 | | UINT32 hunksize, offset; |
| 609 | | UINT64 logicalbytes; |
| 610 | | chd_file *chd = NULL; |
| 611 | | chd_error err; |
| 612 | | |
| 613 | | /* require 4, 5, or 6 args total */ |
| 614 | | if (argc != 4 && argc != 5 && argc != 6) |
| 615 | | return usage(); |
| 616 | | |
| 617 | | /* extract the first few parameters */ |
| 618 | | inputfile = argv[2]; |
| 619 | | outputfile = argv[3]; |
| 620 | | offset = (argc >= 5) ? atoi(argv[4]) : 0; |
| 621 | | hunksize = (argc >= 6) ? atoi(argv[5]) : 4096; |
| 622 | | logicalbytes = get_file_size(inputfile) - offset; |
| 623 | | |
| 624 | | /* print some info */ |
| 625 | | printf("Input file: %s\n", inputfile); |
| 626 | | printf("Output file: %s\n", outputfile); |
| 627 | | printf("Input offset: %d\n", offset); |
| 628 | | printf("Bytes/hunk: %d\n", hunksize); |
| 629 | | printf("Logical size: %s\n", big_int_string(logicalbytes)); |
| 630 | | |
| 631 | | /* create the new CHD */ |
| 632 | | err = chd_create(outputfile, logicalbytes, hunksize, CHDCOMPRESSION_ZLIB_PLUS, NULL); |
| 633 | | if (err != CHDERR_NONE) |
| 374 | public: |
| 375 | // construction/destruction |
| 376 | chd_avi_compressor(avi_file &file, avi_info &info, UINT32 first_frame, UINT32 num_frames) |
| 377 | : m_file(file), |
| 378 | m_info(info), |
| 379 | m_bitmap(info.width, info.height * (info.interlaced ? 2 : 1)), |
| 380 | m_start_frame(first_frame), |
| 381 | m_frame_count(num_frames), |
| 382 | m_ldframedata(num_frames * VBI_PACKED_BYTES), |
| 383 | m_rawdata(info.bytes_per_frame) { } |
| 384 | |
| 385 | // getters |
| 386 | const dynamic_buffer &ldframedata() const { return m_ldframedata; } |
| 387 | |
| 388 | // read interface |
| 389 | virtual UINT32 read_data(void *_dest, UINT64 offset, UINT32 length) |
| 634 | 390 | { |
| 635 | | fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); |
| 636 | | goto cleanup; |
| 637 | | } |
| 391 | UINT8 *dest = reinterpret_cast<UINT8 *>(_dest); |
| 392 | int interlace_factor = m_info.interlaced ? 2 : 1; |
| 393 | UINT32 length_remaining = length; |
| 638 | 394 | |
| 639 | | /* open the new CHD */ |
| 640 | | err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); |
| 641 | | if (err != CHDERR_NONE) |
| 642 | | { |
| 643 | | fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); |
| 644 | | goto cleanup; |
| 645 | | } |
| 646 | | |
| 647 | | /* compress the CHD */ |
| 648 | | err = chdman_compress_file(chd, inputfile, offset); |
| 649 | | if (err != CHDERR_NONE) |
| 650 | | fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); |
| 651 | | |
| 652 | | cleanup: |
| 653 | | /* close everything down */ |
| 654 | | if (chd != NULL) |
| 655 | | chd_close(chd); |
| 656 | | if (err != CHDERR_NONE) |
| 657 | | osd_rmfile(outputfile); |
| 658 | | return (err != CHDERR_NONE); |
| 659 | | } |
| 660 | | |
| 661 | | |
| 662 | | /*------------------------------------------------- |
| 663 | | do_createcd - create a new compressed CD |
| 664 | | image from a raw file |
| 665 | | -------------------------------------------------*/ |
| 666 | | |
| 667 | | static int do_createcd(int argc, char *argv[], int param) |
| 668 | | { |
| 669 | | static chdcd_track_input_info track_info; |
| 670 | | static cdrom_toc toc; |
| 671 | | UINT32 hunksize = CD_FRAME_SIZE * CD_FRAMES_PER_HUNK; |
| 672 | | UINT32 sectorsize = CD_FRAME_SIZE; |
| 673 | | const char *inputfile, *outputfile; |
| 674 | | core_file *srcfile = NULL; |
| 675 | | UINT32 origtotalsectors; |
| 676 | | chd_file *chd = NULL; |
| 677 | | UINT8 *cache = NULL; |
| 678 | | UINT32 totalsectors; |
| 679 | | double ratio = 1.0; |
| 680 | | UINT32 totalhunks; |
| 681 | | file_error filerr; |
| 682 | | chd_error err; |
| 683 | | int i; |
| 684 | | |
| 685 | | /* require 4 args total */ |
| 686 | | if (argc != 4) |
| 687 | | return usage(); |
| 688 | | |
| 689 | | /* extract the data */ |
| 690 | | inputfile = argv[2]; |
| 691 | | outputfile = argv[3]; |
| 692 | | |
| 693 | | /* clear the TOC */ |
| 694 | | memset(&toc, 0, sizeof(toc)); |
| 695 | | |
| 696 | | /* allocate a cache */ |
| 697 | | cache = (UINT8 *)malloc(hunksize); |
| 698 | | if (cache == NULL) |
| 699 | | { |
| 700 | | fprintf(stderr, "Out of memory allocating temporary buffer\n"); |
| 701 | | err = CHDERR_OUT_OF_MEMORY; |
| 702 | | goto cleanup; |
| 703 | | } |
| 704 | | |
| 705 | | /* setup the CDROM module and get the disc info */ |
| 706 | | err = chdcd_parse_toc(inputfile, &toc, &track_info); |
| 707 | | if (err != CHDERR_NONE) |
| 708 | | { |
| 709 | | fprintf(stderr, "Error reading input file: %s\n", chd_error_string(err)); |
| 710 | | goto cleanup; |
| 711 | | } |
| 712 | | |
| 713 | | /* pad each track to a hunk boundry. cdrom.c will deal with this on the read side */ |
| 714 | | for (i = 0; i < toc.numtrks; i++) |
| 715 | | { |
| 716 | | int hunks = (toc.tracks[i].frames + CD_FRAMES_PER_HUNK - 1) / CD_FRAMES_PER_HUNK; |
| 717 | | toc.tracks[i].extraframes = hunks * CD_FRAMES_PER_HUNK - toc.tracks[i].frames; |
| 718 | | } |
| 719 | | |
| 720 | | |
| 721 | | /* count up the total number of frames */ |
| 722 | | origtotalsectors = totalsectors = 0; |
| 723 | | for (i = 0; i < toc.numtrks; i++) |
| 724 | | { |
| 725 | | origtotalsectors += toc.tracks[i].frames; |
| 726 | | totalsectors += toc.tracks[i].frames + toc.tracks[i].extraframes; |
| 727 | | } |
| 728 | | printf("\nCD-ROM %s has %d tracks and %d total frames\n", inputfile, toc.numtrks, origtotalsectors); |
| 729 | | |
| 730 | | /* create the new CHD file */ |
| 731 | | if (param) err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_ZLIB_PLUS_WITH_FLAC, NULL); |
| 732 | | else err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_ZLIB_PLUS, NULL); |
| 733 | | if (err != CHDERR_NONE) |
| 734 | | { |
| 735 | | fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); |
| 736 | | goto cleanup; |
| 737 | | } |
| 738 | | |
| 739 | | /* open the new CHD file */ |
| 740 | | err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); |
| 741 | | if (err != CHDERR_NONE) |
| 742 | | { |
| 743 | | fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); |
| 744 | | goto cleanup; |
| 745 | | } |
| 746 | | |
| 747 | | /* write the metadata */ |
| 748 | | err = cdrom_write_metadata(chd, &toc); |
| 749 | | if (err != CHDERR_NONE) |
| 750 | | { |
| 751 | | fprintf(stderr, "Error adding CD-ROM metadata: %s\n", chd_error_string(err)); |
| 752 | | goto cleanup; |
| 753 | | } |
| 754 | | |
| 755 | | /* begin state for writing */ |
| 756 | | err = chd_compress_begin(chd); |
| 757 | | if (err != CHDERR_NONE) |
| 758 | | { |
| 759 | | fprintf(stderr, "Error compressing: %s\n", chd_error_string(err)); |
| 760 | | goto cleanup; |
| 761 | | } |
| 762 | | |
| 763 | | /* loop over tracks */ |
| 764 | | totalhunks = 0; |
| 765 | | for (i = 0; i < toc.numtrks; i++) |
| 766 | | { |
| 767 | | int frames = 0; |
| 768 | | int bytespersector = toc.tracks[i].datasize + toc.tracks[i].subsize; |
| 769 | | int trackhunks = (toc.tracks[i].frames + toc.tracks[i].extraframes) / CD_FRAMES_PER_HUNK; |
| 770 | | UINT64 sourcefileoffset = track_info.offset[i]; |
| 771 | | int curhunk; |
| 772 | | |
| 773 | | /* open the input file for this track */ |
| 774 | | filerr = core_fopen(track_info.fname[i], OPEN_FLAG_READ, &srcfile); |
| 775 | | if (filerr != FILERR_NONE) |
| 776 | | { |
| 777 | | fprintf(stderr, "Unable to open file: %s\n", track_info.fname[i]); |
| 778 | | err = CHDERR_FILE_NOT_FOUND; |
| 779 | | goto cleanup; |
| 780 | | } |
| 781 | | |
| 782 | | printf("Track %02d/%02d (%s:%d,%d frames,%d hunks,swap %d,pregap %d,postgap %d)\n", i+1, toc.numtrks, track_info.fname[i], track_info.offset[i], toc.tracks[i].frames, trackhunks, track_info.swap[i], toc.tracks[i].pregap, toc.tracks[i].postgap); |
| 783 | | |
| 784 | | /* loop over hunks */ |
| 785 | | for (curhunk = 0; curhunk < trackhunks; curhunk++, totalhunks++) |
| 786 | | { |
| 787 | | int secnum; |
| 788 | | |
| 789 | | progress(FALSE, "Compressing hunk %d/%d... (ratio=%d%%) \r", totalhunks, chd_get_header(chd)->totalhunks, (int)(ratio * 100)); |
| 790 | | |
| 791 | | /* loop over sectors in this hunk, reading the source data into a fixed start location */ |
| 792 | | /* relative to the start; we zero out the buffer ahead of time to ensure that unpopulated */ |
| 793 | | /* areas are cleared */ |
| 794 | | memset(cache, 0, hunksize); |
| 795 | | for (secnum = 0; secnum < CD_FRAMES_PER_HUNK; secnum++) |
| 395 | // iterate over frames |
| 396 | INT32 start_frame = offset / m_info.bytes_per_frame; |
| 397 | INT32 end_frame = (offset + length - 1) / m_info.bytes_per_frame; |
| 398 | for (INT32 framenum = start_frame; framenum <= end_frame; framenum++) |
| 399 | if (framenum < m_frame_count) |
| 796 | 400 | { |
| 797 | | if (frames < toc.tracks[i].frames) |
| 401 | // determine effective frame number and first/last samples |
| 402 | INT32 effframe = m_start_frame + framenum; |
| 403 | UINT32 first_sample = (UINT64(m_info.rate) * UINT64(effframe) * UINT64(1000000) + m_info.fps_times_1million - 1) / UINT64(m_info.fps_times_1million); |
| 404 | UINT32 samples = (UINT64(m_info.rate) * UINT64(effframe + 1) * UINT64(1000000) + m_info.fps_times_1million - 1) / UINT64(m_info.fps_times_1million) - first_sample; |
| 405 | |
| 406 | // loop over channels and read the samples |
| 407 | int channels = MIN(m_info.channels, ARRAY_LENGTH(m_audio)); |
| 408 | INT16 *samplesptr[ARRAY_LENGTH(m_audio)]; |
| 409 | for (int chnum = 0; chnum < channels; chnum++) |
| 798 | 410 | { |
| 799 | | core_fseek(srcfile, sourcefileoffset, SEEK_SET); |
| 800 | | core_fread(srcfile, &cache[secnum * CD_FRAME_SIZE], bytespersector); |
| 411 | // read the sound samples |
| 412 | m_audio[chnum].resize(samples); |
| 413 | samplesptr[chnum] = m_audio[chnum]; |
| 414 | avi_error avierr = avi_read_sound_samples(&m_file, chnum, first_sample, samples, m_audio[chnum]); |
| 415 | if (avierr != AVIERR_NONE) |
| 416 | report_error(1, "Error reading audio samples %d-%d from channel %d: %s", first_sample, samples, chnum, avi_error_string(avierr)); |
| 417 | } |
| 801 | 418 | |
| 802 | | if (track_info.swap[i]) |
| 803 | | { |
| 804 | | int swapindex; |
| 419 | // read the video data |
| 420 | avi_error avierr = avi_read_video_frame(&m_file, effframe / interlace_factor, m_bitmap); |
| 421 | if (avierr != AVIERR_NONE) |
| 422 | report_error(1, "Error reading AVI frame %d: %s", effframe / interlace_factor, avi_error_string(avierr)); |
| 423 | bitmap_yuy16 subbitmap(&m_bitmap.pix(effframe % interlace_factor), m_bitmap.width(), m_bitmap.height() / interlace_factor, m_bitmap.rowpixels() * interlace_factor); |
| 805 | 424 | |
| 806 | | for (swapindex = 0; swapindex < 2352; swapindex += 2 ) |
| 807 | | { |
| 808 | | int swapoffset = ( secnum * CD_FRAME_SIZE ) + swapindex; |
| 809 | | |
| 810 | | int swaptemp = cache[ swapoffset ]; |
| 811 | | cache[ swapoffset ] = cache[ swapoffset + 1 ]; |
| 812 | | cache[ swapoffset + 1 ] = swaptemp; |
| 813 | | } |
| 814 | | } |
| 425 | // update metadata for this frame |
| 426 | if (m_info.height == 524/2 || m_info.height == 624/2) |
| 427 | { |
| 428 | vbi_metadata vbi; |
| 429 | vbi_parse_all(&subbitmap.pix16(0), subbitmap.rowpixels(), subbitmap.width(), 8, &vbi); |
| 430 | vbi_metadata_pack(&m_ldframedata[framenum * VBI_PACKED_BYTES], framenum, &vbi); |
| 815 | 431 | } |
| 816 | 432 | |
| 817 | | sourcefileoffset += bytespersector; |
| 818 | | frames++; |
| 433 | // assemble the data into final form |
| 434 | avhuff_error averr = avhuff_encoder::assemble_data(m_rawdata, m_rawdata.count(), subbitmap, channels, samples, samplesptr); |
| 435 | if (averr != AVHERR_NONE) |
| 436 | report_error(1, "Error assembling data for frame %d", framenum); |
| 437 | UINT32 rawsize = avhuff_encoder::raw_data_size(m_rawdata); |
| 438 | if (rawsize < m_rawdata.count()) |
| 439 | memset(&m_rawdata[rawsize], 0, m_rawdata.count() - rawsize); |
| 440 | |
| 441 | // copy to the destination |
| 442 | UINT64 start_offset = UINT64(framenum) * UINT64(m_info.bytes_per_frame); |
| 443 | UINT64 end_offset = start_offset + m_info.bytes_per_frame; |
| 444 | UINT32 bytes_to_copy = MIN(length_remaining, end_offset - offset); |
| 445 | memcpy(dest, &m_rawdata[offset - start_offset], bytes_to_copy); |
| 446 | |
| 447 | // advance |
| 448 | offset += bytes_to_copy; |
| 449 | dest += bytes_to_copy; |
| 450 | length_remaining -= bytes_to_copy; |
| 819 | 451 | } |
| 820 | 452 | |
| 821 | | int is_half_hunk = 0; |
| 822 | | if ((curhunk == trackhunks-1) && (toc.tracks[i].extraframes>3)) |
| 823 | | is_half_hunk = 1; |
| 824 | | |
| 825 | | |
| 826 | | /* compress the current hunk */ |
| 827 | | |
| 828 | | err = chd_compress_hunk(chd, cache, &ratio, is_half_hunk); |
| 829 | | if (err != CHDERR_NONE) |
| 830 | | { |
| 831 | | fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); |
| 832 | | goto cleanup; |
| 833 | | } |
| 834 | | } |
| 835 | | |
| 836 | | /* close the file */ |
| 837 | | core_fclose(srcfile); |
| 838 | | srcfile = NULL; |
| 453 | return length; |
| 839 | 454 | } |
| 840 | 455 | |
| 841 | | /* cleanup */ |
| 842 | | err = chd_compress_finish(chd, TRUE); |
| 843 | | if (err != CHDERR_NONE) |
| 844 | | fprintf(stderr, "Error during compression finalization: %s\n", chd_error_string(err)); |
| 845 | | else |
| 846 | | progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio)); |
| 456 | private: |
| 457 | // internal state |
| 458 | avi_file & m_file; |
| 459 | avi_info & m_info; |
| 460 | bitmap_yuy16 m_bitmap; |
| 461 | UINT32 m_start_frame; |
| 462 | UINT32 m_frame_count; |
| 463 | dynamic_array<INT16> m_audio[8]; |
| 464 | dynamic_buffer m_ldframedata; |
| 465 | dynamic_buffer m_rawdata; |
| 466 | }; |
| 847 | 467 | |
| 848 | | cleanup: |
| 849 | | if (cache != NULL) |
| 850 | | free(cache); |
| 851 | | if (srcfile != NULL) |
| 852 | | core_fclose(srcfile); |
| 853 | | if (chd != NULL) |
| 854 | | chd_close(chd); |
| 855 | | if (err != CHDERR_NONE) |
| 856 | | osd_rmfile(outputfile); |
| 857 | | return (err != CHDERR_NONE); |
| 858 | | } |
| 859 | 468 | |
| 860 | 469 | |
| 861 | | /*------------------------------------------------- |
| 862 | | read_avi_frame - read an AVI frame |
| 863 | | -------------------------------------------------*/ |
| 470 | //************************************************************************** |
| 471 | // GLOBAL VARIABLES |
| 472 | //************************************************************************** |
| 864 | 473 | |
| 865 | | static avi_error read_avi_frame(avi_file *avi, UINT32 framenum, UINT32 first_sample, bitmap_yuy16 &fullbitmap, int interlaced, av_codec_compress_config *avconfig) |
| 866 | | { |
| 867 | | const avi_movie_info *info = avi_get_movie_info(avi); |
| 868 | | int interlace_factor = interlaced ? 2 : 1; |
| 869 | | avi_error avierr = AVIERR_NONE; |
| 870 | | int chnum; |
| 474 | // timing |
| 475 | static clock_t lastprogress = 0; |
| 871 | 476 | |
| 872 | | /* loop over channels and read the samples */ |
| 873 | | for (chnum = 0; chnum < info->audio_channels; chnum++) |
| 874 | | { |
| 875 | | /* read the sound samples */ |
| 876 | | avierr = avi_read_sound_samples(avi, chnum, first_sample, avconfig->samples, avconfig->audio[chnum]); |
| 877 | | if (avierr != AVIERR_NONE) |
| 878 | | goto cleanup; |
| 879 | | } |
| 880 | 477 | |
| 881 | | /* read the video data when we hit a new frame */ |
| 882 | | if (framenum % interlace_factor == 0) |
| 883 | | { |
| 884 | | avierr = avi_read_video_frame(avi, framenum / interlace_factor, fullbitmap); |
| 885 | | if (avierr != AVIERR_NONE) |
| 886 | | goto cleanup; |
| 887 | | } |
| 478 | // default compressors |
| 479 | static const chd_codec_type s_default_raw_compression[4] = { CHD_CODEC_LZMA, CHD_CODEC_ZLIB, CHD_CODEC_HUFFMAN }; |
| 480 | static const chd_codec_type s_default_hd_compression[4] = { CHD_CODEC_LZMA, CHD_CODEC_ZLIB, CHD_CODEC_HUFFMAN }; |
| 481 | static const chd_codec_type s_default_cd_compression[4] = { CHD_CODEC_LZMA, CHD_CODEC_ZLIB, CHD_CODEC_HUFFMAN, CHD_CODEC_FLAC_BE }; |
| 482 | static const chd_codec_type s_default_ld_compression[4] = { CHD_CODEC_AVHUFF }; |
| 888 | 483 | |
| 889 | | /* build the fake bitmap */ |
| 890 | | if (!interlaced) |
| 891 | | avconfig->video.wrap(fullbitmap, fullbitmap.cliprect()); |
| 892 | | else |
| 893 | | avconfig->video.wrap(&fullbitmap.pix16(framenum % interlace_factor), fullbitmap.width(), fullbitmap.height() / 2, fullbitmap.rowpixels() * 2); |
| 894 | 484 | |
| 895 | | cleanup: |
| 896 | | return avierr; |
| 897 | | } |
| 898 | | |
| 899 | | |
| 900 | | /*------------------------------------------------- |
| 901 | | fake_avi_frame - fake an AVI frame |
| 902 | | -------------------------------------------------*/ |
| 903 | | |
| 904 | | static avi_error fake_avi_frame(avi_file *avi, UINT32 framenum, UINT32 first_sample, bitmap_yuy16 &fullbitmap, int interlaced, av_codec_compress_config *avconfig) |
| 485 | // descriptions for each option |
| 486 | static const option_description s_options[] = |
| 905 | 487 | { |
| 906 | | static int framecounter = 0; |
| 907 | | int leftsamp = (framenum % 200 < 10) ? 10000 : 0; |
| 908 | | int rightsamp = (framenum % 200 >= 100 && framenum % 200 < 110) ? 10000 : 0; |
| 909 | | int interlace_factor = interlaced ? 2 : 1; |
| 910 | | int chnum, sampnum, x, y; |
| 911 | | int whiteflag, line1718; |
| 488 | { OPTION_INPUT, "i", true, " <filename>: input file name" }, |
| 489 | { OPTION_INPUT_PARENT, "ip", true, " <filename>: parent file name for input CHD" }, |
| 490 | { OPTION_OUTPUT, "o", true, " <filename>: output file name" }, |
| 491 | { OPTION_OUTPUT_BIN, "ob", true, " <filename>: output file name for binary data" }, |
| 492 | { OPTION_OUTPUT_FORCE, "f", false, ": force overwriting an existing file" }, |
| 493 | { OPTION_OUTPUT_PARENT, "op", true, " <filename>: parent file name for output CHD" }, |
| 494 | { OPTION_INPUT_START_BYTE, "isb", true, " <offset>: starting byte offset within the input" }, |
| 495 | { OPTION_INPUT_START_HUNK, "ish", true, " <offset>: starting hunk offset within the input" }, |
| 496 | { OPTION_INPUT_START_FRAME, "isf", true, " <offset>: starting frame within the input" }, |
| 497 | { OPTION_INPUT_LENGTH_BYTES, "ib", true, " <length>: effective length of input in bytes" }, |
| 498 | { OPTION_INPUT_LENGTH_HUNKS, "ih", true, " <length>: effective length of input in hunks" }, |
| 499 | { OPTION_INPUT_LENGTH_FRAMES, "if", true, " <length>: effective length of input in frames" }, |
| 500 | { OPTION_HUNK_SIZE, "hs", true, " <bytes>: size of each hunk, in bytes" }, |
| 501 | { OPTION_UNIT_SIZE, "us", true, " <bytes>: size of each unit, in bytes" }, |
| 502 | { OPTION_COMPRESSION, "c", true, " <none|type1[,type2[,...]]>: which compression codecs to use (up to 4)" }, |
| 503 | { OPTION_IDENT, "id", true, " <filename>: name of ident file to provide CHS information" }, |
| 504 | { OPTION_CHS, "chs", true, " <cylinders,heads,sectors>: specifies CHS values directly" }, |
| 505 | { OPTION_SECTOR_SIZE, "ss", true, " <bytes>: size of each hard disk sector" }, |
| 506 | { OPTION_TAG, "t", true, " <tag>: 4-character tag for metadata" }, |
| 507 | { OPTION_INDEX, "ix", true, " <index>: indexed instance of this metadata tag" }, |
| 508 | { OPTION_VALUE_TEXT, "vt", true, " <text>: text for the metadata" }, |
| 509 | { OPTION_VALUE_FILE, "vf", true, " <file>: file containing data to add" }, |
| 510 | #if NUM_PROCESSORS_SUPPORTED |
| 511 | { OPTION_NUMPROCESSORS, "np", true, " <processors>: limit the number of processors to use during compression" }, |
| 512 | #endif |
| 513 | { OPTION_NO_CHECKSUM, "nocs", false, ": do not include this metadata information in the overall SHA-1" }, |
| 514 | { OPTION_FIX, "f", false, ": fix the SHA-1 if it is incorrect" }, |
| 515 | { OPTION_VERBOSE, "v", false, ": output additional information" }, |
| 516 | }; |
| 912 | 517 | |
| 913 | | /* reset framecounter to 1 on frame 0 */ |
| 914 | | if (framenum == 0) |
| 915 | | framecounter = 1; |
| 916 | 518 | |
| 917 | | /* loop over channels and read the samples */ |
| 918 | | for (chnum = 0; chnum < AVI_FAKE_CHANNELS; chnum++) |
| 919 | | { |
| 920 | | int modcheck = AVI_FAKE_SAMPLERATE / ((chnum == 0) ? 110 : 220); |
| 921 | | int samp = (chnum == 0) ? leftsamp : rightsamp; |
| 922 | | INT16 *dest = avconfig->audio[chnum]; |
| 923 | | |
| 924 | | /* store them to the audio buffer */ |
| 925 | | for (sampnum = 0; sampnum < avconfig->samples; sampnum++) |
| 926 | | *dest++ = ((first_sample + sampnum) % modcheck < modcheck / 2) ? samp : -samp; |
| 927 | | } |
| 928 | | |
| 929 | | /* determine what metadata we should generate */ |
| 930 | | whiteflag = line1718 = 0; |
| 931 | | if (framenum < 2) |
| 932 | | { |
| 933 | | whiteflag = (framenum == 0); |
| 934 | | line1718 = 0x88ffff; |
| 935 | | } |
| 936 | | else if (framenum >= AVI_FAKE_FRAMES * 2 - 2) |
| 937 | | { |
| 938 | | whiteflag = (framenum == AVI_FAKE_FRAMES * 2 - 2); |
| 939 | | line1718 = 0x80eeee; |
| 940 | | } |
| 941 | | else |
| 942 | | { |
| 943 | | int effnum = framenum - 2; |
| 944 | | if (avi == AVI_FAKE_FILE_22 && effnum % 2 == 0) |
| 945 | | whiteflag = 1; |
| 946 | | else if (avi == AVI_FAKE_FILE_32 && (effnum % 5 == 0 || effnum % 5 == 2)) |
| 947 | | whiteflag = 1; |
| 948 | | if (whiteflag) |
| 519 | // descriptions for each command |
| 520 | static const command_description s_commands[] = |
| 521 | { |
| 522 | { COMMAND_INFO, do_info, ": displays information about a CHD", |
| 949 | 523 | { |
| 950 | | line1718 = 0xf80000 | (((framecounter / 10000) % 10) << 16) | (((framecounter / 1000) % 10) << 12) | |
| 951 | | (((framecounter / 100) % 10) << 8) | (((framecounter / 10) % 10) << 4) | (framecounter % 10); |
| 952 | | framecounter++; |
| 524 | REQUIRED OPTION_INPUT, |
| 525 | OPTION_VERBOSE |
| 526 | } |
| 527 | }, |
| 528 | |
| 529 | { COMMAND_VERIFY, do_verify, ": verifies a CHD's integrity", |
| 530 | { |
| 531 | REQUIRED OPTION_INPUT, |
| 532 | OPTION_INPUT_PARENT |
| 533 | } |
| 534 | }, |
| 535 | |
| 536 | { COMMAND_CREATE_RAW, do_create_raw, ": create a raw CHD from the input file", |
| 537 | { |
| 538 | REQUIRED OPTION_OUTPUT, |
| 539 | OPTION_OUTPUT_PARENT, |
| 540 | OPTION_OUTPUT_FORCE, |
| 541 | REQUIRED OPTION_INPUT, |
| 542 | OPTION_INPUT_START_BYTE, |
| 543 | OPTION_INPUT_START_HUNK, |
| 544 | OPTION_INPUT_LENGTH_BYTES, |
| 545 | OPTION_INPUT_LENGTH_HUNKS, |
| 546 | REQUIRED OPTION_HUNK_SIZE, |
| 547 | REQUIRED OPTION_UNIT_SIZE, |
| 548 | OPTION_COMPRESSION, |
| 549 | #if NUM_PROCESSORS_SUPPORTED |
| 550 | OPTION_NUMPROCESSORS |
| 551 | #endif |
| 953 | 552 | } |
| 954 | | } |
| 955 | | |
| 956 | | /* build the fake bitmap */ |
| 957 | | if (!interlaced) |
| 958 | | avconfig->video.wrap(fullbitmap, fullbitmap.cliprect()); |
| 959 | | else |
| 960 | | avconfig->video.wrap(&fullbitmap.pix16(framenum % interlace_factor), fullbitmap.width(), fullbitmap.height() / 2, fullbitmap.rowpixels() * 2); |
| 961 | | |
| 962 | | /* loop over the data and copy it to the cache */ |
| 963 | | for (y = 0; y < avconfig->video.height(); y++) |
| 964 | | { |
| 965 | | UINT16 *dest = &avconfig->video.pix16(y); |
| 966 | | |
| 967 | | /* white flag? */ |
| 968 | | if (y == 11 && whiteflag) |
| 553 | }, |
| 554 | |
| 555 | { COMMAND_CREATE_HD, do_create_hd, ": create a hard disk CHD from the input file", |
| 969 | 556 | { |
| 970 | | for (x = 0; x < AVI_FAKE_WIDTH; x++) |
| 971 | | *dest++ = (x > 10 && x < avconfig->video.width() - 10) ? 0xff80 : 0x0080; |
| 557 | REQUIRED OPTION_OUTPUT, |
| 558 | OPTION_OUTPUT_PARENT, |
| 559 | OPTION_OUTPUT_FORCE, |
| 560 | OPTION_INPUT, |
| 561 | OPTION_INPUT_START_BYTE, |
| 562 | OPTION_INPUT_START_HUNK, |
| 563 | OPTION_INPUT_LENGTH_BYTES, |
| 564 | OPTION_INPUT_LENGTH_HUNKS, |
| 565 | OPTION_HUNK_SIZE, |
| 566 | OPTION_COMPRESSION, |
| 567 | OPTION_IDENT, |
| 568 | OPTION_CHS, |
| 569 | OPTION_SECTOR_SIZE, |
| 570 | #if NUM_PROCESSORS_SUPPORTED |
| 571 | OPTION_NUMPROCESSORS |
| 572 | #endif |
| 972 | 573 | } |
| 973 | | |
| 974 | | /* line 17/18 */ |
| 975 | | else if ((y == 17 || y == 18) && line1718 != 0) |
| 574 | }, |
| 575 | |
| 576 | { COMMAND_CREATE_CD, do_create_cd, ": create a CD CHD from the input file", |
| 976 | 577 | { |
| 977 | | for (x = 0; x < avconfig->video.width(); x++) |
| 978 | | { |
| 979 | | UINT16 pixel = 0x0080; |
| 980 | | if (x >= 20) |
| 981 | | { |
| 982 | | int bitnum = (x - 20) / 28; |
| 983 | | if (bitnum < 24) |
| 984 | | { |
| 985 | | int bithalf = (x - (bitnum * 28 + 20)) / 14; |
| 986 | | if ((line1718 << bitnum) & 0x800000) bithalf ^= 1; |
| 987 | | pixel = bithalf ? 0x0080 : 0xff80; |
| 988 | | } |
| 989 | | } |
| 990 | | *dest++ = pixel; |
| 991 | | } |
| 578 | REQUIRED OPTION_OUTPUT, |
| 579 | OPTION_OUTPUT_PARENT, |
| 580 | OPTION_OUTPUT_FORCE, |
| 581 | REQUIRED OPTION_INPUT, |
| 582 | OPTION_HUNK_SIZE, |
| 583 | OPTION_COMPRESSION, |
| 584 | #if NUM_PROCESSORS_SUPPORTED |
| 585 | OPTION_NUMPROCESSORS |
| 586 | #endif |
| 992 | 587 | } |
| 993 | | |
| 994 | | /* anything else in VBI-land */ |
| 995 | | else if (y < 22) |
| 588 | }, |
| 589 | |
| 590 | { COMMAND_CREATE_LD, do_create_ld, ": create a laserdisc CHD from the input file", |
| 996 | 591 | { |
| 997 | | for (x = 0; x < avconfig->video.width(); x++) |
| 998 | | *dest++ = 0x0080; |
| 592 | REQUIRED OPTION_OUTPUT, |
| 593 | OPTION_OUTPUT_PARENT, |
| 594 | OPTION_OUTPUT_FORCE, |
| 595 | REQUIRED OPTION_INPUT, |
| 596 | OPTION_INPUT_START_FRAME, |
| 597 | OPTION_INPUT_LENGTH_FRAMES, |
| 598 | OPTION_HUNK_SIZE, |
| 599 | OPTION_COMPRESSION, |
| 600 | #if NUM_PROCESSORS_SUPPORTED |
| 601 | OPTION_NUMPROCESSORS |
| 602 | #endif |
| 999 | 603 | } |
| 1000 | | |
| 1001 | | /* everything else */ |
| 1002 | | else |
| 604 | }, |
| 605 | |
| 606 | { COMMAND_EXTRACT_RAW, do_extract_raw, ": extract raw file from a CHD input file", |
| 1003 | 607 | { |
| 1004 | | for (x = 0; x < avconfig->video.width(); x++) |
| 1005 | | *dest++ = framenum; |
| 608 | REQUIRED OPTION_OUTPUT, |
| 609 | OPTION_OUTPUT_FORCE, |
| 610 | REQUIRED OPTION_INPUT, |
| 611 | OPTION_INPUT_PARENT, |
| 612 | OPTION_INPUT_START_BYTE, |
| 613 | OPTION_INPUT_START_HUNK, |
| 614 | OPTION_INPUT_LENGTH_BYTES, |
| 615 | OPTION_INPUT_LENGTH_HUNKS |
| 1006 | 616 | } |
| 1007 | | } |
| 1008 | | |
| 1009 | | return AVIERR_NONE; |
| 1010 | | } |
| 1011 | | |
| 1012 | | |
| 1013 | | /*------------------------------------------------- |
| 1014 | | do_createav - create a new A/V file from an |
| 1015 | | input AVI file and metadata |
| 1016 | | -------------------------------------------------*/ |
| 1017 | | |
| 1018 | | static int do_createav(int argc, char *argv[], int param) |
| 1019 | | { |
| 1020 | | UINT32 fps_times_1million, width, height, interlaced, channels, rate, totalframes; |
| 1021 | | UINT32 max_samples_per_frame, bytes_per_frame, firstframe, numframes; |
| 1022 | | av_codec_compress_config avconfig; |
| 1023 | | const char *inputfile, *outputfile; |
| 1024 | | const avi_movie_info *info; |
| 1025 | | UINT8 *ldframedata = NULL; |
| 1026 | | const chd_header *header; |
| 1027 | | chd_file *chd = NULL; |
| 1028 | | avi_file *avi = NULL; |
| 1029 | | double ratio = 1.0; |
| 1030 | | char metadata[256]; |
| 1031 | | avi_error avierr; |
| 1032 | | UINT32 framenum; |
| 1033 | | chd_error err; |
| 1034 | | int chnum; |
| 1035 | | |
| 1036 | | /* require 4-6 args total */ |
| 1037 | | if (argc < 4 || argc > 6) |
| 1038 | | return usage(); |
| 1039 | | |
| 1040 | | /* extract the first few parameters */ |
| 1041 | | inputfile = argv[2]; |
| 1042 | | outputfile = argv[3]; |
| 1043 | | firstframe = (argc > 4) ? atoi(argv[4]) : 0; |
| 1044 | | numframes = (argc > 5) ? atoi(argv[5]) : 1000000; |
| 1045 | | |
| 1046 | | /* print some info */ |
| 1047 | | printf("Input file: %s\n", inputfile); |
| 1048 | | printf("Output file: %s\n", outputfile); |
| 1049 | | |
| 1050 | | /* special AVI files */ |
| 1051 | | if (strcmp(inputfile, "2:2") == 0 || strcmp(inputfile, "3:2") == 0) |
| 1052 | | { |
| 1053 | | /* create a fake handle */ |
| 1054 | | avi = (strcmp(inputfile, "2:2") == 0) ? AVI_FAKE_FILE_22 : AVI_FAKE_FILE_32; |
| 1055 | | |
| 1056 | | /* fake the movie information */ |
| 1057 | | fps_times_1million = AVI_FAKE_FRAMERATE; |
| 1058 | | width = AVI_FAKE_WIDTH; |
| 1059 | | height = AVI_FAKE_HEIGHT; |
| 1060 | | interlaced = TRUE; |
| 1061 | | channels = 2; |
| 1062 | | rate = AVI_FAKE_SAMPLERATE; |
| 1063 | | totalframes = AVI_FAKE_FRAMES; |
| 1064 | | } |
| 1065 | | else |
| 1066 | | { |
| 1067 | | /* open the source file */ |
| 1068 | | avierr = avi_open(inputfile, &avi); |
| 1069 | | if (avierr != AVIERR_NONE) |
| 617 | }, |
| 618 | |
| 619 | { COMMAND_EXTRACT_HD, do_extract_raw, ": extract raw hard disk file from a CHD input file", |
| 1070 | 620 | { |
| 1071 | | fprintf(stderr, "Error opening AVI file: %s\n", avi_error_string(avierr)); |
| 1072 | | err = CHDERR_INVALID_FILE; |
| 1073 | | goto cleanup; |
| 621 | REQUIRED OPTION_OUTPUT, |
| 622 | OPTION_OUTPUT_FORCE, |
| 623 | REQUIRED OPTION_INPUT, |
| 624 | OPTION_INPUT_PARENT, |
| 625 | OPTION_INPUT_START_BYTE, |
| 626 | OPTION_INPUT_START_HUNK, |
| 627 | OPTION_INPUT_LENGTH_BYTES, |
| 628 | OPTION_INPUT_LENGTH_HUNKS |
| 1074 | 629 | } |
| 1075 | | |
| 1076 | | /* get the movie information */ |
| 1077 | | info = avi_get_movie_info(avi); |
| 1078 | | fps_times_1million = (UINT64)info->video_timescale * 1000000 / info->video_sampletime; |
| 1079 | | width = info->video_width; |
| 1080 | | height = info->video_height; |
| 1081 | | interlaced = ((fps_times_1million / 1000000) <= 30) && (height % 2 == 0) && (height > 288); |
| 1082 | | channels = info->audio_channels; |
| 1083 | | rate = info->audio_samplerate; |
| 1084 | | totalframes = info->video_numsamples; |
| 1085 | | } |
| 1086 | | numframes = MIN(totalframes - firstframe, numframes); |
| 1087 | | |
| 1088 | | /* print some of it */ |
| 1089 | | printf("Use frames: %d-%d\n", firstframe, firstframe + numframes - 1); |
| 1090 | | printf("Frame rate: %d.%06d\n", fps_times_1million / 1000000, fps_times_1million % 1000000); |
| 1091 | | printf("Frame size: %d x %d %s\n", width, height, interlaced ? "interlaced" : "non-interlaced"); |
| 1092 | | printf("Audio: %d channels at %d Hz\n", channels, rate); |
| 1093 | | printf("Total frames: %d (%02d:%02d:%02d)\n", totalframes, |
| 1094 | | (UINT32)((UINT64)totalframes * 1000000 / fps_times_1million / 60 / 60), |
| 1095 | | (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million / 60) % 60), |
| 1096 | | (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million) % 60)); |
| 1097 | | |
| 1098 | | /* adjust for interlacing */ |
| 1099 | | if (interlaced) |
| 1100 | | { |
| 1101 | | fps_times_1million *= 2; |
| 1102 | | totalframes *= 2; |
| 1103 | | height /= 2; |
| 1104 | | firstframe *= 2; |
| 1105 | | numframes *= 2; |
| 1106 | | } |
| 1107 | | |
| 1108 | | /* allocate space for the frame data */ |
| 1109 | | if (height == 524/2 || height == 624/2) |
| 1110 | | { |
| 1111 | | ldframedata = (UINT8 *)malloc(numframes * VBI_PACKED_BYTES); |
| 1112 | | if (ldframedata == NULL) |
| 630 | }, |
| 631 | |
| 632 | { COMMAND_EXTRACT_CD, do_extract_cd, ": extract CD file from a CHD input file", |
| 1113 | 633 | { |
| 1114 | | fprintf(stderr, "Out of memory allocating frame metadata\n"); |
| 1115 | | err = CHDERR_OUT_OF_MEMORY; |
| 1116 | | goto cleanup; |
| 634 | REQUIRED OPTION_OUTPUT, |
| 635 | OPTION_OUTPUT_BIN, |
| 636 | OPTION_OUTPUT_FORCE, |
| 637 | REQUIRED OPTION_INPUT |
| 638 | OPTION_INPUT_PARENT, |
| 1117 | 639 | } |
| 1118 | | memset(ldframedata, 0, numframes * VBI_PACKED_BYTES); |
| 1119 | | } |
| 1120 | | |
| 1121 | | /* determine the number of bytes per frame */ |
| 1122 | | max_samples_per_frame = ((UINT64)rate * 1000000 + fps_times_1million - 1) / fps_times_1million; |
| 1123 | | bytes_per_frame = 12 + channels * max_samples_per_frame * 2 + width * height * 2; |
| 1124 | | |
| 1125 | | /* allocate a video buffer */ |
| 1126 | | { |
| 1127 | | bitmap_yuy16 fullbitmap(width, height * (interlaced ? 2 : 1)); |
| 1128 | | avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); |
| 1129 | | |
| 1130 | | /* allocate audio buffers */ |
| 1131 | | avconfig.channels = channels; |
| 1132 | | for (chnum = 0; chnum < channels; chnum++) |
| 640 | }, |
| 641 | |
| 642 | { COMMAND_EXTRACT_LD, do_extract_ld, ": extract laserdisc AVI from a CHD input file", |
| 1133 | 643 | { |
| 1134 | | avconfig.audio[chnum] = (INT16 *)malloc(max_samples_per_frame * 2); |
| 1135 | | if (avconfig.audio[chnum] == NULL) |
| 1136 | | { |
| 1137 | | fprintf(stderr, "Out of memory allocating temporary audio buffer\n"); |
| 1138 | | err = CHDERR_OUT_OF_MEMORY; |
| 1139 | | goto cleanup; |
| 1140 | | } |
| 644 | REQUIRED OPTION_OUTPUT, |
| 645 | OPTION_OUTPUT_FORCE, |
| 646 | REQUIRED OPTION_INPUT, |
| 647 | OPTION_INPUT_PARENT, |
| 648 | OPTION_INPUT_START_FRAME, |
| 649 | OPTION_INPUT_LENGTH_FRAMES |
| 1141 | 650 | } |
| 1142 | | |
| 1143 | | /* create the new CHD */ |
| 1144 | | err = chd_create(outputfile, (UINT64)numframes * (UINT64)bytes_per_frame, bytes_per_frame, CHDCOMPRESSION_AV, NULL); |
| 1145 | | if (err != CHDERR_NONE) |
| 651 | }, |
| 652 | |
| 653 | { COMMAND_COPY, do_copy, ": copy data from one CHD to another of the same type", |
| 1146 | 654 | { |
| 1147 | | fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); |
| 1148 | | goto cleanup; |
| 655 | REQUIRED OPTION_OUTPUT, |
| 656 | OPTION_OUTPUT_PARENT, |
| 657 | OPTION_OUTPUT_FORCE, |
| 658 | REQUIRED OPTION_INPUT, |
| 659 | OPTION_INPUT_PARENT, |
| 660 | OPTION_INPUT_START_BYTE, |
| 661 | OPTION_INPUT_START_HUNK, |
| 662 | OPTION_INPUT_LENGTH_BYTES, |
| 663 | OPTION_INPUT_LENGTH_HUNKS, |
| 664 | OPTION_HUNK_SIZE, |
| 665 | OPTION_COMPRESSION, |
| 666 | #if NUM_PROCESSORS_SUPPORTED |
| 667 | OPTION_NUMPROCESSORS |
| 668 | #endif |
| 1149 | 669 | } |
| 1150 | | |
| 1151 | | /* open the new CHD */ |
| 1152 | | err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); |
| 1153 | | if (err != CHDERR_NONE) |
| 670 | }, |
| 671 | |
| 672 | { COMMAND_ADD_METADATA, do_add_metadata, ": add metadata to the CHD", |
| 1154 | 673 | { |
| 1155 | | fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); |
| 1156 | | goto cleanup; |
| 674 | REQUIRED OPTION_INPUT, |
| 675 | REQUIRED OPTION_TAG, |
| 676 | OPTION_INDEX, |
| 677 | OPTION_VALUE_TEXT, |
| 678 | OPTION_VALUE_FILE, |
| 679 | OPTION_NO_CHECKSUM |
| 1157 | 680 | } |
| 1158 | | header = chd_get_header(chd); |
| 1159 | | |
| 1160 | | /* write the metadata */ |
| 1161 | | sprintf(metadata, AV_METADATA_FORMAT, fps_times_1million / 1000000, fps_times_1million % 1000000, width, height, interlaced, channels, rate); |
| 1162 | | err = chd_set_metadata(chd, AV_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); |
| 1163 | | if (err != CHDERR_NONE) |
| 681 | }, |
| 682 | |
| 683 | { COMMAND_DEL_METADATA, do_del_metadata, ": remove metadata from the CHD", |
| 1164 | 684 | { |
| 1165 | | fprintf(stderr, "Error adding AV metadata: %s\n", chd_error_string(err)); |
| 1166 | | goto cleanup; |
| 685 | REQUIRED OPTION_INPUT, |
| 686 | REQUIRED OPTION_TAG, |
| 687 | OPTION_INDEX |
| 1167 | 688 | } |
| 689 | } |
| 690 | }; |
| 1168 | 691 | |
| 1169 | | /* begin compressing */ |
| 1170 | | err = chd_compress_begin(chd); |
| 1171 | | if (err != CHDERR_NONE) |
| 1172 | | goto cleanup; |
| 1173 | 692 | |
| 1174 | | /* loop over source hunks until we run out */ |
| 1175 | | for (framenum = 0; framenum < numframes; framenum++) |
| 1176 | | { |
| 1177 | | int effframe = firstframe + framenum; |
| 1178 | | UINT32 first_sample; |
| 1179 | 693 | |
| 1180 | | /* progress */ |
| 1181 | | progress(framenum == 0, "Compressing hunk %d/%d... (ratio=%d%%) \r", framenum, header->totalhunks, (int)(100.0 * ratio)); |
| 694 | //************************************************************************** |
| 695 | // IMPLEMENTATION |
| 696 | //************************************************************************** |
| 1182 | 697 | |
| 1183 | | /* compute the number of samples in this frame */ |
| 1184 | | first_sample = ((UINT64)rate * (UINT64)effframe * (UINT64)1000000 + fps_times_1million - 1) / (UINT64)fps_times_1million; |
| 1185 | | avconfig.samples = ((UINT64)rate * (UINT64)(effframe + 1) * (UINT64)1000000 + fps_times_1million - 1) / (UINT64)fps_times_1million - first_sample; |
| 698 | //------------------------------------------------- |
| 699 | // report_error - report an error |
| 700 | //------------------------------------------------- |
| 1186 | 701 | |
| 1187 | | /* read the frame into its proper format in the cache */ |
| 1188 | | if (IS_FAKE_AVI_FILE(avi)) |
| 1189 | | avierr = fake_avi_frame(avi, effframe, first_sample, fullbitmap, interlaced, &avconfig); |
| 1190 | | else |
| 1191 | | avierr = read_avi_frame(avi, effframe, first_sample, fullbitmap, interlaced, &avconfig); |
| 1192 | | if (avierr != AVIERR_NONE) |
| 1193 | | { |
| 1194 | | fprintf(stderr, "Error reading frame %d from AVI file: %s\n", effframe, avi_error_string(avierr)); |
| 1195 | | err = CHDERR_COMPRESSION_ERROR; |
| 1196 | | } |
| 702 | static void report_error(int error, const char *format, ...) |
| 703 | { |
| 704 | // output to stderr |
| 705 | va_list arg; |
| 706 | va_start(arg, format); |
| 707 | vfprintf(stderr, format, arg); |
| 708 | fflush(stderr); |
| 709 | va_end(arg); |
| 710 | fprintf(stderr, "\n"); |
| 711 | |
| 712 | // reset time for progress and return the error |
| 713 | lastprogress = 0; |
| 714 | throw fatal_error(error); |
| 715 | } |
| 1197 | 716 | |
| 1198 | | /* update metadata for this frame */ |
| 1199 | | if (ldframedata != NULL) |
| 1200 | | { |
| 1201 | | /* parse the data and pack it */ |
| 1202 | | vbi_metadata vbi; |
| 1203 | | vbi_parse_all(&avconfig.video.pix16(0), avconfig.video.rowpixels(), avconfig.video.width(), 8, &vbi); |
| 1204 | | vbi_metadata_pack(&ldframedata[framenum * VBI_PACKED_BYTES], framenum, &vbi); |
| 1205 | | } |
| 1206 | 717 | |
| 1207 | | /* configure the compressor for this frame */ |
| 1208 | | chd_codec_config(chd, AV_CODEC_COMPRESS_CONFIG, &avconfig); |
| 718 | //------------------------------------------------- |
| 719 | // progress - generic progress callback |
| 720 | //------------------------------------------------- |
| 1209 | 721 | |
| 1210 | | /* append the data */ |
| 1211 | | err = chd_compress_hunk(chd, NULL, &ratio); |
| 1212 | | if (err != CHDERR_NONE) |
| 1213 | | goto cleanup; |
| 1214 | | } |
| 722 | static void ATTR_PRINTF(2,3) progress(bool forceit, const char *format, ...) |
| 723 | { |
| 724 | // skip if it hasn't been long enough |
| 725 | clock_t curtime = clock(); |
| 726 | if (!forceit && lastprogress != 0 && curtime - lastprogress < CLOCKS_PER_SEC / 2) |
| 727 | return; |
| 728 | lastprogress = curtime; |
| 1215 | 729 | |
| 1216 | | /* write the final metadata */ |
| 1217 | | if (ldframedata != NULL) |
| 1218 | | { |
| 1219 | | err = chd_set_metadata(chd, AV_LD_METADATA_TAG, 0, ldframedata, numframes * VBI_PACKED_BYTES, CHD_MDFLAGS_CHECKSUM); |
| 1220 | | if (err != CHDERR_NONE) |
| 1221 | | { |
| 1222 | | fprintf(stderr, "Error adding AVLD metadata: %s\n", chd_error_string(err)); |
| 1223 | | goto cleanup; |
| 1224 | | } |
| 1225 | | } |
| 1226 | | |
| 1227 | | /* finish compression */ |
| 1228 | | err = chd_compress_finish(chd, TRUE); |
| 1229 | | if (err != CHDERR_NONE) |
| 1230 | | goto cleanup; |
| 1231 | | else |
| 1232 | | progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio)); |
| 1233 | | } |
| 1234 | | |
| 1235 | | cleanup: |
| 1236 | | /* close everything down */ |
| 1237 | | if (avi != NULL && !IS_FAKE_AVI_FILE(avi)) |
| 1238 | | avi_close(avi); |
| 1239 | | if (chd != NULL) |
| 1240 | | chd_close(chd); |
| 1241 | | for (chnum = 0; chnum < ARRAY_LENGTH(avconfig.audio); chnum++) |
| 1242 | | if (avconfig.audio[chnum] != NULL) |
| 1243 | | free(avconfig.audio[chnum]); |
| 1244 | | if (ldframedata != NULL) |
| 1245 | | free(ldframedata); |
| 1246 | | if (err != CHDERR_NONE) |
| 1247 | | osd_rmfile(outputfile); |
| 1248 | | return (err != CHDERR_NONE); |
| 730 | // standard vfprintf stuff here |
| 731 | va_list arg; |
| 732 | va_start(arg, format); |
| 733 | vfprintf(stderr, format, arg); |
| 734 | fflush(stderr); |
| 735 | va_end(arg); |
| 1249 | 736 | } |
| 1250 | 737 | |
| 1251 | 738 | |
| 1252 | | /*------------------------------------------------- |
| 1253 | | do_createblankhd - create a new non-compressed |
| 1254 | | hard disk image, with all hunks filled with 0s |
| 1255 | | -------------------------------------------------*/ |
| 739 | //------------------------------------------------- |
| 740 | // print_help - print help for all the commands |
| 741 | //------------------------------------------------- |
| 1256 | 742 | |
| 1257 | | static int do_createblankhd(int argc, char *argv[], int param) |
| 743 | static int print_help(const char *argv0, const char *error = NULL) |
| 1258 | 744 | { |
| 1259 | | UINT32 cylinders, heads, sectors, sectorsize, hunksize, totalsectors, hunknum; |
| 1260 | | const char *outputfile; |
| 1261 | | chd_file *chd = NULL; |
| 1262 | | UINT8 *cache = NULL; |
| 1263 | | char metadata[256]; |
| 1264 | | chd_error err; |
| 745 | // print the error message first |
| 746 | if (error != NULL) |
| 747 | fprintf(stderr, "Error: %s\n\n", error); |
| 1265 | 748 | |
| 1266 | | /* require 6, 7, or 8 args total */ |
| 1267 | | if (argc != 6 && argc != 7 && argc != 8) |
| 1268 | | return usage(); |
| 749 | // print a summary of each command |
| 750 | printf("Usage:\n"); |
| 751 | for (int cmdnum = 0; cmdnum < ARRAY_LENGTH(s_commands); cmdnum++) |
| 752 | { |
| 753 | const command_description &desc = s_commands[cmdnum]; |
| 754 | printf(" %s %s%s\n", argv0, desc.name, desc.description); |
| 755 | } |
| 756 | printf("\nFor help with any command, run:\n"); |
| 757 | printf(" %s %s <command>\n", argv0, COMMAND_HELP); |
| 758 | return 1; |
| 759 | } |
| 1269 | 760 | |
| 1270 | | /* extract the data */ |
| 1271 | | outputfile = argv[2]; |
| 1272 | | cylinders = atoi(argv[3]); |
| 1273 | | heads = atoi(argv[4]); |
| 1274 | | sectors = atoi(argv[5]); |
| 1275 | | sectorsize = (argc >= 7) ? atoi(argv[6]) : IDE_SECTOR_SIZE; |
| 1276 | | if (sectorsize == 0) sectorsize = IDE_SECTOR_SIZE; |
| 1277 | | hunksize = (argc >= 8) ? atoi(argv[7]) : (sectorsize > 4096) ? sectorsize : ((4096 / sectorsize) * sectorsize); |
| 1278 | | totalsectors = cylinders * heads * sectors; |
| 1279 | 761 | |
| 1280 | | /* print some info */ |
| 1281 | | printf("Output file: %s\n", outputfile); |
| 1282 | | printf("Cylinders: %d\n", cylinders); |
| 1283 | | printf("Heads: %d\n", heads); |
| 1284 | | printf("Sectors: %d\n", sectors); |
| 1285 | | printf("Bytes/sector: %d\n", sectorsize); |
| 1286 | | printf("Sectors/hunk: %d\n", hunksize / sectorsize); |
| 1287 | | printf("Logical size: %s\n", big_int_string((UINT64)totalsectors * (UINT64)sectorsize)); |
| 762 | //------------------------------------------------- |
| 763 | // print_help - print help for all a specific |
| 764 | // command |
| 765 | //------------------------------------------------- |
| 1288 | 766 | |
| 1289 | | /* create the new hard drive */ |
| 1290 | | err = chd_create(outputfile, (UINT64)totalsectors * (UINT64)sectorsize, hunksize, CHDCOMPRESSION_NONE, NULL); |
| 1291 | | if (err != CHDERR_NONE) |
| 1292 | | { |
| 1293 | | fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); |
| 1294 | | goto cleanup; |
| 1295 | | } |
| 767 | static int print_help(const char *argv0, const command_description &desc, const char *error = NULL) |
| 768 | { |
| 769 | // print the error message first |
| 770 | if (error != NULL) |
| 771 | fprintf(stderr, "Error: %s\n\n", error); |
| 1296 | 772 | |
| 1297 | | /* open the new hard drive */ |
| 1298 | | err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &chd); |
| 1299 | | if (err != CHDERR_NONE) |
| 773 | // print usage for this command |
| 774 | printf("Usage:\n"); |
| 775 | printf(" %s %s [options], where valid options are:\n", argv0, desc.name); |
| 776 | for (int valid = 0; valid < ARRAY_LENGTH(desc.valid_options); valid++) |
| 1300 | 777 | { |
| 1301 | | fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); |
| 1302 | | goto cleanup; |
| 778 | // determine whether we are required |
| 779 | const char *option = desc.valid_options[valid]; |
| 780 | if (option == NULL) |
| 781 | break; |
| 782 | bool required = (option[0] == REQUIRED[0]); |
| 783 | if (required) |
| 784 | option++; |
| 785 | |
| 786 | // find the option |
| 787 | for (int optnum = 0; optnum < ARRAY_LENGTH(s_options); optnum++) |
| 788 | if (strcmp(option, s_options[optnum].name) == 0) |
| 789 | { |
| 790 | const option_description &odesc = s_options[optnum]; |
| 791 | printf(" --%s", odesc.name); |
| 792 | if (odesc.shortname != NULL) |
| 793 | printf(", -%s", odesc.shortname); |
| 794 | printf("%s%s\n", odesc.description, required ? " (required)" : ""); |
| 795 | } |
| 1303 | 796 | } |
| 797 | return 1; |
| 798 | } |
| 1304 | 799 | |
| 1305 | | /* write the metadata */ |
| 1306 | | sprintf(metadata, HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sectorsize); |
| 1307 | | err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); |
| 1308 | | if (err != CHDERR_NONE) |
| 1309 | | { |
| 1310 | | fprintf(stderr, "Error adding hard disk metadata: %s\n", chd_error_string(err)); |
| 1311 | | goto cleanup; |
| 1312 | | } |
| 1313 | 800 | |
| 1314 | | /* alloc and zero buffer*/ |
| 1315 | | cache = (UINT8 *)malloc(hunksize); |
| 1316 | | if (cache == NULL) |
| 1317 | | { |
| 1318 | | fprintf(stderr, "Error allocating memory buffer\n"); |
| 1319 | | err = CHDERR_OUT_OF_MEMORY; |
| 1320 | | goto cleanup; |
| 1321 | | } |
| 1322 | | memset(cache, 0, hunksize); |
| 801 | //------------------------------------------------- |
| 802 | // big_int_string - create a 64-bit string |
| 803 | //------------------------------------------------- |
| 1323 | 804 | |
| 1324 | | /* Zero every hunk */ |
| 1325 | | for (hunknum = 0; hunknum < chd_get_header(chd)->totalhunks; hunknum++) |
| 805 | const char *big_int_string(astring &string, UINT64 intvalue) |
| 806 | { |
| 807 | // 0 is a special case |
| 808 | if (intvalue == 0) |
| 809 | return string.cpy("0"); |
| 810 | |
| 811 | // loop until all chunks are done |
| 812 | string.reset(); |
| 813 | bool first = true; |
| 814 | while (intvalue != 0) |
| 1326 | 815 | { |
| 1327 | | /* progress */ |
| 1328 | | progress(hunknum == 0, "Zeroing hunk %d/%d... \r", hunknum, chd_get_header(chd)->totalhunks); |
| 816 | int chunk = intvalue % 1000; |
| 817 | intvalue /= 1000; |
| 818 | |
| 819 | astring insert; |
| 820 | insert.format((intvalue != 0) ? "%03d" : "%d", chunk); |
| 1329 | 821 | |
| 1330 | | /* write out the data */ |
| 1331 | | err = chd_write(chd, hunknum, cache); |
| 1332 | | if (err != CHDERR_NONE) |
| 1333 | | { |
| 1334 | | fprintf(stderr, "Error writing CHD file: %s\n", chd_error_string(err)); |
| 1335 | | goto cleanup; |
| 1336 | | } |
| 822 | if (!first) |
| 823 | string.ins(0, ","); |
| 824 | first = false; |
| 825 | string.ins(0, insert); |
| 1337 | 826 | } |
| 1338 | | progress(TRUE, "Creation complete! \n"); |
| 1339 | | |
| 1340 | | cleanup: |
| 1341 | | /* close everything down */ |
| 1342 | | if (cache != NULL) |
| 1343 | | free(cache); |
| 1344 | | if (chd != NULL) |
| 1345 | | chd_close(chd); |
| 1346 | | if (err != CHDERR_NONE) |
| 1347 | | osd_rmfile(outputfile); |
| 1348 | | return (err != CHDERR_NONE); |
| 827 | return string; |
| 1349 | 828 | } |
| 1350 | 829 | |
| 1351 | 830 | |
| 1352 | | /*------------------------------------------------- |
| 1353 | | do_copydata - copy all hunks of data from one |
| 1354 | | CHD file to another. The hunk sizes do not |
| 1355 | | need to match. If the source is shorter than |
| 1356 | | the destination, the source data will be |
| 1357 | | padded with 0s. |
| 1358 | | -------------------------------------------------*/ |
| 831 | //------------------------------------------------- |
| 832 | // msf_string_from_frames - output the given |
| 833 | // number of frames in M:S:F format |
| 834 | //------------------------------------------------- |
| 1359 | 835 | |
| 1360 | | static int do_copydata(int argc, char *argv[], int param) |
| 836 | const char *msf_string_from_frames(astring &string, UINT32 frames) |
| 1361 | 837 | { |
| 1362 | | const char *inputfile, *outputfile; |
| 1363 | | chd_file *outputchd = NULL; |
| 1364 | | chd_file *inputchd = NULL; |
| 1365 | | chd_error err; |
| 838 | return string.format("%02d:%02d:%02d", frames / (75 * 60), (frames / 75) % 60, frames % 75); |
| 839 | } |
| 1366 | 840 | |
| 1367 | | /* require 4 args total */ |
| 1368 | | if (argc != 4) |
| 1369 | | return usage(); |
| 1370 | 841 | |
| 1371 | | /* extract the data */ |
| 1372 | | inputfile = argv[2]; |
| 1373 | | outputfile = argv[3]; |
| 842 | //------------------------------------------------- |
| 843 | // parse_number - parse a number string with an |
| 844 | // optional k/m/g suffix |
| 845 | //------------------------------------------------- |
| 1374 | 846 | |
| 1375 | | /* print some info */ |
| 1376 | | printf("Input file: %s\n", inputfile); |
| 1377 | | printf("Output file: %s\n", outputfile); |
| 1378 | | |
| 1379 | | /* open the src hard drive */ |
| 1380 | | err = chd_open(inputfile, CHD_OPEN_READ, NULL, &inputchd); |
| 1381 | | if (err != CHDERR_NONE) |
| 847 | UINT64 parse_number(const char *string) |
| 848 | { |
| 849 | // 0-length string is 0 |
| 850 | int length = strlen(string); |
| 851 | if (length == 0) |
| 852 | return 0; |
| 853 | |
| 854 | // scan forward over digits |
| 855 | UINT64 result = 0; |
| 856 | while (isdigit(*string)) |
| 1382 | 857 | { |
| 1383 | | fprintf(stderr, "Error opening src CHD file: %s\n", chd_error_string(err)); |
| 1384 | | goto cleanup; |
| 858 | result = (result * 10) + (*string - '0'); |
| 859 | string++; |
| 1385 | 860 | } |
| 1386 | | |
| 1387 | | /* open the dest hard drive */ |
| 1388 | | err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &outputchd); |
| 1389 | | if (err != CHDERR_NONE) |
| 1390 | | { |
| 1391 | | fprintf(stderr, "Error opening dest CHD file: %s\n", chd_error_string(err)); |
| 1392 | | goto cleanup; |
| 1393 | | } |
| 1394 | | |
| 1395 | | /* compress the source into the dest */ |
| 1396 | | err = chdman_compress_chd(outputchd, inputchd, 0); |
| 1397 | | if (err != CHDERR_NONE) |
| 1398 | | fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); |
| 1399 | | |
| 1400 | | cleanup: |
| 1401 | | /* close everything down */ |
| 1402 | | if (outputchd != NULL) |
| 1403 | | chd_close(outputchd); |
| 1404 | | if (inputchd != NULL) |
| 1405 | | chd_close(inputchd); |
| 1406 | | if (err != CHDERR_NONE) |
| 1407 | | osd_rmfile(outputfile); |
| 1408 | | return (err != CHDERR_NONE); |
| 861 | |
| 862 | // handle multipliers |
| 863 | if (*string == 'k' || *string == 'K') |
| 864 | result *= 1024; |
| 865 | if (*string == 'm' || *string == 'M') |
| 866 | result *= 1024 * 1024; |
| 867 | if (*string == 'g' || *string == 'G') |
| 868 | result *= 1024 * 1024 * 1024; |
| 869 | |
| 870 | return result; |
| 1409 | 871 | } |
| 1410 | 872 | |
| 1411 | 873 | |
| 1412 | | /*------------------------------------------------- |
| 1413 | | do_extract - extract a raw file from a |
| 1414 | | CHD image |
| 1415 | | -------------------------------------------------*/ |
| 874 | //------------------------------------------------- |
| 875 | // guess_chs - given a file and an offset, |
| 876 | // compute a best guess CHS value set |
| 877 | //------------------------------------------------- |
| 1416 | 878 | |
| 1417 | | static int do_extract(int argc, char *argv[], int param) |
| 879 | static void guess_chs(astring *filename, UINT64 filesize, int sectorsize, UINT32 &cylinders, UINT32 &heads, UINT32 §ors, UINT32 &bps) |
| 1418 | 880 | { |
| 1419 | | const char *inputfile, *outputfile; |
| 1420 | | core_file *outfile = NULL; |
| 1421 | | chd_file *infile = NULL; |
| 1422 | | const chd_header *header; |
| 1423 | | UINT64 bytesremaining; |
| 1424 | | void *hunk = NULL; |
| 1425 | | file_error filerr; |
| 1426 | | chd_error err; |
| 1427 | | int hunknum; |
| 881 | // if this is a direct physical drive read, handle it specially |
| 882 | if (filename != NULL && osd_get_physical_drive_geometry(*filename, &cylinders, &heads, §ors, &bps)) |
| 883 | return; |
| 884 | |
| 885 | // if we have no length to work with, we can't guess |
| 886 | if (filesize == 0) |
| 887 | report_error(1, "Can't guess CHS values because there is no input file"); |
| 1428 | 888 | |
| 1429 | | /* require 4 args total */ |
| 1430 | | if (argc != 4) |
| 1431 | | return usage(); |
| 889 | // validate the size |
| 890 | if (filesize % sectorsize != 0) |
| 891 | report_error(1, "Can't guess CHS values because data size is not divisible by %d", sectorsize); |
| 892 | ; |
| 1432 | 893 | |
| 1433 | | /* extract the data */ |
| 1434 | | inputfile = argv[2]; |
| 1435 | | outputfile = argv[3]; |
| 894 | // now find a valid value |
| 895 | for (UINT32 totalsectors = filesize / sectorsize; ; totalsectors++) |
| 896 | for (UINT32 cursectors = 63; cursectors > 1; cursectors--) |
| 897 | if (totalsectors % cursectors == 0) |
| 898 | { |
| 899 | UINT32 totalheads = totalsectors / cursectors; |
| 900 | for (UINT32 curheads = 16; curheads > 1; curheads--) |
| 901 | if (totalheads % curheads == 0) |
| 902 | { |
| 903 | cylinders = totalheads / curheads; |
| 904 | heads = curheads; |
| 905 | sectors = cursectors; |
| 906 | return; |
| 907 | } |
| 908 | } |
| 1436 | 909 | |
| 1437 | | /* print some info */ |
| 1438 | | printf("Input file: %s\n", inputfile); |
| 1439 | | printf("Output file: %s\n", outputfile); |
| 910 | // ack, it didn't work! |
| 911 | report_error(1, "Can't guess CHS values because no logical combination works!"); |
| 912 | } |
| 1440 | 913 | |
| 1441 | | /* get the header */ |
| 1442 | | err = chd_open(inputfile, CHD_OPEN_READ, NULL, &infile); |
| 1443 | | if (err != CHDERR_NONE) |
| 1444 | | { |
| 1445 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); |
| 1446 | | goto cleanup; |
| 1447 | | } |
| 1448 | | header = chd_get_header(infile); |
| 1449 | 914 | |
| 1450 | | /* allocate memory to hold a hunk */ |
| 1451 | | hunk = malloc(header->hunkbytes); |
| 1452 | | if (hunk == NULL) |
| 1453 | | { |
| 1454 | | fprintf(stderr, "Out of memory allocating hunk buffer!\n"); |
| 1455 | | err = CHDERR_OUT_OF_MEMORY; |
| 1456 | | goto cleanup; |
| 1457 | | } |
| 915 | //------------------------------------------------- |
| 916 | // parse_input_chd_parameters - parse the |
| 917 | // standard set of input CHD parameters |
| 918 | //------------------------------------------------- |
| 1458 | 919 | |
| 1459 | | /* create the output file */ |
| 1460 | | filerr = core_fopen(outputfile, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &outfile); |
| 1461 | | if (filerr != FILERR_NONE) |
| 920 | static void parse_input_chd_parameters(const parameters_t ¶ms, chd_file &input_chd, chd_file &input_parent_chd, bool writeable = false) |
| 921 | { |
| 922 | // process input parent file |
| 923 | astring *input_chd_parent_str = params.find(OPTION_INPUT_PARENT); |
| 924 | if (input_chd_parent_str != NULL) |
| 1462 | 925 | { |
| 1463 | | fprintf(stderr, "Error opening output file '%s'\n", outputfile); |
| 1464 | | err = CHDERR_CANT_CREATE_FILE; |
| 1465 | | goto cleanup; |
| 926 | chd_error err = input_parent_chd.open(*input_chd_parent_str); |
| 927 | if (err != CHDERR_NONE) |
| 928 | report_error(1, "Error opening parent CHD file (%s): %s", input_chd_parent_str->cstr(), chd_file::error_string(err)); |
| 1466 | 929 | } |
| 1467 | 930 | |
| 1468 | | /* loop over hunks, reading and writing */ |
| 1469 | | bytesremaining = header->logicalbytes; |
| 1470 | | for (hunknum = 0; hunknum < header->totalhunks; hunknum++) |
| 931 | // process input file |
| 932 | astring *input_chd_str = params.find(OPTION_INPUT); |
| 933 | if (input_chd_str != NULL) |
| 1471 | 934 | { |
| 1472 | | UINT32 byteswritten, bytes_to_write; |
| 1473 | | |
| 1474 | | /* progress */ |
| 1475 | | progress(hunknum == 0, "Extracting hunk %d/%d... \r", hunknum, header->totalhunks); |
| 1476 | | |
| 1477 | | /* read the hunk into a buffer */ |
| 1478 | | err = chd_read(infile, hunknum, hunk); |
| 935 | chd_error err = input_chd.open(*input_chd_str, writeable, input_parent_chd.opened() ? &input_parent_chd : NULL); |
| 1479 | 936 | if (err != CHDERR_NONE) |
| 1480 | | { |
| 1481 | | fprintf(stderr, "Error reading hunk %d from CHD file: %s\n", hunknum, chd_error_string(err)); |
| 1482 | | goto cleanup; |
| 1483 | | } |
| 1484 | | |
| 1485 | | /* write the hunk to the file */ |
| 1486 | | bytes_to_write = MIN(bytesremaining, header->hunkbytes); |
| 1487 | | core_fseek(outfile, (UINT64)hunknum * (UINT64)header->hunkbytes, SEEK_SET); |
| 1488 | | byteswritten = core_fwrite(outfile, hunk, bytes_to_write); |
| 1489 | | if (byteswritten != bytes_to_write) |
| 1490 | | { |
| 1491 | | fprintf(stderr, "Error writing hunk %d to output file: %s\n", hunknum, chd_error_string(CHDERR_WRITE_ERROR)); |
| 1492 | | err = CHDERR_WRITE_ERROR; |
| 1493 | | goto cleanup; |
| 1494 | | } |
| 1495 | | bytesremaining -= byteswritten; |
| 937 | report_error(1, "Error opening CHD file (%s): %s", input_chd_str->cstr(), chd_file::error_string(err)); |
| 1496 | 938 | } |
| 1497 | | progress(TRUE, "Extraction complete! \n"); |
| 1498 | | |
| 1499 | | cleanup: |
| 1500 | | /* clean up our mess */ |
| 1501 | | if (outfile != NULL) |
| 1502 | | core_fclose(outfile); |
| 1503 | | if (hunk != NULL) |
| 1504 | | free(hunk); |
| 1505 | | if (infile != NULL) |
| 1506 | | chd_close(infile); |
| 1507 | | if (err != CHDERR_NONE) |
| 1508 | | osd_rmfile(outputfile); |
| 1509 | | return (err != CHDERR_NONE); |
| 1510 | 939 | } |
| 1511 | 940 | |
| 1512 | 941 | |
| 1513 | | /*------------------------------------------------- |
| 1514 | | do_extractcd - extract a CDRDAO .toc/.bin |
| 1515 | | or CDRWIN .cue/.bin file from a CHD-CD image |
| 1516 | | -------------------------------------------------*/ |
| 942 | //------------------------------------------------- |
| 943 | // parse_input_start_end - parse input start/end |
| 944 | // parameters in a standard way |
| 945 | //------------------------------------------------- |
| 1517 | 946 | |
| 1518 | | static int do_extractcd(int argc, char *argv[], int param) |
| 947 | static void parse_input_start_end(const parameters_t ¶ms, UINT64 logical_size, UINT32 hunkbytes, UINT32 framebytes, UINT64 &input_start, UINT64 &input_end) |
| 1519 | 948 | { |
| 1520 | | const char *inputfile, *outputfile, *outputfile2; |
| 1521 | | core_file *outfile2 = NULL; |
| 1522 | | chd_file *inputchd = NULL; |
| 1523 | | cdrom_file *cdrom = NULL; |
| 1524 | | FILE *outfile = NULL; |
| 1525 | | const cdrom_toc *toc; |
| 1526 | | UINT64 out2offs, discoffs; |
| 1527 | | file_error filerr; |
| 1528 | | chd_error err; |
| 1529 | | int track, cuemode; |
| 949 | // process start/end if we were provided an input CHD |
| 950 | input_start = 0; |
| 951 | input_end = logical_size; |
| 1530 | 952 | |
| 1531 | | /* require 5 args total */ |
| 1532 | | if (argc != 5) |
| 1533 | | return usage(); |
| 953 | // process input start |
| 954 | astring *input_start_byte_str = params.find(OPTION_INPUT_START_BYTE); |
| 955 | astring *input_start_hunk_str = params.find(OPTION_INPUT_START_HUNK); |
| 956 | astring *input_start_frame_str = params.find(OPTION_INPUT_START_FRAME); |
| 957 | if (input_start_byte_str != NULL) |
| 958 | input_start = parse_number(*input_start_byte_str); |
| 959 | if (input_start_hunk_str != NULL) |
| 960 | input_start = parse_number(*input_start_hunk_str) * hunkbytes; |
| 961 | if (input_start_frame_str != NULL) |
| 962 | input_start = parse_number(*input_start_frame_str) * framebytes; |
| 963 | if (input_start >= input_end) |
| 964 | report_error(1, "Input start offset greater than input file size"); |
| 965 | |
| 966 | // process input length |
| 967 | astring *input_length_bytes_str = params.find(OPTION_INPUT_LENGTH_BYTES); |
| 968 | astring *input_length_hunks_str = params.find(OPTION_INPUT_LENGTH_HUNKS); |
| 969 | astring *input_length_frames_str = params.find(OPTION_INPUT_LENGTH_FRAMES); |
| 970 | UINT64 input_length = input_end; |
| 971 | if (input_length_bytes_str != NULL) |
| 972 | input_length = parse_number(*input_length_bytes_str); |
| 973 | if (input_length_hunks_str != NULL) |
| 974 | input_length = parse_number(*input_length_hunks_str) * hunkbytes; |
| 975 | if (input_length_frames_str != NULL) |
| 976 | input_length = parse_number(*input_length_frames_str) * framebytes; |
| 977 | if (input_start + input_length < input_end) |
| 978 | input_end = input_start + input_length; |
| 979 | } |
| 1534 | 980 | |
| 1535 | | /* extract the data */ |
| 1536 | | inputfile = argv[2]; |
| 1537 | | outputfile = argv[3]; |
| 1538 | | outputfile2 = argv[4]; |
| 1539 | 981 | |
| 1540 | | /* print some info */ |
| 1541 | | printf("Input file: %s\n", inputfile); |
| 1542 | | printf("Output files: %s and %s\n", outputfile, outputfile2); |
| 982 | //------------------------------------------------- |
| 983 | // parse_output_chd_parameters - parse the |
| 984 | // standard set of output CHD parameters |
| 985 | //------------------------------------------------- |
| 1543 | 986 | |
| 1544 | | /* get the header */ |
| 1545 | | err = chd_open(inputfile, CHD_OPEN_READ, NULL, &inputchd); |
| 1546 | | if (err != CHDERR_NONE) |
| 987 | static astring *parse_output_chd_parameters(const parameters_t ¶ms, chd_file &output_parent_chd) |
| 988 | { |
| 989 | // process output parent file |
| 990 | astring *output_chd_parent_str = params.find(OPTION_OUTPUT_PARENT); |
| 991 | if (output_chd_parent_str != NULL) |
| 1547 | 992 | { |
| 1548 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); |
| 1549 | | goto cleanup; |
| 993 | chd_error err = output_parent_chd.open(*output_chd_parent_str); |
| 994 | if (err != CHDERR_NONE) |
| 995 | report_error(1, "Error opening parent CHD file (%s): %s", output_chd_parent_str->cstr(), chd_file::error_string(err)); |
| 1550 | 996 | } |
| 1551 | 997 | |
| 1552 | | /* open the CD */ |
| 1553 | | cdrom = cdrom_open(inputchd); |
| 1554 | | if (cdrom == NULL) |
| 998 | // process output file |
| 999 | astring *output_chd_str = params.find(OPTION_OUTPUT); |
| 1000 | if (output_chd_str != NULL && params.find(OPTION_OUTPUT_FORCE) == NULL) |
| 1555 | 1001 | { |
| 1556 | | fprintf(stderr, "Error opening CHD-CD '%s'\n", inputfile); |
| 1557 | | err = CHDERR_INVALID_FILE; |
| 1558 | | goto cleanup; |
| 1002 | core_file *file; |
| 1003 | file_error filerr = core_fopen(*output_chd_str, OPEN_FLAG_READ, &file); |
| 1004 | if (filerr == FILERR_NONE) |
| 1005 | { |
| 1006 | core_fclose(file); |
| 1007 | report_error(1, "Error: file already exists (%s)\nUse --force (or -f) to force overwriting", output_chd_str->cstr()); |
| 1008 | } |
| 1559 | 1009 | } |
| 1010 | return output_chd_str; |
| 1011 | } |
| 1560 | 1012 | |
| 1561 | | /* check for CDRWIN format */ |
| 1562 | | cuemode = 0; |
| 1563 | | if (strstr(outputfile, ".cue")) |
| 1564 | | { |
| 1565 | | cuemode = 1; |
| 1566 | | } |
| 1567 | 1013 | |
| 1568 | | /* get the TOC data */ |
| 1569 | | toc = cdrom_get_toc(cdrom); |
| 1014 | //------------------------------------------------- |
| 1015 | // parse_hunk_size - parse the hunk_size |
| 1016 | // parameter in a standard way |
| 1017 | //------------------------------------------------- |
| 1570 | 1018 | |
| 1571 | | /* create the output files */ |
| 1572 | | outfile = fopen(outputfile, "w"); |
| 1573 | | if (outfile == NULL) |
| 1019 | static void parse_hunk_size(const parameters_t ¶ms, UINT32 required_granularity, UINT32 &hunk_size) |
| 1020 | { |
| 1021 | astring *hunk_size_str = params.find(OPTION_HUNK_SIZE); |
| 1022 | if (hunk_size_str != NULL) |
| 1574 | 1023 | { |
| 1575 | | fprintf(stderr, "Error opening output file '%s'\n", outputfile); |
| 1576 | | err = CHDERR_CANT_CREATE_FILE; |
| 1577 | | goto cleanup; |
| 1024 | hunk_size = parse_number(*hunk_size_str); |
| 1025 | if (hunk_size < 16 || hunk_size > 1024 * 1024) |
| 1026 | report_error(1, "Invalid hunk size"); |
| 1027 | if (hunk_size % required_granularity != 0) |
| 1028 | report_error(1, "Hunk size is not an even multiple of %d", required_granularity); |
| 1578 | 1029 | } |
| 1030 | } |
| 1579 | 1031 | |
| 1580 | | if (!cuemode) |
| 1032 | |
| 1033 | //------------------------------------------------- |
| 1034 | // parse_compression - parse a standard |
| 1035 | // compression parameter string |
| 1036 | //------------------------------------------------- |
| 1037 | |
| 1038 | static void parse_compression(const parameters_t ¶ms, chd_codec_type compression[4]) |
| 1039 | { |
| 1040 | // see if anything was specified |
| 1041 | astring *compression_str = params.find(OPTION_COMPRESSION); |
| 1042 | if (compression_str == NULL) |
| 1043 | return; |
| 1044 | |
| 1045 | // special case: 'none' |
| 1046 | if (*compression_str == "none") |
| 1581 | 1047 | { |
| 1582 | | fprintf(outfile, "CD_ROM\n\n\n"); |
| 1048 | compression[0] = compression[1] = compression[2] = compression[3] = CHD_CODEC_NONE; |
| 1049 | return; |
| 1583 | 1050 | } |
| 1584 | | |
| 1585 | | filerr = core_fopen(outputfile2, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &outfile2); |
| 1586 | | if (filerr != FILERR_NONE) |
| 1051 | |
| 1052 | // iterate through compressors |
| 1053 | int index = 0; |
| 1054 | for (int start = 0, end = compression_str->chr(0, ','); index < 4; start = end + 1, end = compression_str->chr(end + 1, ',')) |
| 1587 | 1055 | { |
| 1588 | | fprintf(stderr, "Error opening output file '%s'\n", outputfile2); |
| 1589 | | err = CHDERR_CANT_CREATE_FILE; |
| 1590 | | goto cleanup; |
| 1056 | astring name(*compression_str, start, (end == -1) ? -1 : end - start); |
| 1057 | if (name.len() != 4) |
| 1058 | report_error(1, "Invalid compressor '%s' specified", name.cstr()); |
| 1059 | chd_codec_type type = CHD_MAKE_TAG(name[0], name[1], name[2], name[3]); |
| 1060 | if (!chd_codec_list::codec_exists(type)) |
| 1061 | report_error(1, "Invalid compressor '%s' specified", name.cstr()); |
| 1062 | compression[index++] = type; |
| 1063 | if (end == -1) |
| 1064 | break; |
| 1591 | 1065 | } |
| 1066 | } |
| 1592 | 1067 | |
| 1593 | | /* process away */ |
| 1594 | | out2offs = 0; |
| 1595 | | discoffs = 0; |
| 1596 | | for (track = 0; track < toc->numtrks; track++) |
| 1597 | | { |
| 1598 | | UINT32 m, s, f, frame, trackframes; |
| 1599 | 1068 | |
| 1600 | | progress(TRUE, "Extracting track %d... \r", track+1); |
| 1069 | //------------------------------------------------- |
| 1070 | // parse_numprocessors - handle the numprocessors |
| 1071 | // command |
| 1072 | //------------------------------------------------- |
| 1601 | 1073 | |
| 1602 | | trackframes = toc->tracks[track].frames; |
| 1074 | static void parse_numprocessors(const parameters_t ¶ms) |
| 1075 | { |
| 1076 | #if NUM_PROCESSORS_SUPPORTED |
| 1077 | astring *numprocessors_str = params.find(OPTION_NUMPROCESSORS); |
| 1078 | if (numprocessors_str == NULL) |
| 1079 | return; |
| 1603 | 1080 | |
| 1604 | | if (cuemode) |
| 1605 | | { |
| 1606 | | char modestr[16]; |
| 1081 | int count = atoi(*numprocessors_str); |
| 1082 | if (count > 0) |
| 1083 | { |
| 1084 | extern int osd_num_processors; |
| 1085 | osd_num_processors = count; |
| 1086 | } |
| 1087 | #endif |
| 1088 | } |
| 1607 | 1089 | |
| 1608 | | if (track == 0) |
| 1609 | | { |
| 1610 | | fprintf(outfile, "FILE \"%s\" BINARY\n", outputfile2); |
| 1611 | | } |
| 1612 | 1090 | |
| 1613 | | switch (toc->tracks[track].trktype) |
| 1614 | | { |
| 1615 | | case CD_TRACK_MODE1: |
| 1616 | | case CD_TRACK_MODE1_RAW: |
| 1617 | | sprintf(modestr, "MODE1/%04d", toc->tracks[track].datasize); |
| 1618 | | break; |
| 1091 | //------------------------------------------------- |
| 1092 | // compression_string - create a friendly string |
| 1093 | // describing a set of compressors |
| 1094 | //------------------------------------------------- |
| 1619 | 1095 | |
| 1620 | | case CD_TRACK_MODE2: |
| 1621 | | case CD_TRACK_MODE2_FORM1: |
| 1622 | | case CD_TRACK_MODE2_FORM2: |
| 1623 | | case CD_TRACK_MODE2_FORM_MIX: |
| 1624 | | case CD_TRACK_MODE2_RAW: |
| 1625 | | sprintf(modestr, "MODE2/%04d", toc->tracks[track].datasize); |
| 1626 | | break; |
| 1096 | static const char *compression_string(astring &string, chd_codec_type compression[4]) |
| 1097 | { |
| 1098 | // output compression types |
| 1099 | string.reset(); |
| 1100 | if (compression[0] == CHD_CODEC_NONE) |
| 1101 | return string.cpy("none"); |
| 1102 | |
| 1103 | // iterate over types |
| 1104 | for (int index = 0; index < 4; index++) |
| 1105 | { |
| 1106 | chd_codec_type type = compression[index]; |
| 1107 | if (type == CHD_CODEC_NONE) |
| 1108 | break; |
| 1109 | if (index != 0) |
| 1110 | string.cat(", "); |
| 1111 | string.cat((type >> 24) & 0xff).cat((type >> 16) & 0xff).cat((type >> 8) & 0xff).cat(type & 0xff); |
| 1112 | string.cat(" (").cat(chd_codec_list::codec_name(type)).cat(")"); |
| 1113 | } |
| 1114 | return string; |
| 1115 | } |
| 1116 | |
| 1627 | 1117 | |
| 1628 | | case CD_TRACK_AUDIO: |
| 1629 | | strcpy(modestr, "AUDIO"); |
| 1630 | | break; |
| 1631 | | } |
| 1118 | //------------------------------------------------- |
| 1119 | // compress_common - standard compression loop |
| 1120 | //------------------------------------------------- |
| 1632 | 1121 | |
| 1633 | | fprintf(outfile, " TRACK %02d %s\n", track+1, modestr); |
| 1122 | static void compress_common(chd_file_compressor &chd) |
| 1123 | { |
| 1124 | // begin compressing |
| 1125 | chd.compress_begin(); |
| 1634 | 1126 | |
| 1635 | | if (toc->tracks[track].pregap > 0) |
| 1636 | | { |
| 1637 | | f = toc->tracks[track].pregap; |
| 1638 | | s = f / 75; |
| 1639 | | f %= 75; |
| 1640 | | m = s / 60; |
| 1641 | | s %= 60; |
| 1642 | | fprintf(outfile, " PREGAP %02d:%02d:%02d\n", m, s, f); |
| 1643 | | } |
| 1644 | | |
| 1645 | | f = discoffs; |
| 1646 | | s = f / 75; |
| 1647 | | f %= 75; |
| 1648 | | m = s / 60; |
| 1649 | | s %= 60; |
| 1650 | | fprintf(outfile, " INDEX 01 %02d:%02d:%02d\n", m, s, f); |
| 1651 | | |
| 1652 | | if (toc->tracks[track].postgap > 0) |
| 1653 | | { |
| 1654 | | f = toc->tracks[track].postgap; |
| 1655 | | s = f / 75; |
| 1656 | | f %= 75; |
| 1657 | | m = s / 60; |
| 1658 | | s %= 60; |
| 1659 | | fprintf(outfile, " POSTGAP %02d:%02d:%02d\n", m, s, f); |
| 1660 | | } |
| 1661 | | } |
| 1127 | // loop until done |
| 1128 | double complete, ratio; |
| 1129 | chd_error err; |
| 1130 | while ((err = chd.compress_continue(complete, ratio)) == CHDERR_WALKING_PARENT || err == CHDERR_COMPRESSING) |
| 1131 | if (err == CHDERR_WALKING_PARENT) |
| 1132 | progress(false, "Examining parent, %.1f%% complete... \r", 100.0 * complete); |
| 1662 | 1133 | else |
| 1663 | | { |
| 1664 | | char modesubmode[64]; |
| 1134 | progress(false, "Compressing, %.1f%% complete... (ratio=%.1f%%) \r", 100.0 * complete, 100.0 * ratio); |
| 1665 | 1135 | |
| 1666 | | fprintf(outfile, "// Track %d\n", track+1); |
| 1136 | // handle errors |
| 1137 | if (err != CHDERR_NONE) |
| 1138 | report_error(1, "Error during compression: %-40s", chd_file::error_string(err)); |
| 1667 | 1139 | |
| 1668 | | /* write out the track type */ |
| 1669 | | if (toc->tracks[track].subtype != CD_SUB_NONE) |
| 1670 | | { |
| 1671 | | sprintf(modesubmode, "%s %s", cdrom_get_type_string(toc->tracks[track].trktype), cdrom_get_subtype_string(toc->tracks[track].subtype)); |
| 1672 | | } |
| 1673 | | else |
| 1674 | | { |
| 1675 | | sprintf(modesubmode, "%s", cdrom_get_type_string(toc->tracks[track].trktype)); |
| 1676 | | } |
| 1140 | // final progress update |
| 1141 | progress(true, "Compression complete ... final ratio = %.1f%% \n", 100.0 * ratio); |
| 1142 | } |
| 1677 | 1143 | |
| 1678 | | fprintf(outfile, "TRACK %s\n", modesubmode); |
| 1679 | 1144 | |
| 1680 | | /* write out the attributes */ |
| 1681 | | fprintf(outfile, "NO COPY\n"); |
| 1682 | | if (toc->tracks[track].trktype == CD_TRACK_AUDIO) |
| 1683 | | { |
| 1684 | | fprintf(outfile, "NO PRE_EMPHASIS\n"); |
| 1685 | | fprintf(outfile, "TWO_CHANNEL_AUDIO\n"); |
| 1686 | | } |
| 1145 | //------------------------------------------------- |
| 1146 | // output_track_metadata - output track metadata |
| 1147 | // to a CUE file |
| 1148 | //------------------------------------------------- |
| 1687 | 1149 | |
| 1688 | | if (toc->tracks[track].pregap > 0) |
| 1689 | | { |
| 1690 | | f = toc->tracks[track].pregap; |
| 1691 | | s = f / 75; |
| 1692 | | f %= 75; |
| 1693 | | m = s / 60; |
| 1694 | | s %= 60; |
| 1150 | void output_track_metadata(bool cuemode, core_file *file, int tracknum, const cdrom_track_info &info, const char *filename, UINT32 frameoffs, UINT64 discoffs) |
| 1151 | { |
| 1152 | // CUE mode? |
| 1153 | if (cuemode) |
| 1154 | { |
| 1155 | // first track specifies the file |
| 1156 | if (tracknum == 0) |
| 1157 | core_fprintf(file, "FILE \"%s\" BINARY\n", filename); |
| 1695 | 1158 | |
| 1696 | | fprintf(outfile, "ZERO %s %02d:%02d:%02d\n", modesubmode, m, s, f); |
| 1697 | | } |
| 1159 | // determine submode |
| 1160 | astring tempstr; |
| 1161 | switch (info.trktype) |
| 1162 | { |
| 1163 | case CD_TRACK_MODE1: |
| 1164 | case CD_TRACK_MODE1_RAW: |
| 1165 | tempstr.format("MODE1/%04d", info.datasize); |
| 1166 | break; |
| 1698 | 1167 | |
| 1699 | | /* convert to minutes/seconds/frames */ |
| 1700 | | f = trackframes; |
| 1701 | | s = f / 75; |
| 1702 | | f %= 75; |
| 1703 | | m = s / 60; |
| 1704 | | s %= 60; |
| 1168 | case CD_TRACK_MODE2: |
| 1169 | case CD_TRACK_MODE2_FORM1: |
| 1170 | case CD_TRACK_MODE2_FORM2: |
| 1171 | case CD_TRACK_MODE2_FORM_MIX: |
| 1172 | case CD_TRACK_MODE2_RAW: |
| 1173 | tempstr.format("MODE2/%04d", info.datasize); |
| 1174 | break; |
| 1705 | 1175 | |
| 1706 | | /* all tracks but the first one have a file offset */ |
| 1707 | | if (track > 0) |
| 1708 | | 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)); |
| 1709 | | else |
| 1710 | | 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)); |
| 1711 | | |
| 1712 | | /* tracks with pregaps get a START marker too */ |
| 1713 | | if (toc->tracks[track].pregap > 0) |
| 1714 | | { |
| 1715 | | f = toc->tracks[track].pregap; |
| 1716 | | s = f / 75; |
| 1717 | | f %= 75; |
| 1718 | | m = s / 60; |
| 1719 | | s %= 60; |
| 1720 | | |
| 1721 | | fprintf(outfile, "START %02d:%02d:%02d\n", m, s, f); |
| 1722 | | } |
| 1723 | | |
| 1724 | | fprintf(outfile, "\n\n"); |
| 1176 | case CD_TRACK_AUDIO: |
| 1177 | tempstr.cpy("AUDIO"); |
| 1178 | break; |
| 1725 | 1179 | } |
| 1180 | |
| 1181 | // output TRACK entry |
| 1182 | core_fprintf(file, " TRACK %02d %s\n", tracknum + 1, tempstr.cstr()); |
| 1726 | 1183 | |
| 1727 | | /* now write the actual data */ |
| 1728 | | for (frame = 0; frame < trackframes; frame++) |
| 1729 | | { |
| 1730 | | UINT8 sector[CD_MAX_SECTOR_DATA + CD_MAX_SUBCODE_DATA]; |
| 1731 | | UINT32 byteswritten; |
| 1184 | // output PREGAP |
| 1185 | if (info.pregap > 0) |
| 1186 | core_fprintf(file, " PREGAP %s\n", msf_string_from_frames(tempstr, info.pregap)); |
| 1732 | 1187 | |
| 1733 | | progress(frame == 0, "Extracting track %d... %d/%d... \r", track+1, frame, trackframes); |
| 1188 | // output track data |
| 1189 | core_fprintf(file, " INDEX 01 %s\n", msf_string_from_frames(tempstr, frameoffs)); |
| 1734 | 1190 | |
| 1735 | | /* read the raw data */ |
| 1736 | | cdrom_read_data(cdrom, cdrom_get_track_start(cdrom, track) + frame, sector, toc->tracks[track].trktype); |
| 1191 | // output POSTGAP |
| 1192 | if (info.postgap > 0) |
| 1193 | core_fprintf(file, " POSTGAP %s\n", msf_string_from_frames(tempstr, info.postgap)); |
| 1194 | } |
| 1195 | |
| 1196 | // non-CUE mode |
| 1197 | else |
| 1198 | { |
| 1199 | // header on the first track |
| 1200 | if (tracknum == 0) |
| 1201 | core_fprintf(file, "CD_ROM\n\n\n"); |
| 1202 | core_fprintf(file, "// Track %d\n", tracknum + 1); |
| 1737 | 1203 | |
| 1738 | | /* for CDRWin, audio tracks must be reversed */ |
| 1739 | | if ((cuemode) && (toc->tracks[track].trktype == CD_TRACK_AUDIO)) |
| 1740 | | { |
| 1741 | | int swapindex; |
| 1204 | // write out the track type |
| 1205 | astring modesubmode; |
| 1206 | if (info.subtype != CD_SUB_NONE) |
| 1207 | modesubmode.format("%s %s", cdrom_get_type_string(info.trktype), cdrom_get_subtype_string(info.subtype)); |
| 1208 | else |
| 1209 | modesubmode.format("%s", cdrom_get_type_string(info.trktype)); |
| 1210 | core_fprintf(file, "TRACK %s\n", modesubmode.cstr()); |
| 1742 | 1211 | |
| 1743 | | for (swapindex = 0; swapindex < toc->tracks[track].datasize; swapindex += 2) |
| 1744 | | { |
| 1745 | | UINT8 swaptemp = sector[swapindex]; |
| 1746 | | sector[swapindex] = sector[swapindex+1]; |
| 1747 | | sector[swapindex + 1] = swaptemp; |
| 1748 | | } |
| 1749 | | } |
| 1212 | // write out the attributes |
| 1213 | core_fprintf(file, "NO COPY\n"); |
| 1214 | if (info.trktype == CD_TRACK_AUDIO) |
| 1215 | { |
| 1216 | core_fprintf(file, "NO PRE_EMPHASIS\n"); |
| 1217 | core_fprintf(file, "TWO_CHANNEL_AUDIO\n"); |
| 1218 | } |
| 1750 | 1219 | |
| 1751 | | /* write it out */ |
| 1752 | | core_fseek(outfile2, out2offs, SEEK_SET); |
| 1753 | | byteswritten = core_fwrite(outfile2, sector, toc->tracks[track].datasize); |
| 1754 | | if (byteswritten != toc->tracks[track].datasize) |
| 1755 | | { |
| 1756 | | fprintf(stderr, "Error writing frame %d to output file: %s\n", frame, chd_error_string(CHDERR_WRITE_ERROR)); |
| 1757 | | err = CHDERR_WRITE_ERROR; |
| 1758 | | goto cleanup; |
| 1759 | | } |
| 1760 | | out2offs += toc->tracks[track].datasize; |
| 1761 | | discoffs++; |
| 1220 | // output pregap |
| 1221 | astring tempstr; |
| 1222 | if (info.pregap > 0) |
| 1223 | core_fprintf(file, "ZERO %s %s\n", modesubmode.cstr(), msf_string_from_frames(tempstr, info.pregap)); |
| 1762 | 1224 | |
| 1763 | | /* read the subcode data */ |
| 1764 | | if (toc->tracks[track].subtype != CD_SUB_NONE) |
| 1765 | | { |
| 1766 | | cdrom_read_subcode(cdrom, cdrom_get_track_start(cdrom, track) + frame, sector); |
| 1225 | // all tracks but the first one have a file offset |
| 1226 | if (tracknum > 0) |
| 1227 | core_fprintf(file, "DATAFILE \"%s\" #%d %s // length in bytes: %d\n", filename, UINT32(discoffs), msf_string_from_frames(tempstr, info.frames), info.frames * (info.datasize + info.subsize)); |
| 1228 | else |
| 1229 | core_fprintf(file, "DATAFILE \"%s\" %s // length in bytes: %d\n", filename, msf_string_from_frames(tempstr, info.frames), info.frames * (info.datasize + info.subsize)); |
| 1767 | 1230 | |
| 1768 | | /* write it out */ |
| 1769 | | core_fseek(outfile2, out2offs, SEEK_SET); |
| 1770 | | byteswritten = core_fwrite(outfile2, sector, toc->tracks[track].subsize); |
| 1771 | | if (byteswritten != toc->tracks[track].subsize) |
| 1772 | | { |
| 1773 | | fprintf(stderr, "Error writing frame %d to output file: %s\n", frame, chd_error_string(CHDERR_WRITE_ERROR)); |
| 1774 | | err = CHDERR_WRITE_ERROR; |
| 1775 | | goto cleanup; |
| 1776 | | } |
| 1777 | | out2offs += toc->tracks[track].subsize; |
| 1778 | | } |
| 1779 | | } |
| 1780 | | progress(TRUE, "Extracting track %d... complete \n", track+1); |
| 1781 | | } |
| 1782 | | progress(TRUE, "Completed!\n"); |
| 1231 | // tracks with pregaps get a START marker too |
| 1232 | if (info.pregap > 0) |
| 1233 | core_fprintf(file, "START %s\n", msf_string_from_frames(tempstr, info.pregap)); |
| 1783 | 1234 | |
| 1784 | | cleanup: |
| 1785 | | /* close everything down */ |
| 1786 | | if (outfile != NULL) |
| 1787 | | fclose(outfile); |
| 1788 | | if (outfile2 != NULL) |
| 1789 | | core_fclose(outfile2); |
| 1790 | | if (cdrom != NULL) |
| 1791 | | cdrom_close(cdrom); |
| 1792 | | if (inputchd != NULL) |
| 1793 | | chd_close(inputchd); |
| 1794 | | if (err != CHDERR_NONE) |
| 1795 | | osd_rmfile(outputfile); |
| 1796 | | return (err != CHDERR_NONE); |
| 1235 | core_fprintf(file, "\n\n"); |
| 1236 | } |
| 1797 | 1237 | } |
| 1798 | 1238 | |
| 1799 | 1239 | |
| 1800 | | /*------------------------------------------------- |
| 1801 | | do_extractav - extract an AVI file from a |
| 1802 | | CHD image |
| 1803 | | -------------------------------------------------*/ |
| 1240 | //------------------------------------------------- |
| 1241 | // do_info - dump the header information from |
| 1242 | // a drive image |
| 1243 | //------------------------------------------------- |
| 1804 | 1244 | |
| 1805 | | static int do_extractav(int argc, char *argv[], int param) |
| 1245 | static void do_info(parameters_t ¶ms) |
| 1806 | 1246 | { |
| 1807 | | int fps, fpsfrac, width, height, interlaced, channels, rate, totalframes; |
| 1808 | | av_codec_decompress_config avconfig; |
| 1809 | | const char *inputfile, *outputfile; |
| 1810 | | UINT32 firstframe, numframes; |
| 1811 | | UINT32 framenum, numsamples; |
| 1812 | | UINT32 fps_times_1million; |
| 1813 | | const chd_header *header; |
| 1814 | | chd_file *chd = NULL; |
| 1815 | | avi_file *avi = NULL; |
| 1816 | | avi_movie_info info; |
| 1817 | | char metadata[256]; |
| 1818 | | avi_error avierr; |
| 1819 | | chd_error err; |
| 1820 | | int chnum; |
| 1247 | // parse out input files |
| 1248 | chd_file input_parent_chd; |
| 1249 | chd_file input_chd; |
| 1250 | parse_input_chd_parameters(params, input_chd, input_parent_chd); |
| 1821 | 1251 | |
| 1822 | | /* require 4-6 args total */ |
| 1823 | | if (argc < 4 || argc > 6) |
| 1824 | | return usage(); |
| 1252 | // print filename and version |
| 1253 | astring tempstr; |
| 1254 | printf("Input file: %s\n", params.find(OPTION_INPUT)->cstr()); |
| 1255 | printf("File Version: %d\n", input_chd.version()); |
| 1256 | if (input_chd.version() < 3) |
| 1257 | report_error(1, "Unsupported version (%d); use an older chdman to upgrade to version 3 or later", input_chd.version()); |
| 1825 | 1258 | |
| 1826 | | /* extract the data */ |
| 1827 | | inputfile = argv[2]; |
| 1828 | | outputfile = argv[3]; |
| 1829 | | firstframe = (argc > 4) ? atoi(argv[4]) : 0; |
| 1830 | | numframes = (argc > 5) ? atoi(argv[5]) : 1000000; |
| 1259 | // output cmpression and size information |
| 1260 | chd_codec_type compression[4] = { input_chd.compression(0), input_chd.compression(1), input_chd.compression(2), input_chd.compression(3) }; |
| 1261 | printf("Logical size: %s bytes\n", big_int_string(tempstr, input_chd.logical_bytes())); |
| 1262 | printf("Hunk Size: %s bytes\n", big_int_string(tempstr, input_chd.hunk_bytes())); |
| 1263 | printf("Total Hunks: %s\n", big_int_string(tempstr, input_chd.hunk_count())); |
| 1264 | printf("Unit Size: %s bytes\n", big_int_string(tempstr, input_chd.unit_bytes())); |
| 1265 | printf("Total Units: %s\n", big_int_string(tempstr, input_chd.unit_count())); |
| 1266 | printf("Compression: %s\n", compression_string(tempstr, compression)); |
| 1267 | printf("CHD size: %s bytes\n", big_int_string(tempstr, core_fsize(input_chd))); |
| 1268 | if (compression[0] != CHD_CODEC_NONE) |
| 1269 | printf("Ratio: %.1f%%\n", 100.0 * double(core_fsize(input_chd)) / double(input_chd.logical_bytes())); |
| 1831 | 1270 | |
| 1832 | | /* print some info */ |
| 1833 | | printf("Input file: %s\n", inputfile); |
| 1834 | | printf("Output file: %s\n", outputfile); |
| 1835 | | |
| 1836 | | /* get the header */ |
| 1837 | | err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd); |
| 1838 | | if (err != CHDERR_NONE) |
| 1271 | // add SHA1 output |
| 1272 | sha1_t overall = input_chd.sha1(); |
| 1273 | if (overall != sha1_t::null) |
| 1839 | 1274 | { |
| 1840 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); |
| 1841 | | goto cleanup; |
| 1275 | printf("SHA1: %s\n", overall.as_string(tempstr)); |
| 1276 | if (input_chd.version() >= 4) |
| 1277 | printf("Data SHA1: %s\n", input_chd.raw_sha1().as_string(tempstr)); |
| 1842 | 1278 | } |
| 1843 | | header = chd_get_header(chd); |
| 1279 | sha1_t parent = input_chd.parent_sha1(); |
| 1280 | if (parent != sha1_t::null) |
| 1281 | printf("Parent SHA1: %s\n", parent.as_string(tempstr)); |
| 1844 | 1282 | |
| 1845 | | /* get the metadata */ |
| 1846 | | err = chd_get_metadata(chd, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); |
| 1847 | | if (err != CHDERR_NONE) |
| 1283 | // print out metadata |
| 1284 | dynamic_buffer buffer; |
| 1285 | dynamic_array<metadata_index_info> info; |
| 1286 | for (int index = 0; ; index++) |
| 1848 | 1287 | { |
| 1849 | | fprintf(stderr, "Error getting A/V metadata: %s\n", chd_error_string(err)); |
| 1850 | | goto cleanup; |
| 1851 | | } |
| 1852 | | |
| 1853 | | /* extract the info */ |
| 1854 | | if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) |
| 1855 | | { |
| 1856 | | fprintf(stderr, "Improperly formatted metadata\n"); |
| 1857 | | err = CHDERR_INVALID_METADATA; |
| 1858 | | goto cleanup; |
| 1859 | | } |
| 1860 | | fps_times_1million = fps * 1000000 + fpsfrac; |
| 1861 | | totalframes = header->totalhunks; |
| 1862 | | |
| 1863 | | /* adjust for interlacing */ |
| 1864 | | if (interlaced) |
| 1865 | | { |
| 1866 | | fps_times_1million /= 2; |
| 1867 | | height *= 2; |
| 1868 | | firstframe *= 2; |
| 1869 | | numframes *= 2; |
| 1870 | | } |
| 1871 | | numframes = MIN(totalframes - firstframe, numframes); |
| 1872 | | |
| 1873 | | /* allocate a video buffer */ |
| 1874 | | { |
| 1875 | | bitmap_yuy16 fullbitmap(width, height); |
| 1876 | | avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); |
| 1877 | | |
| 1878 | | /* allocate audio buffers */ |
| 1879 | | avconfig.maxsamples = ((UINT64)rate * 1000000 + fps_times_1million - 1) / fps_times_1million; |
| 1880 | | avconfig.actsamples = &numsamples; |
| 1881 | | for (chnum = 0; chnum < channels; chnum++) |
| 1882 | | { |
| 1883 | | avconfig.audio[chnum] = (INT16 *)malloc(avconfig.maxsamples * 2); |
| 1884 | | if (avconfig.audio[chnum] == NULL) |
| 1288 | // get the indexed metadata item; stop when we hit an error |
| 1289 | chd_metadata_tag metatag; |
| 1290 | UINT8 metaflags; |
| 1291 | chd_error err = input_chd.read_metadata(CHDMETATAG_WILDCARD, index, buffer, metatag, metaflags); |
| 1292 | if (err != CHDERR_NONE) |
| 1293 | break; |
| 1294 | |
| 1295 | // determine our index |
| 1296 | UINT32 metaindex = ~0; |
| 1297 | for (int cur = 0; cur < info.count(); cur++) |
| 1298 | if (info[cur].tag == metatag) |
| 1885 | 1299 | { |
| 1886 | | fprintf(stderr, "Out of memory allocating temporary audio buffer\n"); |
| 1887 | | err = CHDERR_OUT_OF_MEMORY; |
| 1888 | | goto cleanup; |
| 1300 | metaindex = ++info[cur].index; |
| 1301 | break; |
| 1889 | 1302 | } |
| 1890 | | } |
| 1891 | | |
| 1892 | | /* print some of it */ |
| 1893 | | printf("Use frames: %d-%d\n", firstframe, firstframe + numframes - 1); |
| 1894 | | printf("Frame rate: %d.%06d\n", fps_times_1million / 1000000, fps_times_1million % 1000000); |
| 1895 | | printf("Frame size: %d x %d %s\n", width, height, interlaced ? "interlaced" : "non-interlaced"); |
| 1896 | | printf("Audio: %d channels at %d Hz\n", channels, rate); |
| 1897 | | printf("Total frames: %d (%02d:%02d:%02d)\n", totalframes, |
| 1898 | | (UINT32)((UINT64)totalframes * 1000000 / fps_times_1million / 60 / 60), |
| 1899 | | (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million / 60) % 60), |
| 1900 | | (UINT32)(((UINT64)totalframes * 1000000 / fps_times_1million) % 60)); |
| 1901 | | |
| 1902 | | /* build up the movie info */ |
| 1903 | | info.video_format = FORMAT_YUY2; |
| 1904 | | info.video_timescale = fps_times_1million; |
| 1905 | | info.video_sampletime = 1000000; |
| 1906 | | info.video_width = width; |
| 1907 | | info.video_height = height; |
| 1908 | | info.video_depth = 16; |
| 1909 | | info.audio_format = 0; |
| 1910 | | info.audio_timescale = rate; |
| 1911 | | info.audio_sampletime = 1; |
| 1912 | | info.audio_channels = channels; |
| 1913 | | info.audio_samplebits = 16; |
| 1914 | | info.audio_samplerate = rate; |
| 1915 | | |
| 1916 | | /* create the output file */ |
| 1917 | | avierr = avi_create(outputfile, &info, &avi); |
| 1918 | | if (avierr != AVIERR_NONE) |
| 1303 | |
| 1304 | // if not found, add to our tracking |
| 1305 | if (metaindex == ~0) |
| 1919 | 1306 | { |
| 1920 | | fprintf(stderr, "Error opening output file '%s': %s\n", outputfile, avi_error_string(avierr)); |
| 1921 | | err = CHDERR_CANT_CREATE_FILE; |
| 1922 | | goto cleanup; |
| 1307 | metadata_index_info curinfo = { metatag, 0 }; |
| 1308 | info.append(curinfo); |
| 1309 | metaindex = 0; |
| 1923 | 1310 | } |
| 1924 | 1311 | |
| 1925 | | /* loop over hunks, reading and writing */ |
| 1926 | | for (framenum = 0; framenum < numframes; framenum++) |
| 1927 | | { |
| 1928 | | /* progress */ |
| 1929 | | progress(framenum == 0, "Extracting hunk %d/%d... \r", framenum, numframes); |
| 1312 | // print either a string representation or a hex representation of the tag |
| 1313 | if (isprint((metatag >> 24) & 0xff) && isprint((metatag >> 16) & 0xff) && isprint((metatag >> 8) & 0xff) && isprint(metatag & 0xff)) |
| 1314 | printf("Metadata: Tag='%c%c%c%c' Index=%d Length=%d bytes\n", (metatag >> 24) & 0xff, (metatag >> 16) & 0xff, (metatag >> 8) & 0xff, metatag & 0xff, metaindex, buffer.count()); |
| 1315 | else |
| 1316 | printf("Metadata: Tag=%08x Index=%d Length=%d bytes\n", metatag, metaindex, buffer.count()); |
| 1317 | printf(" "); |
| 1930 | 1318 | |
| 1931 | | /* set up the fake bitmap for this frame */ |
| 1932 | | if (!interlaced) |
| 1933 | | avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); |
| 1934 | | else |
| 1935 | | avconfig.video.wrap(&fullbitmap.pix16(framenum % 2), fullbitmap.width(), fullbitmap.height() / 2, fullbitmap.rowpixels() * 2); |
| 1936 | | |
| 1937 | | /* configure the decompressor for this frame */ |
| 1938 | | chd_codec_config(chd, AV_CODEC_DECOMPRESS_CONFIG, &avconfig); |
| 1939 | | |
| 1940 | | /* read the hunk into the buffers */ |
| 1941 | | err = chd_read(chd, firstframe + framenum, NULL); |
| 1319 | // print up to 60 characters of metadata |
| 1320 | UINT32 count = MIN(60, buffer.count()); |
| 1321 | for (int chnum = 0; chnum < count; chnum++) |
| 1322 | printf("%c", isprint(UINT8(buffer[chnum])) ? buffer[chnum] : '.'); |
| 1323 | printf("\n"); |
| 1324 | } |
| 1325 | |
| 1326 | // print compression stats if verbose |
| 1327 | if (params.find(OPTION_VERBOSE) != NULL) |
| 1328 | { |
| 1329 | UINT32 compression_types[10] = { 0 }; |
| 1330 | for (UINT32 hunknum = 0; hunknum < input_chd.hunk_count(); hunknum++) |
| 1331 | { |
| 1332 | // get info on this hunk |
| 1333 | chd_codec_type codec; |
| 1334 | UINT32 compbytes; |
| 1335 | chd_error err = input_chd.hunk_info(hunknum, codec, compbytes); |
| 1942 | 1336 | if (err != CHDERR_NONE) |
| 1337 | report_error(1, "Error getting info on hunk %d: %s", hunknum, chd_file::error_string(err)); |
| 1338 | |
| 1339 | // decode into our data |
| 1340 | if (codec > CHD_CODEC_MINI) |
| 1341 | for (int comptype = 0; comptype < 4; comptype++) |
| 1342 | if (codec == input_chd.compression(comptype)) |
| 1343 | { |
| 1344 | codec = CHD_CODEC_MINI + 1 + comptype; |
| 1345 | break; |
| 1346 | } |
| 1347 | if (codec > ARRAY_LENGTH(compression_types)) |
| 1348 | codec = ARRAY_LENGTH(compression_types) - 1; |
| 1349 | |
| 1350 | // count stats |
| 1351 | compression_types[codec]++; |
| 1352 | } |
| 1353 | |
| 1354 | // output the stats |
| 1355 | printf("\n"); |
| 1356 | printf(" Hunks Percent Name\n"); |
| 1357 | printf("---------- ------- ------------------------------------\n"); |
| 1358 | for (int comptype = 0; comptype < ARRAY_LENGTH(compression_types); comptype++) |
| 1359 | if (compression_types[comptype] != 0) |
| 1943 | 1360 | { |
| 1944 | | fprintf(stderr, "Error reading hunk %d from CHD file: %s\n", firstframe + framenum, chd_error_string(err)); |
| 1945 | | goto cleanup; |
| 1946 | | } |
| 1947 | | |
| 1948 | | /* write audio */ |
| 1949 | | for (chnum = 0; chnum < channels; chnum++) |
| 1950 | | { |
| 1951 | | avierr = avi_append_sound_samples(avi, chnum, avconfig.audio[chnum], numsamples, 0); |
| 1952 | | if (avierr != AVIERR_NONE) |
| 1361 | // determine the name |
| 1362 | const char *name = "Unknown"; |
| 1363 | switch (comptype) |
| 1953 | 1364 | { |
| 1954 | | fprintf(stderr, "Error writing samples for hunk %d to AVI file: %s\n", firstframe + framenum, avi_error_string(avierr)); |
| 1955 | | goto cleanup; |
| 1365 | case CHD_CODEC_NONE: name = "Uncompressed"; break; |
| 1366 | case CHD_CODEC_SELF: name = "Copy from self"; break; |
| 1367 | case CHD_CODEC_PARENT: name = "Copy from parent"; break; |
| 1368 | case CHD_CODEC_MINI: name = "Legacy 8-byte mini"; break; |
| 1369 | default: |
| 1370 | int index = comptype - 1 - CHD_CODEC_MINI; |
| 1371 | if (index < 4) |
| 1372 | name = chd_codec_list::codec_name(input_chd.compression(index)); |
| 1373 | break; |
| 1956 | 1374 | } |
| 1375 | |
| 1376 | // output the stats |
| 1377 | astring tempstr; |
| 1378 | printf("%10s %5.1f%% %-40s\n", |
| 1379 | big_int_string(tempstr, compression_types[comptype]), |
| 1380 | 100.0 * double(compression_types[comptype]) / double(input_chd.hunk_count()), |
| 1381 | name); |
| 1957 | 1382 | } |
| 1958 | | |
| 1959 | | /* write video */ |
| 1960 | | if (!interlaced || (firstframe + framenum) % 2 == 1) |
| 1961 | | { |
| 1962 | | avierr = avi_append_video_frame(avi, fullbitmap); |
| 1963 | | if (avierr != AVIERR_NONE) |
| 1964 | | { |
| 1965 | | fprintf(stderr, "Error writing video for hunk %d to AVI file: %s\n", firstframe + framenum, avi_error_string(avierr)); |
| 1966 | | goto cleanup; |
| 1967 | | } |
| 1968 | | } |
| 1969 | | } |
| 1970 | | progress(TRUE, "Extraction complete! \n"); |
| 1971 | 1383 | } |
| 1972 | | |
| 1973 | | cleanup: |
| 1974 | | /* clean up our mess */ |
| 1975 | | if (avi != NULL) |
| 1976 | | avi_close(avi); |
| 1977 | | for (chnum = 0; chnum < ARRAY_LENGTH(avconfig.audio); chnum++) |
| 1978 | | if (avconfig.audio[chnum] != NULL) |
| 1979 | | free(avconfig.audio[chnum]); |
| 1980 | | if (chd != NULL) |
| 1981 | | chd_close(chd); |
| 1982 | | if (err != CHDERR_NONE) |
| 1983 | | osd_rmfile(outputfile); |
| 1984 | | return (err != CHDERR_NONE); |
| 1985 | 1384 | } |
| 1986 | 1385 | |
| 1987 | 1386 | |
| 1988 | | /*------------------------------------------------- |
| 1989 | | do_verify - validate the MD5/SHA1 on a drive |
| 1990 | | image |
| 1991 | | -------------------------------------------------*/ |
| 1387 | //------------------------------------------------- |
| 1388 | // do_verify - validate the SHA1 on a CHD |
| 1389 | //------------------------------------------------- |
| 1992 | 1390 | |
| 1993 | | static int do_verify(int argc, char *argv[], int param) |
| 1391 | static void do_verify(parameters_t ¶ms) |
| 1994 | 1392 | { |
| 1995 | | chd_verify_result verify; |
| 1996 | | const char *inputfile; |
| 1997 | | chd_file *chd = NULL; |
| 1998 | | chd_header header; |
| 1999 | | int fixed = FALSE; |
| 2000 | | chd_error err; |
| 2001 | | int i; |
| 1393 | // parse out input files |
| 1394 | chd_file input_parent_chd; |
| 1395 | chd_file input_chd; |
| 1396 | parse_input_chd_parameters(params, input_chd, input_parent_chd); |
| 2002 | 1397 | |
| 2003 | | /* require 3 args total */ |
| 2004 | | if (argc != 3) |
| 2005 | | return usage(); |
| 1398 | // only makes sense for compressed CHDs with valid SHA1's |
| 1399 | if (!input_chd.compressed()) |
| 1400 | report_error(0, "No verification to be done; CHD is uncompressed"); |
| 1401 | sha1_t raw_sha1 = (input_chd.version() <= 3) ? input_chd.sha1() : input_chd.raw_sha1(); |
| 1402 | if (raw_sha1 == sha1_t::null) |
| 1403 | report_error(0, "No verification to be done; CHD has no checksum"); |
| 2006 | 1404 | |
| 2007 | | /* extract the data */ |
| 2008 | | inputfile = argv[2]; |
| 2009 | | |
| 2010 | | /* print some info */ |
| 2011 | | printf("Input file: %s\n", inputfile); |
| 2012 | | |
| 2013 | | /* open the CHD file */ |
| 2014 | | err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd); |
| 2015 | | if (err != CHDERR_NONE) |
| 1405 | // create an array to read into |
| 1406 | dynamic_buffer buffer((TEMP_BUFFER_SIZE / input_chd.hunk_bytes()) * input_chd.hunk_bytes()); |
| 1407 | |
| 1408 | // read all the data and build up an SHA-1 |
| 1409 | sha1_creator rawsha1; |
| 1410 | for (UINT64 offset = 0; offset < input_chd.logical_bytes(); ) |
| 2016 | 1411 | { |
| 2017 | | fprintf(stderr, "Error opening CHD file: %s\n", chd_error_string(err)); |
| 2018 | | goto cleanup; |
| 2019 | | } |
| 2020 | | header = *chd_get_header(chd); |
| 1412 | progress(false, "Verifying, %.1f%% complete... \r", 100.0 * double(offset) / double(input_chd.logical_bytes())); |
| 2021 | 1413 | |
| 2022 | | /* verify the CHD data */ |
| 2023 | | err = chd_verify_begin(chd); |
| 2024 | | if (err == CHDERR_NONE) |
| 2025 | | { |
| 2026 | | UINT32 hunknum; |
| 2027 | | for (hunknum = 0; hunknum < header.totalhunks; hunknum++) |
| 2028 | | { |
| 2029 | | /* progress */ |
| 2030 | | progress(FALSE, "Verifying hunk %d/%d... \r", hunknum, header.totalhunks); |
| 2031 | | |
| 2032 | | /* verify the data */ |
| 2033 | | err = chd_verify_hunk(chd); |
| 2034 | | if (err != CHDERR_NONE) |
| 2035 | | break; |
| 2036 | | } |
| 2037 | | |
| 2038 | | /* finish it */ |
| 2039 | | if (err == CHDERR_NONE) |
| 2040 | | err = chd_verify_finish(chd, &verify); |
| 1414 | // determine how much to read |
| 1415 | UINT32 bytes_to_read = MIN(buffer.count(), input_chd.logical_bytes() - offset); |
| 1416 | chd_error err = input_chd.read_bytes(offset, buffer, bytes_to_read); |
| 1417 | if (err != CHDERR_NONE) |
| 1418 | report_error(1, "Error reading CHD file (%s): %s", params.find(OPTION_INPUT)->cstr(), chd_file::error_string(err)); |
| 1419 | |
| 1420 | // add to the checksum |
| 1421 | rawsha1.append(buffer, bytes_to_read); |
| 1422 | offset += bytes_to_read; |
| 2041 | 1423 | } |
| 2042 | | |
| 2043 | | /* handle errors */ |
| 2044 | | if (err != CHDERR_NONE) |
| 1424 | sha1_t computed_sha1 = rawsha1.finish(); |
| 1425 | |
| 1426 | // finish up |
| 1427 | if (raw_sha1 != computed_sha1) |
| 2045 | 1428 | { |
| 2046 | | if (err == CHDERR_CANT_VERIFY) |
| 2047 | | fprintf(stderr, "Can't verify this type of image (probably writeable)\n"); |
| 2048 | | else |
| 2049 | | fprintf(stderr, "\nError during verify: %s\n", chd_error_string(err)); |
| 2050 | | goto cleanup; |
| 2051 | | } |
| 1429 | astring tempstr; |
| 1430 | fprintf(stderr, "Error: Raw SHA1 in header = %s\n", raw_sha1.as_string(tempstr)); |
| 1431 | fprintf(stderr, " actual SHA1 = %s\n", computed_sha1.as_string(tempstr)); |
| 2052 | 1432 | |
| 2053 | | /* verify the MD5 */ |
| 2054 | | if (header.version <= 3) |
| 2055 | | { |
| 2056 | | if (memcmp(header.md5, verify.md5, sizeof(header.md5)) == 0) |
| 2057 | | printf("MD5 verification successful!\n"); |
| 2058 | | else |
| 1433 | // fix it if requested; this also fixes the overall one so we don't need to do any more |
| 1434 | if (params.find(OPTION_FIX) != NULL) |
| 2059 | 1435 | { |
| 2060 | | fprintf(stderr, "Error: MD5 in header = "); |
| 2061 | | for (i = 0; i < CHD_MD5_BYTES; i++) |
| 2062 | | fprintf(stderr, "%02x", header.md5[i]); |
| 2063 | | fprintf(stderr, "\n"); |
| 2064 | | fprintf(stderr, " actual MD5 = "); |
| 2065 | | for (i = 0; i < CHD_MD5_BYTES; i++) |
| 2066 | | fprintf(stderr, "%02x", verify.md5[i]); |
| 2067 | | fprintf(stderr, "\n"); |
| 2068 | | |
| 2069 | | /* fix it */ |
| 2070 | | if (param) |
| 2071 | | { |
| 2072 | | memcpy(header.md5, verify.md5, sizeof(header.md5)); |
| 2073 | | fixed = TRUE; |
| 2074 | | } |
| 1436 | input_chd.set_raw_sha1(computed_sha1); |
| 1437 | printf("SHA-1 updated to correct value in input CHD\n"); |
| 2075 | 1438 | } |
| 2076 | 1439 | } |
| 2077 | | |
| 2078 | | /* verify the SHA1 */ |
| 2079 | | if (header.version >= 3) |
| 1440 | else |
| 2080 | 1441 | { |
| 2081 | | if (memcmp(header.sha1, verify.sha1, sizeof(header.sha1)) == 0) |
| 2082 | | printf("SHA1 verification successful!\n"); |
| 2083 | | else |
| 1442 | printf("Raw SHA1 verification successful!\n"); |
| 1443 | |
| 1444 | // now include the metadata for >= v4 |
| 1445 | if (input_chd.version() >= 4) |
| 2084 | 1446 | { |
| 2085 | | fprintf(stderr, "Error: SHA1 in header = "); |
| 2086 | | for (i = 0; i < CHD_SHA1_BYTES; i++) |
| 2087 | | fprintf(stderr, "%02x", header.sha1[i]); |
| 2088 | | fprintf(stderr, "\n"); |
| 2089 | | fprintf(stderr, " actual SHA1 = "); |
| 2090 | | for (i = 0; i < CHD_SHA1_BYTES; i++) |
| 2091 | | fprintf(stderr, "%02x", verify.sha1[i]); |
| 2092 | | fprintf(stderr, "\n"); |
| 2093 | | |
| 2094 | | /* fix it */ |
| 2095 | | if (param) |
| 1447 | sha1_t computed_overall_sha1 = input_chd.compute_overall_sha1(computed_sha1); |
| 1448 | if (input_chd.sha1() == computed_overall_sha1) |
| 1449 | printf("Overall SHA1 verification successful!\n"); |
| 1450 | else |
| 2096 | 1451 | { |
| 2097 | | memcpy(header.sha1, verify.sha1, sizeof(header.sha1)); |
| 2098 | | fixed = TRUE; |
| 2099 | | } |
| 1452 | astring tempstr; |
| 1453 | fprintf(stderr, "Error: Overall SHA1 in header = %s\n", input_chd.sha1().as_string(tempstr)); |
| 1454 | fprintf(stderr, " actual SHA1 = %s\n", computed_overall_sha1.as_string(tempstr)); |
| 2100 | 1455 | |
| 2101 | | /* verify the raw SHA1 */ |
| 2102 | | if (header.version >= 4) |
| 2103 | | { |
| 2104 | | if (memcmp(header.rawsha1, verify.rawsha1, sizeof(header.rawsha1)) != 0) |
| 1456 | // fix it if requested |
| 1457 | if (params.find(OPTION_FIX) != NULL) |
| 2105 | 1458 | { |
| 2106 | | fprintf(stderr, "Error: raw SHA1 in header = "); |
| 2107 | | for (i = 0; i < CHD_SHA1_BYTES; i++) |
| 2108 | | fprintf(stderr, "%02x", header.rawsha1[i]); |
| 2109 | | fprintf(stderr, "\n"); |
| 2110 | | fprintf(stderr, " actual raw SHA1 = "); |
| 2111 | | for (i = 0; i < CHD_SHA1_BYTES; i++) |
| 2112 | | fprintf(stderr, "%02x", verify.rawsha1[i]); |
| 2113 | | fprintf(stderr, "\n"); |
| 2114 | | |
| 2115 | | /* fix it */ |
| 2116 | | if (param) |
| 2117 | | { |
| 2118 | | memcpy(header.rawsha1, verify.rawsha1, sizeof(header.rawsha1)); |
| 2119 | | fixed = TRUE; |
| 2120 | | } |
| 1459 | input_chd.set_raw_sha1(computed_sha1); |
| 1460 | printf("SHA-1 updated to correct value in input CHD\n"); |
| 2121 | 1461 | } |
| 2122 | 1462 | } |
| 2123 | 1463 | } |
| 2124 | 1464 | } |
| 2125 | | |
| 2126 | | /* close the drive */ |
| 2127 | | chd_close(chd); |
| 2128 | | chd = NULL; |
| 2129 | | |
| 2130 | | /* update the header */ |
| 2131 | | if (fixed) |
| 2132 | | { |
| 2133 | | err = chd_set_header(inputfile, &header); |
| 2134 | | if (err != CHDERR_NONE) |
| 2135 | | fprintf(stderr, "Error writing new header: %s\n", chd_error_string(err)); |
| 2136 | | else |
| 2137 | | printf("Updated header successfully\n"); |
| 2138 | | } |
| 2139 | | |
| 2140 | | cleanup: |
| 2141 | | /* close everything down */ |
| 2142 | | if (chd != NULL) |
| 2143 | | chd_close(chd); |
| 2144 | | return (err != CHDERR_NONE); |
| 2145 | 1465 | } |
| 2146 | 1466 | |
| 2147 | 1467 | |
| 2148 | | /*------------------------------------------------- |
| 2149 | | do_fixavdata - fix the AV metadata for an |
| 2150 | | A/V file |
| 2151 | | -------------------------------------------------*/ |
| 1468 | //------------------------------------------------- |
| 1469 | // do_create_raw - create a new compressed raw |
| 1470 | // image from a raw file |
| 1471 | //------------------------------------------------- |
| 2152 | 1472 | |
| 2153 | | static int do_fixavdata(int argc, char *argv[], int param) |
| 1473 | static void do_create_raw(parameters_t ¶ms) |
| 2154 | 1474 | { |
| 2155 | | int fps, fpsfrac, width, height, interlaced, channels, rate; |
| 2156 | | av_codec_decompress_config avconfig; |
| 2157 | | const char *inputfile; |
| 2158 | | UINT8 *vbidata = NULL; |
| 2159 | | int writeable = FALSE; |
| 2160 | | chd_file *chd = NULL; |
| 2161 | | bitmap_yuy16 fakebitmap; |
| 2162 | | char metadata[256]; |
| 2163 | | chd_header header; |
| 2164 | | UINT32 actlength; |
| 2165 | | UINT32 framenum; |
| 2166 | | chd_error err; |
| 2167 | | int fixframes = 0; |
| 2168 | | int fixes = 0; |
| 1475 | // catch errors so we can close & delete the output file |
| 1476 | try |
| 1477 | { |
| 1478 | // process input file |
| 1479 | core_file *input_file = NULL; |
| 1480 | astring *input_file_str = params.find(OPTION_INPUT); |
| 1481 | if (input_file_str != NULL) |
| 1482 | { |
| 1483 | file_error filerr = core_fopen(*input_file_str, OPEN_FLAG_READ, &input_file); |
| 1484 | if (filerr != FILERR_NONE) |
| 1485 | report_error(1, "Unable to open file (%s)", input_file_str->cstr()); |
| 1486 | } |
| 2169 | 1487 | |
| 2170 | | /* require 3 args total */ |
| 2171 | | if (argc != 3) |
| 2172 | | return usage(); |
| 1488 | // process output CHD |
| 1489 | chd_file output_parent; |
| 1490 | astring *output_chd_str = parse_output_chd_parameters(params, output_parent); |
| 1491 | |
| 1492 | // process hunk size |
| 1493 | UINT32 hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : 0; |
| 1494 | parse_hunk_size(params, 1, hunk_size); |
| 2173 | 1495 | |
| 2174 | | /* extract the data */ |
| 2175 | | inputfile = argv[2]; |
| 1496 | // process unit size |
| 1497 | UINT32 unit_size = output_parent.opened() ? output_parent.unit_bytes() : 0; |
| 1498 | astring *unit_size_str = params.find(OPTION_UNIT_SIZE); |
| 1499 | if (unit_size_str != NULL) |
| 1500 | { |
| 1501 | unit_size = parse_number(*unit_size_str); |
| 1502 | if (hunk_size % unit_size != 0) |
| 1503 | report_error(1, "Unit size is not an even divisor of the hunk size"); |
| 1504 | } |
| 2176 | 1505 | |
| 2177 | | /* print some info */ |
| 2178 | | printf("Input file: %s\n", inputfile); |
| 1506 | // process input start/end (needs to know hunk_size) |
| 1507 | UINT64 input_start; |
| 1508 | UINT64 input_end; |
| 1509 | parse_input_start_end(params, core_fsize(input_file), hunk_size, hunk_size, input_start, input_end); |
| 1510 | |
| 1511 | // process compression |
| 1512 | chd_codec_type compression[4]; |
| 1513 | memcpy(compression, s_default_raw_compression, sizeof(compression)); |
| 1514 | parse_compression(params, compression); |
| 1515 | |
| 1516 | // process numprocessors |
| 1517 | parse_numprocessors(params); |
| 2179 | 1518 | |
| 2180 | | /* get the header */ |
| 2181 | | err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd); |
| 2182 | | if (err != CHDERR_NONE) |
| 2183 | | { |
| 2184 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); |
| 2185 | | goto cleanup; |
| 2186 | | } |
| 2187 | | header = *chd_get_header(chd); |
| 1519 | // print some info |
| 1520 | astring tempstr; |
| 1521 | printf("Output CHD: %s\n", output_chd_str->cstr()); |
| 1522 | if (output_parent.opened()) |
| 1523 | printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); |
| 1524 | printf("Input file: %s\n", input_file_str->cstr()); |
| 1525 | if (input_start != 0 || input_end != core_fsize(input_file)) |
| 1526 | { |
| 1527 | printf("Input start: %s\n", big_int_string(tempstr, input_start)); |
| 1528 | printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); |
| 1529 | } |
| 1530 | printf("Compression: %s\n", compression_string(tempstr, compression)); |
| 1531 | printf("Hunk size: %s\n", big_int_string(tempstr, hunk_size)); |
| 1532 | printf("Logical size: %s\n", big_int_string(tempstr, input_end - input_start)); |
| 2188 | 1533 | |
| 2189 | | /* get the metadata */ |
| 2190 | | err = chd_get_metadata(chd, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); |
| 2191 | | if (err != CHDERR_NONE) |
| 2192 | | { |
| 2193 | | fprintf(stderr, "Error getting A/V metadata: %s\n", chd_error_string(err)); |
| 2194 | | goto cleanup; |
| 2195 | | } |
| 1534 | // create the new CHD |
| 1535 | chd_rawfile_compressor chd(input_file, input_start, input_end); |
| 1536 | chd_error err; |
| 1537 | if (output_parent.opened()) |
| 1538 | err = chd.create(*output_chd_str, input_end - input_start, hunk_size, compression, output_parent); |
| 1539 | else |
| 1540 | err = chd.create(*output_chd_str, input_end - input_start, hunk_size, unit_size, compression); |
| 1541 | if (err != CHDERR_NONE) |
| 1542 | report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); |
| 1543 | |
| 1544 | // if we have a parent, copy forward all the metadata |
| 1545 | if (output_parent.opened()) |
| 1546 | chd.clone_all_metadata(output_parent); |
| 2196 | 1547 | |
| 2197 | | /* extract the info */ |
| 2198 | | if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) |
| 2199 | | { |
| 2200 | | fprintf(stderr, "Improperly formatted metadata\n"); |
| 2201 | | err = CHDERR_INVALID_METADATA; |
| 2202 | | goto cleanup; |
| 1548 | // compress it generically |
| 1549 | compress_common(chd); |
| 2203 | 1550 | } |
| 2204 | | |
| 2205 | | /* allocate space for the frame data */ |
| 2206 | | if ((height != 524/2 && height != 624/2) || !interlaced) |
| 1551 | catch (...) |
| 2207 | 1552 | { |
| 2208 | | fprintf(stderr, "This file does not need VBI metadata\n"); |
| 2209 | | err = CHDERR_INVALID_METADATA; |
| 2210 | | goto cleanup; |
| 1553 | // delete the output file |
| 1554 | astring *output_chd_str = params.find(OPTION_OUTPUT); |
| 1555 | if (output_chd_str != NULL) |
| 1556 | osd_rmfile(*output_chd_str); |
| 1557 | throw; |
| 2211 | 1558 | } |
| 1559 | } |
| 2212 | 1560 | |
| 2213 | | /* allocate a video buffer */ |
| 2214 | | { |
| 2215 | | bitmap_yuy16 fullbitmap(width, height); |
| 2216 | | avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); |
| 2217 | 1561 | |
| 2218 | | /* allocate memory for VBI data */ |
| 2219 | | vbidata = (UINT8 *)malloc(header.totalhunks * VBI_PACKED_BYTES); |
| 2220 | | if (vbidata == NULL) |
| 1562 | //------------------------------------------------- |
| 1563 | // do_create_hd - create a new compressed hard |
| 1564 | // disk image from a raw file |
| 1565 | //------------------------------------------------- |
| 1566 | |
| 1567 | static void do_create_hd(parameters_t ¶ms) |
| 1568 | { |
| 1569 | // catch errors so we can close & delete the output file |
| 1570 | try |
| 1571 | { |
| 1572 | // process input file |
| 1573 | core_file *input_file = NULL; |
| 1574 | astring *input_file_str = params.find(OPTION_INPUT); |
| 1575 | if (input_file_str != NULL) |
| 2221 | 1576 | { |
| 2222 | | fprintf(stderr, "Out of memory allocating VBI data\n"); |
| 2223 | | err = CHDERR_OUT_OF_MEMORY; |
| 2224 | | goto cleanup; |
| 1577 | file_error filerr = core_fopen(*input_file_str, OPEN_FLAG_READ, &input_file); |
| 1578 | if (filerr != FILERR_NONE) |
| 1579 | report_error(1, "Unable to open file (%s)", input_file_str->cstr()); |
| 2225 | 1580 | } |
| 2226 | | |
| 2227 | | /* read the metadata */ |
| 2228 | | err = chd_get_metadata(chd, AV_LD_METADATA_TAG, 0, vbidata, header.totalhunks * VBI_PACKED_BYTES, &actlength, NULL, NULL); |
| 2229 | | if (err != CHDERR_NONE) |
| 1581 | |
| 1582 | // process output CHD |
| 1583 | chd_file output_parent; |
| 1584 | astring *output_chd_str = parse_output_chd_parameters(params, output_parent); |
| 1585 | |
| 1586 | // process sectorsize |
| 1587 | UINT32 sector_size = output_parent.opened() ? output_parent.unit_bytes() : IDE_SECTOR_SIZE; |
| 1588 | astring *sectorsize_str = params.find(OPTION_SECTOR_SIZE); |
| 1589 | if (sectorsize_str != NULL) |
| 2230 | 1590 | { |
| 2231 | | fprintf(stderr, "Error getting VBI metadata: %s\n", chd_error_string(err)); |
| 2232 | | memset(vbidata, 0, header.totalhunks * VBI_PACKED_BYTES); |
| 2233 | | fixes++; |
| 1591 | if (output_parent.opened()) |
| 1592 | report_error(1, "Sector size does not apply when creating a diff from the parent"); |
| 1593 | sector_size = parse_number(*sectorsize_str); |
| 2234 | 1594 | } |
| 2235 | | if (actlength != header.totalhunks * VBI_PACKED_BYTES) |
| 1595 | |
| 1596 | // process hunk size (needs to know sector_size) |
| 1597 | UINT32 hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : MAX((4096 / sector_size) * sector_size, sector_size); |
| 1598 | parse_hunk_size(params, sector_size, hunk_size); |
| 1599 | |
| 1600 | // process input start/end (needs to know hunk_size) |
| 1601 | UINT64 input_start; |
| 1602 | UINT64 input_end; |
| 1603 | parse_input_start_end(params, core_fsize(input_file), hunk_size, hunk_size, input_start, input_end); |
| 1604 | |
| 1605 | // process compression |
| 1606 | chd_codec_type compression[4]; |
| 1607 | memcpy(compression, s_default_hd_compression, sizeof(compression)); |
| 1608 | if (input_file == NULL) |
| 1609 | compression[0] = compression[1] = compression[2] = compression[3] = CHD_CODEC_NONE; |
| 1610 | parse_compression(params, compression); |
| 1611 | if (input_file == NULL && compression[0] != CHD_CODEC_NONE) |
| 1612 | report_error(1, "Blank hard disks must be uncompressed"); |
| 1613 | |
| 1614 | // process numprocessors |
| 1615 | parse_numprocessors(params); |
| 1616 | |
| 1617 | // process chs |
| 1618 | UINT32 cylinders = 0; |
| 1619 | UINT32 heads = 0; |
| 1620 | UINT32 sectors = 0; |
| 1621 | astring *chs_str = params.find(OPTION_CHS); |
| 1622 | if (chs_str != NULL) |
| 2236 | 1623 | { |
| 2237 | | fprintf(stderr, "VBI metadata incorrect size\n"); |
| 2238 | | memset(vbidata, 0, header.totalhunks * VBI_PACKED_BYTES); |
| 2239 | | fixes++; |
| 1624 | if (output_parent.opened()) |
| 1625 | report_error(1, "CHS does not apply when creating a diff from the parent"); |
| 1626 | if (sscanf(*chs_str, "%d,%d,%d", &cylinders, &heads, §ors) != 3) |
| 1627 | report_error(1, "Invalid CHS string; must be of the form <cylinders>,<heads>,<sectors>"); |
| 2240 | 1628 | } |
| 2241 | 1629 | |
| 2242 | | /* loop over hunks, reading */ |
| 2243 | | for (framenum = 0; framenum < header.totalhunks; framenum++) |
| 1630 | // process ident |
| 1631 | dynamic_buffer identdata; |
| 1632 | if (output_parent.opened()) |
| 1633 | output_parent.read_metadata(HARD_DISK_IDENT_METADATA_TAG, 0, identdata); |
| 1634 | astring *ident_str = params.find(OPTION_IDENT); |
| 1635 | if (ident_str != NULL) |
| 2244 | 1636 | { |
| 2245 | | vbi_metadata origvbi; |
| 2246 | | vbi_metadata vbi; |
| 2247 | | UINT32 vbiframe; |
| 1637 | // load the file |
| 1638 | file_error filerr = core_fload(*ident_str, identdata); |
| 1639 | if (filerr != FILERR_NONE) |
| 1640 | report_error(1, "Error reading ident file (%s)", ident_str->cstr()); |
| 2248 | 1641 | |
| 2249 | | /* progress */ |
| 2250 | | progress(framenum == 0, "Processing hunk %d/%d... \r", framenum, header.totalhunks); |
| 2251 | | |
| 2252 | | /* set up the fake bitmap for this frame */ |
| 2253 | | avconfig.video.wrap(fullbitmap, fullbitmap.cliprect()); |
| 2254 | | |
| 2255 | | /* configure the decompressor for this frame */ |
| 2256 | | chd_codec_config(chd, AV_CODEC_DECOMPRESS_CONFIG, &avconfig); |
| 2257 | | |
| 2258 | | /* read the hunk into the buffers */ |
| 2259 | | err = chd_read(chd, framenum, NULL); |
| 2260 | | if (err != CHDERR_NONE) |
| 1642 | // must be at least 14 bytes; extract CHS data from there |
| 1643 | if (identdata.count() < 14) |
| 1644 | report_error(1, "Ident file '%s' is invalid (too short)", ident_str->cstr()); |
| 1645 | cylinders = (identdata[3] << 8) | identdata[2]; |
| 1646 | heads = (identdata[7] << 8) | identdata[6]; |
| 1647 | sectors = (identdata[13] << 8) | identdata[12]; |
| 1648 | } |
| 1649 | |
| 1650 | // extract geometry from the parent if we have one |
| 1651 | if (output_parent.opened() && cylinders == 0) |
| 1652 | { |
| 1653 | astring metadata; |
| 1654 | if (output_parent.read_metadata(HARD_DISK_METADATA_TAG, 0, metadata) != CHDERR_NONE) |
| 1655 | report_error(1, "Unable to find hard disk metadata in parent CHD"); |
| 1656 | if (sscanf(metadata, HARD_DISK_METADATA_FORMAT, &cylinders, &heads, §ors, §or_size) != 4) |
| 1657 | report_error(1, "Error parsing hard disk metadata in parent CHD"); |
| 1658 | } |
| 1659 | |
| 1660 | // if no CHS values, try to guess them |
| 1661 | if (cylinders == 0) |
| 1662 | { |
| 1663 | if (input_file == NULL && input_end - input_start == 0) |
| 1664 | report_error(1, "Blank hard drives must specify either a length or a set of CHS values"); |
| 1665 | guess_chs(input_file_str, input_end - input_start, sector_size, cylinders, heads, sectors, sector_size); |
| 1666 | } |
| 1667 | UINT32 totalsectors = cylinders * heads * sectors; |
| 1668 | |
| 1669 | // print some info |
| 1670 | astring tempstr; |
| 1671 | printf("Output CHD: %s\n", output_chd_str->cstr()); |
| 1672 | if (output_parent.opened()) |
| 1673 | printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); |
| 1674 | if (input_file != NULL) |
| 1675 | { |
| 1676 | printf("Input file: %s\n", input_file_str->cstr()); |
| 1677 | if (input_start != 0 || input_end != core_fsize(input_file)) |
| 2261 | 1678 | { |
| 2262 | | fprintf(stderr, "Error reading hunk %d from CHD file: %s\n", framenum, chd_error_string(err)); |
| 2263 | | goto cleanup; |
| 1679 | printf("Input start: %s\n", big_int_string(tempstr, input_start)); |
| 1680 | printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); |
| 2264 | 1681 | } |
| 2265 | | |
| 2266 | | /* unpack the current data for this frame */ |
| 2267 | | vbi_metadata_unpack(&origvbi, &vbiframe, &vbidata[framenum * VBI_PACKED_BYTES]); |
| 2268 | | |
| 2269 | | /* parse the video data */ |
| 2270 | | vbi_parse_all(&avconfig.video.pix16(0), avconfig.video.rowpixels(), avconfig.video.width(), 8, &vbi); |
| 2271 | | |
| 2272 | | /* verify the data */ |
| 2273 | | if (vbiframe != 0 || origvbi.white != 0 || origvbi.line16 != 0 || origvbi.line17 != 0 || origvbi.line18 != 0 || origvbi.line1718 != 0) |
| 2274 | | { |
| 2275 | | int errors = 0; |
| 2276 | | |
| 2277 | | if (vbiframe != framenum) |
| 2278 | | { |
| 2279 | | fprintf(stderr, "%d:Frame mismatch in VBI data (%d, should be %d)\n", framenum, vbiframe, framenum); |
| 2280 | | errors++; |
| 2281 | | } |
| 2282 | | if (vbi.white != origvbi.white) |
| 2283 | | { |
| 2284 | | fprintf(stderr, "%d:White flag mismatch in VBI data (%d, should be %d)\n", framenum, origvbi.white, vbi.white); |
| 2285 | | errors++; |
| 2286 | | } |
| 2287 | | if (vbi.line16 != origvbi.line16) |
| 2288 | | { |
| 2289 | | fprintf(stderr, "%d:Line 16 mismatch in VBI data (%06X, should be %06X)\n", framenum, origvbi.line16, vbi.line16); |
| 2290 | | errors++; |
| 2291 | | } |
| 2292 | | if (vbi.line17 != origvbi.line17) |
| 2293 | | { |
| 2294 | | fprintf(stderr, "%d:Line 17 mismatch in VBI data (%06X, should be %06X)\n", framenum, origvbi.line17, vbi.line17); |
| 2295 | | errors++; |
| 2296 | | } |
| 2297 | | if (vbi.line18 != origvbi.line18) |
| 2298 | | { |
| 2299 | | fprintf(stderr, "%d:Line 18 mismatch in VBI data (%06X, should be %06X)\n", framenum, origvbi.line18, vbi.line18); |
| 2300 | | errors++; |
| 2301 | | } |
| 2302 | | if (vbi.line1718 != origvbi.line1718) |
| 2303 | | { |
| 2304 | | fprintf(stderr, "%d:Line 17/18 mismatch in VBI data (%06X, should be %06X)\n", framenum, origvbi.line1718, vbi.line1718); |
| 2305 | | errors++; |
| 2306 | | } |
| 2307 | | fixes += errors; |
| 2308 | | fixframes += (errors != 0); |
| 2309 | | } |
| 2310 | | |
| 2311 | | /* pack the new data */ |
| 2312 | | vbi_metadata_pack(&vbidata[framenum * VBI_PACKED_BYTES], framenum, &vbi); |
| 2313 | 1682 | } |
| 2314 | | progress(TRUE, "Processing complete! \n"); |
| 1683 | printf("Compression: %s\n", compression_string(tempstr, compression)); |
| 1684 | printf("Cylinders: %d\n", cylinders); |
| 1685 | printf("Heads: %d\n", heads); |
| 1686 | printf("Sectors: %d\n", sectors); |
| 1687 | printf("Bytes/sector: %d\n", sector_size); |
| 1688 | printf("Sectors/hunk: %d\n", hunk_size / sector_size); |
| 1689 | printf("Logical size: %s\n", big_int_string(tempstr, UINT64(totalsectors) * UINT64(sector_size))); |
| 2315 | 1690 | |
| 2316 | | /* print final results */ |
| 2317 | | if (fixes == 0) |
| 2318 | | printf("\nNo fixes required\n"); |
| 1691 | // create the new hard drive |
| 1692 | chd_rawfile_compressor chd(input_file, input_start, input_end); |
| 1693 | chd_error err; |
| 1694 | if (output_parent.opened()) |
| 1695 | err = chd.create(*output_chd_str, UINT64(totalsectors) * UINT64(sector_size), hunk_size, compression, output_parent); |
| 2319 | 1696 | else |
| 2320 | | printf("\nFound %d errors on %d frames\n", fixes, fixframes); |
| 1697 | err = chd.create(*output_chd_str, UINT64(totalsectors) * UINT64(sector_size), hunk_size, sector_size, compression); |
| 1698 | if (err != CHDERR_NONE) |
| 1699 | report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); |
| 2321 | 1700 | |
| 2322 | | /* close the drive */ |
| 2323 | | chd_close(chd); |
| 2324 | | chd = NULL; |
| 1701 | // add the standard hard disk metadata |
| 1702 | astring metadata; |
| 1703 | metadata.format(HARD_DISK_METADATA_FORMAT, cylinders, heads, sectors, sector_size); |
| 1704 | err = chd.write_metadata(HARD_DISK_METADATA_TAG, 0, metadata); |
| 1705 | if (err != CHDERR_NONE) |
| 1706 | report_error(1, "Error adding hard disk metadata: %s", chd_file::error_string(err)); |
| 2325 | 1707 | |
| 2326 | | /* apply fixes */ |
| 2327 | | if (fixes > 0) |
| 1708 | // write the ident if present |
| 1709 | if (identdata.count() > 0) |
| 2328 | 1710 | { |
| 2329 | | /* mark the CHD writeable */ |
| 2330 | | header.flags |= CHDFLAGS_IS_WRITEABLE; |
| 2331 | | err = chd_set_header(inputfile, &header); |
| 1711 | err = chd.write_metadata(HARD_DISK_IDENT_METADATA_TAG, 0, identdata); |
| 2332 | 1712 | if (err != CHDERR_NONE) |
| 2333 | | fprintf(stderr, "Error writing new header: %s\n", chd_error_string(err)); |
| 2334 | | header.flags &= ~CHDFLAGS_IS_WRITEABLE; |
| 2335 | | writeable = TRUE; |
| 2336 | | |
| 2337 | | /* open the file */ |
| 2338 | | err = chd_open(inputfile, CHD_OPEN_READWRITE, NULL, &chd); |
| 2339 | | if (err != CHDERR_NONE) |
| 2340 | | { |
| 2341 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); |
| 2342 | | goto cleanup; |
| 2343 | | } |
| 2344 | | |
| 2345 | | /* write new metadata */ |
| 2346 | | err = chd_set_metadata(chd, AV_LD_METADATA_TAG, 0, vbidata, header.totalhunks * VBI_PACKED_BYTES, CHD_MDFLAGS_CHECKSUM); |
| 2347 | | if (err != CHDERR_NONE) |
| 2348 | | { |
| 2349 | | fprintf(stderr, "Error adding AVLD metadata: %s\n", chd_error_string(err)); |
| 2350 | | goto cleanup; |
| 2351 | | } |
| 2352 | | else |
| 2353 | | printf("Updated metadata written successfully\n"); |
| 2354 | | |
| 2355 | | /* allow cleanup code to close the file and revert the header */ |
| 1713 | report_error(1, "Error adding hard disk metadata: %s", chd_file::error_string(err)); |
| 2356 | 1714 | } |
| 2357 | | } |
| 2358 | 1715 | |
| 2359 | | cleanup: |
| 2360 | | /* clean up our mess */ |
| 2361 | | if (vbidata != NULL) |
| 2362 | | free(vbidata); |
| 2363 | | if (chd != NULL) |
| 2364 | | chd_close(chd); |
| 2365 | | if (writeable) |
| 2366 | | chd_set_header(inputfile, &header); |
| 2367 | | return (err != CHDERR_NONE); |
| 1716 | // compress it generically |
| 1717 | if (input_file != NULL) |
| 1718 | compress_common(chd); |
| 1719 | } |
| 1720 | catch (...) |
| 1721 | { |
| 1722 | // delete the output file |
| 1723 | astring *output_chd_str = params.find(OPTION_OUTPUT); |
| 1724 | if (output_chd_str != NULL) |
| 1725 | osd_rmfile(*output_chd_str); |
| 1726 | throw; |
| 1727 | } |
| 2368 | 1728 | } |
| 2369 | 1729 | |
| 2370 | 1730 | |
| 2371 | | /*------------------------------------------------- |
| 2372 | | do_info - dump the header information from |
| 2373 | | a drive image |
| 2374 | | -------------------------------------------------*/ |
| 1731 | //------------------------------------------------- |
| 1732 | // do_create_cd - create a new compressed CD |
| 1733 | // image from a raw file |
| 1734 | //------------------------------------------------- |
| 2375 | 1735 | |
| 2376 | | static int do_info(int argc, char *argv[], int param) |
| 1736 | static void do_create_cd(parameters_t ¶ms) |
| 2377 | 1737 | { |
| 2378 | | const char *inputfile; |
| 2379 | | chd_file *chd = NULL; |
| 2380 | | UINT8 metadata[256]; |
| 2381 | | chd_header header; |
| 2382 | | chd_error err; |
| 2383 | | int i, j; |
| 1738 | // catch errors so we can close & delete the output file |
| 1739 | try |
| 1740 | { |
| 1741 | // process input file |
| 1742 | chdcd_track_input_info track_info; |
| 1743 | cdrom_toc toc = { 0 }; |
| 1744 | astring *input_file_str = params.find(OPTION_INPUT); |
| 1745 | if (input_file_str != NULL) |
| 1746 | { |
| 1747 | chd_error err = chdcd_parse_toc(*input_file_str, toc, track_info); |
| 1748 | if (err != CHDERR_NONE) |
| 1749 | report_error(1, "Error parsing input file (%s: %s\n", input_file_str->cstr(), chd_file::error_string(err)); |
| 1750 | } |
| 1751 | |
| 1752 | // process output CHD |
| 1753 | chd_file output_parent; |
| 1754 | astring *output_chd_str = parse_output_chd_parameters(params, output_parent); |
| 2384 | 1755 | |
| 2385 | | /* require 3 args total */ |
| 2386 | | if (argc != 3) |
| 2387 | | return usage(); |
| 1756 | // process hunk size |
| 1757 | UINT32 hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : CD_FRAMES_PER_HUNK * CD_FRAME_SIZE; |
| 1758 | parse_hunk_size(params, CD_FRAME_SIZE, hunk_size); |
| 1759 | |
| 1760 | // process compression |
| 1761 | chd_codec_type compression[4]; |
| 1762 | memcpy(compression, s_default_cd_compression, sizeof(compression)); |
| 1763 | parse_compression(params, compression); |
| 2388 | 1764 | |
| 2389 | | /* extract the data */ |
| 2390 | | inputfile = argv[2]; |
| 1765 | // process numprocessors |
| 1766 | parse_numprocessors(params); |
| 2391 | 1767 | |
| 2392 | | /* print some info */ |
| 2393 | | printf("Input file: %s\n", inputfile); |
| 1768 | // pad each track to a 4-frame boundry. cdrom.c will deal with this on the read side |
| 1769 | UINT32 origtotalsectors = 0; |
| 1770 | UINT32 totalsectors = 0; |
| 1771 | for (int tracknum = 0; tracknum < toc.numtrks; tracknum++) |
| 1772 | { |
| 1773 | cdrom_track_info &trackinfo = toc.tracks[tracknum]; |
| 1774 | int padded = (trackinfo.frames + CD_TRACK_PADDING - 1) / CD_TRACK_PADDING; |
| 1775 | trackinfo.extraframes = padded * CD_TRACK_PADDING - trackinfo.frames; |
| 1776 | origtotalsectors += trackinfo.frames; |
| 1777 | totalsectors += trackinfo.frames + trackinfo.extraframes; |
| 1778 | } |
| 2394 | 1779 | |
| 2395 | | /* get the header */ |
| 2396 | | err = chd_open(inputfile, CHD_OPEN_READ, NULL, &chd); |
| 2397 | | if (err != CHDERR_NONE) |
| 2398 | | { |
| 2399 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); |
| 2400 | | goto cleanup; |
| 2401 | | } |
| 2402 | | header = *chd_get_header(chd); |
| 1780 | // print some info |
| 1781 | astring tempstr; |
| 1782 | printf("Output CHD: %s\n", output_chd_str->cstr()); |
| 1783 | if (output_parent.opened()) |
| 1784 | printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); |
| 1785 | printf("Input file: %s\n", input_file_str->cstr()); |
| 1786 | printf("Input tracks: %d\n", toc.numtrks); |
| 1787 | printf("Input length: %s\n", msf_string_from_frames(tempstr, origtotalsectors)); |
| 1788 | printf("Compression: %s\n", compression_string(tempstr, compression)); |
| 1789 | printf("Logical size: %s\n", big_int_string(tempstr, UINT64(totalsectors) * CD_FRAME_SIZE)); |
| 2403 | 1790 | |
| 2404 | | /* print the info */ |
| 2405 | | printf("Header Size: %d bytes\n", header.length); |
| 2406 | | printf("File Version: %d\n", header.version); |
| 2407 | | printf("Flags: %s, %s\n", |
| 2408 | | (header.flags & CHDFLAGS_HAS_PARENT) ? "HAS_PARENT" : "NO_PARENT", |
| 2409 | | (header.flags & CHDFLAGS_IS_WRITEABLE) ? "WRITEABLE" : "READ_ONLY"); |
| 2410 | | printf("Compression: %s\n", chd_get_codec_name(header.compression)); |
| 2411 | | printf("Hunk Size: %d bytes\n", header.hunkbytes); |
| 2412 | | printf("Total Hunks: %d\n", header.totalhunks); |
| 2413 | | printf("Logical size: %s bytes\n", big_int_string(header.logicalbytes)); |
| 2414 | | if (!(header.flags & CHDFLAGS_IS_WRITEABLE) && header.version <= 3) |
| 2415 | | printf("MD5: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", |
| 2416 | | header.md5[0], header.md5[1], header.md5[2], header.md5[3], |
| 2417 | | header.md5[4], header.md5[5], header.md5[6], header.md5[7], |
| 2418 | | header.md5[8], header.md5[9], header.md5[10], header.md5[11], |
| 2419 | | header.md5[12], header.md5[13], header.md5[14], header.md5[15]); |
| 2420 | | if (!(header.flags & CHDFLAGS_IS_WRITEABLE) && header.version >= 3) |
| 2421 | | printf("SHA1: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", |
| 2422 | | header.sha1[0], header.sha1[1], header.sha1[2], header.sha1[3], |
| 2423 | | header.sha1[4], header.sha1[5], header.sha1[6], header.sha1[7], |
| 2424 | | header.sha1[8], header.sha1[9], header.sha1[10], header.sha1[11], |
| 2425 | | header.sha1[12], header.sha1[13], header.sha1[14], header.sha1[15], |
| 2426 | | header.sha1[16], header.sha1[17], header.sha1[18], header.sha1[19]); |
| 2427 | | if (!(header.flags & CHDFLAGS_IS_WRITEABLE) && header.version >= 4) |
| 2428 | | { |
| 2429 | | printf("Raw SHA1: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", |
| 2430 | | header.rawsha1[0], header.rawsha1[1], header.rawsha1[2], header.rawsha1[3], |
| 2431 | | header.rawsha1[4], header.rawsha1[5], header.rawsha1[6], header.rawsha1[7], |
| 2432 | | header.rawsha1[8], header.rawsha1[9], header.rawsha1[10], header.rawsha1[11], |
| 2433 | | header.rawsha1[12], header.rawsha1[13], header.rawsha1[14], header.rawsha1[15], |
| 2434 | | header.rawsha1[16], header.rawsha1[17], header.rawsha1[18], header.rawsha1[19]); |
| 2435 | | } |
| 2436 | | if (header.flags & CHDFLAGS_HAS_PARENT) |
| 2437 | | { |
| 2438 | | printf("Parent MD5: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", |
| 2439 | | header.parentmd5[0], header.parentmd5[1], header.parentmd5[2], header.parentmd5[3], |
| 2440 | | header.parentmd5[4], header.parentmd5[5], header.parentmd5[6], header.parentmd5[7], |
| 2441 | | header.parentmd5[8], header.parentmd5[9], header.parentmd5[10], header.parentmd5[11], |
| 2442 | | header.parentmd5[12], header.parentmd5[13], header.parentmd5[14], header.parentmd5[15]); |
| 2443 | | if (header.version >= 3) |
| 2444 | | printf("Parent SHA1: %02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x\n", |
| 2445 | | header.parentsha1[0], header.parentsha1[1], header.parentsha1[2], header.parentsha1[3], |
| 2446 | | header.parentsha1[4], header.parentsha1[5], header.parentsha1[6], header.parentsha1[7], |
| 2447 | | header.parentsha1[8], header.parentsha1[9], header.parentsha1[10], header.parentsha1[11], |
| 2448 | | header.parentsha1[12], header.parentsha1[13], header.parentsha1[14], header.parentsha1[15], |
| 2449 | | header.parentsha1[16], header.parentsha1[17], header.parentsha1[18], header.parentsha1[19]); |
| 2450 | | } |
| 1791 | // create the new hard drive |
| 1792 | chd_cd_compressor chd(toc, track_info); |
| 1793 | chd_error err; |
| 1794 | if (output_parent.opened()) |
| 1795 | err = chd.create(*output_chd_str, UINT64(totalsectors) * UINT64(CD_FRAME_SIZE), hunk_size, compression, output_parent); |
| 1796 | else |
| 1797 | err = chd.create(*output_chd_str, UINT64(totalsectors) * UINT64(CD_FRAME_SIZE), hunk_size, CD_FRAME_SIZE, compression); |
| 1798 | if (err != CHDERR_NONE) |
| 1799 | report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); |
| 2451 | 1800 | |
| 2452 | | /* print out metadata */ |
| 2453 | | for (i = 0; ; i++) |
| 2454 | | { |
| 2455 | | UINT32 metatag, metasize; |
| 2456 | | |
| 2457 | | /* get the indexed metadata item; stop when we hit an error */ |
| 2458 | | err = chd_get_metadata(chd, CHDMETATAG_WILDCARD, i, metadata, sizeof(metadata), &metasize, &metatag, NULL); |
| 1801 | // add the standard CD metadata; we do this even if we have a parent because it might be different |
| 1802 | err = cdrom_write_metadata(&chd, &toc); |
| 2459 | 1803 | if (err != CHDERR_NONE) |
| 2460 | | break; |
| 1804 | report_error(1, "Error adding CD metadata: %s", chd_file::error_string(err)); |
| 2461 | 1805 | |
| 2462 | | /* print either a string representation or a hex representation of the tag */ |
| 2463 | | if (isprint((metatag >> 24) & 0xff) && isprint((metatag >> 16) & 0xff) && isprint((metatag >> 8) & 0xff) && isprint(metatag & 0xff)) |
| 2464 | | printf("Metadata: Tag='%c%c%c%c' Length=%d\n", (metatag >> 24) & 0xff, (metatag >> 16) & 0xff, (metatag >> 8) & 0xff, metatag & 0xff, metasize); |
| 2465 | | else |
| 2466 | | printf("Metadata: Tag=%08x Length=%d\n", metatag, metasize); |
| 2467 | | printf(" "); |
| 2468 | | |
| 2469 | | /* print up to 60 characters of metadata */ |
| 2470 | | metasize = MIN(60, metasize); |
| 2471 | | for (j = 0; j < metasize; j++) |
| 2472 | | printf("%c", isprint(metadata[j]) ? metadata[j] : '.'); |
| 2473 | | printf("\n"); |
| 1806 | // compress it generically |
| 1807 | compress_common(chd); |
| 2474 | 1808 | } |
| 2475 | | |
| 2476 | | cleanup: |
| 2477 | | /* close everything down */ |
| 2478 | | if (chd != NULL) |
| 2479 | | chd_close(chd); |
| 2480 | | return (err != CHDERR_NONE); |
| 1809 | catch (...) |
| 1810 | { |
| 1811 | // delete the output file |
| 1812 | astring *output_chd_str = params.find(OPTION_OUTPUT); |
| 1813 | if (output_chd_str != NULL) |
| 1814 | osd_rmfile(*output_chd_str); |
| 1815 | throw; |
| 1816 | } |
| 2481 | 1817 | } |
| 2482 | 1818 | |
| 2483 | 1819 | |
| 2484 | | /*------------------------------------------------- |
| 2485 | | handle_custom_chomp - custom chomp a file |
| 2486 | | -------------------------------------------------*/ |
| 1820 | //------------------------------------------------- |
| 1821 | // do_create_ld - create a new A/V file from an |
| 1822 | // input AVI file and metadata |
| 1823 | //------------------------------------------------- |
| 2487 | 1824 | |
| 2488 | | #if ENABLE_CUSTOM_CHOMP |
| 2489 | | static chd_error handle_custom_chomp(const char *name, chd_file *chd, UINT32 *maxhunk) |
| 1825 | static void do_create_ld(parameters_t ¶ms) |
| 2490 | 1826 | { |
| 2491 | | const chd_header *header = chd_get_header(chd); |
| 2492 | | int sectors_per_hunk = (header->hunkbytes / IDE_SECTOR_SIZE); |
| 2493 | | chd_error err = CHDERR_INVALID_DATA; |
| 2494 | | UINT8 *temp = NULL; |
| 2495 | | |
| 2496 | | /* allocate memory to hold a hunk */ |
| 2497 | | temp = malloc(header->hunkbytes); |
| 2498 | | if (temp == NULL) |
| 1827 | // catch errors so we can close & delete the output file |
| 1828 | try |
| 2499 | 1829 | { |
| 2500 | | err = CHDERR_OUT_OF_MEMORY; |
| 2501 | | goto cleanup; |
| 2502 | | } |
| 2503 | | |
| 2504 | | /* check for midway */ |
| 2505 | | if (strcmp(name, "midway") == 0) |
| 2506 | | { |
| 2507 | | UINT32 maxsector = 0; |
| 2508 | | UINT32 numparts; |
| 2509 | | chd_error err; |
| 2510 | | int i; |
| 2511 | | |
| 2512 | | /* read sector 0 */ |
| 2513 | | err = chd_read(chd, 0, temp); |
| 2514 | | if (err != CHDERR_NONE) |
| 1830 | // process input file |
| 1831 | avi_file *input_file = NULL; |
| 1832 | astring *input_file_str = params.find(OPTION_INPUT); |
| 1833 | if (input_file_str != NULL) |
| 2515 | 1834 | { |
| 2516 | | fprintf(stderr, "Error reading hunk 0\n"); |
| 2517 | | goto cleanup; |
| 1835 | avi_error avierr = avi_open(*input_file_str, &input_file); |
| 1836 | if (avierr != AVIERR_NONE) |
| 1837 | report_error(1, "Error opening AVI file (%s): %s\n", input_file_str->cstr(), avi_error_string(avierr)); |
| 2518 | 1838 | } |
| 1839 | const avi_movie_info *aviinfo = avi_get_movie_info(input_file); |
| 1840 | |
| 1841 | // process output CHD |
| 1842 | chd_file output_parent; |
| 1843 | astring *output_chd_str = parse_output_chd_parameters(params, output_parent); |
| 1844 | |
| 1845 | // process input start/end |
| 1846 | UINT64 input_start; |
| 1847 | UINT64 input_end; |
| 1848 | parse_input_start_end(params, aviinfo->video_numsamples, 0, 1, input_start, input_end); |
| 1849 | |
| 1850 | // determine parameters of the incoming video stream |
| 1851 | avi_info info; |
| 1852 | info.fps_times_1million = UINT64(aviinfo->video_timescale) * 1000000 / aviinfo->video_sampletime; |
| 1853 | info.width = aviinfo->video_width; |
| 1854 | info.height = aviinfo->video_height; |
| 1855 | info.interlaced = ((info.fps_times_1million / 1000000) <= 30) && (info.height % 2 == 0) && (info.height > 288); |
| 1856 | info.channels = aviinfo->audio_channels; |
| 1857 | info.rate = aviinfo->audio_samplerate; |
| 2519 | 1858 | |
| 2520 | | /* look for the signature */ |
| 2521 | | if (temp[0] != 0x54 || temp[1] != 0x52 || temp[2] != 0x41 || temp[3] != 0x50) |
| 2522 | | goto cleanup; |
| 2523 | | |
| 2524 | | /* determine the number of partitions */ |
| 2525 | | numparts = temp[4] | (temp[5] << 8) | (temp[6] << 16) | (temp[7] << 24); |
| 2526 | | printf("%d partitions\n", numparts); |
| 2527 | | |
| 2528 | | /* get the partition information for each one and track the maximum referenced sector */ |
| 2529 | | for (i = 0; i < numparts; i++) |
| 1859 | // adjust for interlacing |
| 1860 | if (info.interlaced) |
| 2530 | 1861 | { |
| 2531 | | UINT32 pstart = temp[i*12 + 8] | (temp[i*12 + 9] << 8) | (temp[i*12 + 10] << 16) | (temp[i*12 + 11] << 24); |
| 2532 | | UINT32 psize = temp[i*12 + 12] | (temp[i*12 + 13] << 8) | (temp[i*12 + 14] << 16) | (temp[i*12 + 15] << 24); |
| 2533 | | UINT32 pflags = temp[i*12 + 16] | (temp[i*12 + 17] << 8) | (temp[i*12 + 18] << 16) | (temp[i*12 + 19] << 24); |
| 2534 | | printf(" %2d. %7d - %7d (%X)\n", i, pstart, pstart + psize - 1, pflags); |
| 2535 | | if (i != 0 && pstart + psize > maxsector) |
| 2536 | | maxsector = pstart + psize; |
| 1862 | info.fps_times_1million *= 2; |
| 1863 | info.height /= 2; |
| 1864 | input_start *= 2; |
| 1865 | input_end *= 2; |
| 2537 | 1866 | } |
| 2538 | 1867 | |
| 2539 | | /* the maximum hunk is the one that contains the last sector */ |
| 2540 | | *maxhunk = (maxsector + sectors_per_hunk - 1) / sectors_per_hunk; |
| 2541 | | printf("Maximum hunk: %d\n", *maxhunk); |
| 1868 | // determine the number of bytes per frame |
| 1869 | info.max_samples_per_frame = (UINT64(info.rate) * 1000000 + info.fps_times_1million - 1) / info.fps_times_1million; |
| 1870 | info.bytes_per_frame = avhuff_encoder::raw_data_size(info.width, info.height, info.channels, info.max_samples_per_frame); |
| 1871 | |
| 1872 | // process hunk size |
| 1873 | UINT32 hunk_size = output_parent.opened() ? output_parent.hunk_bytes() : info.bytes_per_frame; |
| 1874 | parse_hunk_size(params, info.bytes_per_frame, hunk_size); |
| 1875 | |
| 1876 | // process compression |
| 1877 | chd_codec_type compression[4]; |
| 1878 | memcpy(compression, s_default_ld_compression, sizeof(compression)); |
| 1879 | parse_compression(params, compression); |
| 2542 | 1880 | |
| 2543 | | /* warn if there will be no effect */ |
| 2544 | | if (*maxhunk >= header->totalhunks) |
| 2545 | | { |
| 2546 | | printf("Warning: chomp will have no effect\n"); |
| 2547 | | *maxhunk = header->totalhunks; |
| 2548 | | } |
| 2549 | | } |
| 1881 | // process numprocessors |
| 1882 | parse_numprocessors(params); |
| 2550 | 1883 | |
| 2551 | | /* check for atari */ |
| 2552 | | if (strcmp(name, "atari") == 0) |
| 2553 | | { |
| 2554 | | UINT32 sectors[4]; |
| 2555 | | UINT8 *data; |
| 2556 | | int i, maxdiff; |
| 1884 | // print some info |
| 1885 | astring tempstr; |
| 1886 | printf("Output CHD: %s\n", output_chd_str->cstr()); |
| 1887 | if (output_parent.opened()) |
| 1888 | printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); |
| 1889 | printf("Input file: %s\n", input_file_str->cstr()); |
| 1890 | if (input_start != 0 && input_end != aviinfo->video_numsamples) |
| 1891 | printf("Input start: %s\n", big_int_string(tempstr, input_start)); |
| 1892 | printf("Input length: %s (%02d:%02d:%02d)\n", big_int_string(tempstr, input_end - input_start), |
| 1893 | UINT32((UINT64(input_end - input_start) * 1000000 / info.fps_times_1million / 60 / 60)), |
| 1894 | UINT32(((UINT64(input_end - input_start) * 1000000 / info.fps_times_1million / 60) % 60)), |
| 1895 | UINT32(((UINT64(input_end - input_start) * 1000000 / info.fps_times_1million) % 60))); |
| 1896 | printf("Frame rate: %d.%06d\n", info.fps_times_1million / 1000000, info.fps_times_1million % 1000000); |
| 1897 | printf("Frame size: %d x %d %s\n", info.width, info.height * (info.interlaced ? 2 : 1), info.interlaced ? "interlaced" : "non-interlaced"); |
| 1898 | printf("Audio: %d channels at %d Hz\n", info.channels, info.rate); |
| 1899 | printf("Compression: %s\n", compression_string(tempstr, compression)); |
| 1900 | printf("Hunk size: %s\n", big_int_string(tempstr, hunk_size)); |
| 1901 | printf("Logical size: %s\n", big_int_string(tempstr, UINT64(input_end - input_start) * hunk_size)); |
| 1902 | |
| 1903 | // create the new CHD |
| 1904 | chd_avi_compressor chd(*input_file, info, input_start, input_end); |
| 2557 | 1905 | chd_error err; |
| 1906 | if (output_parent.opened()) |
| 1907 | err = chd.create(*output_chd_str, UINT64(input_end - input_start) * hunk_size, hunk_size, compression, output_parent); |
| 1908 | else |
| 1909 | err = chd.create(*output_chd_str, UINT64(input_end - input_start) * hunk_size, hunk_size, info.bytes_per_frame, compression); |
| 1910 | if (err != CHDERR_NONE) |
| 1911 | report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); |
| 2558 | 1912 | |
| 2559 | | /* read the second sector */ |
| 2560 | | err = chd_read(chd, 0x200 / header->hunkbytes, temp); |
| 1913 | // write the core A/V metadata |
| 1914 | astring metadata; |
| 1915 | metadata.format(AV_METADATA_FORMAT, info.fps_times_1million / 1000000, info.fps_times_1million % 1000000, info.width, info.height, info.interlaced, info.channels, info.rate); |
| 1916 | err = chd.write_metadata(AV_METADATA_TAG, 0, metadata); |
| 2561 | 1917 | if (err != CHDERR_NONE) |
| 2562 | | { |
| 2563 | | fprintf(stderr, "Error reading sector 1\n"); |
| 2564 | | goto cleanup; |
| 2565 | | } |
| 2566 | | data = &temp[0x200 % header->hunkbytes]; |
| 1918 | report_error(1, "Error adding AV metadata: %s\n", chd_file::error_string(err)); |
| 2567 | 1919 | |
| 2568 | | /* look for the signature */ |
| 2569 | | if (data[0] != 0x0d || data[1] != 0xf0 || data[2] != 0xed || data[3] != 0xfe) |
| 2570 | | goto cleanup; |
| 2571 | | |
| 2572 | | /* loop over 4 partitions and compute the number of sectors in each */ |
| 2573 | | for (i = 0; i < 4; i++) |
| 2574 | | sectors[i] = data[i*4+0x40] | (data[i*4+0x41] << 8) | (data[i*4+0x42] << 16) | (data[i*4+0x43] << 24); |
| 2575 | | maxdiff = sectors[2] - sectors[1]; |
| 2576 | | if (sectors[3] - sectors[2] > maxdiff) |
| 2577 | | maxdiff = sectors[3] - sectors[2]; |
| 2578 | | if (sectors[0] != 8) |
| 2579 | | goto cleanup; |
| 2580 | | |
| 2581 | | /* the maximum hunk is the end of the fourth copy of the data */ |
| 2582 | | *maxhunk = (sectors[3] + maxdiff + sectors_per_hunk - 1) / sectors_per_hunk; |
| 2583 | | printf("Maximum hunk: %d\n", *maxhunk); |
| 2584 | | |
| 2585 | | /* warn if there will be no effect */ |
| 2586 | | if (*maxhunk >= header->totalhunks) |
| 1920 | // create the compressor and then run it generically |
| 1921 | compress_common(chd); |
| 1922 | |
| 1923 | // write the final LD metadata |
| 1924 | if (info.height == 524/2 || info.height == 624/2) |
| 2587 | 1925 | { |
| 2588 | | fprintf(stderr, "Warning: chomp will have no effect\n"); |
| 2589 | | *maxhunk = header->totalhunks; |
| 1926 | err = chd.write_metadata(AV_LD_METADATA_TAG, 0, chd.ldframedata()); |
| 1927 | if (err != CHDERR_NONE) |
| 1928 | report_error(1, "Error adding AVLD metadata: %s\n", chd_file::error_string(err)); |
| 2590 | 1929 | } |
| 2591 | 1930 | } |
| 2592 | | |
| 2593 | | /* if we fall through, there was no error */ |
| 2594 | | err = CHDERR_NONE; |
| 2595 | | |
| 2596 | | cleanup: |
| 2597 | | if (temp != NULL) |
| 2598 | | free(temp); |
| 2599 | | if (err == CHDERR_INVALID_DATA) |
| 2600 | | fprintf(stderr, "Error: unable to identify file or compute chomping size.\n"); |
| 2601 | | return err; |
| 1931 | catch (...) |
| 1932 | { |
| 1933 | // delete the output file |
| 1934 | astring *output_chd_str = params.find(OPTION_OUTPUT); |
| 1935 | if (output_chd_str != NULL) |
| 1936 | osd_rmfile(*output_chd_str); |
| 1937 | throw; |
| 1938 | } |
| 2602 | 1939 | } |
| 2603 | | #endif |
| 2604 | 1940 | |
| 2605 | 1941 | |
| 2606 | | /*------------------------------------------------- |
| 2607 | | do_merge_update_chomp - merge a parent and its |
| 2608 | | child together (also works for update & chomp) |
| 2609 | | -------------------------------------------------*/ |
| 1942 | //------------------------------------------------- |
| 1943 | // do_copy - create a new CHD with data from |
| 1944 | // another CHD |
| 1945 | //------------------------------------------------- |
| 2610 | 1946 | |
| 2611 | | static int do_merge_update_chomp(int argc, char *argv[], int param) |
| 1947 | static void do_copy(parameters_t ¶ms) |
| 2612 | 1948 | { |
| 2613 | | const char *parentfile, *inputfile, *outputfile; |
| 2614 | | const chd_header *inputheader; |
| 2615 | | chd_file *parentchd = NULL; |
| 2616 | | chd_file *outputchd = NULL; |
| 2617 | | chd_file *inputchd = NULL; |
| 2618 | | UINT32 maxhunk = ~0; |
| 2619 | | chd_error err; |
| 2620 | | |
| 2621 | | /* require 4-5 args total */ |
| 2622 | | if (param == OPERATION_UPDATE && argc != 4) |
| 2623 | | return usage(); |
| 2624 | | if ((param == OPERATION_MERGE || param == OPERATION_CHOMP) && argc != 5) |
| 2625 | | return usage(); |
| 2626 | | |
| 2627 | | /* extract the data */ |
| 2628 | | if (param == OPERATION_MERGE) |
| 1949 | // catch errors so we can close & delete the output file |
| 1950 | try |
| 2629 | 1951 | { |
| 2630 | | parentfile = argv[2]; |
| 2631 | | inputfile = argv[3]; |
| 2632 | | outputfile = argv[4]; |
| 2633 | | } |
| 2634 | | else |
| 2635 | | { |
| 2636 | | parentfile = NULL; |
| 2637 | | inputfile = argv[2]; |
| 2638 | | outputfile = argv[3]; |
| 2639 | | if (param == OPERATION_CHOMP) |
| 2640 | | maxhunk = atoi(argv[4]); |
| 2641 | | } |
| 1952 | // parse out input files |
| 1953 | chd_file input_parent_chd; |
| 1954 | chd_file input_chd; |
| 1955 | parse_input_chd_parameters(params, input_chd, input_parent_chd); |
| 2642 | 1956 | |
| 2643 | | /* print some info */ |
| 2644 | | if (parentfile != NULL) |
| 2645 | | { |
| 2646 | | printf("Parent file: %s\n", parentfile); |
| 2647 | | printf("Diff file: %s\n", inputfile); |
| 2648 | | } |
| 2649 | | else |
| 2650 | | printf("Input file: %s\n", inputfile); |
| 2651 | | printf("Output file: %s\n", outputfile); |
| 2652 | | if (param == OPERATION_CHOMP) |
| 2653 | | printf("Maximum hunk: %d\n", maxhunk); |
| 1957 | // parse out input start/end |
| 1958 | UINT64 input_start; |
| 1959 | UINT64 input_end; |
| 1960 | parse_input_start_end(params, input_chd.logical_bytes(), input_chd.hunk_bytes(), input_chd.hunk_bytes(), input_start, input_end); |
| 2654 | 1961 | |
| 2655 | | /* open the parent CHD */ |
| 2656 | | if (parentfile != NULL) |
| 2657 | | { |
| 2658 | | err = chd_open(parentfile, CHD_OPEN_READ, NULL, &parentchd); |
| 2659 | | if (err != CHDERR_NONE) |
| 1962 | // process output CHD |
| 1963 | chd_file output_parent; |
| 1964 | astring *output_chd_str = parse_output_chd_parameters(params, output_parent); |
| 1965 | |
| 1966 | // process hunk size |
| 1967 | UINT32 hunk_size = input_chd.hunk_bytes(); |
| 1968 | parse_hunk_size(params, 1, hunk_size); |
| 1969 | if (hunk_size % input_chd.hunk_bytes() != 0 && input_chd.hunk_bytes() % hunk_size != 0) |
| 1970 | report_error(1, "Hunk size is not an even multiple or divisor of input hunk size"); |
| 1971 | |
| 1972 | // process compression; we default to our current preferences using metadata to pick the type |
| 1973 | chd_codec_type compression[4]; |
| 2660 | 1974 | { |
| 2661 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", parentfile, chd_error_string(err)); |
| 2662 | | goto cleanup; |
| 1975 | dynamic_buffer metadata; |
| 1976 | if (input_chd.read_metadata(HARD_DISK_METADATA_TAG, 0, metadata) == CHDERR_NONE) |
| 1977 | memcpy(compression, s_default_hd_compression, sizeof(compression)); |
| 1978 | else if (input_chd.read_metadata(AV_METADATA_TAG, 0, metadata) == CHDERR_NONE) |
| 1979 | memcpy(compression, s_default_ld_compression, sizeof(compression)); |
| 1980 | else if (input_chd.read_metadata(CDROM_OLD_METADATA_TAG, 0, metadata) == CHDERR_NONE || |
| 1981 | input_chd.read_metadata(CDROM_TRACK_METADATA_TAG, 0, metadata) == CHDERR_NONE || |
| 1982 | input_chd.read_metadata(CDROM_TRACK_METADATA2_TAG, 0, metadata) == CHDERR_NONE) |
| 1983 | memcpy(compression, s_default_cd_compression, sizeof(compression)); |
| 1984 | else |
| 1985 | memcpy(compression, s_default_raw_compression, sizeof(compression)); |
| 2663 | 1986 | } |
| 2664 | | } |
| 1987 | parse_compression(params, compression); |
| 2665 | 1988 | |
| 2666 | | /* open the diff CHD */ |
| 2667 | | err = chd_open(inputfile, CHD_OPEN_READ, parentchd, &inputchd); |
| 2668 | | if (err != CHDERR_NONE) |
| 2669 | | { |
| 2670 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); |
| 2671 | | goto cleanup; |
| 2672 | | } |
| 2673 | | inputheader = chd_get_header(inputchd); |
| 1989 | // process numprocessors |
| 1990 | parse_numprocessors(params); |
| 2674 | 1991 | |
| 2675 | | #if ENABLE_CUSTOM_CHOMP |
| 2676 | | /* if we're chomping with a auto parameter, now is the time to figure it out */ |
| 2677 | | if (param == OPERATION_CHOMP && maxhunk == 0) |
| 2678 | | if (handle_custom_chomp(argv[4], inputchd, &maxhunk) != CHDERR_NONE) |
| 2679 | | return 1; |
| 2680 | | #endif |
| 1992 | // print some info |
| 1993 | astring tempstr; |
| 1994 | printf("Output CHD: %s\n", output_chd_str->cstr()); |
| 1995 | if (output_parent.opened()) |
| 1996 | printf("Parent CHD: %s\n", params.find(OPTION_OUTPUT_PARENT)->cstr()); |
| 1997 | printf("Input CHD: %s\n", params.find(OPTION_INPUT)->cstr()); |
| 1998 | if (input_start != 0 || input_end != input_chd.logical_bytes()) |
| 1999 | { |
| 2000 | printf("Input start: %s\n", big_int_string(tempstr, input_start)); |
| 2001 | printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); |
| 2002 | } |
| 2003 | printf("Compression: %s\n", compression_string(tempstr, compression)); |
| 2004 | printf("Hunk size: %s\n", big_int_string(tempstr, hunk_size)); |
| 2005 | printf("Logical size: %s\n", big_int_string(tempstr, input_end - input_start)); |
| 2681 | 2006 | |
| 2682 | | /* create the new merged CHD */ |
| 2683 | | err = chd_create(outputfile, inputheader->logicalbytes, inputheader->hunkbytes, inputheader->compression, NULL); |
| 2684 | | if (err != CHDERR_NONE) |
| 2685 | | { |
| 2686 | | fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); |
| 2687 | | goto cleanup; |
| 2688 | | } |
| 2007 | // create the new CHD |
| 2008 | chd_chdfile_compressor chd(input_chd, input_start, input_end); |
| 2009 | chd_error err; |
| 2010 | if (output_parent.opened()) |
| 2011 | err = chd.create(*output_chd_str, input_end - input_start, hunk_size, compression, output_parent); |
| 2012 | else |
| 2013 | err = chd.create(*output_chd_str, input_end - input_start, hunk_size, input_chd.unit_bytes(), compression); |
| 2014 | if (err != CHDERR_NONE) |
| 2015 | report_error(1, "Error creating CHD file (%s): %s", output_chd_str->cstr(), chd_file::error_string(err)); |
| 2689 | 2016 | |
| 2690 | | /* open the new CHD */ |
| 2691 | | err = chd_open(outputfile, CHD_OPEN_READWRITE, NULL, &outputchd); |
| 2692 | | if (err != CHDERR_NONE) |
| 2693 | | { |
| 2694 | | fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); |
| 2695 | | goto cleanup; |
| 2017 | // clone all the metadata, upgrading where appropriate |
| 2018 | dynamic_buffer metadata; |
| 2019 | chd_metadata_tag metatag; |
| 2020 | UINT8 metaflags; |
| 2021 | UINT32 index = 0; |
| 2022 | bool redo_cd = false; |
| 2023 | for (err = input_chd.read_metadata(CHDMETATAG_WILDCARD, index++, metadata, metatag, metaflags); err == CHDERR_NONE; err = input_chd.read_metadata(CHDMETATAG_WILDCARD, index++, metadata, metatag, metaflags)) |
| 2024 | { |
| 2025 | // if this is an old CD-CHD tag, note that we want to re-do it |
| 2026 | if (metatag == CDROM_OLD_METADATA_TAG || metatag == CDROM_TRACK_METADATA_TAG) |
| 2027 | { |
| 2028 | redo_cd = true; |
| 2029 | continue; |
| 2030 | } |
| 2031 | |
| 2032 | // otherwise, clone it |
| 2033 | err = chd.write_metadata(metatag, CHDMETAINDEX_APPEND, metadata); |
| 2034 | if (err != CHDERR_NONE) |
| 2035 | report_error(1, "Error writing cloned metadata: %s", chd_file::error_string(err)); |
| 2036 | } |
| 2037 | |
| 2038 | // if we need to re-do the CD metadata, do it now |
| 2039 | if (redo_cd) |
| 2040 | { |
| 2041 | cdrom_file *cdrom = cdrom_open(&input_chd); |
| 2042 | if (cdrom == NULL) |
| 2043 | report_error(1, "Error upgrading CD metadata"); |
| 2044 | const cdrom_toc *toc = cdrom_get_toc(cdrom); |
| 2045 | err = cdrom_write_metadata(&chd, toc); |
| 2046 | if (err != CHDERR_NONE) |
| 2047 | report_error(1, "Error writing upgraded CD metadata: %s", chd_file::error_string(err)); |
| 2048 | } |
| 2049 | |
| 2050 | // compress it generically |
| 2051 | compress_common(chd); |
| 2696 | 2052 | } |
| 2697 | | |
| 2698 | | /* clone the metadata from the input file (which should have inherited from the parent) */ |
| 2699 | | err = chdman_clone_metadata(inputchd, outputchd); |
| 2700 | | if (err != CHDERR_NONE) |
| 2053 | catch (...) |
| 2701 | 2054 | { |
| 2702 | | fprintf(stderr, "Error cloning metadata: %s\n", chd_error_string(err)); |
| 2703 | | goto cleanup; |
| 2055 | // delete the output file |
| 2056 | astring *output_chd_str = params.find(OPTION_OUTPUT); |
| 2057 | if (output_chd_str != NULL) |
| 2058 | osd_rmfile(*output_chd_str); |
| 2059 | throw; |
| 2704 | 2060 | } |
| 2705 | | |
| 2706 | | /* do the compression; our interface will route reads for us */ |
| 2707 | | err = chdman_compress_chd(outputchd, inputchd, (param == OPERATION_CHOMP) ? (maxhunk + 1) : 0); |
| 2708 | | if (err != CHDERR_NONE) |
| 2709 | | fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); |
| 2710 | | |
| 2711 | | cleanup: |
| 2712 | | /* close everything down */ |
| 2713 | | if (outputchd != NULL) |
| 2714 | | chd_close(outputchd); |
| 2715 | | if (inputchd != NULL) |
| 2716 | | chd_close(inputchd); |
| 2717 | | if (parentchd != NULL) |
| 2718 | | chd_close(parentchd); |
| 2719 | | if (err != CHDERR_NONE) |
| 2720 | | osd_rmfile(outputfile); |
| 2721 | | return (err != CHDERR_NONE); |
| 2722 | 2061 | } |
| 2723 | 2062 | |
| 2724 | 2063 | |
| 2725 | | /*------------------------------------------------- |
| 2726 | | do_diff - generate a difference between two |
| 2727 | | CHD files |
| 2728 | | -------------------------------------------------*/ |
| 2064 | //------------------------------------------------- |
| 2065 | // do_extract_raw - extract a raw file from a |
| 2066 | // CHD image |
| 2067 | //------------------------------------------------- |
| 2729 | 2068 | |
| 2730 | | static int do_diff(int argc, char *argv[], int param) |
| 2069 | static void do_extract_raw(parameters_t ¶ms) |
| 2731 | 2070 | { |
| 2732 | | const char *parentfile = NULL, *inputfile = NULL, *outputfile = NULL; |
| 2733 | | chd_file *parentchd = NULL; |
| 2734 | | chd_file *outputchd = NULL; |
| 2735 | | chd_file *inputchd = NULL; |
| 2736 | | chd_error err; |
| 2071 | // catch errors so we can close & delete the output file |
| 2072 | astring *output_file_str = params.find(OPTION_OUTPUT); |
| 2073 | core_file *output_file = NULL; |
| 2074 | try |
| 2075 | { |
| 2076 | // parse out input files |
| 2077 | chd_file input_parent_chd; |
| 2078 | chd_file input_chd; |
| 2079 | parse_input_chd_parameters(params, input_chd, input_parent_chd); |
| 2737 | 2080 | |
| 2738 | | /* require 5 args total */ |
| 2739 | | if (argc != 5) |
| 2740 | | return usage(); |
| 2081 | // parse out input start/end |
| 2082 | UINT64 input_start; |
| 2083 | UINT64 input_end; |
| 2084 | parse_input_start_end(params, input_chd.logical_bytes(), input_chd.hunk_bytes(), input_chd.hunk_bytes(), input_start, input_end); |
| 2085 | |
| 2086 | // process output file |
| 2087 | if (output_file_str != NULL) |
| 2088 | { |
| 2089 | file_error filerr = core_fopen(*output_file_str, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &output_file); |
| 2090 | if (filerr != FILERR_NONE) |
| 2091 | report_error(1, "Unable to open file (%s)", output_file_str->cstr()); |
| 2092 | } |
| 2741 | 2093 | |
| 2742 | | /* extract the data */ |
| 2743 | | parentfile = argv[2]; |
| 2744 | | inputfile = argv[3]; |
| 2745 | | outputfile = argv[4]; |
| 2094 | // print some info |
| 2095 | astring tempstr; |
| 2096 | printf("Output File: %s\n", output_file_str->cstr()); |
| 2097 | printf("Input CHD: %s\n", params.find(OPTION_INPUT)->cstr()); |
| 2098 | if (input_start != 0 || input_end != input_chd.logical_bytes()) |
| 2099 | { |
| 2100 | printf("Input start: %s\n", big_int_string(tempstr, input_start)); |
| 2101 | printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); |
| 2102 | } |
| 2746 | 2103 | |
| 2747 | | /* print some info */ |
| 2748 | | printf("Parent file: %s\n", parentfile); |
| 2749 | | printf("Input file: %s\n", inputfile); |
| 2750 | | printf("Diff file: %s\n", outputfile); |
| 2104 | // copy all data |
| 2105 | dynamic_buffer buffer((TEMP_BUFFER_SIZE / input_chd.hunk_bytes()) * input_chd.hunk_bytes()); |
| 2106 | for (UINT64 offset = input_start; offset < input_end; ) |
| 2107 | { |
| 2108 | progress(false, "Extracting, %.1f%% complete... \r", 100.0 * double(offset - input_start) / double(input_end - input_start)); |
| 2751 | 2109 | |
| 2752 | | /* open the soon-to-be-parent CHD */ |
| 2753 | | err = chd_open(parentfile, CHD_OPEN_READ, NULL, &parentchd); |
| 2754 | | if (err != CHDERR_NONE) |
| 2755 | | { |
| 2756 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", parentfile, chd_error_string(err)); |
| 2757 | | goto cleanup; |
| 2758 | | } |
| 2110 | // determine how much to read |
| 2111 | UINT32 bytes_to_read = MIN(buffer.count(), input_end - offset); |
| 2112 | chd_error err = input_chd.read_bytes(offset, buffer, bytes_to_read); |
| 2113 | if (err != CHDERR_NONE) |
| 2114 | report_error(1, "Error reading CHD file (%s): %s", params.find(OPTION_INPUT)->cstr(), chd_file::error_string(err)); |
| 2115 | |
| 2116 | // write to the output |
| 2117 | UINT32 count = core_fwrite(output_file, buffer, bytes_to_read); |
| 2118 | if (count != bytes_to_read) |
| 2119 | report_error(1, "Error writing to file; check disk space (%s)", output_file_str->cstr()); |
| 2759 | 2120 | |
| 2760 | | /* open the input CHD */ |
| 2761 | | err = chd_open(inputfile, CHD_OPEN_READ, NULL, &inputchd); |
| 2762 | | if (err != CHDERR_NONE) |
| 2763 | | { |
| 2764 | | fprintf(stderr, "Error opening CHD file '%s': %s\n", inputfile, chd_error_string(err)); |
| 2765 | | goto cleanup; |
| 2121 | // advance |
| 2122 | offset += bytes_to_read; |
| 2123 | } |
| 2124 | |
| 2125 | // finish up |
| 2126 | core_fclose(output_file); |
| 2127 | printf("Extraction complete \n"); |
| 2766 | 2128 | } |
| 2767 | | |
| 2768 | | /* create the new CHD as a diff against the parent */ |
| 2769 | | err = chd_create(outputfile, 0, 0, chd_get_header(parentchd)->compression, parentchd); |
| 2770 | | if (err != CHDERR_NONE) |
| 2129 | catch (...) |
| 2771 | 2130 | { |
| 2772 | | fprintf(stderr, "Error creating CHD file: %s\n", chd_error_string(err)); |
| 2773 | | goto cleanup; |
| 2131 | // delete the output file |
| 2132 | if (output_file != NULL) |
| 2133 | core_fclose(output_file); |
| 2134 | osd_rmfile(*output_file_str); |
| 2135 | throw; |
| 2774 | 2136 | } |
| 2775 | | |
| 2776 | | /* open the new CHD */ |
| 2777 | | err = chd_open(outputfile, CHD_OPEN_READWRITE, parentchd, &outputchd); |
| 2778 | | if (err != CHDERR_NONE) |
| 2779 | | { |
| 2780 | | fprintf(stderr, "Error opening new CHD file: %s\n", chd_error_string(err)); |
| 2781 | | goto cleanup; |
| 2782 | | } |
| 2783 | | |
| 2784 | | /* do the compression; our interface will route reads for us */ |
| 2785 | | err = chdman_compress_chd(outputchd, inputchd, 0); |
| 2786 | | if (err != CHDERR_NONE) |
| 2787 | | fprintf(stderr, "Error during compression: %s\n", chd_error_string(err)); |
| 2788 | | |
| 2789 | | cleanup: |
| 2790 | | /* close everything down */ |
| 2791 | | if (outputchd != NULL) |
| 2792 | | chd_close(outputchd); |
| 2793 | | if (inputchd != NULL) |
| 2794 | | chd_close(inputchd); |
| 2795 | | if (parentchd != NULL) |
| 2796 | | chd_close(parentchd); |
| 2797 | | if (err != CHDERR_NONE) |
| 2798 | | osd_rmfile(outputfile); |
| 2799 | | return (err != CHDERR_NONE); |
| 2800 | 2137 | } |
| 2801 | 2138 | |
| 2802 | 2139 | |
| 2803 | | /*------------------------------------------------- |
| 2804 | | do_setchs - change the CHS values on a hard |
| 2805 | | disk image |
| 2806 | | -------------------------------------------------*/ |
| 2140 | //------------------------------------------------- |
| 2141 | // do_extract_cd - extract a CD file from a |
| 2142 | // CHD image |
| 2143 | //------------------------------------------------- |
| 2807 | 2144 | |
| 2808 | | static int do_setchs(int argc, char *argv[], int param) |
| 2145 | static void do_extract_cd(parameters_t ¶ms) |
| 2809 | 2146 | { |
| 2810 | | int oldcyls, oldhds, oldsecs, oldsecsize; |
| 2811 | | UINT8 was_readonly = FALSE; |
| 2812 | | UINT64 old_logicalbytes; |
| 2813 | | const char *inoutfile; |
| 2814 | | chd_file *chd = NULL; |
| 2815 | | int cyls, hds, secs; |
| 2816 | | char metadata[256]; |
| 2817 | | chd_header header; |
| 2818 | | chd_error err; |
| 2819 | | |
| 2820 | | /* require 6 args total */ |
| 2821 | | if (argc != 6) |
| 2822 | | return usage(); |
| 2823 | | |
| 2824 | | /* extract the data */ |
| 2825 | | inoutfile = argv[2]; |
| 2826 | | cyls = atoi(argv[3]); |
| 2827 | | hds = atoi(argv[4]); |
| 2828 | | secs = atoi(argv[5]); |
| 2829 | | |
| 2830 | | /* print some info */ |
| 2831 | | printf("Input file: %s\n", inoutfile); |
| 2832 | | printf("Cylinders: %d\n", cyls); |
| 2833 | | printf("Heads: %d\n", hds); |
| 2834 | | printf("Sectors: %d\n", secs); |
| 2835 | | |
| 2836 | | /* open the file read-only and get the header */ |
| 2837 | | err = chd_open(inoutfile, CHD_OPEN_READ, NULL, &chd); |
| 2838 | | if (err != CHDERR_NONE) |
| 2147 | // catch errors so we can close & delete the output file |
| 2148 | astring *output_file_str = params.find(OPTION_OUTPUT); |
| 2149 | astring *output_bin_file_str = params.find(OPTION_OUTPUT_BIN); |
| 2150 | core_file *output_bin_file = NULL; |
| 2151 | core_file *output_toc_file = NULL; |
| 2152 | try |
| 2839 | 2153 | { |
| 2840 | | fprintf(stderr, "Error opening CHD file '%s' read-only: %s\n", inoutfile, chd_error_string(err)); |
| 2841 | | goto cleanup; |
| 2842 | | } |
| 2843 | | header = *chd_get_header(chd); |
| 2844 | | chd_close(chd); |
| 2845 | | chd = NULL; |
| 2154 | // parse out input files |
| 2155 | chd_file input_parent_chd; |
| 2156 | chd_file input_chd; |
| 2157 | parse_input_chd_parameters(params, input_chd, input_parent_chd); |
| 2846 | 2158 | |
| 2847 | | /* if the drive is not writeable, note that, and make it so */ |
| 2848 | | if (!(header.flags & CHDFLAGS_IS_WRITEABLE)) |
| 2849 | | { |
| 2850 | | was_readonly = TRUE; |
| 2851 | | header.flags |= CHDFLAGS_IS_WRITEABLE; |
| 2159 | // further process input file |
| 2160 | cdrom_file *cdrom = cdrom_open(&input_chd); |
| 2161 | if (cdrom == NULL) |
| 2162 | report_error(1, "Unable to recognize CHD file as a CD"); |
| 2163 | const cdrom_toc *toc = cdrom_get_toc(cdrom); |
| 2164 | |
| 2165 | // process output file |
| 2166 | if (output_file_str != NULL) |
| 2167 | { |
| 2168 | file_error filerr = core_fopen(*output_file_str, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_NO_BOM, &output_toc_file); |
| 2169 | if (filerr != FILERR_NONE) |
| 2170 | report_error(1, "Unable to open file (%s)", output_file_str->cstr()); |
| 2171 | } |
| 2172 | bool cuemode = (output_file_str->find(".cue") != -1); |
| 2852 | 2173 | |
| 2853 | | /* write the new header */ |
| 2854 | | err = chd_set_header(inoutfile, &header); |
| 2855 | | if (err != CHDERR_NONE) |
| 2174 | // process output BIN file |
| 2175 | astring default_name(*output_file_str); |
| 2176 | int chop = default_name.rchr(0, '.'); |
| 2177 | if (chop != -1) |
| 2178 | default_name.substr(0, chop); |
| 2179 | default_name.cat(".bin"); |
| 2180 | if (output_bin_file_str == NULL) |
| 2181 | output_bin_file_str = &default_name; |
| 2856 | 2182 | { |
| 2857 | | fprintf(stderr, "Error making CHD file writeable: %s\n", chd_error_string(err)); |
| 2858 | | goto cleanup; |
| 2183 | file_error filerr = core_fopen(*output_bin_file_str, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &output_bin_file); |
| 2184 | if (filerr != FILERR_NONE) |
| 2185 | report_error(1, "Unable to open file (%s)", output_bin_file_str->cstr()); |
| 2859 | 2186 | } |
| 2860 | | } |
| 2861 | 2187 | |
| 2862 | | /* open the file read/write */ |
| 2863 | | err = chd_open(inoutfile, CHD_OPEN_READWRITE, NULL, &chd); |
| 2864 | | if (err != CHDERR_NONE) |
| 2865 | | { |
| 2866 | | fprintf(stderr, "Error opening CHD file '%s' read/write: %s\n", inoutfile, chd_error_string(err)); |
| 2867 | | goto cleanup; |
| 2868 | | } |
| 2188 | // print some info |
| 2189 | astring tempstr; |
| 2190 | printf("Output TOC: %s\n", output_file_str->cstr()); |
| 2191 | printf("Output Data: %s\n", output_bin_file_str->cstr()); |
| 2192 | printf("Input CHD: %s\n", params.find(OPTION_INPUT)->cstr()); |
| 2869 | 2193 | |
| 2870 | | /* get the hard disk metadata */ |
| 2871 | | err = chd_get_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); |
| 2872 | | if (err != CHDERR_NONE || sscanf(metadata, HARD_DISK_METADATA_FORMAT, &oldcyls, &oldhds, &oldsecs, &oldsecsize) != 4) |
| 2873 | | { |
| 2874 | | fprintf(stderr, "CHD file '%s' is not a hard disk!\n", inoutfile); |
| 2875 | | err = CHDERR_INVALID_FILE; |
| 2876 | | goto cleanup; |
| 2877 | | } |
| 2194 | // determine total frames |
| 2195 | UINT64 total_bytes = 0; |
| 2196 | for (int tracknum = 0; tracknum < toc->numtrks; tracknum++) |
| 2197 | total_bytes += toc->tracks[tracknum].frames * (toc->tracks[tracknum].datasize + toc->tracks[tracknum].subsize); |
| 2198 | |
| 2199 | // iterate over tracks and copy all data |
| 2200 | UINT64 outputoffs = 0; |
| 2201 | UINT32 discoffs = 0; |
| 2202 | dynamic_buffer buffer; |
| 2203 | for (int tracknum = 0; tracknum < toc->numtrks; tracknum++) |
| 2204 | { |
| 2205 | // output the metadata about the track to the TOC file |
| 2206 | const cdrom_track_info &trackinfo = toc->tracks[tracknum]; |
| 2207 | output_track_metadata(cuemode, output_toc_file, tracknum, trackinfo, *output_bin_file_str, discoffs, outputoffs); |
| 2878 | 2208 | |
| 2879 | | /* write our own */ |
| 2880 | | sprintf(metadata, HARD_DISK_METADATA_FORMAT, cyls, hds, secs, oldsecsize); |
| 2881 | | err = chd_set_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, strlen(metadata) + 1, CHD_MDFLAGS_CHECKSUM); |
| 2882 | | if (err != CHDERR_NONE) |
| 2883 | | { |
| 2884 | | fprintf(stderr, "Error writing new metadata to CHD file: %s\n", chd_error_string(err)); |
| 2885 | | goto cleanup; |
| 2886 | | } |
| 2209 | // resize the buffer for the track |
| 2210 | UINT32 output_frame_size = trackinfo.datasize + ((trackinfo.subtype != CD_SUB_NONE) ? trackinfo.subsize : 0); |
| 2211 | buffer.resize((TEMP_BUFFER_SIZE / output_frame_size) * output_frame_size); |
| 2212 | |
| 2213 | // now read and output the actual data |
| 2214 | UINT32 bufferoffs = 0; |
| 2215 | for (int frame = 0; frame < trackinfo.frames; frame++) |
| 2216 | { |
| 2217 | progress(false, "Extracting, %.1f%% complete... \r", 100.0 * double(outputoffs) / double(total_bytes)); |
| 2887 | 2218 | |
| 2888 | | /* get the header and compute the new logical size */ |
| 2889 | | header = *chd_get_header(chd); |
| 2890 | | old_logicalbytes = header.logicalbytes; |
| 2891 | | header.logicalbytes = (UINT64)cyls * (UINT64)hds * (UINT64)secs * (UINT64)oldsecsize; |
| 2219 | // read the data |
| 2220 | cdrom_read_data(cdrom, cdrom_get_track_start(cdrom, tracknum) + frame, &buffer[bufferoffs], trackinfo.trktype); |
| 2892 | 2221 | |
| 2893 | | /* close the file */ |
| 2894 | | chd_close(chd); |
| 2895 | | chd = NULL; |
| 2222 | // for CDRWin, audio tracks must be reversed |
| 2223 | if (cuemode && (trackinfo.trktype == CD_TRACK_AUDIO)) |
| 2224 | for (int swapindex = 0; swapindex < trackinfo.datasize; swapindex += 2) |
| 2225 | { |
| 2226 | UINT8 swaptemp = buffer[bufferoffs + swapindex]; |
| 2227 | buffer[bufferoffs + swapindex] = buffer[bufferoffs + swapindex + 1]; |
| 2228 | buffer[bufferoffs + swapindex + 1] = swaptemp; |
| 2229 | } |
| 2230 | bufferoffs += trackinfo.datasize; |
| 2231 | discoffs++; |
| 2896 | 2232 | |
| 2897 | | /* restore the read-only state */ |
| 2898 | | if (was_readonly) |
| 2899 | | header.flags &= ~CHDFLAGS_IS_WRITEABLE; |
| 2233 | // read the subcode data |
| 2234 | if (trackinfo.subtype != CD_SUB_NONE) |
| 2235 | { |
| 2236 | cdrom_read_subcode(cdrom, cdrom_get_track_start(cdrom, tracknum) + frame, &buffer[bufferoffs]); |
| 2237 | bufferoffs += trackinfo.subsize; |
| 2238 | } |
| 2900 | 2239 | |
| 2901 | | /* set the new logical size */ |
| 2902 | | if (header.logicalbytes != old_logicalbytes || was_readonly) |
| 2903 | | { |
| 2904 | | err = chd_set_header(inoutfile, &header); |
| 2905 | | if (err != CHDERR_NONE) |
| 2906 | | fprintf(stderr, "Error writing new header to CHD file: %s\n", chd_error_string(err)); |
| 2240 | // write it out if we need to |
| 2241 | if (bufferoffs == buffer.count() || frame == trackinfo.frames - 1) |
| 2242 | { |
| 2243 | core_fseek(output_bin_file, outputoffs, SEEK_SET); |
| 2244 | UINT32 byteswritten = core_fwrite(output_bin_file, buffer, bufferoffs); |
| 2245 | if (byteswritten != bufferoffs) |
| 2246 | report_error(1, "Error writing frame %d to file (%s): %s\n", frame, output_file_str->cstr(), chd_file::error_string(CHDERR_WRITE_ERROR)); |
| 2247 | outputoffs += bufferoffs; |
| 2248 | bufferoffs = 0; |
| 2249 | } |
| 2250 | } |
| 2251 | } |
| 2252 | |
| 2253 | // finish up |
| 2254 | core_fclose(output_bin_file); |
| 2255 | core_fclose(output_toc_file); |
| 2256 | printf("Extraction complete \n"); |
| 2907 | 2257 | } |
| 2908 | | |
| 2909 | | /* print a warning if the size is different */ |
| 2910 | | if (header.logicalbytes < old_logicalbytes) |
| 2911 | | fprintf(stderr, "WARNING: new size is smaller; run chdman -update to reclaim empty space\n"); |
| 2912 | | else if (header.logicalbytes > old_logicalbytes) |
| 2913 | | fprintf(stderr, "WARNING: new size is larger; run chdman -update to account for new empty space\n"); |
| 2914 | | |
| 2915 | | cleanup: |
| 2916 | | if (chd != NULL) |
| 2917 | | chd_close(chd); |
| 2918 | | if (err != CHDERR_NONE && was_readonly) |
| 2258 | catch (...) |
| 2919 | 2259 | { |
| 2920 | | header.flags &= ~CHDFLAGS_IS_WRITEABLE; |
| 2921 | | chd_set_header(inoutfile, &header); |
| 2260 | // delete the output files |
| 2261 | if (output_bin_file != NULL) |
| 2262 | core_fclose(output_bin_file); |
| 2263 | if (output_toc_file != NULL) |
| 2264 | core_fclose(output_toc_file); |
| 2265 | osd_rmfile(*output_bin_file_str); |
| 2266 | osd_rmfile(*output_file_str); |
| 2267 | throw; |
| 2922 | 2268 | } |
| 2923 | | return (err != CHDERR_NONE); |
| 2924 | 2269 | } |
| 2925 | 2270 | |
| 2926 | 2271 | |
| 2927 | | /*------------------------------------------------- |
| 2928 | | do_addmeta - add metadata to a CHD from a |
| 2929 | | file |
| 2930 | | -------------------------------------------------*/ |
| 2272 | //------------------------------------------------- |
| 2273 | // do_extract_ld - extract an AVI file from a |
| 2274 | // CHD image |
| 2275 | //------------------------------------------------- |
| 2931 | 2276 | |
| 2932 | | static int do_addmeta(int argc, char *argv[], int param) |
| 2277 | static void do_extract_ld(parameters_t ¶ms) |
| 2933 | 2278 | { |
| 2934 | | const char *inoutfile, *srcfile, *tagstring; |
| 2935 | | UINT8 was_readonly = FALSE; |
| 2936 | | UINT8 *metadata = NULL; |
| 2937 | | chd_file *chd = NULL; |
| 2938 | | chd_header header; |
| 2939 | | file_error filerr; |
| 2940 | | UINT32 metalength; |
| 2941 | | UINT32 metaindex; |
| 2942 | | UINT32 metatag; |
| 2943 | | chd_error err; |
| 2944 | | |
| 2945 | | /* require 5 or 6 args total */ |
| 2946 | | if (argc != 5 && argc != 6) |
| 2947 | | return usage(); |
| 2948 | | |
| 2949 | | /* extract the data */ |
| 2950 | | inoutfile = argv[2]; |
| 2951 | | tagstring = argv[3]; |
| 2952 | | if (argc == 5) |
| 2279 | // catch errors so we can close & delete the output file |
| 2280 | astring *output_file_str = params.find(OPTION_OUTPUT); |
| 2281 | avi_file *output_file = NULL; |
| 2282 | try |
| 2953 | 2283 | { |
| 2954 | | metaindex = 0; |
| 2955 | | srcfile = argv[4]; |
| 2956 | | } |
| 2957 | | else |
| 2958 | | { |
| 2959 | | metaindex = atoi(argv[4]); |
| 2960 | | srcfile = argv[5]; |
| 2961 | | } |
| 2284 | // parse out input files |
| 2285 | chd_file input_parent_chd; |
| 2286 | chd_file input_chd; |
| 2287 | parse_input_chd_parameters(params, input_chd, input_parent_chd); |
| 2962 | 2288 | |
| 2963 | | /* verify the tag */ |
| 2964 | | if (strlen(tagstring) > 4) |
| 2965 | | { |
| 2966 | | fprintf(stderr, "Invalid tag '%s'; must be 4 characters or less\n", tagstring); |
| 2967 | | return CHDERR_INVALID_PARAMETER; |
| 2968 | | } |
| 2969 | | metatag = ((tagstring[0] == 0) ? ' ' : tagstring[0]) << 24; |
| 2970 | | metatag |= ((tagstring[1] == 0) ? ' ' : tagstring[1]) << 16; |
| 2971 | | metatag |= ((tagstring[2] == 0) ? ' ' : tagstring[2]) << 8; |
| 2972 | | metatag |= ((tagstring[3] == 0) ? ' ' : tagstring[3]) << 0; |
| 2289 | // read core metadata |
| 2290 | astring metadata; |
| 2291 | chd_error err = input_chd.read_metadata(AV_METADATA_TAG, 0, metadata); |
| 2292 | if (err != CHDERR_NONE) |
| 2293 | report_error(1, "Unable to find A/V metadata in the input CHD"); |
| 2973 | 2294 | |
| 2974 | | /* print some info */ |
| 2975 | | printf("Input file: %s\n", inoutfile); |
| 2976 | | printf("Tag: '%c%c%c%c'\n", (metatag >> 24) & 0xff, (metatag >> 16) & 0xff, (metatag >> 8) & 0xff, metatag & 0xff); |
| 2977 | | printf("Index: %d\n", metaindex); |
| 2978 | | printf("Source file: %s\n", srcfile); |
| 2295 | // parse the metadata |
| 2296 | UINT32 fps_times_1million; |
| 2297 | UINT32 max_samples_per_frame; |
| 2298 | UINT32 frame_bytes; |
| 2299 | int width; |
| 2300 | int height; |
| 2301 | int interlaced; |
| 2302 | int channels; |
| 2303 | int rate; |
| 2304 | { |
| 2305 | int fps; |
| 2306 | int fpsfrac; |
| 2307 | if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) |
| 2308 | report_error(1, "Improperly formatted A/V metadata found"); |
| 2309 | fps_times_1million = fps * 1000000 + fpsfrac; |
| 2310 | } |
| 2311 | int interlace_factor = interlaced ? 2 : 1; |
| 2979 | 2312 | |
| 2980 | | /* open the file read-only and get the header */ |
| 2981 | | err = chd_open(inoutfile, CHD_OPEN_READ, NULL, &chd); |
| 2982 | | if (err != CHDERR_NONE) |
| 2983 | | { |
| 2984 | | fprintf(stderr, "Error opening CHD file '%s' read-only: %s\n", inoutfile, chd_error_string(err)); |
| 2985 | | goto cleanup; |
| 2986 | | } |
| 2987 | | header = *chd_get_header(chd); |
| 2988 | | chd_close(chd); |
| 2989 | | chd = NULL; |
| 2313 | // determine key parameters and validate |
| 2314 | max_samples_per_frame = (UINT64(rate) * 1000000 + fps_times_1million - 1) / fps_times_1million; |
| 2315 | frame_bytes = avhuff_encoder::raw_data_size(width, height, channels, max_samples_per_frame); |
| 2316 | if (frame_bytes != input_chd.hunk_bytes()) |
| 2317 | report_error(1, "Frame size does not match hunk size for this CHD"); |
| 2318 | |
| 2319 | // parse out input start/end |
| 2320 | UINT64 input_start; |
| 2321 | UINT64 input_end; |
| 2322 | parse_input_start_end(params, input_chd.hunk_count() / interlace_factor, 0, 1, input_start, input_end); |
| 2323 | input_start *= interlace_factor; |
| 2324 | input_end *= interlace_factor; |
| 2990 | 2325 | |
| 2991 | | /* if the drive is not writeable, note that, and make it so */ |
| 2992 | | if (!(header.flags & CHDFLAGS_IS_WRITEABLE)) |
| 2993 | | { |
| 2994 | | was_readonly = TRUE; |
| 2995 | | header.flags |= CHDFLAGS_IS_WRITEABLE; |
| 2326 | // build up the movie info |
| 2327 | avi_movie_info info; |
| 2328 | info.video_format = FORMAT_YUY2; |
| 2329 | info.video_timescale = fps_times_1million / interlace_factor; |
| 2330 | info.video_sampletime = 1000000; |
| 2331 | info.video_width = width; |
| 2332 | info.video_height = height * interlace_factor; |
| 2333 | info.video_depth = 16; |
| 2334 | info.audio_format = 0; |
| 2335 | info.audio_timescale = rate; |
| 2336 | info.audio_sampletime = 1; |
| 2337 | info.audio_channels = channels; |
| 2338 | info.audio_samplebits = 16; |
| 2339 | info.audio_samplerate = rate; |
| 2996 | 2340 | |
| 2997 | | /* write the new header */ |
| 2998 | | err = chd_set_header(inoutfile, &header); |
| 2999 | | if (err != CHDERR_NONE) |
| 2341 | // process output file |
| 2342 | if (output_file_str != NULL) |
| 3000 | 2343 | { |
| 3001 | | fprintf(stderr, "Error making CHD file writeable: %s\n", chd_error_string(err)); |
| 3002 | | goto cleanup; |
| 2344 | avi_error avierr = avi_create(*output_file_str, &info, &output_file); |
| 2345 | if (avierr != AVIERR_NONE) |
| 2346 | report_error(1, "Unable to open file (%s)", output_file_str->cstr()); |
| 3003 | 2347 | } |
| 3004 | | } |
| 3005 | 2348 | |
| 3006 | | /* open the file read/write */ |
| 3007 | | err = chd_open(inoutfile, CHD_OPEN_READWRITE, NULL, &chd); |
| 3008 | | if (err != CHDERR_NONE) |
| 3009 | | { |
| 3010 | | fprintf(stderr, "Error opening CHD file '%s' read/write: %s\n", inoutfile, chd_error_string(err)); |
| 3011 | | goto cleanup; |
| 3012 | | } |
| 2349 | // print some info |
| 2350 | astring tempstr; |
| 2351 | printf("Output File: %s\n", output_file_str->cstr()); |
| 2352 | printf("Input CHD: %s\n", params.find(OPTION_INPUT)->cstr()); |
| 2353 | if (input_start != 0 || input_end != input_chd.hunk_count()) |
| 2354 | { |
| 2355 | printf("Input start: %s\n", big_int_string(tempstr, input_start)); |
| 2356 | printf("Input length: %s\n", big_int_string(tempstr, input_end - input_start)); |
| 2357 | } |
| 3013 | 2358 | |
| 3014 | | /* attempt to open the source file */ |
| 3015 | | filerr = core_fload(srcfile, (void **)&metadata, &metalength); |
| 3016 | | if (filerr != FILERR_NONE) |
| 3017 | | { |
| 3018 | | fprintf(stderr, "Error opening source file '%s'\n", srcfile); |
| 3019 | | err = CHDERR_FILE_NOT_FOUND; |
| 3020 | | goto cleanup; |
| 3021 | | } |
| 3022 | | |
| 3023 | | /* if it's text, strip any trailing Ctrl-Z and CR/LF and add a trailing NULL */ |
| 3024 | | if (param) |
| 3025 | | { |
| 3026 | | metadata = (UINT8 *)realloc(metadata, metalength + 1); |
| 3027 | | if (metadata == NULL) |
| 2359 | // create the codec configuration |
| 2360 | avhuff_decompress_config avconfig; |
| 2361 | dynamic_array<INT16> audio_data[16]; |
| 2362 | UINT32 actsamples; |
| 2363 | avconfig.maxsamples = max_samples_per_frame; |
| 2364 | avconfig.actsamples = &actsamples; |
| 2365 | for (int chnum = 0; chnum < ARRAY_LENGTH(audio_data); chnum++) |
| 3028 | 2366 | { |
| 3029 | | fprintf(stderr, "Out of memory preparing metadata\n"); |
| 3030 | | err = CHDERR_OUT_OF_MEMORY; |
| 3031 | | goto cleanup; |
| 2367 | audio_data[chnum].resize(max_samples_per_frame); |
| 2368 | avconfig.audio[chnum] = audio_data[chnum]; |
| 3032 | 2369 | } |
| 3033 | | metadata[metalength++] = 0; |
| 3034 | | while (metalength > 0 && (metadata[metalength - 2] == 0x0a || metadata[metalength - 2] == 0x0d || metadata[metalength - 2] == 0x1a)) |
| 3035 | | metadata[--metalength] = 0; |
| 3036 | | } |
| 2370 | |
| 2371 | // iterate over frames |
| 2372 | bitmap_yuy16 fullbitmap(width, height * interlace_factor); |
| 2373 | for (int framenum = input_start; framenum < input_end; framenum++) |
| 2374 | { |
| 2375 | progress(framenum == 0, "Extracting, %.1f%% complete... \r", 100.0 * double(framenum - input_start) / double(input_end - input_start)); |
| 3037 | 2376 | |
| 3038 | | /* write the new metadata */ |
| 3039 | | err = chd_set_metadata(chd, metatag, metaindex, metadata, metalength, CHD_MDFLAGS_CHECKSUM); |
| 3040 | | if (err != CHDERR_NONE) |
| 3041 | | { |
| 3042 | | fprintf(stderr, "Error writing new metadata to CHD file: %s\n", chd_error_string(err)); |
| 3043 | | goto cleanup; |
| 3044 | | } |
| 3045 | | header = *chd_get_header(chd); |
| 2377 | // set up the fake bitmap for this frame |
| 2378 | avconfig.video.wrap(&fullbitmap.pix(framenum % interlace_factor), fullbitmap.width(), fullbitmap.height() / interlace_factor, fullbitmap.rowpixels() * interlace_factor); |
| 2379 | input_chd.codec_configure(CHD_CODEC_AVHUFF, AVHUFF_CODEC_DECOMPRESS_CONFIG, &avconfig); |
| 2380 | |
| 2381 | // read the hunk into the buffers |
| 2382 | chd_error err = input_chd.read_hunk(framenum, NULL); |
| 2383 | if (err != CHDERR_NONE) |
| 2384 | report_error(1, "Error reading hunk %d from CHD file (%s): %s\n", framenum, params.find(OPTION_INPUT)->cstr(), chd_file::error_string(err)); |
| 3046 | 2385 | |
| 3047 | | /* close the file */ |
| 3048 | | chd_close(chd); |
| 3049 | | chd = NULL; |
| 2386 | // write audio |
| 2387 | for (int chnum = 0; chnum < channels; chnum++) |
| 2388 | { |
| 2389 | avi_error avierr = avi_append_sound_samples(output_file, chnum, avconfig.audio[chnum], actsamples, 0); |
| 2390 | if (avierr != AVIERR_NONE) |
| 2391 | report_error(1, "Error writing samples for hunk %d to file (%s): %s\n", framenum, output_file_str->cstr(), avi_error_string(avierr)); |
| 2392 | } |
| 3050 | 2393 | |
| 3051 | | /* restore the read-only state */ |
| 3052 | | if (was_readonly) |
| 3053 | | { |
| 3054 | | header.flags &= ~CHDFLAGS_IS_WRITEABLE; |
| 3055 | | err = chd_set_header(inoutfile, &header); |
| 3056 | | if (err != CHDERR_NONE) |
| 3057 | | fprintf(stderr, "Error writing new header to CHD file: %s\n", chd_error_string(err)); |
| 3058 | | } |
| 3059 | | if (err == CHDERR_NONE) |
| 3060 | | printf("Metadata added successfully as %s\n", param ? "text" : "binary"); |
| 2394 | // write video |
| 2395 | if (framenum % interlace_factor == interlace_factor - 1) |
| 2396 | { |
| 2397 | avi_error avierr = avi_append_video_frame(output_file, fullbitmap); |
| 2398 | if (avierr != AVIERR_NONE) |
| 2399 | report_error(1, "Error writing video for hunk %d to file (%s): %s\n", framenum, output_file_str->cstr(), avi_error_string(avierr)); |
| 2400 | } |
| 2401 | } |
| 3061 | 2402 | |
| 3062 | | cleanup: |
| 3063 | | if (metadata != NULL) |
| 3064 | | osd_free(metadata); |
| 3065 | | if (chd != NULL) |
| 3066 | | chd_close(chd); |
| 3067 | | if (err != CHDERR_NONE && was_readonly) |
| 2403 | // close and return |
| 2404 | avi_close(output_file); |
| 2405 | printf("Extraction complete \n"); |
| 2406 | } |
| 2407 | catch (...) |
| 3068 | 2408 | { |
| 3069 | | header.flags &= ~CHDFLAGS_IS_WRITEABLE; |
| 3070 | | chd_set_header(inoutfile, &header); |
| 2409 | // delete the output file |
| 2410 | if (output_file != NULL) |
| 2411 | avi_close(output_file); |
| 2412 | osd_rmfile(*output_file_str); |
| 2413 | throw; |
| 3071 | 2414 | } |
| 3072 | | return (err != CHDERR_NONE); |
| 3073 | 2415 | } |
| 3074 | 2416 | |
| 3075 | 2417 | |
| 3076 | | /*------------------------------------------------- |
| 3077 | | chdman_compress_file - compress a regular |
| 3078 | | file via the compression interfaces |
| 3079 | | -------------------------------------------------*/ |
| 2418 | //------------------------------------------------- |
| 2419 | // do_add_metadata - add metadata to a CHD from a |
| 2420 | // file |
| 2421 | //------------------------------------------------- |
| 3080 | 2422 | |
| 3081 | | static chd_error chdman_compress_file(chd_file *chd, const char *rawfile, UINT32 offset) |
| 2423 | static void do_add_metadata(parameters_t ¶ms) |
| 3082 | 2424 | { |
| 3083 | | core_file *sourcefile; |
| 3084 | | const chd_header *header; |
| 3085 | | UINT64 sourceoffset = 0; |
| 3086 | | UINT8 *cache = NULL; |
| 3087 | | double ratio = 1.0; |
| 3088 | | file_error filerr; |
| 3089 | | chd_error err; |
| 3090 | | int hunknum; |
| 2425 | // parse out input files |
| 2426 | chd_file input_parent_chd; |
| 2427 | chd_file input_chd; |
| 2428 | parse_input_chd_parameters(params, input_chd, input_parent_chd, true); |
| 3091 | 2429 | |
| 3092 | | /* open the raw file */ |
| 3093 | | filerr = core_fopen(rawfile, OPEN_FLAG_READ, &sourcefile); |
| 3094 | | if (filerr != FILERR_NONE) |
| 2430 | // process tag |
| 2431 | chd_metadata_tag tag = CHD_MAKE_TAG('?','?','?','?'); |
| 2432 | astring *tag_str = params.find(OPTION_TAG); |
| 2433 | if (tag_str != NULL) |
| 3095 | 2434 | { |
| 3096 | | err = CHDERR_FILE_NOT_FOUND; |
| 3097 | | goto cleanup; |
| 2435 | tag_str->cat(" "); |
| 2436 | tag = CHD_MAKE_TAG((*tag_str)[0], (*tag_str)[1], (*tag_str)[2], (*tag_str)[3]); |
| 3098 | 2437 | } |
| 3099 | | |
| 3100 | | /* get the header */ |
| 3101 | | header = chd_get_header(chd); |
| 3102 | | cache = (UINT8 *)malloc(header->hunkbytes); |
| 3103 | | if (cache == NULL) |
| 2438 | |
| 2439 | // process index |
| 2440 | UINT32 index = 0; |
| 2441 | astring *index_str = params.find(OPTION_INDEX); |
| 2442 | if (index_str != NULL) |
| 2443 | index = atoi(*index_str); |
| 2444 | |
| 2445 | // process text input |
| 2446 | astring *text_str = params.find(OPTION_VALUE_TEXT); |
| 2447 | astring text; |
| 2448 | if (text_str != NULL) |
| 3104 | 2449 | { |
| 3105 | | err = CHDERR_OUT_OF_MEMORY; |
| 3106 | | goto cleanup; |
| 2450 | text = *text_str; |
| 2451 | if (text[0] == '"' && text[text.len() - 1] == '"') |
| 2452 | text.substr(1, text.len() - 2); |
| 3107 | 2453 | } |
| 3108 | | |
| 3109 | | /* begin compressing */ |
| 3110 | | err = chd_compress_begin(chd); |
| 3111 | | if (err != CHDERR_NONE) |
| 3112 | | goto cleanup; |
| 3113 | | |
| 3114 | | /* loop over source hunks until we run out */ |
| 3115 | | for (hunknum = 0; hunknum < header->totalhunks; hunknum++) |
| 2454 | |
| 2455 | // process file input |
| 2456 | astring *file_str = params.find(OPTION_VALUE_FILE); |
| 2457 | dynamic_buffer file; |
| 2458 | if (file_str != NULL) |
| 3116 | 2459 | { |
| 3117 | | UINT32 bytesread; |
| 3118 | | |
| 3119 | | /* progress */ |
| 3120 | | progress(hunknum == 0, "Compressing hunk %d/%d... (ratio=%d%%) \r", hunknum, header->totalhunks, (int)(100.0 * ratio)); |
| 3121 | | |
| 3122 | | /* read the data */ |
| 3123 | | core_fseek(sourcefile, sourceoffset + offset, SEEK_SET); |
| 3124 | | bytesread = core_fread(sourcefile, cache, header->hunkbytes); |
| 3125 | | if (bytesread < header->hunkbytes) |
| 3126 | | memset(&cache[bytesread], 0, header->hunkbytes - bytesread); |
| 3127 | | |
| 3128 | | /* append the data */ |
| 3129 | | err = chd_compress_hunk(chd, cache, &ratio); |
| 3130 | | if (err != CHDERR_NONE) |
| 3131 | | goto cleanup; |
| 3132 | | |
| 3133 | | /* prepare for the next hunk */ |
| 3134 | | sourceoffset += header->hunkbytes; |
| 2460 | file_error filerr = core_fload(*file_str, file); |
| 2461 | if (filerr != FILERR_NONE) |
| 2462 | report_error(1, "Error reading metadata file (%s)", file_str->cstr()); |
| 3135 | 2463 | } |
| 2464 | |
| 2465 | // make sure we have one or the other |
| 2466 | if (text_str == NULL && file_str == NULL) |
| 2467 | report_error(1, "Error: missing either --valuetext/-vt or --valuefile/-vf parameters"); |
| 2468 | if (text_str != NULL && file_str != NULL) |
| 2469 | report_error(1, "Error: both --valuetext/-vt or --valuefile/-vf parameters specified; only one permitted"); |
| 2470 | |
| 2471 | // process no checksum |
| 2472 | UINT8 flags = CHD_MDFLAGS_CHECKSUM; |
| 2473 | if (params.find(OPTION_NO_CHECKSUM) != NULL) |
| 2474 | flags &= ~CHD_MDFLAGS_CHECKSUM; |
| 2475 | |
| 2476 | // print some info |
| 2477 | astring tempstr; |
| 2478 | printf("Input file: %s\n", params.find(OPTION_INPUT)->cstr()); |
| 2479 | printf("Tag: %c%c%c%c\n", (tag >> 24) & 0xff, (tag >> 16) & 0xff, (tag >> 8) & 0xff, tag & 0xff); |
| 2480 | printf("Index: %d\n", index); |
| 2481 | if (text_str != NULL) |
| 2482 | printf("Text: %s\n", text.cstr()); |
| 2483 | else |
| 2484 | printf("Data: %s (%d bytes)\n", file_str->cstr(), file.count()); |
| 3136 | 2485 | |
| 3137 | | /* finish compression */ |
| 3138 | | err = chd_compress_finish(chd, TRUE); |
| 2486 | // write the metadata |
| 2487 | chd_error err; |
| 2488 | if (text_str != NULL) |
| 2489 | err = input_chd.write_metadata(tag, index, text, flags); |
| 2490 | else |
| 2491 | err = input_chd.write_metadata(tag, index, file, flags); |
| 3139 | 2492 | if (err != CHDERR_NONE) |
| 3140 | | goto cleanup; |
| 3141 | | |
| 3142 | | /* final progress update */ |
| 3143 | | progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio)); |
| 3144 | | |
| 3145 | | cleanup: |
| 3146 | | if (sourcefile != NULL) |
| 3147 | | core_fclose(sourcefile); |
| 3148 | | if (cache != NULL) |
| 3149 | | free(cache); |
| 3150 | | return err; |
| 2493 | report_error(1, "Error adding metadata: %s", chd_file::error_string(err)); |
| 2494 | else |
| 2495 | printf("Metadata added\n"); |
| 3151 | 2496 | } |
| 3152 | 2497 | |
| 3153 | 2498 | |
| 3154 | | /*------------------------------------------------- |
| 3155 | | chdman_compress_chd - (re)compress a CHD file |
| 3156 | | via the compression interfaces |
| 3157 | | -------------------------------------------------*/ |
| 2499 | //------------------------------------------------- |
| 2500 | // do_del_metadata - remove metadata from a CHD |
| 2501 | //------------------------------------------------- |
| 3158 | 2502 | |
| 3159 | | static chd_error chdman_compress_chd(chd_file *chd, chd_file *source, UINT32 totalhunks) |
| 2503 | static void do_del_metadata(parameters_t ¶ms) |
| 3160 | 2504 | { |
| 3161 | | const chd_header *source_header; |
| 3162 | | const chd_header *header; |
| 3163 | | UINT8 *source_cache = NULL; |
| 3164 | | UINT64 source_offset = 0; |
| 3165 | | UINT32 source_bytes = 0; |
| 3166 | | UINT8 *cache = NULL; |
| 3167 | | double ratio = 1.0; |
| 3168 | | chd_error err, verifyerr; |
| 3169 | | int hunknum; |
| 2505 | // parse out input files |
| 2506 | chd_file input_parent_chd; |
| 2507 | chd_file input_chd; |
| 2508 | parse_input_chd_parameters(params, input_chd, input_parent_chd, true); |
| 3170 | 2509 | |
| 3171 | | /* get the header */ |
| 3172 | | header = chd_get_header(chd); |
| 3173 | | cache = (UINT8 *)malloc(header->hunkbytes); |
| 3174 | | if (cache == NULL) |
| 2510 | // process tag |
| 2511 | chd_metadata_tag tag = CHD_MAKE_TAG('?','?','?','?'); |
| 2512 | astring *tag_str = params.find(OPTION_TAG); |
| 2513 | if (tag_str != NULL) |
| 3175 | 2514 | { |
| 3176 | | err = CHDERR_OUT_OF_MEMORY; |
| 3177 | | goto cleanup; |
| 2515 | tag_str->cat(" "); |
| 2516 | tag = CHD_MAKE_TAG((*tag_str)[0], (*tag_str)[1], (*tag_str)[2], (*tag_str)[3]); |
| 3178 | 2517 | } |
| 2518 | |
| 2519 | // process index |
| 2520 | UINT32 index = 0; |
| 2521 | astring *index_str = params.find(OPTION_INDEX); |
| 2522 | if (index_str != NULL) |
| 2523 | index = atoi(*index_str); |
| 2524 | |
| 2525 | // print some info |
| 2526 | astring tempstr; |
| 2527 | printf("Input file: %s\n", params.find(OPTION_INPUT)->cstr()); |
| 2528 | printf("Tag: %c%c%c%c\n", (tag >> 24) & 0xff, (tag >> 16) & 0xff, (tag >> 8) & 0xff, tag & 0xff); |
| 2529 | printf("Index: %d\n", index); |
| 3179 | 2530 | |
| 3180 | | /* get the source CHD header */ |
| 3181 | | source_header = chd_get_header(source); |
| 3182 | | source_cache = (UINT8 *)malloc(source_header->hunkbytes); |
| 3183 | | if (source_cache == NULL) |
| 3184 | | { |
| 3185 | | err = CHDERR_OUT_OF_MEMORY; |
| 3186 | | goto cleanup; |
| 3187 | | } |
| 3188 | | |
| 3189 | | /* begin compressing */ |
| 3190 | | err = chd_compress_begin(chd); |
| 2531 | // write the metadata |
| 2532 | chd_error err = input_chd.delete_metadata(tag, index); |
| 3191 | 2533 | if (err != CHDERR_NONE) |
| 3192 | | goto cleanup; |
| 2534 | report_error(1, "Error removing metadata: %s", chd_file::error_string(err)); |
| 2535 | else |
| 2536 | printf("Metadata removed\n"); |
| 2537 | } |
| 3193 | 2538 | |
| 3194 | | /* also begin verifying the source driver */ |
| 3195 | | verifyerr = chd_verify_begin(source); |
| 3196 | 2539 | |
| 3197 | | /* a zero count means the natural number */ |
| 3198 | | if (totalhunks == 0) |
| 3199 | | totalhunks = source_header->totalhunks; |
| 2540 | //------------------------------------------------- |
| 2541 | // main - entry point |
| 2542 | //------------------------------------------------- |
| 3200 | 2543 | |
| 3201 | | /* loop over source hunks until we run out */ |
| 3202 | | for (hunknum = 0; hunknum < totalhunks; hunknum++) |
| 3203 | | { |
| 3204 | | UINT32 bytesremaining = header->hunkbytes; |
| 3205 | | UINT8 *dest = cache; |
| 2544 | int CLIB_DECL main(int argc, char *argv[]) |
| 2545 | { |
| 2546 | // print the header |
| 2547 | extern const char build_version[]; |
| 2548 | printf("chdman - MAME Compressed Hunks of Data (CHD) manager %s\n", build_version); |
| 3206 | 2549 | |
| 3207 | | /* progress */ |
| 3208 | | progress(hunknum == 0, "Compressing hunk %d/%d... (ratio=%d%%) \r", hunknum, totalhunks, (int)(100.0 * ratio)); |
| 3209 | | |
| 3210 | | /* read the data */ |
| 3211 | | while (bytesremaining > 0) |
| 2550 | // handle help specially |
| 2551 | if (argc < 2) |
| 2552 | return print_help(argv[0]); |
| 2553 | int argnum = 1; |
| 2554 | const char *command = argv[argnum++]; |
| 2555 | bool help = (strcmp(command, COMMAND_HELP) == 0); |
| 2556 | if (help) |
| 2557 | { |
| 2558 | if (argc <= 2) |
| 2559 | return print_help(argv[0]); |
| 2560 | command = argv[argnum++]; |
| 2561 | } |
| 2562 | |
| 2563 | // iterate over commands to find our match |
| 2564 | for (int cmdnum = 0; cmdnum < ARRAY_LENGTH(s_commands); cmdnum++) |
| 2565 | if (strcmp(command, s_commands[cmdnum].name) == 0) |
| 3212 | 2566 | { |
| 3213 | | /* if we have data in the buffer, copy it */ |
| 3214 | | if (source_bytes > 0) |
| 3215 | | { |
| 3216 | | UINT32 bytestocopy = MIN(bytesremaining, source_bytes); |
| 3217 | | memcpy(dest, &source_cache[source_header->hunkbytes - source_bytes], bytestocopy); |
| 3218 | | dest += bytestocopy; |
| 3219 | | source_bytes -= bytestocopy; |
| 3220 | | bytesremaining -= bytestocopy; |
| 3221 | | } |
| 2567 | const command_description &desc = s_commands[cmdnum]; |
| 2568 | |
| 2569 | // print help if that was requested |
| 2570 | if (help) |
| 2571 | return print_help(argv[0], desc); |
| 3222 | 2572 | |
| 3223 | | /* otherwise, read in another hunk of the source */ |
| 3224 | | else |
| 2573 | // otherwise, verify the parameters |
| 2574 | tagmap_t<astring *> parameters; |
| 2575 | while (argnum < argc) |
| 3225 | 2576 | { |
| 3226 | | /* verify the next hunk */ |
| 3227 | | if (verifyerr == CHDERR_NONE) |
| 3228 | | err = chd_verify_hunk(source); |
| 2577 | // should be an option name |
| 2578 | const char *arg = argv[argnum++]; |
| 2579 | if (arg[0] != '-') |
| 2580 | return print_help(argv[0], desc, "Expected option, not parameter"); |
| 3229 | 2581 | |
| 3230 | | /* then read it (should be the same) */ |
| 3231 | | err = chd_read(source, source_offset / source_header->hunkbytes, source_cache); |
| 3232 | | if (err != CHDERR_NONE) |
| 3233 | | memset(source_cache, 0, source_header->hunkbytes); |
| 3234 | | source_bytes = source_header->hunkbytes; |
| 3235 | | source_offset += source_bytes; |
| 3236 | | } |
| 3237 | | } |
| 2582 | // iterate over valid options |
| 2583 | int valid; |
| 2584 | for (valid = 0; valid < ARRAY_LENGTH(desc.valid_options); valid++) |
| 2585 | { |
| 2586 | // reduce to the option name |
| 2587 | const char *validname = desc.valid_options[valid]; |
| 2588 | if (validname == NULL) |
| 2589 | break; |
| 2590 | if (*validname == REQUIRED[0]) |
| 2591 | validname++; |
| 3238 | 2592 | |
| 3239 | | /* append the data */ |
| 3240 | | err = chd_compress_hunk(chd, cache, &ratio); |
| 3241 | | if (err != CHDERR_NONE) |
| 3242 | | goto cleanup; |
| 3243 | | } |
| 2593 | // find the matching option description |
| 2594 | int optnum; |
| 2595 | for (optnum = 0; optnum < ARRAY_LENGTH(s_options); optnum++) |
| 2596 | if (strcmp(s_options[optnum].name, validname) == 0) |
| 2597 | break; |
| 2598 | assert(optnum != ARRAY_LENGTH(s_options)); |
| 3244 | 2599 | |
| 3245 | | /* if we read all the source data, verify the checksums */ |
| 3246 | | if (verifyerr == CHDERR_NONE && source_offset >= source_header->logicalbytes) |
| 3247 | | { |
| 3248 | | static const UINT8 empty_checksum[CHD_SHA1_BYTES] = { 0 }; |
| 3249 | | chd_verify_result verify; |
| 3250 | | int i; |
| 2600 | // do we match? |
| 2601 | const option_description &odesc = s_options[optnum]; |
| 2602 | if ((arg[1] == '-' && strcmp(odesc.name, &arg[2]) == 0) || |
| 2603 | (arg[1] != '-' && odesc.shortname != NULL && strcmp(odesc.shortname, &arg[1]) == 0)) |
| 2604 | { |
| 2605 | // if we need a parameter, consume it |
| 2606 | const char *param = ""; |
| 2607 | if (odesc.parameter) |
| 2608 | { |
| 2609 | if (argnum >= argc || argv[argnum][0] == '-') |
| 2610 | return print_help(argv[0], desc, "Option is missing parameter"); |
| 2611 | param = argv[argnum++]; |
| 2612 | } |
| 2613 | |
| 2614 | // add to the map |
| 2615 | if (parameters.add(odesc.name, new astring(param)) == TMERR_DUPLICATE) |
| 2616 | return print_help(argv[0], desc, "Multiple parameters of the same type specified"); |
| 2617 | break; |
| 2618 | } |
| 2619 | } |
| 3251 | 2620 | |
| 3252 | | /* get the final values */ |
| 3253 | | err = chd_verify_finish(source, &verify); |
| 3254 | | |
| 3255 | | /* check the MD5 */ |
| 3256 | | if (memcmp(source_header->md5, empty_checksum, CHD_MD5_BYTES) != 0) |
| 3257 | | { |
| 3258 | | if (memcmp(source_header->md5, verify.md5, CHD_MD5_BYTES) != 0) |
| 3259 | | { |
| 3260 | | progress(TRUE, "WARNING: expected input MD5 = "); |
| 3261 | | for (i = 0; i < CHD_MD5_BYTES; i++) |
| 3262 | | progress(TRUE, "%02x", source_header->md5[i]); |
| 3263 | | progress(TRUE, "\n"); |
| 3264 | | |
| 3265 | | progress(TRUE, " actual MD5 = "); |
| 3266 | | for (i = 0; i < CHD_MD5_BYTES; i++) |
| 3267 | | progress(TRUE, "%02x", verify.md5[i]); |
| 3268 | | progress(TRUE, "\n"); |
| 2621 | // if not valid, error |
| 2622 | if (valid == ARRAY_LENGTH(desc.valid_options)) |
| 2623 | return print_help(argv[0], desc, "Option not valid for this command"); |
| 3269 | 2624 | } |
| 3270 | | else |
| 3271 | | progress(TRUE, "Input MD5 verified \n"); |
| 3272 | | } |
| 3273 | | |
| 3274 | | /* check the SHA1 */ |
| 3275 | | if (memcmp(source_header->sha1, empty_checksum, CHD_SHA1_BYTES) != 0) |
| 3276 | | { |
| 3277 | | if (memcmp(source_header->sha1, verify.sha1, CHD_SHA1_BYTES) != 0) |
| 2625 | |
| 2626 | // make sure we got all our required parameters |
| 2627 | for (int valid = 0; valid < ARRAY_LENGTH(desc.valid_options); valid++) |
| 3278 | 2628 | { |
| 3279 | | progress(TRUE, "WARNING: expected input SHA1 = "); |
| 3280 | | for (i = 0; i < CHD_SHA1_BYTES; i++) |
| 3281 | | progress(TRUE, "%02x", source_header->sha1[i]); |
| 3282 | | progress(TRUE, "\n"); |
| 3283 | | |
| 3284 | | progress(TRUE, " actual SHA1 = "); |
| 3285 | | for (i = 0; i < CHD_SHA1_BYTES; i++) |
| 3286 | | progress(TRUE, "%02x", verify.sha1[i]); |
| 3287 | | progress(TRUE, "\n"); |
| 2629 | const char *validname = desc.valid_options[valid]; |
| 2630 | if (validname == NULL) |
| 2631 | break; |
| 2632 | if (*validname == REQUIRED[0] && parameters.find(++validname) == NULL) |
| 2633 | return print_help(argv[0], desc, "Required parameters missing"); |
| 3288 | 2634 | } |
| 3289 | | else |
| 3290 | | progress(TRUE, "Input SHA1 verified \n"); |
| 3291 | | } |
| 3292 | | } |
| 3293 | | |
| 3294 | | /* finish compression */ |
| 3295 | | err = chd_compress_finish(chd, !(source_header->flags & CHDFLAGS_IS_WRITEABLE)); |
| 3296 | | if (err != CHDERR_NONE) |
| 3297 | | goto cleanup; |
| 3298 | | |
| 3299 | | /* final progress update */ |
| 3300 | | progress(TRUE, "Compression complete ... final ratio = %d%% \n", (int)(100.0 * ratio)); |
| 3301 | | |
| 3302 | | cleanup: |
| 3303 | | if (source_cache != NULL) |
| 3304 | | free(source_cache); |
| 3305 | | if (cache != NULL) |
| 3306 | | free(cache); |
| 3307 | | return err; |
| 3308 | | } |
| 3309 | | |
| 3310 | | |
| 3311 | | /*------------------------------------------------- |
| 3312 | | chdman_clone_metadata - clone the metadata from |
| 3313 | | one CHD to a second |
| 3314 | | -------------------------------------------------*/ |
| 3315 | | |
| 3316 | | static chd_error chdman_clone_metadata(chd_file *source, chd_file *dest) |
| 3317 | | { |
| 3318 | | const chd_header *header = chd_get_header(source); |
| 3319 | | UINT8 metabuffer[MAX(4096, sizeof(cdrom_toc))]; |
| 3320 | | UINT32 metatag, metasize, metaindex; |
| 3321 | | UINT8 metaflags; |
| 3322 | | chd_error err; |
| 3323 | | |
| 3324 | | /* clone the metadata */ |
| 3325 | | for (metaindex = 0; ; metaindex++) |
| 3326 | | { |
| 3327 | | /* fetch the next piece of metadata */ |
| 3328 | | err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, metabuffer, sizeof(metabuffer), &metasize, &metatag, &metaflags); |
| 3329 | | if (err != CHDERR_NONE) |
| 3330 | | { |
| 3331 | | if (err == CHDERR_METADATA_NOT_FOUND) |
| 3332 | | err = CHDERR_NONE; |
| 3333 | | break; |
| 3334 | | } |
| 3335 | | |
| 3336 | | /* promote certain bits of metadata to checksummed for older CHDs */ |
| 3337 | | if (header->version <= 3) |
| 3338 | | { |
| 3339 | | if (metatag == HARD_DISK_METADATA_TAG || metatag == CDROM_OLD_METADATA_TAG || |
| 3340 | | metatag == CDROM_TRACK_METADATA_TAG || metatag == AV_METADATA_TAG || |
| 3341 | | metatag == CDROM_TRACK_METADATA2_TAG || metatag == AV_LD_METADATA_TAG) |
| 2635 | |
| 2636 | // all clear, run the command |
| 2637 | try |
| 3342 | 2638 | { |
| 3343 | | metaflags |= CHD_MDFLAGS_CHECKSUM; |
| 2639 | (*s_commands[cmdnum].handler)(parameters); |
| 2640 | return 0; |
| 3344 | 2641 | } |
| 3345 | | |
| 3346 | | /* convert old-style CD-ROM data to newer */ |
| 3347 | | if (metatag == CDROM_OLD_METADATA_TAG) |
| 2642 | catch (fatal_error &err) |
| 3348 | 2643 | { |
| 3349 | | cdrom_toc *toc = (cdrom_toc *)metabuffer; |
| 3350 | | err = cdrom_parse_metadata(source, toc); |
| 3351 | | if (err == CHDERR_NONE) |
| 3352 | | err = cdrom_write_metadata(dest, toc); |
| 3353 | | if (err == CHDERR_NONE) |
| 3354 | | continue; |
| 2644 | return err.error(); |
| 3355 | 2645 | } |
| 3356 | | } |
| 3357 | | |
| 3358 | | /* if that fit, just write it back from the temporary buffer */ |
| 3359 | | if (metasize <= sizeof(metabuffer)) |
| 3360 | | { |
| 3361 | | /* write it to the target */ |
| 3362 | | err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, metabuffer, metasize, metaflags); |
| 3363 | | if (err != CHDERR_NONE) |
| 3364 | | break; |
| 3365 | | } |
| 3366 | | |
| 3367 | | /* otherwise, allocate a bigger temporary buffer */ |
| 3368 | | else |
| 3369 | | { |
| 3370 | | UINT8 *allocbuffer = (UINT8 *)malloc(metasize); |
| 3371 | | if (allocbuffer == NULL) |
| 2646 | catch (std::bad_alloc &) |
| 3372 | 2647 | { |
| 3373 | | err = CHDERR_OUT_OF_MEMORY; |
| 3374 | | break; |
| 2648 | fprintf(stderr, "Out of memory\n"); |
| 2649 | return 1; |
| 3375 | 2650 | } |
| 3376 | | |
| 3377 | | /* re-read the whole thing */ |
| 3378 | | err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, allocbuffer, metasize, &metasize, &metatag, &metaflags); |
| 3379 | | if (err != CHDERR_NONE) |
| 2651 | catch (...) |
| 3380 | 2652 | { |
| 3381 | | free(allocbuffer); |
| 3382 | | break; |
| 2653 | fprintf(stderr, "Unhandled exception\n"); |
| 2654 | return 1; |
| 3383 | 2655 | } |
| 3384 | | |
| 3385 | | /* write it to the target */ |
| 3386 | | err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, allocbuffer, metasize, metaflags); |
| 3387 | | free(allocbuffer); |
| 3388 | | if (err != CHDERR_NONE) |
| 3389 | | break; |
| 3390 | 2656 | } |
| 3391 | | } |
| 3392 | | return err; |
| 2657 | |
| 2658 | // print generic help if nothing found |
| 2659 | return print_help(argv[0]); |
| 3393 | 2660 | } |
| 3394 | | |
| 3395 | | |
| 3396 | | /*------------------------------------------------- |
| 3397 | | main - entry point |
| 3398 | | -------------------------------------------------*/ |
| 3399 | | |
| 3400 | | int CLIB_DECL main(int argc, char *argv[]) |
| 3401 | | { |
| 3402 | | static const struct |
| 3403 | | { |
| 3404 | | const char * option; |
| 3405 | | int (*callback)(int argc, char *argv[], int param); |
| 3406 | | int param; |
| 3407 | | } option_list[] = |
| 3408 | | { |
| 3409 | | { "-createhd", do_createhd, 0 }, |
| 3410 | | { "-createuncomphd", do_createhd_uncomp, 0 }, |
| 3411 | | { "-createraw", do_createraw, 0 }, |
| 3412 | | { "-createcd", do_createcd, 0 }, |
| 3413 | | // { "-createcdflac", do_createcd, 1 }, |
| 3414 | | { "-createblankhd", do_createblankhd, 0 }, |
| 3415 | | { "-createav", do_createav, 0 }, |
| 3416 | | { "-copydata", do_copydata, 0 }, |
| 3417 | | { "-extract", do_extract, 0 }, |
| 3418 | | { "-extractcd", do_extractcd, 0 }, |
| 3419 | | { "-extractav", do_extractav, 0 }, |
| 3420 | | { "-verify", do_verify, 0 }, |
| 3421 | | { "-verifyfix", do_verify, 1 }, |
| 3422 | | { "-update", do_merge_update_chomp, OPERATION_UPDATE }, |
| 3423 | | { "-chomp", do_merge_update_chomp, OPERATION_CHOMP }, |
| 3424 | | { "-info", do_info, 0 }, |
| 3425 | | { "-merge", do_merge_update_chomp, OPERATION_MERGE }, |
| 3426 | | { "-diff", do_diff, 0 }, |
| 3427 | | { "-setchs", do_setchs, 0 }, |
| 3428 | | { "-fixavdata", do_fixavdata, 0 }, |
| 3429 | | { "-addmetatext", do_addmeta, TRUE }, |
| 3430 | | { "-addmetabin", do_addmeta, FALSE }, |
| 3431 | | }; |
| 3432 | | extern const char build_version[]; |
| 3433 | | int i; |
| 3434 | | |
| 3435 | | /* print the header */ |
| 3436 | | printf("chdman - MAME Compressed Hunks of Data (CHD) manager %s\n", build_version); |
| 3437 | | |
| 3438 | | /* require at least 1 argument */ |
| 3439 | | if (argc < 2) |
| 3440 | | return usage(); |
| 3441 | | |
| 3442 | | /* handle the appropriate command */ |
| 3443 | | for (i = 0; i < ARRAY_LENGTH(option_list); i++) |
| 3444 | | if (strcmp(argv[1], option_list[i].option) == 0) |
| 3445 | | return (*option_list[i].callback)(argc, argv, option_list[i].param); |
| 3446 | | |
| 3447 | | return usage(); |
| 3448 | | } |
src/lib/util/chd.c
| r14481 | r14482 | |
| 38 | 38 | ***************************************************************************/ |
| 39 | 39 | |
| 40 | 40 | #include "chd.h" |
| 41 | | #include "avcomp.h" |
| 42 | | #include "md5.h" |
| 43 | | #include "sha1.h" |
| 41 | #include "avhuff.h" |
| 42 | #include "hashing.h" |
| 43 | #include "flac.h" |
| 44 | 44 | #include "cdrom.h" |
| 45 | #include "coretmpl.h" |
| 45 | 46 | #include <zlib.h> |
| 46 | 47 | #include <time.h> |
| 47 | 48 | #include <stddef.h> |
| 48 | 49 | #include <stdlib.h> |
| 49 | 50 | #include <new> |
| 50 | 51 | |
| 51 | | #include "../../lib/libflac/include/flac/all.h" |
| 52 | 52 | |
| 53 | //************************************************************************** |
| 54 | // CONSTANTS |
| 55 | //************************************************************************** |
| 53 | 56 | |
| 54 | | /*************************************************************************** |
| 55 | | DEBUGGING |
| 56 | | ***************************************************************************/ |
| 57 | // standard metadata formats |
| 58 | const char *HARD_DISK_METADATA_FORMAT = "CYLS:%d,HEADS:%d,SECS:%d,BPS:%d"; |
| 59 | const char *CDROM_TRACK_METADATA_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d"; |
| 60 | const char *CDROM_TRACK_METADATA2_FORMAT = "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d PREGAP:%d PGTYPE:%s PGSUB:%s POSTGAP:%d"; |
| 61 | const char *AV_METADATA_FORMAT = "FPS:%d.%06d WIDTH:%d HEIGHT:%d INTERLACED:%d CHANNELS:%d SAMPLERATE:%d"; |
| 57 | 62 | |
| 58 | | #define PRINTF_MAX_HUNK (0) |
| 63 | static const UINT32 METADATA_HEADER_SIZE = 16; // metadata header size |
| 59 | 64 | |
| 65 | static const UINT8 V34_MAP_ENTRY_FLAG_TYPE_MASK = 0x0f; // what type of hunk |
| 66 | static const UINT8 V34_MAP_ENTRY_FLAG_NO_CRC = 0x10; // no CRC is present |
| 60 | 67 | |
| 61 | 68 | |
| 62 | | /*************************************************************************** |
| 63 | | CONSTANTS |
| 64 | | ***************************************************************************/ |
| 65 | 69 | |
| 66 | | #define MAP_STACK_ENTRIES 512 /* max number of entries to use on the stack */ |
| 67 | | #define MAP_ENTRY_SIZE 16 /* V3 and later */ |
| 68 | | #define OLD_MAP_ENTRY_SIZE 8 /* V1-V2 */ |
| 69 | | #define METADATA_HEADER_SIZE 16 /* metadata header size */ |
| 70 | | #define CRCMAP_HASH_SIZE 4095 /* number of CRC hashtable entries */ |
| 71 | | |
| 72 | | #define MAP_ENTRY_FLAG_TYPE_MASK 0x0f /* what type of hunk */ |
| 73 | | #define MAP_ENTRY_FLAG_NO_CRC 0x10 /* no CRC is present */ |
| 74 | | #define MAP_ENTRY_FLAG_HALF_HUNK 0x20 /* only the first half of this hunk is included in the SHA1 calculation (workaround for CD track padding issue) */ |
| 75 | | |
| 76 | | #define MAP_ENTRY_TYPE_INVALID 0x00 /* invalid type */ |
| 77 | | #define MAP_ENTRY_TYPE_COMPRESSED 0x01 /* standard compression */ |
| 78 | | #define MAP_ENTRY_TYPE_UNCOMPRESSED 0x02 /* uncompressed data */ |
| 79 | | #define MAP_ENTRY_TYPE_MINI 0x03 /* mini: use offset as raw data */ |
| 80 | | #define MAP_ENTRY_TYPE_SELF_HUNK 0x04 /* same as another hunk in this file */ |
| 81 | | #define MAP_ENTRY_TYPE_PARENT_HUNK 0x05 /* same as a hunk in the parent file */ |
| 82 | | #define MAP_ENTRY_TYPE_2ND_COMPRESSED 0x06 /* compressed with secondary algorithm (usually FLAC CDDA) */ |
| 83 | | |
| 84 | | #define CHD_V1_SECTOR_SIZE 512 /* size of a "sector" in the V1 header */ |
| 85 | | |
| 86 | | #define COOKIE_VALUE 0xbaadf00d |
| 87 | | #define MAX_ZLIB_ALLOCS 64 |
| 88 | | |
| 89 | | #define END_OF_LIST_COOKIE "EndOfListCookie" |
| 90 | | |
| 91 | | #define NO_MATCH (~0) |
| 92 | | |
| 93 | | |
| 94 | | |
| 95 | | /*************************************************************************** |
| 96 | | MACROS |
| 97 | | ***************************************************************************/ |
| 98 | | |
| 99 | | #define EARLY_EXIT(x) do { (void)(x); goto cleanup; } while (0) |
| 100 | | |
| 101 | | |
| 102 | | |
| 103 | | /*************************************************************************** |
| 104 | | TYPE DEFINITIONS |
| 105 | | ***************************************************************************/ |
| 106 | | |
| 107 | | /* interface to a codec */ |
| 108 | | typedef struct _codec_interface codec_interface; |
| 109 | | struct _codec_interface |
| 70 | // V3-V4 entry types |
| 71 | enum |
| 110 | 72 | { |
| 111 | | UINT32 compression; /* type of compression */ |
| 112 | | const char *compname; /* name of the algorithm */ |
| 113 | | UINT8 lossy; /* is this a lossy algorithm? */ |
| 114 | | chd_error (*init)(chd_file *chd); /* codec initialize */ |
| 115 | | void (*free)(chd_file *chd); /* codec free */ |
| 116 | | chd_error (*compress)(chd_file *chd, const void *src, UINT32 *complen); /* compress data */ |
| 117 | | chd_error (*decompress)(chd_file *chd, UINT32 complen, void *dst); /* decompress data */ |
| 118 | | chd_error (*config)(chd_file *chd, int param, void *config); /* configure */ |
| 119 | | |
| 120 | | chd_error (*secondary_compress)(chd_file *chd, const void *src, UINT32 *complen); /* secondary compress data */ |
| 121 | | chd_error (*secondary_decompress)(chd_file *chd, UINT32 complen, void *dst); /* secondary decompress data */ |
| 122 | | |
| 73 | V34_MAP_ENTRY_TYPE_INVALID = 0, // invalid type |
| 74 | V34_MAP_ENTRY_TYPE_COMPRESSED = 1, // standard compression |
| 75 | V34_MAP_ENTRY_TYPE_UNCOMPRESSED = 2, // uncompressed data |
| 76 | V34_MAP_ENTRY_TYPE_MINI = 3, // mini: use offset as raw data |
| 77 | V34_MAP_ENTRY_TYPE_SELF_HUNK = 4, // same as another hunk in this file |
| 78 | V34_MAP_ENTRY_TYPE_PARENT_HUNK = 5, // same as a hunk in the parent file |
| 79 | V34_MAP_ENTRY_TYPE_2ND_COMPRESSED = 6 // compressed with secondary algorithm (usually FLAC CDDA) |
| 123 | 80 | }; |
| 124 | 81 | |
| 125 | | |
| 126 | | /* a single map entry */ |
| 127 | | typedef struct _map_entry map_entry; |
| 128 | | struct _map_entry |
| 82 | // V5 compression types |
| 83 | enum |
| 129 | 84 | { |
| 130 | | UINT64 offset; /* offset within the file of the data */ |
| 131 | | UINT32 crc; /* 32-bit CRC of the data */ |
| 132 | | UINT32 length; /* length of the data */ |
| 133 | | UINT8 flags; /* misc flags */ |
| 85 | // these types are live when running |
| 86 | COMPRESSION_TYPE_0 = 0, // codec #0 |
| 87 | COMPRESSION_TYPE_1 = 1, // codec #1 |
| 88 | COMPRESSION_TYPE_2 = 2, // codec #2 |
| 89 | COMPRESSION_TYPE_3 = 3, // codec #3 |
| 90 | COMPRESSION_NONE = 4, // no compression; implicit length = hunkbytes |
| 91 | COMPRESSION_SELF = 5, // same as another block in this chd |
| 92 | COMPRESSION_PARENT = 6, // same as a hunk's worth of units in the parent chd |
| 93 | |
| 94 | // these additional pseudo-types are used for compressed encodings: |
| 95 | COMPRESSION_RLE_SMALL, // start of small RLE run (4-bit length) |
| 96 | COMPRESSION_RLE_LARGE, // start of large RLE run (8-bit length) |
| 97 | COMPRESSION_SELF_0, // same as the last COMPRESSION_SELF block |
| 98 | COMPRESSION_SELF_1, // same as the last COMPRESSION_SELF block + 1 |
| 99 | COMPRESSION_PARENT_SELF, // same block in the parent |
| 100 | COMPRESSION_PARENT_0, // same as the last COMPRESSION_PARENT block |
| 101 | COMPRESSION_PARENT_1 // same as the last COMPRESSION_PARENT block + 1 |
| 134 | 102 | }; |
| 135 | 103 | |
| 136 | 104 | |
| 137 | | /* simple linked-list of hunks used for our CRC map */ |
| 138 | | typedef struct _crcmap_entry crcmap_entry; |
| 139 | | struct _crcmap_entry |
| 140 | | { |
| 141 | | UINT32 hunknum; /* hunk number */ |
| 142 | | crcmap_entry * next; /* next entry in list */ |
| 143 | | }; |
| 144 | 105 | |
| 106 | //************************************************************************** |
| 107 | // TYPE DEFINITIONS |
| 108 | //************************************************************************** |
| 145 | 109 | |
| 146 | | /* a single metadata entry */ |
| 147 | | typedef struct _metadata_entry metadata_entry; |
| 148 | | struct _metadata_entry |
| 149 | | { |
| 150 | | UINT64 offset; /* offset within the file of the header */ |
| 151 | | UINT64 next; /* offset within the file of the next header */ |
| 152 | | UINT64 prev; /* offset within the file of the previous header */ |
| 153 | | UINT32 length; /* length of the metadata */ |
| 154 | | UINT32 metatag; /* metadata tag */ |
| 155 | | UINT8 flags; /* flag bits */ |
| 156 | | }; |
| 110 | // ======================> metadata_entry |
| 157 | 111 | |
| 158 | | |
| 159 | | /* internal representation of an open CHD file */ |
| 160 | | struct _chd_file |
| 112 | // description of where a metadata entry lives within the file |
| 113 | struct chd_file::metadata_entry |
| 161 | 114 | { |
| 162 | | UINT32 cookie; /* cookie, should equal COOKIE_VALUE */ |
| 163 | | |
| 164 | | core_file * file; /* handle to the open core file */ |
| 165 | | UINT8 owns_file; /* flag indicating if this file should be closed on chd_close() */ |
| 166 | | chd_header header; /* header, extracted from file */ |
| 167 | | |
| 168 | | chd_file * parent; /* pointer to parent file, or NULL */ |
| 169 | | |
| 170 | | map_entry * map; /* array of map entries */ |
| 171 | | |
| 172 | | UINT8 * cache; /* hunk cache pointer */ |
| 173 | | UINT32 cachehunk; /* index of currently cached hunk */ |
| 174 | | |
| 175 | | UINT8 * compare; /* hunk compare pointer */ |
| 176 | | UINT32 comparehunk; /* index of current compare data */ |
| 177 | | |
| 178 | | UINT8 * compressed; /* pointer to buffer for compressed data */ |
| 179 | | const codec_interface * codecintf; /* interface to the codec */ |
| 180 | | void * codecdata; /* opaque pointer to codec data */ |
| 181 | | |
| 182 | | crcmap_entry * crcmap; /* CRC map entries */ |
| 183 | | crcmap_entry * crcfree; /* free list CRC entries */ |
| 184 | | crcmap_entry ** crctable; /* table of CRC entries */ |
| 185 | | |
| 186 | | UINT32 maxhunk; /* maximum hunk accessed */ |
| 187 | | |
| 188 | | UINT8 compressing; /* are we compressing? */ |
| 189 | | struct MD5Context compmd5; /* running MD5 during compression */ |
| 190 | | struct sha1_ctx compsha1; /* running SHA1 during compression */ |
| 191 | | UINT32 comphunk; /* next hunk we will compress */ |
| 192 | | |
| 193 | | UINT8 verifying; /* are we verifying? */ |
| 194 | | struct MD5Context vermd5; /* running MD5 during verification */ |
| 195 | | struct sha1_ctx versha1; /* running SHA1 during verification */ |
| 196 | | UINT32 verhunk; /* next hunk we will verify */ |
| 197 | | |
| 198 | | osd_work_queue * workqueue; /* pointer to work queue for async operations */ |
| 199 | | osd_work_item * workitem; /* active work item, or NULL if none */ |
| 200 | | UINT32 async_hunknum; /* hunk index for asynchronous operations */ |
| 201 | | void * async_buffer; /* buffer pointer for asynchronous operations */ |
| 115 | UINT64 offset; // offset within the file of the header |
| 116 | UINT64 next; // offset within the file of the next header |
| 117 | UINT64 prev; // offset within the file of the previous header |
| 118 | UINT32 length; // length of the metadata |
| 119 | UINT32 metatag; // metadata tag |
| 120 | UINT8 flags; // flag bits |
| 202 | 121 | }; |
| 203 | 122 | |
| 204 | 123 | |
| 205 | | /* codec-private data for the ZLIB codec */ |
| 206 | | typedef struct _zlib_codec_data zlib_codec_data; |
| 207 | | struct _zlib_codec_data |
| 208 | | { |
| 209 | | z_stream inflater; |
| 210 | | z_stream deflater; |
| 211 | | UINT32 * allocptr[MAX_ZLIB_ALLOCS]; |
| 212 | | }; |
| 124 | // ======================> metadata_hash |
| 213 | 125 | |
| 214 | | |
| 215 | | /* codec-private data for the A/V codec */ |
| 216 | | struct av_codec_data |
| 126 | struct chd_file::metadata_hash |
| 217 | 127 | { |
| 218 | | avcomp_state * compstate; |
| 219 | | av_codec_compress_config compress; |
| 220 | | av_codec_decompress_config decompress; |
| 128 | UINT8 tag[4]; // tag of the metadata in big-endian |
| 129 | sha1_t sha1; // hash data |
| 221 | 130 | }; |
| 222 | 131 | |
| 223 | 132 | |
| 224 | | /* a single metadata hash entry */ |
| 225 | | typedef struct _metadata_hash metadata_hash; |
| 226 | | struct _metadata_hash |
| 227 | | { |
| 228 | | UINT8 tag[4]; /* tag of the metadata in big-endian */ |
| 229 | | UINT8 sha1[CHD_SHA1_BYTES]; /* hash */ |
| 230 | | }; |
| 231 | 133 | |
| 134 | //************************************************************************** |
| 135 | // INLINE FUNCTIONS |
| 136 | //************************************************************************** |
| 232 | 137 | |
| 138 | //------------------------------------------------- |
| 139 | // be_read - extract a big-endian number from |
| 140 | // a byte buffer |
| 141 | //------------------------------------------------- |
| 233 | 142 | |
| 234 | | /*************************************************************************** |
| 235 | | GLOBAL VARIABLES |
| 236 | | ***************************************************************************/ |
| 143 | inline UINT64 chd_file::be_read(const UINT8 *base, int numbytes) |
| 144 | { |
| 145 | UINT64 result = 0; |
| 146 | while (numbytes--) |
| 147 | result = (result << 8) | *base++; |
| 148 | return result; |
| 149 | } |
| 237 | 150 | |
| 238 | | static const UINT8 nullmd5[CHD_MD5_BYTES] = { 0 }; |
| 239 | | static const UINT8 nullsha1[CHD_SHA1_BYTES] = { 0 }; |
| 240 | 151 | |
| 152 | //------------------------------------------------- |
| 153 | // be_write - write a big-endian number to a byte |
| 154 | // buffer |
| 155 | //------------------------------------------------- |
| 241 | 156 | |
| 242 | | |
| 243 | | /*************************************************************************** |
| 244 | | PROTOTYPES |
| 245 | | ***************************************************************************/ |
| 246 | | |
| 247 | | /* internal async operations */ |
| 248 | | static void *async_read_callback(void *param, int threadid); |
| 249 | | static void *async_write_callback(void *param, int threadid); |
| 250 | | |
| 251 | | /* internal header operations */ |
| 252 | | static chd_error header_validate(const chd_header *header); |
| 253 | | static chd_error header_read(core_file *file, chd_header *header); |
| 254 | | static chd_error header_write(core_file *file, const chd_header *header); |
| 255 | | |
| 256 | | /* internal hunk read/write */ |
| 257 | | static chd_error hunk_read_into_cache(chd_file *chd, UINT32 hunknum); |
| 258 | | static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *dest); |
| 259 | | static chd_error hunk_write_from_memory(chd_file *chd, UINT32 hunknum, const UINT8 *src, int is_half_hunk = 0); |
| 260 | | |
| 261 | | /* internal map access */ |
| 262 | | static chd_error map_write_initial(core_file *file, chd_file *parent, const chd_header *header); |
| 263 | | static chd_error map_read(chd_file *chd); |
| 264 | | |
| 265 | | /* internal CRC map access */ |
| 266 | | static void crcmap_init(chd_file *chd, int prepopulate); |
| 267 | | static void crcmap_add_entry(chd_file *chd, UINT32 hunknum); |
| 268 | | static UINT32 crcmap_find_hunk(chd_file *chd, UINT32 hunknum, UINT32 crc, const UINT8 *rawdata); |
| 269 | | |
| 270 | | /* metadata management */ |
| 271 | | static chd_error metadata_find_entry(chd_file *chd, UINT32 metatag, UINT32 metaindex, metadata_entry *metaentry); |
| 272 | | static chd_error metadata_set_previous_next(chd_file *chd, UINT64 prevoffset, UINT64 nextoffset); |
| 273 | | static chd_error metadata_set_length(chd_file *chd, UINT64 offset, UINT32 length); |
| 274 | | static chd_error metadata_compute_hash(chd_file *chd, const UINT8 *rawsha1, UINT8 *finalsha1); |
| 275 | | static int CLIB_DECL metadata_hash_compare(const void *elem1, const void *elem2); |
| 276 | | |
| 277 | | /* zlib compression codec */ |
| 278 | | static chd_error zlib_codec_init(chd_file *chd); |
| 279 | | static void zlib_codec_free(chd_file *chd); |
| 280 | | static chd_error zlib_codec_compress(chd_file *chd, const void *src, UINT32 *length); |
| 281 | | static chd_error zlib_codec_decompress(chd_file *chd, UINT32 srclength, void *dest); |
| 282 | | static voidpf zlib_fast_alloc(voidpf opaque, uInt items, uInt size); |
| 283 | | static void zlib_fast_free(voidpf opaque, voidpf address); |
| 284 | | |
| 285 | | /* flac compression codec */ |
| 286 | | static chd_error flac_codec_compress(chd_file *chd, const void *src, UINT32 *length, int swap); |
| 287 | | static chd_error flac_codec_compress_normal(chd_file *chd, const void *src, UINT32 *length); |
| 288 | | static chd_error flac_codec_decompress(chd_file *chd, UINT32 srclength, void *dest); |
| 289 | | |
| 290 | | |
| 291 | | /* A/V compression codec */ |
| 292 | | static chd_error av_codec_init(chd_file *chd); |
| 293 | | static void av_codec_free(chd_file *chd); |
| 294 | | static chd_error av_codec_compress(chd_file *chd, const void *src, UINT32 *length); |
| 295 | | static chd_error av_codec_decompress(chd_file *chd, UINT32 srclength, void *dest); |
| 296 | | static chd_error av_codec_config(chd_file *chd, int param, void *config); |
| 297 | | static chd_error av_codec_postinit(chd_file *chd); |
| 298 | | |
| 299 | | |
| 300 | | |
| 301 | | /*************************************************************************** |
| 302 | | CODEC INTERFACES |
| 303 | | ***************************************************************************/ |
| 304 | | |
| 305 | | static const codec_interface codec_interfaces[] = |
| 157 | inline void chd_file::be_write(UINT8 *base, UINT64 value, int numbytes) |
| 306 | 158 | { |
| 307 | | /* "none" or no compression */ |
| 159 | base += numbytes; |
| 160 | while (numbytes--) |
| 308 | 161 | { |
| 309 | | CHDCOMPRESSION_NONE, |
| 310 | | "none", |
| 311 | | FALSE, |
| 312 | | NULL, |
| 313 | | NULL, |
| 314 | | NULL, |
| 315 | | NULL, |
| 316 | | NULL, |
| 317 | | NULL, |
| 318 | | NULL |
| 319 | | }, |
| 162 | *--base = value; |
| 163 | value >>= 8; |
| 164 | } |
| 165 | } |
| 320 | 166 | |
| 321 | | /* standard zlib compression */ |
| 322 | | { |
| 323 | | CHDCOMPRESSION_ZLIB, |
| 324 | | "zlib", |
| 325 | | FALSE, |
| 326 | | zlib_codec_init, |
| 327 | | zlib_codec_free, |
| 328 | | zlib_codec_compress, |
| 329 | | zlib_codec_decompress, |
| 330 | | NULL, |
| 331 | | NULL, |
| 332 | | NULL |
| 333 | | }, |
| 334 | 167 | |
| 335 | | /* zlib+ compression */ |
| 336 | | { |
| 337 | | CHDCOMPRESSION_ZLIB_PLUS, |
| 338 | | "zlib+", |
| 339 | | FALSE, |
| 340 | | zlib_codec_init, |
| 341 | | zlib_codec_free, |
| 342 | | zlib_codec_compress, |
| 343 | | zlib_codec_decompress, |
| 344 | | NULL, |
| 345 | | NULL, |
| 346 | | NULL |
| 347 | | }, |
| 168 | //------------------------------------------------- |
| 169 | // be_read_sha1 - fetch a sha1_t from a data |
| 170 | // stream in bigendian order |
| 171 | //------------------------------------------------- |
| 348 | 172 | |
| 349 | | /* a/v compression */ |
| 350 | | { |
| 351 | | CHDCOMPRESSION_AV, |
| 352 | | "A/V", |
| 353 | | TRUE, |
| 354 | | av_codec_init, |
| 355 | | av_codec_free, |
| 356 | | av_codec_compress, |
| 357 | | av_codec_decompress, |
| 358 | | av_codec_config, |
| 359 | | NULL, |
| 360 | | NULL |
| 361 | | }, |
| 362 | | |
| 363 | | /* zlib+ with FLAC compression */ |
| 364 | | { |
| 365 | | CHDCOMPRESSION_ZLIB_PLUS_WITH_FLAC, |
| 366 | | "zlib+ with FLAC", |
| 367 | | FALSE, |
| 368 | | zlib_codec_init, |
| 369 | | zlib_codec_free, |
| 370 | | zlib_codec_compress, |
| 371 | | zlib_codec_decompress, |
| 372 | | NULL, |
| 373 | | flac_codec_compress_normal, |
| 374 | | flac_codec_decompress, |
| 375 | | }, |
| 376 | | }; |
| 377 | | |
| 378 | | |
| 379 | | |
| 380 | | /*************************************************************************** |
| 381 | | INLINE FUNCTIONS |
| 382 | | ***************************************************************************/ |
| 383 | | |
| 384 | | /*------------------------------------------------- |
| 385 | | get_bigendian_uint64 - fetch a UINT64 from |
| 386 | | the data stream in bigendian order |
| 387 | | -------------------------------------------------*/ |
| 388 | | |
| 389 | | INLINE UINT64 get_bigendian_uint64(const UINT8 *base) |
| 173 | inline sha1_t chd_file::be_read_sha1(const UINT8 *base) |
| 390 | 174 | { |
| 391 | | return ((UINT64)base[0] << 56) | ((UINT64)base[1] << 48) | ((UINT64)base[2] << 40) | ((UINT64)base[3] << 32) | |
| 392 | | ((UINT64)base[4] << 24) | ((UINT64)base[5] << 16) | ((UINT64)base[6] << 8) | (UINT64)base[7]; |
| 175 | sha1_t result; |
| 176 | memcpy(&result.m_raw[0], base, sizeof(result.m_raw)); |
| 177 | return result; |
| 393 | 178 | } |
| 394 | 179 | |
| 395 | 180 | |
| 396 | | /*------------------------------------------------- |
| 397 | | put_bigendian_uint64 - write a UINT64 to |
| 398 | | the data stream in bigendian order |
| 399 | | -------------------------------------------------*/ |
| 181 | //------------------------------------------------- |
| 182 | // be_write_sha1 - write a sha1_t to a data |
| 183 | // stream in bigendian order |
| 184 | //------------------------------------------------- |
| 400 | 185 | |
| 401 | | INLINE void put_bigendian_uint64(UINT8 *base, UINT64 value) |
| 186 | inline void chd_file::be_write_sha1(UINT8 *base, sha1_t value) |
| 402 | 187 | { |
| 403 | | base[0] = value >> 56; |
| 404 | | base[1] = value >> 48; |
| 405 | | base[2] = value >> 40; |
| 406 | | base[3] = value >> 32; |
| 407 | | base[4] = value >> 24; |
| 408 | | base[5] = value >> 16; |
| 409 | | base[6] = value >> 8; |
| 410 | | base[7] = value; |
| 188 | memcpy(base, &value.m_raw[0], sizeof(value.m_raw)); |
| 411 | 189 | } |
| 412 | 190 | |
| 413 | 191 | |
| 414 | | /*------------------------------------------------- |
| 415 | | get_bigendian_uint32 - fetch a UINT32 from |
| 416 | | the data stream in bigendian order |
| 417 | | -------------------------------------------------*/ |
| 192 | //------------------------------------------------- |
| 193 | // file_read - read from the file at the given |
| 194 | // offset; on failure throw an error |
| 195 | //------------------------------------------------- |
| 418 | 196 | |
| 419 | | INLINE UINT32 get_bigendian_uint32(const UINT8 *base) |
| 197 | inline void chd_file::file_read(UINT64 offset, void *dest, UINT32 length) |
| 420 | 198 | { |
| 421 | | return (base[0] << 24) | (base[1] << 16) | (base[2] << 8) | base[3]; |
| 199 | // no file = failure |
| 200 | if (m_file == NULL) |
| 201 | throw CHDERR_NOT_OPEN; |
| 202 | |
| 203 | // seek and read |
| 204 | core_fseek(m_file, offset, SEEK_SET); |
| 205 | UINT32 count = core_fread(m_file, dest, length); |
| 206 | if (count != length) |
| 207 | throw CHDERR_READ_ERROR; |
| 422 | 208 | } |
| 423 | 209 | |
| 424 | 210 | |
| 425 | | /*------------------------------------------------- |
| 426 | | put_bigendian_uint32 - write a UINT32 to |
| 427 | | the data stream in bigendian order |
| 428 | | -------------------------------------------------*/ |
| 211 | //------------------------------------------------- |
| 212 | // file_write - write to the file at the given |
| 213 | // offset; on failure throw an error |
| 214 | //------------------------------------------------- |
| 429 | 215 | |
| 430 | | INLINE void put_bigendian_uint32(UINT8 *base, UINT32 value) |
| 216 | inline void chd_file::file_write(UINT64 offset, const void *source, UINT32 length) |
| 431 | 217 | { |
| 432 | | base[0] = value >> 24; |
| 433 | | base[1] = value >> 16; |
| 434 | | base[2] = value >> 8; |
| 435 | | base[3] = value; |
| 218 | // no file = failure |
| 219 | if (m_file == NULL) |
| 220 | throw CHDERR_NOT_OPEN; |
| 221 | |
| 222 | // seek and write |
| 223 | core_fseek(m_file, offset, SEEK_SET); |
| 224 | UINT32 count = core_fwrite(m_file, source, length); |
| 225 | if (count != length) |
| 226 | throw CHDERR_WRITE_ERROR; |
| 436 | 227 | } |
| 437 | 228 | |
| 438 | 229 | |
| 439 | | /*------------------------------------------------- |
| 440 | | get_bigendian_uint16 - fetch a UINT16 from |
| 441 | | the data stream in bigendian order |
| 442 | | -------------------------------------------------*/ |
| 230 | //------------------------------------------------- |
| 231 | // file_append - append to the file at the given |
| 232 | // offset, ensuring we start at the given |
| 233 | // alignment; on failure throw an error |
| 234 | //------------------------------------------------- |
| 443 | 235 | |
| 444 | | INLINE UINT16 get_bigendian_uint16(const UINT8 *base) |
| 236 | inline UINT64 chd_file::file_append(const void *source, UINT32 length, UINT32 alignment) |
| 445 | 237 | { |
| 446 | | return (base[0] << 8) | base[1]; |
| 447 | | } |
| 238 | // no file = failure |
| 239 | if (m_file == NULL) |
| 240 | throw CHDERR_NOT_OPEN; |
| 448 | 241 | |
| 242 | // seek to the end and align if necessary |
| 243 | core_fseek(m_file, 0, SEEK_END); |
| 244 | if (alignment != 0) |
| 245 | { |
| 246 | UINT64 offset = core_ftell(m_file); |
| 247 | UINT32 delta = offset % alignment; |
| 248 | if (delta != 0) |
| 249 | { |
| 250 | // pad with 0's from a local buffer |
| 251 | UINT8 buffer[1024]; |
| 252 | memset(buffer, 0, sizeof(buffer)); |
| 253 | delta = alignment - delta; |
| 254 | while (delta != 0) |
| 255 | { |
| 256 | UINT32 bytes_to_write = MIN(sizeof(buffer), delta); |
| 257 | UINT32 count = core_fwrite(m_file, buffer, bytes_to_write); |
| 258 | if (count != length) |
| 259 | throw CHDERR_WRITE_ERROR; |
| 260 | delta -= bytes_to_write; |
| 261 | } |
| 262 | } |
| 263 | } |
| 449 | 264 | |
| 450 | | /*------------------------------------------------- |
| 451 | | put_bigendian_uint16 - write a UINT16 to |
| 452 | | the data stream in bigendian order |
| 453 | | -------------------------------------------------*/ |
| 454 | | |
| 455 | | INLINE void put_bigendian_uint16(UINT8 *base, UINT16 value) |
| 456 | | { |
| 457 | | base[0] = value >> 8; |
| 458 | | base[1] = value; |
| 265 | // write the real data |
| 266 | UINT64 offset = core_ftell(m_file); |
| 267 | UINT32 count = core_fwrite(m_file, source, length); |
| 268 | if (count != length) |
| 269 | throw CHDERR_READ_ERROR; |
| 270 | return offset; |
| 459 | 271 | } |
| 460 | 272 | |
| 461 | 273 | |
| 462 | | /*------------------------------------------------- |
| 463 | | map_extract - extract a single map |
| 464 | | entry from the datastream |
| 465 | | -------------------------------------------------*/ |
| 274 | //------------------------------------------------- |
| 275 | // bits_for_value - return the number of bits |
| 276 | // necessary to represent all numbers 0..value |
| 277 | //------------------------------------------------- |
| 466 | 278 | |
| 467 | | INLINE void map_extract(const UINT8 *base, map_entry *entry) |
| 279 | inline UINT8 chd_file::bits_for_value(UINT64 value) |
| 468 | 280 | { |
| 469 | | entry->offset = get_bigendian_uint64(&base[0]); |
| 470 | | entry->crc = get_bigendian_uint32(&base[8]); |
| 471 | | entry->length = get_bigendian_uint16(&base[12]) | (base[14] << 16); |
| 472 | | entry->flags = base[15]; |
| 281 | UINT8 result = 0; |
| 282 | while (value != 0) |
| 283 | value >>= 1, result++; |
| 284 | return result; |
| 473 | 285 | } |
| 474 | 286 | |
| 475 | 287 | |
| 476 | | /*------------------------------------------------- |
| 477 | | map_assemble - write a single map |
| 478 | | entry to the datastream |
| 479 | | -------------------------------------------------*/ |
| 480 | 288 | |
| 481 | | INLINE void map_assemble(UINT8 *base, map_entry *entry) |
| 482 | | { |
| 483 | | put_bigendian_uint64(&base[0], entry->offset); |
| 484 | | put_bigendian_uint32(&base[8], entry->crc); |
| 485 | | put_bigendian_uint16(&base[12], entry->length); |
| 486 | | base[14] = entry->length >> 16; |
| 487 | | base[15] = entry->flags; |
| 488 | | } |
| 289 | //************************************************************************** |
| 290 | // CHD FILE MANAGEMENT |
| 291 | //************************************************************************** |
| 489 | 292 | |
| 293 | //------------------------------------------------- |
| 294 | // chd_file - constructor |
| 295 | //------------------------------------------------- |
| 490 | 296 | |
| 491 | | /*------------------------------------------------- |
| 492 | | map_extract_old - extract a single map |
| 493 | | entry in old format from the datastream |
| 494 | | -------------------------------------------------*/ |
| 495 | | |
| 496 | | INLINE void map_extract_old(const UINT8 *base, map_entry *entry, UINT32 hunkbytes) |
| 297 | chd_file::chd_file() |
| 298 | : m_file(NULL) |
| 497 | 299 | { |
| 498 | | entry->offset = get_bigendian_uint64(&base[0]); |
| 499 | | entry->crc = 0; |
| 500 | | entry->length = entry->offset >> 44; |
| 501 | | entry->flags = MAP_ENTRY_FLAG_NO_CRC | ((entry->length == hunkbytes) ? MAP_ENTRY_TYPE_UNCOMPRESSED : MAP_ENTRY_TYPE_COMPRESSED); |
| 502 | | #ifdef __MWERKS__ |
| 503 | | entry->offset = entry->offset & 0x00000FFFFFFFFFFFLL; |
| 504 | | #else |
| 505 | | entry->offset = (entry->offset << 20) >> 20; |
| 506 | | #endif |
| 300 | // reset state |
| 301 | memset(m_decompressor, 0, sizeof(m_decompressor)); |
| 302 | close(); |
| 507 | 303 | } |
| 508 | 304 | |
| 509 | 305 | |
| 510 | | /*------------------------------------------------- |
| 511 | | queue_async_operation - queue a new work |
| 512 | | item |
| 513 | | -------------------------------------------------*/ |
| 306 | //------------------------------------------------- |
| 307 | // ~chd_file - destructor |
| 308 | //------------------------------------------------- |
| 514 | 309 | |
| 515 | | INLINE int queue_async_operation(chd_file *chd, osd_work_callback callback) |
| 310 | chd_file::~chd_file() |
| 516 | 311 | { |
| 517 | | /* if no queue yet, create one on the fly */ |
| 518 | | if (chd->workqueue == NULL) |
| 519 | | { |
| 520 | | chd->workqueue = osd_work_queue_alloc(WORK_QUEUE_FLAG_IO); |
| 521 | | if (chd->workqueue == NULL) |
| 522 | | return FALSE; |
| 523 | | } |
| 524 | | |
| 525 | | /* make sure we cleared out the previous item */ |
| 526 | | if (chd->workitem != NULL) |
| 527 | | return FALSE; |
| 528 | | |
| 529 | | /* create a new work item to run the job */ |
| 530 | | chd->workitem = osd_work_item_queue(chd->workqueue, callback, chd, 0); |
| 531 | | if (chd->workitem == NULL) |
| 532 | | return FALSE; |
| 533 | | |
| 534 | | return TRUE; |
| 312 | // close any open files |
| 313 | close(); |
| 535 | 314 | } |
| 536 | 315 | |
| 537 | 316 | |
| 538 | | /*------------------------------------------------- |
| 539 | | wait_for_pending_async - wait for any pending |
| 540 | | async |
| 541 | | -------------------------------------------------*/ |
| 317 | //------------------------------------------------- |
| 318 | // sha1 - return our SHA1 value |
| 319 | //------------------------------------------------- |
| 542 | 320 | |
| 543 | | INLINE void wait_for_pending_async(chd_file *chd) |
| 321 | sha1_t chd_file::sha1() |
| 544 | 322 | { |
| 545 | | /* if something is pending, wait for it */ |
| 546 | | if (chd->workitem != NULL) |
| 323 | try |
| 547 | 324 | { |
| 548 | | /* 10 seconds should be enough for anything! */ |
| 549 | | int wait_successful = osd_work_item_wait(chd->workitem, 10 * osd_ticks_per_second()); |
| 550 | | if (!wait_successful) |
| 551 | | osd_break_into_debugger("Pending async operation never completed!"); |
| 325 | // read the big-endian version |
| 326 | UINT8 rawbuf[sizeof(sha1_t)]; |
| 327 | file_read(m_sha1_offset, rawbuf, sizeof(rawbuf)); |
| 328 | return be_read_sha1(rawbuf); |
| 552 | 329 | } |
| 330 | catch (chd_error &) |
| 331 | { |
| 332 | // on failure, return NULL |
| 333 | return sha1_t::null; |
| 334 | } |
| 553 | 335 | } |
| 554 | 336 | |
| 555 | 337 | |
| 338 | //------------------------------------------------- |
| 339 | // raw_sha1 - return our raw SHA1 value |
| 340 | //------------------------------------------------- |
| 556 | 341 | |
| 557 | | /*************************************************************************** |
| 558 | | CHD FILE MANAGEMENT |
| 559 | | ***************************************************************************/ |
| 560 | | |
| 561 | | /*------------------------------------------------- |
| 562 | | chd_create_file - create a new CHD file |
| 563 | | -------------------------------------------------*/ |
| 564 | | |
| 565 | | chd_error chd_create_file(core_file *file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent) |
| 342 | sha1_t chd_file::raw_sha1() |
| 566 | 343 | { |
| 567 | | chd_file *newchd = NULL; |
| 568 | | chd_header header; |
| 569 | | chd_error err; |
| 570 | | int intfnum; |
| 571 | | |
| 572 | | /* verify parameters */ |
| 573 | | if (file == NULL) |
| 574 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 575 | | if (parent == NULL && (logicalbytes == 0 || hunkbytes == 0)) |
| 576 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 577 | | |
| 578 | | /* verify the compression type */ |
| 579 | | for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) |
| 580 | | if (codec_interfaces[intfnum].compression == compression) |
| 581 | | break; |
| 582 | | if (intfnum == ARRAY_LENGTH(codec_interfaces)) |
| 583 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 584 | | |
| 585 | | /* if we have a parent, the sizes come from there */ |
| 586 | | if (parent != NULL) |
| 344 | try |
| 587 | 345 | { |
| 588 | | logicalbytes = parent->header.logicalbytes; |
| 589 | | hunkbytes = parent->header.hunkbytes; |
| 346 | // determine offset within the file for data-only |
| 347 | if (m_rawsha1_offset == 0) |
| 348 | throw CHDERR_UNSUPPORTED_VERSION; |
| 349 | |
| 350 | // read the big-endian version |
| 351 | UINT8 rawbuf[sizeof(sha1_t)]; |
| 352 | file_read(m_rawsha1_offset, rawbuf, sizeof(rawbuf)); |
| 353 | return be_read_sha1(rawbuf); |
| 590 | 354 | } |
| 591 | | |
| 592 | | /* if we have a parent, it must be V3 or later */ |
| 593 | | if (parent != NULL && parent->header.version < 3) |
| 594 | | EARLY_EXIT(err = CHDERR_UNSUPPORTED_VERSION); |
| 595 | | |
| 596 | | /* build the header */ |
| 597 | | memset(&header, 0, sizeof(header)); |
| 598 | | header.length = CHD_V4_HEADER_SIZE; |
| 599 | | header.version = CHD_HEADER_VERSION; |
| 600 | | header.flags = CHDFLAGS_IS_WRITEABLE; |
| 601 | | header.compression = compression; |
| 602 | | header.hunkbytes = hunkbytes; |
| 603 | | header.totalhunks = (logicalbytes + hunkbytes - 1) / hunkbytes; |
| 604 | | header.logicalbytes = logicalbytes; |
| 605 | | |
| 606 | | /* tweaks if there is a parent */ |
| 607 | | if (parent != NULL) |
| 355 | catch (chd_error &) |
| 608 | 356 | { |
| 609 | | header.flags |= CHDFLAGS_HAS_PARENT; |
| 610 | | memcpy(&header.parentmd5[0], &parent->header.md5[0], sizeof(header.parentmd5)); |
| 611 | | memcpy(&header.parentsha1[0], &parent->header.sha1[0], sizeof(header.parentsha1)); |
| 357 | // on failure, return NULL |
| 358 | return sha1_t::null; |
| 612 | 359 | } |
| 360 | } |
| 613 | 361 | |
| 614 | | /* validate it */ |
| 615 | | err = header_validate(&header); |
| 616 | | if (err != CHDERR_NONE) |
| 617 | | EARLY_EXIT(err); |
| 618 | 362 | |
| 619 | | /* write the resulting header */ |
| 620 | | err = header_write(file, &header); |
| 621 | | if (err != CHDERR_NONE) |
| 622 | | EARLY_EXIT(err); |
| 363 | //------------------------------------------------- |
| 364 | // parent_sha1 - return our parent's SHA1 value |
| 365 | //------------------------------------------------- |
| 623 | 366 | |
| 624 | | /* create an initial map */ |
| 625 | | err = map_write_initial(file, parent, &header); |
| 626 | | if (err != CHDERR_NONE) |
| 627 | | EARLY_EXIT(err); |
| 628 | | |
| 629 | | /* if we have a parent, clone the metadata */ |
| 630 | | if (parent != NULL) |
| 367 | sha1_t chd_file::parent_sha1() |
| 368 | { |
| 369 | try |
| 631 | 370 | { |
| 632 | | /* open the new CHD via the standard mechanism */ |
| 633 | | err = chd_open_file(file, CHD_OPEN_READWRITE, parent, &newchd); |
| 634 | | if (err != CHDERR_NONE) |
| 635 | | EARLY_EXIT(err); |
| 636 | | |
| 637 | | /* close the metadata */ |
| 638 | | err = chd_clone_metadata(parent, newchd); |
| 639 | | if (err != CHDERR_NONE) |
| 640 | | EARLY_EXIT(err); |
| 641 | | |
| 642 | | /* close the CHD */ |
| 643 | | chd_close(newchd); |
| 371 | // determine offset within the file |
| 372 | if (m_parentsha1_offset == 0) |
| 373 | throw CHDERR_UNSUPPORTED_VERSION; |
| 374 | |
| 375 | // read the big-endian version |
| 376 | UINT8 rawbuf[sizeof(sha1_t)]; |
| 377 | file_read(m_parentsha1_offset, rawbuf, sizeof(rawbuf)); |
| 378 | return be_read_sha1(rawbuf); |
| 644 | 379 | } |
| 645 | | |
| 646 | | return CHDERR_NONE; |
| 647 | | |
| 648 | | cleanup: |
| 649 | | if (newchd != NULL) |
| 650 | | chd_close(newchd); |
| 651 | | return err; |
| 380 | catch (chd_error &) |
| 381 | { |
| 382 | // on failure, return NULL |
| 383 | return sha1_t::null; |
| 384 | } |
| 652 | 385 | } |
| 653 | 386 | |
| 654 | 387 | |
| 655 | | /*------------------------------------------------- |
| 656 | | chd_open_file - open a CHD file for access |
| 657 | | -------------------------------------------------*/ |
| 388 | //------------------------------------------------- |
| 389 | // hunk_info - return information about this |
| 390 | // hunk |
| 391 | //------------------------------------------------- |
| 658 | 392 | |
| 659 | | chd_error chd_open_file(core_file *file, int mode, chd_file *parent, chd_file **chd) |
| 393 | chd_error chd_file::hunk_info(UINT32 hunknum, chd_codec_type &compressor, UINT32 &compbytes) |
| 660 | 394 | { |
| 661 | | chd_file *newchd = NULL; |
| 662 | | chd_error err; |
| 663 | | int intfnum; |
| 664 | | |
| 665 | | /* verify parameters */ |
| 666 | | if (file == NULL) |
| 667 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 668 | | |
| 669 | | /* punt if invalid parent */ |
| 670 | | if (parent != NULL && parent->cookie != COOKIE_VALUE) |
| 671 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 672 | | |
| 673 | | /* allocate memory for the final result */ |
| 674 | | newchd = (chd_file *)malloc(sizeof(**chd)); |
| 675 | | if (newchd == NULL) |
| 676 | | EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY); |
| 677 | | memset(newchd, 0, sizeof(*newchd)); |
| 678 | | newchd->cookie = COOKIE_VALUE; |
| 679 | | newchd->parent = parent; |
| 680 | | newchd->file = file; |
| 681 | | |
| 682 | | /* now attempt to read the header */ |
| 683 | | err = header_read(newchd->file, &newchd->header); |
| 684 | | if (err != CHDERR_NONE) |
| 685 | | EARLY_EXIT(err); |
| 686 | | |
| 687 | | /* validate the header */ |
| 688 | | err = header_validate(&newchd->header); |
| 689 | | if (err != CHDERR_NONE) |
| 690 | | EARLY_EXIT(err); |
| 691 | | |
| 692 | | /* make sure we don't open a read-only file writeable */ |
| 693 | | if (mode == CHD_OPEN_READWRITE && !(newchd->header.flags & CHDFLAGS_IS_WRITEABLE)) |
| 694 | | EARLY_EXIT(err = CHDERR_FILE_NOT_WRITEABLE); |
| 695 | | |
| 696 | | /* also, never open an older version writeable */ |
| 697 | | if (mode == CHD_OPEN_READWRITE && newchd->header.version < CHD_HEADER_VERSION) |
| 698 | | EARLY_EXIT(err = CHDERR_UNSUPPORTED_VERSION); |
| 699 | | |
| 700 | | /* if we need a parent, make sure we have one */ |
| 701 | | if (parent == NULL && (newchd->header.flags & CHDFLAGS_HAS_PARENT)) |
| 702 | | EARLY_EXIT(err = CHDERR_REQUIRES_PARENT); |
| 703 | | |
| 704 | | /* make sure we have a valid parent */ |
| 705 | | if (parent != NULL) |
| 395 | // error if invalid |
| 396 | if (hunknum >= m_hunkcount) |
| 397 | return CHDERR_HUNK_OUT_OF_RANGE; |
| 398 | |
| 399 | // get the map pointer |
| 400 | UINT8 *rawmap; |
| 401 | switch (m_version) |
| 706 | 402 | { |
| 707 | | /* check MD5 if it isn't empty */ |
| 708 | | if (memcmp(nullmd5, newchd->header.parentmd5, sizeof(newchd->header.parentmd5)) != 0 && |
| 709 | | memcmp(nullmd5, newchd->parent->header.md5, sizeof(newchd->parent->header.md5)) != 0 && |
| 710 | | memcmp(newchd->parent->header.md5, newchd->header.parentmd5, sizeof(newchd->header.parentmd5)) != 0) |
| 711 | | EARLY_EXIT(err = CHDERR_INVALID_PARENT); |
| 403 | // v3/v4 map entries |
| 404 | case 3: |
| 405 | case 4: |
| 406 | rawmap = m_rawmap + 16 * hunknum; |
| 407 | switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK) |
| 408 | { |
| 409 | case V34_MAP_ENTRY_TYPE_COMPRESSED: |
| 410 | compressor = CHD_CODEC_ZLIB; |
| 411 | compbytes = be_read(&rawmap[12], 2) + (rawmap[14] << 16); |
| 412 | break; |
| 413 | |
| 414 | case V34_MAP_ENTRY_TYPE_UNCOMPRESSED: |
| 415 | compressor = CHD_CODEC_NONE; |
| 416 | compbytes = m_hunkbytes; |
| 417 | break; |
| 418 | |
| 419 | case V34_MAP_ENTRY_TYPE_MINI: |
| 420 | compressor = CHD_CODEC_MINI; |
| 421 | compbytes = 0; |
| 422 | break; |
| 423 | |
| 424 | case V34_MAP_ENTRY_TYPE_SELF_HUNK: |
| 425 | compressor = CHD_CODEC_SELF; |
| 426 | compbytes = 0; |
| 427 | break; |
| 712 | 428 | |
| 713 | | /* check SHA1 if it isn't empty */ |
| 714 | | if (memcmp(nullsha1, newchd->header.parentsha1, sizeof(newchd->header.parentsha1)) != 0 && |
| 715 | | memcmp(nullsha1, newchd->parent->header.sha1, sizeof(newchd->parent->header.sha1)) != 0 && |
| 716 | | memcmp(newchd->parent->header.sha1, newchd->header.parentsha1, sizeof(newchd->header.parentsha1)) != 0) |
| 717 | | EARLY_EXIT(err = CHDERR_INVALID_PARENT); |
| 718 | | } |
| 429 | case V34_MAP_ENTRY_TYPE_PARENT_HUNK: |
| 430 | compressor = CHD_CODEC_PARENT; |
| 431 | compbytes = 0; |
| 432 | break; |
| 433 | } |
| 434 | break; |
| 435 | |
| 436 | // v5 map entries |
| 437 | case 5: |
| 438 | rawmap = m_rawmap + m_mapentrybytes * hunknum; |
| 719 | 439 | |
| 720 | | /* now read the hunk map */ |
| 721 | | err = map_read(newchd); |
| 722 | | if (err != CHDERR_NONE) |
| 723 | | EARLY_EXIT(err); |
| 440 | // uncompressed case |
| 441 | if (!compressed()) |
| 442 | { |
| 443 | if (be_read(&rawmap[0], 4) == 0) |
| 444 | { |
| 445 | compressor = CHD_CODEC_PARENT; |
| 446 | compbytes = 0; |
| 447 | } |
| 448 | else |
| 449 | { |
| 450 | compressor = CHD_CODEC_NONE; |
| 451 | compbytes = m_hunkbytes; |
| 452 | } |
| 453 | break; |
| 454 | } |
| 455 | |
| 456 | // compressed case |
| 457 | switch (rawmap[0]) |
| 458 | { |
| 459 | case COMPRESSION_TYPE_0: |
| 460 | case COMPRESSION_TYPE_1: |
| 461 | case COMPRESSION_TYPE_2: |
| 462 | case COMPRESSION_TYPE_3: |
| 463 | compressor = m_compression[rawmap[0]]; |
| 464 | compbytes = be_read(&rawmap[1], 3); |
| 465 | break; |
| 724 | 466 | |
| 725 | | /* allocate and init the hunk cache */ |
| 726 | | newchd->cache = (UINT8 *)malloc(newchd->header.hunkbytes); |
| 727 | | newchd->compare = (UINT8 *)malloc(newchd->header.hunkbytes); |
| 728 | | if (newchd->cache == NULL || newchd->compare == NULL) |
| 729 | | EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY); |
| 730 | | newchd->cachehunk = ~0; |
| 731 | | newchd->comparehunk = ~0; |
| 467 | case COMPRESSION_NONE: |
| 468 | compressor = CHD_CODEC_NONE; |
| 469 | compbytes = m_hunkbytes; |
| 470 | break; |
| 732 | 471 | |
| 733 | | /* allocate the temporary compressed buffer */ |
| 734 | | newchd->compressed = (UINT8 *)malloc(newchd->header.hunkbytes); |
| 735 | | if (newchd->compressed == NULL) |
| 736 | | EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY); |
| 737 | | |
| 738 | | /* find the codec interface */ |
| 739 | | for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) |
| 740 | | if (codec_interfaces[intfnum].compression == newchd->header.compression) |
| 741 | | { |
| 742 | | newchd->codecintf = &codec_interfaces[intfnum]; |
| 472 | case COMPRESSION_SELF: |
| 473 | compressor = CHD_CODEC_SELF; |
| 474 | compbytes = 0; |
| 475 | break; |
| 476 | |
| 477 | case COMPRESSION_PARENT: |
| 478 | compressor = CHD_CODEC_PARENT; |
| 479 | compbytes = 0; |
| 480 | break; |
| 481 | } |
| 743 | 482 | break; |
| 744 | | } |
| 745 | | if (intfnum == ARRAY_LENGTH(codec_interfaces)) |
| 746 | | EARLY_EXIT(err = CHDERR_UNSUPPORTED_FORMAT); |
| 747 | | |
| 748 | | /* initialize the codec */ |
| 749 | | if (newchd->codecintf->init != NULL) |
| 750 | | err = (*newchd->codecintf->init)(newchd); |
| 751 | | if (err != CHDERR_NONE) |
| 752 | | EARLY_EXIT(err); |
| 753 | | |
| 754 | | /* all done */ |
| 755 | | *chd = newchd; |
| 483 | } |
| 756 | 484 | return CHDERR_NONE; |
| 757 | | |
| 758 | | cleanup: |
| 759 | | if (newchd != NULL) |
| 760 | | chd_close(newchd); |
| 761 | | return err; |
| 762 | 485 | } |
| 763 | 486 | |
| 764 | 487 | |
| 765 | | /*------------------------------------------------- |
| 766 | | chd_create - create a CHD file by |
| 767 | | filename |
| 768 | | -------------------------------------------------*/ |
| 488 | //------------------------------------------------- |
| 489 | // set_raw_sha1 - set our SHA1 values |
| 490 | //------------------------------------------------- |
| 769 | 491 | |
| 770 | | chd_error chd_create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent) |
| 492 | void chd_file::set_raw_sha1(sha1_t rawdata) |
| 771 | 493 | { |
| 772 | | core_file *file = NULL; |
| 773 | | file_error filerr; |
| 774 | | chd_error chderr; |
| 775 | | |
| 776 | | filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file); |
| 777 | | if (filerr != FILERR_NONE) |
| 778 | | { |
| 779 | | chderr = CHDERR_FILE_NOT_FOUND; |
| 780 | | goto cleanup; |
| 781 | | } |
| 782 | | |
| 783 | | chderr = chd_create_file(file, logicalbytes, hunkbytes, compression, parent); |
| 784 | | if (chderr != CHDERR_NONE) |
| 785 | | goto cleanup; |
| 786 | | |
| 787 | | cleanup: |
| 788 | | if (file != NULL) |
| 789 | | core_fclose(file); |
| 790 | | return chderr; |
| 494 | // create a big-endian version |
| 495 | UINT8 rawbuf[sizeof(sha1_t)]; |
| 496 | be_write_sha1(rawbuf, rawdata); |
| 497 | |
| 498 | // write to the header |
| 499 | UINT64 offset = (m_rawsha1_offset != 0) ? m_rawsha1_offset : m_sha1_offset; |
| 500 | assert(offset != 0); |
| 501 | file_write(offset, rawbuf, sizeof(rawbuf)); |
| 502 | |
| 503 | // if we have a separate rawsha1_offset, update the full sha1 as well |
| 504 | if (m_rawsha1_offset != 0) |
| 505 | metadata_update_hash(); |
| 791 | 506 | } |
| 792 | 507 | |
| 793 | 508 | |
| 794 | | /*------------------------------------------------- |
| 795 | | chd_open - open a CHD file by |
| 796 | | filename |
| 797 | | -------------------------------------------------*/ |
| 509 | //------------------------------------------------- |
| 510 | // set_parent_sha1 - set the parent SHA1 value |
| 511 | //------------------------------------------------- |
| 798 | 512 | |
| 799 | | chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd) |
| 513 | void chd_file::set_parent_sha1(sha1_t parent) |
| 800 | 514 | { |
| 801 | | chd_error err; |
| 802 | | file_error filerr; |
| 803 | | core_file *file = NULL; |
| 804 | | UINT32 openflags; |
| 515 | // if no file, fail |
| 516 | if (m_file == NULL) |
| 517 | throw CHDERR_INVALID_FILE; |
| 805 | 518 | |
| 806 | | /* choose the proper mode */ |
| 807 | | switch(mode) |
| 808 | | { |
| 809 | | case CHD_OPEN_READ: |
| 810 | | openflags = OPEN_FLAG_READ; |
| 811 | | break; |
| 519 | // create a big-endian version |
| 520 | UINT8 rawbuf[sizeof(sha1_t)]; |
| 521 | be_write_sha1(rawbuf, parent); |
| 522 | |
| 523 | // write to the header |
| 524 | assert(m_parentsha1_offset != 0); |
| 525 | file_write(m_parentsha1_offset, rawbuf, sizeof(rawbuf)); |
| 526 | } |
| 812 | 527 | |
| 813 | | case CHD_OPEN_READWRITE: |
| 814 | | openflags = OPEN_FLAG_READ | OPEN_FLAG_WRITE; |
| 815 | | break; |
| 816 | 528 | |
| 817 | | default: |
| 818 | | err = CHDERR_INVALID_PARAMETER; |
| 819 | | goto cleanup; |
| 820 | | } |
| 529 | //------------------------------------------------- |
| 530 | // create - create a new file with no parent |
| 531 | // using an existing opened file handle |
| 532 | //------------------------------------------------- |
| 821 | 533 | |
| 822 | | /* open the file */ |
| 823 | | filerr = core_fopen(filename, openflags, &file); |
| 824 | | if (filerr != FILERR_NONE) |
| 825 | | { |
| 826 | | err = CHDERR_FILE_NOT_FOUND; |
| 827 | | goto cleanup; |
| 828 | | } |
| 534 | chd_error chd_file::create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4]) |
| 535 | { |
| 536 | // make sure we don't already have a file open |
| 537 | if (m_file != NULL) |
| 538 | return CHDERR_ALREADY_OPEN; |
| 829 | 539 | |
| 830 | | /* now open the CHD */ |
| 831 | | err = chd_open_file(file, mode, parent, chd); |
| 832 | | if (err != CHDERR_NONE) |
| 833 | | goto cleanup; |
| 540 | // set the header parameters |
| 541 | m_version = HEADER_VERSION; |
| 542 | m_logicalbytes = logicalbytes; |
| 543 | m_metaoffset = 0; |
| 544 | m_hunkbytes = hunkbytes; |
| 545 | m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes; |
| 546 | m_unitbytes = unitbytes; |
| 547 | memcpy(m_compression, compression, sizeof(m_compression)); |
| 548 | m_parent = NULL; |
| 834 | 549 | |
| 835 | | /* we now own this file */ |
| 836 | | (*chd)->owns_file = TRUE; |
| 837 | | |
| 838 | | cleanup: |
| 839 | | if ((err != CHDERR_NONE) && (file != NULL)) |
| 840 | | core_fclose(file); |
| 841 | | return err; |
| 550 | // take ownership of the file |
| 551 | m_file = &file; |
| 552 | m_owns_file = false; |
| 553 | return create_common(); |
| 842 | 554 | } |
| 843 | 555 | |
| 844 | 556 | |
| 845 | | /*------------------------------------------------- |
| 846 | | chd_close - close a CHD file for access |
| 847 | | -------------------------------------------------*/ |
| 557 | //------------------------------------------------- |
| 558 | // create - create a new file with a parent |
| 559 | // using an existing opened file handle |
| 560 | //------------------------------------------------- |
| 848 | 561 | |
| 849 | | void chd_close(chd_file *chd) |
| 562 | chd_error chd_file::create(core_file &file, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent) |
| 850 | 563 | { |
| 851 | | /* punt if NULL or invalid */ |
| 852 | | if (chd == NULL || chd->cookie != COOKIE_VALUE) |
| 853 | | return; |
| 564 | // make sure we don't already have a file open |
| 565 | if (m_file != NULL) |
| 566 | return CHDERR_ALREADY_OPEN; |
| 854 | 567 | |
| 855 | | /* wait for any pending async operations */ |
| 856 | | wait_for_pending_async(chd); |
| 568 | // set the header parameters |
| 569 | m_version = HEADER_VERSION; |
| 570 | m_logicalbytes = logicalbytes; |
| 571 | m_metaoffset = 0; |
| 572 | m_hunkbytes = hunkbytes; |
| 573 | m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes; |
| 574 | m_unitbytes = parent.unit_bytes(); |
| 575 | memcpy(m_compression, compression, sizeof(m_compression)); |
| 576 | m_parent = &parent; |
| 857 | 577 | |
| 858 | | /* kill the work queue and any work item */ |
| 859 | | if (chd->workitem != NULL) |
| 860 | | osd_work_item_release(chd->workitem); |
| 861 | | if (chd->workqueue != NULL) |
| 862 | | osd_work_queue_free(chd->workqueue); |
| 863 | | |
| 864 | | /* deinit the codec */ |
| 865 | | if (chd->codecintf != NULL && chd->codecintf->free != NULL) |
| 866 | | (*chd->codecintf->free)(chd); |
| 867 | | |
| 868 | | /* free the compressed data buffer */ |
| 869 | | if (chd->compressed != NULL) |
| 870 | | free(chd->compressed); |
| 871 | | |
| 872 | | /* free the hunk cache and compare data */ |
| 873 | | if (chd->compare != NULL) |
| 874 | | free(chd->compare); |
| 875 | | if (chd->cache != NULL) |
| 876 | | free(chd->cache); |
| 877 | | |
| 878 | | /* free the hunk map */ |
| 879 | | if (chd->map != NULL) |
| 880 | | free(chd->map); |
| 881 | | |
| 882 | | /* free the CRC table */ |
| 883 | | if (chd->crctable != NULL) |
| 884 | | free(chd->crctable); |
| 885 | | |
| 886 | | /* free the CRC map */ |
| 887 | | if (chd->crcmap != NULL) |
| 888 | | free(chd->crcmap); |
| 889 | | |
| 890 | | /* close the file */ |
| 891 | | if (chd->owns_file && chd->file != NULL) |
| 892 | | core_fclose(chd->file); |
| 893 | | |
| 894 | | if (PRINTF_MAX_HUNK) printf("Max hunk = %d/%d\n", chd->maxhunk, chd->header.totalhunks); |
| 895 | | |
| 896 | | /* free our memory */ |
| 897 | | free(chd); |
| 578 | // take ownership of the file |
| 579 | m_file = &file; |
| 580 | m_owns_file = false; |
| 581 | return create_common(); |
| 898 | 582 | } |
| 899 | 583 | |
| 900 | 584 | |
| 901 | | /*------------------------------------------------- |
| 902 | | chd_core_file - return the associated |
| 903 | | core_file |
| 904 | | -------------------------------------------------*/ |
| 585 | //------------------------------------------------- |
| 586 | // create - create a new file with no parent |
| 587 | // using a filename |
| 588 | //------------------------------------------------- |
| 905 | 589 | |
| 906 | | core_file *chd_core_file(chd_file *chd) |
| 590 | chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 unitbytes, chd_codec_type compression[4]) |
| 907 | 591 | { |
| 908 | | return chd->file; |
| 909 | | } |
| 592 | // make sure we don't already have a file open |
| 593 | if (m_file != NULL) |
| 594 | return CHDERR_ALREADY_OPEN; |
| 910 | 595 | |
| 596 | // create the new file |
| 597 | core_file *file = NULL; |
| 598 | file_error filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file); |
| 599 | if (filerr != FILERR_NONE) |
| 600 | return CHDERR_FILE_NOT_FOUND; |
| 911 | 601 | |
| 912 | | /*------------------------------------------------- |
| 913 | | chd_error_string - return an error string for |
| 914 | | the given CHD error |
| 915 | | -------------------------------------------------*/ |
| 916 | | |
| 917 | | const char *chd_error_string(chd_error err) |
| 918 | | { |
| 919 | | switch (err) |
| 602 | // create the file normally, then claim the file |
| 603 | chd_error chderr = create(*file, logicalbytes, hunkbytes, unitbytes, compression); |
| 604 | m_owns_file = true; |
| 605 | |
| 606 | // if an error happened, close and delete the file |
| 607 | if (chderr != CHDERR_NONE) |
| 920 | 608 | { |
| 921 | | case CHDERR_NONE: return "no error"; |
| 922 | | case CHDERR_NO_INTERFACE: return "no drive interface"; |
| 923 | | case CHDERR_OUT_OF_MEMORY: return "out of memory"; |
| 924 | | case CHDERR_INVALID_FILE: return "invalid file"; |
| 925 | | case CHDERR_INVALID_PARAMETER: return "invalid parameter"; |
| 926 | | case CHDERR_INVALID_DATA: return "invalid data"; |
| 927 | | case CHDERR_FILE_NOT_FOUND: return "file not found"; |
| 928 | | case CHDERR_REQUIRES_PARENT: return "requires parent"; |
| 929 | | case CHDERR_FILE_NOT_WRITEABLE: return "file not writeable"; |
| 930 | | case CHDERR_READ_ERROR: return "read error"; |
| 931 | | case CHDERR_WRITE_ERROR: return "write error"; |
| 932 | | case CHDERR_CODEC_ERROR: return "codec error"; |
| 933 | | case CHDERR_INVALID_PARENT: return "invalid parent"; |
| 934 | | case CHDERR_HUNK_OUT_OF_RANGE: return "hunk out of range"; |
| 935 | | case CHDERR_DECOMPRESSION_ERROR: return "decompression error"; |
| 936 | | case CHDERR_COMPRESSION_ERROR: return "compression error"; |
| 937 | | case CHDERR_CANT_CREATE_FILE: return "can't create file"; |
| 938 | | case CHDERR_CANT_VERIFY: return "can't verify file"; |
| 939 | | case CHDERR_NOT_SUPPORTED: return "operation not supported"; |
| 940 | | case CHDERR_METADATA_NOT_FOUND: return "can't find metadata"; |
| 941 | | case CHDERR_INVALID_METADATA_SIZE: return "invalid metadata size"; |
| 942 | | case CHDERR_UNSUPPORTED_VERSION: return "unsupported CHD version"; |
| 943 | | case CHDERR_VERIFY_INCOMPLETE: return "incomplete verify"; |
| 944 | | case CHDERR_INVALID_METADATA: return "invalid metadata"; |
| 945 | | case CHDERR_INVALID_STATE: return "invalid state"; |
| 946 | | case CHDERR_OPERATION_PENDING: return "operation pending"; |
| 947 | | case CHDERR_NO_ASYNC_OPERATION: return "no async operation in progress"; |
| 948 | | case CHDERR_UNSUPPORTED_FORMAT: return "unsupported format"; |
| 949 | | default: return "undocumented error"; |
| 609 | core_fclose(file); |
| 610 | osd_rmfile(filename); |
| 950 | 611 | } |
| 612 | return chderr; |
| 951 | 613 | } |
| 952 | 614 | |
| 953 | 615 | |
| 616 | //------------------------------------------------- |
| 617 | // create - create a new file with a parent |
| 618 | // using a filename |
| 619 | //------------------------------------------------- |
| 954 | 620 | |
| 955 | | /*************************************************************************** |
| 956 | | CHD HEADER MANAGEMENT |
| 957 | | ***************************************************************************/ |
| 958 | | |
| 959 | | /*------------------------------------------------- |
| 960 | | chd_get_header - return a pointer to the |
| 961 | | extracted header data |
| 962 | | -------------------------------------------------*/ |
| 963 | | |
| 964 | | const chd_header *chd_get_header(chd_file *chd) |
| 621 | chd_error chd_file::create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, chd_codec_type compression[4], chd_file &parent) |
| 965 | 622 | { |
| 966 | | /* punt if NULL or invalid */ |
| 967 | | if (chd == NULL || chd->cookie != COOKIE_VALUE) |
| 968 | | return NULL; |
| 623 | // make sure we don't already have a file open |
| 624 | if (m_file != NULL) |
| 625 | return CHDERR_ALREADY_OPEN; |
| 969 | 626 | |
| 970 | | return &chd->header; |
| 971 | | } |
| 627 | // create the new file |
| 628 | core_file *file = NULL; |
| 629 | file_error filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file); |
| 630 | if (filerr != FILERR_NONE) |
| 631 | return CHDERR_FILE_NOT_FOUND; |
| 972 | 632 | |
| 973 | | |
| 974 | | /*------------------------------------------------- |
| 975 | | chd_set_header_file - write the current header to |
| 976 | | the file |
| 977 | | -------------------------------------------------*/ |
| 978 | | |
| 979 | | chd_error chd_set_header_file(core_file *file, const chd_header *header) |
| 980 | | { |
| 981 | | chd_header oldheader; |
| 982 | | chd_error err; |
| 983 | | |
| 984 | | /* validate the header */ |
| 985 | | err = header_validate(header); |
| 986 | | if (err != CHDERR_NONE) |
| 987 | | EARLY_EXIT(err); |
| 988 | | |
| 989 | | /* read the old header */ |
| 990 | | err = header_read(file, &oldheader); |
| 991 | | if (err != CHDERR_NONE) |
| 992 | | EARLY_EXIT(err); |
| 993 | | |
| 994 | | /* make sure we're only making valid changes */ |
| 995 | | if (header->length != oldheader.length) |
| 996 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 997 | | if (header->version != oldheader.version) |
| 998 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 999 | | if (header->compression != oldheader.compression) |
| 1000 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 1001 | | if (header->hunkbytes != oldheader.hunkbytes) |
| 1002 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 1003 | | if (header->totalhunks != oldheader.totalhunks) |
| 1004 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 1005 | | if (header->metaoffset != oldheader.metaoffset) |
| 1006 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 1007 | | if (header->obsolete_hunksize != oldheader.obsolete_hunksize) |
| 1008 | | EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); |
| 1009 | | |
| 1010 | | /* write the new header */ |
| 1011 | | err = header_write(file, header); |
| 1012 | | if (err != CHDERR_NONE) |
| 1013 | | EARLY_EXIT(err); |
| 1014 | | |
| 1015 | | return CHDERR_NONE; |
| 1016 | | |
| 1017 | | cleanup: |
| 1018 | | return err; |
| 633 | // create the file normally, then claim the file |
| 634 | chd_error chderr = create(*file, logicalbytes, hunkbytes, compression, parent); |
| 635 | m_owns_file = true; |
| 636 | |
| 637 | // if an error happened, close and delete the file |
| 638 | if (chderr != CHDERR_NONE) |
| 639 | { |
| 640 | core_fclose(file); |
| 641 | osd_rmfile(filename); |
| 642 | } |
| 643 | return chderr; |
| 1019 | 644 | } |
| 1020 | 645 | |
| 1021 | 646 | |
| 1022 | | /*------------------------------------------------- |
| 1023 | | chd_set_header - write the current |
| 1024 | | header to the file |
| 1025 | | -------------------------------------------------*/ |
| 647 | //------------------------------------------------- |
| 648 | // open - open an existing file for read or |
| 649 | // read/write |
| 650 | //------------------------------------------------- |
| 1026 | 651 | |
| 1027 | | chd_error chd_set_header(const char *filename, const chd_header *header) |
| 652 | chd_error chd_file::open(const char *filename, bool writeable, chd_file *parent) |
| 1028 | 653 | { |
| 1029 | | core_file *file = NULL; |
| 1030 | | file_error filerr; |
| 1031 | | chd_error err; |
| 654 | // make sure we don't already have a file open |
| 655 | if (m_file != NULL) |
| 656 | return CHDERR_ALREADY_OPEN; |
| 1032 | 657 | |
| 1033 | | filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE, &file); |
| 658 | // open the file |
| 659 | UINT32 openflags = writeable ? (OPEN_FLAG_READ | OPEN_FLAG_WRITE) : OPEN_FLAG_READ; |
| 660 | core_file *file = NULL; |
| 661 | file_error filerr = core_fopen(filename, openflags, &file); |
| 1034 | 662 | if (filerr != FILERR_NONE) |
| 1035 | | { |
| 1036 | | err = CHDERR_FILE_NOT_FOUND; |
| 1037 | | goto cleanup; |
| 1038 | | } |
| 663 | return CHDERR_FILE_NOT_FOUND; |
| 1039 | 664 | |
| 1040 | | err = chd_set_header_file(file, header); |
| 665 | // now open the CHD |
| 666 | chd_error err = open(*file, writeable, parent); |
| 1041 | 667 | if (err != CHDERR_NONE) |
| 1042 | | goto cleanup; |
| 1043 | | |
| 1044 | | cleanup: |
| 1045 | | if (file != NULL) |
| 668 | { |
| 1046 | 669 | core_fclose(file); |
| 670 | return err; |
| 671 | } |
| 672 | |
| 673 | // we now own this file |
| 674 | m_owns_file = true; |
| 1047 | 675 | return err; |
| 1048 | 676 | } |
| 1049 | 677 | |
| 1050 | 678 | |
| 679 | //------------------------------------------------- |
| 680 | // open - open an existing file for read or |
| 681 | // read/write |
| 682 | //------------------------------------------------- |
| 1051 | 683 | |
| 1052 | | /*************************************************************************** |
| 1053 | | CORE DATA READ/WRITE |
| 1054 | | ***************************************************************************/ |
| 1055 | | |
| 1056 | | /*------------------------------------------------- |
| 1057 | | chd_read - read a single hunk from the CHD |
| 1058 | | file |
| 1059 | | -------------------------------------------------*/ |
| 1060 | | |
| 1061 | | chd_error chd_read(chd_file *chd, UINT32 hunknum, void *buffer) |
| 684 | chd_error chd_file::open(core_file &file, bool writeable, chd_file *parent) |
| 1062 | 685 | { |
| 1063 | | /* punt if NULL or invalid */ |
| 1064 | | if (chd == NULL || chd->cookie != COOKIE_VALUE) |
| 1065 | | return CHDERR_INVALID_PARAMETER; |
| 686 | // make sure we don't already have a file open |
| 687 | if (m_file != NULL) |
| 688 | return CHDERR_ALREADY_OPEN; |
| 1066 | 689 | |
| 1067 | | /* if we're past the end, fail */ |
| 1068 | | if (hunknum >= chd->header.totalhunks) |
| 1069 | | return CHDERR_HUNK_OUT_OF_RANGE; |
| 1070 | | |
| 1071 | | /* wait for any pending async operations */ |
| 1072 | | wait_for_pending_async(chd); |
| 1073 | | |
| 1074 | | /* perform the read */ |
| 1075 | | return hunk_read_into_memory(chd, hunknum, (UINT8 *)buffer); |
| 690 | // open the file |
| 691 | m_file = &file; |
| 692 | m_owns_file = false; |
| 693 | m_parent = parent; |
| 694 | return open_common(writeable); |
| 1076 | 695 | } |
| 1077 | 696 | |
| 1078 | 697 | |
| 1079 | | /*------------------------------------------------- |
| 1080 | | chd_read_async - read a single hunk from the |
| 1081 | | CHD file asynchronously |
| 1082 | | -------------------------------------------------*/ |
| 698 | //------------------------------------------------- |
| 699 | // close - close a CHD file for access |
| 700 | //------------------------------------------------- |
| 1083 | 701 | |
| 1084 | | chd_error chd_read_async(chd_file *chd, UINT32 hunknum, void *buffer) |
| 702 | void chd_file::close() |
| 1085 | 703 | { |
| 1086 | | /* punt if NULL or invalid */ |
| 1087 | | if (chd == NULL || chd->cookie != COOKIE_VALUE) |
| 1088 | | return CHDERR_INVALID_PARAMETER; |
| 704 | // reset file characteristics |
| 705 | if (m_owns_file && m_file != NULL) |
| 706 | core_fclose(m_file); |
| 707 | m_file = NULL; |
| 708 | m_owns_file = false; |
| 709 | m_allow_reads = false; |
| 710 | m_allow_writes = false; |
| 711 | |
| 712 | // reset core parameters from the header |
| 713 | m_version = HEADER_VERSION; |
| 714 | m_logicalbytes = 0; |
| 715 | m_mapoffset = 0; |
| 716 | m_metaoffset = 0; |
| 717 | m_hunkbytes = 0; |
| 718 | m_hunkcount = 0; |
| 719 | m_unitbytes = 0; |
| 720 | m_unitcount = 0; |
| 721 | memset(m_compression, 0, sizeof(m_compression)); |
| 722 | m_parent = NULL; |
| 723 | m_parent_missing = false; |
| 1089 | 724 | |
| 1090 | | /* if we're past the end, fail */ |
| 1091 | | if (hunknum >= chd->header.totalhunks) |
| 1092 | | return CHDERR_HUNK_OUT_OF_RANGE; |
| 725 | // reset key offsets within the header |
| 726 | m_mapoffset_offset = 0; |
| 727 | m_metaoffset_offset = 0; |
| 728 | m_sha1_offset = 0; |
| 729 | m_rawsha1_offset = 0; |
| 730 | m_parentsha1_offset = 0; |
| 1093 | 731 | |
| 1094 | | /* wait for any pending async operations */ |
| 1095 | | wait_for_pending_async(chd); |
| 732 | // reset map information |
| 733 | m_mapentrybytes = 0; |
| 734 | m_rawmap.reset(); |
| 1096 | 735 | |
| 1097 | | /* set the async parameters */ |
| 1098 | | chd->async_hunknum = hunknum; |
| 1099 | | chd->async_buffer = buffer; |
| 736 | // reset compression management |
| 737 | for (int decompnum = 0; decompnum < ARRAY_LENGTH(m_decompressor); decompnum++) |
| 738 | { |
| 739 | delete m_decompressor[decompnum]; |
| 740 | m_decompressor[decompnum] = NULL; |
| 741 | } |
| 742 | m_compressed.reset(); |
| 1100 | 743 | |
| 1101 | | /* queue the work item */ |
| 1102 | | if (queue_async_operation(chd, async_read_callback)) |
| 1103 | | return CHDERR_OPERATION_PENDING; |
| 1104 | | |
| 1105 | | /* if we fail, fall back on the sync version */ |
| 1106 | | return chd_read(chd, hunknum, buffer); |
| 744 | // reset caching |
| 745 | m_cache.reset(); |
| 746 | m_cachehunk = ~0; |
| 1107 | 747 | } |
| 1108 | 748 | |
| 1109 | 749 | |
| 1110 | | /*------------------------------------------------- |
| 1111 | | chd_write - write a single hunk to the CHD |
| 1112 | | file |
| 1113 | | -------------------------------------------------*/ |
| 750 | //------------------------------------------------- |
| 751 | // read - read a single hunk from the CHD file |
| 752 | //------------------------------------------------- |
| 1114 | 753 | |
| 1115 | | chd_error chd_write(chd_file *chd, UINT32 hunknum, const void *buffer) |
| 754 | chd_error chd_file::read_hunk(UINT32 hunknum, void *buffer) |
| 1116 | 755 | { |
| 1117 | | /* punt if NULL or invalid */ |
| 1118 | | if (chd == NULL || chd->cookie != COOKIE_VALUE) |
| 1119 | | return CHDERR_INVALID_PARAMETER; |
| 756 | // wrap this for clean reporting |
| 757 | try |
| 758 | { |
| 759 | // punt if no file |
| 760 | if (m_file == NULL) |
| 761 | throw CHDERR_NOT_OPEN; |
| 762 | |
| 763 | // return an error if out of range |
| 764 | if (hunknum >= m_hunkcount) |
| 765 | throw CHDERR_HUNK_OUT_OF_RANGE; |
| 1120 | 766 | |
| 1121 | | /* if we're past the end, fail */ |
| 1122 | | if (hunknum >= chd->header.totalhunks) |
| 1123 | | return CHDERR_HUNK_OUT_OF_RANGE; |
| 767 | // get a pointer to the map entry |
| 768 | UINT64 blockoffs; |
| 769 | UINT32 blocklen; |
| 770 | UINT32 blockcrc; |
| 771 | UINT8 *rawmap; |
| 772 | UINT8 *dest = reinterpret_cast<UINT8 *>(buffer); |
| 773 | switch (m_version) |
| 774 | { |
| 775 | // v3/v4 map entries |
| 776 | case 3: |
| 777 | case 4: |
| 778 | rawmap = m_rawmap + 16 * hunknum; |
| 779 | blockoffs = be_read(&rawmap[0], 8); |
| 780 | blockcrc = be_read(&rawmap[8], 4); |
| 781 | switch (rawmap[15] & V34_MAP_ENTRY_FLAG_TYPE_MASK) |
| 782 | { |
| 783 | case V34_MAP_ENTRY_TYPE_COMPRESSED: |
| 784 | blocklen = be_read(&rawmap[12], 2) + (rawmap[14] << 16); |
| 785 | file_read(blockoffs, m_compressed, blocklen); |
| 786 | m_decompressor[0]->decompress(m_compressed, blocklen, dest, m_hunkbytes); |
| 787 | if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && dest != NULL && crc32_creator::simple(dest, m_hunkbytes) != blockcrc) |
| 788 | throw CHDERR_DECOMPRESSION_ERROR; |
| 789 | return CHDERR_NONE; |
| 790 | |
| 791 | case V34_MAP_ENTRY_TYPE_UNCOMPRESSED: |
| 792 | file_read(blockoffs, dest, m_hunkbytes); |
| 793 | if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && crc32_creator::simple(dest, m_hunkbytes) != blockcrc) |
| 794 | throw CHDERR_DECOMPRESSION_ERROR; |
| 795 | return CHDERR_NONE; |
| 796 | |
| 797 | case V34_MAP_ENTRY_TYPE_MINI: |
| 798 | be_write(dest, blockoffs, 8); |
| 799 | for (UINT32 bytes = 8; bytes < m_hunkbytes; bytes++) |
| 800 | dest[bytes] = dest[bytes - 8]; |
| 801 | if (!(rawmap[15] & V34_MAP_ENTRY_FLAG_NO_CRC) && crc32_creator::simple(dest, m_hunkbytes) != blockcrc) |
| 802 | throw CHDERR_DECOMPRESSION_ERROR; |
| 803 | return CHDERR_NONE; |
| 804 | |
| 805 | case V34_MAP_ENTRY_TYPE_SELF_HUNK: |
| 806 | return read_hunk(blockoffs, dest); |
| 1124 | 807 | |
| 1125 | | /* wait for any pending async operations */ |
| 1126 | | wait_for_pending_async(chd); |
| 808 | case V34_MAP_ENTRY_TYPE_PARENT_HUNK: |
| 809 | if (m_parent_missing) |
| 810 | throw CHDERR_REQUIRES_PARENT; |
| 811 | return m_parent->read_hunk(blockoffs, dest); |
| 812 | } |
| 813 | break; |
| 814 | |
| 815 | // v5 map entries |
| 816 | case 5: |
| 817 | rawmap = m_rawmap + m_mapentrybytes * hunknum; |
| 1127 | 818 | |
| 1128 | | /* then write out the hunk */ |
| 1129 | | return hunk_write_from_memory(chd, hunknum, (const UINT8 *)buffer); |
| 1130 | | } |
| 819 | // uncompressed case |
| 820 | if (!compressed()) |
| 821 | { |
| 822 | blockoffs = UINT64(be_read(rawmap, 4)) * UINT64(m_hunkbytes); |
| 823 | if (blockoffs != 0) |
| 824 | file_read(blockoffs, dest, m_hunkbytes); |
| 825 | else if (m_parent_missing) |
| 826 | throw CHDERR_REQUIRES_PARENT; |
| 827 | else if (m_parent != NULL) |
| 828 | m_parent->read_hunk(hunknum, dest); |
| 829 | else |
| 830 | memset(dest, 0, m_hunkbytes); |
| 831 | return CHDERR_NONE; |
| 832 | } |
| 833 | |
| 834 | // compressed case |
| 835 | blocklen = be_read(&rawmap[1], 3); |
| 836 | blockoffs = be_read(&rawmap[4], 6); |
| 837 | blockcrc = be_read(&rawmap[10], 2); |
| 838 | switch (rawmap[0]) |
| 839 | { |
| 840 | case COMPRESSION_TYPE_0: |
| 841 | case COMPRESSION_TYPE_1: |
| 842 | case COMPRESSION_TYPE_2: |
| 843 | case COMPRESSION_TYPE_3: |
| 844 | file_read(blockoffs, m_compressed, blocklen); |
| 845 | m_decompressor[rawmap[0]]->decompress(m_compressed, blocklen, dest, m_hunkbytes); |
| 846 | if (!m_decompressor[rawmap[0]]->lossy() && dest != NULL && crc16_creator::simple(dest, m_hunkbytes) != blockcrc) |
| 847 | throw CHDERR_DECOMPRESSION_ERROR; |
| 848 | if (m_decompressor[rawmap[0]]->lossy() && crc16_creator::simple(m_compressed, blocklen) != blockcrc) |
| 849 | throw CHDERR_DECOMPRESSION_ERROR; |
| 850 | return CHDERR_NONE; |
| 1131 | 851 | |
| 852 | case COMPRESSION_NONE: |
| 853 | file_read(blockoffs, dest, m_hunkbytes); |
| 854 | if (crc16_creator::simple(dest, m_hunkbytes) != blockcrc) |
| 855 | throw CHDERR_DECOMPRESSION_ERROR; |
| 856 | return CHDERR_NONE; |
| 1132 | 857 | |
| 1133 | | /*------------------------------------------------- |
| 1134 | | chd_write_async - write a single hunk to the |
| 1135 | | CHD file asynchronously |
| 1136 | | -------------------------------------------------*/ |
| 1137 | | |
| 1138 | | chd_error chd_write_async(chd_file *chd, UINT32 hunknum, const void *buffer) |
| 1139 | | { |
| 1140 | | /* punt if NULL or invalid */ |
| 1141 | | if (chd == NULL || chd->cookie != COOKIE_VALUE) |
| 1142 | | return CHDERR_INVALID_PARAMETER; |
| 1143 | | |
| 1144 | | /* if we're past the end, fail */ |
| 1145 | | if (hunknum >= chd->header.totalhunks) |
| 1146 | | return CHDERR_HUNK_OUT_OF_RANGE; |
| 1147 | | |
| 1148 | | /* wait for any pending async operations */ |
| 1149 | | wait_for_pending_async(chd); |
| 1150 | | |
| 1151 | | /* set the async parameters */ |
| 1152 | | chd->async_hunknum = hunknum; |
| 1153 | | chd->async_buffer = (void *)buffer; |
| 1154 | | |
| 1155 | | /* queue the work item */ |
| 1156 | | if (queue_async_operation(chd, async_write_callback)) |
| 1157 | | return CHDERR_OPERATION_PENDING; |
| 1158 | | |
| 1159 | | /* if we fail, fall back on the sync version */ |
| 1160 | | return chd_write(chd, hunknum, buffer); |
| 858 | case COMPRESSION_SELF: |
| 859 | return read_hunk(blockoffs, dest); |
| 860 | |
| 861 | case COMPRESSION_PARENT: |
| 862 | if (m_parent_missing) |
| 863 | throw CHDERR_REQUIRES_PARENT; |
| 864 | return m_parent->read_bytes(UINT64(blockoffs) * UINT64(m_parent->unit_bytes()), dest, m_hunkbytes); |
| 865 | } |
| 866 | break; |
| 867 | } |
| 868 | |
| 869 | // if we get here, something was wrong |
| 870 | throw CHDERR_READ_ERROR; |
| 871 | } |
| 872 | |
| 873 | // just return errors |
| 874 | catch (chd_error &err) |
| 875 | { |
| 876 | return err; |
| 877 | } |
| 1161 | 878 | } |
| 1162 | 879 | |
| 1163 | 880 | |
| 1164 | | /*------------------------------------------------- |
| 1165 | | chd_async_complete - get the result of a |
| 1166 | | completed work item and clear it out of the |
| 1167 | | system |
| 1168 | | -------------------------------------------------*/ |
| 881 | //------------------------------------------------- |
| 882 | // write - write a single hunk to the CHD file |
| 883 | //------------------------------------------------- |
| 1169 | 884 | |
| 1170 | | chd_error chd_async_complete(chd_file *chd) |
| 885 | chd_error chd_file::write_hunk(UINT32 hunknum, const void *buffer) |
| 1171 | 886 | { |
| 1172 | | void *result; |
| 887 | // wrap this for clean reporting |
| 888 | try |
| 889 | { |
| 890 | // punt if no file |
| 891 | if (m_file == NULL) |
| 892 | throw CHDERR_NOT_OPEN; |
| 1173 | 893 | |
| 1174 | | /* if nothing present, return an error */ |
| 1175 | | if (chd->workitem == NULL) |
| 1176 | | return CHDERR_NO_ASYNC_OPERATION; |
| 894 | // return an error if out of range |
| 895 | if (hunknum >= m_hunkcount) |
| 896 | throw CHDERR_HUNK_OUT_OF_RANGE; |
| 1177 | 897 | |
| 1178 | | /* wait for the work to complete */ |
| 1179 | | wait_for_pending_async(chd); |
| 898 | // if not writeable, fail |
| 899 | if (!m_allow_writes) |
| 900 | throw CHDERR_FILE_NOT_WRITEABLE; |
| 1180 | 901 | |
| 1181 | | /* get the result and free the work item */ |
| 1182 | | result = osd_work_item_result(chd->workitem); |
| 1183 | | osd_work_item_release(chd->workitem); |
| 1184 | | chd->workitem = NULL; |
| 902 | // uncompressed writes only via this interface |
| 903 | if (compressed()) |
| 904 | throw CHDERR_FILE_NOT_WRITEABLE; |
| 905 | |
| 906 | // see if we have allocated the space on disk for this hunk |
| 907 | UINT8 *rawmap = m_rawmap + hunknum * 4; |
| 908 | UINT32 rawentry = be_read(rawmap, 4); |
| 1185 | 909 | |
| 1186 | | return (chd_error)(ptrdiff_t)result; |
| 1187 | | } |
| 1188 | | |
| 1189 | | |
| 1190 | | |
| 1191 | | /*************************************************************************** |
| 1192 | | METADATA MANAGEMENT |
| 1193 | | ***************************************************************************/ |
| 1194 | | |
| 1195 | | /*------------------------------------------------- |
| 1196 | | chd_get_metadata - get the indexed metadata |
| 1197 | | of the given type |
| 1198 | | -------------------------------------------------*/ |
| 1199 | | |
| 1200 | | chd_error chd_get_metadata(chd_file *chd, UINT32 searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 *resultlen, UINT32 *resulttag, UINT8 *resultflags) |
| 1201 | | { |
| 1202 | | metadata_entry metaentry; |
| 1203 | | chd_error err; |
| 1204 | | UINT32 count; |
| 1205 | | |
| 1206 | | /* wait for any pending async operations */ |
| 1207 | | wait_for_pending_async(chd); |
| 1208 | | |
| 1209 | | /* if we didn't find it, just return */ |
| 1210 | | err = metadata_find_entry(chd, searchtag, searchindex, &metaentry); |
| 1211 | | if (err != CHDERR_NONE) |
| 1212 | | { |
| 1213 | | /* unless we're an old version and they are requesting hard disk metadata */ |
| 1214 | | if (chd->header.version < 3 && (searchtag == HARD_DISK_METADATA_TAG || searchtag == CHDMETATAG_WILDCARD) && searchindex == 0) |
| 910 | // if not, allocate one now |
| 911 | if (rawentry == 0) |
| 1215 | 912 | { |
| 1216 | | char faux_metadata[256]; |
| 1217 | | UINT32 faux_length; |
| 1218 | | |
| 1219 | | /* fill in the faux metadata */ |
| 1220 | | sprintf(faux_metadata, HARD_DISK_METADATA_FORMAT, chd->header.obsolete_cylinders, chd->header.obsolete_heads, chd->header.obsolete_sectors, chd->header.hunkbytes / chd->header.obsolete_hunksize); |
| 1221 | | faux_length = (UINT32)strlen(faux_metadata) + 1; |
| 1222 | | |
| 1223 | | /* copy the metadata itself */ |
| 1224 | | memcpy(output, faux_metadata, MIN(outputlen, faux_length)); |
| 1225 | | |
| 1226 | | /* return the length of the data and the tag */ |
| 1227 | | if (resultlen != NULL) |
| 1228 | | *resultlen = faux_length; |
| 1229 | | if (resulttag != NULL) |
| 1230 | | *resulttag = HARD_DISK_METADATA_TAG; |
| 1231 | | return CHDERR_NONE; |
| 913 | // append new data to the end of the file, aligning the first chunk |
| 914 | rawentry = file_append(buffer, m_hunkbytes, m_hunkbytes) / m_hunkbytes; |
| 915 | |
| 916 | // write the map entry back |
| 917 | be_write(rawmap, rawentry, 4); |
| 918 | file_write(m_mapoffset + hunknum * 4, rawmap, 4); |
| 919 | |
| 920 | // update the cached hunk if we just wrote it |
| 921 | if (hunknum == m_cachehunk && buffer != m_cache) |
| 922 | memcpy(m_cache, buffer, m_hunkbytes); |
| 1232 | 923 | } |
| 924 | |
| 925 | // otherwise, just overwrite |
| 926 | else |
| 927 | file_write(UINT64(rawentry) * UINT64(m_hunkbytes), buffer, m_hunkbytes); |
| 928 | return CHDERR_NONE; |
| 929 | } |
| 930 | |
| 931 | // just return errors |
| 932 | catch (chd_error &err) |
| 933 | { |
| 1233 | 934 | return err; |
| 1234 | 935 | } |
| 1235 | | |
| 1236 | | /* read the metadata */ |
| 1237 | | outputlen = MIN(outputlen, metaentry.length); |
| 1238 | | core_fseek(chd->file, metaentry.offset + METADATA_HEADER_SIZE, SEEK_SET); |
| 1239 | | count = core_fread(chd->file, output, outputlen); |
| 1240 | | if (count != outputlen) |
| 1241 | | return CHDERR_READ_ERROR; |
| 1242 | | |
| 1243 | | /* return the length of the data and the tag */ |
| 1244 | | if (resultlen != NULL) |
| 1245 | | *resultlen = metaentry.length; |
| 1246 | | if (resulttag != NULL) |
| 1247 | | *resulttag = metaentry.metatag; |
| 1248 | | if (resultflags != NULL) |
| 1249 | | *resultflags = metaentry.flags; |
| 1250 | | return CHDERR_NONE; |
| 1251 | 936 | } |
| 1252 | 937 | |
| 1253 | 938 | |
| 1254 | | /*------------------------------------------------- |
| 1255 | | chd_set_metadata - write the indexed metadata |
| 1256 | | of the given type |
| 1257 | | -------------------------------------------------*/ |
| 939 | //------------------------------------------------- |
| 940 | // read_units - read the given number of units |
| 941 | // from the CHD |
| 942 | //------------------------------------------------- |
| 1258 | 943 | |
| 1259 | | chd_error chd_set_metadata(chd_file *chd, UINT32 metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags) |
| 944 | chd_error chd_file::read_units(UINT64 unitnum, void *buffer, UINT32 count) |
| 1260 | 945 | { |
| 1261 | | UINT8 raw_meta_header[METADATA_HEADER_SIZE]; |
| 1262 | | metadata_entry metaentry = { 0 }; |
| 1263 | | chd_error err; |
| 1264 | | UINT64 offset; |
| 1265 | | UINT32 count; |
| 946 | return read_bytes(unitnum * UINT64(m_unitbytes), buffer, count * m_unitbytes); |
| 947 | } |
| 1266 | 948 | |
| 1267 | | /* if the disk is an old version, punt */ |
| 1268 | | if (chd->header.version < 3) |
| 1269 | | return CHDERR_NOT_SUPPORTED; |
| 1270 | 949 | |
| 1271 | | /* if the disk isn't writeable, punt */ |
| 1272 | | if (!(chd->header.flags & CHDFLAGS_IS_WRITEABLE)) |
| 1273 | | return CHDERR_FILE_NOT_WRITEABLE; |
| 950 | //------------------------------------------------- |
| 951 | // write_units - write the given number of units |
| 952 | // to the CHD |
| 953 | //------------------------------------------------- |
| 1274 | 954 | |
| 1275 | | /* must write at least 1 byte */ |
| 1276 | | if (inputlen < 1) |
| 1277 | | return CHDERR_INVALID_PARAMETER; |
| 955 | chd_error chd_file::write_units(UINT64 unitnum, const void *buffer, UINT32 count) |
| 956 | { |
| 957 | return write_bytes(unitnum * UINT64(m_unitbytes), buffer, count * m_unitbytes); |
| 958 | } |
| 1278 | 959 | |
| 1279 | | /* no more than 16MB */ |
| 1280 | | if (inputlen >= 16 * 1024 * 1024) |
| 1281 | | return CHDERR_INVALID_PARAMETER; |
| 1282 | 960 | |
| 1283 | | /* wait for any pending async operations */ |
| 1284 | | wait_for_pending_async(chd); |
| 961 | //------------------------------------------------- |
| 962 | // read_bytes - read from the CHD at a byte level, |
| 963 | // using the cache to handle partial hunks |
| 964 | //------------------------------------------------- |
| 1285 | 965 | |
| 1286 | | /* find the entry if it already exists */ |
| 1287 | | err = metadata_find_entry(chd, metatag, metaindex, &metaentry); |
| 1288 | | |
| 1289 | | /* if it's there and it fits, just overwrite */ |
| 1290 | | if (err == CHDERR_NONE && inputlen <= metaentry.length) |
| 966 | chd_error chd_file::read_bytes(UINT64 offset, void *buffer, UINT32 bytes) |
| 967 | { |
| 968 | // iterate over hunks |
| 969 | UINT32 first_hunk = offset / m_hunkbytes; |
| 970 | UINT32 last_hunk = (offset + bytes - 1) / m_hunkbytes; |
| 971 | UINT8 *dest = reinterpret_cast<UINT8 *>(buffer); |
| 972 | for (UINT32 curhunk = first_hunk; curhunk <= last_hunk; curhunk++) |
| 1291 | 973 | { |
| 1292 | | /* overwrite the original data with our new input data */ |
| 1293 | | core_fseek(chd->file, metaentry.offset + METADATA_HEADER_SIZE, SEEK_SET); |
| 1294 | | count = core_fwrite(chd->file, inputbuf, inputlen); |
| 1295 | | if (count != inputlen) |
| 974 | // determine start/end boundaries |
| 975 | UINT32 startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0; |
| 976 | UINT32 endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1); |
| 977 | |
| 978 | // if it's a full block, just read directly from disk unless it's the cached hunk |
| 979 | chd_error err = CHDERR_NONE; |
| 980 | if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk) |
| 981 | err = read_hunk(curhunk, dest); |
| 982 | |
| 983 | // otherwise, read from the cache |
| 984 | else |
| 1296 | 985 | { |
| 1297 | | err = CHDERR_WRITE_ERROR; |
| 1298 | | goto update; |
| 986 | if (curhunk != m_cachehunk) |
| 987 | { |
| 988 | err = read_hunk(curhunk, m_cache); |
| 989 | if (err != CHDERR_NONE) |
| 990 | return err; |
| 991 | m_cachehunk = curhunk; |
| 992 | } |
| 993 | memcpy(dest, &m_cache[startoffs], endoffs + 1 - startoffs); |
| 1299 | 994 | } |
| 1300 | 995 | |
| 1301 | | /* if the lengths don't match, we need to update the length in our header */ |
| 1302 | | if (inputlen != metaentry.length) |
| 1303 | | err = metadata_set_length(chd, metaentry.offset, inputlen); |
| 1304 | | goto update; |
| 1305 | | } |
| 1306 | | |
| 1307 | | /* if we already have an entry, unlink it */ |
| 1308 | | if (err == CHDERR_NONE) |
| 1309 | | { |
| 1310 | | err = metadata_set_previous_next(chd, metaentry.prev, metaentry.next); |
| 996 | // handle errors and advance |
| 1311 | 997 | if (err != CHDERR_NONE) |
| 1312 | | goto update; |
| 998 | return err; |
| 999 | dest += endoffs + 1 - startoffs; |
| 1313 | 1000 | } |
| 1314 | | |
| 1315 | | /* now build us a new entry */ |
| 1316 | | put_bigendian_uint32(&raw_meta_header[0], metatag); |
| 1317 | | put_bigendian_uint32(&raw_meta_header[4], (inputlen & 0x00ffffff) | (flags << 24)); |
| 1318 | | put_bigendian_uint64(&raw_meta_header[8], (err == CHDERR_NONE) ? metaentry.next : 0); |
| 1319 | | |
| 1320 | | /* write out the new header */ |
| 1321 | | offset = core_fsize(chd->file); |
| 1322 | | core_fseek(chd->file, offset, SEEK_SET); |
| 1323 | | count = core_fwrite(chd->file, raw_meta_header, sizeof(raw_meta_header)); |
| 1324 | | if (count != sizeof(raw_meta_header)) |
| 1325 | | { |
| 1326 | | err = CHDERR_WRITE_ERROR; |
| 1327 | | goto update; |
| 1328 | | } |
| 1329 | | |
| 1330 | | /* follow that with the data */ |
| 1331 | | core_fseek(chd->file, offset + METADATA_HEADER_SIZE, SEEK_SET); |
| 1332 | | count = core_fwrite(chd->file, inputbuf, inputlen); |
| 1333 | | if (count != inputlen) |
| 1334 | | { |
| 1335 | | err = CHDERR_WRITE_ERROR; |
| 1336 | | goto update; |
| 1337 | | } |
| 1338 | | |
| 1339 | | /* set the previous entry to point to us */ |
| 1340 | | err = metadata_set_previous_next(chd, metaentry.prev, offset); |
| 1341 | | |
| 1342 | | update: |
| 1343 | | /* update the hash */ |
| 1344 | | if (metadata_compute_hash(chd, chd->header.rawsha1, chd->header.sha1) == CHDERR_NONE) |
| 1345 | | err = header_write(chd->file, &chd->header); |
| 1346 | | return err; |
| 1001 | return CHDERR_NONE; |
| 1347 | 1002 | } |
| 1348 | 1003 | |
| 1349 | 1004 | |
| 1350 | | /*------------------------------------------------- |
| 1351 | | chd_clone_metadata - clone the metadata from |
| 1352 | | one CHD to a second |
| 1353 | | -------------------------------------------------*/ |
| 1005 | //------------------------------------------------- |
| 1006 | // write_bytes - write to the CHD at a byte level, |
| 1007 | // using the cache to handle partial hunks |
| 1008 | //------------------------------------------------- |
| 1354 | 1009 | |
| 1355 | | chd_error chd_clone_metadata(chd_file *source, chd_file *dest) |
| 1010 | chd_error chd_file::write_bytes(UINT64 offset, const void *buffer, UINT32 bytes) |
| 1356 | 1011 | { |
| 1357 | | UINT32 metatag, metasize, metaindex; |
| 1358 | | UINT8 metabuffer[1024]; |
| 1359 | | UINT8 metaflags; |
| 1360 | | chd_error err; |
| 1361 | | |
| 1362 | | /* clone the metadata */ |
| 1363 | | for (metaindex = 0; ; metaindex++) |
| 1012 | // iterate over hunks |
| 1013 | UINT32 first_hunk = offset / m_hunkbytes; |
| 1014 | UINT32 last_hunk = (offset + bytes - 1) / m_hunkbytes; |
| 1015 | const UINT8 *source = reinterpret_cast<const UINT8 *>(buffer); |
| 1016 | for (UINT32 curhunk = first_hunk; curhunk <= last_hunk; curhunk++) |
| 1364 | 1017 | { |
| 1365 | | /* fetch the next piece of metadata */ |
| 1366 | | err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, metabuffer, sizeof(metabuffer), &metasize, &metatag, &metaflags); |
| 1367 | | if (err != CHDERR_NONE) |
| 1368 | | { |
| 1369 | | if (err == CHDERR_METADATA_NOT_FOUND) |
| 1370 | | err = CHDERR_NONE; |
| 1371 | | break; |
| 1372 | | } |
| 1373 | | |
| 1374 | | /* if that fit, just write it back from the temporary buffer */ |
| 1375 | | if (metasize <= sizeof(metabuffer)) |
| 1376 | | { |
| 1377 | | /* write it to the target */ |
| 1378 | | err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, metabuffer, metasize, metaflags); |
| 1379 | | if (err != CHDERR_NONE) |
| 1380 | | break; |
| 1381 | | } |
| 1382 | | |
| 1383 | | /* otherwise, allocate a bigger temporary buffer */ |
| 1018 | // determine start/end boundaries |
| 1019 | UINT32 startoffs = (curhunk == first_hunk) ? (offset % m_hunkbytes) : 0; |
| 1020 | UINT32 endoffs = (curhunk == last_hunk) ? ((offset + bytes - 1) % m_hunkbytes) : (m_hunkbytes - 1); |
| 1021 | |
| 1022 | // if it's a full block, just write directly to disk unless it's the cached hunk |
| 1023 | chd_error err = CHDERR_NONE; |
| 1024 | if (startoffs == 0 && endoffs == m_hunkbytes - 1 && curhunk != m_cachehunk) |
| 1025 | err = write_hunk(curhunk, source); |
| 1026 | |
| 1027 | // otherwise, write from the cache |
| 1384 | 1028 | else |
| 1385 | 1029 | { |
| 1386 | | UINT8 *allocbuffer = (UINT8 *)malloc(metasize); |
| 1387 | | if (allocbuffer == NULL) |
| 1030 | if (curhunk != m_cachehunk) |
| 1388 | 1031 | { |
| 1389 | | err = CHDERR_OUT_OF_MEMORY; |
| 1390 | | break; |
| 1032 | err = read_hunk(curhunk, m_cache); |
| 1033 | if (err != CHDERR_NONE) |
| 1034 | return err; |
| 1035 | m_cachehunk = curhunk; |
| 1391 | 1036 | } |
| 1392 | | |
| 1393 | | /* re-read the whole thing */ |
| 1394 | | err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, allocbuffer, metasize, &metasize, &metatag, &metaflags); |
| 1395 | | if (err != CHDERR_NONE) |
| 1396 | | { |
| 1397 | | free(allocbuffer); |
| 1398 | | break; |
| 1399 | | } |
| 1400 | | |
| 1401 | | /* write it to the target */ |
| 1402 | | err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, allocbuffer, metasize, metaflags); |
| 1403 | | free(allocbuffer); |
| 1404 | | if (err != CHDERR_NONE) |
| 1405 | | break; |
| 1037 | memcpy(&m_cache[startoffs], source, endoffs + 1 - startoffs); |
| 1038 | err = write_hunk(curhunk, m_cache); |
| 1406 | 1039 | } |
| 1040 | |
| 1041 | // handle errors and advance |
| 1042 | if (err != CHDERR_NONE) |
| 1043 | return err; |
| 1044 | source += endoffs + 1 - startoffs; |
| 1407 | 1045 | } |
| 1408 | | return err; |
| 1046 | return CHDERR_NONE; |
| 1409 | 1047 | } |
| 1410 | 1048 | |
| 1411 | 1049 | |
| 1050 | //------------------------------------------------- |
| 1051 | // read_metadata - read the indexed metadata |
| 1052 | // of the given type |
| 1053 | //------------------------------------------------- |
| 1412 | 1054 | |
| 1413 | | /*************************************************************************** |
| 1414 | | COMPRESSION MANAGEMENT |
| 1415 | | ***************************************************************************/ |
| 1416 | | |
| 1417 | | /*------------------------------------------------- |
| 1418 | | chd_compress_begin - begin compressing data |
| 1419 | | into a CHD |
| 1420 | | -------------------------------------------------*/ |
| 1421 | | |
| 1422 | | chd_error chd_compress_begin(chd_file *chd) |
| 1055 | chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, astring &output) |
| 1423 | 1056 | { |
| 1424 | | chd_error err; |
| 1057 | // wrap this for clean reporting |
| 1058 | try |
| 1059 | { |
| 1060 | // if we didn't find it, just return |
| 1061 | metadata_entry metaentry; |
| 1062 | if (!metadata_find(searchtag, searchindex, metaentry)) |
| 1063 | throw CHDERR_METADATA_NOT_FOUND; |
| 1425 | 1064 | |
| 1426 | | /* verify parameters */ |
| 1427 | | if (chd == NULL) |
| 1428 | | return CHDERR_INVALID_PARAMETER; |
| 1429 | | |
| 1430 | | /* wait for any pending async operations */ |
| 1431 | | wait_for_pending_async(chd); |
| 1432 | | |
| 1433 | | /* mark the CHD writeable and write the updated header */ |
| 1434 | | chd->header.flags |= CHDFLAGS_IS_WRITEABLE; |
| 1435 | | err = header_write(chd->file, &chd->header); |
| 1436 | | if (err != CHDERR_NONE) |
| 1065 | // read the metadata |
| 1066 | file_read(metaentry.offset + METADATA_HEADER_SIZE, output.stringbuffer(metaentry.length), metaentry.length); |
| 1067 | return CHDERR_NONE; |
| 1068 | } |
| 1069 | |
| 1070 | // just return errors |
| 1071 | catch (chd_error &err) |
| 1072 | { |
| 1437 | 1073 | return err; |
| 1438 | | |
| 1439 | | /* create CRC maps for the new CHD and the parent */ |
| 1440 | | crcmap_init(chd, FALSE); |
| 1441 | | if (chd->parent != NULL) |
| 1442 | | crcmap_init(chd->parent, TRUE); |
| 1443 | | |
| 1444 | | /* init the MD5/SHA1 computations */ |
| 1445 | | MD5Init(&chd->compmd5); |
| 1446 | | sha1_init(&chd->compsha1); |
| 1447 | | chd->compressing = TRUE; |
| 1448 | | chd->comphunk = 0; |
| 1449 | | |
| 1450 | | return CHDERR_NONE; |
| 1074 | } |
| 1451 | 1075 | } |
| 1452 | 1076 | |
| 1453 | | |
| 1454 | | /*------------------------------------------------- |
| 1455 | | chd_compress_hunk - append data to a CHD |
| 1456 | | that is being compressed |
| 1457 | | -------------------------------------------------*/ |
| 1458 | | |
| 1459 | | chd_error chd_compress_hunk(chd_file *chd, const void *data, double *curratio, int is_half_hunk) |
| 1077 | chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output) |
| 1460 | 1078 | { |
| 1461 | | UINT32 thishunk = chd->comphunk++; |
| 1462 | | UINT64 sourceoffset = (UINT64)thishunk * (UINT64)chd->header.hunkbytes; |
| 1463 | | UINT32 bytestochecksum; |
| 1464 | | const void *crcdata; |
| 1465 | | chd_error err; |
| 1466 | | |
| 1467 | | /* error if in the wrong state */ |
| 1468 | | if (!chd->compressing) |
| 1469 | | return CHDERR_INVALID_STATE; |
| 1470 | | |
| 1471 | | /* write out the hunk */ |
| 1472 | | err = hunk_write_from_memory(chd, thishunk, (const UINT8 *)data, is_half_hunk); |
| 1473 | | if (err != CHDERR_NONE) |
| 1474 | | return err; |
| 1475 | | |
| 1476 | | /* if we are lossy, then we need to use the decompressed version in */ |
| 1477 | | /* the cache as our MD5/SHA1 source */ |
| 1478 | | crcdata = (chd->codecintf->lossy || data == NULL) ? chd->cache : data; |
| 1479 | | |
| 1480 | | /* update the MD5/SHA1 */ |
| 1481 | | bytestochecksum = chd->header.hunkbytes; |
| 1482 | | |
| 1483 | | if (is_half_hunk) |
| 1079 | // wrap this for clean reporting |
| 1080 | try |
| 1484 | 1081 | { |
| 1485 | | bytestochecksum = bytestochecksum/2; |
| 1486 | | } |
| 1082 | // if we didn't find it, just return |
| 1083 | metadata_entry metaentry; |
| 1084 | if (!metadata_find(searchtag, searchindex, metaentry)) |
| 1085 | throw CHDERR_METADATA_NOT_FOUND; |
| 1487 | 1086 | |
| 1488 | | if (sourceoffset + chd->header.hunkbytes > chd->header.logicalbytes) |
| 1489 | | { |
| 1490 | | if (sourceoffset >= chd->header.logicalbytes) |
| 1491 | | bytestochecksum = 0; |
| 1492 | | else |
| 1493 | | bytestochecksum = chd->header.logicalbytes - sourceoffset; |
| 1087 | // read the metadata |
| 1088 | output.resize(metaentry.length); |
| 1089 | file_read(metaentry.offset + METADATA_HEADER_SIZE, output, metaentry.length); |
| 1090 | return CHDERR_NONE; |
| 1494 | 1091 | } |
| 1495 | | if (bytestochecksum > 0) |
| 1092 | |
| 1093 | // just return errors |
| 1094 | catch (chd_error &err) |
| 1496 | 1095 | { |
| 1497 | | MD5Update(&chd->compmd5, (const unsigned char *)crcdata, bytestochecksum); |
| 1498 | | sha1_update(&chd->compsha1, bytestochecksum, (const UINT8 *)crcdata); |
| 1096 | return err; |
| 1499 | 1097 | } |
| 1098 | } |
| 1500 | 1099 | |
| 1501 | | /* update our CRC map */ |
| 1502 | | if ((chd->map[thishunk].flags & MAP_ENTRY_FLAG_TYPE_MASK) != MAP_ENTRY_TYPE_SELF_HUNK && |
| 1503 | | (chd->map[thishunk].flags & MAP_ENTRY_FLAG_TYPE_MASK) != MAP_ENTRY_TYPE_PARENT_HUNK) |
| 1504 | | crcmap_add_entry(chd, thishunk); |
| 1100 | chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 &resultlen) |
| 1101 | { |
| 1102 | // wrap this for clean reporting |
| 1103 | try |
| 1104 | { |
| 1105 | // if we didn't find it, just return |
| 1106 | metadata_entry metaentry; |
| 1107 | if (!metadata_find(searchtag, searchindex, metaentry)) |
| 1108 | throw CHDERR_METADATA_NOT_FOUND; |
| 1505 | 1109 | |
| 1506 | | /* update the ratio */ |
| 1507 | | if (curratio != NULL) |
| 1110 | // read the metadata |
| 1111 | resultlen = metaentry.length; |
| 1112 | file_read(metaentry.offset + METADATA_HEADER_SIZE, output, MIN(outputlen, resultlen)); |
| 1113 | return CHDERR_NONE; |
| 1114 | } |
| 1115 | |
| 1116 | // just return errors |
| 1117 | catch (chd_error &err) |
| 1508 | 1118 | { |
| 1509 | | UINT64 curlength = core_fsize(chd->file); |
| 1510 | | *curratio = 1.0 - (double)curlength / (double)((UINT64)chd->comphunk * (UINT64)chd->header.hunkbytes); |
| 1119 | return err; |
| 1511 | 1120 | } |
| 1512 | | |
| 1513 | | return CHDERR_NONE; |
| 1514 | 1121 | } |
| 1515 | 1122 | |
| 1516 | | |
| 1517 | | /*------------------------------------------------- |
| 1518 | | chd_compress_finish - complete compression of |
| 1519 | | a CHD |
| 1520 | | -------------------------------------------------*/ |
| 1521 | | |
| 1522 | | chd_error chd_compress_finish(chd_file *chd, int write_protect) |
| 1123 | chd_error chd_file::read_metadata(chd_metadata_tag searchtag, UINT32 searchindex, dynamic_buffer &output, chd_metadata_tag &resulttag, UINT8 &resultflags) |
| 1523 | 1124 | { |
| 1524 | | /* error if in the wrong state */ |
| 1525 | | if (!chd->compressing) |
| 1526 | | return CHDERR_INVALID_STATE; |
| 1125 | // wrap this for clean reporting |
| 1126 | try |
| 1127 | { |
| 1128 | // if we didn't find it, just return |
| 1129 | metadata_entry metaentry; |
| 1130 | if (!metadata_find(searchtag, searchindex, metaentry)) |
| 1131 | throw CHDERR_METADATA_NOT_FOUND; |
| 1527 | 1132 | |
| 1528 | | /* compute the final MD5/SHA1 values */ |
| 1529 | | MD5Final(chd->header.md5, &chd->compmd5); |
| 1530 | | sha1_final(&chd->compsha1); |
| 1531 | | sha1_digest(&chd->compsha1, SHA1_DIGEST_SIZE, chd->header.rawsha1); |
| 1532 | | metadata_compute_hash(chd, chd->header.rawsha1, chd->header.sha1); |
| 1533 | | |
| 1534 | | /* turn off the writeable flag and re-write the header */ |
| 1535 | | if (chd->header.compression != CHDCOMPRESSION_NONE || write_protect) |
| 1536 | | chd->header.flags &= ~CHDFLAGS_IS_WRITEABLE; |
| 1537 | | chd->compressing = FALSE; |
| 1538 | | return header_write(chd->file, &chd->header); |
| 1133 | // read the metadata |
| 1134 | output.resize(metaentry.length); |
| 1135 | file_read(metaentry.offset + METADATA_HEADER_SIZE, output, metaentry.length); |
| 1136 | resulttag = metaentry.metatag; |
| 1137 | resultflags = metaentry.flags; |
| 1138 | return CHDERR_NONE; |
| 1139 | } |
| 1140 | |
| 1141 | // just return errors |
| 1142 | catch (chd_error &err) |
| 1143 | { |
| 1144 | return err; |
| 1145 | } |
| 1539 | 1146 | } |
| 1540 | 1147 | |
| 1541 | 1148 | |
| 1149 | //------------------------------------------------- |
| 1150 | // write_metadata - write the indexed metadata |
| 1151 | // of the given type |
| 1152 | //------------------------------------------------- |
| 1542 | 1153 | |
| 1543 | | /*************************************************************************** |
| 1544 | | VERIFICATION |
| 1545 | | ***************************************************************************/ |
| 1546 | | |
| 1547 | | /*------------------------------------------------- |
| 1548 | | chd_verify_begin - begin compressing data |
| 1549 | | into a CHD |
| 1550 | | -------------------------------------------------*/ |
| 1551 | | |
| 1552 | | chd_error chd_verify_begin(chd_file *chd) |
| 1154 | chd_error chd_file::write_metadata(chd_metadata_tag metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen, UINT8 flags) |
| 1553 | 1155 | { |
| 1554 | | /* verify parameters */ |
| 1555 | | if (chd == NULL) |
| 1556 | | return CHDERR_INVALID_PARAMETER; |
| 1156 | // wrap this for clean reporting |
| 1157 | try |
| 1158 | { |
| 1159 | // must write at least 1 byte and no more than 16MB |
| 1160 | if (inputlen < 1 || inputlen >= 16 * 1024 * 1024) |
| 1161 | return CHDERR_INVALID_PARAMETER; |
| 1557 | 1162 | |
| 1558 | | /* if this is a writeable file image, we can't verify */ |
| 1559 | | if (chd->header.flags & CHDFLAGS_IS_WRITEABLE) |
| 1560 | | return CHDERR_CANT_VERIFY; |
| 1163 | // find the entry if it already exists |
| 1164 | metadata_entry metaentry; |
| 1165 | bool finished = false; |
| 1166 | if (metadata_find(metatag, metaindex, metaentry)) |
| 1167 | { |
| 1168 | // if the new data fits over the old data, just overwrite |
| 1169 | if (inputlen <= metaentry.length) |
| 1170 | { |
| 1171 | file_write(metaentry.offset + METADATA_HEADER_SIZE, inputbuf, inputlen); |
| 1561 | 1172 | |
| 1562 | | /* wait for any pending async operations */ |
| 1563 | | wait_for_pending_async(chd); |
| 1173 | // if the lengths don't match, we need to update the length in our header |
| 1174 | if (inputlen != metaentry.length) |
| 1175 | { |
| 1176 | UINT8 length[3]; |
| 1177 | be_write(length, inputlen, 3); |
| 1178 | file_write(metaentry.offset + 5, length, sizeof(length)); |
| 1179 | } |
| 1180 | |
| 1181 | // indicate we did everything |
| 1182 | finished = true; |
| 1183 | } |
| 1184 | |
| 1185 | // if it doesn't fit, unlink the current entry |
| 1186 | else |
| 1187 | metadata_set_previous_next(metaentry.prev, metaentry.next); |
| 1188 | } |
| 1189 | |
| 1190 | // if not yet done, create a new entry and append |
| 1191 | if (!finished) |
| 1192 | { |
| 1193 | // now build us a new entry |
| 1194 | UINT8 raw_meta_header[METADATA_HEADER_SIZE]; |
| 1195 | be_write(&raw_meta_header[0], metatag, 4); |
| 1196 | raw_meta_header[4] = flags; |
| 1197 | be_write(&raw_meta_header[5], (inputlen & 0x00ffffff) | (flags << 24), 3); |
| 1198 | be_write(&raw_meta_header[8], 0, 8); |
| 1199 | |
| 1200 | // append the new header, then the data |
| 1201 | UINT64 offset = file_append(raw_meta_header, sizeof(raw_meta_header)); |
| 1202 | file_append(inputbuf, inputlen); |
| 1564 | 1203 | |
| 1565 | | /* init the MD5/SHA1 computations */ |
| 1566 | | MD5Init(&chd->vermd5); |
| 1567 | | sha1_init(&chd->versha1); |
| 1568 | | chd->verifying = TRUE; |
| 1569 | | chd->verhunk = 0; |
| 1204 | // set the previous entry to point to us |
| 1205 | metadata_set_previous_next(metaentry.prev, offset); |
| 1206 | } |
| 1570 | 1207 | |
| 1571 | | return CHDERR_NONE; |
| 1208 | // update the hash |
| 1209 | metadata_update_hash(); |
| 1210 | return CHDERR_NONE; |
| 1211 | } |
| 1212 | |
| 1213 | // return any errors |
| 1214 | catch (chd_error &err) |
| 1215 | { |
| 1216 | return err; |
| 1217 | } |
| 1572 | 1218 | } |
| 1573 | 1219 | |
| 1574 | 1220 | |
| 1575 | | /*------------------------------------------------- |
| 1576 | | chd_verify_hunk - verify the next hunk in |
| 1577 | | the CHD |
| 1578 | | -------------------------------------------------*/ |
| 1221 | //------------------------------------------------- |
| 1222 | // delete_metadata - remove the given metadata |
| 1223 | // from the list |
| 1224 | //------------------------------------------------- |
| 1579 | 1225 | |
| 1580 | | chd_error chd_verify_hunk(chd_file *chd) |
| 1226 | chd_error chd_file::delete_metadata(chd_metadata_tag metatag, UINT32 metaindex) |
| 1581 | 1227 | { |
| 1582 | | UINT32 thishunk = chd->verhunk++; |
| 1583 | | UINT64 hunkoffset = (UINT64)thishunk * (UINT64)chd->header.hunkbytes; |
| 1584 | | map_entry *entry; |
| 1585 | | chd_error err; |
| 1586 | | |
| 1587 | | /* error if in the wrong state */ |
| 1588 | | if (!chd->verifying) |
| 1589 | | return CHDERR_INVALID_STATE; |
| 1590 | | |
| 1591 | | /* read the hunk into the cache */ |
| 1592 | | err = hunk_read_into_cache(chd, thishunk); |
| 1593 | | if (err != CHDERR_NONE) |
| 1228 | // wrap this for clean reporting |
| 1229 | try |
| 1230 | { |
| 1231 | // find the entry |
| 1232 | metadata_entry metaentry; |
| 1233 | if (!metadata_find(metatag, metaindex, metaentry)) |
| 1234 | throw CHDERR_METADATA_NOT_FOUND; |
| 1235 | |
| 1236 | // point the previous to the next, unlinking us |
| 1237 | metadata_set_previous_next(metaentry.prev, metaentry.next); |
| 1238 | return CHDERR_NONE; |
| 1239 | } |
| 1240 | |
| 1241 | // return any errors |
| 1242 | catch (chd_error &err) |
| 1243 | { |
| 1594 | 1244 | return err; |
| 1245 | } |
| 1246 | } |
| 1595 | 1247 | |
| 1596 | | entry = &chd->map[thishunk]; |
| 1597 | 1248 | |
| 1598 | | /* update the MD5/SHA1 */ |
| 1599 | | if (hunkoffset < chd->header.logicalbytes) |
| 1600 | | { |
| 1601 | | UINT64 bytestochecksum = MIN(chd->header.hunkbytes, chd->header.logicalbytes - hunkoffset); |
| 1249 | //------------------------------------------------- |
| 1250 | // clone_all_metadata - clone the metadata from |
| 1251 | // one CHD to a second |
| 1252 | //------------------------------------------------- |
| 1602 | 1253 | |
| 1603 | | if (entry->flags & MAP_ENTRY_FLAG_HALF_HUNK) |
| 1254 | chd_error chd_file::clone_all_metadata(chd_file &source) |
| 1255 | { |
| 1256 | // wrap this for clean reporting |
| 1257 | try |
| 1258 | { |
| 1259 | // iterate over metadata entries in the source |
| 1260 | dynamic_buffer filedata; |
| 1261 | metadata_entry metaentry; |
| 1262 | for (bool has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = source.metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true)) |
| 1604 | 1263 | { |
| 1605 | | bytestochecksum /= 2; |
| 1264 | // read the metadata item |
| 1265 | filedata.resize(metaentry.length); |
| 1266 | source.file_read(metaentry.offset + METADATA_HEADER_SIZE, filedata, metaentry.length); |
| 1267 | |
| 1268 | // write it to the destination |
| 1269 | chd_error err = write_metadata(metaentry.metatag, -1, filedata, metaentry.length, metaentry.flags); |
| 1270 | if (err != CHDERR_NONE) |
| 1271 | throw err; |
| 1606 | 1272 | } |
| 1607 | | |
| 1608 | | if (bytestochecksum > 0) |
| 1609 | | { |
| 1610 | | MD5Update(&chd->vermd5, chd->cache, bytestochecksum); |
| 1611 | | sha1_update(&chd->versha1, bytestochecksum, chd->cache); |
| 1612 | | } |
| 1273 | return CHDERR_NONE; |
| 1613 | 1274 | } |
| 1614 | | |
| 1615 | | /* validate the CRC if we have one */ |
| 1616 | | if (!(entry->flags & MAP_ENTRY_FLAG_NO_CRC) && entry->crc != crc32(0, chd->cache, chd->header.hunkbytes)) |
| 1617 | | return CHDERR_DECOMPRESSION_ERROR; |
| 1618 | | |
| 1619 | | return CHDERR_NONE; |
| 1275 | |
| 1276 | // return any errors |
| 1277 | catch (chd_error &err) |
| 1278 | { |
| 1279 | return err; |
| 1280 | } |
| 1620 | 1281 | } |
| 1621 | 1282 | |
| 1622 | 1283 | |
| 1623 | | /*------------------------------------------------- |
| 1624 | | chd_verify_finish - finish verification of |
| 1625 | | the CHD |
| 1626 | | -------------------------------------------------*/ |
| 1284 | //------------------------------------------------- |
| 1285 | // compute_overall_sha1 - iterate through the |
| 1286 | // metadata and compute the overall hash of the |
| 1287 | // CHD file |
| 1288 | //------------------------------------------------- |
| 1627 | 1289 | |
| 1628 | | chd_error chd_verify_finish(chd_file *chd, chd_verify_result *result) |
| 1290 | sha1_t chd_file::compute_overall_sha1(sha1_t rawsha1) |
| 1629 | 1291 | { |
| 1630 | | /* error if in the wrong state */ |
| 1631 | | if (!chd->verifying) |
| 1632 | | return CHDERR_INVALID_STATE; |
| 1292 | // only works for v4 and above |
| 1293 | if (m_version < 4) |
| 1294 | return rawsha1; |
| 1633 | 1295 | |
| 1634 | | /* compute the final MD5 */ |
| 1635 | | MD5Final(result->md5, &chd->vermd5); |
| 1296 | // iterate over metadata |
| 1297 | dynamic_buffer filedata; |
| 1298 | dynamic_array<metadata_hash> hasharray; |
| 1299 | metadata_entry metaentry; |
| 1300 | for (bool has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry); has_data; has_data = metadata_find(CHDMETATAG_WILDCARD, 0, metaentry, true)) |
| 1301 | { |
| 1302 | // if not checksumming, continue |
| 1303 | if ((metaentry.flags & CHD_MDFLAGS_CHECKSUM) == 0) |
| 1304 | continue; |
| 1636 | 1305 | |
| 1637 | | /* compute the final SHA1 */ |
| 1638 | | sha1_final(&chd->versha1); |
| 1639 | | sha1_digest(&chd->versha1, SHA1_DIGEST_SIZE, result->rawsha1); |
| 1306 | // allocate memory and read the data |
| 1307 | filedata.resize(metaentry.length); |
| 1308 | file_read(metaentry.offset + METADATA_HEADER_SIZE, filedata, metaentry.length); |
| 1640 | 1309 | |
| 1641 | | /* compute the overall hash including metadata */ |
| 1642 | | metadata_compute_hash(chd, result->rawsha1, result->sha1); |
| 1310 | // create an entry for this metadata and add it |
| 1311 | metadata_hash hashentry; |
| 1312 | be_write(hashentry.tag, metaentry.metatag, 4); |
| 1313 | hashentry.sha1 = sha1_creator::simple(filedata, metaentry.length); |
| 1314 | hasharray.append(hashentry); |
| 1315 | } |
| 1643 | 1316 | |
| 1644 | | /* return an error */ |
| 1645 | | chd->verifying = FALSE; |
| 1646 | | return (chd->verhunk < chd->header.totalhunks) ? CHDERR_VERIFY_INCOMPLETE : CHDERR_NONE; |
| 1647 | | } |
| 1317 | // sort the array |
| 1318 | if (hasharray.count() != 0) |
| 1319 | qsort(&hasharray[0], hasharray.count(), sizeof(hasharray[0]), metadata_hash_compare); |
| 1648 | 1320 | |
| 1649 | | |
| 1650 | | |
| 1651 | | /*************************************************************************** |
| 1652 | | CODEC INTERFACES |
| 1653 | | ***************************************************************************/ |
| 1654 | | |
| 1655 | | /*------------------------------------------------- |
| 1656 | | chd_codec_config - set internal codec |
| 1657 | | parameters |
| 1658 | | -------------------------------------------------*/ |
| 1659 | | |
| 1660 | | chd_error chd_codec_config(chd_file *chd, int param, void *config) |
| 1661 | | { |
| 1662 | | /* wait for any pending async operations */ |
| 1663 | | wait_for_pending_async(chd); |
| 1664 | | |
| 1665 | | /* if the codec has a configuration callback, call through to it */ |
| 1666 | | if (chd->codecintf->config != NULL) |
| 1667 | | return (*chd->codecintf->config)(chd, param, config); |
| 1668 | | |
| 1669 | | return CHDERR_INVALID_PARAMETER; |
| 1321 | // read the raw data hash from our header and start a new SHA1 with that data |
| 1322 | sha1_creator overall_sha1; |
| 1323 | overall_sha1.append(&rawsha1, sizeof(rawsha1)); |
| 1324 | if (hasharray.count() != 0) |
| 1325 | overall_sha1.append(&hasharray[0], hasharray.count() * sizeof(hasharray[0])); |
| 1326 | return overall_sha1.finish(); |
| 1670 | 1327 | } |
| 1671 | 1328 | |
| 1672 | 1329 | |
| 1673 | | /*------------------------------------------------- |
| 1674 | | chd_get_codec_name - get the name of a |
| 1675 | | particular codec |
| 1676 | | -------------------------------------------------*/ |
| 1330 | //------------------------------------------------- |
| 1331 | // codec_config - set internal codec parameters |
| 1332 | //------------------------------------------------- |
| 1677 | 1333 | |
| 1678 | | const char *chd_get_codec_name(UINT32 codec) |
| 1334 | chd_error chd_file::codec_configure(chd_codec_type codec, int param, void *config) |
| 1679 | 1335 | { |
| 1680 | | int intfnum; |
| 1681 | | |
| 1682 | | /* look for a matching codec and return its string */ |
| 1683 | | for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) |
| 1684 | | if (codec_interfaces[intfnum].compression == codec) |
| 1685 | | return codec_interfaces[intfnum].compname; |
| 1686 | | |
| 1687 | | return "Unknown"; |
| 1336 | // wrap this for clean reporting |
| 1337 | try |
| 1338 | { |
| 1339 | // find the codec and call its configuration |
| 1340 | for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compression); codecnum++) |
| 1341 | if (m_compression[codecnum] == codec) |
| 1342 | { |
| 1343 | m_decompressor[codecnum]->configure(param, config); |
| 1344 | return CHDERR_NONE; |
| 1345 | } |
| 1346 | return CHDERR_INVALID_PARAMETER; |
| 1347 | } |
| 1348 | |
| 1349 | // return any errors |
| 1350 | catch (chd_error &err) |
| 1351 | { |
| 1352 | return err; |
| 1353 | } |
| 1688 | 1354 | } |
| 1689 | 1355 | |
| 1690 | 1356 | |
| 1357 | //------------------------------------------------- |
| 1358 | // error_string - return an error string for |
| 1359 | // the given CHD error |
| 1360 | //------------------------------------------------- |
| 1691 | 1361 | |
| 1692 | | /*************************************************************************** |
| 1693 | | INTERNAL ASYNC OPERATIONS |
| 1694 | | ***************************************************************************/ |
| 1695 | | |
| 1696 | | /*------------------------------------------------- |
| 1697 | | async_read_callback - asynchronous reading |
| 1698 | | callback |
| 1699 | | -------------------------------------------------*/ |
| 1700 | | |
| 1701 | | static void *async_read_callback(void *param, int threadid) |
| 1362 | const char *chd_file::error_string(chd_error err) |
| 1702 | 1363 | { |
| 1703 | | chd_file *chd = (chd_file *)param; |
| 1704 | | chd_error err; |
| 1705 | | |
| 1706 | | /* read the hunk into the cache */ |
| 1707 | | err = hunk_read_into_memory(chd, chd->async_hunknum, (UINT8 *)chd->async_buffer); |
| 1708 | | |
| 1709 | | /* return the error */ |
| 1710 | | return (void *)err; |
| 1364 | switch (err) |
| 1365 | { |
| 1366 | case CHDERR_NONE: return "no error"; |
| 1367 | case CHDERR_NO_INTERFACE: return "no drive interface"; |
| 1368 | case CHDERR_OUT_OF_MEMORY: return "out of memory"; |
| 1369 | case CHDERR_INVALID_FILE: return "invalid file"; |
| 1370 | case CHDERR_INVALID_PARAMETER: return "invalid parameter"; |
| 1371 | case CHDERR_INVALID_DATA: return "invalid data"; |
| 1372 | case CHDERR_FILE_NOT_FOUND: return "file not found"; |
| 1373 | case CHDERR_REQUIRES_PARENT: return "requires parent"; |
| 1374 | case CHDERR_FILE_NOT_WRITEABLE: return "file not writeable"; |
| 1375 | case CHDERR_READ_ERROR: return "read error"; |
| 1376 | case CHDERR_WRITE_ERROR: return "write error"; |
| 1377 | case CHDERR_CODEC_ERROR: return "codec error"; |
| 1378 | case CHDERR_INVALID_PARENT: return "invalid parent"; |
| 1379 | case CHDERR_HUNK_OUT_OF_RANGE: return "hunk out of range"; |
| 1380 | case CHDERR_DECOMPRESSION_ERROR: return "decompression error"; |
| 1381 | case CHDERR_COMPRESSION_ERROR: return "compression error"; |
| 1382 | case CHDERR_CANT_CREATE_FILE: return "can't create file"; |
| 1383 | case CHDERR_CANT_VERIFY: return "can't verify file"; |
| 1384 | case CHDERR_NOT_SUPPORTED: return "operation not supported"; |
| 1385 | case CHDERR_METADATA_NOT_FOUND: return "can't find metadata"; |
| 1386 | case CHDERR_INVALID_METADATA_SIZE: return "invalid metadata size"; |
| 1387 | case CHDERR_UNSUPPORTED_VERSION: return "unsupported CHD version"; |
| 1388 | case CHDERR_VERIFY_INCOMPLETE: return "incomplete verify"; |
| 1389 | case CHDERR_INVALID_METADATA: return "invalid metadata"; |
| 1390 | case CHDERR_INVALID_STATE: return "invalid state"; |
| 1391 | case CHDERR_OPERATION_PENDING: return "operation pending"; |
| 1392 | case CHDERR_UNSUPPORTED_FORMAT: return "unsupported format"; |
| 1393 | default: return "undocumented error"; |
| 1394 | } |
| 1711 | 1395 | } |
| 1712 | 1396 | |
| 1713 | 1397 | |
| 1714 | | /*------------------------------------------------- |
| 1715 | | async_write_callback - asynchronous writing |
| 1716 | | callback |
| 1717 | | -------------------------------------------------*/ |
| 1718 | 1398 | |
| 1719 | | static void *async_write_callback(void *param, int threadid) |
| 1720 | | { |
| 1721 | | chd_file *chd = (chd_file *)param; |
| 1722 | | chd_error err; |
| 1399 | //************************************************************************** |
| 1400 | // INTERNAL HELPERS |
| 1401 | //************************************************************************** |
| 1723 | 1402 | |
| 1724 | | /* write the hunk from memory */ |
| 1725 | | err = hunk_write_from_memory(chd, chd->async_hunknum, (const UINT8 *)chd->async_buffer); |
| 1403 | //------------------------------------------------- |
| 1404 | // guess_unitbytes - for older CHD formats, take |
| 1405 | // a guess at the bytes/unit based on metadata |
| 1406 | //------------------------------------------------- |
| 1726 | 1407 | |
| 1727 | | /* return the error */ |
| 1728 | | return (void *)err; |
| 1729 | | } |
| 1730 | | |
| 1731 | | |
| 1732 | | |
| 1733 | | /*************************************************************************** |
| 1734 | | INTERNAL HEADER OPERATIONS |
| 1735 | | ***************************************************************************/ |
| 1736 | | |
| 1737 | | /*------------------------------------------------- |
| 1738 | | header_validate - check the validity of a |
| 1739 | | CHD header |
| 1740 | | -------------------------------------------------*/ |
| 1741 | | |
| 1742 | | static chd_error header_validate(const chd_header *header) |
| 1408 | UINT32 chd_file::guess_unitbytes() |
| 1743 | 1409 | { |
| 1744 | | int intfnum; |
| 1410 | // look for hard disk metadata; if found, then the unit size == sector size |
| 1411 | astring metadata; |
| 1412 | int i0, i1, i2, i3; |
| 1413 | if (read_metadata(HARD_DISK_METADATA_TAG, 0, metadata) == CHDERR_NONE && sscanf(metadata, HARD_DISK_METADATA_FORMAT, &i0, &i1, &i2, &i3) == 4) |
| 1414 | return i3; |
| 1745 | 1415 | |
| 1746 | | /* require a valid version */ |
| 1747 | | if (header->version == 0 || header->version > CHD_HEADER_VERSION) |
| 1748 | | return CHDERR_UNSUPPORTED_VERSION; |
| 1749 | | |
| 1750 | | /* require a valid length */ |
| 1751 | | if ((header->version == 1 && header->length != CHD_V1_HEADER_SIZE) || |
| 1752 | | (header->version == 2 && header->length != CHD_V2_HEADER_SIZE) || |
| 1753 | | (header->version == 3 && header->length != CHD_V3_HEADER_SIZE) || |
| 1754 | | (header->version == 4 && header->length != CHD_V4_HEADER_SIZE)) |
| 1755 | | return CHDERR_INVALID_PARAMETER; |
| 1756 | | |
| 1757 | | /* require valid flags */ |
| 1758 | | if (header->flags & CHDFLAGS_UNDEFINED) |
| 1759 | | return CHDERR_INVALID_PARAMETER; |
| 1760 | | |
| 1761 | | /* require a supported compression mechanism */ |
| 1762 | | for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) |
| 1763 | | if (codec_interfaces[intfnum].compression == header->compression) |
| 1764 | | break; |
| 1765 | | if (intfnum == ARRAY_LENGTH(codec_interfaces)) |
| 1766 | | return CHDERR_INVALID_PARAMETER; |
| 1767 | | |
| 1768 | | /* require a valid hunksize */ |
| 1769 | | if (header->hunkbytes == 0 || header->hunkbytes >= 65536 * 256) |
| 1770 | | return CHDERR_INVALID_PARAMETER; |
| 1771 | | |
| 1772 | | /* require a valid hunk count */ |
| 1773 | | if (header->totalhunks == 0) |
| 1774 | | return CHDERR_INVALID_PARAMETER; |
| 1775 | | |
| 1776 | | /* require a valid MD5 and/or SHA1 if we're using a parent */ |
| 1777 | | if ((header->flags & CHDFLAGS_HAS_PARENT) && memcmp(header->parentmd5, nullmd5, sizeof(nullmd5)) == 0 && memcmp(header->parentsha1, nullsha1, sizeof(nullsha1)) == 0) |
| 1778 | | return CHDERR_INVALID_PARAMETER; |
| 1779 | | |
| 1780 | | /* if we're V3 or later, the obsolete fields must be 0 */ |
| 1781 | | if (header->version >= 3 && |
| 1782 | | (header->obsolete_cylinders != 0 || header->obsolete_sectors != 0 || |
| 1783 | | header->obsolete_heads != 0 || header->obsolete_hunksize != 0)) |
| 1784 | | return CHDERR_INVALID_PARAMETER; |
| 1785 | | |
| 1786 | | /* if we're pre-V3, the obsolete fields must NOT be 0 */ |
| 1787 | | if (header->version < 3 && |
| 1788 | | (header->obsolete_cylinders == 0 || header->obsolete_sectors == 0 || |
| 1789 | | header->obsolete_heads == 0 || header->obsolete_hunksize == 0)) |
| 1790 | | return CHDERR_INVALID_PARAMETER; |
| 1791 | | |
| 1792 | | return CHDERR_NONE; |
| 1416 | // look for CD-ROM metadata; if found, then the unit size == CD frame size |
| 1417 | if (read_metadata(CDROM_OLD_METADATA_TAG, 0, metadata) == CHDERR_NONE || |
| 1418 | read_metadata(CDROM_TRACK_METADATA_TAG, 0, metadata) == CHDERR_NONE || |
| 1419 | read_metadata(CDROM_TRACK_METADATA2_TAG, 0, metadata) == CHDERR_NONE) |
| 1420 | return CD_FRAME_SIZE; |
| 1421 | |
| 1422 | // otherwise, just map 1:1 with the hunk size |
| 1423 | return m_hunkbytes; |
| 1793 | 1424 | } |
| 1794 | 1425 | |
| 1795 | 1426 | |
| 1796 | | /*------------------------------------------------- |
| 1797 | | header_read - read a CHD header into the |
| 1798 | | internal data structure |
| 1799 | | -------------------------------------------------*/ |
| 1427 | //------------------------------------------------- |
| 1428 | // parse_v3_header - parse the header from a v3 |
| 1429 | // file and configure core parameters |
| 1430 | //------------------------------------------------- |
| 1800 | 1431 | |
| 1801 | | static chd_error header_read(core_file *file, chd_header *header) |
| 1432 | void chd_file::parse_v3_header(UINT8 *rawheader, sha1_t &parentsha1) |
| 1802 | 1433 | { |
| 1803 | | UINT8 rawheader[CHD_MAX_HEADER_SIZE]; |
| 1804 | | UINT32 count; |
| 1434 | // verify header length |
| 1435 | if (be_read(&rawheader[8], 4) != V3_HEADER_SIZE) |
| 1436 | throw CHDERR_INVALID_FILE; |
| 1805 | 1437 | |
| 1806 | | /* punt if NULL */ |
| 1807 | | if (header == NULL) |
| 1808 | | return CHDERR_INVALID_PARAMETER; |
| 1438 | // extract core info |
| 1439 | m_logicalbytes = be_read(&rawheader[28], 8); |
| 1440 | m_mapoffset = 120; |
| 1441 | m_metaoffset = be_read(&rawheader[36], 8); |
| 1442 | m_hunkbytes = be_read(&rawheader[76], 4); |
| 1443 | m_hunkcount = be_read(&rawheader[24], 4); |
| 1444 | |
| 1445 | // extract parent SHA-1 |
| 1446 | UINT32 flags = be_read(&rawheader[16], 4); |
| 1447 | if ((flags & 2) && m_allow_writes) |
| 1448 | throw CHDERR_FILE_NOT_WRITEABLE; |
| 1809 | 1449 | |
| 1810 | | /* punt if invalid file */ |
| 1811 | | if (file == NULL) |
| 1812 | | return CHDERR_INVALID_FILE; |
| 1813 | | |
| 1814 | | /* seek and read */ |
| 1815 | | core_fseek(file, 0, SEEK_SET); |
| 1816 | | count = core_fread(file, rawheader, sizeof(rawheader)); |
| 1817 | | if (count != sizeof(rawheader)) |
| 1818 | | return CHDERR_READ_ERROR; |
| 1819 | | |
| 1820 | | /* verify the tag */ |
| 1821 | | if (strncmp((char *)rawheader, "MComprHD", 8) != 0) |
| 1822 | | return CHDERR_INVALID_DATA; |
| 1823 | | |
| 1824 | | /* extract the direct data */ |
| 1825 | | memset(header, 0, sizeof(*header)); |
| 1826 | | header->length = get_bigendian_uint32(&rawheader[8]); |
| 1827 | | header->version = get_bigendian_uint32(&rawheader[12]); |
| 1828 | | |
| 1829 | | /* make sure it's a version we understand */ |
| 1830 | | if (header->version == 0 || header->version > CHD_HEADER_VERSION) |
| 1831 | | return CHDERR_UNSUPPORTED_VERSION; |
| 1832 | | |
| 1833 | | /* make sure the length is expected */ |
| 1834 | | if ((header->version == 1 && header->length != CHD_V1_HEADER_SIZE) || |
| 1835 | | (header->version == 2 && header->length != CHD_V2_HEADER_SIZE) || |
| 1836 | | (header->version == 3 && header->length != CHD_V3_HEADER_SIZE) || |
| 1837 | | (header->version == 4 && header->length != CHD_V4_HEADER_SIZE)) |
| 1838 | | return CHDERR_INVALID_DATA; |
| 1839 | | |
| 1840 | | /* extract the common data */ |
| 1841 | | header->flags = get_bigendian_uint32(&rawheader[16]); |
| 1842 | | header->compression = get_bigendian_uint32(&rawheader[20]); |
| 1843 | | |
| 1844 | | /* extract the V1/V2-specific data */ |
| 1845 | | if (header->version < 3) |
| 1450 | // determine compression |
| 1451 | switch (be_read(&rawheader[20], 4)) |
| 1846 | 1452 | { |
| 1847 | | int seclen = (header->version == 1) ? CHD_V1_SECTOR_SIZE : get_bigendian_uint32(&rawheader[76]); |
| 1848 | | header->obsolete_hunksize = get_bigendian_uint32(&rawheader[24]); |
| 1849 | | header->totalhunks = get_bigendian_uint32(&rawheader[28]); |
| 1850 | | header->obsolete_cylinders = get_bigendian_uint32(&rawheader[32]); |
| 1851 | | header->obsolete_heads = get_bigendian_uint32(&rawheader[36]); |
| 1852 | | header->obsolete_sectors = get_bigendian_uint32(&rawheader[40]); |
| 1853 | | memcpy(header->md5, &rawheader[44], CHD_MD5_BYTES); |
| 1854 | | memcpy(header->parentmd5, &rawheader[60], CHD_MD5_BYTES); |
| 1855 | | header->logicalbytes = (UINT64)header->obsolete_cylinders * (UINT64)header->obsolete_heads * (UINT64)header->obsolete_sectors * (UINT64)seclen; |
| 1856 | | header->hunkbytes = seclen * header->obsolete_hunksize; |
| 1857 | | header->metaoffset = 0; |
| 1453 | case 0: m_compression[0] = CHD_CODEC_NONE; break; |
| 1454 | case 1: m_compression[0] = CHD_CODEC_ZLIB; break; |
| 1455 | case 2: m_compression[0] = CHD_CODEC_ZLIB; break; |
| 1456 | case 3: m_compression[0] = CHD_CODEC_AVHUFF; break; |
| 1457 | default: throw CHDERR_UNKNOWN_COMPRESSION; |
| 1858 | 1458 | } |
| 1459 | m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC_NONE; |
| 1460 | |
| 1461 | // describe the format |
| 1462 | m_mapoffset_offset = 0; |
| 1463 | m_metaoffset_offset = 36; |
| 1464 | m_sha1_offset = 80; |
| 1465 | m_rawsha1_offset = 0; |
| 1466 | m_parentsha1_offset = 100; |
| 1467 | |
| 1468 | // determine properties of map entries |
| 1469 | m_mapentrybytes = 16; |
| 1859 | 1470 | |
| 1860 | | /* extract the V3-specific data */ |
| 1861 | | else if (header->version == 3) |
| 1862 | | { |
| 1863 | | header->totalhunks = get_bigendian_uint32(&rawheader[24]); |
| 1864 | | header->logicalbytes = get_bigendian_uint64(&rawheader[28]); |
| 1865 | | header->metaoffset = get_bigendian_uint64(&rawheader[36]); |
| 1866 | | memcpy(header->md5, &rawheader[44], CHD_MD5_BYTES); |
| 1867 | | memcpy(header->parentmd5, &rawheader[60], CHD_MD5_BYTES); |
| 1868 | | header->hunkbytes = get_bigendian_uint32(&rawheader[76]); |
| 1869 | | memcpy(header->sha1, &rawheader[80], CHD_SHA1_BYTES); |
| 1870 | | memcpy(header->parentsha1, &rawheader[100], CHD_SHA1_BYTES); |
| 1871 | | } |
| 1872 | | |
| 1873 | | /* extract the V4-specific data */ |
| 1874 | | else |
| 1875 | | { |
| 1876 | | header->totalhunks = get_bigendian_uint32(&rawheader[24]); |
| 1877 | | header->logicalbytes = get_bigendian_uint64(&rawheader[28]); |
| 1878 | | header->metaoffset = get_bigendian_uint64(&rawheader[36]); |
| 1879 | | header->hunkbytes = get_bigendian_uint32(&rawheader[44]); |
| 1880 | | memcpy(header->sha1, &rawheader[48], CHD_SHA1_BYTES); |
| 1881 | | memcpy(header->parentsha1, &rawheader[68], CHD_SHA1_BYTES); |
| 1882 | | memcpy(header->rawsha1, &rawheader[88], CHD_SHA1_BYTES); |
| 1883 | | } |
| 1884 | | |
| 1885 | | /* guess it worked */ |
| 1886 | | return CHDERR_NONE; |
| 1471 | // extract parent SHA-1 |
| 1472 | if (flags & 1) |
| 1473 | parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]); |
| 1474 | |
| 1475 | // guess at the units based on snooping the metadata |
| 1476 | m_unitbytes = guess_unitbytes(); |
| 1477 | m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes; |
| 1887 | 1478 | } |
| 1888 | 1479 | |
| 1889 | 1480 | |
| 1890 | | /*------------------------------------------------- |
| 1891 | | header_write - write a CHD header from the |
| 1892 | | internal data structure |
| 1893 | | -------------------------------------------------*/ |
| 1481 | //------------------------------------------------- |
| 1482 | // parse_v4_header - parse the header from a v4 |
| 1483 | // file and configure core parameters |
| 1484 | //------------------------------------------------- |
| 1894 | 1485 | |
| 1895 | | static chd_error header_write(core_file *file, const chd_header *header) |
| 1486 | void chd_file::parse_v4_header(UINT8 *rawheader, sha1_t &parentsha1) |
| 1896 | 1487 | { |
| 1897 | | UINT8 rawheader[CHD_MAX_HEADER_SIZE]; |
| 1898 | | UINT32 count; |
| 1488 | // verify header length |
| 1489 | if (be_read(&rawheader[8], 4) != V4_HEADER_SIZE) |
| 1490 | throw CHDERR_INVALID_FILE; |
| 1899 | 1491 | |
| 1900 | | /* punt if NULL */ |
| 1901 | | if (header == NULL) |
| 1902 | | return CHDERR_INVALID_PARAMETER; |
| 1492 | // extract core info |
| 1493 | m_logicalbytes = be_read(&rawheader[28], 8); |
| 1494 | m_mapoffset = 108; |
| 1495 | m_metaoffset = be_read(&rawheader[36], 8); |
| 1496 | m_hunkbytes = be_read(&rawheader[44], 4); |
| 1497 | m_hunkcount = be_read(&rawheader[24], 4); |
| 1498 | |
| 1499 | // extract parent SHA-1 |
| 1500 | UINT32 flags = be_read(&rawheader[16], 4); |
| 1501 | if ((flags & 2) && m_allow_writes) |
| 1502 | throw CHDERR_FILE_NOT_WRITEABLE; |
| 1903 | 1503 | |
| 1904 | | /* punt if invalid file */ |
| 1905 | | if (file == NULL) |
| 1906 | | return CHDERR_INVALID_FILE; |
| 1504 | // determine compression |
| 1505 | switch (be_read(&rawheader[20], 4)) |
| 1506 | { |
| 1507 | case 0: m_compression[0] = CHD_CODEC_NONE; break; |
| 1508 | case 1: m_compression[0] = CHD_CODEC_ZLIB; break; |
| 1509 | case 2: m_compression[0] = CHD_CODEC_ZLIB; break; |
| 1510 | case 3: m_compression[0] = CHD_CODEC_AVHUFF; break; |
| 1511 | default: throw CHDERR_UNKNOWN_COMPRESSION; |
| 1512 | } |
| 1513 | m_compression[1] = m_compression[2] = m_compression[3] = CHD_CODEC_NONE; |
| 1514 | |
| 1515 | // describe the format |
| 1516 | m_mapoffset_offset = 0; |
| 1517 | m_metaoffset_offset = 36; |
| 1518 | m_sha1_offset = 48; |
| 1519 | m_rawsha1_offset = 88; |
| 1520 | m_parentsha1_offset = 68; |
| 1521 | |
| 1522 | // determine properties of map entries |
| 1523 | m_mapentrybytes = 16; |
| 1907 | 1524 | |
| 1908 | | /* only support writing modern headers */ |
| 1909 | | if (header->version != 4) |
| 1910 | | return CHDERR_INVALID_PARAMETER; |
| 1911 | | |
| 1912 | | /* assemble the data */ |
| 1913 | | memset(rawheader, 0, sizeof(rawheader)); |
| 1914 | | memcpy(rawheader, "MComprHD", 8); |
| 1915 | | |
| 1916 | | put_bigendian_uint32(&rawheader[8], CHD_V4_HEADER_SIZE); |
| 1917 | | put_bigendian_uint32(&rawheader[12], header->version); |
| 1918 | | put_bigendian_uint32(&rawheader[16], header->flags); |
| 1919 | | put_bigendian_uint32(&rawheader[20], header->compression); |
| 1920 | | put_bigendian_uint32(&rawheader[24], header->totalhunks); |
| 1921 | | put_bigendian_uint64(&rawheader[28], header->logicalbytes); |
| 1922 | | put_bigendian_uint64(&rawheader[36], header->metaoffset); |
| 1923 | | put_bigendian_uint32(&rawheader[44], header->hunkbytes); |
| 1924 | | memcpy(&rawheader[48], header->sha1, CHD_SHA1_BYTES); |
| 1925 | | memcpy(&rawheader[68], header->parentsha1, CHD_SHA1_BYTES); |
| 1926 | | memcpy(&rawheader[88], header->rawsha1, CHD_SHA1_BYTES); |
| 1927 | | |
| 1928 | | /* seek and write */ |
| 1929 | | core_fseek(file, 0, SEEK_SET); |
| 1930 | | count = core_fwrite(file, rawheader, CHD_V4_HEADER_SIZE); |
| 1931 | | if (count != CHD_V4_HEADER_SIZE) |
| 1932 | | return CHDERR_WRITE_ERROR; |
| 1933 | | |
| 1934 | | return CHDERR_NONE; |
| 1525 | // extract parent SHA-1 |
| 1526 | if (flags & 1) |
| 1527 | parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]); |
| 1528 | |
| 1529 | // guess at the units based on snooping the metadata |
| 1530 | m_unitbytes = guess_unitbytes(); |
| 1531 | m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes; |
| 1935 | 1532 | } |
| 1936 | 1533 | |
| 1937 | 1534 | |
| 1535 | //------------------------------------------------- |
| 1536 | // parse_v5_header - read the header from a v5 |
| 1537 | // file and configure core parameters |
| 1538 | //------------------------------------------------- |
| 1938 | 1539 | |
| 1939 | | /*************************************************************************** |
| 1940 | | INTERNAL HUNK READ/WRITE |
| 1941 | | ***************************************************************************/ |
| 1942 | | |
| 1943 | | /*------------------------------------------------- |
| 1944 | | hunk_read_into_cache - read a hunk into |
| 1945 | | the CHD's hunk cache |
| 1946 | | -------------------------------------------------*/ |
| 1947 | | |
| 1948 | | static chd_error hunk_read_into_cache(chd_file *chd, UINT32 hunknum) |
| 1540 | void chd_file::parse_v5_header(UINT8 *rawheader, sha1_t &parentsha1) |
| 1949 | 1541 | { |
| 1950 | | chd_error err; |
| 1542 | // verify header length |
| 1543 | if (be_read(&rawheader[8], 4) != V5_HEADER_SIZE) |
| 1544 | throw CHDERR_INVALID_FILE; |
| 1951 | 1545 | |
| 1952 | | /* track the max */ |
| 1953 | | if (hunknum > chd->maxhunk) |
| 1954 | | chd->maxhunk = hunknum; |
| 1546 | // extract core info |
| 1547 | m_logicalbytes = be_read(&rawheader[32], 8); |
| 1548 | m_mapoffset = be_read(&rawheader[40], 8); |
| 1549 | m_metaoffset = be_read(&rawheader[48], 8); |
| 1550 | m_hunkbytes = be_read(&rawheader[56], 4); |
| 1551 | m_hunkcount = (m_logicalbytes + m_hunkbytes - 1) / m_hunkbytes; |
| 1552 | m_unitbytes = be_read(&rawheader[60], 4); |
| 1553 | m_unitcount = (m_logicalbytes + m_unitbytes - 1) / m_unitbytes; |
| 1554 | |
| 1555 | // determine compression |
| 1556 | m_compression[0] = be_read(&rawheader[16], 4); |
| 1557 | m_compression[1] = be_read(&rawheader[20], 4); |
| 1558 | m_compression[2] = be_read(&rawheader[24], 4); |
| 1559 | m_compression[3] = be_read(&rawheader[28], 4); |
| 1560 | |
| 1561 | // describe the format |
| 1562 | m_mapoffset_offset = 40; |
| 1563 | m_metaoffset_offset = 48; |
| 1564 | m_sha1_offset = 84; |
| 1565 | m_rawsha1_offset = 64; |
| 1566 | m_parentsha1_offset = 104; |
| 1567 | |
| 1568 | // determine properties of map entries |
| 1569 | m_mapentrybytes = compressed() ? 12 : 4; |
| 1955 | 1570 | |
| 1956 | | /* if we're already in the cache, we're done */ |
| 1957 | | if (chd->cachehunk == hunknum) |
| 1958 | | return CHDERR_NONE; |
| 1959 | | chd->cachehunk = ~0; |
| 1960 | | |
| 1961 | | /* otherwise, read the data */ |
| 1962 | | err = hunk_read_into_memory(chd, hunknum, chd->cache); |
| 1963 | | if (err != CHDERR_NONE) |
| 1964 | | return err; |
| 1965 | | |
| 1966 | | /* mark the hunk successfully cached in */ |
| 1967 | | chd->cachehunk = hunknum; |
| 1968 | | return CHDERR_NONE; |
| 1571 | // extract parent SHA-1 |
| 1572 | parentsha1 = be_read_sha1(&rawheader[m_parentsha1_offset]); |
| 1969 | 1573 | } |
| 1970 | 1574 | |
| 1971 | 1575 | |
| 1972 | | /*------------------------------------------------- |
| 1973 | | hunk_read_into_memory - read a hunk into |
| 1974 | | memory at the given location |
| 1975 | | -------------------------------------------------*/ |
| 1576 | //------------------------------------------------- |
| 1577 | // compress_v5_map - compress the v5 map and |
| 1578 | // write it to the end of the file |
| 1579 | //------------------------------------------------- |
| 1976 | 1580 | |
| 1977 | | static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *dest) |
| 1581 | chd_error chd_file::compress_v5_map() |
| 1978 | 1582 | { |
| 1979 | | map_entry *entry = &chd->map[hunknum]; |
| 1980 | | chd_error err; |
| 1981 | | UINT32 bytes; |
| 1982 | | |
| 1983 | | /* return an error if out of range */ |
| 1984 | | if (hunknum >= chd->header.totalhunks) |
| 1985 | | return CHDERR_HUNK_OUT_OF_RANGE; |
| 1986 | | |
| 1987 | | /* switch off the entry type */ |
| 1988 | | switch (entry->flags & MAP_ENTRY_FLAG_TYPE_MASK) |
| 1583 | try |
| 1989 | 1584 | { |
| 1990 | | /* compressed data */ |
| 1991 | | case MAP_ENTRY_TYPE_COMPRESSED: |
| 1585 | // first get a CRC-16 of the original rawmap |
| 1586 | crc16_t mapcrc = crc16_creator::simple(m_rawmap, m_hunkcount * 12); |
| 1992 | 1587 | |
| 1993 | | /* read it into the decompression buffer */ |
| 1994 | | core_fseek(chd->file, entry->offset, SEEK_SET); |
| 1995 | | bytes = core_fread(chd->file, chd->compressed, entry->length); |
| 1996 | | if (bytes != entry->length) |
| 1997 | | return CHDERR_READ_ERROR; |
| 1588 | // create a buffer to hold the RLE data |
| 1589 | dynamic_buffer compression_rle(m_hunkcount); |
| 1590 | UINT8 *dest = compression_rle; |
| 1591 | |
| 1592 | // use a huffman encoder for 16 different codes, maximum length is 8 bits |
| 1593 | huffman_encoder<16, 8> encoder; |
| 1594 | encoder.histo_reset(); |
| 1998 | 1595 | |
| 1999 | | /* now decompress using the codec */ |
| 2000 | | err = CHDERR_NONE; |
| 2001 | | if (chd->codecintf->decompress != NULL) |
| 2002 | | err = (*chd->codecintf->decompress)(chd, entry->length, dest); |
| 2003 | | if (err != CHDERR_NONE) |
| 2004 | | return err; |
| 2005 | | break; |
| 2006 | | |
| 2007 | | /* uncompressed data */ |
| 2008 | | case MAP_ENTRY_TYPE_UNCOMPRESSED: |
| 2009 | | core_fseek(chd->file, entry->offset, SEEK_SET); |
| 2010 | | bytes = core_fread(chd->file, dest, chd->header.hunkbytes); |
| 2011 | | if (bytes != chd->header.hunkbytes) |
| 2012 | | return CHDERR_READ_ERROR; |
| 2013 | | break; |
| 2014 | | |
| 2015 | | /* mini-compressed data */ |
| 2016 | | case MAP_ENTRY_TYPE_MINI: |
| 2017 | | put_bigendian_uint64(&dest[0], entry->offset); |
| 2018 | | for (bytes = 8; bytes < chd->header.hunkbytes; bytes++) |
| 2019 | | dest[bytes] = dest[bytes - 8]; |
| 2020 | | break; |
| 2021 | | |
| 2022 | | /* self-referenced data */ |
| 2023 | | case MAP_ENTRY_TYPE_SELF_HUNK: |
| 2024 | | if (chd->cachehunk == entry->offset && dest == chd->cache) |
| 2025 | | break; |
| 2026 | | return hunk_read_into_memory(chd, entry->offset, dest); |
| 2027 | | |
| 2028 | | /* parent-referenced data */ |
| 2029 | | case MAP_ENTRY_TYPE_PARENT_HUNK: |
| 2030 | | err = hunk_read_into_memory(chd->parent, entry->offset, dest); |
| 2031 | | if (err != CHDERR_NONE) |
| 2032 | | return err; |
| 2033 | | break; |
| 2034 | | |
| 2035 | | case MAP_ENTRY_TYPE_2ND_COMPRESSED: |
| 2036 | | /* read it into the decompression buffer */ |
| 2037 | | core_fseek(chd->file, entry->offset, SEEK_SET); |
| 2038 | | bytes = core_fread(chd->file, chd->compressed, entry->length); |
| 2039 | | if (bytes != entry->length) |
| 2040 | | return CHDERR_READ_ERROR; |
| 2041 | | |
| 2042 | | /* now decompress using the codec */ |
| 2043 | | err = CHDERR_NONE; |
| 2044 | | if (chd->codecintf->secondary_decompress != NULL) |
| 2045 | | err = (*chd->codecintf->secondary_decompress)(chd, entry->length, dest); |
| 2046 | | if (err != CHDERR_NONE) |
| 2047 | | return err; |
| 2048 | | break; |
| 2049 | | |
| 2050 | | } |
| 2051 | | return CHDERR_NONE; |
| 2052 | | } |
| 2053 | | |
| 2054 | | |
| 2055 | | /*------------------------------------------------- |
| 2056 | | hunk_write_from_memory - write a hunk from |
| 2057 | | memory into a CHD |
| 2058 | | -------------------------------------------------*/ |
| 2059 | | |
| 2060 | | |
| 2061 | | static chd_error hunk_write_from_memory(chd_file *chd, UINT32 hunknum, const UINT8 *src, int is_half_hunk) |
| 2062 | | { |
| 2063 | | map_entry *entry = &chd->map[hunknum]; |
| 2064 | | map_entry newentry; |
| 2065 | | UINT8 fileentry[MAP_ENTRY_SIZE]; |
| 2066 | | const void *data = src; |
| 2067 | | UINT32 bytes = 0, match; |
| 2068 | | chd_error err; |
| 2069 | | bool is_likely_cd = false; |
| 2070 | | int strategy = 0; |
| 2071 | | |
| 2072 | | /* track the max */ |
| 2073 | | if (hunknum > chd->maxhunk) |
| 2074 | | chd->maxhunk = hunknum; |
| 2075 | | |
| 2076 | | /* first compute the CRC of the original data */ |
| 2077 | | newentry.flags = 0; |
| 2078 | | newentry.length = 0; |
| 2079 | | newentry.offset = 0; |
| 2080 | | newentry.crc = 0; |
| 2081 | | if (src != NULL) |
| 2082 | | newentry.crc = crc32(0, &src[0], chd->header.hunkbytes); |
| 2083 | | |
| 2084 | | /* if we're not a lossy codec, compute the CRC and look for matches */ |
| 2085 | | if (!chd->codecintf->lossy && src != NULL) |
| 2086 | | { |
| 2087 | | /* some extra stuff for zlib+ compression */ |
| 2088 | | if (chd->header.compression >= CHDCOMPRESSION_ZLIB_PLUS) |
| 1596 | // RLE-compress the compression type since we expect runs of the same |
| 1597 | UINT32 max_self = 0; |
| 1598 | UINT32 last_self = 0; |
| 1599 | UINT64 max_parent = 0; |
| 1600 | UINT64 last_parent = 0; |
| 1601 | UINT32 max_complen = 0; |
| 1602 | UINT8 lastcomp = 0; |
| 1603 | int count = 0; |
| 1604 | for (int hunknum = 0; hunknum < m_hunkcount; hunknum++) |
| 2089 | 1605 | { |
| 2090 | | /* see if we can mini-compress first */ |
| 2091 | | for (bytes = 8; bytes < chd->header.hunkbytes; bytes++) |
| 2092 | | if (src[bytes] != src[bytes - 8]) |
| 2093 | | break; |
| 2094 | | |
| 2095 | | /* if so, we don't need to write any data */ |
| 2096 | | if (bytes == chd->header.hunkbytes) |
| 1606 | UINT8 curcomp = m_rawmap[hunknum * 12 + 0]; |
| 1607 | |
| 1608 | // promote self block references to more compact forms |
| 1609 | if (curcomp == COMPRESSION_SELF) |
| 2097 | 1610 | { |
| 2098 | | newentry.offset = get_bigendian_uint64(&src[0]); |
| 2099 | | newentry.length = 0; |
| 2100 | | newentry.flags = MAP_ENTRY_TYPE_MINI; |
| 2101 | | goto write_entry; |
| 1611 | UINT32 refhunk = be_read(&m_rawmap[hunknum * 12 + 4], 6); |
| 1612 | if (refhunk == last_self) |
| 1613 | curcomp = COMPRESSION_SELF_0; |
| 1614 | else if (refhunk == last_self + 1) |
| 1615 | curcomp = COMPRESSION_SELF_1; |
| 1616 | else |
| 1617 | max_self = MAX(max_self, refhunk); |
| 1618 | last_self = refhunk; |
| 2102 | 1619 | } |
| 2103 | 1620 | |
| 2104 | | /* otherwise, see if we can find a match in the current file */ |
| 2105 | | match = crcmap_find_hunk(chd, hunknum, newentry.crc, &src[0]); |
| 2106 | | if (match != NO_MATCH) |
| 1621 | // promote parent block references to more compact forms |
| 1622 | else if (curcomp == COMPRESSION_PARENT) |
| 2107 | 1623 | { |
| 2108 | | newentry.offset = match; |
| 2109 | | newentry.length = 0; |
| 2110 | | newentry.flags = MAP_ENTRY_TYPE_SELF_HUNK; |
| 2111 | | goto write_entry; |
| 1624 | UINT32 refunit = be_read(&m_rawmap[hunknum * 12 + 4], 6); |
| 1625 | if (refunit == (UINT64(hunknum) * UINT64(m_hunkbytes)) / m_unitbytes) |
| 1626 | curcomp = COMPRESSION_PARENT_SELF; |
| 1627 | else if (refunit == last_parent) |
| 1628 | curcomp = COMPRESSION_PARENT_0; |
| 1629 | else if (refunit == last_parent + m_hunkbytes / m_unitbytes) |
| 1630 | curcomp = COMPRESSION_PARENT_1; |
| 1631 | else |
| 1632 | max_parent = MAX(max_parent, refunit); |
| 1633 | last_parent = refunit; |
| 2112 | 1634 | } |
| 2113 | | |
| 2114 | | /* if we have a parent, see if we can find a match in there */ |
| 2115 | | if (chd->header.flags & CHDFLAGS_HAS_PARENT) |
| 1635 | |
| 1636 | // track maximum compressed length |
| 1637 | else if (curcomp >= COMPRESSION_TYPE_0 && curcomp <= COMPRESSION_TYPE_3) |
| 1638 | max_complen = MAX(max_complen, be_read(&m_rawmap[hunknum * 12 + 1], 3)); |
| 1639 | |
| 1640 | // track repeats |
| 1641 | if (curcomp == lastcomp) |
| 1642 | count++; |
| 1643 | |
| 1644 | // if no repeat, or we're at the end, flush it |
| 1645 | if (curcomp != lastcomp || hunknum == m_hunkcount - 1) |
| 2116 | 1646 | { |
| 2117 | | match = crcmap_find_hunk(chd->parent, ~0, newentry.crc, &src[0]); |
| 2118 | | if (match != NO_MATCH) |
| 1647 | while (count != 0) |
| 2119 | 1648 | { |
| 2120 | | newentry.offset = match; |
| 2121 | | newentry.length = 0; |
| 2122 | | newentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK; |
| 2123 | | goto write_entry; |
| 1649 | if (count < 3) |
| 1650 | encoder.histo_one(*dest++ = lastcomp), count--; |
| 1651 | else if (count <= 3+15) |
| 1652 | { |
| 1653 | encoder.histo_one(*dest++ = COMPRESSION_RLE_SMALL); |
| 1654 | encoder.histo_one(*dest++ = count - 3); |
| 1655 | count = 0; |
| 1656 | } |
| 1657 | else |
| 1658 | { |
| 1659 | int this_count = MIN(count, 3+16+255); |
| 1660 | encoder.histo_one(*dest++ = COMPRESSION_RLE_LARGE); |
| 1661 | encoder.histo_one(*dest++ = (this_count - 3 - 16) >> 4); |
| 1662 | encoder.histo_one(*dest++ = (this_count - 3 - 16) & 15); |
| 1663 | count -= this_count; |
| 1664 | } |
| 2124 | 1665 | } |
| 1666 | if (curcomp != lastcomp) |
| 1667 | encoder.histo_one(*dest++ = lastcomp = curcomp); |
| 2125 | 1668 | } |
| 2126 | 1669 | } |
| 2127 | | } |
| 1670 | |
| 1671 | // compute a tree and export it to the buffer |
| 1672 | dynamic_buffer compressed(m_hunkcount * 6); |
| 1673 | bitstream_out bitbuf(&compressed[16], compressed.count() - 16); |
| 1674 | huffman_error err = encoder.compute_tree_from_histo(); |
| 1675 | if (err != HUFFERR_NONE) |
| 1676 | throw CHDERR_COMPRESSION_ERROR; |
| 1677 | err = encoder.export_tree_rle(bitbuf); |
| 1678 | if (err != HUFFERR_NONE) |
| 1679 | throw CHDERR_COMPRESSION_ERROR; |
| 2128 | 1680 | |
| 2129 | | if (chd->codecintf->secondary_compress != NULL) |
| 2130 | | { |
| 2131 | | if (chd->header.hunkbytes == (CD_MAX_SECTOR_DATA+CD_MAX_SUBCODE_DATA) * CD_FRAMES_PER_HUNK) |
| 2132 | | is_likely_cd = true; |
| 1681 | // encode the data |
| 1682 | for (UINT8 *src = compression_rle; src < dest; src++) |
| 1683 | encoder.encode_one(bitbuf, *src); |
| 1684 | |
| 1685 | // determine the number of bits we need to hold the a length |
| 1686 | // and a hunk index |
| 1687 | UINT8 lengthbits = bits_for_value(max_complen); |
| 1688 | UINT8 selfbits = bits_for_value(max_self); |
| 1689 | UINT8 parentbits = bits_for_value(max_parent); |
| 2133 | 1690 | |
| 2134 | | if (is_likely_cd) |
| 1691 | // for each compression type, output the relevant data |
| 1692 | lastcomp = 0; |
| 1693 | count = 0; |
| 1694 | UINT8 *src = compression_rle; |
| 1695 | UINT64 firstoffs = 0; |
| 1696 | for (int hunknum = 0; hunknum < m_hunkcount; hunknum++) |
| 2135 | 1697 | { |
| 2136 | | int offset = 0; |
| 2137 | | for (int frames=0;frames<CD_FRAMES_PER_HUNK;frames++) |
| 1698 | UINT8 *rawmap = &m_rawmap[hunknum * 12]; |
| 1699 | UINT32 length = be_read(&rawmap[1], 3); |
| 1700 | UINT64 offset = be_read(&rawmap[4], 6); |
| 1701 | UINT16 crc = be_read(&rawmap[10], 2); |
| 1702 | |
| 1703 | // if no count remaining, fetch the next entry |
| 1704 | if (count == 0) |
| 2138 | 1705 | { |
| 2139 | | int secoff; |
| 2140 | | for (secoff=0;secoff<CD_MAX_SECTOR_DATA;secoff++) |
| 2141 | | { |
| 2142 | | offset++; |
| 2143 | | } |
| 2144 | | for (secoff=0;secoff<CD_MAX_SUBCODE_DATA;secoff++) |
| 2145 | | { |
| 2146 | | if (src[offset]!=0x00) |
| 2147 | | is_likely_cd = false; |
| 2148 | | |
| 2149 | | offset++; |
| 2150 | | } |
| 1706 | UINT8 val = *src++; |
| 1707 | if (val == COMPRESSION_RLE_SMALL) |
| 1708 | count = 2 + *src++; |
| 1709 | else if (val == COMPRESSION_RLE_LARGE) |
| 1710 | count = 2 + 16 + (*src++ << 4), count += *src++; |
| 1711 | else |
| 1712 | lastcomp = val; |
| 2151 | 1713 | } |
| 2152 | | } |
| 2153 | | } |
| 2154 | | |
| 2155 | | if (is_likely_cd) |
| 2156 | | { |
| 2157 | | err = CHDERR_COMPRESSION_ERROR; |
| 2158 | | |
| 2159 | | UINT8* tempram = (UINT8 *)malloc(chd->header.hunkbytes); |
| 2160 | | UINT32 tempbytes = 0; |
| 2161 | | chd_error temperror = err; |
| 2162 | | int tempstrategy = 0; |
| 2163 | | |
| 2164 | | /* try strategy 0 - zlib */ |
| 2165 | | if (chd->codecintf->compress != NULL) |
| 2166 | | { |
| 2167 | | err = (*chd->codecintf->compress)(chd, src, &bytes); |
| 2168 | | |
| 2169 | | /* store current results and errors */ |
| 2170 | | memcpy(tempram, chd->compressed, bytes); |
| 2171 | | tempbytes = bytes; |
| 2172 | | temperror = err; |
| 2173 | | tempstrategy = 0; |
| 2174 | | } |
| 2175 | | |
| 2176 | | /* try strategy 1 - flac */ |
| 2177 | | if (chd->codecintf->secondary_compress != NULL) |
| 2178 | | { |
| 2179 | | strategy = 1; |
| 2180 | | |
| 2181 | | err = (*chd->codecintf->secondary_compress)(chd, src, &bytes); |
| 2182 | | |
| 2183 | | /* check against previous compression attempt if that was successful */ |
| 2184 | | if (temperror == CHDERR_NONE) |
| 1714 | else |
| 1715 | count--; |
| 1716 | |
| 1717 | // output additional data needed for this entry |
| 1718 | switch (lastcomp) |
| 2185 | 1719 | { |
| 2186 | | /* if the previous compression was better, restore that ... */ |
| 2187 | | if (bytes>=tempbytes) |
| 2188 | | { |
| 2189 | | strategy = tempstrategy; |
| 2190 | | memcpy(chd->compressed, tempram, tempbytes); |
| 2191 | | bytes = tempbytes; |
| 2192 | | err = temperror; |
| 2193 | | } |
| 1720 | case COMPRESSION_TYPE_0: |
| 1721 | case COMPRESSION_TYPE_1: |
| 1722 | case COMPRESSION_TYPE_2: |
| 1723 | case COMPRESSION_TYPE_3: |
| 1724 | assert(length < (1 << lengthbits)); |
| 1725 | bitbuf.write(length, lengthbits); |
| 1726 | bitbuf.write(crc, 16); |
| 1727 | if (firstoffs == 0) |
| 1728 | firstoffs = offset; |
| 1729 | break; |
| 1730 | |
| 1731 | case COMPRESSION_NONE: |
| 1732 | bitbuf.write(crc, 16); |
| 1733 | if (firstoffs == 0) |
| 1734 | firstoffs = offset; |
| 1735 | break; |
| 1736 | |
| 1737 | case COMPRESSION_SELF: |
| 1738 | assert(offset < (UINT64(1) << selfbits)); |
| 1739 | bitbuf.write(offset, selfbits); |
| 1740 | break; |
| 1741 | |
| 1742 | case COMPRESSION_PARENT: |
| 1743 | assert(offset < (UINT64(1) << parentbits)); |
| 1744 | bitbuf.write(offset, parentbits); |
| 1745 | break; |
| 1746 | |
| 1747 | case COMPRESSION_SELF_0: |
| 1748 | case COMPRESSION_SELF_1: |
| 1749 | case COMPRESSION_PARENT_SELF: |
| 1750 | case COMPRESSION_PARENT_0: |
| 1751 | case COMPRESSION_PARENT_1: |
| 1752 | break; |
| 2194 | 1753 | } |
| 2195 | 1754 | } |
| 2196 | | |
| 2197 | | free(tempram); |
| 1755 | |
| 1756 | // write the map header |
| 1757 | UINT32 complen = bitbuf.flush(); |
| 1758 | assert(!bitbuf.overflow()); |
| 1759 | be_write(&compressed[0], complen, 4); |
| 1760 | be_write(&compressed[4], firstoffs, 6); |
| 1761 | be_write(&compressed[10], mapcrc, 2); |
| 1762 | compressed[12] = lengthbits; |
| 1763 | compressed[13] = selfbits; |
| 1764 | compressed[14] = parentbits; |
| 1765 | |
| 1766 | // write the result |
| 1767 | m_mapoffset = file_append(compressed, complen + 16); |
| 1768 | |
| 1769 | // then write the map offset |
| 1770 | UINT8 rawbuf[sizeof(UINT64)]; |
| 1771 | be_write(rawbuf, m_mapoffset, 8); |
| 1772 | file_write(m_mapoffset_offset, rawbuf, sizeof(rawbuf)); |
| 1773 | return CHDERR_NONE; |
| 2198 | 1774 | } |
| 2199 | | else |
| 1775 | catch (chd_error &err) |
| 2200 | 1776 | { |
| 2201 | | /* now try compressing the data */ |
| 2202 | | err = CHDERR_COMPRESSION_ERROR; |
| 2203 | | if (chd->codecintf->compress != NULL) |
| 2204 | | err = (*chd->codecintf->compress)(chd, src, &bytes); |
| 1777 | return err; |
| 2205 | 1778 | } |
| 1779 | } |
| 2206 | 1780 | |
| 2207 | | /* if that worked, and we're lossy, decompress and CRC the result */ |
| 2208 | | if (err == CHDERR_NONE && (chd->codecintf->lossy || src == NULL)) |
| 1781 | |
| 1782 | //------------------------------------------------- |
| 1783 | // decompress_v5_map - decompress the v5 map |
| 1784 | //------------------------------------------------- |
| 1785 | |
| 1786 | void chd_file::decompress_v5_map() |
| 1787 | { |
| 1788 | // if no offset, we haven't written it yet |
| 1789 | if (m_mapoffset == 0) |
| 2209 | 1790 | { |
| 2210 | | err = (*chd->codecintf->decompress)(chd, bytes, chd->cache); |
| 2211 | | if (err == CHDERR_NONE) |
| 2212 | | newentry.crc = crc32(0, chd->cache, chd->header.hunkbytes); |
| 1791 | memset(m_rawmap, 0xff, m_rawmap.count()); |
| 1792 | return; |
| 2213 | 1793 | } |
| 2214 | 1794 | |
| 2215 | | /* if we succeeded in compressing the data, replace our data pointer and mark it so */ |
| 2216 | | if (err == CHDERR_NONE) |
| 1795 | // read the reader |
| 1796 | UINT8 rawbuf[16]; |
| 1797 | file_read(m_mapoffset, rawbuf, sizeof(rawbuf)); |
| 1798 | UINT32 mapbytes = be_read(&rawbuf[0], 4); |
| 1799 | UINT64 firstoffs = be_read(&rawbuf[4], 6); |
| 1800 | UINT16 mapcrc = be_read(&rawbuf[10], 2); |
| 1801 | UINT8 lengthbits = rawbuf[12]; |
| 1802 | UINT8 selfbits = rawbuf[13]; |
| 1803 | UINT8 parentbits = rawbuf[14]; |
| 1804 | |
| 1805 | // now read the map |
| 1806 | dynamic_buffer compressed(mapbytes); |
| 1807 | file_read(m_mapoffset + 16, compressed, mapbytes); |
| 1808 | bitstream_in bitbuf(compressed, compressed.count()); |
| 1809 | |
| 1810 | // first decode the compression types |
| 1811 | huffman_decoder<16, 8> decoder; |
| 1812 | huffman_error err = decoder.import_tree_rle(bitbuf); |
| 1813 | if (err != HUFFERR_NONE) |
| 1814 | throw CHDERR_DECOMPRESSION_ERROR; |
| 1815 | UINT8 lastcomp = 0; |
| 1816 | int repcount = 0; |
| 1817 | for (int hunknum = 0; hunknum < m_hunkcount; hunknum++) |
| 2217 | 1818 | { |
| 2218 | | data = chd->compressed; |
| 2219 | | newentry.length = bytes; |
| 2220 | | |
| 2221 | | if (strategy == 0) |
| 1819 | UINT8 *rawmap = &m_rawmap[hunknum * 12]; |
| 1820 | if (repcount > 0) |
| 1821 | rawmap[0] = lastcomp, repcount--; |
| 1822 | else |
| 2222 | 1823 | { |
| 2223 | | newentry.flags = MAP_ENTRY_TYPE_COMPRESSED; |
| 1824 | UINT8 val = decoder.decode_one(bitbuf); |
| 1825 | if (val == COMPRESSION_RLE_SMALL) |
| 1826 | rawmap[0] = lastcomp, repcount = 2 + decoder.decode_one(bitbuf); |
| 1827 | else if (val == COMPRESSION_RLE_LARGE) |
| 1828 | rawmap[0] = lastcomp, repcount = 2 + 16 + (decoder.decode_one(bitbuf) << 4), repcount += decoder.decode_one(bitbuf); |
| 1829 | else |
| 1830 | rawmap[0] = lastcomp = val; |
| 2224 | 1831 | } |
| 2225 | | else if (strategy == 1) |
| 1832 | } |
| 1833 | |
| 1834 | // then iterate through the hunks and extract the needed data |
| 1835 | UINT64 curoffset = firstoffs; |
| 1836 | UINT32 last_self = 0; |
| 1837 | UINT64 last_parent = 0; |
| 1838 | for (int hunknum = 0; hunknum < m_hunkcount; hunknum++) |
| 1839 | { |
| 1840 | UINT8 *rawmap = &m_rawmap[hunknum * 12]; |
| 1841 | UINT64 offset = curoffset; |
| 1842 | UINT32 length = 0; |
| 1843 | UINT16 crc = 0; |
| 1844 | switch (rawmap[0]) |
| 2226 | 1845 | { |
| 2227 | | newentry.flags = MAP_ENTRY_TYPE_2ND_COMPRESSED; |
| 1846 | // base types |
| 1847 | case COMPRESSION_TYPE_0: |
| 1848 | case COMPRESSION_TYPE_1: |
| 1849 | case COMPRESSION_TYPE_2: |
| 1850 | case COMPRESSION_TYPE_3: |
| 1851 | curoffset += length = bitbuf.read(lengthbits); |
| 1852 | crc = bitbuf.read(16); |
| 1853 | break; |
| 1854 | |
| 1855 | case COMPRESSION_NONE: |
| 1856 | curoffset += length = m_hunkbytes; |
| 1857 | crc = bitbuf.read(16); |
| 1858 | break; |
| 1859 | |
| 1860 | case COMPRESSION_SELF: |
| 1861 | last_self = offset = bitbuf.read(selfbits); |
| 1862 | break; |
| 1863 | |
| 1864 | case COMPRESSION_PARENT: |
| 1865 | offset = bitbuf.read(parentbits); |
| 1866 | last_parent = offset; |
| 1867 | break; |
| 1868 | |
| 1869 | // pseudo-types; convert into base types |
| 1870 | case COMPRESSION_SELF_1: |
| 1871 | last_self++; |
| 1872 | case COMPRESSION_SELF_0: |
| 1873 | rawmap[0] = COMPRESSION_SELF; |
| 1874 | offset = last_self; |
| 1875 | break; |
| 1876 | |
| 1877 | case COMPRESSION_PARENT_SELF: |
| 1878 | rawmap[0] = COMPRESSION_PARENT; |
| 1879 | last_parent = offset = (UINT64(hunknum) * UINT64(m_hunkbytes)) / m_unitbytes; |
| 1880 | break; |
| 1881 | |
| 1882 | case COMPRESSION_PARENT_1: |
| 1883 | last_parent += m_hunkbytes / m_unitbytes; |
| 1884 | case COMPRESSION_PARENT_0: |
| 1885 | rawmap[0] = COMPRESSION_PARENT; |
| 1886 | offset = last_parent; |
| 1887 | break; |
| 2228 | 1888 | } |
| 1889 | be_write(&rawmap[1], length, 3); |
| 1890 | be_write(&rawmap[4], offset, 6); |
| 1891 | be_write(&rawmap[10], crc, 2); |
| 2229 | 1892 | } |
| 2230 | | |
| 2231 | | /* otherwise, mark it uncompressed and use the original data */ |
| 2232 | | else |
| 2233 | | { |
| 2234 | | newentry.length = chd->header.hunkbytes; |
| 2235 | | newentry.flags = MAP_ENTRY_TYPE_UNCOMPRESSED; |
| 2236 | | } |
| 2237 | | |
| 2238 | | |
| 2239 | | /* if the data doesn't fit into the previous entry, make a new one at the eof */ |
| 2240 | | newentry.offset = entry->offset; |
| 2241 | | if (newentry.offset == 0 || newentry.length > entry->length) |
| 2242 | | newentry.offset = core_fsize(chd->file); |
| 2243 | | |
| 2244 | | /* write the data */ |
| 2245 | | core_fseek(chd->file, newentry.offset, SEEK_SET); |
| 2246 | | bytes = core_fwrite(chd->file, data, newentry.length); |
| 2247 | | if (bytes != newentry.length) |
| 2248 | | return CHDERR_WRITE_ERROR; |
| 2249 | | |
| 2250 | | /* update the entry in memory */ |
| 2251 | | write_entry: |
| 2252 | | |
| 2253 | | if (is_half_hunk) |
| 2254 | | newentry.flags |= MAP_ENTRY_FLAG_HALF_HUNK; |
| 2255 | | |
| 2256 | | *entry = newentry; |
| 2257 | | |
| 2258 | | /* update the map on file */ |
| 2259 | | map_assemble(&fileentry[0], &chd->map[hunknum]); |
| 2260 | | core_fseek(chd->file, chd->header.length + hunknum * sizeof(fileentry), SEEK_SET); |
| 2261 | | bytes = core_fwrite(chd->file, &fileentry[0], sizeof(fileentry)); |
| 2262 | | if (bytes != sizeof(fileentry)) |
| 2263 | | return CHDERR_WRITE_ERROR; |
| 2264 | | |
| 2265 | | return CHDERR_NONE; |
| 1893 | |
| 1894 | // verify the final CRC |
| 1895 | if (crc16_creator::simple(m_rawmap, m_hunkcount * 12) != mapcrc) |
| 1896 | throw CHDERR_DECOMPRESSION_ERROR; |
| 2266 | 1897 | } |
| 1898 | |
| 2267 | 1899 | |
| 1900 | //------------------------------------------------- |
| 1901 | // create_common - command path when creating a |
| 1902 | // new CHD file |
| 1903 | //------------------------------------------------- |
| 2268 | 1904 | |
| 2269 | | |
| 2270 | | /*************************************************************************** |
| 2271 | | INTERNAL MAP ACCESS |
| 2272 | | ***************************************************************************/ |
| 2273 | | |
| 2274 | | /*------------------------------------------------- |
| 2275 | | map_write_initial - write an initial map to |
| 2276 | | a new CHD file |
| 2277 | | -------------------------------------------------*/ |
| 2278 | | |
| 2279 | | static chd_error map_write_initial(core_file *file, chd_file *parent, const chd_header *header) |
| 1905 | chd_error chd_file::create_common() |
| 2280 | 1906 | { |
| 2281 | | UINT8 blank_map_entries[MAP_STACK_ENTRIES * MAP_ENTRY_SIZE]; |
| 2282 | | int fullchunks, remainder, count, i, j; |
| 2283 | | map_entry mapentry; |
| 2284 | | UINT64 fileoffset; |
| 1907 | // wrap in try for proper error handling |
| 1908 | try |
| 1909 | { |
| 1910 | // if we have a parent, it must be V3 or later |
| 1911 | if (m_parent != NULL && m_parent->version() < 3) |
| 1912 | throw CHDERR_UNSUPPORTED_VERSION; |
| 1913 | |
| 1914 | // must be an even number of units per hunk |
| 1915 | if (m_hunkbytes % m_unitbytes != 0) |
| 1916 | throw CHDERR_INVALID_PARAMETER; |
| 1917 | if (m_parent != NULL && m_unitbytes != m_parent->unit_bytes()) |
| 1918 | throw CHDERR_INVALID_PARAMETER; |
| 1919 | |
| 1920 | // writes are obviously permitted; reads only if uncompressed |
| 1921 | m_allow_writes = true; |
| 1922 | m_allow_reads = !compressed(); |
| 1923 | |
| 1924 | // verify the compression types |
| 1925 | bool found_zero = false; |
| 1926 | for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_compression); codecnum++) |
| 1927 | { |
| 1928 | // once we hit an empty slot, all later slots must be empty as well |
| 1929 | if (m_compression[codecnum] == CHD_CODEC_NONE) |
| 1930 | found_zero = true; |
| 1931 | else if (found_zero) |
| 1932 | throw CHDERR_INVALID_PARAMETER; |
| 1933 | else if (!chd_codec_list::codec_exists(m_compression[codecnum])) |
| 1934 | throw CHDERR_UNKNOWN_COMPRESSION; |
| 1935 | } |
| 2285 | 1936 | |
| 2286 | | /* create a mini hunk of 0's */ |
| 2287 | | mapentry.offset = 0; |
| 2288 | | mapentry.crc = 0; |
| 2289 | | mapentry.length = 0; |
| 2290 | | mapentry.flags = MAP_ENTRY_TYPE_MINI | MAP_ENTRY_FLAG_NO_CRC; |
| 2291 | | for (i = 0; i < MAP_STACK_ENTRIES; i++) |
| 2292 | | map_assemble(&blank_map_entries[i * MAP_ENTRY_SIZE], &mapentry); |
| 1937 | // create our V5 header |
| 1938 | assert(m_version == HEADER_VERSION); |
| 1939 | UINT8 rawheader[V5_HEADER_SIZE]; |
| 1940 | memcpy(&rawheader[0], "MComprHD", 8); |
| 1941 | be_write(&rawheader[8], V5_HEADER_SIZE, 4); |
| 1942 | be_write(&rawheader[12], m_version, 4); |
| 1943 | be_write(&rawheader[16], m_compression[0], 4); |
| 1944 | be_write(&rawheader[20], m_compression[1], 4); |
| 1945 | be_write(&rawheader[24], m_compression[2], 4); |
| 1946 | be_write(&rawheader[28], m_compression[3], 4); |
| 1947 | be_write(&rawheader[32], m_logicalbytes, 8); |
| 1948 | be_write(&rawheader[40], compressed() ? 0 : V5_HEADER_SIZE, 8); |
| 1949 | be_write(&rawheader[48], m_metaoffset, 8); |
| 1950 | be_write(&rawheader[56], m_hunkbytes, 4); |
| 1951 | be_write(&rawheader[60], m_unitbytes, 4); |
| 1952 | be_write_sha1(&rawheader[64], sha1_t::null); |
| 1953 | be_write_sha1(&rawheader[84], sha1_t::null); |
| 1954 | be_write_sha1(&rawheader[104], (m_parent != NULL) ? m_parent->sha1() : sha1_t::null); |
| 2293 | 1955 | |
| 2294 | | /* prepare to write a blank hunk map immediately following */ |
| 2295 | | fileoffset = header->length; |
| 2296 | | fullchunks = header->totalhunks / MAP_STACK_ENTRIES; |
| 2297 | | remainder = header->totalhunks % MAP_STACK_ENTRIES; |
| 2298 | | |
| 2299 | | /* first write full chunks of blank entries */ |
| 2300 | | for (i = 0; i < fullchunks; i++) |
| 2301 | | { |
| 2302 | | /* parent drives need to be mapped through */ |
| 2303 | | if (parent != NULL) |
| 2304 | | for (j = 0; j < MAP_STACK_ENTRIES; j++) |
| 1956 | // write the resulting header |
| 1957 | file_write(0, rawheader, sizeof(rawheader)); |
| 1958 | |
| 1959 | // parse it back out to set up fields appropriately |
| 1960 | sha1_t parentsha1; |
| 1961 | parse_v5_header(rawheader, parentsha1); |
| 1962 | |
| 1963 | // write out the map (if not compressed) |
| 1964 | if (!compressed()) |
| 1965 | { |
| 1966 | UINT32 mapsize = m_mapentrybytes * m_hunkcount; |
| 1967 | UINT8 buffer[4096] = { 0 }; |
| 1968 | UINT64 offset = m_mapoffset; |
| 1969 | while (mapsize != 0) |
| 2305 | 1970 | { |
| 2306 | | mapentry.offset = i * MAP_STACK_ENTRIES + j; |
| 2307 | | mapentry.crc = parent->map[i * MAP_STACK_ENTRIES + j].crc; |
| 2308 | | mapentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK; |
| 2309 | | map_assemble(&blank_map_entries[j * MAP_ENTRY_SIZE], &mapentry); |
| 1971 | UINT32 bytes_to_write = MIN(mapsize, sizeof(buffer)); |
| 1972 | file_write(offset, buffer, bytes_to_write); |
| 1973 | offset += bytes_to_write; |
| 1974 | mapsize -= bytes_to_write; |
| 2310 | 1975 | } |
| 1976 | } |
| 2311 | 1977 | |
| 2312 | | /* write the chunks */ |
| 2313 | | core_fseek(file, fileoffset, SEEK_SET); |
| 2314 | | count = core_fwrite(file, blank_map_entries, sizeof(blank_map_entries)); |
| 2315 | | if (count != sizeof(blank_map_entries)) |
| 2316 | | return CHDERR_WRITE_ERROR; |
| 2317 | | fileoffset += sizeof(blank_map_entries); |
| 1978 | // finish opening the file |
| 1979 | create_open_common(); |
| 2318 | 1980 | } |
| 2319 | | |
| 2320 | | /* then write the remainder */ |
| 2321 | | if (remainder > 0) |
| 1981 | |
| 1982 | // handle errors by closing ourself |
| 1983 | catch (chd_error &err) |
| 2322 | 1984 | { |
| 2323 | | /* parent drives need to be mapped through */ |
| 2324 | | if (parent != NULL) |
| 2325 | | for (j = 0; j < remainder; j++) |
| 2326 | | { |
| 2327 | | mapentry.offset = i * MAP_STACK_ENTRIES + j; |
| 2328 | | mapentry.crc = parent->map[i * MAP_STACK_ENTRIES + j].crc; |
| 2329 | | mapentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK; |
| 2330 | | map_assemble(&blank_map_entries[j * MAP_ENTRY_SIZE], &mapentry); |
| 2331 | | } |
| 2332 | | |
| 2333 | | /* write the chunks */ |
| 2334 | | core_fseek(file, fileoffset, SEEK_SET); |
| 2335 | | count = core_fwrite(file, blank_map_entries, remainder * MAP_ENTRY_SIZE); |
| 2336 | | if (count != remainder * MAP_ENTRY_SIZE) |
| 2337 | | return CHDERR_WRITE_ERROR; |
| 2338 | | fileoffset += remainder * MAP_ENTRY_SIZE; |
| 1985 | close(); |
| 1986 | return err; |
| 2339 | 1987 | } |
| 2340 | | |
| 2341 | | /* then write a special end-of-list cookie */ |
| 2342 | | memcpy(&blank_map_entries[0], END_OF_LIST_COOKIE, MAP_ENTRY_SIZE); |
| 2343 | | core_fseek(file, fileoffset, SEEK_SET); |
| 2344 | | count = core_fwrite(file, blank_map_entries, MAP_ENTRY_SIZE); |
| 2345 | | if (count != MAP_ENTRY_SIZE) |
| 2346 | | return CHDERR_WRITE_ERROR; |
| 2347 | | |
| 1988 | catch (...) |
| 1989 | { |
| 1990 | close(); |
| 1991 | throw; |
| 1992 | } |
| 2348 | 1993 | return CHDERR_NONE; |
| 2349 | 1994 | } |
| 2350 | 1995 | |
| 2351 | 1996 | |
| 2352 | | /*------------------------------------------------- |
| 2353 | | map_read - read the initial sector map |
| 2354 | | -------------------------------------------------*/ |
| 1997 | //------------------------------------------------- |
| 1998 | // open_common - common path when opening an |
| 1999 | // existing CHD file for input |
| 2000 | //------------------------------------------------- |
| 2355 | 2001 | |
| 2356 | | static chd_error map_read(chd_file *chd) |
| 2002 | chd_error chd_file::open_common(bool writeable) |
| 2357 | 2003 | { |
| 2358 | | UINT32 entrysize = (chd->header.version < 3) ? OLD_MAP_ENTRY_SIZE : MAP_ENTRY_SIZE; |
| 2359 | | UINT8 raw_map_entries[MAP_STACK_ENTRIES * MAP_ENTRY_SIZE]; |
| 2360 | | UINT64 fileoffset, maxoffset = 0; |
| 2361 | | UINT8 cookie[MAP_ENTRY_SIZE]; |
| 2362 | | UINT32 count; |
| 2363 | | chd_error err; |
| 2364 | | int i; |
| 2365 | | |
| 2366 | | /* first allocate memory */ |
| 2367 | | chd->map = (map_entry *)malloc(sizeof(chd->map[0]) * chd->header.totalhunks); |
| 2368 | | if (!chd->map) |
| 2369 | | return CHDERR_OUT_OF_MEMORY; |
| 2370 | | |
| 2371 | | /* read the map entries in in chunks and extract to the map list */ |
| 2372 | | fileoffset = chd->header.length; |
| 2373 | | for (i = 0; i < chd->header.totalhunks; i += MAP_STACK_ENTRIES) |
| 2004 | // wrap in try for proper error handling |
| 2005 | try |
| 2374 | 2006 | { |
| 2375 | | /* compute how many entries this time */ |
| 2376 | | int entries = chd->header.totalhunks - i, j; |
| 2377 | | if (entries > MAP_STACK_ENTRIES) |
| 2378 | | entries = MAP_STACK_ENTRIES; |
| 2379 | | |
| 2380 | | /* read that many */ |
| 2381 | | core_fseek(chd->file, fileoffset, SEEK_SET); |
| 2382 | | count = core_fread(chd->file, raw_map_entries, entries * entrysize); |
| 2383 | | if (count != entries * entrysize) |
| 2007 | // reads are always permitted; writes possibly as well |
| 2008 | m_allow_reads = true; |
| 2009 | m_allow_writes = writeable; |
| 2010 | |
| 2011 | // read the raw header |
| 2012 | UINT8 rawheader[MAX_HEADER_SIZE]; |
| 2013 | file_read(0, rawheader, sizeof(rawheader)); |
| 2014 | |
| 2015 | // verify the signature |
| 2016 | if (memcmp(rawheader, "MComprHD", 8) != 0) |
| 2017 | throw CHDERR_INVALID_FILE; |
| 2018 | |
| 2019 | // only allow writes to the most recent version |
| 2020 | m_version = be_read(&rawheader[12], 4); |
| 2021 | if (m_allow_writes && m_version < HEADER_VERSION) |
| 2022 | throw CHDERR_UNSUPPORTED_VERSION; |
| 2023 | |
| 2024 | // read the header if we support it |
| 2025 | sha1_t parentsha1 = sha1_t::null; |
| 2026 | switch (m_version) |
| 2384 | 2027 | { |
| 2385 | | err = CHDERR_READ_ERROR; |
| 2386 | | goto cleanup; |
| 2028 | case 3: parse_v3_header(rawheader, parentsha1); break; |
| 2029 | case 4: parse_v4_header(rawheader, parentsha1); break; |
| 2030 | case 5: parse_v5_header(rawheader, parentsha1); break; |
| 2031 | default: throw CHDERR_UNSUPPORTED_VERSION; |
| 2387 | 2032 | } |
| 2388 | | fileoffset += entries * entrysize; |
| 2389 | | |
| 2390 | | /* process that many */ |
| 2391 | | if (entrysize == MAP_ENTRY_SIZE) |
| 2033 | |
| 2034 | // make sure we have a parent if we need one (and don't if we don't) |
| 2035 | if (parentsha1 != sha1_t::null) |
| 2392 | 2036 | { |
| 2393 | | for (j = 0; j < entries; j++) |
| 2394 | | map_extract(&raw_map_entries[j * MAP_ENTRY_SIZE], &chd->map[i + j]); |
| 2037 | if (m_parent == NULL) |
| 2038 | m_parent_missing = true; |
| 2039 | else if (m_parent->sha1() != parentsha1) |
| 2040 | throw CHDERR_INVALID_PARENT; |
| 2395 | 2041 | } |
| 2396 | | else |
| 2397 | | { |
| 2398 | | for (j = 0; j < entries; j++) |
| 2399 | | map_extract_old(&raw_map_entries[j * OLD_MAP_ENTRY_SIZE], &chd->map[i + j], chd->header.hunkbytes); |
| 2400 | | } |
| 2401 | | |
| 2402 | | /* track the maximum offset */ |
| 2403 | | for (j = 0; j < entries; j++) |
| 2404 | | if ((chd->map[i + j].flags & MAP_ENTRY_FLAG_TYPE_MASK) == MAP_ENTRY_TYPE_COMPRESSED || |
| 2405 | | (chd->map[i + j].flags & MAP_ENTRY_FLAG_TYPE_MASK) == MAP_ENTRY_TYPE_2ND_COMPRESSED || |
| 2406 | | (chd->map[i + j].flags & MAP_ENTRY_FLAG_TYPE_MASK) == MAP_ENTRY_TYPE_UNCOMPRESSED) |
| 2407 | | maxoffset = MAX(maxoffset, chd->map[i + j].offset + chd->map[i + j].length); |
| 2042 | else if (parentsha1 == sha1_t::null && m_parent != NULL) |
| 2043 | throw CHDERR_INVALID_PARAMETER; |
| 2044 | |
| 2045 | // finish opening the file |
| 2046 | create_open_common(); |
| 2047 | return CHDERR_NONE; |
| 2408 | 2048 | } |
| 2409 | | |
| 2410 | | /* verify the cookie */ |
| 2411 | | core_fseek(chd->file, fileoffset, SEEK_SET); |
| 2412 | | count = core_fread(chd->file, &cookie, entrysize); |
| 2413 | | if (count != entrysize || memcmp(&cookie, END_OF_LIST_COOKIE, entrysize)) |
| 2049 | |
| 2050 | // handle errors by closing ourself |
| 2051 | catch (chd_error &err) |
| 2414 | 2052 | { |
| 2415 | | err = CHDERR_INVALID_FILE; |
| 2416 | | goto cleanup; |
| 2053 | close(); |
| 2054 | return err; |
| 2417 | 2055 | } |
| 2418 | | |
| 2419 | | /* verify the length */ |
| 2420 | | if (maxoffset > core_fsize(chd->file)) |
| 2421 | | { |
| 2422 | | err = CHDERR_INVALID_FILE; |
| 2423 | | goto cleanup; |
| 2424 | | } |
| 2425 | | return CHDERR_NONE; |
| 2426 | | |
| 2427 | | cleanup: |
| 2428 | | if (chd->map) |
| 2429 | | free(chd->map); |
| 2430 | | chd->map = NULL; |
| 2431 | | return err; |
| 2432 | 2056 | } |
| 2433 | 2057 | |
| 2434 | 2058 | |
| 2059 | //------------------------------------------------- |
| 2060 | // create_open_common - common code for handling |
| 2061 | // creation and opening of a file |
| 2062 | //------------------------------------------------- |
| 2435 | 2063 | |
| 2436 | | /*************************************************************************** |
| 2437 | | INTERNAL CRC MAP ACCESS |
| 2438 | | ***************************************************************************/ |
| 2439 | | |
| 2440 | | /*------------------------------------------------- |
| 2441 | | crcmap_init - initialize the CRC map |
| 2442 | | -------------------------------------------------*/ |
| 2443 | | |
| 2444 | | static void crcmap_init(chd_file *chd, int prepopulate) |
| 2064 | void chd_file::create_open_common() |
| 2445 | 2065 | { |
| 2446 | | int i; |
| 2447 | | |
| 2448 | | /* if we already have one, bail */ |
| 2449 | | if (chd->crcmap != NULL) |
| 2450 | | return; |
| 2451 | | |
| 2452 | | /* reset all pointers */ |
| 2453 | | chd->crcmap = NULL; |
| 2454 | | chd->crcfree = NULL; |
| 2455 | | chd->crctable = NULL; |
| 2456 | | |
| 2457 | | /* allocate a list; one for each hunk */ |
| 2458 | | chd->crcmap = (crcmap_entry *)malloc(chd->header.totalhunks * sizeof(chd->crcmap[0])); |
| 2459 | | if (chd->crcmap == NULL) |
| 2460 | | return; |
| 2461 | | |
| 2462 | | /* allocate a CRC map table */ |
| 2463 | | chd->crctable = (crcmap_entry **)malloc(CRCMAP_HASH_SIZE * sizeof(chd->crctable[0])); |
| 2464 | | if (chd->crctable == NULL) |
| 2066 | // verify the compression types and initialize the codecs |
| 2067 | for (int decompnum = 0; decompnum < ARRAY_LENGTH(m_compression); decompnum++) |
| 2465 | 2068 | { |
| 2466 | | free(chd->crcmap); |
| 2467 | | chd->crcmap = NULL; |
| 2468 | | return; |
| 2069 | m_decompressor[decompnum] = chd_codec_list::new_decompressor(m_compression[decompnum], *this); |
| 2070 | if (m_decompressor[decompnum] == NULL && m_compression[decompnum] != 0) |
| 2071 | throw CHDERR_UNKNOWN_COMPRESSION; |
| 2469 | 2072 | } |
| 2073 | |
| 2074 | // read the map; v5+ compressed drives need to read and decompress their map |
| 2075 | m_rawmap.resize(m_hunkcount * m_mapentrybytes); |
| 2076 | if (m_version >= 5 && compressed()) |
| 2077 | decompress_v5_map(); |
| 2078 | else |
| 2079 | file_read(m_mapoffset, m_rawmap, m_rawmap.count()); |
| 2470 | 2080 | |
| 2471 | | /* initialize the free list */ |
| 2472 | | for (i = 0; i < chd->header.totalhunks; i++) |
| 2473 | | { |
| 2474 | | chd->crcmap[i].next = chd->crcfree; |
| 2475 | | chd->crcfree = &chd->crcmap[i]; |
| 2476 | | } |
| 2477 | | |
| 2478 | | /* initialize the table */ |
| 2479 | | memset(chd->crctable, 0, CRCMAP_HASH_SIZE * sizeof(chd->crctable[0])); |
| 2480 | | |
| 2481 | | /* if we're to prepopulate, go for it */ |
| 2482 | | if (prepopulate) |
| 2483 | | for (i = 0; i < chd->header.totalhunks; i++) |
| 2484 | | crcmap_add_entry(chd, i); |
| 2081 | // allocate the temporary compressed buffer and a buffer for caching |
| 2082 | m_compressed.resize(m_hunkbytes); |
| 2083 | m_cache.resize(m_hunkbytes); |
| 2485 | 2084 | } |
| 2486 | 2085 | |
| 2487 | 2086 | |
| 2488 | | /*------------------------------------------------- |
| 2489 | | crcmap_add_entry - log a CRC entry |
| 2490 | | -------------------------------------------------*/ |
| 2087 | //------------------------------------------------- |
| 2088 | // verify_proper_compression_append - verify that |
| 2089 | // the given hunk is a proper candidate for |
| 2090 | // appending to a compressed CHD |
| 2091 | //------------------------------------------------- |
| 2491 | 2092 | |
| 2492 | | static void crcmap_add_entry(chd_file *chd, UINT32 hunknum) |
| 2093 | void chd_file::verify_proper_compression_append(UINT32 hunknum) |
| 2493 | 2094 | { |
| 2494 | | UINT32 hash = chd->map[hunknum].crc % CRCMAP_HASH_SIZE; |
| 2495 | | crcmap_entry *crcmap; |
| 2095 | // punt if no file |
| 2096 | if (m_file == NULL) |
| 2097 | throw CHDERR_NOT_OPEN; |
| 2496 | 2098 | |
| 2497 | | /* pull a free entry off the list */ |
| 2498 | | crcmap = chd->crcfree; |
| 2499 | | chd->crcfree = crcmap->next; |
| 2099 | // return an error if out of range |
| 2100 | if (hunknum >= m_hunkcount) |
| 2101 | throw CHDERR_HUNK_OUT_OF_RANGE; |
| 2500 | 2102 | |
| 2501 | | /* set up the entry and link it into the hash table */ |
| 2502 | | crcmap->hunknum = hunknum; |
| 2503 | | crcmap->next = chd->crctable[hash]; |
| 2504 | | chd->crctable[hash] = crcmap; |
| 2505 | | } |
| 2103 | // if not writeable, fail |
| 2104 | if (!m_allow_writes) |
| 2105 | throw CHDERR_FILE_NOT_WRITEABLE; |
| 2506 | 2106 | |
| 2107 | // compressed writes only via this interface |
| 2108 | if (!compressed()) |
| 2109 | throw CHDERR_FILE_NOT_WRITEABLE; |
| 2507 | 2110 | |
| 2508 | | /*------------------------------------------------- |
| 2509 | | crcmap_verify_hunk_match - verify that a |
| 2510 | | hunk really matches by doing a byte-for-byte |
| 2511 | | compare |
| 2512 | | -------------------------------------------------*/ |
| 2111 | // only permitted to write new blocks |
| 2112 | UINT8 *rawmap = &m_rawmap[hunknum * 12]; |
| 2113 | if (rawmap[0] != 0xff) |
| 2114 | throw CHDERR_COMPRESSION_ERROR; |
| 2115 | |
| 2116 | // if this isn't the first block, only permitted to write immediately |
| 2117 | // after the previous one |
| 2118 | if (hunknum != 0 && rawmap[-12] == 0xff) |
| 2119 | throw CHDERR_COMPRESSION_ERROR; |
| 2120 | } |
| 2121 | |
| 2513 | 2122 | |
| 2514 | | static int crcmap_verify_hunk_match(chd_file *chd, UINT32 hunknum, const UINT8 *rawdata) |
| 2123 | //------------------------------------------------- |
| 2124 | // hunk_write_compressed - write a hunk to a |
| 2125 | // compressed CHD, discovering the best |
| 2126 | // technique |
| 2127 | //------------------------------------------------- |
| 2128 | |
| 2129 | void chd_file::hunk_write_compressed(UINT32 hunknum, INT8 compression, const UINT8 *compressed, UINT32 complength, crc16_t crc16) |
| 2515 | 2130 | { |
| 2516 | | /* we have a potential match -- better be sure */ |
| 2517 | | /* read the hunk from disk and compare byte-for-byte */ |
| 2518 | | if (hunknum != chd->comparehunk) |
| 2519 | | { |
| 2520 | | chd->comparehunk = ~0; |
| 2521 | | if (hunk_read_into_memory(chd, hunknum, chd->compare) == CHDERR_NONE) |
| 2522 | | chd->comparehunk = hunknum; |
| 2523 | | } |
| 2524 | | return (hunknum == chd->comparehunk && memcmp(rawdata, chd->compare, chd->header.hunkbytes) == 0); |
| 2131 | // verify that we are appending properly to a compressed file |
| 2132 | verify_proper_compression_append(hunknum); |
| 2133 | |
| 2134 | // write the final result |
| 2135 | UINT64 offset = file_append(compressed, complength); |
| 2136 | |
| 2137 | // update the map entry |
| 2138 | UINT8 *rawmap = &m_rawmap[hunknum * 12]; |
| 2139 | rawmap[0] = (compression == -1) ? COMPRESSION_NONE : compression; |
| 2140 | be_write(&rawmap[1], complength, 3); |
| 2141 | be_write(&rawmap[4], offset, 6); |
| 2142 | be_write(&rawmap[10], crc16, 2); |
| 2525 | 2143 | } |
| 2526 | 2144 | |
| 2527 | 2145 | |
| 2528 | | /*------------------------------------------------- |
| 2529 | | crcmap_find_hunk - find a hunk with a matching |
| 2530 | | CRC in the map |
| 2531 | | -------------------------------------------------*/ |
| 2146 | //------------------------------------------------- |
| 2147 | // hunk_copy_from_self - mark a hunk as being a |
| 2148 | // copy of another hunk in the same CHD |
| 2149 | //------------------------------------------------- |
| 2532 | 2150 | |
| 2533 | | static UINT32 crcmap_find_hunk(chd_file *chd, UINT32 hunknum, UINT32 crc, const UINT8 *rawdata) |
| 2151 | void chd_file::hunk_copy_from_self(UINT32 hunknum, UINT32 otherhunk) |
| 2534 | 2152 | { |
| 2535 | | UINT32 lasthunk = (hunknum < chd->header.totalhunks) ? hunknum : chd->header.totalhunks; |
| 2536 | | int curhunk; |
| 2153 | // verify that we are appending properly to a compressed file |
| 2154 | verify_proper_compression_append(hunknum); |
| 2155 | |
| 2156 | // only permitted to reference prior hunks |
| 2157 | if (otherhunk >= hunknum) |
| 2158 | throw CHDERR_INVALID_PARAMETER; |
| 2537 | 2159 | |
| 2538 | | /* if we have a CRC map, use that */ |
| 2539 | | if (chd->crctable) |
| 2540 | | { |
| 2541 | | crcmap_entry *curentry; |
| 2542 | | for (curentry = chd->crctable[crc % CRCMAP_HASH_SIZE]; curentry; curentry = curentry->next) |
| 2543 | | { |
| 2544 | | curhunk = curentry->hunknum; |
| 2545 | | if (chd->map[curhunk].crc == crc && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_NO_CRC) && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_HALF_HUNK) && crcmap_verify_hunk_match(chd, curhunk, rawdata)) |
| 2546 | | return curhunk; |
| 2547 | | } |
| 2548 | | return NO_MATCH; |
| 2549 | | } |
| 2550 | | |
| 2551 | | /* first see if the last match is a valid one */ |
| 2552 | | if (chd->comparehunk < chd->header.totalhunks && chd->map[chd->comparehunk].crc == crc && !(chd->map[chd->comparehunk].flags & MAP_ENTRY_FLAG_NO_CRC) && !(chd->map[chd->comparehunk].flags & MAP_ENTRY_FLAG_HALF_HUNK) && |
| 2553 | | memcmp(rawdata, chd->compare, chd->header.hunkbytes) == 0) |
| 2554 | | return chd->comparehunk; |
| 2555 | | |
| 2556 | | /* scan through the CHD's hunk map looking for a match */ |
| 2557 | | for (curhunk = 0; curhunk < lasthunk; curhunk++) |
| 2558 | | if (chd->map[curhunk].crc == crc && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_NO_CRC) && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_HALF_HUNK) && crcmap_verify_hunk_match(chd, curhunk, rawdata)) |
| 2559 | | return curhunk; |
| 2560 | | |
| 2561 | | return NO_MATCH; |
| 2160 | // update the map entry |
| 2161 | UINT8 *rawmap = &m_rawmap[hunknum * 12]; |
| 2162 | rawmap[0] = COMPRESSION_SELF; |
| 2163 | be_write(&rawmap[1], 0, 3); |
| 2164 | be_write(&rawmap[4], otherhunk, 6); |
| 2165 | be_write(&rawmap[10], 0, 2); |
| 2562 | 2166 | } |
| 2563 | 2167 | |
| 2564 | 2168 | |
| 2169 | //------------------------------------------------- |
| 2170 | // hunk_copy_from_parent - mark a hunk as being a |
| 2171 | // copy of a hunk from a parent CHD |
| 2172 | //------------------------------------------------- |
| 2565 | 2173 | |
| 2566 | | /*************************************************************************** |
| 2567 | | INTERNAL METADATA ACCESS |
| 2568 | | ***************************************************************************/ |
| 2569 | | |
| 2570 | | /*------------------------------------------------- |
| 2571 | | metadata_find_entry - find a metadata entry |
| 2572 | | -------------------------------------------------*/ |
| 2573 | | |
| 2574 | | static chd_error metadata_find_entry(chd_file *chd, UINT32 metatag, UINT32 metaindex, metadata_entry *metaentry) |
| 2174 | void chd_file::hunk_copy_from_parent(UINT32 hunknum, UINT64 parentunit) |
| 2575 | 2175 | { |
| 2576 | | /* start at the beginning */ |
| 2577 | | metaentry->offset = chd->header.metaoffset; |
| 2578 | | metaentry->prev = 0; |
| 2176 | // verify that we are appending properly to a compressed file |
| 2177 | verify_proper_compression_append(hunknum); |
| 2579 | 2178 | |
| 2580 | | /* loop until we run out of options */ |
| 2581 | | while (metaentry->offset != 0) |
| 2582 | | { |
| 2583 | | UINT8 raw_meta_header[METADATA_HEADER_SIZE]; |
| 2584 | | UINT32 count; |
| 2585 | | |
| 2586 | | /* read the raw header */ |
| 2587 | | core_fseek(chd->file, metaentry->offset, SEEK_SET); |
| 2588 | | count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); |
| 2589 | | if (count != sizeof(raw_meta_header)) |
| 2590 | | break; |
| 2591 | | |
| 2592 | | /* extract the data */ |
| 2593 | | metaentry->metatag = get_bigendian_uint32(&raw_meta_header[0]); |
| 2594 | | metaentry->length = get_bigendian_uint32(&raw_meta_header[4]); |
| 2595 | | metaentry->next = get_bigendian_uint64(&raw_meta_header[8]); |
| 2596 | | |
| 2597 | | /* flags are encoded in the high byte of length */ |
| 2598 | | metaentry->flags = metaentry->length >> 24; |
| 2599 | | metaentry->length &= 0x00ffffff; |
| 2600 | | |
| 2601 | | /* if we got a match, proceed */ |
| 2602 | | if (metatag == CHDMETATAG_WILDCARD || metaentry->metatag == metatag) |
| 2603 | | if (metaindex-- == 0) |
| 2604 | | return CHDERR_NONE; |
| 2605 | | |
| 2606 | | /* no match, fetch the next link */ |
| 2607 | | metaentry->prev = metaentry->offset; |
| 2608 | | metaentry->offset = metaentry->next; |
| 2609 | | } |
| 2610 | | |
| 2611 | | /* if we get here, we didn't find it */ |
| 2612 | | return CHDERR_METADATA_NOT_FOUND; |
| 2179 | // update the map entry |
| 2180 | UINT8 *rawmap = &m_rawmap[hunknum * 12]; |
| 2181 | rawmap[0] = COMPRESSION_PARENT; |
| 2182 | be_write(&rawmap[1], 0, 3); |
| 2183 | be_write(&rawmap[4], parentunit, 6); |
| 2184 | be_write(&rawmap[10], 0, 2); |
| 2613 | 2185 | } |
| 2614 | 2186 | |
| 2615 | 2187 | |
| 2616 | | /*------------------------------------------------- |
| 2617 | | metadata_set_previous_next - set the 'next' |
| 2618 | | offset of a piece of metadata |
| 2619 | | -------------------------------------------------*/ |
| 2188 | //------------------------------------------------- |
| 2189 | // metadata_find - find a metadata entry |
| 2190 | //------------------------------------------------- |
| 2620 | 2191 | |
| 2621 | | static chd_error metadata_set_previous_next(chd_file *chd, UINT64 prevoffset, UINT64 nextoffset) |
| 2192 | bool chd_file::metadata_find(chd_metadata_tag metatag, INT32 metaindex, metadata_entry &metaentry, bool resume) |
| 2622 | 2193 | { |
| 2623 | | UINT8 raw_meta_header[METADATA_HEADER_SIZE]; |
| 2624 | | chd_error err; |
| 2625 | | UINT32 count; |
| 2626 | | |
| 2627 | | /* if we were the first entry, make the next entry the first */ |
| 2628 | | if (prevoffset == 0) |
| 2194 | // start at the beginning unless we're resuming a previous search |
| 2195 | if (!resume) |
| 2629 | 2196 | { |
| 2630 | | chd->header.metaoffset = nextoffset; |
| 2631 | | err = header_write(chd->file, &chd->header); |
| 2632 | | if (err != CHDERR_NONE) |
| 2633 | | return err; |
| 2197 | metaentry.offset = m_metaoffset; |
| 2198 | metaentry.prev = 0; |
| 2634 | 2199 | } |
| 2635 | | |
| 2636 | | /* otherwise, update the link in the previous pointer */ |
| 2637 | 2200 | else |
| 2638 | 2201 | { |
| 2639 | | /* read the previous raw header */ |
| 2640 | | core_fseek(chd->file, prevoffset, SEEK_SET); |
| 2641 | | count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); |
| 2642 | | if (count != sizeof(raw_meta_header)) |
| 2643 | | return CHDERR_READ_ERROR; |
| 2644 | | |
| 2645 | | /* copy our next pointer into the previous->next offset */ |
| 2646 | | put_bigendian_uint64(&raw_meta_header[8], nextoffset); |
| 2647 | | |
| 2648 | | /* write the previous raw header */ |
| 2649 | | core_fseek(chd->file, prevoffset, SEEK_SET); |
| 2650 | | count = core_fwrite(chd->file, raw_meta_header, sizeof(raw_meta_header)); |
| 2651 | | if (count != sizeof(raw_meta_header)) |
| 2652 | | return CHDERR_WRITE_ERROR; |
| 2202 | metaentry.prev = metaentry.offset; |
| 2203 | metaentry.offset = metaentry.next; |
| 2653 | 2204 | } |
| 2654 | 2205 | |
| 2655 | | return CHDERR_NONE; |
| 2656 | | } |
| 2657 | | |
| 2658 | | |
| 2659 | | /*------------------------------------------------- |
| 2660 | | metadata_set_length - set the length field of |
| 2661 | | a piece of metadata |
| 2662 | | -------------------------------------------------*/ |
| 2663 | | |
| 2664 | | static chd_error metadata_set_length(chd_file *chd, UINT64 offset, UINT32 length) |
| 2665 | | { |
| 2666 | | UINT8 raw_meta_header[METADATA_HEADER_SIZE]; |
| 2667 | | UINT32 oldlength; |
| 2668 | | UINT32 count; |
| 2669 | | |
| 2670 | | /* read the raw header */ |
| 2671 | | core_fseek(chd->file, offset, SEEK_SET); |
| 2672 | | count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); |
| 2673 | | if (count != sizeof(raw_meta_header)) |
| 2674 | | return CHDERR_READ_ERROR; |
| 2675 | | |
| 2676 | | /* update the length at offset 4, preserving the flags in the upper byte */ |
| 2677 | | oldlength = get_bigendian_uint32(&raw_meta_header[4]); |
| 2678 | | length = (length & 0x00ffffff) | (oldlength & 0xff000000); |
| 2679 | | put_bigendian_uint32(&raw_meta_header[4], length); |
| 2680 | | |
| 2681 | | /* write the raw header */ |
| 2682 | | core_fseek(chd->file, offset, SEEK_SET); |
| 2683 | | count = core_fwrite(chd->file, raw_meta_header, sizeof(raw_meta_header)); |
| 2684 | | if (count != sizeof(raw_meta_header)) |
| 2685 | | return CHDERR_WRITE_ERROR; |
| 2686 | | |
| 2687 | | return CHDERR_NONE; |
| 2688 | | } |
| 2689 | | |
| 2690 | | |
| 2691 | | /*------------------------------------------------- |
| 2692 | | metadata_compute_hash - compute the SHA1 |
| 2693 | | hash of all metadata that requests it |
| 2694 | | -------------------------------------------------*/ |
| 2695 | | |
| 2696 | | static chd_error metadata_compute_hash(chd_file *chd, const UINT8 *rawsha1, UINT8 *finalsha1) |
| 2697 | | { |
| 2698 | | metadata_hash *hasharray = NULL; |
| 2699 | | chd_error err = CHDERR_NONE; |
| 2700 | | struct sha1_ctx sha1; |
| 2701 | | UINT32 hashindex = 0; |
| 2702 | | UINT32 hashalloc = 0; |
| 2703 | | UINT64 offset, next; |
| 2704 | | |
| 2705 | | /* only works for V4 and above */ |
| 2706 | | if (chd->header.version < 4) |
| 2206 | // loop until we run out of options |
| 2207 | while (metaentry.offset != 0) |
| 2707 | 2208 | { |
| 2708 | | memcpy(finalsha1, rawsha1, SHA1_DIGEST_SIZE); |
| 2709 | | return CHDERR_NONE; |
| 2710 | | } |
| 2711 | | |
| 2712 | | /* loop until we run out of data */ |
| 2713 | | for (offset = chd->header.metaoffset; offset != 0; offset = next) |
| 2714 | | { |
| 2209 | // read the raw header |
| 2715 | 2210 | UINT8 raw_meta_header[METADATA_HEADER_SIZE]; |
| 2716 | | UINT32 count, metalength, metatag; |
| 2717 | | UINT8 *tempbuffer; |
| 2718 | | UINT8 metaflags; |
| 2211 | file_read(metaentry.offset, raw_meta_header, sizeof(raw_meta_header)); |
| 2719 | 2212 | |
| 2720 | | /* read the raw header */ |
| 2721 | | core_fseek(chd->file, offset, SEEK_SET); |
| 2722 | | count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); |
| 2723 | | if (count != sizeof(raw_meta_header)) |
| 2724 | | break; |
| 2213 | // extract the data |
| 2214 | metaentry.metatag = be_read(&raw_meta_header[0], 4); |
| 2215 | metaentry.flags = raw_meta_header[4]; |
| 2216 | metaentry.length = be_read(&raw_meta_header[5], 3); |
| 2217 | metaentry.next = be_read(&raw_meta_header[8], 8); |
| 2725 | 2218 | |
| 2726 | | /* extract the data */ |
| 2727 | | metatag = get_bigendian_uint32(&raw_meta_header[0]); |
| 2728 | | metalength = get_bigendian_uint32(&raw_meta_header[4]); |
| 2729 | | next = get_bigendian_uint64(&raw_meta_header[8]); |
| 2219 | // if we got a match, proceed |
| 2220 | if (metatag == CHDMETATAG_WILDCARD || metaentry.metatag == metatag) |
| 2221 | if (metaindex-- == 0) |
| 2222 | return true; |
| 2730 | 2223 | |
| 2731 | | /* flags are encoded in the high byte of length */ |
| 2732 | | metaflags = metalength >> 24; |
| 2733 | | metalength &= 0x00ffffff; |
| 2734 | | |
| 2735 | | /* if not checksumming, continue */ |
| 2736 | | if (!(metaflags & CHD_MDFLAGS_CHECKSUM)) |
| 2737 | | continue; |
| 2738 | | |
| 2739 | | /* allocate memory */ |
| 2740 | | tempbuffer = (UINT8 *)malloc(metalength); |
| 2741 | | if (tempbuffer == NULL) |
| 2742 | | { |
| 2743 | | err = CHDERR_OUT_OF_MEMORY; |
| 2744 | | goto cleanup; |
| 2745 | | } |
| 2746 | | |
| 2747 | | /* seek and read the metadata */ |
| 2748 | | core_fseek(chd->file, offset + METADATA_HEADER_SIZE, SEEK_SET); |
| 2749 | | count = core_fread(chd->file, tempbuffer, metalength); |
| 2750 | | if (count != metalength) |
| 2751 | | { |
| 2752 | | free(tempbuffer); |
| 2753 | | err = CHDERR_READ_ERROR; |
| 2754 | | goto cleanup; |
| 2755 | | } |
| 2756 | | |
| 2757 | | /* compute this entry's hash */ |
| 2758 | | sha1_init(&sha1); |
| 2759 | | sha1_update(&sha1, metalength, tempbuffer); |
| 2760 | | sha1_final(&sha1); |
| 2761 | | free(tempbuffer); |
| 2762 | | |
| 2763 | | /* expand the hasharray if necessary */ |
| 2764 | | if (hashindex >= hashalloc) |
| 2765 | | { |
| 2766 | | hashalloc += 256; |
| 2767 | | hasharray = (metadata_hash *)realloc(hasharray, hashalloc * sizeof(hasharray[0])); |
| 2768 | | if (hasharray == NULL) |
| 2769 | | { |
| 2770 | | err = CHDERR_OUT_OF_MEMORY; |
| 2771 | | goto cleanup; |
| 2772 | | } |
| 2773 | | } |
| 2774 | | |
| 2775 | | /* fill in the entry */ |
| 2776 | | put_bigendian_uint32(hasharray[hashindex].tag, metatag); |
| 2777 | | sha1_digest(&sha1, SHA1_DIGEST_SIZE, hasharray[hashindex].sha1); |
| 2778 | | hashindex++; |
| 2224 | // no match, fetch the next link |
| 2225 | metaentry.prev = metaentry.offset; |
| 2226 | metaentry.offset = metaentry.next; |
| 2779 | 2227 | } |
| 2780 | 2228 | |
| 2781 | | /* sort the array */ |
| 2782 | | qsort(hasharray, hashindex, sizeof(hasharray[0]), metadata_hash_compare); |
| 2783 | | |
| 2784 | | /* compute the SHA1 of the raw plus the various metadata */ |
| 2785 | | sha1_init(&sha1); |
| 2786 | | sha1_update(&sha1, CHD_SHA1_BYTES, rawsha1); |
| 2787 | | sha1_update(&sha1, hashindex * sizeof(hasharray[0]), (const UINT8 *)hasharray); |
| 2788 | | sha1_final(&sha1); |
| 2789 | | sha1_digest(&sha1, SHA1_DIGEST_SIZE, finalsha1); |
| 2790 | | |
| 2791 | | cleanup: |
| 2792 | | if (hasharray != NULL) |
| 2793 | | free(hasharray); |
| 2794 | | return err; |
| 2229 | // if we get here, we didn't find it |
| 2230 | return false; |
| 2795 | 2231 | } |
| 2796 | 2232 | |
| 2797 | 2233 | |
| 2798 | | /*------------------------------------------------- |
| 2799 | | metadata_hash_compare - compare two hash |
| 2800 | | entries |
| 2801 | | -------------------------------------------------*/ |
| 2234 | //------------------------------------------------- |
| 2235 | // metadata_set_previous_next - set the 'next' |
| 2236 | // offset of a piece of metadata |
| 2237 | //------------------------------------------------- |
| 2802 | 2238 | |
| 2803 | | static int CLIB_DECL metadata_hash_compare(const void *elem1, const void *elem2) |
| 2239 | void chd_file::metadata_set_previous_next(UINT64 prevoffset, UINT64 nextoffset) |
| 2804 | 2240 | { |
| 2805 | | return memcmp(elem1, elem2, sizeof(metadata_hash)); |
| 2806 | | } |
| 2241 | UINT64 offset = 0; |
| 2807 | 2242 | |
| 2808 | | |
| 2809 | | |
| 2810 | | /*************************************************************************** |
| 2811 | | ZLIB COMPRESSION CODEC |
| 2812 | | ***************************************************************************/ |
| 2813 | | |
| 2814 | | /*------------------------------------------------- |
| 2815 | | zlib_codec_init - initialize the ZLIB codec |
| 2816 | | -------------------------------------------------*/ |
| 2817 | | |
| 2818 | | static chd_error zlib_codec_init(chd_file *chd) |
| 2819 | | { |
| 2820 | | zlib_codec_data *data; |
| 2821 | | chd_error err; |
| 2822 | | int zerr; |
| 2823 | | |
| 2824 | | /* allocate memory for the 2 stream buffers */ |
| 2825 | | data = (zlib_codec_data *)malloc(sizeof(*data)); |
| 2826 | | if (data == NULL) |
| 2827 | | return CHDERR_OUT_OF_MEMORY; |
| 2828 | | |
| 2829 | | /* clear the buffers */ |
| 2830 | | memset(data, 0, sizeof(*data)); |
| 2831 | | |
| 2832 | | /* init the inflater first */ |
| 2833 | | data->inflater.next_in = (Bytef *)data; /* bogus, but that's ok */ |
| 2834 | | data->inflater.avail_in = 0; |
| 2835 | | data->inflater.zalloc = zlib_fast_alloc; |
| 2836 | | data->inflater.zfree = zlib_fast_free; |
| 2837 | | data->inflater.opaque = data; |
| 2838 | | zerr = inflateInit2(&data->inflater, -MAX_WBITS); |
| 2839 | | |
| 2840 | | /* if that worked, initialize the deflater */ |
| 2841 | | if (zerr == Z_OK) |
| 2243 | // if we were the first entry, make the next entry the first |
| 2244 | if (prevoffset == 0) |
| 2842 | 2245 | { |
| 2843 | | data->deflater.next_in = (Bytef *)data; /* bogus, but that's ok */ |
| 2844 | | data->deflater.avail_in = 0; |
| 2845 | | data->deflater.zalloc = zlib_fast_alloc; |
| 2846 | | data->deflater.zfree = zlib_fast_free; |
| 2847 | | data->deflater.opaque = data; |
| 2848 | | zerr = deflateInit2(&data->deflater, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); |
| 2246 | offset = m_metaoffset_offset; |
| 2247 | m_metaoffset = nextoffset; |
| 2849 | 2248 | } |
| 2850 | 2249 | |
| 2851 | | /* convert errors */ |
| 2852 | | if (zerr == Z_MEM_ERROR) |
| 2853 | | err = CHDERR_OUT_OF_MEMORY; |
| 2854 | | else if (zerr != Z_OK) |
| 2855 | | err = CHDERR_CODEC_ERROR; |
| 2250 | // otherwise, update the link in the previous header |
| 2856 | 2251 | else |
| 2857 | | err = CHDERR_NONE; |
| 2858 | | |
| 2859 | | /* handle an error */ |
| 2860 | | if (err == CHDERR_NONE) |
| 2861 | | chd->codecdata = data; |
| 2862 | | else |
| 2863 | | free(data); |
| 2864 | | |
| 2865 | | return err; |
| 2252 | offset = prevoffset + 8; |
| 2253 | |
| 2254 | // create a big-endian version |
| 2255 | UINT8 rawbuf[sizeof(UINT64)]; |
| 2256 | be_write(rawbuf, nextoffset, 8); |
| 2257 | |
| 2258 | // write to the header and update our local copy |
| 2259 | file_write(offset, rawbuf, sizeof(rawbuf)); |
| 2866 | 2260 | } |
| 2867 | 2261 | |
| 2868 | 2262 | |
| 2869 | | /*------------------------------------------------- |
| 2870 | | zlib_codec_free - free data for the ZLIB |
| 2871 | | codec |
| 2872 | | -------------------------------------------------*/ |
| 2263 | //------------------------------------------------- |
| 2264 | // metadata_update_hash - compute the SHA1 |
| 2265 | // hash of all metadata that requests it |
| 2266 | //------------------------------------------------- |
| 2873 | 2267 | |
| 2874 | | static void zlib_codec_free(chd_file *chd) |
| 2268 | void chd_file::metadata_update_hash() |
| 2875 | 2269 | { |
| 2876 | | zlib_codec_data *data = (zlib_codec_data *)chd->codecdata; |
| 2270 | // only works for V4 and above, and only for compressed CHDs |
| 2271 | if (m_version < 4 || !compressed()) |
| 2272 | return; |
| 2877 | 2273 | |
| 2878 | | /* deinit the streams */ |
| 2879 | | if (data != NULL) |
| 2880 | | { |
| 2881 | | int i; |
| 2882 | | |
| 2883 | | inflateEnd(&data->inflater); |
| 2884 | | deflateEnd(&data->deflater); |
| 2885 | | |
| 2886 | | /* free our fast memory */ |
| 2887 | | for (i = 0; i < MAX_ZLIB_ALLOCS; i++) |
| 2888 | | if (data->allocptr[i]) |
| 2889 | | free(data->allocptr[i]); |
| 2890 | | free(data); |
| 2891 | | } |
| 2274 | // compute the new overall hash |
| 2275 | sha1_t fullsha1 = compute_overall_sha1(raw_sha1()); |
| 2276 | |
| 2277 | // create a big-endian version |
| 2278 | UINT8 rawbuf[sizeof(sha1_t)]; |
| 2279 | be_write_sha1(&rawbuf[0], fullsha1); |
| 2280 | |
| 2281 | // write to the header |
| 2282 | file_write(m_sha1_offset, rawbuf, sizeof(rawbuf)); |
| 2892 | 2283 | } |
| 2893 | 2284 | |
| 2894 | 2285 | |
| 2895 | | /*------------------------------------------------- |
| 2896 | | zlib_codec_compress - compress data using the |
| 2897 | | ZLIB codec |
| 2898 | | -------------------------------------------------*/ |
| 2286 | //------------------------------------------------- |
| 2287 | // metadata_hash_compare - compare two hash |
| 2288 | // entries |
| 2289 | //------------------------------------------------- |
| 2899 | 2290 | |
| 2900 | | static chd_error zlib_codec_compress(chd_file *chd, const void *src, UINT32 *length) |
| 2291 | int CLIB_DECL chd_file::metadata_hash_compare(const void *elem1, const void *elem2) |
| 2901 | 2292 | { |
| 2902 | | zlib_codec_data *data = (zlib_codec_data *)chd->codecdata; |
| 2903 | | int zerr; |
| 2904 | | |
| 2905 | | /* reset the decompressor */ |
| 2906 | | data->deflater.next_in = (Bytef *)src; |
| 2907 | | data->deflater.avail_in = chd->header.hunkbytes; |
| 2908 | | data->deflater.total_in = 0; |
| 2909 | | data->deflater.next_out = chd->compressed; |
| 2910 | | data->deflater.avail_out = chd->header.hunkbytes; |
| 2911 | | data->deflater.total_out = 0; |
| 2912 | | zerr = deflateReset(&data->deflater); |
| 2913 | | if (zerr != Z_OK) |
| 2914 | | return CHDERR_COMPRESSION_ERROR; |
| 2915 | | |
| 2916 | | /* do it */ |
| 2917 | | zerr = deflate(&data->deflater, Z_FINISH); |
| 2918 | | |
| 2919 | | /* if we ended up with more data than we started with, return an error */ |
| 2920 | | if (zerr != Z_STREAM_END || data->deflater.total_out >= chd->header.hunkbytes) |
| 2921 | | return CHDERR_COMPRESSION_ERROR; |
| 2922 | | |
| 2923 | | /* otherwise, fill in the length and return success */ |
| 2924 | | *length = data->deflater.total_out; |
| 2925 | | return CHDERR_NONE; |
| 2293 | return memcmp(elem1, elem2, sizeof(metadata_hash)); |
| 2926 | 2294 | } |
| 2927 | 2295 | |
| 2928 | 2296 | |
| 2929 | | /*------------------------------------------------- |
| 2930 | | zlib_codec_decompress - decomrpess data using |
| 2931 | | the ZLIB codec |
| 2932 | | -------------------------------------------------*/ |
| 2933 | 2297 | |
| 2934 | | static chd_error zlib_codec_decompress(chd_file *chd, UINT32 srclength, void *dest) |
| 2935 | | { |
| 2936 | | zlib_codec_data *data = (zlib_codec_data *)chd->codecdata; |
| 2937 | | int zerr; |
| 2298 | //************************************************************************** |
| 2299 | // CHD COMPRESSOR |
| 2300 | //************************************************************************** |
| 2938 | 2301 | |
| 2939 | | /* reset the decompressor */ |
| 2940 | | data->inflater.next_in = chd->compressed; |
| 2941 | | data->inflater.avail_in = srclength; |
| 2942 | | data->inflater.total_in = 0; |
| 2943 | | data->inflater.next_out = (Bytef *)dest; |
| 2944 | | data->inflater.avail_out = chd->header.hunkbytes; |
| 2945 | | data->inflater.total_out = 0; |
| 2946 | | zerr = inflateReset(&data->inflater); |
| 2947 | | if (zerr != Z_OK) |
| 2948 | | return CHDERR_DECOMPRESSION_ERROR; |
| 2302 | //------------------------------------------------- |
| 2303 | // chd_file_compressor - constructor |
| 2304 | //------------------------------------------------- |
| 2949 | 2305 | |
| 2950 | | /* do it */ |
| 2951 | | zerr = inflate(&data->inflater, Z_FINISH); |
| 2952 | | if (data->inflater.total_out != chd->header.hunkbytes) |
| 2953 | | return CHDERR_DECOMPRESSION_ERROR; |
| 2954 | | |
| 2955 | | return CHDERR_NONE; |
| 2956 | | } |
| 2957 | | |
| 2958 | | |
| 2959 | | /*------------------------------------------------- |
| 2960 | | zlib_fast_alloc - fast malloc for ZLIB, which |
| 2961 | | allocates and frees memory frequently |
| 2962 | | -------------------------------------------------*/ |
| 2963 | | |
| 2964 | | static voidpf zlib_fast_alloc(voidpf opaque, uInt items, uInt size) |
| 2306 | chd_file_compressor::chd_file_compressor() |
| 2307 | : m_walking_parent(false), |
| 2308 | m_total_in(0), |
| 2309 | m_total_out(0), |
| 2310 | m_read_queue(NULL), |
| 2311 | m_read_queue_offset(0), |
| 2312 | m_read_done_offset(0), |
| 2313 | m_read_error(false), |
| 2314 | m_work_queue(NULL), |
| 2315 | m_write_hunk(0) |
| 2965 | 2316 | { |
| 2966 | | zlib_codec_data *data = (zlib_codec_data *)opaque; |
| 2967 | | UINT32 *ptr; |
| 2968 | | int i; |
| 2317 | // zap arrays |
| 2318 | memset(m_work_item, 0, sizeof(m_work_item)); |
| 2319 | memset(m_codecs, 0, sizeof(m_codecs)); |
| 2969 | 2320 | |
| 2970 | | /* compute the size, rounding to the nearest 1k */ |
| 2971 | | size = (size * items + 0x3ff) & ~0x3ff; |
| 2972 | | |
| 2973 | | /* reuse a hunk if we can */ |
| 2974 | | for (i = 0; i < MAX_ZLIB_ALLOCS; i++) |
| 2975 | | { |
| 2976 | | ptr = data->allocptr[i]; |
| 2977 | | if (ptr && size == *ptr) |
| 2978 | | { |
| 2979 | | /* set the low bit of the size so we don't match next time */ |
| 2980 | | *ptr |= 1; |
| 2981 | | return ptr + 1; |
| 2982 | | } |
| 2983 | | } |
| 2984 | | |
| 2985 | | /* alloc a new one */ |
| 2986 | | ptr = (UINT32 *)malloc(size + sizeof(UINT32)); |
| 2987 | | if (!ptr) |
| 2988 | | return NULL; |
| 2989 | | |
| 2990 | | /* put it into the list */ |
| 2991 | | for (i = 0; i < MAX_ZLIB_ALLOCS; i++) |
| 2992 | | if (!data->allocptr[i]) |
| 2993 | | { |
| 2994 | | data->allocptr[i] = ptr; |
| 2995 | | break; |
| 2996 | | } |
| 2997 | | |
| 2998 | | /* set the low bit of the size so we don't match next time */ |
| 2999 | | *ptr = size | 1; |
| 3000 | | return ptr + 1; |
| 2321 | // allocate work queues |
| 2322 | m_read_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_IO); |
| 2323 | m_work_queue = osd_work_queue_alloc(WORK_QUEUE_FLAG_MULTI); |
| 3001 | 2324 | } |
| 3002 | 2325 | |
| 3003 | 2326 | |
| 3004 | | /*------------------------------------------------- |
| 3005 | | zlib_fast_free - fast free for ZLIB, which |
| 3006 | | allocates and frees memory frequently |
| 3007 | | -------------------------------------------------*/ |
| 2327 | //------------------------------------------------- |
| 2328 | // ~chd_file_compressor - destructor |
| 2329 | //------------------------------------------------- |
| 3008 | 2330 | |
| 3009 | | static void zlib_fast_free(voidpf opaque, voidpf address) |
| 2331 | chd_file_compressor::~chd_file_compressor() |
| 3010 | 2332 | { |
| 3011 | | zlib_codec_data *data = (zlib_codec_data *)opaque; |
| 3012 | | UINT32 *ptr = (UINT32 *)address - 1; |
| 3013 | | int i; |
| 3014 | | |
| 3015 | | /* find the hunk */ |
| 3016 | | for (i = 0; i < MAX_ZLIB_ALLOCS; i++) |
| 3017 | | if (ptr == data->allocptr[i]) |
| 3018 | | { |
| 3019 | | /* clear the low bit of the size to allow matches */ |
| 3020 | | *ptr &= ~1; |
| 3021 | | return; |
| 3022 | | } |
| 2333 | // free the work queues |
| 2334 | osd_work_queue_free(m_read_queue); |
| 2335 | osd_work_queue_free(m_work_queue); |
| 2336 | |
| 2337 | // delete allocated arrays |
| 2338 | for (int codecnum = 0; codecnum < ARRAY_LENGTH(m_codecs); codecnum++) |
| 2339 | delete m_codecs[codecnum]; |
| 3023 | 2340 | } |
| 3024 | 2341 | |
| 3025 | | /*------------------------------------------------- |
| 3026 | | flac_codec_compress - compress data using the |
| 3027 | | FLAC codec |
| 3028 | | -------------------------------------------------*/ |
| 3029 | 2342 | |
| 2343 | //------------------------------------------------- |
| 2344 | // compress_begin - initiate compression |
| 2345 | //------------------------------------------------- |
| 3030 | 2346 | |
| 3031 | | const int INITIAL_BUFFER_SIZE = 0x20000; |
| 3032 | | const int INITIAL_GROW_SIZE = 0x20000; |
| 3033 | | |
| 3034 | | |
| 3035 | | struct flac_encoder_data |
| 2347 | void chd_file_compressor::compress_begin() |
| 3036 | 2348 | { |
| 3037 | | FLAC__int32* pcm; |
| 3038 | | FLAC__byte* tempbuffer; |
| 3039 | | UINT8* flac_outputbuffer; |
| 3040 | | size_t flac_outputbuffer_size; |
| 3041 | | FLAC__uint64 flac_output_buffer_curpos; |
| 3042 | | FLAC__uint64 flac_output_buffer_total; |
| 3043 | | }; |
| 3044 | | |
| 3045 | | static FLAC__StreamEncoderWriteStatus flac_encoder_write_callback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data) |
| 3046 | | { |
| 3047 | | if (((flac_encoder_data*)client_data)->flac_output_buffer_curpos + bytes >= ((flac_encoder_data*)client_data)->flac_outputbuffer_size) |
| 2349 | // reset state |
| 2350 | m_walking_parent = (m_parent != NULL); |
| 2351 | m_total_in = 0; |
| 2352 | m_total_out = 0; |
| 2353 | m_compsha1.reset(); |
| 2354 | |
| 2355 | // reset our maps |
| 2356 | m_parent_map.reset(); |
| 2357 | m_current_map.reset(); |
| 2358 | |
| 2359 | // reset read state |
| 2360 | m_read_queue_offset = 0; |
| 2361 | m_read_done_offset = 0; |
| 2362 | m_read_error = false; |
| 2363 | |
| 2364 | // reset work item state |
| 2365 | m_work_buffer.resize(hunk_bytes() * (WORK_BUFFER_HUNKS + 1)); |
| 2366 | m_compressed_buffer.resize(hunk_bytes() * WORK_BUFFER_HUNKS); |
| 2367 | for (int itemnum = 0; itemnum < WORK_BUFFER_HUNKS; itemnum++) |
| 3048 | 2368 | { |
| 3049 | | ((flac_encoder_data*)client_data)->flac_outputbuffer = (UINT8*)realloc(((flac_encoder_data*)client_data)->flac_outputbuffer, ((flac_encoder_data*)client_data)->flac_outputbuffer_size+INITIAL_GROW_SIZE); |
| 3050 | | ((flac_encoder_data*)client_data)->flac_outputbuffer_size = ((flac_encoder_data*)client_data)->flac_outputbuffer_size+INITIAL_GROW_SIZE; |
| 2369 | work_item &item = m_work_item[itemnum]; |
| 2370 | item.m_compressor = this; |
| 2371 | item.m_data = m_work_buffer + hunk_bytes() * itemnum; |
| 2372 | item.m_compressed = m_compressed_buffer + hunk_bytes() * itemnum; |
| 2373 | item.m_hash.resize(hunk_bytes() / unit_bytes()); |
| 3051 | 2374 | } |
| 3052 | | |
| 3053 | | memcpy(((flac_encoder_data*)client_data)->flac_outputbuffer+((flac_encoder_data*)client_data)->flac_output_buffer_curpos, buffer, bytes); |
| 3054 | | if (((flac_encoder_data*)client_data)->flac_output_buffer_curpos+bytes > ((flac_encoder_data*)client_data)->flac_output_buffer_total) |
| 3055 | | ((flac_encoder_data*)client_data)->flac_output_buffer_total=((flac_encoder_data*)client_data)->flac_output_buffer_curpos+bytes; |
| 3056 | | |
| 3057 | | ((flac_encoder_data*)client_data)->flac_output_buffer_curpos+=bytes; |
| 3058 | | |
| 3059 | | return FLAC__STREAM_ENCODER_WRITE_STATUS_OK; |
| 2375 | |
| 2376 | // initialize codec instances |
| 2377 | for (int instance = 0; instance < ARRAY_LENGTH(m_codecs); instance++) |
| 2378 | { |
| 2379 | delete m_codecs[instance]; |
| 2380 | m_codecs[instance] = new chd_compressor_group(*this, m_compression); |
| 2381 | } |
| 2382 | |
| 2383 | // reset write state |
| 2384 | m_write_hunk = 0; |
| 3060 | 2385 | } |
| 3061 | 2386 | |
| 3062 | | #define FLAC_HEADER_SIZE (86) |
| 3063 | 2387 | |
| 3064 | | static chd_error flac_codec_compress(chd_file *chd, const void *src, UINT32 *length, int swap) |
| 3065 | | { |
| 3066 | | int FLAC_ENCODER_READSIZE = (CD_MAX_SECTOR_DATA/4); |
| 3067 | | int FLAC_ENCODER_FULLSIZE = ((CD_MAX_SECTOR_DATA+CD_MAX_SUBCODE_DATA)/4); |
| 2388 | //------------------------------------------------- |
| 2389 | // compress_continue - continue compression |
| 2390 | //------------------------------------------------- |
| 3068 | 2391 | |
| 3069 | | flac_encoder_data flac_encoder_client; |
| 3070 | | flac_encoder_data* flac_encoder_client_ptr = &flac_encoder_client; |
| 3071 | | flac_encoder_client_ptr->flac_outputbuffer = (UINT8*)malloc(INITIAL_BUFFER_SIZE); |
| 3072 | | flac_encoder_client_ptr->flac_outputbuffer_size = INITIAL_BUFFER_SIZE; |
| 3073 | | flac_encoder_client_ptr->flac_output_buffer_curpos = 0; |
| 3074 | | flac_encoder_client_ptr->flac_output_buffer_total = 0; |
| 3075 | | flac_encoder_client_ptr->pcm=(FLAC__int32*)malloc(FLAC_ENCODER_READSIZE * 2 * 4); |
| 3076 | | flac_encoder_client_ptr->tempbuffer=(FLAC__byte*)malloc(FLAC_ENCODER_READSIZE * 2 * 2); |
| 3077 | | |
| 3078 | | |
| 3079 | | |
| 3080 | | FLAC__StreamEncoder *encoder = 0; |
| 3081 | | FLAC__bool ok = true; |
| 3082 | | |
| 3083 | | if((encoder = FLAC__stream_encoder_new()) == NULL) |
| 2392 | chd_error chd_file_compressor::compress_continue(double &progress, double &ratio) |
| 2393 | { |
| 2394 | // if done reading, queue some more |
| 2395 | while (m_read_queue_offset < m_logicalbytes && osd_work_queue_items(m_read_queue) < 2) |
| 3084 | 2396 | { |
| 3085 | | printf("ERROR: allocating encoder\n"); |
| 3086 | | return CHDERR_COMPRESSION_ERROR; |
| 3087 | | } |
| 2397 | // if we got an error, return an error |
| 2398 | if (m_read_error) |
| 2399 | return CHDERR_READ_ERROR; |
| 3088 | 2400 | |
| 3089 | | ok &= FLAC__stream_encoder_set_verify(encoder, false); // we trust libFLAC ;-) |
| 3090 | | ok &= FLAC__stream_encoder_set_compression_level(encoder, 8); |
| 3091 | | ok &= FLAC__stream_encoder_set_channels(encoder, 2); |
| 3092 | | ok &= FLAC__stream_encoder_set_bits_per_sample(encoder, 16); |
| 3093 | | ok &= FLAC__stream_encoder_set_sample_rate(encoder, 44100); |
| 3094 | | ok &= FLAC__stream_encoder_set_total_samples_estimate(encoder, 0); |
| 3095 | | ok &= FLAC__stream_encoder_set_streamable_subset(encoder, false); |
| 3096 | | ok &= FLAC__stream_encoder_set_blocksize(encoder, ((CD_MAX_SECTOR_DATA)*CD_FRAMES_PER_HUNK)/4 ); // /4 because this is in SAMPLES, not bytes |
| 2401 | // see if we have enough free work items to read the next half of a buffer |
| 2402 | UINT32 startitem = m_read_queue_offset / hunk_bytes(); |
| 2403 | UINT32 enditem = startitem + WORK_BUFFER_HUNKS / 2; |
| 2404 | UINT32 curitem; |
| 2405 | for (curitem = startitem; curitem < enditem; curitem++) |
| 2406 | if (m_work_item[curitem % WORK_BUFFER_HUNKS].m_status != WS_READY) |
| 2407 | break; |
| 3097 | 2408 | |
| 3098 | | if (!ok) |
| 3099 | | { |
| 3100 | | printf("error setting up stream encoder\n"); |
| 3101 | | return CHDERR_COMPRESSION_ERROR; |
| 3102 | | } |
| 2409 | // if it's not all clear, defer |
| 2410 | if (curitem != enditem) |
| 2411 | break; |
| 2412 | |
| 2413 | // if we're walking the parent, we want one more item to have cleared so we |
| 2414 | // can read an extra hunk there |
| 2415 | if (m_walking_parent && m_work_item[curitem % WORK_BUFFER_HUNKS].m_status != WS_READY) |
| 2416 | break; |
| 3103 | 2417 | |
| 3104 | | if (FLAC__stream_encoder_init_stream(encoder, flac_encoder_write_callback, NULL, NULL, NULL, flac_encoder_client_ptr) != FLAC__STREAM_ENCODER_INIT_STATUS_OK) |
| 3105 | | { |
| 3106 | | printf("error initializing encoder\n"); |
| 3107 | | return CHDERR_COMPRESSION_ERROR; |
| 2418 | // queue the next read |
| 2419 | for (curitem = startitem; curitem < enditem; curitem++) |
| 2420 | m_work_item[curitem % WORK_BUFFER_HUNKS].m_status = WS_READING; |
| 2421 | osd_work_item_queue(m_read_queue, async_read_static, this, WORK_ITEM_FLAG_AUTO_RELEASE); |
| 2422 | m_read_queue_offset += WORK_BUFFER_HUNKS * hunk_bytes() / 2; |
| 3108 | 2423 | } |
| 3109 | | |
| 3110 | | size_t left = (size_t)chd->header.hunkbytes; |
| 3111 | | UINT8* srcdata = (UINT8*)src; |
| 3112 | | |
| 3113 | | while(ok && left) |
| 2424 | |
| 2425 | // flush out any finished items |
| 2426 | while (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status == WS_COMPLETE) |
| 3114 | 2427 | { |
| 3115 | | memcpy(flac_encoder_client_ptr->tempbuffer, srcdata, FLAC_ENCODER_READSIZE*4); |
| 3116 | | srcdata += FLAC_ENCODER_FULLSIZE*4; |
| 2428 | work_item &item = m_work_item[m_write_hunk % WORK_BUFFER_HUNKS]; |
| 2429 | |
| 2430 | // free any OSD work item |
| 2431 | if (item.m_osd != NULL) |
| 2432 | osd_work_item_release(item.m_osd); |
| 2433 | item.m_osd = NULL; |
| 2434 | |
| 2435 | // for parent walking, just add to the hashmap |
| 2436 | if (m_walking_parent) |
| 3117 | 2437 | { |
| 3118 | | size_t i; |
| 3119 | | for(i = 0; i < FLAC_ENCODER_READSIZE*2; i++) |
| 2438 | UINT32 uph = hunk_bytes() / unit_bytes(); |
| 2439 | UINT32 units = uph; |
| 2440 | if (item.m_hunknum == hunk_count() - 1 || !compressed()) |
| 2441 | units = 1; |
| 2442 | for (UINT32 unit = 0; unit < units; unit++) |
| 2443 | if (m_parent_map.find(item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1) == hashmap::NOT_FOUND) |
| 2444 | m_parent_map.add(item.m_hunknum * uph + unit, item.m_hash[unit].m_crc16, item.m_hash[unit].m_sha1); |
| 2445 | } |
| 2446 | |
| 2447 | // if we're uncompressed, use regular writes |
| 2448 | else if (!compressed()) |
| 2449 | { |
| 2450 | bool skip = true; |
| 2451 | |
| 2452 | // see if it's all 0 |
| 2453 | for (UINT32 offs = 0; offs < m_hunkbytes && skip; offs++) |
| 2454 | if (item.m_data[offs] != 0) |
| 2455 | skip = false; |
| 2456 | |
| 2457 | // see if it's in the parent map |
| 2458 | if (!skip && m_parent != NULL && m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) != hashmap::NOT_FOUND) |
| 2459 | skip = true; |
| 2460 | |
| 2461 | // write the block |
| 2462 | if (!skip) |
| 3120 | 2463 | { |
| 3121 | | if (!swap) flac_encoder_client_ptr->pcm[i] = (FLAC__int32)(((FLAC__int16)(FLAC__int8)flac_encoder_client_ptr->tempbuffer[2*i] << 8) | (FLAC__int16)flac_encoder_client_ptr->tempbuffer[2*i+1]); |
| 3122 | | else flac_encoder_client_ptr->pcm[i] = (FLAC__int32)(((FLAC__int16)(FLAC__int8)flac_encoder_client_ptr->tempbuffer[2*i+1] << 8) | (FLAC__int16)flac_encoder_client_ptr->tempbuffer[2*i]); |
| 2464 | chd_error err = write_hunk(item.m_hunknum, item.m_data); |
| 2465 | if (err != CHDERR_NONE) |
| 2466 | return err; |
| 2467 | m_total_out += m_hunkbytes; |
| 3123 | 2468 | } |
| 3124 | | |
| 3125 | | ok = FLAC__stream_encoder_process_interleaved(encoder, flac_encoder_client_ptr->pcm, FLAC_ENCODER_READSIZE); |
| 3126 | 2469 | } |
| 3127 | | |
| 3128 | | |
| 3129 | | left -= (FLAC_ENCODER_FULLSIZE*4); |
| 2470 | |
| 2471 | // for compressing, process the result |
| 2472 | else do |
| 2473 | { |
| 2474 | // first see if the hunk is in the parent or self maps |
| 2475 | UINT64 selfhunk = m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1); |
| 2476 | if (selfhunk != hashmap::NOT_FOUND) |
| 2477 | { |
| 2478 | hunk_copy_from_self(item.m_hunknum, selfhunk); |
| 2479 | break; |
| 2480 | } |
| 2481 | |
| 2482 | // if not, see if it's in the parent map |
| 2483 | if (m_parent != NULL) |
| 2484 | { |
| 2485 | UINT64 parentunit = m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1); |
| 2486 | if (parentunit != hashmap::NOT_FOUND) |
| 2487 | { |
| 2488 | hunk_copy_from_parent(item.m_hunknum, parentunit); |
| 2489 | break; |
| 2490 | } |
| 2491 | } |
| 2492 | |
| 2493 | // otherwise, append it compressed and add to the self map |
| 2494 | hunk_write_compressed(item.m_hunknum, item.m_compression, item.m_compressed, item.m_complen, item.m_hash[0].m_crc16); |
| 2495 | m_total_out += item.m_complen; |
| 2496 | m_current_map.add(item.m_hunknum, item.m_hash[0].m_crc16, item.m_hash[0].m_sha1); |
| 2497 | } while (0); |
| 2498 | |
| 2499 | // reset the item and advance |
| 2500 | item.m_status = WS_READY; |
| 2501 | m_write_hunk++; |
| 2502 | |
| 2503 | // if we hit the end, finalize |
| 2504 | if (m_write_hunk == m_hunkcount) |
| 2505 | { |
| 2506 | // if this is just walking the parent, reset and get ready for compression |
| 2507 | if (m_walking_parent) |
| 2508 | { |
| 2509 | m_walking_parent = false; |
| 2510 | m_read_queue_offset = m_read_done_offset = 0; |
| 2511 | m_write_hunk = 0; |
| 2512 | for (int itemnum = 0; itemnum < WORK_BUFFER_HUNKS; itemnum++) |
| 2513 | m_work_item[itemnum].m_status = WS_READY; |
| 2514 | } |
| 2515 | |
| 2516 | // wait for all reads to finish and if we're compressed, write the final SHA1 and map |
| 2517 | else |
| 2518 | { |
| 2519 | osd_work_queue_wait(m_read_queue, 30 * osd_ticks_per_second()); |
| 2520 | if (!compressed()) |
| 2521 | return CHDERR_NONE; |
| 2522 | set_raw_sha1(m_compsha1.finish()); |
| 2523 | return compress_v5_map(); |
| 2524 | } |
| 2525 | } |
| 3130 | 2526 | } |
| 2527 | |
| 2528 | // update progress and ratio |
| 2529 | if (m_walking_parent) |
| 2530 | progress = double(m_read_done_offset) / double(logical_bytes()); |
| 2531 | else |
| 2532 | progress = double(m_write_hunk) / double(m_hunkcount); |
| 2533 | ratio = (m_total_in == 0) ? 1.0 : double(m_total_out) / double(m_total_in); |
| 3131 | 2534 | |
| 3132 | | if (!ok) |
| 3133 | | { |
| 3134 | | printf("error encoding!\n"); |
| 3135 | | return CHDERR_COMPRESSION_ERROR; |
| 3136 | | } |
| 2535 | // if we're waiting for work, wait |
| 2536 | while (m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_status != WS_COMPLETE && m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd != NULL) |
| 2537 | osd_work_item_wait(m_work_item[m_write_hunk % WORK_BUFFER_HUNKS].m_osd, osd_ticks_per_second()); |
| 3137 | 2538 | |
| 3138 | | ok &= FLAC__stream_encoder_finish(encoder); |
| 2539 | return m_walking_parent ? CHDERR_WALKING_PARENT : CHDERR_COMPRESSING; |
| 2540 | } |
| 3139 | 2541 | |
| 3140 | | if (!ok) |
| 3141 | | { |
| 3142 | | printf("error finishing!\n"); |
| 3143 | | return CHDERR_COMPRESSION_ERROR; |
| 3144 | | } |
| 3145 | 2542 | |
| 3146 | | int totalout = flac_encoder_client_ptr->flac_output_buffer_total-FLAC_HEADER_SIZE; |
| 2543 | //------------------------------------------------- |
| 2544 | // async_walk_parent - handle asynchronous parent |
| 2545 | // walking operations |
| 2546 | //------------------------------------------------- |
| 3147 | 2547 | |
| 2548 | void *chd_file_compressor::async_walk_parent_static(void *param, int threadid) |
| 2549 | { |
| 2550 | work_item *item = reinterpret_cast<work_item *>(param); |
| 2551 | item->m_compressor->async_walk_parent(*item); |
| 2552 | return NULL; |
| 2553 | } |
| 3148 | 2554 | |
| 3149 | | |
| 3150 | | FLAC__stream_encoder_delete(encoder); |
| 3151 | | |
| 3152 | | if (totalout >= chd->header.hunkbytes) |
| 2555 | void chd_file_compressor::async_walk_parent(work_item &item) |
| 2556 | { |
| 2557 | // compute CRC-16 and SHA-1 hashes for each unit, unless we're the last one or we're uncompressed |
| 2558 | UINT32 units = hunk_bytes() / unit_bytes(); |
| 2559 | if (item.m_hunknum == m_hunkcount - 1 || !compressed()) |
| 2560 | units = 1; |
| 2561 | for (UINT32 unit = 0; unit < units; unit++) |
| 3153 | 2562 | { |
| 3154 | | free(flac_encoder_client_ptr->flac_outputbuffer); |
| 3155 | | return CHDERR_COMPRESSION_ERROR; |
| 2563 | item.m_hash[unit].m_crc16 = crc16_creator::simple(item.m_data + unit * unit_bytes(), hunk_bytes()); |
| 2564 | item.m_hash[unit].m_sha1 = sha1_creator::simple(item.m_data + unit * unit_bytes(), hunk_bytes()); |
| 3156 | 2565 | } |
| 3157 | | |
| 3158 | | *length = totalout; |
| 3159 | | memcpy(chd->compressed, flac_encoder_client_ptr->flac_outputbuffer+FLAC_HEADER_SIZE, flac_encoder_client_ptr->flac_output_buffer_total-FLAC_HEADER_SIZE); |
| 3160 | | |
| 3161 | | free(flac_encoder_client_ptr->flac_outputbuffer); |
| 3162 | | free(flac_encoder_client_ptr->pcm); |
| 3163 | | free(flac_encoder_client_ptr->tempbuffer); |
| 3164 | | return CHDERR_NONE; |
| 2566 | item.m_status = WS_COMPLETE; |
| 3165 | 2567 | } |
| 3166 | 2568 | |
| 3167 | | static chd_error flac_codec_compress_normal(chd_file *chd, const void *src, UINT32 *length) |
| 3168 | | { |
| 3169 | | return flac_codec_compress(chd, src, length, 0); |
| 3170 | | } |
| 3171 | 2569 | |
| 3172 | | // this data is always the same for our blocks, so don't store it in the file. |
| 3173 | | static UINT8 flacHeader[FLAC_HEADER_SIZE] = { |
| 3174 | | 0x66, 0x4C, 0x61, 0x43, 0x00, 0x00, 0x00, 0x22, 0x12, 0x60, 0x12, 0x60, |
| 3175 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0A, 0xC4, 0x42, 0xF0, 0x00, 0x00, |
| 3176 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 3177 | | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x00, 0x00, 0x28, 0x20, 0x00, |
| 3178 | | 0x00, 0x00, 0x72, 0x65, 0x66, 0x65, 0x72, 0x65, 0x6E, 0x63, 0x65, 0x20, |
| 3179 | | 0x6C, 0x69, 0x62, 0x46, 0x4C, 0x41, 0x43, 0x20, 0x31, 0x2E, 0x32, 0x2E, |
| 3180 | | 0x31, 0x20, 0x32, 0x30, 0x30, 0x37, 0x30, 0x39, 0x31, 0x37, 0x00, 0x00, |
| 3181 | | 0x00, 0x00, |
| 3182 | | }; |
| 3183 | | /*------------------------------------------------- |
| 3184 | | flac_codec_decompress - decomrpess data using |
| 3185 | | the FLAC codec |
| 3186 | | -------------------------------------------------*/ |
| 2570 | //------------------------------------------------- |
| 2571 | // async_compress_hunk - handle asynchronous |
| 2572 | // hunk compression |
| 2573 | //------------------------------------------------- |
| 3187 | 2574 | |
| 3188 | | |
| 3189 | | struct flac_decoder_data |
| 2575 | void *chd_file_compressor::async_compress_hunk_static(void *param, int threadid) |
| 3190 | 2576 | { |
| 3191 | | int readoffset; |
| 3192 | | size_t readbuffersize; |
| 3193 | | UINT8* readbuffer; |
| 3194 | | |
| 3195 | | int writeoffset; |
| 3196 | | INT16 tempbuffer[(CD_MAX_SECTOR_DATA * CD_FRAMES_PER_HUNK)/2]; |
| 3197 | | UINT64 amount_to_decode; |
| 3198 | | }; |
| 3199 | | |
| 3200 | | FLAC__StreamDecoderWriteStatus flac_decoder_write_callback(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data) |
| 3201 | | { |
| 3202 | | |
| 3203 | | int blocksize = frame->header.blocksize; |
| 3204 | | int i = 0; |
| 3205 | | while (blocksize && ((flac_decoder_data*)client_data)->amount_to_decode) |
| 3206 | | { |
| 3207 | | ((flac_decoder_data*)client_data)->tempbuffer[(((flac_decoder_data*)client_data)->writeoffset*2)+0] = buffer[0][i]; |
| 3208 | | ((flac_decoder_data*)client_data)->tempbuffer[(((flac_decoder_data*)client_data)->writeoffset*2)+1] = buffer[1][i]; |
| 3209 | | |
| 3210 | | blocksize--; |
| 3211 | | i++; |
| 3212 | | ((flac_decoder_data*)client_data)->amount_to_decode-=4; |
| 3213 | | ((flac_decoder_data*)client_data)->writeoffset++; |
| 3214 | | } |
| 3215 | | |
| 3216 | | return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE; |
| 2577 | work_item *item = reinterpret_cast<work_item *>(param); |
| 2578 | item->m_compressor->async_compress_hunk(*item, threadid); |
| 2579 | return NULL; |
| 3217 | 2580 | } |
| 3218 | 2581 | |
| 3219 | | FLAC__StreamDecoderReadStatus flac_decoder_read_callback(const FLAC__StreamDecoder *decoder, FLAC__byte buffer[], size_t *bytes, void *client_data) |
| 2582 | void chd_file_compressor::async_compress_hunk(work_item &item, int threadid) |
| 3220 | 2583 | { |
| 3221 | | size_t readsize = *bytes; |
| 3222 | | size_t readbuffersize = ((flac_decoder_data*)client_data)->readbuffersize; |
| 2584 | // use our thread's codec |
| 2585 | assert(threadid < ARRAY_LENGTH(m_codecs)); |
| 2586 | item.m_codecs = m_codecs[threadid]; |
| 3223 | 2587 | |
| 3224 | | if ((((flac_decoder_data*)client_data)->readoffset + readsize) > readbuffersize) |
| 3225 | | { |
| 3226 | | readsize = ((flac_decoder_data*)client_data)->readbuffersize-((flac_decoder_data*)client_data)->readoffset; |
| 3227 | | } |
| 2588 | // compute CRC-16 and SHA-1 hashes |
| 2589 | item.m_hash[0].m_crc16 = crc16_creator::simple(item.m_data, hunk_bytes()); |
| 2590 | item.m_hash[0].m_sha1 = sha1_creator::simple(item.m_data, hunk_bytes()); |
| 3228 | 2591 | |
| 3229 | | if (readsize==0) return FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM; |
| 2592 | // find the best compression scheme, unless we already have a self or parent match |
| 2593 | // (note we may miss a self match from blocks not yet added, but this just results in extra work) |
| 2594 | if (m_current_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND && |
| 2595 | m_parent_map.find(item.m_hash[0].m_crc16, item.m_hash[0].m_sha1) == hashmap::NOT_FOUND) |
| 2596 | item.m_compression = item.m_codecs->find_best_compressor(item.m_data, item.m_compressed, item.m_complen); |
| 3230 | 2597 | |
| 3231 | | memcpy(buffer, ((flac_decoder_data*)client_data)->readbuffer+((flac_decoder_data*)client_data)->readoffset, readsize); |
| 3232 | | |
| 3233 | | ((flac_decoder_data*)client_data)->readoffset += readsize; |
| 3234 | | |
| 3235 | | return FLAC__STREAM_DECODER_READ_STATUS_CONTINUE; |
| 2598 | // mark us complete |
| 2599 | item.m_status = WS_COMPLETE; |
| 3236 | 2600 | } |
| 3237 | 2601 | |
| 3238 | 2602 | |
| 2603 | //------------------------------------------------- |
| 2604 | // async_read - handle asynchronous source file |
| 2605 | // reading |
| 2606 | //------------------------------------------------- |
| 3239 | 2607 | |
| 3240 | | void flac_decoder_metadata_callback(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data) |
| 2608 | void *chd_file_compressor::async_read_static(void *param, int threadid) |
| 3241 | 2609 | { |
| 3242 | | |
| 2610 | reinterpret_cast<chd_file_compressor *>(param)->async_read(); |
| 2611 | return NULL; |
| 3243 | 2612 | } |
| 3244 | 2613 | |
| 3245 | | void flac_decoder_error_callback(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data) |
| 2614 | void chd_file_compressor::async_read() |
| 3246 | 2615 | { |
| 2616 | // if in the error or complete state, stop |
| 2617 | if (m_read_error) |
| 2618 | return; |
| 3247 | 2619 | |
| 3248 | | } |
| 2620 | // determine parameters for the read |
| 2621 | UINT32 work_buffer_bytes = WORK_BUFFER_HUNKS * hunk_bytes(); |
| 2622 | UINT32 numbytes = work_buffer_bytes / 2; |
| 2623 | if (m_read_done_offset + numbytes > logical_bytes()) |
| 2624 | numbytes = logical_bytes() - m_read_done_offset; |
| 3249 | 2625 | |
| 3250 | | static chd_error flac_codec_decompress(chd_file *chd, UINT32 srclength, void *dest) |
| 3251 | | { |
| 3252 | | FLAC__StreamDecoder *decoder = FLAC__stream_decoder_new(); |
| 3253 | | flac_decoder_data flac_decoder_client; |
| 3254 | | flac_decoder_data* flac_decoder_client_ptr = &flac_decoder_client; |
| 3255 | | |
| 3256 | | flac_decoder_client_ptr->readoffset = 0; |
| 3257 | | |
| 3258 | | flac_decoder_client_ptr->readbuffersize = srclength+FLAC_HEADER_SIZE; |
| 3259 | | flac_decoder_client_ptr->readbuffer = (UINT8*)malloc(flac_decoder_client_ptr->readbuffersize); |
| 3260 | | flac_decoder_client_ptr->amount_to_decode = (CD_MAX_SECTOR_DATA*CD_FRAMES_PER_HUNK); |
| 3261 | | int frames_to_decode = flac_decoder_client_ptr->amount_to_decode / CD_MAX_SECTOR_DATA; |
| 3262 | | |
| 3263 | | flac_decoder_client_ptr->writeoffset = 0; |
| 3264 | | |
| 3265 | | memcpy(flac_decoder_client_ptr->readbuffer, flacHeader, FLAC_HEADER_SIZE); |
| 3266 | | memcpy(flac_decoder_client_ptr->readbuffer+FLAC_HEADER_SIZE, chd->compressed, srclength); |
| 3267 | | |
| 3268 | | if (!decoder) |
| 2626 | // catch any exceptions coming out of here |
| 2627 | try |
| 3269 | 2628 | { |
| 3270 | | printf("unable to create FLAC decoder\n"); |
| 3271 | | return CHDERR_READ_ERROR; |
| 3272 | | } |
| 2629 | // do the read |
| 2630 | UINT8 *dest = m_work_buffer + (m_read_done_offset % work_buffer_bytes); |
| 2631 | assert(dest == m_work_buffer || dest == m_work_buffer + work_buffer_bytes/2); |
| 2632 | UINT64 end_offset = m_read_done_offset + numbytes; |
| 3273 | 2633 | |
| 3274 | | if(FLAC__stream_decoder_init_stream( |
| 3275 | | decoder, |
| 3276 | | flac_decoder_read_callback, |
| 3277 | | NULL, |
| 3278 | | NULL, |
| 3279 | | NULL, |
| 3280 | | NULL, |
| 3281 | | flac_decoder_write_callback, |
| 3282 | | flac_decoder_metadata_callback, |
| 3283 | | flac_decoder_error_callback, |
| 3284 | | flac_decoder_client_ptr ) != FLAC__STREAM_DECODER_INIT_STATUS_OK) |
| 3285 | | { |
| 3286 | | printf("unable to init FLAC decoder\n"); |
| 3287 | | return CHDERR_READ_ERROR; |
| 3288 | | } |
| 3289 | | |
| 3290 | | if (FLAC__stream_decoder_process_until_end_of_metadata(decoder) != FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM) |
| 3291 | | { |
| 3292 | | printf("Fail FLAC__stream_decoder_process_until_end_of_metadata\n"); |
| 3293 | | return CHDERR_READ_ERROR; |
| 3294 | | } |
| 3295 | | |
| 3296 | | /* only ever a single frame */ |
| 3297 | | if (FLAC__stream_decoder_process_single(decoder) != FLAC__STREAM_DECODER_READ_STATUS_END_OF_STREAM) |
| 3298 | | { |
| 3299 | | printf("Fail FLAC__stream_decoder_process_until_end_of_metadata\n"); |
| 3300 | | return CHDERR_READ_ERROR; |
| 3301 | | } |
| 3302 | | |
| 3303 | | |
| 3304 | | int srcoffset = 0; |
| 3305 | | UINT8* dest2 = (UINT8*)dest; |
| 3306 | | for (int frame = 0; frame<frames_to_decode;frame++) |
| 3307 | | { |
| 3308 | | int destoffset = frame * (CD_FRAME_SIZE); |
| 3309 | | |
| 3310 | | |
| 3311 | | int index; |
| 3312 | | |
| 3313 | | for (index = 0; index < 2352; index += 2 ) |
| 2634 | // if walking the parent, read in hunks from the parent CHD |
| 2635 | if (m_walking_parent) |
| 3314 | 2636 | { |
| 3315 | | dest2[ destoffset + index +1 ] = flac_decoder_client_ptr->tempbuffer[srcoffset] & 0xff; |
| 3316 | | dest2[ destoffset + index ] = flac_decoder_client_ptr->tempbuffer[srcoffset] >> 8; |
| 2637 | UINT8 *curdest = dest; |
| 2638 | for (UINT64 curoffs = m_read_done_offset; curoffs < end_offset + 1; curoffs += hunk_bytes()) |
| 2639 | { |
| 2640 | m_parent->read_hunk(curoffs / hunk_bytes(), curdest); |
| 2641 | curdest += hunk_bytes(); |
| 2642 | } |
| 2643 | } |
| 2644 | |
| 2645 | // otherwise, call the virtual function |
| 2646 | else |
| 2647 | read_data(dest, m_read_done_offset, numbytes); |
| 2648 | |
| 2649 | // spawn off work for each hunk |
| 2650 | for (UINT64 curoffs = m_read_done_offset; curoffs < end_offset; curoffs += hunk_bytes()) |
| 2651 | { |
| 2652 | UINT32 hunknum = curoffs / hunk_bytes(); |
| 2653 | work_item &item = m_work_item[hunknum % WORK_BUFFER_HUNKS]; |
| 2654 | assert(item.m_status == WS_READING); |
| 2655 | item.m_status = WS_QUEUED; |
| 2656 | item.m_hunknum = hunknum; |
| 2657 | item.m_osd = osd_work_item_queue(m_work_queue, m_walking_parent ? async_walk_parent_static : async_compress_hunk_static, &item, 0); |
| 2658 | } |
| 3317 | 2659 | |
| 3318 | | srcoffset++; |
| 2660 | // continue the running SHA-1 |
| 2661 | if (!m_walking_parent) |
| 2662 | { |
| 2663 | if (compressed()) |
| 2664 | m_compsha1.append(dest, numbytes); |
| 2665 | m_total_in += numbytes; |
| 3319 | 2666 | } |
| 2667 | |
| 2668 | // advance the read pointer |
| 2669 | m_read_done_offset += numbytes; |
| 3320 | 2670 | } |
| 3321 | | |
| 3322 | | |
| 3323 | | if (FLAC__stream_decoder_finish (decoder) != true) |
| 2671 | catch (...) |
| 3324 | 2672 | { |
| 3325 | | printf("unable to finish FLAC decoder\n"); |
| 3326 | | return CHDERR_READ_ERROR; |
| 2673 | m_read_error = true; |
| 3327 | 2674 | } |
| 3328 | | |
| 3329 | | FLAC__stream_decoder_delete(decoder); |
| 3330 | | free(flac_decoder_client_ptr->readbuffer); |
| 3331 | | |
| 3332 | | return CHDERR_NONE; |
| 3333 | 2675 | } |
| 3334 | 2676 | |
| 3335 | 2677 | |
| 3336 | 2678 | |
| 2679 | //************************************************************************** |
| 2680 | // CHD COMPRESSOR HASHMAP |
| 2681 | //************************************************************************** |
| 3337 | 2682 | |
| 3338 | | /*************************************************************************** |
| 3339 | | AV COMPRESSION CODEC |
| 3340 | | ***************************************************************************/ |
| 2683 | //------------------------------------------------- |
| 2684 | // hashmap - constructor |
| 2685 | //------------------------------------------------- |
| 3341 | 2686 | |
| 3342 | | /*------------------------------------------------- |
| 3343 | | av_raw_data_size - compute the raw data size |
| 3344 | | -------------------------------------------------*/ |
| 3345 | | |
| 3346 | | INLINE UINT32 av_raw_data_size(const UINT8 *data) |
| 2687 | chd_file_compressor::hashmap::hashmap() |
| 2688 | : m_block_list(new entry_block(NULL)) |
| 3347 | 2689 | { |
| 3348 | | int size = 0; |
| 3349 | | |
| 3350 | | /* make sure we have a correct header */ |
| 3351 | | if (data[0] == 'c' && data[1] == 'h' && data[2] == 'a' && data[3] == 'v') |
| 3352 | | { |
| 3353 | | /* add in header size plus metadata length */ |
| 3354 | | size = 12 + data[4]; |
| 3355 | | |
| 3356 | | /* add in channels * samples */ |
| 3357 | | size += 2 * data[5] * ((data[6] << 8) + data[7]); |
| 3358 | | |
| 3359 | | /* add in 2 * width * height */ |
| 3360 | | size += 2 * ((data[8] << 8) + data[9]) * (((data[10] << 8) + data[11]) & 0x7fff); |
| 3361 | | } |
| 3362 | | return size; |
| 2690 | // initialize the map to empty |
| 2691 | memset(m_map, 0, sizeof(m_map)); |
| 3363 | 2692 | } |
| 3364 | 2693 | |
| 3365 | 2694 | |
| 3366 | | /*------------------------------------------------- |
| 3367 | | av_codec_init - initialize the A/V codec |
| 3368 | | -------------------------------------------------*/ |
| 2695 | //------------------------------------------------- |
| 2696 | // ~hashmap - destructor |
| 2697 | //------------------------------------------------- |
| 3369 | 2698 | |
| 3370 | | static chd_error av_codec_init(chd_file *chd) |
| 2699 | chd_file_compressor::hashmap::~hashmap() |
| 3371 | 2700 | { |
| 3372 | | av_codec_data *data; |
| 3373 | | |
| 3374 | | /* allocate memory for the 2 stream buffers */ |
| 3375 | | data = new(std::nothrow) av_codec_data; |
| 3376 | | if (data == NULL) |
| 3377 | | return CHDERR_OUT_OF_MEMORY; |
| 3378 | | |
| 3379 | | /* clear the buffers */ |
| 3380 | | chd->codecdata = data; |
| 3381 | | |
| 3382 | | /* attempt to do a post-init now; if we're creating a new CHD, this won't work */ |
| 3383 | | /* but that's ok */ |
| 3384 | | av_codec_postinit(chd); |
| 3385 | | return CHDERR_NONE; |
| 2701 | reset(); |
| 3386 | 2702 | } |
| 3387 | 2703 | |
| 3388 | 2704 | |
| 3389 | | /*------------------------------------------------- |
| 3390 | | av_codec_free - free data for the A/V |
| 3391 | | codec |
| 3392 | | -------------------------------------------------*/ |
| 2705 | //------------------------------------------------- |
| 2706 | // reset - reset the state of the map |
| 2707 | //------------------------------------------------- |
| 3393 | 2708 | |
| 3394 | | static void av_codec_free(chd_file *chd) |
| 2709 | void chd_file_compressor::hashmap::reset() |
| 3395 | 2710 | { |
| 3396 | | av_codec_data *data = (av_codec_data *)chd->codecdata; |
| 3397 | | |
| 3398 | | /* deinit avcomp */ |
| 3399 | | if (data != NULL) |
| 2711 | // delete all the blocks |
| 2712 | while (m_block_list->m_next != NULL) |
| 3400 | 2713 | { |
| 3401 | | if (data->compstate != NULL) |
| 3402 | | avcomp_free(data->compstate); |
| 3403 | | delete data; |
| 2714 | entry_block *block = m_block_list; |
| 2715 | m_block_list = block->m_next; |
| 2716 | delete block; |
| 3404 | 2717 | } |
| 2718 | m_block_list->m_nextalloc = 0; |
| 2719 | |
| 2720 | // reset the hash |
| 2721 | memset(m_map, 0, sizeof(m_map)); |
| 3405 | 2722 | } |
| 3406 | 2723 | |
| 3407 | 2724 | |
| 3408 | | /*------------------------------------------------- |
| 3409 | | av_codec_compress - compress data using the |
| 3410 | | A/V codec |
| 3411 | | -------------------------------------------------*/ |
| 2725 | //------------------------------------------------- |
| 2726 | // find - find an item in the CRC map |
| 2727 | //------------------------------------------------- |
| 3412 | 2728 | |
| 3413 | | static chd_error av_codec_compress(chd_file *chd, const void *src, UINT32 *length) |
| 2729 | UINT64 chd_file_compressor::hashmap::find(crc16_t crc16, sha1_t sha1) |
| 3414 | 2730 | { |
| 3415 | | av_codec_data *data = (av_codec_data *)chd->codecdata; |
| 3416 | | int averr; |
| 3417 | | int size; |
| 3418 | | |
| 3419 | | /* if we haven't yet set up the avcomp code, do it now */ |
| 3420 | | if (data->compstate == NULL) |
| 3421 | | { |
| 3422 | | chd_error chderr = av_codec_postinit(chd); |
| 3423 | | if (chderr != CHDERR_NONE) |
| 3424 | | return chderr; |
| 3425 | | } |
| 3426 | | |
| 3427 | | /* make sure short frames are padded with 0 */ |
| 3428 | | if (src != NULL) |
| 3429 | | { |
| 3430 | | size = av_raw_data_size((const UINT8 *)src); |
| 3431 | | while (size < chd->header.hunkbytes) |
| 3432 | | if (((const UINT8 *)src)[size++] != 0) |
| 3433 | | return CHDERR_INVALID_DATA; |
| 3434 | | } |
| 3435 | | |
| 3436 | | /* encode the audio and video */ |
| 3437 | | averr = avcomp_encode_data(data->compstate, (const UINT8 *)src, chd->compressed, length); |
| 3438 | | if (averr != AVCERR_NONE || *length > chd->header.hunkbytes) |
| 3439 | | return CHDERR_COMPRESSION_ERROR; |
| 3440 | | |
| 3441 | | return CHDERR_NONE; |
| 2731 | // look up the entry in the map |
| 2732 | for (entry_t *entry = m_map[crc16]; entry != NULL; entry = entry->m_next) |
| 2733 | if (entry->m_sha1 == sha1) |
| 2734 | return entry->m_itemnum; |
| 2735 | return NOT_FOUND; |
| 3442 | 2736 | } |
| 3443 | 2737 | |
| 3444 | 2738 | |
| 3445 | | /*------------------------------------------------- |
| 3446 | | av_codec_decompress - decomrpess data using |
| 3447 | | the A/V codec |
| 3448 | | -------------------------------------------------*/ |
| 2739 | //------------------------------------------------- |
| 2740 | // add - add an item to the CRC map |
| 2741 | //------------------------------------------------- |
| 3449 | 2742 | |
| 3450 | | static chd_error av_codec_decompress(chd_file *chd, UINT32 srclength, void *dest) |
| 2743 | void chd_file_compressor::hashmap::add(UINT64 itemnum, crc16_t crc16, sha1_t sha1) |
| 3451 | 2744 | { |
| 3452 | | av_codec_data *data = (av_codec_data *)chd->codecdata; |
| 3453 | | const UINT8 *source; |
| 3454 | | avcomp_error averr; |
| 3455 | | int size; |
| 3456 | | |
| 3457 | | /* if we haven't yet set up the avcomp code, do it now */ |
| 3458 | | if (data->compstate == NULL) |
| 3459 | | { |
| 3460 | | chd_error chderr = av_codec_postinit(chd); |
| 3461 | | if (chderr != CHDERR_NONE) |
| 3462 | | return chderr; |
| 3463 | | } |
| 3464 | | |
| 3465 | | /* decode the audio and video */ |
| 3466 | | source = chd->compressed; |
| 3467 | | averr = avcomp_decode_data(data->compstate, source, srclength, (UINT8 *)dest); |
| 3468 | | if (averr != AVCERR_NONE) |
| 3469 | | return CHDERR_DECOMPRESSION_ERROR; |
| 3470 | | |
| 3471 | | /* pad short frames with 0 */ |
| 3472 | | if (dest != NULL) |
| 3473 | | { |
| 3474 | | size = av_raw_data_size((const UINT8 *)dest); |
| 3475 | | while (size < chd->header.hunkbytes) |
| 3476 | | ((UINT8 *)dest)[size++] = 0; |
| 3477 | | } |
| 3478 | | |
| 3479 | | return CHDERR_NONE; |
| 2745 | // add to the appropriate map |
| 2746 | if (m_block_list->m_nextalloc == ARRAY_LENGTH(m_block_list->m_array)) |
| 2747 | m_block_list = new entry_block(m_block_list); |
| 2748 | entry_t *entry = &m_block_list->m_array[m_block_list->m_nextalloc++]; |
| 2749 | entry->m_itemnum = itemnum; |
| 2750 | entry->m_sha1 = sha1; |
| 2751 | entry->m_next = m_map[crc16]; |
| 2752 | m_map[crc16] = entry; |
| 3480 | 2753 | } |
| 3481 | | |
| 3482 | | |
| 3483 | | /*------------------------------------------------- |
| 3484 | | av_codec_config - codec-specific configuration |
| 3485 | | for the A/V codec |
| 3486 | | -------------------------------------------------*/ |
| 3487 | | |
| 3488 | | static chd_error av_codec_config(chd_file *chd, int param, void *config) |
| 3489 | | { |
| 3490 | | av_codec_data *data = (av_codec_data *)chd->codecdata; |
| 3491 | | |
| 3492 | | /* if we're getting the compression configuration, apply it now */ |
| 3493 | | if (param == AV_CODEC_COMPRESS_CONFIG) |
| 3494 | | { |
| 3495 | | av_codec_compress_config *configsrc = reinterpret_cast<av_codec_compress_config *>(config); |
| 3496 | | data->compress.video.wrap(configsrc->video, configsrc->video.cliprect()); |
| 3497 | | data->compress.channels = configsrc->channels; |
| 3498 | | data->compress.samples = configsrc->samples; |
| 3499 | | memcpy(data->compress.audio, configsrc->audio, sizeof(data->compress.audio)); |
| 3500 | | data->compress.metalength = configsrc->metalength; |
| 3501 | | data->compress.metadata = configsrc->metadata; |
| 3502 | | if (data->compstate != NULL) |
| 3503 | | avcomp_config_compress(data->compstate, &data->compress); |
| 3504 | | return CHDERR_NONE; |
| 3505 | | } |
| 3506 | | |
| 3507 | | /* if we're getting the decompression configuration, apply it now */ |
| 3508 | | else if (param == AV_CODEC_DECOMPRESS_CONFIG) |
| 3509 | | { |
| 3510 | | av_codec_decompress_config *configsrc = reinterpret_cast<av_codec_decompress_config *>(config); |
| 3511 | | data->decompress.video.wrap(configsrc->video, configsrc->video.cliprect()); |
| 3512 | | data->decompress.maxsamples = configsrc->maxsamples; |
| 3513 | | data->decompress.actsamples = configsrc->actsamples; |
| 3514 | | memcpy(data->decompress.audio, configsrc->audio, sizeof(data->decompress.audio)); |
| 3515 | | data->decompress.maxmetalength = configsrc->maxmetalength; |
| 3516 | | data->decompress.actmetalength = configsrc->actmetalength; |
| 3517 | | data->decompress.metadata = configsrc->metadata; |
| 3518 | | if (data->compstate != NULL) |
| 3519 | | avcomp_config_decompress(data->compstate, &data->decompress); |
| 3520 | | return CHDERR_NONE; |
| 3521 | | } |
| 3522 | | |
| 3523 | | /* anything else is invalid */ |
| 3524 | | return CHDERR_INVALID_PARAMETER; |
| 3525 | | } |
| 3526 | | |
| 3527 | | |
| 3528 | | /*------------------------------------------------- |
| 3529 | | av_codec_postinit - actual initialization of |
| 3530 | | avcomp happens here, on the first attempt |
| 3531 | | to compress or decompress data |
| 3532 | | -------------------------------------------------*/ |
| 3533 | | |
| 3534 | | static chd_error av_codec_postinit(chd_file *chd) |
| 3535 | | { |
| 3536 | | int fps, fpsfrac, width, height, interlaced, channels, rate; |
| 3537 | | UINT32 fps_times_1million, max_samples_per_frame, bytes_per_frame; |
| 3538 | | av_codec_data *data = (av_codec_data *)chd->codecdata; |
| 3539 | | char metadata[256]; |
| 3540 | | chd_error err; |
| 3541 | | |
| 3542 | | /* the code below won't work asynchronously */ |
| 3543 | | if (chd->workitem != NULL) |
| 3544 | | return CHDERR_OPERATION_PENDING; |
| 3545 | | |
| 3546 | | /* get the metadata */ |
| 3547 | | err = chd_get_metadata(chd, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL, NULL); |
| 3548 | | if (err != CHDERR_NONE) |
| 3549 | | return err; |
| 3550 | | |
| 3551 | | /* extract the info */ |
| 3552 | | if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate) != 7) |
| 3553 | | return CHDERR_INVALID_METADATA; |
| 3554 | | |
| 3555 | | /* compute the bytes per frame */ |
| 3556 | | fps_times_1million = fps * 1000000 + fpsfrac; |
| 3557 | | max_samples_per_frame = ((UINT64)rate * 1000000 + fps_times_1million - 1) / fps_times_1million; |
| 3558 | | bytes_per_frame = 12 + channels * max_samples_per_frame * 2 + width * height * 2; |
| 3559 | | if (bytes_per_frame > chd->header.hunkbytes) |
| 3560 | | return CHDERR_INVALID_METADATA; |
| 3561 | | |
| 3562 | | /* create the avcomp state */ |
| 3563 | | data->compstate = avcomp_init(width, height, channels); |
| 3564 | | |
| 3565 | | /* configure the codec */ |
| 3566 | | avcomp_config_compress(data->compstate, &data->compress); |
| 3567 | | avcomp_config_decompress(data->compstate, &data->decompress); |
| 3568 | | return CHDERR_NONE; |
| 3569 | | } |
src/lib/util/huffman.c
| r14481 | r14482 | |
| 2 | 2 | |
| 3 | 3 | huffman.c |
| 4 | 4 | |
| 5 | | Video compression and decompression helpers. |
| 5 | Static Huffman compression and decompression helpers. |
| 6 | 6 | |
| 7 | 7 | **************************************************************************** |
| 8 | 8 | |
| r14481 | r14482 | |
| 127 | 127 | |
| 128 | 128 | #include <stdlib.h> |
| 129 | 129 | |
| 130 | #include "coretmpl.h" |
| 130 | 131 | #include "huffman.h" |
| 131 | 132 | |
| 132 | 133 | |
| 133 | 134 | |
| 134 | | /*************************************************************************** |
| 135 | | CONSTANTS |
| 136 | | ***************************************************************************/ |
| 135 | //************************************************************************** |
| 136 | // MACROS |
| 137 | //************************************************************************** |
| 137 | 138 | |
| 138 | | #define HUFFMAN_CODES 256 |
| 139 | | #define HUFFMAN_DELTARLE_CODES (HUFFMAN_CODES + 16) |
| 139 | #define MAKE_LOOKUP(code,bits) (((code) << 5) | ((bits) & 0x1f)) |
| 140 | 140 | |
| 141 | | #define MAX_HUFFMAN_CODES (HUFFMAN_DELTARLE_CODES) |
| 142 | | #define MAX_HUFFMAN_NODES (MAX_HUFFMAN_CODES + MAX_HUFFMAN_CODES) |
| 143 | 141 | |
| 144 | 142 | |
| 143 | //************************************************************************** |
| 144 | // IMPLEMENTATION |
| 145 | //************************************************************************** |
| 145 | 146 | |
| 146 | | /*************************************************************************** |
| 147 | | MACROS |
| 148 | | ***************************************************************************/ |
| 147 | //------------------------------------------------- |
| 148 | // huffman_context_base - create an encoding/ |
| 149 | // decoding context |
| 150 | //------------------------------------------------- |
| 149 | 151 | |
| 150 | | #define MAKE_LOOKUP(code,bits) (((code) << 6) | ((bits) & 0x1f)) |
| 151 | | #define LOOKUP_CODE(val) ((val) >> 6) |
| 152 | | #define LOOKUP_BITS(val) ((val) & 0x1f) |
| 153 | | |
| 154 | | |
| 155 | | |
| 156 | | /*************************************************************************** |
| 157 | | TYPE DEFINITIONS |
| 158 | | ***************************************************************************/ |
| 159 | | |
| 160 | | typedef struct _bit_buffer bit_buffer; |
| 161 | | struct _bit_buffer |
| 152 | huffman_context_base::huffman_context_base(int numcodes, int maxbits, lookup_value *lookup, UINT32 *histo, node_t *nodes) |
| 153 | : m_numcodes(numcodes), |
| 154 | m_maxbits(maxbits), |
| 155 | m_prevdata(0), |
| 156 | m_rleremaining(0), |
| 157 | m_lookup(lookup), |
| 158 | m_datahisto(histo), |
| 159 | m_huffnode(nodes) |
| 162 | 160 | { |
| 163 | | UINT32 buffer; /* current bit accumulator */ |
| 164 | | int bits; /* number of bits in the accumulator */ |
| 165 | | union |
| 166 | | { |
| 167 | | const UINT8 * read; /* read pointer */ |
| 168 | | UINT8 * write; /* write pointer */ |
| 169 | | } data; |
| 170 | | UINT32 doffset; /* byte offset within the data */ |
| 171 | | UINT32 dlength; /* length of the data */ |
| 172 | | int overflow; /* flag: true if we read/wrote past the end */ |
| 173 | | }; |
| 174 | | |
| 175 | | |
| 176 | | typedef struct _huffman_node huffman_node; |
| 177 | | struct _huffman_node |
| 178 | | { |
| 179 | | huffman_node * parent; /* pointer to parent node */ |
| 180 | | UINT32 count; /* number of hits on this node */ |
| 181 | | UINT32 weight; /* assigned weight of this node */ |
| 182 | | UINT32 bits; /* bits used to encode the node */ |
| 183 | | UINT8 numbits; /* number of bits needed for this node */ |
| 184 | | }; |
| 185 | | |
| 186 | | |
| 187 | | struct _huffman_context |
| 188 | | { |
| 189 | | UINT8 maxbits; /* maximum bits per code */ |
| 190 | | UINT8 lookupdirty; /* TRUE if the lookup table is dirty */ |
| 191 | | UINT8 prevdata; /* value of the previous data (for delta-RLE encoding) */ |
| 192 | | UINT32 datahisto[MAX_HUFFMAN_CODES]; /* histogram of data values */ |
| 193 | | int rleremaining; /* number of RLE bytes remaining (for delta-RLE encoding) */ |
| 194 | | huffman_node huffnode[MAX_HUFFMAN_NODES]; /* array of nodes */ |
| 195 | | huffman_lookup_value * lookup; /* pointer to the lookup table */ |
| 196 | | }; |
| 197 | | |
| 198 | | |
| 199 | | |
| 200 | | /*************************************************************************** |
| 201 | | PROTOTYPES |
| 202 | | ***************************************************************************/ |
| 203 | | |
| 204 | | static huffman_error huffman_deltarle_decode_data_interleaved_0102(huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength); |
| 205 | | |
| 206 | | static huffman_error import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength, UINT32 numcodes); |
| 207 | | static huffman_error export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength, UINT32 numcodes); |
| 208 | | static void write_rle_tree_bits(bit_buffer *bitbuf, int value, int repcount, int numbits); |
| 209 | | static int CLIB_DECL tree_node_compare(const void *item1, const void *item2); |
| 210 | | static huffman_error compute_optimal_tree(huffman_context *context, const UINT32 *datahisto, UINT32 numcodes); |
| 211 | | static int huffman_build_tree(huffman_context *context, const UINT32 *datahisto, UINT32 totaldata, UINT32 totalweight, UINT32 numcodes); |
| 212 | | static huffman_error assign_canonical_codes(huffman_context *context, UINT32 numcodes); |
| 213 | | static huffman_error build_lookup_table(huffman_context *context, UINT32 numcodes); |
| 214 | | |
| 215 | | |
| 216 | | |
| 217 | | /*************************************************************************** |
| 218 | | INLINE FUNCTIONS |
| 219 | | ***************************************************************************/ |
| 220 | | |
| 221 | | /*------------------------------------------------- |
| 222 | | bit_buffer_write_init - initialize a bit |
| 223 | | buffer for writing |
| 224 | | -------------------------------------------------*/ |
| 225 | | |
| 226 | | INLINE void bit_buffer_write_init(bit_buffer *bitbuf, UINT8 *data, UINT32 dlength) |
| 227 | | { |
| 228 | | /* fill in the basic data structure */ |
| 229 | | bitbuf->buffer = 0; |
| 230 | | bitbuf->bits = 0; |
| 231 | | bitbuf->data.write = data; |
| 232 | | bitbuf->doffset = 0; |
| 233 | | bitbuf->dlength = dlength; |
| 234 | | bitbuf->overflow = FALSE; |
| 235 | | } |
| 236 | | |
| 237 | | |
| 238 | | /*------------------------------------------------- |
| 239 | | bit_buffer_write - write 'numbits' to the |
| 240 | | bit buffer, assuming that 'newbits' is right- |
| 241 | | justified |
| 242 | | -------------------------------------------------*/ |
| 243 | | |
| 244 | | INLINE void bit_buffer_write(bit_buffer *bitbuf, UINT32 newbits, int numbits) |
| 245 | | { |
| 246 | | /* flush the buffer if we're going to overflow it */ |
| 247 | | if (bitbuf->bits + numbits > 32) |
| 248 | | while (bitbuf->bits >= 8) |
| 249 | | { |
| 250 | | if (bitbuf->doffset < bitbuf->dlength) |
| 251 | | bitbuf->data.write[bitbuf->doffset] = bitbuf->buffer >> 24; |
| 252 | | else |
| 253 | | bitbuf->overflow = TRUE; |
| 254 | | bitbuf->doffset++; |
| 255 | | bitbuf->buffer <<= 8; |
| 256 | | bitbuf->bits -= 8; |
| 257 | | } |
| 258 | | |
| 259 | | /* shift the bits to the top */ |
| 260 | | newbits <<= 32 - numbits; |
| 261 | | |
| 262 | | /* now shift it down to account for the number of bits we already have and OR them in */ |
| 263 | | bitbuf->buffer |= newbits >> bitbuf->bits; |
| 264 | | bitbuf->bits += numbits; |
| 265 | | } |
| 266 | | |
| 267 | | |
| 268 | | /*------------------------------------------------- |
| 269 | | bit_buffer_flush - flush any bits in the write |
| 270 | | buffer and return the final data offset |
| 271 | | -------------------------------------------------*/ |
| 272 | | |
| 273 | | INLINE UINT32 bit_buffer_flush(bit_buffer *bitbuf) |
| 274 | | { |
| 275 | | while (bitbuf->bits > 0) |
| 276 | | { |
| 277 | | if (bitbuf->doffset < bitbuf->dlength) |
| 278 | | bitbuf->data.write[bitbuf->doffset] = bitbuf->buffer >> 24; |
| 279 | | else |
| 280 | | bitbuf->overflow = TRUE; |
| 281 | | bitbuf->doffset++; |
| 282 | | bitbuf->buffer <<= 8; |
| 283 | | bitbuf->bits -= 8; |
| 284 | | } |
| 285 | | return bitbuf->doffset; |
| 286 | | } |
| 287 | | |
| 288 | | |
| 289 | | /*------------------------------------------------- |
| 290 | | bit_buffer_read_init - initialize a bit |
| 291 | | buffer for reading |
| 292 | | -------------------------------------------------*/ |
| 293 | | |
| 294 | | INLINE void bit_buffer_read_init(bit_buffer *bitbuf, const UINT8 *data, UINT32 dlength) |
| 295 | | { |
| 296 | | /* fill in the basic data structure */ |
| 297 | | bitbuf->buffer = 0; |
| 298 | | bitbuf->bits = 0; |
| 299 | | bitbuf->data.read = data; |
| 300 | | bitbuf->doffset = 0; |
| 301 | | bitbuf->dlength = dlength; |
| 302 | | bitbuf->overflow = FALSE; |
| 303 | | } |
| 304 | | |
| 305 | | |
| 306 | | /*------------------------------------------------- |
| 307 | | bit_buffer_read - read 'numbits' bits from |
| 308 | | the buffer, returning them right-justified |
| 309 | | -------------------------------------------------*/ |
| 310 | | |
| 311 | | INLINE UINT32 bit_buffer_read(bit_buffer *bitbuf, int numbits) |
| 312 | | { |
| 313 | | UINT32 result; |
| 314 | | |
| 315 | | /* fetch data if we need more */ |
| 316 | | if (numbits > bitbuf->bits) |
| 317 | | { |
| 318 | | while (bitbuf->bits <= 24) |
| 319 | | { |
| 320 | | if (bitbuf->doffset < bitbuf->dlength) |
| 321 | | bitbuf->buffer |= bitbuf->data.read[bitbuf->doffset] << (24 - bitbuf->bits); |
| 322 | | bitbuf->doffset++; |
| 323 | | bitbuf->bits += 8; |
| 324 | | } |
| 325 | | if (numbits > bitbuf->bits) |
| 326 | | bitbuf->overflow = TRUE; |
| 327 | | } |
| 328 | | |
| 329 | | /* return the data */ |
| 330 | | result = bitbuf->buffer >> (32 - numbits); |
| 331 | | bitbuf->buffer <<= numbits; |
| 332 | | bitbuf->bits -= numbits; |
| 333 | | return result; |
| 334 | | } |
| 335 | | |
| 336 | | |
| 337 | | /*------------------------------------------------- |
| 338 | | bit_buffer_peek - peek ahead and return |
| 339 | | 'numbits' bits from the buffer, returning |
| 340 | | them right-justified |
| 341 | | -------------------------------------------------*/ |
| 342 | | |
| 343 | | INLINE UINT32 bit_buffer_peek(bit_buffer *bitbuf, int numbits) |
| 344 | | { |
| 345 | | /* fetch data if we need more */ |
| 346 | | if (numbits > bitbuf->bits) |
| 347 | | { |
| 348 | | while (bitbuf->bits <= 24) |
| 349 | | { |
| 350 | | if (bitbuf->doffset < bitbuf->dlength) |
| 351 | | bitbuf->buffer |= bitbuf->data.read[bitbuf->doffset] << (24 - bitbuf->bits); |
| 352 | | bitbuf->doffset++; |
| 353 | | bitbuf->bits += 8; |
| 354 | | } |
| 355 | | if (numbits > bitbuf->bits) |
| 356 | | bitbuf->overflow = TRUE; |
| 357 | | } |
| 358 | | |
| 359 | | /* return the data */ |
| 360 | | return bitbuf->buffer >> (32 - numbits); |
| 361 | | } |
| 362 | | |
| 363 | | |
| 364 | | /*------------------------------------------------- |
| 365 | | bit_buffer_remove - remove 'numbits' bits |
| 366 | | from the bit buffer; this presupposes that |
| 367 | | at least 'numbits' are present |
| 368 | | -------------------------------------------------*/ |
| 369 | | |
| 370 | | INLINE void bit_buffer_remove(bit_buffer *bitbuf, int numbits) |
| 371 | | { |
| 372 | | bitbuf->buffer <<= numbits; |
| 373 | | bitbuf->bits -= numbits; |
| 374 | | } |
| 375 | | |
| 376 | | |
| 377 | | /*------------------------------------------------- |
| 378 | | bit_buffer_read_offset - return the current |
| 379 | | rounded byte reading offset |
| 380 | | -------------------------------------------------*/ |
| 381 | | |
| 382 | | INLINE UINT32 bit_buffer_read_offset(bit_buffer *bitbuf) |
| 383 | | { |
| 384 | | UINT32 result = bitbuf->doffset; |
| 385 | | int bits = bitbuf->bits; |
| 386 | | while (bits >= 8) |
| 387 | | { |
| 388 | | result--; |
| 389 | | bits -= 8; |
| 390 | | } |
| 391 | | return result; |
| 392 | | } |
| 393 | | |
| 394 | | |
| 395 | | /*------------------------------------------------- |
| 396 | | code_to_rlecount - number of RLE repetitions |
| 397 | | encoded in a given byte |
| 398 | | -------------------------------------------------*/ |
| 399 | | |
| 400 | | INLINE int code_to_rlecount(int code) |
| 401 | | { |
| 402 | | if (code == 0x00) |
| 403 | | return 1; |
| 404 | | if (code <= 0x107) |
| 405 | | return 8 + (code - 0x100); |
| 406 | | return 16 << (code - 0x108); |
| 407 | | } |
| 408 | | |
| 409 | | |
| 410 | | /*------------------------------------------------- |
| 411 | | rlecount_to_byte - return a byte encoding |
| 412 | | the maximum RLE count less than or equal to |
| 413 | | the provided amount |
| 414 | | -------------------------------------------------*/ |
| 415 | | |
| 416 | | INLINE int rlecount_to_code(int rlecount) |
| 417 | | { |
| 418 | | if (rlecount >= 2048) |
| 419 | | return 0x10f; |
| 420 | | if (rlecount >= 1024) |
| 421 | | return 0x10e; |
| 422 | | if (rlecount >= 512) |
| 423 | | return 0x10d; |
| 424 | | if (rlecount >= 256) |
| 425 | | return 0x10c; |
| 426 | | if (rlecount >= 128) |
| 427 | | return 0x10b; |
| 428 | | if (rlecount >= 64) |
| 429 | | return 0x10a; |
| 430 | | if (rlecount >= 32) |
| 431 | | return 0x109; |
| 432 | | if (rlecount >= 16) |
| 433 | | return 0x108; |
| 434 | | if (rlecount >= 8) |
| 435 | | return 0x100 + (rlecount - 8); |
| 436 | | return 0x00; |
| 437 | | } |
| 438 | | |
| 439 | | |
| 440 | | |
| 441 | | /*************************************************************************** |
| 442 | | IMPLEMENTATION |
| 443 | | ***************************************************************************/ |
| 444 | | |
| 445 | | /*------------------------------------------------- |
| 446 | | huffman_create_context - create an encoding/ |
| 447 | | decoding context |
| 448 | | -------------------------------------------------*/ |
| 449 | | |
| 450 | | huffman_error huffman_create_context(huffman_context **context, int maxbits) |
| 451 | | { |
| 452 | | /* limit to 24 bits */ |
| 161 | // limit to 24 bits |
| 453 | 162 | if (maxbits > 24) |
| 454 | | return HUFFERR_TOO_MANY_BITS; |
| 455 | | |
| 456 | | /* allocate a context */ |
| 457 | | *context = (huffman_context *)malloc(sizeof(**context)); |
| 458 | | if (*context == NULL) |
| 459 | | return HUFFERR_OUT_OF_MEMORY; |
| 460 | | |
| 461 | | /* set the info */ |
| 462 | | memset(*context, 0, sizeof(**context)); |
| 463 | | (*context)->maxbits = maxbits; |
| 464 | | (*context)->lookupdirty = TRUE; |
| 465 | | |
| 466 | | return HUFFERR_NONE; |
| 163 | throw HUFFERR_TOO_MANY_BITS; |
| 467 | 164 | } |
| 468 | 165 | |
| 469 | 166 | |
| 470 | | /*------------------------------------------------- |
| 471 | | huffman_free_context - free an encoding/ |
| 472 | | decoding context |
| 473 | | -------------------------------------------------*/ |
| 167 | //------------------------------------------------- |
| 168 | // import_tree_rle - import an RLE-encoded |
| 169 | // huffman tree from a source data stream |
| 170 | //------------------------------------------------- |
| 474 | 171 | |
| 475 | | void huffman_free_context(huffman_context *context) |
| 172 | huffman_error huffman_context_base::import_tree_rle(bitstream_in &bitbuf) |
| 476 | 173 | { |
| 477 | | if (context->lookup != NULL) |
| 478 | | free(context->lookup); |
| 479 | | free(context); |
| 480 | | } |
| 174 | // bits per entry depends on the maxbits |
| 175 | int numbits; |
| 176 | if (m_maxbits >= 16) |
| 177 | numbits = 5; |
| 178 | else if (m_maxbits >= 8) |
| 179 | numbits = 4; |
| 180 | else |
| 181 | numbits = 3; |
| 481 | 182 | |
| 482 | | |
| 483 | | /*------------------------------------------------- |
| 484 | | huffman_import_tree - import a huffman tree |
| 485 | | from a source data stream |
| 486 | | -------------------------------------------------*/ |
| 487 | | |
| 488 | | huffman_error huffman_import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength) |
| 489 | | { |
| 490 | | return import_tree(context, source, slength, actlength, HUFFMAN_CODES); |
| 491 | | } |
| 492 | | |
| 493 | | |
| 494 | | /*------------------------------------------------- |
| 495 | | huffman_export_tree - export a huffman tree |
| 496 | | to a target data stream |
| 497 | | -------------------------------------------------*/ |
| 498 | | |
| 499 | | huffman_error huffman_export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength) |
| 500 | | { |
| 501 | | return export_tree(context, dest, dlength, actlength, HUFFMAN_CODES); |
| 502 | | } |
| 503 | | |
| 504 | | |
| 505 | | /*------------------------------------------------- |
| 506 | | huffman_deltarle_import_tree - import a |
| 507 | | huffman tree from a source data stream for |
| 508 | | delta-RLE encoded data |
| 509 | | -------------------------------------------------*/ |
| 510 | | |
| 511 | | huffman_error huffman_deltarle_import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength) |
| 512 | | { |
| 513 | | return import_tree(context, source, slength, actlength, HUFFMAN_DELTARLE_CODES); |
| 514 | | } |
| 515 | | |
| 516 | | |
| 517 | | /*------------------------------------------------- |
| 518 | | huffman__deltarle_export_tree - export a |
| 519 | | huffman tree to a target data stream for |
| 520 | | delta-RLE encoded data |
| 521 | | -------------------------------------------------*/ |
| 522 | | |
| 523 | | huffman_error huffman_deltarle_export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength) |
| 524 | | { |
| 525 | | return export_tree(context, dest, dlength, actlength, HUFFMAN_DELTARLE_CODES); |
| 526 | | } |
| 527 | | |
| 528 | | |
| 529 | | /*------------------------------------------------- |
| 530 | | huffman_compute_tree - compute an optimal |
| 531 | | huffman tree for the given source data |
| 532 | | -------------------------------------------------*/ |
| 533 | | |
| 534 | | huffman_error huffman_compute_tree(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor) |
| 535 | | { |
| 536 | | return huffman_compute_tree_interleaved(1, &context, source, swidth, sheight, sstride, sxor); |
| 537 | | } |
| 538 | | |
| 539 | | huffman_error huffman_compute_tree_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor) |
| 540 | | { |
| 541 | | UINT32 sx, sy, ctxnum; |
| 542 | | huffman_error error; |
| 543 | | |
| 544 | | /* initialize all nodes */ |
| 545 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 183 | // loop until we read all the nodes |
| 184 | int curnode; |
| 185 | for (curnode = 0; curnode < m_numcodes; ) |
| 546 | 186 | { |
| 547 | | huffman_context *context = contexts[ctxnum]; |
| 548 | | memset(context->datahisto, 0, sizeof(context->datahisto)); |
| 549 | | } |
| 187 | // a non-one value is just raw |
| 188 | int nodebits = bitbuf.read(numbits); |
| 189 | if (nodebits != 1) |
| 190 | m_huffnode[curnode++].m_numbits = nodebits; |
| 550 | 191 | |
| 551 | | /* iterate over "height" */ |
| 552 | | for (sy = 0; sy < sheight; sy++) |
| 553 | | { |
| 554 | | /* iterate over "width" */ |
| 555 | | for (sx = 0; sx < swidth; ) |
| 192 | // a one value is an escape code |
| 193 | else |
| 556 | 194 | { |
| 557 | | /* iterate over contexts */ |
| 558 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, sx++) |
| 559 | | { |
| 560 | | huffman_context *context = contexts[ctxnum]; |
| 561 | | context->datahisto[source[sx ^ sxor]]++; |
| 562 | | } |
| 563 | | } |
| 195 | // a double 1 is just a single 1 |
| 196 | nodebits = bitbuf.read(numbits); |
| 197 | if (nodebits == 1) |
| 198 | m_huffnode[curnode++].m_numbits = nodebits; |
| 564 | 199 | |
| 565 | | /* advance to the next row */ |
| 566 | | source += sstride; |
| 567 | | } |
| 568 | | |
| 569 | | /* compute optimal trees for each */ |
| 570 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 571 | | { |
| 572 | | huffman_context *context = contexts[ctxnum]; |
| 573 | | error = compute_optimal_tree(context, context->datahisto, HUFFMAN_CODES); |
| 574 | | if (error != HUFFERR_NONE) |
| 575 | | return error; |
| 576 | | } |
| 577 | | return HUFFERR_NONE; |
| 578 | | } |
| 579 | | |
| 580 | | |
| 581 | | /*------------------------------------------------- |
| 582 | | huffman_deltarle_compute_tree - compute an |
| 583 | | optimal huffman tree for the given source |
| 584 | | data, with pre-encoding as delta-RLE |
| 585 | | -------------------------------------------------*/ |
| 586 | | |
| 587 | | huffman_error huffman_deltarle_compute_tree(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor) |
| 588 | | { |
| 589 | | return huffman_deltarle_compute_tree_interleaved(1, &context, source, swidth, sheight, sstride, sxor); |
| 590 | | } |
| 591 | | |
| 592 | | huffman_error huffman_deltarle_compute_tree_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor) |
| 593 | | { |
| 594 | | UINT32 sx, sy, ctxnum; |
| 595 | | huffman_error error; |
| 596 | | |
| 597 | | /* initialize all nodes */ |
| 598 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 599 | | { |
| 600 | | huffman_context *context = contexts[ctxnum]; |
| 601 | | memset(context->datahisto, 0, sizeof(context->datahisto)); |
| 602 | | context->prevdata = 0; |
| 603 | | } |
| 604 | | |
| 605 | | /* iterate over "height" */ |
| 606 | | for (sy = 0; sy < sheight; sy++) |
| 607 | | { |
| 608 | | /* reset RLE counts */ |
| 609 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 610 | | { |
| 611 | | huffman_context *context = contexts[ctxnum]; |
| 612 | | context->rleremaining = 0; |
| 613 | | } |
| 614 | | |
| 615 | | /* iterate over "width" */ |
| 616 | | for (sx = 0; sx < swidth; ) |
| 617 | | { |
| 618 | | /* iterate over contexts */ |
| 619 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, sx++) |
| 200 | // otherwise, we need one for value for the repeat count |
| 201 | else |
| 620 | 202 | { |
| 621 | | huffman_context *context = contexts[ctxnum]; |
| 622 | | UINT8 newdata, delta; |
| 623 | | |
| 624 | | /* if still counting RLE, do nothing */ |
| 625 | | if (context->rleremaining != 0) |
| 626 | | { |
| 627 | | context->rleremaining--; |
| 628 | | continue; |
| 629 | | } |
| 630 | | |
| 631 | | /* fetch new data and compute the delta */ |
| 632 | | newdata = source[sx ^ sxor]; |
| 633 | | delta = newdata - context->prevdata; |
| 634 | | context->prevdata = newdata; |
| 635 | | |
| 636 | | /* 0 deltas scan forward for a count */ |
| 637 | | if (delta == 0) |
| 638 | | { |
| 639 | | int zerocount = 1; |
| 640 | | int rlecode; |
| 641 | | UINT32 scan; |
| 642 | | |
| 643 | | /* count the number of consecutive values */ |
| 644 | | for (scan = sx + 1; scan < swidth; scan++) |
| 645 | | if (contexts[scan % numcontexts] == context) |
| 646 | | { |
| 647 | | if (newdata == source[scan ^ sxor]) |
| 648 | | zerocount++; |
| 649 | | else |
| 650 | | break; |
| 651 | | } |
| 652 | | |
| 653 | | /* if we hit the end of row, maximize the count */ |
| 654 | | if (scan >= swidth && zerocount >= 8) |
| 655 | | zerocount = 100000; |
| 656 | | |
| 657 | | /* encode the maximal count we can */ |
| 658 | | rlecode = rlecount_to_code(zerocount); |
| 659 | | context->datahisto[rlecode]++; |
| 660 | | |
| 661 | | /* set up the remaining count */ |
| 662 | | context->rleremaining = code_to_rlecount(rlecode) - 1; |
| 663 | | } |
| 664 | | else |
| 665 | | { |
| 666 | | /* encode the actual delta */ |
| 667 | | context->datahisto[delta]++; |
| 668 | | } |
| 203 | int repcount = bitbuf.read(numbits) + 3; |
| 204 | while (repcount--) |
| 205 | m_huffnode[curnode++].m_numbits = nodebits; |
| 669 | 206 | } |
| 670 | 207 | } |
| 671 | | |
| 672 | | /* advance to the next row */ |
| 673 | | source += sstride; |
| 674 | 208 | } |
| 675 | 209 | |
| 676 | | /* compute optimal trees for each */ |
| 677 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 678 | | { |
| 679 | | huffman_context *context = contexts[ctxnum]; |
| 680 | | error = compute_optimal_tree(context, context->datahisto, HUFFMAN_DELTARLE_CODES); |
| 681 | | if (error != HUFFERR_NONE) |
| 682 | | return error; |
| 683 | | } |
| 684 | | return HUFFERR_NONE; |
| 685 | | } |
| 210 | // make sure we ended up with the right number |
| 211 | if (curnode != m_numcodes) |
| 212 | return HUFFERR_INVALID_DATA; |
| 686 | 213 | |
| 214 | // assign canonical codes for all nodes based on their code lengths |
| 215 | huffman_error error = assign_canonical_codes(); |
| 216 | if (error != HUFFERR_NONE) |
| 217 | return error; |
| 218 | |
| 219 | // build the lookup table |
| 220 | build_lookup_table(); |
| 687 | 221 | |
| 688 | | /*------------------------------------------------- |
| 689 | | huffman_encode_data - encode data using the |
| 690 | | given tree |
| 691 | | -------------------------------------------------*/ |
| 692 | | |
| 693 | | huffman_error huffman_encode_data(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength) |
| 694 | | { |
| 695 | | return huffman_encode_data_interleaved(1, &context, source, swidth, sheight, sstride, sxor, dest, dlength, actlength); |
| 222 | // determine final input length and report errors |
| 223 | return bitbuf.overflow() ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 696 | 224 | } |
| 697 | 225 | |
| 698 | | huffman_error huffman_encode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength) |
| 699 | | { |
| 700 | | UINT32 sx, sy, ctxnum; |
| 701 | | bit_buffer bitbuf; |
| 702 | 226 | |
| 703 | | /* initialize the output buffer */ |
| 704 | | bit_buffer_write_init(&bitbuf, dest, dlength); |
| 227 | //------------------------------------------------- |
| 228 | // export_tree_rle - export a huffman tree to an |
| 229 | // RLE target data stream |
| 230 | //------------------------------------------------- |
| 705 | 231 | |
| 706 | | /* iterate over "height" */ |
| 707 | | for (sy = 0; sy < sheight; sy++) |
| 708 | | { |
| 709 | | /* iterate over "width" */ |
| 710 | | for (sx = 0; sx < swidth; ) |
| 711 | | { |
| 712 | | /* iterate over contexts */ |
| 713 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, sx++) |
| 714 | | { |
| 715 | | huffman_context *context = contexts[ctxnum]; |
| 716 | | huffman_node *node = &context->huffnode[source[sx ^ sxor]]; |
| 717 | | bit_buffer_write(&bitbuf, node->bits, node->numbits); |
| 718 | | } |
| 719 | | } |
| 720 | | |
| 721 | | /* advance to the next row */ |
| 722 | | source += sstride; |
| 723 | | } |
| 724 | | |
| 725 | | /* flush and return a status */ |
| 726 | | *actlength = bit_buffer_flush(&bitbuf); |
| 727 | | return bitbuf.overflow ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 728 | | } |
| 729 | | |
| 730 | | |
| 731 | | /*------------------------------------------------- |
| 732 | | huffman_deltarle_encode_data - encode data |
| 733 | | using the given tree with delta-RLE |
| 734 | | pre-encoding |
| 735 | | -------------------------------------------------*/ |
| 736 | | |
| 737 | | huffman_error huffman_deltarle_encode_data(huffman_context *context, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength) |
| 232 | huffman_error huffman_context_base::export_tree_rle(bitstream_out &bitbuf) |
| 738 | 233 | { |
| 739 | | return huffman_deltarle_encode_data_interleaved(1, &context, source, swidth, sheight, sstride, sxor, dest, dlength, actlength); |
| 740 | | } |
| 234 | // bits per entry depends on the maxbits |
| 235 | int numbits; |
| 236 | if (m_maxbits >= 16) |
| 237 | numbits = 5; |
| 238 | else if (m_maxbits >= 8) |
| 239 | numbits = 4; |
| 240 | else |
| 241 | numbits = 3; |
| 741 | 242 | |
| 742 | | huffman_error huffman_deltarle_encode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 swidth, UINT32 sheight, UINT32 sstride, UINT32 sxor, UINT8 *dest, UINT32 dlength, UINT32 *actlength) |
| 743 | | { |
| 744 | | UINT32 sx, sy, ctxnum; |
| 745 | | bit_buffer bitbuf; |
| 746 | | |
| 747 | | /* initialize the output buffer */ |
| 748 | | bit_buffer_write_init(&bitbuf, dest, dlength); |
| 749 | | |
| 750 | | /* initialize the contexts */ |
| 751 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 243 | // RLE encode the lengths |
| 244 | int lastval = ~0; |
| 245 | int repcount = 0; |
| 246 | for (int curcode = 0; curcode < m_numcodes; curcode++) |
| 752 | 247 | { |
| 753 | | huffman_context *context = contexts[ctxnum]; |
| 754 | | context->prevdata = 0; |
| 755 | | } |
| 248 | // if we match the previous value, just bump the repcount |
| 249 | int newval = m_huffnode[curcode].m_numbits; |
| 250 | if (newval == lastval) |
| 251 | repcount++; |
| 756 | 252 | |
| 757 | | /* iterate over "height" */ |
| 758 | | for (sy = 0; sy < sheight; sy++) |
| 759 | | { |
| 760 | | /* reset RLE counts */ |
| 761 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 253 | // otherwise, we need to flush the previous repeats |
| 254 | else |
| 762 | 255 | { |
| 763 | | huffman_context *context = contexts[ctxnum]; |
| 764 | | context->rleremaining = 0; |
| 256 | if (repcount != 0) |
| 257 | write_rle_tree_bits(bitbuf, lastval, repcount, numbits); |
| 258 | lastval = newval; |
| 259 | repcount = 1; |
| 765 | 260 | } |
| 766 | | |
| 767 | | /* iterate over "width" */ |
| 768 | | for (sx = 0; sx < swidth; ) |
| 769 | | { |
| 770 | | /* iterate over contexts */ |
| 771 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, sx++) |
| 772 | | { |
| 773 | | huffman_context *context = contexts[ctxnum]; |
| 774 | | UINT8 newdata, delta; |
| 775 | | huffman_node *node; |
| 776 | | |
| 777 | | /* if still counting RLE, do nothing */ |
| 778 | | if (context->rleremaining != 0) |
| 779 | | { |
| 780 | | context->rleremaining--; |
| 781 | | continue; |
| 782 | | } |
| 783 | | |
| 784 | | /* fetch new data and compute the delta */ |
| 785 | | newdata = source[sx ^ sxor]; |
| 786 | | delta = newdata - context->prevdata; |
| 787 | | context->prevdata = newdata; |
| 788 | | |
| 789 | | /* 0 deltas scan forward for a count */ |
| 790 | | if (delta == 0) |
| 791 | | { |
| 792 | | int zerocount = 1; |
| 793 | | int rlecode; |
| 794 | | UINT32 scan; |
| 795 | | |
| 796 | | /* count the number of consecutive values */ |
| 797 | | for (scan = sx + 1; scan < swidth; scan++) |
| 798 | | if (contexts[scan % numcontexts] == context) |
| 799 | | { |
| 800 | | if (newdata == source[scan ^ sxor]) |
| 801 | | zerocount++; |
| 802 | | else |
| 803 | | break; |
| 804 | | } |
| 805 | | |
| 806 | | /* if we hit the end of row, maximize the count */ |
| 807 | | if (scan >= swidth && zerocount >= 8) |
| 808 | | zerocount = 100000; |
| 809 | | |
| 810 | | /* encode the maximal count we can */ |
| 811 | | rlecode = rlecount_to_code(zerocount); |
| 812 | | node = &context->huffnode[rlecode]; |
| 813 | | bit_buffer_write(&bitbuf, node->bits, node->numbits); |
| 814 | | |
| 815 | | /* set up the remaining count */ |
| 816 | | context->rleremaining = code_to_rlecount(rlecode) - 1; |
| 817 | | } |
| 818 | | else |
| 819 | | { |
| 820 | | /* encode the actual delta */ |
| 821 | | node = &context->huffnode[delta]; |
| 822 | | bit_buffer_write(&bitbuf, node->bits, node->numbits); |
| 823 | | } |
| 824 | | } |
| 825 | | } |
| 826 | | |
| 827 | | /* advance to the next row */ |
| 828 | | source += sstride; |
| 829 | 261 | } |
| 830 | 262 | |
| 831 | | /* flush and return a status */ |
| 832 | | *actlength = bit_buffer_flush(&bitbuf); |
| 833 | | return bitbuf.overflow ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 263 | // flush the last value |
| 264 | write_rle_tree_bits(bitbuf, lastval, repcount, numbits); |
| 265 | return bitbuf.overflow() ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 834 | 266 | } |
| 835 | 267 | |
| 836 | 268 | |
| 837 | | /*------------------------------------------------- |
| 838 | | huffman_decode_data - decode data using the |
| 839 | | given tree |
| 840 | | -------------------------------------------------*/ |
| 269 | //------------------------------------------------- |
| 270 | // import_tree_huffman - import a huffman-encoded |
| 271 | // huffman tree from a source data stream |
| 272 | //------------------------------------------------- |
| 841 | 273 | |
| 842 | | huffman_error huffman_decode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) |
| 274 | huffman_error huffman_context_base::import_tree_huffman(bitstream_in &bitbuf) |
| 843 | 275 | { |
| 844 | | const huffman_lookup_value *table; |
| 845 | | int maxbits = context->maxbits; |
| 846 | | huffman_error error; |
| 847 | | bit_buffer bitbuf; |
| 848 | | UINT32 dx, dy; |
| 849 | | |
| 850 | | /* regenerate the lookup table if necessary */ |
| 851 | | if (context->lookupdirty) |
| 276 | // start by parsing the lengths for the small tree |
| 277 | huffman_decoder<24, 6> smallhuff; |
| 278 | smallhuff.m_huffnode[0].m_numbits = bitbuf.read(3); |
| 279 | int start = bitbuf.read(3) + 1; |
| 280 | int count = 0; |
| 281 | for (int index = 1; index < 24; index++) |
| 852 | 282 | { |
| 853 | | error = build_lookup_table(context, HUFFMAN_CODES); |
| 854 | | if (error != HUFFERR_NONE) |
| 855 | | return error; |
| 856 | | } |
| 857 | | table = context->lookup; |
| 858 | | |
| 859 | | /* initialize our bit buffer */ |
| 860 | | bit_buffer_read_init(&bitbuf, source, slength); |
| 861 | | |
| 862 | | /* iterate over "height" */ |
| 863 | | for (dy = 0; dy < dheight; dy++) |
| 864 | | { |
| 865 | | /* iterate over "width" */ |
| 866 | | for (dx = 0; dx < dwidth; dx++) |
| 283 | if (index < start || count == 7) |
| 284 | smallhuff.m_huffnode[index].m_numbits = 0; |
| 285 | else |
| 867 | 286 | { |
| 868 | | huffman_lookup_value lookup; |
| 869 | | UINT32 bits; |
| 870 | | |
| 871 | | /* peek ahead to get maxbits worth of data */ |
| 872 | | bits = bit_buffer_peek(&bitbuf, maxbits); |
| 873 | | |
| 874 | | /* look it up, then remove the actual number of bits for this code */ |
| 875 | | lookup = table[bits]; |
| 876 | | bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); |
| 877 | | |
| 878 | | /* store the upper byte */ |
| 879 | | dest[dx ^ dxor] = LOOKUP_CODE(lookup); |
| 287 | count = bitbuf.read(3); |
| 288 | smallhuff.m_huffnode[index].m_numbits = (count == 7) ? 0 : count; |
| 880 | 289 | } |
| 881 | | |
| 882 | | /* advance to the next row */ |
| 883 | | dest += dstride; |
| 884 | 290 | } |
| 291 | |
| 292 | // then regenerate the tree |
| 293 | huffman_error error = smallhuff.assign_canonical_codes(); |
| 294 | if (error != HUFFERR_NONE) |
| 295 | return error; |
| 296 | smallhuff.build_lookup_table(); |
| 297 | |
| 298 | // determine the maximum length of an RLE count |
| 299 | UINT32 temp = m_numcodes - 9; |
| 300 | UINT8 rlefullbits = 0; |
| 301 | while (temp != 0) |
| 302 | temp >>= 1, rlefullbits++; |
| 885 | 303 | |
| 886 | | /* determine the actual length and indicate overflow */ |
| 887 | | *actlength = bit_buffer_read_offset(&bitbuf); |
| 888 | | return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 889 | | } |
| 890 | | |
| 891 | | |
| 892 | | /*------------------------------------------------- |
| 893 | | huffman_decode_data_interleaved - decode |
| 894 | | interleaved data using multiple contexts |
| 895 | | -------------------------------------------------*/ |
| 896 | | |
| 897 | | huffman_error huffman_decode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) |
| 898 | | { |
| 899 | | UINT32 dx, dy, ctxnum; |
| 900 | | huffman_error error; |
| 901 | | bit_buffer bitbuf; |
| 902 | | |
| 903 | | /* regenerate the lookup tables if necessary */ |
| 904 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 304 | // now process the rest of the data |
| 305 | int last = 0; |
| 306 | int curcode; |
| 307 | for (curcode = 0; curcode < m_numcodes; ) |
| 905 | 308 | { |
| 906 | | huffman_context *context = contexts[ctxnum]; |
| 907 | | if (context->lookupdirty) |
| 309 | int value = smallhuff.decode_one(bitbuf); |
| 310 | if (value != 0) |
| 311 | m_huffnode[curcode++].m_numbits = last = value - 1; |
| 312 | else |
| 908 | 313 | { |
| 909 | | error = build_lookup_table(context, HUFFMAN_CODES); |
| 910 | | if (error != HUFFERR_NONE) |
| 911 | | return error; |
| 314 | int count = bitbuf.read(3) + 2; |
| 315 | if (count == 7+2) |
| 316 | count += bitbuf.read(rlefullbits); |
| 317 | for ( ; count != 0 && curcode < m_numcodes; count--) |
| 318 | m_huffnode[curcode++].m_numbits = last; |
| 912 | 319 | } |
| 913 | 320 | } |
| 914 | 321 | |
| 915 | | /* initialize our bit buffer */ |
| 916 | | bit_buffer_read_init(&bitbuf, source, slength); |
| 322 | // make sure we ended up with the right number |
| 323 | if (curcode != m_numcodes) |
| 324 | return HUFFERR_INVALID_DATA; |
| 917 | 325 | |
| 918 | | /* iterate over "height" */ |
| 919 | | for (dy = 0; dy < dheight; dy++) |
| 920 | | { |
| 921 | | /* iterate over "width" */ |
| 922 | | for (dx = 0; dx < dwidth; ) |
| 923 | | { |
| 924 | | /* iterate over contexts */ |
| 925 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, dx++) |
| 926 | | { |
| 927 | | huffman_context *context = contexts[ctxnum]; |
| 928 | | huffman_lookup_value lookup; |
| 929 | | UINT32 bits; |
| 326 | // assign canonical codes for all nodes based on their code lengths |
| 327 | error = assign_canonical_codes(); |
| 328 | if (error != HUFFERR_NONE) |
| 329 | return error; |
| 930 | 330 | |
| 931 | | /* peek ahead to get maxbits worth of data */ |
| 932 | | bits = bit_buffer_peek(&bitbuf, context->maxbits); |
| 331 | // build the lookup table |
| 332 | build_lookup_table(); |
| 933 | 333 | |
| 934 | | /* look it up, then remove the actual number of bits for this code */ |
| 935 | | lookup = context->lookup[bits]; |
| 936 | | bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); |
| 937 | | |
| 938 | | /* store the upper byte */ |
| 939 | | dest[dx ^ dxor] = LOOKUP_CODE(lookup); |
| 940 | | } |
| 941 | | } |
| 942 | | |
| 943 | | /* advance to the next row */ |
| 944 | | dest += dstride; |
| 945 | | } |
| 946 | | |
| 947 | | /* determine the actual length and indicate overflow */ |
| 948 | | *actlength = bit_buffer_read_offset(&bitbuf); |
| 949 | | return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 334 | // determine final input length and report errors |
| 335 | return bitbuf.overflow() ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 950 | 336 | } |
| 951 | 337 | |
| 952 | 338 | |
| 953 | | /*------------------------------------------------- |
| 954 | | huffman_deltarle_decode_data - decode data |
| 955 | | using the given tree with delta-RLE |
| 956 | | post-decoding |
| 957 | | -------------------------------------------------*/ |
| 339 | //------------------------------------------------- |
| 340 | // export_tree_huffman - export a huffman tree to |
| 341 | // a huffman target data stream |
| 342 | //------------------------------------------------- |
| 958 | 343 | |
| 959 | | huffman_error huffman_deltarle_decode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) |
| 344 | huffman_error huffman_context_base::export_tree_huffman(bitstream_out &bitbuf) |
| 960 | 345 | { |
| 961 | | const huffman_lookup_value *table; |
| 962 | | int maxbits = context->maxbits; |
| 963 | | UINT32 rleremaining = 0; |
| 964 | | huffman_error error; |
| 965 | | UINT8 prevdata = 0; |
| 966 | | bit_buffer bitbuf; |
| 967 | | UINT32 dx, dy; |
| 346 | // first RLE compress the lengths of all the nodes |
| 347 | dynamic_array<UINT8> rle_data(m_numcodes); |
| 348 | UINT8 *dest = rle_data; |
| 349 | dynamic_array<UINT16> rle_lengths(m_numcodes/3); |
| 350 | UINT16 *lengths = rle_lengths; |
| 351 | int last = ~0; |
| 352 | int repcount = 0; |
| 353 | |
| 354 | // use a small huffman context to create a tree (ignoring RLE lengths) |
| 355 | huffman_encoder<24, 6> smallhuff; |
| 968 | 356 | |
| 969 | | /* regenerate the lookup table if necessary */ |
| 970 | | if (context->lookupdirty) |
| 357 | // RLE-compress the lengths |
| 358 | for (int curcode = 0; curcode < m_numcodes; curcode++) |
| 971 | 359 | { |
| 972 | | error = build_lookup_table(context, HUFFMAN_DELTARLE_CODES); |
| 973 | | if (error != HUFFERR_NONE) |
| 974 | | return error; |
| 975 | | } |
| 976 | | table = context->lookup; |
| 977 | | |
| 978 | | /* initialize our bit buffer */ |
| 979 | | bit_buffer_read_init(&bitbuf, source, slength); |
| 980 | | |
| 981 | | /* iterate over "height" */ |
| 982 | | for (dy = 0; dy < dheight; dy++) |
| 983 | | { |
| 984 | | /* reset RLE counts */ |
| 985 | | rleremaining = 0; |
| 986 | | |
| 987 | | /* iterate over "width" */ |
| 988 | | for (dx = 0; dx < dwidth; dx++) |
| 360 | // if this is the end of a repeat, flush any accumulation |
| 361 | int newval = m_huffnode[curcode].m_numbits; |
| 362 | if (newval != last && repcount > 0) |
| 989 | 363 | { |
| 990 | | huffman_lookup_value lookup; |
| 991 | | UINT32 bits; |
| 992 | | int data; |
| 993 | | |
| 994 | | /* if we have RLE remaining, just store that */ |
| 995 | | if (rleremaining != 0) |
| 996 | | { |
| 997 | | rleremaining--; |
| 998 | | dest[dx ^ dxor] = prevdata; |
| 999 | | continue; |
| 1000 | | } |
| 1001 | | |
| 1002 | | /* peek ahead to get maxbits worth of data */ |
| 1003 | | bits = bit_buffer_peek(&bitbuf, maxbits); |
| 1004 | | |
| 1005 | | /* look it up, then remove the actual number of bits for this code */ |
| 1006 | | lookup = table[bits]; |
| 1007 | | bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); |
| 1008 | | |
| 1009 | | /* compute the data and handle RLE decoding */ |
| 1010 | | data = LOOKUP_CODE(lookup); |
| 1011 | | |
| 1012 | | /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ |
| 1013 | | if (data < 0x100) |
| 1014 | | prevdata += (UINT8)data; |
| 1015 | | else |
| 1016 | | rleremaining = code_to_rlecount(data) - 1; |
| 1017 | | |
| 1018 | | /* store the updated data value */ |
| 1019 | | dest[dx ^ dxor] = prevdata; |
| 364 | if (repcount == 1) |
| 365 | smallhuff.histo_one(*dest++ = last + 1); |
| 366 | else |
| 367 | smallhuff.histo_one(*dest++ = 0), *lengths++ = repcount - 2; |
| 1020 | 368 | } |
| 1021 | | |
| 1022 | | /* advance to the next row */ |
| 1023 | | dest += dstride; |
| 369 | |
| 370 | // if same as last, just track repeats |
| 371 | if (newval == last) |
| 372 | repcount++; |
| 373 | |
| 374 | // otherwise, write it and start a new run |
| 375 | else |
| 376 | { |
| 377 | smallhuff.histo_one(*dest++ = newval + 1); |
| 378 | last = newval; |
| 379 | repcount = 0; |
| 380 | } |
| 1024 | 381 | } |
| 1025 | 382 | |
| 1026 | | /* determine the actual length and indicate overflow */ |
| 1027 | | *actlength = bit_buffer_read_offset(&bitbuf); |
| 1028 | | return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 1029 | | } |
| 1030 | | |
| 1031 | | |
| 1032 | | /*------------------------------------------------- |
| 1033 | | huffman_deltarle_decode_data_interleaved - |
| 1034 | | decode data using multiple contexts and |
| 1035 | | delta-RLE post-decoding |
| 1036 | | -------------------------------------------------*/ |
| 1037 | | |
| 1038 | | huffman_error huffman_deltarle_decode_data_interleaved(int numcontexts, huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) |
| 1039 | | { |
| 1040 | | UINT32 dx, dy, ctxnum; |
| 1041 | | huffman_error error; |
| 1042 | | bit_buffer bitbuf; |
| 1043 | | |
| 1044 | | /* fast case the A/V Y/Cb/Y/Cr case */ |
| 1045 | | if (numcontexts == 4 && contexts[0] == contexts[2] && contexts[0] != contexts[1] && contexts[1] != contexts[3] && |
| 1046 | | contexts[0]->maxbits == contexts[1]->maxbits && contexts[0]->maxbits == contexts[3]->maxbits) |
| 1047 | | return huffman_deltarle_decode_data_interleaved_0102(contexts, source, slength, dest, dwidth, dheight, dstride, dxor, actlength); |
| 1048 | | |
| 1049 | | /* regenerate the lookup tables if necessary */ |
| 1050 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 383 | // flush any final RLE counts |
| 384 | if (repcount > 0) |
| 1051 | 385 | { |
| 1052 | | huffman_context *context = contexts[ctxnum]; |
| 1053 | | if (context->lookupdirty) |
| 1054 | | { |
| 1055 | | error = build_lookup_table(context, HUFFMAN_DELTARLE_CODES); |
| 1056 | | if (error != HUFFERR_NONE) |
| 1057 | | return error; |
| 1058 | | } |
| 1059 | | context->prevdata = 0; |
| 386 | if (repcount == 1) |
| 387 | smallhuff.histo_one(*dest++ = last + 1); |
| 388 | else |
| 389 | smallhuff.histo_one(*dest++ = 0), *lengths++ = repcount - 2; |
| 1060 | 390 | } |
| 1061 | 391 | |
| 1062 | | /* initialize our bit buffer */ |
| 1063 | | bit_buffer_read_init(&bitbuf, source, slength); |
| 392 | // compute an optimal tree |
| 393 | smallhuff.compute_tree_from_histo(); |
| 1064 | 394 | |
| 1065 | | /* iterate over "height" */ |
| 1066 | | for (dy = 0; dy < dheight; dy++) |
| 1067 | | { |
| 1068 | | /* reset RLE counts */ |
| 1069 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++) |
| 395 | // determine the first and last non-zero nodes |
| 396 | int first_non_zero = 31, last_non_zero = 0; |
| 397 | for (int index = 1; index < smallhuff.m_numcodes; index++) |
| 398 | if (smallhuff.m_huffnode[index].m_numbits != 0) |
| 1070 | 399 | { |
| 1071 | | huffman_context *context = contexts[ctxnum]; |
| 1072 | | context->rleremaining = 0; |
| 400 | if (first_non_zero == 31) |
| 401 | first_non_zero = index; |
| 402 | last_non_zero = index; |
| 1073 | 403 | } |
| 1074 | 404 | |
| 1075 | | /* iterate over "width" */ |
| 1076 | | for (dx = 0; dx < dwidth; ) |
| 1077 | | { |
| 1078 | | /* iterate over contexts */ |
| 1079 | | for (ctxnum = 0; ctxnum < numcontexts; ctxnum++, dx++) |
| 1080 | | { |
| 1081 | | huffman_context *context = contexts[ctxnum]; |
| 1082 | | huffman_lookup_value lookup; |
| 1083 | | UINT32 bits; |
| 1084 | | int data; |
| 405 | // clamp first non-zero to be 8 at a maximum |
| 406 | first_non_zero = MIN(first_non_zero, 8); |
| 1085 | 407 | |
| 1086 | | /* if we have RLE remaining, just store that */ |
| 1087 | | if (context->rleremaining != 0) |
| 1088 | | { |
| 1089 | | context->rleremaining--; |
| 1090 | | dest[dx ^ dxor] = context->prevdata; |
| 1091 | | continue; |
| 1092 | | } |
| 408 | // output the lengths of the each small tree node, starting with the RLE |
| 409 | // token (0), followed by the first_non_zero value, followed by the data |
| 410 | // terminated by a 7 |
| 411 | bitbuf.write(smallhuff.m_huffnode[0].m_numbits, 3); |
| 412 | bitbuf.write(first_non_zero - 1, 3); |
| 413 | for (int index = first_non_zero; index <= last_non_zero; index++) |
| 414 | bitbuf.write(smallhuff.m_huffnode[index].m_numbits, 3); |
| 415 | bitbuf.write(7, 3); |
| 416 | |
| 417 | // determine the maximum length of an RLE count |
| 418 | UINT32 temp = m_numcodes - 9; |
| 419 | UINT8 rlefullbits = 0; |
| 420 | while (temp != 0) |
| 421 | temp >>= 1, rlefullbits++; |
| 1093 | 422 | |
| 1094 | | /* peek ahead to get maxbits worth of data */ |
| 1095 | | bits = bit_buffer_peek(&bitbuf, context->maxbits); |
| 1096 | | |
| 1097 | | /* look it up, then remove the actual number of bits for this code */ |
| 1098 | | lookup = context->lookup[bits]; |
| 1099 | | bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); |
| 1100 | | |
| 1101 | | /* compute the data and handle RLE decoding */ |
| 1102 | | data = LOOKUP_CODE(lookup); |
| 1103 | | |
| 1104 | | /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ |
| 1105 | | if (data < 0x100) |
| 1106 | | context->prevdata += (UINT8)data; |
| 1107 | | else |
| 1108 | | context->rleremaining = code_to_rlecount(data) - 1; |
| 1109 | | |
| 1110 | | /* store the updated data value */ |
| 1111 | | dest[dx ^ dxor] = context->prevdata; |
| 1112 | | } |
| 1113 | | } |
| 1114 | | |
| 1115 | | /* advance to the next row */ |
| 1116 | | dest += dstride; |
| 1117 | | } |
| 1118 | | |
| 1119 | | /* determine the actual length and indicate overflow */ |
| 1120 | | *actlength = bit_buffer_read_offset(&bitbuf); |
| 1121 | | return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 1122 | | } |
| 1123 | | |
| 1124 | | |
| 1125 | | /*------------------------------------------------- |
| 1126 | | huffman_deltarle_decode_data_interleaved_0102 - |
| 1127 | | decode data using 3 unique contexts in |
| 1128 | | 0/1/0/2 order (used for Y/Cb/Y/Cr encoding) |
| 1129 | | -------------------------------------------------*/ |
| 1130 | | |
| 1131 | | static huffman_error huffman_deltarle_decode_data_interleaved_0102(huffman_context **contexts, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dwidth, UINT32 dheight, UINT32 dstride, UINT32 dxor, UINT32 *actlength) |
| 1132 | | { |
| 1133 | | const huffman_lookup_value *table02, *table1, *table3; |
| 1134 | | int rleremaining02, rleremaining1, rleremaining3; |
| 1135 | | UINT8 prevdata02 = 0, prevdata1 = 0, prevdata3 = 0; |
| 1136 | | int maxbits = contexts[0]->maxbits; |
| 1137 | | huffman_error error; |
| 1138 | | bit_buffer bitbuf; |
| 1139 | | UINT32 dx, dy; |
| 1140 | | |
| 1141 | | /* regenerate the lookup tables if necessary */ |
| 1142 | | if (contexts[0]->lookupdirty) |
| 423 | // now encode the RLE data |
| 424 | lengths = rle_lengths; |
| 425 | for (UINT8 *src = rle_data; src < dest; src++) |
| 1143 | 426 | { |
| 1144 | | error = build_lookup_table(contexts[0], HUFFMAN_DELTARLE_CODES); |
| 1145 | | if (error != HUFFERR_NONE) |
| 1146 | | return error; |
| 1147 | | } |
| 1148 | | if (contexts[1]->lookupdirty) |
| 1149 | | { |
| 1150 | | error = build_lookup_table(contexts[1], HUFFMAN_DELTARLE_CODES); |
| 1151 | | if (error != HUFFERR_NONE) |
| 1152 | | return error; |
| 1153 | | } |
| 1154 | | if (contexts[3]->lookupdirty) |
| 1155 | | { |
| 1156 | | error = build_lookup_table(contexts[3], HUFFMAN_DELTARLE_CODES); |
| 1157 | | if (error != HUFFERR_NONE) |
| 1158 | | return error; |
| 1159 | | } |
| 1160 | | |
| 1161 | | /* cache the tables locally */ |
| 1162 | | table02 = contexts[0]->lookup; |
| 1163 | | table1 = contexts[1]->lookup; |
| 1164 | | table3 = contexts[3]->lookup; |
| 1165 | | |
| 1166 | | /* initialize our bit buffer */ |
| 1167 | | bit_buffer_read_init(&bitbuf, source, slength); |
| 1168 | | |
| 1169 | | /* iterate over "height" */ |
| 1170 | | for (dy = 0; dy < dheight; dy++) |
| 1171 | | { |
| 1172 | | /* reset RLE counts */ |
| 1173 | | rleremaining02 = rleremaining1 = rleremaining3 = 0; |
| 1174 | | |
| 1175 | | /* iterate over "width" */ |
| 1176 | | for (dx = 0; dx < dwidth; dx += 4) |
| 427 | // encode the data |
| 428 | UINT8 data = *src; |
| 429 | smallhuff.encode_one(bitbuf, data); |
| 430 | |
| 431 | // if this is an RLE token, encode the length following |
| 432 | if (data == 0) |
| 1177 | 433 | { |
| 1178 | | huffman_lookup_value lookup; |
| 1179 | | UINT32 bits; |
| 1180 | | int data; |
| 1181 | | |
| 1182 | | /* ----- offset 0 ----- */ |
| 1183 | | |
| 1184 | | /* if we have RLE remaining, just store that */ |
| 1185 | | if (rleremaining02 != 0) |
| 1186 | | rleremaining02--; |
| 434 | int count = *lengths++; |
| 435 | if (count < 7) |
| 436 | bitbuf.write(count, 3); |
| 1187 | 437 | else |
| 1188 | | { |
| 1189 | | /* peek ahead to get maxbits worth of data */ |
| 1190 | | bits = bit_buffer_peek(&bitbuf, maxbits); |
| 1191 | | |
| 1192 | | /* look it up, then remove the actual number of bits for this code */ |
| 1193 | | lookup = table02[bits]; |
| 1194 | | bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); |
| 1195 | | |
| 1196 | | /* compute the data and handle RLE decoding */ |
| 1197 | | data = LOOKUP_CODE(lookup); |
| 1198 | | |
| 1199 | | /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ |
| 1200 | | if (data < 0x100) |
| 1201 | | prevdata02 += (UINT8)data; |
| 1202 | | else |
| 1203 | | rleremaining02 = code_to_rlecount(data) - 1; |
| 1204 | | } |
| 1205 | | |
| 1206 | | /* store the updated data value */ |
| 1207 | | dest[(dx + 0) ^ dxor] = prevdata02; |
| 1208 | | |
| 1209 | | /* ----- offset 1 ----- */ |
| 1210 | | |
| 1211 | | /* if we have RLE remaining, just store that */ |
| 1212 | | if (rleremaining1 != 0) |
| 1213 | | rleremaining1--; |
| 1214 | | else |
| 1215 | | { |
| 1216 | | /* peek ahead to get maxbits worth of data */ |
| 1217 | | bits = bit_buffer_peek(&bitbuf, maxbits); |
| 1218 | | |
| 1219 | | /* look it up, then remove the actual number of bits for this code */ |
| 1220 | | lookup = table1[bits]; |
| 1221 | | bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); |
| 1222 | | |
| 1223 | | /* compute the data and handle RLE decoding */ |
| 1224 | | data = LOOKUP_CODE(lookup); |
| 1225 | | |
| 1226 | | /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ |
| 1227 | | if (data < 0x100) |
| 1228 | | prevdata1 += (UINT8)data; |
| 1229 | | else |
| 1230 | | rleremaining1 = code_to_rlecount(data) - 1; |
| 1231 | | } |
| 1232 | | |
| 1233 | | /* store the updated data value */ |
| 1234 | | dest[(dx + 1) ^ dxor] = prevdata1; |
| 1235 | | |
| 1236 | | /* ----- offset 2 (same as 0) ----- */ |
| 1237 | | |
| 1238 | | /* if we have RLE remaining, just store that */ |
| 1239 | | if (rleremaining02 != 0) |
| 1240 | | rleremaining02--; |
| 1241 | | else |
| 1242 | | { |
| 1243 | | /* peek ahead to get maxbits worth of data */ |
| 1244 | | bits = bit_buffer_peek(&bitbuf, maxbits); |
| 1245 | | |
| 1246 | | /* look it up, then remove the actual number of bits for this code */ |
| 1247 | | lookup = table02[bits]; |
| 1248 | | bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); |
| 1249 | | |
| 1250 | | /* compute the data and handle RLE decoding */ |
| 1251 | | data = LOOKUP_CODE(lookup); |
| 1252 | | |
| 1253 | | /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ |
| 1254 | | if (data < 0x100) |
| 1255 | | prevdata02 += (UINT8)data; |
| 1256 | | else |
| 1257 | | rleremaining02 = code_to_rlecount(data) - 1; |
| 1258 | | } |
| 1259 | | |
| 1260 | | /* store the updated data value */ |
| 1261 | | dest[(dx + 2) ^ dxor] = prevdata02; |
| 1262 | | |
| 1263 | | /* ----- offset 3 ----- */ |
| 1264 | | |
| 1265 | | /* if we have RLE remaining, just store that */ |
| 1266 | | if (rleremaining3 != 0) |
| 1267 | | rleremaining3--; |
| 1268 | | else |
| 1269 | | { |
| 1270 | | /* peek ahead to get maxbits worth of data */ |
| 1271 | | bits = bit_buffer_peek(&bitbuf, maxbits); |
| 1272 | | |
| 1273 | | /* look it up, then remove the actual number of bits for this code */ |
| 1274 | | lookup = table3[bits]; |
| 1275 | | bit_buffer_remove(&bitbuf, LOOKUP_BITS(lookup)); |
| 1276 | | |
| 1277 | | /* compute the data and handle RLE decoding */ |
| 1278 | | data = LOOKUP_CODE(lookup); |
| 1279 | | |
| 1280 | | /* if not an RLE special, just add to the previous; otherwise, start counting RLE */ |
| 1281 | | if (data < 0x100) |
| 1282 | | prevdata3 += (UINT8)data; |
| 1283 | | else |
| 1284 | | rleremaining3 = code_to_rlecount(data) - 1; |
| 1285 | | } |
| 1286 | | |
| 1287 | | /* store the updated data value */ |
| 1288 | | dest[(dx + 3) ^ dxor] = prevdata3; |
| 438 | bitbuf.write(7, 3), bitbuf.write(count - 7, rlefullbits); |
| 1289 | 439 | } |
| 1290 | | |
| 1291 | | /* advance to the next row */ |
| 1292 | | dest += dstride; |
| 1293 | 440 | } |
| 1294 | | |
| 1295 | | /* determine the actual length and indicate overflow */ |
| 1296 | | *actlength = bit_buffer_read_offset(&bitbuf); |
| 1297 | | return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 441 | |
| 442 | // flush the final buffer |
| 443 | return bitbuf.overflow() ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 1298 | 444 | } |
| 1299 | 445 | |
| 1300 | 446 | |
| 447 | //------------------------------------------------- |
| 448 | // compute_tree_from_histo - common backend for |
| 449 | // computing a tree based on the data histogram |
| 450 | //------------------------------------------------- |
| 1301 | 451 | |
| 1302 | | /*************************************************************************** |
| 1303 | | INTERNAL FUNCTIONS |
| 1304 | | ***************************************************************************/ |
| 1305 | | |
| 1306 | | /*------------------------------------------------- |
| 1307 | | import_tree - import a huffman tree from a |
| 1308 | | source data stream |
| 1309 | | -------------------------------------------------*/ |
| 1310 | | |
| 1311 | | static huffman_error import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength, UINT32 numcodes) |
| 452 | huffman_error huffman_context_base::compute_tree_from_histo() |
| 1312 | 453 | { |
| 1313 | | huffman_error error; |
| 1314 | | bit_buffer bitbuf; |
| 1315 | | int curnode; |
| 1316 | | int numbits; |
| 454 | // compute the number of data items in the histogram |
| 455 | UINT32 sdatacount = 0; |
| 456 | for (int i = 0; i < m_numcodes; i++) |
| 457 | sdatacount += m_datahisto[i]; |
| 1317 | 458 | |
| 1318 | | /* initialize the input buffer */ |
| 1319 | | bit_buffer_read_init(&bitbuf, source, slength); |
| 1320 | | |
| 1321 | | /* bits per entry depends on the maxbits */ |
| 1322 | | if (context->maxbits >= 16) |
| 1323 | | numbits = 5; |
| 1324 | | else if (context->maxbits >= 8) |
| 1325 | | numbits = 4; |
| 1326 | | else |
| 1327 | | numbits = 3; |
| 1328 | | |
| 1329 | | /* loop until we read all the nodes */ |
| 1330 | | for (curnode = 0; curnode < numcodes; ) |
| 459 | // binary search to achieve the optimum encoding |
| 460 | UINT32 lowerweight = 0; |
| 461 | UINT32 upperweight = sdatacount * 2; |
| 462 | while (1) |
| 1331 | 463 | { |
| 1332 | | int nodebits = bit_buffer_read(&bitbuf, numbits); |
| 464 | // build a tree using the current weight |
| 465 | UINT32 curweight = (upperweight + lowerweight) / 2; |
| 466 | int curmaxbits = build_tree(sdatacount, curweight); |
| 1333 | 467 | |
| 1334 | | /* a non-one value is just raw */ |
| 1335 | | if (nodebits != 1) |
| 1336 | | context->huffnode[curnode++].numbits = nodebits; |
| 1337 | | |
| 1338 | | /* a one value is an escape code */ |
| 1339 | | else |
| 468 | // apply binary search here |
| 469 | if (curmaxbits <= m_maxbits) |
| 1340 | 470 | { |
| 1341 | | nodebits = bit_buffer_read(&bitbuf, numbits); |
| 471 | lowerweight = curweight; |
| 1342 | 472 | |
| 1343 | | /* a double 1 is just a single 1 */ |
| 1344 | | if (nodebits == 1) |
| 1345 | | context->huffnode[curnode++].numbits = nodebits; |
| 1346 | | |
| 1347 | | /* otherwise, we need one for value for the repeat count */ |
| 1348 | | else |
| 1349 | | { |
| 1350 | | int repcount = bit_buffer_read(&bitbuf, numbits) + 3; |
| 1351 | | while (repcount--) |
| 1352 | | context->huffnode[curnode++].numbits = nodebits; |
| 1353 | | } |
| 473 | // early out if it worked with the raw weights, or if we're done searching |
| 474 | if (curweight == sdatacount || (upperweight - lowerweight) <= 1) |
| 475 | break; |
| 1354 | 476 | } |
| 477 | else |
| 478 | upperweight = curweight; |
| 1355 | 479 | } |
| 1356 | 480 | |
| 1357 | | /* assign canonical codes for all nodes based on their code lengths */ |
| 1358 | | error = assign_canonical_codes(context, numcodes); |
| 1359 | | if (error != HUFFERR_NONE) |
| 1360 | | return error; |
| 1361 | | |
| 1362 | | /* make sure we ended up with the right number */ |
| 1363 | | if (curnode != numcodes) |
| 1364 | | return HUFFERR_INVALID_DATA; |
| 1365 | | |
| 1366 | | *actlength = bit_buffer_read_offset(&bitbuf); |
| 1367 | | return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 481 | // assign canonical codes for all nodes based on their code lengths |
| 482 | return assign_canonical_codes(); |
| 1368 | 483 | } |
| 1369 | 484 | |
| 1370 | 485 | |
| 1371 | | /*------------------------------------------------- |
| 1372 | | export_tree - export a huffman tree to a |
| 1373 | | target data stream |
| 1374 | | -------------------------------------------------*/ |
| 1375 | 486 | |
| 1376 | | static huffman_error export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength, UINT32 numcodes) |
| 1377 | | { |
| 1378 | | bit_buffer bitbuf; |
| 1379 | | int repcount; |
| 1380 | | int lastval; |
| 1381 | | int numbits; |
| 1382 | | int i; |
| 487 | //************************************************************************** |
| 488 | // INTERNAL FUNCTIONS |
| 489 | //************************************************************************** |
| 1383 | 490 | |
| 1384 | | /* initialize the output buffer */ |
| 1385 | | bit_buffer_write_init(&bitbuf, dest, dlength); |
| 491 | //------------------------------------------------- |
| 492 | // write_rle_tree_bits - write an RLE encoded |
| 493 | // set of data to a target stream |
| 494 | //------------------------------------------------- |
| 1386 | 495 | |
| 1387 | | /* bits per entry depends on the maxbits */ |
| 1388 | | if (context->maxbits >= 16) |
| 1389 | | numbits = 5; |
| 1390 | | else if (context->maxbits >= 8) |
| 1391 | | numbits = 4; |
| 1392 | | else |
| 1393 | | numbits = 3; |
| 1394 | | |
| 1395 | | /* RLE encode the lengths */ |
| 1396 | | lastval = ~0; |
| 1397 | | repcount = 0; |
| 1398 | | for (i = 0; i < numcodes; i++) |
| 1399 | | { |
| 1400 | | int newval = context->huffnode[i].numbits; |
| 1401 | | |
| 1402 | | /* if we match the previous value, just bump the repcount */ |
| 1403 | | if (newval == lastval) |
| 1404 | | repcount++; |
| 1405 | | |
| 1406 | | /* otherwise, we need to flush the previous repeats */ |
| 1407 | | else |
| 1408 | | { |
| 1409 | | if (repcount != 0) |
| 1410 | | write_rle_tree_bits(&bitbuf, lastval, repcount, numbits); |
| 1411 | | lastval = newval; |
| 1412 | | repcount = 1; |
| 1413 | | } |
| 1414 | | } |
| 1415 | | |
| 1416 | | /* flush the last value */ |
| 1417 | | write_rle_tree_bits(&bitbuf, lastval, repcount, numbits); |
| 1418 | | *actlength = bit_buffer_flush(&bitbuf); |
| 1419 | | return bitbuf.overflow ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 1420 | | } |
| 1421 | | |
| 1422 | | |
| 1423 | | /*------------------------------------------------- |
| 1424 | | write_rle_tree_bits - write an RLE encoded |
| 1425 | | set of data to a target stream |
| 1426 | | -------------------------------------------------*/ |
| 1427 | | |
| 1428 | | static void write_rle_tree_bits(bit_buffer *bitbuf, int value, int repcount, int numbits) |
| 496 | void huffman_context_base::write_rle_tree_bits(bitstream_out &bitbuf, int value, int repcount, int numbits) |
| 1429 | 497 | { |
| 1430 | | /* loop until we have output all of the repeats */ |
| 498 | // loop until we have output all of the repeats |
| 1431 | 499 | while (repcount > 0) |
| 1432 | 500 | { |
| 1433 | | /* if we have a 1, write it twice as it is an escape code */ |
| 501 | // if we have a 1, write it twice as it is an escape code |
| 1434 | 502 | if (value == 1) |
| 1435 | 503 | { |
| 1436 | | bit_buffer_write(bitbuf, 1, numbits); |
| 1437 | | bit_buffer_write(bitbuf, 1, numbits); |
| 504 | bitbuf.write(1, numbits); |
| 505 | bitbuf.write(1, numbits); |
| 1438 | 506 | repcount--; |
| 1439 | 507 | } |
| 1440 | 508 | |
| 1441 | | /* if we have two or fewer in a row, write them raw */ |
| 509 | // if we have two or fewer in a row, write them raw |
| 1442 | 510 | else if (repcount <= 2) |
| 1443 | 511 | { |
| 1444 | | bit_buffer_write(bitbuf, value, numbits); |
| 512 | bitbuf.write(value, numbits); |
| 1445 | 513 | repcount--; |
| 1446 | 514 | } |
| 1447 | 515 | |
| 1448 | | /* otherwise, write a triple using 1 as the escape code */ |
| 516 | // otherwise, write a triple using 1 as the escape code |
| 1449 | 517 | else |
| 1450 | 518 | { |
| 1451 | 519 | int cur_reps = MIN(repcount - 3, (1 << numbits) - 1); |
| 1452 | | bit_buffer_write(bitbuf, 1, numbits); |
| 1453 | | bit_buffer_write(bitbuf, value, numbits); |
| 1454 | | bit_buffer_write(bitbuf, cur_reps, numbits); |
| 520 | bitbuf.write(1, numbits); |
| 521 | bitbuf.write(value, numbits); |
| 522 | bitbuf.write(cur_reps, numbits); |
| 1455 | 523 | repcount -= cur_reps + 3; |
| 1456 | 524 | } |
| 1457 | 525 | } |
| 1458 | 526 | } |
| 1459 | 527 | |
| 1460 | 528 | |
| 1461 | | /*------------------------------------------------- |
| 1462 | | tree_node_compare - compare two tree nodes |
| 1463 | | by weight |
| 1464 | | -------------------------------------------------*/ |
| 529 | //------------------------------------------------- |
| 530 | // tree_node_compare - compare two tree nodes |
| 531 | // by weight |
| 532 | //------------------------------------------------- |
| 1465 | 533 | |
| 1466 | | static int CLIB_DECL tree_node_compare(const void *item1, const void *item2) |
| 534 | int CLIB_DECL huffman_context_base::tree_node_compare(const void *item1, const void *item2) |
| 1467 | 535 | { |
| 1468 | | const huffman_node *node1 = *(const huffman_node **)item1; |
| 1469 | | const huffman_node *node2 = *(const huffman_node **)item2; |
| 1470 | | return node2->weight - node1->weight; |
| 536 | const node_t *node1 = *(const node_t **)item1; |
| 537 | const node_t *node2 = *(const node_t **)item2; |
| 538 | return node2->m_weight - node1->m_weight; |
| 1471 | 539 | } |
| 1472 | 540 | |
| 1473 | 541 | |
| 1474 | | /*------------------------------------------------- |
| 1475 | | compute_optimal_tree - common backend for |
| 1476 | | computing a tree based on the data histogram |
| 1477 | | -------------------------------------------------*/ |
| 542 | //------------------------------------------------- |
| 543 | // build_tree - build a huffman tree based on the |
| 544 | // data distribution |
| 545 | //------------------------------------------------- |
| 1478 | 546 | |
| 1479 | | static huffman_error compute_optimal_tree(huffman_context *context, const UINT32 *datahisto, UINT32 numcodes) |
| 547 | int huffman_context_base::build_tree(UINT32 totaldata, UINT32 totalweight) |
| 1480 | 548 | { |
| 1481 | | UINT32 lowerweight, upperweight; |
| 1482 | | UINT32 sdatacount; |
| 1483 | | int i; |
| 1484 | | |
| 1485 | | /* compute the number of data items in the histogram */ |
| 1486 | | sdatacount = 0; |
| 1487 | | for (i = 0; i < numcodes; i++) |
| 1488 | | sdatacount += datahisto[i]; |
| 1489 | | |
| 1490 | | /* binary search to achieve the optimum encoding */ |
| 1491 | | lowerweight = 0; |
| 1492 | | upperweight = sdatacount * 2; |
| 1493 | | while (TRUE) |
| 1494 | | { |
| 1495 | | UINT32 curweight = (upperweight + lowerweight) / 2; |
| 1496 | | int curmaxbits; |
| 1497 | | |
| 1498 | | /* build a tree using the current weight */ |
| 1499 | | curmaxbits = huffman_build_tree(context, datahisto, sdatacount, curweight, numcodes); |
| 1500 | | |
| 1501 | | /* apply binary search here */ |
| 1502 | | if (curmaxbits <= context->maxbits) |
| 549 | // make a list of all non-zero nodes |
| 550 | dynamic_array<node_t *> list(m_numcodes * 2); |
| 551 | int listitems = 0; |
| 552 | memset(m_huffnode, 0, m_numcodes * sizeof(m_huffnode[0])); |
| 553 | for (int curcode = 0; curcode < m_numcodes; curcode++) |
| 554 | if (m_datahisto[curcode] != 0) |
| 1503 | 555 | { |
| 1504 | | lowerweight = curweight; |
| 556 | list[listitems++] = &m_huffnode[curcode]; |
| 557 | m_huffnode[curcode].m_count = m_datahisto[curcode]; |
| 1505 | 558 | |
| 1506 | | /* early out if it worked with the raw weights, or if we're done searching */ |
| 1507 | | if (curweight == sdatacount || (upperweight - lowerweight) <= 1) |
| 1508 | | break; |
| 559 | // scale the weight by the current effective length, ensuring we don't go to 0 |
| 560 | m_huffnode[curcode].m_weight = UINT64(m_datahisto[curcode]) * UINT64(totalweight) / UINT64(totaldata); |
| 561 | if (m_huffnode[curcode].m_weight == 0) |
| 562 | m_huffnode[curcode].m_weight = 1; |
| 1509 | 563 | } |
| 1510 | | else |
| 1511 | | upperweight = curweight; |
| 1512 | | } |
| 1513 | 564 | |
| 1514 | | /* assign canonical codes for all nodes based on their code lengths */ |
| 1515 | | return assign_canonical_codes(context, numcodes); |
| 1516 | | } |
| 1517 | | |
| 1518 | | |
| 1519 | | /*------------------------------------------------- |
| 1520 | | huffman_build_tree - build a huffman tree |
| 1521 | | based on the data distribution |
| 1522 | | -------------------------------------------------*/ |
| 1523 | | |
| 1524 | | static int huffman_build_tree(huffman_context *context, const UINT32 *datahisto, UINT32 totaldata, UINT32 totalweight, UINT32 numcodes) |
| 1525 | | { |
| 1526 | | huffman_node *list[MAX_HUFFMAN_CODES]; |
| 1527 | | int listitems; |
| 1528 | | int nextalloc; |
| 1529 | | int maxbits; |
| 1530 | | int i; |
| 1531 | | |
| 1532 | | /* make a list of all non-zero nodes */ |
| 1533 | | listitems = 0; |
| 1534 | | memset(context->huffnode, 0, numcodes * sizeof(context->huffnode[0])); |
| 1535 | | for (i = 0; i < numcodes; i++) |
| 1536 | | if (datahisto[i] != 0) |
| 1537 | | { |
| 1538 | | list[listitems++] = &context->huffnode[i]; |
| 1539 | | context->huffnode[i].count = datahisto[i]; |
| 1540 | | |
| 1541 | | /* scale the weight by the current effective length, ensuring we don't go to 0 */ |
| 1542 | | context->huffnode[i].weight = (UINT64)datahisto[i] * (UINT64)totalweight / (UINT64)totaldata; |
| 1543 | | if (context->huffnode[i].weight == 0) |
| 1544 | | context->huffnode[i].weight = 1; |
| 1545 | | } |
| 1546 | | |
| 1547 | | /* sort the list by weight, largest weight first */ |
| 565 | // sort the list by weight, largest weight first |
| 1548 | 566 | qsort(list, listitems, sizeof(list[0]), tree_node_compare); |
| 1549 | 567 | |
| 1550 | | /* now build the tree */ |
| 1551 | | nextalloc = MAX_HUFFMAN_CODES; |
| 568 | // now build the tree |
| 569 | int nextalloc = m_numcodes; |
| 1552 | 570 | while (listitems > 1) |
| 1553 | 571 | { |
| 1554 | | huffman_node *node0, *node1, *newnode; |
| 572 | // remove lowest two items |
| 573 | node_t &node1 = *list[--listitems]; |
| 574 | node_t &node0 = *list[--listitems]; |
| 1555 | 575 | |
| 1556 | | /* remove lowest two items */ |
| 1557 | | node1 = list[--listitems]; |
| 1558 | | node0 = list[--listitems]; |
| 576 | // create new node |
| 577 | node_t &newnode = m_huffnode[nextalloc++]; |
| 578 | newnode.m_parent = NULL; |
| 579 | node0.m_parent = node1.m_parent = &newnode; |
| 580 | newnode.m_weight = node0.m_weight + node1.m_weight; |
| 1559 | 581 | |
| 1560 | | /* create new node */ |
| 1561 | | newnode = &context->huffnode[nextalloc++]; |
| 1562 | | newnode->parent = NULL; |
| 1563 | | node0->parent = node1->parent = newnode; |
| 1564 | | newnode->weight = node0->weight + node1->weight; |
| 1565 | | |
| 1566 | | /* insert into list at appropriate location */ |
| 1567 | | for (i = 0; i < listitems; i++) |
| 1568 | | if (newnode->weight > list[i]->weight) |
| 582 | // insert into list at appropriate location |
| 583 | int curitem; |
| 584 | for (curitem = 0; curitem < listitems; curitem++) |
| 585 | if (newnode.m_weight > list[curitem]->m_weight) |
| 1569 | 586 | { |
| 1570 | | memmove(&list[i+1], &list[i], (listitems - i) * sizeof(list[0])); |
| 587 | memmove(&list[curitem+1], &list[curitem], (listitems - curitem) * sizeof(list[0])); |
| 1571 | 588 | break; |
| 1572 | 589 | } |
| 1573 | | list[i] = newnode; |
| 590 | list[curitem] = &newnode; |
| 1574 | 591 | listitems++; |
| 1575 | 592 | } |
| 1576 | 593 | |
| 1577 | | /* compute the number of bits in each code, and fill in another histogram */ |
| 1578 | | maxbits = 0; |
| 1579 | | for (i = 0; i < numcodes; i++) |
| 594 | // compute the number of bits in each code, and fill in another histogram |
| 595 | int maxbits = 0; |
| 596 | for (int curcode = 0; curcode < m_numcodes; curcode++) |
| 1580 | 597 | { |
| 1581 | | huffman_node *node = &context->huffnode[i]; |
| 1582 | | node->numbits = 0; |
| 598 | node_t &node = m_huffnode[curcode]; |
| 599 | node.m_numbits = 0; |
| 1583 | 600 | |
| 1584 | | /* if we have a non-zero weight, compute the number of bits */ |
| 1585 | | if (node->weight > 0) |
| 601 | // if we have a non-zero weight, compute the number of bits |
| 602 | if (node.m_weight > 0) |
| 1586 | 603 | { |
| 1587 | | huffman_node *curnode; |
| 604 | // determine the number of bits for this node |
| 605 | for (node_t *curnode = &node; curnode->m_parent != NULL; curnode = curnode->m_parent) |
| 606 | node.m_numbits++; |
| 607 | if (node.m_numbits == 0) |
| 608 | node.m_numbits = 1; |
| 1588 | 609 | |
| 1589 | | /* determine the number of bits for this node */ |
| 1590 | | for (curnode = node; curnode->parent != NULL; curnode = curnode->parent) |
| 1591 | | node->numbits++; |
| 1592 | | if (node->numbits == 0) |
| 1593 | | node->numbits = 1; |
| 1594 | | |
| 1595 | | /* keep track of the max */ |
| 1596 | | maxbits = MAX(maxbits, node->numbits); |
| 610 | // keep track of the max |
| 611 | maxbits = MAX(maxbits, node.m_numbits); |
| 1597 | 612 | } |
| 1598 | 613 | } |
| 1599 | | |
| 1600 | 614 | return maxbits; |
| 1601 | 615 | } |
| 1602 | 616 | |
| 1603 | 617 | |
| 1604 | | /*------------------------------------------------- |
| 1605 | | assign_canonical_codes - assign |
| 1606 | | canonical codes to all the nodes based on the |
| 1607 | | number of bits in each |
| 1608 | | -------------------------------------------------*/ |
| 618 | //------------------------------------------------- |
| 619 | // assign_canonical_codes - assign canonical codes |
| 620 | // to all the nodes based on the number of bits |
| 621 | // in each |
| 622 | //------------------------------------------------- |
| 1609 | 623 | |
| 1610 | | static huffman_error assign_canonical_codes(huffman_context *context, UINT32 numcodes) |
| 624 | huffman_error huffman_context_base::assign_canonical_codes() |
| 1611 | 625 | { |
| 1612 | | UINT32 bithisto[33]; |
| 1613 | | int curstart; |
| 1614 | | int i; |
| 1615 | | |
| 1616 | | /* build up a histogram of bit lengths */ |
| 1617 | | memset(bithisto, 0, sizeof(bithisto)); |
| 1618 | | for (i = 0; i < numcodes; i++) |
| 626 | // build up a histogram of bit lengths |
| 627 | UINT32 bithisto[33] = { 0 }; |
| 628 | for (int curcode = 0; curcode < m_numcodes; curcode++) |
| 1619 | 629 | { |
| 1620 | | huffman_node *node = &context->huffnode[i]; |
| 1621 | | if (node->numbits > context->maxbits) |
| 630 | node_t &node = m_huffnode[curcode]; |
| 631 | if (node.m_numbits > m_maxbits) |
| 1622 | 632 | return HUFFERR_INTERNAL_INCONSISTENCY; |
| 1623 | | if (node->numbits <= 32) |
| 1624 | | bithisto[node->numbits]++; |
| 633 | if (node.m_numbits <= 32) |
| 634 | bithisto[node.m_numbits]++; |
| 1625 | 635 | } |
| 1626 | 636 | |
| 1627 | | /* for each code length, determine the starting code number */ |
| 1628 | | curstart = 0; |
| 1629 | | for (i = 32; i > 0; i--) |
| 637 | // for each code length, determine the starting code number |
| 638 | UINT32 curstart = 0; |
| 639 | for (int codelen = 32; codelen > 0; codelen--) |
| 1630 | 640 | { |
| 1631 | | UINT32 nextstart = (curstart + bithisto[i]) >> 1; |
| 1632 | | if (i != 1 && nextstart * 2 != (curstart + bithisto[i])) |
| 641 | UINT32 nextstart = (curstart + bithisto[codelen]) >> 1; |
| 642 | if (codelen != 1 && nextstart * 2 != (curstart + bithisto[codelen])) |
| 1633 | 643 | return HUFFERR_INTERNAL_INCONSISTENCY; |
| 1634 | | bithisto[i] = curstart; |
| 644 | bithisto[codelen] = curstart; |
| 1635 | 645 | curstart = nextstart; |
| 1636 | 646 | } |
| 1637 | 647 | |
| 1638 | | /* now assign canonical codes */ |
| 1639 | | for (i = 0; i < numcodes; i++) |
| 648 | // now assign canonical codes |
| 649 | for (int curcode = 0; curcode < m_numcodes; curcode++) |
| 1640 | 650 | { |
| 1641 | | huffman_node *node = &context->huffnode[i]; |
| 1642 | | if (node->numbits > 0) |
| 1643 | | node->bits = bithisto[node->numbits]++; |
| 651 | node_t &node = m_huffnode[curcode]; |
| 652 | if (node.m_numbits > 0) |
| 653 | node.m_bits = bithisto[node.m_numbits]++; |
| 1644 | 654 | } |
| 1645 | | |
| 1646 | | /* if there was a decoding table, get rid of it now */ |
| 1647 | | context->lookupdirty = TRUE; |
| 1648 | 655 | return HUFFERR_NONE; |
| 1649 | 656 | } |
| 1650 | 657 | |
| 1651 | 658 | |
| 1652 | | /*------------------------------------------------- |
| 1653 | | build_lookup_table - build a lookup |
| 1654 | | table for fast decoding |
| 1655 | | -------------------------------------------------*/ |
| 659 | //------------------------------------------------- |
| 660 | // build_lookup_table - build a lookup table for |
| 661 | // fast decoding |
| 662 | //------------------------------------------------- |
| 1656 | 663 | |
| 1657 | | static huffman_error build_lookup_table(huffman_context *context, UINT32 numcodes) |
| 664 | void huffman_context_base::build_lookup_table() |
| 1658 | 665 | { |
| 1659 | | int i; |
| 1660 | | |
| 1661 | | /* allocate a table if needed */ |
| 1662 | | if (context->lookup == NULL) |
| 1663 | | context->lookup = (huffman_lookup_value *)malloc((UINT32)sizeof(context->lookup[0]) * (UINT32)(1 << context->maxbits)); |
| 1664 | | if (context->lookup == NULL) |
| 1665 | | return HUFFERR_OUT_OF_MEMORY; |
| 1666 | | |
| 1667 | | /* now build */ |
| 1668 | | for (i = 0; i < numcodes; i++) |
| 666 | // iterate over all codes |
| 667 | for (int curcode = 0; curcode < m_numcodes; curcode++) |
| 1669 | 668 | { |
| 1670 | | huffman_node *node = &context->huffnode[i]; |
| 1671 | | if (node->numbits > 0) |
| 669 | // process all nodes which have non-zero bits |
| 670 | node_t &node = m_huffnode[curcode]; |
| 671 | if (node.m_numbits > 0) |
| 1672 | 672 | { |
| 1673 | | huffman_lookup_value *dest, *destend; |
| 673 | // set up the entry |
| 674 | lookup_value value = MAKE_LOOKUP(curcode, node.m_numbits); |
| 1674 | 675 | |
| 1675 | | /* left justify this node's bit values to max bits */ |
| 1676 | | int shift = context->maxbits - node->numbits; |
| 1677 | | UINT32 start = node->bits << shift; |
| 1678 | | UINT32 end = ((node->bits + 1) << shift) - 1; |
| 1679 | | huffman_lookup_value value; |
| 1680 | | |
| 1681 | | /* set up the entry */ |
| 1682 | | value = (i << 6) | node->numbits; |
| 1683 | | |
| 1684 | | /* fill all matching entries */ |
| 1685 | | dest = &context->lookup[start]; |
| 1686 | | destend = &context->lookup[end]; |
| 676 | // fill all matching entries |
| 677 | int shift = m_maxbits - node.m_numbits; |
| 678 | lookup_value *dest = &m_lookup[node.m_bits << shift]; |
| 679 | lookup_value *destend = &m_lookup[((node.m_bits + 1) << shift) - 1]; |
| 1687 | 680 | while (dest <= destend) |
| 1688 | 681 | *dest++ = value; |
| 1689 | 682 | } |
| 1690 | 683 | } |
| 684 | } |
| 1691 | 685 | |
| 1692 | | /* no longer dirty */ |
| 1693 | | context->lookupdirty = FALSE; |
| 1694 | | return HUFFERR_NONE; |
| 686 | |
| 687 | |
| 688 | //************************************************************************** |
| 689 | // 8-BIT ENCODER |
| 690 | //************************************************************************** |
| 691 | |
| 692 | //------------------------------------------------- |
| 693 | // huffman_8bit_encoder - constructor |
| 694 | //------------------------------------------------- |
| 695 | |
| 696 | huffman_8bit_encoder::huffman_8bit_encoder() |
| 697 | { |
| 1695 | 698 | } |
| 699 | |
| 700 | |
| 701 | //------------------------------------------------- |
| 702 | // encode - encode a full buffer |
| 703 | //------------------------------------------------- |
| 704 | |
| 705 | huffman_error huffman_8bit_encoder::encode(const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 &complength) |
| 706 | { |
| 707 | // first compute the histogram |
| 708 | histo_reset(); |
| 709 | for (UINT32 cur = 0; cur < slength; cur++) |
| 710 | histo_one(source[cur]); |
| 711 | |
| 712 | // then compute the tree |
| 713 | huffman_error err = compute_tree_from_histo(); |
| 714 | if (err != HUFFERR_NONE) |
| 715 | return err; |
| 716 | |
| 717 | // export the tree |
| 718 | bitstream_out bitbuf(dest, dlength); |
| 719 | err = export_tree_huffman(bitbuf); |
| 720 | if (err != HUFFERR_NONE) |
| 721 | return err; |
| 722 | |
| 723 | // then encode the data |
| 724 | for (UINT32 cur = 0; cur < slength; cur++) |
| 725 | encode_one(bitbuf, source[cur]); |
| 726 | complength = bitbuf.flush(); |
| 727 | return bitbuf.overflow() ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 728 | } |
| 729 | |
| 730 | |
| 731 | |
| 732 | //************************************************************************** |
| 733 | // 8-BIT DECODER |
| 734 | //************************************************************************** |
| 735 | |
| 736 | //------------------------------------------------- |
| 737 | // huffman_8bit_decoder - constructor |
| 738 | //------------------------------------------------- |
| 739 | |
| 740 | huffman_8bit_decoder::huffman_8bit_decoder() |
| 741 | { |
| 742 | } |
| 743 | |
| 744 | |
| 745 | //------------------------------------------------- |
| 746 | // decode - decode a full buffer |
| 747 | //------------------------------------------------- |
| 748 | |
| 749 | huffman_error huffman_8bit_decoder::decode(const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength) |
| 750 | { |
| 751 | // first import the tree |
| 752 | bitstream_in bitbuf(source, slength); |
| 753 | huffman_error err = import_tree_huffman(bitbuf); |
| 754 | if (err != HUFFERR_NONE) |
| 755 | return err; |
| 756 | |
| 757 | // then decode the data |
| 758 | for (UINT32 cur = 0; cur < dlength; cur++) |
| 759 | dest[cur] = decode_one(bitbuf); |
| 760 | bitbuf.flush(); |
| 761 | return bitbuf.overflow() ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; |
| 762 | } |