all repos — mgba @ 5665ac0316df4800bec0e942e4d18ba3fec59310

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 "video-logger.h"
  7
  8#include <mgba/core/core.h>
  9#include <mgba-util/memory.h>
 10#include <mgba-util/vfs.h>
 11
 12#ifdef M_CORE_GBA
 13#include <mgba/gba/core.h>
 14#endif
 15
 16const char mVL_MAGIC[] = "mVL\0";
 17
 18const static struct mVLDescriptor {
 19	enum mPlatform platform;
 20	struct mCore* (*open)(void);
 21} _descriptors[] = {
 22#ifdef M_CORE_GBA
 23	{ PLATFORM_GBA, GBAVideoLogPlayerCreate },
 24#endif
 25	{ PLATFORM_NONE, 0 }
 26};
 27
 28static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length);
 29static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length);
 30static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block);
 31
 32static inline size_t _roundUp(size_t value, int shift) {
 33	value += (1 << shift) - 1;
 34	return value >> shift;
 35}
 36
 37void mVideoLoggerRendererCreate(struct mVideoLogger* logger, bool readonly) {
 38	if (readonly) {
 39		logger->writeData = _writeNull;
 40	} else {
 41		logger->writeData = _writeData;
 42	}
 43	logger->readData = _readData;
 44	logger->vf = NULL;
 45}
 46
 47void mVideoLoggerRendererInit(struct mVideoLogger* logger) {
 48	logger->palette = anonymousMemoryMap(logger->paletteSize);
 49	logger->vram = anonymousMemoryMap(logger->vramSize);
 50	logger->oam = anonymousMemoryMap(logger->oamSize);
 51
 52	logger->vramDirtyBitmap = calloc(_roundUp(logger->vramSize, 17), sizeof(uint32_t));
 53	logger->oamDirtyBitmap = calloc(_roundUp(logger->oamSize, 6), sizeof(uint32_t));
 54}
 55
 56void mVideoLoggerRendererDeinit(struct mVideoLogger* logger) {
 57	mappedMemoryFree(logger->palette, logger->paletteSize);
 58	mappedMemoryFree(logger->vram, logger->vramSize);
 59	mappedMemoryFree(logger->oam, logger->oamSize);
 60
 61	free(logger->vramDirtyBitmap);
 62	free(logger->oamDirtyBitmap);
 63}
 64
 65void mVideoLoggerRendererReset(struct mVideoLogger* logger) {
 66	memset(logger->vramDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->vramSize, 17));
 67	memset(logger->oamDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->oamSize, 6));
 68}
 69
 70void mVideoLoggerRendererWriteVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 71	struct mVideoLoggerDirtyInfo dirty = {
 72		DIRTY_REGISTER,
 73		address,
 74		value,
 75		0xDEADBEEF,
 76	};
 77	logger->writeData(logger, &dirty, sizeof(dirty));
 78}
 79
 80void mVideoLoggerRendererWriteVRAM(struct mVideoLogger* logger, uint32_t address) {
 81	int bit = 1 << (address >> 12);
 82	if (logger->vramDirtyBitmap[address >> 17] & bit) {
 83		return;
 84	}
 85	logger->vramDirtyBitmap[address >> 17] |= bit;
 86}
 87
 88void mVideoLoggerRendererWritePalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 89	struct mVideoLoggerDirtyInfo dirty = {
 90		DIRTY_PALETTE,
 91		address,
 92		value,
 93		0xDEADBEEF,
 94	};
 95	logger->writeData(logger, &dirty, sizeof(dirty));
 96}
 97
 98void mVideoLoggerRendererWriteOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
 99	struct mVideoLoggerDirtyInfo dirty = {
100		DIRTY_OAM,
101		address,
102		value,
103		0xDEADBEEF,
104	};
105	logger->writeData(logger, &dirty, sizeof(dirty));
106}
107
108void mVideoLoggerRendererDrawScanline(struct mVideoLogger* logger, int y) {
109	size_t i;
110	for (i = 0; i < _roundUp(logger->vramSize, 17); ++i) {
111		if (logger->vramDirtyBitmap[i]) {
112			uint32_t bitmap = logger->vramDirtyBitmap[i];
113			logger->vramDirtyBitmap[i] = 0;
114			int j;
115			for (j = 0; j < mVL_MAX_CHANNELS; ++j) {
116				if (!(bitmap & (1 << j))) {
117					continue;
118				}
119				struct mVideoLoggerDirtyInfo dirty = {
120					DIRTY_VRAM,
121					j * 0x1000,
122					0xABCD,
123					0xDEADBEEF,
124				};
125				logger->writeData(logger, &dirty, sizeof(dirty));
126				logger->writeData(logger, logger->vramBlock(logger, j * 0x1000), 0x1000);
127			}
128		}
129	}
130	struct mVideoLoggerDirtyInfo dirty = {
131		DIRTY_SCANLINE,
132		y,
133		0,
134		0xDEADBEEF,
135	};
136	logger->writeData(logger, &dirty, sizeof(dirty));
137}
138
139void mVideoLoggerRendererFlush(struct mVideoLogger* logger) {
140	struct mVideoLoggerDirtyInfo dirty = {
141		DIRTY_FLUSH,
142		0,
143		0,
144		0xDEADBEEF,
145	};
146	logger->writeData(logger, &dirty, sizeof(dirty));
147}
148
149bool mVideoLoggerRendererRun(struct mVideoLogger* logger) {
150	struct mVideoLoggerDirtyInfo item = {0};
151	while (logger->readData(logger, &item, sizeof(item), false)) {
152		switch (item.type) {
153		case DIRTY_REGISTER:
154		case DIRTY_PALETTE:
155		case DIRTY_OAM:
156		case DIRTY_VRAM:
157		case DIRTY_SCANLINE:
158		case DIRTY_FLUSH:
159			if (!logger->parsePacket(logger, &item)) {
160				return true;
161			}
162			break;
163		default:
164			return false;
165		}
166	}
167	return false;
168}
169
170static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
171	return logger->vf->write(logger->vf, data, length) == (ssize_t) length;
172}
173
174static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
175	UNUSED(logger);
176	UNUSED(data);
177	UNUSED(length);
178	return false;
179}
180
181static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) {
182	return logger->vf->read(logger->vf, data, length) == (ssize_t) length;
183}
184
185struct mVideoLogContext* mVideoLoggerCreate(struct mCore* core) {
186	struct mVideoLogContext* context = malloc(sizeof(*context));
187	if (core) {
188		core->startVideoLog(core, context);
189	}
190	return context;
191}
192
193void mVideoLoggerDestroy(struct mCore* core, struct mVideoLogContext* context) {
194	if (core) {
195		core->endVideoLog(core);
196	}
197	free(context);
198}
199
200void mVideoLoggerWrite(struct mCore* core, struct mVideoLogContext* context, struct VFile* vf) {
201	struct mVideoLogHeader header = {{0}};
202	memcpy(header.magic, mVL_MAGIC, sizeof(mVL_MAGIC));
203
204	enum mPlatform platform = core->platform(core);
205	STORE_32LE(platform, 0, &header.platform);
206	STORE_32LE(context->nChannels, 0, &header.nChannels);
207
208	ssize_t pointer = vf->seek(vf, sizeof(header), SEEK_SET);
209	if (context->initialStateSize) {
210		ssize_t written = vf->write(vf, context->initialState, context->initialStateSize);
211		if (written > 0) {
212			STORE_32LE(pointer, 0, &header.initialStatePointer);
213			STORE_32LE(context->initialStateSize, 0, &header.initialStateSize);
214			pointer += written;
215		} else {
216			header.initialStatePointer = 0;
217		}
218	} else {
219		header.initialStatePointer = 0;
220	}
221
222	size_t i;
223	for (i = 0; i < context->nChannels && i < mVL_MAX_CHANNELS; ++i) {
224		struct VFile* channel = context->channels[i].channelData;
225		void* block = channel->map(channel, channel->size(channel), MAP_READ);
226
227		struct mVideoLogChannelHeader chHeader = {0};
228		STORE_32LE(context->channels[i].type, 0, &chHeader.type);
229		STORE_32LE(channel->size(channel), 0, &chHeader.channelSize);
230
231		if (context->channels[i].initialStateSize) {
232			ssize_t written = vf->write(vf, context->channels[i].initialState, context->channels[i].initialStateSize);
233			if (written > 0) {
234				STORE_32LE(pointer, 0, &chHeader.channelInitialStatePointer);
235				STORE_32LE(context->channels[i].initialStateSize, 0, &chHeader.channelInitialStateSize);
236				pointer += written;
237			} else {
238				chHeader.channelInitialStatePointer = 0;
239			}
240		}
241		STORE_32LE(pointer, 0, &header.channelPointers[i]);
242		ssize_t written = vf->write(vf, &chHeader, sizeof(chHeader));
243		if (written != sizeof(chHeader)) {
244			continue;
245		}
246		pointer += written;
247		written = vf->write(vf, block, channel->size(channel));
248		if (written != channel->size(channel)) {
249			break;
250		}
251		pointer += written;
252	}
253	vf->seek(vf, 0, SEEK_SET);
254	vf->write(vf, &header, sizeof(header));
255}
256
257struct mCore* mVideoLogCoreFind(struct VFile* vf) {
258	if (!vf) {
259		return NULL;
260	}
261	struct mVideoLogHeader header = {{0}};
262	vf->seek(vf, 0, SEEK_SET);
263	ssize_t read = vf->read(vf, &header, sizeof(header));
264	if (read != sizeof(header)) {
265		return NULL;
266	}
267	if (memcmp(header.magic, mVL_MAGIC, sizeof(mVL_MAGIC)) != 0) {
268		return NULL;
269	}
270	enum mPlatform platform;
271	LOAD_32LE(platform, 0, &header.platform);
272
273	const struct mVLDescriptor* descriptor;
274	for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) {
275		if (platform == descriptor->platform) {
276			break;
277		}
278	}
279	struct mCore* core = NULL;
280	if (descriptor->open) {
281		core = descriptor->open();
282	}
283	return core;
284}
285
286bool mVideoLogContextLoad(struct VFile* vf, struct mVideoLogContext* context) {
287	if (!vf) {
288		return false;
289	}
290	struct mVideoLogHeader header = {{0}};
291	vf->seek(vf, 0, SEEK_SET);
292	ssize_t read = vf->read(vf, &header, sizeof(header));
293	if (read != sizeof(header)) {
294		return false;
295	}
296	if (memcmp(header.magic, mVL_MAGIC, sizeof(mVL_MAGIC)) != 0) {
297		return false;
298	}
299
300	// TODO: Error check
301	uint32_t initialStatePointer;
302	uint32_t initialStateSize;
303	LOAD_32LE(initialStatePointer, 0, &header.initialStatePointer);
304	LOAD_32LE(initialStateSize, 0, &header.initialStateSize);
305	void* initialState = anonymousMemoryMap(initialStateSize);
306	vf->read(vf, initialState, initialStateSize);
307	context->initialState = initialState;
308	context->initialStateSize = initialStateSize;
309
310	uint32_t nChannels;
311	LOAD_32LE(nChannels, 0, &header.nChannels);
312	context->nChannels = nChannels;
313
314	size_t i;
315	for (i = 0; i < nChannels && i < mVL_MAX_CHANNELS; ++i) {
316		uint32_t channelPointer;
317		LOAD_32LE(channelPointer, 0, &header.channelPointers[i]);
318		vf->seek(vf, channelPointer, SEEK_SET);
319
320		struct mVideoLogChannelHeader chHeader;
321		vf->read(vf, &chHeader, sizeof(chHeader));
322
323		LOAD_32LE(context->channels[i].type, 0, &chHeader.type);
324		LOAD_32LE(context->channels[i].initialStateSize, 0, &chHeader.channelInitialStateSize);
325
326		LOAD_32LE(channelPointer, 0, &chHeader.channelInitialStatePointer);
327		if (channelPointer) {
328			off_t position = vf->seek(vf, 0, SEEK_CUR);
329			vf->seek(vf, channelPointer, SEEK_SET);
330
331			context->channels[i].initialState = anonymousMemoryMap(context->channels[i].initialStateSize);
332			vf->read(vf, context->channels[i].initialState, context->channels[i].initialStateSize);
333			vf->seek(vf, position, SEEK_SET);
334		}
335
336		uint32_t channelSize;
337		LOAD_32LE(channelSize, 0, &chHeader.channelSize);
338		struct VFile* vfm = VFileMemChunk(0, channelSize);
339
340		while (channelSize) {
341			uint8_t buffer[2048];
342			ssize_t toRead = channelSize;
343			if (toRead > (ssize_t) sizeof(buffer)) {
344				toRead = sizeof(buffer);
345			}
346			toRead = vf->read(vf, buffer, toRead);
347			if (toRead > 0) {
348				channelSize -= toRead;
349			} else {
350				break;
351			}
352			vfm->write(vfm, buffer, toRead);
353		}
354		context->channels[i].channelData = vfm;
355	}
356
357	for (; i < mVL_MAX_CHANNELS; ++i) {
358		context->channels[i].channelData = NULL;
359	}
360	return true;
361}