all repos — mgba @ f82ef55517cf3edede6b5ccd2557f8a1099dbc09

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