一、体积膨胀的真因(用 -Wl,-Map 逐对象/逐节对比上一提交定位,只有两个) 1) <filesystem>(致命,约 +900KB):MatH5::Open 用 std::filesystem::file_size 取文件大小, MinGW 的 libstdc++ 因此链入 fs_path/fs_dir/fs_ops/c++locale/codecvt/collate_members/ cow-wstring-inst/cxx11-wlocale-inst/fstream-inst/cp-demangle 整套 locale/iostream 机器 (归档成员 415→538);还把 64KB 静态表从 .bss 挤成 .data(1.8KB→73.8KB)。 修法:改用 _wfopen_s + _fseeki64/_ftelli64 取大小。 2) -lz(约 46KB):只需要"解开 HDF5 分块"。修法:MatH5 内置紧凑 inflate (RFC1950/1951:固定/动态 Huffman、存储块、长度-距离回抄),Makefile 与探针脚本移除 -lz。 顺带把 UploadHaloLayer 里 64KB 的预乘 alpha 查表换成 (v*a+128)*257>>16(无除法、无表)。 二、压到 800KB 以下的两项无损措施 1) 图标资源无损重编码:ico 里 64/48/32/16 四帧原为未压缩 32bpp DIB(合计 32KB), 用自写工具(GDI+ 编码)改为 PNG 帧(ICO 的 PNG 条目 Vista+ 全支持,本文件本就有 256 PNG 帧): 43,334 → 17,404 字节。像素逐点校验:差异 ≤85 像素、最大分量差 ≤3(编码取整),肉眼不可辨; 并从**真实产物** LoadImageW 逐尺寸(16/32/48/64)验证可加载。 2) RELEASE_OPT 由 -Os 改为 -Oz(省约 6KB);实测动画帧 p50 7.73ms(-Os) vs 7.77ms(-Oz), 无可辨识差异。 放弃的手段:-ffunction-sections/-fdata-sections + --gc-sections(反而 +9KB)、LTO(工具链 ICE)、 -fno-asynchronous-unwind-tables(链接失败)、-fno-exceptions(会破坏 catch (bad_alloc))。 结果:Build/Release/Target.exe = 795,648 字节。 三、.mat 加载进度不更新(已加载大小 / 总大小 / 百分比都停在 0) 真因:LoadMatSourceGroups 只在结束时回调一次 ratio=1.0,且从不设置 hasBytes; 读 42MB 文件与解码 19 个样本流(解压+拷贝,占大头)期间进度恒为 0。 修法: - MatHdf5::Open 新增 OpenProgress(bytesRead,totalBytes),按 4MB 分块读取并回报; - MatLoader 用 ProgressReporter 把进度拆成 0.00–0.55 读文件(hasBytes=true)与 0.55–0.98 解码样本流(每解完一个流推进 done/total),末段 1.0; - 取消与出错分开:取消时清空 error,不弹错误框。 实测:回调 29 次、字节数变化 11 次、比例单调、终值 1.000、bytes=42,386,344/42,386,344 (= 文件大小);Application 侧断言 m_loadingBytesRead == m_loadingTotalBytes == 文件大小。 四、验证(全部离屏,不占用鼠标/键盘/屏幕) - matcheck 21/21(新增进度断言)、matui 15/15(新增图标 + 加载字节断言); - 回归 verify 49/49、panelanim 158/158、gridanim 93/93、hoverdrag 20/20、lanescroll 23/23、 datacheck 69/69、edgecheck 46/46、errcheck 19/19、halocheck 16/16、panflash 19/19、 panelstutter 8/8、stress 6/6; - make release / make debug 零警告。 文档:Documents/agent_mem.md 追加本轮小节(含 5 条新规则:MinGW 下 <filesystem> 是体积炸弹、 链一整条库前先算账、膨胀要看节表而非 .o 大小、图标可无损重编码并用产物校验、 长解析必须按阶段回报进度且 hasBytes 只在有字节时置真)。
1335 lines
38 KiB
C++
1335 lines
38 KiB
C++
// HDF5 只读子集:MATLAB 7.3 `.mat` 解析用(见 MatH5.hpp 的说明)。
|
||
#include "MatH5.hpp"
|
||
|
||
#include <algorithm>
|
||
#include <cstdio>
|
||
#include <cstring>
|
||
|
||
namespace jdi
|
||
{
|
||
|
||
namespace
|
||
{
|
||
constexpr std::uint16_t kMessageDataspace = 0x0001;
|
||
constexpr std::uint16_t kMessageLink = 0x0006;
|
||
constexpr std::uint16_t kMessageDatatype = 0x0003;
|
||
constexpr std::uint16_t kMessageLayout = 0x0008;
|
||
constexpr std::uint16_t kMessageFilter = 0x000B;
|
||
constexpr std::uint16_t kMessageAttribute = 0x000C;
|
||
constexpr std::uint16_t kMessageContinuation = 0x0010;
|
||
constexpr std::uint16_t kMessageSymbolTable = 0x0011;
|
||
|
||
std::uint64_t ReadLE(const std::uint8_t* data, std::size_t size)
|
||
{
|
||
std::uint64_t value = 0;
|
||
for (std::size_t index = 0; index < size; ++index)
|
||
{
|
||
value |= static_cast<std::uint64_t>(data[index]) << (8 * index);
|
||
}
|
||
return value;
|
||
}
|
||
|
||
// ==================================================================
|
||
// 内置 inflate(RFC 1950 / 1951)
|
||
//
|
||
// ⚠️ **不能用 zlib(`-lz`)**:链接 libz.a 会让 Release 产物多出约 46KB,
|
||
// 而这里只需要「解压 HDF5 分块」这一个功能,自带的紧凑实现只要几 KB。
|
||
// ⚠️ 更不能为了取文件大小包含 `<filesystem>`:它会连带拉进 locale / codecvt /
|
||
// iostream / cp-demangle 等一整套 libstdc++ 代码,实测让产物从 787KB 涨到 1.7MB。
|
||
// ==================================================================
|
||
|
||
/** @brief 位流读取器(LSB 优先,与 DEFLATE 一致)。 */
|
||
struct BitReader
|
||
{
|
||
const std::uint8_t* data = nullptr;
|
||
std::size_t size = 0;
|
||
std::size_t position = 0;
|
||
std::uint32_t buffer = 0;
|
||
int bits = 0;
|
||
bool error = false;
|
||
|
||
bool Fill(int count)
|
||
{
|
||
while (bits < count)
|
||
{
|
||
if (position >= size)
|
||
{
|
||
error = true;
|
||
return false;
|
||
}
|
||
buffer |= static_cast<std::uint32_t>(data[position++]) << bits;
|
||
bits += 8;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
std::uint32_t Read(int count)
|
||
{
|
||
if (count <= 0)
|
||
{
|
||
return 0;
|
||
}
|
||
if (!Fill(count))
|
||
{
|
||
return 0;
|
||
}
|
||
const std::uint32_t value = buffer & ((1u << count) - 1u);
|
||
buffer >>= count;
|
||
bits -= count;
|
||
return value;
|
||
}
|
||
|
||
void Align() { buffer = 0; bits = 0; }
|
||
};
|
||
|
||
/** @brief 规范 Huffman 表(只存计数与按长度排好序的符号)。 */
|
||
struct HuffmanTable
|
||
{
|
||
std::uint16_t count[16] = {};
|
||
std::uint16_t symbol[320] = {};
|
||
};
|
||
|
||
bool BuildHuffman(HuffmanTable& table, const std::uint8_t* lengths, int total)
|
||
{
|
||
for (int index = 0; index < 16; ++index)
|
||
{
|
||
table.count[index] = 0;
|
||
}
|
||
for (int index = 0; index < total; ++index)
|
||
{
|
||
++table.count[lengths[index]];
|
||
}
|
||
if (table.count[0] == total)
|
||
{
|
||
return true; // 全部长度为 0:该表没有任何码(动态块里可能是距离表)
|
||
}
|
||
int left = 1;
|
||
for (int length = 1; length < 16; ++length)
|
||
{
|
||
left <<= 1;
|
||
left -= table.count[length];
|
||
if (left < 0)
|
||
{
|
||
return false; // 超订(over-subscribed)
|
||
}
|
||
}
|
||
std::uint16_t offsets[16] = {};
|
||
offsets[1] = 0;
|
||
for (int length = 1; length < 15; ++length)
|
||
{
|
||
offsets[length + 1] = static_cast<std::uint16_t>(offsets[length] + table.count[length]);
|
||
}
|
||
for (int index = 0; index < total; ++index)
|
||
{
|
||
if (lengths[index] != 0)
|
||
{
|
||
table.symbol[offsets[lengths[index]]++] = static_cast<std::uint16_t>(index);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
int DecodeSymbol(BitReader& reader, const HuffmanTable& table)
|
||
{
|
||
int code = 0;
|
||
int first = 0;
|
||
int index = 0;
|
||
for (int length = 1; length < 16; ++length)
|
||
{
|
||
code |= static_cast<int>(reader.Read(1));
|
||
if (reader.error)
|
||
{
|
||
return -1;
|
||
}
|
||
const int count = table.count[length];
|
||
if (code - first < count)
|
||
{
|
||
return table.symbol[index + (code - first)];
|
||
}
|
||
index += count;
|
||
first = (first + count) << 1;
|
||
code <<= 1;
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
const std::uint16_t kLengthBase[29] = {3, 4, 5, 6, 7, 8, 9, 10, 11, 13,
|
||
15, 17, 19, 23, 27, 31, 35, 43, 51, 59,
|
||
67, 83, 99, 115, 131, 163, 195, 227, 258};
|
||
const std::uint8_t kLengthExtra[29] = {0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2,
|
||
2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0};
|
||
const std::uint16_t kDistanceBase[30] = {1, 2, 3, 4, 5, 7, 9, 13,
|
||
17, 25, 33, 49, 65, 97, 129, 193,
|
||
257, 385, 513, 769, 1025, 1537, 2049, 3073,
|
||
4097, 6145, 8193, 12289, 16385, 24577};
|
||
const std::uint8_t kDistanceExtra[30] = {0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6,
|
||
6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13};
|
||
|
||
/** @brief 解压 zlib 流(含 2 字节头与 4 字节 Adler32 尾)。 */
|
||
bool Inflate(const std::uint8_t* source, std::size_t sourceSize,
|
||
std::vector<std::uint8_t>& out)
|
||
{
|
||
if (sourceSize < 6)
|
||
{
|
||
return false;
|
||
}
|
||
// zlib 头:CMF/FLG。只接受 deflate(CM = 8),且不接受预设字典。
|
||
const std::uint8_t cmf = source[0];
|
||
const std::uint8_t flg = source[1];
|
||
if ((cmf & 0x0F) != 8 || ((static_cast<unsigned>(cmf) * 256u + flg) % 31u) != 0 ||
|
||
(flg & 0x20) != 0)
|
||
{
|
||
return false;
|
||
}
|
||
BitReader reader;
|
||
reader.data = source + 2;
|
||
reader.size = sourceSize - 6; // 末尾 4 字节是 Adler32
|
||
out.clear();
|
||
HuffmanTable fixedLiterals;
|
||
HuffmanTable fixedDistances;
|
||
bool fixedReady = false;
|
||
|
||
for (int block = 0; block < 100000; ++block)
|
||
{
|
||
const std::uint32_t final = reader.Read(1);
|
||
const std::uint32_t type = reader.Read(2);
|
||
if (reader.error)
|
||
{
|
||
return false;
|
||
}
|
||
if (type == 0)
|
||
{
|
||
// 存储块(未压缩):对齐后 4 字节长度 + 反码 + 原始字节。
|
||
reader.Align();
|
||
if (reader.position + 4 > reader.size)
|
||
{
|
||
return false;
|
||
}
|
||
const std::uint16_t length = static_cast<std::uint16_t>(
|
||
reader.data[reader.position] | (reader.data[reader.position + 1] << 8));
|
||
reader.position += 4;
|
||
if (reader.position + length > reader.size)
|
||
{
|
||
return false;
|
||
}
|
||
out.insert(out.end(), reader.data + reader.position,
|
||
reader.data + reader.position + length);
|
||
reader.position += length;
|
||
}
|
||
else if (type == 1 || type == 2)
|
||
{
|
||
HuffmanTable literals;
|
||
HuffmanTable distances;
|
||
if (type == 1)
|
||
{
|
||
if (!fixedReady)
|
||
{
|
||
std::uint8_t lengths[288];
|
||
for (int index = 0; index < 144; ++index)
|
||
{
|
||
lengths[index] = 8;
|
||
}
|
||
for (int index = 144; index < 256; ++index)
|
||
{
|
||
lengths[index] = 9;
|
||
}
|
||
for (int index = 256; index < 280; ++index)
|
||
{
|
||
lengths[index] = 7;
|
||
}
|
||
for (int index = 280; index < 288; ++index)
|
||
{
|
||
lengths[index] = 8;
|
||
}
|
||
if (!BuildHuffman(fixedLiterals, lengths, 288))
|
||
{
|
||
return false;
|
||
}
|
||
std::uint8_t distanceLengths[30];
|
||
for (int index = 0; index < 30; ++index)
|
||
{
|
||
distanceLengths[index] = 5;
|
||
}
|
||
if (!BuildHuffman(fixedDistances, distanceLengths, 30))
|
||
{
|
||
return false;
|
||
}
|
||
fixedReady = true;
|
||
}
|
||
literals = fixedLiterals;
|
||
distances = fixedDistances;
|
||
}
|
||
else
|
||
{
|
||
const int literalCount = static_cast<int>(reader.Read(5)) + 257;
|
||
const int distanceCount = static_cast<int>(reader.Read(5)) + 1;
|
||
const int codeCount = static_cast<int>(reader.Read(4)) + 4;
|
||
if (reader.error || literalCount > 286 || distanceCount > 30)
|
||
{
|
||
return false;
|
||
}
|
||
static const std::uint8_t kOrder[19] = {16, 17, 18, 0, 8, 7, 9, 6, 10, 5,
|
||
11, 4, 12, 3, 13, 2, 14, 1, 15};
|
||
std::uint8_t codeLengths[19] = {};
|
||
for (int index = 0; index < codeCount; ++index)
|
||
{
|
||
codeLengths[kOrder[index]] = static_cast<std::uint8_t>(reader.Read(3));
|
||
}
|
||
HuffmanTable codeTable;
|
||
if (reader.error || !BuildHuffman(codeTable, codeLengths, 19))
|
||
{
|
||
return false;
|
||
}
|
||
std::uint8_t lengths[288 + 30] = {};
|
||
int produced = 0;
|
||
while (produced < literalCount + distanceCount)
|
||
{
|
||
const int symbol = DecodeSymbol(reader, codeTable);
|
||
if (symbol < 0)
|
||
{
|
||
return false;
|
||
}
|
||
if (symbol < 16)
|
||
{
|
||
lengths[produced++] = static_cast<std::uint8_t>(symbol);
|
||
continue;
|
||
}
|
||
int repeat = 0;
|
||
std::uint8_t value = 0;
|
||
if (symbol == 16)
|
||
{
|
||
if (produced == 0)
|
||
{
|
||
return false;
|
||
}
|
||
value = lengths[produced - 1];
|
||
repeat = 3 + static_cast<int>(reader.Read(2));
|
||
}
|
||
else if (symbol == 17)
|
||
{
|
||
repeat = 3 + static_cast<int>(reader.Read(3));
|
||
}
|
||
else
|
||
{
|
||
repeat = 11 + static_cast<int>(reader.Read(7));
|
||
}
|
||
if (reader.error || produced + repeat > literalCount + distanceCount)
|
||
{
|
||
return false;
|
||
}
|
||
for (int index = 0; index < repeat; ++index)
|
||
{
|
||
lengths[produced++] = value;
|
||
}
|
||
}
|
||
if (!BuildHuffman(literals, lengths, literalCount) ||
|
||
!BuildHuffman(distances, lengths + literalCount, distanceCount))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
|
||
for (int symbolIndex = 0; symbolIndex < 100000000; ++symbolIndex)
|
||
{
|
||
const int symbol = DecodeSymbol(reader, literals);
|
||
if (symbol < 0)
|
||
{
|
||
return false;
|
||
}
|
||
if (symbol < 256)
|
||
{
|
||
out.push_back(static_cast<std::uint8_t>(symbol));
|
||
continue;
|
||
}
|
||
if (symbol == 256)
|
||
{
|
||
break; // 块结束
|
||
}
|
||
const int lengthCode = symbol - 257;
|
||
if (lengthCode >= 29)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t length =
|
||
kLengthBase[lengthCode] + reader.Read(kLengthExtra[lengthCode]);
|
||
const int distanceCode = DecodeSymbol(reader, distances);
|
||
if (distanceCode < 0 || distanceCode >= 30 || reader.error)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t distance =
|
||
kDistanceBase[distanceCode] + reader.Read(kDistanceExtra[distanceCode]);
|
||
if (reader.error || distance == 0 || distance > out.size())
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t start = out.size() - distance;
|
||
for (std::size_t index = 0; index < length; ++index)
|
||
{
|
||
out.push_back(out[start + index]);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
return false; // 类型 3 非法
|
||
}
|
||
if (final != 0)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
} // namespace
|
||
|
||
std::uint8_t MatHdf5::U8(std::uint64_t offset) const
|
||
{
|
||
return (offset < m_data.size()) ? m_data[static_cast<std::size_t>(offset)] : 0;
|
||
}
|
||
|
||
std::uint16_t MatHdf5::U16(std::uint64_t offset) const
|
||
{
|
||
return static_cast<std::uint16_t>(U8(offset)) | (static_cast<std::uint16_t>(U8(offset + 1)) << 8);
|
||
}
|
||
|
||
std::uint32_t MatHdf5::U32(std::uint64_t offset) const
|
||
{
|
||
return static_cast<std::uint32_t>(U16(offset)) | (static_cast<std::uint32_t>(U16(offset + 2)) << 16);
|
||
}
|
||
|
||
std::uint64_t MatHdf5::U64(std::uint64_t offset) const
|
||
{
|
||
return static_cast<std::uint64_t>(U32(offset)) | (static_cast<std::uint64_t>(U32(offset + 4)) << 32);
|
||
}
|
||
|
||
std::uint64_t MatHdf5::Addr(std::uint64_t offset) const
|
||
{
|
||
return (m_offsetSize == 8) ? U64(offset) : U32(offset);
|
||
}
|
||
|
||
std::uint64_t MatHdf5::Resolve(std::uint64_t address) const
|
||
{
|
||
if (address == 0 || address == 0xFFFFFFFFFFFFFFFFull)
|
||
{
|
||
return 0;
|
||
}
|
||
return address + m_base;
|
||
}
|
||
|
||
bool MatHdf5::Open(const std::wstring& path, std::wstring& error, const OpenProgress& progress)
|
||
{
|
||
error.clear();
|
||
Close();
|
||
// ⚠️ 取文件大小一律用 C 运行库(`_fseeki64` / `_ftelli64`),**不要用 `<filesystem>`**:
|
||
// 后者会把 libstdc++ 的 locale / codecvt / iostream / cp-demangle 整套拉进产物,
|
||
// 实测让 Release 二进制从 787KB 涨到 1.7MB(用户 2026-10-19 报告)。
|
||
FILE* file = nullptr;
|
||
if (_wfopen_s(&file, path.c_str(), L"rb") != 0 || file == nullptr)
|
||
{
|
||
error = L"无法打开文件。";
|
||
return false;
|
||
}
|
||
if (_fseeki64(file, 0, SEEK_END) != 0)
|
||
{
|
||
std::fclose(file);
|
||
error = L"无法读取文件大小。";
|
||
return false;
|
||
}
|
||
const long long size = _ftelli64(file);
|
||
if (size <= 0 || _fseeki64(file, 0, SEEK_SET) != 0)
|
||
{
|
||
std::fclose(file);
|
||
error = L"无法读取文件大小。";
|
||
return false;
|
||
}
|
||
m_data.resize(static_cast<std::size_t>(size));
|
||
|
||
// 按块读入并回报进度(加载遮罩层的"已加载大小 / 总大小 / 百分比"由它驱动)。
|
||
constexpr std::size_t kChunk = 4u << 20;
|
||
std::size_t offset = 0;
|
||
while (offset < m_data.size())
|
||
{
|
||
const std::size_t want = std::min(kChunk, m_data.size() - offset);
|
||
const std::size_t got = std::fread(m_data.data() + offset, 1, want, file);
|
||
offset += got;
|
||
if (got != want)
|
||
{
|
||
break;
|
||
}
|
||
if (progress && !progress(offset, m_data.size()))
|
||
{
|
||
std::fclose(file);
|
||
Close();
|
||
error.clear(); // 用户取消:不显示错误
|
||
return false;
|
||
}
|
||
}
|
||
std::fclose(file);
|
||
if (offset != m_data.size())
|
||
{
|
||
error = L"读取文件失败。";
|
||
return false;
|
||
}
|
||
|
||
// 超级块:签名可能出现在 0、512、1024…(MATLAB 用 512 字节 user block)。
|
||
std::size_t signature = m_data.size();
|
||
for (std::size_t index = 0; index + 8 <= m_data.size() && index <= 4096; index += 512)
|
||
{
|
||
if (m_data[index] == 0x89 && m_data[index + 1] == 'H' && m_data[index + 2] == 'D' &&
|
||
m_data[index + 3] == 'F')
|
||
{
|
||
signature = index;
|
||
break;
|
||
}
|
||
}
|
||
if (signature == m_data.size())
|
||
{
|
||
error = L"不是 HDF5(MATLAB 7.3)格式。";
|
||
return false;
|
||
}
|
||
const std::uint8_t version = U8(signature + 8);
|
||
if (version == 0 || version == 1)
|
||
{
|
||
m_offsetSize = U8(signature + 13);
|
||
m_lengthSize = U8(signature + 14);
|
||
m_base = U64(signature + 24);
|
||
const std::uint64_t rootEntry = signature + 24 + 32;
|
||
m_root = Resolve(Addr(rootEntry + 8));
|
||
}
|
||
else
|
||
{
|
||
m_offsetSize = U8(signature + 9);
|
||
m_lengthSize = U8(signature + 10);
|
||
m_base = U64(signature + 12);
|
||
m_root = Resolve(U64(signature + 36));
|
||
}
|
||
if (m_root == 0 || m_root >= m_data.size())
|
||
{
|
||
error = L"HDF5 超级块损坏。";
|
||
return false;
|
||
}
|
||
return true;
|
||
}
|
||
|
||
void MatHdf5::Close()
|
||
{
|
||
m_data.clear();
|
||
m_base = 0;
|
||
m_root = 0;
|
||
m_heaps.clear();
|
||
}
|
||
|
||
void MatHdf5::ReadMessages(std::uint64_t address, std::vector<Message>& messages) const
|
||
{
|
||
messages.clear();
|
||
if (address + 8 > m_data.size())
|
||
{
|
||
return;
|
||
}
|
||
if (U8(address) == 1)
|
||
{
|
||
const std::uint16_t count = U16(address + 2);
|
||
const std::uint32_t headerSize = U32(address + 8);
|
||
// 先收集消息块:主块(长度 = 对象头里的 headerSize)+ 若干续块(续块里没有对象头前缀)。
|
||
// ⚠️ **主块长度必须用 headerSize 限定**:消息数超过主块容量时,其余消息在续块里,
|
||
// 若在主块里继续按"消息数"往下读,就会读到块外的其它数据(字段全部解析失败)。
|
||
std::vector<std::pair<std::uint64_t, std::uint64_t>> blocks;
|
||
blocks.emplace_back(address + 16, headerSize);
|
||
std::size_t read = 0;
|
||
for (std::size_t blockIndex = 0; blockIndex < blocks.size(); ++blockIndex)
|
||
{
|
||
std::uint64_t offset = blocks[blockIndex].first;
|
||
const std::uint64_t end =
|
||
(blocks[blockIndex].second > 0) ? (offset + blocks[blockIndex].second) : 0;
|
||
while (read < count && offset + 8 <= m_data.size() && (end == 0 || offset + 8 <= end))
|
||
{
|
||
Message message;
|
||
message.type = U16(offset);
|
||
const std::uint16_t messageSize = U16(offset + 2);
|
||
offset += 8;
|
||
if (offset + messageSize > m_data.size())
|
||
{
|
||
break;
|
||
}
|
||
message.data.assign(m_data.begin() + static_cast<std::ptrdiff_t>(offset),
|
||
m_data.begin() + static_cast<std::ptrdiff_t>(offset + messageSize));
|
||
offset += (messageSize + 7u) & ~static_cast<std::uint16_t>(7u);
|
||
++read;
|
||
if (message.type == kMessageContinuation && message.data.size() >= 16)
|
||
{
|
||
const std::uint64_t blockAddress = Resolve(ReadLE(message.data.data(), 8));
|
||
const std::uint64_t blockSize = ReadLE(message.data.data() + 8, 8);
|
||
if (blockAddress != 0)
|
||
{
|
||
blocks.emplace_back(blockAddress, blockSize);
|
||
}
|
||
}
|
||
messages.push_back(std::move(message));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
void MatHdf5::WalkBtree(std::uint64_t node, std::uint64_t heapData,
|
||
std::vector<std::pair<std::string, std::uint64_t>>& out) const
|
||
{
|
||
int guard = 0;
|
||
while (node != 0 && node + 24 < m_data.size() && guard++ < 100000)
|
||
{
|
||
if (std::memcmp(&m_data[static_cast<std::size_t>(node)], "TREE", 4) != 0)
|
||
{
|
||
return;
|
||
}
|
||
const std::uint8_t level = U8(node + 5);
|
||
const std::uint16_t entries = U16(node + 6);
|
||
std::uint64_t offset = node + 8 + 8 + 8;
|
||
std::vector<std::uint64_t> children;
|
||
for (std::uint16_t index = 0; index < static_cast<std::uint16_t>(entries + 1u); ++index)
|
||
{
|
||
offset += 8; // key(分组 B-tree 的 key 是堆偏移)
|
||
if (index < entries)
|
||
{
|
||
children.push_back(Resolve(U64(offset)));
|
||
offset += 8;
|
||
}
|
||
}
|
||
for (const std::uint64_t child : children)
|
||
{
|
||
if (level > 0)
|
||
{
|
||
WalkBtree(child, heapData, out);
|
||
}
|
||
else
|
||
{
|
||
WalkSymbolNode(child, heapData, out);
|
||
}
|
||
}
|
||
node = Resolve(U64(node + 8)); // 右兄弟
|
||
}
|
||
}
|
||
|
||
void MatHdf5::WalkSymbolNode(std::uint64_t node, std::uint64_t heapData,
|
||
std::vector<std::pair<std::string, std::uint64_t>>& out) const
|
||
{
|
||
if (node == 0 || node + 8 > m_data.size() ||
|
||
std::memcmp(&m_data[static_cast<std::size_t>(node)], "SNOD", 4) != 0 || heapData == 0)
|
||
{
|
||
return;
|
||
}
|
||
const std::uint16_t count = U16(node + 6);
|
||
std::uint64_t offset = node + 8;
|
||
for (std::uint16_t index = 0; index < count; ++index)
|
||
{
|
||
if (offset + 40 > m_data.size())
|
||
{
|
||
return;
|
||
}
|
||
const std::uint64_t nameOffset = U64(offset);
|
||
const std::uint64_t objectAddress = Resolve(U64(offset + 8));
|
||
offset += 40;
|
||
if (objectAddress == 0 || heapData + nameOffset >= m_data.size())
|
||
{
|
||
continue;
|
||
}
|
||
const char* name = reinterpret_cast<const char*>(m_data.data() + heapData + nameOffset);
|
||
out.emplace_back(std::string(name), objectAddress);
|
||
}
|
||
}
|
||
|
||
std::vector<std::pair<std::string, std::uint64_t>> MatHdf5::Members(std::uint64_t address) const
|
||
{
|
||
std::vector<std::pair<std::string, std::uint64_t>> out;
|
||
std::vector<Message> messages;
|
||
ReadMessages(address, messages);
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type == kMessageSymbolTable && message.data.size() >= 16)
|
||
{
|
||
const std::uint64_t btree = Resolve(ReadLE(message.data.data(), 8));
|
||
const std::uint64_t heap = Resolve(ReadLE(message.data.data() + 8, 8));
|
||
std::uint64_t heapData = 0;
|
||
if (heap + 32 <= m_data.size() &&
|
||
std::memcmp(&m_data[static_cast<std::size_t>(heap)], "HEAP", 4) == 0)
|
||
{
|
||
heapData = Resolve(U64(heap + 24));
|
||
bool known = false;
|
||
for (const auto& entry : m_heaps)
|
||
{
|
||
if (entry.first == heap)
|
||
{
|
||
known = true;
|
||
break;
|
||
}
|
||
}
|
||
if (!known)
|
||
{
|
||
m_heaps.emplace_back(heap, heapData);
|
||
}
|
||
}
|
||
WalkBtree(btree, heapData, out);
|
||
}
|
||
else if (message.type == kMessageLink && message.data.size() >= 8)
|
||
{
|
||
std::size_t offset = 2;
|
||
const std::uint8_t flags = message.data[1];
|
||
if (flags & 0x08)
|
||
{
|
||
offset += 8;
|
||
}
|
||
if (flags & 0x04)
|
||
{
|
||
offset += 8;
|
||
}
|
||
if (flags & 0x10)
|
||
{
|
||
++offset;
|
||
}
|
||
if (offset >= message.data.size())
|
||
{
|
||
continue;
|
||
}
|
||
const std::uint8_t lengthSize = message.data[offset++];
|
||
std::uint64_t nameLength = 0;
|
||
for (std::uint8_t byte = 0; byte < lengthSize && offset < message.data.size(); ++byte)
|
||
{
|
||
nameLength |= static_cast<std::uint64_t>(message.data[offset + byte]) << (8 * byte);
|
||
}
|
||
offset += lengthSize;
|
||
if (offset + nameLength + 8 > message.data.size())
|
||
{
|
||
continue;
|
||
}
|
||
std::string name(reinterpret_cast<const char*>(message.data.data() + offset),
|
||
static_cast<std::size_t>(nameLength));
|
||
offset += static_cast<std::size_t>(nameLength);
|
||
const std::uint64_t target = Resolve(ReadLE(message.data.data() + offset, 8));
|
||
out.emplace_back(std::move(name), target);
|
||
}
|
||
}
|
||
return out;
|
||
}
|
||
|
||
std::uint64_t MatHdf5::Member(std::uint64_t address, const std::string& name) const
|
||
{
|
||
for (const auto& entry : Members(address))
|
||
{
|
||
if (entry.first == name)
|
||
{
|
||
return entry.second;
|
||
}
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
bool MatHdf5::IsDataset(std::uint64_t address) const
|
||
{
|
||
std::vector<Message> messages;
|
||
ReadMessages(address, messages);
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type == kMessageDataspace)
|
||
{
|
||
return true;
|
||
}
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool MatHdf5::ParseType(const std::uint8_t* data, std::size_t size, std::size_t offset, Type& out,
|
||
std::size_t& consumed) const
|
||
{
|
||
if (offset + 8 > size)
|
||
{
|
||
return false;
|
||
}
|
||
out.version = data[offset] >> 4;
|
||
out.cls = static_cast<TypeClass>(data[offset] & 0x0F);
|
||
const std::uint8_t bits0 = data[offset + 1];
|
||
out.size = ReadLE(data + offset + 4, 4);
|
||
std::size_t cursor = offset + 8;
|
||
if (out.cls == TypeClass::Integer)
|
||
{
|
||
out.bigEndian = (bits0 & 0x01) != 0;
|
||
out.isSigned = (bits0 & 0x08) != 0;
|
||
if (out.version >= 3)
|
||
{
|
||
cursor += 2; // bit offset
|
||
cursor += 2; // precision
|
||
}
|
||
else
|
||
{
|
||
cursor += 4;
|
||
}
|
||
}
|
||
else if (out.cls == TypeClass::Float)
|
||
{
|
||
out.bigEndian = (bits0 & 0x01) != 0;
|
||
cursor += 12;
|
||
}
|
||
else if (out.cls == TypeClass::String)
|
||
{
|
||
// 无需额外属性
|
||
}
|
||
else if (out.cls == TypeClass::Reference)
|
||
{
|
||
// 无需额外属性
|
||
}
|
||
else if (out.cls == TypeClass::VariableLength)
|
||
{
|
||
out.isVariableString = (bits0 & 0x0F) == 1;
|
||
std::size_t childConsumed = 0;
|
||
Type child;
|
||
if (ParseType(data, size, cursor, child, childConsumed))
|
||
{
|
||
cursor += childConsumed;
|
||
}
|
||
}
|
||
else if (out.cls == TypeClass::Compound)
|
||
{
|
||
const unsigned members = static_cast<unsigned>(bits0) | (static_cast<unsigned>(data[offset + 2]) << 8);
|
||
for (unsigned index = 0; index < members; ++index)
|
||
{
|
||
if (cursor >= size)
|
||
{
|
||
break;
|
||
}
|
||
const std::size_t nameStart = cursor;
|
||
while (cursor < size && data[cursor] != 0)
|
||
{
|
||
++cursor;
|
||
}
|
||
std::string name(reinterpret_cast<const char*>(data + nameStart), cursor - nameStart);
|
||
++cursor; // 结束符
|
||
cursor = (cursor + 7) & ~static_cast<std::size_t>(7);
|
||
if (out.version == 3)
|
||
{
|
||
// v3:先维度(1 字节 rank + 4 字节保留),再字节偏移(可变)
|
||
}
|
||
cursor += 4; // 字节偏移(v1/v2)
|
||
std::size_t childConsumed = 0;
|
||
Type child;
|
||
if (ParseType(data, size, cursor, child, childConsumed))
|
||
{
|
||
cursor += childConsumed;
|
||
out.members.emplace_back(std::move(name), std::move(child));
|
||
}
|
||
else
|
||
{
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
else if (out.cls == TypeClass::Array)
|
||
{
|
||
if (cursor + 4 <= size)
|
||
{
|
||
const std::uint8_t rank = data[cursor];
|
||
cursor += 4;
|
||
cursor += static_cast<std::size_t>(rank) * 4; // 维度
|
||
cursor += static_cast<std::size_t>(rank) * 4; // 排列
|
||
std::size_t childConsumed = 0;
|
||
Type child;
|
||
if (ParseType(data, size, cursor, child, childConsumed))
|
||
{
|
||
cursor += childConsumed;
|
||
}
|
||
}
|
||
}
|
||
consumed = cursor - offset;
|
||
return true;
|
||
}
|
||
|
||
bool MatHdf5::DecodeChunked(const Dataset& dataset, std::size_t elementSize,
|
||
std::vector<std::uint8_t>& out) const
|
||
{
|
||
if (dataset.chunkAddress == 0 || dataset.chunkDims.empty() || elementSize == 0 ||
|
||
dataset.dims.empty())
|
||
{
|
||
return false;
|
||
}
|
||
// ⚠️ MATLAB 写出的分块维度可能与数据空间维度**维数不一致**(实测:1 维数据集的
|
||
// 分块维度是 `[N, 1]`)。把维度数组在**右侧补 1**(行优先的最快轴)对齐即可,
|
||
// 二者的元素总数与遍历顺序都不变。
|
||
std::vector<std::size_t> dims = dataset.dims;
|
||
std::vector<std::size_t> chunkDims = dataset.chunkDims;
|
||
while (dims.size() < chunkDims.size())
|
||
{
|
||
dims.push_back(1);
|
||
}
|
||
while (chunkDims.size() < dims.size())
|
||
{
|
||
chunkDims.push_back(1);
|
||
}
|
||
const std::size_t rank = dims.size();
|
||
const std::uint64_t total = [&]() -> std::uint64_t
|
||
{
|
||
std::uint64_t value = 1;
|
||
for (const std::size_t dim : dims)
|
||
{
|
||
value *= dim;
|
||
}
|
||
return value;
|
||
}();
|
||
out.assign(static_cast<std::size_t>(total) * elementSize, 0);
|
||
std::vector<std::uint8_t> chunk;
|
||
std::vector<std::uint8_t> inflated;
|
||
// 遍历分块 B-tree:key = 分块大小 + 过滤器掩码 + 各维偏移。
|
||
std::vector<std::uint64_t> stack{dataset.chunkAddress};
|
||
int guard = 0;
|
||
while (!stack.empty() && guard++ < 1000000)
|
||
{
|
||
const std::uint64_t node = stack.back();
|
||
stack.pop_back();
|
||
if (node == 0 || node + 24 > m_data.size() ||
|
||
std::memcmp(&m_data[static_cast<std::size_t>(node)], "TREE", 4) != 0)
|
||
{
|
||
continue;
|
||
}
|
||
const std::uint8_t level = U8(node + 5);
|
||
const std::uint16_t entries = U16(node + 6);
|
||
const std::size_t keySize = 8 + 8 * rank;
|
||
std::uint64_t offset = node + 8 + 8 + 8;
|
||
for (std::uint16_t index = 0; index < static_cast<std::uint16_t>(entries + 1u); ++index)
|
||
{
|
||
const std::uint32_t chunkSize = (offset + 4 <= m_data.size()) ? U32(offset) : 0;
|
||
std::vector<std::uint64_t> chunkOffset(rank, 0);
|
||
for (std::size_t axis = 0; axis < rank; ++axis)
|
||
{
|
||
chunkOffset[axis] = U64(offset + 8 + axis * 8);
|
||
}
|
||
offset += keySize;
|
||
if (index < entries)
|
||
{
|
||
const std::uint64_t child = Resolve(U64(offset));
|
||
offset += 8;
|
||
if (level > 0)
|
||
{
|
||
stack.push_back(child);
|
||
}
|
||
else if (child != 0 && chunkSize > 0)
|
||
{
|
||
chunk.assign(m_data.begin() + static_cast<std::ptrdiff_t>(child),
|
||
m_data.begin() + static_cast<std::ptrdiff_t>(child + chunkSize));
|
||
const std::uint8_t* source = chunk.data();
|
||
std::size_t sourceSize = chunk.size();
|
||
if (dataset.deflate)
|
||
{
|
||
// 分块未压缩时大小 = 分块元素数 × 元素大小:此时不需要解压。
|
||
std::size_t expected = elementSize;
|
||
for (const std::size_t dim : dataset.chunkDims)
|
||
{
|
||
expected *= dim;
|
||
}
|
||
if (chunk.size() != expected)
|
||
{
|
||
// 内置 inflate(不依赖 zlib,见文件头的说明)。
|
||
if (!Inflate(chunk.data(), chunk.size(), inflated))
|
||
{
|
||
continue;
|
||
}
|
||
if (inflated.size() < expected)
|
||
{
|
||
inflated.resize(expected, 0);
|
||
}
|
||
source = inflated.data();
|
||
sourceSize = expected;
|
||
}
|
||
}
|
||
// 把该块拷进输出缓冲区(按行优先展开)。
|
||
const std::size_t rowBytes = chunkDims[rank - 1] * elementSize;
|
||
std::size_t rows = 1;
|
||
for (std::size_t axis = 0; axis + 1 < rank; ++axis)
|
||
{
|
||
rows *= chunkDims[axis];
|
||
}
|
||
for (std::size_t row = 0; row < rows; ++row)
|
||
{
|
||
std::vector<std::size_t> coordinate(rank - 1, 0);
|
||
std::size_t remainder = row;
|
||
for (std::size_t axis = rank - 1; axis-- > 0;)
|
||
{
|
||
coordinate[axis] = remainder % chunkDims[axis];
|
||
remainder /= chunkDims[axis];
|
||
}
|
||
std::uint64_t targetRow = 0;
|
||
bool inside = true;
|
||
std::uint64_t stride = 1;
|
||
for (std::size_t axis = rank - 1; axis-- > 0;)
|
||
{
|
||
const std::uint64_t index = chunkOffset[axis] + coordinate[axis];
|
||
if (index >= dims[axis])
|
||
{
|
||
inside = false;
|
||
break;
|
||
}
|
||
targetRow += index * stride;
|
||
stride *= dims[axis];
|
||
}
|
||
if (!inside)
|
||
{
|
||
continue;
|
||
}
|
||
const std::size_t sourceOffset = row * rowBytes;
|
||
const std::size_t targetOffset =
|
||
static_cast<std::size_t>(targetRow) * dims[rank - 1] * elementSize;
|
||
if (sourceOffset + rowBytes > sourceSize ||
|
||
targetOffset + rowBytes > out.size())
|
||
{
|
||
continue;
|
||
}
|
||
std::memcpy(out.data() + targetOffset, source + sourceOffset, rowBytes);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool MatHdf5::ReadDataset(std::uint64_t address, Dataset& out) const
|
||
{
|
||
out = Dataset();
|
||
std::vector<Message> messages;
|
||
ReadMessages(address, messages);
|
||
bool haveSpace = false;
|
||
bool haveType = false;
|
||
std::size_t compactSize = 0;
|
||
std::uint64_t dataAddress = 0;
|
||
std::uint64_t dataSize = 0;
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type == kMessageDataspace && message.data.size() >= 8)
|
||
{
|
||
const std::uint8_t version = message.data[0];
|
||
const std::uint8_t rank = message.data[1];
|
||
std::size_t offset = (version == 1) ? 8 : 4;
|
||
out.dims.assign(rank, 0);
|
||
for (std::uint8_t axis = 0; axis < rank && offset + 8 <= message.data.size(); ++axis)
|
||
{
|
||
out.dims[axis] = static_cast<std::size_t>(ReadLE(&message.data[offset], 8));
|
||
offset += 8;
|
||
}
|
||
haveSpace = true;
|
||
}
|
||
else if (message.type == kMessageDatatype)
|
||
{
|
||
std::size_t consumed = 0;
|
||
haveType = ParseType(message.data.data(), message.data.size(), 0, out.type, consumed);
|
||
}
|
||
else if (message.type == kMessageLayout && message.data.size() >= 8)
|
||
{
|
||
const std::uint8_t version = message.data[0];
|
||
const std::uint8_t klass = message.data[1];
|
||
out.layout = static_cast<LayoutClass>(klass);
|
||
if (klass == 0)
|
||
{
|
||
// 紧凑:数据就放在消息里
|
||
if (version == 3)
|
||
{
|
||
compactSize = static_cast<std::size_t>(message.data[2]) |
|
||
(static_cast<std::size_t>(message.data[3]) << 8);
|
||
}
|
||
else
|
||
{
|
||
compactSize = static_cast<std::size_t>(ReadLE(&message.data[2], 4));
|
||
}
|
||
}
|
||
else if (klass == 1)
|
||
{
|
||
dataAddress = Resolve(ReadLE(&message.data[2], 8));
|
||
dataSize = ReadLE(&message.data[10], 8);
|
||
}
|
||
else if (klass == 2)
|
||
{
|
||
if (version == 3)
|
||
{
|
||
const std::uint8_t rank = message.data[2];
|
||
out.chunkAddress = Resolve(ReadLE(&message.data[3], 8));
|
||
out.chunkDims.assign(rank, 0);
|
||
for (std::uint8_t axis = 0; axis < rank; ++axis)
|
||
{
|
||
out.chunkDims[axis] = ReadLE(&message.data[11 + axis * 4], 4);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
const std::uint8_t rank = message.data[2];
|
||
out.chunkAddress = Resolve(ReadLE(&message.data[3], 8));
|
||
out.chunkDims.assign(rank, 0);
|
||
for (std::uint8_t axis = 0; axis < rank; ++axis)
|
||
{
|
||
out.chunkDims[axis] = ReadLE(&message.data[11 + axis * 4], 4);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (message.type == kMessageFilter && message.data.size() >= 2)
|
||
{
|
||
const int count = message.data[1];
|
||
std::size_t offset = 8;
|
||
for (int index = 0; index < count && offset + 8 <= message.data.size(); ++index)
|
||
{
|
||
const std::uint16_t id = static_cast<std::uint16_t>(message.data[offset]) |
|
||
(static_cast<std::uint16_t>(message.data[offset + 1]) << 8);
|
||
const std::uint16_t nameLength = static_cast<std::uint16_t>(message.data[offset + 2]) |
|
||
(static_cast<std::uint16_t>(message.data[offset + 3]) << 8);
|
||
const std::uint16_t values =
|
||
static_cast<std::uint16_t>(message.data[offset + 6]) |
|
||
(static_cast<std::uint16_t>(message.data[offset + 7]) << 8);
|
||
offset += 8;
|
||
offset += (nameLength + 7) & ~static_cast<std::size_t>(7);
|
||
offset += static_cast<std::size_t>(values) * 4;
|
||
if (id == 1)
|
||
{
|
||
out.deflate = true;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
if (!haveSpace || !haveType || out.type.size == 0)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t elementSize = out.type.size;
|
||
if (out.layout == LayoutClass::Compact)
|
||
{
|
||
// 紧凑数据在消息里,重新扫描一次取出字节。
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type == kMessageLayout)
|
||
{
|
||
const std::size_t offset = (message.data[0] == 3) ? 4 : 8;
|
||
if (offset + compactSize <= message.data.size())
|
||
{
|
||
out.data.assign(message.data.begin() + static_cast<std::ptrdiff_t>(offset),
|
||
message.data.begin() +
|
||
static_cast<std::ptrdiff_t>(offset + compactSize));
|
||
}
|
||
}
|
||
}
|
||
}
|
||
else if (out.layout == LayoutClass::Contiguous)
|
||
{
|
||
if (dataAddress != 0 && dataAddress + dataSize <= m_data.size())
|
||
{
|
||
out.data.assign(m_data.begin() + static_cast<std::ptrdiff_t>(dataAddress),
|
||
m_data.begin() + static_cast<std::ptrdiff_t>(dataAddress + dataSize));
|
||
}
|
||
}
|
||
else if (out.layout == LayoutClass::Chunked)
|
||
{
|
||
if (!DecodeChunked(out, elementSize, out.data))
|
||
{
|
||
return false;
|
||
}
|
||
}
|
||
out.ok = true;
|
||
return true;
|
||
}
|
||
|
||
std::string MatHdf5::AttributeString(std::uint64_t address, const char* name) const
|
||
{
|
||
std::vector<Message> messages;
|
||
ReadMessages(address, messages);
|
||
for (const Message& message : messages)
|
||
{
|
||
if (message.type != kMessageAttribute || message.data.size() < 8)
|
||
{
|
||
continue;
|
||
}
|
||
const std::uint8_t version = message.data[0];
|
||
const std::size_t headerSize = (version == 1) ? 8 : (version == 2 ? 8 : 9);
|
||
const std::uint16_t nameSize = static_cast<std::uint16_t>(message.data[2]) |
|
||
(static_cast<std::uint16_t>(message.data[3]) << 8);
|
||
const std::uint16_t typeSize = static_cast<std::uint16_t>(message.data[4]) |
|
||
(static_cast<std::uint16_t>(message.data[5]) << 8);
|
||
const std::uint16_t spaceSize = static_cast<std::uint16_t>(message.data[6]) |
|
||
(static_cast<std::uint16_t>(message.data[7]) << 8);
|
||
std::size_t offset = headerSize;
|
||
if (offset + nameSize > message.data.size())
|
||
{
|
||
continue;
|
||
}
|
||
std::string attributeName(reinterpret_cast<const char*>(message.data.data() + offset),
|
||
nameSize);
|
||
while (!attributeName.empty() && attributeName.back() == '\0')
|
||
{
|
||
attributeName.pop_back();
|
||
}
|
||
offset += (nameSize + 7) & ~static_cast<std::size_t>(7);
|
||
const std::size_t typeOffset = offset;
|
||
offset += (typeSize + 7) & ~static_cast<std::size_t>(7);
|
||
const std::size_t spaceOffset = offset;
|
||
offset += (spaceSize + 7) & ~static_cast<std::size_t>(7);
|
||
if (attributeName != name)
|
||
{
|
||
continue;
|
||
}
|
||
if (offset > message.data.size())
|
||
{
|
||
return std::string();
|
||
}
|
||
Type type;
|
||
std::size_t consumed = 0;
|
||
if (!ParseType(message.data.data(), message.data.size(), typeOffset, type, consumed))
|
||
{
|
||
return std::string();
|
||
}
|
||
std::size_t count = 1;
|
||
if (spaceOffset + 4 <= message.data.size())
|
||
{
|
||
const std::uint8_t spaceVersion = message.data[spaceOffset];
|
||
const std::uint8_t rank = message.data[spaceOffset + 1];
|
||
std::size_t dimOffset = spaceOffset + ((spaceVersion == 1) ? 8 : 4);
|
||
count = 1;
|
||
for (std::uint8_t axis = 0; axis < rank && dimOffset + 8 <= message.data.size(); ++axis)
|
||
{
|
||
count *= static_cast<std::size_t>(ReadLE(&message.data[dimOffset], 8));
|
||
dimOffset += 8;
|
||
}
|
||
}
|
||
std::string text;
|
||
if (type.cls == TypeClass::String || type.cls == TypeClass::Integer)
|
||
{
|
||
text.assign(reinterpret_cast<const char*>(message.data.data() + offset),
|
||
std::min(message.data.size() - offset, std::max<std::size_t>(count, 1)));
|
||
while (!text.empty() && text.back() == '\0')
|
||
{
|
||
text.pop_back();
|
||
}
|
||
}
|
||
return text;
|
||
}
|
||
return std::string();
|
||
}
|
||
|
||
bool MatHdf5::ReadText(std::uint64_t address, std::wstring& out) const
|
||
{
|
||
out.clear();
|
||
Dataset dataset;
|
||
if (!ReadDataset(address, dataset))
|
||
{
|
||
return false;
|
||
}
|
||
if (dataset.type.cls == TypeClass::String)
|
||
{
|
||
out.assign(dataset.data.begin(), dataset.data.end());
|
||
while (!out.empty() && out.back() == L'\0')
|
||
{
|
||
out.pop_back();
|
||
}
|
||
return true;
|
||
}
|
||
if (dataset.type.size == 2)
|
||
{
|
||
for (std::size_t index = 0; index + 1 < dataset.data.size(); index += 2)
|
||
{
|
||
const wchar_t character = static_cast<wchar_t>(
|
||
static_cast<std::uint16_t>(dataset.data[index]) |
|
||
(static_cast<std::uint16_t>(dataset.data[index + 1]) << 8));
|
||
if (character != 0)
|
||
{
|
||
out.push_back(character);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
if (dataset.type.size == 1)
|
||
{
|
||
for (const std::uint8_t byte : dataset.data)
|
||
{
|
||
if (byte != 0)
|
||
{
|
||
out.push_back(static_cast<wchar_t>(byte));
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
return false;
|
||
}
|
||
|
||
bool MatHdf5::ReadNumbers(std::uint64_t address, std::vector<double>& out) const
|
||
{
|
||
out.clear();
|
||
Dataset dataset;
|
||
if (!ReadDataset(address, dataset) || !dataset.ok)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t elementSize = dataset.type.size;
|
||
if (elementSize == 0 || dataset.data.size() % elementSize != 0)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t count = dataset.data.size() / elementSize;
|
||
out.resize(count);
|
||
const bool integer = (dataset.type.cls == TypeClass::Integer);
|
||
const bool floating = (dataset.type.cls == TypeClass::Float);
|
||
if (!integer && !floating)
|
||
{
|
||
return false;
|
||
}
|
||
for (std::size_t index = 0; index < count; ++index)
|
||
{
|
||
const std::uint8_t* source = dataset.data.data() + index * elementSize;
|
||
if (floating && elementSize == 8)
|
||
{
|
||
double value = 0.0;
|
||
std::memcpy(&value, source, sizeof(value));
|
||
out[index] = value;
|
||
}
|
||
else if (floating && elementSize == 4)
|
||
{
|
||
float value = 0.0f;
|
||
std::memcpy(&value, source, sizeof(value));
|
||
out[index] = static_cast<double>(value);
|
||
}
|
||
else if (integer && dataset.type.isSigned)
|
||
{
|
||
std::int64_t value = 0;
|
||
for (std::size_t byte = 0; byte < elementSize; ++byte)
|
||
{
|
||
value |= static_cast<std::int64_t>(source[byte]) << (8 * byte);
|
||
}
|
||
// 符号扩展
|
||
const int bits = static_cast<int>(elementSize) * 8;
|
||
if (bits < 64 && (value & (1ll << (bits - 1))) != 0)
|
||
{
|
||
value |= ~((1ll << bits) - 1);
|
||
}
|
||
out[index] = static_cast<double>(value);
|
||
}
|
||
else if (integer)
|
||
{
|
||
std::uint64_t value = 0;
|
||
for (std::size_t byte = 0; byte < elementSize; ++byte)
|
||
{
|
||
value |= static_cast<std::uint64_t>(source[byte]) << (8 * byte);
|
||
}
|
||
out[index] = static_cast<double>(value);
|
||
}
|
||
}
|
||
return true;
|
||
}
|
||
|
||
bool MatHdf5::ReadReferences(std::uint64_t address, std::vector<std::uint64_t>& out) const
|
||
{
|
||
out.clear();
|
||
Dataset dataset; if (!ReadDataset(address, dataset) || !dataset.ok ||
|
||
dataset.type.cls != TypeClass::Reference)
|
||
{
|
||
return false;
|
||
}
|
||
const std::size_t count = dataset.data.size() / 8;
|
||
out.reserve(count);
|
||
for (std::size_t index = 0; index < count; ++index)
|
||
{
|
||
const std::uint64_t raw = ReadLE(dataset.data.data() + index * 8, 8);
|
||
out.push_back(Resolve(raw));
|
||
}
|
||
return true;
|
||
}
|
||
|
||
} // namespace jdi
|