diff options
Diffstat (limited to 'src/lib/util')
42 files changed, 19086 insertions, 0 deletions
diff --git a/src/lib/util/astring.c b/src/lib/util/astring.c new file mode 100644 index 00000000000..b2ae0c6c61d --- /dev/null +++ b/src/lib/util/astring.c @@ -0,0 +1,596 @@ +/*************************************************************************** + + astring.c + + Allocated string manipulation functions. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +****************************************************************************/ + +#include "astring.h" +#include "osdcore.h" + +#include <string.h> +#include <stdarg.h> +#include <stdlib.h> +#include <ctype.h> + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +struct _astring +{ + char * text; + int alloclen; + char smallbuf[256 - sizeof(int) - sizeof(char *)]; +}; + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +static char dummy_text[2] = { 0 }; +static astring dummy_astring = { dummy_text, 1, { 0 } }; + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + ensure_room - ensure we have room for a + given string, or else reallocate our buffer +-------------------------------------------------*/ + +INLINE int ensure_room(astring *str, int length) +{ + char *newbuf, *oldbuf; + int alloclen; + + /* always fail to expand the dummy */ + if (str == &dummy_astring) + return FALSE; + + /* if we have the room, do nothing */ + if (str->alloclen >= length + 1) + return TRUE; + + /* allocate a new buffer with some slop */ + alloclen = length + 256; + newbuf = malloc(alloclen); + if (newbuf == NULL) + return FALSE; + + /* swap in the new buffer and free the old one */ + oldbuf = (str->text == str->smallbuf) ? NULL : str->text; + str->text = strcpy(newbuf, str->text); + str->alloclen = alloclen; + if (oldbuf != NULL) + free(oldbuf); + + return TRUE; +} + + +/*------------------------------------------------- + safe_string_base - return a "safe" string + base for a given start index +-------------------------------------------------*/ + +INLINE char *safe_string_base(char *base, int start) +{ + int max = strlen(base); + return (start >= 0 && start < max) ? base + start : base + max; +} + + +/*------------------------------------------------- + normalize_substr - normalize substr parameters +-------------------------------------------------*/ + +INLINE void normalize_substr(int *start, int *count, int length) +{ + /* limit start */ + if (*start < 0) + *start = 0; + else if (*start > length) + *start = length; + + /* update count */ + if (*count == -1 || *start + *count > length) + *count = length - *start; +} + + + +/*************************************************************************** + ASTRING ALLOCATION +***************************************************************************/ + +/*------------------------------------------------- + astring_alloc - allocate a new astring +-------------------------------------------------*/ + +astring *astring_alloc(void) +{ + astring *str; + + /* allocate memory; if we fail, return the dummy */ + str = malloc(sizeof(*str)); + if (str == NULL) + return &dummy_astring; + memset(str, 0, sizeof(*str)); + + /* initialize the small buffer */ + str->text = str->smallbuf; + str->alloclen = ARRAY_LENGTH(str->smallbuf); + return str; +} + + +/*------------------------------------------------- + astring_free - free an astring +-------------------------------------------------*/ + +void astring_free(astring *str) +{ + /* ignore attempts to free the dummy */ + if (str == &dummy_astring) + return; + + /* if we allocated additional memory, free that */ + if (str->text != str->smallbuf) + free(str->text); + free(str); +} + + + +/*************************************************************************** + INLINE ASTRING CHANGES +***************************************************************************/ + +/*------------------------------------------------- + astring_cpy - copy one astring into another +-------------------------------------------------*/ + +astring *astring_cpy(astring *dst, const astring *src) +{ + return astring_cpyc(dst, src->text); +} + + +/*------------------------------------------------- + astring_cpyc - copy a C string into an astring +-------------------------------------------------*/ + +astring *astring_cpyc(astring *dst, const char *src) +{ + return astring_cpych(dst, src, strlen(src)); +} + + +/*------------------------------------------------- + astring_cpych - copy a character array into + an astring +-------------------------------------------------*/ + +astring *astring_cpych(astring *dst, const char *src, int count) +{ + /* make room; if we fail or if dst is the dummy, do nothing */ + if (!ensure_room(dst, count)) + return dst; + + /* copy the raw data and NULL-terminate */ + if (count > 0) + memcpy(dst->text, src, count); + dst->text[count] = 0; + return dst; +} + + +/*------------------------------------------------- + astring_cpysubstr - copy a substring of one + string to another +-------------------------------------------------*/ + +astring *astring_cpysubstr(astring *dst, const astring *src, int start, int count) +{ + normalize_substr(&start, &count, strlen(src->text)); + return astring_cpych(dst, src->text + start, count); +} + + +/*------------------------------------------------- + astring_ins - insert one astring into another +-------------------------------------------------*/ + +astring *astring_ins(astring *dst, int insbefore, const astring *src) +{ + return astring_insc(dst, insbefore, src->text); +} + + +/*------------------------------------------------- + astring_insc - insert a C string into an + astring +-------------------------------------------------*/ + +astring *astring_insc(astring *dst, int insbefore, const char *src) +{ + return astring_insch(dst, insbefore, src, strlen(src)); +} + + +/*------------------------------------------------- + astring_insch - insert a character array + into an astring +-------------------------------------------------*/ + +astring *astring_insch(astring *dst, int insbefore, const char *src, int count) +{ + int dstlength = strlen(dst->text); + + /* make room; if we fail or if dst is the dummy, do nothing */ + if (!ensure_room(dst, dstlength + count)) + return dst; + + /* adjust insbefore to be logical */ + if (insbefore < 0 || insbefore > dstlength) + insbefore = dstlength; + + /* copy the data an NULL-terminate */ + if (insbefore < dstlength) + memmove(dst->text + insbefore + count, dst->text + insbefore, dstlength - insbefore); + memcpy(dst->text + insbefore, src, count); + dst->text[dstlength + count] = 0; + return dst; +} + + +/*------------------------------------------------- + astring_inssubstr - insert a substring of + one string to another +-------------------------------------------------*/ + +astring *astring_inssubstr(astring *dst, int insbefore, const astring *src, int start, int count) +{ + normalize_substr(&start, &count, strlen(src->text)); + return astring_insch(dst, insbefore, src->text + start, count); +} + + +/*------------------------------------------------- + astring_substr - extract a substring of + ourself, removing everything else +-------------------------------------------------*/ + +astring *astring_substr(astring *str, int start, int count) +{ + /* ignore attempts to do this on the dummy */ + if (str == &dummy_astring) + return str; + + /* normalize parameters */ + normalize_substr(&start, &count, strlen(str->text)); + + /* move the data and NULL-terminate */ + if (count > 0 && start > 0) + memmove(str->text, str->text + start, count); + str->text[count] = 0; + return str; +} + + +/*------------------------------------------------- + astring_printf - printf text into an astring +-------------------------------------------------*/ + +int astring_printf(astring *dst, const char *format, ...) +{ + char tempbuf[4096]; + va_list args; + int result; + + /* sprintf into the temporary buffer */ + va_start(args, format); + result = vsprintf(tempbuf, format, args); + va_end(args); + + /* set the result */ + astring_cpyc(dst, tempbuf); + return result; +} + + + +/*************************************************************************** + ASTRING QUERIES +***************************************************************************/ + +/*------------------------------------------------- + astring_c - return a pointer to a C string + from an astring +-------------------------------------------------*/ + +const char *astring_c(const astring *str) +{ + return str->text; +} + + +/*------------------------------------------------- + astring_len - return the length of an astring +-------------------------------------------------*/ + +int astring_len(const astring *str) +{ + return strlen(str->text); +} + + +/*------------------------------------------------- + astring_cmp - compare one astring to another +-------------------------------------------------*/ + +int astring_cmp(const astring *str1, const astring *str2) +{ + return astring_cmpc(str1, str2->text); +} + + +/*------------------------------------------------- + astring_cmpc - compare a C string to an astring +-------------------------------------------------*/ + +int astring_cmpc(const astring *str1, const char *str2) +{ + const char *s1 = str1->text; + + /* loop while equal until we hit the end of strings */ + while (*s1 != 0 && *str2 != 0 && *s1 == *str2) + s1++, str2++; + return *s1 - *str2; +} + + +/*------------------------------------------------- + astring_cmpch - compare a character array to + an astring +-------------------------------------------------*/ + +int astring_cmpch(const astring *str1, const char *str2, int count) +{ + const char *s1 = str1->text; + int result; + + /* loop while equal until we hit the end of strings */ + while (count-- > 0 && *s1 != 0 && *str2 != 0 && *s1 == *str2) + s1++, str2++; + result = (count == -1) ? 0 : *s1 - *str2; + if (result == 0 && *s1 != 0) + result = 1; + return result; +} + + +/*------------------------------------------------- + astring_cmpsubstr - compare a substring to + an astring +-------------------------------------------------*/ + +int astring_cmpsubstr(const astring *str1, const astring *str2, int start, int count) +{ + normalize_substr(&start, &count, strlen(str2->text)); + return astring_cmpch(str1, str2->text + start, count); +} + + +/*------------------------------------------------- + astring_icmp - case-insenstive compare one + astring to another +-------------------------------------------------*/ + +int astring_icmp(const astring *str1, const astring *str2) +{ + return astring_icmpc(str1, str2->text); +} + + +/*------------------------------------------------- + astring_icmpc - case-insenstive compare a C + string to an astring +-------------------------------------------------*/ + +int astring_icmpc(const astring *str1, const char *str2) +{ + const char *s1 = str1->text; + + /* loop while equal until we hit the end of strings */ + while (*s1 != 0 && *str2 != 0 && tolower(*s1) == tolower(*str2)) + s1++, str2++; + return tolower(*s1) - tolower(*str2); +} + + +/*------------------------------------------------- + astring_icmpch - case-insenstive compare a + character array to an astring +-------------------------------------------------*/ + +int astring_icmpch(const astring *str1, const char *str2, int count) +{ + const char *s1 = str1->text; + int result; + + /* loop while equal until we hit the end of strings */ + while (count-- > 0 && *s1 != 0 && *str2 != 0 && tolower(*s1) == tolower(*str2)) + s1++, str2++; + result = (count == -1) ? 0 : tolower(*s1) - tolower(*str2); + if (result == 0 && *s1 != 0) + result = 1; + return result; +} + + +/*------------------------------------------------- + astring_icmpsubstr - case-insenstive compare a + substring to an astring +-------------------------------------------------*/ + +int astring_icmpsubstr(const astring *str1, const astring *str2, int start, int count) +{ + normalize_substr(&start, &count, strlen(str2->text)); + return astring_icmpch(str1, str2->text + start, count); +} + + +/*------------------------------------------------- + astring_chr - return the index of a character + in an astring +-------------------------------------------------*/ + +int astring_chr(const astring *str, int start, int ch) +{ + char *result = strchr(safe_string_base(str->text, start), ch); + return (result != NULL) ? (result - str->text) : -1; +} + + +/*------------------------------------------------- + astring_rchr - return the index of a character + in an astring, searching from the end +-------------------------------------------------*/ + +int astring_rchr(const astring *str, int start, int ch) +{ + char *result = strrchr(safe_string_base(str->text, start), ch); + return (result != NULL) ? (result - str->text) : -1; +} + + +/*------------------------------------------------- + astring_find - find one astring in another +-------------------------------------------------*/ + +int astring_find(const astring *str, int start, const astring *search) +{ + return astring_findc(str, start, search->text); +} + + +/*------------------------------------------------- + astring_findc - find a C string in an astring +-------------------------------------------------*/ + +int astring_findc(const astring *str, int start, const char *search) +{ + char *result = strstr(safe_string_base(str->text, start), search); + return (result != NULL) ? (result - str->text) : -1; +} + + + +/*************************************************************************** + ASTRING UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + astring_delchr - delete all instances of + 'ch' +-------------------------------------------------*/ + +astring *astring_delchr(astring *str, int ch) +{ + char *src, *dst; + + /* simple deletion */ + for (src = dst = str->text; *src != 0; src++) + if (*src != ch) + *dst++ = *src; + *dst = 0; + + return str; +} + + +/*------------------------------------------------- + astring_replacechr - replace all instances of + 'ch' with 'newch' +-------------------------------------------------*/ + +astring *astring_replacechr(astring *str, int ch, int newch) +{ + char *text; + + /* simple replacement */ + for (text = str->text; *text != 0; text++) + if (*text == ch) + *text = newch; + + return str; +} + + +/*------------------------------------------------- + astring_toupper - convert string to all + upper-case +-------------------------------------------------*/ + +astring *astring_toupper(astring *str) +{ + char *text; + + /* just toupper() on all characters */ + for (text = str->text; *text != 0; text++) + *text = toupper(*text); + + return str; +} + + +/*------------------------------------------------- + astring_tolower - convert string to all + lower-case +-------------------------------------------------*/ + +astring *astring_tolower(astring *str) +{ + char *text; + + /* just toupper() on all characters */ + for (text = str->text; *text != 0; text++) + *text = tolower(*text); + + return str; +} + + +/*------------------------------------------------- + astring_trimspace - remove all space + characters from beginning/end +-------------------------------------------------*/ + +astring *astring_trimspace(astring *str) +{ + char *ptr; + + /* first remove stuff from the end */ + for (ptr = str->text + strlen(str->text) - 1; ptr >= str->text && isspace(*ptr); ptr--) + *ptr = 0; + + /* then count how much to remove from the beginning */ + for (ptr = str->text; *ptr != 0 && isspace(*ptr); ptr++) ; + if (ptr > str->text) + astring_substr(str, ptr - str->text, -1); + + return str; +} diff --git a/src/lib/util/astring.h b/src/lib/util/astring.h new file mode 100644 index 00000000000..3d80216383c --- /dev/null +++ b/src/lib/util/astring.h @@ -0,0 +1,228 @@ +/*************************************************************************** + + astring.h + + Allocated string manipulation functions. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __ASTRING_H__ +#define __ASTRING_H__ + +#include "pool.h" + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _astring astring; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + + +/* ----- astring allocation ----- */ + +/* allocate a new astring */ +astring *astring_alloc(void); + +/* free an astring */ +void astring_free(astring *str); + + + +/* ----- inline astring changes ----- */ + +/* copy one astring into another */ +astring *astring_cpy(astring *dst, const astring *src); + +/* copy a C string into an astring */ +astring *astring_cpyc(astring *dst, const char *src); + +/* copy a character array into an astring */ +astring *astring_cpych(astring *dst, const char *src, int count); + +/* copy a substring of one string into another */ +astring *astring_cpysubstr(astring *dst, const astring *src, int start, int count); + +/* insert one astring into another */ +astring *astring_ins(astring *dst, int insbefore, const astring *src); + +/* insert a C string into an astring */ +astring *astring_insc(astring *dst, int insbefore, const char *src); + +/* insert a character array into an astring */ +astring *astring_insch(astring *dst, int insbefore, const char *src, int count); + +/* insert a substring of one string into another */ +astring *astring_inssubstr(astring *dst, int insbefore, const astring *src, int start, int count); + +/* extract a substring of ourself, removing everything else */ +astring *astring_substr(astring *str, int start, int count); + +/* formatted printf to an astring */ +int astring_printf(astring *dst, const char *format, ...); + + + +/* ----- astring queries ----- */ + +/* return a pointer to a C string from an astring */ +const char *astring_c(const astring *str); + +/* return the length of an astring */ +int astring_len(const astring *str); + +/* compare two astrings */ +int astring_cmp(const astring *str1, const astring *str2); + +/* compare an astring to a C string */ +int astring_cmpc(const astring *str1, const char *str2); + +/* compare an astring to a character buffer */ +int astring_cmpch(const astring *str1, const char *str2, int count); + +/* compare an astring to a character buffer */ +int astring_cmpsubstr(const astring *str1, const astring *str2, int start, int count); + +/* case-insenstive compare two astrings */ +int astring_icmp(const astring *str1, const astring *str2); + +/* case-insenstive compare an astring to a C string */ +int astring_icmpc(const astring *str1, const char *str2); + +/* case-insenstive compare an astring to a character buffer */ +int astring_icmpch(const astring *str1, const char *str2, int count); + +/* case-insenstive compare an astring to a character buffer */ +int astring_icmpsubstr(const astring *str1, const astring *str2, int start, int count); + +/* search an astring for a character, returning offset or -1 if not found */ +int astring_chr(const astring *str, int start, int ch); + +/* reverse search an astring for a character, returning offset or -1 if not found */ +int astring_rchr(const astring *str, int start, int ch); + +/* search in an astring for another astring, returning offset or -1 if not found */ +int astring_find(const astring *str, int start, const astring *search); + +/* search in an astring for a C string, returning offset or -1 if not found */ +int astring_findc(const astring *str, int start, const char *search); + + + +/* ----- astring utilties ----- */ + +/* delete all instances of 'ch' */ +astring *astring_delchr(astring *str, int ch); + +/* replace all instances of 'ch' with 'newch' */ +astring *astring_replacechr(astring *str, int ch, int newch); + +/* convert string to all upper-case */ +astring *astring_toupper(astring *str); + +/* convert string to all lower-case */ +astring *astring_tolower(astring *str); + +/* remove all space characters from beginning/end */ +astring *astring_trimspace(astring *str); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/* allocate a new duplicate of an astring */ +INLINE astring *astring_dup(const astring *str) +{ + return astring_cpy(astring_alloc(), str); +} + +/* allocate a new duplicate of an astring */ +INLINE astring *astring_dupc(const char *str) +{ + return astring_cpyc(astring_alloc(), str); +} + +/* allocate a new duplicate of an astring */ +INLINE astring *astring_dupch(const char *str, int count) +{ + return astring_cpych(astring_alloc(), str, count); +} + +/* allocate a duplicate of a substring */ +INLINE astring *astring_dupsubstr(const astring *str, int start, int count) +{ + return astring_cpysubstr(astring_alloc(), str, start, count); +} + + +/* reset an astring to an empty string */ +INLINE astring *astring_reset(astring *dst) +{ + return astring_cpyc(dst, ""); +} + + +/* concatenate one astring to the end of another */ +INLINE astring *astring_cat(astring *dst, const astring *src) +{ + return astring_ins(dst, -1, src); +} + +/* concatenate a C string to the end of an astring */ +INLINE astring *astring_catc(astring *dst, const char *src) +{ + return astring_insc(dst, -1, src); +} + +/* concatenate a character array to the end of an astring */ +INLINE astring *astring_catch(astring *dst, const char *src, int count) +{ + return astring_insch(dst, -1, src, count); +} + +/* concatenate a substring of one string into another */ +INLINE astring *astring_catsubstr(astring *dst, const astring *src, int start, int count) +{ + return astring_inssubstr(dst, -1, src, start, count); +} + + +/* assemble an astring from 2 C strings */ +INLINE astring *astring_assemble_2(astring *dst, const char *src1, const char *src2) +{ + return astring_catc(astring_cpyc(dst, src1), src2); +} + +/* assemble an astring from 3 C strings */ +INLINE astring *astring_assemble_3(astring *dst, const char *src1, const char *src2, const char *src3) +{ + return astring_catc(astring_assemble_2(dst, src1, src2), src3); +} + +/* assemble an astring from 4 C strings */ +INLINE astring *astring_assemble_4(astring *dst, const char *src1, const char *src2, const char *src3, const char *src4) +{ + return astring_catc(astring_assemble_3(dst, src1, src2, src3), src4); +} + +/* assemble an astring from 5 C strings */ +INLINE astring *astring_assemble_5(astring *dst, const char *src1, const char *src2, const char *src3, const char *src4, const char *src5) +{ + return astring_catc(astring_assemble_4(dst, src1, src2, src3, src4), src5); +} + + +#endif /* __ASTRING_H__ */ diff --git a/src/lib/util/avcomp.c b/src/lib/util/avcomp.c new file mode 100644 index 00000000000..d3144e01175 --- /dev/null +++ b/src/lib/util/avcomp.c @@ -0,0 +1,840 @@ +/* +To do: +* verify Cb,Y,Cr,Y ordering +* swap the ordering(?) +* add backchannel support for samples as well? +*/ +/*************************************************************************** + + avcomp.c + + Audio/video compression and decompression helpers. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +**************************************************************************** + + Each frame is compressed as a unit. The raw data is of the form: + (all multibyte values are stored in big-endian format) + + 'chav' (4 bytes) - fixed header data to identify the format + metasize (1 byte) - size of metadata in bytes (max=255 bytes) + channels (1 byte) - number of audio channels + samples (2 bytes) - number of samples per audio stream + width (2 bytes) - width of video data + height (2 bytes) - height of video data (high bit set means interlaced) + <metadata> - as raw bytes + <audio stream 0> - as signed 16-bit samples + <audio stream 1> - as signed 16-bit samples + ... + <video data> - as a raw array of 8-bit YUY data in (Cb,Y,Cr,Y) order + + When compressed, the data is stored as follows: + (all multibyte values are stored in big-endian format) + + metasize (1 byte) - size of metadata in bytes + channels (1 byte) - number of audio channels + samples (2 bytes) - number of samples per audio stream + width (2 bytes) - width of video data + height (2 bytes) - height of video data (high bit set means interlaced) + audhuffsize (1 byte) - size of the audio Huffman table + str0size (2 bytes) - compressed size of stream 0 + str1size (2 bytes) - compressed size of stream 1 + ... + <metadata> - as raw data + <audio huffman table> - Huffman table for audio decoding + <audio stream 0 data> - Huffman-compressed deltas + <audio stream 1 data> - Huffman-compressed deltas + <...> + <video huffman table> - Huffman table for video decoding + <video data> - compressed data + +**************************************************************************** + + Attempted techniques that have not been worthwhile: + + * Attempted to use integer DCTs from the IJG code; even the "slow" + variants produce a lot of error and thus kill our compression ratio, + since our compression is based on error not bitrate. + + * Tried various other predictors for the lossless video encoding, but + none tended to give any significant gain over predicting the + previous pixel. + +***************************************************************************/ + +#include "avcomp.h" +#include "huffman.h" +#include "chd.h" + +#include <math.h> + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +struct _avcomp_state +{ + /* decompression parameters */ + UINT32 decodemask; + UINT8 * videobuffer; + UINT32 videostride; + UINT32 videoxor; + UINT32 audioxor; + + /* video parameters */ + UINT32 maxwidth, maxheight; + + /* audio parameters */ + UINT32 maxchannels; + + /* intermediate data */ + UINT8 * deltadata; + UINT8 * audiodata; + + /* huffman contexts */ + huffman_context * ycontext; + huffman_context * ccontext; + huffman_context * audiocontext; +}; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + +/* encoding helpers */ +static avcomp_error encode_audio(avcomp_state *state, int channels, int samples, const UINT8 *source, UINT8 *dest, UINT8 *sizes); +static avcomp_error encode_video(avcomp_state *state, int width, int height, int interlaced, const UINT8 *source, UINT8 *dest, UINT32 *complength); +static avcomp_error encode_video_lossless(avcomp_state *state, int width, int height, int interlaced, const UINT8 *source, UINT8 *dest, UINT32 *complength); + +/* decoding helpers */ +static avcomp_error decode_audio(avcomp_state *state, int channels, int samples, const UINT8 *source, UINT8 *dest, const UINT8 *sizes, UINT32 destxor); +static avcomp_error decode_video(avcomp_state *state, int width, int height, int interlaced, const UINT8 *source, UINT32 complength, UINT8 *dest); +static avcomp_error decode_video_lossless(avcomp_state *state, int width, int height, int interlaced, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 deststride, UINT32 destxor); + + + +/*************************************************************************** + IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + avcomp_init - allocate and initialize a + new state block for compression or + decompression +-------------------------------------------------*/ + +avcomp_state *avcomp_init(UINT32 maxwidth, UINT32 maxheight, UINT32 maxchannels) +{ + huffman_error hufferr; + avcomp_state *state; + + /* allocate memory for state block */ + state = malloc(sizeof(*state)); + if (state == NULL) + return NULL; + + /* clear the buffers */ + memset(state, 0, sizeof(*state)); + + /* fill in sensible decompression defaults */ + state->decodemask = AVCOMP_DECODE_DEFAULT; + + /* compute the core info */ + state->maxwidth = maxwidth; + state->maxheight = maxheight; + state->maxchannels = maxchannels; + + /* now allocate data buffers */ + state->deltadata = malloc(state->maxwidth * state->maxheight * 2); + state->audiodata = malloc(65536 * state->maxchannels * 2); + if (state->deltadata == NULL || state->audiodata == NULL) + goto cleanup; + + /* create huffman contexts */ + hufferr = huffman_create_context(&state->ycontext, 12); + if (hufferr != HUFFERR_NONE) + goto cleanup; + hufferr = huffman_create_context(&state->ccontext, 12); + if (hufferr != HUFFERR_NONE) + goto cleanup; + hufferr = huffman_create_context(&state->audiocontext, 12); + if (hufferr != HUFFERR_NONE) + goto cleanup; + + return state; + +cleanup: + avcomp_free(state); + return NULL; +} + + +/*------------------------------------------------- + avcomp_free - free a state block +-------------------------------------------------*/ + +void avcomp_free(avcomp_state *state) +{ + /* free the data buffers */ + if (state->deltadata != NULL) + free(state->deltadata); + if (state->audiodata != NULL) + free(state->audiodata); + + /* free the contexts */ + if (state->ycontext != NULL) + huffman_free_context(state->ycontext); + if (state->ccontext != NULL) + huffman_free_context(state->ccontext); + if (state->audiocontext != NULL) + huffman_free_context(state->audiocontext); + + free(state); +} + + +/*------------------------------------------------- + avcomp_decompress_config - configure compression + parameters +-------------------------------------------------*/ + +void avcomp_decompress_config(avcomp_state *state, UINT32 decodemask, UINT8 *videobuffer, UINT32 videostride, UINT32 videoxor, UINT32 audioxor) +{ + state->decodemask = decodemask; + state->videobuffer = videobuffer; + state->videostride = videostride; + state->videoxor = videoxor; + state->audioxor = audioxor; +} + + + +/*************************************************************************** + ENCODING/DECODING FRONTENDS +***************************************************************************/ + +/*------------------------------------------------- + avcomp_encode_data - encode a block of data + into a compressed data stream +-------------------------------------------------*/ + +avcomp_error avcomp_encode_data(avcomp_state *state, const UINT8 *source, UINT8 *dest, UINT32 *complength) +{ + UINT32 metasize, channels, samples, width, height; + UINT32 srcoffs, dstoffs; + avcomp_error err; + int interlaced; + + /* validate the header */ + if (source[0] != 'c' || source[1] != 'h' || source[2] != 'a' || source[3] != 'v') + return AVCERR_INVALID_DATA; + + /* extract info from the header */ + metasize = source[4]; + channels = source[5]; + samples = (source[6] << 8) + source[7]; + width = (source[8] << 8) + source[9]; + height = (source[10] << 8) + source[11]; + interlaced = (height >> 15) & 1; + height &= 0x7fff; + + /* validate the info from the header */ + if (width > state->maxwidth || height > state->maxheight) + return AVCERR_VIDEO_TOO_LARGE; + if (channels > state->maxchannels) + return AVCERR_AUDIO_TOO_LARGE; + + /* write the basics to the new header */ + dest[0] = metasize; + dest[1] = channels; + dest[2] = samples >> 8; + dest[3] = samples; + dest[4] = width >> 8; + dest[5] = width; + dest[6] = (interlaced << 7) | (height >> 8); + dest[7] = height; + + /* starting offsets */ + srcoffs = 12; + dstoffs = 9 + 2 * channels; + + /* copy the metadata first */ + if (metasize > 0) + { + memcpy(dest + dstoffs, source + srcoffs, metasize); + srcoffs += metasize; + dstoffs += metasize; + } + + /* encode the audio channels */ + if (channels > 0) + { + int chnum; + + /* encode the audio */ + err = encode_audio(state, channels, samples, source + srcoffs, dest + dstoffs, &dest[8]); + if (err != AVCERR_NONE) + return err; + + /* advance the pointers past the data */ + srcoffs += channels * samples * 2; + dstoffs += dest[8]; + for (chnum = 0; chnum < channels; chnum++) + dstoffs += (dest[9 + 2 * chnum] << 8) + dest[10 + 2 * chnum]; + } + + /* encode the video data */ + if (width > 0 && height > 0) + { + UINT32 vidlength; + + /* encode the video */ + err = encode_video(state, width, height, interlaced, source + srcoffs, dest + dstoffs, &vidlength); + if (err != AVCERR_NONE) + return err; + + /* advance the pointers past the data */ + srcoffs += width * height * 2; + dstoffs += vidlength; + } + + /* set the total compression */ + *complength = dstoffs; + return AVCERR_NONE; +} + + +/*------------------------------------------------- + avcomp_decode_data - decode both + audio and video from a raw data stream +-------------------------------------------------*/ + +avcomp_error avcomp_decode_data(avcomp_state *state, const UINT8 *source, UINT32 complength, UINT8 *dest) +{ + UINT32 metasize, channels, samples, width, height; + UINT32 srcoffs, dstoffs, totalsize; + avcomp_error err; + int interlaced; + int chnum; + + /* extract info from the header */ + if (complength < 8) + return AVCERR_INVALID_DATA; + metasize = source[0]; + channels = source[1]; + samples = (source[2] << 8) + source[3]; + width = (source[4] << 8) + source[5]; + height = (source[6] << 8) + source[7]; + interlaced = height >> 15; + height &= 0x7fff; + + /* validate the info from the header */ + if (width > state->maxwidth || height > state->maxheight) + return AVCERR_VIDEO_TOO_LARGE; + if (channels > state->maxchannels) + return AVCERR_AUDIO_TOO_LARGE; + + /* validate that the sizes make sense */ + if (complength < 9 + 2 * channels) + return AVCERR_INVALID_DATA; + totalsize = 9 + 2 * channels + source[8]; + for (chnum = 0; chnum < channels; chnum++) + totalsize += (source[9 + 2 * chnum] << 8) | source[10 + 2 * chnum]; + if (totalsize >= complength) + return AVCERR_INVALID_DATA; + + /* starting offsets */ + srcoffs = 9 + 2 * channels; + dstoffs = 12; + + /* write the basics to the new header */ + dest[0] = 'c'; + dest[1] = 'h'; + dest[2] = 'a'; + dest[3] = 'v'; + dest[4] = metasize; + dest[5] = channels; + dest[6] = samples >> 8; + dest[7] = samples; + dest[8] = width >> 8; + dest[9] = width; + dest[10] = (interlaced << 7) | (height >> 8); + dest[11] = height; + + /* copy the metadata first */ + if (metasize > 0) + { + if ((state->decodemask & AVCOMP_DECODE_META) != 0) + memcpy(dest + dstoffs, source + srcoffs, metasize); + srcoffs += metasize; + dstoffs += metasize; + } + + /* decode the audio channels */ + if (channels > 0) + { + /* decode the audio */ + err = decode_audio(state, channels, samples, source + srcoffs, dest + dstoffs, &source[8], state->audioxor); + if (err != AVCERR_NONE) + return err; + + /* advance the pointers past the data */ + srcoffs += source[8]; + for (chnum = 0; chnum < channels; chnum++) + srcoffs += (source[9 + 2 * chnum] << 8) + source[10 + 2 * chnum]; + dstoffs += channels * samples * 2; + } + + /* decode the video data */ + if (width > 0 && height > 0 && (state->decodemask & AVCOMP_DECODE_VIDEO) != 0) + { + /* decode the video */ + err = decode_video(state, width, height, interlaced, source + srcoffs, complength - srcoffs, dest + dstoffs); + if (err != AVCERR_NONE) + return err; + } + return AVCERR_NONE; +} + + + +/*************************************************************************** + ENCODING HELPERS +***************************************************************************/ + +/*------------------------------------------------- + encode_audio - encode raw audio data + to the destination +-------------------------------------------------*/ + +static avcomp_error encode_audio(avcomp_state *state, int channels, int samples, const UINT8 *source, UINT8 *dest, UINT8 *sizes) +{ + UINT32 size, bytesleft; + huffman_error hufferr; + int chnum, sampnum; + const UINT8 *input; + UINT8 *output; + + /* get input/output pointers */ + input = source; + output = state->audiodata; + + /* extract audio deltas */ + for (chnum = 0; chnum < channels; chnum++) + { + INT16 prev = 0; + + for (sampnum = 0; sampnum < samples; sampnum++) + { + INT16 cursample, delta; + + /* read current sample (big endian) */ + cursample = *input++ << 8; + cursample |= *input++; + + /* compute delta against previous sample in this channel */ + delta = cursample - prev; + prev = cursample; + + /* store the delta (big endian) */ + *output++ = delta >> 8; + *output++ = delta & 0xff; + } + } + + /* reset our output pointer and huffman encode */ + input = state->audiodata; + output = dest; + bytesleft = channels * samples * 2; + + /* compute a single huffman tree for all the audio data */ + hufferr = huffman_compute_tree(state->audiocontext, input, bytesleft, 1); + if (hufferr != HUFFERR_NONE) + return AVCERR_COMPRESSION_ERROR; + hufferr = huffman_export_tree(state->audiocontext, output, bytesleft, &size); + if (hufferr != HUFFERR_NONE) + return AVCERR_COMPRESSION_ERROR; + output += size; + *sizes++ = size; + bytesleft -= size; + + /* encode each stream separately */ + for (chnum = 0; chnum < channels; chnum++) + { + /* encode the data */ + hufferr = huffman_encode_data(state->audiocontext, input, samples * 2, output, bytesleft, &size); + if (hufferr != HUFFERR_NONE) + return AVCERR_COMPRESSION_ERROR; + input += samples * 2; + output += size; + bytesleft = (bytesleft > size) ? (bytesleft - size) : 0; + + /* store the size of this stream */ + *sizes++ = size >> 8; + *sizes++ = size; + } + + /* if we didn't compress enough, set the huffman table size to 0 and memcpy the data */ + if (bytesleft == 0) + { + /* back up to the beginning */ + input = source; + output = dest; + sizes -= 1 + 2 * channels; + + /* huffman table size of 0 */ + *sizes++ = 0; + + /* write the full size of each stream and then copy it */ + size = samples * 2; + for (chnum = 0; chnum < channels; chnum++) + { + /* copy the data */ + memcpy(output, input, size); + input += size; + output += size; + + /* store the size of this stream */ + *sizes++ = size >> 8; + *sizes++ = size; + } + } + + return AVCERR_NONE; +} + + +/*------------------------------------------------- + encode_video - encode raw video data + to the destination +-------------------------------------------------*/ + +static avcomp_error encode_video(avcomp_state *state, int width, int height, int interlaced, const UINT8 *source, UINT8 *dest, UINT32 *complength) +{ + /* only lossless supported at this time */ + return encode_video_lossless(state, width, height, interlaced, source, dest, complength); +} + + +/*------------------------------------------------- + encode_video_lossless - do a lossless video + encoding using deltas and huffman encoding +-------------------------------------------------*/ + +static avcomp_error encode_video_lossless(avcomp_state *state, int width, int height, int interlaced, const UINT8 *source, UINT8 *dest, UINT32 *complength) +{ + UINT32 srcbytes = width * height * 2; + int rowbytes = width * 2; + huffman_error hufferr; + UINT32 outbytes; + UINT8 *output; + int x, y; + + /* loop over rows */ + output = state->deltadata; + for (y = 0; y < height; y++) + { + const UINT8 *src = &source[rowbytes * y]; + UINT8 lasty = (y == 0) ? 0x80 : src[-rowbytes + 1]; + UINT8 lastcb = (y == 0) ? 0x80 : src[-rowbytes + 0]; + UINT8 lastcr = (y == 0) ? 0x80 : src[-rowbytes + 2]; + + /* loop over columns */ + for (x = 0; x < rowbytes; x += 4) + { + *output++ = *src - lastcb; + lastcb = *src++; + *output++ = *src - lasty; + lasty = *src++; + *output++ = *src - lastcr; + lastcr = *src++; + *output++ = *src - lasty; + lasty = *src++; + } + } + + /* set up the output; first byte is 0x80 to indicate lossless encoding */ + output = dest; + *output++ = 0x80; + + /* now encode to the destination using two trees, one for the Y and one for the Cr/Cb */ + + /* compute the histograms for the data */ + hufferr = huffman_compute_tree(state->ycontext, state->deltadata + 0, srcbytes, 2); + if (hufferr != HUFFERR_NONE) + return AVCERR_COMPRESSION_ERROR; + hufferr = huffman_compute_tree(state->ccontext, state->deltadata + 1, srcbytes, 2); + if (hufferr != HUFFERR_NONE) + return AVCERR_COMPRESSION_ERROR; + + /* export the two trees to the data stream */ + hufferr = huffman_export_tree(state->ycontext, output, 256, &outbytes); + if (hufferr != HUFFERR_NONE) + return AVCERR_COMPRESSION_ERROR; + output += outbytes; + hufferr = huffman_export_tree(state->ccontext, output, 256, &outbytes); + if (hufferr != HUFFERR_NONE) + return AVCERR_COMPRESSION_ERROR; + output += outbytes; + + /* encode the data using the two trees */ + hufferr = huffman_encode_data_interleaved_2(state->ycontext, state->ccontext, state->deltadata, srcbytes, output, srcbytes, &outbytes); + if (hufferr != HUFFERR_NONE) + return AVCERR_COMPRESSION_ERROR; + output += outbytes; + + /* set the final length */ + *complength = output - dest; + return AVCERR_NONE; +} + + + +/*************************************************************************** + DECODING HELPERS +***************************************************************************/ + +/*------------------------------------------------- + decode_audio - decode audio from a + compressed data stream +-------------------------------------------------*/ + +static avcomp_error decode_audio(avcomp_state *state, int channels, int samples, const UINT8 *source, UINT8 *dest, const UINT8 *sizes, UINT32 destxor) +{ + huffman_error hufferr; + int chnum, sampnum; + const UINT8 *input; + UINT32 actsize; + UINT8 *output; + UINT16 size; + + /* set up input/output pointers */ + input = source; + output = dest; + + /* if no huffman length, just copy the data */ + size = *sizes++; + if (size == 0) + { + /* loop over channels */ + for (chnum = 0; chnum < channels; chnum++) + { + /* extract the size of this channel */ + size = *sizes++ << 8; + size |= *sizes++; + + /* copy the data */ + if ((state->decodemask & AVCOMP_DECODE_AUDIO(chnum)) != 0) + { + for (sampnum = 0; sampnum < samples; sampnum++) + { + output[0 ^ destxor] = input[0]; + output[1 ^ destxor] = input[1]; + output += 2; + input += 2; + } + } + else + { + input += size; + output += size; + } + } + return AVCERR_NONE; + } + + /* extract the huffman tree */ + input = source; + hufferr = huffman_import_tree(state->audiocontext, input, size, &actsize); + if (hufferr != HUFFERR_NONE || actsize != size) + return AVCERR_INVALID_DATA; + input += actsize; + + /* now loop over channels and decode their data */ + for (chnum = 0; chnum < channels; chnum++) + { + /* extract the size of this channel */ + size = *sizes++ << 8; + size |= *sizes++; + + /* decode the data */ + if ((state->decodemask & AVCOMP_DECODE_AUDIO(chnum)) != 0) + { + hufferr = huffman_decode_data(state->audiocontext, input, size, output, samples * 2, &actsize); + if (hufferr != HUFFERR_NONE || actsize != size) + return AVCERR_INVALID_DATA; + } + + /* advance */ + input += size; + output += samples * 2; + } + + /* reset input/output pointers */ + output = dest; + + /* reassemble audio from the deltas */ + for (chnum = 0; chnum < channels; chnum++) + { + INT16 prev = 0; + + if ((state->decodemask & AVCOMP_DECODE_AUDIO(chnum)) != 0) + { + for (sampnum = 0; sampnum < samples; sampnum++) + { + INT16 cursample; + + /* read current sample (big endian) */ + cursample = output[0] << 8; + cursample |= output[1]; + + /* compute delta against previous sample in this channel */ + cursample += prev; + prev = cursample; + + /* store the delta (big endian) */ + output[0 ^ destxor] = cursample >> 8; + output[1 ^ destxor] = cursample; + output += 2; + } + } + else + output += samples * 2; + } + return AVCERR_NONE; +} + + +/*------------------------------------------------- + decode_video - decode video from a + compressed data stream +-------------------------------------------------*/ + +static avcomp_error decode_video(avcomp_state *state, int width, int height, int interlaced, const UINT8 *source, UINT32 complength, UINT8 *dest) +{ + UINT32 targetstride, targetxor; + UINT8 *target; + + /* if we have a target buffer, use that instead */ + if (state->videobuffer != NULL) + { + target = state->videobuffer; + targetstride = state->videostride; + targetxor = state->videoxor; + } + else + { + target = dest; + targetstride = width * 2; + targetxor = 0; + } + + /* if the high bit of the first byte is set, we decode losslessly */ + if (source[0] & 0x80) + return decode_video_lossless(state, width, height, interlaced, source, complength, target, targetstride, targetxor); + else + return AVCERR_INVALID_DATA; +} + + +/*------------------------------------------------- + decode_video_lossless - do a lossless video + decoding using deltas and huffman encoding +-------------------------------------------------*/ + +static avcomp_error decode_video_lossless(avcomp_state *state, int width, int height, int interlaced, const UINT8 *source, UINT32 complength, UINT8 *dest, UINT32 deststride, UINT32 destxor) +{ + const UINT8 *sourceend = source + complength; + const huffman_lookup_value *table1, *table2; + huffman_error hufferr; + UINT32 bitbuf = 0; + UINT32 actsize; + int sbits = 0; + int x, y; + + /* skip the first byte */ + source++; + + /* decode the table and data */ + hufferr = huffman_import_tree(state->ycontext, source, sourceend - source, &actsize); + if (hufferr != HUFFERR_NONE) + return AVCERR_INVALID_DATA; + source += actsize; + hufferr = huffman_import_tree(state->ccontext, source, sourceend - source, &actsize); + if (hufferr != HUFFERR_NONE) + return AVCERR_INVALID_DATA; + source += actsize; + + /* get the lookup tables */ + hufferr = huffman_get_lookup_table(state->ycontext, &table1); + if (hufferr != HUFFERR_NONE) + return AVCERR_OUT_OF_MEMORY; + hufferr = huffman_get_lookup_table(state->ccontext, &table2); + if (hufferr != HUFFERR_NONE) + return AVCERR_OUT_OF_MEMORY; + + /* loop over rows */ + for (y = 0; y < height; y++) + { + UINT8 *dst = &dest[y * deststride]; + UINT8 lasty = (y == 0) ? 0x80 : (dst - deststride)[1 ^ destxor]; + UINT8 lastcb = (y == 0) ? 0x80 : (dst - deststride)[0 ^ destxor]; + UINT8 lastcr = (y == 0) ? 0x80 : (dst - deststride)[2 ^ destxor]; + + /* loop over columns */ + for (x = 0; x < width * 2; x += 4) + { + huffman_lookup_value lookup; + + /* keep the buffer full */ + while (sbits <= 24) + { + bitbuf |= *source++ << (24 - sbits); + sbits += 8; + } + + /* do the Cb component */ + lookup = table1[bitbuf >> 20]; + dst[(x + 0) ^ destxor] = lastcb += lookup >> 8; + lookup &= 0x1f; + bitbuf <<= lookup; + sbits -= lookup; + + /* do the Y component */ + lookup = table2[bitbuf >> 20]; + dst[(x + 1) ^ destxor] = lasty += lookup >> 8; + lookup &= 0x1f; + bitbuf <<= lookup; + sbits -= lookup; + + /* keep the buffer full */ + while (sbits <= 24) + { + bitbuf |= *source++ << (24 - sbits); + sbits += 8; + } + + /* do the Cr component */ + lookup = table1[bitbuf >> 20]; + dst[(x + 2) ^ destxor] = lastcr += lookup >> 8; + lookup &= 0x1f; + bitbuf <<= lookup; + sbits -= lookup; + + /* do the Y component */ + lookup = table2[bitbuf >> 20]; + dst[(x + 3) ^ destxor] = lasty += lookup >> 8; + lookup &= 0x1f; + bitbuf <<= lookup; + sbits -= lookup; + } + } + + return AVCERR_NONE; +} diff --git a/src/lib/util/avcomp.h b/src/lib/util/avcomp.h new file mode 100644 index 00000000000..9a729e3c1c1 --- /dev/null +++ b/src/lib/util/avcomp.h @@ -0,0 +1,60 @@ +/*************************************************************************** + + avcomp.h + + Audio/video compression and decompression helpers. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#ifndef __AVCOMP_H__ + +#include "osdcore.h" + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* errors */ +enum _avcomp_error +{ + AVCERR_NONE = 0, + AVCERR_INVALID_DATA, + AVCERR_VIDEO_TOO_LARGE, + AVCERR_AUDIO_TOO_LARGE, + AVCERR_OUT_OF_MEMORY, + AVCERR_COMPRESSION_ERROR +}; +typedef enum _avcomp_error avcomp_error; + +/* default decompression parameters */ +#define AVCOMP_DECODE_META (1 << 0) +#define AVCOMP_DECODE_VIDEO (1 << 1) +#define AVCOMP_DECODE_AUDIO(x) (1 << (2 + (x))) +#define AVCOMP_DECODE_DEFAULT (~0) + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _avcomp_state avcomp_state; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + +avcomp_state *avcomp_init(UINT32 maxwidth, UINT32 maxheight, UINT32 maxchannels); +void avcomp_free(avcomp_state *state); +void avcomp_decompress_config(avcomp_state *state, UINT32 decodemask, UINT8 *videobuffer, UINT32 videostride, UINT32 videoxor, UINT32 audioxor); + +avcomp_error avcomp_encode_data(avcomp_state *state, const UINT8 *source, UINT8 *dest, UINT32 *complength); +avcomp_error avcomp_decode_data(avcomp_state *state, const UINT8 *source, UINT32 complength, UINT8 *dest); + +#endif diff --git a/src/lib/util/aviio.c b/src/lib/util/aviio.c new file mode 100644 index 00000000000..5fa464dcdb4 --- /dev/null +++ b/src/lib/util/aviio.c @@ -0,0 +1,2744 @@ +/*************************************************************************** + + aviio.c + + AVI movie format parsing helpers. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include "aviio.h" + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define FILETYPE_READ 1 +#define FILETYPE_CREATE 2 + +#define MAX_RIFF_SIZE (2UL * 1024 * 1024 * 1024 - 1024) /* just under 2GB */ +#define MAX_AVI_SIZE_IN_GB (256) +#define FOUR_GB ((UINT64)1 << 32) + +#define MAX_SOUND_CHANNELS 2 +#define SOUND_BUFFER_MSEC 2000 /* microseconds of sond buffering */ + +#define CHUNKTYPE_RIFF AVI_FOURCC('R','I','F','F') +#define CHUNKTYPE_LIST AVI_FOURCC('L','I','S','T') +#define CHUNKTYPE_JUNK AVI_FOURCC('J','U','N','K') +#define CHUNKTYPE_AVIH AVI_FOURCC('a','v','i','h') +#define CHUNKTYPE_STRH AVI_FOURCC('s','t','r','h') +#define CHUNKTYPE_STRF AVI_FOURCC('s','t','r','f') +#define CHUNKTYPE_IDX1 AVI_FOURCC('i','d','x','1') +#define CHUNKTYPE_INDX AVI_FOURCC('i','n','d','x') +#define CHUNKTYPE_XXDB AVI_FOURCC(0x00,0x00,'d','b') +#define CHUNKTYPE_XXDC AVI_FOURCC(0x00,0x00,'d','c') +#define CHUNKTYPE_XXWB AVI_FOURCC(0x00,0x00,'w','b') +#define CHUNKTYPE_IXXX AVI_FOURCC('i','x',0x00,0x00) +#define CHUNKTYPE_XX_MASK AVI_FOURCC(0x00,0x00,0xff,0xff) + +#define LISTTYPE_AVI AVI_FOURCC('A','V','I',' ') +#define LISTTYPE_AVIX AVI_FOURCC('A','V','I','X') +#define LISTTYPE_HDRL AVI_FOURCC('h','d','r','l') +#define LISTTYPE_STRL AVI_FOURCC('s','t','r','l') +#define LISTTYPE_MOVI AVI_FOURCC('m','o','v','i') + +#define STREAMTYPE_VIDS AVI_FOURCC('v','i','d','s') +#define STREAMTYPE_AUDS AVI_FOURCC('a','u','d','s') + +#define HANDLER_DIB AVI_FOURCC('D','I','B',' ') +#define HANDLER_HFYU AVI_FOURCC('h','f','y','u') + +#define FORMAT_UYVY AVI_FOURCC('U','Y','V','Y') +#define FORMAT_VYUY AVI_FOURCC('V','Y','U','Y') +#define FORMAT_YUY2 AVI_FOURCC('Y','U','Y','2') +#define FORMAT_HFYU AVI_FOURCC('H','F','Y','U') + +/* main AVI header files */ +#define AVIF_HASINDEX 0x00000010 +#define AVIF_MUSTUSEINDEX 0x00000020 +#define AVIF_ISINTERLEAVED 0x00000100 +#define AVIF_COPYRIGHTED 0x00010000 +#define AVIF_WASCAPTUREFILE 0x00020000 + +/* index definitions */ +#define AVI_INDEX_OF_INDEXES 0x00 +#define AVI_INDEX_OF_CHUNKS 0x01 +#define AVI_INDEX_IS_DATA 0x80 +#define AVI_INDEX_2FIELD 0x01 + +/* HuffYUV definitions */ +#define HUFFYUV_PREDICT_LEFT 0 +#define HUFFYUV_PREDICT_GRADIENT 1 +#define HUFFYUV_PREDICT_MEDIAN 2 +#define HUFFYUV_PREDICT_DECORR 0x40 + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _avi_chunk avi_chunk; +struct _avi_chunk +{ + UINT64 offset; /* file offset of chunk header */ + UINT64 size; /* size of this chunk */ + UINT32 type; /* type of this chunk */ + UINT32 listtype; /* type of this list (if we are a list) */ +}; + + +typedef struct _avi_chunk_list avi_chunk_list; +struct _avi_chunk_list +{ + UINT64 offset; /* offset in the file of header */ + UINT32 length; /* length of the chunk including header */ +}; + + +typedef struct _huffyuv_table huffyuv_table; +struct _huffyuv_table +{ + UINT8 shift[256]; /* bit shift amounts */ + UINT32 bits[256]; /* bit match values */ + UINT32 mask[256]; /* bit mask values */ + UINT16 baselookup[65536]; /* base lookup table */ + UINT16 * extralookup; /* extra lookup tables */ +}; + + +typedef struct _huffyuv_data huffyuv_data; +struct _huffyuv_data +{ + UINT8 predictor; /* predictor */ + huffyuv_table table[3]; /* array of tables */ +}; + + +typedef struct _avi_stream avi_stream; +struct _avi_stream +{ + UINT32 type; /* subtype of stream */ + UINT32 format; /* format of stream data */ + + UINT32 rate; /* timescale for stream */ + UINT32 scale; /* duration of one sample in the stream */ + UINT32 samples; /* number of samples */ + + avi_chunk_list * chunk; /* list of chunks */ + UINT32 chunks; /* chunks currently known */ + UINT32 chunksalloc; /* number of chunks allocated */ + + UINT32 width; /* width of video */ + UINT32 height; /* height of video */ + UINT32 depth; /* depth of video */ + UINT8 interlace; /* interlace parameters */ + huffyuv_data * huffyuv; /* huffyuv decompression data */ + + UINT16 channels; /* audio channels */ + UINT16 samplebits; /* audio bits per sample */ + UINT32 samplerate; /* audio sample rate */ + + /* only used when creating */ + UINT64 saved_strh_offset; /* writeoffset of strh chunk */ + UINT64 saved_indx_offset; /* writeoffset of indx chunk */ +}; + + +struct _avi_file +{ + /* shared data */ + osd_file * file; /* pointer to open file */ + int type; /* type of access (read/create) */ + avi_movie_info info; /* movie info structure */ + UINT8 * tempbuffer; /* temporary buffer */ + UINT32 tempbuffersize; /* size of the temporary buffer */ + + /* only used when reading */ + int streams; /* number of streams */ + avi_stream * stream; /* allocated array of stream information */ + avi_chunk rootchunk; /* dummy root chunk that wraps the whole file */ + + /* only used when creating */ + UINT64 writeoffs; /* current file write offset */ + UINT64 riffbase; /* base of the current RIFF */ + + avi_chunk chunkstack[8]; /* stack of chunks we are writing */ + int chunksp; /* stack pointer for the current chunk */ + + UINT64 saved_movi_offset; /* writeoffset of movi list */ + UINT64 saved_avih_offset; /* writeoffset of avih chunk */ + + INT16 * soundbuf; /* buffer for sound data */ + UINT32 soundbuf_samples; /* length of sound buffer in samples */ + UINT32 soundbuf_chansamples[MAX_SOUND_CHANNELS]; /* samples in buffer for each channel */ + UINT32 soundbuf_chunks; /* number of chunks completed so far */ + UINT32 soundbuf_frames; /* number of frames ahead of the video */ +}; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + +/* stream helpers */ + +/* core chunk read routines */ +static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk); +static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk); +static avi_error find_first_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result); +static avi_error find_next_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result); +static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, UINT64 offset); +static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, UINT8 **buffer); + +/* chunk read helpers */ +static avi_error read_movie_data(avi_file *file); +static avi_error extract_movie_info(avi_file *file); +static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih); +static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh); +static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf); +static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf); +static avi_error parse_idx1_chunk(avi_file *file, UINT64 baseoffset, avi_chunk *idx1); + +/* core chunk write routines */ +static avi_error chunk_open(avi_file *file, UINT32 type, UINT32 listtype, UINT32 estlength); +static avi_error chunk_close(avi_file *file); +static avi_error chunk_write(avi_file *file, UINT32 type, const void *data, UINT32 length); +static avi_error chunk_overwrite(avi_file *file, UINT32 type, const void *data, UINT32 length, UINT64 *offset, int initial_write); + +/* chunk write helpers */ +static avi_error write_initial_headers(avi_file *file); +static avi_error write_avih_chunk(avi_file *file, int initial_write); +static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write); +static avi_error write_strf_chunk(avi_file *file, avi_stream *stream); +static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write); +static avi_error write_idx1_chunk(avi_file *file); + +/* sound buffering helpers */ +static avi_error soundbuf_initialize(avi_file *file); +static avi_error soundbuf_write_chunk(avi_file *file, UINT32 framenum); +static avi_error soundbuf_flush(avi_file *file, int only_flush_full); + +/* YUY helpers */ +static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap); +static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes); + +/* HuffYUV helpers */ +static avi_error huffyuv_extract_tables(avi_stream *stream, const UINT8 *chunkdata, UINT32 size); +static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap); + +/* debugging */ +static void printf_chunk_recursive(avi_file *file, avi_chunk *chunk, int indent); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + fetch_16bits - read 16 bits in LSB order + from the given buffer +-------------------------------------------------*/ + +INLINE UINT16 fetch_16bits(const UINT8 *data) +{ + return data[0] | (data[1] << 8); +} + + +/*------------------------------------------------- + fetch_32bits - read 32 bits in LSB order + from the given buffer +-------------------------------------------------*/ + +INLINE UINT32 fetch_32bits(const UINT8 *data) +{ + return data[0] | (data[1] << 8) | (data[2] << 16) | (data[3] << 24); +} + + +/*------------------------------------------------- + fetch_64bits - read 64 bits in LSB order + from the given buffer +-------------------------------------------------*/ + +INLINE UINT64 fetch_64bits(const UINT8 *data) +{ + return (UINT64)data[0] | ((UINT64)data[1] << 8) | + ((UINT64)data[2] << 16) | ((UINT64)data[3] << 24) | + ((UINT64)data[4] << 32) | ((UINT64)data[5] << 40) | + ((UINT64)data[6] << 48) | ((UINT64)data[7] << 56); +} + + +/*------------------------------------------------- + put_16bits - write 16 bits in LSB order + to the given buffer +-------------------------------------------------*/ + +INLINE void put_16bits(UINT8 *data, UINT16 value) +{ + data[0] = value >> 0; + data[1] = value >> 8; +} + + +/*------------------------------------------------- + put_32bits - write 32 bits in LSB order + to the given buffer +-------------------------------------------------*/ + +INLINE void put_32bits(UINT8 *data, UINT32 value) +{ + data[0] = value >> 0; + data[1] = value >> 8; + data[2] = value >> 16; + data[3] = value >> 24; +} + + +/*------------------------------------------------- + put_64bits - write 64 bits in LSB order + to the given buffer +-------------------------------------------------*/ + +INLINE void put_64bits(UINT8 *data, UINT64 value) +{ + data[0] = value >> 0; + data[1] = value >> 8; + data[2] = value >> 16; + data[3] = value >> 24; + data[4] = value >> 32; + data[5] = value >> 40; + data[6] = value >> 48; + data[7] = value >> 56; +} + + +/*------------------------------------------------- + get_video_stream - return a pointer to the + video stream +-------------------------------------------------*/ + +INLINE avi_stream *get_video_stream(avi_file *file) +{ + int streamnum; + + /* find the video stream */ + for (streamnum = 0; streamnum < file->streams; streamnum++) + if (file->stream[streamnum].type == STREAMTYPE_VIDS) + return &file->stream[streamnum]; + + return NULL; +} + + +/*------------------------------------------------- + get_audio_stream - return a pointer to the + audio stream for the 'n'th channel +-------------------------------------------------*/ + +INLINE avi_stream *get_audio_stream(avi_file *file, int channel, int *offset) +{ + int streamnum; + + /* find the audios stream */ + for (streamnum = 0; streamnum < file->streams; streamnum++) + if (file->stream[streamnum].type == STREAMTYPE_AUDS) + { + if (channel < file->stream[streamnum].channels) + { + if (offset != NULL) + *offset = channel; + return &file->stream[streamnum]; + } + channel -= file->stream[streamnum].channels; + } + + return NULL; +} + + +/*------------------------------------------------- + set_stream_chunk_info - set the chunk info + for a given chunk within a stream +-------------------------------------------------*/ + +INLINE avi_error set_stream_chunk_info(avi_stream *stream, UINT32 index, UINT64 offset, UINT32 length) +{ + /* if we need to allocate more, allocate more */ + if (index >= stream->chunksalloc) + { + UINT32 newcount = MAX(index, stream->chunksalloc + 1000); + stream->chunk = realloc(stream->chunk, newcount * sizeof(stream->chunk[0])); + if (stream->chunk == NULL) + return AVIERR_NO_MEMORY; + stream->chunksalloc = newcount; + } + + /* set the data */ + stream->chunk[index].offset = offset; + stream->chunk[index].length = length; + + /* update the number of chunks */ + stream->chunks = MAX(stream->chunks, index + 1); + return AVIERR_NONE; +} + + +/*------------------------------------------------- + compute_idx1_size - compute the size of the + idx1 chunk +-------------------------------------------------*/ + +INLINE UINT32 compute_idx1_size(avi_file *file) +{ + int chunks = 0; + int strnum; + + /* count chunks in streams */ + for (strnum = 0; strnum < file->streams; strnum++) + chunks += file->stream[strnum].chunks; + + return chunks * 16 + 8; +} + + +/*------------------------------------------------- + get_chunkid_for_stream - make a chunk id for + a given stream +-------------------------------------------------*/ + +INLINE UINT32 get_chunkid_for_stream(avi_file *file, avi_stream *stream) +{ + UINT32 chunkid; + + chunkid = AVI_FOURCC('0' + (stream - file->stream) / 10, '0' + (stream - file->stream) % 10, 0, 0); + if (stream->type == STREAMTYPE_VIDS) + chunkid |= (stream->format == 0) ? CHUNKTYPE_XXDB : CHUNKTYPE_XXDC; + else if (stream->type == STREAMTYPE_AUDS) + chunkid |= CHUNKTYPE_XXWB; + + return chunkid; +} + + +/*------------------------------------------------- + framenum_to_samplenum - given a video frame + number, get the first sample number +-------------------------------------------------*/ + +INLINE UINT32 framenum_to_samplenum(avi_file *file, UINT32 framenum) +{ + return ((UINT64)file->info.audio_samplerate * (UINT64)framenum * (UINT64)file->info.video_sampletime + file->info.video_timescale - 1) / (UINT64)file->info.video_timescale; +} + + +/*------------------------------------------------- + expand_tempbuffer - expand the file's + tempbuffer if necessary to contain the + requested amount of data +-------------------------------------------------*/ + +INLINE avi_error expand_tempbuffer(avi_file *file, UINT32 length) +{ + /* expand the tempbuffer to hold the data if necessary */ + if (length > file->tempbuffersize) + { + file->tempbuffersize = 2 * length; + file->tempbuffer = realloc(file->tempbuffer, file->tempbuffersize); + if (file->tempbuffer == NULL) + return AVIERR_NO_MEMORY; + } + return AVIERR_NONE; +} + + + +/*************************************************************************** + IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + avi_open - open an AVI movie file for read +-------------------------------------------------*/ + +avi_error avi_open(const char *filename, avi_file **file) +{ + avi_file *newfile = NULL; + file_error filerr; + avi_error avierr; + UINT64 length; + + /* allocate the file */ + newfile = malloc(sizeof(*newfile)); + if (newfile == NULL) + return AVIERR_NO_MEMORY; + memset(newfile, 0, sizeof(*newfile)); + newfile->type = FILETYPE_READ; + + /* open the file */ + filerr = osd_open(filename, OPEN_FLAG_READ, &newfile->file, &length); + if (filerr != FILERR_NONE) + { + avierr = AVIERR_CANT_OPEN_FILE; + goto error; + } + + /* make a root atom */ + newfile->rootchunk.offset = 0; + newfile->rootchunk.size = length; + newfile->rootchunk.type = 0; + newfile->rootchunk.listtype = 0; + + /* parse the data */ + avierr = read_movie_data(newfile); + if (avierr != AVIERR_NONE) + goto error; + + *file = newfile; + return AVIERR_NONE; + +error: + /* clean up after an error */ + if (newfile != NULL) + { + if (newfile->file != NULL) + osd_close(newfile->file); + free(newfile); + } + return avierr; +} + + +/*------------------------------------------------- + avi_create - create a new QuickTime movie file +-------------------------------------------------*/ + +avi_error avi_create(const char *filename, const avi_movie_info *info, avi_file **file) +{ + avi_file *newfile = NULL; + file_error filerr; + avi_stream *stream; + avi_error avierr; + UINT64 length; + + /* validate video info */ + if ((info->video_format != 0 && info->video_format != FORMAT_UYVY && info->video_format != FORMAT_VYUY && info->video_format != FORMAT_YUY2) || + info->video_width == 0 || + info->video_height == 0 || + info->video_depth == 0 || info->video_depth % 8 != 0) + return AVIERR_UNSUPPORTED_VIDEO_FORMAT; + + /* validate audio info */ + if (info->audio_format != 0 || + info->audio_channels > MAX_SOUND_CHANNELS || + info->audio_samplebits != 16) + return AVIERR_UNSUPPORTED_AUDIO_FORMAT; + + /* allocate the file */ + newfile = malloc(sizeof(*newfile)); + if (newfile == NULL) + return AVIERR_NO_MEMORY; + memset(newfile, 0, sizeof(*newfile)); + newfile->type = FILETYPE_CREATE; + + /* open the file */ + filerr = osd_open(filename, OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS, &newfile->file, &length); + if (filerr != FILERR_NONE) + { + avierr = AVIERR_CANT_OPEN_FILE; + goto error; + } + + /* copy the movie info */ + newfile->info = *info; + newfile->info.video_numsamples = 0; + newfile->info.audio_numsamples = 0; + + /* allocate two streams */ + newfile->stream = malloc(2 * sizeof(newfile->stream[0])); + if (newfile->stream == NULL) + { + avierr = AVIERR_NO_MEMORY; + goto error; + } + memset(newfile->stream, 0, 2 * sizeof(newfile->stream[0])); + + /* initialize the video track */ + stream = &newfile->stream[newfile->streams++]; + stream->type = STREAMTYPE_VIDS; + stream->format = newfile->info.video_format; + stream->rate = newfile->info.video_timescale; + stream->scale = newfile->info.video_sampletime; + stream->width = newfile->info.video_width; + stream->height = newfile->info.video_height; + stream->depth = newfile->info.video_depth; + + /* initialize the audio track */ + if (newfile->info.audio_channels > 0) + { + stream = &newfile->stream[newfile->streams++]; + stream->type = STREAMTYPE_AUDS; + stream->format = newfile->info.audio_format; + stream->rate = newfile->info.audio_timescale; + stream->scale = newfile->info.audio_sampletime; + stream->channels = newfile->info.audio_channels; + stream->samplebits = newfile->info.audio_samplebits; + stream->samplerate = newfile->info.audio_samplerate; + + /* initialize the sound buffering */ + avierr = soundbuf_initialize(newfile); + if (avierr != AVIERR_NONE) + goto error; + } + + /* write the initial headers */ + avierr = write_initial_headers(newfile); + + *file = newfile; + return AVIERR_NONE; + +error: + /* clean up after an error */ + if (newfile != NULL) + { + if (newfile->stream != NULL) + free(newfile->stream); + if (newfile->file != NULL) + { + osd_close(newfile->file); + osd_rmfile(filename); + } + free(newfile); + } + return avierr; +} + + +/*------------------------------------------------- + avi_close - close a QuickTime movie file +-------------------------------------------------*/ + +avi_error avi_close(avi_file *file) +{ + avi_error avierr = AVIERR_NONE; + int strnum; + + /* if we're creating a new file, finalize it by writing out the non-media chunks */ + if (file->type == FILETYPE_CREATE) + { + /* flush any pending sound data */ + avierr = soundbuf_flush(file, FALSE); + + /* close the movi chunk */ + if (avierr == AVIERR_NONE) + avierr = chunk_close(file); + + /* if this is the first RIFF chunk, write an idx1 */ + if (avierr == AVIERR_NONE && file->riffbase == 0) + avierr = write_idx1_chunk(file); + + /* close the RIFF chunk */ + if (avierr == AVIERR_NONE) + avierr = chunk_close(file); + + /* update the avih chunk */ + if (avierr == AVIERR_NONE) + avierr = write_avih_chunk(file, FALSE); + + /* update the strh and indx chunks for each stream */ + for (strnum = 0; strnum < file->streams; strnum++) + { + if (avierr == AVIERR_NONE) + avierr = write_strh_chunk(file, &file->stream[strnum], FALSE); + if (avierr == AVIERR_NONE) + avierr = write_indx_chunk(file, &file->stream[strnum], FALSE); + } + } + + /* close the file */ + osd_close(file->file); + + /* free the stream-specific data */ + for (strnum = 0; strnum < file->streams; strnum++) + { + avi_stream *stream = &file->stream[strnum]; + if (stream->huffyuv != NULL) + { + huffyuv_data *huffyuv = stream->huffyuv; + int table; + + for (table = 0; table < ARRAY_LENGTH(huffyuv->table); table++) + if (huffyuv->table[table].extralookup != NULL) + free(huffyuv->table[table].extralookup); + free(huffyuv); + } + if (stream->chunk != NULL) + free(stream->chunk); + } + + /* free the file itself */ + if (file->soundbuf != NULL) + free(file->soundbuf); + if (file->stream != NULL) + free(file->stream); + if (file->tempbuffer != NULL) + free(file->tempbuffer); + free(file); + return avierr; +} + + +/*------------------------------------------------- + avi_printf_chunks - print the chunks in a file +-------------------------------------------------*/ + +void avi_printf_chunks(avi_file *file) +{ + printf_chunk_recursive(file, &file->rootchunk, 0); +} + + +/*------------------------------------------------- + avi_error_string - get the error string for + an avi_error +-------------------------------------------------*/ + +const char *avi_error_string(avi_error err) +{ + switch (err) + { + case AVIERR_NONE: return "success"; + case AVIERR_END: return "hit end of file"; + case AVIERR_INVALID_DATA: return "invalid data"; + case AVIERR_NO_MEMORY: return "out of memory"; + case AVIERR_READ_ERROR: return "read error"; + case AVIERR_WRITE_ERROR: return "write error"; + case AVIERR_STACK_TOO_DEEP: return "stack overflow"; + case AVIERR_UNSUPPORTED_FEATURE: return "unsupported feature"; + case AVIERR_CANT_OPEN_FILE: return "unable to open file"; + case AVIERR_INCOMPATIBLE_AUDIO_STREAMS: return "found incompatible audio streams"; + case AVIERR_INVALID_SAMPLERATE: return "found invalid sample rate"; + case AVIERR_INVALID_STREAM: return "invalid stream"; + case AVIERR_INVALID_FRAME: return "invalid frame index"; + case AVIERR_INVALID_BITMAP: return "invalid bitmap"; + case AVIERR_UNSUPPORTED_VIDEO_FORMAT: return "unsupported video format"; + case AVIERR_UNSUPPORTED_AUDIO_FORMAT: return "unsupported audio format"; + case AVIERR_EXCEEDED_SOUND_BUFFER: return "sound buffer overflow"; + default: return "undocumented error"; + } +} + + +/*------------------------------------------------- + avi_get_movie_info - return a pointer to the + movie info +-------------------------------------------------*/ + +const avi_movie_info *avi_get_movie_info(avi_file *file) +{ + return &file->info; +} + + +/*------------------------------------------------- + avi_frame_to_sample - convert a frame index + to a sample index +-------------------------------------------------*/ + +UINT32 avi_first_sample_in_frame(avi_file *file, UINT32 framenum) +{ + return framenum_to_samplenum(file, framenum); +} + + +/*------------------------------------------------- + avi_read_video_frame_yuy16 - read video data + for a particular frame from the AVI file, + converting to YUY16 format +-------------------------------------------------*/ + +avi_error avi_read_video_frame_yuy16(avi_file *file, UINT32 framenum, bitmap_t *bitmap) +{ + avi_error avierr = AVIERR_NONE; + UINT32 bytes_read, chunkid; + file_error filerr; + avi_stream *stream; + + /* get the video stream */ + stream = get_video_stream(file); + if (stream == NULL) + return AVIERR_INVALID_STREAM; + + /* validate our ability to handle the data */ + if (stream->format != FORMAT_UYVY && stream->format != FORMAT_VYUY && stream->format != FORMAT_YUY2 && stream->format != FORMAT_HFYU) + return AVIERR_UNSUPPORTED_VIDEO_FORMAT; + + /* assume one chunk == one frame */ + if (framenum >= stream->chunks) + return AVIERR_INVALID_FRAME; + + /* we only support YUY-style bitmaps (16bpp) */ + if (bitmap->format != BITMAP_FORMAT_YUY16 || bitmap->width < stream->width || bitmap->height < stream->height) + return AVIERR_INVALID_BITMAP; + + /* expand the tempbuffer to hold the data if necessary */ + avierr = expand_tempbuffer(file, stream->chunk[framenum].length); + if (avierr != AVIERR_NONE) + return avierr; + + /* read in the data */ + filerr = osd_read(file->file, file->tempbuffer, stream->chunk[framenum].offset, stream->chunk[framenum].length, &bytes_read); + if (filerr != FILERR_NONE || bytes_read != stream->chunk[framenum].length) + return AVIERR_READ_ERROR; + + /* validate this is good data */ + chunkid = fetch_32bits(&file->tempbuffer[0]); + if (chunkid == get_chunkid_for_stream(file, stream)) + { + /* HuffYUV-compressed */ + if (stream->format == FORMAT_HFYU) + avierr = huffyuv_decompress_to_yuy16(stream, file->tempbuffer + 8, stream->chunk[framenum].length - 8, bitmap); + + /* other YUV-compressed */ + else + avierr = yuv_decompress_to_yuy16(stream, file->tempbuffer + 8, stream->chunk[framenum].length - 8, bitmap); + } + else + avierr = AVIERR_INVALID_DATA; + + return avierr; +} + + +/*------------------------------------------------- + avi_read_sound_samples - read sound sample + data from an AVI file +-------------------------------------------------*/ + +avi_error avi_read_sound_samples(avi_file *file, int channel, UINT32 firstsample, UINT32 numsamples, INT16 *output) +{ + avi_error avierr = AVIERR_NONE; + UINT32 bytes_per_sample; + file_error filerr; + avi_stream *stream; + int offset; + + /* get the audio stream */ + stream = get_audio_stream(file, channel, &offset); + if (stream == NULL) + return AVIERR_INVALID_STREAM; + + /* validate our ability to handle the data */ + if (stream->format != 0 || (stream->samplebits != 8 && stream->samplebits != 16)) + return AVIERR_UNSUPPORTED_AUDIO_FORMAT; + + /* verify we are in range */ + if (firstsample >= stream->samples) + return AVIERR_INVALID_FRAME; + if (firstsample + numsamples > stream->samples) + numsamples = stream->samples - firstsample; + + /* determine bytes per sample */ + bytes_per_sample = (stream->samplebits / 8) * stream->channels; + + /* loop until all samples have been extracted */ + while (numsamples > 0) + { + UINT32 chunkbase = 0, chunkend = 0, chunkid; + UINT32 bytes_read, samples_this_chunk; + int chunknum, sampnum; + + /* locate the chunk with the first sample */ + for (chunknum = 0; chunknum < stream->chunks; chunknum++) + { + chunkend = chunkbase + (stream->chunk[chunknum].length - 8) / bytes_per_sample; + if (firstsample < chunkend) + break; + chunkbase = chunkend; + } + + /* expand the tempbuffer to hold the data if necessary */ + avierr = expand_tempbuffer(file, stream->chunk[chunknum].length); + if (avierr != AVIERR_NONE) + return avierr; + + /* read in the data */ + filerr = osd_read(file->file, file->tempbuffer, stream->chunk[chunknum].offset, stream->chunk[chunknum].length, &bytes_read); + if (filerr != FILERR_NONE || bytes_read != stream->chunk[chunknum].length) + return AVIERR_READ_ERROR; + + /* validate this is good data */ + chunkid = fetch_32bits(&file->tempbuffer[0]); + if (chunkid != get_chunkid_for_stream(file, stream)) + return AVIERR_INVALID_DATA; + + /* determine how many samples to copy */ + samples_this_chunk = chunkend - firstsample; + samples_this_chunk = MIN(samples_this_chunk, numsamples); + + /* extract 16-bit samples from the chunk */ + if (stream->samplebits == 16) + { + const INT16 *base = (const INT16 *)(file->tempbuffer + 8); + base += stream->channels * (firstsample - chunkbase) + offset; + for (sampnum = 0; sampnum < samples_this_chunk; sampnum++) + { + *output++ = LITTLE_ENDIANIZE_INT16(*base); + base += stream->channels; + } + } + + /* extract 8-bit samples from the chunk */ + else if (stream->samplebits == 8) + { + const UINT8 *base = (const UINT8 *)(file->tempbuffer + 8); + base += stream->channels * (firstsample - chunkbase) + offset; + for (sampnum = 0; sampnum < samples_this_chunk; sampnum++) + { + *output++ = (*base << 8) - 0x8000; + base += stream->channels; + } + } + + /* update our counters */ + firstsample += samples_this_chunk; + numsamples -= samples_this_chunk; + } + + return avierr; +} + + +/*------------------------------------------------- + avi_append_video_frame_yuy16 - append a frame + of video in YUY16 format +-------------------------------------------------*/ + +avi_error avi_append_video_frame_yuy16(avi_file *file, const bitmap_t *bitmap) +{ + avi_stream *stream = get_video_stream(file); + avi_error avierr; + UINT32 maxlength; + + /* double check bitmap format */ + if (bitmap->format != BITMAP_FORMAT_YUY16) + return AVIERR_INVALID_BITMAP; + + /* write out any sound data first */ + avierr = soundbuf_write_chunk(file, stream->chunks); + if (avierr != AVIERR_NONE) + return avierr; + + /* make sure we have enough room */ + maxlength = 2 * stream->width * stream->height; + avierr = expand_tempbuffer(file, maxlength); + if (avierr != AVIERR_NONE) + return avierr; + + /* now compress the data */ + avierr = yuy16_compress_to_yuy(stream, bitmap, file->tempbuffer, maxlength); + if (avierr != AVIERR_NONE) + return avierr; + + /* set the info for this new chunk */ + avierr = set_stream_chunk_info(stream, stream->chunks, file->writeoffs, maxlength + 8); + if (avierr != AVIERR_NONE) + return avierr; + stream->samples = file->info.video_numsamples = stream->chunks; + + /* write the data */ + return chunk_write(file, get_chunkid_for_stream(file, stream), file->tempbuffer, maxlength); +} + + +/*------------------------------------------------- + avi_append_sound_samples - append sound + samples +-------------------------------------------------*/ + +avi_error avi_append_sound_samples(avi_file *file, int channel, const INT16 *samples, UINT32 numsamples) +{ + UINT32 sampoffset = file->soundbuf_chansamples[channel]; + UINT32 sampnum; + + /* see if we have enough room in the buffer */ + if (sampoffset + numsamples > file->soundbuf_samples) + return AVIERR_EXCEEDED_SOUND_BUFFER; + + /* append samples to the buffer in little-endian format */ + for (sampnum = 0; sampnum < numsamples; sampnum++) + { + INT16 data = *samples++; + data = LITTLE_ENDIANIZE_INT16(data); + file->soundbuf[sampoffset++ * file->info.audio_channels + channel] = data; + } + file->soundbuf_chansamples[channel] = sampoffset; + + /* flush any full sound chunks to disk */ + return soundbuf_flush(file, TRUE); +} + + +/*------------------------------------------------- + read_chunk_data - read a chunk's data into + memory +-------------------------------------------------*/ + +static avi_error read_chunk_data(avi_file *file, const avi_chunk *chunk, UINT8 **buffer) +{ + file_error filerr; + UINT32 bytes_read; + + /* allocate memory for the data */ + *buffer = malloc(chunk->size); + if (*buffer == NULL) + return AVIERR_NO_MEMORY; + + /* read from the file */ + filerr = osd_read(file->file, *buffer, chunk->offset + 8, chunk->size, &bytes_read); + if (filerr != FILERR_NONE || bytes_read != chunk->size) + { + free(*buffer); + *buffer = NULL; + return AVIERR_READ_ERROR; + } + + return AVIERR_NONE; +} + + +/*------------------------------------------------- + get_first_chunk - get information about the + first chunk in a container +-------------------------------------------------*/ + +static avi_error get_first_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk) +{ + UINT64 startoffset = (parent != NULL && parent->type != 0) ? parent->offset + 12 : 0; + if (parent != NULL && parent->type != CHUNKTYPE_LIST && parent->type != CHUNKTYPE_RIFF && parent->type != 0) + return AVIERR_INVALID_DATA; + return get_next_chunk_internal(file, parent, newchunk, startoffset); +} + + +/*------------------------------------------------- + get_next_chunk - get information about the + next chunk in a container +-------------------------------------------------*/ + +static avi_error get_next_chunk(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk) +{ + UINT64 nextoffset = newchunk->offset + 8 + newchunk->size + (newchunk->size & 1); + return get_next_chunk_internal(file, parent, newchunk, nextoffset); +} + + +/*------------------------------------------------- + find_first_chunk - get information about the + first chunk of a particular type in a container +-------------------------------------------------*/ + +static avi_error find_first_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result) +{ + avi_error avierr; + + for (avierr = get_first_chunk(file, container, result); avierr == AVIERR_NONE; avierr = get_next_chunk(file, container, result)) + if (result->type == findme) + return AVIERR_NONE; + + return avierr; +} + + +/*------------------------------------------------- + find_next_chunk - get information about the + next chunk of a particular type in a container +-------------------------------------------------*/ + +static avi_error find_next_chunk(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result) +{ + avi_error avierr; + + for (avierr = get_next_chunk(file, container, result); avierr == AVIERR_NONE; avierr = get_next_chunk(file, container, result)) + if (result->type == findme) + return AVIERR_NONE; + + return avierr; +} + + +/*------------------------------------------------- + find_first_list - get information about the + first list of a particular type in a container +-------------------------------------------------*/ + +static avi_error find_first_list(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result) +{ + avi_error avierr; + + for (avierr = find_first_chunk(file, CHUNKTYPE_LIST, container, result); avierr == AVIERR_NONE; avierr = find_next_chunk(file, CHUNKTYPE_LIST, container, result)) + if (result->listtype == findme) + return AVIERR_NONE; + + return avierr; +} + + +/*------------------------------------------------- + find_next_list - get information about the + next list of a particular type in a container +-------------------------------------------------*/ + +static avi_error find_next_list(avi_file *file, UINT32 findme, const avi_chunk *container, avi_chunk *result) +{ + avi_error avierr; + + for (avierr = find_next_chunk(file, CHUNKTYPE_LIST, container, result); avierr == AVIERR_NONE; avierr = find_next_chunk(file, CHUNKTYPE_LIST, container, result)) + if (result->listtype == findme) + return AVIERR_NONE; + + return avierr; +} + + +/*------------------------------------------------- + get_next_chunk_internal - fetch the next + chunk relative to the current one +-------------------------------------------------*/ + +static avi_error get_next_chunk_internal(avi_file *file, const avi_chunk *parent, avi_chunk *newchunk, UINT64 offset) +{ + file_error filerr; + UINT8 buffer[12]; + UINT32 bytesread; + + /* NULL parent implies the root */ + if (parent == NULL) + parent = &file->rootchunk; + + /* start at the current offset */ + newchunk->offset = offset; + + /* if we're past the bounds of the parent, bail */ + if (newchunk->offset + 8 >= parent->offset + 8 + parent->size) + return AVIERR_END; + + /* read the header */ + filerr = osd_read(file->file, buffer, newchunk->offset, 8, &bytesread); + if (filerr != FILERR_NONE || bytesread != 8) + return AVIERR_INVALID_DATA; + + /* fill in the new chunk */ + newchunk->type = fetch_32bits(&buffer[0]); + newchunk->size = fetch_32bits(&buffer[4]); + + /* if we are a list, fetch the list type */ + if (newchunk->type == CHUNKTYPE_LIST || newchunk->type == CHUNKTYPE_RIFF) + { + filerr = osd_read(file->file, &buffer[8], newchunk->offset + 8, 4, &bytesread); + if (filerr != FILERR_NONE || bytesread != 4) + return AVIERR_INVALID_DATA; + newchunk->listtype = fetch_32bits(&buffer[8]); + } + + return AVIERR_NONE; +} + + +/*------------------------------------------------- + read_movie_data - get data about a movie +-------------------------------------------------*/ + +static avi_error read_movie_data(avi_file *file) +{ + avi_chunk riff, hdrl, avih, strl, strh, strf, indx, movi, idx1; + avi_error avierr; + int strindex; + + /* find the RIFF chunk */ + avierr = find_first_chunk(file, CHUNKTYPE_RIFF, NULL, &riff); + if (avierr != AVIERR_NONE) + goto error; + + /* verify that the RIFF type is AVI */ + if (riff.listtype != LISTTYPE_AVI) + { + avierr = AVIERR_INVALID_DATA; + goto error; + } + + /* find the hdrl LIST chunk within the RIFF */ + avierr = find_first_list(file, LISTTYPE_HDRL, &riff, &hdrl); + if (avierr != AVIERR_NONE) + goto error; + + /* find the avih chunk */ + avierr = find_first_chunk(file, CHUNKTYPE_AVIH, &hdrl, &avih); + if (avierr != AVIERR_NONE) + goto error; + + /* parse the avih chunk */ + avierr = parse_avih_chunk(file, &avih); + if (avierr != AVIERR_NONE) + goto error; + + /* loop over strl LIST chunks */ + strindex = 0; + for (avierr = find_first_list(file, LISTTYPE_STRL, &hdrl, &strl); avierr == AVIERR_NONE; avierr = find_next_list(file, LISTTYPE_STRL, &hdrl, &strl)) + { + /* if we have too many, it's a bad file */ + if (strindex >= file->streams) + goto error; + + /* find the strh chunk */ + avierr = find_first_chunk(file, CHUNKTYPE_STRH, &strl, &strh); + if (avierr != AVIERR_NONE) + goto error; + + /* parse the data */ + avierr = parse_strh_chunk(file, &file->stream[strindex], &strh); + if (avierr != AVIERR_NONE) + goto error; + + /* find the strf chunk */ + avierr = find_first_chunk(file, CHUNKTYPE_STRF, &strl, &strf); + if (avierr != AVIERR_NONE) + goto error; + + /* parse the data */ + avierr = parse_strf_chunk(file, &file->stream[strindex], &strf); + if (avierr != AVIERR_NONE) + goto error; + + /* find the indx chunk, if present */ + avierr = find_first_chunk(file, CHUNKTYPE_INDX, &strl, &indx); + if (avierr == AVIERR_NONE) + avierr = parse_indx_chunk(file, &file->stream[strindex], &indx); + + /* next stream */ + strindex++; + } + + /* normalize the error after parsing the stream headers */ + if (avierr == AVIERR_END) + avierr = AVIERR_NONE; + if (avierr != AVIERR_NONE) + goto error; + + /* find the base of the movi data */ + avierr = find_first_list(file, LISTTYPE_MOVI, &riff, &movi); + if (avierr != AVIERR_NONE) + goto error; + + /* find and parse the idx1 chunk within the RIFF (if present) */ + avierr = find_first_chunk(file, CHUNKTYPE_IDX1, &riff, &idx1); + if (avierr == AVIERR_NONE) + avierr = parse_idx1_chunk(file, movi.offset + 8, &idx1); + avierr = AVIERR_NONE; + + /* now extract the movie info */ + avierr = extract_movie_info(file); + +error: + return avierr; +} + + +/*------------------------------------------------- + extract_movie_info - extract the movie info + from the streams we've read +-------------------------------------------------*/ + +static avi_error extract_movie_info(avi_file *file) +{ + avi_stream *audiostream; + avi_stream *stream; + + /* get the video stream */ + stream = get_video_stream(file); + if (stream != NULL) + { + /* fill in the info */ + file->info.video_format = stream->format; + file->info.video_timescale = stream->rate; + file->info.video_sampletime = stream->scale; + file->info.video_numsamples = stream->samples; + file->info.video_width = stream->width; + file->info.video_height = stream->height; + } + + /* get the first audio stream */ + stream = get_audio_stream(file, 0, NULL); + if (stream != NULL) + { + /* fill in the info */ + file->info.audio_format = stream->format; + file->info.audio_timescale = stream->rate; + file->info.audio_sampletime = stream->scale; + file->info.audio_numsamples = stream->samples; + file->info.audio_channels = 1; + file->info.audio_samplebits = stream->samplebits; + file->info.audio_samplerate = stream->samplerate; + } + + /* now make sure all other audio streams are valid */ + audiostream = stream; + while (1) + { + /* get the stream info */ + stream = get_audio_stream(file, file->info.audio_channels, NULL); + if (stream == NULL) + break; + file->info.audio_channels++; + + /* verify compatibility */ + if (file->info.audio_format != stream->format || + file->info.audio_timescale != stream->rate || + file->info.audio_sampletime != stream->scale || + file->info.audio_numsamples != stream->samples || + file->info.audio_samplebits != stream->samplebits || + file->info.audio_samplerate != stream->samplerate) + return AVIERR_INCOMPATIBLE_AUDIO_STREAMS; + } + + return AVIERR_NONE; +} + + +/*------------------------------------------------- + parse_avih_chunk - parse an avih header + chunk +-------------------------------------------------*/ + +static avi_error parse_avih_chunk(avi_file *file, avi_chunk *avih) +{ + UINT8 *chunkdata = NULL; + avi_error avierr; + + /* read the data */ + avierr = read_chunk_data(file, avih, &chunkdata); + if (avierr != AVIERR_NONE) + goto error; + + /* extract the data */ + file->streams = fetch_32bits(&chunkdata[24]); + + /* allocate memory for the streams */ + file->stream = malloc(sizeof(*file->stream) * file->streams); + if (file->stream == NULL) + goto error; + memset(file->stream, 0, sizeof(*file->stream) * file->streams); + +error: + if (chunkdata != NULL) + free(chunkdata); + return avierr; +} + + +/*------------------------------------------------- + parse_strh_chunk - parse a strh header + chunk +-------------------------------------------------*/ + +static avi_error parse_strh_chunk(avi_file *file, avi_stream *stream, avi_chunk *strh) +{ + UINT8 *chunkdata = NULL; + avi_error avierr; + + /* read the data */ + avierr = read_chunk_data(file, strh, &chunkdata); + if (avierr != AVIERR_NONE) + goto error; + + /* extract the data */ + stream->type = fetch_32bits(&chunkdata[0]); + stream->scale = fetch_32bits(&chunkdata[20]); + stream->rate = fetch_32bits(&chunkdata[24]); + stream->samples = fetch_32bits(&chunkdata[32]); + +error: + if (chunkdata != NULL) + free(chunkdata); + return avierr; +} + + +/*------------------------------------------------- + parse_strf_chunk - parse a strf header + chunk +-------------------------------------------------*/ + +static avi_error parse_strf_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf) +{ + UINT8 *chunkdata = NULL; + avi_error avierr; + + /* read the data */ + avierr = read_chunk_data(file, strf, &chunkdata); + if (avierr != AVIERR_NONE) + goto error; + + /* audio and video streams have differing headers */ + if (stream->type == STREAMTYPE_VIDS) + { + stream->width = fetch_32bits(&chunkdata[4]); + stream->height = fetch_32bits(&chunkdata[8]); + stream->depth = fetch_16bits(&chunkdata[14]); + stream->format = fetch_32bits(&chunkdata[16]); + + /* extra extraction for HuffYUV data */ + if (stream->format == FORMAT_HFYU && strf->size >= 56) + { + avierr = huffyuv_extract_tables(stream, chunkdata, strf->size); + if (avierr != AVIERR_NONE) + goto error; + } + } + else if (stream->type == STREAMTYPE_AUDS) + { + stream->channels = fetch_16bits(&chunkdata[2]); + stream->samplebits = fetch_16bits(&chunkdata[14]); + stream->samplerate = fetch_32bits(&chunkdata[4]); + } + +error: + if (chunkdata != NULL) + free(chunkdata); + return avierr; +} + + +/*------------------------------------------------- + parse_indx_chunk - parse an indx chunk +-------------------------------------------------*/ + +static avi_error parse_indx_chunk(avi_file *file, avi_stream *stream, avi_chunk *strf) +{ + UINT32 entries, entry, id; + UINT8 *chunkdata = NULL; + UINT16 longs_per_entry; + UINT8 subtype, type; + UINT64 baseoffset; + avi_error avierr; + + /* read the data */ + avierr = read_chunk_data(file, strf, &chunkdata); + if (avierr != AVIERR_NONE) + goto error; + + /* extract the data */ + longs_per_entry = fetch_16bits(&chunkdata[0]); + subtype = chunkdata[2]; + type = chunkdata[3]; + entries = fetch_32bits(&chunkdata[4]); + id = fetch_32bits(&chunkdata[8]); + baseoffset = fetch_64bits(&chunkdata[12]); + + /* if this is a superindex, loop over entries and call ourselves recursively */ + if (type == AVI_INDEX_OF_INDEXES) + { + /* validate the size of each entry */ + if (longs_per_entry != 4) + return AVIERR_INVALID_DATA; + + /* loop over entries and create subchunks for each */ + for (entry = 0; entry < entries; entry++) + { + const UINT8 *base = &chunkdata[24 + entry * 16]; + file_error filerr; + avi_chunk subchunk; + UINT32 bytes_read; + UINT8 buffer[8]; + + /* go read the subchunk */ + subchunk.offset = fetch_64bits(&base[0]); + filerr = osd_read(file->file, buffer, subchunk.offset, sizeof(buffer), &bytes_read); + if (filerr != FILERR_NONE || bytes_read != sizeof(buffer)) + { + avierr = AVIERR_READ_ERROR; + break; + } + + /* parse the data */ + subchunk.type = fetch_32bits(&buffer[0]); + subchunk.size = fetch_32bits(&buffer[4]); + + /* recursively parse each referenced chunk; stop if we hit an error */ + avierr = parse_indx_chunk(file, stream, &subchunk); + if (avierr != AVIERR_NONE) + break; + } + } + + /* otherwise, this is a standard index */ + else if (type == AVI_INDEX_OF_CHUNKS) + { + /* validate the size of each entry */ + if (longs_per_entry != 2 && longs_per_entry != 3) + return AVIERR_INVALID_DATA; + + /* loop over entries and parse out the data */ + for (entry = 0; entry < entries; entry++) + { + const UINT8 *base = &chunkdata[24 + entry * 4 * longs_per_entry]; + UINT32 offset = fetch_32bits(&base[0]); + UINT32 size = fetch_32bits(&base[4]); + + /* set the info for this chunk and advance */ + avierr = set_stream_chunk_info(stream, stream->chunks++, baseoffset + offset - 8, size + 8); + if (avierr != AVIERR_NONE) + break; + } + } + +error: + if (chunkdata != NULL) + free(chunkdata); + return avierr; +} + + +/*------------------------------------------------- + parse_idx1_chunk - parse an idx1 chunk +-------------------------------------------------*/ + +static avi_error parse_idx1_chunk(avi_file *file, UINT64 baseoffset, avi_chunk *idx1) +{ + UINT8 *chunkdata = NULL; + avi_error avierr; + UINT32 entries; + UINT32 entry; + + /* read the data */ + avierr = read_chunk_data(file, idx1, &chunkdata); + if (avierr != AVIERR_NONE) + goto error; + + /* loop over entries */ + entries = idx1->size / 16; + for (entry = 0; entry < entries; entry++) + { + const UINT8 *base = &chunkdata[entry * 16]; + UINT32 chunkid = fetch_32bits(&base[0]); + UINT32 offset = fetch_32bits(&base[8]); + UINT32 size = fetch_32bits(&base[12]); + avi_stream *stream; + int streamnum; + + /* determine the stream index */ + streamnum = ((chunkid >> 8) & 0xff) - '0'; + streamnum += 10 * ((chunkid & 0xff) - '0'); + if (streamnum >= file->streams) + { + avierr = AVIERR_INVALID_DATA; + goto error; + } + stream = &file->stream[streamnum]; + + /* set the appropriate entry */ + avierr = set_stream_chunk_info(stream, stream->chunks++, baseoffset + offset, size + 8); + if (avierr != AVIERR_NONE) + goto error; + } + +error: + if (chunkdata != NULL) + free(chunkdata); + return avierr; +} + + +/*------------------------------------------------- + chunk_open - open a new chunk for writing +-------------------------------------------------*/ + +static avi_error chunk_open(avi_file *file, UINT32 type, UINT32 listtype, UINT32 estlength) +{ + file_error filerr; + avi_chunk *chunk; + UINT32 written; + + /* if we're out of stack entries, bail */ + if (file->chunksp >= ARRAY_LENGTH(file->chunkstack)) + return AVIERR_STACK_TOO_DEEP; + chunk = &file->chunkstack[file->chunksp++]; + + /* set up the chunk information */ + chunk->offset = file->writeoffs; + chunk->size = estlength; + chunk->type = type; + chunk->listtype = listtype; + + /* non-list types */ + if (type != CHUNKTYPE_RIFF && type != CHUNKTYPE_LIST) + { + UINT8 buffer[8]; + + /* populate the header */ + put_32bits(&buffer[0], chunk->type); + put_32bits(&buffer[4], chunk->size); + + /* write the header */ + filerr = osd_write(file->file, buffer, file->writeoffs, sizeof(buffer), &written); + if (filerr != FILERR_NONE || written != sizeof(buffer)) + return AVIERR_WRITE_ERROR; + file->writeoffs += written; + } + + /* list types */ + else + { + UINT8 buffer[12]; + + /* populate the header */ + put_32bits(&buffer[0], chunk->type); + put_32bits(&buffer[4], chunk->size); + put_32bits(&buffer[8], chunk->listtype); + + /* write the header */ + filerr = osd_write(file->file, buffer, file->writeoffs, sizeof(buffer), &written); + if (filerr != FILERR_NONE || written != sizeof(buffer)) + return AVIERR_WRITE_ERROR; + file->writeoffs += written; + } + + return AVIERR_NONE; +} + + +/*------------------------------------------------- + chunk_close - finish writing an chunk +-------------------------------------------------*/ + +static avi_error chunk_close(avi_file *file) +{ + avi_chunk *chunk = &file->chunkstack[--file->chunksp]; + UINT64 chunksize = file->writeoffs - (chunk->offset + 8); + UINT32 written; + + /* error if we don't fit into 32 bits */ + if (chunksize != (UINT32)chunksize) + return AVIERR_INVALID_DATA; + + /* write the final size if it is different from the guess */ + if (chunk->size != chunksize) + { + file_error filerr; + UINT8 buffer[4]; + + put_32bits(&buffer[0], (UINT32)chunksize); + filerr = osd_write(file->file, buffer, chunk->offset + 4, sizeof(buffer), &written); + if (filerr != FILERR_NONE || written != sizeof(buffer)) + return AVIERR_WRITE_ERROR; + } + + /* round up to the next word */ + file->writeoffs += chunksize & 1; + + return AVIERR_NONE; +} + + +/*------------------------------------------------- + chunk_write - write an chunk and its data +-------------------------------------------------*/ + +static avi_error chunk_write(avi_file *file, UINT32 type, const void *data, UINT32 length) +{ + file_error filerr; + avi_error avierr; + UINT32 written; + + /* if we are getting too big, split the RIFF */ + if ((file->riffbase == 0 && file->writeoffs + length + compute_idx1_size(file) >= MAX_RIFF_SIZE) || + file->writeoffs + length - file->riffbase >= MAX_RIFF_SIZE) + { + /* close the movi list */ + avierr = chunk_close(file); + if (avierr != AVIERR_NONE) + return avierr; + + /* write the idx1 chunk if this is the first */ + if (file->riffbase == 0) + { + avierr = write_idx1_chunk(file); + if (avierr != AVIERR_NONE) + return avierr; + } + + /* close the RIFF */ + avierr = chunk_close(file); + if (avierr != AVIERR_NONE) + return avierr; + + /* open a new RIFF */ + file->riffbase = file->writeoffs; + avierr = chunk_open(file, CHUNKTYPE_RIFF, LISTTYPE_AVIX, 0); + if (avierr != AVIERR_NONE) + return avierr; + + /* open a nested movi list */ + file->saved_movi_offset = file->writeoffs; + avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_MOVI, 0); + if (avierr != AVIERR_NONE) + return avierr; + } + + /* open the chunk */ + avierr = chunk_open(file, type, 0, length); + if (avierr != AVIERR_NONE) + return avierr; + + /* write the data */ + filerr = osd_write(file->file, data, file->writeoffs, length, &written); + if (filerr != FILERR_NONE || written != length) + return AVIERR_WRITE_ERROR; + file->writeoffs += written; + + /* close the chunk */ + return chunk_close(file); +} + + +/*------------------------------------------------- + chunk_overwrite - write a chunk in two passes; + first pass writes to the end of file and + saves the offset; second pass overwrites the + original +-------------------------------------------------*/ + +static avi_error chunk_overwrite(avi_file *file, UINT32 type, const void *data, UINT32 length, UINT64 *offset, int initial_write) +{ + UINT64 savedoffset = 0; + avi_error avierr; + + /* if this is our initial write, save the offset */ + if (initial_write) + *offset = file->writeoffs; + + /* otherwise, remember the current write offset and replace it with the original */ + else + { + savedoffset = file->writeoffs; + file->writeoffs = *offset; + } + + /* write the chunk */ + avierr = chunk_write(file, type, data, length); + + /* if this isn't the initial write, restore the previous offset */ + if (!initial_write) + file->writeoffs = savedoffset; + + return avierr; +} + + +/*------------------------------------------------- + write_initial_headers - write out the inital + set of AVI and stream headers +-------------------------------------------------*/ + +static avi_error write_initial_headers(avi_file *file) +{ + avi_error avierr; + int strnum; + + /* reset the write pointer */ + file->writeoffs = 0; + + /* open a RIFF chunk */ + avierr = chunk_open(file, CHUNKTYPE_RIFF, LISTTYPE_AVI, 0); + if (avierr != AVIERR_NONE) + return avierr; + + /* open a hdlr LIST */ + avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_HDRL, 0); + if (avierr != AVIERR_NONE) + return avierr; + + /* write an avih chunk */ + avierr = write_avih_chunk(file, TRUE); + if (avierr != AVIERR_NONE) + return avierr; + + /* for each stream, write a strl LIST */ + for (strnum = 0; strnum < file->streams; strnum++) + { + /* open a strl LIST */ + avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_STRL, 0); + if (avierr != AVIERR_NONE) + return avierr; + + /* write the strh chunk */ + avierr = write_strh_chunk(file, &file->stream[strnum], TRUE); + if (avierr != AVIERR_NONE) + return avierr; + + /* write the strf chunk */ + avierr = write_strf_chunk(file, &file->stream[strnum]); + if (avierr != AVIERR_NONE) + return avierr; + + /* write the indx chunk */ + avierr = write_indx_chunk(file, &file->stream[strnum], TRUE); + if (avierr != AVIERR_NONE) + return avierr; + + /* close the strl LIST */ + avierr = chunk_close(file); + if (avierr != AVIERR_NONE) + return avierr; + } + + /* close the hdlr LIST */ + avierr = chunk_close(file); + if (avierr != AVIERR_NONE) + return avierr; + + /* open a movi LIST */ + file->saved_movi_offset = file->writeoffs; + avierr = chunk_open(file, CHUNKTYPE_LIST, LISTTYPE_MOVI, 0); + if (avierr != AVIERR_NONE) + return avierr; + + return avierr; +} + + +/*------------------------------------------------- + write_avih_chunk - write the avih header + chunk +-------------------------------------------------*/ + +static avi_error write_avih_chunk(avi_file *file, int initial_write) +{ + avi_stream *video = get_video_stream(file); + UINT8 buffer[56]; + + /* reset the buffer */ + memset(buffer, 0, sizeof(buffer)); + + put_32bits(&buffer[0], 1000000 * (INT64)video->scale / video->rate); /* dwMicroSecPerFrame */ + put_32bits(&buffer[12], AVIF_HASINDEX | AVIF_ISINTERLEAVED); /* dwFlags */ + put_32bits(&buffer[16], video->samples); /* dwTotalFrames */ + put_32bits(&buffer[24], file->streams); /* dwStreams */ + put_32bits(&buffer[32], video->width); /* dwWidth */ + put_32bits(&buffer[36], video->height); /* dwHeight */ + + /* (over)write the chunk */ + return chunk_overwrite(file, CHUNKTYPE_AVIH, buffer, sizeof(buffer), &file->saved_avih_offset, initial_write); +} + + +/*------------------------------------------------- + write_strh_chunk - write the strh header + chunk +-------------------------------------------------*/ + +static avi_error write_strh_chunk(avi_file *file, avi_stream *stream, int initial_write) +{ + UINT8 buffer[56]; + + /* reset the buffer */ + memset(buffer, 0, sizeof(buffer)); + + put_32bits(&buffer[0], stream->type); /* fccType */ + put_32bits(&buffer[20], stream->scale); /* dwScale */ + put_32bits(&buffer[24], stream->rate); /* dwRate */ + put_32bits(&buffer[32], stream->samples); /* dwLength */ + put_32bits(&buffer[40], 10000); /* dwQuality */ + + /* video-stream specific data */ + if (stream->type == STREAMTYPE_VIDS) + { + put_32bits(&buffer[4], /* fccHandler */ + (stream->format == FORMAT_HFYU) ? HANDLER_HFYU : HANDLER_DIB); + put_32bits(&buffer[36], /* dwSuggestedBufferSize */ + stream->width * stream->height * 4); + put_16bits(&buffer[52], stream->width); /* rcFrame.right */ + put_16bits(&buffer[54], stream->height); /* rcFrame.bottom */ + } + + /* audio-stream specific data */ + if (stream->type == STREAMTYPE_AUDS) + { + put_32bits(&buffer[36], /* dwSuggestedBufferSize */ + stream->samplerate * stream->channels * (stream->samplebits / 8)); + put_32bits(&buffer[44], /* dwSampleSize */ + stream->channels * (stream->samplebits / 8)); + } + + /* write the chunk */ + return chunk_overwrite(file, CHUNKTYPE_STRH, buffer, sizeof(buffer), &stream->saved_strh_offset, initial_write); +} + + +/*------------------------------------------------- + write_strf_chunk - write the strf header + chunk +-------------------------------------------------*/ + +static avi_error write_strf_chunk(avi_file *file, avi_stream *stream) +{ + /* video stream */ + if (stream->type == STREAMTYPE_VIDS) + { + UINT8 buffer[40]; + + /* reset the buffer */ + memset(buffer, 0, sizeof(buffer)); + + put_32bits(&buffer[0], sizeof(buffer)); /* biSize */ + put_32bits(&buffer[4], stream->width); /* biWidth */ + put_32bits(&buffer[8], stream->height); /* biHeight */ + put_16bits(&buffer[12], 1); /* biPlanes */ + put_16bits(&buffer[14], stream->depth); /* biBitCount */ + put_32bits(&buffer[16], stream->format); /* biCompression */ + put_32bits(&buffer[20], /* biSizeImage */ + stream->width * stream->height * (stream->depth + 7) / 8); + + /* write the chunk */ + return chunk_write(file, CHUNKTYPE_STRF, buffer, sizeof(buffer)); + } + + /* audio stream */ + if (stream->type == STREAMTYPE_AUDS) + { + UINT8 buffer[16]; + + /* reset the buffer */ + memset(buffer, 0, sizeof(buffer)); + + put_16bits(&buffer[0], 1); /* wFormatTag */ + put_16bits(&buffer[2], stream->channels); /* nChannels */ + put_32bits(&buffer[4], stream->samplerate); /* nSamplesPerSec */ + put_32bits(&buffer[8], /* nAvgBytesPerSec */ + stream->samplerate * stream->channels * (stream->samplebits / 8)); + put_16bits(&buffer[12], /* nBlockAlign */ + stream->channels * (stream->samplebits / 8)); + put_16bits(&buffer[14], stream->samplebits); /* wBitsPerSample */ + + /* write the chunk */ + return chunk_write(file, CHUNKTYPE_STRF, buffer, sizeof(buffer)); + } + + return AVIERR_INVALID_DATA; +} + + +/*------------------------------------------------- + write_indx_chunk - write the indx header + chunk +-------------------------------------------------*/ + +static avi_error write_indx_chunk(avi_file *file, avi_stream *stream, int initial_write) +{ + UINT8 buffer[24 + 16 * MAX_AVI_SIZE_IN_GB / 4]; + UINT32 chunkid, indexchunkid; + UINT32 master_entries = 0; + + /* reset the buffer */ + memset(buffer, 0, sizeof(buffer)); + + /* construct the chunk ID and index chunk ID */ + chunkid = get_chunkid_for_stream(file, stream); + indexchunkid = AVI_FOURCC('i', 'x', '0' + (stream - file->stream) / 10, '0' + (stream - file->stream) % 10); + + /* loop over chunks of 4GB and write out indexes for them first */ + if (!initial_write && file->riffbase != 0) + { + UINT64 currentbase; + for (currentbase = 0; currentbase < file->writeoffs; currentbase += FOUR_GB) + { + UINT64 currentend = currentbase + FOUR_GB; + UINT32 chunks_this_index = 0; + UINT32 bytes_this_index = 0; + avi_error avierr; + UINT32 chunknum; + UINT8 *tempbuf; + + /* count chunks that are in this region */ + for (chunknum = 0; chunknum < stream->chunks; chunknum++) + if (stream->chunk[chunknum].offset >= currentbase && stream->chunk[chunknum].offset < currentend) + chunks_this_index++; + + /* if no chunks, skip */ + if (chunks_this_index == 0) + continue; + + /* allocate memory */ + tempbuf = malloc(24 + 8 * chunks_this_index); + if (tempbuf == NULL) + return AVIERR_NO_MEMORY; + memset(tempbuf, 0, 24 + 8 * chunks_this_index); + + /* make a regular index */ + put_16bits(&tempbuf[0], 2); /* wLongsPerEntry */ + tempbuf[2] = 0; /* bIndexSubType */ + tempbuf[3] = AVI_INDEX_OF_CHUNKS; /* bIndexType */ + put_32bits(&tempbuf[4], chunks_this_index); /* nEntriesInUse */ + put_32bits(&tempbuf[8], chunkid); /* dwChunkId */ + put_64bits(&tempbuf[12], currentbase); /* qwBaseOffset */ + + /* now fill in the indexes */ + chunks_this_index = 0; + for (chunknum = 0; chunknum < stream->chunks; chunknum++) + if (stream->chunk[chunknum].offset >= currentbase && stream->chunk[chunknum].offset < currentend) + { + put_32bits(&tempbuf[24 + 8 * chunks_this_index + 0], stream->chunk[chunknum].offset + 8 - currentbase); + put_32bits(&tempbuf[24 + 8 * chunks_this_index + 4], stream->chunk[chunknum].length - 8); + bytes_this_index += stream->chunk[chunknum].length; + chunks_this_index++; + } + + /* write the offset of this index to the master table */ + put_64bits(&buffer[24 + 16 * master_entries + 0], file->writeoffs); + put_32bits(&buffer[24 + 16 * master_entries + 8], 24 + 8 * chunks_this_index + 8); + if (stream->type == STREAMTYPE_VIDS) + put_32bits(&buffer[24 + 16 * master_entries + 12], chunks_this_index); + else if (stream->type == STREAMTYPE_AUDS) + put_32bits(&buffer[24 + 16 * master_entries + 12], bytes_this_index / ((stream->samplebits / 8) * stream->channels)); + master_entries++; + + /* write the index */ + avierr = chunk_write(file, indexchunkid, tempbuf, 24 + 8 * chunks_this_index); + free(tempbuf); + if (avierr != AVIERR_NONE) + return avierr; + } + } + + /* build up the master index */ + if (master_entries != 0) + { + put_16bits(&buffer[0], 4); /* wLongsPerEntry */ + buffer[2] = 0; /* bIndexSubType */ + buffer[3] = AVI_INDEX_OF_INDEXES; /* bIndexType */ + put_32bits(&buffer[4], master_entries); /* nEntriesInUse */ + put_32bits(&buffer[8], chunkid); /* dwChunkId */ + } + + /* (over)write the chunk */ + return chunk_overwrite(file, (master_entries == 0) ? CHUNKTYPE_JUNK : CHUNKTYPE_INDX, buffer, sizeof(buffer), &stream->saved_indx_offset, initial_write); +} + + +/*------------------------------------------------- + write_idx1_chunk - write the idx1 chunk +-------------------------------------------------*/ + +static avi_error write_idx1_chunk(avi_file *file) +{ + UINT32 tempbuflength = compute_idx1_size(file) - 8; + UINT32 curchunk[2] = { 0 }; + UINT32 curoffset; + avi_error avierr; + UINT8 *tempbuf; + + /* allocate a temporary buffer */ + tempbuf = malloc(tempbuflength); + if (tempbuf == NULL) + return AVIERR_NO_MEMORY; + + /* fill it in */ + for (curoffset = 0; curoffset < tempbuflength; curoffset += 16) + { + UINT64 minoffset = ~(UINT64)0; + int strnum, minstr = 0; + + /* determine which stream has the next chunk */ + for (strnum = 0; strnum < file->streams; strnum++) + if (curchunk[strnum] < file->stream[strnum].chunks && file->stream[strnum].chunk[curchunk[strnum]].offset < minoffset) + { + minoffset = file->stream[strnum].chunk[curchunk[strnum]].offset; + minstr = strnum; + } + + /* make an entry for this index */ + put_32bits(&tempbuf[curoffset + 0], get_chunkid_for_stream(file, &file->stream[minstr])); + put_32bits(&tempbuf[curoffset + 4], 0x0010 /* AVIIF_KEYFRAME */); + put_32bits(&tempbuf[curoffset + 8], minoffset - (file->saved_movi_offset + 8)); + put_32bits(&tempbuf[curoffset + 12], file->stream[minstr].chunk[curchunk[minstr]].length - 8); + + /* advance the chunk counter for this stream */ + curchunk[minstr]++; + } + + /* write the chunk */ + avierr = chunk_write(file, CHUNKTYPE_IDX1, tempbuf, tempbuflength); + free(tempbuf); + return avierr; +} + + +/*------------------------------------------------- + soundbuf_initialize - initialize the sound + buffering system +-------------------------------------------------*/ + +static avi_error soundbuf_initialize(avi_file *file) +{ + avi_stream *audio = get_audio_stream(file, 0, NULL); + avi_stream *video = get_video_stream(file); + + /* we require a video stream */ + if (video == NULL) + return AVIERR_UNSUPPORTED_VIDEO_FORMAT; + + /* skip if no audio stream */ + if (audio == NULL) + return AVIERR_NONE; + + /* determine the number of samples we want in our buffer; 2 seconds should be enough */ + file->soundbuf_samples = file->info.audio_samplerate * SOUND_BUFFER_MSEC / 1000; + + /* allocate a buffer */ + file->soundbuf = malloc(file->soundbuf_samples * file->info.audio_channels * sizeof(file->soundbuf[0])); + if (file->soundbuf == NULL) + return AVIERR_NO_MEMORY; + memset(file->soundbuf, 0, file->soundbuf_samples * file->info.audio_channels * sizeof(file->soundbuf[0])); + + /* determine the number of frames to be ahead (0.75secs) */ + file->soundbuf_frames = ((UINT64)video->rate * 75) / ((UINT64)video->scale * 100) + 1; + return AVIERR_NONE; +} + + +/*------------------------------------------------- + soundbuf_write_chunk - write the initial + chunk data +-------------------------------------------------*/ + +static avi_error soundbuf_write_chunk(avi_file *file, UINT32 framenum) +{ + avi_stream *stream = get_audio_stream(file, 0, NULL); + avi_error avierr; + UINT32 chunknum; + UINT32 length; + + /* skip if no audio stream */ + if (stream == NULL) + return AVIERR_NONE; + + /* determine the length of this chunk */ + if (framenum == 0) + length = framenum_to_samplenum(file, file->soundbuf_frames); + else + length = framenum_to_samplenum(file, framenum + 1 + file->soundbuf_frames) - framenum_to_samplenum(file, framenum + file->soundbuf_frames); + length *= stream->channels * sizeof(INT16); + + /* set the info for this new chunk */ + chunknum = stream->chunks; + avierr = set_stream_chunk_info(stream, chunknum, file->writeoffs, length + 8); + if (avierr != AVIERR_NONE) + return avierr; + + /* then do the initial write */ + return chunk_overwrite(file, get_chunkid_for_stream(file, stream), file->soundbuf, length, &stream->chunk[chunknum].offset, TRUE); +} + + +/*------------------------------------------------- + soundbuf_flush - flush data from the sound + buffers +-------------------------------------------------*/ + +static avi_error soundbuf_flush(avi_file *file, int only_flush_full) +{ + avi_stream *stream = get_audio_stream(file, 0, NULL); + INT32 channelsamples = file->soundbuf_samples; + INT32 processedsamples = 0; + UINT32 bytes_per_sample; + UINT32 finalchunks; + avi_error avierr; + UINT32 chunknum; + UINT32 chunkid; + int channel; + + /* skip if no stream */ + if (stream == NULL) + return AVIERR_NONE; + + /* get the chunk ID for this stream */ + chunkid = get_chunkid_for_stream(file, stream); + bytes_per_sample = stream->channels * sizeof(INT16); + finalchunks = stream->chunks; + + /* find out how many samples we've accumulated */ + for (channel = 0; channel < stream->channels; channel++) + channelsamples = MIN(channelsamples, file->soundbuf_chansamples[channel]); + + /* loop over pending sound chunks */ + for (chunknum = file->soundbuf_chunks; chunknum < stream->chunks; chunknum++) + { + avi_chunk_list *chunk = &stream->chunk[chunknum]; + UINT32 chunksamples = (chunk->length - 8) / bytes_per_sample; + + /* stop if we don't have enough to satisfy this chunk */ + if (only_flush_full && channelsamples < chunksamples) + break; + + /* if we don't have all the samples we need, pad with 0's */ + if (channelsamples > 0 && channelsamples < chunksamples) + { + if (processedsamples + chunksamples > file->soundbuf_samples) + return AVIERR_EXCEEDED_SOUND_BUFFER; + memset(&file->soundbuf[(processedsamples + channelsamples) * stream->channels], 0, (chunksamples - channelsamples) * bytes_per_sample); + } + + /* if we're completely out of samples, clear the buffer entirely and use the end */ + else if (channelsamples <= 0) + { + processedsamples = file->soundbuf_samples - chunksamples; + memset(&file->soundbuf[processedsamples * stream->channels], 0, chunksamples * bytes_per_sample); + chunkid = CHUNKTYPE_JUNK; + finalchunks--; + } + + /* copy the sample data in */ + avierr = chunk_overwrite(file, chunkid, &file->soundbuf[processedsamples * stream->channels], chunk->length - 8, &chunk->offset, FALSE); + if (avierr != AVIERR_NONE) + return avierr; + + /* add up the samples */ + if (channelsamples > 0) + file->info.audio_numsamples = stream->samples += channelsamples; + + /* advance past those */ + processedsamples += chunksamples; + channelsamples -= chunksamples; + channelsamples = MAX(0, channelsamples); + } + + /* if we have a non-zero offset, shift everything down */ + if (processedsamples > 0) + { + /* first account for the samples we processed */ + memmove(&file->soundbuf[0], &file->soundbuf[processedsamples * stream->channels], (file->soundbuf_samples - processedsamples) * bytes_per_sample); + for (channel = 0; channel < stream->channels; channel++) + file->soundbuf_chansamples[channel] -= processedsamples; + } + + /* update the final chunk count */ + if (!only_flush_full) + stream->chunks = finalchunks; + + /* account for flushed chunks */ + file->soundbuf_chunks = chunknum; + return AVIERR_NONE; +} + + +/*------------------------------------------------- + yuv_decompress_to_yuy16 - decompress a YUV + encoded frame to a YUY16 bitmap +-------------------------------------------------*/ + +static avi_error yuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap) +{ + const UINT16 *dataend = (const UINT16 *)(data + numbytes); + int x, y; + + /* compressed video */ + for (y = 0; y < stream->height; y++) + { + const UINT16 *source = (const UINT16 *)data + y * stream->width; + UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels; + + /* switch off the compression */ + switch (stream->format) + { + case FORMAT_UYVY: + for (x = 0; x < stream->width && source < dataend; x++) + *dest++ = *source++; + break; + + case FORMAT_VYUY: + case FORMAT_YUY2: + for (x = 0; x < stream->width && source < dataend; x++) + { + UINT16 pix = *source++; + *dest++ = (pix >> 8) | (pix << 8); + } + break; + } + } + + return AVIERR_NONE; +} + + +/*------------------------------------------------- + yuy16_compress_to_yuy - "compress" a YUY16 + bitmap to a YUV encoded frame +-------------------------------------------------*/ + +static avi_error yuy16_compress_to_yuy(avi_stream *stream, const bitmap_t *bitmap, UINT8 *data, UINT32 numbytes) +{ + const UINT16 *dataend = (const UINT16 *)(data + numbytes); + int x, y; + + /* compressed video */ + for (y = 0; y < stream->height; y++) + { + const UINT16 *source = (UINT16 *)bitmap->base + y * bitmap->rowpixels; + UINT16 *dest = (UINT16 *)data + y * stream->width; + + /* switch off the compression */ + switch (stream->format) + { + case FORMAT_UYVY: + for (x = 0; x < stream->width && dest < dataend; x++) + *dest++ = *source++; + break; + + case FORMAT_VYUY: + case FORMAT_YUY2: + for (x = 0; x < stream->width && source < dataend; x++) + { + UINT16 pix = *source++; + *dest++ = (pix >> 8) | (pix << 8); + } + break; + } + } + + return AVIERR_NONE; +} + + +/*------------------------------------------------- + huffyuv_extract_tables - extract HuffYUV + tables +-------------------------------------------------*/ + +static avi_error huffyuv_extract_tables(avi_stream *stream, const UINT8 *chunkdata, UINT32 size) +{ + const UINT8 *chunkend = chunkdata + size; + avi_error avierr = AVIERR_NONE; + int tabnum; + + /* allocate memory for the data */ + stream->huffyuv = malloc(sizeof(*stream->huffyuv)); + if (stream->huffyuv == NULL) + { + avierr = AVIERR_NO_MEMORY; + goto error; + } + + /* extract predictor information */ + if (&chunkdata[40] >= chunkend) + return AVIERR_INVALID_DATA; + stream->huffyuv->predictor = chunkdata[40]; + + /* make sure it's the left predictor */ + if ((stream->huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) != HUFFYUV_PREDICT_LEFT) + return AVIERR_UNSUPPORTED_VIDEO_FORMAT; + + /* make sure it's 16bpp YUV data */ + if (chunkdata[41] != 16) + return AVIERR_UNSUPPORTED_VIDEO_FORMAT; + chunkdata += 44; + + /* loop over tables */ + for (tabnum = 0; tabnum < 3; tabnum++) + { + huffyuv_table *table = &stream->huffyuv->table[tabnum]; + UINT32 curbits, bitadd; + UINT16 bitsat16 = 0; + int offset = 0, bits; + + /* loop until we populate the whole table */ + while (offset < 256) + { + int data, shift, count, i; + + /* extract the next run */ + if (chunkdata >= chunkend) + { + avierr = AVIERR_INVALID_DATA; + goto error; + } + data = *chunkdata++; + shift = data & 0x1f; + count = data >> 5; + + /* zero count means next whole byte is a count */ + if (count == 0) + { + if (chunkdata >= chunkend) + { + avierr = AVIERR_INVALID_DATA; + goto error; + } + count = *chunkdata++; + } + for (i = 0; i < count; i++) + table->shift[offset++] = shift; + } + + /* now determine bit patterns and masks */ + curbits = 0; + for (bits = 31; bits >= 0; bits--) + { + bitadd = 1 << (32 - bits); + + /* make sure we've cleared out all the bits below */ + if ((curbits & (bitadd - 1)) != 0) + { + avierr = AVIERR_INVALID_DATA; + goto error; + } + + /* find all entries with this shift count and assign them */ + for (offset = 0; offset < 256; offset++) + if (table->shift[offset] == bits) + { + table->bits[offset] = curbits; + table->mask[offset] = ~(bitadd - 1); + curbits += bitadd; + } + + /* remember the bit pattern when we complete all the 17-bit codes */ + if (bits == 17) + bitsat16 = curbits >> 16; + } + + /* allocate the number of extra lookup tables we need */ + if (bitsat16 > 0) + { + table->extralookup = malloc(bitsat16 * 65536 * sizeof(table->extralookup[0])); + if (table->extralookup == NULL) + { + avierr = AVIERR_NO_MEMORY; + goto error; + } + for (offset = 0; offset < bitsat16; offset++) + table->baselookup[offset] = (offset << 8) | 0; + } + + /* then create lookup tables */ + for (offset = 0; offset < 256; offset++) + if (table->shift[offset] > 16) + { + UINT16 *tablebase = table->extralookup + (table->bits[offset] >> 16) * 65536; + UINT32 start = table->bits[offset] & 0xffff; + UINT32 end = start + ((1 << (32 - table->shift[offset])) - 1); + while (start <= end) + tablebase[start++] = (offset << 8) | (table->shift[offset] - 16); + } + else if (table->shift[offset] > 0) + { + UINT32 start = table->bits[offset] >> 16; + UINT32 end = start + ((1 << (16 - table->shift[offset])) - 1); + while (start <= end) + table->baselookup[start++] = (offset << 8) | table->shift[offset]; + } + } + +error: + if (avierr != AVIERR_NONE && stream->huffyuv != NULL) + { + free(stream->huffyuv); + stream->huffyuv = NULL; + } + return avierr; +} + + +/*------------------------------------------------- + huffyuv_decompress_to_yuy16 - decompress a + HuffYUV-encoded frame to a YUY16 bitmap +-------------------------------------------------*/ + +static avi_error huffyuv_decompress_to_yuy16(avi_stream *stream, const UINT8 *data, UINT32 numbytes, bitmap_t *bitmap) +{ + huffyuv_data *huffyuv = stream->huffyuv; + int prevlines = (stream->height > 288) ? 2 : 1; + UINT8 lastprevy = 0, lastprevcb = 0, lastprevcr = 0; + UINT8 lasty = 0, lastcb = 0, lastcr = 0; + UINT8 bitsinbuffer = 0; + UINT32 bitbuffer = 0; + UINT32 dataoffs = 0; + int x, y; + + /* compressed video */ + for (y = 0; y < stream->height; y++) + { + UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels; + + /* handle the first four bytes independently */ + x = 0; + if (y == 0) + { + /* first DWORD is stored as YUY2 */ + lasty = data[dataoffs++]; + lastcb = data[dataoffs++]; + *dest++ = (lasty << 8) | lastcb; + lasty = data[dataoffs++]; + lastcr = data[dataoffs++]; + *dest++ = (lasty << 8) | lastcr; + x = 2; + } + + /* loop over pixels */ + for ( ; x < stream->width; x++) + { + huffyuv_table *ytable = &huffyuv->table[0]; + huffyuv_table *ctable = &huffyuv->table[1 + (x & 1)]; + UINT16 pixel, huffdata; + int shift; + + /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */ + while (bitsinbuffer <= 24 && dataoffs < numbytes) + { + bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer); + bitsinbuffer += 8; + } + + /* look up the Y component */ + huffdata = ytable->baselookup[bitbuffer >> 16]; + shift = huffdata & 0xff; + if (shift == 0) + { + bitsinbuffer -= 16; + bitbuffer <<= 16; + + /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */ + while (bitsinbuffer <= 24 && dataoffs < numbytes) + { + bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer); + bitsinbuffer += 8; + } + + huffdata = ytable->extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)]; + shift = huffdata & 0xff; + } + bitsinbuffer -= shift; + bitbuffer <<= shift; + pixel = huffdata & 0xff00; + + /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */ + while (bitsinbuffer <= 24 && dataoffs < numbytes) + { + bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer); + bitsinbuffer += 8; + } + + /* look up the Cb/Cr component */ + huffdata = ctable->baselookup[bitbuffer >> 16]; + shift = huffdata & 0xff; + if (shift == 0) + { + bitsinbuffer -= 16; + bitbuffer <<= 16; + + /* fill up the buffer; they store little-endian DWORDs, so we XOR with 3 */ + while (bitsinbuffer <= 24 && dataoffs < numbytes) + { + bitbuffer |= data[dataoffs++ ^ 3] << (24 - bitsinbuffer); + bitsinbuffer += 8; + } + + huffdata = ctable->extralookup[(huffdata >> 8) * 65536 + (bitbuffer >> 16)]; + shift = huffdata & 0xff; + } + bitsinbuffer -= shift; + bitbuffer <<= shift; + *dest++ = pixel | (huffdata >> 8); + } + } + + /* apply deltas */ + lasty = lastcb = lastcr = 0; + lastprevy = lastprevcb = lastprevcr = 0; + for (y = 0; y < stream->height; y++) + { + UINT16 *dest = (UINT16 *)bitmap->base + y * bitmap->rowpixels; + UINT16 *prevrow = dest - prevlines * bitmap->rowpixels; + + /* left predict or gradient predict */ + if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_LEFT || + ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT)) + { + /* first do left deltas */ + for (x = 0; x < stream->width; x += 2) + { + UINT16 pixel0 = dest[x + 0]; + UINT16 pixel1 = dest[x + 1]; + + lasty += pixel0 >> 8; + lastcb += pixel0; + dest[x + 0] = (lasty << 8) | lastcb; + + lasty += pixel1 >> 8; + lastcr += pixel1; + dest[x + 1] = (lasty << 8) | lastcr; + } + + /* for gradient, we then add in the previous row */ + if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_GRADIENT && y >= prevlines) + for (x = 0; x < stream->width; x++) + { + UINT16 curpix = dest[x]; + UINT16 prevpix = prevrow[x]; + UINT8 ysum = (curpix >> 8) + (prevpix >> 8); + UINT8 csum = curpix + prevpix; + dest[x] = (ysum << 8) | csum; + } + } + + /* median predict on rows > 0 */ + else if ((huffyuv->predictor & ~HUFFYUV_PREDICT_DECORR) == HUFFYUV_PREDICT_MEDIAN && y >= prevlines) + { + for (x = 0; x < stream->width; x += 2) + { + UINT16 prevpixel0 = prevrow[x + 0]; + UINT16 prevpixel1 = prevrow[x + 1]; + UINT16 pixel0 = dest[x + 0]; + UINT16 pixel1 = dest[x + 1]; + UINT8 a, b, c; + + /* compute previous, above, and (prev + above - above-left) */ + a = lasty; + b = prevpixel0 >> 8; + c = lastprevy; + lastprevy = b; + if (a > b) { UINT8 tmp = a; a = b; b = tmp; } + if (a > c) { UINT8 tmp = a; a = c; c = tmp; } + if (b > c) { UINT8 tmp = b; b = c; c = tmp; } + lasty = (pixel0 >> 8) + b; + + /* compute previous, above, and (prev + above - above-left) */ + a = lastcb; + b = prevpixel0 & 0xff; + c = lastprevcb; + lastprevcb = b; + if (a > b) { UINT8 tmp = a; a = b; b = tmp; } + if (a > c) { UINT8 tmp = a; a = c; c = tmp; } + if (b > c) { UINT8 tmp = b; b = c; c = tmp; } + lastcb = (pixel0 & 0xff) + b; + dest[x + 0] = (lasty << 8) | lastcb; + + /* compute previous, above, and (prev + above - above-left) */ + a = lasty; + b = prevpixel1 >> 8; + c = lastprevy; + lastprevy = b; + if (a > b) { UINT8 tmp = a; a = b; b = tmp; } + if (a > c) { UINT8 tmp = a; a = c; c = tmp; } + if (b > c) { UINT8 tmp = b; b = c; c = tmp; } + lasty = (pixel1 >> 8) + b; + + /* compute previous, above, and (prev + above - above-left) */ + a = lastcr; + b = prevpixel1 & 0xff; + c = lastprevcr; + lastprevcr = b; + if (a > b) { UINT8 tmp = a; a = b; b = tmp; } + if (a > c) { UINT8 tmp = a; a = c; c = tmp; } + if (b > c) { UINT8 tmp = b; b = c; c = tmp; } + lastcr = (pixel1 & 0xff) + b; + dest[x + 1] = (lasty << 8) | lastcr; + } + } + } + + return AVIERR_NONE; +} + + +static void u64toa(UINT64 val, char *output) +{ + UINT32 lo = (UINT32)(val & 0xffffffff); + UINT32 hi = (UINT32)(val >> 32); + if (hi != 0) + sprintf(output, "%X%08X", hi, lo); + else + sprintf(output, "%X", lo); +} + + +/*------------------------------------------------- + printf_chunk_recursive - print information + about a chunk recursively +-------------------------------------------------*/ + +static void printf_chunk_recursive(avi_file *file, avi_chunk *container, int indent) +{ + char size[20], offset[20]; + avi_chunk curchunk; + int avierr; + + /* iterate over chunks in this container */ + for (avierr = get_first_chunk(file, container, &curchunk); avierr == AVIERR_NONE; avierr = get_next_chunk(file, container, &curchunk)) + { + UINT32 chunksize = curchunk.size; + int recurse = FALSE; + + u64toa(curchunk.size, size); + u64toa(curchunk.offset, offset); + printf("%*schunk = %c%c%c%c, size=%s (%s)\n", indent, "", + (UINT8)(curchunk.type >> 0), + (UINT8)(curchunk.type >> 8), + (UINT8)(curchunk.type >> 16), + (UINT8)(curchunk.type >> 24), + size, offset); + + /* certain chunks are just containers; recurse into them */ + switch (curchunk.type) + { + /* basic containers */ + case CHUNKTYPE_RIFF: + case CHUNKTYPE_LIST: + printf("%*stype = %c%c%c%c\n", indent, "", + (UINT8)(curchunk.listtype >> 0), + (UINT8)(curchunk.listtype >> 8), + (UINT8)(curchunk.listtype >> 16), + (UINT8)(curchunk.listtype >> 24)); + recurse = TRUE; + chunksize = 0; + break; + } + + /* print data within the chunk */ + if (chunksize > 0 && curchunk.size < 1024 * 1024) + { + UINT8 *data = NULL; + int i; + + /* read the data for a chunk */ + avierr = read_chunk_data(file, &curchunk, &data); + if (avierr == AVIERR_NONE) + { + int bytes = MIN(512, chunksize); + for (i = 0; i < bytes; i++) + { + if (i % 16 == 0) printf("%*s ", indent, ""); + printf("%02X ", data[i]); + if (i % 16 == 15) printf("\n"); + } + if (chunksize % 16 != 0) printf("\n"); + free(data); + } + } + + /* if we're recursing, dive down */ + if (recurse) + printf_chunk_recursive(file, &curchunk, indent + 4); + } + + /* if we didn't get a legitimate error, indicate that */ + if (avierr != AVIERR_END) + printf("[chunk error %d]\n", avierr); +} diff --git a/src/lib/util/aviio.h b/src/lib/util/aviio.h new file mode 100644 index 00000000000..41f9eba826d --- /dev/null +++ b/src/lib/util/aviio.h @@ -0,0 +1,123 @@ +/*************************************************************************** + + aviio.h + + AVI movie format parsing helpers. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#ifndef __AVIIO_H__ + +#include "osdcore.h" +#include "bitmap.h" + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +enum _avi_error +{ + AVIERR_NONE = 0, + AVIERR_END, + AVIERR_INVALID_DATA, + AVIERR_NO_MEMORY, + AVIERR_READ_ERROR, + AVIERR_WRITE_ERROR, + AVIERR_STACK_TOO_DEEP, + AVIERR_UNSUPPORTED_FEATURE, + AVIERR_CANT_OPEN_FILE, + AVIERR_INCOMPATIBLE_AUDIO_STREAMS, + AVIERR_INVALID_SAMPLERATE, + AVIERR_INVALID_STREAM, + AVIERR_INVALID_FRAME, + AVIERR_INVALID_BITMAP, + AVIERR_UNSUPPORTED_VIDEO_FORMAT, + AVIERR_UNSUPPORTED_AUDIO_FORMAT, + AVIERR_EXCEEDED_SOUND_BUFFER +}; +typedef enum _avi_error avi_error; + + +enum _avi_datatype +{ + AVIDATA_VIDEO, + AVIDATA_AUDIO_CHAN0, + AVIDATA_AUDIO_CHAN1, + AVIDATA_AUDIO_CHAN2, + AVIDATA_AUDIO_CHAN3, + AVIDATA_AUDIO_CHAN4, + AVIDATA_AUDIO_CHAN5, + AVIDATA_AUDIO_CHAN6, + AVIDATA_AUDIO_CHAN7 +}; +typedef enum _avi_datatype avi_datatype; + + + +/*************************************************************************** + MACROS +***************************************************************************/ + +#define AVI_FOURCC(a,b,c,d) ((a) | ((b) << 8) | ((c) << 16) | ((d) << 24)) + +#define FORMAT_UYVY AVI_FOURCC('U','Y','V','Y') +#define FORMAT_VYUY AVI_FOURCC('V','Y','U','Y') +#define FORMAT_YUY2 AVI_FOURCC('Y','U','Y','2') +#define FORMAT_HFYU AVI_FOURCC('H','F','Y','U') + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _avi_file avi_file; + + +typedef struct _avi_movie_info avi_movie_info; +struct _avi_movie_info +{ + UINT32 video_format; /* format of video data */ + UINT32 video_timescale; /* timescale for video data */ + UINT32 video_sampletime; /* duration of a single video sample (frame) */ + UINT32 video_numsamples; /* total number of video samples */ + UINT32 video_width; /* width of the video */ + UINT32 video_height; /* height of the video */ + UINT32 video_depth; /* depth of the video */ + + UINT32 audio_format; /* format of audio data */ + UINT32 audio_timescale; /* timescale for audio data */ + UINT32 audio_sampletime; /* duration of a single audio sample */ + UINT32 audio_numsamples; /* total number of audio samples */ + UINT32 audio_channels; /* number of audio channels */ + UINT32 audio_samplebits; /* number of bits per channel */ + UINT32 audio_samplerate; /* sample rate of audio */ +}; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + +avi_error avi_open(const char *filename, avi_file **file); +avi_error avi_create(const char *filename, const avi_movie_info *info, avi_file **file); +avi_error avi_close(avi_file *file); + +void avi_printf_chunks(avi_file *file); +const char *avi_error_string(avi_error err); + +const avi_movie_info *avi_get_movie_info(avi_file *file); +UINT32 avi_first_sample_in_frame(avi_file *file, UINT32 framenum); + +avi_error avi_read_video_frame_yuy16(avi_file *file, UINT32 framenum, bitmap_t *bitmap); +avi_error avi_read_sound_samples(avi_file *file, int channel, UINT32 firstsample, UINT32 numsamples, INT16 *output); + +avi_error avi_append_video_frame_yuy16(avi_file *file, const bitmap_t *bitmap); +avi_error avi_append_sound_samples(avi_file *file, int channel, const INT16 *samples, UINT32 numsamples); + +#endif diff --git a/src/lib/util/bitmap.c b/src/lib/util/bitmap.c new file mode 100644 index 00000000000..a7e5d5e46f8 --- /dev/null +++ b/src/lib/util/bitmap.c @@ -0,0 +1,263 @@ +/*************************************************************************** + + bitmap.c + + Core bitmap routines. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include "bitmap.h" + + + +/*************************************************************************** + BITMAP ALLOCATION/CONFIGURATION +***************************************************************************/ + +/*------------------------------------------------- + bitmap_alloc -- allocate memory for a new + bitmap of the given format +-------------------------------------------------*/ + +bitmap_t *bitmap_alloc(int width, int height, bitmap_format format) +{ + return bitmap_alloc_slop(width, height, 0, 0, format); +} + + +/*------------------------------------------------- + bitmap_alloc_slop -- allocate a new bitmap with + additional slop on the borders +-------------------------------------------------*/ + +bitmap_t *bitmap_alloc_slop(int width, int height, int xslop, int yslop, bitmap_format format) +{ + int bpp = bitmap_format_to_bpp(format); + size_t allocbytes; + bitmap_t *bitmap; + int rowpixels; + + /* fail if invalid format */ + if (bpp == 0) + return NULL; + + /* allocate the bitmap itself */ + bitmap = malloc(sizeof(*bitmap)); + if (bitmap == NULL) + return NULL; + memset(bitmap, 0, sizeof(*bitmap)); + + /* round the width to a multiple of 8 and add some padding */ + rowpixels = (width + 2 * xslop + 7) & ~7; + + /* allocate memory for the bitmap itself */ + allocbytes = rowpixels * (height + 2 * yslop) * bpp / 8; + bitmap->alloc = malloc(allocbytes); + if (bitmap->alloc == NULL) + { + free(bitmap); + return NULL; + } + + /* clear to 0 by default */ + memset(bitmap->alloc, 0, allocbytes); + + /* fill in the data */ + bitmap->format = format; + bitmap->width = width; + bitmap->height = height; + bitmap->bpp = bpp; + bitmap->rowpixels = rowpixels; + bitmap->base = (UINT8 *)bitmap->alloc + (rowpixels * yslop + xslop) * (bpp / 8); + + return bitmap; +} + + +/*------------------------------------------------- + bitmap_wrap -- wrap an existing memory buffer + as a bitmap +-------------------------------------------------*/ + +bitmap_t *bitmap_wrap(void *base, int width, int height, int rowpixels, bitmap_format format) +{ + int bpp = bitmap_format_to_bpp(format); + bitmap_t *bitmap; + + /* fail if invalid format */ + if (bpp == 0) + return NULL; + + /* allocate memory */ + bitmap = malloc(sizeof(*bitmap)); + if (bitmap == NULL) + return NULL; + memset(bitmap, 0, sizeof(*bitmap)); + + /* fill in the data */ + bitmap->format = format; + bitmap->width = width; + bitmap->height = height; + bitmap->bpp = bpp; + bitmap->rowpixels = rowpixels; + bitmap->base = base; + + return bitmap; +} + + +/*------------------------------------------------- + bitmap_set_palette -- associate a palette with + a bitmap +-------------------------------------------------*/ + +void bitmap_set_palette(bitmap_t *bitmap, palette_t *palette) +{ + /* first dereference any existing palette */ + if (bitmap->palette != NULL) + { + palette_deref(bitmap->palette); + bitmap->palette = NULL; + } + + /* then reference any new palette */ + if (palette != NULL) + { + palette_ref(palette); + bitmap->palette = palette; + } +} + + +/*------------------------------------------------- + bitmap_free -- release memory allocated for + a bitmap +-------------------------------------------------*/ + +void bitmap_free(bitmap_t *bitmap) +{ + /* dereference the palette */ + if (bitmap->palette != NULL) + palette_deref(bitmap->palette); + + /* free any allocated memory */ + if (bitmap->alloc != NULL) + free(bitmap->alloc); + + /* free the bitmap */ + free(bitmap); +} + + + +/*************************************************************************** + BITMAP DRAWING +***************************************************************************/ + +/*------------------------------------------------- + bitmap_fill -- fill a bitmap with a solid + color +-------------------------------------------------*/ + +void bitmap_fill(bitmap_t *dest, const rectangle *cliprect, UINT32 color) +{ + rectangle fill; + int x, y; + + /* default to the whole bitmap */ + fill.min_x = fill.min_y = 0; + fill.max_x = dest->width - 1; + fill.max_y = dest->height - 1; + + /* if we have a cliprect, intersect with that */ + if (cliprect != NULL) + sect_rect(&fill, cliprect); + + /* early out if nothing to do */ + if (fill.min_x > fill.max_x || fill.min_y > fill.max_y) + return; + + /* based on the bpp go from there */ + switch (dest->bpp) + { + case 8: + /* 8bpp always uses memset */ + for (y = fill.min_y; y <= fill.max_y; y++) + memset(BITMAP_ADDR8(dest, y, fill.min_x), (UINT8)color, fill.max_x + 1 - fill.min_x); + break; + + case 16: + /* 16bpp can use memset if the bytes are equal */ + if ((UINT8)(color >> 8) == (UINT8)color) + { + for (y = fill.min_y; y <= fill.max_y; y++) + memset(BITMAP_ADDR16(dest, y, fill.min_x), (UINT8)color, (fill.max_x + 1 - fill.min_x) * 2); + } + else + { + for (y = fill.min_y; y <= fill.max_y; y++) + { + UINT16 *destrow = BITMAP_ADDR16(dest, y, 0); + for (x = fill.min_x; x <= fill.max_x; x++) + destrow[x] = (UINT16)color; + } + } + break; + + case 32: + /* 32bpp can use memset if the bytes are equal */ + if ((UINT8)(color >> 8) == (UINT8)color && (UINT16)(color >> 16) == (UINT16)color) + { + for (y = fill.min_y; y <= fill.max_y; y++) + memset(BITMAP_ADDR32(dest, y, fill.min_x), (UINT8)color, (fill.max_x + 1 - fill.min_x) * 4); + } + else + { + for (y = fill.min_y; y <= fill.max_y; y++) + { + UINT32 *destrow = BITMAP_ADDR32(dest, y, 0); + for (x = fill.min_x; x <= fill.max_x; x++) + destrow[x] = (UINT32)color; + } + } + break; + } +} + + + +/*************************************************************************** + BITMAP UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + bitmap_format_to_bpp - given a format, return + the bpp +-------------------------------------------------*/ + +int bitmap_format_to_bpp(bitmap_format format) +{ + /* choose a depth for the format */ + switch (format) + { + case BITMAP_FORMAT_INDEXED8: + return 8; + + case BITMAP_FORMAT_INDEXED16: + case BITMAP_FORMAT_RGB15: + case BITMAP_FORMAT_YUY16: + return 16; + + case BITMAP_FORMAT_INDEXED32: + case BITMAP_FORMAT_RGB32: + case BITMAP_FORMAT_ARGB32: + return 32; + + default: + break; + } + return 0; +} diff --git a/src/lib/util/bitmap.h b/src/lib/util/bitmap.h new file mode 100644 index 00000000000..5b4a5badbd0 --- /dev/null +++ b/src/lib/util/bitmap.h @@ -0,0 +1,169 @@ +/*************************************************************************** + + bitmap.h + + Core bitmap routines. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __BITMAP_H__ +#define __BITMAP_H__ + +#include "osdcore.h" +#include "palette.h" + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* bitmap_format describes the various bitmap formats we use */ +enum _bitmap_format +{ + BITMAP_FORMAT_INVALID = 0, /* invalid format */ + BITMAP_FORMAT_INDEXED8, /* 8bpp indexed */ + BITMAP_FORMAT_INDEXED16, /* 16bpp indexed */ + BITMAP_FORMAT_INDEXED32, /* 32bpp indexed */ + BITMAP_FORMAT_RGB15, /* 15bpp 5-5-5 RGB */ + BITMAP_FORMAT_RGB32, /* 32bpp 8-8-8 RGB */ + BITMAP_FORMAT_ARGB32, /* 32bpp 8-8-8-8 ARGB */ + BITMAP_FORMAT_YUY16, /* 16bpp 8-8 Y/Cb, Y/Cr in sequence */ + BITMAP_FORMAT_LAST +}; +typedef enum _bitmap_format bitmap_format; + + +/* rectangles describe a bitmap portion */ +typedef struct _rectangle rectangle; +struct _rectangle +{ + int min_x; /* minimum X, or left coordinate */ + int max_x; /* maximum X, or right coordinate (inclusive) */ + int min_y; /* minimum Y, or top coordinate */ + int max_y; /* maximum Y, or bottom coordinate (inclusive) */ +}; + + +/* bitmaps describe a rectangular array of pixels */ +typedef struct _bitmap_t bitmap_t; +struct _bitmap_t +{ + void * alloc; /* pointer to allocated pixel memory */ + void * base; /* pointer to pixel (0,0) (adjusted for padding) */ + int rowpixels; /* pixels per row (including padding) */ + int width; /* width of the bitmap */ + int height; /* height of the bitmap */ + bitmap_format format; /* format of the bitmap */ + int bpp; /* bits per pixel */ + palette_t * palette; /* optional palette */ +}; + + + +/*************************************************************************** + MACROS +***************************************************************************/ + +/* Macros for accessing bitmap pixels */ +#define BITMAP_ADDR(bitmap, type, y, x) \ + ((type *)(bitmap)->base + (y) * (bitmap)->rowpixels + (x)) + +#define BITMAP_ADDR8(bitmap, y, x) BITMAP_ADDR(bitmap, UINT8, y, x) +#define BITMAP_ADDR16(bitmap, y, x) BITMAP_ADDR(bitmap, UINT16, y, x) +#define BITMAP_ADDR32(bitmap, y, x) BITMAP_ADDR(bitmap, UINT32, y, x) + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + + +/* ----- bitmap allocation ----- */ + +/* allocate a new bitmap of the given dimensions and format */ +bitmap_t *bitmap_alloc(int width, int height, bitmap_format format); + +/* allocate a new bitmap with additional slop on the borders */ +bitmap_t *bitmap_alloc_slop(int width, int height, int xslop, int yslop, bitmap_format format); + +/* wrap a bitmap object around an existing array of data */ +bitmap_t *bitmap_wrap(void *base, int width, int height, int rowpixels, bitmap_format format); + +/* associate a palette with a bitmap */ +void bitmap_set_palette(bitmap_t *bitmap, palette_t *palette); + +/* free allocated data for a bitmap */ +void bitmap_free(bitmap_t *bitmap); + + + +/* ----- bitmap drawing ----- */ + +/* fill a bitmap with a single color, clipping to the given rectangle */ +void bitmap_fill(bitmap_t *dest, const rectangle *clip, rgb_t color); + + + +/* ----- bitmap utilities ----- */ + +/* return the number of bits per pixel for a given bitmap format */ +int bitmap_format_to_bpp(bitmap_format format); + + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + sect_rect - compute the intersection of two + rectangles +-------------------------------------------------*/ + +INLINE void sect_rect(rectangle *dst, const rectangle *src) +{ + if (src->min_x > dst->min_x) dst->min_x = src->min_x; + if (src->max_x < dst->max_x) dst->max_x = src->max_x; + if (src->min_y > dst->min_y) dst->min_y = src->min_y; + if (src->max_y < dst->max_y) dst->max_y = src->max_y; +} + + +/*------------------------------------------------- + union_rect - compute the union of two + rectangles +-------------------------------------------------*/ + +INLINE void union_rect(rectangle *dst, const rectangle *src) +{ + if (src->min_x < dst->min_x) dst->min_x = src->min_x; + if (src->max_x > dst->max_x) dst->max_x = src->max_x; + if (src->min_y < dst->min_y) dst->min_y = src->min_y; + if (src->max_y > dst->max_y) dst->max_y = src->max_y; +} + + +/*------------------------------------------------- + plot_box - draw a filled rectangle into a + bitmap of arbitrary depth +-------------------------------------------------*/ + +INLINE void plot_box(bitmap_t *bitmap, int x, int y, int width, int height, UINT32 color) +{ + rectangle clip; + clip.min_x = x; + clip.min_y = y; + clip.max_x = x + width - 1; + clip.max_y = y + height - 1; + bitmap_fill(bitmap, &clip, color); +} + + +#endif /* __BITMAP_H__ */ diff --git a/src/lib/util/cdrom.c b/src/lib/util/cdrom.c new file mode 100644 index 00000000000..82287ad30eb --- /dev/null +++ b/src/lib/util/cdrom.c @@ -0,0 +1,617 @@ +/*************************************************************************** + + Generic MAME CD-ROM utilties - build IDE and SCSI CD-ROMs on top of this + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +**************************************************************************** + + IMPORTANT: + "physical" block addresses are the actual addresses on the emulated CD. + "chd" block addresses are the block addresses in the CHD file. + Because we pad each track to a hunk boundry, these addressing + schemes will differ after track 1! + +***************************************************************************/ + +#include "cdrom.h" + + + +/*************************************************************************** + DEBUGGING +***************************************************************************/ + +#define VERBOSE (0) + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +struct _cdrom_file +{ + chd_file * chd; /* CHD file */ + cdrom_toc cdtoc; /* TOC for the CD */ + UINT32 hunksectors; /* sectors per hunk */ + UINT32 cachehunk; /* which hunk is cached */ + UINT8 * cache; /* cache of the current hunk */ +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +static chd_error read_sector_into_cache(cdrom_file *file, UINT32 lbasector, UINT32 *sectoroffs, UINT32 *tracknum); +static chd_error parse_metadata(chd_file *chd, cdrom_toc *toc); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + physical_to_chd_lba - find the CHD LBA + and the track number +-------------------------------------------------*/ + +INLINE UINT32 physical_to_chd_lba(cdrom_file *file, UINT32 physlba, UINT32 *tracknum) +{ + UINT32 chdlba; + int track; + + /* loop until our current LBA is less than the start LBA of the next track */ + for (track = 0; track < file->cdtoc.numtrks; track++) + if (physlba < file->cdtoc.tracks[track + 1].physframeofs) + { + chdlba = physlba - file->cdtoc.tracks[track].physframeofs + file->cdtoc.tracks[track].chdframeofs; + if (tracknum != NULL) + *tracknum = track; + return chdlba; + } + + return physlba; +} + + + +/*************************************************************************** + BASE FUNCTIONALITY +***************************************************************************/ + +/*------------------------------------------------- + cdrom_open - "open" a CD-ROM file from an + already-opened CHD file +-------------------------------------------------*/ + +cdrom_file *cdrom_open(chd_file *chd) +{ + const chd_header *header = chd_get_header(chd); + int i; + cdrom_file *file; + UINT32 physofs, chdofs; + chd_error err; + + /* punt if no CHD */ + if (!chd) + return NULL; + + /* validate the CHD information */ + if (header->hunkbytes % CD_FRAME_SIZE != 0) + return NULL; + + /* allocate memory for the CD-ROM file */ + file = malloc(sizeof(cdrom_file)); + if (file == NULL) + return NULL; + + /* fill in the data */ + file->chd = chd; + file->hunksectors = header->hunkbytes / CD_FRAME_SIZE; + file->cachehunk = -1; + + /* read the CD-ROM metadata */ + err = parse_metadata(chd, &file->cdtoc); + if (err != CHDERR_NONE) + { + free(file); + return NULL; + } + + #if VERBOSE + logerror("CD has %d tracks\n", file->cdtoc.numtrks); + #endif + + /* calculate the starting frame for each track, keeping in mind that CHDMAN + pads tracks out with extra frames to fit hunk size boundries + */ + physofs = chdofs = 0; + for (i = 0; i < file->cdtoc.numtrks; i++) + { + file->cdtoc.tracks[i].physframeofs = physofs; + file->cdtoc.tracks[i].chdframeofs = chdofs; + + physofs += file->cdtoc.tracks[i].frames; + chdofs += file->cdtoc.tracks[i].frames; + chdofs += file->cdtoc.tracks[i].extraframes; + + #if VERBOSE + logerror("Track %02d is format %d subtype %d datasize %d subsize %d frames %d extraframes %d physofs %d chdofs %d\n", i+1, + file->cdtoc.tracks[i].trktype, + file->cdtoc.tracks[i].subtype, + file->cdtoc.tracks[i].datasize, + file->cdtoc.tracks[i].subsize, + file->cdtoc.tracks[i].frames, + file->cdtoc.tracks[i].extraframes, + file->cdtoc.tracks[i].physframeofs, + file->cdtoc.tracks[i].chdframeofs); + #endif + } + + /* fill out dummy entries for the last track to help our search */ + file->cdtoc.tracks[i].physframeofs = physofs; + file->cdtoc.tracks[i].chdframeofs = chdofs; + + /* allocate a cache */ + file->cache = malloc(chd_get_header(chd)->hunkbytes); + if (file->cache == NULL) + { + free(file); + return NULL; + } + + return file; +} + + +/*------------------------------------------------- + cdrom_close - "close" a CD-ROM file +-------------------------------------------------*/ + +void cdrom_close(cdrom_file *file) +{ + if (file == NULL) + return; + + /* free the cache */ + if (file->cache) + free(file->cache); + free(file); +} + + + +/*************************************************************************** + CORE READ ACCESS +***************************************************************************/ + +/*------------------------------------------------- + cdrom_read_data - read one or more sectors + from a CD-ROM +-------------------------------------------------*/ + +UINT32 cdrom_read_data(cdrom_file *file, UINT32 lbasector, void *buffer, UINT32 datatype) +{ + UINT32 tracktype, tracknum, sectoroffs; + chd_error err; + + if (file == NULL) + return 0; + + /* cache in the sector */ + err = read_sector_into_cache(file, lbasector, §oroffs, &tracknum); + if (err != CHDERR_NONE) + return 0; + + /* copy out the requested sector */ + tracktype = file->cdtoc.tracks[tracknum].trktype; + if ((datatype == tracktype) || (datatype == CD_TRACK_RAW_DONTCARE)) + { + memcpy(buffer, &file->cache[sectoroffs * CD_FRAME_SIZE], file->cdtoc.tracks[tracknum].datasize); + } + else + { + /* return 2048 bytes of mode1 data from a 2336 byte mode 1 or 2 raw sector */ + if ((datatype == CD_TRACK_MODE1) && ((tracktype == CD_TRACK_MODE1_RAW) || (tracktype == CD_TRACK_MODE2_RAW))) + { + memcpy(buffer, &file->cache[(sectoroffs * CD_FRAME_SIZE) + 16], 2048); + return 1; + } + + /* return 2048 bytes of mode1 data from a mode2 form1 sector */ + if ((datatype == CD_TRACK_MODE1) && (tracktype == CD_TRACK_MODE2_FORM1)) + { + memcpy(buffer, &file->cache[(sectoroffs * CD_FRAME_SIZE) + 24], 2048); + return 1; + } + + /* return mode 2 2336 byte data from a RAW sector */ + if ((datatype == CD_TRACK_MODE2) && ((tracktype == CD_TRACK_MODE1_RAW) || (tracktype == CD_TRACK_MODE2_RAW))) + { + memcpy(buffer, &file->cache[(sectoroffs * CD_FRAME_SIZE) + 16], 2048); + return 1; + } + + #if VERBOSE + logerror("CDROM: Conversion from type %d to type %d not supported!\n", tracktype, datatype); + #endif + return 0; + } + return 1; +} + + +/*------------------------------------------------- + cdrom_read_subcode - read subcode data for + a sector +-------------------------------------------------*/ + +UINT32 cdrom_read_subcode(cdrom_file *file, UINT32 lbasector, void *buffer) +{ + UINT32 sectoroffs, tracknum; + chd_error err; + + if (file == NULL) + return ~0; + + /* cache in the sector */ + err = read_sector_into_cache(file, lbasector, §oroffs, &tracknum); + if (err != CHDERR_NONE) + return 0; + + /* copy out the requested data */ + memcpy(buffer, &file->cache[(sectoroffs * CD_FRAME_SIZE) + file->cdtoc.tracks[tracknum].datasize], file->cdtoc.tracks[tracknum].subsize); + return 1; +} + + + +/*************************************************************************** + HANDY UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + cdrom_get_track - get the track number + for a physical frame number +-------------------------------------------------*/ + +UINT32 cdrom_get_track(cdrom_file *file, UINT32 frame) +{ + UINT32 track = 0; + + if (file == NULL) + return ~0; + + /* convert to a CHD sector offset and get track information */ + physical_to_chd_lba(file, frame, &track); + return track; +} + + +/*------------------------------------------------- + cdrom_get_track_start - get the frame number + that a track starts at +-------------------------------------------------*/ + +UINT32 cdrom_get_track_start(cdrom_file *file, UINT32 track) +{ + if (file == NULL) + return ~0; + + /* handle lead-out specially */ + if (track == 0xaa) + track = file->cdtoc.numtrks; + + return file->cdtoc.tracks[track].physframeofs; +} + + + +/*************************************************************************** + TOC UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + cdrom_get_last_track - returns the last track + number +-------------------------------------------------*/ + +int cdrom_get_last_track(cdrom_file *file) +{ + if (file == NULL) + return -1; + + return file->cdtoc.numtrks; +} + + +/*------------------------------------------------- + cdrom_get_adr_control - get the ADR | CONTROL + for a track +-------------------------------------------------*/ + +int cdrom_get_adr_control(cdrom_file *file, int track) +{ + if (file == NULL) + return -1; + + if (track == 0xaa || file->cdtoc.tracks[track].trktype == CD_TRACK_AUDIO) + { + return 0x10; // audio track, subchannel is position + } + + return 0x14; // data track, subchannel is position +} + + +/*------------------------------------------------- + cdrom_get_track_type - return the track type +-------------------------------------------------*/ + +int cdrom_get_track_type(cdrom_file *file, int track) +{ + if (file == NULL) + return -1; + + return file->cdtoc.tracks[track].trktype; +} + + +/*------------------------------------------------- + cdrom_get_toc - return the TOC data for a + CD-ROM +-------------------------------------------------*/ + +const cdrom_toc *cdrom_get_toc(cdrom_file *file) +{ + if (file == NULL) + return NULL; + + return &file->cdtoc; +} + + + +/*************************************************************************** + EXTRA UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + cdrom_convert_type_string_to_track_info - + take a string and convert it into track type + and track data size +-------------------------------------------------*/ + +void cdrom_convert_type_string_to_track_info(const char *typestring, cdrom_track_info *info) +{ + if (!strcmp(typestring, "MODE1")) + { + info->trktype = CD_TRACK_MODE1; + info->datasize = 2048; + } + else if (!strcmp(typestring, "MODE1_RAW")) + { + info->trktype = CD_TRACK_MODE1_RAW; + info->datasize = 2352; + } + else if (!strcmp(typestring, "MODE2")) + { + info->trktype = CD_TRACK_MODE2; + info->datasize = 2336; + } + else if (!strcmp(typestring, "MODE2_FORM1")) + { + info->trktype = CD_TRACK_MODE2_FORM1; + info->datasize = 2048; + } + else if (!strcmp(typestring, "MODE2_FORM2")) + { + info->trktype = CD_TRACK_MODE2_FORM2; + info->datasize = 2324; + } + else if (!strcmp(typestring, "MODE2_FORM_MIX")) + { + info->trktype = CD_TRACK_MODE2_FORM_MIX; + info->datasize = 2336; + } + else if (!strcmp(typestring, "MODE2_RAW")) + { + info->trktype = CD_TRACK_MODE2_RAW; + info->datasize = 2352; + } + else if (!strcmp(typestring, "AUDIO")) + { + info->trktype = CD_TRACK_AUDIO; + info->datasize = 2352; + } +} + + +/*------------------------------------------------- + cdrom_convert_subtype_string_to_track_info - + take a string and convert it into track subtype + and track subcode data size +-------------------------------------------------*/ + +void cdrom_convert_subtype_string_to_track_info(const char *typestring, cdrom_track_info *info) +{ + if (!strcmp(typestring, "RW")) + { + info->subtype = CD_SUB_NORMAL; + info->subsize = 96; + } + else if (!strcmp(typestring, "RW_RAW")) + { + info->subtype = CD_SUB_RAW; + info->subsize = 96; + } +} + + +/*------------------------------------------------- + cdrom_get_type_string - get the string + associated with the given type +-------------------------------------------------*/ + +const char *cdrom_get_type_string(const cdrom_track_info *info) +{ + switch (info->trktype) + { + case CD_TRACK_MODE1: return "MODE1"; + case CD_TRACK_MODE1_RAW: return "MODE1_RAW"; + case CD_TRACK_MODE2: return "MODE2"; + case CD_TRACK_MODE2_FORM1: return "MODE2_FORM1"; + case CD_TRACK_MODE2_FORM2: return "MODE2_FORM2"; + case CD_TRACK_MODE2_FORM_MIX: return "MODE2_FORM_MIX"; + case CD_TRACK_MODE2_RAW: return "MODE2_RAW"; + case CD_TRACK_AUDIO: return "AUDIO"; + default: return "UNKNOWN"; + } +} + + +/*------------------------------------------------- + cdrom_get_subtype_string - get the string + associated with the given subcode type +-------------------------------------------------*/ + +const char *cdrom_get_subtype_string(const cdrom_track_info *info) +{ + switch (info->subtype) + { + case CD_SUB_NORMAL: return "RW"; + case CD_SUB_RAW: return "RW_RAW"; + default: return "NONE"; + } +} + + + +/*************************************************************************** + INTERNAL UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + read_sector_into_cache - cache a sector at + the given physical LBA +-------------------------------------------------*/ + +static chd_error read_sector_into_cache(cdrom_file *file, UINT32 lbasector, UINT32 *sectoroffs, UINT32 *tracknum) +{ + UINT32 chdsector, hunknum; + chd_error err; + + /* convert to a CHD sector offset and get track information */ + *tracknum = 0; + chdsector = physical_to_chd_lba(file, lbasector, tracknum); + hunknum = chdsector / file->hunksectors; + *sectoroffs = chdsector % file->hunksectors; + + /* if we haven't cached this hunk, read it now */ + if (file->cachehunk != hunknum) + { + err = chd_read(file->chd, hunknum, file->cache); + if (err != CHDERR_NONE) + return err; + file->cachehunk = hunknum; + } + return CHDERR_NONE; +} + + +/*------------------------------------------------- + parse_metadata - parse metadata into the TOC + structure +-------------------------------------------------*/ + +static chd_error parse_metadata(chd_file *chd, cdrom_toc *toc) +{ + static UINT32 oldmetadata[CD_METADATA_WORDS], *mrp; + const chd_header *header = chd_get_header(chd); + UINT32 hunksectors = header->hunkbytes / CD_FRAME_SIZE; + char metadata[256]; + chd_error err; + int i; + + /* start with no tracks */ + for (toc->numtrks = 0; toc->numtrks < CD_MAX_TRACKS; toc->numtrks++) + { + cdrom_track_info *track = &toc->tracks[toc->numtrks]; + int tracknum = -1, frames = 0, hunks; + char type[11], subtype[11]; + + /* fetch the metadata for this track */ + err = chd_get_metadata(chd, CDROM_TRACK_METADATA_TAG, toc->numtrks, metadata, sizeof(metadata), NULL, NULL); + if (err != CHDERR_NONE) + break; + + /* parse the metadata */ + type[0] = subtype[0] = 0; + if (sscanf(metadata, CDROM_TRACK_METADATA_FORMAT, &tracknum, type, subtype, &frames) != 4) + return CHDERR_INVALID_DATA; + if (tracknum != toc->numtrks + 1) + return CHDERR_INVALID_DATA; + + /* extract the track type and determine the data size */ + track->trktype = CD_TRACK_MODE1; + track->datasize = 0; + cdrom_convert_type_string_to_track_info(type, track); + if (track->datasize == 0) + return CHDERR_INVALID_DATA; + + /* extract the subtype and determine the subcode data size */ + track->subtype = CD_SUB_NONE; + track->subsize = 0; + cdrom_convert_subtype_string_to_track_info(subtype, track); + + /* set the frames and extra frames data */ + track->frames = frames; + hunks = (frames + hunksectors - 1) / hunksectors; + track->extraframes = hunks * hunksectors - frames; + } + + /* if we got any tracks this way, we're done */ + if (toc->numtrks > 0) + return CHDERR_NONE; + + /* look for old-style metadata */ + err = chd_get_metadata(chd, CDROM_OLD_METADATA_TAG, 0, oldmetadata, sizeof(oldmetadata), NULL, NULL); + if (err != CHDERR_NONE) + return err; + + /* reconstruct the TOC from it */ + mrp = &oldmetadata[0]; + toc->numtrks = *mrp++; + + for (i = 0; i < CD_MAX_TRACKS; i++) + { + toc->tracks[i].trktype = *mrp++; + toc->tracks[i].subtype = *mrp++; + toc->tracks[i].datasize = *mrp++; + toc->tracks[i].subsize = *mrp++; + toc->tracks[i].frames = *mrp++; + toc->tracks[i].extraframes = *mrp++; + } + + /* TODO: I don't know why sometimes the data is one endian and sometimes another */ + if ((toc->numtrks < 0) || (toc->numtrks > CD_MAX_TRACKS)) + { + toc->numtrks = FLIPENDIAN_INT32(toc->numtrks); + for (i = 0; i < CD_MAX_TRACKS; i++) + { + toc->tracks[i].trktype = FLIPENDIAN_INT32(toc->tracks[i].trktype); + toc->tracks[i].subtype = FLIPENDIAN_INT32(toc->tracks[i].subtype); + toc->tracks[i].datasize = FLIPENDIAN_INT32(toc->tracks[i].datasize); + toc->tracks[i].subsize = FLIPENDIAN_INT32(toc->tracks[i].subsize); + toc->tracks[i].frames = FLIPENDIAN_INT32(toc->tracks[i].frames); + toc->tracks[i].extraframes = FLIPENDIAN_INT32(toc->tracks[i].extraframes); + } + } + + return CHDERR_NONE; +} diff --git a/src/lib/util/cdrom.h b/src/lib/util/cdrom.h new file mode 100644 index 00000000000..75f12e7099a --- /dev/null +++ b/src/lib/util/cdrom.h @@ -0,0 +1,139 @@ +/*************************************************************************** + + cdrom.h + + Generic MAME cd-rom implementation + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __CDROM_H__ +#define __CDROM_H__ + +#include "osdcore.h" +#include "chd.h" + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define CD_MAX_TRACKS (99) /* AFAIK the theoretical limit */ +#define CD_MAX_SECTOR_DATA (2352) +#define CD_MAX_SUBCODE_DATA (96) + +#define CD_FRAME_SIZE (CD_MAX_SECTOR_DATA + CD_MAX_SUBCODE_DATA) +#define CD_FRAMES_PER_HUNK (4) + +#define CD_METADATA_WORDS (1+(CD_MAX_TRACKS * 6)) +enum +{ + CD_TRACK_MODE1 = 0, /* mode 1 2048 bytes/sector */ + CD_TRACK_MODE1_RAW, /* mode 1 2352 bytes/sector */ + CD_TRACK_MODE2, /* mode 2 2336 bytes/sector */ + CD_TRACK_MODE2_FORM1, /* mode 2 2048 bytes/sector */ + CD_TRACK_MODE2_FORM2, /* mode 2 2324 bytes/sector */ + CD_TRACK_MODE2_FORM_MIX, /* mode 2 2336 bytes/sector */ + CD_TRACK_MODE2_RAW, /* mode 2 2352 bytes / sector */ + CD_TRACK_AUDIO, /* redbook audio track 2352 bytes/sector (588 samples) */ + + CD_TRACK_RAW_DONTCARE /* special flag for cdrom_read_data: just return me whatever is there */ +}; + +enum +{ + CD_SUB_NORMAL = 0, /* "cooked" 96 bytes per sector */ + CD_SUB_RAW, /* raw uninterleaved 96 bytes per sector */ + CD_SUB_NONE /* no subcode data stored */ +}; + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _cdrom_file cdrom_file; + + +typedef struct _cdrom_track_info cdrom_track_info; +struct _cdrom_track_info +{ + /* fields used by CHDMAN and in MAME */ + UINT32 trktype; /* track type */ + UINT32 subtype; /* subcode data type */ + UINT32 datasize; /* size of data in each sector of this track */ + UINT32 subsize; /* size of subchannel data in each sector of this track */ + UINT32 frames; /* number of frames in this track */ + UINT32 extraframes; /* number of "spillage" frames in this track */ + + /* fields used in MAME only */ + UINT32 physframeofs; /* frame number on the real CD this track starts at */ + UINT32 chdframeofs; /* frame number this track starts at on the CHD */ +}; + + +typedef struct _cdrom_toc cdrom_toc; +struct _cdrom_toc +{ + UINT32 numtrks; /* number of tracks */ + cdrom_track_info tracks[CD_MAX_TRACKS]; +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +/* base functionality */ +cdrom_file *cdrom_open(chd_file *chd); +void cdrom_close(cdrom_file *file); + +/* core read access */ +UINT32 cdrom_read_data(cdrom_file *file, UINT32 lbasector, void *buffer, UINT32 datatype); +UINT32 cdrom_read_subcode(cdrom_file *file, UINT32 lbasector, void *buffer); + +/* handy utilities */ +UINT32 cdrom_get_track(cdrom_file *file, UINT32 frame); +UINT32 cdrom_get_track_start(cdrom_file *file, UINT32 track); + +/* TOC utilities */ +int cdrom_get_last_track(cdrom_file *file); +int cdrom_get_adr_control(cdrom_file *file, int track); +int cdrom_get_track_type(cdrom_file *file, int track); +const cdrom_toc *cdrom_get_toc(cdrom_file *file); + +/* extra utilities */ +void cdrom_convert_type_string_to_track_info(const char *typestring, cdrom_track_info *info); +void cdrom_convert_subtype_string_to_track_info(const char *typestring, cdrom_track_info *info); +const char *cdrom_get_type_string(const cdrom_track_info *info); +const char *cdrom_get_subtype_string(const cdrom_track_info *info); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +INLINE UINT32 lba_to_msf(UINT32 lba) +{ + UINT8 m, s, f; + + m = lba / (60 * 75); + lba -= m * (60 * 75); + s = lba / 75; + f = lba % 75; + + return ((m / 10) << 20) | ((m % 10) << 16) | + ((s / 10) << 12) | ((s % 10) << 8) | + ((f / 10) << 4) | ((f % 10) << 0); +} + +#endif // __CDROM_H__ + diff --git a/src/lib/util/chd.c b/src/lib/util/chd.c new file mode 100644 index 00000000000..b36bd5c70fe --- /dev/null +++ b/src/lib/util/chd.c @@ -0,0 +1,2860 @@ +/*************************************************************************** + + MAME Compressed Hunks of Data file format + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include "chd.h" +#include "avcomp.h" +#include "md5.h" +#include "sha1.h" +#include <zlib.h> +#include <time.h> + + + +/*************************************************************************** + DEBUGGING +***************************************************************************/ + +#define PRINTF_MAX_HUNK (0) + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define MAP_STACK_ENTRIES 512 /* max number of entries to use on the stack */ +#define MAP_ENTRY_SIZE 16 /* V3 and later */ +#define OLD_MAP_ENTRY_SIZE 8 /* V1-V2 */ +#define METADATA_HEADER_SIZE 16 /* metadata header size */ +#define CRCMAP_HASH_SIZE 4095 /* number of CRC hashtable entries */ + +#define MAP_ENTRY_FLAG_TYPE_MASK 0x0f /* what type of hunk */ +#define MAP_ENTRY_FLAG_NO_CRC 0x10 /* no CRC is present */ + +#define MAP_ENTRY_TYPE_INVALID 0x00 /* invalid type */ +#define MAP_ENTRY_TYPE_COMPRESSED 0x01 /* standard compression */ +#define MAP_ENTRY_TYPE_UNCOMPRESSED 0x02 /* uncompressed data */ +#define MAP_ENTRY_TYPE_MINI 0x03 /* mini: use offset as raw data */ +#define MAP_ENTRY_TYPE_SELF_HUNK 0x04 /* same as another hunk in this file */ +#define MAP_ENTRY_TYPE_PARENT_HUNK 0x05 /* same as a hunk in the parent file */ + +#define CHD_V1_SECTOR_SIZE 512 /* size of a "sector" in the V1 header */ + +#define COOKIE_VALUE 0xbaadf00d +#define MAX_ZLIB_ALLOCS 64 + +#define END_OF_LIST_COOKIE "EndOfListCookie" + +#define NO_MATCH (~0) + + + +/*************************************************************************** + MACROS +***************************************************************************/ + +#define EARLY_EXIT(x) do { (void)(x); goto cleanup; } while (0) + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* interface to a codec */ +typedef struct _codec_interface codec_interface; +struct _codec_interface +{ + UINT32 compression; /* type of compression */ + const char *compname; /* name of the algorithm */ + UINT8 lossy; /* is this a lossy algorithm? */ + chd_error (*init)(chd_file *chd); /* codec initialize */ + void (*free)(chd_file *chd); /* codec free */ + chd_error (*compress)(chd_file *chd, const void *src, UINT32 *complen); /* compress data */ + chd_error (*decompress)(chd_file *chd, UINT32 complen, void *dst); /* decompress data */ + chd_error (*config)(chd_file *chd, int param, void *config); /* configure */ +}; + + +/* a single map entry */ +typedef struct _map_entry map_entry; +struct _map_entry +{ + UINT64 offset; /* offset within the file of the data */ + UINT32 crc; /* 32-bit CRC of the data */ + UINT32 length; /* length of the data */ + UINT8 flags; /* misc flags */ +}; + + +/* simple linked-list of hunks used for our CRC map */ +typedef struct _crcmap_entry crcmap_entry; +struct _crcmap_entry +{ + UINT32 hunknum; /* hunk number */ + crcmap_entry * next; /* next entry in list */ +}; + + +/* a single metadata entry */ +typedef struct _metadata_entry metadata_entry; +struct _metadata_entry +{ + UINT64 offset; /* offset within the file of the header */ + UINT64 next; /* offset within the file of the next header */ + UINT64 prev; /* offset within the file of the previous header */ + UINT32 length; /* length of the metadata */ + UINT32 metatag; /* metadata tag */ +}; + + +/* internal representation of an open CHD file */ +struct _chd_file +{ + UINT32 cookie; /* cookie, should equal COOKIE_VALUE */ + + core_file * file; /* handle to the open core file */ + UINT8 owns_file; /* flag indicating if this file should be closed on chd_close() */ + chd_header header; /* header, extracted from file */ + + chd_file * parent; /* pointer to parent file, or NULL */ + + map_entry * map; /* array of map entries */ + + UINT8 * cache; /* hunk cache pointer */ + UINT32 cachehunk; /* index of currently cached hunk */ + + UINT8 * compare; /* hunk compare pointer */ + UINT32 comparehunk; /* index of current compare data */ + + UINT8 * compressed; /* pointer to buffer for compressed data */ + const codec_interface * codecintf; /* interface to the codec */ + void * codecdata; /* opaque pointer to codec data */ + + crcmap_entry * crcmap; /* CRC map entries */ + crcmap_entry * crcfree; /* free list CRC entries */ + crcmap_entry ** crctable; /* table of CRC entries */ + + UINT32 maxhunk; /* maximum hunk accessed */ + + UINT8 compressing; /* are we compressing? */ + struct MD5Context compmd5; /* running MD5 during compression */ + struct sha1_ctx compsha1; /* running SHA1 during compression */ + UINT32 comphunk; /* next hunk we will compress */ + + UINT8 verifying; /* are we verifying? */ + struct MD5Context vermd5; /* running MD5 during verification */ + struct sha1_ctx versha1; /* running SHA1 during verification */ + UINT32 verhunk; /* next hunk we will verify */ + + osd_work_queue * workqueue; /* pointer to work queue for async operations */ + osd_work_item * workitem; /* active work item, or NULL if none */ + UINT32 async_hunknum; /* hunk index for asynchronous operations */ + void * async_buffer; /* buffer pointer for asynchronous operations */ +}; + + +/* codec-private data for the ZLIB codec */ +typedef struct _zlib_codec_data zlib_codec_data; +struct _zlib_codec_data +{ + z_stream inflater; + z_stream deflater; + UINT32 * allocptr[MAX_ZLIB_ALLOCS]; +}; + + +/* codec-private data for the A/V codec */ +typedef struct _av_codec_data av_codec_data; +struct _av_codec_data +{ + avcomp_state * compstate; + av_codec_decompress_config decompconfig; +}; + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +static const UINT8 nullmd5[CHD_MD5_BYTES] = { 0 }; +static const UINT8 nullsha1[CHD_SHA1_BYTES] = { 0 }; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + +/* internal async operations */ +static void *async_read_callback(void *param, int threadid); +static void *async_write_callback(void *param, int threadid); + +/* internal header operations */ +static chd_error header_validate(const chd_header *header); +static chd_error header_read(core_file *file, chd_header *header); +static chd_error header_write(core_file *file, const chd_header *header); + +/* internal hunk read/write */ +static chd_error hunk_read_into_cache(chd_file *chd, UINT32 hunknum); +static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *dest); +static chd_error hunk_write_from_memory(chd_file *chd, UINT32 hunknum, const UINT8 *src); + +/* internal map access */ +static chd_error map_write_initial(core_file *file, chd_file *parent, const chd_header *header); +static chd_error map_read(chd_file *chd); + +/* internal CRC map access */ +static void crcmap_init(chd_file *chd, int prepopulate); +static void crcmap_add_entry(chd_file *chd, UINT32 hunknum); +static UINT32 crcmap_find_hunk(chd_file *chd, UINT32 hunknum, UINT32 crc, const UINT8 *rawdata); + +/* metadata management */ +static chd_error metadata_find_entry(chd_file *chd, UINT32 metatag, UINT32 metaindex, metadata_entry *metaentry); +static chd_error metadata_set_previous_next(chd_file *chd, UINT64 prevoffset, UINT64 nextoffset); +static chd_error metadata_set_length(chd_file *chd, UINT64 offset, UINT32 length); + +/* zlib compression codec */ +static chd_error zlib_codec_init(chd_file *chd); +static void zlib_codec_free(chd_file *chd); +static chd_error zlib_codec_compress(chd_file *chd, const void *src, UINT32 *length); +static chd_error zlib_codec_decompress(chd_file *chd, UINT32 srclength, void *dest); +static voidpf zlib_fast_alloc(voidpf opaque, uInt items, uInt size); +static void zlib_fast_free(voidpf opaque, voidpf address); + +/* A/V compression codec */ +static chd_error av_codec_init(chd_file *chd); +static void av_codec_free(chd_file *chd); +static chd_error av_codec_compress(chd_file *chd, const void *src, UINT32 *length); +static chd_error av_codec_decompress(chd_file *chd, UINT32 srclength, void *dest); +static chd_error av_codec_config(chd_file *chd, int param, void *config); +static chd_error av_codec_postinit(chd_file *chd); + + + +/*************************************************************************** + CODEC INTERFACES +***************************************************************************/ + +static const codec_interface codec_interfaces[] = +{ + /* "none" or no compression */ + { + CHDCOMPRESSION_NONE, + "none", + FALSE, + NULL, + NULL, + NULL, + NULL, + NULL + }, + + /* standard zlib compression */ + { + CHDCOMPRESSION_ZLIB, + "zlib", + FALSE, + zlib_codec_init, + zlib_codec_free, + zlib_codec_compress, + zlib_codec_decompress, + NULL + }, + + /* zlib+ compression */ + { + CHDCOMPRESSION_ZLIB_PLUS, + "zlib+", + FALSE, + zlib_codec_init, + zlib_codec_free, + zlib_codec_compress, + zlib_codec_decompress, + NULL + }, + + /* a/v compression */ + { + CHDCOMPRESSION_AV, + "A/V", + TRUE, + av_codec_init, + av_codec_free, + av_codec_compress, + av_codec_decompress, + av_codec_config + } +}; + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + get_bigendian_uint64 - fetch a UINT64 from + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE UINT64 get_bigendian_uint64(const UINT8 *base) +{ + return ((UINT64)base[0] << 56) | ((UINT64)base[1] << 48) | ((UINT64)base[2] << 40) | ((UINT64)base[3] << 32) | + ((UINT64)base[4] << 24) | ((UINT64)base[5] << 16) | ((UINT64)base[6] << 8) | (UINT64)base[7]; +} + + +/*------------------------------------------------- + put_bigendian_uint64 - write a UINT64 to + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE void put_bigendian_uint64(UINT8 *base, UINT64 value) +{ + base[0] = value >> 56; + base[1] = value >> 48; + base[2] = value >> 40; + base[3] = value >> 32; + base[4] = value >> 24; + base[5] = value >> 16; + base[6] = value >> 8; + base[7] = value; +} + + +/*------------------------------------------------- + get_bigendian_uint32 - fetch a UINT32 from + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE UINT32 get_bigendian_uint32(const UINT8 *base) +{ + return (base[0] << 24) | (base[1] << 16) | (base[2] << 8) | base[3]; +} + + +/*------------------------------------------------- + put_bigendian_uint32 - write a UINT32 to + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE void put_bigendian_uint32(UINT8 *base, UINT32 value) +{ + base[0] = value >> 24; + base[1] = value >> 16; + base[2] = value >> 8; + base[3] = value; +} + + +/*------------------------------------------------- + get_bigendian_uint16 - fetch a UINT16 from + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE UINT16 get_bigendian_uint16(const UINT8 *base) +{ + return (base[0] << 8) | base[1]; +} + + +/*------------------------------------------------- + put_bigendian_uint16 - write a UINT16 to + the data stream in bigendian order +-------------------------------------------------*/ + +INLINE void put_bigendian_uint16(UINT8 *base, UINT16 value) +{ + base[0] = value >> 8; + base[1] = value; +} + + +/*------------------------------------------------- + map_extract - extract a single map + entry from the datastream +-------------------------------------------------*/ + +INLINE void map_extract(const UINT8 *base, map_entry *entry) +{ + entry->offset = get_bigendian_uint64(&base[0]); + entry->crc = get_bigendian_uint32(&base[8]); + entry->length = get_bigendian_uint16(&base[12]) | (base[14] << 16); + entry->flags = base[15]; +} + + +/*------------------------------------------------- + map_assemble - write a single map + entry to the datastream +-------------------------------------------------*/ + +INLINE void map_assemble(UINT8 *base, map_entry *entry) +{ + put_bigendian_uint64(&base[0], entry->offset); + put_bigendian_uint32(&base[8], entry->crc); + put_bigendian_uint16(&base[12], entry->length); + base[14] = entry->length >> 16; + base[15] = entry->flags; +} + + +/*------------------------------------------------- + map_extract_old - extract a single map + entry in old format from the datastream +-------------------------------------------------*/ + +INLINE void map_extract_old(const UINT8 *base, map_entry *entry, UINT32 hunkbytes) +{ + entry->offset = get_bigendian_uint64(&base[0]); + entry->crc = 0; + entry->length = entry->offset >> 44; + entry->flags = MAP_ENTRY_FLAG_NO_CRC | ((entry->length == hunkbytes) ? MAP_ENTRY_TYPE_UNCOMPRESSED : MAP_ENTRY_TYPE_COMPRESSED); +#ifdef __MWERKS__ + entry->offset = entry->offset & 0x00000FFFFFFFFFFFLL; +#else + entry->offset = (entry->offset << 20) >> 20; +#endif +} + + +/*------------------------------------------------- + queue_async_operation - queue a new work + item +-------------------------------------------------*/ + +INLINE int queue_async_operation(chd_file *chd, osd_work_callback callback) +{ + /* if no queue yet, create one on the fly */ + if (chd->workqueue == NULL) + { + chd->workqueue = osd_work_queue_alloc(WORK_QUEUE_FLAG_IO); + if (chd->workqueue == NULL) + return FALSE; + } + + /* make sure we cleared out the previous item */ + if (chd->workitem != NULL) + return FALSE; + + /* create a new work item to run the job */ + chd->workitem = osd_work_item_queue(chd->workqueue, callback, chd, 0); + if (chd->workitem == NULL) + return FALSE; + + return TRUE; +} + + +/*------------------------------------------------- + wait_for_pending_async - wait for any pending + async +-------------------------------------------------*/ + +INLINE void wait_for_pending_async(chd_file *chd) +{ + /* if something is pending, wait for it */ + if (chd->workitem != NULL) + { + /* 10 seconds should be enough for anything! */ + int wait_successful = osd_work_item_wait(chd->workitem, 10 * osd_ticks_per_second()); + if (!wait_successful) + osd_break_into_debugger("Pending async operation never completed!"); + } +} + + + +/*************************************************************************** + CHD FILE MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + chd_create_file - create a new CHD file +-------------------------------------------------*/ + +chd_error chd_create_file(core_file *file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent) +{ + chd_file *newchd = NULL; + chd_header header; + chd_error err; + int intfnum; + + /* verify parameters */ + if (file == NULL) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (parent == NULL && (logicalbytes == 0 || hunkbytes == 0)) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* verify the compression type */ + for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) + if (codec_interfaces[intfnum].compression == compression) + break; + if (intfnum == ARRAY_LENGTH(codec_interfaces)) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* if we have a parent, the sizes come from there */ + if (parent != NULL) + { + logicalbytes = parent->header.logicalbytes; + hunkbytes = parent->header.hunkbytes; + } + + /* if we have a parent, it must be V3 or later */ + if (parent != NULL && parent->header.version < 3) + EARLY_EXIT(err = CHDERR_UNSUPPORTED_VERSION); + + /* build the header */ + memset(&header, 0, sizeof(header)); + header.length = CHD_V3_HEADER_SIZE; + header.version = CHD_HEADER_VERSION; + header.flags = CHDFLAGS_IS_WRITEABLE; + header.compression = compression; + header.hunkbytes = hunkbytes; + header.totalhunks = (logicalbytes + hunkbytes - 1) / hunkbytes; + header.logicalbytes = logicalbytes; + + /* tweaks if there is a parent */ + if (parent != NULL) + { + header.flags |= CHDFLAGS_HAS_PARENT; + memcpy(&header.parentmd5[0], &parent->header.md5[0], sizeof(header.parentmd5)); + memcpy(&header.parentsha1[0], &parent->header.sha1[0], sizeof(header.parentsha1)); + } + + /* validate it */ + err = header_validate(&header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* write the resulting header */ + err = header_write(file, &header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* create an initial map */ + err = map_write_initial(file, parent, &header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* if we have a parent, clone the metadata */ + if (parent != NULL) + { + /* open the new CHD via the standard mechanism */ + err = chd_open_file(file, CHD_OPEN_READWRITE, parent, &newchd); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* close the metadata */ + err = chd_clone_metadata(parent, newchd); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* close the CHD */ + chd_close(newchd); + } + + return CHDERR_NONE; + +cleanup: + if (newchd != NULL) + chd_close(newchd); + return err; +} + + +/*------------------------------------------------- + chd_open_file - open a CHD file for access +-------------------------------------------------*/ + +chd_error chd_open_file(core_file *file, int mode, chd_file *parent, chd_file **chd) +{ + chd_file *newchd = NULL; + chd_error err; + int intfnum; + + /* verify parameters */ + if (file == NULL) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* punt if invalid parent */ + if (parent != NULL && parent->cookie != COOKIE_VALUE) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* allocate memory for the final result */ + newchd = malloc(sizeof(**chd)); + if (newchd == NULL) + EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY); + memset(newchd, 0, sizeof(*newchd)); + newchd->cookie = COOKIE_VALUE; + newchd->parent = parent; + newchd->file = file; + + /* now attempt to read the header */ + err = header_read(newchd->file, &newchd->header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* validate the header */ + err = header_validate(&newchd->header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* make sure we don't open a read-only file writeable */ + if (mode == CHD_OPEN_READWRITE && !(newchd->header.flags & CHDFLAGS_IS_WRITEABLE)) + EARLY_EXIT(err = CHDERR_FILE_NOT_WRITEABLE); + + /* also, never open an older version writeable */ + if (mode == CHD_OPEN_READWRITE && newchd->header.version < CHD_HEADER_VERSION) + EARLY_EXIT(err = CHDERR_UNSUPPORTED_VERSION); + + /* if we need a parent, make sure we have one */ + if (parent == NULL && (newchd->header.flags & CHDFLAGS_HAS_PARENT)) + EARLY_EXIT(err = CHDERR_REQUIRES_PARENT); + + /* make sure we have a valid parent */ + if (parent != NULL) + { + /* check MD5 if it isn't empty */ + if (memcmp(nullmd5, newchd->header.parentmd5, sizeof(newchd->header.parentmd5)) != 0 && + memcmp(nullmd5, newchd->parent->header.md5, sizeof(newchd->parent->header.md5)) != 0 && + memcmp(newchd->parent->header.md5, newchd->header.parentmd5, sizeof(newchd->header.parentmd5)) != 0) + EARLY_EXIT(err = CHDERR_INVALID_PARENT); + + /* check SHA1 if it isn't empty */ + if (memcmp(nullsha1, newchd->header.parentsha1, sizeof(newchd->header.parentsha1)) != 0 && + memcmp(nullsha1, newchd->parent->header.sha1, sizeof(newchd->parent->header.sha1)) != 0 && + memcmp(newchd->parent->header.sha1, newchd->header.parentsha1, sizeof(newchd->header.parentsha1)) != 0) + EARLY_EXIT(err = CHDERR_INVALID_PARENT); + } + + /* now read the hunk map */ + err = map_read(newchd); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* allocate and init the hunk cache */ + newchd->cache = malloc(newchd->header.hunkbytes); + newchd->compare = malloc(newchd->header.hunkbytes); + if (newchd->cache == NULL || newchd->compare == NULL) + EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY); + newchd->cachehunk = ~0; + newchd->comparehunk = ~0; + + /* allocate the temporary compressed buffer */ + newchd->compressed = malloc(newchd->header.hunkbytes); + if (newchd->compressed == NULL) + EARLY_EXIT(err = CHDERR_OUT_OF_MEMORY); + + /* find the codec interface */ + for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) + if (codec_interfaces[intfnum].compression == newchd->header.compression) + { + newchd->codecintf = &codec_interfaces[intfnum]; + break; + } + if (intfnum == ARRAY_LENGTH(codec_interfaces)) + EARLY_EXIT(err = CHDERR_UNSUPPORTED_FORMAT); + + /* initialize the codec */ + if (newchd->codecintf->init != NULL) + err = (*newchd->codecintf->init)(newchd); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* all done */ + *chd = newchd; + return CHDERR_NONE; + +cleanup: + if (newchd != NULL) + chd_close(newchd); + return err; +} + + +/*------------------------------------------------- + chd_create - create a CHD file by + filename +-------------------------------------------------*/ + +chd_error chd_create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent) +{ + core_file *file = NULL; + file_error filerr; + chd_error chderr; + + filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE | OPEN_FLAG_CREATE, &file); + if (filerr != FILERR_NONE) + { + chderr = CHDERR_FILE_NOT_FOUND; + goto cleanup; + } + + chderr = chd_create_file(file, logicalbytes, hunkbytes, compression, parent); + if (chderr != CHDERR_NONE) + goto cleanup; + +cleanup: + if (file != NULL) + core_fclose(file); + return chderr; +} + + +/*------------------------------------------------- + chd_open - open a CHD file by + filename +-------------------------------------------------*/ + +chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd) +{ + chd_error err; + file_error filerr; + core_file *file = NULL; + UINT32 openflags; + + /* choose the proper mode */ + switch(mode) + { + case CHD_OPEN_READ: + openflags = OPEN_FLAG_READ; + break; + + case CHD_OPEN_READWRITE: + openflags = OPEN_FLAG_READ | OPEN_FLAG_WRITE; + break; + + default: + err = CHDERR_INVALID_PARAMETER; + goto cleanup; + } + + /* open the file */ + filerr = core_fopen(filename, openflags, &file); + if (filerr != FILERR_NONE) + { + err = CHDERR_FILE_NOT_FOUND; + goto cleanup; + } + + /* now open the CHD */ + err = chd_open_file(file, mode, parent, chd); + if (err != CHDERR_NONE) + goto cleanup; + + /* we now own this file */ + (*chd)->owns_file = TRUE; + +cleanup: + if ((err != CHDERR_NONE) && (file != NULL)) + core_fclose(file); + return err; +} + + +/*------------------------------------------------- + chd_close - close a CHD file for access +-------------------------------------------------*/ + +void chd_close(chd_file *chd) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* kill the work queue and any work item */ + if (chd->workitem != NULL) + osd_work_item_release(chd->workitem); + if (chd->workqueue != NULL) + osd_work_queue_free(chd->workqueue); + + /* deinit the codec */ + if (chd->codecintf != NULL && chd->codecintf->free != NULL) + (*chd->codecintf->free)(chd); + + /* free the compressed data buffer */ + if (chd->compressed != NULL) + free(chd->compressed); + + /* free the hunk cache and compare data */ + if (chd->compare != NULL) + free(chd->compare); + if (chd->cache != NULL) + free(chd->cache); + + /* free the hunk map */ + if (chd->map != NULL) + free(chd->map); + + /* free the CRC table */ + if (chd->crctable != NULL) + free(chd->crctable); + + /* free the CRC map */ + if (chd->crcmap != NULL) + free(chd->crcmap); + + /* close the file */ + if (chd->owns_file && chd->file != NULL) + core_fclose(chd->file); + +#if PRINTF_MAX_HUNK + printf("Max hunk = %d/%d\n", chd->maxhunk, chd->header.totalhunks); +#endif + + /* free our memory */ + free(chd); +} + + +/*------------------------------------------------- + chd_core_file - return the associated + core_file +-------------------------------------------------*/ + +core_file *chd_core_file(chd_file *chd) +{ + return chd->file; +} + + +/*------------------------------------------------- + chd_multi_filename - compute the indexed CHD + filename +-------------------------------------------------*/ + +void chd_multi_filename(const char *origname, char *finalname, int index) +{ + char *extension; + char findex[5]; + + /* copy the original name */ + strcpy(finalname, origname); + + /* determine the offset of the extension period */ + extension = strchr(finalname, '.'); + if (extension == NULL) + extension = finalname + strlen(finalname); + + /* compute our extension */ + sprintf(findex, ".%3d", index); + if (findex[1] == ' ') + findex[1] = 'c'; + if (findex[2] == ' ') + findex[2] = 'h'; + strcpy(extension, findex); +} + + + +/*************************************************************************** + CHD HEADER MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + chd_get_header - return a pointer to the + extracted header data +-------------------------------------------------*/ + +const chd_header *chd_get_header(chd_file *chd) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return NULL; + + return &chd->header; +} + + +/*------------------------------------------------- + chd_set_header_file - write the current header to + the file +-------------------------------------------------*/ + +chd_error chd_set_header_file(core_file *file, const chd_header *header) +{ + chd_header oldheader; + chd_error err; + + /* validate the header */ + err = header_validate(header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* read the old header */ + err = header_read(file, &oldheader); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + /* make sure we're only making valid changes */ + if (header->length != oldheader.length) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->version != oldheader.version) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->compression != oldheader.compression) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->hunkbytes != oldheader.hunkbytes) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->totalhunks != oldheader.totalhunks) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->metaoffset != oldheader.metaoffset) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + if (header->obsolete_hunksize != oldheader.obsolete_hunksize) + EARLY_EXIT(err = CHDERR_INVALID_PARAMETER); + + /* write the new header */ + err = header_write(file, header); + if (err != CHDERR_NONE) + EARLY_EXIT(err); + + return CHDERR_NONE; + +cleanup: + return err; +} + + +/*------------------------------------------------- + chd_set_header - write the current + header to the file +-------------------------------------------------*/ + +chd_error chd_set_header(const char *filename, const chd_header *header) +{ + core_file *file = NULL; + file_error filerr; + chd_error err; + + filerr = core_fopen(filename, OPEN_FLAG_READ | OPEN_FLAG_WRITE, &file); + if (filerr != FILERR_NONE) + { + err = CHDERR_FILE_NOT_FOUND; + goto cleanup; + } + + err = chd_set_header_file(file, header); + if (err != CHDERR_NONE) + goto cleanup; + +cleanup: + if (file != NULL) + core_fclose(file); + return err; +} + + + +/*************************************************************************** + CORE DATA READ/WRITE +***************************************************************************/ + +/*------------------------------------------------- + chd_read - read a single hunk from the CHD + file +-------------------------------------------------*/ + +chd_error chd_read(chd_file *chd, UINT32 hunknum, void *buffer) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return CHDERR_INVALID_PARAMETER; + + /* if we're past the end, fail */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* perform the read */ + return hunk_read_into_memory(chd, hunknum, buffer); +} + + +/*------------------------------------------------- + chd_read_async - read a single hunk from the + CHD file asynchronously +-------------------------------------------------*/ + +chd_error chd_read_async(chd_file *chd, UINT32 hunknum, void *buffer) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return CHDERR_INVALID_PARAMETER; + + /* if we're past the end, fail */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* set the async parameters */ + chd->async_hunknum = hunknum; + chd->async_buffer = buffer; + + /* queue the work item */ + if (queue_async_operation(chd, async_read_callback)) + return CHDERR_OPERATION_PENDING; + + /* if we fail, fall back on the sync version */ + return chd_read(chd, hunknum, buffer); +} + + +/*------------------------------------------------- + chd_write - write a single hunk to the CHD + file +-------------------------------------------------*/ + +chd_error chd_write(chd_file *chd, UINT32 hunknum, const void *buffer) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return CHDERR_INVALID_PARAMETER; + + /* if we're past the end, fail */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* then write out the hunk */ + return hunk_write_from_memory(chd, hunknum, buffer); +} + + +/*------------------------------------------------- + chd_write_async - write a single hunk to the + CHD file asynchronously +-------------------------------------------------*/ + +chd_error chd_write_async(chd_file *chd, UINT32 hunknum, const void *buffer) +{ + /* punt if NULL or invalid */ + if (chd == NULL || chd->cookie != COOKIE_VALUE) + return CHDERR_INVALID_PARAMETER; + + /* if we're past the end, fail */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* set the async parameters */ + chd->async_hunknum = hunknum; + chd->async_buffer = (void *)buffer; + + /* queue the work item */ + if (queue_async_operation(chd, async_write_callback)) + return CHDERR_OPERATION_PENDING; + + /* if we fail, fall back on the sync version */ + return chd_write(chd, hunknum, buffer); +} + + +/*------------------------------------------------- + chd_async_complete - get the result of a + completed work item and clear it out of the + system +-------------------------------------------------*/ + +chd_error chd_async_complete(chd_file *chd) +{ + void *result; + + /* if nothing present, return an error */ + if (chd->workitem == NULL) + return CHDERR_NO_ASYNC_OPERATION; + + /* wait for the work to complete */ + wait_for_pending_async(chd); + + /* get the result and free the work item */ + result = osd_work_item_result(chd->workitem); + osd_work_item_release(chd->workitem); + chd->workitem = NULL; + + return (chd_error)result; +} + + + +/*************************************************************************** + METADATA MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + chd_get_metadata - get the indexed metadata + of the given type +-------------------------------------------------*/ + +chd_error chd_get_metadata(chd_file *chd, UINT32 searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 *resultlen, UINT32 *resulttag) +{ + metadata_entry metaentry; + chd_error err; + UINT32 count; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* if we didn't find it, just return */ + err = metadata_find_entry(chd, searchtag, searchindex, &metaentry); + if (err != CHDERR_NONE) + { + /* unless we're an old version and they are requesting hard disk metadata */ + if (chd->header.version < 3 && (searchtag == HARD_DISK_METADATA_TAG || searchtag == CHDMETATAG_WILDCARD) && searchindex == 0) + { + char faux_metadata[256]; + UINT32 faux_length; + + /* fill in the faux metadata */ + 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); + faux_length = (UINT32)strlen(faux_metadata) + 1; + + /* copy the metadata itself */ + memcpy(output, faux_metadata, MIN(outputlen, faux_length)); + + /* return the length of the data and the tag */ + if (resultlen != NULL) + *resultlen = faux_length; + if (resulttag != NULL) + *resulttag = HARD_DISK_METADATA_TAG; + return CHDERR_NONE; + } + return err; + } + + /* read the metadata */ + outputlen = MIN(outputlen, metaentry.length); + core_fseek(chd->file, metaentry.offset + METADATA_HEADER_SIZE, SEEK_SET); + count = core_fread(chd->file, output, outputlen); + if (count != outputlen) + return CHDERR_READ_ERROR; + + /* return the length of the data and the tag */ + if (resultlen != NULL) + *resultlen = metaentry.length; + if (resulttag != NULL) + *resulttag = metaentry.metatag; + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_set_metadata - write the indexed metadata + of the given type +-------------------------------------------------*/ + +chd_error chd_set_metadata(chd_file *chd, UINT32 metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen) +{ + UINT8 raw_meta_header[METADATA_HEADER_SIZE]; + metadata_entry metaentry; + chd_error err; + UINT64 offset; + UINT32 count; + + /* if the disk is an old version, punt */ + if (chd->header.version < 3) + return CHDERR_NOT_SUPPORTED; + + /* if the disk isn't writeable, punt */ + if (!(chd->header.flags & CHDFLAGS_IS_WRITEABLE)) + return CHDERR_FILE_NOT_WRITEABLE; + + /* must write at least 1 byte */ + if (inputlen < 1) + return CHDERR_INVALID_PARAMETER; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* find the entry if it already exists */ + err = metadata_find_entry(chd, metatag, metaindex, &metaentry); + + /* if it's there and it fits, just overwrite */ + if (err == CHDERR_NONE && inputlen <= metaentry.length) + { + /* overwrite the original data with our new input data */ + core_fseek(chd->file, metaentry.offset + METADATA_HEADER_SIZE, SEEK_SET); + count = core_fwrite(chd->file, inputbuf, inputlen); + if (count != inputlen) + return CHDERR_WRITE_ERROR; + + /* if the lengths don't match, we need to update the length in our header */ + if (inputlen != metaentry.length) + { + err = metadata_set_length(chd, metaentry.offset, inputlen); + if (err != CHDERR_NONE) + return err; + } + return CHDERR_NONE; + } + + /* if we already have an entry, unlink it */ + if (err == CHDERR_NONE) + { + err = metadata_set_previous_next(chd, metaentry.prev, metaentry.next); + if (err != CHDERR_NONE) + return err; + } + + /* now build us a new entry */ + put_bigendian_uint32(&raw_meta_header[0], metatag); + put_bigendian_uint32(&raw_meta_header[4], inputlen); + put_bigendian_uint64(&raw_meta_header[8], (err == CHDERR_NONE) ? metaentry.next : 0); + + /* write out the new header */ + offset = core_fsize(chd->file); + core_fseek(chd->file, offset, SEEK_SET); + count = core_fwrite(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + return CHDERR_WRITE_ERROR; + + /* follow that with the data */ + core_fseek(chd->file, offset + METADATA_HEADER_SIZE, SEEK_SET); + count = core_fwrite(chd->file, inputbuf, inputlen); + if (count != inputlen) + return CHDERR_WRITE_ERROR; + + /* set the previous entry to point to us */ + err = metadata_set_previous_next(chd, metaentry.prev, offset); + if (err != CHDERR_NONE) + return err; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_clone_metadata - clone the metadata from + one CHD to a second +-------------------------------------------------*/ + +chd_error chd_clone_metadata(chd_file *source, chd_file *dest) +{ + UINT32 metatag, metasize, metaindex; + UINT8 metabuffer[1024]; + chd_error err; + + /* clone the metadata */ + for (metaindex = 0; ; metaindex++) + { + /* fetch the next piece of metadata */ + err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, metabuffer, sizeof(metabuffer), &metasize, &metatag); + if (err != CHDERR_NONE) + { + if (err == CHDERR_METADATA_NOT_FOUND) + err = CHDERR_NONE; + break; + } + + /* if that fit, just write it back from the temporary buffer */ + if (metasize <= sizeof(metabuffer)) + { + /* write it to the target */ + err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, metabuffer, metasize); + if (err != CHDERR_NONE) + break; + } + + /* otherwise, allocate a bigger temporary buffer */ + else + { + UINT8 *allocbuffer = malloc(metasize); + if (allocbuffer == NULL) + { + err = CHDERR_OUT_OF_MEMORY; + break; + } + + /* re-read the whole thing */ + err = chd_get_metadata(source, CHDMETATAG_WILDCARD, metaindex, allocbuffer, metasize, &metasize, &metatag); + if (err != CHDERR_NONE) + { + free(allocbuffer); + break; + } + + /* write it to the target */ + err = chd_set_metadata(dest, metatag, CHD_METAINDEX_APPEND, allocbuffer, metasize); + free(allocbuffer); + if (err != CHDERR_NONE) + break; + } + } + return err; +} + + + +/*************************************************************************** + COMPRESSION MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + chd_compress_begin - begin compressing data + into a CHD +-------------------------------------------------*/ + +chd_error chd_compress_begin(chd_file *chd) +{ + chd_error err; + + /* verify parameters */ + if (chd == NULL) + return CHDERR_INVALID_PARAMETER; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* mark the CHD writeable and write the updated header */ + chd->header.flags |= CHDFLAGS_IS_WRITEABLE; + err = header_write(chd->file, &chd->header); + if (err != CHDERR_NONE) + return err; + + /* create CRC maps for the new CHD and the parent */ + crcmap_init(chd, FALSE); + if (chd->parent != NULL) + crcmap_init(chd->parent, TRUE); + + /* init the MD5/SHA1 computations */ + MD5Init(&chd->compmd5); + sha1_init(&chd->compsha1); + chd->compressing = TRUE; + chd->comphunk = 0; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_compress_hunk - append data to a CHD + that is being compressed +-------------------------------------------------*/ + +chd_error chd_compress_hunk(chd_file *chd, const void *data, double *curratio) +{ + UINT32 thishunk = chd->comphunk++; + UINT64 sourceoffset = (UINT64)thishunk * (UINT64)chd->header.hunkbytes; + UINT32 bytestochecksum; + const void *crcdata; + chd_error err; + + /* error if in the wrong state */ + if (!chd->compressing) + return CHDERR_INVALID_STATE; + + /* write out the hunk */ + err = hunk_write_from_memory(chd, thishunk, data); + if (err != CHDERR_NONE) + return err; + + /* if we are lossy, then we need to use the decompressed version in */ + /* the cache as our MD5/SHA1 source */ + crcdata = chd->codecintf->lossy ? chd->cache : data; + + /* update the MD5/SHA1 */ + bytestochecksum = chd->header.hunkbytes; + if (sourceoffset + chd->header.hunkbytes > chd->header.logicalbytes) + { + if (sourceoffset >= chd->header.logicalbytes) + bytestochecksum = 0; + else + bytestochecksum = chd->header.logicalbytes - sourceoffset; + } + if (bytestochecksum > 0) + { + MD5Update(&chd->compmd5, crcdata, bytestochecksum); + sha1_update(&chd->compsha1, bytestochecksum, crcdata); + } + + /* update our CRC map */ + if ((chd->map[thishunk].flags & MAP_ENTRY_FLAG_TYPE_MASK) != MAP_ENTRY_TYPE_SELF_HUNK && + (chd->map[thishunk].flags & MAP_ENTRY_FLAG_TYPE_MASK) != MAP_ENTRY_TYPE_PARENT_HUNK) + crcmap_add_entry(chd, thishunk); + + /* update the ratio */ + if (curratio != NULL) + { + UINT64 curlength = core_fsize(chd->file); + *curratio = 1.0 - (double)curlength / (double)((UINT64)chd->comphunk * (UINT64)chd->header.hunkbytes); + } + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_compress_finish - complete compression of + a CHD +-------------------------------------------------*/ + +chd_error chd_compress_finish(chd_file *chd) +{ + /* error if in the wrong state */ + if (!chd->compressing) + return CHDERR_INVALID_STATE; + + /* compute the final MD5/SHA1 values */ + MD5Final(chd->header.md5, &chd->compmd5); + sha1_final(&chd->compsha1); + sha1_digest(&chd->compsha1, SHA1_DIGEST_SIZE, chd->header.sha1); + + /* turn off the writeable flag and re-write the header */ + chd->header.flags &= ~CHDFLAGS_IS_WRITEABLE; + chd->compressing = FALSE; + return header_write(chd->file, &chd->header); +} + + + +/*************************************************************************** + VERIFICATION +***************************************************************************/ + +/*------------------------------------------------- + chd_verify_begin - begin compressing data + into a CHD +-------------------------------------------------*/ + +chd_error chd_verify_begin(chd_file *chd) +{ + /* verify parameters */ + if (chd == NULL) + return CHDERR_INVALID_PARAMETER; + + /* if this is a writeable file image, we can't verify */ + if (chd->header.flags & CHDFLAGS_IS_WRITEABLE) + return CHDERR_CANT_VERIFY; + + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* init the MD5/SHA1 computations */ + MD5Init(&chd->vermd5); + sha1_init(&chd->versha1); + chd->verifying = TRUE; + chd->verhunk = 0; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_verify_hunk - verify the next hunk in + the CHD +-------------------------------------------------*/ + +chd_error chd_verify_hunk(chd_file *chd) +{ + UINT32 thishunk = chd->verhunk++; + UINT64 hunkoffset = (UINT64)thishunk * (UINT64)chd->header.hunkbytes; + map_entry *entry; + chd_error err; + + /* error if in the wrong state */ + if (!chd->verifying) + return CHDERR_INVALID_STATE; + + /* read the hunk into the cache */ + err = hunk_read_into_cache(chd, thishunk); + if (err != CHDERR_NONE) + return err; + + /* update the MD5/SHA1 */ + if (hunkoffset < chd->header.logicalbytes) + { + UINT64 bytestochecksum = MIN(chd->header.hunkbytes, chd->header.logicalbytes - hunkoffset); + if (bytestochecksum > 0) + { + MD5Update(&chd->vermd5, chd->cache, bytestochecksum); + sha1_update(&chd->versha1, bytestochecksum, chd->cache); + } + } + + /* validate the CRC if we have one */ + entry = &chd->map[thishunk]; + if (!(entry->flags & MAP_ENTRY_FLAG_NO_CRC) && entry->crc != crc32(0, chd->cache, chd->header.hunkbytes)) + return CHDERR_DECOMPRESSION_ERROR; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + chd_verify_finish - finish verification of + the CHD +-------------------------------------------------*/ + +chd_error chd_verify_finish(chd_file *chd, UINT8 *finalmd5, UINT8 *finalsha1) +{ + /* error if in the wrong state */ + if (!chd->verifying) + return CHDERR_INVALID_STATE; + + /* compute the final MD5 */ + if (finalmd5 != NULL) + MD5Final(finalmd5, &chd->vermd5); + + /* compute the final SHA1 */ + if (finalsha1 != NULL) + { + sha1_final(&chd->versha1); + sha1_digest(&chd->versha1, SHA1_DIGEST_SIZE, finalsha1); + } + + /* return an error */ + chd->verifying = FALSE; + return (chd->verhunk < chd->header.totalhunks) ? CHDERR_VERIFY_INCOMPLETE : CHDERR_NONE; +} + + + +/*************************************************************************** + CODEC INTERFACES +***************************************************************************/ + +/*------------------------------------------------- + chd_codec_config - set internal codec + parameters +-------------------------------------------------*/ + +chd_error chd_codec_config(chd_file *chd, int param, void *config) +{ + /* wait for any pending async operations */ + wait_for_pending_async(chd); + + /* if the codec has a configuration callback, call through to it */ + if (chd->codecintf->config != NULL) + return (*chd->codecintf->config)(chd, param, config); + + return CHDERR_INVALID_PARAMETER; +} + + +/*------------------------------------------------- + chd_get_codec_name - get the name of a + particular codec +-------------------------------------------------*/ + +const char *chd_get_codec_name(UINT32 codec) +{ + int intfnum; + + /* look for a matching codec and return its string */ + for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) + if (codec_interfaces[intfnum].compression == codec) + return codec_interfaces[intfnum].compname; + + return "Unknown"; +} + + + +/*************************************************************************** + INTERNAL ASYNC OPERATIONS +***************************************************************************/ + +/*------------------------------------------------- + async_read_callback - asynchronous reading + callback +-------------------------------------------------*/ + +static void *async_read_callback(void *param, int threadid) +{ + chd_file *chd = param; + chd_error err; + + /* read the hunk into the cache */ + err = hunk_read_into_memory(chd, chd->async_hunknum, chd->async_buffer); + + /* return the error */ + return (void *)err; +} + + +/*------------------------------------------------- + async_write_callback - asynchronous writing + callback +-------------------------------------------------*/ + +static void *async_write_callback(void *param, int threadid) +{ + chd_file *chd = param; + chd_error err; + + /* write the hunk from memory */ + err = hunk_write_from_memory(chd, chd->async_hunknum, chd->async_buffer); + + /* return the error */ + return (void *)err; +} + + + +/*************************************************************************** + INTERNAL HEADER OPERATIONS +***************************************************************************/ + +/*------------------------------------------------- + header_validate - check the validity of a + CHD header +-------------------------------------------------*/ + +static chd_error header_validate(const chd_header *header) +{ + int intfnum; + + /* require a valid version */ + if (header->version == 0 || header->version > CHD_HEADER_VERSION) + return CHDERR_UNSUPPORTED_VERSION; + + /* require a valid length */ + if ((header->version == 1 && header->length != CHD_V1_HEADER_SIZE) || + (header->version == 2 && header->length != CHD_V2_HEADER_SIZE) || + (header->version == 3 && header->length != CHD_V3_HEADER_SIZE)) + return CHDERR_INVALID_PARAMETER; + + /* require valid flags */ + if (header->flags & CHDFLAGS_UNDEFINED) + return CHDERR_INVALID_PARAMETER; + + /* require a supported compression mechanism */ + for (intfnum = 0; intfnum < ARRAY_LENGTH(codec_interfaces); intfnum++) + if (codec_interfaces[intfnum].compression == header->compression) + break; + if (intfnum == ARRAY_LENGTH(codec_interfaces)) + return CHDERR_INVALID_PARAMETER; + + /* require a valid hunksize */ + if (header->hunkbytes == 0 || header->hunkbytes >= 65536 * 256) + return CHDERR_INVALID_PARAMETER; + + /* require a valid hunk count */ + if (header->totalhunks == 0) + return CHDERR_INVALID_PARAMETER; + + /* require a valid MD5 and/or SHA1 if we're using a parent */ + if ((header->flags & CHDFLAGS_HAS_PARENT) && memcmp(header->parentmd5, nullmd5, sizeof(nullmd5)) == 0 && memcmp(header->parentsha1, nullsha1, sizeof(nullsha1)) == 0) + return CHDERR_INVALID_PARAMETER; + + /* if we're V3 or later, the obsolete fields must be 0 */ + if (header->version >= 3 && + (header->obsolete_cylinders != 0 || header->obsolete_sectors != 0 || + header->obsolete_heads != 0 || header->obsolete_hunksize != 0)) + return CHDERR_INVALID_PARAMETER; + + /* if we're pre-V3, the obsolete fields must NOT be 0 */ + if (header->version < 3 && + (header->obsolete_cylinders == 0 || header->obsolete_sectors == 0 || + header->obsolete_heads == 0 || header->obsolete_hunksize == 0)) + return CHDERR_INVALID_PARAMETER; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + header_read - read a CHD header into the + internal data structure +-------------------------------------------------*/ + +static chd_error header_read(core_file *file, chd_header *header) +{ + UINT8 rawheader[CHD_MAX_HEADER_SIZE]; + UINT32 count; + + /* punt if NULL */ + if (!header) + return CHDERR_INVALID_PARAMETER; + + /* punt if invalid file */ + if (!file) + return CHDERR_INVALID_FILE; + + /* seek and read */ + core_fseek(file, 0, SEEK_SET); + count = core_fread(file, rawheader, sizeof(rawheader)); + if (count != sizeof(rawheader)) + return CHDERR_READ_ERROR; + + /* verify the tag */ + if (strncmp((char *)rawheader, "MComprHD", 8) != 0) + return CHDERR_INVALID_DATA; + + /* extract the direct data */ + memset(header, 0, sizeof(*header)); + header->length = get_bigendian_uint32(&rawheader[8]); + header->version = get_bigendian_uint32(&rawheader[12]); + + /* make sure it's a version we understand */ + if (header->version == 0 || header->version > CHD_HEADER_VERSION) + return CHDERR_UNSUPPORTED_VERSION; + + /* make sure the length is expected */ + if ((header->version == 1 && header->length != CHD_V1_HEADER_SIZE) || + (header->version == 2 && header->length != CHD_V2_HEADER_SIZE) || + (header->version == 3 && header->length != CHD_V3_HEADER_SIZE)) + return CHDERR_INVALID_DATA; + + /* extract the common data */ + header->flags = get_bigendian_uint32(&rawheader[16]); + header->compression = get_bigendian_uint32(&rawheader[20]); + memcpy(header->md5, &rawheader[44], CHD_MD5_BYTES); + memcpy(header->parentmd5, &rawheader[60], CHD_MD5_BYTES); + + /* extract the V1/V2-specific data */ + if (header->version < 3) + { + int seclen = (header->version == 1) ? CHD_V1_SECTOR_SIZE : get_bigendian_uint32(&rawheader[76]); + header->obsolete_hunksize = get_bigendian_uint32(&rawheader[24]); + header->totalhunks = get_bigendian_uint32(&rawheader[28]); + header->obsolete_cylinders = get_bigendian_uint32(&rawheader[32]); + header->obsolete_heads = get_bigendian_uint32(&rawheader[36]); + header->obsolete_sectors = get_bigendian_uint32(&rawheader[40]); + header->logicalbytes = (UINT64)header->obsolete_cylinders * (UINT64)header->obsolete_heads * (UINT64)header->obsolete_sectors * (UINT64)seclen; + header->hunkbytes = seclen * header->obsolete_hunksize; + header->metaoffset = 0; + } + + /* extract the V3-specific data */ + else + { + header->totalhunks = get_bigendian_uint32(&rawheader[24]); + header->logicalbytes = get_bigendian_uint64(&rawheader[28]); + header->metaoffset = get_bigendian_uint64(&rawheader[36]); + header->hunkbytes = get_bigendian_uint32(&rawheader[76]); + memcpy(header->sha1, &rawheader[80], CHD_SHA1_BYTES); + memcpy(header->parentsha1, &rawheader[100], CHD_SHA1_BYTES); + } + + /* guess it worked */ + return CHDERR_NONE; +} + + +/*------------------------------------------------- + header_write - write a CHD header from the + internal data structure +-------------------------------------------------*/ + +static chd_error header_write(core_file *file, const chd_header *header) +{ + UINT8 rawheader[CHD_MAX_HEADER_SIZE]; + UINT32 count; + + /* punt if NULL */ + if (!header) + return CHDERR_INVALID_PARAMETER; + + /* punt if invalid file */ + if (!file) + return CHDERR_INVALID_FILE; + + /* only support writing modern headers */ + if (header->version != 3) + return CHDERR_INVALID_PARAMETER; + + /* assemble the data */ + memset(rawheader, 0, sizeof(rawheader)); + memcpy(rawheader, "MComprHD", 8); + + put_bigendian_uint32(&rawheader[8], CHD_V3_HEADER_SIZE); + put_bigendian_uint32(&rawheader[12], header->version); + put_bigendian_uint32(&rawheader[16], header->flags); + put_bigendian_uint32(&rawheader[20], header->compression); + put_bigendian_uint32(&rawheader[24], header->totalhunks); + put_bigendian_uint64(&rawheader[28], header->logicalbytes); + put_bigendian_uint64(&rawheader[36], header->metaoffset); + memcpy(&rawheader[44], header->md5, CHD_MD5_BYTES); + memcpy(&rawheader[60], header->parentmd5, CHD_MD5_BYTES); + put_bigendian_uint32(&rawheader[76], header->hunkbytes); + memcpy(&rawheader[80], header->sha1, CHD_SHA1_BYTES); + memcpy(&rawheader[100], header->parentsha1, CHD_SHA1_BYTES); + + /* seek and write */ + core_fseek(file, 0, SEEK_SET); + count = core_fwrite(file, rawheader, CHD_V3_HEADER_SIZE); + if (count != CHD_V3_HEADER_SIZE) + return CHDERR_WRITE_ERROR; + + return CHDERR_NONE; +} + + + +/*************************************************************************** + INTERNAL HUNK READ/WRITE +***************************************************************************/ + +/*------------------------------------------------- + hunk_read_into_cache - read a hunk into + the CHD's hunk cache +-------------------------------------------------*/ + +static chd_error hunk_read_into_cache(chd_file *chd, UINT32 hunknum) +{ + chd_error err; + + /* track the max */ + if (hunknum > chd->maxhunk) + chd->maxhunk = hunknum; + + /* if we're already in the cache, we're done */ + if (chd->cachehunk == hunknum) + return CHDERR_NONE; + chd->cachehunk = ~0; + + /* otherwise, read the data */ + err = hunk_read_into_memory(chd, hunknum, chd->cache); + if (err != CHDERR_NONE) + return err; + + /* mark the hunk successfully cached in */ + chd->cachehunk = hunknum; + return CHDERR_NONE; +} + + +/*------------------------------------------------- + hunk_read_into_memory - read a hunk into + memory at the given location +-------------------------------------------------*/ + +static chd_error hunk_read_into_memory(chd_file *chd, UINT32 hunknum, UINT8 *dest) +{ + map_entry *entry = &chd->map[hunknum]; + chd_error err; + UINT32 bytes; + + /* return an error if out of range */ + if (hunknum >= chd->header.totalhunks) + return CHDERR_HUNK_OUT_OF_RANGE; + + /* switch off the entry type */ + switch (entry->flags & MAP_ENTRY_FLAG_TYPE_MASK) + { + /* compressed data */ + case MAP_ENTRY_TYPE_COMPRESSED: + + /* read it into the decompression buffer */ + core_fseek(chd->file, entry->offset, SEEK_SET); + bytes = core_fread(chd->file, chd->compressed, entry->length); + if (bytes != entry->length) + return CHDERR_READ_ERROR; + + /* now decompress using the codec */ + err = CHDERR_NONE; + if (chd->codecintf->decompress != NULL) + err = (*chd->codecintf->decompress)(chd, entry->length, dest); + if (err != CHDERR_NONE) + return err; + break; + + /* uncompressed data */ + case MAP_ENTRY_TYPE_UNCOMPRESSED: + core_fseek(chd->file, entry->offset, SEEK_SET); + bytes = core_fread(chd->file, dest, chd->header.hunkbytes); + if (bytes != chd->header.hunkbytes) + return CHDERR_READ_ERROR; + break; + + /* mini-compressed data */ + case MAP_ENTRY_TYPE_MINI: + put_bigendian_uint64(&dest[0], entry->offset); + for (bytes = 8; bytes < chd->header.hunkbytes; bytes++) + dest[bytes] = dest[bytes - 8]; + break; + + /* self-referenced data */ + case MAP_ENTRY_TYPE_SELF_HUNK: + if (chd->cachehunk == entry->offset && dest == chd->cache) + break; + return hunk_read_into_memory(chd, entry->offset, dest); + + /* parent-referenced data */ + case MAP_ENTRY_TYPE_PARENT_HUNK: + err = hunk_read_into_memory(chd->parent, entry->offset, dest); + if (err != CHDERR_NONE) + return err; + break; + } + return CHDERR_NONE; +} + + +/*------------------------------------------------- + hunk_write_from_memory - write a hunk from + memory into a CHD +-------------------------------------------------*/ + +static chd_error hunk_write_from_memory(chd_file *chd, UINT32 hunknum, const UINT8 *src) +{ + map_entry *entry = &chd->map[hunknum]; + map_entry newentry; + UINT8 fileentry[MAP_ENTRY_SIZE]; + const void *data = src; + UINT32 bytes = 0, match; + chd_error err; + + /* track the max */ + if (hunknum > chd->maxhunk) + chd->maxhunk = hunknum; + + /* first compute the CRC of the original data */ + newentry.crc = crc32(0, &src[0], chd->header.hunkbytes); + + /* if we're not a lossy codec, compute the CRC and look for matches */ + if (!chd->codecintf->lossy) + { + /* some extra stuff for zlib+ compression */ + if (chd->header.compression >= CHDCOMPRESSION_ZLIB_PLUS) + { + /* see if we can mini-compress first */ + for (bytes = 8; bytes < chd->header.hunkbytes; bytes++) + if (src[bytes] != src[bytes - 8]) + break; + + /* if so, we don't need to write any data */ + if (bytes == chd->header.hunkbytes) + { + newentry.offset = get_bigendian_uint64(&src[0]); + newentry.length = 0; + newentry.flags = MAP_ENTRY_TYPE_MINI; + goto write_entry; + } + + /* otherwise, see if we can find a match in the current file */ + match = crcmap_find_hunk(chd, hunknum, newentry.crc, &src[0]); + if (match != NO_MATCH) + { + newentry.offset = match; + newentry.length = 0; + newentry.flags = MAP_ENTRY_TYPE_SELF_HUNK; + goto write_entry; + } + + /* if we have a parent, see if we can find a match in there */ + if (chd->header.flags & CHDFLAGS_HAS_PARENT) + { + match = crcmap_find_hunk(chd->parent, ~0, newentry.crc, &src[0]); + if (match != NO_MATCH) + { + newentry.offset = match; + newentry.length = 0; + newentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK; + goto write_entry; + } + } + } + } + + /* now try compressing the data */ + err = CHDERR_COMPRESSION_ERROR; + if (chd->codecintf->compress != NULL) + err = (*chd->codecintf->compress)(chd, src, &bytes); + + /* if that worked, and we're lossy, decompress and CRC the result */ + if (err == CHDERR_NONE && chd->codecintf->lossy) + { + err = (*chd->codecintf->decompress)(chd, bytes, chd->cache); + if (err == CHDERR_NONE) + newentry.crc = crc32(0, chd->cache, chd->header.hunkbytes); + } + + /* if we succeeded in compressing the data, replace our data pointer and mark it so */ + if (err == CHDERR_NONE) + { + data = chd->compressed; + newentry.length = bytes; + newentry.flags = MAP_ENTRY_TYPE_COMPRESSED; + } + + /* otherwise, mark it uncompressed and use the original data */ + else + { + newentry.length = chd->header.hunkbytes; + newentry.flags = MAP_ENTRY_TYPE_UNCOMPRESSED; + } + + /* if the data doesn't fit into the previous entry, make a new one at the eof */ + newentry.offset = entry->offset; + if (newentry.offset == 0 || newentry.length > entry->length) + newentry.offset = core_fsize(chd->file); + + /* write the data */ + core_fseek(chd->file, newentry.offset, SEEK_SET); + bytes = core_fwrite(chd->file, data, newentry.length); + if (bytes != newentry.length) + return CHDERR_WRITE_ERROR; + + /* update the entry in memory */ +write_entry: + *entry = newentry; + + /* update the map on file */ + map_assemble(&fileentry[0], &chd->map[hunknum]); + core_fseek(chd->file, chd->header.length + hunknum * sizeof(fileentry), SEEK_SET); + bytes = core_fwrite(chd->file, &fileentry[0], sizeof(fileentry)); + if (bytes != sizeof(fileentry)) + return CHDERR_WRITE_ERROR; + + return CHDERR_NONE; +} + + + +/*************************************************************************** + INTERNAL MAP ACCESS +***************************************************************************/ + +/*------------------------------------------------- + map_write_initial - write an initial map to + a new CHD file +-------------------------------------------------*/ + +static chd_error map_write_initial(core_file *file, chd_file *parent, const chd_header *header) +{ + UINT8 blank_map_entries[MAP_STACK_ENTRIES * MAP_ENTRY_SIZE]; + int fullchunks, remainder, count, i, j; + map_entry mapentry; + UINT64 fileoffset; + + /* create a mini hunk of 0's */ + mapentry.offset = 0; + mapentry.crc = 0; + mapentry.length = 0; + mapentry.flags = MAP_ENTRY_TYPE_MINI | MAP_ENTRY_FLAG_NO_CRC; + for (i = 0; i < MAP_STACK_ENTRIES; i++) + map_assemble(&blank_map_entries[i * MAP_ENTRY_SIZE], &mapentry); + + /* prepare to write a blank hunk map immediately following */ + fileoffset = header->length; + fullchunks = header->totalhunks / MAP_STACK_ENTRIES; + remainder = header->totalhunks % MAP_STACK_ENTRIES; + + /* first write full chunks of blank entries */ + for (i = 0; i < fullchunks; i++) + { + /* parent drives need to be mapped through */ + if (parent != NULL) + for (j = 0; j < MAP_STACK_ENTRIES; j++) + { + mapentry.offset = i * MAP_STACK_ENTRIES + j; + mapentry.crc = parent->map[i * MAP_STACK_ENTRIES + j].crc; + mapentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK; + map_assemble(&blank_map_entries[j * MAP_ENTRY_SIZE], &mapentry); + } + + /* write the chunks */ + core_fseek(file, fileoffset, SEEK_SET); + count = core_fwrite(file, blank_map_entries, sizeof(blank_map_entries)); + if (count != sizeof(blank_map_entries)) + return CHDERR_WRITE_ERROR; + fileoffset += sizeof(blank_map_entries); + } + + /* then write the remainder */ + if (remainder > 0) + { + /* parent drives need to be mapped through */ + if (parent != NULL) + for (j = 0; j < remainder; j++) + { + mapentry.offset = i * MAP_STACK_ENTRIES + j; + mapentry.crc = parent->map[i * MAP_STACK_ENTRIES + j].crc; + mapentry.flags = MAP_ENTRY_TYPE_PARENT_HUNK; + map_assemble(&blank_map_entries[j * MAP_ENTRY_SIZE], &mapentry); + } + + /* write the chunks */ + core_fseek(file, fileoffset, SEEK_SET); + count = core_fwrite(file, blank_map_entries, remainder * MAP_ENTRY_SIZE); + if (count != remainder * MAP_ENTRY_SIZE) + return CHDERR_WRITE_ERROR; + fileoffset += remainder * MAP_ENTRY_SIZE; + } + + /* then write a special end-of-list cookie */ + memcpy(&blank_map_entries[0], END_OF_LIST_COOKIE, MAP_ENTRY_SIZE); + core_fseek(file, fileoffset, SEEK_SET); + count = core_fwrite(file, blank_map_entries, MAP_ENTRY_SIZE); + if (count != MAP_ENTRY_SIZE) + return CHDERR_WRITE_ERROR; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + map_read - read the initial sector map +-------------------------------------------------*/ + +static chd_error map_read(chd_file *chd) +{ + UINT32 entrysize = (chd->header.version < 3) ? OLD_MAP_ENTRY_SIZE : MAP_ENTRY_SIZE; + UINT8 raw_map_entries[MAP_STACK_ENTRIES * MAP_ENTRY_SIZE]; + UINT64 fileoffset, maxoffset = 0; + UINT8 cookie[MAP_ENTRY_SIZE]; + UINT32 count; + int i, err; + + /* first allocate memory */ + chd->map = malloc(sizeof(chd->map[0]) * chd->header.totalhunks); + if (!chd->map) + return CHDERR_OUT_OF_MEMORY; + + /* read the map entries in in chunks and extract to the map list */ + fileoffset = chd->header.length; + for (i = 0; i < chd->header.totalhunks; i += MAP_STACK_ENTRIES) + { + /* compute how many entries this time */ + int entries = chd->header.totalhunks - i, j; + if (entries > MAP_STACK_ENTRIES) + entries = MAP_STACK_ENTRIES; + + /* read that many */ + core_fseek(chd->file, fileoffset, SEEK_SET); + count = core_fread(chd->file, raw_map_entries, entries * entrysize); + if (count != entries * entrysize) + { + err = CHDERR_READ_ERROR; + goto cleanup; + } + fileoffset += entries * entrysize; + + /* process that many */ + if (entrysize == MAP_ENTRY_SIZE) + { + for (j = 0; j < entries; j++) + map_extract(&raw_map_entries[j * MAP_ENTRY_SIZE], &chd->map[i + j]); + } + else + { + for (j = 0; j < entries; j++) + map_extract_old(&raw_map_entries[j * OLD_MAP_ENTRY_SIZE], &chd->map[i + j], chd->header.hunkbytes); + } + + /* track the maximum offset */ + for (j = 0; j < entries; j++) + if ((chd->map[i + j].flags & MAP_ENTRY_FLAG_TYPE_MASK) == MAP_ENTRY_TYPE_COMPRESSED || + (chd->map[i + j].flags & MAP_ENTRY_FLAG_TYPE_MASK) == MAP_ENTRY_TYPE_UNCOMPRESSED) + maxoffset = MAX(maxoffset, chd->map[i + j].offset + chd->map[i + j].length); + } + + /* verify the cookie */ + core_fseek(chd->file, fileoffset, SEEK_SET); + count = core_fread(chd->file, &cookie, entrysize); + if (count != entrysize || memcmp(&cookie, END_OF_LIST_COOKIE, entrysize)) + { + err = CHDERR_INVALID_FILE; + goto cleanup; + } + + /* verify the length */ + if (maxoffset > core_fsize(chd->file)) + { + err = CHDERR_INVALID_FILE; + goto cleanup; + } + return CHDERR_NONE; + +cleanup: + if (chd->map) + free(chd->map); + chd->map = NULL; + return err; +} + + + +/*************************************************************************** + INTERNAL CRC MAP ACCESS +***************************************************************************/ + +/*------------------------------------------------- + crcmap_init - initialize the CRC map +-------------------------------------------------*/ + +static void crcmap_init(chd_file *chd, int prepopulate) +{ + int i; + + /* if we already have one, bail */ + if (chd->crcmap != NULL) + return; + + /* reset all pointers */ + chd->crcmap = NULL; + chd->crcfree = NULL; + chd->crctable = NULL; + + /* allocate a list; one for each hunk */ + chd->crcmap = malloc(chd->header.totalhunks * sizeof(chd->crcmap[0])); + if (chd->crcmap == NULL) + return; + + /* allocate a CRC map table */ + chd->crctable = malloc(CRCMAP_HASH_SIZE * sizeof(chd->crctable[0])); + if (chd->crctable == NULL) + { + free(chd->crcmap); + chd->crcmap = NULL; + return; + } + + /* initialize the free list */ + for (i = 0; i < chd->header.totalhunks; i++) + { + chd->crcmap[i].next = chd->crcfree; + chd->crcfree = &chd->crcmap[i]; + } + + /* initialize the table */ + memset(chd->crctable, 0, CRCMAP_HASH_SIZE * sizeof(chd->crctable[0])); + + /* if we're to prepopulate, go for it */ + if (prepopulate) + for (i = 0; i < chd->header.totalhunks; i++) + crcmap_add_entry(chd, i); +} + + +/*------------------------------------------------- + crcmap_add_entry - log a CRC entry +-------------------------------------------------*/ + +static void crcmap_add_entry(chd_file *chd, UINT32 hunknum) +{ + UINT32 hash = chd->map[hunknum].crc % CRCMAP_HASH_SIZE; + crcmap_entry *crcmap; + + /* pull a free entry off the list */ + crcmap = chd->crcfree; + chd->crcfree = crcmap->next; + + /* set up the entry and link it into the hash table */ + crcmap->hunknum = hunknum; + crcmap->next = chd->crctable[hash]; + chd->crctable[hash] = crcmap; +} + + +/*------------------------------------------------- + crcmap_verify_hunk_match - verify that a + hunk really matches by doing a byte-for-byte + compare +-------------------------------------------------*/ + +static int crcmap_verify_hunk_match(chd_file *chd, UINT32 hunknum, const UINT8 *rawdata) +{ + /* we have a potential match -- better be sure */ + /* read the hunk from disk and compare byte-for-byte */ + if (hunknum != chd->comparehunk) + { + chd->comparehunk = ~0; + if (hunk_read_into_memory(chd, hunknum, chd->compare) == CHDERR_NONE) + chd->comparehunk = hunknum; + } + return (hunknum == chd->comparehunk && memcmp(rawdata, chd->compare, chd->header.hunkbytes) == 0); +} + + +/*------------------------------------------------- + crcmap_find_hunk - find a hunk with a matching + CRC in the map +-------------------------------------------------*/ + +static UINT32 crcmap_find_hunk(chd_file *chd, UINT32 hunknum, UINT32 crc, const UINT8 *rawdata) +{ + UINT32 lasthunk = (hunknum < chd->header.totalhunks) ? hunknum : chd->header.totalhunks; + int curhunk; + + /* if we have a CRC map, use that */ + if (chd->crctable) + { + crcmap_entry *curentry; + for (curentry = chd->crctable[crc % CRCMAP_HASH_SIZE]; curentry; curentry = curentry->next) + { + curhunk = curentry->hunknum; + if (chd->map[curhunk].crc == crc && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_NO_CRC) && crcmap_verify_hunk_match(chd, curhunk, rawdata)) + return curhunk; + } + return NO_MATCH; + } + + /* first see if the last match is a valid one */ + if (chd->comparehunk < chd->header.totalhunks && chd->map[chd->comparehunk].crc == crc && !(chd->map[chd->comparehunk].flags & MAP_ENTRY_FLAG_NO_CRC) && + memcmp(rawdata, chd->compare, chd->header.hunkbytes) == 0) + return chd->comparehunk; + + /* scan through the CHD's hunk map looking for a match */ + for (curhunk = 0; curhunk < lasthunk; curhunk++) + if (chd->map[curhunk].crc == crc && !(chd->map[curhunk].flags & MAP_ENTRY_FLAG_NO_CRC) && crcmap_verify_hunk_match(chd, curhunk, rawdata)) + return curhunk; + + return NO_MATCH; +} + + + +/*************************************************************************** + INTERNAL METADATA ACCESS +***************************************************************************/ + +/*------------------------------------------------- + metadata_find_entry - find a metadata entry +-------------------------------------------------*/ + +static chd_error metadata_find_entry(chd_file *chd, UINT32 metatag, UINT32 metaindex, metadata_entry *metaentry) +{ + /* start at the beginning */ + metaentry->offset = chd->header.metaoffset; + metaentry->prev = 0; + + /* loop until we run out of options */ + while (metaentry->offset != 0) + { + UINT8 raw_meta_header[METADATA_HEADER_SIZE]; + UINT32 count; + + /* read the raw header */ + core_fseek(chd->file, metaentry->offset, SEEK_SET); + count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + break; + + /* extract the data */ + metaentry->metatag = get_bigendian_uint32(&raw_meta_header[0]); + metaentry->length = get_bigendian_uint32(&raw_meta_header[4]); + metaentry->next = get_bigendian_uint64(&raw_meta_header[8]); + + /* if we got a match, proceed */ + if (metatag == CHDMETATAG_WILDCARD || metaentry->metatag == metatag) + if (metaindex-- == 0) + return CHDERR_NONE; + + /* no match, fetch the next link */ + metaentry->prev = metaentry->offset; + metaentry->offset = metaentry->next; + } + + /* if we get here, we didn't find it */ + return CHDERR_METADATA_NOT_FOUND; +} + + +/*------------------------------------------------- + metadata_set_previous_next - set the 'next' + offset of a piece of metadata +-------------------------------------------------*/ + +static chd_error metadata_set_previous_next(chd_file *chd, UINT64 prevoffset, UINT64 nextoffset) +{ + UINT8 raw_meta_header[METADATA_HEADER_SIZE]; + chd_error err; + UINT32 count; + + /* if we were the first entry, make the next entry the first */ + if (prevoffset == 0) + { + chd->header.metaoffset = nextoffset; + err = header_write(chd->file, &chd->header); + if (err != CHDERR_NONE) + return err; + } + + /* otherwise, update the link in the previous pointer */ + else + { + /* read the previous raw header */ + core_fseek(chd->file, prevoffset, SEEK_SET); + count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + return CHDERR_READ_ERROR; + + /* copy our next pointer into the previous->next offset */ + put_bigendian_uint64(&raw_meta_header[8], nextoffset); + + /* write the previous raw header */ + core_fseek(chd->file, prevoffset, SEEK_SET); + count = core_fwrite(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + return CHDERR_WRITE_ERROR; + } + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + metadata_set_length - set the length field of + a piece of metadata +-------------------------------------------------*/ + +static chd_error metadata_set_length(chd_file *chd, UINT64 offset, UINT32 length) +{ + UINT8 raw_meta_header[METADATA_HEADER_SIZE]; + UINT32 count; + + /* read the raw header */ + core_fseek(chd->file, offset, SEEK_SET); + count = core_fread(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + return CHDERR_READ_ERROR; + + /* update the length at offset 4 */ + put_bigendian_uint32(&raw_meta_header[4], length); + + /* write the raw header */ + core_fseek(chd->file, offset, SEEK_SET); + count = core_fwrite(chd->file, raw_meta_header, sizeof(raw_meta_header)); + if (count != sizeof(raw_meta_header)) + return CHDERR_WRITE_ERROR; + + return CHDERR_NONE; +} + + + +/*************************************************************************** + ZLIB COMPRESSION CODEC +***************************************************************************/ + +/*------------------------------------------------- + zlib_codec_init - initialize the ZLIB codec +-------------------------------------------------*/ + +static chd_error zlib_codec_init(chd_file *chd) +{ + zlib_codec_data *data; + chd_error err; + int zerr; + + /* allocate memory for the 2 stream buffers */ + data = malloc(sizeof(*data)); + if (data == NULL) + return CHDERR_OUT_OF_MEMORY; + + /* clear the buffers */ + memset(data, 0, sizeof(*data)); + + /* init the inflater first */ + data->inflater.next_in = (Bytef *)data; /* bogus, but that's ok */ + data->inflater.avail_in = 0; + data->inflater.zalloc = zlib_fast_alloc; + data->inflater.zfree = zlib_fast_free; + data->inflater.opaque = data; + zerr = inflateInit2(&data->inflater, -MAX_WBITS); + + /* if that worked, initialize the deflater */ + if (zerr == Z_OK) + { + data->deflater.next_in = (Bytef *)data; /* bogus, but that's ok */ + data->deflater.avail_in = 0; + data->deflater.zalloc = zlib_fast_alloc; + data->deflater.zfree = zlib_fast_free; + data->deflater.opaque = data; + zerr = deflateInit2(&data->deflater, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, 8, Z_DEFAULT_STRATEGY); + } + + /* convert errors */ + if (zerr == Z_MEM_ERROR) + err = CHDERR_OUT_OF_MEMORY; + else if (zerr != Z_OK) + err = CHDERR_CODEC_ERROR; + else + err = CHDERR_NONE; + + /* handle an error */ + if (err == CHDERR_NONE) + chd->codecdata = data; + else + free(data); + + return err; +} + + +/*------------------------------------------------- + zlib_codec_free - free data for the ZLIB + codec +-------------------------------------------------*/ + +static void zlib_codec_free(chd_file *chd) +{ + zlib_codec_data *data = chd->codecdata; + + /* deinit the streams */ + if (data != NULL) + { + int i; + + inflateEnd(&data->inflater); + deflateEnd(&data->deflater); + + /* free our fast memory */ + for (i = 0; i < MAX_ZLIB_ALLOCS; i++) + if (data->allocptr[i]) + free(data->allocptr[i]); + free(data); + } +} + + +/*------------------------------------------------- + zlib_codec_compress - compress data using the + ZLIB codec +-------------------------------------------------*/ + +static chd_error zlib_codec_compress(chd_file *chd, const void *src, UINT32 *length) +{ + zlib_codec_data *data = chd->codecdata; + int zerr; + + /* reset the decompressor */ + data->deflater.next_in = (void *)src; + data->deflater.avail_in = chd->header.hunkbytes; + data->deflater.total_in = 0; + data->deflater.next_out = chd->compressed; + data->deflater.avail_out = chd->header.hunkbytes; + data->deflater.total_out = 0; + zerr = deflateReset(&data->deflater); + if (zerr != Z_OK) + return CHDERR_COMPRESSION_ERROR; + + /* do it */ + zerr = deflate(&data->deflater, Z_FINISH); + + /* if we ended up with more data than we started with, return an error */ + if (zerr != Z_STREAM_END || data->deflater.total_out >= chd->header.hunkbytes) + return CHDERR_COMPRESSION_ERROR; + + /* otherwise, fill in the length and return success */ + *length = data->deflater.total_out; + return CHDERR_NONE; +} + + +/*------------------------------------------------- + zlib_codec_decompress - decomrpess data using + the ZLIB codec +-------------------------------------------------*/ + +static chd_error zlib_codec_decompress(chd_file *chd, UINT32 srclength, void *dest) +{ + zlib_codec_data *data = chd->codecdata; + int zerr; + + /* reset the decompressor */ + data->inflater.next_in = chd->compressed; + data->inflater.avail_in = srclength; + data->inflater.total_in = 0; + data->inflater.next_out = dest; + data->inflater.avail_out = chd->header.hunkbytes; + data->inflater.total_out = 0; + zerr = inflateReset(&data->inflater); + if (zerr != Z_OK) + return CHDERR_DECOMPRESSION_ERROR; + + /* do it */ + zerr = inflate(&data->inflater, Z_FINISH); + if (data->inflater.total_out != chd->header.hunkbytes) + return CHDERR_DECOMPRESSION_ERROR; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + zlib_fast_alloc - fast malloc for ZLIB, which + allocates and frees memory frequently +-------------------------------------------------*/ + +static voidpf zlib_fast_alloc(voidpf opaque, uInt items, uInt size) +{ + zlib_codec_data *data = opaque; + UINT32 *ptr; + int i; + + /* compute the size, rounding to the nearest 1k */ + size = (size * items + 0x3ff) & ~0x3ff; + + /* reuse a hunk if we can */ + for (i = 0; i < MAX_ZLIB_ALLOCS; i++) + { + ptr = data->allocptr[i]; + if (ptr && size == *ptr) + { + /* set the low bit of the size so we don't match next time */ + *ptr |= 1; + return ptr + 1; + } + } + + /* alloc a new one */ + ptr = malloc(size + sizeof(UINT32)); + if (!ptr) + return NULL; + + /* put it into the list */ + for (i = 0; i < MAX_ZLIB_ALLOCS; i++) + if (!data->allocptr[i]) + { + data->allocptr[i] = ptr; + break; + } + + /* set the low bit of the size so we don't match next time */ + *ptr = size | 1; + return ptr + 1; +} + + +/*------------------------------------------------- + zlib_fast_free - fast free for ZLIB, which + allocates and frees memory frequently +-------------------------------------------------*/ + +static void zlib_fast_free(voidpf opaque, voidpf address) +{ + zlib_codec_data *data = opaque; + UINT32 *ptr = (UINT32 *)address - 1; + int i; + + /* find the hunk */ + for (i = 0; i < MAX_ZLIB_ALLOCS; i++) + if (ptr == data->allocptr[i]) + { + /* clear the low bit of the size to allow matches */ + *ptr &= ~1; + return; + } +} + + + +/*************************************************************************** + AV COMPRESSION CODEC +***************************************************************************/ + +/*------------------------------------------------- + av_raw_data_size - compute the raw data size +-------------------------------------------------*/ + +INLINE UINT32 av_raw_data_size(const UINT8 *data) +{ + int size = 0; + + /* make sure we have a correct header */ + if (data[0] == 'c' && data[1] == 'h' && data[2] == 'a' && data[3] == 'v') + { + /* add in header size plus metadata length */ + size = 12 + data[4]; + + /* add in channels * samples */ + size += 2 * data[5] * ((data[6] << 8) + data[7]); + + /* add in 2 * width * height */ + size += 2 * ((data[8] << 8) + data[9]) * (((data[10] << 8) + data[11]) & 0x7fff); + } + return size; +} + + +/*------------------------------------------------- + av_codec_init - initialize the A/V codec +-------------------------------------------------*/ + +static chd_error av_codec_init(chd_file *chd) +{ + av_codec_data *data; + + /* allocate memory for the 2 stream buffers */ + data = malloc(sizeof(*data)); + if (data == NULL) + return CHDERR_OUT_OF_MEMORY; + + /* clear the buffers */ + memset(data, 0, sizeof(*data)); + chd->codecdata = data; + + /* set up the default decompression configuration */ + data->decompconfig.decode_mask = AVCOMP_DECODE_DEFAULT; + + /* attempt to do a post-init now; if we're creating a new CHD, this won't work */ + /* but that's ok */ + av_codec_postinit(chd); + return CHDERR_NONE; +} + + +/*------------------------------------------------- + av_codec_free - free data for the A/V + codec +-------------------------------------------------*/ + +static void av_codec_free(chd_file *chd) +{ + av_codec_data *data = chd->codecdata; + + /* deinit avcomp */ + if (data != NULL) + { + if (data->compstate != NULL) + avcomp_free(data->compstate); + free(data); + } +} + + +/*------------------------------------------------- + av_codec_compress - compress data using the + A/V codec +-------------------------------------------------*/ + +static chd_error av_codec_compress(chd_file *chd, const void *src, UINT32 *length) +{ + av_codec_data *data = chd->codecdata; + int averr; + int size; + + /* if we haven't yet set up the avcomp code, do it now */ + if (data->compstate == NULL) + { + chd_error chderr = av_codec_postinit(chd); + if (chderr != CHDERR_NONE) + return chderr; + } + + /* make sure short frames are padded with 0 */ + size = av_raw_data_size(src); + while (size < chd->header.hunkbytes) + if (((const UINT8 *)src)[size++] != 0) + return CHDERR_INVALID_DATA; + + /* encode the audio and video */ + averr = avcomp_encode_data(data->compstate, src, chd->compressed, length); + if (averr != AVCERR_NONE || *length > chd->header.hunkbytes) + return CHDERR_COMPRESSION_ERROR; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + av_codec_decompress - decomrpess data using + the A/V codec +-------------------------------------------------*/ + +static chd_error av_codec_decompress(chd_file *chd, UINT32 srclength, void *dest) +{ + av_codec_data *data = chd->codecdata; + const UINT8 *source; + avcomp_error averr; + int size; + + /* if we haven't yet set up the avcomp code, do it now */ + if (data->compstate == NULL) + { + chd_error chderr = av_codec_postinit(chd); + if (chderr != CHDERR_NONE) + return chderr; + } + + /* decode the audio and video */ + source = chd->compressed; + averr = avcomp_decode_data(data->compstate, source, srclength, dest); + if (averr != AVCERR_NONE) + return CHDERR_DECOMPRESSION_ERROR; + + /* pad short frames with 0 */ + size = av_raw_data_size(dest); + while (size < chd->header.hunkbytes) + ((UINT8 *)dest)[size++] = 0; + + return CHDERR_NONE; +} + + +/*------------------------------------------------- + av_codec_config - codec-specific configuration + for the A/V codec +-------------------------------------------------*/ + +static chd_error av_codec_config(chd_file *chd, int param, void *config) +{ + av_codec_data *data = chd->codecdata; + + /* if we're getting the decompression configuration, apply it now */ + if (param == AV_CODEC_DECOMPRESS_CONFIG) + { + data->decompconfig = *(av_codec_decompress_config *)config; + if (data->compstate != NULL) + avcomp_decompress_config(data->compstate, data->decompconfig.decode_mask, data->decompconfig.video_buffer, data->decompconfig.video_stride, data->decompconfig.video_xor, data->decompconfig.audio_xor); + return CHDERR_NONE; + } + + /* anything else is invalid */ + return CHDERR_INVALID_PARAMETER; +} + + +/*------------------------------------------------- + av_codec_postinit - actual initialization of + avcomp happens here, on the first attempt + to compress or decompress data +-------------------------------------------------*/ + +static chd_error av_codec_postinit(chd_file *chd) +{ + int fps, fpsfrac, width, height, interlaced, channels, rate, metabytes; + UINT32 fps_times_1million, max_samples_per_frame, bytes_per_frame; + av_codec_data *data = chd->codecdata; + char metadata[256]; + chd_error err; + + /* the code below won't work asynchronously */ + if (chd->workitem != NULL) + return CHDERR_OPERATION_PENDING; + + /* get the metadata */ + err = chd_get_metadata(chd, AV_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL); + if (err != CHDERR_NONE) + return err; + + /* extract the info */ + if (sscanf(metadata, AV_METADATA_FORMAT, &fps, &fpsfrac, &width, &height, &interlaced, &channels, &rate, &metabytes) != 8) + return CHDERR_INVALID_METADATA; + + /* compute the bytes per frame */ + fps_times_1million = fps * 1000000 + fpsfrac; + max_samples_per_frame = ((UINT64)rate * 1000000 + fps_times_1million - 1) / fps_times_1million; + bytes_per_frame = 12 + metabytes + channels * max_samples_per_frame * 2 + width * height * 2; + if (bytes_per_frame > chd->header.hunkbytes) + return CHDERR_INVALID_METADATA; + + /* create the avcomp state */ + data->compstate = avcomp_init(width, height, channels); + + /* configure the codec */ + avcomp_decompress_config(data->compstate, data->decompconfig.decode_mask, data->decompconfig.video_buffer, data->decompconfig.video_stride, data->decompconfig.video_xor, data->decompconfig.audio_xor); + return CHDERR_NONE; +} diff --git a/src/lib/util/chd.h b/src/lib/util/chd.h new file mode 100644 index 00000000000..dd549cd76ca --- /dev/null +++ b/src/lib/util/chd.h @@ -0,0 +1,325 @@ +/*************************************************************************** + + MAME Compressed Hunks of Data file format + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __CHD_H__ +#define __CHD_H__ + +#include "osdcore.h" +#include "corefile.h" + + +/*************************************************************************** + + Compressed Hunks of Data header format. All numbers are stored in + Motorola (big-endian) byte ordering. The header is 76 (V1) or 80 (V2) + bytes long. + + V1 header: + + [ 0] char tag[8]; // 'MComprHD' + [ 8] UINT32 length; // length of header (including tag and length fields) + [ 12] UINT32 version; // drive format version + [ 16] UINT32 flags; // flags (see below) + [ 20] UINT32 compression; // compression type + [ 24] UINT32 hunksize; // 512-byte sectors per hunk + [ 28] UINT32 totalhunks; // total # of hunks represented + [ 32] UINT32 cylinders; // number of cylinders on hard disk + [ 36] UINT32 heads; // number of heads on hard disk + [ 40] UINT32 sectors; // number of sectors on hard disk + [ 44] UINT8 md5[16]; // MD5 checksum of raw data + [ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file + [ 76] (V1 header length) + + V2 header: + + [ 0] char tag[8]; // 'MComprHD' + [ 8] UINT32 length; // length of header (including tag and length fields) + [ 12] UINT32 version; // drive format version + [ 16] UINT32 flags; // flags (see below) + [ 20] UINT32 compression; // compression type + [ 24] UINT32 hunksize; // seclen-byte sectors per hunk + [ 28] UINT32 totalhunks; // total # of hunks represented + [ 32] UINT32 cylinders; // number of cylinders on hard disk + [ 36] UINT32 heads; // number of heads on hard disk + [ 40] UINT32 sectors; // number of sectors on hard disk + [ 44] UINT8 md5[16]; // MD5 checksum of raw data + [ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file + [ 76] UINT32 seclen; // number of bytes per sector + [ 80] (V2 header length) + + V3 header: + + [ 0] char tag[8]; // 'MComprHD' + [ 8] UINT32 length; // length of header (including tag and length fields) + [ 12] UINT32 version; // drive format version + [ 16] UINT32 flags; // flags (see below) + [ 20] UINT32 compression; // compression type + [ 24] UINT32 totalhunks; // total # of hunks represented + [ 28] UINT64 logicalbytes; // logical size of the data (in bytes) + [ 36] UINT64 metaoffset; // offset to the first blob of metadata + [ 44] UINT8 md5[16]; // MD5 checksum of raw data + [ 60] UINT8 parentmd5[16]; // MD5 checksum of parent file + [ 76] UINT32 hunkbytes; // number of bytes per hunk + [ 80] UINT8 sha1[20]; // SHA1 checksum of raw data + [100] UINT8 parentsha1[20];// SHA1 checksum of parent file + [120] (V3 header length) + + Flags: + 0x00000001 - set if this drive has a parent + 0x00000002 - set if this drive allows writes + +***************************************************************************/ + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* header information */ +#define CHD_HEADER_VERSION 3 +#define CHD_V1_HEADER_SIZE 76 +#define CHD_V2_HEADER_SIZE 80 +#define CHD_V3_HEADER_SIZE 120 +#define CHD_MAX_HEADER_SIZE CHD_V3_HEADER_SIZE + +/* checksumming information */ +#define CHD_MD5_BYTES 16 +#define CHD_SHA1_BYTES 20 + +/* CHD global flags */ +#define CHDFLAGS_HAS_PARENT 0x00000001 +#define CHDFLAGS_IS_WRITEABLE 0x00000002 +#define CHDFLAGS_UNDEFINED 0xfffffffc + +/* compression types */ +#define CHDCOMPRESSION_NONE 0 +#define CHDCOMPRESSION_ZLIB 1 +#define CHDCOMPRESSION_ZLIB_PLUS 2 +#define CHDCOMPRESSION_AV 3 + +/* A/V codec configuration parameters */ +#define AV_CODEC_COMPRESS_CONFIG 0 +#define AV_CODEC_DECOMPRESS_CONFIG 1 + +/* metadata parameters */ +#define CHDMETATAG_WILDCARD 0 +#define CHD_METAINDEX_APPEND ((UINT32)-1) + +/* standard hard disk metadata */ +#define HARD_DISK_METADATA_TAG 0x47444444 /* 'GDDD' */ +#define HARD_DISK_METADATA_FORMAT "CYLS:%d,HEADS:%d,SECS:%d,BPS:%d" + +/* standard CD-ROM metadata */ +#define CDROM_OLD_METADATA_TAG 0x43484344 /* 'CHCD' */ +#define CDROM_TRACK_METADATA_TAG 0x43485452 /* 'CHTR' */ +#define CDROM_TRACK_METADATA_FORMAT "TRACK:%d TYPE:%s SUBTYPE:%s FRAMES:%d" + +/* standard A/V metadata */ +#define AV_METADATA_TAG 0x41564156 /* 'AVAV' */ +#define AV_METADATA_FORMAT "FPS:%d.%06d WIDTH:%d HEIGHT:%d INTERLACED:%d CHANNELS:%d SAMPLERATE:%d META:%d" + +/* CHD open values */ +#define CHD_OPEN_READ 1 +#define CHD_OPEN_READWRITE 2 + +/* error types */ +enum _chd_error +{ + CHDERR_NONE, + CHDERR_NO_INTERFACE, + CHDERR_OUT_OF_MEMORY, + CHDERR_INVALID_FILE, + CHDERR_INVALID_PARAMETER, + CHDERR_INVALID_DATA, + CHDERR_FILE_NOT_FOUND, + CHDERR_REQUIRES_PARENT, + CHDERR_FILE_NOT_WRITEABLE, + CHDERR_READ_ERROR, + CHDERR_WRITE_ERROR, + CHDERR_CODEC_ERROR, + CHDERR_INVALID_PARENT, + CHDERR_HUNK_OUT_OF_RANGE, + CHDERR_DECOMPRESSION_ERROR, + CHDERR_COMPRESSION_ERROR, + CHDERR_CANT_CREATE_FILE, + CHDERR_CANT_VERIFY, + CHDERR_NOT_SUPPORTED, + CHDERR_METADATA_NOT_FOUND, + CHDERR_INVALID_METADATA_SIZE, + CHDERR_UNSUPPORTED_VERSION, + CHDERR_VERIFY_INCOMPLETE, + CHDERR_INVALID_METADATA, + CHDERR_INVALID_STATE, + CHDERR_OPERATION_PENDING, + CHDERR_NO_ASYNC_OPERATION, + CHDERR_UNSUPPORTED_FORMAT +}; +typedef enum _chd_error chd_error; + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* opaque types */ +typedef struct _chd_file chd_file; + + +/* progress callback function */ +typedef void (*chd_progress_printf)(const char *format, ...); + + +/* extract header structure (NOT the on-disk header structure) */ +typedef struct _chd_header chd_header; +struct _chd_header +{ + UINT32 length; /* length of header data */ + UINT32 version; /* drive format version */ + UINT32 flags; /* flags field */ + UINT32 compression; /* compression type */ + UINT32 hunkbytes; /* number of bytes per hunk */ + UINT32 totalhunks; /* total # of hunks represented */ + UINT64 logicalbytes; /* logical size of the data */ + UINT64 metaoffset; /* offset in file of first metadata */ + UINT8 md5[CHD_MD5_BYTES]; /* MD5 checksum of raw data */ + UINT8 parentmd5[CHD_MD5_BYTES]; /* MD5 checksum of parent file */ + UINT8 sha1[CHD_SHA1_BYTES]; /* SHA1 checksum of raw data */ + UINT8 parentsha1[CHD_SHA1_BYTES]; /* SHA1 checksum of parent file */ + + UINT32 obsolete_cylinders; /* obsolete field -- do not use! */ + UINT32 obsolete_sectors; /* obsolete field -- do not use! */ + UINT32 obsolete_heads; /* obsolete field -- do not use! */ + UINT32 obsolete_hunksize; /* obsolete field -- do not use! */ +}; + + +/* A/V codec decompression configuration */ +typedef struct _av_codec_decompress_config av_codec_decompress_config; +struct _av_codec_decompress_config +{ + UINT32 decode_mask; /* decoding mask */ + UINT8 * video_buffer; /* pointer to alternate video buffer */ + UINT32 video_stride; /* bytes between rows in video buffer */ + UINT32 video_xor; /* XOR to apply to bytes in video buffer */ + UINT32 audio_xor; /* XOR to apply to bytes in audio buffer */ +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + + +/* ----- CHD file management ----- */ + +/* create a new CHD file fitting the given description */ +chd_error chd_create(const char *filename, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent); + +/* same as chd_create(), but accepts an already-opened core_file object */ +chd_error chd_create_file(core_file *file, UINT64 logicalbytes, UINT32 hunkbytes, UINT32 compression, chd_file *parent); + +/* open an existing CHD file */ +chd_error chd_open(const char *filename, int mode, chd_file *parent, chd_file **chd); + +/* same as chd_open(), but accepts an already-opened core_file object */ +chd_error chd_open_file(core_file *file, int mode, chd_file *parent, chd_file **chd); + +/* close a CHD file */ +void chd_close(chd_file *chd); + +/* return the associated core_file */ +core_file *chd_core_file(chd_file *chd); + + + +/* ----- CHD header management ----- */ + +/* return a pointer to the extracted CHD header data */ +const chd_header *chd_get_header(chd_file *chd); + +/* set a modified header */ +chd_error chd_set_header(const char *filename, const chd_header *header); + +/* same as chd_set_header(), but accepts an already-opened core_file object */ +chd_error chd_set_header_file(core_file *file, const chd_header *header); + + + +/* ----- core data read/write ----- */ + +/* read one hunk from the CHD file */ +chd_error chd_read(chd_file *chd, UINT32 hunknum, void *buffer); + +/* read one hunk from the CHD file asynchronously */ +chd_error chd_read_async(chd_file *chd, UINT32 hunknum, void *buffer); + +/* write one hunk to a CHD file */ +chd_error chd_write(chd_file *chd, UINT32 hunknum, const void *buffer); + +/* write one hunk to a CHD file asynchronously */ +chd_error chd_write_async(chd_file *chd, UINT32 hunknum, const void *buffer); + +/* wait for a previously issued async read/write to complete and return the error */ +chd_error chd_async_complete(chd_file *chd); + + + +/* ----- metadata management ----- */ + +/* get indexed metadata of a particular sort */ +chd_error chd_get_metadata(chd_file *chd, UINT32 searchtag, UINT32 searchindex, void *output, UINT32 outputlen, UINT32 *resultlen, UINT32 *resulttag); + +/* set indexed metadata of a particular sort */ +chd_error chd_set_metadata(chd_file *chd, UINT32 metatag, UINT32 metaindex, const void *inputbuf, UINT32 inputlen); + +/* clone all of the metadata from one CHD to another */ +chd_error chd_clone_metadata(chd_file *source, chd_file *dest); + + + +/* ----- compression management ----- */ + +/* begin compressing data to a CHD */ +chd_error chd_compress_begin(chd_file *chd); + +/* compress the next hunk of data */ +chd_error chd_compress_hunk(chd_file *chd, const void *data, double *curratio); + +/* finish compressing data to a CHD */ +chd_error chd_compress_finish(chd_file *chd); + + + +/* ----- verification management ----- */ + +/* begin verifying a CHD */ +chd_error chd_verify_begin(chd_file *chd); + +/* verify a single hunk of data */ +chd_error chd_verify_hunk(chd_file *chd); + +/* finish verifying a CHD, returning the computed MD5 and SHA1 */ +chd_error chd_verify_finish(chd_file *chd, UINT8 *finalmd5, UINT8 *finalsha1); + + + +/* ----- codec interfaces ----- */ + +/* set internal codec parameters */ +chd_error chd_codec_config(chd_file *chd, int param, void *config); + +/* return a string description of a codec */ +const char *chd_get_codec_name(UINT32 codec); + + +#endif /* __CHD_H__ */ diff --git a/src/lib/util/corefile.c b/src/lib/util/corefile.c new file mode 100644 index 00000000000..008e919a8f7 --- /dev/null +++ b/src/lib/util/corefile.c @@ -0,0 +1,726 @@ +/*************************************************************************** + + corefile.c + + File access functions. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include "corefile.h" +#include "unicode.h" + +#include <stdarg.h> +#include <ctype.h> + + + +/*************************************************************************** + VALIDATION +***************************************************************************/ + +#if !defined(CRLF) || (CRLF < 1) || (CRLF > 3) +#error CRLF undefined: must be 1 (CR), 2 (LF) or 3 (CR/LF) +#endif + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define FILE_BUFFER_SIZE 512 + +#define OPEN_FLAG_HAS_CRC 0x10000 + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +enum _text_file_type +{ + TFT_OSD = 0, /* OSD depdendent encoding format used when BOMs missing */ + TFT_UTF8, /* UTF-8 */ + TFT_UTF16BE, /* UTF-16 (big endian) */ + TFT_UTF16LE, /* UTF-16 (little endian) */ + TFT_UTF32BE, /* UTF-32 (UCS-4) (big endian) */ + TFT_UTF32LE /* UTF-32 (UCS-4) (little endian) */ +}; +typedef enum _text_file_type text_file_type; + + +/* typedef struct _core_file core_file -- declared in corefile.h */ +struct _core_file +{ + osd_file * file; /* OSD file handle */ + UINT32 openflags; /* flags we were opened with */ + UINT8 data_allocated; /* was the data allocated by us? */ + UINT8 * data; /* file data, if RAM-based */ + UINT64 offset; /* current file offset */ + UINT64 length; /* total file length */ + text_file_type text_type; /* text output format */ + char back_chars[UTF8_CHAR_MAX]; /* buffer to hold characters for ungetc */ + int back_char_head; /* head of ungetc buffer */ + int back_char_tail; /* tail of ungetc buffer */ + UINT64 bufferbase; /* base offset of internal buffer */ + UINT32 bufferbytes; /* bytes currently loaded into buffer */ + UINT8 buffer[FILE_BUFFER_SIZE]; /* buffer data */ +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +/* misc helpers */ +static UINT32 safe_buffer_copy(const void *source, UINT32 sourceoffs, UINT32 sourcelen, void *dest, UINT32 destoffs, UINT32 destlen); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + is_directory_separator - is a given character + a directory separator? The following logic + works for most platforms +-------------------------------------------------*/ + +INLINE int is_directory_separator(char c) +{ + return (c == '\\' || c == '/' || c == ':'); +} + + + +/*************************************************************************** + FILE OPEN/CLOSE +***************************************************************************/ + +/*------------------------------------------------- + core_fopen - open a file for access and + return an error code +-------------------------------------------------*/ + +file_error core_fopen(const char *filename, UINT32 openflags, core_file **file) +{ + file_error filerr = FILERR_NOT_FOUND; + + /* allocate the file itself */ + *file = malloc(sizeof(**file)); + if (*file == NULL) + return FILERR_OUT_OF_MEMORY; + memset(*file, 0, sizeof(**file)); + + /* attempt to open the file */ + filerr = osd_open(filename, openflags, &(*file)->file, &(*file)->length); + (*file)->openflags = openflags; + + /* handle errors and return */ + if (filerr != FILERR_NONE) + { + core_fclose(*file); + *file = NULL; + } + return filerr; +} + + +/*------------------------------------------------- + core_fopen_ram - open a RAM-based buffer for + file-like access and return an error code +-------------------------------------------------*/ + +file_error core_fopen_ram(const void *data, size_t length, UINT32 openflags, core_file **file) +{ + /* can only do this for read access */ + if ((openflags & OPEN_FLAG_WRITE) != 0) + return FILERR_INVALID_ACCESS; + if ((openflags & OPEN_FLAG_CREATE) != 0) + return FILERR_INVALID_ACCESS; + + /* allocate the file itself */ + *file = malloc(sizeof(**file)); + if (*file == NULL) + return FILERR_OUT_OF_MEMORY; + memset(*file, 0, sizeof(**file)); + + /* claim the buffer */ + (*file)->data = (UINT8 *)data; + (*file)->length = length; + (*file)->openflags = openflags; + + return FILERR_NONE; +} + + +/*------------------------------------------------- + core_fclose - closes a file +-------------------------------------------------*/ + +void core_fclose(core_file *file) +{ + /* close files and free memory */ + if (file->file != NULL) + osd_close(file->file); + if (file->data != NULL && file->data_allocated) + free(file->data); + free(file); +} + + + +/*************************************************************************** + FILE POSITIONING +***************************************************************************/ + +/*------------------------------------------------- + core_fseek - seek within a file +-------------------------------------------------*/ + +int core_fseek(core_file *file, INT64 offset, int whence) +{ + int err = 0; + + /* flush any buffered char */ + file->back_char_head = 0; + file->back_char_tail = 0; + + /* switch off the relative location */ + switch (whence) + { + case SEEK_SET: + file->offset = offset; + break; + + case SEEK_CUR: + file->offset += offset; + break; + + case SEEK_END: + file->offset = file->length + offset; + break; + } + return err; +} + + +/*------------------------------------------------- + core_ftell - return the current file position +-------------------------------------------------*/ + +UINT64 core_ftell(core_file *file) +{ + /* return the current offset */ + return file->offset; +} + + +/*------------------------------------------------- + core_feof - return 1 if we're at the end + of file +-------------------------------------------------*/ + +int core_feof(core_file *file) +{ + /* check for buffered chars */ + if (file->back_char_head != file->back_char_tail) + return 0; + + /* if the offset == length, we're at EOF */ + return (file->offset >= file->length); +} + + +/*------------------------------------------------- + core_fsize - returns the size of a file +-------------------------------------------------*/ + +UINT64 core_fsize(core_file *file) +{ + return file->length; +} + + + +/*************************************************************************** + FILE READ +***************************************************************************/ + +/*------------------------------------------------- + core_fread - read from a file +-------------------------------------------------*/ + +UINT32 core_fread(core_file *file, void *buffer, UINT32 length) +{ + file_error filerr; + UINT32 bytes_read = 0; + + /* flush any buffered char */ + file->back_char_head = 0; + file->back_char_tail = 0; + + /* handle real files */ + if (file->data == NULL) + { + /* if we're within the buffer, consume that first */ + if (file->offset >= file->bufferbase && file->offset < file->bufferbase + file->bufferbytes) + bytes_read += safe_buffer_copy(file->buffer, file->offset - file->bufferbase, file->bufferbytes, buffer, bytes_read, length); + + /* if we've got a small amount left, read it into the buffer first */ + if (bytes_read < length) + { + if (length - bytes_read < sizeof(file->buffer) / 2) + { + /* read as much as makes sense into the buffer */ + file->bufferbase = file->offset + bytes_read; + file->bufferbytes = 0; + filerr = osd_read(file->file, file->buffer, file->bufferbase, sizeof(file->buffer), &file->bufferbytes); + + /* do a bounded copy from the buffer to the destination */ + bytes_read += safe_buffer_copy(file->buffer, 0, file->bufferbytes, buffer, bytes_read, length); + } + + /* read the remainder directly from the file */ + else + { + UINT32 new_bytes_read; + filerr = osd_read(file->file, (UINT8 *)buffer + bytes_read, file->offset + bytes_read, length - bytes_read, &new_bytes_read); + bytes_read += new_bytes_read; + } + } + } + + /* handle RAM-based files */ + else + bytes_read += safe_buffer_copy(file->data, (UINT32)file->offset, file->length, buffer, bytes_read, length); + + /* return the number of bytes read */ + file->offset += bytes_read; + return bytes_read; +} + + +/*------------------------------------------------- + core_fgetc - read a character from a file +-------------------------------------------------*/ + +int core_fgetc(core_file *file) +{ + int result; + + /* refresh buffer, if necessary */ + if (file->back_char_head == file->back_char_tail) + { + utf16_char utf16_buffer[UTF16_CHAR_MAX]; + char utf8_buffer[UTF8_CHAR_MAX]; + char default_buffer[16]; + unicode_char uchar = (unicode_char) ~0; + int readlen, charlen; + + /* do we need to check the byte order marks? */ + if (file->offset == 0) + { + UINT8 bom[4]; + int pos = 0; + + if (core_fread(file, bom, 4) == 4) + { + if (bom[0] == 0xef && bom[1] == 0xbb && bom[2] == 0xbf) + { + file->text_type = TFT_UTF8; + pos = 3; + } + else if (bom[0] == 0x00 && bom[1] == 0x00 && bom[2] == 0xfe && bom[3] == 0xff) + { + file->text_type = TFT_UTF32BE; + pos = 4; + } + else if (bom[0] == 0xff && bom[1] == 0xfe && bom[2] == 0x00 && bom[3] == 0x00) + { + file->text_type = TFT_UTF32LE; + pos = 4; + } + else if (bom[0] == 0xfe && bom[1] == 0xff) + { + file->text_type = TFT_UTF16BE; + pos = 2; + } + else if (bom[0] == 0xff && bom[1] == 0xfe) + { + file->text_type = TFT_UTF16LE; + pos = 2; + } + else + { + file->text_type = TFT_OSD; + pos = 0; + } + } + core_fseek(file, pos, SEEK_SET); + } + + /* fetch the next character */ + switch (file->text_type) + { + default: + case TFT_OSD: + readlen = core_fread(file, default_buffer, sizeof(default_buffer)); + if (readlen > 0) + { + charlen = osd_uchar_from_osdchar(&uchar, default_buffer, readlen / sizeof(default_buffer[0])); + core_fseek(file, (INT64) (charlen * sizeof(default_buffer[0])) - readlen, SEEK_CUR); + } + break; + + case TFT_UTF8: + readlen = core_fread(file, utf8_buffer, sizeof(utf8_buffer)); + if (readlen > 0) + { + charlen = uchar_from_utf8(&uchar, utf8_buffer, readlen / sizeof(utf8_buffer[0])); + core_fseek(file, (INT64) (charlen * sizeof(utf8_buffer[0])) - readlen, SEEK_CUR); + } + break; + + case TFT_UTF16BE: + readlen = core_fread(file, utf16_buffer, sizeof(utf16_buffer)); + if (readlen > 0) + { + charlen = uchar_from_utf16be(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0])); + core_fseek(file, (INT64) (charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR); + } + break; + + case TFT_UTF16LE: + readlen = core_fread(file, utf16_buffer, sizeof(utf16_buffer)); + if (readlen > 0) + { + charlen = uchar_from_utf16le(&uchar, utf16_buffer, readlen / sizeof(utf16_buffer[0])); + core_fseek(file, (INT64) (charlen * sizeof(utf16_buffer[0])) - readlen, SEEK_CUR); + } + break; + + case TFT_UTF32BE: + if (core_fread(file, &uchar, sizeof(uchar)) == sizeof(uchar)) + uchar = BIG_ENDIANIZE_INT32(uchar); + break; + + case TFT_UTF32LE: + if (core_fread(file, &uchar, sizeof(uchar)) == sizeof(uchar)) + uchar = LITTLE_ENDIANIZE_INT32(uchar); + break; + } + + if (uchar != ~0) + { + /* place the new character in the ring buffer */ + file->back_char_head = 0; + file->back_char_tail = utf8_from_uchar(file->back_chars, ARRAY_LENGTH(file->back_chars), uchar); +/* assert(file->back_char_tail != -1);*/ + } + } + + /* now read from the ring buffer */ + if (file->back_char_head != file->back_char_tail) + { + result = file->back_chars[file->back_char_head++]; + file->back_char_head %= ARRAY_LENGTH(file->back_chars); + } + else + result = EOF; + + return result; +} + + +/*------------------------------------------------- + core_ungetc - put back a character read from + a file +-------------------------------------------------*/ + +int core_ungetc(int c, core_file *file) +{ + file->back_chars[file->back_char_tail++] = (char) c; + file->back_char_tail %= ARRAY_LENGTH(file->back_chars); + return c; +} + + +/*------------------------------------------------- + core_fgets - read a line from a text file +-------------------------------------------------*/ + +char *core_fgets(char *s, int n, core_file *file) +{ + char *cur = s; + + /* loop while we have characters */ + while (n > 0) + { + int c = core_fgetc(file); + if (c == EOF) + break; + + /* if there's a CR, look for an LF afterwards */ + if (c == 0x0d) + { + int c2 = core_fgetc(file); + if (c2 != 0x0a) + core_ungetc(c2, file); + *cur++ = 0x0d; + n--; + break; + } + + /* if there's an LF, reinterp as a CR for consistency */ + else if (c == 0x0a) + { + *cur++ = 0x0d; + n--; + break; + } + + /* otherwise, pop the character in and continue */ + *cur++ = c; + n--; + } + + /* if we put nothing in, return NULL */ + if (cur == s) + return NULL; + + /* otherwise, terminate */ + if (n > 0) + *cur++ = 0; + return s; +} + + +/*------------------------------------------------- + core_fbuffer - return a pointer to the file + buffer; if it doesn't yet exist, load the + file into RAM first +-------------------------------------------------*/ + +const void *core_fbuffer(core_file *file) +{ + file_error filerr; + UINT32 read_length; + + /* if we already have data, just return it */ + if (file->data != NULL) + return file->data; + + /* allocate some memory */ + file->data = malloc(file->length); + if (file->data == NULL) + return NULL; + file->data_allocated = TRUE; + + /* read the file */ + filerr = osd_read(file->file, file->data, 0, file->length, &read_length); + if (filerr != FILERR_NONE || read_length != file->length) + { + free(file->data); + file->data = NULL; + return NULL; + } + + /* close the file because we don't need it anymore */ + osd_close(file->file); + file->file = NULL; + return file->data; +} + + + +/*************************************************************************** + FILE WRITE +***************************************************************************/ + +/*------------------------------------------------- + core_fwrite - write to a file +-------------------------------------------------*/ + +UINT32 core_fwrite(core_file *file, const void *buffer, UINT32 length) +{ + UINT32 bytes_written = 0; + file_error filerr; + + /* can't write to RAM-based stuff */ + if (file->data != NULL) + return 0; + + /* flush any buffered char */ + file->back_char_head = 0; + file->back_char_tail = 0; + + /* invalidate any buffered data */ + file->bufferbytes = 0; + + /* do the write */ + filerr = osd_write(file->file, buffer, file->offset, length, &bytes_written); + + /* return the number of bytes read */ + file->offset += bytes_written; + file->length = MAX(file->length, file->offset); + return bytes_written; +} + + +/*------------------------------------------------- + core_fputs - write a line to a text file +-------------------------------------------------*/ + +int core_fputs(core_file *f, const char *s) +{ + char convbuf[1024]; + char *pconvbuf = convbuf; + int count = 0; + + /* is this the beginning of the file? if so, write a byte order mark */ + if (f->offset == 0 && !(f->openflags & OPEN_FLAG_NO_BOM)) + { + *pconvbuf++ = 0xef; + *pconvbuf++ = 0xbb; + *pconvbuf++ = 0xbf; + } + + /* convert '\n' to platform dependant line endings */ + while (*s != 0) + { + if (*s == '\n') + { + if (CRLF == 1) /* CR only */ + *pconvbuf++ = 13; + else if (CRLF == 2) /* LF only */ + *pconvbuf++ = 10; + else if (CRLF == 3) /* CR+LF */ + { + *pconvbuf++ = 13; + *pconvbuf++ = 10; + } + } + else + *pconvbuf++ = *s; + s++; + + /* if we overflow, break into chunks */ + if (pconvbuf >= convbuf + ARRAY_LENGTH(convbuf) - 10) + { + count += core_fwrite(f, convbuf, pconvbuf - convbuf); + pconvbuf = convbuf; + } + } + + /* final flush */ + if (pconvbuf != convbuf) + count += core_fwrite(f, convbuf, pconvbuf - convbuf); + + return count; +} + + +/*------------------------------------------------- + core_vfprintf - vfprintf to a text file +-------------------------------------------------*/ + +int core_vfprintf(core_file *f, const char *fmt, va_list va) +{ + char buf[1024]; + vsnprintf(buf, sizeof(buf), fmt, va); + return core_fputs(f, buf); +} + + +/*------------------------------------------------- + core_fprintf - vfprintf to a text file +-------------------------------------------------*/ + +int CLIB_DECL core_fprintf(core_file *f, const char *fmt, ...) +{ + int rc; + va_list va; + va_start(va, fmt); + rc = core_vfprintf(f, fmt, va); + va_end(va); + return rc; +} + + + +/*************************************************************************** + FILENAME UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + core_filename_extract_base - extract the base + name from a filename; note that this makes + assumptions about path separators +-------------------------------------------------*/ + +astring *core_filename_extract_base(astring *result, const char *name, int strip_extension) +{ + /* find the start of the name */ + const char *start = name + strlen(name); + while (start > name && !is_directory_separator(start[-1])) + start--; + + /* copy the rest into an astring */ + astring_cpyc(result, start); + + /* chop the extension if present */ + if (strip_extension) + astring_substr(result, 0, astring_rchr(result, 0, '.')); + return result; +} + + +/*------------------------------------------------- + core_filename_ends_with - does the given + filename end with the specified extension? +-------------------------------------------------*/ + +int core_filename_ends_with(const char *filename, const char *extension) +{ + int namelen = strlen(filename); + int extlen = strlen(extension); + int matches = TRUE; + + /* work backwards checking for a match */ + while (extlen > 0) + if (tolower(filename[--namelen]) != tolower(extension[--extlen])) + { + matches = FALSE; + break; + } + + return matches; +} + + + +/*************************************************************************** + MISC HELPERS +***************************************************************************/ + +/*------------------------------------------------- + safe_buffer_copy - copy safely from one + bounded buffer to another +-------------------------------------------------*/ + +static UINT32 safe_buffer_copy(const void *source, UINT32 sourceoffs, UINT32 sourcelen, void *dest, UINT32 destoffs, UINT32 destlen) +{ + UINT32 sourceavail = sourcelen - sourceoffs; + UINT32 destavail = destlen - destoffs; + UINT32 bytes_to_copy = MIN(sourceavail, destavail); + if (bytes_to_copy > 0) + memcpy((UINT8 *)dest + destoffs, (const UINT8 *)source + sourceoffs, bytes_to_copy); + return bytes_to_copy; +} diff --git a/src/lib/util/corefile.h b/src/lib/util/corefile.h new file mode 100644 index 00000000000..dc9e69ce9f5 --- /dev/null +++ b/src/lib/util/corefile.h @@ -0,0 +1,116 @@ +/*************************************************************************** + + corefile.h + + Core file I/O interface functions and definitions. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __COREFILE_H__ +#define __COREFILE_H__ + +#include <stdarg.h> +#include "osdcore.h" +#include "astring.h" + + + +/*************************************************************************** + ADDITIONAL OPEN FLAGS +***************************************************************************/ + +#define OPEN_FLAG_NO_BOM 0x0100 /* don't output BOM */ + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _core_file core_file; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + + +/* ----- file open/close ----- */ + +/* open a file with the specified filename */ +file_error core_fopen(const char *filename, UINT32 openflags, core_file **file); + +/* open a RAM-based "file" using the given data and length (read-only) */ +file_error core_fopen_ram(const void *data, size_t length, UINT32 openflags, core_file **file); + +/* close an open file */ +void core_fclose(core_file *file); + + + +/* ----- file positioning ----- */ + +/* adjust the file pointer within the file */ +int core_fseek(core_file *file, INT64 offset, int whence); + +/* return the current file pointer */ +UINT64 core_ftell(core_file *file); + +/* return true if we are at the EOF */ +int core_feof(core_file *file); + +/* return the total size of the file */ +UINT64 core_fsize(core_file *file); + + + +/* ----- file read ----- */ + +/* standard binary read from a file */ +UINT32 core_fread(core_file *file, void *buffer, UINT32 length); + +/* read one character from the file */ +int core_fgetc(core_file *file); + +/* put back one character from the file */ +int core_ungetc(int c, core_file *file); + +/* read a full line of text from the file */ +char *core_fgets(char *s, int n, core_file *file); + +/* get a pointer to a buffer that holds the full file data in RAM */ +/* this function may cause the full file data to be read */ +const void *core_fbuffer(core_file *file); + + + +/* ----- file write ----- */ + +/* standard binary write to a file */ +UINT32 core_fwrite(core_file *file, const void *buffer, UINT32 length); + +/* write a line of text to the file */ +int core_fputs(core_file *f, const char *s); + +/* printf-style text write to a file */ +int core_vfprintf(core_file *f, const char *fmt, va_list va); +int CLIB_DECL core_fprintf(core_file *f, const char *fmt, ...); + + + +/* ----- filename utilities ----- */ + +/* extract the base part of a filename (remove extensions and paths) */ +astring *core_filename_extract_base(astring *result, const char *name, int strip_extension); + +/* true if the given filename ends with a particular extension */ +int core_filename_ends_with(const char *filename, const char *extension); + + +#endif /* __COREFILE_H__ */ diff --git a/src/lib/util/corestr.c b/src/lib/util/corestr.c new file mode 100644 index 00000000000..dd7bb1610e8 --- /dev/null +++ b/src/lib/util/corestr.c @@ -0,0 +1,120 @@ +/*************************************************************************** + + corestr.c + + Core string functions used throughout MAME. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +****************************************************************************/ + +#include "corestr.h" +#include <ctype.h> +#include <stdlib.h> + + +/*------------------------------------------------- + core_stricmp - case-insensitive string compare +-------------------------------------------------*/ + +int core_stricmp(const char *s1, const char *s2) +{ + for (;;) + { + int c1 = tolower(*s1++); + int c2 = tolower(*s2++); + if (c1 == 0 || c1 != c2) + return c1 - c2; + } +} + + +/*------------------------------------------------- + core_strnicmp - case-insensitive string compare +-------------------------------------------------*/ + +int core_strnicmp(const char *s1, const char *s2, size_t n) +{ + size_t i; + for (i = 0; i < n; i++) + { + int c1 = tolower(*s1++); + int c2 = tolower(*s2++); + if (c1 == 0 || c1 != c2) + return c1 - c2; + } + + return 0; +} + + +/*------------------------------------------------- + core_strwildcmp - case-insensitive wildcard + string compare (up to 8 characters at the + moment) +-------------------------------------------------*/ + +int core_strwildcmp(const char *sp1, const char *sp2) +{ + char s1[9], s2[9]; + int i, l1, l2; + char *p; + + strncpy(s1, sp1, 8); s1[8] = 0; if (s1[0] == 0) strcpy(s1, "*"); + + strncpy(s2, sp2, 8); s2[8] = 0; if (s2[0] == 0) strcpy(s2, "*"); + + p = strchr(s1, '*'); + if (p) + { + for (i = p - s1; i < 8; i++) s1[i] = '?'; + s1[8] = 0; + } + + p = strchr(s2, '*'); + if (p) + { + for (i = p - s2; i < 8; i++) s2[i] = '?'; + s2[8] = 0; + } + + l1 = (int)strlen(s1); + if (l1 < 8) + { + for (i = l1 + 1; i < 8; i++) s1[i] = ' '; + s1[8] = 0; + } + + l2 = (int)strlen(s2); + if (l2 < 8) + { + for (i = l2 + 1; i < 8; i++) s2[i] = ' '; + s2[8] = 0; + } + + for (i = 0; i < 8; i++) + { + if (s1[i] == '?' && s2[i] != '?') s1[i] = s2[i]; + if (s2[i] == '?' && s1[i] != '?') s2[i] = s1[i]; + } + + return core_stricmp(s1, s2); +} + + +/*------------------------------------------------- + core_strdup - string duplication via malloc +-------------------------------------------------*/ + +char *core_strdup(const char *str) +{ + char *cpy = NULL; + if (str != NULL) + { + cpy = malloc(strlen(str) + 1); + if (cpy != NULL) + strcpy(cpy, str); + } + return cpy; +} diff --git a/src/lib/util/corestr.h b/src/lib/util/corestr.h new file mode 100644 index 00000000000..cd2ef4028b7 --- /dev/null +++ b/src/lib/util/corestr.h @@ -0,0 +1,60 @@ +/*************************************************************************** + + corestr.h + + Core string functions used throughout MAME. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __CORESTR_H__ +#define __CORESTR_H__ + +#include <string.h> + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +/* since stricmp is not part of the standard, we use this instead */ +int core_stricmp(const char *s1, const char *s2); + +/* this macro prevents people from using stricmp directly */ +#undef stricmp +#define stricmp !MUST_USE_CORE_STRICMP_INSTEAD! + +/* this macro prevents people from using strcasecmp directly */ +#undef strcasecmp +#define strcasecmp !MUST_USE_CORE_STRICMP_INSTEAD! + + +/* since strnicmp is not part of the standard, we use this instead */ +int core_strnicmp(const char *s1, const char *s2, size_t n); + +/* this macro prevents people from using strnicmp directly */ +#undef strnicmp +#define strnicmp !MUST_USE_CORE_STRNICMP_INSTEAD! + +/* this macro prevents people from using strncasecmp directly */ +#undef strncasecmp +#define strncasecmp !MUST_USE_CORE_STRNICMP_INSTEAD! + + +/* since strdup is not part of the standard, we use this instead */ +char *core_strdup(const char *str); + +/* this macro prevents people from using strdup directly */ +#undef strdup +#define strdup !MUST_USE_CORE_STRDUP_INSTEAD! + + +/* additional string compare helper */ +int core_strwildcmp(const char *sp1, const char *sp2); + + +#endif /* __CORESTR_H__ */ diff --git a/src/lib/util/coreutil.c b/src/lib/util/coreutil.c new file mode 100644 index 00000000000..daaf116dc9c --- /dev/null +++ b/src/lib/util/coreutil.c @@ -0,0 +1,102 @@ +/*************************************************************************** + + coreutil.c + + Miscellaneous utility code + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +****************************************************************************/ + +#include "coreutil.h" + + +/*************************************************************************** + BINARY CODED DECIMAL HELPERS +***************************************************************************/ + +int bcd_adjust(int value) +{ + if ((value & 0xf) >= 0xa) + value = value + 0x10 - 0xa; + if ((value & 0xf0) >= 0xa0) + value = value - 0xa0 + 0x100; + return value; +} + + +UINT32 dec_2_bcd(UINT32 a) +{ + UINT32 result = 0; + int shift = 0; + + while (a != 0) + { + result |= (a % 10) << shift; + a /= 10; + shift += 4; + } + return result; +} + + +UINT32 bcd_2_dec(UINT32 a) +{ + UINT32 result = 0; + UINT32 scale = 1; + + while (a != 0) + { + result += (a & 0x0f) * scale; + a >>= 4; + scale *= 10; + } + return result; +} + + + +/*************************************************************************** + GREGORIAN CALENDAR HELPERS +***************************************************************************/ + +int gregorian_is_leap_year(int year) +{ + return !(year % 100 ? year % 4 : year % 400); +} + + +/* months are one counted */ +int gregorian_days_in_month(int month, int year) +{ + if (month == 2) + return gregorian_is_leap_year(year) ? 29 : 28; + else if (month == 4 || month == 6 || month == 9 || month == 11) + return 30; + else + return 31; +} + + +/*************************************************************************** + MISC +***************************************************************************/ + +void rand_memory(void *memory, size_t length) +{ + UINT8 *bytes = (UINT8 *) memory; + UINT32 currand; + size_t i; + + currand = rand(); + currand <<= 15; + currand ^= rand(); + + for (i = 0; i < length; i++) + { + currand = currand * 214013 + 2531011; + bytes[i] = (UINT8) (currand >> 16); + } +} + diff --git a/src/lib/util/coreutil.h b/src/lib/util/coreutil.h new file mode 100644 index 00000000000..e251d61ca80 --- /dev/null +++ b/src/lib/util/coreutil.h @@ -0,0 +1,44 @@ +/*************************************************************************** + + coreutil.h + + Miscellaneous utility code + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __COREUTIL_H__ +#define __COREUTIL_H__ + +#include "osdcomm.h" + + +/*************************************************************************** + BINARY CODED DECIMAL HELPERS +***************************************************************************/ + +int bcd_adjust(int value); +UINT32 dec_2_bcd(UINT32 a); +UINT32 bcd_2_dec(UINT32 a); + + +/*************************************************************************** + GREGORIAN CALENDAR HELPERS +***************************************************************************/ + +int gregorian_is_leap_year(int year); +int gregorian_days_in_month(int month, int year); + + +/*************************************************************************** + MISC +***************************************************************************/ + +void rand_memory(void *memory, size_t length); + + +#endif /* __COREUTIL_H__ */ diff --git a/src/lib/util/harddisk.c b/src/lib/util/harddisk.c new file mode 100644 index 00000000000..a4edf8524d9 --- /dev/null +++ b/src/lib/util/harddisk.c @@ -0,0 +1,170 @@ +/*************************************************************************** + + Generic MAME hard disk implementation, with differencing files + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include "harddisk.h" + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +struct _hard_disk_file +{ + chd_file * chd; /* CHD file */ + hard_disk_info info; /* hard disk info */ + UINT32 hunksectors; /* sectors per hunk */ + UINT32 cachehunk; /* which hunk is cached */ + UINT8 * cache; /* cache of the current hunk */ +}; + + + +/*************************************************************************** + CORE IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + hard_disk_open - open a hard disk handle, + given a chd_file +-------------------------------------------------*/ + +hard_disk_file *hard_disk_open(chd_file *chd) +{ + int cylinders, heads, sectors, sectorbytes; + hard_disk_file *file; + char metadata[256]; + chd_error err; + + /* punt if no CHD */ + if (chd == NULL) + return NULL; + + /* read the hard disk metadata */ + err = chd_get_metadata(chd, HARD_DISK_METADATA_TAG, 0, metadata, sizeof(metadata), NULL, NULL); + if (err != CHDERR_NONE) + return NULL; + + /* parse the metadata */ + if (sscanf(metadata, HARD_DISK_METADATA_FORMAT, &cylinders, &heads, §ors, §orbytes) != 4) + return NULL; + + /* allocate memory for the hard disk file */ + file = malloc(sizeof(hard_disk_file)); + if (file == NULL) + return NULL; + + /* fill in the data */ + file->chd = chd; + file->info.cylinders = cylinders; + file->info.heads = heads; + file->info.sectors = sectors; + file->info.sectorbytes = sectorbytes; + file->hunksectors = chd_get_header(chd)->hunkbytes / file->info.sectorbytes; + file->cachehunk = -1; + + /* allocate a cache */ + file->cache = malloc(chd_get_header(chd)->hunkbytes); + if (file->cache == NULL) + { + free(file); + return NULL; + } + + return file; +} + + +/*------------------------------------------------- + hard_disk_close - close a hard disk handle +-------------------------------------------------*/ + +void hard_disk_close(hard_disk_file *file) +{ + /* free the cache */ + if (file->cache != NULL) + free(file->cache); + free(file); +} + + +/*------------------------------------------------- + hard_disk_get_chd - get a handle to a CHD + from a hard disk +-------------------------------------------------*/ + +chd_file *hard_disk_get_chd(hard_disk_file *file) +{ + return file->chd; +} + + +/*------------------------------------------------- + hard_disk_get_info - return information about + a hard disk +-------------------------------------------------*/ + +hard_disk_info *hard_disk_get_info(hard_disk_file *file) +{ + return &file->info; +} + + +/*------------------------------------------------- + hard_disk_read - read sectors from a hard + disk +-------------------------------------------------*/ + +UINT32 hard_disk_read(hard_disk_file *file, UINT32 lbasector, void *buffer) +{ + UINT32 hunknum = lbasector / file->hunksectors; + UINT32 sectoroffs = lbasector % file->hunksectors; + + /* if we haven't cached this hunk, read it now */ + if (file->cachehunk != hunknum) + { + chd_error err = chd_read(file->chd, hunknum, file->cache); + if (err != CHDERR_NONE) + return 0; + file->cachehunk = hunknum; + } + + /* copy out the requested sector */ + memcpy(buffer, &file->cache[sectoroffs * file->info.sectorbytes], file->info.sectorbytes); + return 1; +} + + +/*------------------------------------------------- + hard_disk_write - write sectors to a hard + disk +-------------------------------------------------*/ + +UINT32 hard_disk_write(hard_disk_file *file, UINT32 lbasector, const void *buffer) +{ + UINT32 hunknum = lbasector / file->hunksectors; + UINT32 sectoroffs = lbasector % file->hunksectors; + chd_error err; + + /* if we haven't cached this hunk, read it now */ + if (file->cachehunk != hunknum) + { + err = chd_read(file->chd, hunknum, file->cache); + if (err != CHDERR_NONE) + return 0; + file->cachehunk = hunknum; + } + + /* copy in the requested data */ + memcpy(&file->cache[sectoroffs * file->info.sectorbytes], buffer, file->info.sectorbytes); + + /* write it back out */ + err = chd_write(file->chd, hunknum, file->cache); + return (err == CHDERR_NONE) ? 1 : 0; +} diff --git a/src/lib/util/harddisk.h b/src/lib/util/harddisk.h new file mode 100644 index 00000000000..2636fc80094 --- /dev/null +++ b/src/lib/util/harddisk.h @@ -0,0 +1,51 @@ +/*************************************************************************** + + harddisk.h + + Generic MAME hard disk implementation, with differencing files + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __HARDDISK_H__ +#define __HARDDISK_H__ + +#include "osdcore.h" +#include "chd.h" + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _hard_disk_file hard_disk_file; + +typedef struct _hard_disk_info hard_disk_info; +struct _hard_disk_info +{ + UINT32 cylinders; + UINT32 heads; + UINT32 sectors; + UINT32 sectorbytes; +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +hard_disk_file *hard_disk_open(chd_file *chd); +void hard_disk_close(hard_disk_file *file); + +chd_file *hard_disk_get_chd(hard_disk_file *file); +hard_disk_info *hard_disk_get_info(hard_disk_file *file); + +UINT32 hard_disk_read(hard_disk_file *file, UINT32 lbasector, void *buffer); +UINT32 hard_disk_write(hard_disk_file *file, UINT32 lbasector, const void *buffer); + +#endif /* __HARDDISK_H__ */ diff --git a/src/lib/util/huffman.c b/src/lib/util/huffman.c new file mode 100644 index 00000000000..0465f5d94d3 --- /dev/null +++ b/src/lib/util/huffman.c @@ -0,0 +1,934 @@ +/*************************************************************************** + + huffman.c + + Video compression and decompression helpers. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +**************************************************************************** + + Maximum codelength is officially (alphabetsize - 1). This would be 255 bits + (since we use 1 byte values). However, it is also dependent upon the number + of samples used, as follows: + + 2 bits -> 3..4 samples + 3 bits -> 5..7 samples + 4 bits -> 8..12 samples + 5 bits -> 13..20 samples + 6 bits -> 21..33 samples + 7 bits -> 34..54 samples + 8 bits -> 55..88 samples + 9 bits -> 89..143 samples + 10 bits -> 144..232 samples + 11 bits -> 233..376 samples + 12 bits -> 377..609 samples + 13 bits -> 610..986 samples + 14 bits -> 987..1596 samples + 15 bits -> 1597..2583 samples + 16 bits -> 2584..4180 samples -> note that a 4k data size guarantees codelength <= 16 bits + 17 bits -> 4181..6764 samples + 18 bits -> 6765..10945 samples + 19 bits -> 10946..17710 samples + 20 bits -> 17711..28656 samples + 21 bits -> 28657..46367 samples + 22 bits -> 46368..75024 samples + 23 bits -> 75025..121392 samples + 24 bits -> 121393..196417 samples + 25 bits -> 196418..317810 samples + 26 bits -> 317811..514228 samples + 27 bits -> 514229..832039 samples + 28 bits -> 832040..1346268 samples + 29 bits -> 1346269..2178308 samples + 30 bits -> 2178309..3524577 samples + 31 bits -> 3524578..5702886 samples + 32 bits -> 5702887..9227464 samples + + Looking at it differently, here is where powers of 2 fall into these buckets: + + 256 samples -> 11 bits max + 512 samples -> 12 bits max + 1k samples -> 14 bits max + 2k samples -> 15 bits max + 4k samples -> 16 bits max + 8k samples -> 18 bits max + 16k samples -> 19 bits max + 32k samples -> 21 bits max + 64k samples -> 22 bits max + 128k samples -> 24 bits max + 256k samples -> 25 bits max + 512k samples -> 27 bits max + 1M samples -> 28 bits max + 2M samples -> 29 bits max + 4M samples -> 31 bits max + 8M samples -> 32 bits max + +***************************************************************************/ + +#include "huffman.h" + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define MAX_HUFFMAN_NODES (256 + 256) + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _bit_buffer bit_buffer; +struct _bit_buffer +{ + UINT32 buffer; + int bits; + union + { + const UINT8 *read; + UINT8 * write; + } data; + UINT32 doffset; + UINT32 dlength; + int overflow; +}; + + +typedef struct _huffman_node huffman_node; +struct _huffman_node +{ + huffman_node * parent; + UINT32 count; + UINT32 weight; + UINT32 bits; + UINT8 numbits; +}; + + +struct _huffman_context +{ + UINT8 maxbits; + UINT8 lookupdirty; + huffman_node huffnode[MAX_HUFFMAN_NODES]; + UINT32 lookupmask; + huffman_lookup_value *lookup; +}; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + +static void huffman_write_rle_tree_bits(bit_buffer *bitbuf, int value, int repcount, int numbits); +static int CLIB_DECL huffman_tree_node_compare(const void *item1, const void *item2); +static int huffman_build_tree(huffman_context *context, const UINT32 *datahisto, UINT32 totaldata, UINT32 totalweight); +static huffman_error huffman_assign_canonical_codes(huffman_context *context); +static huffman_error huffman_build_lookup_table(huffman_context *context); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + bit_buffer_write_init - initialize a bit + buffer for writing +-------------------------------------------------*/ + +INLINE void bit_buffer_write_init(bit_buffer *bitbuf, UINT8 *data, UINT32 dlength) +{ + /* fill in the basic data structure */ + bitbuf->buffer = 0; + bitbuf->bits = 0; + bitbuf->data.write = data; + bitbuf->doffset = 0; + bitbuf->dlength = dlength; + bitbuf->overflow = FALSE; +} + + +/*------------------------------------------------- + bit_buffer_write - write 'numbits' to the + bit buffer, assuming that 'newbits' is right- + justified +-------------------------------------------------*/ + +INLINE void bit_buffer_write(bit_buffer *bitbuf, UINT32 newbits, int numbits) +{ + /* flush the buffer if we're going to overflow it */ + if (bitbuf->bits + numbits > 32) + while (bitbuf->bits >= 8) + { + if (bitbuf->doffset < bitbuf->dlength) + bitbuf->data.write[bitbuf->doffset] = bitbuf->buffer >> 24; + else + bitbuf->overflow = TRUE; + bitbuf->doffset++; + bitbuf->buffer <<= 8; + bitbuf->bits -= 8; + } + + /* shift the bits to the top */ + newbits <<= 32 - numbits; + + /* now shift it down to account for the number of bits we already have and OR them in */ + bitbuf->buffer |= newbits >> bitbuf->bits; + bitbuf->bits += numbits; +} + + +/*------------------------------------------------- + bit_buffer_flush - flush any bits in the write + buffer and return the final data offset +-------------------------------------------------*/ + +INLINE UINT32 bit_buffer_flush(bit_buffer *bitbuf) +{ + while (bitbuf->bits > 0) + { + if (bitbuf->doffset < bitbuf->dlength) + bitbuf->data.write[bitbuf->doffset] = bitbuf->buffer >> 24; + else + bitbuf->overflow = TRUE; + bitbuf->doffset++; + bitbuf->buffer <<= 8; + bitbuf->bits -= 8; + } + return bitbuf->doffset; +} + + +/*------------------------------------------------- + bit_buffer_read_init - initialize a bit + buffer for reading +-------------------------------------------------*/ + +INLINE void bit_buffer_read_init(bit_buffer *bitbuf, const UINT8 *data, UINT32 dlength) +{ + /* fill in the basic data structure */ + bitbuf->buffer = 0; + bitbuf->bits = 0; + bitbuf->data.read = data; + bitbuf->doffset = 0; + bitbuf->dlength = dlength; + bitbuf->overflow = FALSE; +} + + +/*------------------------------------------------- + bit_buffer_read - read 'numbits' bits from + the buffer, returning the right-justified +-------------------------------------------------*/ + +INLINE UINT32 bit_buffer_read(bit_buffer *bitbuf, int numbits) +{ + UINT32 result; + + /* fetch data if we need more */ + if (numbits > bitbuf->bits) + { + while (bitbuf->bits <= 24) + { + if (bitbuf->doffset < bitbuf->dlength) + bitbuf->buffer |= bitbuf->data.read[bitbuf->doffset] << (24 - bitbuf->bits); + bitbuf->doffset++; + bitbuf->bits += 8; + } + if (numbits > bitbuf->bits) + bitbuf->overflow = TRUE; + } + + /* return the data */ + result = bitbuf->buffer >> (32 - numbits); + bitbuf->buffer <<= numbits; + bitbuf->bits -= numbits; + return result; +} + + +/*------------------------------------------------- + bit_buffer_read_offset - return the current + rounded byte reading offset +-------------------------------------------------*/ + +INLINE UINT32 bit_buffer_read_offset(bit_buffer *bitbuf) +{ + UINT32 result = bitbuf->doffset; + int bits = bitbuf->bits; + while (bits >= 8) + { + result--; + bits -= 8; + } + return result; +} + + + +/*************************************************************************** + IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + huffman_create_context - create an encoding/ + decoding context +-------------------------------------------------*/ + +huffman_error huffman_create_context(huffman_context **context, int maxbits) +{ + /* limit to 24 bits */ + if (maxbits > 24) + return HUFFERR_TOO_MANY_BITS; + + /* allocate a context */ + *context = malloc(sizeof(**context)); + if (*context == NULL) + return HUFFERR_OUT_OF_MEMORY; + + /* set the info */ + memset(*context, 0, sizeof(**context)); + (*context)->maxbits = maxbits; + (*context)->lookupmask = (1 << maxbits) - 1; + (*context)->lookupdirty = TRUE; + + return HUFFERR_NONE; +} + + +/*------------------------------------------------- + huffman_free_context - free an encoding/ + decoding context +-------------------------------------------------*/ + +void huffman_free_context(huffman_context *context) +{ + if (context->lookup != NULL) + free(context->lookup); + free(context); +} + + +/*------------------------------------------------- + huffman_compute_tree - compute an optimal + huffman tree for the given source data +-------------------------------------------------*/ + +huffman_error huffman_compute_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 sstride) +{ + UINT32 lowerweight, upperweight; + UINT32 datahisto[256]; + int i; + + /* build the data histogram */ + memset(datahisto, 0, sizeof(datahisto)); + for (i = 0; i < slength; i += sstride) + datahisto[source[i]]++; + + /* binary search to achieve the optimum encoding */ + lowerweight = 0; + upperweight = slength * 2; + while (TRUE) + { + UINT32 curweight = (upperweight + lowerweight) / 2; + int curmaxbits; + + /* build a tree using the current weight */ + curmaxbits = huffman_build_tree(context, datahisto, slength, curweight); + + /* apply binary search here */ + if (curmaxbits <= context->maxbits) + { + lowerweight = curweight; + + /* early out if it worked with the raw weights, or if we're done searching */ + if (curweight == slength || (upperweight - lowerweight) <= 1) + break; + } + else + upperweight = curweight; + } + + /* assign canonical codes for all nodes based on their code lengths */ + return huffman_assign_canonical_codes(context); +} + + +/*------------------------------------------------- + huffman_import_tree - import a huffman tree + from a source data stream +-------------------------------------------------*/ + +huffman_error huffman_import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength) +{ + huffman_error error; + bit_buffer bitbuf; + int curnode; + int numbits; + + /* initialize the input buffer */ + bit_buffer_read_init(&bitbuf, source, slength); + + /* bits per entry depends on the maxbits */ + if (context->maxbits >= 16) + numbits = 5; + else if (context->maxbits >= 8) + numbits = 4; + else + numbits = 3; + + /* loop until we read all the nodes */ + for (curnode = 0; curnode < 256; ) + { + int nodebits = bit_buffer_read(&bitbuf, numbits); + + /* a non-one value is just raw */ + if (nodebits != 1) + context->huffnode[curnode++].numbits = nodebits; + + /* a one value is an escape code */ + else + { + nodebits = bit_buffer_read(&bitbuf, numbits); + + /* a double 1 is just a single 1 */ + if (nodebits == 1) + context->huffnode[curnode++].numbits = nodebits; + + /* otherwise, we need one for value for the repeat count */ + else + { + int repcount = bit_buffer_read(&bitbuf, numbits) + 3; + while (repcount--) + context->huffnode[curnode++].numbits = nodebits; + } + } + } + + /* assign canonical codes for all nodes based on their code lengths */ + error = huffman_assign_canonical_codes(context); + if (error != HUFFERR_NONE) + return error; + + /* make sure we ended up with the right number */ + if (curnode != 256) + return HUFFERR_INVALID_DATA; + + *actlength = bit_buffer_read_offset(&bitbuf); + return bitbuf.overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; +} + + +/*------------------------------------------------- + huffman_export_tree - export a huffman tree + to a target data stream +-------------------------------------------------*/ + +huffman_error huffman_export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength) +{ + bit_buffer bitbuf; + int repcount; + int lastval; + int numbits; + int i; + + /* initialize the output buffer */ + bit_buffer_write_init(&bitbuf, dest, dlength); + + /* bits per entry depends on the maxbits */ + if (context->maxbits >= 16) + numbits = 5; + else if (context->maxbits >= 8) + numbits = 4; + else + numbits = 3; + + /* RLE encode the lengths */ + lastval = ~0; + repcount = 0; + for (i = 0; i < 256; i++) + { + int newval = context->huffnode[i].numbits; + + /* if we match the previous value, just bump the repcount */ + if (newval == lastval) + repcount++; + + /* otherwise, we need to flush the previous repeats */ + else + { + if (repcount != 0) + huffman_write_rle_tree_bits(&bitbuf, lastval, repcount, numbits); + lastval = newval; + repcount = 1; + } + } + + /* flush the last value */ + huffman_write_rle_tree_bits(&bitbuf, lastval, repcount, numbits); + *actlength = bit_buffer_flush(&bitbuf); + return bitbuf.overflow ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; +} + + +/*------------------------------------------------- + huffman_get_lookup_table - return a pointer to + the lookup table +-------------------------------------------------*/ + +huffman_error huffman_get_lookup_table(huffman_context *context, const huffman_lookup_value **table) +{ + if (context->lookupdirty) + { + huffman_error error = huffman_build_lookup_table(context); + if (error != HUFFERR_NONE) + return error; + } + *table = context->lookup; + return HUFFERR_NONE; +} + + +/*------------------------------------------------- + huffman_encode_data - encode data using the + current tree +-------------------------------------------------*/ + +huffman_error huffman_encode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 *actlength) +{ + bit_buffer bitbuf; + UINT32 soffset; + + /* initialize the output buffer */ + bit_buffer_write_init(&bitbuf, dest, dlength); + + /* loop over source data and encode */ + for (soffset = 0; soffset < slength; soffset++) + { + huffman_node *node = &context->huffnode[source[soffset]]; + bit_buffer_write(&bitbuf, node->bits, node->numbits); + } + *actlength = bit_buffer_flush(&bitbuf); + return bitbuf.overflow ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; +} + + +/*------------------------------------------------- + huffman_encode_data_interleaved_2 - encode + alternating data with two contexts +-------------------------------------------------*/ + +huffman_error huffman_encode_data_interleaved_2(huffman_context *context1, huffman_context *context2, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 *actlength) +{ + bit_buffer bitbuf; + UINT32 soffset; + + /* initialize the output buffer */ + bit_buffer_write_init(&bitbuf, dest, dlength); + + /* loop over source data and encode */ + for (soffset = 0; soffset < slength; soffset += 2) + { + huffman_node *node; + + node = &context1->huffnode[source[soffset + 0]]; + bit_buffer_write(&bitbuf, node->bits, node->numbits); + + node = &context2->huffnode[source[soffset + 1]]; + bit_buffer_write(&bitbuf, node->bits, node->numbits); + } + *actlength = bit_buffer_flush(&bitbuf); + return bitbuf.overflow ? HUFFERR_OUTPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; +} + + +/*------------------------------------------------- + huffman_decode_data - decode data using the + current tree +-------------------------------------------------*/ + +huffman_error huffman_decode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 *actlength) +{ + int maxbits = context->maxbits; + int shiftbits = 32 - maxbits; + const huffman_lookup_value *table; + int overflow = FALSE; + huffman_error error; + UINT32 doffset = 0; + UINT32 soffset = 0; + UINT32 bitbuf = 0; + int sbits = 0; + + /* regenerate the lookup table if necessary */ + error = huffman_get_lookup_table(context, &table); + if (error != HUFFERR_NONE) + return error; + + /* decode until we process all of the destination data */ + for (doffset = 0; doffset < dlength; doffset++) + { + huffman_lookup_value lookup; + + /* if we don't have enough bits, load up the buffer */ + if (sbits < maxbits) + { + while (sbits <= 24) + { + if (soffset < slength) + bitbuf |= source[soffset] << (24 - sbits); + soffset++; + sbits += 8; + } + if (sbits < maxbits) + overflow = TRUE; + } + + /* lookup the data */ + lookup = table[bitbuf >> shiftbits]; + + /* store the upper byte */ + dest[doffset] = lookup >> 8; + + /* count the bits */ + lookup &= 0x1f; + bitbuf <<= lookup; + sbits -= lookup; + } + + /* back off soffset while we have whole bytes */ + while (sbits >= 8) + { + sbits -= 8; + soffset--; + } + *actlength = soffset; + return overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; +} + + +/*------------------------------------------------- + huffman_decode_data_interleaved_2 - decode + interleaved data using two contexts +-------------------------------------------------*/ + +huffman_error huffman_decode_data_interleaved_2(huffman_context *context1, huffman_context *context2, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 *actlength) +{ + int maxbits1 = context1->maxbits, maxbits2 = context2->maxbits; + const huffman_lookup_value *table1, *table2; + int shiftbits1 = 32 - maxbits1; + int shiftbits2 = 32 - maxbits2; + int overflow = FALSE; + huffman_error error; + UINT32 doffset = 0; + UINT32 soffset = 0; + UINT32 bitbuf = 0; + int sbits = 0; + + /* regenerate the lookup table if necessary */ + error = huffman_get_lookup_table(context1, &table1); + if (error != HUFFERR_NONE) + return error; + error = huffman_get_lookup_table(context2, &table2); + if (error != HUFFERR_NONE) + return error; + + /* decode until we process all of the destination data */ + for (doffset = 0; doffset < dlength; doffset += 2) + { + huffman_lookup_value lookup; + + /* if we don't have enough bits, load up the buffer */ + if (sbits < maxbits1) + { + while (sbits <= 24) + { + if (soffset < slength) + bitbuf |= source[soffset] << (24 - sbits); + soffset++; + sbits += 8; + } + if (sbits < maxbits1) + overflow = TRUE; + } + + /* lookup the data */ + lookup = table1[bitbuf >> shiftbits1]; + + /* store the upper byte */ + dest[doffset + 0] = lookup >> 8; + + /* count the bits */ + lookup &= 0x1f; + bitbuf <<= lookup; + sbits -= lookup; + + /* if we don't have enough bits, load up the buffer */ + if (sbits < maxbits2) + { + while (sbits <= 24) + { + if (soffset < slength) + bitbuf |= source[soffset] << (24 - sbits); + soffset++; + sbits += 8; + } + if (sbits < maxbits2) + overflow = TRUE; + } + + /* lookup the data */ + lookup = table2[bitbuf >> shiftbits2]; + + /* store the upper byte */ + dest[doffset + 1] = lookup >> 8; + + /* count the bits */ + lookup &= 0x1f; + bitbuf <<= lookup; + sbits -= lookup; + } + + /* back off soffset while we have whole bytes */ + while (sbits >= 8) + { + sbits -= 8; + soffset--; + } + *actlength = soffset; + return overflow ? HUFFERR_INPUT_BUFFER_TOO_SMALL : HUFFERR_NONE; +} + + +/*------------------------------------------------- + huffman_write_rle_tree_bits - write an RLE + encoded set of data to a target stream +-------------------------------------------------*/ + +static void huffman_write_rle_tree_bits(bit_buffer *bitbuf, int value, int repcount, int numbits) +{ + /* loop until we have output all of the repeats */ + while (repcount > 0) + { + /* if we have a 1, write it twice as it is an escape code */ + if (value == 1) + { + bit_buffer_write(bitbuf, 1, numbits); + bit_buffer_write(bitbuf, 1, numbits); + repcount--; + } + + /* if we have two or fewer in a row, write them raw */ + else if (repcount <= 2) + { + bit_buffer_write(bitbuf, value, numbits); + repcount--; + } + + /* otherwise, write a triple using 1 as the escape code */ + else + { + int cur_reps = MIN(repcount - 3, (1 << numbits) - 1); + bit_buffer_write(bitbuf, 1, numbits); + bit_buffer_write(bitbuf, value, numbits); + bit_buffer_write(bitbuf, cur_reps, numbits); + repcount -= cur_reps + 3; + } + } +} + + +/*------------------------------------------------- + huffman_tree_node_compare - compare two + tree nodes by weight +-------------------------------------------------*/ + +static int CLIB_DECL huffman_tree_node_compare(const void *item1, const void *item2) +{ + const huffman_node *node1 = *(const huffman_node **)item1; + const huffman_node *node2 = *(const huffman_node **)item2; + return node2->weight - node1->weight; +} + + +/*------------------------------------------------- + huffman_build_tree - build a huffman tree + based on the data distribution +-------------------------------------------------*/ + +static int huffman_build_tree(huffman_context *context, const UINT32 *datahisto, UINT32 totaldata, UINT32 totalweight) +{ + huffman_node *list[256]; + int listitems; + int nextalloc; + int maxbits; + int i; + + /* make a list of all non-zero nodes */ + listitems = 0; + memset(context->huffnode, 0, 256 * sizeof(context->huffnode[0])); + for (i = 0; i < 256; i++) + if (datahisto[i] != 0) + { + list[listitems++] = &context->huffnode[i]; + context->huffnode[i].count = datahisto[i]; + + /* scale the weight by the current effective length, ensuring we don't go to 0 */ + context->huffnode[i].weight = (UINT64)datahisto[i] * (UINT64)totalweight / (UINT64)totaldata; + if (context->huffnode[i].weight == 0) + context->huffnode[i].weight = 1; + } + + /* sort the list by weight, largest weight first */ + qsort(list, listitems, sizeof(list[0]), huffman_tree_node_compare); + + /* now build the tree */ + nextalloc = 256; + while (listitems > 1) + { + huffman_node *node0, *node1, *newnode; + + /* remove lowest two items */ + node1 = list[--listitems]; + node0 = list[--listitems]; + + /* create new node */ + newnode = &context->huffnode[nextalloc++]; + newnode->parent = NULL; + node0->parent = node1->parent = newnode; + newnode->weight = node0->weight + node1->weight; + + /* insert into list at appropriate location */ + for (i = 0; i < listitems; i++) + if (newnode->weight > list[i]->weight) + { + memmove(&list[i+1], &list[i], (listitems - i) * sizeof(list[0])); + break; + } + list[i] = newnode; + listitems++; + } + + /* compute the number of bits in each code, and fill in another histogram */ + maxbits = 0; + for (i = 0; i < 256; i++) + { + huffman_node *node = &context->huffnode[i]; + node->numbits = 0; + + /* if we have a non-zero weight, compute the number of bits */ + if (node->weight > 0) + { + huffman_node *curnode; + + /* determine the number of bits for this node */ + for (curnode = node; curnode->parent != NULL; curnode = curnode->parent) + node->numbits++; + if (node->numbits == 0) + node->numbits = 1; + + /* keep track of the max */ + maxbits = MAX(maxbits, node->numbits); + } + } + + return maxbits; +} + + +/*------------------------------------------------- + huffman_assign_canonical_codes - assign + canonical codes to all the nodes based on the + number of bits in each +-------------------------------------------------*/ + +static huffman_error huffman_assign_canonical_codes(huffman_context *context) +{ + UINT32 bithisto[33]; + int curstart; + int i; + + /* build up a histogram of bit lengths */ + memset(bithisto, 0, sizeof(bithisto)); + for (i = 0; i < 256; i++) + { + huffman_node *node = &context->huffnode[i]; + if (node->numbits > context->maxbits) + return HUFFERR_INTERNAL_INCONSISTENCY; + if (node->numbits <= 32) + bithisto[node->numbits]++; + } + + /* for each code length, determine the starting code number */ + curstart = 0; + for (i = 32; i > 0; i--) + { + UINT32 nextstart = (curstart + bithisto[i]) >> 1; + if (i != 1 && nextstart * 2 != (curstart + bithisto[i])) + return HUFFERR_INTERNAL_INCONSISTENCY; + bithisto[i] = curstart; + curstart = nextstart; + } + + /* now assign canonical codes */ + for (i = 0; i < 256; i++) + { + huffman_node *node = &context->huffnode[i]; + if (node->numbits > 0) + node->bits = bithisto[node->numbits]++; + } + + /* if there was a decoding table, get rid of it now */ + context->lookupdirty = TRUE; + return HUFFERR_NONE; +} + + +/*------------------------------------------------- + huffman_build_lookup_table - build a lookup + table for fast decoding +-------------------------------------------------*/ + +static huffman_error huffman_build_lookup_table(huffman_context *context) +{ + int i; + + /* allocate a table if needed */ + if (context->lookup == NULL) + context->lookup = malloc((UINT32)sizeof(context->lookup[0]) * (UINT32)(1 << context->maxbits)); + if (context->lookup == NULL) + return HUFFERR_OUT_OF_MEMORY; + + /* now build */ + for (i = 0; i < 256; i++) + { + huffman_node *node = &context->huffnode[i]; + if (node->numbits > 0) + { + huffman_lookup_value *dest, *destend; + + /* left justify this node's bit values to max bits */ + int shift = context->maxbits - node->numbits; + UINT32 start = node->bits << shift; + UINT32 end = ((node->bits + 1) << shift) - 1; + huffman_lookup_value value; + + /* set up the entry */ + value = (i << 8) | node->numbits; + + /* fill all matching entries */ + dest = &context->lookup[start]; + destend = &context->lookup[end]; + while (dest <= destend) + *dest++ = value; + } + } + + /* no longer dirty */ + context->lookupdirty = FALSE; + return HUFFERR_NONE; +} diff --git a/src/lib/util/huffman.h b/src/lib/util/huffman.h new file mode 100644 index 00000000000..efc451b7483 --- /dev/null +++ b/src/lib/util/huffman.h @@ -0,0 +1,62 @@ +/*************************************************************************** + + huffman.h + + Huffman compression routines. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#ifndef __HUFFMAN_H__ + +#include "osdcore.h" + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +enum _huffman_error +{ + HUFFERR_NONE = 0, + HUFFERR_OUT_OF_MEMORY, + HUFFERR_TOO_MANY_BITS, + HUFFERR_INVALID_DATA, + HUFFERR_INPUT_BUFFER_TOO_SMALL, + HUFFERR_OUTPUT_BUFFER_TOO_SMALL, + HUFFERR_INTERNAL_INCONSISTENCY +}; +typedef enum _huffman_error huffman_error; + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef UINT16 huffman_lookup_value; + +typedef struct _huffman_context huffman_context; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +huffman_error huffman_create_context(huffman_context **context, int maxbits); +void huffman_free_context(huffman_context *context); + +huffman_error huffman_compute_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 sstride); +huffman_error huffman_import_tree(huffman_context *context, const UINT8 *source, UINT32 slength, UINT32 *actlength); +huffman_error huffman_export_tree(huffman_context *context, UINT8 *dest, UINT32 dlength, UINT32 *actlength); +huffman_error huffman_get_lookup_table(huffman_context *context, const huffman_lookup_value **table); + +huffman_error huffman_encode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 *actlength); +huffman_error huffman_encode_data_interleaved_2(huffman_context *context1, huffman_context *context2, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 *actlength); +huffman_error huffman_decode_data(huffman_context *context, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 *actlength); +huffman_error huffman_decode_data_interleaved_2(huffman_context *context1, huffman_context *context2, const UINT8 *source, UINT32 slength, UINT8 *dest, UINT32 dlength, UINT32 *actlength); + +#endif diff --git a/src/lib/util/jedparse.c b/src/lib/util/jedparse.c new file mode 100644 index 00000000000..5a06b9353a5 --- /dev/null +++ b/src/lib/util/jedparse.c @@ -0,0 +1,435 @@ +/*************************************************************************** + + jedparse.c + + Parser for .JED files into raw fusemaps. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +**************************************************************************** + + Binary file format: + + Offset + 0 = Total number of fuses (32 bits) + 4 = Raw fuse data, packed 8 bits at a time, LSB to MSB + +***************************************************************************/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include "jedparse.h" + + + +/*************************************************************************** + DEBUGGING +***************************************************************************/ + +#define LOG_PARSE 0 + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _parse_info parse_info; +struct _parse_info +{ + UINT16 checksum; /* checksum value */ + UINT32 explicit_numfuses; /* explicitly specified number of fuses */ +}; + + + +/*************************************************************************** + UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + ishex - is a character a valid hex digit? +-------------------------------------------------*/ + +static int ishex(char c) +{ + return (c >= '0' && c <= '9') || (c >= 'A' && c <= 'F'); +} + + + +/*------------------------------------------------- + hexval - the hex value of a given character +-------------------------------------------------*/ + +static int hexval(char c) +{ + return (c >= '0' && c <= '9') ? (c - '0') : (10 + c - 'A'); +} + + + +/*------------------------------------------------- + isdelim - is a character a JEDEC delimiter? +-------------------------------------------------*/ + +static int isdelim(char c) +{ + return (c == ' ' || c == 13 || c == 10); +} + + + +/*------------------------------------------------- + suck_number - read a decimal value from the + character stream +-------------------------------------------------*/ + +static UINT32 suck_number(const UINT8 **psrc) +{ + const UINT8 *src = *psrc; + UINT32 value = 0; + + /* skip delimiters */ + while (isdelim(*src)) + src++; + + /* loop over and accumulate digits */ + while (isdigit(*src)) + { + value = value * 10 + *src - '0'; + src++; + } + + /* return a pointer to the string afterwards */ + *psrc = src; + return value; +} + + + +/*************************************************************************** + CORE IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + process_field - process a single JEDEC field +-------------------------------------------------*/ + +static void process_field(jed_data *data, const UINT8 *cursrc, const UINT8 *srcend, parse_info *pinfo) +{ + /* switch off of the field type */ + switch (*cursrc) + { + case 'Q': + cursrc++; + switch (*cursrc) + { + /* number of fuses */ + case 'F': + cursrc++; + pinfo->explicit_numfuses = data->numfuses = suck_number(&cursrc); + break; + } + break; + + /* default fuse state (0 or 1) */ + case 'F': + cursrc++; + if (LOG_PARSE) printf("F%c\n", *cursrc); + if (*cursrc == '0') + memset(data->fusemap, 0x00, sizeof(data->fusemap)); + else + memset(data->fusemap, 0xff, sizeof(data->fusemap)); + break; + + /* fuse states */ + case 'L': + { + UINT32 curfuse; + + /* read the fuse number */ + cursrc++; + curfuse = suck_number(&cursrc); + if (LOG_PARSE) printf("L%d\n", curfuse); + + /* read digits, skipping delimiters */ + for ( ; cursrc < srcend; cursrc++) + if (*cursrc == '0' || *cursrc == '1') + { + jed_set_fuse(data, curfuse, *cursrc - '0'); + if (LOG_PARSE) printf(" fuse %d = %d\n", curfuse, 0); + if (curfuse >= data->numfuses) + data->numfuses = curfuse + 1; + curfuse++; + } + break; + } + + /* fuse checksum */ + case 'C': + cursrc++; + if (cursrc < srcend + 4 && ishex(cursrc[0]) && ishex(cursrc[1]) && ishex(cursrc[2]) && ishex(cursrc[3])) + { + pinfo->checksum = 0; + while (ishex(*cursrc) && cursrc < srcend) + pinfo->checksum = (pinfo->checksum << 4) | hexval(*cursrc++); + } + break; + } +} + + + +/*------------------------------------------------- + jed_parse - parse a .JED file that has been + loaded raw into memory +-------------------------------------------------*/ + +int jed_parse(const void *data, size_t length, jed_data *result) +{ + const UINT8 *cursrc = data; + const UINT8 *srcend = cursrc + length; + const UINT8 *scan; + parse_info pinfo; + UINT16 checksum; + int i; + + /* initialize the output and the intermediate info struct */ + memset(result, 0, sizeof(*result)); + memset(&pinfo, 0, sizeof(pinfo)); + + /* first scan for the STX character; ignore anything prior */ + while (cursrc < srcend && *cursrc != 0x02) + cursrc++; + if (cursrc >= srcend) + return JEDERR_INVALID_DATA; + + /* then scan to see if we have an ETX */ + scan = cursrc; + checksum = 0; + while (scan < srcend && *scan != 0x03) + checksum += *scan++ & 0x7f; + if (scan >= srcend) + return JEDERR_INVALID_DATA; + + /* see if there is a transmission checksum at the end */ + checksum += *scan; + if (scan + 4 < srcend && ishex(scan[1]) && ishex(scan[2]) && ishex(scan[3]) && ishex(scan[4])) + { + UINT16 dessum = (hexval(scan[1]) << 12) | (hexval(scan[2]) << 8) | (hexval(scan[3]) << 4) | hexval(scan[4] << 0); + if (dessum != 0 && dessum != checksum) + return JEDERR_BAD_XMIT_SUM; + } + + /* the ETX becomes the real srcend */ + srcend = scan; + + /* blast through the comment field */ + cursrc++; + while (cursrc < srcend && *cursrc != '*') + cursrc++; + + /* now loop over fields and decide which ones go in the file output */ + cursrc++; + while (cursrc < srcend) + { + /* skip over delimiters */ + while (cursrc < srcend && isdelim(*cursrc)) + cursrc++; + if (cursrc >= srcend) + break; + + /* end of field is an asterisk -- find it */ + scan = cursrc; + while (scan < srcend && *scan != '*') + scan++; + if (scan >= srcend) + return JEDERR_INVALID_DATA; + + /* process the field */ + process_field(result, cursrc, scan, &pinfo); + + /* advance past it */ + cursrc = scan + 1; + } + + /* if we got an explicit fuse count, override our computed count */ + if (pinfo.explicit_numfuses != 0) + result->numfuses = pinfo.explicit_numfuses; + + /* clear out leftover bits */ + if (result->numfuses % 8 != 0) + result->fusemap[result->numfuses / 8] &= (1 << (result->numfuses % 8)) - 1; + memset(&result->fusemap[(result->numfuses + 7) / 8], 0, sizeof(result->fusemap) - (result->numfuses + 7) / 8); + + /* validate the checksum */ + checksum = 0; + for (i = 0; i < (result->numfuses + 7) / 8; i++) + checksum += result->fusemap[i]; + if (pinfo.checksum != 0 && checksum != pinfo.checksum) + return JEDERR_BAD_FUSE_SUM; + + return JEDERR_NONE; +} + + + +/*------------------------------------------------- + jed_output - generate a new .JED file based + on the jed_data provided +-------------------------------------------------*/ + +size_t jed_output(const jed_data *data, void *result, size_t length) +{ + UINT8 *curdst = result; + UINT8 *dstend = curdst + length; + int i, zeros, ones; + char tempbuf[256]; + UINT16 checksum; + UINT8 defbyte; + UINT8 *temp; + + /* always start the DST with a standard header and an STX */ + tempbuf[0] = 0x02; + sprintf(&tempbuf[1], "JEDEC file generated by jedutil*\n"); + if (curdst + strlen(tempbuf) <= dstend) + memcpy(curdst, tempbuf, strlen(tempbuf)); + curdst += strlen(tempbuf); + + /* append the package information */ + sprintf(tempbuf, "QF%d*\n", data->numfuses); + if (curdst + strlen(tempbuf) <= dstend) + memcpy(curdst, tempbuf, strlen(tempbuf)); + curdst += strlen(tempbuf); + + /* compute the checksum */ + checksum = 0; + for (i = 0; i < data->numfuses / 8; i++) + checksum += data->fusemap[i]; + if (data->numfuses % 8 != 0) + checksum += data->fusemap[data->numfuses / 8] & ((1 << (data->numfuses % 8)) - 1); + + /* determine if we are mostly 0's or mostly 1's */ + for (i = zeros = ones = 0; i < data->numfuses / 8; i++) + if (data->fusemap[i] == 0x00) + zeros++; + else if (data->fusemap[i] == 0xff) + ones++; + defbyte = (ones > zeros) ? 0xff : 0x00; + + /* output the default fuse state */ + sprintf(tempbuf, "F%d*\n", defbyte & 1); + if (curdst + strlen(tempbuf) <= dstend) + memcpy(curdst, tempbuf, strlen(tempbuf)); + curdst += strlen(tempbuf); + + /* now loop over groups of 32 fuses and output non-default groups */ + for (i = 0; i < data->numfuses; i += 32) + if (data->fusemap[i / 8 + 0] != defbyte || + data->fusemap[i / 8 + 1] != defbyte || + data->fusemap[i / 8 + 2] != defbyte || + data->fusemap[i / 8 + 3] != defbyte) + { + int stroffs; + int j; + + /* build up a string of 32 fuses */ + stroffs = sprintf(tempbuf, "L%05d ", i); + for (j = 0; j < 32 && i+j < data->numfuses; j++) + tempbuf[stroffs++] = '0' + jed_get_fuse(data, i + j); + stroffs += sprintf(&tempbuf[stroffs], "*\n"); + + /* append to the buffer */ + if (curdst + strlen(tempbuf) <= dstend) + memcpy(curdst, tempbuf, strlen(tempbuf)); + curdst += strlen(tempbuf); + } + + /* write the checksum */ + sprintf(tempbuf, "C%04X*\n", checksum); + if (curdst + strlen(tempbuf) <= dstend) + memcpy(curdst, tempbuf, strlen(tempbuf)); + curdst += strlen(tempbuf); + + /* now compute the transmission checksum */ + checksum = 0; + for (temp = result; temp < curdst && temp < dstend; temp++) + checksum += *temp & 0x7f; + checksum += 0x03; + + /* append the ETX and the transmission checksum */ + tempbuf[0] = 0x03; + sprintf(&tempbuf[1], "%04X", checksum); + if (curdst + strlen(tempbuf) <= dstend) + memcpy(curdst, tempbuf, strlen(tempbuf)); + curdst += strlen(tempbuf); + + /* return the final size */ + return curdst - (UINT8 *)result; +} + + + +/*------------------------------------------------- + jedbin_parse - parse a binary JED file that + has been loaded raw into memory +-------------------------------------------------*/ + +int jedbin_parse(const void *data, size_t length, jed_data *result) +{ + const UINT8 *cursrc = data; + + /* initialize the output */ + memset(result, 0, sizeof(*result)); + + /* need at least 4 bytes */ + if (length < 4) + return JEDERR_INVALID_DATA; + + /* first unpack the number of fuses */ + result->numfuses = (cursrc[0] << 24) | (cursrc[1] << 16) | (cursrc[2] << 8) | cursrc[3]; + cursrc += 4; + if (result->numfuses == 0 || result->numfuses > JED_MAX_FUSES) + return JEDERR_INVALID_DATA; + + /* now make sure we have enough data in the source */ + if (length < 4 + (result->numfuses + 7) / 8) + return JEDERR_INVALID_DATA; + + /* copy in the data */ + memcpy(result->fusemap, cursrc, (result->numfuses + 7) / 8); + return JEDERR_NONE; +} + + + +/*------------------------------------------------- + jedbin_output - generate a new binary JED file + based on the jed_data provided +-------------------------------------------------*/ + +size_t jedbin_output(const jed_data *data, void *result, size_t length) +{ + UINT8 *curdst = result; + + /* ensure we have enough room */ + if (length >= 4 + (data->numfuses + 7) / 8) + { + /* store the number of fuses */ + *curdst++ = data->numfuses >> 24; + *curdst++ = data->numfuses >> 16; + *curdst++ = data->numfuses >> 8; + *curdst++ = data->numfuses >> 0; + + /* copy in the rest of the data */ + memcpy(curdst, data->fusemap, (data->numfuses + 7) / 8); + } + + /* return the final size */ + return 4 + (data->numfuses + 7) / 8; +} + diff --git a/src/lib/util/jedparse.h b/src/lib/util/jedparse.h new file mode 100644 index 00000000000..4697914cfac --- /dev/null +++ b/src/lib/util/jedparse.h @@ -0,0 +1,89 @@ +/*************************************************************************** + + jedparse.h + + Parser for .JED files into raw fusemaps. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#ifndef __JEDPARSE_H__ +#define __JEDPARSE_H__ + +#include "osdcore.h" + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define JED_MAX_FUSES (65536) + +/* error codes */ +#define JEDERR_NONE 0 +#define JEDERR_INVALID_DATA 1 +#define JEDERR_BAD_XMIT_SUM 2 +#define JEDERR_BAD_FUSE_SUM 3 + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _jed_data jed_data; +struct _jed_data +{ + UINT32 numfuses; /* number of defined fuses */ + UINT8 fusemap[JED_MAX_FUSES / 8];/* array of bit-packed data */ +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +/* parse a file (read into memory) into a jed_data structure */ +int jed_parse(const void *data, size_t length, jed_data *result); + +/* output a jed_data structure into a well-formatted JED file */ +size_t jed_output(const jed_data *data, void *result, size_t length); + +/* parse a binary JED file (read into memory) into a jed_data structure */ +int jedbin_parse(const void *data, size_t length, jed_data *result); + +/* output a jed_data structure into a binary JED file */ +size_t jedbin_output(const jed_data *data, void *result, size_t length); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +INLINE int jed_get_fuse(const jed_data *data, UINT32 fusenum) +{ + if (fusenum < JED_MAX_FUSES) + return (data->fusemap[fusenum / 8] >> (fusenum % 8)) & 1; + else + return 0; +} + + +INLINE void jed_set_fuse(jed_data *data, UINT32 fusenum, UINT8 value) +{ + if (fusenum < JED_MAX_FUSES) + { + /* set or clear the bit as appropriate */ + if (value) + data->fusemap[fusenum / 8] |= (1 << (fusenum % 8)); + else + data->fusemap[fusenum / 8] &= ~(1 << (fusenum % 8)); + } +} + +#endif /* __JEDPARSE_H__ */ diff --git a/src/lib/util/md5.c b/src/lib/util/md5.c new file mode 100644 index 00000000000..28797aa811c --- /dev/null +++ b/src/lib/util/md5.c @@ -0,0 +1,239 @@ +/* + * This code implements the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Changed so as no longer to depend on Colin Plumb's `usual.h' header + * definitions; now uses stuff from dpkg's config.h. + * - Ian Jackson <ijackson@nyx.cs.du.edu>. + * Still in the public domain. + */ + +#include <string.h> /* for memcpy() */ + +#include "md5.h" + +#ifndef LSB_FIRST +void +byteSwap(UWORD32 *buf, unsigned words) +{ + md5byte *p = (md5byte *)buf; + + do { + *buf++ = (UWORD32)((unsigned)p[3] << 8 | p[2]) << 16 | + ((unsigned)p[1] << 8 | p[0]); + p += 4; + } while (--words); +} +#else +#define byteSwap(buf,words) +#endif + +/* + * Start MD5 accumulation. Set bit count to 0 and buffer to mysterious + * initialization constants. + */ +void +MD5Init(struct MD5Context *ctx) +{ + ctx->buf[0] = 0x67452301; + ctx->buf[1] = 0xefcdab89; + ctx->buf[2] = 0x98badcfe; + ctx->buf[3] = 0x10325476; + + ctx->bytes[0] = 0; + ctx->bytes[1] = 0; +} + +/* + * Update context to reflect the concatenation of another buffer full + * of bytes. + */ +void +MD5Update(struct MD5Context *ctx, md5byte const *buf, unsigned len) +{ + UWORD32 t; + + /* Update byte count */ + + t = ctx->bytes[0]; + if ((ctx->bytes[0] = t + len) < t) + ctx->bytes[1]++; /* Carry from low to high */ + + t = 64 - (t & 0x3f); /* Space available in ctx->in (at least 1) */ + if (t > len) { + memcpy((md5byte *)ctx->in + 64 - t, buf, len); + return; + } + /* First chunk is an odd size */ + memcpy((md5byte *)ctx->in + 64 - t, buf, t); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += t; + len -= t; + + /* Process data in 64-byte chunks */ + while (len >= 64) { + memcpy(ctx->in, buf, 64); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + buf += 64; + len -= 64; + } + + /* Handle any remaining bytes of data. */ + memcpy(ctx->in, buf, len); +} + +/* + * Final wrapup - pad to 64-byte boundary with the bit pattern + * 1 0* (64-bit count of bits processed, MSB-first) + */ +void +MD5Final(md5byte digest[16], struct MD5Context *ctx) +{ + int count = ctx->bytes[0] & 0x3f; /* Number of bytes in ctx->in */ + md5byte *p = (md5byte *)ctx->in + count; + + /* Set the first char of padding to 0x80. There is always room. */ + *p++ = 0x80; + + /* Bytes of padding needed to make 56 bytes (-8..55) */ + count = 56 - 1 - count; + + if (count < 0) { /* Padding forces an extra block */ + memset(p, 0, count + 8); + byteSwap(ctx->in, 16); + MD5Transform(ctx->buf, ctx->in); + p = (md5byte *)ctx->in; + count = 56; + } + memset(p, 0, count); + byteSwap(ctx->in, 14); + + /* Append length in bits and transform */ + ctx->in[14] = ctx->bytes[0] << 3; + ctx->in[15] = ctx->bytes[1] << 3 | ctx->bytes[0] >> 29; + MD5Transform(ctx->buf, ctx->in); + + byteSwap(ctx->buf, 4); + memcpy(digest, ctx->buf, 16); + memset(ctx, 0, sizeof(ctx)); /* In case it's sensitive */ +} + +#ifndef ASM_MD5 + +/* The four core functions - F1 is optimized somewhat */ + +/* #define F1(x, y, z) (x & y | ~x & z) */ +#define F1(x, y, z) (z ^ (x & (y ^ z))) +#define F2(x, y, z) F1(z, x, y) +#define F3(x, y, z) (x ^ y ^ z) +#define F4(x, y, z) (y ^ (x | ~z)) + +/* This is the central step in the MD5 algorithm. */ +#define MD5STEP(f,w,x,y,z,in,s) \ + (w += f(x,y,z) + in, w = (w<<s | w>>(32-s)) + x) + +/* + * The core of the MD5 algorithm, this alters an existing MD5 hash to + * reflect the addition of 16 longwords of new data. MD5Update blocks + * the data and converts bytes into longwords for this routine. + */ +void +MD5Transform(UWORD32 buf[4], UWORD32 const in[16]) +{ + register UWORD32 a, b, c, d; + + a = buf[0]; + b = buf[1]; + c = buf[2]; + d = buf[3]; + + MD5STEP(F1, a, b, c, d, in[0] + 0xd76aa478, 7); + MD5STEP(F1, d, a, b, c, in[1] + 0xe8c7b756, 12); + MD5STEP(F1, c, d, a, b, in[2] + 0x242070db, 17); + MD5STEP(F1, b, c, d, a, in[3] + 0xc1bdceee, 22); + MD5STEP(F1, a, b, c, d, in[4] + 0xf57c0faf, 7); + MD5STEP(F1, d, a, b, c, in[5] + 0x4787c62a, 12); + MD5STEP(F1, c, d, a, b, in[6] + 0xa8304613, 17); + MD5STEP(F1, b, c, d, a, in[7] + 0xfd469501, 22); + MD5STEP(F1, a, b, c, d, in[8] + 0x698098d8, 7); + MD5STEP(F1, d, a, b, c, in[9] + 0x8b44f7af, 12); + MD5STEP(F1, c, d, a, b, in[10] + 0xffff5bb1, 17); + MD5STEP(F1, b, c, d, a, in[11] + 0x895cd7be, 22); + MD5STEP(F1, a, b, c, d, in[12] + 0x6b901122, 7); + MD5STEP(F1, d, a, b, c, in[13] + 0xfd987193, 12); + MD5STEP(F1, c, d, a, b, in[14] + 0xa679438e, 17); + MD5STEP(F1, b, c, d, a, in[15] + 0x49b40821, 22); + + MD5STEP(F2, a, b, c, d, in[1] + 0xf61e2562, 5); + MD5STEP(F2, d, a, b, c, in[6] + 0xc040b340, 9); + MD5STEP(F2, c, d, a, b, in[11] + 0x265e5a51, 14); + MD5STEP(F2, b, c, d, a, in[0] + 0xe9b6c7aa, 20); + MD5STEP(F2, a, b, c, d, in[5] + 0xd62f105d, 5); + MD5STEP(F2, d, a, b, c, in[10] + 0x02441453, 9); + MD5STEP(F2, c, d, a, b, in[15] + 0xd8a1e681, 14); + MD5STEP(F2, b, c, d, a, in[4] + 0xe7d3fbc8, 20); + MD5STEP(F2, a, b, c, d, in[9] + 0x21e1cde6, 5); + MD5STEP(F2, d, a, b, c, in[14] + 0xc33707d6, 9); + MD5STEP(F2, c, d, a, b, in[3] + 0xf4d50d87, 14); + MD5STEP(F2, b, c, d, a, in[8] + 0x455a14ed, 20); + MD5STEP(F2, a, b, c, d, in[13] + 0xa9e3e905, 5); + MD5STEP(F2, d, a, b, c, in[2] + 0xfcefa3f8, 9); + MD5STEP(F2, c, d, a, b, in[7] + 0x676f02d9, 14); + MD5STEP(F2, b, c, d, a, in[12] + 0x8d2a4c8a, 20); + + MD5STEP(F3, a, b, c, d, in[5] + 0xfffa3942, 4); + MD5STEP(F3, d, a, b, c, in[8] + 0x8771f681, 11); + MD5STEP(F3, c, d, a, b, in[11] + 0x6d9d6122, 16); + MD5STEP(F3, b, c, d, a, in[14] + 0xfde5380c, 23); + MD5STEP(F3, a, b, c, d, in[1] + 0xa4beea44, 4); + MD5STEP(F3, d, a, b, c, in[4] + 0x4bdecfa9, 11); + MD5STEP(F3, c, d, a, b, in[7] + 0xf6bb4b60, 16); + MD5STEP(F3, b, c, d, a, in[10] + 0xbebfbc70, 23); + MD5STEP(F3, a, b, c, d, in[13] + 0x289b7ec6, 4); + MD5STEP(F3, d, a, b, c, in[0] + 0xeaa127fa, 11); + MD5STEP(F3, c, d, a, b, in[3] + 0xd4ef3085, 16); + MD5STEP(F3, b, c, d, a, in[6] + 0x04881d05, 23); + MD5STEP(F3, a, b, c, d, in[9] + 0xd9d4d039, 4); + MD5STEP(F3, d, a, b, c, in[12] + 0xe6db99e5, 11); + MD5STEP(F3, c, d, a, b, in[15] + 0x1fa27cf8, 16); + MD5STEP(F3, b, c, d, a, in[2] + 0xc4ac5665, 23); + + MD5STEP(F4, a, b, c, d, in[0] + 0xf4292244, 6); + MD5STEP(F4, d, a, b, c, in[7] + 0x432aff97, 10); + MD5STEP(F4, c, d, a, b, in[14] + 0xab9423a7, 15); + MD5STEP(F4, b, c, d, a, in[5] + 0xfc93a039, 21); + MD5STEP(F4, a, b, c, d, in[12] + 0x655b59c3, 6); + MD5STEP(F4, d, a, b, c, in[3] + 0x8f0ccc92, 10); + MD5STEP(F4, c, d, a, b, in[10] + 0xffeff47d, 15); + MD5STEP(F4, b, c, d, a, in[1] + 0x85845dd1, 21); + MD5STEP(F4, a, b, c, d, in[8] + 0x6fa87e4f, 6); + MD5STEP(F4, d, a, b, c, in[15] + 0xfe2ce6e0, 10); + MD5STEP(F4, c, d, a, b, in[6] + 0xa3014314, 15); + MD5STEP(F4, b, c, d, a, in[13] + 0x4e0811a1, 21); + MD5STEP(F4, a, b, c, d, in[4] + 0xf7537e82, 6); + MD5STEP(F4, d, a, b, c, in[11] + 0xbd3af235, 10); + MD5STEP(F4, c, d, a, b, in[2] + 0x2ad7d2bb, 15); + MD5STEP(F4, b, c, d, a, in[9] + 0xeb86d391, 21); + + buf[0] += a; + buf[1] += b; + buf[2] += c; + buf[3] += d; +} + +#endif + diff --git a/src/lib/util/md5.h b/src/lib/util/md5.h new file mode 100644 index 00000000000..4a41494e718 --- /dev/null +++ b/src/lib/util/md5.h @@ -0,0 +1,42 @@ +/* + * This is the header file for the MD5 message-digest algorithm. + * The algorithm is due to Ron Rivest. This code was + * written by Colin Plumb in 1993, no copyright is claimed. + * This code is in the public domain; do with it what you wish. + * + * Equivalent code is available from RSA Data Security, Inc. + * This code has been tested against that, and is equivalent, + * except that you don't need to include two pages of legalese + * with every copy. + * + * To compute the message digest of a chunk of bytes, declare an + * MD5Context structure, pass it to MD5Init, call MD5Update as + * needed on buffers full of bytes, and then call MD5Final, which + * will fill a supplied 16-byte array with the digest. + * + * Changed so as no longer to depend on Colin Plumb's `usual.h' + * header definitions; now uses stuff from dpkg's config.h + * - Ian Jackson <ijackson@nyx.cs.du.edu>. + * Still in the public domain. + */ + +#ifndef MD5_H +#define MD5_H + +typedef unsigned int UWORD32; + +#define md5byte unsigned char + +struct MD5Context { + UWORD32 buf[4]; + UWORD32 bytes[2]; + UWORD32 in[16]; +}; + +void MD5Init(struct MD5Context *context); +void MD5Update(struct MD5Context *context, md5byte const *buf, unsigned len); +void MD5Final(unsigned char digest[16], struct MD5Context *context); +void MD5Transform(UWORD32 buf[4], UWORD32 const in[16]); + +#endif /* !MD5_H */ + diff --git a/src/lib/util/options.c b/src/lib/util/options.c new file mode 100644 index 00000000000..44534e2247d --- /dev/null +++ b/src/lib/util/options.c @@ -0,0 +1,1133 @@ +/*************************************************************************** + + options.c + + Core options code code + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include <stdarg.h> +#include <ctype.h> +#include "options.h" +#include "astring.h" + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define MAX_ENTRY_NAMES 4 + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* forward references */ +typedef struct _options_data options_data; + + +/* range parameter for minimum/maximum values */ +typedef union _options_range_parameter options_range_parameter; +union _options_range_parameter +{ + float f; + int i; +}; + + +/* hash table entries */ +typedef struct _options_hash_entry options_hash_entry; +struct _options_hash_entry +{ + options_hash_entry * next; /* link to the next entry */ + astring * name; /* name under the current link */ + options_data * data; /* link to associated options_data */ +}; + + +/* information about a single entry in the options */ +struct _options_data +{ + options_hash_entry links[MAX_ENTRY_NAMES]; /* array of hash table entries (one per name) */ + options_data * next; /* link to the next data */ + UINT32 flags; /* flags from the entry */ + UINT32 seqid; /* sequence ID; bumped on each change */ + int error_reported; /* have we reported an error on this option yet? */ + int priority; /* priority of the data set */ + astring * data; /* data for this item */ + astring * defdata; /* default data for this item */ + const char * description; /* description for this item */ + options_range_type range_type; /* the type of range to apply to this item */ + options_range_parameter range_minimum; /* the minimum of the range */ + options_range_parameter range_maximum; /* the maximum of the range */ + void (*callback)(core_options *opts, const char *arg); /* callback to be invoked when parsing */ +}; + + +/* structure holding information about a collection of options */ +struct _core_options +{ + void (*output[OPTMSG_COUNT])(const char *s);/* output callbacks */ + + /* linked list, for sequential iteration */ + options_data * datalist; /* linked list for sequential iteration */ + options_data ** datalist_nextptr; /* pointer to pointer to tail entry */ + + /* hashtable, for fast lookup */ + options_hash_entry * hashtable[101]; /* hash table for fast lookup */ +}; + + +/* information about an in-progress options enumeration */ +struct _options_enumerator +{ + options_data * current; +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +static options_data *find_entry_data(core_options *opts, const char *string, int is_command_line); +static void update_data(core_options *opts, options_data *data, const char *newdata, int priority); +static int parse_option_name(core_options *opts, const char *srcstring, options_data *data); + +static void message(core_options *opts, options_message msgtype, const char *format, ...); +static UINT32 hash_value(core_options *opts, const char *str); +static void output_printf(void (*output)(const char *s), const char *format, ...); + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +const char *option_unadorned[MAX_UNADORNED_OPTIONS] = +{ + "<UNADORNED0>", + "<UNADORNED1>", + "<UNADORNED2>", + "<UNADORNED3>", + "<UNADORNED4>", + "<UNADORNED5>", + "<UNADORNED6>", + "<UNADORNED7>", + "<UNADORNED8>", + "<UNADORNED9>", + "<UNADORNED10>", + "<UNADORNED11>", + "<UNADORNED12>", + "<UNADORNED13>", + "<UNADORNED14>", + "<UNADORNED15>" +}; + + + +/*************************************************************************** + OPTIONS COLLECTION MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + options_create - creates a new instance of + core options +-------------------------------------------------*/ + +core_options *options_create(void (*fail)(const char *message)) +{ + /* allocate memory for the option block */ + core_options *opts = (core_options *)malloc(sizeof(*opts)); + if (opts == NULL) + goto error; + + /* and set up the structure */ + memset(opts, 0, sizeof(*opts)); + opts->datalist_nextptr = &opts->datalist; + return opts; + +error: + return NULL; +} + + +/*------------------------------------------------- + options_free - frees an options object +-------------------------------------------------*/ + +void options_free(core_options *opts) +{ + options_data *data, *next; + + /* loop over data items and free them */ + for (data = opts->datalist; data != NULL; data = next) + { + int linknum; + + next = data->next; + + /* free names */ + for (linknum = 0; linknum < ARRAY_LENGTH(data->links); linknum++) + if (data->links[linknum].name != NULL) + astring_free(data->links[linknum].name); + + /* free strings */ + astring_free(data->data); + astring_free(data->defdata); + + /* free the data itself */ + free(data); + } + + /* free the options itself */ + free(opts); +} + + +/*------------------------------------------------- + options_set_output_callback - installs a callback + to be invoked when data is outputted in the + process of outputting options +-------------------------------------------------*/ + +void options_set_output_callback(core_options *opts, options_message msgtype, void (*callback)(const char *s)) +{ + opts->output[msgtype] = callback; +} + + +/*------------------------------------------------- + options_revert - revert options at or below + a certain priority back to their defaults +-------------------------------------------------*/ + +void options_revert(core_options *opts, int priority) +{ + options_data *data; + + /* iterate over options and revert to defaults if below the given priority */ + for (data = opts->datalist; data != NULL; data = data->next) + if (data->priority <= priority) + { + astring_cpy(data->data, data->defdata); + data->priority = OPTION_PRIORITY_DEFAULT; + } +} + + +/*------------------------------------------------- + options_copy - copy options from one core_options + to another +-------------------------------------------------*/ + +int options_copy(core_options *dest_opts, core_options *src_opts) +{ + options_data *data; + + /* iterate over options in the destination */ + for (data = dest_opts->datalist; data != NULL; data = data->next) + if (!(data->flags & OPTION_HEADER)) + { + options_data *srcdata = find_entry_data(src_opts, astring_c(data->links[0].name), FALSE); + + /* if the option exists in the source, set it in the destination */ + if (srcdata != NULL) + options_set_string(dest_opts, astring_c(srcdata->links[0].name), astring_c(srcdata->data), srcdata->priority); + } + + return TRUE; +} + + +/*------------------------------------------------- + options_equal - compare two sets of options +-------------------------------------------------*/ + +int options_equal(core_options *opts1, core_options *opts2) +{ + options_data *data; + + /* iterate over options in the first list */ + for (data = opts1->datalist; data != NULL; data = data->next) + if (!(data->flags & OPTION_HEADER)) + { + const char *value1 = options_get_string(opts1, astring_c(data->links[0].name)); + const char *value2 = options_get_string(opts2, astring_c(data->links[0].name)); + + /* if the values differ, return false */ + if (strcmp(value1, value2) != 0) + return FALSE; + } + + return TRUE; +} + + + +/*************************************************************************** + OPTION DEFINITIONS +***************************************************************************/ + +/*------------------------------------------------- + options_add_entries - add entries to the + current options sets +-------------------------------------------------*/ + +int options_add_entries(core_options *opts, const options_entry *entrylist) +{ + /* loop over entries until we hit a NULL name */ + for ( ; entrylist->name != NULL || (entrylist->flags & OPTION_HEADER); entrylist++) + { + options_data *match = NULL; + int i; + + /* allocate a new item */ + options_data *data = malloc(sizeof(*data)); + if (data == NULL) + return FALSE; + memset(data, 0, sizeof(*data)); + + /* parse the option name */ + if (entrylist->name != NULL) + parse_option_name(opts, entrylist->name, data); + + /* do we match an existing entry? */ + for (i = 0; i < ARRAY_LENGTH(data->links) && match == NULL; i++) + if (data->links[i].name != NULL) + match = find_entry_data(opts, astring_c(data->links[i].name), FALSE); + + /* if so, throw away this entry and replace the data */ + if (match != NULL) + { + /* free what we've allocated so far */ + for (i = 0; i < ARRAY_LENGTH(data->links); i++) + if (data->links[i].name != NULL) + astring_free(data->links[i].name); + free(data); + + /* use the matching entry as our data */ + data = match; + } + + /* otherwise, finish making the new entry */ + else + { + /* allocate strings */ + data->data = astring_alloc(); + data->defdata = astring_alloc(); + + /* copy the flags, and set the value equal to the default */ + data->flags = entrylist->flags; + data->description = entrylist->description; + + /* add us to the end of the sequential list */ + *opts->datalist_nextptr = data; + opts->datalist_nextptr = &data->next; + + /* add each name to the appropriate hash table with a link back to us */ + for (i = 0; i < ARRAY_LENGTH(data->links); i++) + if (data->links[i].name != NULL) + { + int hash_entry = hash_value(opts, astring_c(data->links[i].name)); + + /* set up link */ + data->links[i].data = data; + data->links[i].next = opts->hashtable[hash_entry]; + opts->hashtable[hash_entry] = &data->links[i]; + } + } + + /* copy in the data and default data values */ + if (entrylist->defvalue != NULL) + { + astring_cpyc(data->data, entrylist->defvalue); + astring_cpyc(data->defdata, entrylist->defvalue); + } + data->priority = OPTION_PRIORITY_DEFAULT; + } + return TRUE; +} + + +/*------------------------------------------------- + options_set_option_default_value - change the + default value of an option +-------------------------------------------------*/ + +int options_set_option_default_value(core_options *opts, const char *name, const char *defvalue) +{ + options_data *data = find_entry_data(opts, name, TRUE); + + /* if we don't have an entry for this, fail */ + if (data == NULL) + return FALSE; + + /* update the data and default data; note that we assume that data == defdata */ + astring_cpyc(data->data, defvalue); + astring_cpyc(data->defdata, defvalue); + data->priority = OPTION_PRIORITY_DEFAULT; + return TRUE; +} + + +/*------------------------------------------------- + options_set_option_callback - specifies a + callback to be invoked when parsing options +-------------------------------------------------*/ + +int options_set_option_callback(core_options *opts, const char *name, void (*callback)(core_options *opts, const char *arg)) +{ + options_data *data = find_entry_data(opts, name, TRUE); + + /* if we don't find an entry, fail */ + if (data == NULL) + return FALSE; + + /* set the callback */ + data->callback = callback; + return TRUE; +} + + + +/*************************************************************************** + OPTION DATA EXTRACTION +***************************************************************************/ + +/*------------------------------------------------- + options_parse_command_line - parse a series + of command line arguments +-------------------------------------------------*/ + +int options_parse_command_line(core_options *opts, int argc, char **argv, int priority) +{ + int unadorned_index = 0; + int arg; + + /* loop over commands, looking for options */ + for (arg = 1; arg < argc; arg++) + { + const char *optionname, *newdata; + options_data *data; + int is_unadorned; + + /* determine the entry name to search for */ + is_unadorned = (argv[arg][0] != '-'); + if (!is_unadorned) + optionname = &argv[arg][1]; + else + optionname = OPTION_UNADORNED(unadorned_index); + + /* find our entry */ + data = find_entry_data(opts, optionname, TRUE); + if (data == NULL) + { + message(opts, OPTMSG_ERROR, "Error: unknown option: %s\n", argv[arg]); + return 1; + } + + /* if unadorned, we have to bump the count (unless if the option repeats) */ + if (is_unadorned && !(data->flags & OPTION_REPEATS)) + unadorned_index++; + + /* get the data for this argument, special casing booleans */ + if ((data->flags & (OPTION_BOOLEAN | OPTION_COMMAND)) != 0) + newdata = (strncmp(&argv[arg][1], "no", 2) == 0) ? "0" : "1"; + else if (argv[arg][0] != '-') + newdata = argv[arg]; + else if (arg + 1 < argc) + newdata = argv[++arg]; + else + { + message(opts, OPTMSG_ERROR, "Error: option %s expected a parameter\n", argv[arg]); + return 1; + } + + /* if the option is deprecated or internal, don't process further */ + if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL)) != 0) + continue; + + /* invoke callback, if present */ + if (data->callback != NULL) + (*data->callback)(opts, newdata); + + /* allocate a new copy of data for this */ + update_data(opts, data, newdata, priority); + } + return 0; +} + + +/*------------------------------------------------- + options_parse_ini_file - parse a series + of entries in an INI file +-------------------------------------------------*/ + +int options_parse_ini_file(core_options *opts, core_file *inifile, int priority) +{ + char buffer[4096]; + + /* loop over data */ + while (core_fgets(buffer, ARRAY_LENGTH(buffer), inifile) != NULL) + { + char *optionname, *optiondata, *temp; + options_data *data; + int inquotes = FALSE; + + /* find the name */ + for (optionname = buffer; *optionname != 0; optionname++) + if (!isspace(*optionname)) + break; + + /* skip comments */ + if (*optionname == 0 || *optionname == '#') + continue; + + /* scan forward to find the first space */ + for (temp = optionname; *temp != 0; temp++) + if (isspace(*temp)) + break; + + /* if we hit the end early, print a warning and continue */ + if (*temp == 0) + { + message(opts, OPTMSG_WARNING, "Warning: invalid line in INI: %s", buffer); + continue; + } + + /* NULL-terminate */ + *temp++ = 0; + optiondata = temp; + + /* scan the data, stopping when we hit a comment */ + for (temp = optiondata; *temp != 0; temp++) + { + if (*temp == '"') + inquotes = !inquotes; + if (*temp == '#' && !inquotes) + break; + } + *temp = 0; + + /* find our entry */ + data = find_entry_data(opts, optionname, FALSE); + if (data == NULL) + { + message(opts, OPTMSG_WARNING, "Warning: unknown option in INI: %s\n", optionname); + continue; + } + if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL)) != 0) + continue; + + /* allocate a new copy of data for this */ + update_data(opts, data, optiondata, priority); + } + return 0; +} + + + +/*************************************************************************** + OPTIONS OUTPUT +***************************************************************************/ + +/*------------------------------------------------- + options_output_diff_ini_file - output the diff + of the current state from a base state to an + INI file +-------------------------------------------------*/ + +void options_output_diff_ini_file(core_options *opts, core_options *baseopts, core_file *inifile) +{ + options_data *data; + const char *last_header = NULL; + const char *name; + const char *value; + options_data *basedata; + + /* loop over all items */ + for (data = opts->datalist; data != NULL; data = data->next) + { + /* header: record description */ + if ((data->flags & OPTION_HEADER) != 0) + last_header = data->description; + + /* otherwise, output entries for all non-deprecated and non-command items (if not in baseopts) */ + else if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL | OPTION_COMMAND)) == 0) + { + /* get name and data of this value */ + name = astring_c(data->links[0].name); + value = astring_c(data->data); + + /* look up counterpart in baseopts, if baseopts is specified */ + basedata = (baseopts != NULL) ? find_entry_data(baseopts, name, FALSE) : NULL; + + /* is our data different, or not in baseopts? */ + if ((basedata == NULL) || (strcmp(value, astring_c(basedata->data)) != 0)) + { + /* output header, if we have one */ + if (last_header != NULL) + { + core_fprintf(inifile, "\n#\n# %s\n#\n", last_header); + last_header = NULL; + } + + /* and finally output the data */ + if (strchr(value, ' ') != NULL) + core_fprintf(inifile, "%-25s \"%s\"\n", name, value); + else + core_fprintf(inifile, "%-25s %s\n", name, value); + } + } + } +} + + +/*------------------------------------------------- + options_output_ini_file - output the current + state to an INI file +-------------------------------------------------*/ + +void options_output_ini_file(core_options *opts, core_file *inifile) +{ + options_output_diff_ini_file(opts, NULL, inifile); +} + + +/*------------------------------------------------- + options_output_ini_file - output the current + state to an INI file +-------------------------------------------------*/ + +void options_output_ini_stdfile(core_options *opts, FILE *inifile) +{ + options_data *data; + + /* loop over all items */ + for (data = opts->datalist; data != NULL; data = data->next) + { + /* header: just print */ + if ((data->flags & OPTION_HEADER) != 0) + fprintf(inifile, "\n#\n# %s\n#\n", data->description); + + /* otherwise, output entries for all non-deprecated and non-command items */ + else if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL | OPTION_COMMAND)) == 0) + { + if (astring_chr(data->data, 0, ' ') != -1) + fprintf(inifile, "%-25s \"%s\"\n", astring_c(data->links[0].name), astring_c(data->data)); + else + fprintf(inifile, "%-25s %s\n", astring_c(data->links[0].name), astring_c(data->data)); + } + } +} + + +/*------------------------------------------------- + options_output_help - output option help to + a file +-------------------------------------------------*/ + +void options_output_help(core_options *opts, void (*output)(const char *)) +{ + options_data *data; + + /* loop over all items */ + for (data = opts->datalist; data != NULL; data = data->next) + { + /* header: just print */ + if ((data->flags & OPTION_HEADER) != 0) + output_printf(output, "\n#\n# %s\n#\n", data->description); + + /* otherwise, output entries for all non-deprecated items */ + else if ((data->flags & (OPTION_DEPRECATED | OPTION_INTERNAL)) == 0 && data->description != NULL) + output_printf(output, "-%-20s%s\n", astring_c(data->links[0].name), data->description); + } +} + + + +/*************************************************************************** + OPTIONS READING +***************************************************************************/ + +/*------------------------------------------------- + options_get_string - return data formatted + as a string +-------------------------------------------------*/ + +const char *options_get_string(core_options *opts, const char *name) +{ + options_data *data = find_entry_data(opts, name, FALSE); + const char *value = ""; + + /* error if not found */ + if (data == NULL) + message(opts, OPTMSG_ERROR, "Unexpected option %s queried\n", name); + + /* copy if non-NULL */ + else + value = astring_c(data->data); + + return value; +} + + +/*------------------------------------------------- + options_get_bool - return data formatted as + a boolean +-------------------------------------------------*/ + +int options_get_bool(core_options *opts, const char *name) +{ + options_data *data = find_entry_data(opts, name, FALSE); + int value = FALSE; + + /* error if not found */ + if (data == NULL) + message(opts, OPTMSG_ERROR, "Unexpected boolean option %s queried\n", name); + + /* also error if we don't have a valid boolean value */ + else if (sscanf(astring_c(data->data), "%d", &value) != 1 || value < 0 || value > 1) + { + options_set_string(opts, name, astring_c(data->defdata), 0); + sscanf(astring_c(data->data), "%d", &value); + if (!data->error_reported) + { + message(opts, OPTMSG_ERROR, "Illegal boolean value for %s; reverting to %d\n", astring_c(data->links[0].name), value); + data->error_reported = TRUE; + } + } + return value; +} + + +/*------------------------------------------------- + options_get_int - return data formatted as + an integer +-------------------------------------------------*/ + +int options_get_int(core_options *opts, const char *name) +{ + options_data *data = find_entry_data(opts, name, FALSE); + int value = 0; + + /* error if not found */ + if (data == NULL) + message(opts, OPTMSG_ERROR, "Unexpected integer option %s queried\n", name); + + /* also error if we don't have a valid integer value */ + else if (sscanf(astring_c(data->data), "%d", &value) != 1) + { + options_set_string(opts, name, astring_c(data->defdata), 0); + sscanf(astring_c(data->data), "%d", &value); + if (!data->error_reported) + { + message(opts, OPTMSG_ERROR, "Illegal integer value for %s; reverting to %d\n", astring_c(data->links[0].name), value); + data->error_reported = TRUE; + } + } + return value; +} + + +/*------------------------------------------------- + options_get_float - return data formatted as + a float +-------------------------------------------------*/ + +float options_get_float(core_options *opts, const char *name) +{ + options_data *data = find_entry_data(opts, name, FALSE); + float value = 0; + + /* error if not found */ + if (data == NULL) + message(opts, OPTMSG_ERROR, "Unexpected float option %s queried\n", name); + + /* also error if we don't have a valid floating point value */ + else if (sscanf(astring_c(data->data), "%f", &value) != 1) + { + options_set_string(opts, name, astring_c(data->defdata), 0); + sscanf(astring_c(data->data), "%f", &value); + if (!data->error_reported) + { + message(opts, OPTMSG_ERROR, "Illegal float value for %s; reverting to %f\n", astring_c(data->links[0].name), (double)value); + data->error_reported = TRUE; + } + } + return value; +} + + +/*------------------------------------------------- + options_get_seqid - return the seqid for an + entry +-------------------------------------------------*/ + +UINT32 options_get_seqid(core_options *opts, const char *name) +{ + options_data *data = find_entry_data(opts, name, FALSE); + return (data == NULL) ? 0 : data->seqid; +} + + + +/*************************************************************************** + OPTIONS SETTING +***************************************************************************/ + +/*------------------------------------------------- + options_set_string - set a string value +-------------------------------------------------*/ + +void options_set_string(core_options *opts, const char *name, const char *value, int priority) +{ + options_data *data = find_entry_data(opts, name, FALSE); + update_data(opts, data, value, priority); +} + + +/*------------------------------------------------- + options_set_bool - set a boolean value +-------------------------------------------------*/ + +void options_set_bool(core_options *opts, const char *name, int value, int priority) +{ + char temp[4]; + sprintf(temp, "%d", value ? 1 : 0); + options_set_string(opts, name, temp, priority); +} + + +/*------------------------------------------------- + options_set_int - set an integer value +-------------------------------------------------*/ + +void options_set_int(core_options *opts, const char *name, int value, int priority) +{ + char temp[20]; + sprintf(temp, "%d", value); + options_set_string(opts, name, temp, priority); +} + + +/*------------------------------------------------- + options_set_float - set a float value +-------------------------------------------------*/ + +void options_set_float(core_options *opts, const char *name, float value, int priority) +{ + char temp[100]; + sprintf(temp, "%f", value); + options_set_string(opts, name, temp, priority); +} + + + +/*************************************************************************** + OPTION DEFINITION QUERIES +***************************************************************************/ + +/*------------------------------------------------- + options_enumerator_begin - retrieve the range of + a float option +-------------------------------------------------*/ + +options_enumerator *options_enumerator_begin(core_options *opts) +{ + options_enumerator *enumerator; + + /* allocate memory for the enumerator */ + enumerator = malloc(sizeof(*enumerator)); + if (enumerator == NULL) + return NULL; + + /* start at the head of the list */ + enumerator->current = opts->datalist; + return enumerator; +} + + + +/*------------------------------------------------- + options_enumerator_next - returns the current + option and advances the enumerator +-------------------------------------------------*/ + +const char *options_enumerator_next(options_enumerator *enumerator) +{ + astring *option_name = NULL; + + /* be sure to skip over false options */ + while (option_name == NULL && enumerator->current != NULL) + { + /* retrieve the current option name and advance the enumerator */ + option_name = enumerator->current->links[0].name; + enumerator->current = enumerator->current->next; + } + return (option_name != NULL) ? astring_c(option_name) : NULL; +} + + + +/*------------------------------------------------- + options_enumerator_free - disposes an options + enumerator +-------------------------------------------------*/ + +void options_enumerator_free(options_enumerator *enumerator) +{ + free(enumerator); +} + + +/*------------------------------------------------- + options_get_range_type - determine the type + of range for a particular option +-------------------------------------------------*/ + +options_range_type options_get_range_type(core_options *opts, const char *name) +{ + options_data *data = find_entry_data(opts, name, FALSE); + return data->range_type; +} + + +/*------------------------------------------------- + options_get_range_int - retrieve the range of + an integer option +-------------------------------------------------*/ + +void options_get_range_int(core_options *opts, const char *name, int *minval, int *maxval) +{ + options_data *data = find_entry_data(opts, name, FALSE); + *minval = data->range_minimum.i; + *maxval = data->range_maximum.i; +} + + + +/*------------------------------------------------- + options_get_range_float - retrieve the range of + a float option +-------------------------------------------------*/ + +void options_get_range_float(core_options *opts, const char *name, float *minval, float *maxval) +{ + options_data *data = find_entry_data(opts, name, FALSE); + *minval = data->range_minimum.f; + *maxval = data->range_maximum.f; +} + + + +/*************************************************************************** + INTERNAL UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + find_entry_data - locate an entry whose name + matches the given string +-------------------------------------------------*/ + +static options_data *find_entry_data(core_options *opts, const char *string, int is_command_line) +{ + int hash_entry = hash_value(opts, string); + options_hash_entry *link; + + /* scan all entries */ + for (link = opts->hashtable[hash_entry]; link != NULL; link = link->next) + if (!(link->data->flags & OPTION_HEADER) && link->name != NULL && astring_cmpc(link->name, string) == 0) + return link->data; + + /* haven't found it? if we are prefixed with "no", then try to search for that */ + if (is_command_line && string[0] == 'n' && string[1] == 'o') + { + options_data *data = find_entry_data(opts, &string[2], FALSE); + if (data != NULL && (data->flags & OPTION_BOOLEAN)) + return data; + } + + /* didn't find it at all */ + return NULL; +} + + +/*------------------------------------------------- + update_data - update the data value for a + given entry +-------------------------------------------------*/ + +static void update_data(core_options *opts, options_data *data, const char *newdata, int priority) +{ + const char *dataend = newdata + strlen(newdata) - 1; + const char *datastart = newdata; + float f; + int i; + + /* strip off leading/trailing spaces */ + while (isspace(*datastart) && datastart <= dataend) + datastart++; + while (isspace(*dataend) && datastart <= dataend) + dataend--; + + /* strip off quotes */ + if (datastart != dataend && *datastart == '"' && *dataend == '"') + datastart++, dataend--; + + /* check against range */ + switch (data->range_type) + { + case OPTION_RANGE_NONE: + /* do nothing */ + break; + + case OPTION_RANGE_INT: + /* check against integer range */ + i = 0; + if (sscanf(datastart, "%d", &i) != 1) + { + message(opts, OPTMSG_ERROR, "Illegal integer value for %s; keeping value of %s\n", astring_c(data->links[0].name), astring_c(data->data)); + data->error_reported = TRUE; + return; + } + if (i < data->range_minimum.i || i > data->range_maximum.i) + { + message(opts, OPTMSG_ERROR, "Invalid %s value (must be between %i and %i); keeping value of %s\n", + astring_c(data->links[0].name), data->range_minimum.i, data->range_maximum.i, astring_c(data->data)); + data->error_reported = TRUE; + return; + } + break; + + case OPTION_RANGE_FLOAT: + /* check against float range */ + f = 0; + if (sscanf(datastart, "%f", &f) != 1) + { + message(opts, OPTMSG_ERROR, "Illegal float value for %s; keeping value of %s\n", astring_c(data->links[0].name), astring_c(data->data)); + data->error_reported = TRUE; + return; + } + if (f < data->range_minimum.f || f > data->range_maximum.f) + { + message(opts, OPTMSG_ERROR, "Invalid %s value (must be between %f and %f); keeping value of %s\n", + astring_c(data->links[0].name), data->range_minimum.f, data->range_maximum.f, astring_c(data->data)); + data->error_reported = TRUE; + return; + } + break; + } + + /* ignore if we don't have priority */ + if (priority < data->priority) + return; + + /* allocate a copy of the data */ + astring_cpych(data->data, datastart, dataend + 1 - datastart); + data->priority = priority; + + /* bump the seqid and clear the error reporting */ + data->seqid++; + data->error_reported = FALSE; +} + + +/*------------------------------------------------- + parse_option_name - read data from an option_entry + name into an option_data structure +-------------------------------------------------*/ + +static int parse_option_name(core_options *opts, const char *srcstring, options_data *data) +{ + const char *start; + const char *end = NULL; + int curentry; + + /* start with the original string and loop over entries */ + start = srcstring; + for (curentry = 0; curentry < ARRAY_LENGTH(data->links); curentry++) + { + /* find the end of this entry and copy the string */ + for (end = start; *end != 0 && *end != ';' && *end != '('; end++) + ; + data->links[curentry].name = astring_dupch(start, end - start); + + /* if we hit the end of the source, stop */ + if (*end != ';') + break; + start = end + 1; + } + + /* have we found a range? */ + if (end != NULL && *end == '(') + { + if (sscanf(end, "(%d-%d)", &data->range_minimum.i, &data->range_maximum.i) == 2) + data->range_type = OPTION_RANGE_INT; + else if (sscanf(end, "(%f-%f)", &data->range_minimum.f, &data->range_maximum.f) == 2) + data->range_type = OPTION_RANGE_FLOAT; + } + return curentry; +} + + +/*------------------------------------------------- + message - outputs a message to a listener +-------------------------------------------------*/ + +static void message(core_options *opts, options_message msgtype, const char *format, ...) +{ + char buf[1024]; + va_list argptr; + + /* output a message if there is a non-NULL handler for it */ + if (opts->output[msgtype] != NULL) + { + va_start(argptr, format); + vsprintf(buf, format, argptr); + va_end(argptr); + + (*opts->output[msgtype])(buf); + } +} + + +/*------------------------------------------------- + hash_value - computes the hash value for a string +-------------------------------------------------*/ + +static UINT32 hash_value(core_options *opts, const char *str) +{ + UINT32 hash = 5381; + int c; + + while ((c = *str++) != 0) + hash = ((hash << 5) + hash) + c; + + return hash % ARRAY_LENGTH(opts->hashtable); +} + + +/*------------------------------------------------- + output_printf - outputs an arbitrary message + to a callback +-------------------------------------------------*/ + +static void output_printf(void (*output)(const char *s), const char *format, ...) +{ + char buf[1024]; + va_list argptr; + + va_start(argptr, format); + vsprintf(buf, format, argptr); + va_end(argptr); + + output(buf); +} diff --git a/src/lib/util/options.h b/src/lib/util/options.h new file mode 100644 index 00000000000..35abc73f226 --- /dev/null +++ b/src/lib/util/options.h @@ -0,0 +1,216 @@ +/*************************************************************************** + + options.h + + Core options code code + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#ifndef __OPTIONS_H__ +#define __OPTIONS_H__ + +#include "osdcore.h" +#include "corefile.h" + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* unadorned option names */ +#define MAX_UNADORNED_OPTIONS 16 +#define OPTION_UNADORNED(x) (((x) < MAX_UNADORNED_OPTIONS) ? option_unadorned[x] : "") + +/* option flags */ +#define OPTION_BOOLEAN 0x0001 /* option is a boolean value */ +#define OPTION_DEPRECATED 0x0002 /* option is deprecated */ +#define OPTION_COMMAND 0x0004 /* option is a command */ +#define OPTION_HEADER 0x0008 /* text-only header */ +#define OPTION_INTERNAL 0x0010 /* option is internal-only */ +#define OPTION_REPEATS 0x0020 /* unadorned option repeats */ + +/* option priorities */ +#define OPTION_PRIORITY_DEFAULT 0 /* defaults are at 0 priority */ +#define OPTION_PRIORITY_LOW 50 /* low priority */ +#define OPTION_PRIORITY_NORMAL 100 /* normal priority */ +#define OPTION_PRIORITY_HIGH 150 /* high priority */ +#define OPTION_PRIORITY_MAXIMUM 255 /* maximum priority */ + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* opaque type representing a collection of options */ +typedef struct _core_options core_options; + +/* opaque type use for enumeration of options */ +typedef struct _options_enumerator options_enumerator; + +/* describes a single option with its description and default value */ +typedef struct _options_entry options_entry; +struct _options_entry +{ + const char * name; /* name on the command line */ + const char * defvalue; /* default value of this argument */ + UINT32 flags; /* flags to describe the option */ + const char * description; /* description for -showusage */ +}; + +/* output messages are one of the following types */ +enum _options_message +{ + OPTMSG_INFO, + OPTMSG_WARNING, + OPTMSG_ERROR, + OPTMSG_COUNT +}; +typedef enum _options_message options_message; + +/* option ranges are one of the following types */ +enum _options_range_type +{ + OPTION_RANGE_NONE, + OPTION_RANGE_INT, + OPTION_RANGE_FLOAT +}; +typedef enum _options_range_type options_range_type; + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +extern const char *option_unadorned[MAX_UNADORNED_OPTIONS]; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + + +/* ----- options collection management ----- */ + +/* create a new collection of options */ +core_options *options_create(void (*fail)(const char *message)); + +/* free a collection of options */ +void options_free(core_options *opts); + +/* set a callback for a particular class of message */ +void options_set_output_callback(core_options *opts, options_message msgtype, void (*callback)(const char *s)); + +/* revert options at or below a certain priority back to their defaults */ +void options_revert(core_options *opts, int priority); + +/* copy one collection of options into another */ +int options_copy(core_options *dest_opts, core_options *src_opts); + +/* compare two collections of options */ +int options_equal(core_options *opts1, core_options *opts2); + + + +/* ----- option definitions ----- */ + +/* add a set of entries to an options collection */ +int options_add_entries(core_options *opts, const options_entry *entrylist); + +/* set the default value for a particular option entry */ +int options_set_option_default_value(core_options *opts, const char *name, const char *defvalue); + +/* set a callback for a particular option entry */ +int options_set_option_callback(core_options *opts, const char *name, void (*callback)(core_options *opts, const char *arg)); + + + +/* ----- option data extraction ----- */ + +/* parse option data from a command line */ +int options_parse_command_line(core_options *opts, int argc, char **argv, int priority); + +/* parse option data from an INI file */ +int options_parse_ini_file(core_options *opts, core_file *inifile, int priority); + + + +/* ----- options output ----- */ + +/* output option data to an INI file */ +void options_output_ini_file(core_options *opts, core_file *inifile); + +/* output differing option data to an INI file */ +void options_output_diff_ini_file(core_options *opts, core_options *baseopts, core_file *inifile); + +/* output option data to a standard file handle */ +void options_output_ini_stdfile(core_options *opts, FILE *inifile); + +/* output help using the specified output function */ +void options_output_help(core_options *opts, void (*output)(const char *s)); + + + +/* ----- options reading ----- */ + +/* read an option as a string */ +const char *options_get_string(core_options *opts, const char *name); + +/* read an option as a boolean */ +int options_get_bool(core_options *opts, const char *name); + +/* read an option as an integer */ +int options_get_int(core_options *opts, const char *name); + +/* read an option as a floating point value */ +float options_get_float(core_options *opts, const char *name); + +/* read an option as a string */ +UINT32 options_get_seqid(core_options *opts, const char *name); + + + +/* ----- options setting ----- */ + +/* set an option as a string */ +void options_set_string(core_options *opts, const char *name, const char *value, int priority); + +/* set an option as a boolean */ +void options_set_bool(core_options *opts, const char *name, int value, int priority); + +/* set an option as an integer */ +void options_set_int(core_options *opts, const char *name, int value, int priority); + +/* set an option as a floating point value */ +void options_set_float(core_options *opts, const char *name, float value, int priority); + + + +/* ----- option definition queries ----- */ + +/* begin enumerating option definitions */ +options_enumerator *options_enumerator_begin(core_options *opts); + +/* get the next option in sequence */ +const char *options_enumerator_next(options_enumerator *enumerator); + +/* free memory allocated for enumeration */ +void options_enumerator_free(options_enumerator *enumerator); + +/* get the type of range for a given option */ +options_range_type options_get_range_type(core_options *opts, const char *name); + +/* return an integer range for a given option */ +void options_get_range_int(core_options *opts, const char *name, int *minval, int *maxval); + +/* return a floating point range for a given option */ +void options_get_range_float(core_options *opts, const char *name, float *minval, float *maxval); + + +#endif /* __OPTIONS_H__ */ diff --git a/src/lib/util/palette.c b/src/lib/util/palette.c new file mode 100644 index 00000000000..83b1a851b02 --- /dev/null +++ b/src/lib/util/palette.c @@ -0,0 +1,651 @@ +/****************************************************************************** + + palette.c + + Palette handling functions. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +******************************************************************************/ + +#include "palette.h" + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* object to track dirty states */ +typedef struct _dirty_state dirty_state; +struct _dirty_state +{ + UINT32 * dirty; /* bitmap of dirty entries */ + UINT32 mindirty; /* minimum dirty entry */ + UINT32 maxdirty; /* minimum dirty entry */ +}; + + +/* a single palette client */ +struct _palette_client +{ + palette_client *next; /* pointer to next client */ + palette_t * palette; /* reference to the palette */ + dirty_state live; /* live dirty state */ + dirty_state previous; /* previous dirty state */ +}; + + +/* a palette object */ +struct _palette_t +{ + UINT32 refcount; /* reference count on the palette */ + UINT32 numcolors; /* number of colors in the palette */ + UINT32 numgroups; /* number of groups in the palette */ + + rgb_t * entry_color; /* array of raw colors */ + float * entry_contrast; /* contrast value for each entry */ + rgb_t * adjusted_color; /* array of adjusted colors */ + rgb_t * adjusted_rgb15; /* array of adjusted colors as RGB15 */ + + float * group_bright; /* brightness value for each group */ + float * group_contrast; /* contrast value for each group */ + + palette_client *client_list; /* list of clients for this palette */ +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +static void internal_palette_free(palette_t *palette); +static void update_adjusted_color(palette_t *palette, UINT32 group, UINT32 index); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + adjust_palette_entry - adjust a palette + entry for brightness +-------------------------------------------------*/ + +INLINE rgb_t adjust_palette_entry(rgb_t entry, float brightness, float contrast) +{ + int r = rgb_clamp((float)RGB_RED(entry) * contrast + brightness); + int g = rgb_clamp((float)RGB_GREEN(entry) * contrast + brightness); + int b = rgb_clamp((float)RGB_BLUE(entry) * contrast + brightness); + return MAKE_RGB(r,g,b); +} + + + +/*************************************************************************** + PALETTE ALLOCATION +***************************************************************************/ + +/*------------------------------------------------- + palette_alloc - allocate a new palette object + and take a single reference on it +-------------------------------------------------*/ + +palette_t *palette_alloc(UINT32 numcolors, UINT32 numgroups) +{ + palette_t *palette; + UINT32 index; + + /* allocate memory */ + palette = malloc(sizeof(*palette)); + if (palette == NULL) + goto error; + memset(palette, 0, sizeof(*palette)); + + /* allocate an array of palette entries and individual contrasts for each */ + palette->entry_color = malloc(sizeof(*palette->entry_color) * numcolors); + palette->entry_contrast = malloc(sizeof(*palette->entry_contrast) * numcolors); + if (palette->entry_color == NULL || palette->entry_contrast == NULL) + goto error; + + /* initialize the entries */ + for (index = 0; index < numcolors; index++) + { + palette->entry_color[index] = RGB_BLACK; + palette->entry_contrast[index] = 1.0f; + } + + /* allocate an array of brightness and contrast for each group */ + palette->group_bright = malloc(sizeof(*palette->group_bright) * numgroups); + palette->group_contrast = malloc(sizeof(*palette->group_contrast) * numgroups); + if (palette->group_bright == NULL || palette->group_contrast == NULL) + goto error; + + /* initialize the entries */ + for (index = 0; index < numgroups; index++) + { + palette->group_bright[index] = 0.0f; + palette->group_contrast[index] = 1.0f; + } + + /* allocate arrays for the adjusted colors */ + palette->adjusted_color = malloc(sizeof(*palette->adjusted_color) * (numcolors * numgroups + 2)); + palette->adjusted_rgb15 = malloc(sizeof(*palette->adjusted_rgb15) * (numcolors * numgroups + 2)); + if (palette->adjusted_color == NULL || palette->adjusted_rgb15 == NULL) + goto error; + + /* initialize the arrays */ + for (index = 0; index < numcolors * numgroups; index++) + { + palette->adjusted_color[index] = RGB_BLACK; + palette->adjusted_rgb15[index] = rgb_to_rgb15(RGB_BLACK); + } + + /* add black and white as the last two colors */ + palette->adjusted_color[index] = RGB_BLACK; + palette->adjusted_rgb15[index++] = rgb_to_rgb15(RGB_BLACK); + palette->adjusted_color[index] = RGB_WHITE; + palette->adjusted_rgb15[index++] = rgb_to_rgb15(RGB_WHITE); + + /* initialize the remainder of the structure */ + palette->refcount = 1; + palette->numcolors = numcolors; + palette->numgroups = numgroups; + return palette; + +error: + if (palette != NULL) + internal_palette_free(palette); + + return NULL; +} + + +/*------------------------------------------------- + palette_ref - reference a palette object, + incrementing its reference count +-------------------------------------------------*/ + +void palette_ref(palette_t *palette) +{ + palette->refcount++; +} + + +/*------------------------------------------------- + palette_deref - dereference a palette object; + if the reference count goes to 0, it is freed +-------------------------------------------------*/ + +void palette_deref(palette_t *palette) +{ + /* if the reference count goes to 0, free */ + if (--palette->refcount == 0) + internal_palette_free(palette); +} + + + +/*************************************************************************** + PALETTE INFORMATION +***************************************************************************/ + +/*------------------------------------------------- + palette_get_num_colors - return the number of + colors allocated in the palette +-------------------------------------------------*/ + +int palette_get_num_colors(palette_t *palette) +{ + return palette->numcolors; +} + + +/*------------------------------------------------- + palette_get_num_groups - return the number of + groups managed by the palette +-------------------------------------------------*/ + +int palette_get_num_groups(palette_t *palette) +{ + return palette->numgroups; +} + + +/*------------------------------------------------- + palette_get_black_entry - return the index of + the black entry +-------------------------------------------------*/ + +UINT32 palette_get_black_entry(palette_t *palette) +{ + return palette->numcolors * palette->numgroups + 0; +} + + +/*------------------------------------------------- + palette_get_white_entry - return the index of + the white entry +-------------------------------------------------*/ + +UINT32 palette_get_white_entry(palette_t *palette) +{ + return palette->numcolors * palette->numgroups + 1; +} + + + +/*************************************************************************** + PALETTE CLIENTS +***************************************************************************/ + +/*------------------------------------------------- + palette_client_alloc - add a new client to a + palette +-------------------------------------------------*/ + +palette_client *palette_client_alloc(palette_t *palette) +{ + UINT32 total_colors = palette->numcolors * palette->numgroups; + UINT32 dirty_dwords = (total_colors + 31) / 32; + palette_client *client; + + /* allocate memory for the client */ + client = malloc(sizeof(*client)); + if (client == NULL) + goto error; + memset(client, 0, sizeof(*client)); + + /* allocate dirty lists */ + client->live.dirty = malloc(dirty_dwords * sizeof(UINT32)); + client->previous.dirty = malloc(dirty_dwords * sizeof(UINT32)); + if (client->live.dirty == NULL || client->previous.dirty == NULL) + goto error; + + /* mark everything dirty to start with */ + memset(client->live.dirty, 0xff, dirty_dwords * sizeof(UINT32)); + memset(client->previous.dirty, 0xff, dirty_dwords * sizeof(UINT32)); + client->live.dirty[dirty_dwords - 1] &= (1 << (total_colors % 32)) - 1; + client->previous.dirty[dirty_dwords - 1] &= (1 << (total_colors % 32)) - 1; + + /* initialize the rest of the structure and add a reference to a palette */ + client->palette = palette; + palette_ref(palette); + client->live.mindirty = 0; + client->live.maxdirty = total_colors - 1; + + /* now add us to the list of clients */ + client->next = palette->client_list; + palette->client_list = client; + return client; + +error: + if (client != NULL) + { + if (client->live.dirty != NULL) + free(client->live.dirty); + if (client->previous.dirty != NULL) + free(client->previous.dirty); + free(client); + } + return NULL; +} + + +/*------------------------------------------------- + palette_client_free - remove a client from a + palette +-------------------------------------------------*/ + +void palette_client_free(palette_client *client) +{ + palette_client **curptr; + + /* first locate and remove ourself from the palette's list */ + for (curptr = &client->palette->client_list; *curptr != NULL; curptr = &(*curptr)->next) + if (*curptr == client) + { + *curptr = client->next; + break; + } + + /* now deref the palette */ + palette_deref(client->palette); + + /* free our data */ + if (client->live.dirty != NULL) + free(client->live.dirty); + if (client->previous.dirty != NULL) + free(client->previous.dirty); + free(client); +} + + +/*------------------------------------------------- + palette_client_get_palette - return a pointer + to the palette for this client +-------------------------------------------------*/ + +palette_t *palette_client_get_palette(palette_client *client) +{ + return client->palette; +} + + +/*------------------------------------------------- + palette_client_get_dirty_list - atomically get + the current dirty list for a client +-------------------------------------------------*/ + +const UINT32 *palette_client_get_dirty_list(palette_client *client, UINT32 *mindirty, UINT32 *maxdirty) +{ + dirty_state temp; + + /* fill in the mindirty/maxdirty */ + if (mindirty != NULL) + *mindirty = client->live.mindirty; + if (maxdirty != NULL) + *maxdirty = client->live.maxdirty; + + /* if nothing to report, report nothing and don't swap */ + if (client->live.mindirty > client->live.maxdirty) + return NULL; + + /* swap the live and previous lists */ + temp = client->live; + client->live = client->previous; + client->previous = temp; + + /* erase relevant entries in the new live one */ + if (client->live.mindirty <= client->live.maxdirty) + memset(client->live.dirty, client->live.mindirty / 8, (client->live.maxdirty / 8) + 1 - (client->live.mindirty / 8)); + client->live.mindirty = client->palette->numcolors * client->palette->numgroups; + client->live.maxdirty = 0; + + /* return a pointer to the previous table */ + return client->previous.dirty; +} + + + +/*************************************************************************** + PALETTE COLOR MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + palette_entry_set_color - set the raw RGB + color for a given palette index +-------------------------------------------------*/ + +void palette_entry_set_color(palette_t *palette, UINT32 index, rgb_t rgb) +{ + int groupnum; + + /* if out of range, or unchanged, ignore */ + if (index >= palette->numcolors || palette->entry_color[index] == rgb) + return; + + /* set the color */ + palette->entry_color[index] = rgb; + + /* update across all groups */ + for (groupnum = 0; groupnum < palette->numgroups; groupnum++) + update_adjusted_color(palette, groupnum, index); +} + + +/*------------------------------------------------- + palette_entry_get_color - return the raw RGB + color for a given palette index +-------------------------------------------------*/ + +rgb_t palette_entry_get_color(palette_t *palette, UINT32 index) +{ + return (index < palette->numcolors) ? palette->entry_color[index] : RGB_BLACK; +} + + +/*------------------------------------------------- + palette_entry_get_adjusted_color - return the + adjusted RGB color (after all adjustments) for + a given palette index +-------------------------------------------------*/ + +rgb_t palette_entry_get_adjusted_color(palette_t *palette, UINT32 index) +{ + return (index < palette->numcolors * palette->numgroups) ? palette->adjusted_color[index] : RGB_BLACK; +} + + +/*------------------------------------------------- + palette_entry_list_raw - return the entire + palette as an array of raw RGB values +-------------------------------------------------*/ + +const rgb_t *palette_entry_list_raw(palette_t *palette) +{ + return palette->entry_color; +} + + +/*------------------------------------------------- + palette_entry_list_adjusted - return the + entire palette as an array of adjusted RGB + values +-------------------------------------------------*/ + +const rgb_t *palette_entry_list_adjusted(palette_t *palette) +{ + return palette->adjusted_color; +} + + +/*------------------------------------------------- + palette_entry_list_adjusted_rgb15 - return the + entire palette as an array of adjusted RGB-15 + values +-------------------------------------------------*/ + +const rgb_t *palette_entry_list_adjusted_rgb15(palette_t *palette) +{ + return palette->adjusted_rgb15; +} + + + +/*************************************************************************** + PALETTE ADJUSTMENTS +***************************************************************************/ + +/*------------------------------------------------- + palette_entry_set_contrast - set the contrast + adjustment for a single palette index +-------------------------------------------------*/ + +void palette_entry_set_contrast(palette_t *palette, UINT32 index, float contrast) +{ + int groupnum; + + /* if out of range, or unchanged, ignore */ + if (index >= palette->numcolors || palette->entry_contrast[index] == contrast) + return; + + /* set the contrast */ + palette->entry_contrast[index] = contrast; + + /* update across all groups */ + for (groupnum = 0; groupnum < palette->numgroups; groupnum++) + update_adjusted_color(palette, groupnum, index); +} + + +/*------------------------------------------------- + palette_entry_get_contrast - return the + contrast adjustment for a single palette index +-------------------------------------------------*/ + +float palette_entry_get_contrast(palette_t *palette, UINT32 index) +{ + return (index < palette->numcolors) ? palette->entry_contrast[index] : 1.0f; +} + + +/*------------------------------------------------- + palette_group_set_brightness - configure + overall brightness for a palette group +-------------------------------------------------*/ + +void palette_group_set_brightness(palette_t *palette, UINT32 group, float brightness) +{ + int index; + + /* if out of range, or unchanged, ignore */ + if (group >= palette->numgroups || palette->group_bright[group] == brightness) + return; + + /* set the contrast */ + palette->group_bright[group] = brightness; + + /* update across all colors */ + for (index = 0; index < palette->numcolors; index++) + update_adjusted_color(palette, group, index); +} + + +/*------------------------------------------------- + palette_group_set_contrast - configure + overall contrast for a palette group +-------------------------------------------------*/ + +void palette_group_set_contrast(palette_t *palette, UINT32 group, float contrast) +{ + int index; + + /* if out of range, or unchanged, ignore */ + if (group >= palette->numgroups || palette->group_contrast[group] == contrast) + return; + + /* set the contrast */ + palette->group_contrast[group] = contrast; + + /* update across all colors */ + for (index = 0; index < palette->numcolors; index++) + update_adjusted_color(palette, group, index); +} + + + +/*************************************************************************** + PALETTE UTILITIES +***************************************************************************/ + +/*------------------------------------------------- + palette_normalize_range - normalize a range + of palette entries +-------------------------------------------------*/ + +void palette_normalize_range(palette_t *palette, UINT32 start, UINT32 end, int lum_min, int lum_max) +{ + UINT32 ymin = 1000 * 255, ymax = 0; + UINT32 tmin, tmax; + UINT32 index; + + /* clamp within range */ + start = MAX(start, 0); + end = MIN(end, palette->numcolors - 1); + + /* find the minimum and maximum brightness of all the colors in the range */ + for (index = start; index <= end; index++) + { + rgb_t rgb = palette->entry_color[index]; + UINT32 y = 299 * RGB_RED(rgb) + 587 * RGB_GREEN(rgb) + 114 * RGB_BLUE(rgb); + ymin = MIN(ymin, y); + ymax = MAX(ymax, y); + } + + /* determine target minimum/maximum */ + tmin = (lum_min < 0) ? ((ymin + 500) / 1000) : lum_min; + tmax = (lum_max < 0) ? ((ymax + 500) / 1000) : lum_max; + + /* now normalize the palette */ + for (index = start; index <= end; index++) + { + rgb_t rgb = palette->entry_color[index]; + UINT32 y = 299 * RGB_RED(rgb) + 587 * RGB_GREEN(rgb) + 114 * RGB_BLUE(rgb); + UINT32 target = tmin + ((y - ymin) * (tmax - tmin + 1)) / (ymax - ymin); + UINT8 r = (y == 0) ? 0 : rgb_clamp(RGB_RED(rgb) * 1000 * target / y); + UINT8 g = (y == 0) ? 0 : rgb_clamp(RGB_GREEN(rgb) * 1000 * target / y); + UINT8 b = (y == 0) ? 0 : rgb_clamp(RGB_BLUE(rgb) * 1000 * target / y); + palette_entry_set_color(palette, index, MAKE_RGB(r, g, b)); + } +} + + + +/*************************************************************************** + INTERNAL ROUTINES +***************************************************************************/ + +/*------------------------------------------------- + internal_palette_free - free all allocations + from a palette +-------------------------------------------------*/ + +static void internal_palette_free(palette_t *palette) +{ + /* free per-color data */ + if (palette->entry_color != NULL) + free(palette->entry_color); + if (palette->entry_contrast != NULL) + free(palette->entry_contrast); + + /* free per-group data */ + if (palette->group_bright != NULL) + free(palette->group_bright); + if (palette->group_contrast != NULL) + free(palette->group_contrast); + + /* free adjusted data */ + if (palette->adjusted_color != NULL) + free(palette->adjusted_color); + if (palette->adjusted_rgb15 != NULL) + free(palette->adjusted_rgb15); + + /* and the palette itself */ + free(palette); +} + + +/*------------------------------------------------- + update_adjusted_color - update a color index + by group and index pair +-------------------------------------------------*/ + +static void update_adjusted_color(palette_t *palette, UINT32 group, UINT32 index) +{ + UINT32 finalindex = group * palette->numcolors + index; + palette_client *client; + rgb_t adjusted; + + /* compute the adjusted value */ + adjusted = adjust_palette_entry(palette->entry_color[index], palette->group_bright[group], palette->group_contrast[group] * palette->entry_contrast[index]); + + /* if not different, ignore */ + if (palette->adjusted_color[finalindex] == adjusted) + return; + + /* otherwise, modify the adjusted color array */ + palette->adjusted_color[finalindex] = adjusted; + palette->adjusted_rgb15[finalindex] = rgb_to_rgb15(adjusted); + + /* mark dirty in all clients */ + for (client = palette->client_list; client != NULL; client = client->next) + { + client->live.dirty[finalindex / 32] |= 1 << (finalindex % 32); + client->live.mindirty = MIN(client->live.mindirty, finalindex); + client->live.maxdirty = MAX(client->live.maxdirty, finalindex); + } +} diff --git a/src/lib/util/palette.h b/src/lib/util/palette.h new file mode 100644 index 00000000000..cef180c1262 --- /dev/null +++ b/src/lib/util/palette.h @@ -0,0 +1,259 @@ +/****************************************************************************** + + palette.c + + Core palette routines. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __PALETTE_H__ +#define __PALETTE_H__ + +#include "osdcore.h" + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* an rgb_t is a single combined R,G,B (and optionally alpha) value */ +typedef UINT32 rgb_t; + +/* an rgb15_t is a single combined 15-bit R,G,B value */ +typedef UINT16 rgb15_t; + +/* a palette is an opaque, reference counted object */ +typedef struct _palette_t palette_t; + +/* a palette client is someone who is tracking the dirty state of a palette */ +typedef struct _palette_client palette_client; + + + +/*************************************************************************** + MACROS +***************************************************************************/ + +/* macros to assemble rgb_t values */ +#define MAKE_RGB(r,g,b) ((((rgb_t)(r) & 0xff) << 16) | (((rgb_t)(g) & 0xff) << 8) | ((rgb_t)(b) & 0xff)) +#define MAKE_ARGB(a,r,g,b) (MAKE_RGB(r,g,b) | (((rgb_t)(a) & 0xff) << 24)) + +/* macros to extract components from rgb_t values */ +#define RGB_ALPHA(rgb) (((rgb) >> 24) & 0xff) +#define RGB_RED(rgb) (((rgb) >> 16) & 0xff) +#define RGB_GREEN(rgb) (((rgb) >> 8) & 0xff) +#define RGB_BLUE(rgb) ((rgb) & 0xff) + +/* common colors */ +#define RGB_BLACK (MAKE_RGB(0,0,0)) +#define RGB_WHITE (MAKE_RGB(255,255,255)) + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + + +/* ----- palette allocation ----- */ + +/* allocate a new palette object and take a single reference on it */ +palette_t *palette_alloc(UINT32 numcolors, UINT32 numgroups); + +/* reference a palette object, incrementing its reference count */ +void palette_ref(palette_t *palette); + +/* dereference a palette object; if the reference count goes to 0, it is freed */ +void palette_deref(palette_t *palette); + + + +/* ----- palette information ----- */ + +/* return the number of colors managed by the palette */ +int palette_get_num_colors(palette_t *palette); + +/* return the number of groups managed by the palette */ +int palette_get_num_groups(palette_t *palette); + +/* return the index of the black entry */ +UINT32 palette_get_black_entry(palette_t *palette); + +/* return the index of the white entry */ +UINT32 palette_get_white_entry(palette_t *palette); + + + +/* ----- palette clients ----- */ + +/* add a new client to a palette */ +palette_client *palette_client_alloc(palette_t *palette); + +/* remove a client from a palette */ +void palette_client_free(palette_client *client); + +/* return a pointer to the palette for this client */ +palette_t *palette_client_get_palette(palette_client *client); + +/* atomically get the current dirty list for a client */ +const UINT32 *palette_client_get_dirty_list(palette_client *client, UINT32 *mindirty, UINT32 *maxdirty); + + + +/* ----- color management ----- */ + +/* set the raw RGB color for a given palette index */ +void palette_entry_set_color(palette_t *palette, UINT32 index, rgb_t rgb); + +/* return the raw RGB color for a given palette index */ +rgb_t palette_entry_get_color(palette_t *palette, UINT32 index); + +/* return the adjusted RGB color (after all adjustments) for a given palette index */ +rgb_t palette_entry_get_adjusted_color(palette_t *palette, UINT32 index); + +/* return the entire palette as an array of raw RGB values */ +const rgb_t *palette_entry_list_raw(palette_t *palette); + +/* return the entire palette as an array of adjusted RGB values */ +const rgb_t *palette_entry_list_adjusted(palette_t *palette); + +/* return the entire palette as an array of adjusted RGB-15 values */ +const rgb_t *palette_entry_list_adjusted_rgb15(palette_t *palette); + + + +/* ----- palette adjustments ----- */ + +/* set the contrast adjustment for a single palette index */ +void palette_entry_set_contrast(palette_t *palette, UINT32 index, float contrast); + +/* return the contrast adjustment for a single palette index */ +float palette_entry_get_contrast(palette_t *palette, UINT32 index); + +/* configure overall brightness for a palette group */ +void palette_group_set_brightness(palette_t *palette, UINT32 group, float brightness); + +/* configure overall contrast for a palette group */ +void palette_group_set_contrast(palette_t *palette, UINT32 group, float contrast); + + + +/* ----- palette utilities ----- */ + +/* normalize a range of palette entries, mapping minimum brightness to lum_min and maximum + brightness to lum_max; if either value is < 0, that boundary value is not modified */ +void palette_normalize_range(palette_t *palette, UINT32 start, UINT32 end, int lum_min, int lum_max); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + rgb_to_rgb15 - convert an RGB triplet to + a 15-bit OSD-specified RGB value +-------------------------------------------------*/ + +INLINE rgb15_t rgb_to_rgb15(rgb_t rgb) +{ + return ((RGB_RED(rgb) >> 3) << 10) | ((RGB_GREEN(rgb) >> 3) << 5) | ((RGB_BLUE(rgb) >> 3) << 0); +} + + +/*------------------------------------------------- + rgb_clamp - clamp an RGB component to 0-255 +-------------------------------------------------*/ + +INLINE UINT8 rgb_clamp(INT32 value) +{ + if (value < 0) + return 0; + if (value > 255) + return 255; + return value; +} + + +/*------------------------------------------------- + pal1bit - convert a 1-bit value to 8 bits +-------------------------------------------------*/ + +INLINE UINT8 pal1bit(UINT8 bits) +{ + return (bits & 1) ? 0xff : 0x00; +} + + +/*------------------------------------------------- + pal2bit - convert a 2-bit value to 8 bits +-------------------------------------------------*/ + +INLINE UINT8 pal2bit(UINT8 bits) +{ + bits &= 3; + return (bits << 6) | (bits << 4) | (bits << 2) | bits; +} + + +/*------------------------------------------------- + pal3bit - convert a 3-bit value to 8 bits +-------------------------------------------------*/ + +INLINE UINT8 pal3bit(UINT8 bits) +{ + bits &= 7; + return (bits << 5) | (bits << 2) | (bits >> 1); +} + + +/*------------------------------------------------- + pal4bit - convert a 4-bit value to 8 bits +-------------------------------------------------*/ + +INLINE UINT8 pal4bit(UINT8 bits) +{ + bits &= 0xf; + return (bits << 4) | bits; +} + + +/*------------------------------------------------- + pal5bit - convert a 5-bit value to 8 bits +-------------------------------------------------*/ + +INLINE UINT8 pal5bit(UINT8 bits) +{ + bits &= 0x1f; + return (bits << 3) | (bits >> 2); +} + + +/*------------------------------------------------- + pal6bit - convert a 6-bit value to 8 bits +-------------------------------------------------*/ + +INLINE UINT8 pal6bit(UINT8 bits) +{ + bits &= 0x3f; + return (bits << 2) | (bits >> 4); +} + + +/*------------------------------------------------- + pal7bit - convert a 7-bit value to 8 bits +-------------------------------------------------*/ + +INLINE UINT8 pal7bit(UINT8 bits) +{ + bits &= 0x7f; + return (bits << 1) | (bits >> 6); +} + + +#endif /* __PALETTE_H__ */ diff --git a/src/lib/util/png.c b/src/lib/util/png.c new file mode 100644 index 00000000000..865703c0d73 --- /dev/null +++ b/src/lib/util/png.c @@ -0,0 +1,1143 @@ +/********************************************************************* + + png.c + + PNG reading functions. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + + +*********************************************************************/ + +#include <math.h> +#include <zlib.h> +#include "png.h" + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _image_data_chunk image_data_chunk; +struct _image_data_chunk +{ + image_data_chunk * next; + int length; + UINT8 * data; +}; + + +typedef struct _png_private png_private; +struct _png_private +{ + png_info * pnginfo; + image_data_chunk * idata; + image_data_chunk ** idata_next; + UINT8 bpp; + UINT32 rowbytes; +}; + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +static const int samples[] = { 1, 0, 3, 1, 2, 0, 4 }; + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +INLINE UINT8 fetch_8bit(UINT8 *v) +{ + return *v; +} + + +INLINE UINT16 fetch_16bit(UINT8 *v) +{ + return BIG_ENDIANIZE_INT16(*(UINT16 *)v); +} + + +INLINE UINT32 fetch_32bit(UINT8 *v) +{ + return BIG_ENDIANIZE_INT32(*(UINT32 *)v); +} + + +INLINE void put_8bit(UINT8 *v, UINT8 data) +{ + *v = data; +} + + +INLINE void put_16bit(UINT8 *v, UINT16 data) +{ + *(UINT16 *)v = BIG_ENDIANIZE_INT16(data); +} + + +INLINE void put_32bit(UINT8 *v, UINT32 data) +{ + *(UINT32 *)v = BIG_ENDIANIZE_INT32(data); +} + + +INLINE int compute_bpp(const png_info *pnginfo) +{ + return samples[pnginfo->color_type] * pnginfo->bit_depth / 8; +} + + +INLINE int compute_rowbytes(const png_info *pnginfo) +{ + return (pnginfo->width * samples[pnginfo->color_type] * pnginfo->bit_depth + 7) / 8; +} + + + +/*************************************************************************** + GENERAL FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + png_free - free all memory allocated in a + pnginfo structure +-------------------------------------------------*/ + +void png_free(png_info *pnginfo) +{ + while (pnginfo->textlist != NULL) + { + png_text *temp = pnginfo->textlist; + pnginfo->textlist = temp->next; + if (temp->keyword != NULL) + free((void *)temp->keyword); + free(temp); + } + + if (pnginfo->palette != NULL) + free(pnginfo->palette); + pnginfo->palette = NULL; + + if (pnginfo->trans != NULL) + free(pnginfo->trans); + pnginfo->trans = NULL; + + if (pnginfo->image != NULL) + free(pnginfo->image); + pnginfo->image = NULL; +} + + + +/*************************************************************************** + PNG READING FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + verify_header - verify the PNG + header at the current file location +-------------------------------------------------*/ + +static png_error verify_header(core_file *fp) +{ + UINT8 signature[8]; + + /* read 8 bytes */ + if (core_fread(fp, signature, 8) != 8) + return PNGERR_FILE_TRUNCATED; + + /* return an error if we don't match */ + if (memcmp(signature, PNG_Signature, 8) != 0) + return PNGERR_BAD_SIGNATURE; + + return PNGERR_NONE; +} + + +/*------------------------------------------------- + read_chunk - read the next PNG chunk +-------------------------------------------------*/ + +static png_error read_chunk(core_file *fp, UINT8 **data, UINT32 *type, UINT32 *length) +{ + UINT32 crc, chunk_crc; + UINT8 tempbuff[4]; + + /* fetch the length of this chunk */ + if (core_fread(fp, tempbuff, 4) != 4) + return PNGERR_FILE_TRUNCATED; + *length = fetch_32bit(tempbuff); + + /* fetch the type of this chunk */ + if (core_fread(fp, tempbuff, 4) != 4) + return PNGERR_FILE_TRUNCATED; + *type = fetch_32bit(tempbuff); + + /* stop when we hit an IEND chunk */ + if (*type == PNG_CN_IEND) + return PNGERR_NONE; + + /* start the CRC with the chunk type (but not the length) */ + crc = crc32(0, tempbuff, 4); + + /* read the chunk itself into an allocated memory buffer */ + *data = NULL; + if (*length != 0) + { + /* allocate memory for this chunk */ + *data = (UINT8 *)malloc(*length); + if (*data == NULL) + return PNGERR_OUT_OF_MEMORY; + + /* read the data from the file */ + if (core_fread(fp, *data, *length) != *length) + { + free(*data); + *data = NULL; + return PNGERR_FILE_TRUNCATED; + } + + /* update the CRC */ + crc = crc32(crc, *data, *length); + } + + /* read the CRC */ + if (core_fread(fp, tempbuff, 4) != 4) + { + free(*data); + *data = NULL; + return PNGERR_FILE_TRUNCATED; + } + chunk_crc = fetch_32bit(tempbuff); + + /* validate the CRC */ + if (crc != chunk_crc) + { + free(*data); + *data = NULL; + return PNGERR_FILE_CORRUPT; + } + return PNGERR_NONE; +} + + +/*------------------------------------------------- + process_chunk - process a PNG chunk +-------------------------------------------------*/ + +static png_error process_chunk(png_private *png, UINT8 *data, UINT32 type, UINT32 length, int *keepmem) +{ + /* default to not keeping memory */ + *keepmem = FALSE; + + /* switch off of the type */ + switch (type) + { + /* image header */ + case PNG_CN_IHDR: + png->pnginfo->width = fetch_32bit(data); + png->pnginfo->height = fetch_32bit(data + 4); + png->pnginfo->bit_depth = fetch_8bit(data + 8); + png->pnginfo->color_type = fetch_8bit(data + 9); + png->pnginfo->compression_method = fetch_8bit(data + 10); + png->pnginfo->filter_method = fetch_8bit(data + 11); + png->pnginfo->interlace_method = fetch_8bit(data + 12); + break; + + /* palette */ + case PNG_CN_PLTE: + png->pnginfo->num_palette = length / 3; + png->pnginfo->palette = data; + *keepmem = TRUE; + break; + + /* transparency information */ + case PNG_CN_tRNS: + png->pnginfo->num_trans = length; + png->pnginfo->trans = data; + *keepmem = TRUE; + break; + + /* image data */ + case PNG_CN_IDAT: + + /* allocate a new image data descriptor */ + *png->idata_next = malloc(sizeof(**png->idata_next)); + if (*png->idata_next == NULL) + return PNGERR_OUT_OF_MEMORY; + + /* add it to the tail of the list */ + (*png->idata_next)->next = NULL; + (*png->idata_next)->length = length; + (*png->idata_next)->data = data; + png->idata_next = &(*png->idata_next)->next; + *keepmem = TRUE; + break; + + /* gamma */ + case PNG_CN_gAMA: + png->pnginfo->source_gamma = fetch_32bit(data) / 100000.0; + break; + + /* physical information */ + case PNG_CN_pHYs: + png->pnginfo->xres = fetch_32bit(data); + png->pnginfo->yres = fetch_32bit(data + 4); + png->pnginfo->resolution_unit = fetch_8bit(data + 8); + break; + + /* text */ + case PNG_CN_tEXt: + { + png_text *text, *pt, *ct; + + /* allocate a new text item */ + text = malloc(sizeof(*text)); + if (text == NULL) + return PNGERR_OUT_OF_MEMORY; + + /* set the elements */ + text->keyword = (char *)data; + text->text = text->keyword + strlen(text->keyword) + 1; + text->next = NULL; + + /* add to the end of the list */ + for (pt = NULL, ct = png->pnginfo->textlist; ct != NULL; pt = ct, ct = ct->next) ; + if (pt == NULL) + png->pnginfo->textlist = text; + else + pt->next = text; + + *keepmem = TRUE; + break; + } + + /* anything else */ + default: + if ((type & 0x20000000) == 0) + return PNGERR_UNKNOWN_CHUNK; + break; + } + return PNGERR_NONE; +} + + +/*------------------------------------------------- + unfilter_row - unfilter a single row of pixels +-------------------------------------------------*/ + +static png_error unfilter_row(int type, UINT8 *src, UINT8 *dst, UINT8 *dstprev, int bpp, int rowbytes) +{ + int x; + + /* switch off of it */ + switch (type) + { + /* no filter, just copy */ + case PNG_PF_None: + for (x = 0; x < rowbytes; x++) + *dst++ = *src++; + break; + + /* SUB = previous pixel */ + case PNG_PF_Sub: + for (x = 0; x < bpp; x++) + *dst++ = *src++; + for (x = bpp; x < rowbytes; x++, dst++) + *dst = *src++ + dst[-bpp]; + break; + + /* UP = pixel above */ + case PNG_PF_Up: + if (dstprev == NULL) + return unfilter_row(PNG_PF_None, src, dst, dstprev, bpp, rowbytes); + for (x = 0; x < rowbytes; x++, dst++) + *dst = *src++ + *dstprev++; + break; + + /* AVERAGE = average of pixel above and previous pixel */ + case PNG_PF_Average: + if (dstprev == NULL) + { + for (x = 0; x < bpp; x++) + *dst++ = *src++; + for (x = bpp; x < rowbytes; x++, dst++) + *dst = *src++ + dst[-bpp] / 2; + } + else + { + for (x = 0; x < bpp; x++, dst++) + *dst = *src++ + *dstprev++ / 2; + for (x = bpp; x < rowbytes; x++, dst++) + *dst = *src++ + (*dstprev++ + dst[-bpp]) / 2; + } + break; + + /* PAETH = special filter */ + case PNG_PF_Paeth: + for (x = 0; x < rowbytes; x++) + { + INT32 pa = (x < bpp) ? 0 : dst[-bpp]; + INT32 pc = (x < bpp || dstprev == NULL) ? 0 : dstprev[-bpp]; + INT32 pb = (dstprev == NULL) ? 0 : *dstprev++; + INT32 prediction = pa + pb - pc; + INT32 da = abs(prediction - pa); + INT32 db = abs(prediction - pb); + INT32 dc = abs(prediction - pc); + if (da <= db && da <= dc) + *dst++ = *src++ + pa; + else if (db <= dc) + *dst++ = *src++ + pb; + else + *dst++ = *src++ + pc; + } + break; + + /* unknown filter type */ + default: + return PNGERR_UNKNOWN_FILTER; + } + + return PNGERR_NONE; +} + + +/*------------------------------------------------- + process_image - post-process a loaded iamge +-------------------------------------------------*/ + +static png_error process_image(png_private *png) +{ + int rowbytes, bpp, imagesize; + png_error error = PNGERR_NONE; + image_data_chunk *idat; + UINT8 *src, *dst; + z_stream stream; + int zerr, y; + + /* compute some basic parameters */ + bpp = compute_bpp(png->pnginfo); + rowbytes = compute_rowbytes(png->pnginfo); + imagesize = png->pnginfo->height * (rowbytes + 1); + + /* allocate memory for the filtered image */ + png->pnginfo->image = (UINT8 *)malloc(imagesize); + if (png->pnginfo->image == NULL) + return PNGERR_OUT_OF_MEMORY; + + /* initialize the stream */ + memset(&stream, 0, sizeof(stream)); + stream.next_out = png->pnginfo->image; + stream.avail_out = imagesize; + zerr = inflateInit(&stream); + if (zerr != Z_OK) + { + error = PNGERR_DECOMPRESS_ERROR; + goto handle_error; + } + + /* loop over IDAT and decompress each as part of a larger stream */ + for (idat = png->idata; idat != NULL; idat = idat->next) + { + /* decompress this chunk */ + stream.next_in = idat->data; + stream.avail_in = idat->length; + zerr = inflate(&stream, Z_NO_FLUSH); + + /* stop at the end of the stream */ + if (zerr == Z_STREAM_END) + break; + + /* other errors are fatal */ + if (zerr != Z_OK) + { + error = PNGERR_DECOMPRESS_ERROR; + goto handle_error; + } + } + + /* clean up */ + zerr = inflateEnd(&stream); + if (zerr != Z_OK) + { + error = PNGERR_DECOMPRESS_ERROR; + goto handle_error; + } + + /* we de-filter in place */ + src = dst = png->pnginfo->image; + + /* iterate over rows */ + for (y = 0; y < png->pnginfo->height && error == PNGERR_NONE; y++) + { + /* first byte of each row is the filter type */ + int filter = *src++; + error = unfilter_row(filter, src, dst, (y == 0) ? NULL : &dst[-rowbytes], bpp, rowbytes); + src += rowbytes; + dst += rowbytes; + } + +handle_error: + /* if we errored, free the image data */ + if (error != PNGERR_NONE) + { + free(png->pnginfo->image); + png->pnginfo->image = NULL; + } + return error; +} + + +/*------------------------------------------------- + png_read_file - read a PNG from a core stream +-------------------------------------------------*/ + +png_error png_read_file(core_file *fp, png_info *pnginfo) +{ + UINT8 *chunk_data = NULL; + png_private png; + png_error error; + + /* initialize the data structures */ + memset(&png, 0, sizeof(png)); + memset(pnginfo, 0, sizeof(*pnginfo)); + png.pnginfo = pnginfo; + png.idata_next = &png.idata; + + /* verify the signature at the start of the file */ + error = verify_header(fp); + if (error != PNGERR_NONE) + goto handle_error; + + /* loop until we hit an IEND chunk */ + for ( ; ; ) + { + UINT32 chunk_type, chunk_length; + int keepmem; + + /* read a chunk */ + error = read_chunk(fp, &chunk_data, &chunk_type, &chunk_length); + if (error != PNGERR_NONE) + goto handle_error; + + /* stop when we hit an IEND chunk */ + if (chunk_type == PNG_CN_IEND) + break; + + /* process the chunk */ + error = process_chunk(&png, chunk_data, chunk_type, chunk_length, &keepmem); + if (error != PNGERR_NONE) + goto handle_error; + + /* free memory if we didn't want to keep it */ + if (!keepmem) + free(chunk_data); + chunk_data = NULL; + } + + /* finish processing the image */ + error = process_image(&png); + if (error != PNGERR_NONE) + goto handle_error; + +handle_error: + + /* free all intermediate data */ + while (png.idata != NULL) + { + image_data_chunk *next = png.idata->next; + if (png.idata->data != NULL) + free(png.idata->data); + free(png.idata); + png.idata = next; + } + if (chunk_data != NULL) + free(chunk_data); + + /* if we have an error, free all the other data as well */ + if (error != PNGERR_NONE) + { + png_free(pnginfo); + memset(pnginfo, 0, sizeof(*pnginfo)); + } + return error; +} + + +/*------------------------------------------------- + png_read_bitmap - load a PNG file into a + bitmap_t +-------------------------------------------------*/ + +png_error png_read_bitmap(core_file *fp, bitmap_t **bitmap) +{ + png_error result; + png_info png; + UINT8 *src; + int x, y; + + /* read the PNG data */ + result = png_read_file(fp, &png); + if (result != PNGERR_NONE) + return result; + + /* verify we can handle this PNG */ + if (png.bit_depth > 8 || png.interlace_method != 0 || + (png.color_type != 0 && png.color_type != 3 && png.color_type != 2 && png.color_type != 6)) + { + png_free(&png); + return PNGERR_UNSUPPORTED_FORMAT; + } + + /* if less than 8 bits, upsample */ + png_expand_buffer_8bit(&png); + + /* allocate a bitmap of the appropriate size and copy it */ + *bitmap = bitmap_alloc(png.width, png.height, BITMAP_FORMAT_ARGB32); + if (*bitmap == NULL) + { + png_free(&png); + return PNGERR_OUT_OF_MEMORY; + } + + /* handle 8bpp palettized case */ + src = png.image; + if (png.color_type == 3) + { + /* loop over width/height */ + for (y = 0; y < png.height; y++) + for (x = 0; x < png.width; x++, src++) + { + /* determine alpha and expand to 32bpp */ + UINT8 alpha = (*src < png.num_trans) ? png.trans[*src] : 0xff; + *BITMAP_ADDR32(*bitmap, y, x) = (alpha << 24) | (png.palette[*src * 3] << 16) | (png.palette[*src * 3 + 1] << 8) | png.palette[*src * 3 + 2]; + } + } + + /* handle 8bpp grayscale case */ + else if (png.color_type == 0) + { + for (y = 0; y < png.height; y++) + for (x = 0; x < png.width; x++, src++) + *BITMAP_ADDR32(*bitmap, y, x) = 0xff000000 | (*src << 16) | (*src << 8) | *src; + } + + /* handle 32bpp non-alpha case */ + else if (png.color_type == 2) + { + for (y = 0; y < png.height; y++) + for (x = 0; x < png.width; x++, src += 3) + *BITMAP_ADDR32(*bitmap, y, x) = 0xff000000 | (src[0] << 16) | (src[1] << 8) | src[2]; + } + + /* handle 32bpp alpha case */ + else if (png.color_type == 6) + { + for (y = 0; y < png.height; y++) + for (x = 0; x < png.width; x++, src += 4) + *BITMAP_ADDR32(*bitmap, y, x) = (src[3] << 24) | (src[0] << 16) | (src[1] << 8) | src[2]; + } + + /* free our temporary data and return */ + png_free(&png); + return PNGERR_NONE; +} + + +/*------------------------------------------------- + png_expand_buffer_8bit - expand a buffer from + sub 8-bit to 8-bit +-------------------------------------------------*/ + +png_error png_expand_buffer_8bit(png_info *pnginfo) +{ + int i,j, k; + UINT8 *inp, *outp, *outbuf; + + /* nothing to do if we're at 8 or greater already */ + if (pnginfo->bit_depth >= 8) + return PNGERR_NONE; + + /* allocate a new buffer at 8-bit */ + outbuf = malloc(pnginfo->width * pnginfo->height); + if (outbuf == NULL) + return PNGERR_OUT_OF_MEMORY; + + inp = pnginfo->image; + outp = outbuf; + + for (i = 0; i < pnginfo->height; i++) + { + for(j = 0; j < pnginfo->width / ( 8 / pnginfo->bit_depth); j++) + { + for (k = 8 / pnginfo->bit_depth-1; k >= 0; k--) + *outp++ = (*inp >> k * pnginfo->bit_depth) & (0xff >> (8 - pnginfo->bit_depth)); + inp++; + } + if (pnginfo->width % (8 / pnginfo->bit_depth)) + { + for (k = pnginfo->width % (8 / pnginfo->bit_depth)-1; k >= 0; k--) + *outp++ = (*inp >> k * pnginfo->bit_depth) & (0xff >> (8 - pnginfo->bit_depth)); + inp++; + } + } + free (pnginfo->image); + pnginfo->image = outbuf; + + return PNGERR_NONE; +} + + + +/*************************************************************************** + PNG WRITING FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + png_add_text - add a text entry to the png_info +-------------------------------------------------*/ + +png_error png_add_text(png_info *pnginfo, const char *keyword, const char *text) +{ + png_text *newtext, *pt, *ct; + char *textdata; + int keylen; + + /* allocate a new text element */ + newtext = malloc(sizeof(*newtext)); + if (newtext == NULL) + return PNGERR_OUT_OF_MEMORY; + + /* allocate a string long enough to hold both */ + keylen = (int)strlen(keyword); + textdata = malloc(keylen + 1 + strlen(text) + 1); + if (textdata == NULL) + { + free(newtext); + return PNGERR_OUT_OF_MEMORY; + } + + /* copy in the data */ + strcpy(textdata, keyword); + strcpy(textdata + keylen + 1, text); + + /* text follows a trailing NULL */ + newtext->keyword = textdata; + newtext->text = textdata + keylen + 1; + newtext->next = NULL; + + /* add us to the end of the linked list */ + for (pt = NULL, ct = pnginfo->textlist; ct != NULL; pt = ct, ct = ct->next) ; + if (pt == NULL) + pnginfo->textlist = newtext; + else + pt->next = newtext; + + return PNGERR_NONE; +} + + +/*------------------------------------------------- + write_chunk - write an in-memory chunk to + the given file +-------------------------------------------------*/ + +static png_error write_chunk(core_file *fp, const UINT8 *data, UINT32 type, UINT32 length) +{ + UINT8 tempbuff[8]; + UINT32 crc; + + /* stuff the length/type into the buffer */ + put_32bit(tempbuff + 0, length); + put_32bit(tempbuff + 4, type); + crc = crc32(0, tempbuff + 4, 4); + + /* write that data */ + if (core_fwrite(fp, tempbuff, 8) != 8) + return PNGERR_FILE_ERROR; + + /* append the actual data */ + if (length > 0) + { + if (core_fwrite(fp, data, length) != length) + return PNGERR_FILE_ERROR; + crc = crc32(crc, data, length); + } + + /* write the CRC */ + put_32bit(tempbuff, crc); + if (core_fwrite(fp, tempbuff, 4) != 4) + return PNGERR_FILE_ERROR; + + return PNGERR_NONE; +} + + +/*------------------------------------------------- + write_deflated_chunk - write an in-memory + chunk to the given file by deflating it +-------------------------------------------------*/ + +static png_error write_deflated_chunk(core_file *fp, UINT8 *data, UINT32 type, UINT32 length) +{ + UINT64 lengthpos = core_ftell(fp); + UINT8 tempbuff[8192]; + UINT32 zlength = 0; + z_stream stream; + UINT32 crc; + int zerr; + + /* stuff the length/type into the buffer */ + put_32bit(tempbuff + 0, length); + put_32bit(tempbuff + 4, type); + crc = crc32(0, tempbuff + 4, 4); + + /* write that data */ + if (core_fwrite(fp, tempbuff, 8) != 8) + return PNGERR_FILE_ERROR; + + /* initialize the stream */ + memset(&stream, 0, sizeof(stream)); + stream.next_in = data; + stream.avail_in = length; + zerr = deflateInit(&stream, Z_DEFAULT_COMPRESSION); + if (zerr != Z_OK) + return PNGERR_COMPRESS_ERROR; + + /* now loop until we run out of data */ + for ( ; ; ) + { + /* compress this chunk */ + stream.next_out = tempbuff; + stream.avail_out = sizeof(tempbuff); + zerr = deflate(&stream, Z_FINISH); + + /* if there's data to write, do it */ + if (stream.avail_out < sizeof(tempbuff)) + { + int bytes = sizeof(tempbuff) - stream.avail_out; + if (core_fwrite(fp, tempbuff, bytes) != bytes) + { + deflateEnd(&stream); + return PNGERR_FILE_ERROR; + } + crc = crc32(crc, tempbuff, bytes); + zlength += bytes; + } + + /* stop at the end of the stream */ + if (zerr == Z_STREAM_END) + break; + + /* other errors are fatal */ + if (zerr != Z_OK) + { + deflateEnd(&stream); + return PNGERR_COMPRESS_ERROR; + } + } + + /* clean up deflater(maus) */ + zerr = deflateEnd(&stream); + if (zerr != Z_OK) + return PNGERR_COMPRESS_ERROR; + + /* write the CRC */ + put_32bit(tempbuff, crc); + if (core_fwrite(fp, tempbuff, 4) != 4) + return PNGERR_FILE_ERROR; + + /* seek back and update the length */ + core_fseek(fp, lengthpos, SEEK_SET); + put_32bit(tempbuff + 0, zlength); + if (core_fwrite(fp, tempbuff, 4) != 4) + return PNGERR_FILE_ERROR; + + /* return to the end */ + core_fseek(fp, lengthpos + 8 + zlength + 4, SEEK_SET); + return PNGERR_NONE; +} + + +/*------------------------------------------------- + convert_bitmap_to_image_palette - convert a + bitmap to a palettized image +-------------------------------------------------*/ + +static png_error convert_bitmap_to_image_palette(png_info *pnginfo, const bitmap_t *bitmap, int palette_length, const rgb_t *palette) +{ + int rowbytes; + int x, y; + + /* set the common info */ + pnginfo->width = bitmap->width; + pnginfo->height = bitmap->height; + pnginfo->bit_depth = 8; + pnginfo->color_type = 3; + pnginfo->num_palette = 256; + rowbytes = pnginfo->width; + + /* allocate memory for the palette */ + pnginfo->palette = malloc(3 * 256); + if (pnginfo->palette == NULL) + return PNGERR_OUT_OF_MEMORY; + + /* build the palette */ + memset(pnginfo->palette, 0, 3 * 256); + for (x = 0; x < palette_length; x++) + { + rgb_t color = palette[x]; + pnginfo->palette[3 * x + 0] = RGB_RED(color); + pnginfo->palette[3 * x + 1] = RGB_GREEN(color); + pnginfo->palette[3 * x + 2] = RGB_BLUE(color); + } + + /* allocate memory for the image */ + pnginfo->image = malloc(pnginfo->height * (rowbytes + 1)); + if (pnginfo->image == NULL) + { + free(pnginfo->palette); + return PNGERR_OUT_OF_MEMORY; + } + + /* copy in the pixels, specifying a NULL filter */ + for (y = 0; y < pnginfo->height; y++) + { + UINT16 *src = (UINT16 *)bitmap->base + y * bitmap->rowpixels; + UINT8 *dst = pnginfo->image + y * (rowbytes + 1); + + /* store the filter byte, then copy the data */ + *dst++ = 0; + for (x = 0; x < pnginfo->width; x++) + *dst++ = *src++; + } + + return PNGERR_NONE; +} + + +/*------------------------------------------------- + convert_bitmap_to_image_rgb - convert a + bitmap to an RGB image +-------------------------------------------------*/ + +static png_error convert_bitmap_to_image_rgb(png_info *pnginfo, const bitmap_t *bitmap, int palette_length, const rgb_t *palette) +{ + int alpha = (bitmap->format == BITMAP_FORMAT_ARGB32); + int rowbytes; + int x, y; + + /* set the common info */ + pnginfo->width = bitmap->width; + pnginfo->height = bitmap->height; + pnginfo->bit_depth = 8; + pnginfo->color_type = alpha ? 6 : 2; + rowbytes = pnginfo->width * (alpha ? 4 : 3); + + /* allocate memory for the image */ + pnginfo->image = malloc(pnginfo->height * (rowbytes + 1)); + if (pnginfo->image == NULL) + return PNGERR_OUT_OF_MEMORY; + + /* copy in the pixels, specifying a NULL filter */ + for (y = 0; y < pnginfo->height; y++) + { + UINT32 *src32 = BITMAP_ADDR32(bitmap, y, 0); + UINT16 *src16 = BITMAP_ADDR16(bitmap, y, 0); + UINT8 *dst = pnginfo->image + y * (rowbytes + 1); + + /* store the filter byte, then copy the data */ + *dst++ = 0; + + /* 16bpp palettized format */ + if (bitmap->format == BITMAP_FORMAT_INDEXED16) + { + for (x = 0; x < pnginfo->width; x++) + { + rgb_t color = palette[*src16++]; + *dst++ = RGB_RED(color); + *dst++ = RGB_GREEN(color); + *dst++ = RGB_BLUE(color); + } + } + + /* RGB formats */ + else if (bitmap->format == BITMAP_FORMAT_RGB15) + { + for (x = 0; x < pnginfo->width; x++) + { + UINT16 raw = *src16++; + *dst++ = pal5bit(raw >> 10); + *dst++ = pal5bit(raw >> 5); + *dst++ = pal5bit(raw >> 0); + } + } + + /* 32-bit RGB direct */ + else if (bitmap->format == BITMAP_FORMAT_RGB32) + { + for (x = 0; x < pnginfo->width; x++) + { + UINT32 raw = *src32++; + *dst++ = RGB_RED(raw); + *dst++ = RGB_GREEN(raw); + *dst++ = RGB_BLUE(raw); + } + } + + /* 32-bit ARGB direct */ + else if (bitmap->format == BITMAP_FORMAT_ARGB32) + { + for (x = 0; x < pnginfo->width; x++) + { + UINT32 raw = *src32++; + *dst++ = RGB_RED(raw); + *dst++ = RGB_GREEN(raw); + *dst++ = RGB_BLUE(raw); + *dst++ = RGB_ALPHA(raw); + } + } + + /* unsupported format */ + else + return PNGERR_UNSUPPORTED_FORMAT; + } + + return PNGERR_NONE; +} + + +/*------------------------------------------------- + write_png_stream - stream a series of PNG + chunks to the given file +-------------------------------------------------*/ + +static png_error write_png_stream(core_file *fp, png_info *pnginfo, const bitmap_t *bitmap, int palette_length, const rgb_t *palette) +{ + UINT8 tempbuff[16]; + png_text *text; + png_error error; + + /* create an unfiltered image in either palette or RGB form */ + if (bitmap->format == BITMAP_FORMAT_INDEXED16 && palette_length <= 256) + error = convert_bitmap_to_image_palette(pnginfo, bitmap, palette_length, palette); + else + error = convert_bitmap_to_image_rgb(pnginfo, bitmap, palette_length, palette); + if (error != PNGERR_NONE) + goto handle_error; + + /* if we wanted to get clever and do filtering, we would do it here */ + + /* write the IHDR chunk */ + put_32bit(tempbuff + 0, pnginfo->width); + put_32bit(tempbuff + 4, pnginfo->height); + put_8bit(tempbuff + 8, pnginfo->bit_depth); + put_8bit(tempbuff + 9, pnginfo->color_type); + put_8bit(tempbuff + 10, pnginfo->compression_method); + put_8bit(tempbuff + 11, pnginfo->filter_method); + put_8bit(tempbuff + 12, pnginfo->interlace_method); + error = write_chunk(fp, tempbuff, PNG_CN_IHDR, 13); + if (error != PNGERR_NONE) + goto handle_error; + + /* write the PLTE chunk */ + if (pnginfo->num_palette > 0) + error = write_chunk(fp, pnginfo->palette, PNG_CN_PLTE, pnginfo->num_palette * 3); + if (error != PNGERR_NONE) + goto handle_error; + + /* write a single IDAT chunk */ + error = write_deflated_chunk(fp, pnginfo->image, PNG_CN_IDAT, pnginfo->height * (compute_rowbytes(pnginfo) + 1)); + if (error != PNGERR_NONE) + goto handle_error; + + /* write TEXT chunks */ + for (text = pnginfo->textlist; text != NULL; text = text->next) + { + error = write_chunk(fp, (UINT8 *)text->keyword, PNG_CN_tEXt, (UINT32)strlen(text->keyword) + 1 + (UINT32)strlen(text->text)); + if (error != PNGERR_NONE) + goto handle_error; + } + + /* write an IEND chunk */ + error = write_chunk(fp, NULL, PNG_CN_IEND, 0); + +handle_error: + return error; +} + + + +png_error png_write_bitmap(core_file *fp, png_info *info, bitmap_t *bitmap, int palette_length, const UINT32 *palette) +{ + png_info pnginfo; + png_error error; + + /* use a dummy pnginfo if none passed to us */ + if (info == NULL) + { + info = &pnginfo; + memset(&pnginfo, 0, sizeof(pnginfo)); + } + + /* write the PNG signature */ + if (core_fwrite(fp, PNG_Signature, 8) != 8) + { + if (info == &pnginfo) + png_free(&pnginfo); + return PNGERR_FILE_ERROR; + } + + /* write the rest of the PNG data */ + error = write_png_stream(fp, info, bitmap, palette_length, palette); + if (info == &pnginfo) + png_free(&pnginfo); + return error; +} + + + +/******************************************************************************** + + MNG write functions + +********************************************************************************/ + +png_error mng_capture_start(core_file *fp, bitmap_t *bitmap, double rate) +{ + UINT8 mhdr[28]; + png_error error; + + if (core_fwrite(fp, MNG_Signature, 8) != 8) + return PNGERR_FILE_ERROR; + + memset(mhdr, 0, 28); + put_32bit(mhdr + 0, bitmap->width); + put_32bit(mhdr + 4, bitmap->height); + put_32bit(mhdr + 8, rate); + put_32bit(mhdr + 24, 0x0041); /* Simplicity profile */ + /* frame count and play time unspecified because + we don't know at this stage */ + error = write_chunk(fp, mhdr, MNG_CN_MHDR, 28); + if (error != PNGERR_NONE) + return error; + + return PNGERR_NONE; +} + +png_error mng_capture_frame(core_file *fp, png_info *info, bitmap_t *bitmap, int palette_length, const UINT32 *palette) +{ + return write_png_stream(fp, info, bitmap, palette_length, palette); +} + +png_error mng_capture_stop(core_file *fp) +{ + return write_chunk(fp, NULL, MNG_CN_MEND, 0); +} diff --git a/src/lib/util/png.h b/src/lib/util/png.h new file mode 100644 index 00000000000..9d11d895d64 --- /dev/null +++ b/src/lib/util/png.h @@ -0,0 +1,137 @@ +/*************************************************************************** + + png.h + + PNG file management. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __PNG_H__ +#define __PNG_H__ + +#include "osdcore.h" +#include "bitmap.h" +#include "corefile.h" + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define PNG_Signature "\x89\x50\x4E\x47\x0D\x0A\x1A\x0A" +#define MNG_Signature "\x8A\x4D\x4E\x47\x0D\x0A\x1A\x0A" + +/* Chunk names */ +#define PNG_CN_IHDR 0x49484452L +#define PNG_CN_PLTE 0x504C5445L +#define PNG_CN_IDAT 0x49444154L +#define PNG_CN_IEND 0x49454E44L +#define PNG_CN_gAMA 0x67414D41L +#define PNG_CN_sBIT 0x73424954L +#define PNG_CN_cHRM 0x6348524DL +#define PNG_CN_tRNS 0x74524E53L +#define PNG_CN_bKGD 0x624B4744L +#define PNG_CN_hIST 0x68495354L +#define PNG_CN_tEXt 0x74455874L +#define PNG_CN_zTXt 0x7A545874L +#define PNG_CN_pHYs 0x70485973L +#define PNG_CN_oFFs 0x6F464673L +#define PNG_CN_tIME 0x74494D45L +#define PNG_CN_sCAL 0x7343414CL + +/* MNG Chunk names */ +#define MNG_CN_MHDR 0x4D484452L +#define MNG_CN_MEND 0x4D454E44L +#define MNG_CN_TERM 0x5445524DL +#define MNG_CN_BACK 0x4241434BL + +/* Prediction filters */ +#define PNG_PF_None 0 +#define PNG_PF_Sub 1 +#define PNG_PF_Up 2 +#define PNG_PF_Average 3 +#define PNG_PF_Paeth 4 + +/* Error types */ +enum _png_error +{ + PNGERR_NONE, + PNGERR_OUT_OF_MEMORY, + PNGERR_UNKNOWN_FILTER, + PNGERR_FILE_ERROR, + PNGERR_BAD_SIGNATURE, + PNGERR_DECOMPRESS_ERROR, + PNGERR_FILE_TRUNCATED, + PNGERR_FILE_CORRUPT, + PNGERR_UNKNOWN_CHUNK, + PNGERR_COMPRESS_ERROR, + PNGERR_UNSUPPORTED_FORMAT +}; +typedef enum _png_error png_error; + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _png_text png_text; +struct _png_text +{ + png_text * next; + const char * keyword; /* this is allocated */ + const char * text; /* this points to a part of keyword */ +}; + + +typedef struct _png_info png_info; +struct _png_info +{ + UINT8 * image; + UINT32 width, height; + UINT32 xres, yres; + rectangle screen; + double xscale, yscale; + double source_gamma; + UINT32 resolution_unit; + UINT8 bit_depth; + UINT8 color_type; + UINT8 compression_method; + UINT8 filter_method; + UINT8 interlace_method; + + UINT8 * palette; + UINT32 num_palette; + + UINT8 * trans; + UINT32 num_trans; + + png_text * textlist; +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +void png_free(png_info *pnginfo); + +png_error png_read_file(core_file *fp, png_info *pnginfo); +png_error png_read_bitmap(core_file *fp, bitmap_t **bitmap); +png_error png_expand_buffer_8bit(png_info *p); + +png_error png_add_text(png_info *pnginfo, const char *keyword, const char *text); +png_error png_write_bitmap(core_file *fp, png_info *info, bitmap_t *bitmap, int palette_length, const UINT32 *palette); + +png_error mng_capture_start(core_file *fp, bitmap_t *bitmap, double rate); +png_error mng_capture_frame(core_file *fp, png_info *info, bitmap_t *bitmap, int palette_length, const UINT32 *palette); +png_error mng_capture_stop(core_file *fp); + +#endif /* __PNG_H__ */ diff --git a/src/lib/util/pool.c b/src/lib/util/pool.c new file mode 100644 index 00000000000..62b131416fb --- /dev/null +++ b/src/lib/util/pool.c @@ -0,0 +1,614 @@ +/*************************************************************************** + + pool.c + + Resource pool code + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include <stdarg.h> +#include <stddef.h> +#include "pool.h" +#include "coreutil.h" + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define OBJECT_ENTRY_BLOCK 256 +#define POOL_HASH_SIZE 3797 + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* an object type entry */ +typedef struct _objtype_entry objtype_entry; +struct _objtype_entry +{ + objtype_entry * next; + UINT32 type; + const char * friendly; + void (*destructor)(void *, size_t); +}; + + +/* an entry in a pool */ +typedef struct _object_entry object_entry; +struct _object_entry +{ + object_entry * next; + objtype_entry * type; + void * object; + size_t size; + const char * file; + int line; +}; + + +/* a block of entry items */ +typedef struct _object_entry_block object_entry_block; +struct _object_entry_block +{ + object_entry_block *next; + object_entry entry[OBJECT_ENTRY_BLOCK]; +}; + + +/* the object pool itself */ +struct _object_pool +{ + object_entry * hashtable[POOL_HASH_SIZE]; + object_entry * freelist; + object_entry_block *blocklist; + objtype_entry * typelist; + void (*fail)(const char *message); +}; + + +/* an iterator over objects in a pool */ +struct _object_pool_iterator +{ + object_pool * pool; + object_type type; + int hashnum; + object_entry * last; +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +static void memory_destruct(void *object, size_t size); +static void report_failure(object_pool *pool, const char *format, ...); + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + hash_object - compute the hash for a given + object +-------------------------------------------------*/ + +INLINE int hash_object(void *object) +{ + return ((size_t)object >> 4) % POOL_HASH_SIZE; +} + + +/*------------------------------------------------- + hash_object - compute the hash for a given + object +-------------------------------------------------*/ + +INLINE objtype_entry *get_object_type(object_pool *pool, object_type type) +{ + objtype_entry *entry; + + for (entry = pool->typelist; entry != NULL; entry = entry->next) + if (entry->type == type) + return entry; + + return NULL; +} + + + +/*************************************************************************** + RESOURCE POOL MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + pool_alloc - allocates a new memory pool +-------------------------------------------------*/ + +object_pool *pool_alloc(void (*fail)(const char *message)) +{ + object_pool *pool; + + /* allocate memory for the pool itself */ + pool = malloc(sizeof(*pool)); + if (pool == NULL) + return NULL; + memset(pool, 0, sizeof(*pool)); + + /* register the built-in types */ + pool_type_register(pool, OBJTYPE_MEMORY, "Memory", memory_destruct); + + return pool; +} + + +/*------------------------------------------------- + pool_type_register - register a new pool + type +-------------------------------------------------*/ + +void pool_type_register(object_pool *pool, object_type type, const char *friendly, void (*destructor)(void *, size_t)) +{ + objtype_entry *newtype = get_object_type(pool, type); + + /* if the type doesn't already exist... */ + if (newtype == NULL) + { + /* allocate a new entry */ + newtype = malloc(sizeof(*newtype)); + if (newtype == NULL) + { + report_failure(pool, "Error adding new type %s\n", friendly); + return; + } + memset(newtype, 0, sizeof(*newtype)); + + /* link us in */ + newtype->next = pool->typelist; + pool->typelist = newtype; + } + + /* fill in the type information */ + newtype->type = type; + newtype->friendly = friendly; + newtype->destructor = destructor; +} + + +/*------------------------------------------------- + pool_clear - frees all contained memory blocks + in a pool +-------------------------------------------------*/ + +void pool_clear(object_pool *pool) +{ + int hashnum; + + /* iterate over hash buckets */ + for (hashnum = 0; hashnum < ARRAY_LENGTH(pool->hashtable); hashnum++) + { + object_entry *entry, *next; + + /* iterate over entries in this hash bucket and free them */ + for (entry = pool->hashtable[hashnum]; entry != NULL; entry = next) + { + /* remember the next entry */ + next = entry->next; + + /* call the destructor */ + (*entry->type->destructor)(entry->object, entry->size); + + /* add ourself to the free list */ + entry->next = pool->freelist; + pool->freelist = entry; + } + + /* reset the list we just walked */ + pool->hashtable[hashnum] = NULL; + } +} + + +/*------------------------------------------------- + pool_free - frees a memory pool and all + contained memory blocks +-------------------------------------------------*/ + +void pool_free(object_pool *pool) +{ + object_entry_block *block, *nextblock; + objtype_entry *type, *nexttype; + + /* clear the pool */ + pool_clear(pool); + + /* free all entry blocks */ + for (block = pool->blocklist; block != NULL; block = nextblock) + { + nextblock = block->next; + free(block); + } + + /* free all types */ + for (type = pool->typelist; type != NULL; type = nexttype) + { + nexttype = type->next; + free(type); + } + + /* free the pool itself */ + free(pool); +} + + + +/*************************************************************************** + OBJECT MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + pool_object_add_file_line - add an object to + the resource pool +-------------------------------------------------*/ + +void *pool_object_add_file_line(object_pool *pool, object_type _type, void *object, size_t size, const char *file, int line) +{ + objtype_entry *type = get_object_type(pool, _type); + int hashnum = hash_object(object); + object_entry *entry; + + /* if we have an invalid type, fail */ + if (type == NULL) + { + report_failure(pool, "pool_object_add (via %s:%d): Attempted to add object of unknown type", file, line, (int)size); + return object; + } + + /* if we get a NULL object, fail */ + if (object == NULL) + { + report_failure(pool, "pool_object_add (via %s:%d): Attempted to add a NULL object of size %d", file, line, (int)size); + return object; + } + + /* allocate a new entry */ + if (pool->freelist == NULL) + { + object_entry_block *block; + int entrynum; + + /* if we need a new block, allocate that now */ + block = malloc(sizeof(*block)); + if (block == NULL) + return NULL; + memset(block, 0, sizeof(*block)); + + /* hook us into the blocklist */ + block->next = pool->blocklist; + pool->blocklist = block; + + /* add all entries to the free list */ + for (entrynum = 0; entrynum < ARRAY_LENGTH(block->entry); entrynum++) + { + block->entry[entrynum].next = pool->freelist; + pool->freelist = &block->entry[entrynum]; + } + } + + /* remove the head of the freelist */ + entry = pool->freelist; + pool->freelist = entry->next; + + /* fill in the entry */ + entry->type = type; + entry->object = object; + entry->size = size; + entry->file = file; + entry->line = line; + + /* hook us into the list */ + entry->next = pool->hashtable[hashnum]; + pool->hashtable[hashnum] = entry; + return object; +} + + +/*------------------------------------------------- + pool_object_remove - remove an entry from a + pool, optionally calling its destructor +-------------------------------------------------*/ + +void *pool_object_remove(object_pool *pool, void *object, int destruct) +{ + int hashnum = hash_object(object); + object_entry **entryptr; + + /* find the object in question and remove it */ + for (entryptr = &pool->hashtable[hashnum]; *entryptr != NULL; entryptr = &(*entryptr)->next) + if ((*entryptr)->object == object) + { + object_entry *entry = *entryptr; + + /* call the destructor */ + if (destruct) + (*entry->type->destructor)(entry->object, entry->size); + + /* remove us from the list */ + *entryptr = entry->next; + + /* add us to the free list */ + entry->next = pool->freelist; + pool->freelist = entry; + break; + } + + return NULL; +} + + +/*------------------------------------------------- + pool_object_exists - return TRUE if an + object exists in the pool +-------------------------------------------------*/ + +int pool_object_exists(object_pool *pool, object_type type, void *object) +{ + int hashnum = hash_object(object); + object_entry *entry; + + /* find the object in question */ + for (entry = pool->hashtable[hashnum]; entry != NULL; entry = entry->next) + if (entry->object == object && (type == OBJTYPE_WILDCARD || entry->type->type == type)) + return TRUE; + + return FALSE; +} + + + +/*************************************************************************** + OBJECT ITERATORS +***************************************************************************/ + +/*------------------------------------------------- + pool_iterate_begin - begin iterating over + objects in an object pool +-------------------------------------------------*/ + +object_pool_iterator *pool_iterate_begin(object_pool *pool, object_type type) +{ + object_pool_iterator *iter; + + /* allocate the iterator */ + iter = malloc(sizeof(*iter)); + if (iter == NULL) + return NULL; + memset(iter, 0, sizeof(*iter)); + + /* fill it in */ + iter->pool = pool; + iter->type = type; + iter->hashnum = 0; + iter->last = NULL; + return iter; +} + + +/*------------------------------------------------- + pool_iterate_next - get the next object in the + object pool +-------------------------------------------------*/ + +int pool_iterate_next(object_pool_iterator *iter, void **objectptr, size_t *sizeptr, object_type *typeptr) +{ + /* advance from the last one */ + while (iter->hashnum < ARRAY_LENGTH(iter->pool->hashtable)) + { + /* if the last entry is non-NULL, advance */ + if (iter->last == NULL) + iter->last = iter->pool->hashtable[iter->hashnum++]; + else + iter->last = iter->last->next; + + /* stop when we get one */ + if (iter->last != NULL) + { + if (objectptr != NULL) + *objectptr = iter->last; + if (sizeptr != NULL) + *sizeptr = iter->last->size; + if (typeptr != NULL) + *typeptr = iter->last->type->type; + return TRUE; + } + } + + /* nothing left */ + return FALSE; +} + + +/*------------------------------------------------- + pool_iterate_end - finish iterating over + objects in an object pool +-------------------------------------------------*/ + +void pool_iterate_end(object_pool_iterator *iter) +{ + free(iter); +} + + + +/*************************************************************************** + MEMORY HELPERS +***************************************************************************/ + +/*------------------------------------------------- + pool_malloc_file_line - allocates memory in a + memory pool +-------------------------------------------------*/ + +void *pool_malloc_file_line(object_pool *pool, size_t size, const char *file, int line) +{ +#ifdef MALLOC_DEBUG + void *ptr = malloc_file_line(size, file, line); +#else + void *ptr = malloc(size); +#endif + return pool_object_add_file_line(pool, OBJTYPE_MEMORY, ptr, size, file, line); +} + + +/*------------------------------------------------- + pool_realloc_file_line - reallocs memory in a + memory pool +-------------------------------------------------*/ + +void *pool_realloc_file_line(object_pool *pool, void *ptr, size_t size, const char *file, int line) +{ + if (ptr != NULL) + pool_object_remove(pool, ptr, FALSE); +#ifdef MALLOC_DEBUG + ptr = realloc_file_line(ptr, size, file, line); +#else + ptr = realloc(ptr, size); +#endif + if (size != 0) + pool_object_add_file_line(pool, OBJTYPE_MEMORY, ptr, size, file, line); + return ptr; +} + + +/*------------------------------------------------- + pool_strdup_file_line - strdup in a memory + pool +-------------------------------------------------*/ + +char *pool_strdup_file_line(object_pool *pool, const char *str, const char *file, int line) +{ + char *ptr = pool_malloc_file_line(pool, strlen(str) + 1, file, line); + if (ptr != NULL) + strcpy(ptr, str); + return ptr; +} + + + +/*************************************************************************** + GENERIC DESTRUCTORS +***************************************************************************/ + +/*------------------------------------------------- + memory_destruct - generic destructor + that just does a free() on the object +-------------------------------------------------*/ + +static void memory_destruct(void *object, size_t size) +{ + free(object); +} + + + +/*************************************************************************** + INTERNAL FUNCTIONS +***************************************************************************/ + +/*------------------------------------------------- + report_failure - report a failure to the + failure callback +-------------------------------------------------*/ + +static void report_failure(object_pool *pool, const char *format, ...) +{ + /* only do the work if we have a callback */ + if (pool->fail != NULL) + { + char message[1024]; + va_list argptr; + + /* generate the final message text */ + va_start(argptr, format); + vsprintf(message, format, argptr); + va_end(argptr); + + /* call the callback with the message */ + (*pool->fail)(message); + } +} + + + +/*************************************************************************** + TESTING FUNCTIONS +***************************************************************************/ + +static int has_memory_error; + + +/*------------------------------------------------- + memory_error - report a memory error +-------------------------------------------------*/ + +static void memory_error(const char *message) +{ + printf("memory test failure: %s\n", message); + has_memory_error = TRUE; +} + + +/*------------------------------------------------- + test_memory_pools - unit tests for memory + pool behavior +-------------------------------------------------*/ + +int test_memory_pools(void) +{ + object_pool *pool; + void *ptrs[16]; + int i; + + has_memory_error = FALSE; + pool = pool_alloc(memory_error); + memset(ptrs, 0, sizeof(ptrs)); + + ptrs[0] = pool_malloc(pool, 50); + ptrs[1] = pool_malloc(pool, 100); + + ptrs[0] = pool_realloc(pool, ptrs[0], 150); + ptrs[1] = pool_realloc(pool, ptrs[1], 200); + + ptrs[2] = pool_malloc(pool, 250); + ptrs[3] = pool_malloc(pool, 300); + + ptrs[0] = pool_realloc(pool, ptrs[0], 350); + ptrs[1] = pool_realloc(pool, ptrs[1], 400); + + ptrs[2] = pool_realloc(pool, ptrs[2], 450); + ptrs[3] = pool_realloc(pool, ptrs[3], 500); + + ptrs[0] = pool_realloc(pool, ptrs[0], 0); + ptrs[1] = pool_realloc(pool, ptrs[1], 0); + + ptrs[2] = pool_realloc(pool, ptrs[2], 550); + ptrs[3] = pool_realloc(pool, ptrs[3], 600); + + /* some heavier stress tests */ + for (i = 0; i < 512; i++) + { + ptrs[i % ARRAY_LENGTH(ptrs)] = pool_realloc(pool, + ptrs[i % ARRAY_LENGTH(ptrs)], rand() % 1000); + } + + pool_free(pool); + return has_memory_error; +} diff --git a/src/lib/util/pool.h b/src/lib/util/pool.h new file mode 100644 index 00000000000..6d746c36de5 --- /dev/null +++ b/src/lib/util/pool.h @@ -0,0 +1,117 @@ +/*************************************************************************** + + pool.h + + Abstract object pool management + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#ifndef __POOL_H__ +#define __POOL_H__ + +#include "osdcore.h" + + +/*************************************************************************** + MACROS +***************************************************************************/ + +/* helper macros for memory pools that pass file/line number */ +#define pool_malloc(pool, size) pool_malloc_file_line((pool), (size), __FILE__, __LINE__) +#define pool_realloc(pool, ptr, size) pool_realloc_file_line((pool), (ptr), (size), __FILE__, __LINE__) +#define pool_strdup(pool, size) pool_strdup_file_line((pool), (size), __FILE__, __LINE__) + +/* macro to define a 4-character type for a pool */ +#define OBJECT_TYPE(a,b,c,d) (((a) << 24) | ((b) << 16) | ((c) << 8) | (d)) + +/* built-in pool types */ +#define OBJTYPE_WILDCARD (0) +#define OBJTYPE_MEMORY OBJECT_TYPE('m','e','m','o') + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* pool types are UINT32s */ +typedef UINT32 object_type; + +/* opaque type representing a pool of objects */ +typedef struct _object_pool object_pool; + +/* opaque type representing an iterator over pool objects */ +typedef struct _object_pool_iterator object_pool_iterator; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + + +/* ----- object pool management ----- */ + +/* allocate a new object pool */ +object_pool *pool_alloc(void (*fail)(const char *message)); + +/* register a new object type; returns TRUE if the type already existed and was overridden */ +void pool_type_register(object_pool *pool, object_type type, const char *friendly, void (*destructor)(void *, size_t)); + +/* free all allocated objects in a pool */ +void pool_clear(object_pool *pool); + +/* free an object pool, including all allocated objects */ +void pool_free(object_pool *pool); + + + +/* ----- object management ----- */ + +/* add an object to the pool, along with its filename/line number */ +void *pool_object_add_file_line(object_pool *pool, object_type type, void *object, size_t size, const char *file, int line); + +/* remove an object from the pool (optionally calling destructor) */ +void *pool_object_remove(object_pool *pool, void *object, int destruct); + +/* does an object exist in the pool? */ +int pool_object_exists(object_pool *pool, object_type type, void *object); + + + +/* ----- object iterators ----- */ + +/* begin iterating over objects in an object pool */ +object_pool_iterator *pool_iterate_begin(object_pool *pool, object_type type); + +/* get the next object in the object pool */ +int pool_iterate_next(object_pool_iterator *iter, void **objectptr, size_t *sizeptr, object_type *typeptr); + +/* finish iterating over objects in an object pool */ +void pool_iterate_end(object_pool_iterator *iter); + + + +/* ----- memory helpers ----- */ + +/* malloc memory and register it with the given pool */ +void *pool_malloc_file_line(object_pool *pool, size_t size, const char *file, int line); + +/* realloc memory and register it with the given pool */ +void *pool_realloc_file_line(object_pool *pool, void *ptr, size_t size, const char *file, int line); + +/* strdup memory and register it with the given pool */ +char *pool_strdup_file_line(object_pool *pool, const char *str, const char *file, int line); + + + +/* ----- miscellaneous ----- */ + +/* internal unit tests */ +int test_memory_pools(void); + + +#endif /* __POOL_H__ */ diff --git a/src/lib/util/sha1.c b/src/lib/util/sha1.c new file mode 100644 index 00000000000..bf376874816 --- /dev/null +++ b/src/lib/util/sha1.c @@ -0,0 +1,387 @@ +/* sha1.h + * + * The sha1 hash function. + */ + +/* nettle, low-level cryptographics library + * + * Copyright (C) 2001 Peter Gutmann, Andrew Kuchling, Niels Moeller + * + * The nettle library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The nettle library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the nettle library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "sha1.h" +//#include "macros.h" + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +static unsigned int READ_UINT32(const UINT8* data) +{ + return ((UINT32)data[0] << 24) | + ((UINT32)data[1] << 16) | + ((UINT32)data[2] << 8) | + ((UINT32)data[3]); +} + +static void WRITE_UINT32(unsigned char* data, UINT32 val) +{ + data[0] = (val >> 24) & 0xFF; + data[1] = (val >> 16) & 0xFF; + data[2] = (val >> 8) & 0xFF; + data[3] = (val >> 0) & 0xFF; +} + + +/* A block, treated as a sequence of 32-bit words. */ +#define SHA1_DATA_LENGTH 16 + +/* The SHA f()-functions. The f1 and f3 functions can be optimized to + save one boolean operation each - thanks to Rich Schroeppel, + rcs@cs.arizona.edu for discovering this */ + +/* #define f1(x,y,z) ( ( x & y ) | ( ~x & z ) ) Rounds 0-19 */ +#define f1(x,y,z) ( z ^ ( x & ( y ^ z ) ) ) /* Rounds 0-19 */ +#define f2(x,y,z) ( x ^ y ^ z ) /* Rounds 20-39 */ +/* #define f3(x,y,z) ( ( x & y ) | ( x & z ) | ( y & z ) ) Rounds 40-59 */ +#define f3(x,y,z) ( ( x & y ) | ( z & ( x | y ) ) ) /* Rounds 40-59 */ +#define f4(x,y,z) ( x ^ y ^ z ) /* Rounds 60-79 */ + +/* The SHA Mysterious Constants */ + +#define K1 0x5A827999L /* Rounds 0-19 */ +#define K2 0x6ED9EBA1L /* Rounds 20-39 */ +#define K3 0x8F1BBCDCL /* Rounds 40-59 */ +#define K4 0xCA62C1D6L /* Rounds 60-79 */ + +/* SHA initial values */ + +#define h0init 0x67452301L +#define h1init 0xEFCDAB89L +#define h2init 0x98BADCFEL +#define h3init 0x10325476L +#define h4init 0xC3D2E1F0L + +/* 32-bit rotate left - kludged with shifts */ +#ifdef _MSC_VER +#define ROTL(n,X) _lrotl(X, n) +#else +#define ROTL(n,X) ( ( (X) << (n) ) | ( (X) >> ( 32 - (n) ) ) ) +#endif + +/* The initial expanding function. The hash function is defined over an + 80-word expanded input array W, where the first 16 are copies of the input + data, and the remaining 64 are defined by + + W[ i ] = W[ i - 16 ] ^ W[ i - 14 ] ^ W[ i - 8 ] ^ W[ i - 3 ] + + This implementation generates these values on the fly in a circular + buffer - thanks to Colin Plumb, colin@nyx10.cs.du.edu for this + optimization. + + The updated SHA changes the expanding function by adding a rotate of 1 + bit. Thanks to Jim Gillogly, jim@rand.org, and an anonymous contributor + for this information */ + +#define expand(W,i) ( W[ i & 15 ] = \ + ROTL( 1, ( W[ i & 15 ] ^ W[ (i - 14) & 15 ] ^ \ + W[ (i - 8) & 15 ] ^ W[ (i - 3) & 15 ] ) ) ) + + +/* The prototype SHA sub-round. The fundamental sub-round is: + + a' = e + ROTL( 5, a ) + f( b, c, d ) + k + data; + b' = a; + c' = ROTL( 30, b ); + d' = c; + e' = d; + + but this is implemented by unrolling the loop 5 times and renaming the + variables ( e, a, b, c, d ) = ( a', b', c', d', e' ) each iteration. + This code is then replicated 20 times for each of the 4 functions, using + the next 20 values from the W[] array each time */ + +#define subRound(a, b, c, d, e, f, k, data) \ + ( e += ROTL( 5, a ) + f( b, c, d ) + k + data, b = ROTL( 30, b ) ) + +/* Initialize the SHA values */ + +void +sha1_init(struct sha1_ctx *ctx) +{ + /* Set the h-vars to their initial values */ + ctx->digest[ 0 ] = h0init; + ctx->digest[ 1 ] = h1init; + ctx->digest[ 2 ] = h2init; + ctx->digest[ 3 ] = h3init; + ctx->digest[ 4 ] = h4init; + + /* Initialize bit count */ + ctx->count_low = ctx->count_high = 0; + + /* Initialize buffer */ + ctx->index = 0; +} + +/* Perform the SHA transformation. Note that this code, like MD5, seems to + break some optimizing compilers due to the complexity of the expressions + and the size of the basic block. It may be necessary to split it into + sections, e.g. based on the four subrounds + + Note that this function destroys the data area */ + +static void +sha1_transform(UINT32 *state, UINT32 *data) +{ + UINT32 A, B, C, D, E; /* Local vars */ + + /* Set up first buffer and local data buffer */ + A = state[0]; + B = state[1]; + C = state[2]; + D = state[3]; + E = state[4]; + + /* Heavy mangling, in 4 sub-rounds of 20 interations each. */ + subRound( A, B, C, D, E, f1, K1, data[ 0] ); + subRound( E, A, B, C, D, f1, K1, data[ 1] ); + subRound( D, E, A, B, C, f1, K1, data[ 2] ); + subRound( C, D, E, A, B, f1, K1, data[ 3] ); + subRound( B, C, D, E, A, f1, K1, data[ 4] ); + subRound( A, B, C, D, E, f1, K1, data[ 5] ); + subRound( E, A, B, C, D, f1, K1, data[ 6] ); + subRound( D, E, A, B, C, f1, K1, data[ 7] ); + subRound( C, D, E, A, B, f1, K1, data[ 8] ); + subRound( B, C, D, E, A, f1, K1, data[ 9] ); + subRound( A, B, C, D, E, f1, K1, data[10] ); + subRound( E, A, B, C, D, f1, K1, data[11] ); + subRound( D, E, A, B, C, f1, K1, data[12] ); + subRound( C, D, E, A, B, f1, K1, data[13] ); + subRound( B, C, D, E, A, f1, K1, data[14] ); + subRound( A, B, C, D, E, f1, K1, data[15] ); + subRound( E, A, B, C, D, f1, K1, expand( data, 16 ) ); + subRound( D, E, A, B, C, f1, K1, expand( data, 17 ) ); + subRound( C, D, E, A, B, f1, K1, expand( data, 18 ) ); + subRound( B, C, D, E, A, f1, K1, expand( data, 19 ) ); + + subRound( A, B, C, D, E, f2, K2, expand( data, 20 ) ); + subRound( E, A, B, C, D, f2, K2, expand( data, 21 ) ); + subRound( D, E, A, B, C, f2, K2, expand( data, 22 ) ); + subRound( C, D, E, A, B, f2, K2, expand( data, 23 ) ); + subRound( B, C, D, E, A, f2, K2, expand( data, 24 ) ); + subRound( A, B, C, D, E, f2, K2, expand( data, 25 ) ); + subRound( E, A, B, C, D, f2, K2, expand( data, 26 ) ); + subRound( D, E, A, B, C, f2, K2, expand( data, 27 ) ); + subRound( C, D, E, A, B, f2, K2, expand( data, 28 ) ); + subRound( B, C, D, E, A, f2, K2, expand( data, 29 ) ); + subRound( A, B, C, D, E, f2, K2, expand( data, 30 ) ); + subRound( E, A, B, C, D, f2, K2, expand( data, 31 ) ); + subRound( D, E, A, B, C, f2, K2, expand( data, 32 ) ); + subRound( C, D, E, A, B, f2, K2, expand( data, 33 ) ); + subRound( B, C, D, E, A, f2, K2, expand( data, 34 ) ); + subRound( A, B, C, D, E, f2, K2, expand( data, 35 ) ); + subRound( E, A, B, C, D, f2, K2, expand( data, 36 ) ); + subRound( D, E, A, B, C, f2, K2, expand( data, 37 ) ); + subRound( C, D, E, A, B, f2, K2, expand( data, 38 ) ); + subRound( B, C, D, E, A, f2, K2, expand( data, 39 ) ); + + subRound( A, B, C, D, E, f3, K3, expand( data, 40 ) ); + subRound( E, A, B, C, D, f3, K3, expand( data, 41 ) ); + subRound( D, E, A, B, C, f3, K3, expand( data, 42 ) ); + subRound( C, D, E, A, B, f3, K3, expand( data, 43 ) ); + subRound( B, C, D, E, A, f3, K3, expand( data, 44 ) ); + subRound( A, B, C, D, E, f3, K3, expand( data, 45 ) ); + subRound( E, A, B, C, D, f3, K3, expand( data, 46 ) ); + subRound( D, E, A, B, C, f3, K3, expand( data, 47 ) ); + subRound( C, D, E, A, B, f3, K3, expand( data, 48 ) ); + subRound( B, C, D, E, A, f3, K3, expand( data, 49 ) ); + subRound( A, B, C, D, E, f3, K3, expand( data, 50 ) ); + subRound( E, A, B, C, D, f3, K3, expand( data, 51 ) ); + subRound( D, E, A, B, C, f3, K3, expand( data, 52 ) ); + subRound( C, D, E, A, B, f3, K3, expand( data, 53 ) ); + subRound( B, C, D, E, A, f3, K3, expand( data, 54 ) ); + subRound( A, B, C, D, E, f3, K3, expand( data, 55 ) ); + subRound( E, A, B, C, D, f3, K3, expand( data, 56 ) ); + subRound( D, E, A, B, C, f3, K3, expand( data, 57 ) ); + subRound( C, D, E, A, B, f3, K3, expand( data, 58 ) ); + subRound( B, C, D, E, A, f3, K3, expand( data, 59 ) ); + + subRound( A, B, C, D, E, f4, K4, expand( data, 60 ) ); + subRound( E, A, B, C, D, f4, K4, expand( data, 61 ) ); + subRound( D, E, A, B, C, f4, K4, expand( data, 62 ) ); + subRound( C, D, E, A, B, f4, K4, expand( data, 63 ) ); + subRound( B, C, D, E, A, f4, K4, expand( data, 64 ) ); + subRound( A, B, C, D, E, f4, K4, expand( data, 65 ) ); + subRound( E, A, B, C, D, f4, K4, expand( data, 66 ) ); + subRound( D, E, A, B, C, f4, K4, expand( data, 67 ) ); + subRound( C, D, E, A, B, f4, K4, expand( data, 68 ) ); + subRound( B, C, D, E, A, f4, K4, expand( data, 69 ) ); + subRound( A, B, C, D, E, f4, K4, expand( data, 70 ) ); + subRound( E, A, B, C, D, f4, K4, expand( data, 71 ) ); + subRound( D, E, A, B, C, f4, K4, expand( data, 72 ) ); + subRound( C, D, E, A, B, f4, K4, expand( data, 73 ) ); + subRound( B, C, D, E, A, f4, K4, expand( data, 74 ) ); + subRound( A, B, C, D, E, f4, K4, expand( data, 75 ) ); + subRound( E, A, B, C, D, f4, K4, expand( data, 76 ) ); + subRound( D, E, A, B, C, f4, K4, expand( data, 77 ) ); + subRound( C, D, E, A, B, f4, K4, expand( data, 78 ) ); + subRound( B, C, D, E, A, f4, K4, expand( data, 79 ) ); + + /* Build message digest */ + state[0] += A; + state[1] += B; + state[2] += C; + state[3] += D; + state[4] += E; +} + +static void +sha1_block(struct sha1_ctx *ctx, const UINT8 *block) +{ + UINT32 data[SHA1_DATA_LENGTH]; + int i; + + /* Update block count */ + if (!++ctx->count_low) + ++ctx->count_high; + + /* Endian independent conversion */ + for (i = 0; i<SHA1_DATA_LENGTH; i++, block += 4) + data[i] = READ_UINT32(block); + + sha1_transform(ctx->digest, data); +} + +void +sha1_update(struct sha1_ctx *ctx, + unsigned length, const UINT8 *buffer) +{ + if (ctx->index) + { /* Try to fill partial block */ + unsigned left = SHA1_DATA_SIZE - ctx->index; + if (length < left) + { + memcpy(ctx->block + ctx->index, buffer, length); + ctx->index += length; + return; /* Finished */ + } + else + { + memcpy(ctx->block + ctx->index, buffer, left); + sha1_block(ctx, ctx->block); + buffer += left; + length -= left; + } + } + while (length >= SHA1_DATA_SIZE) + { + sha1_block(ctx, buffer); + buffer += SHA1_DATA_SIZE; + length -= SHA1_DATA_SIZE; + } + if ((ctx->index = length)) /* This assignment is intended */ + /* Buffer leftovers */ + memcpy(ctx->block, buffer, length); +} + +/* Final wrapup - pad to SHA1_DATA_SIZE-byte boundary with the bit pattern + 1 0* (64-bit count of bits processed, MSB-first) */ + +void +sha1_final(struct sha1_ctx *ctx) +{ + UINT32 data[SHA1_DATA_LENGTH]; + int i; + int words; + + i = ctx->index; + + /* Set the first char of padding to 0x80. This is safe since there is + always at least one byte free */ + + assert(i < SHA1_DATA_SIZE); + ctx->block[i++] = 0x80; + + /* Fill rest of word */ + for( ; i & 3; i++) + ctx->block[i] = 0; + + /* i is now a multiple of the word size 4 */ + words = i >> 2; + for (i = 0; i < words; i++) + data[i] = READ_UINT32(ctx->block + 4*i); + + if (words > (SHA1_DATA_LENGTH-2)) + { /* No room for length in this block. Process it and + * pad with another one */ + for (i = words ; i < SHA1_DATA_LENGTH; i++) + data[i] = 0; + sha1_transform(ctx->digest, data); + for (i = 0; i < (SHA1_DATA_LENGTH-2); i++) + data[i] = 0; + } + else + for (i = words ; i < SHA1_DATA_LENGTH - 2; i++) + data[i] = 0; + + /* There are 512 = 2^9 bits in one block */ + data[SHA1_DATA_LENGTH-2] = (ctx->count_high << 9) | (ctx->count_low >> 23); + data[SHA1_DATA_LENGTH-1] = (ctx->count_low << 9) | (ctx->index << 3); + sha1_transform(ctx->digest, data); +} + +void +sha1_digest(const struct sha1_ctx *ctx, + unsigned length, + UINT8 *digest) +{ + unsigned i; + unsigned words; + unsigned leftover; + + assert(length <= SHA1_DIGEST_SIZE); + + words = length / 4; + leftover = length % 4; + + for (i = 0; i < words; i++, digest += 4) + WRITE_UINT32(digest, ctx->digest[i]); + + if (leftover) + { + UINT32 word; + unsigned j = leftover; + + assert(i < _SHA1_DIGEST_LENGTH); + + word = ctx->digest[i]; + + switch (leftover) + { + default: + /* this is just here to keep the compiler happy; it can never happen */ + case 3: + digest[--j] = (word >> 8) & 0xff; + /* Fall through */ + case 2: + digest[--j] = (word >> 16) & 0xff; + /* Fall through */ + case 1: + digest[--j] = (word >> 24) & 0xff; + } + } +} diff --git a/src/lib/util/sha1.h b/src/lib/util/sha1.h new file mode 100644 index 00000000000..8f9d9cdd835 --- /dev/null +++ b/src/lib/util/sha1.h @@ -0,0 +1,61 @@ +/* sha1.h + * + * The sha1 hash function. + */ + +/* nettle, low-level cryptographics library + * + * Copyright (C) 2001 Niels Moeller + * + * The nettle library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The nettle library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the nettle library; see the file COPYING.LIB. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef NETTLE_SHA1_H_INCLUDED +#define NETTLE_SHA1_H_INCLUDED + +#include "osdcore.h" + +#define SHA1_DIGEST_SIZE 20 +#define SHA1_DATA_SIZE 64 + +/* Digest is kept internally as 4 32-bit words. */ +#define _SHA1_DIGEST_LENGTH 5 + +struct sha1_ctx +{ + UINT32 digest[_SHA1_DIGEST_LENGTH]; /* Message digest */ + UINT32 count_low, count_high; /* 64-bit block count */ + UINT8 block[SHA1_DATA_SIZE]; /* SHA1 data buffer */ + unsigned int index; /* index into buffer */ +}; + +void +sha1_init(struct sha1_ctx *ctx); + +void +sha1_update(struct sha1_ctx *ctx, + unsigned length, + const UINT8 *data); + +void +sha1_final(struct sha1_ctx *ctx); + +void +sha1_digest(const struct sha1_ctx *ctx, + unsigned length, + UINT8 *digest); + +#endif /* NETTLE_SHA1_H_INCLUDED */ diff --git a/src/lib/util/unicode.c b/src/lib/util/unicode.c new file mode 100644 index 00000000000..33dac112fa7 --- /dev/null +++ b/src/lib/util/unicode.c @@ -0,0 +1,346 @@ +/********************************************************************* + + unicode.c + + Unicode related functions + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +*********************************************************************/ + +#include "unicode.h" + + +/*------------------------------------------------- + uchar_isvalid - return true if a given + character is a legitimate unicode character +-------------------------------------------------*/ + +int uchar_isvalid(unicode_char uchar) +{ + return (uchar < 0x110000) && !((uchar >= 0xd800) && (uchar <= 0xdfff)); +} + + +/*------------------------------------------------- + uchar_from_utf8 - convert a UTF-8 sequence + into a unicode character +-------------------------------------------------*/ + +int uchar_from_utf8(unicode_char *uchar, const char *utf8char, size_t count) +{ + unicode_char c, minchar; + int auxlen, i; + char auxchar; + + /* validate parameters */ + if (utf8char == NULL || count == 0) + return 0; + + /* start with the first byte */ + c = (unsigned char) *utf8char; + count--; + utf8char++; + + /* based on that, determine how many additional bytes we need */ + if (c < 0x80) + { + /* unicode char 0x00000000 - 0x0000007F */ + c &= 0x7f; + auxlen = 0; + minchar = 0x00000000; + } + else if (c >= 0xc0 && c < 0xe0) + { + /* unicode char 0x00000080 - 0x000007FF */ + c &= 0x1f; + auxlen = 1; + minchar = 0x00000080; + } + else if (c >= 0xe0 && c < 0xf0) + { + /* unicode char 0x00000800 - 0x0000FFFF */ + c &= 0x0f; + auxlen = 2; + minchar = 0x00000800; + } + else if (c >= 0xf0 && c < 0xf8) + { + /* unicode char 0x00010000 - 0x001FFFFF */ + c &= 0x07; + auxlen = 3; + minchar = 0x00010000; + } + else if (c >= 0xf8 && c < 0xfc) + { + /* unicode char 0x00200000 - 0x03FFFFFF */ + c &= 0x03; + auxlen = 4; + minchar = 0x00200000; + } + else if (c >= 0xfc && c < 0xfe) + { + /* unicode char 0x04000000 - 0x7FFFFFFF */ + c &= 0x01; + auxlen = 5; + minchar = 0x04000000; + } + else + { + /* invalid */ + return -1; + } + + /* exceeds the count? */ + if (auxlen > count) + return -1; + + /* we now know how long the char is, now compute it */ + for (i = 0; i < auxlen; i++) + { + auxchar = utf8char[i]; + + /* all auxillary chars must be between 0x80-0xbf */ + if ((auxchar & 0xc0) != 0x80) + return -1; + + c = c << 6; + c |= auxchar & 0x3f; + } + + /* make sure that this char is above the minimum */ + if (c < minchar) + return -1; + + *uchar = c; + return auxlen + 1; +} + + +/*------------------------------------------------- + uchar_from_utf16 - convert a UTF-16 sequence + into a unicode character +-------------------------------------------------*/ + +int uchar_from_utf16(unicode_char *uchar, const utf16_char *utf16char, size_t count) +{ + int rc = -1; + + /* validate parameters */ + if (utf16char == NULL || count == 0) + return 0; + + /* handle the two-byte case */ + if (utf16char[0] >= 0xd800 && utf16char[0] <= 0xdbff) + { + if (count > 1 && utf16char[1] >= 0xdc00 && utf16char[1] <= 0xdfff) + { + *uchar = 0x10000 + ((utf16char[0] & 0x3ff) * 0x400) + (utf16char[1] & 0x3ff); + rc = 2; + } + } + + /* handle the one-byte case */ + else if (utf16char[0] < 0xdc00 || utf16char[0] > 0xdfff) + { + *uchar = utf16char[0]; + rc = 1; + } + + return rc; +} + + +/*------------------------------------------------- + uchar_from_utf16f - convert a UTF-16 sequence + into a unicode character from a flipped + byte order +-------------------------------------------------*/ + +int uchar_from_utf16f(unicode_char *uchar, const utf16_char *utf16char, size_t count) +{ + utf16_char buf[2] = {0}; + if (count > 0) + buf[0] = FLIPENDIAN_INT16(utf16char[0]); + if (count > 1) + buf[1] = FLIPENDIAN_INT16(utf16char[1]); + return uchar_from_utf16(uchar, buf, count); +} + + +/*------------------------------------------------- + utf8_from_uchar - convert a unicode character + into a UTF-8 sequence +-------------------------------------------------*/ + +int utf8_from_uchar(char *utf8string, size_t count, unicode_char uchar) +{ + int rc = 0; + + /* error on invalid characters */ + if (!uchar_isvalid(uchar)) + return -1; + + /* based on the value, output the appropriate number of bytes */ + if (uchar < 0x80) + { + /* unicode char 0x00000000 - 0x0000007F */ + if (count < 1) + return -1; + utf8string[rc++] = (char) uchar; + } + else if (uchar < 0x800) + { + /* unicode char 0x00000080 - 0x000007FF */ + if (count < 2) + return -1; + utf8string[rc++] = ((char) (uchar >> 6)) | 0xC0; + utf8string[rc++] = ((char) ((uchar >> 0) & 0x3F)) | 0x80; + } + else if (uchar < 0x10000) + { + /* unicode char 0x00000800 - 0x0000FFFF */ + if (count < 3) + return -1; + utf8string[rc++] = ((char) (uchar >> 12)) | 0xE0; + utf8string[rc++] = ((char) ((uchar >> 6) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 0) & 0x3F)) | 0x80; + } + else if (uchar < 0x00200000) + { + /* unicode char 0x00010000 - 0x001FFFFF */ + if (count < 4) + return -1; + utf8string[rc++] = ((char) (uchar >> 18)) | 0xF0; + utf8string[rc++] = ((char) ((uchar >> 12) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 6) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 0) & 0x3F)) | 0x80; + } + else if (uchar < 0x04000000) + { + /* unicode char 0x00200000 - 0x03FFFFFF */ + if (count < 5) + return -1; + utf8string[rc++] = ((char) (uchar >> 24)) | 0xF8; + utf8string[rc++] = ((char) ((uchar >> 18) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 12) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 6) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 0) & 0x3F)) | 0x80; + } + else if (uchar < 0x80000000) + { + /* unicode char 0x04000000 - 0x7FFFFFFF */ + if (count < 6) + return -1; + utf8string[rc++] = ((char) (uchar >> 30)) | 0xFC; + utf8string[rc++] = ((char) ((uchar >> 24) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 18) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 12) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 6) & 0x3F)) | 0x80; + utf8string[rc++] = ((char) ((uchar >> 0) & 0x3F)) | 0x80; + } + else + rc = -1; + + return rc; +} + + +/*------------------------------------------------- + utf16_from_uchar - convert a unicode character + into a UTF-16 sequence +-------------------------------------------------*/ + +int utf16_from_uchar(utf16_char *utf16string, size_t count, unicode_char uchar) +{ + int rc; + + /* error on invalid characters */ + if (!uchar_isvalid(uchar)) + return -1; + + /* single word case */ + if (uchar < 0x10000) + { + if (count < 1) + return -1; + utf16string[0] = (utf16_char) uchar; + rc = 1; + } + + /* double word case */ + else if (uchar < 0x100000) + { + if (count < 2) + return -1; + utf16string[0] = ((uchar >> 10) & 0x03ff) | 0xd800; + utf16string[1] = ((uchar >> 0) & 0x03ff) | 0xdc00; + rc = 2; + } + else + return -1; + return rc; +} + + +/*------------------------------------------------- + utf16_from_uchar - convert a unicode character + into a UTF-16 sequence with flipped endianness +-------------------------------------------------*/ + +int utf16f_from_uchar(utf16_char *utf16string, size_t count, unicode_char uchar) +{ + int rc; + utf16_char buf[2] = { 0, 0 }; + + rc = utf16_from_uchar(buf, count, uchar); + + if (rc >= 1) + utf16string[0] = FLIPENDIAN_INT16(buf[0]); + if (rc >= 2) + utf16string[1] = FLIPENDIAN_INT16(buf[1]); + return rc; +} + + +/*------------------------------------------------- + utf8_previous_char - return a pointer to the + previous character in a string +-------------------------------------------------*/ + +const char *utf8_previous_char(const char *utf8string) +{ + while ((*--utf8string & 0xc0) == 0x80) + ; + return utf8string; +} + + +/*------------------------------------------------- + utf8_is_valid_string - return true if the + given string is a properly formed sequence of + UTF-8 characters +-------------------------------------------------*/ + +int utf8_is_valid_string(const char *utf8string) +{ + int remaining_length = strlen(utf8string); + + while (*utf8string != 0) + { + unicode_char uchar = 0; + int charlen; + + /* extract the current character and verify it */ + charlen = uchar_from_utf8(&uchar, utf8string, remaining_length); + if (charlen <= 0 || uchar == 0 || !uchar_isvalid(uchar)) + return FALSE; + + /* advance */ + utf8string += charlen; + remaining_length -= charlen; + } + + return TRUE; +} diff --git a/src/lib/util/unicode.h b/src/lib/util/unicode.h new file mode 100644 index 00000000000..19eb3eab9e0 --- /dev/null +++ b/src/lib/util/unicode.h @@ -0,0 +1,92 @@ +/********************************************************************* + + unicode.h + + Unicode related functions + + This code is for converting between UTF-8, UTF-16, and 32-bit + Unicode strings. These functions roughly parallel C runtime + library functions like mbtowc() and similar functions, but are + specific for these Unicode encodings. Specifically, there are + functions that convert UTF-8 and UTF-16 char clusters to and from + singular 32-bit Unicode chars. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +*********************************************************************/ + +#pragma once + +#ifndef UNICODE_H +#define UNICODE_H + +#include <stdlib.h> +#include "osdcore.h" + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* these defines specify the maximum size of different types of Unicode + * character encodings */ +#define UTF8_CHAR_MAX 6 +#define UTF16_CHAR_MAX 2 + +/* these are UTF-8 encoded strings for common characters */ +#define UTF8_NBSP "\xc2\xa0" /* non-breaking space */ +#define UTF8_MULTIPLY "\xc3\x97" /* multiplication symbol */ + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef UINT16 utf16_char; +typedef UINT32 unicode_char; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +/* tests to see if a unicode char is a valid code point */ +int uchar_isvalid(unicode_char uchar); + +/* converting strings to 32-bit Unicode chars */ +int uchar_from_utf8(unicode_char *uchar, const char *utf8char, size_t count); +int uchar_from_utf16(unicode_char *uchar, const utf16_char *utf16char, size_t count); +int uchar_from_utf16f(unicode_char *uchar, const utf16_char *utf16char, size_t count); + +/* converting 32-bit Unicode chars to strings */ +int utf8_from_uchar(char *utf8string, size_t count, unicode_char uchar); +int utf16_from_uchar(utf16_char *utf16string, size_t count, unicode_char uchar); +int utf16f_from_uchar(utf16_char *utf16string, size_t count, unicode_char uchar); + +/* misc UTF-8 helpers */ +const char *utf8_previous_char(const char *utf8string); +int utf8_is_valid_string(const char *utf8string); + + + +/*************************************************************************** + MACROS +***************************************************************************/ + +#ifdef LSB_FIRST +#define uchar_from_utf16be uchar_from_utf16f +#define uchar_from_utf16le uchar_from_utf16 +#define utf16be_from_uchar utf16f_from_uchar +#define utf16le_from_uchar utf16_from_uchar +#else +#define uchar_from_utf16be uchar_from_utf16 +#define uchar_from_utf16le uchar_from_utf16f +#define utf16be_from_uchar utf16_from_uchar +#define utf16le_from_uchar utf16f_from_uchar +#endif + +#endif /* UNICODE_H */ diff --git a/src/lib/util/unzip.c b/src/lib/util/unzip.c new file mode 100644 index 00000000000..9b95321ded7 --- /dev/null +++ b/src/lib/util/unzip.c @@ -0,0 +1,616 @@ +/*************************************************************************** + + unzip.c + + Functions to manipulate data within ZIP files. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include "osdcore.h" +#include "unzip.h" + +#include <ctype.h> +#include <zlib.h> + + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +/* number of open files to cache */ +#define ZIP_CACHE_SIZE 8 + +/* offsets in end of central directory structure */ +#define ZIPESIG 0x00 +#define ZIPEDSK 0x04 +#define ZIPECEN 0x06 +#define ZIPENUM 0x08 +#define ZIPECENN 0x0a +#define ZIPECSZ 0x0c +#define ZIPEOFST 0x10 +#define ZIPECOML 0x14 +#define ZIPECOM 0x16 + +/* offsets in central directory entry structure */ +#define ZIPCENSIG 0x00 +#define ZIPCVER 0x04 +#define ZIPCOS 0x05 +#define ZIPCVXT 0x06 +#define ZIPCEXOS 0x07 +#define ZIPCFLG 0x08 +#define ZIPCMTHD 0x0a +#define ZIPCTIM 0x0c +#define ZIPCDAT 0x0e +#define ZIPCCRC 0x10 +#define ZIPCSIZ 0x14 +#define ZIPCUNC 0x18 +#define ZIPCFNL 0x1c +#define ZIPCXTL 0x1e +#define ZIPCCML 0x20 +#define ZIPDSK 0x22 +#define ZIPINT 0x24 +#define ZIPEXT 0x26 +#define ZIPOFST 0x2a +#define ZIPCFN 0x2e + +/* offsets in local file header structure */ +#define ZIPLOCSIG 0x00 +#define ZIPVER 0x04 +#define ZIPGENFLG 0x06 +#define ZIPMTHD 0x08 +#define ZIPTIME 0x0a +#define ZIPDATE 0x0c +#define ZIPCRC 0x0e +#define ZIPSIZE 0x12 +#define ZIPUNCMP 0x16 +#define ZIPFNLN 0x1a +#define ZIPXTRALN 0x1c +#define ZIPNAME 0x1e + + + +/*************************************************************************** + INLINE FUNCTIONS +***************************************************************************/ + +INLINE UINT16 read_word(UINT8 *buf) +{ + return (buf[1] << 8) | buf[0]; +} + +INLINE UINT32 read_dword(UINT8 *buf) +{ + return (buf[3] << 24) | (buf[2] << 16) | (buf[1] << 8) | buf[0]; +} + + + +/*************************************************************************** + GLOBAL VARIABLES +***************************************************************************/ + +static zip_file *zip_cache[ZIP_CACHE_SIZE]; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + +/* cache management */ +static void free_zip_file(zip_file *zip); + +/* ZIP file parsing */ +static zip_error read_ecd(zip_file *zip); +static zip_error get_compressed_data_offset(zip_file *zip, UINT64 *offset); + +/* decompression interfaces */ +static zip_error decompress_data_type_0(zip_file *zip, UINT64 offset, void *buffer, UINT32 length); +static zip_error decompress_data_type_8(zip_file *zip, UINT64 offset, void *buffer, UINT32 length); + + + +/*************************************************************************** + ZIP FILE ACCESS +***************************************************************************/ + +/*------------------------------------------------- + zip_file_open - opens a ZIP file for reading +-------------------------------------------------*/ + +zip_error zip_file_open(const char *filename, zip_file **zip) +{ + zip_error ziperr = ZIPERR_NONE; + file_error filerr; + UINT32 read_length; + zip_file *newzip; + char *string; + int cachenum; + + /* ensure we start with a NULL result */ + *zip = NULL; + + /* see if we are in the cache, and reopen if so */ + for (cachenum = 0; cachenum < ARRAY_LENGTH(zip_cache); cachenum++) + { + zip_file *cached = zip_cache[cachenum]; + + /* if we have a valid entry and it matches our filename, use it and remove from the cache */ + if (cached != NULL && cached->filename != NULL && strcmp(filename, cached->filename) == 0) + { + *zip = cached; + zip_cache[cachenum] = NULL; + return ZIPERR_NONE; + } + } + + /* allocate memory for the zip_file structure */ + newzip = malloc(sizeof(*newzip)); + if (newzip == NULL) + return ZIPERR_OUT_OF_MEMORY; + memset(newzip, 0, sizeof(*newzip)); + + /* open the file */ + filerr = osd_open(filename, OPEN_FLAG_READ, &newzip->file, &newzip->length); + if (filerr != FILERR_NONE) + { + ziperr = ZIPERR_FILE_ERROR; + goto error; + } + + /* read ecd data */ + ziperr = read_ecd(newzip); + if (ziperr != ZIPERR_NONE) + goto error; + + /* verify that we can work with this zipfile (no disk spanning allowed) */ + if (newzip->ecd.disk_number != newzip->ecd.cd_start_disk_number || newzip->ecd.cd_disk_entries != newzip->ecd.cd_total_entries) + { + ziperr = ZIPERR_UNSUPPORTED; + goto error; + } + + /* allocate memory for the central directory */ + newzip->cd = malloc(newzip->ecd.cd_size + 1); + if (newzip->cd == NULL) + { + ziperr = ZIPERR_OUT_OF_MEMORY; + goto error; + } + + /* read the central directory */ + filerr = osd_read(newzip->file, newzip->cd, newzip->ecd.cd_start_disk_offset, newzip->ecd.cd_size, &read_length); + if (filerr != FILERR_NONE || read_length != newzip->ecd.cd_size) + { + ziperr = (filerr == FILERR_NONE) ? ZIPERR_FILE_TRUNCATED : ZIPERR_FILE_ERROR; + goto error; + } + + /* make a copy of the filename for caching purposes */ + string = malloc(strlen(filename) + 1); + if (string == NULL) + { + ziperr = ZIPERR_OUT_OF_MEMORY; + goto error; + } + strcpy(string, filename); + newzip->filename = string; + *zip = newzip; + return ZIPERR_NONE; + +error: + free_zip_file(newzip); + return ziperr; +} + + +/*------------------------------------------------- + zip_file_close - close a ZIP file and add it + to the cache +-------------------------------------------------*/ + +void zip_file_close(zip_file *zip) +{ + int cachenum; + + /* close the open files */ + if (zip->file != NULL) + osd_close(zip->file); + zip->file = NULL; + + /* find the first NULL entry in the cache */ + for (cachenum = 0; cachenum < ARRAY_LENGTH(zip_cache); cachenum++) + if (zip_cache[cachenum] == NULL) + break; + + /* if no room left in the cache, free the bottommost entry */ + if (cachenum == ARRAY_LENGTH(zip_cache)) + free_zip_file(zip_cache[--cachenum]); + + /* move everyone else down and place us at the top */ + if (cachenum != 0) + memmove(&zip_cache[1], &zip_cache[0], cachenum * sizeof(zip_cache[0])); + zip_cache[0] = zip; +} + + +/*------------------------------------------------- + zip_file_cache_clear - clear the ZIP file + cache and free all memory +-------------------------------------------------*/ + +void zip_file_cache_clear(void) +{ + int cachenum; + + /* clear call cache entries */ + for (cachenum = 0; cachenum < ARRAY_LENGTH(zip_cache); cachenum++) + if (zip_cache[cachenum] != NULL) + { + free_zip_file(zip_cache[cachenum]); + zip_cache[cachenum] = NULL; + } +} + + + +/*************************************************************************** + CONTAINED FILE ACCESS +***************************************************************************/ + +/*------------------------------------------------- + zip_file_first_entry - return the first entry + in the ZIP +-------------------------------------------------*/ + +const zip_file_header *zip_file_first_file(zip_file *zip) +{ + /* reset the position and go from there */ + zip->cd_pos = 0; + return zip_file_next_file(zip); +} + + +/*------------------------------------------------- + zip_file_next_entry - return the next entry + in the ZIP +-------------------------------------------------*/ + +const zip_file_header *zip_file_next_file(zip_file *zip) +{ + /* fix up any modified data */ + if (zip->header.raw != NULL) + { + zip->header.raw[ZIPCFN + zip->header.filename_length] = zip->header.saved; + zip->header.raw = NULL; + } + + /* if we're at or past the end, we're done */ + if (zip->cd_pos >= zip->ecd.cd_size) + return NULL; + + /* extract file header info */ + zip->header.raw = zip->cd + zip->cd_pos; + zip->header.rawlength = ZIPCFN; + zip->header.signature = read_dword(zip->header.raw + ZIPCENSIG); + zip->header.version_created = read_word (zip->header.raw + ZIPCVER); + zip->header.version_needed = read_word (zip->header.raw + ZIPCVXT); + zip->header.bit_flag = read_word (zip->header.raw + ZIPCFLG); + zip->header.compression = read_word (zip->header.raw + ZIPCMTHD); + zip->header.file_time = read_word (zip->header.raw + ZIPCTIM); + zip->header.file_date = read_word (zip->header.raw + ZIPCDAT); + zip->header.crc = read_dword(zip->header.raw + ZIPCCRC); + zip->header.compressed_length = read_dword(zip->header.raw + ZIPCSIZ); + zip->header.uncompressed_length = read_dword(zip->header.raw + ZIPCUNC); + zip->header.filename_length = read_word (zip->header.raw + ZIPCFNL); + zip->header.extra_field_length = read_word (zip->header.raw + ZIPCXTL); + zip->header.file_comment_length = read_word (zip->header.raw + ZIPCCML); + zip->header.start_disk_number = read_word (zip->header.raw + ZIPDSK); + zip->header.internal_attributes = read_word (zip->header.raw + ZIPINT); + zip->header.external_attributes = read_dword(zip->header.raw + ZIPEXT); + zip->header.local_header_offset = read_dword(zip->header.raw + ZIPOFST); + zip->header.filename = (char *)zip->header.raw + ZIPCFN; + + /* make sure we have enough data */ + zip->header.rawlength += zip->header.filename_length; + zip->header.rawlength += zip->header.extra_field_length; + zip->header.rawlength += zip->header.file_comment_length; + if (zip->cd_pos + zip->header.rawlength > zip->ecd.cd_size) + return NULL; + + /* NULL terminate the filename */ + zip->header.saved = zip->header.raw[ZIPCFN + zip->header.filename_length]; + zip->header.raw[ZIPCFN + zip->header.filename_length] = 0; + + /* advance the position */ + zip->cd_pos += zip->header.rawlength; + return &zip->header; +} + + +/*------------------------------------------------- + zip_file_decompress - decompress a file + from a ZIP into the target buffer +-------------------------------------------------*/ + +zip_error zip_file_decompress(zip_file *zip, void *buffer, UINT32 length) +{ + zip_error ziperr; + UINT64 offset; + + /* if we don't have enough buffer, error */ + if (length < zip->header.uncompressed_length) + return ZIPERR_BUFFER_TOO_SMALL; + + /* make sure the info in the header aligns with what we know */ + if (zip->header.start_disk_number != zip->ecd.disk_number) + return ZIPERR_UNSUPPORTED; + + /* get the compressed data offset */ + ziperr = get_compressed_data_offset(zip, &offset); + if (ziperr != ZIPERR_NONE) + return ziperr; + + /* handle compression types */ + switch (zip->header.compression) + { + case 0: + ziperr = decompress_data_type_0(zip, offset, buffer, length); + break; + + case 8: + ziperr = decompress_data_type_8(zip, offset, buffer, length); + break; + + default: + ziperr = ZIPERR_UNSUPPORTED; + break; + } + return ziperr; +} + + + +/*************************************************************************** + CACHE MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + free_zip_file - free all the data for a + zip_file +-------------------------------------------------*/ + +static void free_zip_file(zip_file *zip) +{ + if (zip != NULL) + { + if (zip->file != NULL) + osd_close(zip->file); + if (zip->filename != NULL) + free((void *)zip->filename); + if (zip->ecd.raw != NULL) + free(zip->ecd.raw); + if (zip->cd != NULL) + free(zip->cd); + free(zip); + } +} + + + +/*************************************************************************** + ZIP FILE PARSING +***************************************************************************/ + +/*------------------------------------------------- + read_ecd - read the ECD data +-------------------------------------------------*/ + +static zip_error read_ecd(zip_file *zip) +{ + UINT32 buflen = 1024; + UINT8 *buffer; + + /* we may need multiple tries */ + while (buflen < 65536) + { + file_error error; + UINT32 read_length; + INT32 offset; + + /* max out the buffer length at the size of the file */ + if (buflen > zip->length) + buflen = zip->length; + + /* allocate buffer */ + buffer = malloc(buflen + 1); + if (buffer == NULL) + return ZIPERR_OUT_OF_MEMORY; + + /* read in one buffers' worth of data */ + error = osd_read(zip->file, buffer, zip->length - buflen, buflen, &read_length); + if (error != FILERR_NONE || read_length != buflen) + { + free(buffer); + return ZIPERR_FILE_ERROR; + } + + /* find the ECD signature */ + for (offset = buflen - 22; offset >= 0; offset--) + if (buffer[offset + 0] == 'P' && buffer[offset + 1] == 'K' && buffer[offset + 2] == 0x05 && buffer[offset + 3] == 0x06) + break; + + /* if we found it, fill out the data */ + if (offset >= 0) + { + /* reuse the buffer as our ECD buffer */ + zip->ecd.raw = buffer; + zip->ecd.rawlength = buflen - offset; + + /* append a NULL terminator to the comment */ + memmove(&buffer[0], &buffer[offset], zip->ecd.rawlength); + zip->ecd.raw[zip->ecd.rawlength] = 0; + + /* extract ecd info */ + zip->ecd.signature = read_dword(zip->ecd.raw + ZIPESIG); + zip->ecd.disk_number = read_word (zip->ecd.raw + ZIPEDSK); + zip->ecd.cd_start_disk_number = read_word (zip->ecd.raw + ZIPECEN); + zip->ecd.cd_disk_entries = read_word (zip->ecd.raw + ZIPENUM); + zip->ecd.cd_total_entries = read_word (zip->ecd.raw + ZIPECENN); + zip->ecd.cd_size = read_dword(zip->ecd.raw + ZIPECSZ); + zip->ecd.cd_start_disk_offset = read_dword(zip->ecd.raw + ZIPEOFST); + zip->ecd.comment_length = read_word (zip->ecd.raw + ZIPECOML); + zip->ecd.comment = (const char *)(zip->ecd.raw + ZIPECOM); + return ZIPERR_NONE; + } + + /* didn't find it; free this buffer and expand our search */ + free(buffer); + if (buflen < zip->length) + buflen *= 2; + else + return ZIPERR_BAD_SIGNATURE; + } + return ZIPERR_OUT_OF_MEMORY; +} + + +/*------------------------------------------------- + get_compressed_data_offset - return the + offset of the compressed data +-------------------------------------------------*/ + +static zip_error get_compressed_data_offset(zip_file *zip, UINT64 *offset) +{ + file_error error; + UINT32 read_length; + + /* make sure the file handle is open */ + if (zip->file == NULL) + { + int filerr = osd_open(zip->filename, OPEN_FLAG_READ, &zip->file, &zip->length); + if (filerr != FILERR_NONE) + return ZIPERR_FILE_ERROR; + } + + /* now go read the fixed-sized part of the local file header */ + error = osd_read(zip->file, zip->buffer, zip->header.local_header_offset, ZIPNAME, &read_length); + if (error != FILERR_NONE || read_length != ZIPNAME) + return (error == FILERR_NONE) ? ZIPERR_FILE_TRUNCATED : ZIPERR_FILE_ERROR; + + /* compute the final offset */ + *offset = zip->header.local_header_offset + ZIPNAME; + *offset += read_word(zip->buffer + ZIPFNLN); + *offset += read_word(zip->buffer + ZIPXTRALN); + + return ZIPERR_NONE; +} + + + +/*************************************************************************** + DECOMPRESSION INTERFACES +***************************************************************************/ + +/*------------------------------------------------- + decompress_data_type_0 - "decompress" + type 0 data (which is uncompressed) +-------------------------------------------------*/ + +static zip_error decompress_data_type_0(zip_file *zip, UINT64 offset, void *buffer, UINT32 length) +{ + file_error filerr; + UINT32 read_length; + + /* the data is uncompressed; just read it */ + filerr = osd_read(zip->file, buffer, offset, zip->header.compressed_length, &read_length); + if (filerr != FILERR_NONE) + return ZIPERR_FILE_ERROR; + else if (read_length != zip->header.compressed_length) + return ZIPERR_FILE_TRUNCATED; + else + return ZIPERR_NONE; +} + + +/*------------------------------------------------- + decompress_data_type_8 - decompress + type 8 data (which is deflated) +-------------------------------------------------*/ + +static zip_error decompress_data_type_8(zip_file *zip, UINT64 offset, void *buffer, UINT32 length) +{ + UINT32 input_remaining = zip->header.compressed_length; + UINT32 read_length; + z_stream stream; + int filerr; + int zerr; + + /* make sure we don't need a newer mechanism */ + if (zip->header.version_needed > 0x14) + return ZIPERR_UNSUPPORTED; + + /* reset the stream */ + memset(&stream, 0, sizeof(stream)); + stream.next_out = buffer; + stream.avail_out = length; + + /* initialize the decompressor */ + zerr = inflateInit2(&stream, -MAX_WBITS); + if (zerr != Z_OK) + return ZIPERR_DECOMPRESS_ERROR; + + /* loop until we're done */ + while (1) + { + /* read in the next chunk of data */ + filerr = osd_read(zip->file, zip->buffer, offset, MIN(input_remaining, sizeof(zip->buffer)), &read_length); + if (filerr != FILERR_NONE) + { + inflateEnd(&stream); + return ZIPERR_FILE_ERROR; + } + offset += read_length; + + /* if we read nothing, but still have data left, the file is truncated */ + if (read_length == 0 && input_remaining > 0) + { + inflateEnd(&stream); + return ZIPERR_FILE_TRUNCATED; + } + + /* fill out the input data */ + stream.next_in = zip->buffer; + stream.avail_in = read_length; + input_remaining -= read_length; + + /* add a dummy byte at end of compressed data */ + if (input_remaining == 0) + stream.avail_in++; + + /* now inflate */ + zerr = inflate(&stream, Z_NO_FLUSH); + if (zerr == Z_STREAM_END) + break; + if (zerr != Z_OK) + { + inflateEnd(&stream); + return ZIPERR_DECOMPRESS_ERROR; + } + } + + /* finish decompression */ + zerr = inflateEnd(&stream); + if (zerr != Z_OK) + return ZIPERR_DECOMPRESS_ERROR; + + /* if anything looks funny, report an error */ + if (stream.avail_out > 0 || input_remaining > 0) + return ZIPERR_DECOMPRESS_ERROR; + + return ZIPERR_NONE; +} diff --git a/src/lib/util/unzip.h b/src/lib/util/unzip.h new file mode 100644 index 00000000000..0cc9842994d --- /dev/null +++ b/src/lib/util/unzip.h @@ -0,0 +1,143 @@ +/*************************************************************************** + + unzip.h + + ZIP file management. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __UNZIP_H__ +#define __UNZIP_H__ + +#include "osdcore.h" + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define ZIP_DECOMPRESS_BUFSIZE 16384 + +/* Error types */ +enum _zip_error +{ + ZIPERR_NONE = 0, + ZIPERR_OUT_OF_MEMORY, + ZIPERR_FILE_ERROR, + ZIPERR_BAD_SIGNATURE, + ZIPERR_DECOMPRESS_ERROR, + ZIPERR_FILE_TRUNCATED, + ZIPERR_FILE_CORRUPT, + ZIPERR_UNSUPPORTED, + ZIPERR_BUFFER_TOO_SMALL +}; +typedef enum _zip_error zip_error; + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* contains extracted file header information */ +typedef struct _zip_file_header zip_file_header; +struct _zip_file_header +{ + UINT32 signature; /* central file header signature */ + UINT16 version_created; /* version made by */ + UINT16 version_needed; /* version needed to extract */ + UINT16 bit_flag; /* general purpose bit flag */ + UINT16 compression; /* compression method */ + UINT16 file_time; /* last mod file time */ + UINT16 file_date; /* last mod file date */ + UINT32 crc; /* crc-32 */ + UINT32 compressed_length; /* compressed size */ + UINT32 uncompressed_length; /* uncompressed size */ + UINT16 filename_length; /* filename length */ + UINT16 extra_field_length; /* extra field length */ + UINT16 file_comment_length; /* file comment length */ + UINT16 start_disk_number; /* disk number start */ + UINT16 internal_attributes; /* internal file attributes */ + UINT32 external_attributes; /* external file attributes */ + UINT32 local_header_offset; /* relative offset of local header */ + const char * filename; /* filename */ + + UINT8 * raw; /* pointer to the raw data */ + UINT32 rawlength; /* length of the raw data */ + UINT8 saved; /* saved byte from after filename */ +}; + + +/* contains extracted end of central directory information */ +typedef struct _zip_ecd zip_ecd; +struct _zip_ecd +{ + UINT32 signature; /* end of central dir signature */ + UINT16 disk_number; /* number of this disk */ + UINT16 cd_start_disk_number; /* number of the disk with the start of the central directory */ + UINT16 cd_disk_entries; /* total number of entries in the central directory on this disk */ + UINT16 cd_total_entries; /* total number of entries in the central directory */ + UINT32 cd_size; /* size of the central directory */ + UINT32 cd_start_disk_offset; /* offset of start of central directory with respect to the starting disk number */ + UINT16 comment_length; /* .ZIP file comment length */ + const char * comment; /* .ZIP file comment */ + + UINT8 * raw; /* pointer to the raw data */ + UINT32 rawlength; /* length of the raw data */ +}; + + +/* describes an open ZIP file */ +typedef struct _zip_file zip_file; +struct _zip_file +{ + const char * filename; /* copy of ZIP filename (for caching) */ + osd_file * file; /* OSD file handle */ + UINT64 length; /* length of zip file */ + + zip_ecd ecd; /* end of central directory */ + + UINT8 * cd; /* central directory raw data */ + UINT32 cd_pos; /* position in central directory */ + zip_file_header header; /* current file header */ + + UINT8 buffer[ZIP_DECOMPRESS_BUFSIZE]; /* buffer for decompression */ +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + + +/* ----- ZIP file access ----- */ + +/* open a ZIP file and parse its central directory */ +zip_error zip_file_open(const char *filename, zip_file **zip); + +/* close a ZIP file (may actually be left open due to caching) */ +void zip_file_close(zip_file *zip); + +/* clear out all open ZIP files from the cache */ +void zip_file_cache_clear(void); + + +/* ----- contained file access ----- */ + +/* find the first file in the ZIP */ +const zip_file_header *zip_file_first_file(zip_file *zip); + +/* find the next file in the ZIP */ +const zip_file_header *zip_file_next_file(zip_file *zip); + +/* decompress the most recently found file in the ZIP */ +zip_error zip_file_decompress(zip_file *zip, void *buffer, UINT32 length); + + +#endif /* __UNZIP_H__ */ diff --git a/src/lib/util/xmlfile.c b/src/lib/util/xmlfile.c new file mode 100644 index 00000000000..a220cc05e4a --- /dev/null +++ b/src/lib/util/xmlfile.c @@ -0,0 +1,857 @@ +/*************************************************************************** + + xmlfile.c + + XML file parsing code. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#include "xmlfile.h" +#include <ctype.h> +#include <expat.h> + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +#define TEMP_BUFFER_SIZE 4096 + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +typedef struct _xml_parse_info xml_parse_info; +struct _xml_parse_info +{ + XML_Parser parser; + xml_data_node * rootnode; + xml_data_node * curnode; + UINT32 flags; +}; + + + +/*************************************************************************** + PROTOTYPES +***************************************************************************/ + +/* expat interfaces */ +static int expat_setup_parser(xml_parse_info *parse_info, xml_parse_options *opts); +static void expat_element_start(void *data, const XML_Char *name, const XML_Char **attributes); +static void expat_data(void *data, const XML_Char *s, int len); +static void expat_element_end(void *data, const XML_Char *name); + +/* node/attributes additions */ +static xml_data_node *add_child(xml_data_node *parent, const char *name, const char *value); +static xml_attribute_node *add_attribute(xml_data_node *node, const char *name, const char *value); + +/* recursive tree operations */ +static void write_node_recursive(xml_data_node *node, int indent, core_file *file); +static void free_node_recursive(xml_data_node *node); + + + +/*************************************************************************** + CORE IMPLEMENTATION +***************************************************************************/ + +/*------------------------------------------------- + copystring - make an allocated copy of a + string +-------------------------------------------------*/ + +static const char *copystring(const char *input) +{ + char *newstr; + + /* NULL just passes through */ + if (input == NULL) + return NULL; + + /* make a lower-case copy if the allocation worked */ + newstr = malloc(strlen(input) + 1); + if (newstr != NULL) + strcpy(newstr, input); + + return newstr; +} + + +/*------------------------------------------------- + copystring_lower - make an allocated copy of + a string and convert it to lowercase along + the way +-------------------------------------------------*/ + +static const char *copystring_lower(const char *input) +{ + char *newstr; + int i; + + /* NULL just passes through */ + if (input == NULL) + return NULL; + + /* make a lower-case copy if the allocation worked */ + newstr = malloc(strlen(input) + 1); + if (newstr != NULL) + { + for (i = 0; input[i] != 0; i++) + newstr[i] = tolower(input[i]); + newstr[i] = 0; + } + + return newstr; +} + + + +/*************************************************************************** + XML FILE OBJECTS +***************************************************************************/ + +/*------------------------------------------------- + xml_file_create - create a new xml file + object +-------------------------------------------------*/ + +xml_data_node *xml_file_create(void) +{ + xml_data_node *rootnode; + + /* create a root node */ + rootnode = malloc(sizeof(*rootnode)); + if (rootnode == NULL) + return NULL; + memset(rootnode, 0, sizeof(*rootnode)); + return rootnode; +} + + +/*------------------------------------------------- + xml_file_read - parse an XML file into its + nodes +-------------------------------------------------*/ + +xml_data_node *xml_file_read(core_file *file, xml_parse_options *opts) +{ + xml_parse_info parse_info; + int done; + + /* set up the parser */ + if (!expat_setup_parser(&parse_info, opts)) + return NULL; + + /* loop through the file and parse it */ + do + { + char tempbuf[TEMP_BUFFER_SIZE]; + + /* read as much as we can */ + int bytes = core_fread(file, tempbuf, sizeof(tempbuf)); + done = core_feof(file); + + /* parse the data */ + if (XML_Parse(parse_info.parser, tempbuf, bytes, done) == XML_STATUS_ERROR) + { + if (opts != NULL && opts->error != NULL) + { + opts->error->error_message = XML_ErrorString(XML_GetErrorCode(parse_info.parser)); + opts->error->error_line = XML_GetCurrentLineNumber(parse_info.parser); + opts->error->error_column = XML_GetCurrentColumnNumber(parse_info.parser); + } + + xml_file_free(parse_info.rootnode); + XML_ParserFree(parse_info.parser); + return NULL; + } + + } while (!done); + + /* free the parser */ + XML_ParserFree(parse_info.parser); + + /* return the root node */ + return parse_info.rootnode; +} + + +/*------------------------------------------------- + xml_string_read - parse an XML string into its + nodes +-------------------------------------------------*/ + +xml_data_node *xml_string_read(const char *string, xml_parse_options *opts) +{ + xml_parse_info parse_info; + int length = (int)strlen(string); + + /* set up the parser */ + if (!expat_setup_parser(&parse_info, opts)) + return NULL; + + /* parse the data */ + if (XML_Parse(parse_info.parser, string, length, TRUE) == XML_STATUS_ERROR) + { + if (opts != NULL && opts->error != NULL) + { + opts->error->error_message = XML_ErrorString(XML_GetErrorCode(parse_info.parser)); + opts->error->error_line = XML_GetCurrentLineNumber(parse_info.parser); + opts->error->error_column = XML_GetCurrentColumnNumber(parse_info.parser); + } + + xml_file_free(parse_info.rootnode); + XML_ParserFree(parse_info.parser); + return NULL; + } + + /* free the parser */ + XML_ParserFree(parse_info.parser); + + /* return the root node */ + return parse_info.rootnode; +} + + +/*------------------------------------------------- + xml_file_write - write an XML tree to a file +-------------------------------------------------*/ + +void xml_file_write(xml_data_node *node, core_file *file) +{ + /* ensure this is a root node */ + if (node->name != NULL) + return; + + /* output a simple header */ + core_fprintf(file, "<?xml version=\"1.0\"?>\n"); + core_fprintf(file, "<!-- This file is autogenerated; comments and unknown tags will be stripped -->\n"); + + /* loop over children of the root and output */ + for (node = node->child; node; node = node->next) + write_node_recursive(node, 0, file); +} + + +/*------------------------------------------------- + xml_file_free - free an XML file object +-------------------------------------------------*/ + +void xml_file_free(xml_data_node *node) +{ + /* ensure this is a root node */ + if (node->name != NULL) + return; + + free_node_recursive(node); +} + + + +/*************************************************************************** + XML NODE MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + xml_count_children - count the number of + child nodes +-------------------------------------------------*/ + +int xml_count_children(xml_data_node *node) +{ + int count = 0; + + /* loop over children and count */ + for (node = node->child; node; node = node->next) + count++; + return count; +} + + +/*------------------------------------------------- + xml_get_sibling - find the next sibling of + the specified node with the specified tag +-------------------------------------------------*/ + +xml_data_node *xml_get_sibling(xml_data_node *node, const char *name) +{ + /* loop over siblings and find a matching name */ + for ( ; node; node = node->next) + if (strcmp(node->name, name) == 0) + return node; + return NULL; +} + + +/*------------------------------------------------- + xml_find_matching_sibling - find the next + sibling of the specified node with the + specified tag or attribute/value pair +-------------------------------------------------*/ + +xml_data_node *xml_find_matching_sibling(xml_data_node *node, const char *name, const char *attribute, const char *matchval) +{ + /* loop over siblings and find a matching attribute */ + for ( ; node; node = node->next) + { + /* can pass NULL as a wildcard for the node name */ + if (name == NULL || strcmp(name, node->name) == 0) + { + /* find a matching attribute */ + xml_attribute_node *attr = xml_get_attribute(node, attribute); + if (attr != NULL && strcmp(attr->value, matchval) == 0) + return node; + } + } + return NULL; +} + + +/*------------------------------------------------- + xml_add_child - add a new child node to the + given node +-------------------------------------------------*/ + +xml_data_node *xml_add_child(xml_data_node *node, const char *name, const char *value) +{ + /* just a standard add child */ + return add_child(node, name, value); +} + + +/*------------------------------------------------- + xml_get_or_add_child - find a child node of + the specified type; if not found, add one +-------------------------------------------------*/ + +xml_data_node *xml_get_or_add_child(xml_data_node *node, const char *name, const char *value) +{ + xml_data_node *child; + + /* find the child first */ + child = xml_get_sibling(node->child, name); + if (child != NULL) + return child; + + /* if not found, do a standard add child */ + return add_child(node, name, value); +} + + +/*------------------------------------------------- + xml_delete_node - delete a node and its + children +-------------------------------------------------*/ + +void xml_delete_node(xml_data_node *node) +{ + xml_data_node **pnode; + + /* first unhook us from the list of children of our parent */ + for (pnode = &node->parent->child; *pnode; pnode = &(*pnode)->next) + if (*pnode == node) + { + *pnode = node->next; + break; + } + + /* now free ourselves and our children */ + free_node_recursive(node); +} + + + +/*************************************************************************** + XML ATTRIBUTE MANAGEMENT +***************************************************************************/ + +/*------------------------------------------------- + xml_get_attribute - get the value of the + specified attribute, or NULL if not found +-------------------------------------------------*/ + +xml_attribute_node *xml_get_attribute(xml_data_node *node, const char *attribute) +{ + xml_attribute_node *anode; + + /* loop over attributes and find a match */ + for (anode = node->attribute; anode; anode = anode->next) + if (strcmp(anode->name, attribute) == 0) + return anode; + return NULL; +} + + +/*------------------------------------------------- + xml_get_attribute_string - get the string + value of the specified attribute; if not + found, return = the provided default +-------------------------------------------------*/ + +const char *xml_get_attribute_string(xml_data_node *node, const char *attribute, const char *defvalue) +{ + xml_attribute_node *attr = xml_get_attribute(node, attribute); + return attr ? attr->value : defvalue; +} + + +/*------------------------------------------------- + xml_get_attribute_int - get the integer + value of the specified attribute; if not + found, return = the provided default +-------------------------------------------------*/ + +int xml_get_attribute_int(xml_data_node *node, const char *attribute, int defvalue) +{ + const char *string = xml_get_attribute_string(node, attribute, NULL); + int value; + + if (string == NULL || sscanf(string, "%d", &value) != 1) + return defvalue; + return value; +} + + +/*------------------------------------------------- + xml_get_attribute_float - get the float + value of the specified attribute; if not + found, return = the provided default +-------------------------------------------------*/ + +float xml_get_attribute_float(xml_data_node *node, const char *attribute, float defvalue) +{ + const char *string = xml_get_attribute_string(node, attribute, NULL); + float value; + + if (string == NULL || sscanf(string, "%f", &value) != 1) + return defvalue; + return value; +} + + +/*------------------------------------------------- + xml_set_attribute - set a new attribute and + string value on the node +-------------------------------------------------*/ + +xml_attribute_node *xml_set_attribute(xml_data_node *node, const char *name, const char *value) +{ + xml_attribute_node *anode; + + /* first find an existing one to replace */ + anode = xml_get_attribute(node, name); + + /* if we found it, free the old value and replace it */ + if (anode != NULL) + { + if (anode->value != NULL) + free((void *)anode->value); + anode->value = copystring(value); + } + + /* otherwise, create a new node */ + else + anode = add_attribute(node, name, value); + + return anode; +} + + +/*------------------------------------------------- + xml_set_attribute_int - set a new attribute and + integer value on the node +-------------------------------------------------*/ + +xml_attribute_node *xml_set_attribute_int(xml_data_node *node, const char *name, int value) +{ + char buffer[100]; + sprintf(buffer, "%d", value); + return xml_set_attribute(node, name, buffer); +} + + +/*------------------------------------------------- + xml_set_attribute_int - set a new attribute and + float value on the node +-------------------------------------------------*/ + +xml_attribute_node *xml_set_attribute_float(xml_data_node *node, const char *name, float value) +{ + char buffer[100]; + sprintf(buffer, "%f", value); + return xml_set_attribute(node, name, buffer); +} + + + +/*************************************************************************** + MISCELLANEOUS INTERFACES +***************************************************************************/ + +/*------------------------------------------------- + xml_normalize_string - normalize a string + to ensure it doesn't contain embedded tags +-------------------------------------------------*/ + +const char *xml_normalize_string(const char *string) +{ + static char buffer[1024]; + char *d = &buffer[0]; + + if (string != NULL) + { + while (*string) + { + switch (*string) + { + case '\"' : d += sprintf(d, """); break; + case '&' : d += sprintf(d, "&"); break; + case '<' : d += sprintf(d, "<"); break; + case '>' : d += sprintf(d, ">"); break; + default: + if (*string >= ' ' && *string <= '~') + *d++ = *string; + else + d += sprintf(d, "&#%d;", (unsigned)(unsigned char)*string); + } + ++string; + } + } + *d++ = 0; + return buffer; +} + + + +/*************************************************************************** + EXPAT INTERFACES +***************************************************************************/ + +/*------------------------------------------------- + expat_setup_parser - set up expat for parsing +-------------------------------------------------*/ + +static int expat_setup_parser(xml_parse_info *parse_info, xml_parse_options *opts) +{ + /* setup parse_info structure */ + memset(parse_info, 0, sizeof(*parse_info)); + if (opts != NULL) + { + parse_info->flags = opts->flags; + if (opts->error != NULL) + { + opts->error->error_message = NULL; + opts->error->error_line = 0; + opts->error->error_column = 0; + } + } + + /* create a root node */ + parse_info->rootnode = xml_file_create(); + if (parse_info->rootnode == NULL) + return FALSE; + parse_info->curnode = parse_info->rootnode; + + /* create the XML parser */ + parse_info->parser = XML_ParserCreate(NULL); + if (parse_info->parser == NULL) + { + free(parse_info->rootnode); + return FALSE; + } + + /* configure the parser */ + XML_SetElementHandler(parse_info->parser, expat_element_start, expat_element_end); + XML_SetCharacterDataHandler(parse_info->parser, expat_data); + XML_SetUserData(parse_info->parser, parse_info); + + /* optional parser initialization step */ + if (opts != NULL && opts->init_parser != NULL) + (*opts->init_parser)(parse_info->parser); + return TRUE; +} + + +/*------------------------------------------------- + expat_element_start - expat callback for a new + element +-------------------------------------------------*/ + +static void expat_element_start(void *data, const XML_Char *name, const XML_Char **attributes) +{ + xml_parse_info *parse_info = (xml_parse_info *) data; + xml_data_node **curnode = &parse_info->curnode; + xml_data_node *newnode; + int attr; + + /* add a new child node to the current node */ + newnode = add_child(*curnode, name, NULL); + if (newnode == NULL) + return; + + /* add all the attributes as well */ + for (attr = 0; attributes[attr]; attr += 2) + add_attribute(newnode, attributes[attr+0], attributes[attr+1]); + + /* set us up as the current node */ + *curnode = newnode; +} + + +/*------------------------------------------------- + expat_data - expat callback for a additional + element data +-------------------------------------------------*/ + +static void expat_data(void *data, const XML_Char *s, int len) +{ + xml_parse_info *parse_info = (xml_parse_info *) data; + xml_data_node **curnode = &parse_info->curnode; + int oldlen = 0; + char *newdata; + + /* if no data, skip */ + if (len == 0) + return; + + /* determine how much data we currently have */ + if ((*curnode)->value != NULL) + oldlen = (int)strlen((*curnode)->value); + + /* realloc */ + newdata = realloc((void *)(*curnode)->value, oldlen + len + 1); + if (newdata == NULL) + return; + + /* copy in the new data a NULL-terminate */ + memcpy(&newdata[oldlen], s, len); + newdata[oldlen + len] = 0; + (*curnode)->value = newdata; +} + + +/*------------------------------------------------- + expat_element_end - expat callback for the end + of an element +-------------------------------------------------*/ + +static void expat_element_end(void *data, const XML_Char *name) +{ + xml_parse_info *parse_info = (xml_parse_info *) data; + xml_data_node **curnode = &parse_info->curnode; + char *orig; + + /* strip leading/trailing spaces from the value data */ + orig = (char *)(*curnode)->value; + if (orig != NULL && !(parse_info->flags & XML_PARSE_FLAG_WHITESPACE_SIGNIFICANT)) + { + char *start = orig; + char *end = start + strlen(start); + + /* first strip leading spaces */ + while (*start && isspace(*start)) + start++; + + /* then strip trailing spaces */ + while (end > start && isspace(end[-1])) + end--; + + /* if nothing left, just free it */ + if (start == end) + { + free(orig); + (*curnode)->value = NULL; + } + + /* otherwise, memmove the data */ + else + { + memmove(orig, start, end - start); + orig[end - start] = 0; + } + } + + /* back us up a node */ + *curnode = (*curnode)->parent; +} + + + +/*************************************************************************** + NODE/ATTRIBUTE ADDITIONS +***************************************************************************/ + +/*------------------------------------------------- + add_child - add a new node to the parent +-------------------------------------------------*/ + +static xml_data_node *add_child(xml_data_node *parent, const char *name, const char *value) +{ + xml_data_node **pnode; + xml_data_node *node; + + /* new element: create a new node */ + node = malloc(sizeof(*node)); + if (node == NULL) + return NULL; + + /* initialize the members */ + node->next = NULL; + node->parent = parent; + node->child = NULL; + node->name = copystring_lower(name); + if (node->name == NULL) + { + free(node); + return NULL; + } + node->value = copystring(value); + if (node->value == NULL && value != NULL) + { + free((void *)node->name); + free(node); + return NULL; + } + node->attribute = NULL; + + /* add us to the end of the list of siblings */ + for (pnode = &parent->child; *pnode; pnode = &(*pnode)->next) ; + *pnode = node; + + return node; +} + + +/*------------------------------------------------- + add_attribute - add a new attribute to the + given node +-------------------------------------------------*/ + +static xml_attribute_node *add_attribute(xml_data_node *node, const char *name, const char *value) +{ + xml_attribute_node *anode, **panode; + + /* allocate a new attribute node */ + anode = malloc(sizeof(*anode)); + if (anode == NULL) + return NULL; + + /* fill it in */ + anode->next = NULL; + anode->name = copystring_lower(name); + if (anode->name == NULL) + { + free(anode); + return NULL; + } + anode->value = copystring(value); + if (anode->value == NULL) + { + free((void *)anode->name); + free(anode); + return NULL; + } + + /* add us to the end of the list of attributes */ + for (panode = &node->attribute; *panode; panode = &(*panode)->next) ; + *panode = anode; + + return anode; +} + + + +/*************************************************************************** + RECURSIVE TREE OPERATIONS +***************************************************************************/ + +/*------------------------------------------------- + write_node_recursive - recursively write + an XML node and its children to a file +-------------------------------------------------*/ + +static void write_node_recursive(xml_data_node *node, int indent, core_file *file) +{ + xml_attribute_node *anode; + xml_data_node *child; + + /* output this tag */ + core_fprintf(file, "%*s<%s", indent, "", node->name); + + /* output any attributes */ + for (anode = node->attribute; anode; anode = anode->next) + core_fprintf(file, " %s=\"%s\"", anode->name, anode->value); + + /* if there are no children and no value, end the tag here */ + if (node->child == NULL && node->value == NULL) + core_fprintf(file, " />\n"); + + /* otherwise, close this tag and output more stuff */ + else + { + core_fprintf(file, ">\n"); + + /* if there is a value, output that here */ + if (node->value != NULL) + core_fprintf(file, "%*s%s\n", indent + 4, "", node->value); + + /* loop over children and output them as well */ + if (node->child != NULL) + { + for (child = node->child; child; child = child->next) + write_node_recursive(child, indent + 4, file); + } + + /* write a closing tag */ + core_fprintf(file, "%*s</%s>\n", indent, "", node->name); + } +} + + +/*------------------------------------------------- + free_node_recursive - recursively free + the data allocated to an XML node +-------------------------------------------------*/ + +static void free_node_recursive(xml_data_node *node) +{ + xml_attribute_node *anode, *nanode; + xml_data_node *child, *nchild; + + /* free name/value */ + if (node->name != NULL) + free((void *)node->name); + if (node->value != NULL) + free((void *)node->value); + + /* free attributes */ + for (anode = node->attribute; anode; anode = nanode) + { + /* free name/value */ + if (anode->name != NULL) + free((void *)anode->name); + if (anode->value != NULL) + free((void *)anode->value); + + /* note the next node and free this node */ + nanode = anode->next; + free(anode); + } + + /* free the children */ + for (child = node->child; child; child = nchild) + { + /* note the next node and free this node */ + nchild = child->next; + free_node_recursive(child); + } + + /* finally free ourself */ + free(node); +} diff --git a/src/lib/util/xmlfile.h b/src/lib/util/xmlfile.h new file mode 100644 index 00000000000..f68c81fddb2 --- /dev/null +++ b/src/lib/util/xmlfile.h @@ -0,0 +1,160 @@ +/*************************************************************************** + + xmlfile.h + + XML file parsing code. + + Copyright (c) 1996-2007, Nicola Salmoria and the MAME Team. + Visit http://mamedev.org for licensing and usage restrictions. + +***************************************************************************/ + +#pragma once + +#ifndef __XMLFILE_H__ +#define __XMLFILE_H__ + +#include "osdcore.h" +#include "corefile.h" + + +/*************************************************************************** + CONSTANTS +***************************************************************************/ + +enum +{ + XML_PARSE_FLAG_WHITESPACE_SIGNIFICANT = 1 +}; + + + +/*************************************************************************** + TYPE DEFINITIONS +***************************************************************************/ + +/* forward type declarations */ +struct XML_ParserStruct; + + +/* a node representing an attribute */ +typedef struct _xml_attribute_node xml_attribute_node; +struct _xml_attribute_node +{ + xml_attribute_node * next; /* pointer to next attribute node */ + const char * name; /* pointer to copy of tag name */ + const char * value; /* pointer to copy of value string */ +}; + + +/* a node representing a data item and its relationships */ +typedef struct _xml_data_node xml_data_node; +struct _xml_data_node +{ + xml_data_node * next; /* pointer to next sibling node */ + xml_data_node * parent; /* pointer to parent node */ + xml_data_node * child; /* pointer to first child node */ + const char * name; /* pointer to copy of tag name */ + const char * value; /* pointer to copy of value string */ + xml_attribute_node * attribute; /* pointer to array of attribute nodes */ +}; + + +/* extended error information from parsing */ +typedef struct _xml_parse_error xml_parse_error; +struct _xml_parse_error +{ + const char * error_message; + int error_line; + int error_column; +}; + + +/* parsing options */ +typedef struct _xml_parse_options xml_parse_options; +struct _xml_parse_options +{ + xml_parse_error * error; + void (*init_parser)(struct XML_ParserStruct *parser); + UINT32 flags; +}; + + + +/*************************************************************************** + FUNCTION PROTOTYPES +***************************************************************************/ + + +/* ----- XML file objects ----- */ + +/* create a new empty xml file object */ +xml_data_node *xml_file_create(void); + +/* parse an XML file into its nodes */ +xml_data_node *xml_file_read(core_file *file, xml_parse_options *opts); + +/* parse an XML string into its nodes */ +xml_data_node *xml_string_read(const char *string, xml_parse_options *opts); + +/* write an XML tree to a file */ +void xml_file_write(xml_data_node *node, core_file *file); + +/* free an XML file object */ +void xml_file_free(xml_data_node *node); + + + +/* ----- XML node management ----- */ + +/* count the number of child nodes */ +int xml_count_children(xml_data_node *node); + +/* find the next sibling with the given tag */ +xml_data_node *xml_get_sibling(xml_data_node *node, const char *name); + +/* find the next sibling with the given tag and/or attribute/value pair */ +xml_data_node *xml_find_matching_sibling(xml_data_node *node, const char *name, const char *attribute, const char *matchval); + +/* add a new child node */ +xml_data_node *xml_add_child(xml_data_node *node, const char *name, const char *value); + +/* either return an existing child node or create one if it doesn't exist */ +xml_data_node *xml_get_or_add_child(xml_data_node *node, const char *name, const char *value); + +/* delete a node and its children */ +void xml_delete_node(xml_data_node *node); + + + +/* ----- XML attribute management ----- */ + +/* find an attribute node with the specified tag */ +xml_attribute_node *xml_get_attribute(xml_data_node *node, const char *attribute); + +/* return the string value of an attribute, or the specified default if not present */ +const char *xml_get_attribute_string(xml_data_node *node, const char *attribute, const char *defvalue); + +/* return the integer value of an attribute, or the specified default if not present */ +int xml_get_attribute_int(xml_data_node *node, const char *attribute, int defvalue); + +/* return the float value of an attribute, or the specified default if not present */ +float xml_get_attribute_float(xml_data_node *node, const char *attribute, float defvalue); + +/* set the string value of an attribute */ +xml_attribute_node *xml_set_attribute(xml_data_node *node, const char *name, const char *value); + +/* set the integer value of an attribute */ +xml_attribute_node *xml_set_attribute_int(xml_data_node *node, const char *name, int value); + +/* set the float value of an attribute */ +xml_attribute_node *xml_set_attribute_float(xml_data_node *node, const char *name, float value); + + + +/* ----- miscellaneous interfaces ----- */ + +/* normalize a string into something that can be written to an XML file */ +const char *xml_normalize_string(const char *string); + +#endif /* __XMLFILE_H__ */ |