all repos — mgba @ de9bff4a29db25554e5234ff788617ed46a9436c

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}
262
263void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger) {
264	struct mVideoLoggerDirtyInfo dirty = {
265		DIRTY_FRAME,
266		0,
267		0,
268		0xDEADBEEF,
269	};
270	logger->writeData(logger, &dirty, sizeof(dirty));
271}
272
273void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uint32_t offset, uint32_t length, const void* data) {
274	struct mVideoLoggerDirtyInfo dirty = {
275		DIRTY_BUFFER,
276		bufferId,
277		offset,
278		length,
279	};
280	logger->writeData(logger, &dirty, sizeof(dirty));
281	logger->writeData(logger, data, length);
282}
283
284bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) {
285	struct mVideoLoggerDirtyInfo item = {0};
286	while (logger->readData(logger, &item, sizeof(item), block)) {
287		switch (item.type) {
288		case DIRTY_REGISTER:
289		case DIRTY_PALETTE:
290		case DIRTY_OAM:
291		case DIRTY_VRAM:
292		case DIRTY_SCANLINE:
293		case DIRTY_FLUSH:
294		case DIRTY_FRAME:
295		case DIRTY_RANGE:
296		case DIRTY_BUFFER:
297			if (!logger->parsePacket(logger, &item)) {
298				return true;
299			}
300			break;
301		default:
302			return false;
303		}
304	}
305	return !block;
306}
307
308static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
309	struct mVideoLogChannel* channel = logger->dataContext;
310	return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
311}
312
313static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
314	UNUSED(logger);
315	UNUSED(data);
316	UNUSED(length);
317	return false;
318}
319
320static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) {
321	UNUSED(block);
322	struct mVideoLogChannel* channel = logger->dataContext;
323	return mVideoLoggerReadChannel(channel, data, length) == (ssize_t) length;
324}
325
326#ifdef USE_ZLIB
327static void _copyVf(struct VFile* dest, struct VFile* src) {
328	size_t size = src->size(src);
329	void* mem = src->map(src, size, MAP_READ);
330	dest->write(dest, mem, size);
331	src->unmap(src, mem, size);
332}
333
334static void _compress(struct VFile* dest, struct VFile* src) {
335	uint8_t writeBuffer[0x800];
336	uint8_t compressBuffer[0x400];
337	z_stream zstr;
338	zstr.zalloc = Z_NULL;
339	zstr.zfree = Z_NULL;
340	zstr.opaque = Z_NULL;
341	zstr.avail_in = 0;
342	zstr.avail_out = sizeof(compressBuffer);
343	zstr.next_out = (Bytef*) compressBuffer;
344	if (deflateInit(&zstr, 9) != Z_OK) {
345		return;
346	}
347
348	while (true) {
349		size_t read = src->read(src, writeBuffer, sizeof(writeBuffer));
350		if (!read) {
351			break;
352		}
353		zstr.avail_in = read;
354		zstr.next_in = (Bytef*) writeBuffer;
355		while (zstr.avail_in) {
356			if (deflate(&zstr, Z_NO_FLUSH) == Z_STREAM_ERROR) {
357				break;
358			}
359			dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out);
360			zstr.avail_out = sizeof(compressBuffer);
361			zstr.next_out = (Bytef*) compressBuffer;
362		}
363	}
364
365	do {
366		zstr.avail_out = sizeof(compressBuffer);
367		zstr.next_out = (Bytef*) compressBuffer;
368		zstr.avail_in = 0;
369		int ret = deflate(&zstr, Z_FINISH);
370		if (ret == Z_STREAM_ERROR) {
371			break;
372		}
373		dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out);
374	} while (sizeof(compressBuffer) - zstr.avail_out);
375}
376
377static bool _decompress(struct VFile* dest, struct VFile* src, size_t compressedLength) {
378	uint8_t fbuffer[0x400];
379	uint8_t zbuffer[0x800];
380	z_stream zstr;
381	zstr.zalloc = Z_NULL;
382	zstr.zfree = Z_NULL;
383	zstr.opaque = Z_NULL;
384	zstr.avail_in = 0;
385	zstr.avail_out = sizeof(zbuffer);
386	zstr.next_out = (Bytef*) zbuffer;
387	bool started = false;
388
389	while (true) {
390		size_t thisWrite = sizeof(zbuffer);
391		size_t thisRead = 0;
392		if (zstr.avail_in) {
393			zstr.next_out = zbuffer;
394			zstr.avail_out = thisWrite;
395			thisRead = zstr.avail_in;
396		} else if (compressedLength) {
397			thisRead = sizeof(fbuffer);
398			if (thisRead > compressedLength) {
399				thisRead = compressedLength;
400			}
401
402			thisRead = src->read(src, fbuffer, thisRead);
403			if (thisRead <= 0) {
404				break;
405			}
406
407			zstr.next_in = fbuffer;
408			zstr.avail_in = thisRead;
409			zstr.next_out = zbuffer;
410			zstr.avail_out = thisWrite;
411
412			if (!started) {
413				if (inflateInit(&zstr) != Z_OK) {
414					break;
415				}
416				started = true;
417			}
418		} else {
419			zstr.next_in = Z_NULL;
420			zstr.avail_in = 0;
421			zstr.next_out = zbuffer;
422			zstr.avail_out = thisWrite;
423		}
424
425		int ret = inflate(&zstr, Z_NO_FLUSH);
426
427		if (zstr.next_in != Z_NULL) {
428			thisRead -= zstr.avail_in;
429			compressedLength -= thisRead;
430		}
431
432		if (ret != Z_OK) {
433			inflateEnd(&zstr);
434			started = false;
435			if (ret != Z_STREAM_END) {
436				break;
437			}
438		}
439
440		thisWrite = dest->write(dest, zbuffer, thisWrite - zstr.avail_out);
441
442		if (!started) {
443			break;
444		}
445	}
446	return !compressedLength;
447}
448#endif
449
450void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId) {
451	if (channelId >= mVL_MAX_CHANNELS) {
452		return;
453	}
454	logger->dataContext = &context->channels[channelId];
455}
456
457struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) {
458	struct mVideoLogContext* context = malloc(sizeof(*context));
459	memset(context, 0, sizeof(*context));
460
461	context->write = !!core;
462	context->initialStateSize = 0;
463	context->initialState = NULL;
464
465	if (core) {
466		context->initialStateSize = core->stateSize(core);
467		context->initialState = anonymousMemoryMap(context->initialStateSize);
468		core->saveState(core, context->initialState);
469		core->startVideoLog(core, context);
470	}
471
472	context->activeChannel = 0;
473	return context;
474}
475
476void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* vf) {
477	context->backing = vf;
478	vf->truncate(vf, 0);
479	vf->seek(vf, 0, SEEK_SET);
480}
481
482void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) {
483	struct mVideoLogHeader header = { { 0 } };
484	memcpy(header.magic, mVL_MAGIC, sizeof(header.magic));
485	enum mPlatform platform = core->platform(core);
486	STORE_32LE(platform, 0, &header.platform);
487	STORE_32LE(context->nChannels, 0, &header.nChannels);
488
489	uint32_t flags = 0;
490	if (context->initialState) {
491		flags |= mVL_FLAG_HAS_INITIAL_STATE;
492	}
493	STORE_32LE(flags, 0, &header.flags);
494	context->backing->write(context->backing, &header, sizeof(header));
495	if (context->initialState) {
496		struct mVLBlockHeader chheader = { 0 };
497		STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType);
498#ifdef USE_ZLIB
499		STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags);
500
501		struct VFile* vfm = VFileMemChunk(NULL, 0);
502		struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize);
503		_compress(vfm, src);
504		src->close(src);
505		STORE_32LE(vfm->size(vfm), 0, &chheader.length);
506		context->backing->write(context->backing, &chheader, sizeof(chheader));
507		_copyVf(context->backing, vfm);
508		vfm->close(vfm);
509#else
510		STORE_32LE(context->initialStateSize, 0, &chheader.length);
511		context->backing->write(context->backing, &chheader, sizeof(chheader));
512		context->backing->write(context->backing, context->initialState, context->initialStateSize);
513#endif
514	}
515
516 	size_t i;
517	for (i = 0; i < context->nChannels; ++i) {
518		struct mVLBlockHeader chheader = { 0 };
519		STORE_32LE(mVL_BLOCK_CHANNEL_HEADER, 0, &chheader.blockType);
520		STORE_32LE(i, 0, &chheader.channelId);
521		context->backing->write(context->backing, &chheader, sizeof(chheader));
522	}
523}
524
525bool _readBlockHeader(struct mVideoLogContext* context, struct mVLBlockHeader* header) {
526	struct mVLBlockHeader buffer;
527	if (context->backing->read(context->backing, &buffer, sizeof(buffer)) != sizeof(buffer)) {
528		return false;
529	}
530	LOAD_32LE(header->blockType, 0, &buffer.blockType);
531	LOAD_32LE(header->length, 0, &buffer.length);
532	LOAD_32LE(header->channelId, 0, &buffer.channelId);
533	LOAD_32LE(header->flags, 0, &buffer.flags);
534
535	if (header->length > MAX_BLOCK_SIZE) {
536		// Pre-emptively reject blocks that are too big.
537		// If we encounter one, the file is probably corrupted.
538		return false;
539	}
540	return true;
541}
542
543bool _readHeader(struct mVideoLogContext* context) {
544	struct mVideoLogHeader header;
545	context->backing->seek(context->backing, 0, SEEK_SET);
546	if (context->backing->read(context->backing, &header, sizeof(header)) != sizeof(header)) {
547		return false;
548	}
549	if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
550		return false;
551	}
552
553	LOAD_32LE(context->nChannels, 0, &header.nChannels);
554	if (context->nChannels > mVL_MAX_CHANNELS) {
555		return false;
556	}
557
558	uint32_t flags;
559	LOAD_32LE(flags, 0, &header.flags);
560	if (flags & mVL_FLAG_HAS_INITIAL_STATE) {
561		struct mVLBlockHeader header;
562		if (!_readBlockHeader(context, &header)) {
563			return false;
564		}
565		if (header.blockType != mVL_BLOCK_INITIAL_STATE || !header.length) {
566			return false;
567		}
568		if (context->initialState) {
569			mappedMemoryFree(context->initialState, context->initialStateSize);
570			context->initialState = NULL;
571			context->initialStateSize = 0;
572		}
573		if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
574#ifdef USE_ZLIB
575			struct VFile* vfm = VFileMemChunk(NULL, 0);
576			if (!_decompress(vfm, context->backing, header.length)) {
577				vfm->close(vfm);
578				return false;
579			}
580			context->initialStateSize = vfm->size(vfm);
581			context->initialState = anonymousMemoryMap(context->initialStateSize);
582			void* mem = vfm->map(vfm, context->initialStateSize, MAP_READ);
583			memcpy(context->initialState, mem, context->initialStateSize);
584			vfm->unmap(vfm, mem, context->initialStateSize);
585			vfm->close(vfm);
586#else
587			return false;
588#endif
589		} else {
590			context->initialStateSize = header.length;
591			context->initialState = anonymousMemoryMap(header.length);
592			context->backing->read(context->backing, context->initialState, context->initialStateSize);
593		}
594	}
595	return true;
596}
597
598bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) {
599	context->backing = vf;
600
601	if (!_readHeader(context)) {
602		return false;
603	}
604
605	off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
606
607	size_t i;
608	for (i = 0; i < context->nChannels; ++i) {
609		CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE);
610		context->channels[i].bufferRemaining = 0;
611		context->channels[i].currentPointer = pointer;
612		context->channels[i].p = context;
613#ifdef USE_ZLIB
614		context->channels[i].inflating = false;
615#endif
616	}
617	return true;
618}
619
620#ifdef USE_ZLIB
621static void _flushBufferCompressed(struct mVideoLogContext* context) {
622	struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
623	if (!CircleBufferSize(buffer)) {
624		return;
625	}
626	struct VFile* vfm = VFileMemChunk(NULL, 0);
627	struct VFile* src = VFileFIFO(buffer);
628	_compress(vfm, src);
629	src->close(src);
630
631	size_t size = vfm->size(vfm);
632
633	struct mVLBlockHeader header = { 0 };
634	STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
635	STORE_32LE(context->activeChannel, 0, &header.channelId);
636	STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &header.flags);
637	STORE_32LE(size, 0, &header.length);
638
639	context->backing->write(context->backing, &header, sizeof(header));
640	_copyVf(context->backing, vfm);
641	vfm->close(vfm);
642}
643#endif
644
645static void _flushBuffer(struct mVideoLogContext* context) {
646#ifdef USE_ZLIB
647	// TODO: Make optional
648	_flushBufferCompressed(context);
649	return;
650#endif
651
652	struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
653	if (!CircleBufferSize(buffer)) {
654		return;
655	}
656	struct mVLBlockHeader header = { 0 };
657	STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
658	STORE_32LE(CircleBufferSize(buffer), 0, &header.length);
659	STORE_32LE(context->activeChannel, 0, &header.channelId);
660
661	context->backing->write(context->backing, &header, sizeof(header));
662
663	uint8_t writeBuffer[0x800];
664	while (CircleBufferSize(buffer)) {
665		size_t read = CircleBufferRead(buffer, writeBuffer, sizeof(writeBuffer));
666		context->backing->write(context->backing, writeBuffer, read);
667	}
668}
669
670void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* context) {
671	if (context->write) {
672		_flushBuffer(context);
673
674		struct mVLBlockHeader header = { 0 };
675		STORE_32LE(mVL_BLOCK_FOOTER, 0, &header.blockType);
676		context->backing->write(context->backing, &header, sizeof(header));
677	}
678
679	if (core) {
680		core->endVideoLog(core);
681	}
682	if (context->initialState) {
683		mappedMemoryFree(context->initialState, context->initialStateSize);
684	}
685
686	size_t i;
687	for (i = 0; i < context->nChannels; ++i) {
688		CircleBufferDeinit(&context->channels[i].buffer);
689#ifdef USE_ZLIB
690		if (context->channels[i].inflating) {
691			inflateEnd(&context->channels[i].inflateStream);
692			context->channels[i].inflating = false;
693		}
694#endif
695	}
696
697	free(context);
698}
699
700void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core) {
701	_readHeader(context);
702	if (core) {
703		size_t size = core->stateSize(core);
704		if (size <= context->initialStateSize) {
705			core->loadState(core, context->initialState);
706		} else {
707			void* extendedState = anonymousMemoryMap(size);
708			memcpy(extendedState, context->initialState, context->initialStateSize);
709			core->loadState(core, extendedState);
710			mappedMemoryFree(extendedState, size);
711		}
712	}
713
714	off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
715
716	size_t i;
717	for (i = 0; i < context->nChannels; ++i) {
718		CircleBufferClear(&context->channels[i].buffer);
719		context->channels[i].bufferRemaining = 0;
720		context->channels[i].currentPointer = pointer;
721#ifdef USE_ZLIB
722		if (context->channels[i].inflating) {
723			inflateEnd(&context->channels[i].inflateStream);
724			context->channels[i].inflating = false;
725		}
726#endif
727	}
728}
729
730void* mVideoLogContextInitialState(struct mVideoLogContext* context, size_t* size) {
731	if (size) {
732		*size = context->initialStateSize;
733	}
734	return context->initialState;
735}
736
737int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
738	if (context->nChannels >= mVL_MAX_CHANNELS) {
739		return -1;
740	}
741	int chid = context->nChannels;
742	++context->nChannels;
743	context->channels[chid].p = context;
744	CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
745	return chid;
746}
747
748#ifdef USE_ZLIB
749static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
750	uint8_t fbuffer[0x400];
751	uint8_t zbuffer[0x800];
752	size_t read = 0;
753
754	// TODO: Share with _decompress
755	channel->inflateStream.avail_in = 0;
756	while (length) {
757		size_t thisWrite = sizeof(zbuffer);
758		if (thisWrite > length) {
759			thisWrite = length;
760		}
761
762		size_t thisRead = 0;
763		if (channel->inflating && channel->inflateStream.avail_in) {
764			channel->inflateStream.next_out = zbuffer;
765			channel->inflateStream.avail_out = thisWrite;
766			thisRead = channel->inflateStream.avail_in;
767		} else if (channel->bufferRemaining) {
768			thisRead = sizeof(fbuffer);
769			if (thisRead > channel->bufferRemaining) {
770				thisRead = channel->bufferRemaining;
771			}
772
773			thisRead = vf->read(vf, fbuffer, thisRead);
774			if (thisRead <= 0) {
775				break;
776			}
777
778			channel->inflateStream.next_in = fbuffer;
779			channel->inflateStream.avail_in = thisRead;
780			channel->inflateStream.next_out = zbuffer;
781			channel->inflateStream.avail_out = thisWrite;
782
783			if (!channel->inflating) {
784				if (inflateInit(&channel->inflateStream) != Z_OK) {
785					break;
786				}
787				channel->inflating = true;
788			}
789		} else {
790			channel->inflateStream.next_in = Z_NULL;
791			channel->inflateStream.avail_in = 0;
792			channel->inflateStream.next_out = zbuffer;
793			channel->inflateStream.avail_out = thisWrite;
794		}
795
796		int ret = inflate(&channel->inflateStream, Z_NO_FLUSH);
797
798		if (channel->inflateStream.next_in != Z_NULL) {
799			thisRead -= channel->inflateStream.avail_in;
800			channel->currentPointer += thisRead;
801			channel->bufferRemaining -= thisRead;
802		}
803
804		if (ret != Z_OK) {
805			inflateEnd(&channel->inflateStream);
806			channel->inflating = false;
807			if (ret != Z_STREAM_END) {
808				break;
809			}
810		}
811
812		thisWrite = CircleBufferWrite(&channel->buffer, zbuffer, thisWrite - channel->inflateStream.avail_out);
813		length -= thisWrite;
814		read += thisWrite;
815
816		if (!channel->inflating) {
817			break;
818		}
819	}
820	return read;
821}
822#endif
823
824static void _readBuffer(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
825	uint8_t buffer[0x800];
826	while (length) {
827		size_t thisRead = sizeof(buffer);
828		if (thisRead > length) {
829			thisRead = length;
830		}
831		thisRead = vf->read(vf, buffer, thisRead);
832		if (thisRead <= 0) {
833			return;
834		}
835		size_t thisWrite = CircleBufferWrite(&channel->buffer, buffer, thisRead);
836		length -= thisWrite;
837		channel->bufferRemaining -= thisWrite;
838		channel->currentPointer += thisWrite;
839		if (thisWrite < thisRead) {
840			break;
841		}
842	}
843}
844
845static bool _fillBuffer(struct mVideoLogContext* context, size_t channelId, size_t length) {
846	struct mVideoLogChannel* channel = &context->channels[channelId];
847	context->backing->seek(context->backing, channel->currentPointer, SEEK_SET);
848	struct mVLBlockHeader header;
849	while (length) {
850		size_t bufferRemaining = channel->bufferRemaining;
851		if (bufferRemaining) {
852#ifdef USE_ZLIB
853			if (channel->inflating) {
854				length -= _readBufferCompressed(context->backing, channel, length);
855				continue;
856			}
857#endif
858			if (bufferRemaining > length) {
859				bufferRemaining = length;
860			}
861
862			_readBuffer(context->backing, channel, bufferRemaining);
863			length -= bufferRemaining;
864			continue;
865		}
866
867		if (!_readBlockHeader(context, &header)) {
868			return false;
869		}
870		if (header.blockType == mVL_BLOCK_FOOTER) {
871			return true;
872		}
873		if (header.channelId != channelId || header.blockType != mVL_BLOCK_DATA) {
874			context->backing->seek(context->backing, header.length, SEEK_CUR);
875			continue;
876		}
877		channel->currentPointer = context->backing->seek(context->backing, 0, SEEK_CUR);
878		if (!header.length) {
879			continue;
880		}
881		channel->bufferRemaining = header.length;
882
883		if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
884#ifdef USE_ZLIB
885			length -= _readBufferCompressed(context->backing, channel, length);
886#else
887			return false;
888#endif
889		}
890	}
891	return true;
892}
893
894static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length) {
895	struct mVideoLogContext* context = channel->p;
896	unsigned channelId = channel - context->channels;
897	if (channelId >= mVL_MAX_CHANNELS) {
898		return 0;
899	}
900	if (CircleBufferSize(&channel->buffer) >= length) {
901		return CircleBufferRead(&channel->buffer, data, length);
902	}
903	ssize_t size = 0;
904	if (CircleBufferSize(&channel->buffer)) {
905		size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer));
906		if (size <= 0) {
907			return size;
908		}
909		data = (uint8_t*) data + size;
910		length -= size;
911	}
912	if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
913		return size;
914	}
915	size += CircleBufferRead(&channel->buffer, data, length);
916	return size;
917}
918
919static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length) {
920	struct mVideoLogContext* context = channel->p;
921	unsigned channelId = channel - context->channels;
922	if (channelId >= mVL_MAX_CHANNELS) {
923		return 0;
924	}
925	if (channelId != context->activeChannel) {
926		_flushBuffer(context);
927		context->activeChannel = channelId;
928	}
929	if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) {
930		_flushBuffer(context);
931		if (CircleBufferCapacity(&channel->buffer) < length) {
932			CircleBufferDeinit(&channel->buffer);
933			CircleBufferInit(&channel->buffer, toPow2(length << 1));
934		}
935	}
936
937	ssize_t read = CircleBufferWrite(&channel->buffer, data, length);
938	if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) {
939		_flushBuffer(context);
940	}
941	return read;
942}
943
944struct mCore* mVideoLogCoreFind(struct VFile* vf) {
945	if (!vf) {
946		return NULL;
947	}
948	struct mVideoLogHeader header = { { 0 } };
949	vf->seek(vf, 0, SEEK_SET);
950	ssize_t read = vf->read(vf, &header, sizeof(header));
951	if (read != sizeof(header)) {
952		return NULL;
953	}
954	if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
955		return NULL;
956	}
957	enum mPlatform platform;
958	LOAD_32LE(platform, 0, &header.platform);
959
960	const struct mVLDescriptor* descriptor;
961	for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) {
962		if (platform == descriptor->platform) {
963			break;
964		}
965	}
966	struct mCore* core = NULL;
967	if (descriptor->open) {
968		core = descriptor->open();
969	}
970	return core;
971}