all repos — mgba @ 91491e704e21343f8d7a908d1a8119cafecd6de2

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