$NetBSD: patch-af,v 1.2 2004/08/15 12:13:53 dillo Exp $

--- src/gens/util/chd.c.orig	2004-08-15 11:35:14.000000000 +0200
+++ src/gens/util/chd.c
@@ -0,0 +1,400 @@
+/*
+  NiH: chd.c,v 1.6 2004/06/25 23:31:08 dillo Exp
+
+  chd.c -- accessing chd files
+  Copyright (C) 2004 Dieter Baron and Thomas Klausner
+
+  This file is part of ckmame, a program to check rom sets for MAME.
+  The authors can be contacted at <nih@giga.or.at>
+
+  This program is free software; you can redistribute it and/or modify
+  it under the terms of the GNU General Public License, version 2, as
+  published by the Free Software Foundation.
+
+  This program 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 General Public License for more details.
+
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+*/
+
+
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <zlib.h>
+
+#include "chd.h"
+
+
+
+#define MAX_HEADERLEN	120		/* maximum header length */
+#define TAG		"MComprHD"
+#define TAG_LEN		8		/* length of tag */
+#define TAG_AND_LEN	12		/* length of tag + header length */
+
+#define MAP_ENTRY_SIZE_V12	8	/* size of map entry, versions 1 & 2 */
+#define MAP_ENTRY_SIZE_V3	16	/* size of map entry, version 3 */
+
+#define GET_LONG(b)	(b+=4,((b)[-4]<<24)|((b)[-3]<<16)|((b)[-2]<<8)|(b)[-1])
+#define GET_QUAD(b)	(b+=8,((uint64_t)(b)[-8]<<56)|((uint64_t)(b)[-7]<<48) \
+			 |((uint64_t)(b)[-6]<<40)|((uint64_t)(b)[-5]<<32)     \
+			 |((uint64_t)(b)[-4]<<24)|((uint64_t)(b)[-3]<<16)     \
+			 |((uint64_t)(b)[-2]<<8)|((uint64_t)(b)[-1]))
+#define GET_SHORT(b)	(b+=2,((b)[-2]<<8)|(b)[-1])
+
+static int read_header(struct chd *);
+static int read_map(struct chd *);
+
+
+
+void
+chd_close(struct chd *chd)
+{
+    fclose(chd->f);
+    free(chd->name);
+    free(chd->map);
+    free(chd->buf);
+    free(chd->hbuf);
+    free(chd);
+}   
+
+
+
+struct chd *
+chd_open(const char *name, int *errp)
+{
+    struct chd *chd;
+    FILE *f;
+
+    if ((f=fopen(name, "rb")) == NULL) {
+	if (errp)
+	    *errp = CHD_ERR_OPEN;
+	return NULL;
+    }
+
+    if ((chd=malloc(sizeof(*chd))) == NULL) {
+	if (errp)
+	    *errp = CHD_ERR_NOMEM;
+	return NULL;
+    }
+    chd->f = f;
+    if ((chd->name=strdup(name)) == NULL) {
+	if (errp)
+	    *errp = CHD_ERR_NOMEM;
+	chd_close(chd);
+	return NULL;
+    }
+    chd->error = 0;
+    chd->map = NULL;
+    chd->buf = NULL;
+    chd->hno = -1;
+    chd->hbuf = NULL;
+
+    if (read_header(chd) < 0) {
+	if (errp)
+	    *errp = chd->error;
+	chd_close(chd);
+	return NULL;
+    }
+
+    return chd;
+}
+
+
+
+int
+chd_read_hunk(struct chd *chd, int idx, char *b)
+{
+    int i, n, err;
+
+    if (idx < 0 || idx > chd->total_hunks) {
+	chd->error = CHD_ERR_INVAL;
+	return -1;
+    }
+
+    if (chd->map == NULL) {
+	if (read_map(chd) < 0)
+	    return -1;
+    }
+
+    if (chd->map[idx].length > chd->hunk_len) {
+	chd->error = CHD_ERR_NOTSUP;
+	return -1;
+    }
+
+    switch (chd->map[idx].flags & CHD_MAP_TYPE_MASK) {
+    case CHD_MAP_TYPE_COMPRESSED:
+	/* XXX: CHD_COMP_NONE? */
+	if (chd->compression != CHD_COMP_ZLIB
+	    && chd->compression != CHD_COMP_ZLIB_PLUS) {
+	    chd->error = CHD_ERR_NOTSUP;
+	    return -1;
+	}
+
+	if (chd->buf == NULL) {
+	    if ((chd->buf=malloc(chd->hunk_len)) == NULL) {
+		chd->error = CHD_ERR_NOMEM;
+		return -1;
+	    }
+	    chd->z.avail_in = 0;
+	    chd->z.zalloc = Z_NULL;
+	    chd->z.zfree = Z_NULL;
+	    chd->z.opaque = NULL;
+	    err = inflateInit2(&chd->z, -MAX_WBITS);
+	}
+	else
+	    err = inflateReset(&chd->z);
+	if (err != Z_OK) {
+	    chd->error = CHD_ERR_ZLIB;
+	    return -1;
+	}
+
+	if (fseek(chd->f, chd->map[idx].offset, SEEK_SET) == -1) {
+	    chd->error = CHD_ERR_SEEK;
+	    return -1;
+	}
+	if ((n=fread(chd->buf, 1, chd->map[idx].length, chd->f)) < 0) {
+	    chd->error = CHD_ERR_READ;
+	    return -1;
+	}
+
+	chd->z.next_in = chd->buf;
+	chd->z.avail_in = n;
+	chd->z.next_out = b;
+	chd->z.avail_out = chd->hunk_len;
+	/* XXX: should use Z_FINISH, but that returns Z_BUF_ERROR */
+	if ((err=inflate(&chd->z, 0)) != Z_OK && err != Z_STREAM_END) {
+	    chd->error = CHD_ERR_ZLIB;
+	    return -1;
+	}
+	/* XXX: chd->z.avail_out should be 0 */
+	n = chd->hunk_len - chd->z.avail_out;
+	break;
+
+    case CHD_MAP_TYPE_UNCOMPRESSED:
+	if (fseek(chd->f, chd->map[idx].offset, SEEK_SET) == -1) {
+	    chd->error = CHD_ERR_SEEK;
+	    return -1;
+	}
+	/* XXX: use chd->hunk_len instead? */
+	if ((n=fread(b, 1, chd->map[idx].length, chd->f)) < 0) {
+	    chd->error = CHD_ERR_READ;
+	    return -1;
+	}
+	break;
+
+    case CHD_MAP_TYPE_MINI:
+	b[0] = (chd->map[idx].offset >> 56) & 0xff;
+	b[1] = (chd->map[idx].offset >> 48) & 0xff;
+	b[2] = (chd->map[idx].offset >> 40) & 0xff;
+	b[3] = (chd->map[idx].offset >> 32) & 0xff;
+	b[4] = (chd->map[idx].offset >> 24) & 0xff;
+	b[5] = (chd->map[idx].offset >> 16) & 0xff;
+	b[6] = (chd->map[idx].offset >> 8) & 0xff;
+	b[7] = chd->map[idx].offset & 0xff;
+	n = chd->hunk_len;
+	for (i=8; i<n; i++)
+	    b[i] = b[i-8];
+	break;
+
+    case CHD_MAP_TYPE_SELF_HUNK:
+	/* XXX: check CRC here too? */
+	return chd_read_hunk(chd, chd->map[idx].offset, b);
+
+    case CHD_MAP_TYPE_PARENT_HUNK:
+	chd->error = CHD_ERR_NOTSUP;
+	return -1;
+
+    default:
+	chd->error = CHD_ERR_NOTSUP; /* XXX: wrong error */
+	return -1;
+    }
+    
+    if ((chd->map[idx].flags & CHD_MAP_FL_NOCRC) == 0) {
+	if (crc32(0, b, n) != chd->map[idx].crc) {
+	    chd->error = CHD_ERR_CRC;
+	    return -1;
+	}
+    }
+    
+    return n;
+}
+
+
+
+int
+chd_read_range(struct chd *chd, char *b, int off, int len)
+{
+    int i, s, n;
+    int copied, o2, l2;
+
+    /* XXX: error handling */
+
+    s = off/chd->hunk_len;
+    n = (off+len+chd->hunk_len-1)/chd->hunk_len - s;
+
+    copied = 0;
+    o2 = off % chd->hunk_len;
+    l2 = chd->hunk_len - o2;
+
+    for (i=0; i<n; i++) {
+	if (i == 1) {
+	    o2 = 0;
+	    l2 = chd->hunk_len;
+	}
+	if (i == n-1) {
+	    if (l2 > len-copied)
+		l2 = len-copied;
+	}
+	if (o2 == 0 && l2 == chd->hunk_len && s+i != chd->hno) {
+	    if (chd_read_hunk(chd, s+i, b+copied) < 0)
+		return -1;
+	    copied += chd->hunk_len;
+	}
+	else {
+	    if (chd->hbuf == NULL)
+		if ((chd->hbuf=malloc(chd->hunk_len)) == NULL) {
+		    chd->error = CHD_ERR_NOMEM;
+		    return -1;
+		}
+	    if (s+i != chd->hno) {
+		if (chd_read_hunk(chd, s+i, chd->hbuf) < 0)
+		    return  -1;
+		chd->hno = s+i;
+	    }
+	    memcpy(b+copied, chd->hbuf+o2, l2);
+	    copied += l2;
+	}
+    }
+
+    return len;
+}
+
+
+
+static int
+read_header(struct chd *chd)
+{
+    uint32_t len;
+
+    unsigned char b[MAX_HEADERLEN], *p;
+
+    if (fread(b, TAG_AND_LEN, 1, chd->f) != 1) {
+	chd->error = CHD_ERR_READ;
+	return -1;
+    }
+
+    if (memcmp(b, TAG, TAG_LEN) != 0) {
+	chd->error = CHD_ERR_NO_CHD;
+	return -1;
+    }
+
+    p = b+TAG_LEN;
+    len = GET_LONG(p);
+    if (len > MAX_HEADERLEN) {
+	chd->error = CHD_ERR_NO_CHD;
+	return -1;
+    }
+    if (fread(p, len-TAG_AND_LEN, 1, chd->f) != 1) {
+	chd->error = CHD_ERR_READ;
+	return -1;
+    }
+    
+    chd->hdr_length = len;
+    chd->version = GET_LONG(p);
+    chd->flags = GET_LONG(p);
+    chd->compression = GET_LONG(p);
+
+    if (chd->version > 3) {
+	chd->error = CHD_ERR_VERSION;
+	return -1;
+    }
+    /* XXX: check chd->hdr_length against expected value for version */
+
+    if (chd->version < 3) {
+	chd->hunk_len = GET_LONG(p);
+	chd->total_hunks = GET_LONG(p);
+	p += 12; /* skip c/h/s */
+	memcpy(chd->md5, p, sizeof(chd->md5));
+	p += sizeof(chd->md5);
+	memcpy(chd->parent_md5, p, sizeof(chd->parent_md5));
+	p += sizeof(chd->parent_md5);
+
+	if (chd->version == 1)
+	    chd->hunk_len *= 512;
+	else
+	    chd->hunk_len *= GET_LONG(p);
+	chd->total_len = chd->hunk_len * chd->total_hunks;
+	chd->meta_offset = 0;
+	memset(chd->sha1, 0, sizeof(chd->sha1));
+	memset(chd->parent_sha1, 0, sizeof(chd->parent_sha1));
+    }
+    else {
+	chd->total_hunks = GET_LONG(p);
+	chd->total_len = GET_QUAD(p);
+	chd->meta_offset = GET_QUAD(p);
+	memcpy(chd->md5, p, sizeof(chd->md5));
+	p += sizeof(chd->md5);
+	memcpy(chd->parent_md5, p, sizeof(chd->parent_md5));
+	p += sizeof(chd->parent_md5);
+	chd->hunk_len = GET_LONG(p);
+	memcpy(chd->sha1, p, sizeof(chd->sha1));
+	p += sizeof(chd->sha1);
+    }
+
+    return 0;
+}
+
+
+
+static int
+read_map(struct chd *chd)
+{
+    unsigned char b[MAP_ENTRY_SIZE_V3], *p;
+    int i, len;
+    uint64_t v;
+
+    if ((chd->map=malloc(sizeof(*chd->map)*chd->total_hunks)) == NULL) {
+	chd->error = CHD_ERR_NOMEM;
+	return -1;
+    }
+
+    if (chd->version < 3)
+	len = MAP_ENTRY_SIZE_V12;
+    else
+	len = MAP_ENTRY_SIZE_V3;
+
+    for (i=0; i<chd->total_hunks; i++) {
+	if (fread(b, len, 1, chd->f) != 1) {
+	    chd->error = CHD_ERR_READ;
+	    return -1;
+	}
+	p = b;
+
+	if (i == 1832)
+	    chd->version = 3;
+
+	if (chd->version < 3) {
+	    v = GET_QUAD(p);
+	    chd->map[i].offset = v & 0xFFFFFFFFFFFLL;
+	    chd->map[i].crc = 0;
+	    chd->map[i].length = v >> 44;
+	    chd->map[i].flags = CHD_MAP_FL_NOCRC
+		| (chd->map[i].length == chd->hunk_len
+		   ? CHD_MAP_TYPE_UNCOMPRESSED : CHD_MAP_TYPE_COMPRESSED);
+	}
+	else {
+	    chd->map[i].offset = GET_QUAD(p);
+	    chd->map[i].crc = GET_LONG(p);
+	    chd->map[i].length = GET_SHORT(p);
+	    chd->map[i].flags = GET_SHORT(p);
+	}
+    }
+
+    return 0;
+}
