all repos — mgba @ 3cce95b287c589bce3b371a1677c01afd3adf0ef

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