/* Copyright (c) 2013-2017 Jeffrey Pfau * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include #include #include #include #include #ifdef M_CORE_GBA #include #endif #ifdef M_CORE_GB #include #endif #ifdef USE_ZLIB #include #endif #define BUFFER_BASE_SIZE 0x20000 #define MAX_BLOCK_SIZE 0x800000 const char mVL_MAGIC[] = "mVL\0"; static const struct mVLDescriptor { enum mPlatform platform; struct mCore* (*open)(void); } _descriptors[] = { #ifdef M_CORE_GBA { PLATFORM_GBA, GBAVideoLogPlayerCreate }, #endif #ifdef M_CORE_GB { PLATFORM_GB, GBVideoLogPlayerCreate }, #endif { PLATFORM_NONE, 0 } }; enum mVLBlockType { mVL_BLOCK_DUMMY = 0, mVL_BLOCK_INITIAL_STATE, mVL_BLOCK_CHANNEL_HEADER, mVL_BLOCK_DATA, mVL_BLOCK_FOOTER = 0x784C566D }; enum mVLHeaderFlag { mVL_FLAG_HAS_INITIAL_STATE = 1 }; struct mVLBlockHeader { uint32_t blockType; uint32_t length; uint32_t channelId; uint32_t flags; }; enum mVLBlockFlag { mVL_FLAG_BLOCK_COMPRESSED = 1 }; struct mVideoLogHeader { char magic[4]; uint32_t flags; uint32_t platform; uint32_t nChannels; }; struct mVideoLogContext; struct mVideoLogChannel { struct mVideoLogContext* p; uint32_t type; void* initialState; size_t initialStateSize; off_t currentPointer; size_t bufferRemaining; #ifdef USE_ZLIB bool inflating; z_stream inflateStream; #endif struct CircleBuffer buffer; }; struct mVideoLogContext { void* initialState; size_t initialStateSize; uint32_t nChannels; struct mVideoLogChannel channels[mVL_MAX_CHANNELS]; bool write; uint32_t activeChannel; struct VFile* backing; }; static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length); static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length); static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block); static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length); static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length); static inline size_t _roundUp(size_t value, int shift) { value += (1 << shift) - 1; return value >> shift; } void mVideoLoggerRendererCreate(struct mVideoLogger* logger, bool readonly) { if (readonly) { logger->writeData = _writeNull; logger->block = true; } else { logger->writeData = _writeData; } logger->readData = _readData; logger->dataContext = NULL; logger->init = NULL; logger->deinit = NULL; logger->reset = NULL; logger->lock = NULL; logger->unlock = NULL; logger->wait = NULL; logger->wake = NULL; } void mVideoLoggerRendererInit(struct mVideoLogger* logger) { logger->palette = anonymousMemoryMap(logger->paletteSize); logger->vram = anonymousMemoryMap(logger->vramSize); logger->oam = anonymousMemoryMap(logger->oamSize); logger->vramDirtyBitmap = calloc(_roundUp(logger->vramSize, 17), sizeof(uint32_t)); logger->oamDirtyBitmap = calloc(_roundUp(logger->oamSize, 6), sizeof(uint32_t)); if (logger->init) { logger->init(logger); } } void mVideoLoggerRendererDeinit(struct mVideoLogger* logger) { if (logger->deinit) { logger->deinit(logger); } mappedMemoryFree(logger->palette, logger->paletteSize); mappedMemoryFree(logger->vram, logger->vramSize); mappedMemoryFree(logger->oam, logger->oamSize); free(logger->vramDirtyBitmap); free(logger->oamDirtyBitmap); } void mVideoLoggerRendererReset(struct mVideoLogger* logger) { memset(logger->vramDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->vramSize, 17)); memset(logger->oamDirtyBitmap, 0, sizeof(uint32_t) * _roundUp(logger->oamSize, 6)); if (logger->reset) { logger->reset(logger); } } void mVideoLoggerRendererWriteVideoRegister(struct mVideoLogger* logger, uint32_t address, uint16_t value) { struct mVideoLoggerDirtyInfo dirty = { DIRTY_REGISTER, address, value, 0xDEADBEEF, }; logger->writeData(logger, &dirty, sizeof(dirty)); } void mVideoLoggerRendererWriteVRAM(struct mVideoLogger* logger, uint32_t address) { int bit = 1 << (address >> 12); if (logger->vramDirtyBitmap[address >> 17] & bit) { return; } logger->vramDirtyBitmap[address >> 17] |= bit; } void mVideoLoggerRendererWritePalette(struct mVideoLogger* logger, uint32_t address, uint16_t value) { struct mVideoLoggerDirtyInfo dirty = { DIRTY_PALETTE, address, value, 0xDEADBEEF, }; logger->writeData(logger, &dirty, sizeof(dirty)); } void mVideoLoggerRendererWriteOAM(struct mVideoLogger* logger, uint32_t address, uint16_t value) { struct mVideoLoggerDirtyInfo dirty = { DIRTY_OAM, address, value, 0xDEADBEEF, }; logger->writeData(logger, &dirty, sizeof(dirty)); } static void _flushVRAM(struct mVideoLogger* logger) { size_t i; for (i = 0; i < _roundUp(logger->vramSize, 17); ++i) { if (logger->vramDirtyBitmap[i]) { uint32_t bitmap = logger->vramDirtyBitmap[i]; logger->vramDirtyBitmap[i] = 0; int j; for (j = 0; j < mVL_MAX_CHANNELS; ++j) { if (!(bitmap & (1 << j))) { continue; } struct mVideoLoggerDirtyInfo dirty = { DIRTY_VRAM, j * 0x1000, 0x1000, 0xDEADBEEF, }; logger->writeData(logger, &dirty, sizeof(dirty)); logger->writeData(logger, logger->vramBlock(logger, j * 0x1000), 0x1000); } } } } void mVideoLoggerRendererDrawScanline(struct mVideoLogger* logger, int y) { _flushVRAM(logger); struct mVideoLoggerDirtyInfo dirty = { DIRTY_SCANLINE, y, 0, 0xDEADBEEF, }; logger->writeData(logger, &dirty, sizeof(dirty)); } void mVideoLoggerRendererDrawRange(struct mVideoLogger* logger, int startX, int endX, int y) { _flushVRAM(logger); struct mVideoLoggerDirtyInfo dirty = { DIRTY_RANGE, y, startX, endX, }; logger->writeData(logger, &dirty, sizeof(dirty)); } void mVideoLoggerRendererFlush(struct mVideoLogger* logger) { struct mVideoLoggerDirtyInfo dirty = { DIRTY_FLUSH, 0, 0, 0xDEADBEEF, }; logger->writeData(logger, &dirty, sizeof(dirty)); if (logger->wait) { logger->wait(logger); } } void mVideoLoggerRendererFinishFrame(struct mVideoLogger* logger) { struct mVideoLoggerDirtyInfo dirty = { DIRTY_FRAME, 0, 0, 0xDEADBEEF, }; logger->writeData(logger, &dirty, sizeof(dirty)); } void mVideoLoggerWriteBuffer(struct mVideoLogger* logger, uint32_t bufferId, uint32_t offset, uint32_t length, const void* data) { struct mVideoLoggerDirtyInfo dirty = { DIRTY_BUFFER, bufferId, offset, length, }; logger->writeData(logger, &dirty, sizeof(dirty)); logger->writeData(logger, data, length); } bool mVideoLoggerRendererRun(struct mVideoLogger* logger, bool block) { struct mVideoLoggerDirtyInfo item = {0}; while (logger->readData(logger, &item, sizeof(item), block)) { switch (item.type) { case DIRTY_REGISTER: case DIRTY_PALETTE: case DIRTY_OAM: case DIRTY_VRAM: case DIRTY_SCANLINE: case DIRTY_FLUSH: case DIRTY_FRAME: case DIRTY_RANGE: case DIRTY_BUFFER: if (!logger->parsePacket(logger, &item)) { return true; } break; default: return false; } } return !block; } static bool _writeData(struct mVideoLogger* logger, const void* data, size_t length) { struct mVideoLogChannel* channel = logger->dataContext; return mVideoLoggerWriteChannel(channel, data, length) == (ssize_t) length; } static bool _writeNull(struct mVideoLogger* logger, const void* data, size_t length) { UNUSED(logger); UNUSED(data); UNUSED(length); return false; } static bool _readData(struct mVideoLogger* logger, void* data, size_t length, bool block) { UNUSED(block); struct mVideoLogChannel* channel = logger->dataContext; return mVideoLoggerReadChannel(channel, data, length) == (ssize_t) length; } #ifdef USE_ZLIB static void _copyVf(struct VFile* dest, struct VFile* src) { size_t size = src->size(src); void* mem = src->map(src, size, MAP_READ); dest->write(dest, mem, size); src->unmap(src, mem, size); } static void _compress(struct VFile* dest, struct VFile* src) { uint8_t writeBuffer[0x800]; uint8_t compressBuffer[0x400]; z_stream zstr; zstr.zalloc = Z_NULL; zstr.zfree = Z_NULL; zstr.opaque = Z_NULL; zstr.avail_in = 0; zstr.avail_out = sizeof(compressBuffer); zstr.next_out = (Bytef*) compressBuffer; if (deflateInit(&zstr, 9) != Z_OK) { return; } while (true) { size_t read = src->read(src, writeBuffer, sizeof(writeBuffer)); if (!read) { break; } zstr.avail_in = read; zstr.next_in = (Bytef*) writeBuffer; while (zstr.avail_in) { if (deflate(&zstr, Z_NO_FLUSH) == Z_STREAM_ERROR) { break; } dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out); zstr.avail_out = sizeof(compressBuffer); zstr.next_out = (Bytef*) compressBuffer; } } do { zstr.avail_out = sizeof(compressBuffer); zstr.next_out = (Bytef*) compressBuffer; zstr.avail_in = 0; int ret = deflate(&zstr, Z_FINISH); if (ret == Z_STREAM_ERROR) { break; } dest->write(dest, compressBuffer, sizeof(compressBuffer) - zstr.avail_out); } while (sizeof(compressBuffer) - zstr.avail_out); } static bool _decompress(struct VFile* dest, struct VFile* src, size_t compressedLength) { uint8_t fbuffer[0x400]; uint8_t zbuffer[0x800]; z_stream zstr; zstr.zalloc = Z_NULL; zstr.zfree = Z_NULL; zstr.opaque = Z_NULL; zstr.avail_in = 0; zstr.avail_out = sizeof(zbuffer); zstr.next_out = (Bytef*) zbuffer; bool started = false; while (true) { size_t thisWrite = sizeof(zbuffer); size_t thisRead = 0; if (zstr.avail_in) { zstr.next_out = zbuffer; zstr.avail_out = thisWrite; thisRead = zstr.avail_in; } else if (compressedLength) { thisRead = sizeof(fbuffer); if (thisRead > compressedLength) { thisRead = compressedLength; } thisRead = src->read(src, fbuffer, thisRead); if (thisRead <= 0) { break; } zstr.next_in = fbuffer; zstr.avail_in = thisRead; zstr.next_out = zbuffer; zstr.avail_out = thisWrite; if (!started) { if (inflateInit(&zstr) != Z_OK) { break; } started = true; } } else { zstr.next_in = Z_NULL; zstr.avail_in = 0; zstr.next_out = zbuffer; zstr.avail_out = thisWrite; } int ret = inflate(&zstr, Z_NO_FLUSH); if (zstr.next_in != Z_NULL) { thisRead -= zstr.avail_in; compressedLength -= thisRead; } if (ret != Z_OK) { inflateEnd(&zstr); started = false; if (ret != Z_STREAM_END) { break; } } thisWrite = dest->write(dest, zbuffer, thisWrite - zstr.avail_out); if (!started) { break; } } return !compressedLength; } #endif void mVideoLoggerAttachChannel(struct mVideoLogger* logger, struct mVideoLogContext* context, size_t channelId) { if (channelId >= mVL_MAX_CHANNELS) { return; } logger->dataContext = &context->channels[channelId]; } struct mVideoLogContext* mVideoLogContextCreate(struct mCore* core) { struct mVideoLogContext* context = malloc(sizeof(*context)); memset(context, 0, sizeof(*context)); context->write = !!core; context->initialStateSize = 0; context->initialState = NULL; if (core) { context->initialStateSize = core->stateSize(core); context->initialState = anonymousMemoryMap(context->initialStateSize); core->saveState(core, context->initialState); core->startVideoLog(core, context); } context->activeChannel = 0; return context; } void mVideoLogContextSetOutput(struct mVideoLogContext* context, struct VFile* vf) { context->backing = vf; vf->truncate(vf, 0); vf->seek(vf, 0, SEEK_SET); } void mVideoLogContextWriteHeader(struct mVideoLogContext* context, struct mCore* core) { struct mVideoLogHeader header = { { 0 } }; memcpy(header.magic, mVL_MAGIC, sizeof(header.magic)); enum mPlatform platform = core->platform(core); STORE_32LE(platform, 0, &header.platform); STORE_32LE(context->nChannels, 0, &header.nChannels); uint32_t flags = 0; if (context->initialState) { flags |= mVL_FLAG_HAS_INITIAL_STATE; } STORE_32LE(flags, 0, &header.flags); context->backing->write(context->backing, &header, sizeof(header)); if (context->initialState) { struct mVLBlockHeader chheader = { 0 }; STORE_32LE(mVL_BLOCK_INITIAL_STATE, 0, &chheader.blockType); #ifdef USE_ZLIB STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &chheader.flags); struct VFile* vfm = VFileMemChunk(NULL, 0); struct VFile* src = VFileFromConstMemory(context->initialState, context->initialStateSize); _compress(vfm, src); src->close(src); STORE_32LE(vfm->size(vfm), 0, &chheader.length); context->backing->write(context->backing, &chheader, sizeof(chheader)); _copyVf(context->backing, vfm); vfm->close(vfm); #else STORE_32LE(context->initialStateSize, 0, &chheader.length); context->backing->write(context->backing, &chheader, sizeof(chheader)); context->backing->write(context->backing, context->initialState, context->initialStateSize); #endif } size_t i; for (i = 0; i < context->nChannels; ++i) { struct mVLBlockHeader chheader = { 0 }; STORE_32LE(mVL_BLOCK_CHANNEL_HEADER, 0, &chheader.blockType); STORE_32LE(i, 0, &chheader.channelId); context->backing->write(context->backing, &chheader, sizeof(chheader)); } } bool _readBlockHeader(struct mVideoLogContext* context, struct mVLBlockHeader* header) { struct mVLBlockHeader buffer; if (context->backing->read(context->backing, &buffer, sizeof(buffer)) != sizeof(buffer)) { return false; } LOAD_32LE(header->blockType, 0, &buffer.blockType); LOAD_32LE(header->length, 0, &buffer.length); LOAD_32LE(header->channelId, 0, &buffer.channelId); LOAD_32LE(header->flags, 0, &buffer.flags); if (header->length > MAX_BLOCK_SIZE) { // Pre-emptively reject blocks that are too big. // If we encounter one, the file is probably corrupted. return false; } return true; } bool _readHeader(struct mVideoLogContext* context) { struct mVideoLogHeader header; context->backing->seek(context->backing, 0, SEEK_SET); if (context->backing->read(context->backing, &header, sizeof(header)) != sizeof(header)) { return false; } if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) { return false; } LOAD_32LE(context->nChannels, 0, &header.nChannels); if (context->nChannels > mVL_MAX_CHANNELS) { return false; } uint32_t flags; LOAD_32LE(flags, 0, &header.flags); if (flags & mVL_FLAG_HAS_INITIAL_STATE) { struct mVLBlockHeader header; if (!_readBlockHeader(context, &header)) { return false; } if (header.blockType != mVL_BLOCK_INITIAL_STATE || !header.length) { return false; } if (context->initialState) { mappedMemoryFree(context->initialState, context->initialStateSize); context->initialState = NULL; context->initialStateSize = 0; } if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) { #ifdef USE_ZLIB struct VFile* vfm = VFileMemChunk(NULL, 0); if (!_decompress(vfm, context->backing, header.length)) { vfm->close(vfm); return false; } context->initialStateSize = vfm->size(vfm); context->initialState = anonymousMemoryMap(context->initialStateSize); void* mem = vfm->map(vfm, context->initialStateSize, MAP_READ); memcpy(context->initialState, mem, context->initialStateSize); vfm->unmap(vfm, mem, context->initialStateSize); vfm->close(vfm); #else return false; #endif } else { context->initialStateSize = header.length; context->initialState = anonymousMemoryMap(header.length); context->backing->read(context->backing, context->initialState, context->initialStateSize); } } return true; } bool mVideoLogContextLoad(struct mVideoLogContext* context, struct VFile* vf) { context->backing = vf; if (!_readHeader(context)) { return false; } off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR); size_t i; for (i = 0; i < context->nChannels; ++i) { CircleBufferInit(&context->channels[i].buffer, BUFFER_BASE_SIZE); context->channels[i].bufferRemaining = 0; context->channels[i].currentPointer = pointer; context->channels[i].p = context; #ifdef USE_ZLIB context->channels[i].inflating = false; #endif } return true; } #ifdef USE_ZLIB static void _flushBufferCompressed(struct mVideoLogContext* context) { struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer; if (!CircleBufferSize(buffer)) { return; } struct VFile* vfm = VFileMemChunk(NULL, 0); struct VFile* src = VFileFIFO(buffer); _compress(vfm, src); src->close(src); size_t size = vfm->size(vfm); struct mVLBlockHeader header = { 0 }; STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType); STORE_32LE(context->activeChannel, 0, &header.channelId); STORE_32LE(mVL_FLAG_BLOCK_COMPRESSED, 0, &header.flags); STORE_32LE(size, 0, &header.length); context->backing->write(context->backing, &header, sizeof(header)); _copyVf(context->backing, vfm); vfm->close(vfm); } #endif static void _flushBuffer(struct mVideoLogContext* context) { #ifdef USE_ZLIB // TODO: Make optional _flushBufferCompressed(context); return; #endif struct CircleBuffer* buffer = &context->channels[context->activeChannel].buffer; if (!CircleBufferSize(buffer)) { return; } struct mVLBlockHeader header = { 0 }; STORE_32LE(mVL_BLOCK_DATA, 0, &header.blockType); STORE_32LE(CircleBufferSize(buffer), 0, &header.length); STORE_32LE(context->activeChannel, 0, &header.channelId); context->backing->write(context->backing, &header, sizeof(header)); uint8_t writeBuffer[0x800]; while (CircleBufferSize(buffer)) { size_t read = CircleBufferRead(buffer, writeBuffer, sizeof(writeBuffer)); context->backing->write(context->backing, writeBuffer, read); } } void mVideoLogContextDestroy(struct mCore* core, struct mVideoLogContext* context) { if (context->write) { _flushBuffer(context); struct mVLBlockHeader header = { 0 }; STORE_32LE(mVL_BLOCK_FOOTER, 0, &header.blockType); context->backing->write(context->backing, &header, sizeof(header)); } if (core) { core->endVideoLog(core); } if (context->initialState) { mappedMemoryFree(context->initialState, context->initialStateSize); } size_t i; for (i = 0; i < context->nChannels; ++i) { CircleBufferDeinit(&context->channels[i].buffer); #ifdef USE_ZLIB if (context->channels[i].inflating) { inflateEnd(&context->channels[i].inflateStream); context->channels[i].inflating = false; } #endif } free(context); } void mVideoLogContextRewind(struct mVideoLogContext* context, struct mCore* core) { _readHeader(context); if (core) { size_t size = core->stateSize(core); if (size <= context->initialStateSize) { core->loadState(core, context->initialState); } else { void* extendedState = anonymousMemoryMap(size); memcpy(extendedState, context->initialState, context->initialStateSize); core->loadState(core, extendedState); mappedMemoryFree(extendedState, size); } } off_t pointer = context->backing->seek(context->backing, 0, SEEK_CUR); size_t i; for (i = 0; i < context->nChannels; ++i) { CircleBufferClear(&context->channels[i].buffer); context->channels[i].bufferRemaining = 0; context->channels[i].currentPointer = pointer; #ifdef USE_ZLIB if (context->channels[i].inflating) { inflateEnd(&context->channels[i].inflateStream); context->channels[i].inflating = false; } #endif } } void* mVideoLogContextInitialState(struct mVideoLogContext* context, size_t* size) { if (size) { *size = context->initialStateSize; } return context->initialState; } int mVideoLoggerAddChannel(struct mVideoLogContext* context) { if (context->nChannels >= mVL_MAX_CHANNELS) { return -1; } int chid = context->nChannels; ++context->nChannels; context->channels[chid].p = context; CircleBufferInit(&context->channels[chid].buffer, BUFFER_BASE_SIZE); return chid; } #ifdef USE_ZLIB static size_t _readBufferCompressed(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) { uint8_t fbuffer[0x400]; uint8_t zbuffer[0x800]; size_t read = 0; // TODO: Share with _decompress channel->inflateStream.avail_in = 0; while (length) { size_t thisWrite = sizeof(zbuffer); if (thisWrite > length) { thisWrite = length; } size_t thisRead = 0; if (channel->inflating && channel->inflateStream.avail_in) { channel->inflateStream.next_out = zbuffer; channel->inflateStream.avail_out = thisWrite; thisRead = channel->inflateStream.avail_in; } else if (channel->bufferRemaining) { thisRead = sizeof(fbuffer); if (thisRead > channel->bufferRemaining) { thisRead = channel->bufferRemaining; } thisRead = vf->read(vf, fbuffer, thisRead); if (thisRead <= 0) { break; } channel->inflateStream.next_in = fbuffer; channel->inflateStream.avail_in = thisRead; channel->inflateStream.next_out = zbuffer; channel->inflateStream.avail_out = thisWrite; if (!channel->inflating) { if (inflateInit(&channel->inflateStream) != Z_OK) { break; } channel->inflating = true; } } else { channel->inflateStream.next_in = Z_NULL; channel->inflateStream.avail_in = 0; channel->inflateStream.next_out = zbuffer; channel->inflateStream.avail_out = thisWrite; } int ret = inflate(&channel->inflateStream, Z_NO_FLUSH); if (channel->inflateStream.next_in != Z_NULL) { thisRead -= channel->inflateStream.avail_in; channel->currentPointer += thisRead; channel->bufferRemaining -= thisRead; } if (ret != Z_OK) { inflateEnd(&channel->inflateStream); channel->inflating = false; if (ret != Z_STREAM_END) { break; } } thisWrite = CircleBufferWrite(&channel->buffer, zbuffer, thisWrite - channel->inflateStream.avail_out); length -= thisWrite; read += thisWrite; if (!channel->inflating) { break; } } return read; } #endif static void _readBuffer(struct VFile* vf, struct mVideoLogChannel* channel, size_t length) { uint8_t buffer[0x800]; while (length) { size_t thisRead = sizeof(buffer); if (thisRead > length) { thisRead = length; } thisRead = vf->read(vf, buffer, thisRead); if (thisRead <= 0) { return; } size_t thisWrite = CircleBufferWrite(&channel->buffer, buffer, thisRead); length -= thisWrite; channel->bufferRemaining -= thisWrite; channel->currentPointer += thisWrite; if (thisWrite < thisRead) { break; } } } static bool _fillBuffer(struct mVideoLogContext* context, size_t channelId, size_t length) { struct mVideoLogChannel* channel = &context->channels[channelId]; context->backing->seek(context->backing, channel->currentPointer, SEEK_SET); struct mVLBlockHeader header; while (length) { size_t bufferRemaining = channel->bufferRemaining; if (bufferRemaining) { #ifdef USE_ZLIB if (channel->inflating) { length -= _readBufferCompressed(context->backing, channel, length); continue; } #endif if (bufferRemaining > length) { bufferRemaining = length; } _readBuffer(context->backing, channel, bufferRemaining); length -= bufferRemaining; continue; } if (!_readBlockHeader(context, &header)) { return false; } if (header.blockType == mVL_BLOCK_FOOTER) { return true; } if (header.channelId != channelId || header.blockType != mVL_BLOCK_DATA) { context->backing->seek(context->backing, header.length, SEEK_CUR); continue; } channel->currentPointer = context->backing->seek(context->backing, 0, SEEK_CUR); if (!header.length) { continue; } channel->bufferRemaining = header.length; if (header.flags & mVL_FLAG_BLOCK_COMPRESSED) { #ifdef USE_ZLIB length -= _readBufferCompressed(context->backing, channel, length); #else return false; #endif } } return true; } static ssize_t mVideoLoggerReadChannel(struct mVideoLogChannel* channel, void* data, size_t length) { struct mVideoLogContext* context = channel->p; unsigned channelId = channel - context->channels; if (channelId >= mVL_MAX_CHANNELS) { return 0; } if (CircleBufferSize(&channel->buffer) >= length) { return CircleBufferRead(&channel->buffer, data, length); } ssize_t size = 0; if (CircleBufferSize(&channel->buffer)) { size = CircleBufferRead(&channel->buffer, data, CircleBufferSize(&channel->buffer)); if (size <= 0) { return size; } data = (uint8_t*) data + size; length -= size; } if (!_fillBuffer(context, channelId, BUFFER_BASE_SIZE)) { return size; } size += CircleBufferRead(&channel->buffer, data, length); return size; } static ssize_t mVideoLoggerWriteChannel(struct mVideoLogChannel* channel, const void* data, size_t length) { struct mVideoLogContext* context = channel->p; unsigned channelId = channel - context->channels; if (channelId >= mVL_MAX_CHANNELS) { return 0; } if (channelId != context->activeChannel) { _flushBuffer(context); context->activeChannel = channelId; } if (CircleBufferCapacity(&channel->buffer) - CircleBufferSize(&channel->buffer) < length) { _flushBuffer(context); if (CircleBufferCapacity(&channel->buffer) < length) { CircleBufferDeinit(&channel->buffer); CircleBufferInit(&channel->buffer, toPow2(length << 1)); } } ssize_t read = CircleBufferWrite(&channel->buffer, data, length); if (CircleBufferCapacity(&channel->buffer) == CircleBufferSize(&channel->buffer)) { _flushBuffer(context); } return read; } struct mCore* mVideoLogCoreFind(struct VFile* vf) { if (!vf) { return NULL; } struct mVideoLogHeader header = { { 0 } }; vf->seek(vf, 0, SEEK_SET); ssize_t read = vf->read(vf, &header, sizeof(header)); if (read != sizeof(header)) { return NULL; } if (memcmp(header.magic, mVL_MAGIC, sizeof(header.magic)) != 0) { return NULL; } enum mPlatform platform; LOAD_32LE(platform, 0, &header.platform); const struct mVLDescriptor* descriptor; for (descriptor = &_descriptors[0]; descriptor->platform != PLATFORM_NONE; ++descriptor) { if (platform == descriptor->platform) { break; } } struct mCore* core = NULL; if (descriptor->open) { core = descriptor->open(); } return core; }