all repos — mgba @ e27ac1268a3957332d22ca1b670184d64000e42a

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