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}