all repos — mgba @ b1828dbc59b8a9cc080c8c7f9472c46a3d39e90c

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		context->nChannels = 0;
 617		return false;
 618	}
 619
 620	uint32_t flags;
 621	LOAD_32LE(flags, 0, &header.flags);
 622	if (flags & mVL_FLAG_HAS_INITIAL_STATE) {
 623		struct mVLBlockHeader header;
 624		if (!_readBlockHeader(context, &header)) {
 625			return false;
 626		}
 627		if (header.blockType != mVL_BLOCK_INITIAL_STATE || !header.length) {
 628			return false;
 629		}
 630		if (context->initialState) {
 631			mappedMemoryFree(context->initialState, context->initialStateSize);
 632			context->initialState = NULL;
 633			context->initialStateSize = 0;
 634		}
 635		if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
 636#ifdef USE_ZLIB
 637			struct VFile* vfm = VFileMemChunk(NULL, 0);
 638			if (!_decompress(vfm, context->backing, header.length)) {
 639				vfm->close(vfm);
 640				return false;
 641			}
 642			context->initialStateSize = vfm->size(vfm);
 643			context->initialState = anonymousMemoryMap(context->initialStateSize);
 644			void* mem = vfm->map(vfm, context->initialStateSize, MAP_READ);
 645			memcpy(context->initialState, mem, context->initialStateSize);
 646			vfm->unmap(vfm, mem, context->initialStateSize);
 647			vfm->close(vfm);
 648#else
 649			return false;
 650#endif
 651		} else {
 652			context->initialStateSize = header.length;
 653			context->initialState = anonymousMemoryMap(header.length);
 654			context->backing->read(context->backing, context->initialState, context->initialStateSize);
 655		}
 656	}
 657	return true;
 658}
 659
 660bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) {
 661	context->backing = vf;
 662
 663	if (!_readHeader(context)) {
 664		return false;
 665	}
 666
 667	off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
 668
 669	size_t i;
 670	for (i = 0; i < context->nChannels; ++i) {
 671		CircleBufferInit(&context->channels[i].injectedBuffer, BUFFER_BASE_SIZE);
 672		CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE);
 673		context->channels[i].bufferRemaining = 0;
 674		context->channels[i].currentPointer = pointer;
 675		context->channels[i].p = context;
 676#ifdef USE_ZLIB
 677		context->channels[i].inflating = false;
 678#endif
 679	}
 680	return true;
 681}
 682
 683#ifdef USE_ZLIB
 684static void _flushBufferCompressed(struct mVideoLogContext* context) {
 685	struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
 686	if (!CircleBufferSize(buffer)) {
 687		return;
 688	}
 689	struct VFile* vfm = VFileMemChunk(NULL, 0);
 690	struct VFile* src = VFileFIFO(buffer);
 691	_compress(vfm, src);
 692	src->close(src);
 693
 694	size_t size = vfm->size(vfm);
 695
 696	struct mVLBlockHeader header = { 0 };
 697	STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
 698	STORE_32LE(context->activeChannel, 0, &header.channelId);
 699	STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &header.flags);
 700	STORE_32LE(size, 0, &header.length);
 701
 702	context->backing->write(context->backing, &header, sizeof(header));
 703	_copyVf(context->backing, vfm);
 704	vfm->close(vfm);
 705}
 706#endif
 707
 708static void _flushBuffer(struct mVideoLogContext* context) {
 709#ifdef USE_ZLIB
 710	if (context->compression) {
 711		_flushBufferCompressed(context);
 712		return;
 713	}
 714#endif
 715
 716	struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
 717	if (!CircleBufferSize(buffer)) {
 718		return;
 719	}
 720	struct mVLBlockHeader header = { 0 };
 721	STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
 722	STORE_32LE(CircleBufferSize(buffer), 0, &header.length);
 723	STORE_32LE(context->activeChannel, 0, &header.channelId);
 724
 725	context->backing->write(context->backing, &header, sizeof(header));
 726
 727	uint8_t writeBuffer[0x800];
 728	while (CircleBufferSize(buffer)) {
 729		size_t read = CircleBufferRead(buffer, writeBuffer, sizeof(writeBuffer));
 730		context->backing->write(context->backing, writeBuffer, read);
 731	}
 732}
 733
 734void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* context, bool closeVF) {
 735	if (context->write) {
 736		_flushBuffer(context);
 737
 738		struct mVLBlockHeader header = { 0 };
 739		STORE_32LE(mVL_BLOCK_FOOTER, 0, &header.blockType);
 740		context->backing->write(context->backing, &header, sizeof(header));
 741	}
 742
 743	if (core) {
 744		core->endVideoLog(core);
 745	}
 746	if (context->initialState) {
 747		mappedMemoryFree(context->initialState, context->initialStateSize);
 748	}
 749
 750	size_t i;
 751	for (i = 0; i < context->nChannels; ++i) {
 752		CircleBufferDeinit(&context->channels[i].injectedBuffer);
 753		CircleBufferDeinit(&context->channels[i].buffer);
 754#ifdef USE_ZLIB
 755		if (context->channels[i].inflating) {
 756			inflateEnd(&context->channels[i].inflateStream);
 757			context->channels[i].inflating = false;
 758		}
 759#endif
 760	}
 761
 762	if (closeVF && context->backing) {
 763		context->backing->close(context->backing);
 764	}
 765
 766	free(context);
 767}
 768
 769void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core) {
 770	_readHeader(context);
 771	if (core) {
 772		size_t size = core->stateSize(core);
 773		if (size <= context->initialStateSize) {
 774			core->loadState(core, context->initialState);
 775		} else {
 776			void* extendedState = anonymousMemoryMap(size);
 777			memcpy(extendedState, context->initialState, context->initialStateSize);
 778			core->loadState(core, extendedState);
 779			mappedMemoryFree(extendedState, size);
 780		}
 781	}
 782
 783	off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
 784
 785	size_t i;
 786	for (i = 0; i < context->nChannels; ++i) {
 787		CircleBufferClear(&context->channels[i].injectedBuffer);
 788		CircleBufferClear(&context->channels[i].buffer);
 789		context->channels[i].bufferRemaining = 0;
 790		context->channels[i].currentPointer = pointer;
 791#ifdef USE_ZLIB
 792		if (context->channels[i].inflating) {
 793			inflateEnd(&context->channels[i].inflateStream);
 794			context->channels[i].inflating = false;
 795		}
 796#endif
 797	}
 798}
 799
 800void* mVideoLogContextInitialState(struct mVideoLogContext* context, size_t* size) {
 801	if (size) {
 802		*size = context->initialStateSize;
 803	}
 804	return context->initialState;
 805}
 806
 807int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
 808	if (context->nChannels >= mVL_MAX_CHANNELS) {
 809		return -1;
 810	}
 811	int chid = context->nChannels;
 812	++context->nChannels;
 813	context->channels[chid].p = context;
 814	CircleBufferInit(&context->channels[chid].injectedBuffer, BUFFER_BASE_SIZE);
 815	CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
 816	context->channels[chid].injecting = false;
 817	context->channels[chid].injectionPoint = LOGGER_INJECTION_IMMEDIATE;
 818	context->channels[chid].ignorePackets = 0;
 819	return chid;
 820}
 821
 822void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 823	struct mVideoLogChannel* channel = logger->dataContext;
 824	channel->injecting = true;
 825	mVideoLoggerRendererWriteVideoRegister(logger, address, value);
 826	channel->injecting = false;
 827}
 828
 829void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 830	struct mVideoLogChannel* channel = logger->dataContext;
 831	channel->injecting = true;
 832	mVideoLoggerRendererWritePalette(logger, address, value);
 833	channel->injecting = false;
 834}
 835
 836void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 837	struct mVideoLogChannel* channel = logger->dataContext;
 838	channel->injecting = true;
 839	mVideoLoggerRendererWriteOAM(logger, address, value);
 840	channel->injecting = false;
 841}
 842
 843#ifdef USE_ZLIB
 844static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
 845	uint8_t fbuffer[0x400];
 846	uint8_t zbuffer[0x800];
 847	size_t read = 0;
 848
 849	// TODO: Share with _decompress
 850	channel->inflateStream.avail_in = 0;
 851	while (length) {
 852		size_t thisWrite = sizeof(zbuffer);
 853		if (thisWrite > length) {
 854			thisWrite = length;
 855		}
 856
 857		size_t thisRead = 0;
 858		if (channel->inflating && channel->inflateStream.avail_in) {
 859			channel->inflateStream.next_out = zbuffer;
 860			channel->inflateStream.avail_out = thisWrite;
 861			thisRead = channel->inflateStream.avail_in;
 862		} else if (channel->bufferRemaining) {
 863			thisRead = sizeof(fbuffer);
 864			if (thisRead > channel->bufferRemaining) {
 865				thisRead = channel->bufferRemaining;
 866			}
 867
 868			thisRead = vf->read(vf, fbuffer, thisRead);
 869			if (thisRead <= 0) {
 870				break;
 871			}
 872
 873			channel->inflateStream.next_in = fbuffer;
 874			channel->inflateStream.avail_in = thisRead;
 875			channel->inflateStream.next_out = zbuffer;
 876			channel->inflateStream.avail_out = thisWrite;
 877
 878			if (!channel->inflating) {
 879				if (inflateInit(&channel->inflateStream) != Z_OK) {
 880					break;
 881				}
 882				channel->inflating = true;
 883			}
 884		} else {
 885			channel->inflateStream.next_in = Z_NULL;
 886			channel->inflateStream.avail_in = 0;
 887			channel->inflateStream.next_out = zbuffer;
 888			channel->inflateStream.avail_out = thisWrite;
 889		}
 890
 891		int ret = inflate(&channel->inflateStream, Z_NO_FLUSH);
 892
 893		if (channel->inflateStream.next_in != Z_NULL) {
 894			thisRead -= channel->inflateStream.avail_in;
 895			channel->currentPointer += thisRead;
 896			channel->bufferRemaining -= thisRead;
 897		}
 898
 899		if (ret != Z_OK) {
 900			inflateEnd(&channel->inflateStream);
 901			channel->inflating = false;
 902			if (ret != Z_STREAM_END) {
 903				break;
 904			}
 905		}
 906
 907		thisWrite = CircleBufferWrite(&channel->buffer, zbuffer, thisWrite - channel->inflateStream.avail_out);
 908		length -= thisWrite;
 909		read += thisWrite;
 910
 911		if (!channel->inflating) {
 912			break;
 913		}
 914	}
 915	return read;
 916}
 917#endif
 918
 919static void _readBuffer(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
 920	uint8_t buffer[0x800];
 921	while (length) {
 922		size_t thisRead = sizeof(buffer);
 923		if (thisRead > length) {
 924			thisRead = length;
 925		}
 926		thisRead = vf->read(vf, buffer, thisRead);
 927		if (thisRead <= 0) {
 928			return;
 929		}
 930		size_t thisWrite = CircleBufferWrite(&channel->buffer, buffer, thisRead);
 931		length -= thisWrite;
 932		channel->bufferRemaining -= thisWrite;
 933		channel->currentPointer += thisWrite;
 934		if (thisWrite < thisRead) {
 935			break;
 936		}
 937	}
 938}
 939
 940static bool _fillBuffer(struct mVideoLogContext* context, size_t channelId, size_t length) {
 941	struct mVideoLogChannel* channel = &context->channels[channelId];
 942	context->backing->seek(context->backing, channel->currentPointer, SEEK_SET);
 943	struct mVLBlockHeader header;
 944	while (length) {
 945		size_t bufferRemaining = channel->bufferRemaining;
 946		if (bufferRemaining) {
 947#ifdef USE_ZLIB
 948			if (channel->inflating) {
 949				length -= _readBufferCompressed(context->backing, channel, length);
 950				continue;
 951			}
 952#endif
 953			if (bufferRemaining > length) {
 954				bufferRemaining = length;
 955			}
 956
 957			_readBuffer(context->backing, channel, bufferRemaining);
 958			length -= bufferRemaining;
 959			continue;
 960		}
 961
 962		if (!_readBlockHeader(context, &header)) {
 963			return false;
 964		}
 965		if (header.blockType == mVL_BLOCK_FOOTER) {
 966			return true;
 967		}
 968		if (header.channelId != channelId || header.blockType != mVL_BLOCK_DATA) {
 969			context->backing->seek(context->backing, header.length, SEEK_CUR);
 970			continue;
 971		}
 972		channel->currentPointer = context->backing->seek(context->backing, 0, SEEK_CUR);
 973		if (!header.length) {
 974			continue;
 975		}
 976		channel->bufferRemaining = header.length;
 977
 978		if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
 979#ifdef USE_ZLIB
 980			length -= _readBufferCompressed(context->backing, channel, length);
 981#else
 982			return false;
 983#endif
 984		}
 985	}
 986	return true;
 987}
 988
 989static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length) {
 990	struct mVideoLogContext* context = channel->p;
 991	unsigned channelId = channel - context->channels;
 992	if (channelId >= mVL_MAX_CHANNELS) {
 993		return 0;
 994	}
 995	struct CircleBuffer* buffer = &channel->buffer;
 996	if (channel->injecting) {
 997		buffer = &channel->injectedBuffer;
 998	}
 999	if (CircleBufferSize(buffer) >= length) {
1000		return CircleBufferRead(buffer, data, length);
1001	}
1002	ssize_t size = 0;
1003	if (CircleBufferSize(buffer)) {
1004		size = CircleBufferRead(buffer, data, CircleBufferSize(buffer));
1005		if (size <= 0) {
1006			return size;
1007		}
1008		data = (uint8_t*) data + size;
1009		length -= size;
1010	}
1011	if (channel->injecting || !_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
1012		return size;
1013	}
1014	size += CircleBufferRead(buffer, data, length);
1015	return size;
1016}
1017
1018static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length) {
1019	struct mVideoLogContext* context = channel->p;
1020	unsigned channelId = channel - context->channels;
1021	if (channelId >= mVL_MAX_CHANNELS) {
1022		return 0;
1023	}
1024	if (channelId != context->activeChannel) {
1025		_flushBuffer(context);
1026		context->activeChannel = channelId;
1027	}
1028	struct CircleBuffer* buffer = &channel->buffer;
1029	if (channel->injecting) {
1030		buffer = &channel->injectedBuffer;
1031	}
1032	if (CircleBufferCapacity(buffer) - CircleBufferSize(buffer) < length) {
1033		_flushBuffer(context);
1034		if (CircleBufferCapacity(buffer) < length) {
1035			CircleBufferDeinit(buffer);
1036			CircleBufferInit(buffer, toPow2(length << 1));
1037		}
1038	}
1039
1040	ssize_t read = CircleBufferWrite(buffer, data, length);
1041	if (CircleBufferCapacity(buffer) == CircleBufferSize(buffer)) {
1042		_flushBuffer(context);
1043	}
1044	return read;
1045}
1046
1047static const struct mVLDescriptor* _mVideoLogDescriptor(struct VFile* vf) {
1048	if (!vf) {
1049		return NULL;
1050	}
1051	struct mVideoLogHeader header = { { 0 } };
1052	vf->seek(vf, 0, SEEK_SET);
1053	ssize_t read = vf->read(vf, &header, sizeof(header));
1054	if (read != sizeof(header)) {
1055		return NULL;
1056	}
1057	if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
1058		return NULL;
1059	}
1060	enum mPlatform platform;
1061	LOAD_32LE(platform, 0, &header.platform);
1062
1063	const struct mVLDescriptor* descriptor;
1064	for (descriptor = &_descriptors[0]; descriptor->platform != mPLATFORM_NONE; ++descriptor) {
1065		if (platform == descriptor->platform) {
1066			break;
1067		}
1068	}
1069	if (descriptor->platform == mPLATFORM_NONE) {
1070		return NULL;
1071	}
1072	return descriptor;
1073}
1074
1075enum mPlatform mVideoLogIsCompatible(struct VFile* vf) {
1076	const struct mVLDescriptor* descriptor = _mVideoLogDescriptor(vf);
1077	if (descriptor) {
1078		return descriptor->platform;
1079	}
1080	return mPLATFORM_NONE;
1081}
1082
1083struct mCore* mVideoLogCoreFind(struct VFile* vf) {
1084	const struct mVLDescriptor* descriptor = _mVideoLogDescriptor(vf);
1085	if (!descriptor) {
1086		return NULL;
1087	}
1088	struct mCore* core = NULL;
1089	if (descriptor->open) {
1090		core = descriptor->open();
1091	}
1092	return core;
1093}