BSP是Quake系列引擎使用的二进制地图格式,C#直接读会失败,因其无统一Schema、字节序/编码/结构随引擎版本而异,需先校验魔数、按lump索引精确定位并手动处理大小端与\0截断。

什么是BSP文件,为什么C#直接读会失败
BSP(Binary Space Partitioning)是Quake系列引擎使用的地图二进制格式,不是文本,没有统一Schema,不同引擎版本(Quake 1 / Quake 2 / Half-Life / GoldSrc)的header结构、lump偏移、字节序(小端/大端)、字符串编码(ASCII / null-terminated)全都不一样。直接用BinaryReader硬读int或float大概率得到乱数——因为没对齐lump起始位置,也没跳过padding,更没处理LittleEndian强制转换。
- Quake 1 BSP:固定17个
lump,header共8字节,之后是lump_t[17]数组 - Half-Life BSP(GoldSrc):扩展到64个
lump,但前17个含义兼容,第32个开始才是模型实体数据 - 所有版本都要求先读
header,再按lump索引定位数据块,不能靠“经验偏移”硬算
怎么安全读取BSP header和lump描述符
核心是:别信“读4字节就是版本号”,先确认魔数(magic),再按对应规范解析。Quake 1/2用"IBSP"(小端ASCII),Half-Life用"VBSP";魔数错就立刻停,避免后续全崩。
- 用
FileStream打开文件,BinaryReader设leaveOpen: true,避免流提前关闭 - 先读4字节
magic,再读4字节version(注意:Quake 1是0x2e即46,Half-Life是0x2f即47) - 接着读
lump_t[64](统一按64读,超出部分offset == 0 && length == 0可跳过) - 每个
lump_t是8字节:int offset+int length,必须用BitConverter.ToInt32(buffer, i) & BitConverter.IsLittleEndian ? ... : ...手动翻转
// 示例:读lump 0(LUMP_ENTITIES) int lumpIndex = 0; int offset = BitConverter.ToInt32(lumpData, lumpIndex * 8); int length = BitConverter.ToInt32(lumpData, lumpIndex * 8 + 4); if (offset == 0 || length == 0) return null; byte[] entitiesRaw = new byte[length]; fs.Seek(offset, SeekOrigin.Begin); fs.Read(entitiesRaw, 0, length);
解析LUMP_ENTITIES时字符串怎么处理才不乱码
LUMP_ENTITIES是纯ASCII文本块,以{...}为单位,但没有UTF-8 BOM,没有行末\r\n保证,中间可能含\0。用Encoding.ASCII.GetString()直接转会截断——因为遇到第一个\0就停了。
必须按字节遍历,遇
{开始,遇匹配}结束,中间所有\0当空格或丢弃属性值用
key "value"格式,引号内可能有转义(\"、\n),得手动解不要用
Split('}'),Split('"')这种粗暴切法:实体里可能有嵌套JSON风格注释或路径(如model="models/player.mdl")建议先用
MemoryStream包装entitiesRaw,再逐字节状态机解析(start brace → key → quote → value → quote → end brace)遇到
\或\"要合并处理,单"不闭合就继续读
读完顶点和面数据后为啥渲染出来是黑的或翻转的
BSP中LUMP_VERTEXES存的是Vector3(3×float),LUMP_PLANES存的是Vector4(normal.x/y/z + dist),但Half-Life默认用左手坐标系,Quake 1是右手,且Z轴朝向相反。直接喂给Unity或OpenGL会镜像或背面剔除。
读
VERTEXES后,对每个点做v.Z = -v.Z(Quake→OpenGL适配常见操作)读
PLANES时,dist字段也要同步取反,否则平面方程ax+by+cz+d=0符号错LUMP_FACES里的plane number是索引,必须检查是否在PLANES长度范围内,越界就跳过——很多地图导出工具有bug,留了脏索引另外,
LUMP_LIGHTING是未压缩的亮度数组,每像素1字节,但采样步长取决于面的lightmapSize,不是简单按顺序贴图
实际解析比看起来琐碎得多:一个lump读错,后面全偏;一个字节序没翻,整个面法线反向;一个\0没跳过,实体属性截半。别指望“通用BSP库”一劳永逸——引擎差异就在这些细节里卡死。











