all repos — mgba @ 746ee657d70899000daf34730ad15af5c9baac6c

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