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