all repos — mgba @ b8e9f50c92a55c3d81b2f5f12bbc6d49a84f1a9c

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 = 1 << (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) {
 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	free(context);
 755}
 756
 757void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core) {
 758	_readHeader(context);
 759	if (core) {
 760		size_t size = core->stateSize(core);
 761		if (size <= context->initialStateSize) {
 762			core->loadState(core, context->initialState);
 763		} else {
 764			void* extendedState = anonymousMemoryMap(size);
 765			memcpy(extendedState, context->initialState, context->initialStateSize);
 766			core->loadState(core, extendedState);
 767			mappedMemoryFree(extendedState, size);
 768		}
 769	}
 770
 771	off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
 772
 773	size_t i;
 774	for (i = 0; i < context->nChannels; ++i) {
 775		CircleBufferClear(&context->channels[i].injectedBuffer);
 776		CircleBufferClear(&context->channels[i].buffer);
 777		context->channels[i].bufferRemaining = 0;
 778		context->channels[i].currentPointer = pointer;
 779#ifdef USE_ZLIB
 780		if (context->channels[i].inflating) {
 781			inflateEnd(&context->channels[i].inflateStream);
 782			context->channels[i].inflating = false;
 783		}
 784#endif
 785	}
 786}
 787
 788void* mVideoLogContextInitialState(struct mVideoLogContext* context, size_t* size) {
 789	if (size) {
 790		*size = context->initialStateSize;
 791	}
 792	return context->initialState;
 793}
 794
 795int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
 796	if (context->nChannels >= mVL_MAX_CHANNELS) {
 797		return -1;
 798	}
 799	int chid = context->nChannels;
 800	++context->nChannels;
 801	context->channels[chid].p = context;
 802	CircleBufferInit(&context->channels[chid].injectedBuffer, BUFFER_BASE_SIZE);
 803	CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
 804	context->channels[chid].injecting = false;
 805	context->channels[chid].injectionPoint = LOGGER_INJECTION_IMMEDIATE;
 806	context->channels[chid].ignorePackets = 0;
 807	return chid;
 808}
 809
 810void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 811	struct mVideoLogChannel* channel = logger->dataContext;
 812	channel->injecting = true;
 813	mVideoLoggerRendererWriteVideoRegister(logger, address, value);
 814	channel->injecting = false;
 815}
 816
 817void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 818	struct mVideoLogChannel* channel = logger->dataContext;
 819	channel->injecting = true;
 820	mVideoLoggerRendererWritePalette(logger, address, value);
 821	channel->injecting = false;
 822}
 823
 824void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 825	struct mVideoLogChannel* channel = logger->dataContext;
 826	channel->injecting = true;
 827	mVideoLoggerRendererWriteOAM(logger, address, value);
 828	channel->injecting = false;
 829}
 830
 831#ifdef USE_ZLIB
 832static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
 833	uint8_t fbuffer[0x400];
 834	uint8_t zbuffer[0x800];
 835	size_t read = 0;
 836
 837	// TODO: Share with _decompress
 838	channel->inflateStream.avail_in = 0;
 839	while (length) {
 840		size_t thisWrite = sizeof(zbuffer);
 841		if (thisWrite > length) {
 842			thisWrite = length;
 843		}
 844
 845		size_t thisRead = 0;
 846		if (channel->inflating && channel->inflateStream.avail_in) {
 847			channel->inflateStream.next_out = zbuffer;
 848			channel->inflateStream.avail_out = thisWrite;
 849			thisRead = channel->inflateStream.avail_in;
 850		} else if (channel->bufferRemaining) {
 851			thisRead = sizeof(fbuffer);
 852			if (thisRead > channel->bufferRemaining) {
 853				thisRead = channel->bufferRemaining;
 854			}
 855
 856			thisRead = vf->read(vf, fbuffer, thisRead);
 857			if (thisRead <= 0) {
 858				break;
 859			}
 860
 861			channel->inflateStream.next_in = fbuffer;
 862			channel->inflateStream.avail_in = thisRead;
 863			channel->inflateStream.next_out = zbuffer;
 864			channel->inflateStream.avail_out = thisWrite;
 865
 866			if (!channel->inflating) {
 867				if (inflateInit(&channel->inflateStream) != Z_OK) {
 868					break;
 869				}
 870				channel->inflating = true;
 871			}
 872		} else {
 873			channel->inflateStream.next_in = Z_NULL;
 874			channel->inflateStream.avail_in = 0;
 875			channel->inflateStream.next_out = zbuffer;
 876			channel->inflateStream.avail_out = thisWrite;
 877		}
 878
 879		int ret = inflate(&channel->inflateStream, Z_NO_FLUSH);
 880
 881		if (channel->inflateStream.next_in != Z_NULL) {
 882			thisRead -= channel->inflateStream.avail_in;
 883			channel->currentPointer += thisRead;
 884			channel->bufferRemaining -= thisRead;
 885		}
 886
 887		if (ret != Z_OK) {
 888			inflateEnd(&channel->inflateStream);
 889			channel->inflating = false;
 890			if (ret != Z_STREAM_END) {
 891				break;
 892			}
 893		}
 894
 895		thisWrite = CircleBufferWrite(&channel->buffer, zbuffer, thisWrite - channel->inflateStream.avail_out);
 896		length -= thisWrite;
 897		read += thisWrite;
 898
 899		if (!channel->inflating) {
 900			break;
 901		}
 902	}
 903	return read;
 904}
 905#endif
 906
 907static void _readBuffer(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
 908	uint8_t buffer[0x800];
 909	while (length) {
 910		size_t thisRead = sizeof(buffer);
 911		if (thisRead > length) {
 912			thisRead = length;
 913		}
 914		thisRead = vf->read(vf, buffer, thisRead);
 915		if (thisRead <= 0) {
 916			return;
 917		}
 918		size_t thisWrite = CircleBufferWrite(&channel->buffer, buffer, thisRead);
 919		length -= thisWrite;
 920		channel->bufferRemaining -= thisWrite;
 921		channel->currentPointer += thisWrite;
 922		if (thisWrite < thisRead) {
 923			break;
 924		}
 925	}
 926}
 927
 928static bool _fillBuffer(struct mVideoLogContext* context, size_t channelId, size_t length) {
 929	struct mVideoLogChannel* channel = &context->channels[channelId];
 930	context->backing->seek(context->backing, channel->currentPointer, SEEK_SET);
 931	struct mVLBlockHeader header;
 932	while (length) {
 933		size_t bufferRemaining = channel->bufferRemaining;
 934		if (bufferRemaining) {
 935#ifdef USE_ZLIB
 936			if (channel->inflating) {
 937				length -= _readBufferCompressed(context->backing, channel, length);
 938				continue;
 939			}
 940#endif
 941			if (bufferRemaining > length) {
 942				bufferRemaining = length;
 943			}
 944
 945			_readBuffer(context->backing, channel, bufferRemaining);
 946			length -= bufferRemaining;
 947			continue;
 948		}
 949
 950		if (!_readBlockHeader(context, &header)) {
 951			return false;
 952		}
 953		if (header.blockType == mVL_BLOCK_FOOTER) {
 954			return true;
 955		}
 956		if (header.channelId != channelId || header.blockType != mVL_BLOCK_DATA) {
 957			context->backing->seek(context->backing, header.length, SEEK_CUR);
 958			continue;
 959		}
 960		channel->currentPointer = context->backing->seek(context->backing, 0, SEEK_CUR);
 961		if (!header.length) {
 962			continue;
 963		}
 964		channel->bufferRemaining = header.length;
 965
 966		if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
 967#ifdef USE_ZLIB
 968			length -= _readBufferCompressed(context->backing, channel, length);
 969#else
 970			return false;
 971#endif
 972		}
 973	}
 974	return true;
 975}
 976
 977static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length) {
 978	struct mVideoLogContext* context = channel->p;
 979	unsigned channelId = channel - context->channels;
 980	if (channelId >= mVL_MAX_CHANNELS) {
 981		return 0;
 982	}
 983	struct CircleBuffer* buffer = &channel->buffer;
 984	if (channel->injecting) {
 985		buffer = &channel->injectedBuffer;
 986	}
 987	if (CircleBufferSize(buffer) >= length) {
 988		return CircleBufferRead(buffer, data, length);
 989	}
 990	ssize_t size = 0;
 991	if (CircleBufferSize(buffer)) {
 992		size = CircleBufferRead(buffer, data, CircleBufferSize(buffer));
 993		if (size <= 0) {
 994			return size;
 995		}
 996		data = (uint8_t*) data + size;
 997		length -= size;
 998	}
 999	if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
1000		return size;
1001	}
1002	size += CircleBufferRead(buffer, data, length);
1003	return size;
1004}
1005
1006static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length) {
1007	struct mVideoLogContext* context = channel->p;
1008	unsigned channelId = channel - context->channels;
1009	if (channelId >= mVL_MAX_CHANNELS) {
1010		return 0;
1011	}
1012	if (channelId != context->activeChannel) {
1013		_flushBuffer(context);
1014		context->activeChannel = channelId;
1015	}
1016	struct CircleBuffer* buffer = &channel->buffer;
1017	if (channel->injecting) {
1018		buffer = &channel->injectedBuffer;
1019	}
1020	if (CircleBufferCapacity(buffer) - CircleBufferSize(buffer) < length) {
1021		_flushBuffer(context);
1022		if (CircleBufferCapacity(buffer) < length) {
1023			CircleBufferDeinit(buffer);
1024			CircleBufferInit(buffer, toPow2(length << 1));
1025		}
1026	}
1027
1028	ssize_t read = CircleBufferWrite(buffer, data, length);
1029	if (CircleBufferCapacity(buffer) == CircleBufferSize(buffer)) {
1030		_flushBuffer(context);
1031	}
1032	return read;
1033}
1034
1035static const struct mVLDescriptor* _mVideoLogDescriptor(struct VFile* vf) {
1036	if (!vf) {
1037		return NULL;
1038	}
1039	struct mVideoLogHeader header = { { 0 } };
1040	vf->seek(vf, 0, SEEK_SET);
1041	ssize_t read = vf->read(vf, &header, sizeof(header));
1042	if (read != sizeof(header)) {
1043		return NULL;
1044	}
1045	if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
1046		return NULL;
1047	}
1048	enum mPlatform platform;
1049	LOAD_32LE(platform, 0, &header.platform);
1050
1051	const struct mVLDescriptor* descriptor;
1052	for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) {
1053		if (platform == descriptor->platform) {
1054			break;
1055		}
1056	}
1057	if (descriptor->platform == PLATFORM_NONE) {
1058		return NULL;
1059	}
1060	return descriptor;
1061}
1062
1063enum mPlatform mVideoLogIsCompatible(struct VFile* vf) {
1064	const struct mVLDescriptor* descriptor = _mVideoLogDescriptor(vf);
1065	if (descriptor) {
1066		return descriptor->platform;
1067	}
1068	return PLATFORM_NONE;
1069}
1070
1071struct mCore* mVideoLogCoreFind(struct VFile* vf) {
1072	const struct mVLDescriptor* descriptor = _mVideoLogDescriptor(vf);
1073	if (!descriptor) {
1074		return NULL;
1075	}
1076	struct mCore* core = NULL;
1077	if (descriptor->open) {
1078		core = descriptor->open();
1079	}
1080	return core;
1081}