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->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->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}