From adf7256c390aee093392ca0097932eb7475aabf2 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sun, 26 Mar 2023 12:36:03 +0400 Subject: [PATCH] patterns/bsp: Added GoldSrc engine maps file format (#101) Co-authored-by: Nik --- README.md | 1 + patterns/bsp_goldsrc.hexpat | 208 ++++++++++++++++++ .../patterns/test_data/bsp_goldsrc.hexpat.bsp | Bin 0 -> 34908 bytes 3 files changed, 209 insertions(+) create mode 100644 patterns/bsp_goldsrc.hexpat create mode 100644 tests/patterns/test_data/bsp_goldsrc.hexpat.bsp diff --git a/README.md b/README.md index 7bc4347..425e485 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Hex patterns, include patterns and magic files for the use with the ImHex Hex Ed | DS_Store | `application/octet-stream` | [`patterns/dsstore.hexpat`](patterns/dsstore.hexpat) | .DS_Store file format | | UEFI | | [`patterns/uefi.hexpat`](patterns/uefi.hexpat)` | UEFI structs for parsing efivars | | EVTX | | [`patterns/evtx.hexpat`](patterns/evtx.hexpat) | MS Windows Vista Event Log | +| BSP | | [`patterns/bsp_goldsrc.hexpat`](patterns/bsp_goldsrc.hexpat) | GoldSrc engine maps format (used in Half-Life 1) | ### Scripts diff --git a/patterns/bsp_goldsrc.hexpat b/patterns/bsp_goldsrc.hexpat new file mode 100644 index 0000000..4f3058c --- /dev/null +++ b/patterns/bsp_goldsrc.hexpat @@ -0,0 +1,208 @@ +#include +#include +#include + +#pragma endian little + +#define HEADER_LUMPS 15 +#define MAX_MAP_HULLS 4 +#define NUM_AMBIENTS 4 +#define MAXLIGHTMAPS 4 + +enum LumpIndex : u32 { + Entities, + Planes, + Textures, + Vertexes, + Visibility, + Nodes, + Texinfo, + Faces, + Lighting, + Clipnodes, + Leafs, + Marksurfaces, + Edges, + Surfedges, + Models, +}; + +struct vec3f +{ + float x, y, z; +}; + +struct dlump_t +{ + s32 fileofs; + s32 filelen; +}; + +struct dheader_t +{ + s32 version; + dlump_t lumps[HEADER_LUMPS]; + std::assert(version == 30, "This version of BSP format is unsupported."); +}; + +struct dmodel_t +{ + vec3f mins; + vec3f maxs; + vec3f origin; // for sounds or lights + s32 headnode[MAX_MAP_HULLS]; + s32 visleafs; // not including the solid leaf 0 + s32 firstface; + s32 numfaces; +}; + +struct dplane_t +{ + vec3f normal; + float dist; + s32 type; +}; + +struct dtexinfo_t +{ + float vecs[8]; // texmatrix [s/t][xyz offset] + s32 miptex; + s16 flags; + s16 faceinfo; // -1 no face info otherwise dfaceinfo_t +}; + +struct dleaf_t +{ + s32 contents; + s32 visofs; // -1 = no visibility info + s16 mins[3]; + s16 maxs[3]; + u16 firstmarksurface; + u16 nummarksurfaces; + u8 ambient_level[NUM_AMBIENTS]; +}; + +struct dnode_t +{ + s32 planenum; + s16 children[2]; // negative numbers are -(leafs + 1), not nodes + s16 mins[3]; + s16 maxs[3]; + u16 firstface; + u16 numfaces; // counting both sides +}; + +struct dface_t +{ + u16 planenum; + s16 side; + s32 firstedge; + s16 numedges; + s16 texinfo; + u8 styles[MAXLIGHTMAPS]; + s32 lightofs; // start of [numstyles*surfsize] samples +}; + +struct dedge_t +{ + u16 v[2]; // indices of vertexes +}; + +struct dclipnode_t +{ + s32 planenum; + s16 children[2]; +}; + +using dmarkface_t = u16; +using dsurfedge_t = s32; +using dvertex_t = vec3f; + +fn get_miptex_pixeldata_size(auto width, auto height, auto offset) { + if (offset != 0) { + return std::mem::align_to(4, width * height * 85 / 64 + sizeof(u16) + 768); + } + else { + return 0; + } +}; + +struct miptex_t +{ + char name[16]; + u32 width; + u32 height; + u32 offsets[4]; // four mip maps stored + u8 pixeldata[get_miptex_pixeldata_size(width, height, offsets[0])]; +}; + +dheader_t file_header @ 0x00; + +fn get_lump_element_count(auto index, auto elem_size) { + return file_header.lumps[index].filelen / elem_size; +}; + +fn get_lump_address(auto index) { + return file_header.lumps[index].fileofs; +}; + +fn get_miptex_ptr_base(auto offset) { + return file_header.lumps[LumpIndex::Textures].fileofs; +}; + +struct MiptexPointer +{ + miptex_t *data: u32 [[pointer_base("get_miptex_ptr_base"), inline]]; +}; + +struct dmiptexlump_t +{ + s32 nummiptex; + MiptexPointer dataofs[nummiptex]; +}; + +struct VisibilityData +{ + u8 data[file_header.lumps[LumpIndex::Visibility].filelen]; + u8 pad[std::mem::align_to(4, sizeof(this)) - sizeof(this)]; +}; + +struct LightmapData +{ + u8 data[file_header.lumps[LumpIndex::Lighting].filelen]; + u8 pad[std::mem::align_to(4, sizeof(this)) - sizeof(this)]; +}; + +struct EntityData +{ + char text[]; + u8 pad[std::mem::align_to(4, sizeof(this)) - sizeof(this)]; +}; + +s32 models_count = get_lump_element_count(LumpIndex::Models, sizeof(dmodel_t)); +s32 vertexes_count = get_lump_element_count(LumpIndex::Vertexes, sizeof(vec3f)); +s32 planes_count = get_lump_element_count(LumpIndex::Planes, sizeof(dplane_t)); +s32 leafs_count = get_lump_element_count(LumpIndex::Leafs, sizeof(dleaf_t)); +s32 nodes_count = get_lump_element_count(LumpIndex::Nodes, sizeof(dnode_t)); +s32 faces_count = get_lump_element_count(LumpIndex::Faces, sizeof(dface_t)); +s32 markfaces_count = get_lump_element_count(LumpIndex::Marksurfaces, sizeof(dmarkface_t)); +s32 surfedges_count = get_lump_element_count(LumpIndex::Surfedges, sizeof(dsurfedge_t)); +s32 edges_count = get_lump_element_count(LumpIndex::Edges, sizeof(dedge_t)); +s32 clipnodes_count = get_lump_element_count(LumpIndex::Clipnodes, sizeof(dclipnode_t)); +s32 texinfo_count = get_lump_element_count(LumpIndex::Texinfo, sizeof(dtexinfo_t)); + +dmodel_t models_lump[models_count] @ get_lump_address(LumpIndex::Models); +dvertex_t vertexes_lump[vertexes_count] @ get_lump_address(LumpIndex::Vertexes); +dplane_t planes_lump[planes_count] @ get_lump_address(LumpIndex::Planes); +dleaf_t leafs_lump[leafs_count] @ get_lump_address(LumpIndex::Leafs); +dnode_t nodes_lump[nodes_count] @ get_lump_address(LumpIndex::Nodes); +dface_t faces_lump[faces_count] @ get_lump_address(LumpIndex::Faces); +dmarkface_t markfaces_lump[markfaces_count] @ get_lump_address(LumpIndex::Marksurfaces); +dsurfedge_t surfedges_lump[surfedges_count] @ get_lump_address(LumpIndex::Surfedges); +dedge_t edges_lump[edges_count] @ get_lump_address(LumpIndex::Edges); +dclipnode_t clipnodes_lump[clipnodes_count] @ get_lump_address(LumpIndex::Clipnodes); +dtexinfo_t texinfo_lump[texinfo_count] @ get_lump_address(LumpIndex::Texinfo); +dmiptexlump_t textures_lump @ get_lump_address(LumpIndex::Textures); +VisibilityData visdata_lump @ get_lump_address(LumpIndex::Visibility); +LightmapData lightdata_lump @ get_lump_address(LumpIndex::Lighting); +EntityData entdata_lump @ get_lump_address(LumpIndex::Entities); diff --git a/tests/patterns/test_data/bsp_goldsrc.hexpat.bsp b/tests/patterns/test_data/bsp_goldsrc.hexpat.bsp new file mode 100644 index 0000000000000000000000000000000000000000..9e1985c7da5e70ff4998b4a49df62f8def8fd9ca GIT binary patch literal 34908 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 IIG_Ii0%E*NKmY&$ literal 0 HcmV?d00001