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