changeset 2305:6aca1734d573

Merge
author Michael Pavone <pavone@retrodev.com>
date Wed, 15 Mar 2023 19:28:11 -0700
parents b3832f73444f (current diff) 0343f0d5add0 (diff)
children 62f316b76e9a
files
diffstat 20 files changed, 898 insertions(+), 39 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Wed Mar 15 18:50:24 2023 -0700
+++ b/Makefile	Wed Mar 15 19:28:11 2023 -0700
@@ -196,7 +196,7 @@
 endif
 endif
 endif
-AUDIOOBJS=ym2612.o psg.o wave.o vgm.o event_log.o render_audio.o rf5c164.o oscilloscope.o
+AUDIOOBJS=ym2612.o psg.o wave.o flac.o vgm.o event_log.o render_audio.o rf5c164.o
 CONFIGOBJS=config.o tern.o util.o paths.o
 NUKLEAROBJS=$(FONT) nuklear_ui/blastem_nuklear.o nuklear_ui/sfnt.o
 RENDEROBJS=ppm.o controller_info.o
@@ -215,9 +215,9 @@
 MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o $(RENDEROBJS) io.o romdb.o hash.o menu.o xband.o \
 	realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
 	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o gen_player.o \
-	segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o mediaplayer.o
+	segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o mediaplayer.o oscilloscope.o
 
-LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.o xband.o realtec.o \
+LIBOBJS=libblastem.o system.o genesis.o vdp.o io.o romdb.o hash.o xband.o realtec.o \
 	i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
 	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o jcart.o rom.db.o gen_player.o $(LIBZOBJS) \
 	segacd.o lc8951.o cdimage.o cdd_mcu.o cd_graphics.o cdd_fader.o sft_mapper.o mediaplayer.o
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/blastem.exe.manifest	Wed Mar 15 19:28:11 2023 -0700
@@ -0,0 +1,15 @@
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1" manifestVersion="1.0" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+  <assemblyIdentity version="0.6.3.0" name="retrodev.BlastEm"/>
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+	<supportedOS Id="{e2011457-1546-43c5-a5fe-008deee3d3f0}"/>
+	<supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/>
+	<supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/>
+	<supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/>
+	<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/>
+  </compatibility>
+  <asmv3:application>
+    <asmv3:windowsSettings xmlns="http://schemas.microsoft.com/SMI/2019/WindowsSettings">
+      <activeCodePage>UTF-8</activeCodePage>
+    </asmv3:windowsSettings>
+  </asmv3:application>
+</assembly>
--- a/blastem.rc	Wed Mar 15 18:50:24 2023 -0700
+++ b/blastem.rc	Wed Mar 15 19:28:11 2023 -0700
@@ -1,6 +1,6 @@
 1 VERSIONINFO
-FILEVERSION     0,4,0,0
-PRODUCTVERSION  0,4,0,0
+FILEVERSION     0,6,3,0
+PRODUCTVERSION  0,6,3,0
 BEGIN
   BLOCK "StringFileInfo"
   BEGIN
@@ -10,10 +10,10 @@
       VALUE "FileDescription", "BlastEm - A fast and accurate Genesis/Megadrive emulator"
       VALUE "FileVersion", "1.0"
       VALUE "InternalName", "blastem"
-      VALUE "LegalCopyright", "Copyright 2011-2016 Michael Pavone"
+      VALUE "LegalCopyright", "Copyright 2011-2023 Michael Pavone"
       VALUE "OriginalFilename", "blastem.exe"
       VALUE "ProductName", "BlastEm"
-      VALUE "ProductVersion", "0.4.0"
+      VALUE "ProductVersion", "0.6.3-pre"
     END
   END
 
@@ -23,3 +23,4 @@
   END
 END
 2 ICON "icons/windows.ico"
+3 RTF_MANIFEST blastem.exe.manifest
--- a/cdimage.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/cdimage.c	Wed Mar 15 19:28:11 2023 -0700
@@ -111,17 +111,21 @@
 			if (track) {
 				lba -= media->tracks[track - 1].end_lba;
 			}
-			if (media->tracks[track].has_subcodes) {
-				if (!media->tmp_buffer) {
-					media->tmp_buffer = calloc(1, 96);
+			if (media->tracks[track].flac) {
+				flac_seek(media->tracks[track].flac, (media->tracks[track].file_offset + lba * media->tracks[track].sector_bytes) / 4);
+			} else {
+				if (media->tracks[track].has_subcodes) {
+					if (!media->tmp_buffer) {
+						media->tmp_buffer = calloc(1, 96);
+					}
+					fseek(media->tracks[track].f, media->tracks[track].file_offset + (lba + 1) * media->tracks[track].sector_bytes - 96, SEEK_SET);
+					int bytes = fread(media->tmp_buffer, 1, 96, media->tracks[track].f);
+					if (bytes != 96) {
+						fprintf(stderr, "Only read %d subcode bytes\n", bytes);
+					}
 				}
-				fseek(media->tracks[track].f, media->tracks[track].file_offset + (lba + 1) * media->tracks[track].sector_bytes - 96, SEEK_SET);
-				int bytes = fread(media->tmp_buffer, 1, 96, media->tracks[track].f);
-				if (bytes != 96) {
-					fprintf(stderr, "Only read %d subcode bytes\n", bytes);
-				}
+				fseek(media->tracks[track].f, media->tracks[track].file_offset + lba * media->tracks[track].sector_bytes, SEEK_SET);
 			}
-			fseek(media->tracks[track].f, media->tracks[track].file_offset + lba * media->tracks[track].sector_bytes, SEEK_SET);
 		}
 	}
 	return track;
@@ -156,12 +160,23 @@
 		return 0;
 	} else if ((media->tracks[media->cur_track].sector_bytes < 2352 && offset < 16) || offset > (media->tracks[media->cur_track].sector_bytes + 16)) {
 		return fake_read(media->cur_sector, offset);
+	} else if (media->tracks[media->cur_track].flac) {
+		if (offset & 3) {
+			return media->byte_storage[(offset & 3) - 1];
+		} else {
+			int16_t samples[2];
+			flac_get_sample(media->tracks[media->cur_track].flac, samples, 2);
+			media->byte_storage[0] = samples[0] >> 8;
+			media->byte_storage[1] = samples[1];
+			media->byte_storage[2] = samples[1] >> 8;
+			return samples[0];
+		}
 	} else {
 		if (media->tracks[media->cur_track].need_swap) {
 			if (offset & 1) {
-				return media->byte_storage;
+				return media->byte_storage[0];
 			}
-			media->byte_storage = fgetc(media->tracks[media->cur_track].f);
+			media->byte_storage[0] = fgetc(media->tracks[media->cur_track].f);
 		}
 		return fgetc(media->tracks[media->cur_track].f);
 	}
@@ -198,6 +213,7 @@
 	int track = -1;
 	uint8_t audio_byte_swap = 0;
 	FILE *f = NULL;
+	flac_file *flac = NULL;
 	int track_of_file = -1;
 	uint8_t has_index_0 = 0;
 	uint32_t extra_offset = 0;
@@ -215,6 +231,7 @@
 					warning("Expected track %d, but found track %d in CUE sheet\n", track + 1, file_track);
 				}
 				tracks[track].f = f;
+				tracks[track].flac = flac;
 
 
 				cmd = cmd_start(end);
@@ -259,11 +276,12 @@
 							memcpy(fname + dirlen + 1, cmd, end-cmd);
 							fname[dirlen + 1 + (end-cmd)] = 0;
 						}
+						flac = NULL;
 						f = fopen(fname, "rb");
 						if (!f) {
 							fatal_error("Failed to open %s specified by FILE command in CUE sheet %s.%s\n", fname, media->name, media->extension);
 						}
-						free(fname);
+
 						track_of_file = -1;
 						for (end++; *end && *end != '\n' && *end != '\r'; end++)
 						{
@@ -276,19 +294,29 @@
 								} else if (startswith(end, "WAVE")) {
 									audio_byte_swap = 0;
 									wave_header wave;
-									if (!wave_read_header(f, &wave)) {
-										fatal_error("Wave file %s specified by cute sheet %s.%s is not valid\n", fname, media->name, media->extension);
+									if (wave_read_header(f, &wave)) {
+										if (wave.audio_format != 1 || wave.num_channels != 2 || wave.sample_rate != 44100 || wave.bits_per_sample != 16) {
+											warning("BlastEm only suports WAVE tracks in 16-bit stereo PCM format at 44100 hz, file %s does not match\n", fname);
+										}
+										extra_offset = wave.format_header.size + sizeof(wave.data_header) + sizeof(wave.chunk);
+									} else {
+										fseek(f, 0, SEEK_SET);
+										flac = flac_file_from_file(f);
+										if (!flac) {
+											fatal_error("WAVE file %s in cue sheet %s.%s is neither a valid WAVE nor a valid FLAC file\n", fname, media->name, media->extension);
+										}
+										if (flac->sample_rate != 44100 || flac->bits_per_sample != 16 || flac->channels != 2) {
+											warning("FLAC files in a CUE sheet should match CD audio specs, %s does not\n", fname);
+										}
+
 									}
-									if (wave.audio_format != 1 || wave.num_channels != 2 || wave.sample_rate != 44100 || wave.bits_per_sample != 16) {
-										warning("BlastEm only suports WAVE tracks in 16-bit stereo PCM format at 44100 hz, file %s does not match\n", fname);
-									}
-									extra_offset = wave.format_header.size + sizeof(wave.data_header) + sizeof(wave.chunk);
 								} else {
 									warning("Unsupported FILE type in CUE sheet. Only BINARY and MOTOROLA are supported\n");
 								}
 								break;
 							}
 						}
+						free(fname);
 					}
 				}
 			} else if (track >= 0) {
@@ -344,10 +372,22 @@
 		for (int track = 0; track < media->num_tracks; track++) {
 			if (track == media->num_tracks - 1 && tracks[track].f) {
 				uint32_t start_lba =tracks[track].fake_pregap ? tracks[track].start_lba : tracks[track].pregap_lba;
-				tracks[track].end_lba = start_lba + (file_size(tracks[track].f) - tracks[track].file_offset)/ tracks[track].sector_bytes;
+				uint32_t fsize;
+				if (tracks[track].flac) {
+					fsize = tracks[track].flac->total_samples * 4;
+				} else {
+					fsize = file_size(tracks[track].f);
+				}
+				tracks[track].end_lba = start_lba + (fsize - tracks[track].file_offset)/ tracks[track].sector_bytes;
 			} else if (tracks[track].f != f) {
 				uint32_t start_lba =tracks[track-1].fake_pregap ? tracks[track-1].start_lba : tracks[track-1].pregap_lba;
-				tracks[track-1].end_lba = start_lba + (file_size(tracks[track-1].f) - tracks[track-1].file_offset)/ tracks[track-1].sector_bytes;
+				uint32_t fsize;
+				if (tracks[track-1].flac) {
+					fsize = tracks[track-1].flac->total_samples * 4;
+				} else {
+					fsize = file_size(tracks[track-1].f);
+				}
+				tracks[track-1].end_lba = start_lba + (fsize - tracks[track-1].file_offset)/ tracks[track-1].sector_bytes;
 				offset = tracks[track-1].end_lba;
 			}
 			if (!tracks[track].fake_pregap) {
@@ -359,7 +399,7 @@
 		//replace cue sheet with first sector
 		free(media->buffer);
 		media->buffer = calloc(2048, 1);
-		if (tracks[0].type == TRACK_DATA && tracks[0].sector_bytes == 2352) {
+		if (tracks[0].type == TRACK_DATA && tracks[0].sector_bytes == 2352 && !tracks[0].flac) {
 			// if the first track is a data track, don't trust the CUE sheet and look at the MM:SS:FF from first sector
 			uint8_t msf[3];
 			fseek(tracks[0].f, 12, SEEK_SET);
@@ -588,10 +628,12 @@
 		save_int32(buf, 0);
 	}
 	save_int8(buf, media->in_fake_pregap);
-	save_int8(buf, media->byte_storage);
+	save_int8(buf, media->byte_storage[0]);
 	if (media->tmp_buffer) {
 		save_buffer8(buf, media->tmp_buffer, 96);
 	}
+	save_int8(buf, media->byte_storage[1]);
+	save_int8(buf, media->byte_storage[2]);
 }
 
 void cdimage_deserialize(deserialize_buffer *buf, void *vmedia)
@@ -607,8 +649,12 @@
 		fseek(media->tracks[media->cur_track].f, seekpos, SEEK_SET);
 	}
 	media->in_fake_pregap = load_int8(buf);
-	media->byte_storage = load_int8(buf);
+	media->byte_storage[0] = load_int8(buf);
 	if (media->tmp_buffer) {
 		load_buffer8(buf, media->tmp_buffer, 96);
 	}
+	if (buf->size - buf->cur_pos >= 2) {
+		media->byte_storage[1] = load_int8(buf);
+		media->byte_storage[2] = load_int8(buf);
+	}
 }
--- a/debug.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/debug.c	Wed Mar 15 19:28:11 2023 -0700
@@ -1055,7 +1055,9 @@
 #endif
 	do {
 		process_events();
+#ifndef IS_LIB
 		render_update_display();
+#endif
 #ifndef _WIN32
 		timeout.tv_sec = 0;
 		timeout.tv_usec = 16667;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flac.c	Wed Mar 15 19:28:11 2023 -0700
@@ -0,0 +1,657 @@
+#include <stdlib.h>
+#include <string.h>
+#include <inttypes.h>
+#include "flac.h"
+
+static uint8_t read_byte_buffer(flac_file *f)
+{
+	if (f->offset >= f->buffer_size) {
+		return 0;
+	}
+	uint8_t *buf = f->read_data;
+	return buf[f->offset++];
+}
+
+static void seek_buffer(flac_file *f, uint32_t offset, uint8_t relative)
+{
+	f->offset = relative ? f->offset + offset : offset;
+}
+
+static uint32_t tell_buffer(flac_file *f)
+{
+	return f->offset;
+}
+
+static uint8_t read_byte_file(flac_file *f)
+{
+	int result = fgetc(f->read_data);
+	if (result == EOF) {
+		return 0;
+	}
+	return result;
+}
+
+static void seek_file(flac_file *f, uint32_t offset, uint8_t relative)
+{
+	fseek(f->read_data, offset, relative ? SEEK_CUR : SEEK_SET);
+}
+
+static uint32_t tell_file(flac_file *f)
+{
+	return ftell(f->read_data);
+}
+
+static void read_chars(flac_file *f, char *dest, uint32_t count)
+{
+	for (; count > 0; --count)
+	{
+		*(dest++) = f->read_byte(f);
+	}
+}
+
+static uint16_t read16(flac_file *f)
+{
+	uint16_t ret = f->read_byte(f) << 8;
+	ret |= f->read_byte(f);
+	return ret;
+}
+
+static uint64_t read64(flac_file *f)
+{
+	uint64_t value = ((uint64_t)f->read_byte(f)) << 56;
+	value |= ((uint64_t)f->read_byte(f)) << 48;
+	value |= ((uint64_t)f->read_byte(f)) << 40;
+	value |= ((uint64_t)f->read_byte(f)) << 32;
+	value |= ((uint64_t)f->read_byte(f)) << 24;
+	value |= f->read_byte(f) << 16;
+	value |= f->read_byte(f) << 8;
+	value |= f->read_byte(f);
+	return value;
+}
+
+static uint32_t read_bits(flac_file *f, uint32_t num_bits)
+{
+	uint32_t ret = 0;
+	while (num_bits)
+	{
+		if (!f->bits) {
+			f->cur_byte = f->read_byte(f);
+			f->bits = 8;
+		}
+		uint32_t new_bits = f->bits;
+		if (new_bits > num_bits) {
+			new_bits = num_bits;
+		}
+		ret <<= new_bits;
+		uint32_t mask = (1 << new_bits) - 1;
+		ret |= (f->cur_byte >> (f->bits - new_bits)) & mask;
+		f->bits -= new_bits;
+		num_bits -= new_bits;
+	}
+	return ret;
+}
+
+static uint64_t read_bits64(flac_file *f, uint64_t num_bits)
+{
+	uint64_t ret = 0;
+	while (num_bits)
+	{
+		if (!f->bits) {
+			f->cur_byte = f->read_byte(f);
+			f->bits = 8;
+		}
+		uint64_t new_bits = f->bits;
+		if (new_bits > num_bits) {
+			new_bits = num_bits;
+		}
+		ret <<= new_bits;
+		uint64_t mask = (1 << new_bits) - 1;
+		ret |= f->cur_byte & mask;
+		f->cur_byte >>= new_bits;
+		f->bits -= new_bits;
+		num_bits -= new_bits;
+	}
+	return ret;
+}
+
+typedef struct {
+	uint32_t size;
+	uint8_t  type;
+	uint8_t  is_last;
+} meta_block_header;
+
+enum {
+	STREAMINFO,
+	PADDING,
+	APPLICATION,
+	SEEKTABLE,
+	VORBIS_COMMENT,
+	CUESHEET,
+	PICTURE
+};
+
+static void read_meta_block_header(flac_file *f, meta_block_header *dest)
+{
+	dest->is_last = read_bits(f, 1);
+	dest->type = read_bits(f, 7);
+	dest->size = read_bits(f, 24);
+}
+
+static void parse_streaminfo(flac_file *f)
+{
+	read16(f);//min block size
+	read16(f);//max block size
+	read_bits(f, 24);//min frame size
+	read_bits(f, 24);//max frame size
+	f->sample_rate = read_bits(f, 20);
+	f->channels = read_bits(f, 3) + 1;
+	f->bits_per_sample = read_bits(f, 5) + 1;
+	f->total_samples = read_bits64(f, 36);
+	f->seek(f, 16, 1);//MD5
+}
+
+static void parse_seektable(flac_file *f, uint32_t size)
+{
+	f->num_seekpoints = size / 18;
+	f->seekpoints = calloc(f->num_seekpoints, sizeof(flac_seekpoint));
+	for (uint32_t i = 0; i < f->num_seekpoints; i++)
+	{
+		f->seekpoints[i].sample_number = read64(f);
+		f->seekpoints[i].offset = read64(f);
+		f->seekpoints[i].sample_count = read16(f);
+	}
+}
+
+static uint8_t parse_header(flac_file *f)
+{
+	char id[4];
+	read_chars(f, id, sizeof(id));
+	if (memcmp("fLaC", id, sizeof(id))) {
+		return 0;
+	}
+	meta_block_header header;
+	do {
+		read_meta_block_header(f, &header);
+		if (header.type == STREAMINFO) {
+			parse_streaminfo(f);
+		} else if (header.type == SEEKTABLE) {
+			parse_seektable(f, header.size);
+		} else {
+			f->seek(f, header.size, 1);
+		}
+	} while (!header.is_last);
+	f->first_frame_offset = f->tell(f);
+	return 1;
+}
+
+flac_file *flac_file_from_buffer(void *buffer, uint32_t size)
+{
+	flac_file *f = calloc(1, sizeof(flac_file));
+	f->read_data = buffer;
+	f->read_byte = read_byte_buffer;
+	f->seek = seek_buffer;
+	f->tell = tell_buffer;
+	f->buffer_size = size;
+	if (parse_header(f)) {
+		return f;
+	}
+	free(f);
+	return NULL;
+}
+
+flac_file *flac_file_from_file(FILE *file)
+{
+	flac_file *f = calloc(1, sizeof(flac_file));
+	f->read_data = file;
+	f->read_byte = read_byte_file;
+	f->seek = seek_file;
+	f->tell = tell_file;
+	if (parse_header(f)) {
+		return f;
+	}
+	free(f);
+	return NULL;
+}
+
+static uint64_t read_utf64(flac_file *f)
+{
+	uint8_t byte = f->read_byte(f);
+	if (!(byte & 0x80)) {
+		return byte;
+	}
+	uint8_t mask = 0x40;
+	uint8_t length = 0;
+	while (byte & mask)
+	{
+		mask >>= 1;
+		length++;
+	}
+	uint64_t value = byte & (mask - 1);
+	for (uint8_t i = 0; i < length; i++)
+	{
+		value <<= 6;
+		value |= f->read_byte(f) & 0x3F;
+	}
+	return value;
+}
+
+static uint32_t read_utf32(flac_file *f)
+{
+	uint8_t byte = f->read_byte(f);
+	if (!(byte & 0x80)) {
+		return byte;
+	}
+	uint8_t mask = 0x40;
+	uint8_t length = 0;
+	while (byte & mask)
+	{
+		mask >>= 1;
+		length++;
+	}
+	uint32_t value = byte & (mask - 1);
+	for (uint8_t i = 0; i < length; i++)
+	{
+		value <<= 6;
+		value |= f->read_byte(f) & 0x3F;
+	}
+	return value;
+}
+
+static uint8_t parse_frame_header(flac_file *f)
+{
+	uint16_t sync = read_bits(f, 14);
+	if (sync != 0x3FFE) {
+		fprintf(stderr, "Invalid sync FLAC sync pattern: %X\n", sync);
+		return 0;
+	}
+	read_bits(f, 1);//reserved
+	uint8_t block_size_strategy = read_bits(f, 1);
+	uint8_t block_size_code = read_bits(f, 4);
+	uint8_t sample_rate_code = read_bits(f, 4);
+	uint8_t channels = read_bits(f, 4);
+	uint8_t joint_stereo = 0;
+	if (channels > 7) {
+		joint_stereo = (channels & 7) + 1;
+		channels = 2;
+	} else {
+		++channels;
+	}
+	f->frame_channels = channels;
+	f->frame_joint_stereo = joint_stereo;
+	uint8_t bits_per_sample_code = read_bits(f, 3);
+	if (!bits_per_sample_code) {
+		f->frame_bits_per_sample = f->bits_per_sample;
+	} else if (bits_per_sample_code < 3) {
+		f->frame_bits_per_sample = 4 + 4 * bits_per_sample_code;
+	} else {
+		f->frame_bits_per_sample = 4 * bits_per_sample_code;
+	}
+	read_bits(f, 1);//reserved
+	uint32_t block_num;
+	if (block_size_strategy) {
+		f->frame_start_sample = read_utf64(f);
+	} else {
+		block_num = read_utf32(f);
+	}
+	uint32_t block_size = 0;
+	switch (block_size_code)
+	{
+	case 0:
+		fputs("Detected reserved block size 0", stderr);
+		return 0;
+	case 1:
+		block_size = 192;
+		break;
+	case 6:
+		block_size = f->read_byte(f) + 1;
+		break;
+	case 7:
+		block_size = read16(f) + 1;
+		break;
+	default:
+		if (block_size_code < 8) {
+			block_size = 576 * (1 << (block_size_code - 2));
+		} else {
+			block_size = 256 * (1 << (block_size_code - 8));
+		}
+		break;
+	}
+	f->frame_block_size = block_size;
+	if (!block_size_strategy) {
+		f->frame_start_sample  = ((uint64_t)block_num) * ((uint64_t)block_size);
+	}
+	uint32_t sample_rate;
+	switch (sample_rate_code)
+	{
+	case 15:
+		fputs("Invalid frame header sample rate", stderr);
+	case 0:
+		sample_rate = f->sample_rate;
+		break;
+	case 1:
+		sample_rate = 88200;
+		break;
+	case 2:
+		sample_rate = 176400;
+		break;
+	case 3:
+		sample_rate = 192000;
+		break;
+	case 4:
+		sample_rate = 8000;
+		break;
+	case 5:
+		sample_rate = 16000;
+		break;
+	case 6:
+		sample_rate = 22050;
+		break;
+	case 7:
+		sample_rate = 44100;
+		break;
+	case 8:
+		sample_rate = 32000;
+		break;
+	case 9:
+		sample_rate = 44100;
+		break;
+	case 10:
+		sample_rate = 48000;
+		break;
+	case 11:
+		sample_rate = 96000;
+		break;
+	case 12:
+		sample_rate = f->read_byte(f) * 1000;
+		break;
+	case 13:
+		sample_rate = read16(f);
+		break;
+	case 14:
+		sample_rate = read16(f) * 10;
+		break;
+	}
+	f->frame_sample_rate = sample_rate;
+	f->read_byte(f);//CRC-8
+	return 1;
+}
+
+enum {
+	SUBFRAME_CONSTANT,
+	SUBFRAME_VERBATIM,
+	SUBFRAME_FIXED = 8,
+	SUBFRAME_LPC = 0x20,
+};
+
+static int32_t sign_extend(uint32_t value, uint32_t bits)
+{
+	if (value & (1 << (bits - 1))) {
+		value |= ~((1 << bits) - 1);
+	}
+	return value;
+}
+
+static int32_t signed_sample(uint32_t sample_bits, uint32_t sample, uint8_t wasted_bits)
+{
+	sample <<= wasted_bits;
+	sample = sign_extend(sample, sample_bits);
+	return sample;
+}
+
+static void decode_residuals(flac_file *f, flac_subframe *sub, int64_t *coefficients, uint32_t order, int64_t shift)
+{
+	uint8_t residual_method = read_bits(f, 2);
+	uint8_t rice_param_bits = residual_method ? 5 : 4;
+	uint32_t partition_count = 1 << read_bits(f, 4);
+	uint32_t cur = order;
+	uint32_t partition_size = f->frame_block_size / partition_count;
+	for (uint32_t partition = 0; partition < partition_count; partition++)
+	{
+		uint32_t rice_param = read_bits(f, rice_param_bits);
+		if (rice_param == (1 << rice_param_bits) - 1) {
+			//escape code, residuals are unencoded
+			rice_param = read_bits(f, rice_param_bits);
+			for (uint32_t end = partition ? cur + partition_size : partition_size; cur < end; cur++)
+			{
+				int64_t prediction = 0;
+				for (uint32_t i = 0; i < order; i++)
+				{
+					prediction += ((int64_t)sub->decoded[cur - 1 - i]) * coefficients[i];
+				}
+				if (shift) {
+					prediction >>= shift;
+				}
+				prediction += sign_extend(read_bits(f, rice_param), rice_param);
+				sub->decoded[cur] = prediction;
+			}
+		} else {
+			for (uint32_t end = partition ? cur + partition_size : partition_size; cur < end; cur++)
+			{
+				int64_t prediction = 0;
+				for (uint32_t i = 0; i < order; i++)
+				{
+					prediction += ((int64_t)sub->decoded[cur - 1 - i]) * coefficients[i];
+				}
+				if (shift) {
+					prediction >>= shift;
+				}
+				uint32_t residual = 0;
+				while (!read_bits(f, 1))
+				{
+					++residual;
+				}
+				residual <<= rice_param;
+				residual |= read_bits(f, rice_param);
+				if (residual & 1) {
+					sub->decoded[cur] = prediction - (residual >> 1) - 1;
+				} else {
+					sub->decoded[cur] = prediction + (residual >> 1);
+				}
+			}
+		}
+	}
+}
+
+static void decode_subframe(flac_file *f, flac_subframe *sub)
+{
+	if (f->frame_block_size > sub->allocated_samples) {
+		sub->decoded = realloc(sub->decoded, sizeof(int32_t) * f->frame_block_size);
+		sub->allocated_samples = f->frame_block_size ;
+	}
+	int64_t prediction_coefficients[32];
+	read_bits(f, 1);//reserved
+	uint8_t type = read_bits(f, 6);
+	uint8_t has_wasted_bits = read_bits(f, 1);
+	uint8_t wasted_bits = 0;
+	if (has_wasted_bits) {
+		++wasted_bits;
+		while (!read_bits(f, 1))
+		{
+			++wasted_bits;
+		}
+	}
+	uint32_t sample_bits = f->frame_bits_per_sample - wasted_bits;
+	if (f->frame_joint_stereo) {
+		int channel = sub - f->subframes;
+		if (f->frame_joint_stereo == 2 && !channel || (channel && f->frame_joint_stereo != 2)) {
+			sample_bits++;
+		}
+	}
+	if (type == SUBFRAME_CONSTANT) {
+		int32_t sample = signed_sample(sample_bits, read_bits(f, sample_bits), wasted_bits);
+		for (uint32_t i = 0; i < f->frame_block_size; i++)
+		{
+			sub->decoded[i] = sample;
+		}
+	} else if (type == SUBFRAME_VERBATIM) {
+		for (uint32_t i = 0; i < f->frame_block_size; i++)
+		{
+			sub->decoded[i] = signed_sample(sample_bits, read_bits(f, sample_bits), wasted_bits);
+		}
+	} else if (type & SUBFRAME_LPC) {
+		uint32_t order = (type & 0x1F) + 1;
+		for (uint32_t i = 0; i < order; i++)
+		{
+			sub->decoded[i] = signed_sample(sample_bits, read_bits(f, sample_bits), wasted_bits);
+		}
+		uint32_t coefficient_bits = read_bits(f, 4) + 1;
+		int64_t shift_bits = read_bits(f, 5);
+		for (uint32_t i = 0; i < order; i++)
+		{
+			prediction_coefficients[i] = sign_extend(read_bits(f, coefficient_bits), coefficient_bits);
+		}
+		decode_residuals(f, sub, prediction_coefficients, order, shift_bits);
+	} else if (type & SUBFRAME_FIXED) {
+		uint32_t order = type & 7;
+		for (uint32_t i = 0; i < order; i++)
+		{
+			sub->decoded[i] = signed_sample(sample_bits, read_bits(f, sample_bits), wasted_bits);
+		}
+		switch (order)
+		{
+		case 1:
+			prediction_coefficients[0] = 1;
+			break;
+		case 2:
+			prediction_coefficients[0] = 2;
+			prediction_coefficients[1] = -1;
+			break;
+		case 3:
+			prediction_coefficients[0] = 3;
+			prediction_coefficients[1] = -3;
+			prediction_coefficients[2] = 1;
+			break;
+		case 4:
+			prediction_coefficients[0] = 4;
+			prediction_coefficients[1] = -6;
+			prediction_coefficients[2] = 4;
+			prediction_coefficients[3] = -1;
+			break;
+		}
+		decode_residuals(f, sub, prediction_coefficients, order, 0);
+	} else {
+		fprintf(stderr, "Invalid subframe type %X\n", type);
+	}
+}
+
+static uint8_t decode_frame(flac_file *f)
+{
+	if (!parse_frame_header(f)) {
+		return 0;
+	}
+	if (f->frame_channels > f->subframe_alloc) {
+		f->subframes = realloc(f->subframes, sizeof(flac_subframe) * f->frame_channels);
+		memset(f->subframes + f->subframe_alloc, 0, sizeof(flac_subframe) * (f->frame_channels - f->subframe_alloc));
+		f->subframe_alloc = f->frame_channels;
+	}
+	for (uint8_t channel = 0; channel < f->frame_channels; channel++)
+	{
+		decode_subframe(f, f->subframes + channel);
+	}
+	f->bits = 0;
+	read16(f);//Frame footer CRC-16
+	f->frame_sample_pos = 0;
+	return 1;
+}
+
+uint8_t flac_get_sample(flac_file *f, int16_t *out, uint8_t desired_channels)
+{
+	if (f->frame_sample_pos == f->frame_block_size) {
+		if (!decode_frame(f)) {
+			return 0;
+		}
+	}
+	uint8_t copy_channels;
+	if (f->frame_channels == 1 && desired_channels > 1) {
+		int16_t sample = f->subframes->decoded[f->frame_sample_pos];
+		*(out++) = sample;
+		*(out++) = sample;
+		copy_channels = 2;
+	} else {
+		int32_t left, right, mid, diff;
+		switch (f->frame_joint_stereo)
+		{
+		case 0:
+			copy_channels = desired_channels;
+			if (copy_channels > f->frame_channels) {
+				copy_channels = f->frame_channels;
+			}
+			for (uint8_t i = 0; i < copy_channels; i++)
+			{
+				*(out++) = f->subframes[i].decoded[f->frame_sample_pos];
+			}
+			break;
+		case 1:
+			//left-side
+			copy_channels = 2;
+			*(out++) = left = f->subframes[0].decoded[f->frame_sample_pos];
+			if (desired_channels > 1) {
+				*(out++) = left + f->subframes[1].decoded[f->frame_sample_pos];
+			}
+			break;
+		case 2:
+			//side-right
+			copy_channels = 2;
+			right = f->subframes[1].decoded[f->frame_sample_pos];
+			left = right + f->subframes[0].decoded[f->frame_sample_pos];
+			*(out++) = left;
+			if (desired_channels > 1) {
+				*(out++) = right;
+			}
+			break;
+		case 3:
+			//mid-side
+			copy_channels = 2;
+			mid = f->subframes[0].decoded[f->frame_sample_pos];
+			diff = f->subframes[1].decoded[f->frame_sample_pos];
+			left = (diff + 2 * mid) >> 1;
+			*(out++) = left;
+			if (desired_channels > 1) {
+				*(out++) = left - diff;
+			}
+			break;
+		}
+	}
+	for (uint8_t i = copy_channels; i < desired_channels; i++)
+	{
+		*(out++) = 0;
+	}
+	f->frame_sample_pos++;
+
+	return 1;
+}
+
+void flac_seek(flac_file *f, uint64_t sample_number)
+{
+	if (sample_number >= f->frame_start_sample && sample_number < f->frame_start_sample + f->frame_block_size) {
+		f->frame_sample_pos = sample_number - f->frame_start_sample;
+		return;
+	}
+	uint32_t best_seekpoint = f->num_seekpoints + 1;
+	if (f->num_seekpoints) {
+		uint64_t best_diff;
+		for (uint32_t i = 0; i < f->num_seekpoints; i++)
+		{
+			if (f->seekpoints[i].sample_number > sample_number) {
+				continue;
+			}
+			uint64_t diff = sample_number - f->seekpoints[i].sample_number;
+			if (best_seekpoint > f->num_seekpoints || diff < best_diff) {
+				best_seekpoint = i;
+				best_diff = diff;
+			}
+		}
+	}
+	//TODO: more efficient seeking
+	if (best_seekpoint > f->num_seekpoints) {
+		f->seek(f, f->first_frame_offset, 0);
+	} else if (f->seekpoints[best_seekpoint].sample_number > f->frame_start_sample || f->frame_start_sample > sample_number){
+		f->seek(f, f->seekpoints[best_seekpoint].offset + f->first_frame_offset, 0);
+	}
+	do {
+		if (!decode_frame(f)) {
+			return;
+		}
+	} while ((f->frame_start_sample + f->frame_block_size) <= sample_number);
+	f->frame_sample_pos = sample_number - f->frame_start_sample;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/flac.h	Wed Mar 15 19:28:11 2023 -0700
@@ -0,0 +1,59 @@
+#ifndef FLAC_H_
+#define FLAC_H_
+
+#include <stdint.h>
+#include <stdio.h>
+
+typedef struct flac_file flac_file;
+
+typedef uint8_t (*flac_read_fun)(flac_file *f);
+typedef void (*flac_seek_fun)(flac_file *f, uint32_t offset, uint8_t relative);
+typedef uint32_t (*flac_tell_fun)(flac_file *f);
+
+typedef struct {
+	uint32_t allocated_samples;
+	int32_t *decoded;
+} flac_subframe;
+
+typedef struct {
+	uint64_t sample_number;
+	uint64_t offset;
+	uint16_t sample_count;
+} flac_seekpoint;
+
+struct flac_file {
+	uint64_t       total_samples;
+	uint64_t       frame_start_sample;
+	void           *read_data;
+	flac_read_fun  read_byte;
+	flac_seek_fun  seek;
+	flac_tell_fun  tell;
+	flac_subframe  *subframes;
+	flac_seekpoint *seekpoints;
+	uint32_t       num_seekpoints;
+	uint32_t       offset;
+	uint32_t       buffer_size;
+	uint32_t       first_frame_offset;
+
+	uint32_t       frame_sample_pos;
+
+	uint32_t       sample_rate;
+	uint32_t       frame_sample_rate;
+	uint32_t       frame_block_size;
+	uint8_t        bits_per_sample;
+	uint8_t        frame_bits_per_sample;
+	uint8_t        channels;
+	uint8_t        frame_channels;
+	uint8_t        frame_joint_stereo;
+	uint8_t        subframe_alloc;
+
+	uint8_t        cur_byte;
+	uint8_t        bits;
+};
+
+flac_file *flac_file_from_buffer(void *buffer, uint32_t size);
+flac_file *flac_file_from_file(FILE *file);
+uint8_t flac_get_sample(flac_file *f, int16_t *out, uint8_t desired_channels);
+void flac_seek(flac_file *f, uint64_t sample_number);
+
+#endif //FLAC_H_
--- a/genesis.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/genesis.c	Wed Mar 15 19:28:11 2023 -0700
@@ -418,7 +418,9 @@
 				z80_run(z_context, z_context->current_cycle + 4);
 			}
 			gen->enter_z80_debugger = 0;
+#ifndef IS_LIB
 			zdebugger(z_context, z_context->pc);
+#endif
 		}
 		z80_run(z_context, mclks);
 	} else
@@ -485,9 +487,11 @@
 		gen->reset_cycle = CYCLE_NEVER;
 	}
 	if (v_context->frame != gen->last_frame) {
+#ifndef IS_LIB
 		if (gen->ym->scope) {
 			scope_render(gen->ym->scope);
 		}
+#endif
 		//printf("reached frame end %d | MCLK Cycles: %d, Target: %d, VDP cycles: %d, vcounter: %d, hslot: %d\n", gen->last_frame, mclks, gen->frame_end, v_context->cycles, v_context->vcounter, v_context->hslot);
 		uint32_t elapsed = v_context->frame - gen->last_frame;
 		gen->last_frame = v_context->frame;
@@ -556,11 +560,13 @@
 	if (address) {
 		if (gen->header.enter_debugger) {
 			gen->header.enter_debugger = 0;
+#ifndef IS_LIB
 			if (gen->header.debugger_type == DEBUGGER_NATIVE) {
 				debugger(context, address);
 			} else {
 				gdb_debug_enter(context, address);
 			}
+#endif
 		}
 #ifdef NEW_CORE
 		if (gen->header.save_state) {
@@ -1483,16 +1489,20 @@
 		printf("Loaded %s\n", statefile);
 		if (gen->header.enter_debugger) {
 			gen->header.enter_debugger = 0;
+#ifndef IS_LIB
 			insert_breakpoint(gen->m68k, pc, gen->header.debugger_type == DEBUGGER_NATIVE ? debugger : gdb_debug_enter);
+#endif
 		}
 		adjust_int_cycle(gen->m68k, gen->vdp);
 		start_68k_context(gen->m68k, pc);
 	} else {
 		if (gen->header.enter_debugger) {
 			gen->header.enter_debugger = 0;
+#ifndef IS_LIB
 			uint32_t address = read_word(4, (void **)gen->m68k->mem_pointers, &gen->m68k->options->gen, gen->m68k) << 16
 				| read_word(6, (void **)gen->m68k->mem_pointers, &gen->m68k->options->gen, gen->m68k);
 			insert_breakpoint(gen->m68k, address, gen->header.debugger_type == DEBUGGER_NATIVE ? debugger : gdb_debug_enter);
+#endif
 		}
 		m68k_reset(gen->m68k);
 	}
@@ -1773,6 +1783,7 @@
 
 static void toggle_debug_view(system_header *system, uint8_t debug_view)
 {
+#ifndef IS_LIB
 	genesis_context *gen = (genesis_context *)system;
 	if (debug_view < DEBUG_OSCILLOSCOPE) {
 		vdp_toggle_debug_view(gen->vdp, debug_view);
@@ -1798,6 +1809,7 @@
 	} else if (debug_view == DEBUG_CD_GRAPHICS && gen->expansion) {
 		scd_toggle_graphics_debug(gen->expansion);
 	}
+#endif
 }
 
 static void *tmss_rom_write_16(uint32_t address, void *context, uint16_t value)
--- a/libblastem.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/libblastem.c	Wed Mar 15 19:28:11 2023 -0700
@@ -77,7 +77,11 @@
 tern_node *config;
 uint8_t use_native_states = 1;
 system_header *current_system;
-system_media media;
+static system_media media;
+const system_media *current_media(void)
+{
+	return &media;
+}
 
 RETRO_API void retro_init(void)
 {
@@ -238,10 +242,10 @@
 	media.size = game->size;
 	stype = detect_system_type(&media);
 	current_system = alloc_config_system(stype, &media, 0, 0);
-	
+
 	unsigned format = RETRO_PIXEL_FORMAT_XRGB8888;
 	retro_environment(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &format);
-	
+
 	return current_system != NULL;
 }
 
--- a/mediaplayer.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/mediaplayer.c	Wed Mar 15 19:28:11 2023 -0700
@@ -443,7 +443,16 @@
 
 void flac_frame(media_player *player)
 {
-	render_sleep_ms(15);
+	for (uint32_t remaining_samples = player->flac->sample_rate / 60; remaining_samples > 0; remaining_samples--)
+	{
+		int16_t samples[2];
+		if (flac_get_sample(player->flac, samples, 2)) {
+			render_put_stereo_sample(player->audio, samples[0], samples[1]);
+		} else {
+			player->state = STATE_PAUSED;
+			return;
+		}
+	}
 }
 
 void vgm_init(media_player *player, uint32_t opts)
@@ -571,6 +580,14 @@
 	free(player->wave);
 }
 
+static void flac_player_init(media_player *player)
+{
+	player->flac = flac_file_from_buffer(player->media->buffer, player->media->size);
+	if (player->flac) {
+		player->audio = render_audio_source("Audio File", player->flac->sample_rate, 1, 2);
+	}
+}
+
 static void resume_player(system_header *system)
 {
 	media_player *player = (media_player *)system;
@@ -594,10 +611,15 @@
 			}
 			break;
 		case STATE_PAUSED:
+#ifndef IS_LIB
 			render_sleep_ms(15);
+#endif
 			break;
 		}
+	//TODO: Fix this for libretro build properly
+#ifndef IS_LIB
 		render_update_display();
+#endif
 	}
 }
 
@@ -689,6 +711,9 @@
 	case AUDIO_WAVE:
 		wave_player_init(player);
 		break;
+	case AUDIO_FLAC:
+		flac_player_init(player);
+		break;
 	}
 
 	return player;
--- a/mediaplayer.h	Wed Mar 15 18:50:24 2023 -0700
+++ b/mediaplayer.h	Wed Mar 15 19:28:11 2023 -0700
@@ -5,6 +5,7 @@
 #include "system.h"
 #include "vgm.h"
 #include "wave.h"
+#include "flac.h"
 #include "render_audio.h"
 
 typedef struct chip_info chip_info;
@@ -28,6 +29,7 @@
 	vgm_extended_header *vgm_ext;
 	data_block          *ym_seek_block;
 	wave_header         *wave;
+	flac_file           *flac;
 	audio_source        *audio;
 	chip_info           *chips;
 	uint32_t            num_chips;
--- a/nuklear_ui/blastem_nuklear.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/nuklear_ui/blastem_nuklear.c	Wed Mar 15 19:28:11 2023 -0700
@@ -927,17 +927,15 @@
 	memcpy(button_key + pad_key_size, button_base, sizeof(button_base));
 
 	char *final_key;
-	for (int i = 0; i < SDL_CONTROLLER_BUTTON_MAX; i++)
+	for (int i = 0; i <= SDL_CONTROLLER_BUTTON_MAX; i++)
 	{
 		char *base;
 		const char *suffix;
 		size_t base_key_len;
-		if ( i < SDL_CONTROLLER_BUTTON_DPAD_UP) {
+		if ( i < SDL_CONTROLLER_BUTTON_DPAD_UP || i > SDL_CONTROLLER_BUTTON_DPAD_RIGHT) {
 			suffix = SDL_GameControllerGetStringForButton(i);
 			base_key_len = button_key_size;
 			base = button_key;
-
-
 		} else {
 			static const char *dir_keys[] = {"up", "down", "left", "right"};
 			suffix = dir_keys[i - SDL_CONTROLLER_BUTTON_DPAD_UP];
--- a/oscilloscope.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/oscilloscope.c	Wed Mar 15 19:28:11 2023 -0700
@@ -1,6 +1,8 @@
 #include "oscilloscope.h"
 #include "render.h"
 #include "blastem.h"
+#include <stdlib.h>
+#include <string.h>
 
 #define INVALID_TRIGGER 0xFFFFFFFF
 #define WIDTH 1280
--- a/psg.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/psg.c	Wed Mar 15 19:28:11 2023 -0700
@@ -23,6 +23,7 @@
 
 void psg_enable_scope(psg_context *context, oscilloscope *scope, uint32_t master_clock)
 {
+#ifndef IS_LIB
 	context->scope = scope;
 	static const char *names[] = {
 		"PSG #1",
@@ -34,6 +35,7 @@
 	{
 		context->scope_channel[i] = scope_add_channel(scope, names[i], master_clock / context->clock_inc);
 	}
+#endif
 }
 
 void psg_free(psg_context *context)
@@ -146,9 +148,11 @@
 			} else {
 				value = 0;
 			}
+#ifndef IS_LIB
 			if (context->scope) {
 				scope_add_sample(context->scope, context->scope_channel[i], value, trigger[i]);
 			}
+#endif
 		}
 		value = 0;
 		if (context->noise_out) {
@@ -160,9 +164,11 @@
 				right_accum += value;
 			}
 		}
+#ifndef IS_LIB
 		if (context->scope) {
 			scope_add_sample(context->scope, context->scope_channel[3], value, trigger[3]);
 		}
+#endif
 
 		render_put_stereo_sample(context->audio, left_accum, right_accum);
 
--- a/rf5c164.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/rf5c164.c	Wed Mar 15 19:28:11 2023 -0700
@@ -154,14 +154,20 @@
 			int16_t left = (sample * (pcm->channels[pcm->cur_channel].regs[PAN] >> 4)) >> 5;
 			int16_t right = (sample * (pcm->channels[pcm->cur_channel].regs[PAN] & 0xF)) >> 5;
 			//printf("chan %d, raw %X, sample %d, left %d, right %d, ptr %X (raw %X)\n", pcm->cur_channel, pcm->channels[pcm->cur_channel].sample, sample, left, right, pcm->channels[pcm->cur_channel].cur_ptr >> 11, pcm->channels[pcm->cur_channel].cur_ptr);
+#ifndef IS_LIB
 			if (pcm->scope) {
 				scope_add_sample(pcm->scope, pcm->channels[pcm->cur_channel].scope_channel, sample, pcm->channels[pcm->cur_channel].trigger);
 			}
+#endif
 			pcm->left += left;
 			pcm->right += right;
+#ifdef IS_LIB
+		}
+#else
 		} else if (pcm->scope) {
 			scope_add_sample(pcm->scope, pcm->channels[pcm->cur_channel].scope_channel, 0, 0);
 		}
+#endif
 		write_if_not_sounding(pcm);
 		CHECK;
 	case 10:
@@ -246,6 +252,7 @@
 
 void rf5c164_enable_scope(rf5c164* pcm, oscilloscope *scope)
 {
+#ifndef IS_LIB
 	static const char *names[] = {
 		"Richo #1",
 		"Richo #2",
@@ -261,6 +268,7 @@
 	{
 		pcm->channels[i].scope_channel = scope_add_channel(scope, names[i], 50000000 / (pcm->clock_step * 96));
 	}
+#endif
 }
 
 void rf5c164_serialize(rf5c164* pcm, serialize_buffer *buf)
--- a/rom.db	Wed Mar 15 18:50:24 2023 -0700
+++ b/rom.db	Wed Mar 15 19:28:11 2023 -0700
@@ -1530,3 +1530,9 @@
 		}
 	}
 }
+c8a3667631fdbd4d0073e42ada9f7199d09c0cfa {
+	# European version of this game specifies JU in header
+	# but is not entirely 60Hz tolerant
+	name Alisia Dragoon
+	regions E
+}
--- a/segacd.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/segacd.c	Wed Mar 15 19:28:11 2023 -0700
@@ -1136,11 +1136,13 @@
 		if (cd->enter_debugger) {
 			genesis_context *gen = cd->genesis;
 			cd->enter_debugger = 0;
+#ifndef IS_LIB
 			if (gen->header.debugger_type == DEBUGGER_NATIVE) {
 				debugger(context, address);
 			} else {
 				gdb_debug_enter(context, address);
 			}
+#endif
 		}
 		cd->m68k_pc = address;
 	}
--- a/sms.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/sms.c	Wed Mar 15 19:28:11 2023 -0700
@@ -426,9 +426,11 @@
 
 		}
 		if (sms->vdp->frame != sms->last_frame) {
+#ifndef IS_LIB
 			if (sms->psg->scope) {
 				scope_render(sms->psg->scope);
 			}
+#endif
 			uint32_t elapsed = sms->vdp->frame - sms->last_frame;
 			sms->last_frame = sms->vdp->frame;
 			if (system->enter_debugger_frames) {
@@ -450,7 +452,9 @@
 		}
 		if (system->enter_debugger && sms->z80->pc) {
 			system->enter_debugger = 0;
+#ifndef IS_LIB
 			zdebugger(sms->z80, sms->z80->pc);
+#endif
 		}
 #ifdef NEW_CORE
 		if (sms->z80->nmi_cycle == CYCLE_NEVER) {
@@ -523,7 +527,9 @@
 
 	if (system->enter_debugger) {
 		system->enter_debugger = 0;
+#ifndef IS_LIB
 		zinsert_breakpoint(sms->z80, sms->z80->pc, (uint8_t *)zdebugger);
+#endif
 	}
 
 	run_sms(system);
@@ -653,6 +659,7 @@
 
 static void toggle_debug_view(system_header *system, uint8_t debug_view)
 {
+#ifndef IS_LIB
 	sms_context *sms = (sms_context *)system;
 	if (debug_view < DEBUG_OSCILLOSCOPE) {
 		vdp_toggle_debug_view(sms->vdp, debug_view);
@@ -666,6 +673,7 @@
 			psg_enable_scope(sms->psg, scope, sms->normal_clock);
 		}
 	}
+#endif
 }
 
 sms_context *alloc_configure_sms(system_media *media, uint32_t opts, uint8_t force_region)
--- a/system.h	Wed Mar 15 18:50:24 2023 -0700
+++ b/system.h	Wed Mar 15 19:28:11 2023 -0700
@@ -3,6 +3,7 @@
 #include <stddef.h>
 #include <stdint.h>
 #include <stdio.h>
+#include "flac.h"
 
 typedef struct system_header system_header;
 typedef struct system_media system_media;
@@ -111,6 +112,7 @@
 
 typedef struct {
 	FILE       *f;
+	flac_file  *flac;
 	uint32_t   file_offset;
 	uint32_t   fake_pregap;
 	uint32_t   pregap_lba;
@@ -142,7 +144,7 @@
 	uint32_t     cur_sector;
 	media_type   type;
 	uint8_t      in_fake_pregap;
-	uint8_t      byte_storage;
+	uint8_t      byte_storage[3];
 };
 
 #define OPT_ADDRESS_LOG (1U << 31U)
--- a/ym2612.c	Wed Mar 15 18:50:24 2023 -0700
+++ b/ym2612.c	Wed Mar 15 19:28:11 2023 -0700
@@ -635,9 +635,11 @@
 		if (context->channels[i].logfile) {
 			fwrite(&value, sizeof(value), 1, context->channels[i].logfile);
 		}
+#ifndef IS_LIB
 		if (context->scope) {
 			scope_add_sample(context->scope, context->channels[i].scope_channel, value, context->channels[i].phase_overflow);
 		}
+#endif
 		if (context->channels[i].lr & 0x80) {
 			left += (value * context->volume_mult) / context->volume_div;
 		} else if (context->zero_offset) {
@@ -1400,6 +1402,7 @@
 
 void ym_enable_scope(ym2612_context *context, oscilloscope *scope, uint32_t master_clock)
 {
+#ifndef IS_LIB
 	static const char *names[] = {
 		"YM2612 #1",
 		"YM2612 #2",
@@ -1413,4 +1416,5 @@
 	{
 		context->channels[i].scope_channel = scope_add_channel(scope, names[i], master_clock / (context->clock_inc * NUM_OPERATORS));
 	}
+#endif
 }