all repos — mgba @ ca4d53f014d925207a278b8d70d843f3fd970192

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	free(context);
686}
687
688void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core) {
689	_readHeader(context);
690	if (core) {
691		size_t size = core->stateSize(core);
692		if (size <= context->initialStateSize) {
693			core->loadState(core, context->initialState);
694		} else {
695			void* extendedState = anonymousMemoryMap(size);
696			memcpy(extendedState, context->initialState, context->initialStateSize);
697			core->loadState(core, extendedState);
698			mappedMemoryFree(extendedState, size);
699		}
700	}
701
702	off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
703
704	size_t i;
705	for (i = 0; i < context->nChannels; ++i) {
706		CircleBufferClear(&context->channels[i].buffer);
707		context->channels[i].bufferRemaining = 0;
708		context->channels[i].currentPointer = pointer;
709#ifdef USE_ZLIB
710		if (context->channels[i].inflating) {
711			inflateEnd(&context->channels[i].inflateStream);
712			context->channels[i].inflating = false;
713		}
714#endif
715	}
716}
717
718void* mVideoLogContextInitialState(struct mVideoLogContext* context, size_t* size) {
719	if (size) {
720		*size = context->initialStateSize;
721	}
722	return context->initialState;
723}
724
725int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
726	if (context->nChannels >= mVL_MAX_CHANNELS) {
727		return -1;
728	}
729	int chid = context->nChannels;
730	++context->nChannels;
731	context->channels[chid].p = context;
732	CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
733	return chid;
734}
735
736#ifdef USE_ZLIB
737static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
738	uint8_t fbuffer[0x400];
739	uint8_t zbuffer[0x800];
740	size_t read = 0;
741
742	// TODO: Share with _decompress
743	channel->inflateStream.avail_in = 0;
744	while (length) {
745		size_t thisWrite = sizeof(zbuffer);
746		if (thisWrite > length) {
747			thisWrite = length;
748		}
749
750		size_t thisRead = 0;
751		if (channel->inflating && channel->inflateStream.avail_in) {
752			channel->inflateStream.next_out = zbuffer;
753			channel->inflateStream.avail_out = thisWrite;
754			thisRead = channel->inflateStream.avail_in;
755		} else if (channel->bufferRemaining) {
756			thisRead = sizeof(fbuffer);
757			if (thisRead > channel->bufferRemaining) {
758				thisRead = channel->bufferRemaining;
759			}
760
761			thisRead = vf->read(vf, fbuffer, thisRead);
762			if (thisRead <= 0) {
763				break;
764			}
765
766			channel->inflateStream.next_in = fbuffer;
767			channel->inflateStream.avail_in = thisRead;
768			channel->inflateStream.next_out = zbuffer;
769			channel->inflateStream.avail_out = thisWrite;
770
771			if (!channel->inflating) {
772				if (inflateInit(&channel->inflateStream) != Z_OK) {
773					break;
774				}
775				channel->inflating = true;
776			}
777		} else {
778			channel->inflateStream.next_in = Z_NULL;
779			channel->inflateStream.avail_in = 0;
780			channel->inflateStream.next_out = zbuffer;
781			channel->inflateStream.avail_out = thisWrite;
782		}
783
784		int ret = inflate(&channel->inflateStream, Z_NO_FLUSH);
785
786		if (channel->inflateStream.next_in != Z_NULL) {
787			thisRead -= channel->inflateStream.avail_in;
788			channel->currentPointer += thisRead;
789			channel->bufferRemaining -= thisRead;
790		}
791
792		if (ret != Z_OK) {
793			inflateEnd(&channel->inflateStream);
794			channel->inflating = false;
795			if (ret != Z_STREAM_END) {
796				break;
797			}
798		}
799
800		thisWrite = CircleBufferWrite(&channel->buffer, zbuffer, thisWrite - channel->inflateStream.avail_out);
801		length -= thisWrite;
802		read += thisWrite;
803
804		if (!channel->inflating) {
805			break;
806		}
807	}
808	return read;
809}
810#endif
811
812static void _readBuffer(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
813	uint8_t buffer[0x800];
814	while (length) {
815		size_t thisRead = sizeof(buffer);
816		if (thisRead > length) {
817			thisRead = length;
818		}
819		thisRead = vf->read(vf, buffer, thisRead);
820		if (thisRead <= 0) {
821			return;
822		}
823		size_t thisWrite = CircleBufferWrite(&channel->buffer, buffer, thisRead);
824		length -= thisWrite;
825		channel->bufferRemaining -= thisWrite;
826		channel->currentPointer += thisWrite;
827		if (thisWrite < thisRead) {
828			break;
829		}
830	}
831}
832
833static bool _fillBuffer(struct mVideoLogContext* context, size_t channelId, size_t length) {
834	struct mVideoLogChannel* channel = &context->channels[channelId];
835	context->backing->seek(context->backing, channel->currentPointer, SEEK_SET);
836	struct mVLBlockHeader header;
837	while (length) {
838		size_t bufferRemaining = channel->bufferRemaining;
839		if (bufferRemaining) {
840#ifdef USE_ZLIB
841			if (channel->inflating) {
842				length -= _readBufferCompressed(context->backing, channel, length);
843				continue;
844			}
845#endif
846			if (bufferRemaining > length) {
847				bufferRemaining = length;
848			}
849
850			_readBuffer(context->backing, channel, bufferRemaining);
851			length -= bufferRemaining;
852			continue;
853		}
854
855		if (!_readBlockHeader(context, &header)) {
856			return false;
857		}
858		if (header.blockType == mVL_BLOCK_FOOTER) {
859			return true;
860		}
861		if (header.channelId != channelId || header.blockType != mVL_BLOCK_DATA) {
862			context->backing->seek(context->backing, header.length, SEEK_CUR);
863			continue;
864		}
865		channel->currentPointer = context->backing->seek(context->backing, 0, SEEK_CUR);
866		if (!header.length) {
867			continue;
868		}
869		channel->bufferRemaining = header.length;
870
871		if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
872#ifdef USE_ZLIB
873			length -= _readBufferCompressed(context->backing, channel, length);
874#else
875			return false;
876#endif
877		}
878	}
879	return true;
880}
881
882static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length) {
883	struct mVideoLogContext* context = channel->p;
884	unsigned channelId = channel - context->channels;
885	if (channelId >= mVL_MAX_CHANNELS) {
886		return 0;
887	}
888	if (CircleBufferSize(&channel->buffer) >= length) {
889		return CircleBufferRead(&channel->buffer, data, length);
890	}
891	ssize_t size = 0;
892	if (CircleBufferSize(&channel->buffer)) {
893		size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer));
894		if (size <= 0) {
895			return size;
896		}
897		data = (uint8_t*) data + size;
898		length -= size;
899	}
900	if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
901		return size;
902	}
903	size += CircleBufferRead(&channel->buffer, data, length);
904	return size;
905}
906
907static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length) {
908	struct mVideoLogContext* context = channel->p;
909	unsigned channelId = channel - context->channels;
910	if (channelId >= mVL_MAX_CHANNELS) {
911		return 0;
912	}
913	if (channelId != context->activeChannel) {
914		_flushBuffer(context);
915		context->activeChannel = channelId;
916	}
917	if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) {
918		_flushBuffer(context);
919		if (CircleBufferCapacity(&channel->buffer) < length) {
920			CircleBufferDeinit(&channel->buffer);
921			CircleBufferInit(&channel->buffer, toPow2(length << 1));
922		}
923	}
924
925	ssize_t read = CircleBufferWrite(&channel->buffer, data, length);
926	if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) {
927		_flushBuffer(context);
928	}
929	return read;
930}
931
932struct mCore* mVideoLogCoreFind(struct VFile* vf) {
933	if (!vf) {
934		return NULL;
935	}
936	struct mVideoLogHeader header = { { 0 } };
937	vf->seek(vf, 0, SEEK_SET);
938	ssize_t read = vf->read(vf, &header, sizeof(header));
939	if (read != sizeof(header)) {
940		return NULL;
941	}
942	if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
943		return NULL;
944	}
945	enum mPlatform platform;
946	LOAD_32LE(platform, 0, &header.platform);
947
948	const struct mVLDescriptor* descriptor;
949	for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) {
950		if (platform == descriptor->platform) {
951			break;
952		}
953	}
954	struct mCore* core = NULL;
955	if (descriptor->open) {
956		core = descriptor->open();
957	}
958	return core;
959}