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