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 "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
26const char mVL_MAGIC[] = "mVL\0";
27
28const static struct mVLDescriptor {
29 enum mPlatform platform;
30 struct mCore* (*open)(void);
31} _descriptors[] = {
32#ifdef M_CORE_GBA
33 { PLATFORM_GBA, GBAVideoLogPlayerCreate },
34#endif
35#ifdef M_CORE_GB
36 { PLATFORM_GB, GBVideoLogPlayerCreate },
37#endif
38 { PLATFORM_NONE, 0 }
39};
40
41enum mVLBlockType {
42 mVL_BLOCK_DUMMY = 0,
43 mVL_BLOCK_INITIAL_STATE,
44 mVL_BLOCK_CHANNEL_HEADER,
45 mVL_BLOCK_DATA,
46 mVL_BLOCK_FOOTER = 0x784C566D
47};
48
49enum mVLHeaderFlag {
50 mVL_FLAG_HAS_INITIAL_STATE = 1
51};
52
53struct mVLBlockHeader {
54 uint32_t blockType;
55 uint32_t length;
56 uint32_t channelId;
57 uint32_t flags;
58};
59
60enum mVLBlockFlag {
61 mVL_FLAG_BLOCK_COMPRESSED = 1
62};
63
64struct mVideoLogHeader {
65 char magic[4];
66 uint32_t flags;
67 uint32_t platform;
68 uint32_t nChannels;
69};
70
71struct mVideoLogContext;
72struct mVideoLogChannel {
73 struct mVideoLogContext* p;
74
75 uint32_t type;
76 void* initialState;
77 size_t initialStateSize;
78
79 off_t currentPointer;
80 size_t bufferRemaining;
81#ifdef USE_ZLIB
82 bool inflating;
83 z_stream inflateStream;
84#endif
85
86 struct CircleBuffer buffer;
87};
88
89struct mVideoLogContext {
90 void* initialState;
91 size_t initialStateSize;
92 uint32_t nChannels;
93 struct mVideoLogChannel channels[mVL_MAX_CHANNELS];
94
95 bool write;
96 uint32_t activeChannel;
97 struct VFile* backing;
98};
99
100
101static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length);
102static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length);
103static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block);
104
105static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length);
106static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length);
107
108static inline size_t _roundUp(size_t value, int shift) {
109 value += (1 << shift) - 1;
110 return value >> shift;
111}
112
113void mVideoLoggerRendererCreate(struct mVideoLogger* logger, bool readonly) {
114 if (readonly) {
115 logger->writeData = _writeNull;
116 logger->block = true;
117 } else {
118 logger->writeData = _writeData;
119 }
120 logger->readData = _readData;
121 logger->dataContext = NULL;
122
123 logger->init = NULL;
124 logger->deinit = NULL;
125 logger->reset = NULL;
126
127 logger->lock = NULL;
128 logger->unlock = NULL;
129 logger->wait = NULL;
130 logger->wake = NULL;
131}
132
133void mVideoLoggerRendererInit(struct mVideoLogger* logger) {
134 logger->palette = anonymousMemoryMap(logger->paletteSize);
135 logger->vram = anonymousMemoryMap(logger->vramSize);
136 logger->oam = anonymousMemoryMap(logger->oamSize);
137
138 logger->vramDirtyBitmap = calloc(_roundUp(logger->vramSize, 17), sizeof(uint32_t));
139 logger->oamDirtyBitmap = calloc(_roundUp(logger->oamSize, 6), sizeof(uint32_t));
140
141 if (logger->init) {
142 logger->init(logger);
143 }
144}
145
146void mVideoLoggerRendererDeinit(struct mVideoLogger* logger) {
147 if (logger->deinit) {
148 logger->deinit(logger);
149 }
150
151 mappedMemoryFree(logger->palette, logger->paletteSize);
152 mappedMemoryFree(logger->vram, logger->vramSize);
153 mappedMemoryFree(logger->oam, logger->oamSize);
154
155 free(logger->vramDirtyBitmap);
156 free(logger->oamDirtyBitmap);
157}
158
159void mVideoLoggerRendererReset(struct mVideoLogger* logger) {
160 memset(logger->vramDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->vramSize, 17));
161 memset(logger->oamDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->oamSize, 6));
162
163 if (logger->reset) {
164 logger->reset(logger);
165 }
166}
167
168void mVideoLoggerRendererWriteVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
169 struct mVideoLoggerDirtyInfo dirty = {
170 DIRTY_REGISTER,
171 address,
172 value,
173 0xDEADBEEF,
174 };
175 logger->writeData(logger, &dirty, sizeof(dirty));
176}
177
178void mVideoLoggerRendererWriteVRAM(struct mVideoLogger* logger, uint32_t address) {
179 int bit = 1 << (address >> 12);
180 if (logger->vramDirtyBitmap[address >> 17] & bit) {
181 return;
182 }
183 logger->vramDirtyBitmap[address >> 17] |= bit;
184}
185
186void mVideoLoggerRendererWritePalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
187 struct mVideoLoggerDirtyInfo dirty = {
188 DIRTY_PALETTE,
189 address,
190 value,
191 0xDEADBEEF,
192 };
193 logger->writeData(logger, &dirty, sizeof(dirty));
194}
195
196void mVideoLoggerRendererWriteOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) {
197 struct mVideoLoggerDirtyInfo dirty = {
198 DIRTY_OAM,
199 address,
200 value,
201 0xDEADBEEF,
202 };
203 logger->writeData(logger, &dirty, sizeof(dirty));
204}
205
206static void _flushVRAM(struct mVideoLogger* logger) {
207 size_t i;
208 for (i = 0; i < _roundUp(logger->vramSize, 17); ++i) {
209 if (logger->vramDirtyBitmap[i]) {
210 uint32_t bitmap = logger->vramDirtyBitmap[i];
211 logger->vramDirtyBitmap[i] = 0;
212 int j;
213 for (j = 0; j < mVL_MAX_CHANNELS; ++j) {
214 if (!(bitmap & (1 << j))) {
215 continue;
216 }
217 struct mVideoLoggerDirtyInfo dirty = {
218 DIRTY_VRAM,
219 j * 0x1000,
220 0x1000,
221 0xDEADBEEF,
222 };
223 logger->writeData(logger, &dirty, sizeof(dirty));
224 logger->writeData(logger, logger->vramBlock(logger, j * 0x1000), 0x1000);
225 }
226 }
227 }
228}
229
230void mVideoLoggerRendererDrawScanline(struct mVideoLogger* logger, int y) {
231 _flushVRAM(logger);
232 struct mVideoLoggerDirtyInfo dirty = {
233 DIRTY_SCANLINE,
234 y,
235 0,
236 0xDEADBEEF,
237 };
238 logger->writeData(logger, &dirty, sizeof(dirty));
239}
240
241void mVideoLoggerRendererDrawRange(struct mVideoLogger* logger, int startX, int endX, int y) {
242 _flushVRAM(logger);
243 struct mVideoLoggerDirtyInfo dirty = {
244 DIRTY_RANGE,
245 y,
246 startX,
247 endX,
248 };
249 logger->writeData(logger, &dirty, sizeof(dirty));
250}
251
252void mVideoLoggerRendererFlush(struct mVideoLogger* logger) {
253 struct mVideoLoggerDirtyInfo dirty = {
254 DIRTY_FLUSH,
255 0,
256 0,
257 0xDEADBEEF,
258 };
259 logger->writeData(logger, &dirty, sizeof(dirty));
260}
261
262void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger) {
263 struct mVideoLoggerDirtyInfo dirty = {
264 DIRTY_FRAME,
265 0,
266 0,
267 0xDEADBEEF,
268 };
269 logger->writeData(logger, &dirty, sizeof(dirty));
270}
271
272void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uint32_t offset, uint32_t length, const void* data) {
273 struct mVideoLoggerDirtyInfo dirty = {
274 DIRTY_BUFFER,
275 bufferId,
276 offset,
277 length,
278 };
279 logger->writeData(logger, &dirty, sizeof(dirty));
280 logger->writeData(logger, data, length);
281}
282
283bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) {
284 struct mVideoLoggerDirtyInfo item = {0};
285 while (logger->readData(logger, &item, sizeof(item), block)) {
286 switch (item.type) {
287 case DIRTY_REGISTER:
288 case DIRTY_PALETTE:
289 case DIRTY_OAM:
290 case DIRTY_VRAM:
291 case DIRTY_SCANLINE:
292 case DIRTY_FLUSH:
293 case DIRTY_FRAME:
294 case DIRTY_RANGE:
295 case DIRTY_BUFFER:
296 if (!logger->parsePacket(logger, &item)) {
297 return true;
298 }
299 break;
300 default:
301 return false;
302 }
303 }
304 return !block;
305}
306
307static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) {
308 struct mVideoLogChannel* channel = logger->dataContext;
309 return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length;
310}
311
312static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) {
313 UNUSED(logger);
314 UNUSED(data);
315 UNUSED(length);
316 return false;
317}
318
319static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) {
320 UNUSED(block);
321 struct mVideoLogChannel* channel = logger->dataContext;
322 return mVideoLoggerReadChannel(channel, data, length) == (ssize_t) length;
323}
324
325void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId) {
326 if (channelId >= mVL_MAX_CHANNELS) {
327 return;
328 }
329 logger->dataContext = &context->channels[channelId];
330}
331
332struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) {
333 struct mVideoLogContext* context = malloc(sizeof(*context));
334 memset(context, 0, sizeof(*context));
335
336 context->write = !!core;
337
338 if (core) {
339 context->initialStateSize = core->stateSize(core);
340 context->initialState = anonymousMemoryMap(context->initialStateSize);
341 core->saveState(core, context->initialState);
342 core->startVideoLog(core, context);
343 }
344
345 context->activeChannel = 0;
346 return context;
347}
348
349void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* vf) {
350 context->backing = vf;
351 vf->truncate(vf, 0);
352 vf->seek(vf, 0, SEEK_SET);
353}
354
355void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) {
356 struct mVideoLogHeader header = { { 0 } };
357 memcpy(header.magic, mVL_MAGIC, sizeof(header.magic));
358 enum mPlatform platform = core->platform(core);
359 STORE_32LE(platform, 0, &header.platform);
360 STORE_32LE(context->nChannels, 0, &header.nChannels);
361
362 uint32_t flags = 0;
363 if (context->initialState) {
364 flags |= mVL_FLAG_HAS_INITIAL_STATE;
365 }
366 STORE_32LE(flags, 0, &header.flags);
367 context->backing->write(context->backing, &header, sizeof(header));
368 if (context->initialState) {
369 struct mVLBlockHeader chheader = { 0 };
370 STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType);
371 STORE_32LE(context->initialStateSize, 0, &chheader.length);
372 context->backing->write(context->backing, &chheader, sizeof(chheader));
373 context->backing->write(context->backing, context->initialState, context->initialStateSize);
374 }
375
376 size_t i;
377 for (i = 0; i < context->nChannels; ++i) {
378 struct mVLBlockHeader chheader = { 0 };
379 STORE_32LE(mVL_BLOCK_CHANNEL_HEADER, 0, &chheader.blockType);
380 STORE_32LE(i, 0, &chheader.channelId);
381 context->backing->write(context->backing, &chheader, sizeof(chheader));
382 }
383}
384
385bool _readBlockHeader(struct mVideoLogContext* context, struct mVLBlockHeader* header) {
386 struct mVLBlockHeader buffer;
387 if (context->backing->read(context->backing, &buffer, sizeof(buffer)) != sizeof(buffer)) {
388 return false;
389 }
390 LOAD_32LE(header->blockType, 0, &buffer.blockType);
391 LOAD_32LE(header->length, 0, &buffer.length);
392 LOAD_32LE(header->channelId, 0, &buffer.channelId);
393 LOAD_32LE(header->flags, 0, &buffer.flags);
394 return true;
395}
396
397bool _readHeader(struct mVideoLogContext* context) {
398 struct mVideoLogHeader header;
399 context->backing->seek(context->backing, 0, SEEK_SET);
400 if (context->backing->read(context->backing, &header, sizeof(header)) != sizeof(header)) {
401 return false;
402 }
403 if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
404 return false;
405 }
406
407 LOAD_32LE(context->nChannels, 0, &header.nChannels);
408 if (context->nChannels > mVL_MAX_CHANNELS) {
409 return false;
410 }
411
412 uint32_t flags;
413 LOAD_32LE(flags, 0, &header.flags);
414 if (flags & mVL_FLAG_HAS_INITIAL_STATE) {
415 struct mVLBlockHeader header;
416 if (!_readBlockHeader(context, &header)) {
417 return false;
418 }
419 if (header.blockType != mVL_BLOCK_INITIAL_STATE) {
420 return false;
421 }
422 context->initialStateSize = header.length;
423 context->initialState = anonymousMemoryMap(header.length);
424 context->backing->read(context->backing, context->initialState, context->initialStateSize);
425 }
426 return true;
427}
428
429bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) {
430 context->backing = vf;
431
432 if (!_readHeader(context)) {
433 return false;
434 }
435
436 off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
437
438 size_t i;
439 for (i = 0; i < context->nChannels; ++i) {
440 CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE);
441 context->channels[i].bufferRemaining = 0;
442 context->channels[i].currentPointer = pointer;
443 context->channels[i].p = context;
444#ifdef USE_ZLIB
445 context->channels[i].inflating = false;
446#endif
447 }
448 return true;
449}
450
451#ifdef USE_ZLIB
452static void _flushBufferCompressed(struct mVideoLogContext* context) {
453 struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
454 if (!CircleBufferSize(buffer)) {
455 return;
456 }
457 uint8_t writeBuffer[0x400];
458 struct mVLBlockHeader header = { 0 };
459 STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
460
461 STORE_32LE(context->activeChannel, 0, &header.channelId);
462 STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &header.flags);
463
464 uint8_t compressBuffer[0x800];
465 z_stream zstr;
466 zstr.zalloc = Z_NULL;
467 zstr.zfree = Z_NULL;
468 zstr.opaque = Z_NULL;
469 zstr.avail_in = 0;
470 zstr.avail_out = sizeof(compressBuffer);
471 zstr.next_out = (Bytef*) compressBuffer;
472 if (deflateInit(&zstr, 9) != Z_OK) {
473 return;
474 }
475
476 struct VFile* vfm = VFileMemChunk(NULL, 0);
477
478 while (CircleBufferSize(buffer)) {
479 size_t read = CircleBufferRead(buffer, writeBuffer, sizeof(writeBuffer));
480 zstr.avail_in = read;
481 zstr.next_in = (Bytef*) writeBuffer;
482 while (zstr.avail_in) {
483 if (deflate(&zstr, Z_NO_FLUSH) == Z_STREAM_ERROR) {
484 break;
485 }
486 vfm->write(vfm, compressBuffer, sizeof(compressBuffer) - zstr.avail_out);
487 zstr.avail_out = sizeof(compressBuffer);
488 zstr.next_out = (Bytef*) compressBuffer;
489 }
490 }
491
492 do {
493 zstr.avail_out = sizeof(compressBuffer);
494 zstr.next_out = (Bytef*) compressBuffer;
495 zstr.avail_in = 0;
496 int ret = deflate(&zstr, Z_FINISH);
497 if (ret == Z_STREAM_ERROR) {
498 break;
499 }
500 vfm->write(vfm, compressBuffer, sizeof(compressBuffer) - zstr.avail_out);
501 } while (sizeof(compressBuffer) - zstr.avail_out);
502
503 size_t size = vfm->size(vfm);
504 STORE_32LE(size, 0, &header.length);
505 context->backing->write(context->backing, &header, sizeof(header));
506 void* vfmm = vfm->map(vfm, size, MAP_READ);
507 context->backing->write(context->backing, vfmm, size);
508 vfm->unmap(vfm, vfmm, size);
509 vfm->close(vfm);
510}
511#endif
512
513static void _flushBuffer(struct mVideoLogContext* context) {
514#ifdef USE_ZLIB
515 // TODO: Make optional
516 _flushBufferCompressed(context);
517 return;
518#endif
519
520 struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer;
521 if (!CircleBufferSize(buffer)) {
522 return;
523 }
524 struct mVLBlockHeader header = { 0 };
525 STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType);
526 STORE_32LE(CircleBufferSize(buffer), 0, &header.length);
527 STORE_32LE(context->activeChannel, 0, &header.channelId);
528
529 context->backing->write(context->backing, &header, sizeof(header));
530
531 uint8_t writeBuffer[0x800];
532 while (CircleBufferSize(buffer)) {
533 size_t read = CircleBufferRead(buffer, writeBuffer, sizeof(writeBuffer));
534 context->backing->write(context->backing, writeBuffer, read);
535 }
536}
537
538void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* context) {
539 if (context->write) {
540 _flushBuffer(context);
541
542 struct mVLBlockHeader header = { 0 };
543 STORE_32LE(mVL_BLOCK_FOOTER, 0, &header.blockType);
544 context->backing->write(context->backing, &header, sizeof(header));
545 }
546
547 if (core) {
548 core->endVideoLog(core);
549 }
550 if (context->initialState) {
551 mappedMemoryFree(context->initialState, context->initialStateSize);
552 }
553 free(context);
554}
555
556void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core) {
557 _readHeader(context);
558 if (core) {
559 core->loadState(core, context->initialState);
560 }
561
562 off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR);
563
564 size_t i;
565 for (i = 0; i < context->nChannels; ++i) {
566 CircleBufferClear(&context->channels[i].buffer);
567 context->channels[i].bufferRemaining = 0;
568 context->channels[i].currentPointer = pointer;
569 }
570}
571
572void* mVideoLogContextInitialState(struct mVideoLogContext* context, size_t* size) {
573 if (size) {
574 *size = context->initialStateSize;
575 }
576 return context->initialState;
577}
578
579int mVideoLoggerAddChannel(struct mVideoLogContext* context) {
580 if (context->nChannels >= mVL_MAX_CHANNELS) {
581 return -1;
582 }
583 int chid = context->nChannels;
584 ++context->nChannels;
585 context->channels[chid].p = context;
586 CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE);
587 return chid;
588}
589
590#ifdef USE_ZLIB
591static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
592 uint8_t fbuffer[0x400];
593 uint8_t zbuffer[0x800];
594 size_t read = 0;
595
596 channel->inflateStream.avail_in = 0;
597 while (length) {
598 size_t thisWrite = sizeof(zbuffer);
599 if (thisWrite > length) {
600 thisWrite = length;
601 }
602
603 size_t thisRead = 0;
604 if (channel->inflating && channel->inflateStream.avail_in) {
605 channel->inflateStream.next_out = zbuffer;
606 channel->inflateStream.avail_out = thisWrite;
607 thisRead = channel->inflateStream.avail_in;
608 } else if (channel->bufferRemaining) {
609 thisRead = sizeof(fbuffer);
610 if (thisRead > channel->bufferRemaining) {
611 thisRead = channel->bufferRemaining;
612 }
613
614 thisRead = vf->read(vf, fbuffer, thisRead);
615 if (thisRead <= 0) {
616 break;
617 }
618
619 channel->inflateStream.next_in = fbuffer;
620 channel->inflateStream.avail_in = thisRead;
621 channel->inflateStream.next_out = zbuffer;
622 channel->inflateStream.avail_out = thisWrite;
623
624 if (!channel->inflating) {
625 if (inflateInit(&channel->inflateStream) != Z_OK) {
626 break;
627 }
628 channel->inflating = true;
629 }
630 } else {
631 channel->inflateStream.next_in = Z_NULL;
632 channel->inflateStream.avail_in = 0;
633 channel->inflateStream.next_out = zbuffer;
634 channel->inflateStream.avail_out = thisWrite;
635 }
636
637 int ret = inflate(&channel->inflateStream, Z_NO_FLUSH);
638
639 if (channel->inflateStream.next_in != Z_NULL) {
640 thisRead -= channel->inflateStream.avail_in;
641 channel->currentPointer += thisRead;
642 channel->bufferRemaining -= thisRead;
643 }
644
645 if (ret != Z_OK) {
646 inflateEnd(&channel->inflateStream);
647 channel->inflating = false;
648 if (ret != Z_STREAM_END) {
649 break;
650 }
651 }
652
653 thisWrite = CircleBufferWrite(&channel->buffer, zbuffer, thisWrite - channel->inflateStream.avail_out);
654 length -= thisWrite;
655 read += thisWrite;
656
657 if (!channel->inflating) {
658 break;
659 }
660 }
661 return read;
662}
663#endif
664
665static void _readBuffer(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) {
666 uint8_t buffer[0x800];
667 while (length) {
668 size_t thisRead = sizeof(buffer);
669 if (thisRead > length) {
670 thisRead = length;
671 }
672 thisRead = vf->read(vf, buffer, thisRead);
673 if (thisRead <= 0) {
674 return;
675 }
676 size_t thisWrite = CircleBufferWrite(&channel->buffer, buffer, thisRead);
677 length -= thisWrite;
678 channel->bufferRemaining -= thisWrite;
679 channel->currentPointer += thisWrite;
680 if (thisWrite < thisRead) {
681 break;
682 }
683 }
684}
685
686static bool _fillBuffer(struct mVideoLogContext* context, size_t channelId, size_t length) {
687 struct mVideoLogChannel* channel = &context->channels[channelId];
688 context->backing->seek(context->backing, channel->currentPointer, SEEK_SET);
689 struct mVLBlockHeader header;
690 while (length) {
691 size_t bufferRemaining = channel->bufferRemaining;
692 if (bufferRemaining) {
693#ifdef USE_ZLIB
694 if (channel->inflating) {
695 length -= _readBufferCompressed(context->backing, channel, length);
696 continue;
697 }
698#endif
699 if (bufferRemaining > length) {
700 bufferRemaining = length;
701 }
702
703 _readBuffer(context->backing, channel, bufferRemaining);
704 length -= bufferRemaining;
705 continue;
706 }
707
708 if (!_readBlockHeader(context, &header)) {
709 return false;
710 }
711 if (header.blockType == mVL_BLOCK_FOOTER) {
712 return false;
713 }
714 if (header.channelId != channelId || header.blockType != mVL_BLOCK_DATA) {
715 context->backing->seek(context->backing, header.length, SEEK_CUR);
716 continue;
717 }
718 channel->currentPointer = context->backing->seek(context->backing, 0, SEEK_CUR);
719 if (!header.length) {
720 continue;
721 }
722 channel->bufferRemaining = header.length;
723
724 if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) {
725#ifdef USE_ZLIB
726 length -= _readBufferCompressed(context->backing, channel, length);
727#else
728 return false;
729#endif
730 }
731 }
732 return true;
733}
734
735static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length) {
736 struct mVideoLogContext* context = channel->p;
737 unsigned channelId = channel - context->channels;
738 if (channelId >= mVL_MAX_CHANNELS) {
739 return 0;
740 }
741 if (CircleBufferSize(&channel->buffer) >= length) {
742 return CircleBufferRead(&channel->buffer, data, length);
743 }
744 ssize_t size = 0;
745 if (CircleBufferSize(&channel->buffer)) {
746 size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer));
747 if (size <= 0) {
748 return size;
749 }
750 data = (uint8_t*) data + size;
751 length -= size;
752 }
753 if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) {
754 return size;
755 }
756 size += CircleBufferRead(&channel->buffer, data, length);
757 return size;
758}
759
760static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length) {
761 struct mVideoLogContext* context = channel->p;
762 unsigned channelId = channel - context->channels;
763 if (channelId >= mVL_MAX_CHANNELS) {
764 return 0;
765 }
766 if (channelId != context->activeChannel) {
767 _flushBuffer(context);
768 context->activeChannel = channelId;
769 }
770 if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) {
771 _flushBuffer(context);
772 if (CircleBufferCapacity(&channel->buffer) < length) {
773 CircleBufferDeinit(&channel->buffer);
774 CircleBufferInit(&channel->buffer, toPow2(length << 1));
775 }
776 }
777
778 ssize_t read = CircleBufferWrite(&channel->buffer, data, length);
779 if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) {
780 _flushBuffer(context);
781 }
782 return read;
783}
784
785struct mCore* mVideoLogCoreFind(struct VFile* vf) {
786 if (!vf) {
787 return NULL;
788 }
789 struct mVideoLogHeader header = { { 0 } };
790 vf->seek(vf, 0, SEEK_SET);
791 ssize_t read = vf->read(vf, &header, sizeof(header));
792 if (read != sizeof(header)) {
793 return NULL;
794 }
795 if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) {
796 return NULL;
797 }
798 enum mPlatform platform;
799 LOAD_32LE(platform, 0, &header.platform);
800
801 const struct mVLDescriptor* descriptor;
802 for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) {
803 if (platform == descriptor->platform) {
804 break;
805 }
806 }
807 struct mCore* core = NULL;
808 if (descriptor->open) {
809 core = descriptor->open();
810 }
811 return core;
812}