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 buffer = {0};
300 struct mVideoLoggerDirtyInfo item = {0};
301 while (logger->readData(logger, &buffer, sizeof(buffer), block)) {
302 LOAD_32LE(item.type, 0, &buffer.type);
303 if (ignorePackets & (1 << item.type)) {
304 continue;
305 }
306 LOAD_32LE(item.address, 0, &buffer.address);
307 LOAD_32LE(item.value, 0, &buffer.value);
308 LOAD_32LE(item.value2, 0, &buffer.value2);
309 switch (item.type) {
310 case DIRTY_SCANLINE:
311 if (channel && channel->injectionPoint == LOGGER_INJECTION_FIRST_SCANLINE && !channel->injecting && item.address == 0) {
312 mVideoLoggerRendererRunInjected(logger);
313 ignorePackets = channel->ignorePackets;
314 }
315 // Fall through
316 case DIRTY_REGISTER:
317 case DIRTY_PALETTE:
318 case DIRTY_OAM:
319 case DIRTY_VRAM:
320 case DIRTY_FLUSH:
321 case DIRTY_FRAME:
322 case DIRTY_RANGE:
323 case DIRTY_BUFFER:
324 if (!logger->parsePacket(logger, &item)) {
325 return true;
326 }
327 break;
328 default:
329 return false;
330 }
331 }
332 return !block;
333}
334
335bool mVideoLoggerRendererRunInjected(struct mVideoLogger* logger) {
336 struct mVideoLogChannel* channel = logger->dataContext;
337 channel->injecting = true;
338 bool res = mVideoLoggerRendererRun(logger, false);
339 channel->injecting = false;
340 return res;
341}
342
343void mVideoLoggerInjectionPoint(struct mVideoLogger* logger, enum mVideoLoggerInjectionPoint injectionPoint) {
344 struct mVideoLogChannel* channel = logger->dataContext;
345 channel->injectionPoint = injectionPoint;
346}
347
348void mVideoLoggerIgnoreAfterInjection(struct mVideoLogger* logger, uint32_t mask) {
349 struct mVideoLogChannel* channel = logger->dataContext;
350 channel->ignorePackets = mask;
351}
352
353static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
354 struct mVideoLogChannel* channel = logger->dataContext;
355 return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
356}
357
358static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
359 struct mVideoLogChannel* channel = logger->dataContext;
360 if (channel->injecting) {
361 return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
362 }
363 return false;
364}
365
366static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) {
367 UNUSED(block);
368 struct mVideoLogChannel* channel = logger->dataContext;
369 return mVideoLoggerReadChannel(channel, data, length) == (ssize_t) length;
370}
371
372#ifdef USE_ZLIB
373static void _copyVf(struct VFile* dest, struct VFile* src) {
374 size_t size = src->size(src);
375 void* mem = src->map(src, size, MAP_READ);
376 dest->write(dest, mem, size);
377 src->unmap(src, mem, size);
378}
379
380static void _compress(struct VFile* dest, struct VFile* src) {
381 uint8_t writeBuffer[0x800];
382 uint8_t compressBuffer[0x400];
383 z_stream zstr;
384 zstr.zalloc = Z_NULL;
385 zstr.zfree = Z_NULL;
386 zstr.opaque = Z_NULL;
387 zstr.avail_in = 0;
388 zstr.avail_out = sizeof(compressBuffer);
389 zstr.next_out = (Bytef*) compressBuffer;
390 if (deflateInit(&zstr, 9) != Z_OK) {
391 return;
392 }
393
394 while (true) {
395 size_t read = src->read(src, writeBuffer, sizeof(writeBuffer));
396 if (!read) {
397 break;
398 }
399 zstr.avail_in = read;
400 zstr.next_in = (Bytef*) writeBuffer;
401 while (zstr.avail_in) {
402 if (deflate(&zstr, Z_NO_FLUSH) == Z_STREAM_ERROR) {
403 break;
404 }
405 dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out);
406 zstr.avail_out = sizeof(compressBuffer);
407 zstr.next_out = (Bytef*) compressBuffer;
408 }
409 }
410
411 do {
412 zstr.avail_out = sizeof(compressBuffer);
413 zstr.next_out = (Bytef*) compressBuffer;
414 zstr.avail_in = 0;
415 int ret = deflate(&zstr, Z_FINISH);
416 if (ret == Z_STREAM_ERROR) {
417 break;
418 }
419 dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out);
420 } while (sizeof(compressBuffer) - zstr.avail_out);
421}
422
423static bool _decompress(struct VFile* dest, struct VFile* src, size_t compressedLength) {
424 uint8_t fbuffer[0x400];
425 uint8_t zbuffer[0x800];
426 z_stream zstr;
427 zstr.zalloc = Z_NULL;
428 zstr.zfree = Z_NULL;
429 zstr.opaque = Z_NULL;
430 zstr.avail_in = 0;
431 zstr.avail_out = sizeof(zbuffer);
432 zstr.next_out = (Bytef*) zbuffer;
433 bool started = false;
434
435 while (true) {
436 size_t thisWrite = sizeof(zbuffer);
437 size_t thisRead = 0;
438 if (zstr.avail_in) {
439 zstr.next_out = zbuffer;
440 zstr.avail_out = thisWrite;
441 thisRead = zstr.avail_in;
442 } else if (compressedLength) {
443 thisRead = sizeof(fbuffer);
444 if (thisRead > compressedLength) {
445 thisRead = compressedLength;
446 }
447
448 thisRead = src->read(src, fbuffer, thisRead);
449 if (thisRead <= 0) {
450 break;
451 }
452
453 zstr.next_in = fbuffer;
454 zstr.avail_in = thisRead;
455 zstr.next_out = zbuffer;
456 zstr.avail_out = thisWrite;
457
458 if (!started) {
459 if (inflateInit(&zstr) != Z_OK) {
460 break;
461 }
462 started = true;
463 }
464 } else {
465 zstr.next_in = Z_NULL;
466 zstr.avail_in = 0;
467 zstr.next_out = zbuffer;
468 zstr.avail_out = thisWrite;
469 }
470
471 int ret = inflate(&zstr, Z_NO_FLUSH);
472
473 if (zstr.next_in != Z_NULL) {
474 thisRead -= zstr.avail_in;
475 compressedLength -= thisRead;
476 }
477
478 if (ret != Z_OK) {
479 inflateEnd(&zstr);
480 started = false;
481 if (ret != Z_STREAM_END) {
482 break;
483 }
484 }
485
486 thisWrite = dest->write(dest, zbuffer, thisWrite - zstr.avail_out);
487
488 if (!started) {
489 break;
490 }
491 }
492 return !compressedLength;
493}
494#endif
495
496void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId) {
497 if (channelId >= mVL_MAX_CHANNELS) {
498 return;
499 }
500 logger->dataContext = &context->channels[channelId];
501}
502
503struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) {
504 struct mVideoLogContext* context = malloc(sizeof(*context));
505 memset(context, 0, sizeof(*context));
506
507 context->write = !!core;
508 context->initialStateSize = 0;
509 context->initialState = NULL;
510
511#ifdef USE_ZLIB
512 context->compression = true;
513#else
514 context->compression = false;
515#endif
516
517 if (core) {
518 context->initialStateSize = core->stateSize(core);
519 context->initialState = anonymousMemoryMap(context->initialStateSize);
520 core->saveState(core, context->initialState);
521 core->startVideoLog(core, context);
522 }
523
524 context->activeChannel = 0;
525 return context;
526}
527
528void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* vf) {
529 context->backing = vf;
530 vf->truncate(vf, 0);
531 vf->seek(vf, 0, SEEK_SET);
532}
533
534void mVideoLogContextSetCompression(struct mVideoLogContext* context, bool compression) {
535 context->compression = compression;
536}
537
538void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) {
539 struct mVideoLogHeader header = { { 0 } };
540 memcpy(header.magic, mVL_MAGIC, sizeof(header.magic));
541 enum mPlatform platform = core->platform(core);
542 STORE_32LE(platform, 0, &header.platform);
543 STORE_32LE(context->nChannels, 0, &header.nChannels);
544
545 uint32_t flags = 0;
546 if (context->initialState) {
547 flags |= mVL_FLAG_HAS_INITIAL_STATE;
548 }
549 STORE_32LE(flags, 0, &header.flags);
550 context->backing->write(context->backing, &header, sizeof(header));
551 if (context->initialState) {
552 struct mVLBlockHeader chheader = { 0 };
553 STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType);
554#ifdef USE_ZLIB
555 if (context->compression) {
556 STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags);
557
558 struct VFile* vfm = VFileMemChunk(NULL, 0);
559 struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize);
560 _compress(vfm, src);
561 src->close(src);
562 STORE_32LE(vfm->size(vfm), 0, &chheader.length);
563 context->backing->write(context->backing, &chheader, sizeof(chheader));
564 _copyVf(context->backing, vfm);
565 vfm->close(vfm);
566 } else
567#endif
568 {
569 STORE_32LE(context->initialStateSize, 0, &chheader.length);
570 context->backing->write(context->backing, &chheader, sizeof(chheader));
571 context->backing->write(context->backing, context->initialState, context->initialStateSize);
572 }
573 }
574
575 size_t i;
576 for (i = 0; i < context->nChannels; ++i) {
577 struct mVLBlockHeader chheader = { 0 };
578 STORE_32LE(mVL_BLOCK_CHANNEL_HEADER, 0, &chheader.blockType);
579 STORE_32LE(i, 0, &chheader.channelId);
580 context->backing->write(context->backing, &chheader, sizeof(chheader));
581 }
582}
583
584bool _readBlockHeader(struct mVideoLogContext* context, struct mVLBlockHeader* header) {
585 struct mVLBlockHeader buffer;
586 if (context->backing->read(context->backing, &buffer, sizeof(buffer)) != sizeof(buffer)) {
587 return false;
588 }
589 LOAD_32LE(header->blockType, 0, &buffer.blockType);
590 LOAD_32LE(header->length, 0, &buffer.length);
591 LOAD_32LE(header->channelId, 0, &buffer.channelId);
592 LOAD_32LE(header->flags, 0, &buffer.flags);
593
594 if (header->length > MAX_BLOCK_SIZE) {
595 // Pre-emptively reject blocks that are too big.
596 // If we encounter one, the file is probably corrupted.
597 return false;
598 }
599 return true;
600}
601
602bool _readHeader(struct mVideoLogContext* context) {
603 struct mVideoLogHeader header;
604 context->backing->seek(context->backing, 0, SEEK_SET);
605 if (context->backing->read(context->backing, &header, sizeof(header)) != sizeof(header)) {
606 return false;
607 }
608 if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
609 return false;
610 }
611
612 LOAD_32LE(context->nChannels, 0, &header.nChannels);
613 if (context->nChannels > mVL_MAX_CHANNELS) {
614 return false;
615 }
616
617 uint32_t flags;
618 LOAD_32LE(flags, 0, &header.flags);
619 if (flags & mVL_FLAG_HAS_INITIAL_STATE) {
620 struct mVLBlockHeader header;
621 if (!_readBlockHeader(context, &header)) {
622 return false;
623 }
624 if (header.blockType != mVL_BLOCK_INITIAL_STATE || !header.length) {
625 return false;
626 }
627 if (context->initialState) {
628 mappedMemoryFree(context->initialState, context->initialStateSize);
629 context->initialState = NULL;
630 context->initialStateSize = 0;
631 }
632 if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
633#ifdef USE_ZLIB
634 struct VFile* vfm = VFileMemChunk(NULL, 0);
635 if (!_decompress(vfm, context->backing, header.length)) {
636 vfm->close(vfm);
637 return false;
638 }
639 context->initialStateSize = vfm->size(vfm);
640 context->initialState = anonymousMemoryMap(context->initialStateSize);
641 void* mem = vfm->map(vfm, context->initialStateSize, MAP_READ);
642 memcpy(context->initialState, mem, context->initialStateSize);
643 vfm->unmap(vfm, mem, context->initialStateSize);
644 vfm->close(vfm);
645#else
646 return false;
647#endif
648 } else {
649 context->initialStateSize = header.length;
650 context->initialState = anonymousMemoryMap(header.length);
651 context->backing->read(context->backing, context->initialState, context->initialStateSize);
652 }
653 }
654 return true;
655}
656
657bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) {
658 context->backing = vf;
659
660 if (!_readHeader(context)) {
661 return false;
662 }
663
664 off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
665
666 size_t i;
667 for (i = 0; i < context->nChannels; ++i) {
668 CircleBufferInit(&context->channels[i].injectedBuffer, BUFFER_BASE_SIZE);
669 CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE);
670 context->channels[i].bufferRemaining = 0;
671 context->channels[i].currentPointer = pointer;
672 context->channels[i].p = context;
673#ifdef USE_ZLIB
674 context->channels[i].inflating = false;
675#endif
676 }
677 return true;
678}
679
680#ifdef USE_ZLIB
681static void _flushBufferCompressed(struct mVideoLogContext* context) {
682 struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
683 if (!CircleBufferSize(buffer)) {
684 return;
685 }
686 struct VFile* vfm = VFileMemChunk(NULL, 0);
687 struct VFile* src = VFileFIFO(buffer);
688 _compress(vfm, src);
689 src->close(src);
690
691 size_t size = vfm->size(vfm);
692
693 struct mVLBlockHeader header = { 0 };
694 STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
695 STORE_32LE(context->activeChannel, 0, &header.channelId);
696 STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &header.flags);
697 STORE_32LE(size, 0, &header.length);
698
699 context->backing->write(context->backing, &header, sizeof(header));
700 _copyVf(context->backing, vfm);
701 vfm->close(vfm);
702}
703#endif
704
705static void _flushBuffer(struct mVideoLogContext* context) {
706#ifdef USE_ZLIB
707 if (context->compression) {
708 _flushBufferCompressed(context);
709 return;
710 }
711#endif
712
713 struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
714 if (!CircleBufferSize(buffer)) {
715 return;
716 }
717 struct mVLBlockHeader header = { 0 };
718 STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
719 STORE_32LE(CircleBufferSize(buffer), 0, &header.length);
720 STORE_32LE(context->activeChannel, 0, &header.channelId);
721
722 context->backing->write(context->backing, &header, sizeof(header));
723
724 uint8_t writeBuffer[0x800];
725 while (CircleBufferSize(buffer)) {
726 size_t read = CircleBufferRead(buffer, writeBuffer, sizeof(writeBuffer));
727 context->backing->write(context->backing, writeBuffer, read);
728 }
729}
730
731void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* context, bool closeVF) {
732 if (context->write) {
733 _flushBuffer(context);
734
735 struct mVLBlockHeader header = { 0 };
736 STORE_32LE(mVL_BLOCK_FOOTER, 0, &header.blockType);
737 context->backing->write(context->backing, &header, sizeof(header));
738 }
739
740 if (core) {
741 core->endVideoLog(core);
742 }
743 if (context->initialState) {
744 mappedMemoryFree(context->initialState, context->initialStateSize);
745 }
746
747 size_t i;
748 for (i = 0; i < context->nChannels; ++i) {
749 CircleBufferDeinit(&context->channels[i].injectedBuffer);
750 CircleBufferDeinit(&context->channels[i].buffer);
751#ifdef USE_ZLIB
752 if (context->channels[i].inflating) {
753 inflateEnd(&context->channels[i].inflateStream);
754 context->channels[i].inflating = false;
755 }
756#endif
757 }
758
759 if (closeVF && context->backing) {
760 context->backing->close(context->backing);
761 }
762
763 free(context);
764}
765
766void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core) {
767 _readHeader(context);
768 if (core) {
769 size_t size = core->stateSize(core);
770 if (size <= context->initialStateSize) {
771 core->loadState(core, context->initialState);
772 } else {
773 void* extendedState = anonymousMemoryMap(size);
774 memcpy(extendedState, context->initialState, context->initialStateSize);
775 core->loadState(core, extendedState);
776 mappedMemoryFree(extendedState, size);
777 }
778 }
779
780 off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
781
782 size_t i;
783 for (i = 0; i < context->nChannels; ++i) {
784 CircleBufferClear(&context->channels[i].injectedBuffer);
785 CircleBufferClear(&context->channels[i].buffer);
786 context->channels[i].bufferRemaining = 0;
787 context->channels[i].currentPointer = pointer;
788#ifdef USE_ZLIB
789 if (context->channels[i].inflating) {
790 inflateEnd(&context->channels[i].inflateStream);
791 context->channels[i].inflating = false;
792 }
793#endif
794 }
795}
796
797void* mVideoLogContextInitialState(struct mVideoLogContext* context, size_t* size) {
798 if (size) {
799 *size = context->initialStateSize;
800 }
801 return context->initialState;
802}
803
804int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
805 if (context->nChannels >= mVL_MAX_CHANNELS) {
806 return -1;
807 }
808 int chid = context->nChannels;
809 ++context->nChannels;
810 context->channels[chid].p = context;
811 CircleBufferInit(&context->channels[chid].injectedBuffer, BUFFER_BASE_SIZE);
812 CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
813 context->channels[chid].injecting = false;
814 context->channels[chid].injectionPoint = LOGGER_INJECTION_IMMEDIATE;
815 context->channels[chid].ignorePackets = 0;
816 return chid;
817}
818
819void mVideoLoggerInjectVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
820 struct mVideoLogChannel* channel = logger->dataContext;
821 channel->injecting = true;
822 mVideoLoggerRendererWriteVideoRegister(logger, address, value);
823 channel->injecting = false;
824}
825
826void mVideoLoggerInjectPalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
827 struct mVideoLogChannel* channel = logger->dataContext;
828 channel->injecting = true;
829 mVideoLoggerRendererWritePalette(logger, address, value);
830 channel->injecting = false;
831}
832
833void mVideoLoggerInjectOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
834 struct mVideoLogChannel* channel = logger->dataContext;
835 channel->injecting = true;
836 mVideoLoggerRendererWriteOAM(logger, address, value);
837 channel->injecting = false;
838}
839
840#ifdef USE_ZLIB
841static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
842 uint8_t fbuffer[0x400];
843 uint8_t zbuffer[0x800];
844 size_t read = 0;
845
846 // TODO: Share with _decompress
847 channel->inflateStream.avail_in = 0;
848 while (length) {
849 size_t thisWrite = sizeof(zbuffer);
850 if (thisWrite > length) {
851 thisWrite = length;
852 }
853
854 size_t thisRead = 0;
855 if (channel->inflating && channel->inflateStream.avail_in) {
856 channel->inflateStream.next_out = zbuffer;
857 channel->inflateStream.avail_out = thisWrite;
858 thisRead = channel->inflateStream.avail_in;
859 } else if (channel->bufferRemaining) {
860 thisRead = sizeof(fbuffer);
861 if (thisRead > channel->bufferRemaining) {
862 thisRead = channel->bufferRemaining;
863 }
864
865 thisRead = vf->read(vf, fbuffer, thisRead);
866 if (thisRead <= 0) {
867 break;
868 }
869
870 channel->inflateStream.next_in = fbuffer;
871 channel->inflateStream.avail_in = thisRead;
872 channel->inflateStream.next_out = zbuffer;
873 channel->inflateStream.avail_out = thisWrite;
874
875 if (!channel->inflating) {
876 if (inflateInit(&channel->inflateStream) != Z_OK) {
877 break;
878 }
879 channel->inflating = true;
880 }
881 } else {
882 channel->inflateStream.next_in = Z_NULL;
883 channel->inflateStream.avail_in = 0;
884 channel->inflateStream.next_out = zbuffer;
885 channel->inflateStream.avail_out = thisWrite;
886 }
887
888 int ret = inflate(&channel->inflateStream, Z_NO_FLUSH);
889
890 if (channel->inflateStream.next_in != Z_NULL) {
891 thisRead -= channel->inflateStream.avail_in;
892 channel->currentPointer += thisRead;
893 channel->bufferRemaining -= thisRead;
894 }
895
896 if (ret != Z_OK) {
897 inflateEnd(&channel->inflateStream);
898 channel->inflating = false;
899 if (ret != Z_STREAM_END) {
900 break;
901 }
902 }
903
904 thisWrite = CircleBufferWrite(&channel->buffer, zbuffer, thisWrite - channel->inflateStream.avail_out);
905 length -= thisWrite;
906 read += thisWrite;
907
908 if (!channel->inflating) {
909 break;
910 }
911 }
912 return read;
913}
914#endif
915
916static void _readBuffer(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
917 uint8_t buffer[0x800];
918 while (length) {
919 size_t thisRead = sizeof(buffer);
920 if (thisRead > length) {
921 thisRead = length;
922 }
923 thisRead = vf->read(vf, buffer, thisRead);
924 if (thisRead <= 0) {
925 return;
926 }
927 size_t thisWrite = CircleBufferWrite(&channel->buffer, buffer, thisRead);
928 length -= thisWrite;
929 channel->bufferRemaining -= thisWrite;
930 channel->currentPointer += thisWrite;
931 if (thisWrite < thisRead) {
932 break;
933 }
934 }
935}
936
937static bool _fillBuffer(struct mVideoLogContext* context, size_t channelId, size_t length) {
938 struct mVideoLogChannel* channel = &context->channels[channelId];
939 context->backing->seek(context->backing, channel->currentPointer, SEEK_SET);
940 struct mVLBlockHeader header;
941 while (length) {
942 size_t bufferRemaining = channel->bufferRemaining;
943 if (bufferRemaining) {
944#ifdef USE_ZLIB
945 if (channel->inflating) {
946 length -= _readBufferCompressed(context->backing, channel, length);
947 continue;
948 }
949#endif
950 if (bufferRemaining > length) {
951 bufferRemaining = length;
952 }
953
954 _readBuffer(context->backing, channel, bufferRemaining);
955 length -= bufferRemaining;
956 continue;
957 }
958
959 if (!_readBlockHeader(context, &header)) {
960 return false;
961 }
962 if (header.blockType == mVL_BLOCK_FOOTER) {
963 return true;
964 }
965 if (header.channelId != channelId || header.blockType != mVL_BLOCK_DATA) {
966 context->backing->seek(context->backing, header.length, SEEK_CUR);
967 continue;
968 }
969 channel->currentPointer = context->backing->seek(context->backing, 0, SEEK_CUR);
970 if (!header.length) {
971 continue;
972 }
973 channel->bufferRemaining = header.length;
974
975 if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
976#ifdef USE_ZLIB
977 length -= _readBufferCompressed(context->backing, channel, length);
978#else
979 return false;
980#endif
981 }
982 }
983 return true;
984}
985
986static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length) {
987 struct mVideoLogContext* context = channel->p;
988 unsigned channelId = channel - context->channels;
989 if (channelId >= mVL_MAX_CHANNELS) {
990 return 0;
991 }
992 struct CircleBuffer* buffer = &channel->buffer;
993 if (channel->injecting) {
994 buffer = &channel->injectedBuffer;
995 }
996 if (CircleBufferSize(buffer) >= length) {
997 return CircleBufferRead(buffer, data, length);
998 }
999 ssize_t size = 0;
1000 if (CircleBufferSize(buffer)) {
1001 size = CircleBufferRead(buffer, data, CircleBufferSize(buffer));
1002 if (size <= 0) {
1003 return size;
1004 }
1005 data = (uint8_t*) data + size;
1006 length -= size;
1007 }
1008 if (channel->injecting || !_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
1009 return size;
1010 }
1011 size += CircleBufferRead(buffer, data, length);
1012 return size;
1013}
1014
1015static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length) {
1016 struct mVideoLogContext* context = channel->p;
1017 unsigned channelId = channel - context->channels;
1018 if (channelId >= mVL_MAX_CHANNELS) {
1019 return 0;
1020 }
1021 if (channelId != context->activeChannel) {
1022 _flushBuffer(context);
1023 context->activeChannel = channelId;
1024 }
1025 struct CircleBuffer* buffer = &channel->buffer;
1026 if (channel->injecting) {
1027 buffer = &channel->injectedBuffer;
1028 }
1029 if (CircleBufferCapacity(buffer) - CircleBufferSize(buffer) < length) {
1030 _flushBuffer(context);
1031 if (CircleBufferCapacity(buffer) < length) {
1032 CircleBufferDeinit(buffer);
1033 CircleBufferInit(buffer, toPow2(length << 1));
1034 }
1035 }
1036
1037 ssize_t read = CircleBufferWrite(buffer, data, length);
1038 if (CircleBufferCapacity(buffer) == CircleBufferSize(buffer)) {
1039 _flushBuffer(context);
1040 }
1041 return read;
1042}
1043
1044static const struct mVLDescriptor* _mVideoLogDescriptor(struct VFile* vf) {
1045 if (!vf) {
1046 return NULL;
1047 }
1048 struct mVideoLogHeader header = { { 0 } };
1049 vf->seek(vf, 0, SEEK_SET);
1050 ssize_t read = vf->read(vf, &header, sizeof(header));
1051 if (read != sizeof(header)) {
1052 return NULL;
1053 }
1054 if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
1055 return NULL;
1056 }
1057 enum mPlatform platform;
1058 LOAD_32LE(platform, 0, &header.platform);
1059
1060 const struct mVLDescriptor* descriptor;
1061 for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) {
1062 if (platform == descriptor->platform) {
1063 break;
1064 }
1065 }
1066 if (descriptor->platform == PLATFORM_NONE) {
1067 return NULL;
1068 }
1069 return descriptor;
1070}
1071
1072enum mPlatform mVideoLogIsCompatible(struct VFile* vf) {
1073 const struct mVLDescriptor* descriptor = _mVideoLogDescriptor(vf);
1074 if (descriptor) {
1075 return descriptor->platform;
1076 }
1077 return PLATFORM_NONE;
1078}
1079
1080struct mCore* mVideoLogCoreFind(struct VFile* vf) {
1081 const struct mVLDescriptor* descriptor = _mVideoLogDescriptor(vf);
1082 if (!descriptor) {
1083 return NULL;
1084 }
1085 struct mCore* core = NULL;
1086 if (descriptor->open) {
1087 core = descriptor->open();
1088 }
1089 return core;
1090}