all repos — mgba @ 026d71a1f53db910f3fb2c7998407acf5344f082

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