From 00f0670c7acba4e561bdfcab076a01c2760838c8 Mon Sep 17 00:00:00 2001
From: gameblabla <gameblabla@users.noreply.github.com>
Date: Mon, 16 Aug 2021 21:37:34 +0000
Subject: [PATCH] CHD support from libretro's fork. (#188)

We are implementing it as a sub-module for maintenance reasons.

Co-authored-by: aliaspider <aliaspider@gmail.com>
---
 .gitmodules          |   3 +
 Makefile             |  10 +++
 frontend/menu.c      |   3 +
 libchdr              |   1 +
 libpcsxcore/cdriso.c | 142 +++++++++++++++++++++++++++++++++++++++++++
 5 files changed, 159 insertions(+)
 create mode 160000 libchdr

diff --git a/.gitmodules b/.gitmodules
index f93599e3..5f7f360c 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -4,3 +4,6 @@
 [submodule "warm"]
 	path = frontend/warm
 	url = git://notaz.gp2x.de/~notaz/warm.git
+[submodule "libchdr"]
+	path = libchdr
+	url = https://github.com/rtissera/libchdr.git
diff --git a/Makefile b/Makefile
index 0a3b1fec..c63fd1f5 100644
--- a/Makefile
+++ b/Makefile
@@ -132,6 +132,16 @@ endif
 
 # cdrcimg
 OBJS += plugins/cdrcimg/cdrcimg.o
+ifeq "$(CHD_SUPPORT)" "1"
+OBJS += libchdr/src/libchdr_bitstream.o
+OBJS += libchdr/src/libchdr_cdrom.o
+OBJS += libchdr/src/libchdr_chd.o
+OBJS += libchdr/src/libchdr_flac.o
+OBJS += libchdr/src/libchdr_huffman.o
+OBJS += libchdr/deps/lzma-19.00/src/Alloc.o libchdr/deps/lzma-19.00/src/Bra86.o libchdr/deps/lzma-19.00/src/BraIA64.o libchdr/deps/lzma-19.00/src/CpuArch.o libchdr/deps/lzma-19.00/src/Delta.o
+OBJS += libchdr/deps/lzma-19.00/src/LzFind.o libchdr/deps/lzma-19.00/src/Lzma86Dec.o libchdr/deps/lzma-19.00/src/LzmaDec.o libchdr/deps/lzma-19.00/src/LzmaEnc.o libchdr/deps/lzma-19.00/src/Sort.o
+CFLAGS += -DHAVE_CHD -D_7ZIP_ST -Ilibchdr/include/libchdr -Ilibchdr/include/dr_libs -Ilibchdr/include -Ilibchdr/deps/lzma-19.00/include
+endif
 
 # dfinput
 OBJS += plugins/dfinput/main.o plugins/dfinput/pad.o plugins/dfinput/guncon.o
diff --git a/frontend/menu.c b/frontend/menu.c
index 6d753735..c806aa9e 100644
--- a/frontend/menu.c
+++ b/frontend/menu.c
@@ -707,6 +707,9 @@ fail:
 
 static const char *filter_exts[] = {
 	"bin", "img", "mdf", "iso", "cue", "z",
+	#ifdef HAVE_CHD
+	"chd",
+	#endif
 	"bz",  "znx", "pbp", "cbn", NULL
 };
 
diff --git a/libchdr b/libchdr
new file mode 160000
index 00000000..15ff8d67
--- /dev/null
+++ b/libchdr
@@ -0,0 +1 @@
+Subproject commit 15ff8d67554f8651f4c971f4d42176214b96ce7b
diff --git a/libpcsxcore/cdriso.c b/libpcsxcore/cdriso.c
index dca64fad..f8a4d21a 100644
--- a/libpcsxcore/cdriso.c
+++ b/libpcsxcore/cdriso.c
@@ -39,6 +39,10 @@
 #include <errno.h>
 #include <zlib.h>
 
+#ifdef HAVE_CHD
+#include "chd.h"
+#endif
+
 #define OFF_T_MSB ((off_t)1 << (sizeof(off_t) * 8 - 1))
 
 unsigned int cdrIsoMultidiskCount;
@@ -92,6 +96,19 @@ static struct {
 	unsigned int sector_in_blk;
 } *compr_img;
 
+#ifdef HAVE_CHD
+typedef struct {
+	unsigned char (*buffer)[CD_FRAMESIZE_RAW + SUB_FRAMESIZE];
+	chd_file* chd;
+	const chd_header* header;
+	unsigned int sectors_per_hunk;
+	unsigned int current_hunk;
+	unsigned int sector_in_hunk;
+} CHD_IMG;
+
+static CHD_IMG *chd_img;
+#endif
+
 int (*cdimg_read_func)(FILE *f, unsigned int base, void *dest, int sector);
 
 char* CALLBACK CDR__getDriveLetter(void);
@@ -1029,6 +1046,84 @@ fail_io:
 	return -1;
 }
 
+#ifdef HAVE_CHD
+static int handlechd(const char *isofile) {
+	int frame_offset = 0;
+	int file_offset = 0;
+
+	chd_img = (CHD_IMG *)calloc(1, sizeof(*chd_img));
+	if (chd_img == NULL)
+		goto fail_io;
+
+	if(chd_open(isofile, CHD_OPEN_READ, NULL, &chd_img->chd) != CHDERR_NONE)
+		goto fail_io;
+
+	chd_img->header = chd_get_header(chd_img->chd);
+
+	chd_img->buffer = (unsigned char (*)[CD_FRAMESIZE_RAW + SUB_FRAMESIZE])malloc(chd_img->header->hunkbytes);
+	if (chd_img->buffer == NULL)
+		goto fail_io;
+
+	chd_img->sectors_per_hunk = chd_img->header->hunkbytes / (CD_FRAMESIZE_RAW + SUB_FRAMESIZE);
+	chd_img->current_hunk = (unsigned int)-1;
+
+	cddaBigEndian = TRUE;
+
+	numtracks = 0;
+	memset(ti, 0, sizeof(ti));
+
+	while (1)
+	{
+		struct {
+			char type[64];
+			char subtype[32];
+			char pgtype[32];
+			char pgsub[32];
+			uint32_t track;
+			uint32_t frames;
+			uint32_t pregap;
+			uint32_t postgap;
+		} md = {};
+		char meta[256];
+		uint32_t meta_size = 0;
+
+		if (chd_get_metadata(chd_img->chd, CDROM_TRACK_METADATA2_TAG, numtracks, meta, sizeof(meta), &meta_size, NULL, NULL) == CHDERR_NONE)
+			sscanf(meta, CDROM_TRACK_METADATA2_FORMAT, &md.track, md.type, md.subtype, &md.frames, &md.pregap, md.pgtype, md.pgsub, &md.postgap);
+		else if (chd_get_metadata(chd_img->chd, CDROM_TRACK_METADATA_TAG, numtracks, meta, sizeof(meta), &meta_size, NULL, NULL) == CHDERR_NONE)
+			sscanf(meta, CDROM_TRACK_METADATA_FORMAT, &md.track, md.type, md.subtype, &md.frames);
+		else
+			break;
+
+		if(md.track == 1)
+			md.pregap = 150;
+		else
+			sec2msf(msf2sec(ti[md.track-1].length) + md.pregap, ti[md.track-1].length);
+
+		ti[md.track].type = !strncmp(md.type, "AUDIO", 5) ? CDDA : DATA;
+
+		sec2msf(frame_offset + md.pregap, ti[md.track].start);
+		sec2msf(md.frames, ti[md.track].length);
+
+		ti[md.track].start_offset = file_offset;
+
+		frame_offset += md.pregap + md.frames + md.postgap;
+		file_offset += md.frames + md.postgap;
+		numtracks++;
+	}
+
+	if (numtracks)
+		return 0;
+
+fail_io:
+	if (chd_img != NULL) {
+		free(chd_img->buffer);
+		free(chd_img);
+		chd_img = NULL;
+	}
+	return -1;
+}
+#endif
+
 // this function tries to get the .sub file of the given .img
 static int opensubfile(const char *isoname) {
 	char		subname[MAXPATHLEN];
@@ -1190,6 +1285,30 @@ finish:
 	return CD_FRAMESIZE_RAW;
 }
 
+#ifdef HAVE_CHD
+static int cdread_chd(FILE *f, unsigned int base, void *dest, int sector)
+{
+	int hunk;
+
+	if (base)
+		sector += base;
+
+	hunk = sector / chd_img->sectors_per_hunk;
+	chd_img->sector_in_hunk = sector % chd_img->sectors_per_hunk;
+
+	if (hunk != chd_img->current_hunk)
+	{
+		chd_read(chd_img->chd, hunk, chd_img->buffer);
+		chd_img->current_hunk = hunk;
+	}
+
+	if (dest != cdbuffer) // copy avoid HACK
+		memcpy(dest, chd_img->buffer[chd_img->sector_in_hunk],
+			CD_FRAMESIZE_RAW);
+	return CD_FRAMESIZE_RAW;
+}
+#endif
+
 static int cdread_2048(FILE *f, unsigned int base, void *dest, int sector)
 {
 	int ret;
@@ -1209,6 +1328,12 @@ static unsigned char * CALLBACK ISOgetBuffer_compr(void) {
 	return compr_img->buff_raw[compr_img->sector_in_blk] + 12;
 }
 
+#ifdef HAVE_CHD
+static unsigned char *ISOgetBuffer_chd(void) {
+	return chd_img->buffer[chd_img->sector_in_hunk] + 12;
+}
+#endif
+
 static unsigned char * CALLBACK ISOgetBuffer(void) {
 	return cdbuffer + 12;
 }
@@ -1276,6 +1401,14 @@ static long CALLBACK ISOopen(void) {
 		CDR_getBuffer = ISOgetBuffer_compr;
 		cdimg_read_func = cdread_compressed;
 	}
+	
+#ifdef HAVE_CHD
+	else if (handlechd(GetIsoFile()) == 0) {
+		printf("[chd]");
+		CDR_getBuffer = ISOgetBuffer_chd;
+		cdimg_read_func = cdread_chd;
+	}
+#endif
 
 	if (!subChanMixed && opensubfile(GetIsoFile()) == 0) {
 		SysPrintf("[+sub]");
@@ -1363,6 +1496,15 @@ static long CALLBACK ISOclose(void) {
 		free(compr_img);
 		compr_img = NULL;
 	}
+	
+#ifdef HAVE_CHD
+	if (chd_img != NULL) {
+		chd_close(chd_img->chd);
+		free(chd_img->buffer);
+		free(chd_img);
+		chd_img = NULL;
+	}
+#endif
 
 	for (i = 1; i <= numtracks; i++) {
 		if (ti[i].handle != NULL) {
-- 
2.39.5