Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270// sample_fs.cpp — host-side virtual sample filesystem.//// This module parses .mfs files and edits the image-relative QSPI backing// store. It does not know about Main, Unicorn, or the memory-mapped QSPI// window.
#include "storage/sample_fs.hpp"#include "storage/qspi_image.hpp"
#include <algorithm>#include <array>#include <cstdint>#include <cstring>#include <limits>#include <string_view>#include <utility>#include <vector>
namespace mf::storage::sample_fs {
namespace {constexpr uint32_t kFsBase = 0x00281000U;constexpr uint32_t kFsEnd = 0x00F51000U;constexpr uint32_t kSampleMax = 0x00177000U;constexpr uint32_t kRecordHeader = 0x18U;
struct MfsSample { std::string name; std::vector<uint8_t> pcm;};
// Cursor over the Boost text archive. Strings are length-prefixed and// followed by exactly one separator space (names may contain spaces), while// integers are whitespace-separated.class TextScanner { public: explicit TextScanner(const std::span<const std::byte> bytes) : p_(reinterpret_cast<const char *>(bytes.data())), end_(p_ + bytes.size()) {}
bool read_i64(int64_t &v) { while (p_ < end_ && (*p_ == ' ' || *p_ == '\n' || *p_ == '\r' || *p_ == '\t')) { p_++; } bool neg = false; if (p_ < end_ && (*p_ == '-' || *p_ == '+')) { neg = *p_ == '-'; p_++; } if (p_ >= end_ || *p_ < '0' || *p_ > '9') { return false; } int64_t acc = 0; while (p_ < end_ && *p_ >= '0' && *p_ <= '9') { if (acc > (std::numeric_limits<int64_t>::max() - 9) / 10) { return false; } acc = acc * 10 + (*p_ - '0'); p_++; } v = neg ? -acc : acc; return true; }
bool read_u64(uint64_t &v) { int64_t i = 0; if (!read_i64(i) || i < 0) { return false; } v = static_cast<uint64_t>(i); return true; }
bool skip(const size_t n) { if (static_cast<size_t>(end_ - p_) < n) { return false; } p_ += n; return true; }
bool read_raw(char *dst, const size_t n) { if (static_cast<size_t>(end_ - p_) < n) { return false; } std::memcpy(dst, p_, n); p_ += n; return true; }
private: const char *p_; const char *end_;};
// Parse a `.mfs` (factory sample or Elektroid-exported one).bool parse_mfs(const std::span<const std::byte> bytes, MfsSample &out, std::string &why) { TextScanner s(bytes); uint64_t tag_len = 0; std::array<char, 32> tag{}; if (!s.read_u64(tag_len) || tag_len + 1 > tag.size() || !s.skip(1) || !s.read_raw(tag.data(), tag_len) || std::string_view(tag.data(), tag_len) != "serialization::archive") { why = "not a Boost text archive"; return false; }
int64_t major = 0; int64_t zero1 = 0; int64_t four = 0; uint64_t ver_len = 0; if (!s.read_i64(major) || !s.read_i64(zero1) || !s.read_i64(four) || !s.read_u64(ver_len) || major != 10 || zero1 != 0 || four != 4 || !s.skip(1) || !s.skip(ver_len)) { why = "bad archive prologue"; return false; }
uint64_t name_len = 0; if (!s.read_u64(name_len) || name_len == 0 || name_len > 255 || !s.skip(1)) { why = "bad name length"; return false; } out.name.assign(name_len, '\0'); if (!s.read_raw(out.name.data(), name_len)) { why = "truncated name"; return false; }
int64_t p0 = 0; int64_t z2 = 0; int64_t z3 = 0; uint64_t zeros_len = 0; if (!s.read_i64(p0) || !s.read_i64(z2) || !s.read_i64(z3) || !s.read_u64(zeros_len) || zeros_len != 18 || !s.skip(1) || !s.skip(zeros_len)) { why = "bad sample header"; return false; }
int64_t p3 = 0; int64_t z4 = 0; int64_t p5 = 0; uint64_t datalen = 0; if (!s.read_i64(p3) || !s.read_i64(z4) || !s.read_i64(p5) || !s.read_u64(datalen)) { why = "missing data length"; return false; } if (datalen == 0) { why = "metadata stub (no sample data)"; return false; } if (datalen > kSampleMax) { why = "sample larger than 0x177000 bytes"; return false; }
out.pcm.resize(datalen); for (size_t i = 0; i < datalen; i++) { int64_t v = 0; if (!s.read_i64(v)) { why = "truncated sample data"; return false; } out.pcm[i] = static_cast<uint8_t>(v & 0xFF); } return true;}
uint32_t rd_le32(const std::byte *p) { return static_cast<uint32_t>(std::to_integer<uint8_t>(p[0])) | (static_cast<uint32_t>(std::to_integer<uint8_t>(p[1])) << 8) | (static_cast<uint32_t>(std::to_integer<uint8_t>(p[2])) << 16) | (static_cast<uint32_t>(std::to_integer<uint8_t>(p[3])) << 24);}
void wr_le16(std::byte *p, const uint16_t v) { p[0] = std::byte(v & 0xFF); p[1] = std::byte((v >> 8) & 0xFF);}
void wr_le32(std::byte *p, const uint32_t v) { p[0] = std::byte(v & 0xFF); p[1] = std::byte((v >> 8) & 0xFF); p[2] = std::byte((v >> 16) & 0xFF); p[3] = std::byte((v >> 24) & 0xFF);}
} // namespace
InstallResult install(const std::span<std::byte> image, const std::span<const std::byte> mfs, const uint32_t slot) { if (image.size() != qspi::kSize) { throw Error{Status::argument, "invalid QSPI image"}; } if (slot > 127) { throw Error{Status::argument, "sample slot out of range (0..127)"}; }
MfsSample sample; std::string why; if (!parse_mfs(mfs, sample, why)) { throw Error{Status::argument, ".mfs: " + why}; }
const uint32_t payload = static_cast<uint32_t>(sample.pcm.size()); uint32_t cksum = 0; for (size_t i = 0; i + 1 < sample.pcm.size(); i += 2) { cksum += static_cast<uint16_t>(sample.pcm[i] | (sample.pcm[i + 1] << 8)); }
std::array<std::byte, 13> rec_name{}; const size_t name_len = std::min<size_t>(sample.name.size(), 12); std::memcpy(rec_name.data(), sample.name.data(), name_len);
// Best-fit allocation over the record gaps, mirroring FUN_08045f50. const uint32_t rec_len = kRecordHeader + payload; uint32_t target = 0; uint32_t best_gap = UINT32_MAX; uint32_t gap_start = kFsBase; for (uint32_t cursor = kFsBase; cursor + kRecordHeader <= kFsEnd;) { const uint32_t rec_addr = rd_le32(image.data() + cursor); const uint32_t rec_size = rd_le32(image.data() + cursor + 4); const uint8_t rec_id = std::to_integer<uint8_t>(image[cursor + 0x17]); if (rec_addr != cursor) { cursor += 0x1000U; continue; } if (rec_id == 0xFF) { cursor += (rec_size & ~0xFFFU) + 0x1000U; continue; } if (rec_id == slot) { if (rec_size == payload && static_cast<uint16_t>(rd_le32(image.data() + cursor + 8) & 0xFFFF) == static_cast<uint16_t>(cksum) && std::memcmp(image.data() + cursor + 0x0A, rec_name.data(), rec_name.size()) == 0) { return InstallResult{sample.name, cursor, payload, true}; } throw Error{Status::argument, "sample slot occupied by a different sample"}; } const uint32_t gap = cursor - gap_start; if (gap >= rec_len && gap < best_gap) { best_gap = gap; target = gap_start; } cursor += (rec_size & ~0xFFFU) + 0x1000U; gap_start = cursor; }
const uint32_t tail = kFsEnd - gap_start; if (tail >= rec_len && tail < best_gap) { target = gap_start; } if (target == 0) { throw Error{Status::argument, "sample file system full"}; }
std::array<std::byte, kRecordHeader> hdr{}; wr_le32(hdr.data() + 0, target); wr_le32(hdr.data() + 4, payload); wr_le16(hdr.data() + 8, static_cast<uint16_t>(cksum)); std::memcpy(hdr.data() + 0x0A, rec_name.data(), rec_name.size()); hdr[0x17] = std::byte(slot);
std::memset(image.data() + target, 0, kRecordHeader + payload); std::memcpy(image.data() + target, hdr.data(), hdr.size()); std::memcpy(image.data() + target + kRecordHeader, sample.pcm.data(), payload); return InstallResult{sample.name, target, payload, false};}
} // namespace mf::storage::sample_fs