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