all repos — mgba @ 0980b67736c6f3235710840c9a9e7030b4fcc75d

mGBA Game Boy Advance Emulator

src/feature/video-logger.c (view raw)

  1/* Copyright (c) 2013-2017 Jeffrey Pfau
  2 *
  3 * This Source Code Form is subject to the terms of the Mozilla Public
  4 * License, v. 2.0. If a copy of the MPL was not distributed with this
  5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  6#include <mgba/feature/video-logger.h>
  7
  8#include <mgba/core/core.h>
  9#include <mgba-util/memory.h>
 10#include <mgba-util/vfs.h>
 11#include <mgba-util/math.h>
 12
 13#ifdef M_CORE_GBA
 14#include <mgba/gba/core.h>
 15#endif
 16#ifdef M_CORE_GB
 17#include <mgba/gb/core.h>
 18#endif
 19
 20#ifdef USE_ZLIB
 21#include <zlib.h>
 22#endif
 23
 24#define BUFFER_BASE_SIZE 0x20000
 25#define MAX_BLOCK_SIZE 0x800000
 26
 27const char mVL_MAGIC[] = "mVL\0";
 28
 29static const struct mVLDescriptor {
 30	enum mPlatform platform;
 31	struct mCore* (*open)(void);
 32} _descriptors[] = {
 33#ifdef M_CORE_GBA
 34	{ PLATFORM_GBA, GBAVideoLogPlayerCreate },
 35#endif
 36#ifdef M_CORE_GB
 37	{ PLATFORM_GB, GBVideoLogPlayerCreate },
 38#endif
 39	{ PLATFORM_NONE, 0 }
 40};
 41
 42enum mVLBlockType {
 43	mVL_BLOCK_DUMMY = 0,
 44	mVL_BLOCK_INITIAL_STATE,
 45	mVL_BLOCK_CHANNEL_HEADER,
 46	mVL_BLOCK_DATA,
 47	mVL_BLOCK_FOOTER = 0x784C566D
 48};
 49
 50enum mVLHeaderFlag {
 51	mVL_FLAG_HAS_INITIAL_STATE = 1
 52};
 53
 54struct mVLBlockHeader {
 55	uint32_t blockType;
 56	uint32_t length;
 57	uint32_t channelId;
 58	uint32_t flags;
 59};
 60
 61enum mVLBlockFlag {
 62	mVL_FLAG_BLOCK_COMPRESSED = 1
 63};
 64
 65struct mVideoLogHeader {
 66	char magic[4];
 67	uint32_t flags;
 68	uint32_t platform;
 69	uint32_t nChannels;
 70};
 71
 72struct mVideoLogContext;
 73struct mVideoLogChannel {
 74	struct mVideoLogContext* p;
 75
 76	uint32_t type;
 77	void* initialState;
 78	size_t initialStateSize;
 79
 80	off_t currentPointer;
 81	size_t bufferRemaining;
 82#ifdef USE_ZLIB
 83	bool inflating;
 84	z_stream inflateStream;
 85#endif
 86
 87	struct CircleBuffer buffer;
 88};
 89
 90struct mVideoLogContext {
 91	void* initialState;
 92	size_t initialStateSize;
 93	uint32_t nChannels;
 94	struct mVideoLogChannel channels[mVL_MAX_CHANNELS];
 95
 96	bool write;
 97	uint32_t activeChannel;
 98	struct VFile* backing;
 99};
100
101
102static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length);
103static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length);
104static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block);
105
106static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length);
107static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length);
108
109static inline size_t _roundUp(size_t value, int shift) {
110	value += (1 << shift) - 1;
111	return value >> shift;
112}
113
114void mVideoLoggerRendererCreate(struct mVideoLogger* logger, bool readonly) {
115	if (readonly) {
116		logger->writeData = _writeNull;
117		logger->block = true;
118	} else {
119		logger->writeData = _writeData;
120	}
121	logger->readData = _readData;
122	logger->dataContext = NULL;
123
124	logger->init = NULL;
125	logger->deinit = NULL;
126	logger->reset = NULL;
127
128	logger->lock = NULL;
129	logger->unlock = NULL;
130	logger->wait = NULL;
131	logger->wake = NULL;
132}
133
134void mVideoLoggerRendererInit(struct mVideoLogger* logger) {
135	logger->palette = anonymousMemoryMap(logger->paletteSize);
136	logger->vram = anonymousMemoryMap(logger->vramSize);
137	logger->oam = anonymousMemoryMap(logger->oamSize);
138
139	logger->vramDirtyBitmap = calloc(_roundUp(logger->vramSize, 17), sizeof(uint32_t));
140	logger->oamDirtyBitmap = calloc(_roundUp(logger->oamSize, 6), sizeof(uint32_t));
141
142	if (logger->init) {
143		logger->init(logger);
144	}
145}
146
147void mVideoLoggerRendererDeinit(struct mVideoLogger* logger) {
148	if (logger->deinit) {
149		logger->deinit(logger);
150	}
151
152	mappedMemoryFree(logger->palette, logger->paletteSize);
153	mappedMemoryFree(logger->vram, logger->vramSize);
154	mappedMemoryFree(logger->oam, logger->oamSize);
155
156	free(logger->vramDirtyBitmap);
157	free(logger->oamDirtyBitmap);
158}
159
160void mVideoLoggerRendererReset(struct mVideoLogger* logger) {
161	memset(logger->vramDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->vramSize, 17));
162	memset(logger->oamDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->oamSize, 6));
163
164	if (logger->reset) {
165		logger->reset(logger);
166	}
167}
168
169void mVideoLoggerRendererWriteVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
170	struct mVideoLoggerDirtyInfo dirty = {
171		DIRTY_REGISTER,
172		address,
173		value,
174		0xDEADBEEF,
175	};
176	logger->writeData(logger, &dirty, sizeof(dirty));
177}
178
179void mVideoLoggerRendererWriteVRAM(struct mVideoLogger* logger, uint32_t address) {
180	int bit = 1 << (address >> 12);
181	if (logger->vramDirtyBitmap[address >> 17] & bit) {
182		return;
183	}
184	logger->vramDirtyBitmap[address >> 17] |= bit;
185}
186
187void mVideoLoggerRendererWritePalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
188	struct mVideoLoggerDirtyInfo dirty = {
189		DIRTY_PALETTE,
190		address,
191		value,
192		0xDEADBEEF,
193	};
194	logger->writeData(logger, &dirty, sizeof(dirty));
195}
196
197void mVideoLoggerRendererWriteOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
198	struct mVideoLoggerDirtyInfo dirty = {
199		DIRTY_OAM,
200		address,
201		value,
202		0xDEADBEEF,
203	};
204	logger->writeData(logger, &dirty, sizeof(dirty));
205}
206
207static void _flushVRAM(struct mVideoLogger* logger) {
208	size_t i;
209	for (i = 0; i < _roundUp(logger->vramSize, 17); ++i) {
210		if (logger->vramDirtyBitmap[i]) {
211			uint32_t bitmap = logger->vramDirtyBitmap[i];
212			logger->vramDirtyBitmap[i] = 0;
213			int j;
214			for (j = 0; j < mVL_MAX_CHANNELS; ++j) {
215				if (!(bitmap & (1 << j))) {
216					continue;
217				}
218				struct mVideoLoggerDirtyInfo dirty = {
219					DIRTY_VRAM,
220					j * 0x1000,
221					0x1000,
222					0xDEADBEEF,
223				};
224				logger->writeData(logger, &dirty, sizeof(dirty));
225				logger->writeData(logger, logger->vramBlock(logger, j * 0x1000), 0x1000);
226			}
227		}
228	}
229}
230
231void mVideoLoggerRendererDrawScanline(struct mVideoLogger* logger, int y) {
232	_flushVRAM(logger);
233	struct mVideoLoggerDirtyInfo dirty = {
234		DIRTY_SCANLINE,
235		y,
236		0,
237		0xDEADBEEF,
238	};
239	logger->writeData(logger, &dirty, sizeof(dirty));
240}
241
242void mVideoLoggerRendererDrawRange(struct mVideoLogger* logger, int startX, int endX, int y) {
243	_flushVRAM(logger);
244	struct mVideoLoggerDirtyInfo dirty = {
245		DIRTY_RANGE,
246		y,
247		startX,
248		endX,
249	};
250	logger->writeData(logger, &dirty, sizeof(dirty));
251}
252
253void mVideoLoggerRendererFlush(struct mVideoLogger* logger) {
254	struct mVideoLoggerDirtyInfo dirty = {
255		DIRTY_FLUSH,
256		0,
257		0,
258		0xDEADBEEF,
259	};
260	logger->writeData(logger, &dirty, sizeof(dirty));
261	if (logger->wait) {
262		logger->wait(logger);
263	}
264}
265
266void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger) {
267	struct mVideoLoggerDirtyInfo dirty = {
268		DIRTY_FRAME,
269		0,
270		0,
271		0xDEADBEEF,
272	};
273	logger->writeData(logger, &dirty, sizeof(dirty));
274}
275
276void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uint32_t offset, uint32_t length, const void* data) {
277	struct mVideoLoggerDirtyInfo dirty = {
278		DIRTY_BUFFER,
279		bufferId,
280		offset,
281		length,
282	};
283	logger->writeData(logger, &dirty, sizeof(dirty));
284	logger->writeData(logger, data, length);
285}
286
287bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) {
288	struct mVideoLoggerDirtyInfo item = {0};
289	while (logger->readData(logger, &item, sizeof(item), block)) {
290		switch (item.type) {
291		case DIRTY_REGISTER:
292		case DIRTY_PALETTE:
293		case DIRTY_OAM:
294		case DIRTY_VRAM:
295		case DIRTY_SCANLINE:
296		case DIRTY_FLUSH:
297		case DIRTY_FRAME:
298		case DIRTY_RANGE:
299		case DIRTY_BUFFER:
300			if (!logger->parsePacket(logger, &item)) {
301				return true;
302			}
303			break;
304		default:
305			return false;
306		}
307	}
308	return !block;
309}
310
311static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
312	struct mVideoLogChannel* channel = logger->dataContext;
313	return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
314}
315
316static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
317	UNUSED(logger);
318	UNUSED(data);
319	UNUSED(length);
320	return false;
321}
322
323static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) {
324	UNUSED(block);
325	struct mVideoLogChannel* channel = logger->dataContext;
326	return mVideoLoggerReadChannel(channel, data, length) == (ssize_t) length;
327}
328
329#ifdef USE_ZLIB
330static void _copyVf(struct VFile* dest, struct VFile* src) {
331	size_t size = src->size(src);
332	void* mem = src->map(src, size, MAP_READ);
333	dest->write(dest, mem, size);
334	src->unmap(src, mem, size);
335}
336
337static void _compress(struct VFile* dest, struct VFile* src) {
338	uint8_t writeBuffer[0x800];
339	uint8_t compressBuffer[0x400];
340	z_stream zstr;
341	zstr.zalloc = Z_NULL;
342	zstr.zfree = Z_NULL;
343	zstr.opaque = Z_NULL;
344	zstr.avail_in = 0;
345	zstr.avail_out = sizeof(compressBuffer);
346	zstr.next_out = (Bytef*) compressBuffer;
347	if (deflateInit(&zstr, 9) != Z_OK) {
348		return;
349	}
350
351	while (true) {
352		size_t read = src->read(src, writeBuffer, sizeof(writeBuffer));
353		if (!read) {
354			break;
355		}
356		zstr.avail_in = read;
357		zstr.next_in = (Bytef*) writeBuffer;
358		while (zstr.avail_in) {
359			if (deflate(&zstr, Z_NO_FLUSH) == Z_STREAM_ERROR) {
360				break;
361			}
362			dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out);
363			zstr.avail_out = sizeof(compressBuffer);
364			zstr.next_out = (Bytef*) compressBuffer;
365		}
366	}
367
368	do {
369		zstr.avail_out = sizeof(compressBuffer);
370		zstr.next_out = (Bytef*) compressBuffer;
371		zstr.avail_in = 0;
372		int ret = deflate(&zstr, Z_FINISH);
373		if (ret == Z_STREAM_ERROR) {
374			break;
375		}
376		dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out);
377	} while (sizeof(compressBuffer) - zstr.avail_out);
378}
379
380static bool _decompress(struct VFile* dest, struct VFile* src, size_t compressedLength) {
381	uint8_t fbuffer[0x400];
382	uint8_t zbuffer[0x800];
383	z_stream zstr;
384	zstr.zalloc = Z_NULL;
385	zstr.zfree = Z_NULL;
386	zstr.opaque = Z_NULL;
387	zstr.avail_in = 0;
388	zstr.avail_out = sizeof(zbuffer);
389	zstr.next_out = (Bytef*) zbuffer;
390	bool started = false;
391
392	while (true) {
393		size_t thisWrite = sizeof(zbuffer);
394		size_t thisRead = 0;
395		if (zstr.avail_in) {
396			zstr.next_out = zbuffer;
397			zstr.avail_out = thisWrite;
398			thisRead = zstr.avail_in;
399		} else if (compressedLength) {
400			thisRead = sizeof(fbuffer);
401			if (thisRead > compressedLength) {
402				thisRead = compressedLength;
403			}
404
405			thisRead = src->read(src, fbuffer, thisRead);
406			if (thisRead <= 0) {
407				break;
408			}
409
410			zstr.next_in = fbuffer;
411			zstr.avail_in = thisRead;
412			zstr.next_out = zbuffer;
413			zstr.avail_out = thisWrite;
414
415			if (!started) {
416				if (inflateInit(&zstr) != Z_OK) {
417					break;
418				}
419				started = true;
420			}
421		} else {
422			zstr.next_in = Z_NULL;
423			zstr.avail_in = 0;
424			zstr.next_out = zbuffer;
425			zstr.avail_out = thisWrite;
426		}
427
428		int ret = inflate(&zstr, Z_NO_FLUSH);
429
430		if (zstr.next_in != Z_NULL) {
431			thisRead -= zstr.avail_in;
432			compressedLength -= thisRead;
433		}
434
435		if (ret != Z_OK) {
436			inflateEnd(&zstr);
437			started = false;
438			if (ret != Z_STREAM_END) {
439				break;
440			}
441		}
442
443		thisWrite = dest->write(dest, zbuffer, thisWrite - zstr.avail_out);
444
445		if (!started) {
446			break;
447		}
448	}
449	return !compressedLength;
450}
451#endif
452
453void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId) {
454	if (channelId >= mVL_MAX_CHANNELS) {
455		return;
456	}
457	logger->dataContext = &context->channels[channelId];
458}
459
460struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) {
461	struct mVideoLogContext* context = malloc(sizeof(*context));
462	memset(context, 0, sizeof(*context));
463
464	context->write = !!core;
465	context->initialStateSize = 0;
466	context->initialState = NULL;
467
468	if (core) {
469		context->initialStateSize = core->stateSize(core);
470		context->initialState = anonymousMemoryMap(context->initialStateSize);
471		core->saveState(core, context->initialState);
472		core->startVideoLog(core, context);
473	}
474
475	context->activeChannel = 0;
476	return context;
477}
478
479void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* vf) {
480	context->backing = vf;
481	vf->truncate(vf, 0);
482	vf->seek(vf, 0, SEEK_SET);
483}
484
485void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) {
486	struct mVideoLogHeader header = { { 0 } };
487	memcpy(header.magic, mVL_MAGIC, sizeof(header.magic));
488	enum mPlatform platform = core->platform(core);
489	STORE_32LE(platform, 0, &header.platform);
490	STORE_32LE(context->nChannels, 0, &header.nChannels);
491
492	uint32_t flags = 0;
493	if (context->initialState) {
494		flags |= mVL_FLAG_HAS_INITIAL_STATE;
495	}
496	STORE_32LE(flags, 0, &header.flags);
497	context->backing->write(context->backing, &header, sizeof(header));
498	if (context->initialState) {
499		struct mVLBlockHeader chheader = { 0 };
500		STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType);
501#ifdef USE_ZLIB
502		STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags);
503
504		struct VFile* vfm = VFileMemChunk(NULL, 0);
505		struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize);
506		_compress(vfm, src);
507		src->close(src);
508		STORE_32LE(vfm->size(vfm), 0, &chheader.length);
509		context->backing->write(context->backing, &chheader, sizeof(chheader));
510		_copyVf(context->backing, vfm);
511		vfm->close(vfm);
512#else
513		STORE_32LE(context->initialStateSize, 0, &chheader.length);
514		context->backing->write(context->backing, &chheader, sizeof(chheader));
515		context->backing->write(context->backing, context->initialState, context->initialStateSize);
516#endif
517	}
518
519 	size_t i;
520	for (i = 0; i < context->nChannels; ++i) {
521		struct mVLBlockHeader chheader = { 0 };
522		STORE_32LE(mVL_BLOCK_CHANNEL_HEADER, 0, &chheader.blockType);
523		STORE_32LE(i, 0, &chheader.channelId);
524		context->backing->write(context->backing, &chheader, sizeof(chheader));
525	}
526}
527
528bool _readBlockHeader(struct mVideoLogContext* context, struct mVLBlockHeader* header) {
529	struct mVLBlockHeader buffer;
530	if (context->backing->read(context->backing, &buffer, sizeof(buffer)) != sizeof(buffer)) {
531		return false;
532	}
533	LOAD_32LE(header->blockType, 0, &buffer.blockType);
534	LOAD_32LE(header->length, 0, &buffer.length);
535	LOAD_32LE(header->channelId, 0, &buffer.channelId);
536	LOAD_32LE(header->flags, 0, &buffer.flags);
537
538	if (header->length > MAX_BLOCK_SIZE) {
539		// Pre-emptively reject blocks that are too big.
540		// If we encounter one, the file is probably corrupted.
541		return false;
542	}
543	return true;
544}
545
546bool _readHeader(struct mVideoLogContext* context) {
547	struct mVideoLogHeader header;
548	context->backing->seek(context->backing, 0, SEEK_SET);
549	if (context->backing->read(context->backing, &header, sizeof(header)) != sizeof(header)) {
550		return false;
551	}
552	if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
553		return false;
554	}
555
556	LOAD_32LE(context->nChannels, 0, &header.nChannels);
557	if (context->nChannels > mVL_MAX_CHANNELS) {
558		return false;
559	}
560
561	uint32_t flags;
562	LOAD_32LE(flags, 0, &header.flags);
563	if (flags & mVL_FLAG_HAS_INITIAL_STATE) {
564		struct mVLBlockHeader header;
565		if (!_readBlockHeader(context, &header)) {
566			return false;
567		}
568		if (header.blockType != mVL_BLOCK_INITIAL_STATE || !header.length) {
569			return false;
570		}
571		if (context->initialState) {
572			mappedMemoryFree(context->initialState, context->initialStateSize);
573			context->initialState = NULL;
574			context->initialStateSize = 0;
575		}
576		if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
577#ifdef USE_ZLIB
578			struct VFile* vfm = VFileMemChunk(NULL, 0);
579			if (!_decompress(vfm, context->backing, header.length)) {
580				vfm->close(vfm);
581				return false;
582			}
583			context->initialStateSize = vfm->size(vfm);
584			context->initialState = anonymousMemoryMap(context->initialStateSize);
585			void* mem = vfm->map(vfm, context->initialStateSize, MAP_READ);
586			memcpy(context->initialState, mem, context->initialStateSize);
587			vfm->unmap(vfm, mem, context->initialStateSize);
588			vfm->close(vfm);
589#else
590			return false;
591#endif
592		} else {
593			context->initialStateSize = header.length;
594			context->initialState = anonymousMemoryMap(header.length);
595			context->backing->read(context->backing, context->initialState, context->initialStateSize);
596		}
597	}
598	return true;
599}
600
601bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) {
602	context->backing = vf;
603
604	if (!_readHeader(context)) {
605		return false;
606	}
607
608	off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
609
610	size_t i;
611	for (i = 0; i < context->nChannels; ++i) {
612		CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE);
613		context->channels[i].bufferRemaining = 0;
614		context->channels[i].currentPointer = pointer;
615		context->channels[i].p = context;
616#ifdef USE_ZLIB
617		context->channels[i].inflating = false;
618#endif
619	}
620	return true;
621}
622
623#ifdef USE_ZLIB
624static void _flushBufferCompressed(struct mVideoLogContext* context) {
625	struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
626	if (!CircleBufferSize(buffer)) {
627		return;
628	}
629	struct VFile* vfm = VFileMemChunk(NULL, 0);
630	struct VFile* src = VFileFIFO(buffer);
631	_compress(vfm, src);
632	src->close(src);
633
634	size_t size = vfm->size(vfm);
635
636	struct mVLBlockHeader header = { 0 };
637	STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
638	STORE_32LE(context->activeChannel, 0, &header.channelId);
639	STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &header.flags);
640	STORE_32LE(size, 0, &header.length);
641
642	context->backing->write(context->backing, &header, sizeof(header));
643	_copyVf(context->backing, vfm);
644	vfm->close(vfm);
645}
646#endif
647
648static void _flushBuffer(struct mVideoLogContext* context) {
649#ifdef USE_ZLIB
650	// TODO: Make optional
651	_flushBufferCompressed(context);
652	return;
653#endif
654
655	struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
656	if (!CircleBufferSize(buffer)) {
657		return;
658	}
659	struct mVLBlockHeader header = { 0 };
660	STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
661	STORE_32LE(CircleBufferSize(buffer), 0, &header.length);
662	STORE_32LE(context->activeChannel, 0, &header.channelId);
663
664	context->backing->write(context->backing, &header, sizeof(header));
665
666	uint8_t writeBuffer[0x800];
667	while (CircleBufferSize(buffer)) {
668		size_t read = CircleBufferRead(buffer, writeBuffer, sizeof(writeBuffer));
669		context->backing->write(context->backing, writeBuffer, read);
670	}
671}
672
673void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* context) {
674	if (context->write) {
675		_flushBuffer(context);
676
677		struct mVLBlockHeader header = { 0 };
678		STORE_32LE(mVL_BLOCK_FOOTER, 0, &header.blockType);
679		context->backing->write(context->backing, &header, sizeof(header));
680	}
681
682	if (core) {
683		core->endVideoLog(core);
684	}
685	if (context->initialState) {
686		mappedMemoryFree(context->initialState, context->initialStateSize);
687	}
688
689	size_t i;
690	for (i = 0; i < context->nChannels; ++i) {
691		CircleBufferDeinit(&context->channels[i].buffer);
692#ifdef USE_ZLIB
693		if (context->channels[i].inflating) {
694			inflateEnd(&context->channels[i].inflateStream);
695			context->channels[i].inflating = false;
696		}
697#endif
698	}
699
700	free(context);
701}
702
703void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core) {
704	_readHeader(context);
705	if (core) {
706		size_t size = core->stateSize(core);
707		if (size <= context->initialStateSize) {
708			core->loadState(core, context->initialState);
709		} else {
710			void* extendedState = anonymousMemoryMap(size);
711			memcpy(extendedState, context->initialState, context->initialStateSize);
712			core->loadState(core, extendedState);
713			mappedMemoryFree(extendedState, size);
714		}
715	}
716
717	off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
718
719	size_t i;
720	for (i = 0; i < context->nChannels; ++i) {
721		CircleBufferClear(&context->channels[i].buffer);
722		context->channels[i].bufferRemaining = 0;
723		context->channels[i].currentPointer = pointer;
724#ifdef USE_ZLIB
725		if (context->channels[i].inflating) {
726			inflateEnd(&context->channels[i].inflateStream);
727			context->channels[i].inflating = false;
728		}
729#endif
730	}
731}
732
733void* mVideoLogContextInitialState(struct mVideoLogContext* context, size_t* size) {
734	if (size) {
735		*size = context->initialStateSize;
736	}
737	return context->initialState;
738}
739
740int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
741	if (context->nChannels >= mVL_MAX_CHANNELS) {
742		return -1;
743	}
744	int chid = context->nChannels;
745	++context->nChannels;
746	context->channels[chid].p = context;
747	CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
748	return chid;
749}
750
751#ifdef USE_ZLIB
752static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
753	uint8_t fbuffer[0x400];
754	uint8_t zbuffer[0x800];
755	size_t read = 0;
756
757	// TODO: Share with _decompress
758	channel->inflateStream.avail_in = 0;
759	while (length) {
760		size_t thisWrite = sizeof(zbuffer);
761		if (thisWrite > length) {
762			thisWrite = length;
763		}
764
765		size_t thisRead = 0;
766		if (channel->inflating && channel->inflateStream.avail_in) {
767			channel->inflateStream.next_out = zbuffer;
768			channel->inflateStream.avail_out = thisWrite;
769			thisRead = channel->inflateStream.avail_in;
770		} else if (channel->bufferRemaining) {
771			thisRead = sizeof(fbuffer);
772			if (thisRead > channel->bufferRemaining) {
773				thisRead = channel->bufferRemaining;
774			}
775
776			thisRead = vf->read(vf, fbuffer, thisRead);
777			if (thisRead <= 0) {
778				break;
779			}
780
781			channel->inflateStream.next_in = fbuffer;
782			channel->inflateStream.avail_in = thisRead;
783			channel->inflateStream.next_out = zbuffer;
784			channel->inflateStream.avail_out = thisWrite;
785
786			if (!channel->inflating) {
787				if (inflateInit(&channel->inflateStream) != Z_OK) {
788					break;
789				}
790				channel->inflating = true;
791			}
792		} else {
793			channel->inflateStream.next_in = Z_NULL;
794			channel->inflateStream.avail_in = 0;
795			channel->inflateStream.next_out = zbuffer;
796			channel->inflateStream.avail_out = thisWrite;
797		}
798
799		int ret = inflate(&channel->inflateStream, Z_NO_FLUSH);
800
801		if (channel->inflateStream.next_in != Z_NULL) {
802			thisRead -= channel->inflateStream.avail_in;
803			channel->currentPointer += thisRead;
804			channel->bufferRemaining -= thisRead;
805		}
806
807		if (ret != Z_OK) {
808			inflateEnd(&channel->inflateStream);
809			channel->inflating = false;
810			if (ret != Z_STREAM_END) {
811				break;
812			}
813		}
814
815		thisWrite = CircleBufferWrite(&channel->buffer, zbuffer, thisWrite - channel->inflateStream.avail_out);
816		length -= thisWrite;
817		read += thisWrite;
818
819		if (!channel->inflating) {
820			break;
821		}
822	}
823	return read;
824}
825#endif
826
827static void _readBuffer(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
828	uint8_t buffer[0x800];
829	while (length) {
830		size_t thisRead = sizeof(buffer);
831		if (thisRead > length) {
832			thisRead = length;
833		}
834		thisRead = vf->read(vf, buffer, thisRead);
835		if (thisRead <= 0) {
836			return;
837		}
838		size_t thisWrite = CircleBufferWrite(&channel->buffer, buffer, thisRead);
839		length -= thisWrite;
840		channel->bufferRemaining -= thisWrite;
841		channel->currentPointer += thisWrite;
842		if (thisWrite < thisRead) {
843			break;
844		}
845	}
846}
847
848static bool _fillBuffer(struct mVideoLogContext* context, size_t channelId, size_t length) {
849	struct mVideoLogChannel* channel = &context->channels[channelId];
850	context->backing->seek(context->backing, channel->currentPointer, SEEK_SET);
851	struct mVLBlockHeader header;
852	while (length) {
853		size_t bufferRemaining = channel->bufferRemaining;
854		if (bufferRemaining) {
855#ifdef USE_ZLIB
856			if (channel->inflating) {
857				length -= _readBufferCompressed(context->backing, channel, length);
858				continue;
859			}
860#endif
861			if (bufferRemaining > length) {
862				bufferRemaining = length;
863			}
864
865			_readBuffer(context->backing, channel, bufferRemaining);
866			length -= bufferRemaining;
867			continue;
868		}
869
870		if (!_readBlockHeader(context, &header)) {
871			return false;
872		}
873		if (header.blockType == mVL_BLOCK_FOOTER) {
874			return true;
875		}
876		if (header.channelId != channelId || header.blockType != mVL_BLOCK_DATA) {
877			context->backing->seek(context->backing, header.length, SEEK_CUR);
878			continue;
879		}
880		channel->currentPointer = context->backing->seek(context->backing, 0, SEEK_CUR);
881		if (!header.length) {
882			continue;
883		}
884		channel->bufferRemaining = header.length;
885
886		if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
887#ifdef USE_ZLIB
888			length -= _readBufferCompressed(context->backing, channel, length);
889#else
890			return false;
891#endif
892		}
893	}
894	return true;
895}
896
897static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length) {
898	struct mVideoLogContext* context = channel->p;
899	unsigned channelId = channel - context->channels;
900	if (channelId >= mVL_MAX_CHANNELS) {
901		return 0;
902	}
903	if (CircleBufferSize(&channel->buffer) >= length) {
904		return CircleBufferRead(&channel->buffer, data, length);
905	}
906	ssize_t size = 0;
907	if (CircleBufferSize(&channel->buffer)) {
908		size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer));
909		if (size <= 0) {
910			return size;
911		}
912		data = (uint8_t*) data + size;
913		length -= size;
914	}
915	if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
916		return size;
917	}
918	size += CircleBufferRead(&channel->buffer, data, length);
919	return size;
920}
921
922static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length) {
923	struct mVideoLogContext* context = channel->p;
924	unsigned channelId = channel - context->channels;
925	if (channelId >= mVL_MAX_CHANNELS) {
926		return 0;
927	}
928	if (channelId != context->activeChannel) {
929		_flushBuffer(context);
930		context->activeChannel = channelId;
931	}
932	if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) {
933		_flushBuffer(context);
934		if (CircleBufferCapacity(&channel->buffer) < length) {
935			CircleBufferDeinit(&channel->buffer);
936			CircleBufferInit(&channel->buffer, toPow2(length << 1));
937		}
938	}
939
940	ssize_t read = CircleBufferWrite(&channel->buffer, data, length);
941	if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) {
942		_flushBuffer(context);
943	}
944	return read;
945}
946
947struct mCore* mVideoLogCoreFind(struct VFile* vf) {
948	if (!vf) {
949		return NULL;
950	}
951	struct mVideoLogHeader header = { { 0 } };
952	vf->seek(vf, 0, SEEK_SET);
953	ssize_t read = vf->read(vf, &header, sizeof(header));
954	if (read != sizeof(header)) {
955		return NULL;
956	}
957	if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
958		return NULL;
959	}
960	enum mPlatform platform;
961	LOAD_32LE(platform, 0, &header.platform);
962
963	const struct mVLDescriptor* descriptor;
964	for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) {
965		if (platform == descriptor->platform) {
966			break;
967		}
968	}
969	struct mCore* core = NULL;
970	if (descriptor->open) {
971		core = descriptor->open();
972	}
973	return core;
974}