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