| 28 | PixelStream::~PixelStream() FL_NOEXCEPT { close(); } |
| 29 | |
| 30 | bool PixelStream::begin(filebuf_ptr h) { |
| 31 | close(); |
| 32 | mHandle = h; |
| 33 | mPayloadOffset = 0; |
| 34 | mEmbeddedScreenMapJson.clear(); |
| 35 | // Probe seekability: if seek-to-start succeeds, this is a seekable file. |
| 36 | mType = mHandle->seek(0, seek_dir::beg) ? kFile : kStreaming; |
| 37 | if (mType == kFile) { |
| 38 | // Try to detect a FLED v1 container. Anything that fails the header |
| 39 | // check (wrong magic, wrong version, unsupported pixel_format, |
| 40 | // truncated) falls back to legacy headerless RGB — zero behavior |
| 41 | // change for existing files. |
| 42 | if (mHandle->size() >= kFledHeaderBytes) { |
| 43 | char hdr[kFledHeaderBytes]; |
| 44 | fl::size_t got = mHandle->read(hdr, kFledHeaderBytes); |
| 45 | bool isFled = got == kFledHeaderBytes |
| 46 | && static_cast<fl::u8>(hdr[0]) == kFledMagic[0] |
| 47 | && static_cast<fl::u8>(hdr[1]) == kFledMagic[1] |
| 48 | && static_cast<fl::u8>(hdr[2]) == kFledMagic[2] |
| 49 | && static_cast<fl::u8>(hdr[3]) == kFledMagic[3] |
| 50 | && static_cast<fl::u8>(hdr[4]) == kFledVersionV1 |
| 51 | && static_cast<fl::u8>(hdr[5]) == kFledPixelFormatRgb8; |
| 52 | if (isFled) { |
| 53 | const fl::u32 jsonLen = |
| 54 | static_cast<fl::u32>(static_cast<fl::u8>(hdr[8])) |
| 55 | | (static_cast<fl::u32>(static_cast<fl::u8>(hdr[9])) << 8) |
| 56 | | (static_cast<fl::u32>(static_cast<fl::u8>(hdr[10])) << 16) |
| 57 | | (static_cast<fl::u32>(static_cast<fl::u8>(hdr[11])) << 24); |
| 58 | // Cap the JSON length against a defensive maximum BEFORE |
| 59 | // doing any size arithmetic — guards against a malformed |
| 60 | // file declaring a multi-gigabyte json_length that would |
| 61 | // either overflow the offset calc or trigger a huge resize. |
| 62 | const fl::size_t jsonLenSz = static_cast<fl::size_t>(jsonLen); |
| 63 | const fl::size_t fileSize = mHandle->size(); |
| 64 | const bool jsonInRange = jsonLenSz <= kFledMaxJsonBytes |
| 65 | && jsonLenSz <= fileSize - kFledHeaderBytes; |
| 66 | if (jsonInRange) { |
| 67 | mEmbeddedScreenMapJson.resize(static_cast<fl::size>(jsonLenSz)); |
| 68 | fl::size_t jr = jsonLenSz > 0 |
| 69 | ? mHandle->read(&mEmbeddedScreenMapJson[0], jsonLenSz) |
| 70 | : 0; |
| 71 | if (jr == jsonLenSz) { |
| 72 | mPayloadOffset = kFledHeaderBytes + jsonLenSz; |
| 73 | // Stream is now positioned at the first frame byte. |
| 74 | return mHandle->available(); |
| 75 | } |
| 76 | // JSON slurp short-read — abandon FLED interpretation. |
| 77 | mEmbeddedScreenMapJson.clear(); |
| 78 | } |
| 79 | } |
| 80 | // Not a FLED file (or header rejected). Rewind so subsequent |
| 81 | // reads see the file from byte 0 as raw RGB triplets. |
| 82 | mHandle->seek(0, seek_dir::beg); |
| 83 | } |
| 84 | return mHandle->available(); |
| 85 | } |
| 86 | return mHandle->available(mbytesPerFrame); |
| 87 | } |