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