all repos — mgba @ ffeb5cfe2779b5f0c633e13e15adef651658d160

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