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