diff --git a/CMakeLists.txt b/CMakeLists.txt index 320531b..e84224c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -177,7 +177,6 @@ target_compile_options(${PROJECT_NAME} PRIVATE ) target_compile_options(${PROJECT_NAME} PRIVATE - $<$:-fno-exceptions> $<$:-fno-rtti> $<$:-Wold-style-cast> ) diff --git a/docs/command_line.md b/docs/command_line.md index 2149fb8..7db92f7 100644 --- a/docs/command_line.md +++ b/docs/command_line.md @@ -1,7 +1,6 @@ # Command Line Interface ELFBSP is a command-line tool. -It can handle multiple wad files, and while it modifies each file in-place, there is an option to backup each file first. The output to the terminal is fairly terse, but greater verbosity can be enabled. Generally all the maps in a wad will processed, but this can be limited to a specific set. @@ -40,11 +39,6 @@ elfbsp example.wad --map MAP04,MAP22-MAP25 # or you may combine both Produces more verbose output to the terminal. Some warnings which are normally hidden (except for a final tally) will be shown when enabled. -#### `-b --backup` -Backs up each input file before processing it. -The backup files will have the ".bak" extension (replacing the ".wad" extension). -If the backup file already exists, it will be silently overwritten. - #### `-f --fast` Enables a faster method for selecting partition lines. On large maps this can be significantly faster, however the BSP tree may not be as good. @@ -103,7 +97,7 @@ Generates CSV files containing multiple builds of the input maps, used for data #### `-o --output FILE` This option is provided *only* for compatibility with existing node builders. It causes the input file to be copied to the specified file, and that file is the one processed. -This option *cannot* be used with multiple input files, or with the --backup option. +This option *cannot* be used with multiple input files. #### `-h --help` Displays a brief help screen, then exits. diff --git a/src/blockmap.cpp b/src/blockmap.cpp index bcfb698..6fa4842 100644 --- a/src/blockmap.cpp +++ b/src/blockmap.cpp @@ -348,59 +348,21 @@ static void CompressBlockmap(level_t &level) level.block_compression = std::max(0.0, level.block_compression); } -// compute size of final BLOCKMAP lump. -static size_t CalcBlockmapSize(level_t &level, const char *Prefix, size_t PrefixSize, size_t NumSize, size_t RawHeaderSize) -{ - size_t size = PrefixSize; - size += RawHeaderSize; - size += EXTRA_LINES * NumSize; // null block - size += level.block_count * NumSize; // the pointers (indexes to the line lists) - - // add size of each block - for (size_t i = 0; i < level.block_count; i++) - { - size_t blk_num = level.block_duplicates[i]; - if (blk_num == NO_INDEX) continue; // ignore duplicate or empty blocks - const auto &blk = level.block_lines[blk_num]; - size += (blk.lines.size() + EXTRA_LINES) * NumSize; - } - - if (HAS_BIT(config.debug, DEBUG_BLOCKMAP)) - { - PrintLine(LOG_DEBUG, "[%s] Lump prefix header \'%s\', num type size of %zu, total size of %zu", __func__, Prefix, NumSize, - size); - } - - return size; -} - template -static void WriteBlockmap(level_t &level) +static void WriteBlockmap(WadIO &io, level_t &level) { constexpr size_t NumSize = sizeof(NumType); - size_t max_size = 0; if constexpr (format == BMAP_XBM1) { - max_size = CalcBlockmapSize(level, "XBM1", 8, NumSize, sizeof(raw_blockmap_xbm1_header_t)); - } - else - { - max_size = CalcBlockmapSize(level, "", 0, NumSize, sizeof(raw_blockmap_header_t)); - } - - Lump_c *lump = CreateLevelLump(level, "BLOCKMAP", max_size); - - if constexpr (format == BMAP_XBM1) - { - lump->Write("XBM1\0\0\0\0", 8); + io.AddToLump("XBM1\0\0\0\0", 8); raw_blockmap_xbm1_header_t xbm1_header; xbm1_header.x_origin = GetLittleEndian(IntToFixed(level.block_x)); xbm1_header.y_origin = GetLittleEndian(IntToFixed(level.block_y)); xbm1_header.x_blocks = GetLittleEndian(IndexToInt(level.block_w)); xbm1_header.y_blocks = GetLittleEndian(IndexToInt(level.block_h)); - lump->Write(&xbm1_header, sizeof(xbm1_header)); + io.AddToLump(&xbm1_header, sizeof(xbm1_header)); } else { @@ -409,7 +371,7 @@ static void WriteBlockmap(level_t &level) header.y_origin = GetLittleEndian(level.block_y); header.x_blocks = GetLittleEndian(IndexToShort(level.block_w)); header.y_blocks = GetLittleEndian(IndexToShort(level.block_h)); - lump->Write(&header, sizeof(header)); + io.AddToLump(&header, sizeof(header)); } // handle pointers @@ -420,12 +382,12 @@ static void WriteBlockmap(level_t &level) { PrintLine(LOG_ERROR, "ERROR: WriteBlockmap: offset %zu not set.", i); } - lump->Write(&ptr, NumSize); + io.AddToLump(&ptr, NumSize); } // add the null block which *all* empty blocks will use - lump->Write(&m_zero, NumSize); - lump->Write(&m_neg1, NumSize); + io.AddToLump(&m_zero, NumSize); + io.AddToLump(&m_neg1, NumSize); // handle each block list for (size_t i = 0; i < level.block_count; i++) @@ -437,26 +399,24 @@ static void WriteBlockmap(level_t &level) const auto &blk = level.block_lines[blk_num]; - lump->Write(&m_zero, NumSize); + io.AddToLump(&m_zero, NumSize); for (size_t line : blk.lines) { NumType le_line = GetLittleEndian(static_cast(line)); - lump->Write(&le_line, NumSize); + io.AddToLump(&le_line, NumSize); } - lump->Write(&m_neg1, NumSize); + io.AddToLump(&m_neg1, NumSize); } - - lump->Finish(); } -void PutBlockmap(level_t &level) +void PutBlockmap(WadIO &io, level_t &level) { auto mark = Benchmarker(__func__); + io.StartWritingLump("BLOCKMAP"); - // just create an empty blockmap lump + // just leave an empty blockmap lump if (level.linedefs.size() == 0) { - CreateLevelLump(level, "BLOCKMAP")->Finish(); return; } @@ -468,15 +428,14 @@ void PutBlockmap(level_t &level) switch (level.bmap_format) { case BMAP_DoomBSP: - WriteBlockmap(level); + WriteBlockmap(io, level); break; case BMAP_XBM1: - WriteBlockmap(level); + WriteBlockmap(io, level); break; default: // how did we get here // leave an empty blockmap lump - CreateLevelLump(level, "BLOCKMAP")->Finish(); PrintLine(LOG_NORMAL, "WARNING: Blockmap overflowed (lump will be empty)"); config.total_warnings++; break; diff --git a/src/bsp.cpp b/src/bsp.cpp index 417d3ba..0e6a892 100644 --- a/src/bsp.cpp +++ b/src/bsp.cpp @@ -21,14 +21,17 @@ // //------------------------------------------------------------------------------ -#include - +#include "bsp.hpp" #include "core.hpp" #include "local.hpp" +#include +#include +#include + // Upper-most bit is used for distinguishing sub-sectors, i.e tree leaves -constexpr uint16_t NF_SUBSECTOR_VANILLA = UINT16_C(0x8000); -constexpr uint32_t NF_SUBSECTOR = UINT32_C(0x80000000); +constexpr uint16_t NF_SUBSECTOR_VANILLA = BIT(15); +constexpr uint32_t NF_SUBSECTOR = BIT(31); // // Utility @@ -99,81 +102,42 @@ static inline short_angle_t VanillaSegAngle(const seg_t *seg) return result; } -static void PutVertices_Doom(level_t &level) +void PutVertices(WadIO &io, level_t &level, bool fractional, bool include_new_verts) { - // this size is worst-case scenario - size_t size = level.vertices.size() * sizeof(raw_vertex_t); - Lump_c *lump = CreateLevelLump(level, "VERTEXES", size); + const size_t vert_count = include_new_verts ? level.vertices.size() : level.num_old_vert; + io.StartWritingLump("VERTEXES"); - size_t count = 0; - for (size_t i = 0; i < level.vertices.size(); i++) + for (size_t i = 0; i < vert_count; i++) { - raw_vertex_t raw; - const vertex_t *vert = level.vertices[i]; - - // see: RoundOffVertices() - if (vert->is_new) + if (fractional) { - continue; + raw_vertex_fixed_t raw; + raw.x = GetLittleEndian(FloatToFixed(vert->x)); + raw.y = GetLittleEndian(FloatToFixed(vert->y)); + io.AddToLump(&raw, sizeof(raw)); + } + else + { + raw_vertex_short_t raw; + raw.x = GetLittleEndian(FloatToShort(floor(vert->x))); + raw.y = GetLittleEndian(FloatToShort(floor(vert->y))); + io.AddToLump(&raw, sizeof(raw)); } - - raw.x = GetLittleEndian(FloatToShort(floor(vert->x))); - raw.y = GetLittleEndian(FloatToShort(floor(vert->y))); - - lump->Write(&raw, sizeof(raw_vertex_t)); - - count++; - } - - lump->Finish(); - - if (count != level.num_old_vert) - { - PrintLine(LOG_ERROR, "ERROR: PutVertices miscounted (%zu != %zu)", count, level.num_old_vert); - } -} - -static void PutVertices_Doom64(level_t &level) -{ - // this size is worst-case scenario - size_t size = level.vertices.size() * sizeof(raw_vertex_doom64_t); - Lump_c *lump = CreateLevelLump(level, "VERTEXES", size); - - for (size_t i = 0; i < level.vertices.size(); i++) - { - raw_vertex_doom64_t raw; - - const vertex_t *vert = level.vertices[i]; - - // do not ignore vertex_t::is_new - // it is required for leafs - - raw.x = GetLittleEndian(FloatToFixed(vert->x)); - raw.y = GetLittleEndian(FloatToFixed(vert->y)); - - lump->Write(&raw, sizeof(raw_vertex_doom64_t)); } - - lump->Finish(); } // // Vanilla format // -static void PutSegs_Vanilla(level_t &level) +void PutSegs_DoomBSP(WadIO &io, level_t &level) { - // this size is worst-case scenario - size_t size = level.segs.size() * sizeof(raw_seg_vanilla_t); - - Lump_c *lump = CreateLevelLump(level, "SEGS", size); + io.StartWritingLump("SEGS"); - for (size_t i = 0; i < level.segs.size(); i++) + for (auto seg : level.segs) { - raw_seg_vanilla_t raw; - - const seg_t *seg = level.segs[i]; + raw_seg_doombsp_t raw; raw.start = GetLittleEndian(IndexToShort(seg->start->index)); raw.end = GetLittleEndian(IndexToShort(seg->end->index)); @@ -182,7 +146,7 @@ static void PutSegs_Vanilla(level_t &level) raw.flip = GetLittleEndian(seg->side); raw.dist = GetLittleEndian(VanillaSegDist(seg)); - lump->Write(&raw, sizeof(raw_seg_vanilla_t)); + io.AddToLump(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -191,52 +155,44 @@ static void PutSegs_Vanilla(level_t &level) seg->side ? "L" : "R", GetLittleEndian(raw.angle), seg->start->x, seg->start->y, seg->end->x, seg->end->y); } } - - lump->Finish(); } -static void PutSubsecs_Vanilla(level_t &level) +void PutSubsecs_DoomBSP(WadIO &io, level_t &level) { - size_t size = level.subsecs.size() * sizeof(raw_subsec_vanilla_t); + io.StartWritingLump("SSECTORS"); - Lump_c *lump = CreateLevelLump(level, "SSECTORS", size); - - for (size_t i = 0; i < level.subsecs.size(); i++) + for (auto sub : level.subsecs) { - raw_subsec_vanilla_t raw; - - const subsec_t *sub = level.subsecs[i]; + raw_subsec_doombsp_t raw; raw.first = GetLittleEndian(IndexToShort(sub->seg_list->index)); raw.num = GetLittleEndian(IndexToShort(sub->seg_count)); - lump->Write(&raw, sizeof(raw_subsec_vanilla_t)); + io.AddToLump(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { - PrintLine(LOG_DEBUG, "[%s] %zu First %04X Num %04X", __func__, sub->index, GetLittleEndian(raw.first), - GetLittleEndian(raw.num)); + PrintLine(LOG_DEBUG, "[%s] %zu First %04X Num %04X Mid (%1.1f,%1.1f)", __func__, sub->index, GetLittleEndian(raw.first), + GetLittleEndian(raw.num), sub->mid_x, sub->mid_y); } } - - lump->Finish(); } -static void PutOneNode_Vanilla(Lump_c *lump, node_t *node, size_t &node_cur_index) +static void PutOneNode_Vanilla(WadIO &io, node_t *node, size_t &node_cur_index) { if (node->r.node) { - PutOneNode_Vanilla(lump, node->r.node, node_cur_index); + PutOneNode_Vanilla(io, node->r.node, node_cur_index); } if (node->l.node) { - PutOneNode_Vanilla(lump, node->l.node, node_cur_index); + PutOneNode_Vanilla(io, node->l.node, node_cur_index); } node->index = node_cur_index++; - raw_node_vanilla_t raw; + raw_node_doombsp_t raw; raw.x = GetLittleEndian(FloatToShort(node->x)); raw.y = GetLittleEndian(FloatToShort(node->y)); @@ -279,7 +235,7 @@ static void PutOneNode_Vanilla(Lump_c *lump, node_t *node, size_t &node_cur_inde PrintLine(LOG_ERROR, "ERROR: Bad left child in node %zu", node->index); } - lump->Write(&raw, sizeof(raw_node_vanilla_t)); + io.AddToLump(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -288,59 +244,48 @@ static void PutOneNode_Vanilla(Lump_c *lump, node_t *node, size_t &node_cur_inde } } -static void PutNodes_Vanilla(level_t &level, node_t *root_node) +void PutNodes_DoomBSP(WadIO &io, level_t &level) { - // this can be bigger than the actual size, but never smaller - size_t max_size = (level.nodes.size() + 1) * sizeof(raw_node_vanilla_t); size_t node_cur_index = 0; - Lump_c *lump = CreateLevelLump(level, "NODES", max_size); + io.StartWritingLump("NODES"); - if (root_node != nullptr) + if (level.root_node != nullptr) { - PutOneNode_Vanilla(lump, root_node, node_cur_index); + PutOneNode_Vanilla(io, level.root_node, node_cur_index); } - lump->Finish(); - if (node_cur_index != level.nodes.size()) { PrintLine(LOG_ERROR, "ERROR: PutNodes miscounted (%zu != %zu)", node_cur_index, level.nodes.size()); } } -static void PutLeafs_Vanilla(level_t &level) +void PutLeafs_DoomBSP(WadIO &io, level_t &level) { - Lump_c *lump = CreateLevelLump(level, "LEAFS"); + io.StartWritingLump("LEAFS"); uint16_t actual_seg_index = 0; - for (size_t i = 0; i < level.subsecs.size(); i++) + for (auto subsec : level.subsecs) { - subsec_t *subsec = level.subsecs[i]; seg_t *seg = subsec->seg_list; - size_t seg_count = subsec->seg_count; + uint16_t seg_count = IndexToShort(subsec->seg_count); - lump->Write(&seg_count, sizeof(uint16_t)); + io.AddToLump(&seg_count, sizeof(seg_count)); if (HAS_BIT(config.debug, DEBUG_BSP)) { - PrintLine(LOG_DEBUG, "[%s] Subsector[%zu] leaf references: %zu", __func__, i, seg_count); - } - - if (seg_count < 3) - { - PrintLine(LOG_ERROR, "[%s] Subsector[%zu] has fewer than 3 leaf references", __func__, i); + PrintLine(LOG_DEBUG, "[%s] Subsector[%zu] leaf references: %hu", __func__, subsec->index, seg_count); } for (size_t j = 0; j < seg_count; j++) { // Do not remove minisegs from tree before // we are able to write all this - raw_leaf_vanilla_t raw; + raw_leaf_doombsp_t raw; vertex_t *vert = seg->start; raw.vertex = GetLittleEndian(IndexToShort(vert->index)); if (seg->linedef) - // if (seg->linedef && !vert->is_new) { raw.seg = GetLittleEndian(actual_seg_index); actual_seg_index++; @@ -350,7 +295,8 @@ static void PutLeafs_Vanilla(level_t &level) raw.seg = NO_INDEX_INT16; } - lump->Write(&raw, sizeof(raw)); + io.AddToLump(&raw, sizeof(raw)); + seg = seg->next; if (HAS_BIT(config.debug, DEBUG_BSP)) @@ -365,18 +311,14 @@ static void PutLeafs_Vanilla(level_t &level) // DeePBSPV4 format // -static void PutSegs_DeePBSPV4(level_t &level) +void PutSegs_DeePBSPV4(WadIO &io, level_t &level) { - // this size is worst-case scenario - size_t size = level.segs.size() * sizeof(raw_seg_deepbspv4_t); - Lump_c *lump = CreateLevelLump(level, "SEGS", size); + io.StartWritingLump("SEGS"); - for (size_t i = 0; i < level.segs.size(); i++) + for (auto seg : level.segs) { raw_seg_deepbspv4_t raw; - const seg_t *seg = level.segs[i]; - raw.start = GetLittleEndian(IndexToInt(seg->start->index)); raw.end = GetLittleEndian(IndexToInt(seg->end->index)); raw.angle = GetLittleEndian(VanillaSegAngle(seg)); @@ -384,7 +326,7 @@ static void PutSegs_DeePBSPV4(level_t &level) raw.flip = GetLittleEndian(seg->side); raw.dist = GetLittleEndian(VanillaSegDist(seg)); - lump->Write(&raw, sizeof(raw_seg_deepbspv4_t)); + io.AddToLump(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -393,47 +335,39 @@ static void PutSegs_DeePBSPV4(level_t &level) seg->side ? "L" : "R", GetLittleEndian(raw.angle), seg->start->x, seg->start->y, seg->end->x, seg->end->y); } } - - lump->Finish(); } -static void PutSubsecs_DeePBSPV4(level_t &level) +void PutSubsecs_DeePBSPV4(WadIO &io, level_t &level) { - size_t size = level.subsecs.size() * sizeof(raw_subsec_deepbspv4_t); - - Lump_c *lump = CreateLevelLump(level, "SSECTORS", size); + io.StartWritingLump("SSECTORS"); - for (size_t i = 0; i < level.subsecs.size(); i++) + for (auto sub : level.subsecs) { raw_subsec_deepbspv4_t raw; - const subsec_t *sub = level.subsecs[i]; - raw.first = GetLittleEndian(IndexToInt(sub->seg_list->index)); raw.num = GetLittleEndian(IndexToShort(sub->seg_count)); - lump->Write(&raw, sizeof(raw_subsec_deepbspv4_t)); + io.AddToLump(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { - PrintLine(LOG_DEBUG, "[%s] %zu First %08X Num %04X", __func__, sub->index, GetLittleEndian(raw.first), - GetLittleEndian(raw.num)); + PrintLine(LOG_DEBUG, "[%s] %zu First %08X Num %04X Mid (%1.1f,%1.1f)", __func__, sub->index, GetLittleEndian(raw.first), + GetLittleEndian(raw.num), sub->mid_x, sub->mid_y); } } - - lump->Finish(); } -static void PutOneNode_DeePBSPV4(Lump_c *lump, node_t *node, size_t &node_cur_index) +static void PutOneNode_DeePBSPV4(WadIO &io, node_t *node, size_t &node_cur_index) { if (node->r.node) { - PutOneNode_DeePBSPV4(lump, node->r.node, node_cur_index); + PutOneNode_DeePBSPV4(io, node->r.node, node_cur_index); } if (node->l.node) { - PutOneNode_DeePBSPV4(lump, node->l.node, node_cur_index); + PutOneNode_DeePBSPV4(io, node->l.node, node_cur_index); } node->index = node_cur_index++; @@ -481,7 +415,7 @@ static void PutOneNode_DeePBSPV4(Lump_c *lump, node_t *node, size_t &node_cur_in PrintLine(LOG_ERROR, "ERROR: Bad left child in node %zu", node->index); } - lump->Write(&raw, sizeof(raw_node_deepbspv4_t)); + io.AddToLumpZ(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -490,50 +424,38 @@ static void PutOneNode_DeePBSPV4(Lump_c *lump, node_t *node, size_t &node_cur_in } } -static void PutNodes_DeePBSPV4(level_t &level, node_t *root_node) +void PutNodes_DeePBSPV4(WadIO &io, level_t &level) { - // this can be bigger than the actual size, but never smaller - // 8 bytes for BSP_MAGIC_DEEPBSPV4 header - // size_t max_size = 8 + (level.nodes.size() + 1) * sizeof(raw_node_deepbspv4_t); size_t node_cur_index = 0; + io.StartWritingLump("NODES"); + io.AddToLump("xNd4\0\0\0\0", 8); - Lump_c *lump = CreateLevelLump(level, "NODES"); - lump->Write("xNd4\0\0\0\0", 8); - - if (root_node != nullptr) + if (level.root_node != nullptr) { - PutOneNode_DeePBSPV4(lump, root_node, node_cur_index); + PutOneNode_DeePBSPV4(io, level.root_node, node_cur_index); } - lump->Finish(); - if (node_cur_index != level.nodes.size()) { PrintLine(LOG_ERROR, "ERROR: PutNodes miscounted (%zu != %zu)", node_cur_index, level.nodes.size()); } } -static void PutLeafs_DeePBSPV4(level_t &level) +void PutLeafs_DeePBSPV4(WadIO &io, level_t &level) { - Lump_c *lump = CreateLevelLump(level, "LEAFS"); + io.StartWritingLump("LEAFS"); uint32_t actual_seg_index = 0; - for (size_t i = 0; i < level.subsecs.size(); i++) + for (auto subsec : level.subsecs) { - subsec_t *subsec = level.subsecs[i]; seg_t *seg = subsec->seg_list; - size_t seg_count = subsec->seg_count; + uint32_t seg_count = IndexToInt(subsec->seg_count); - lump->Write(&seg_count, sizeof(uint32_t)); + io.AddToLump(&seg_count, sizeof(seg_count)); if (HAS_BIT(config.debug, DEBUG_BSP)) { - PrintLine(LOG_DEBUG, "[%s] Subsector[%zu] leaf references: %zu", __func__, i, seg_count); - } - - if (seg_count < 3) - { - PrintLine(LOG_ERROR, "[%s] Subsector[%zu] has fewer than 3 leaf references", __func__, i); + PrintLine(LOG_DEBUG, "[%s] Subsector[%zu] leaf references: %u", __func__, subsec->index, seg_count); } for (size_t j = 0; j < seg_count; j++) @@ -543,9 +465,8 @@ static void PutLeafs_DeePBSPV4(level_t &level) raw_leaf_deepbspv4_t raw; vertex_t *vert = seg->start; - raw.vertex = GetLittleEndian(IndexToShort(vert->index)); + raw.vertex = GetLittleEndian(IndexToInt(vert->index)); if (seg->linedef) - // if (seg->linedef && !vert->is_new) { raw.seg = GetLittleEndian(actual_seg_index); actual_seg_index++; @@ -555,7 +476,8 @@ static void PutLeafs_DeePBSPV4(level_t &level) raw.seg = NO_INDEX_INT32; } - lump->Write(&raw, sizeof(raw)); + io.AddToLump(&raw, sizeof(raw)); + seg = seg->next; if (HAS_BIT(config.debug, DEBUG_BSP)) @@ -570,31 +492,19 @@ static void PutLeafs_DeePBSPV4(level_t &level) // ZDoom format -- XNOD // -static inline uint32_t VertexIndex_XNOD(level_t &level, const vertex_t *v) +static void PutVertices_XNOD(WadIO &io, level_t &level) { - if (v->is_new) - { - return IndexToInt(level.num_old_vert + v->index); - } + uint32_t orgverts = GetLittleEndian(IndexToInt(level.num_old_vert)); + uint32_t newverts = GetLittleEndian(IndexToInt(level.num_new_vert)); - return IndexToInt(v->index); -} - -static void PutVertices_Xnod(level_t &level, Lump_c *lump) -{ - size_t orgverts = GetLittleEndian(level.num_old_vert); - size_t newverts = GetLittleEndian(level.num_new_vert); - - lump->WriteZ(&orgverts, 4); - lump->WriteZ(&newverts, 4); + io.AddToLumpZ(&orgverts, sizeof(orgverts)); + io.AddToLumpZ(&newverts, sizeof(newverts)); size_t count = 0; - for (size_t i = 0; i < level.vertices.size(); i++) + for (auto vert : level.vertices) { raw_vertex_xnod_t raw; - const vertex_t *vert = level.vertices[i]; - if (!vert->is_new) { continue; @@ -603,7 +513,7 @@ static void PutVertices_Xnod(level_t &level, Lump_c *lump) raw.x = GetLittleEndian(FloatToFixed(vert->x)); raw.y = GetLittleEndian(FloatToFixed(vert->y)); - lump->WriteZ(&raw, sizeof(raw_vertex_xnod_t)); + io.AddToLumpZ(&raw, sizeof(raw)); count++; } @@ -614,18 +524,17 @@ static void PutVertices_Xnod(level_t &level, Lump_c *lump) } } -static void PutSubsecs_Xnod(level_t &level, Lump_c *lump) +static void PutSubsecs_XNOD(WadIO &io, level_t &level) { - size_t raw_num = GetLittleEndian(level.subsecs.size()); - lump->WriteZ(&raw_num, 4); + uint32_t raw_num = GetLittleEndian(IndexToInt(level.subsecs.size())); + io.AddToLumpZ(&raw_num, sizeof(raw_num)); size_t cur_seg_index = 0; - for (size_t i = 0; i < level.subsecs.size(); i++) + for (auto sub : level.subsecs) { - const subsec_t *sub = level.subsecs[i]; - - raw_num = GetLittleEndian(sub->seg_count); - lump->WriteZ(&raw_num, 4); + raw_subsec_xnod_t raw; + raw.segnum = GetLittleEndian(IndexToInt(sub->seg_count)); + io.AddToLumpZ(&raw, sizeof(raw)); // sanity check the seg index values size_t count = 0; @@ -633,7 +542,8 @@ static void PutSubsecs_Xnod(level_t &level, Lump_c *lump) { if (cur_seg_index != seg->index) { - PrintLine(LOG_ERROR, "ERROR: PutZSubsecs: seg index mismatch in sub %zu (%zu != %zu)", i, cur_seg_index, seg->index); + PrintLine(LOG_ERROR, "ERROR: PutZSubsecs: seg index mismatch in sub %zu (%zu != %zu)", sub->index, cur_seg_index, + seg->index); } count++; @@ -641,7 +551,7 @@ static void PutSubsecs_Xnod(level_t &level, Lump_c *lump) if (count != sub->seg_count) { - PrintLine(LOG_ERROR, "ERROR: PutZSubsecs: miscounted segs in sub %zu (%zu != %zu)", i, count, sub->seg_count); + PrintLine(LOG_ERROR, "ERROR: PutZSubsecs: miscounted segs in sub %zu (%zu != %zu)", sub->index, count, sub->seg_count); } } @@ -651,46 +561,47 @@ static void PutSubsecs_Xnod(level_t &level, Lump_c *lump) } } -static void PutSegs_Xnod(level_t &level, Lump_c *lump) +static void PutSegs_XNOD(WadIO &io, level_t &level) { - size_t raw_num = GetLittleEndian(level.segs.size()); - lump->WriteZ(&raw_num, 4); + uint32_t raw_num = GetLittleEndian(IndexToInt(level.segs.size())); + io.AddToLumpZ(&raw_num, sizeof(raw_num)); - for (size_t i = 0; i < level.segs.size(); i++) + for (auto seg : level.segs) { - const seg_t *seg = level.segs[i]; + raw_seg_xnod_t raw; - if (seg->index != i) - { - PrintLine(LOG_ERROR, "ERROR: PutZSegs: seg index mismatch (%zu != %zu)", seg->index, i); - } - - raw_seg_xnod_t raw = {}; - - raw.start = GetLittleEndian(VertexIndex_XNOD(level, seg->start)); - raw.end = GetLittleEndian(VertexIndex_XNOD(level, seg->end)); + raw.start = GetLittleEndian(IndexToInt(seg->start->index)); + raw.end = GetLittleEndian(IndexToInt(seg->end->index)); raw.linedef = GetLittleEndian(IndexToShort(seg->linedef->index)); raw.side = seg->side; - lump->WriteZ(&raw, sizeof(raw_seg_xnod_t)); + + io.AddToLumpZ(&raw, sizeof(raw)); + + if (HAS_BIT(config.debug, DEBUG_BSP)) + { + PrintLine(LOG_DEBUG, "[%s] %zu Vert %04X->%04X Line %04X %s (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, seg->index, + GetLittleEndian(raw.start), GetLittleEndian(raw.end), GetLittleEndian(raw.linedef), raw.side ? "L" : "R", + seg->start->x, seg->start->y, seg->end->x, seg->end->y); + } } } -static void PutOneNode_Xnod(Lump_c *lump, node_t *node, size_t &node_cur_index) +static void PutOneNode_XNOD(WadIO &io, node_t *node, size_t &node_cur_index) { - raw_node_xnod_t raw; - if (node->r.node) { - PutOneNode_Xnod(lump, node->r.node, node_cur_index); + PutOneNode_XNOD(io, node->r.node, node_cur_index); } if (node->l.node) { - PutOneNode_Xnod(lump, node->l.node, node_cur_index); + PutOneNode_XNOD(io, node->l.node, node_cur_index); } node->index = node_cur_index++; + raw_node_xnod_t raw; + raw.x = GetLittleEndian(FloatToShort(node->x)); raw.y = GetLittleEndian(FloatToShort(node->y)); raw.dx = GetLittleEndian(FloatToShort(node->dx)); @@ -732,7 +643,7 @@ static void PutOneNode_Xnod(Lump_c *lump, node_t *node, size_t &node_cur_index) PrintLine(LOG_ERROR, "ERROR: Bad left child in ZDoom node %zu", node->index); } - lump->WriteZ(&raw, sizeof(raw_node_xnod_t)); + io.AddToLumpZ(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -741,15 +652,15 @@ static void PutOneNode_Xnod(Lump_c *lump, node_t *node, size_t &node_cur_index) } } -static void PutNodes_Xnod(level_t &level, Lump_c *lump, node_t *root) +static void PutNodes_XNOD(WadIO &io, level_t &level) { size_t node_cur_index = 0; - size_t raw_num = GetLittleEndian(level.nodes.size()); - lump->WriteZ(&raw_num, 4); + uint32_t raw_num = GetLittleEndian(IndexToInt(level.nodes.size())); + io.AddToLumpZ(&raw_num, sizeof(raw_num)); - if (root) + if (level.root_node) { - PutOneNode_Xnod(lump, root, node_cur_index); + PutOneNode_XNOD(io, level.root_node, node_cur_index); } if (node_cur_index != level.nodes.size()) @@ -758,104 +669,76 @@ static void PutNodes_Xnod(level_t &level, Lump_c *lump, node_t *root) } } -static size_t CalcXnodNodesSize(level_t &level) -{ - // compute size of the ZDoom format nodes. - // it does not need to be exact, but it *does* need to be bigger - // (or equal) to the actual size of the lump. - - size_t size = 32; // header + a bit extra - - size += 8 + level.vertices.size() * sizeof(raw_vertex_xnod_t); - size += 4 + level.subsecs.size() * sizeof(raw_subsec_xnod_t); - size += 4 + level.segs.size() * sizeof(raw_seg_xgl2_t); - size += 4 + level.nodes.size() * sizeof(raw_node_xgl3_t); - - return size; -} - // // ZDoom format -- XGLN, XGL2, XGL3 // -static void PutSegs_Xgln(level_t &level, Lump_c *lump) +static void PutSegs_XGLN(WadIO &io, level_t &level) { - size_t raw_num = GetLittleEndian(level.segs.size()); - lump->WriteZ(&raw_num, 4); + uint32_t raw_num = GetLittleEndian(IndexToInt(level.segs.size())); + io.AddToLumpZ(&raw_num, sizeof(raw_num)); - for (size_t i = 0; i < level.segs.size(); i++) + for (auto seg : level.segs) { - const seg_t *seg = level.segs[i]; - - if (seg->index != i) - { - PrintLine(LOG_ERROR, "ERROR: PutXGL3Segs: seg index mismatch (%zu != %zu)", seg->index, i); - } + raw_seg_xgln_t raw; - raw_seg_xgln_t raw = {}; - - raw.vertex = GetLittleEndian(VertexIndex_XNOD(level, seg->start)); + raw.vertex = GetLittleEndian(IndexToInt(seg->start->index)); raw.partner = GetLittleEndian(IndexToInt(seg->partner ? seg->partner->index : NO_INDEX)); raw.linedef = GetLittleEndian(IndexToShort(seg->linedef ? seg->linedef->index : NO_INDEX)); raw.side = seg->side; - lump->WriteZ(&raw, sizeof(raw_seg_xgln_t)); + io.AddToLumpZ(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { - PrintLine(LOG_DEBUG, "[%s] SEG[%zu] v1=%d partner=%d line=%d side=%d", __func__, i, raw.vertex, raw.partner, raw.linedef, - raw.side); + PrintLine(LOG_DEBUG, "[%s] %zu Vert %04X Line %04X %s (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, seg->index, + GetLittleEndian(raw.vertex), GetLittleEndian(raw.linedef), raw.side ? "L" : "R", seg->start->x, seg->start->y, + seg->end->x, seg->end->y); } } } -static void PutSegs_Xgl2(level_t &level, Lump_c *lump) +static void PutSegs_XGL2(WadIO &io, level_t &level) { - size_t raw_num = GetLittleEndian(level.segs.size()); - lump->WriteZ(&raw_num, 4); + uint32_t raw_num = GetLittleEndian(IndexToInt(level.segs.size())); + io.AddToLumpZ(&raw_num, sizeof(raw_num)); - for (size_t i = 0; i < level.segs.size(); i++) + for (auto seg : level.segs) { - const seg_t *seg = level.segs[i]; - - if (seg->index != i) - { - PrintLine(LOG_ERROR, "ERROR: PutXGL3Segs: seg index mismatch (%zu != %zu)", seg->index, i); - } - - raw_seg_xgl2_t raw = {}; + raw_seg_xgl2_t raw; - raw.vertex = GetLittleEndian(VertexIndex_XNOD(level, seg->start)); + raw.vertex = GetLittleEndian(IndexToInt(seg->start->index)); raw.partner = GetLittleEndian(IndexToInt(seg->partner ? seg->partner->index : NO_INDEX)); raw.linedef = GetLittleEndian(IndexToInt(seg->linedef ? seg->linedef->index : NO_INDEX)); raw.side = seg->side; - lump->WriteZ(&raw, sizeof(raw_seg_xgl2_t)); + io.AddToLumpZ(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { - PrintLine(LOG_DEBUG, "[%s] SEG[%zu] v1=%d partner=%d line=%d side=%d", __func__, i, raw.vertex, raw.partner, raw.linedef, - raw.side); + PrintLine(LOG_DEBUG, "[%s] %zu Vert %04X Line %04X %s (%1.1f,%1.1f) -> (%1.1f,%1.1f)", __func__, seg->index, + GetLittleEndian(raw.vertex), GetLittleEndian(raw.linedef), raw.side ? "L" : "R", seg->start->x, seg->start->y, + seg->end->x, seg->end->y); } } } -static void PutOneNode_Xgl3(Lump_c *lump, node_t *node, size_t &node_cur_index) +static void PutOneNode_XGL3(WadIO &io, node_t *node, size_t &node_cur_index) { - raw_node_xgl3_t raw; - if (node->r.node) { - PutOneNode_Xgl3(lump, node->r.node, node_cur_index); + PutOneNode_XGL3(io, node->r.node, node_cur_index); } if (node->l.node) { - PutOneNode_Xgl3(lump, node->l.node, node_cur_index); + PutOneNode_XGL3(io, node->l.node, node_cur_index); } node->index = node_cur_index++; + raw_node_xgl3_t raw; + raw.x = GetLittleEndian(FloatToFixed(node->x)); raw.y = GetLittleEndian(FloatToFixed(node->y)); raw.dx = GetLittleEndian(FloatToFixed(node->dx)); @@ -897,7 +780,7 @@ static void PutOneNode_Xgl3(Lump_c *lump, node_t *node, size_t &node_cur_index) PrintLine(LOG_ERROR, "ERROR: Bad left child in ZDoom node %zu", node->index); } - lump->WriteZ(&raw, sizeof(raw_node_xgl3_t)); + io.AddToLumpZ(&raw, sizeof(raw)); if (HAS_BIT(config.debug, DEBUG_BSP)) { @@ -906,15 +789,15 @@ static void PutOneNode_Xgl3(Lump_c *lump, node_t *node, size_t &node_cur_index) } } -static void PutNodes_Xgl3(level_t &level, Lump_c *lump, node_t *root) +static void PutNodes_XGL3(WadIO &io, level_t &level) { size_t node_cur_index = 0; - size_t raw_num = GetLittleEndian(level.nodes.size()); - lump->WriteZ(&raw_num, 4); + uint32_t raw_num = GetLittleEndian(IndexToInt(level.nodes.size())); + io.AddToLumpZ(&raw_num, sizeof(raw_num)); - if (root) + if (level.root_node) { - PutOneNode_Xgl3(lump, root, node_cur_index); + PutOneNode_XGL3(io, level.root_node, node_cur_index); } if (node_cur_index != level.nodes.size()) @@ -924,201 +807,32 @@ static void PutNodes_Xgl3(level_t &level, Lump_c *lump, node_t *root) } // -// Lump writing procedures +// The family of ZDBSP BSP tree formats use a single lump no matter what // - -void SaveDoom_DoomBSP(level_t &level, node_t *root_node) -{ - auto mark = Benchmarker(__func__); - // remove all the minisegs from subsectors - NormaliseBspTree(level); - // reduce vertex precision for classic DOOM nodes. - // some segs can become "degenerate" after this, and these - // are removed from subsectors. - RoundOffBspTree(level); - SortSegs(level); - PutVertices_Doom(level); - PutSegs_Vanilla(level); - PutSubsecs_Vanilla(level); - PutNodes_Vanilla(level, root_node); -} - -void SaveDoom_DeePBSPV4(level_t &level, node_t *root_node) -{ - auto mark = Benchmarker(__func__); - // remove all the minisegs from subsectors - NormaliseBspTree(level); - // reduce vertex precision for classic DOOM nodes. - // some segs can become "degenerate" after this, and these - // are removed from subsectors. - RoundOffBspTree(level); - SortSegs(level); - PutVertices_Doom(level); - PutSegs_DeePBSPV4(level); - PutSubsecs_DeePBSPV4(level); - PutNodes_DeePBSPV4(level, root_node); -} - -void SaveDoom_XNOD(level_t &level, node_t *root_node) -{ - auto mark = Benchmarker(__func__); - CreateLevelLump(level, "SEGS")->Finish(); - CreateLevelLump(level, "SSECTORS")->Finish(); - // remove all the minisegs from subsectors - NormaliseBspTree(level); - SortSegs(level); - - Lump_c *lump = CreateLevelLump(level, "NODES", CalcXnodNodesSize(level)); - - if (level.bsp_compress) lump->Begin_Zlib(); - - lump->Write(level.bsp_compress ? "ZNOD" : "XNOD", 4); - PutVertices_Xnod(level, lump); - PutSubsecs_Xnod(level, lump); - PutSegs_Xnod(level, lump); - PutNodes_Xnod(level, lump, root_node); - - if (level.bsp_compress) lump->Finish_Zlib(); - - lump->Finish(); - lump = nullptr; -} - -void SaveDoom_XGLN(level_t &level, node_t *root_node) -{ - auto mark = Benchmarker(__func__); - // leave SEGS empty - CreateLevelLump(level, "SEGS")->Finish(); - - SortSegs(level); - - Lump_c *lump = CreateLevelLump(level, "SSECTORS", CalcXnodNodesSize(level)); - - if (level.bsp_compress) lump->Begin_Zlib(); - - lump->Write(level.bsp_compress ? "ZGLN" : "XGLN", 4); - PutVertices_Xnod(level, lump); - PutSubsecs_Xnod(level, lump); - PutSegs_Xgln(level, lump); - PutNodes_Xnod(level, lump, root_node); - - if (level.bsp_compress) lump->Finish_Zlib(); - - lump->Finish(); - lump = nullptr; - - // leave NODES empty - CreateLevelLump(level, "NODES")->Finish(); -} - -void SaveDoom_XGL2(level_t &level, node_t *root_node) -{ - auto mark = Benchmarker(__func__); - // leave SEGS empty - CreateLevelLump(level, "SEGS")->Finish(); - - SortSegs(level); - - Lump_c *lump = CreateLevelLump(level, "SSECTORS", CalcXnodNodesSize(level)); - - if (level.bsp_compress) lump->Begin_Zlib(); - - lump->Write(level.bsp_compress ? "ZGL2" : "XGL2", 4); - PutVertices_Xnod(level, lump); - PutSubsecs_Xnod(level, lump); - PutSegs_Xgl2(level, lump); - PutNodes_Xnod(level, lump, root_node); - - if (level.bsp_compress) lump->Finish_Zlib(); - - lump->Finish(); - lump = nullptr; - - // leave NODES empty - CreateLevelLump(level, "NODES")->Finish(); -} - -void SaveDoom_XGL3(level_t &level, node_t *root_node) +void PutTree_ZDBSP(WadIO &io, level_t &level, std::string lumpname, bsp_format_t bsp_format, bool compress) { - auto mark = Benchmarker(__func__); - // leave SEGS empty - CreateLevelLump(level, "SEGS")->Finish(); + if (bsp_format == BSP_XNOD) NormaliseBspTree(level); SortSegs(level); - Lump_c *lump = CreateLevelLump(level, "SSECTORS", CalcXnodNodesSize(level)); + io.StartWritingLump(lumpname.c_str()); - if (level.bsp_compress) lump->Begin_Zlib(); + if (bsp_format == BSP_XNOD) io.AddToLump(compress ? "ZNOD" : "XNOD", 4); + if (bsp_format == BSP_XGLN) io.AddToLump(compress ? "ZGLN" : "XGLN", 4); + if (bsp_format == BSP_XGL2) io.AddToLump(compress ? "ZGL2" : "XGL2", 4); + if (bsp_format == BSP_XGL3) io.AddToLump(compress ? "ZGL3" : "XGL3", 4); - lump->Write(level.bsp_compress ? "ZGL3" : "XGL3", 4); - PutVertices_Xnod(level, lump); - PutSubsecs_Xnod(level, lump); - PutSegs_Xgl2(level, lump); - PutNodes_Xgl3(level, lump, root_node); + if (compress) io.Begin_Zlib(); - if (level.bsp_compress) lump->Finish_Zlib(); - - lump->Finish(); - lump = nullptr; - - // leave NODES empty - CreateLevelLump(level, "NODES")->Finish(); -} - -// -// Doom64 has some differences from Doom-format or Hexen-format -// This could also be shared with PSX Doom and PSX Final Doom, but we don't support those -// - -void SaveDoom64_DoomBSP(level_t &level, node_t *root_node) -{ - // Needed for LEAFS - RoundOffVertices(level); - PutVertices_Doom64(level); - // We need minisegs just for leafs - PutLeafs_Vanilla(level); - // remove all the minisegs from subsectors - NormaliseBspTree(level); - SortSegs(level); - PutSegs_Vanilla(level); - PutSubsecs_Vanilla(level); - PutNodes_Vanilla(level, root_node); -} - -void SaveDoom64_DeePBSPV4(level_t &level, node_t *root_node) -{ - // Needed for LEAFS - RoundOffVertices(level); - PutVertices_Doom64(level); - // We need minisegs just for leafs - PutLeafs_DeePBSPV4(level); - // remove all the minisegs from subsectors - NormaliseBspTree(level); - SortSegs(level); - PutSegs_DeePBSPV4(level); - PutSubsecs_DeePBSPV4(level); - PutNodes_DeePBSPV4(level, root_node); -} - -// -// Unlike the the Doom and Hexen map formats, UDMF has a tight requirement for fractional coordinates. -// Always use the latest high-precision BSP format we support. -// -void SaveTextmap_ZNODES(level_t &level, node_t *root_node) -{ - auto mark = Benchmarker(__func__); - SortSegs(level); + PutVertices_XNOD(io, level); + PutSubsecs_XNOD(io, level); - Lump_c *lump = CreateLevelLump(level, "ZNODES", CalcXnodNodesSize(level)); + if (bsp_format == BSP_XNOD) PutSegs_XNOD(io, level); + if (bsp_format == BSP_XGLN) PutSegs_XGLN(io, level); + if (bsp_format == BSP_XGL2 || bsp_format == BSP_XGL3) PutSegs_XGL2(io, level); - lump->Begin_Zlib(); - lump->Write("ZGL3", 4); - PutVertices_Xnod(level, lump); - PutSubsecs_Xnod(level, lump); - PutSegs_Xgl2(level, lump); - PutNodes_Xgl3(level, lump, root_node); - lump->Finish_Zlib(); + if (bsp_format == BSP_XNOD || bsp_format == BSP_XGLN || bsp_format == BSP_XGL2) PutNodes_XNOD(io, level); + if (bsp_format == BSP_XGL3) PutNodes_XGL3(io, level); - lump->Finish(); - lump = nullptr; + if (compress) io.Finish_Zlib(); } diff --git a/src/bsp.hpp b/src/bsp.hpp new file mode 100644 index 0000000..9eea677 --- /dev/null +++ b/src/bsp.hpp @@ -0,0 +1,210 @@ +//------------------------------------------------------------------------------ +// +// ELFBSP +// +//------------------------------------------------------------------------------ +// +// Copyright 2025-2026 Guilherme Miranda +// Copyright 2000-2018 Andrew Apted, et al +// Copyright 1994-1998 Colin Reed +// Copyright 1997-1998 Lee Killough +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +//------------------------------------------------------------------------------ + +#pragma once + +#include "core.hpp" +#include "local.hpp" + +// +// DoomBSP +// +using raw_node_doombsp_t = struct raw_node_doombsp_s +{ + int16_t x, y; // starting point + int16_t dx, dy; // offset to ending point + raw_bbox_t b1, b2; // bounding rectangles + uint16_t right, left; // children: Node or SSector (if high bit is set) +} PACKEDATTR; + +using raw_subsec_doombsp_t = struct raw_subsec_doombsp_s +{ + uint16_t num; // number of Segs in this Sub-Sector + uint16_t first; // first Seg +} PACKEDATTR; + +using raw_seg_doombsp_t = struct raw_seg_doombsp_s +{ + uint16_t start; // from this vertex... + uint16_t end; // ... to this vertex + uint16_t angle; // angle (0 = east, 16384 = north, ...) + uint16_t linedef; // linedef that this seg goes along + uint16_t flip; // true if not the same direction as linedef + int16_t dist; // distance from starting point +} PACKEDATTR; + +using raw_leaf_doombsp_t = struct raw_leaf_doombsp_s +{ + uint16_t vertex; + uint16_t seg; +} PACKEDATTR; + +// +// DeepSea DeePBSP +// * compared to vanilla, some types were raise to 32bit +// +using raw_node_deepbspv4_t = struct raw_node_deepbspv4_s +{ + int16_t x, y; // starting point + int16_t dx, dy; // offset to ending point + raw_bbox_t b1, b2; // bounding rectangles + uint32_t right, left; // children: Node or SSector (if high bit is set) +} PACKEDATTR; + +using raw_subsec_deepbspv4_t = struct raw_subsec_deepbspv4_s +{ + uint16_t num; // number of Segs in this Sub-Sector + uint32_t first; // first Seg +} PACKEDATTR; + +using raw_seg_deepbspv4_t = struct raw_seg_deepbspv4_s +{ + uint32_t start; // from this vertex... + uint32_t end; // ... to this vertex + uint16_t angle; // angle (0 = east, 16384 = north, ...) + uint16_t linedef; // linedef that this seg goes along + uint16_t flip; // true if not the same direction as linedef + int16_t dist; // distance from starting point +} PACKEDATTR; + +using raw_leaf_deepbspv4_t = struct raw_leaf_deepbspv4_s +{ + uint32_t vertex; + uint32_t seg; +} PACKEDATTR; + +// +// ZDoom BSP +// * compared to vanilla, some types were raise to 32bit +// * each version (XNOD->XGLN->XGL2->XGL3) builds on top of the previous +// +using raw_vertex_xnod_t = struct raw_vertex_xnod_s +{ + fixed_t x; + fixed_t y; +} PACKEDATTR; + +using raw_node_xnod_t = struct raw_node_xnod_s +{ + int16_t x, y; // starting point + int16_t dx, dy; // offset to ending point + raw_bbox_t b1, b2; // bounding rectangles + uint32_t right, left; // children: Node or SSector (if high bit is set) +} PACKEDATTR; + +using raw_subsec_xnod_t = struct raw_subsec_xnod_s +{ + uint32_t segnum; + // NOTE : no "first" value, segs must be contiguous and appear + // in an order dictated by the subsector list, e.g. all + // segs of the second subsector must appear directly after + // all segs of the first subsector. +} PACKEDATTR; + +using raw_seg_xnod_t = struct raw_seg_xnod_s +{ + uint32_t start; // from this vertex... + uint32_t end; // ... to this vertex + uint16_t linedef; // linedef that this seg goes along, or NO_INDEX + uint8_t side; // 0 if on right of linedef, 1 if on left +} PACKEDATTR; + +using raw_seg_xgln_t = struct raw_seg_xgln_s +{ + uint32_t vertex; // from this vertex ... to the next + uint32_t partner; // partner seg, unused by most ports outside of U/G/ZDoom + uint16_t linedef; // linedef that this seg goes along, or NO_INDEX + uint8_t side; // 0 if on right of linedef, 1 if on left +} PACKEDATTR; + +using raw_seg_xgl2_t = struct raw_seg_xgl2_s +{ + uint32_t vertex; // from this vertex ... to the next + uint32_t partner; // partner seg, unused by most ports outside of U/G/ZDoom + uint32_t linedef; // linedef that this seg goes along, or NO_INDEX + uint8_t side; // 0 if on right of linedef, 1 if on left +} PACKEDATTR; + +using raw_node_xgl3_t = struct raw_node_xgl3_s +{ + int32_t x, y; // starting point + int32_t dx, dy; // offset to ending point + raw_bbox_t b1, b2; // bounding rectangles + uint32_t right, left; // children: Node or SSector (if high bit is set) +} PACKEDATTR; + +static_size(raw_node_doombsp_t, 28); +static_size(raw_subsec_doombsp_t, 4); +static_size(raw_seg_doombsp_t, 12); +static_size(raw_node_deepbspv4_t, 32); +static_size(raw_subsec_deepbspv4_t, 6); +static_size(raw_seg_deepbspv4_t, 16); +static_size(raw_vertex_xnod_t, 8); +static_size(raw_node_xnod_t, 32); +static_size(raw_subsec_xnod_t, 4); +static_size(raw_seg_xnod_t, 11); +static_size(raw_seg_xgln_t, 11); +static_size(raw_seg_xgl2_t, 13); +static_size(raw_node_xgl3_t, 40); + +// put all the segs in each subsector into clockwise order, and renumber +// the seg indices. +// +// [ This cannot be done DURING BuildNodes() since splitting a seg with +// a partner will insert another seg into that partner's list, usually +// in the wrong place order-wise. ] +void ClockwiseBspTree(level_t &level); + +// traverse the BSP tree and do whatever is necessary to convert the +// node information from GL standard to normal standard (for example, +// removing minisegs). +void NormaliseBspTree(level_t &level); + +// traverse the BSP tree, doing whatever is necessary to round +// vertices to integer coordinates (for example, removing segs whose +// rounded coordinates degenerate to the same point). +void RoundOffBspTree(level_t &level); + +// both BLOCKAMP and REJECT exist as single lumps on all supported map formats +void InitBlockmap(level_t &level); +void PutBlockmap(WadIO &io, level_t &level); +void PutReject(WadIO &io, level_t &level); + +// the BSP tree lumps differ notably on each map format -- Doom/Hexen/Doom64 +// have NODES, SSECTORS & SEGS, but UDMF is generally only ZNODES, and there's +// some format overlap between them +void SortSegs(level_t &level); + +void PutVertices(WadIO &io, level_t &level, bool fractional, bool include_new_verts); + +void PutSegs_DoomBSP(WadIO &io, level_t &level); +void PutSubsecs_DoomBSP(WadIO &io, level_t &level); +void PutNodes_DoomBSP(WadIO &io, level_t &level); +void PutLeafs_DoomBSP(WadIO &io, level_t &level); + +void PutSegs_DeePBSPV4(WadIO &io, level_t &level); +void PutSubsecs_DeePBSPV4(WadIO &io, level_t &level); +void PutNodes_DeePBSPV4(WadIO &io, level_t &level); +void PutLeafs_DeePBSPV4(WadIO &io, level_t &level); + +void PutTree_ZDBSP(WadIO &io, level_t &level, std::string lumpname, bsp_format_t bsp_format, bool compress); diff --git a/src/core.hpp b/src/core.hpp index 3034d48..652b138 100644 --- a/src/core.hpp +++ b/src/core.hpp @@ -39,8 +39,6 @@ #include #include -#include -#include #include "zlib-ng.h" @@ -408,70 +406,6 @@ constexpr void UtilFree(T *data) free(data); } -//------------------------------------------------------------------------ -// FILE MANAGEMENT -//------------------------------------------------------------------------ - -inline void FileClear(const char *filename) -{ - if (FILE *fp = fopen(filename, "w")) - { - fclose(fp); - } -} - -inline bool FileExists(const char *filename) -{ - - if (FILE *fp = fopen(filename, "rb")) - { - fclose(fp); - return true; - } - - return false; -} - -inline bool FileCopy(const char *src_name, const char *dest_name) -{ - char buffer[MSG_BUFFER_LENGTH]; - - FILE *src = fopen(src_name, "rb"); - if (!src) - { - return false; - } - - FILE *dest = fopen(dest_name, "wb"); - if (!dest) - { - fclose(src); - return false; - } - - while (true) - { - size_t rlen = fread(buffer, 1, sizeof(buffer), src); - if (rlen == 0) - { - break; - } - - size_t wlen = fwrite(buffer, 1, rlen, dest); - if (wlen != rlen) - { - break; - } - } - - bool was_OK = !ferror(src) && !ferror(dest); - - fclose(dest); - fclose(src); - - return was_OK; -} - //------------------------------------------------------------------------ // STRINGS //------------------------------------------------------------------------ @@ -538,40 +472,14 @@ inline bool HasExtension(const char *filename) return false; } -// -// MatchExtension -// -// When ext is nullptr, checks if the file has no extension. -// -inline bool MatchExtension(const char *filename, const char *ext) -{ - if (!ext || !*ext) - { - return !HasExtension(filename); - } - - size_t A = strlen(filename); - size_t B = strlen(ext); - - while (A-- > 0 && B-- > 0) - { - if (toupper(static_cast(filename[A])) != toupper(static_cast(ext[B]))) - { - return false; - } - } - - return (B == NO_INDEX) && filename[A] == '.'; -} - // // FindExtension // // Return offset of the '.', or NO_INDEX when no extension was found. // -inline size_t FindExtension(const char *filename) +inline size_t FindExtension(std::string_view filename) { - const size_t len = strlen(filename); + const size_t len = strlen(filename.data()); if (len == 0) { return NO_INDEX; @@ -624,26 +532,6 @@ inline short_angle_t ComputeAngle_BAM(double dx, double dy) return static_cast(floor(angle * FRACFACTOR / 360.0 + 0.5)); } -//------------------------------------------------------------------------ -// WAD STRUCTURES -//------------------------------------------------------------------------ - -// wad header -using raw_wad_header_t = struct raw_wad_header_s -{ - char ident[4]; - uint32_t num_entries; - uint32_t dir_start; -} PACKEDATTR; - -// directory entry -using raw_wad_entry_t = struct raw_wad_entry_s -{ - uint32_t pos; - uint32_t size; - char name[8]; -} PACKEDATTR; - //------------------------------------------------------------------------ // LEVEL STRUCTURES //------------------------------------------------------------------------ @@ -651,13 +539,14 @@ using raw_wad_entry_t = struct raw_wad_entry_s using map_format_t = enum map_format_e { MapFormat_INVALID = 0, - MapFormat_Doom, MapFormat_Hexen, MapFormat_Doom64, MapFormat_UDMF, }; +static std::string_view format_name[] = {"ERROR", "Doom Format", "Hexen Format", "Doom64 Format", "UDMF"}; + // Lump order in a map WAD: each map needs a couple of lumps // to provide a complete scene geometry description. using lump_order_t = enum lump_order_e @@ -701,12 +590,18 @@ using lump_order_t = enum lump_order_e static constexpr uint32_t MAX_LUMPS_IN_A_LEVEL = 21; -using raw_vertex_t = struct raw_vertex_s +using raw_vertex_short_t = struct raw_vertex_short_s { int16_t x; int16_t y; } PACKEDATTR; +using raw_vertex_fixed_t = struct raw_vertex_fixed_s +{ + fixed_t x; + fixed_t y; +} PACKEDATTR; + using raw_linedef_doom_t = struct raw_linedef_doom_s { uint16_t start; // from this vertex... @@ -777,12 +672,6 @@ using raw_thing_hexen_t = struct raw_thing_hexen_s // Some are shared with PSX Doom's and PSX Final Doom's formats // but we don't support those -using raw_vertex_doom64_t = struct raw_vertex_doom64_s -{ - fixed_t x; - fixed_t y; -} PACKEDATTR; - using raw_thing_doom64_t = struct raw_thing_doom64_s { int16_t x; // x position of thing @@ -899,204 +788,18 @@ using raw_blockmap_xbm1_header_t = struct raw_blockmap_xbm1_header_s uint32_t x_blocks, y_blocks; } PACKEDATTR; -// -// Vanilla BSP -// -using raw_node_vanilla_t = struct raw_node_vanilla_s -{ - int16_t x, y; // starting point - int16_t dx, dy; // offset to ending point - raw_bbox_t b1, b2; // bounding rectangles - uint16_t right, left; // children: Node or SSector (if high bit is set) -} PACKEDATTR; - -using raw_subsec_vanilla_t = struct raw_subsec_vanilla_s -{ - uint16_t num; // number of Segs in this Sub-Sector - uint16_t first; // first Seg -} PACKEDATTR; - -using raw_seg_vanilla_t = struct raw_seg_vanilla_s -{ - uint16_t start; // from this vertex... - uint16_t end; // ... to this vertex - uint16_t angle; // angle (0 = east, 16384 = north, ...) - uint16_t linedef; // linedef that this seg goes along - uint16_t flip; // true if not the same direction as linedef - int16_t dist; // distance from starting point -} PACKEDATTR; - -using raw_leaf_vanilla_t = struct raw_leaf_vanilla_s -{ - uint16_t vertex; - uint16_t seg; -} PACKEDATTR; - -// -// DeepSea BSP -// * compared to vanilla, some types were raise to 32bit -// -using raw_node_deepbspv4_t = struct raw_node_deepbspv4_s -{ - int16_t x, y; // starting point - int16_t dx, dy; // offset to ending point - raw_bbox_t b1, b2; // bounding rectangles - uint32_t right, left; // children: Node or SSector (if high bit is set) -} PACKEDATTR; - -using raw_subsec_deepbspv4_t = struct raw_subsec_deepbspv4_s -{ - uint16_t num; // number of Segs in this Sub-Sector - uint32_t first; // first Seg -} PACKEDATTR; - -using raw_seg_deepbspv4_t = struct raw_seg_deepbspv4_s -{ - uint32_t start; // from this vertex... - uint32_t end; // ... to this vertex - uint16_t angle; // angle (0 = east, 16384 = north, ...) - uint16_t linedef; // linedef that this seg goes along - uint16_t flip; // true if not the same direction as linedef - int16_t dist; // distance from starting point -} PACKEDATTR; - -using raw_leaf_deepbspv4_t = struct raw_leaf_deepbspv4_s -{ - uint32_t vertex; - uint32_t seg; -} PACKEDATTR; - -// -// ZDoom BSP -// * compared to vanilla, some types were raise to 32bit -// * each version (XNOD->XGLN->XGL2->XGL3) builds on top of the previous -// -using raw_vertex_xnod_t = struct raw_vertex_xnod_s -{ - int32_t x; - int32_t y; -} PACKEDATTR; - -using raw_node_xnod_t = struct raw_node_xnod_s -{ - int16_t x, y; // starting point - int16_t dx, dy; // offset to ending point - raw_bbox_t b1, b2; // bounding rectangles - uint32_t right, left; // children: Node or SSector (if high bit is set) -} PACKEDATTR; - -using raw_subsec_xnod_t = struct raw_subsec_xnod_s -{ - uint32_t segnum; - // NOTE : no "first" value, segs must be contiguous and appear - // in an order dictated by the subsector list, e.g. all - // segs of the second subsector must appear directly after - // all segs of the first subsector. -} PACKEDATTR; - -using raw_seg_xnod_t = struct raw_seg_xnod_s -{ - uint32_t start; // from this vertex... - uint32_t end; // ... to this vertex - uint16_t linedef; // linedef that this seg goes along, or NO_INDEX - uint8_t side; // 0 if on right of linedef, 1 if on left -} PACKEDATTR; - -using raw_seg_xgln_t = struct raw_seg_xgln_s -{ - uint32_t vertex; // from this vertex ... to the next - uint32_t partner; // partner seg, unused by most ports outside of U/G/ZDoom - uint16_t linedef; // linedef that this seg goes along, or NO_INDEX - uint8_t side; // 0 if on right of linedef, 1 if on left -} PACKEDATTR; - -using raw_seg_xgl2_t = struct raw_seg_xgl2_s -{ - uint32_t vertex; // from this vertex ... to the next - uint32_t partner; // partner seg, unused by most ports outside of U/G/ZDoom - uint32_t linedef; // linedef that this seg goes along, or NO_INDEX - uint8_t side; // 0 if on right of linedef, 1 if on left -} PACKEDATTR; - -using raw_node_xgl3_t = struct raw_node_xgl3_s -{ - int32_t x, y; // starting point - int32_t dx, dy; // offset to ending point - raw_bbox_t b1, b2; // bounding rectangles - uint32_t right, left; // children: Node or SSector (if high bit is set) -} PACKEDATTR; - -/* ----- Graphical structures ---------------------- */ - -using raw_patchdef_t = struct raw_patchdef_s -{ - int16_t x_origin; - int16_t y_origin; - uint16_t pname; // index into PNAMES - uint16_t stepdir; // NOT USED - uint16_t colormap; // NOT USED -} PACKEDATTR; - -using raw_strife_patchdef_t = struct raw_strife_patchdef_s -{ - int16_t x_origin; - int16_t y_origin; - uint16_t pname; // index into PNAMES -} PACKEDATTR; - -// Texture definition. -// -// Each texture is composed of one or more patches, -// with patches being lumps stored in the WAD. -// -using raw_texture_t = struct raw_texture_s -{ - char name[8]; - uint32_t masked; // NOT USED - uint16_t width; - uint16_t height; - uint16_t column_dir[2]; // NOT USED - uint16_t patch_count; - raw_patchdef_t patches[1]; -} PACKEDATTR; - -using raw_strife_texture_t = struct raw_strife_texture_s -{ - char name[8]; - uint32_t masked; // NOT USED - uint16_t width; - uint16_t height; - uint16_t patch_count; - raw_strife_patchdef_t patches[1]; -} PACKEDATTR; +// Fail way earlier +#define static_size(x, y) static_assert(sizeof(x) == y, "Size mismatch for '" #x "'. Should be " #y ".") -// Patches. -// -// A patch holds one or more columns. -// Patches are used for sprites and all masked pictures, -// and we compose textures from the TEXTURE1/2 lists -// of patches. -// -using raw_patch_t = struct patch_s -{ - int16_t width; // bounding box size - int16_t height; // - int16_t leftoffset; // pixels to the left of origin - int16_t topoffset; // pixels below the origin - uint32_t columnofs[1]; // only [width] used -} PACKEDATTR; +static_size(raw_vertex_short_t, 4); +static_size(raw_vertex_fixed_t, 8); -// Fail way earlier -static_assert(sizeof(raw_wad_header_t) == 12, "Size mismatch for 'raw_wad_header_t'. Should be 12."); -static_assert(sizeof(raw_wad_entry_t) == 16, "Size mismatch for 'raw_wad_entry_t'. Should be 16."); -static_assert(sizeof(raw_vertex_t) == 4, "Size mismatch for 'raw_vertex_t'. Should be 4."); static_assert(sizeof(raw_linedef_doom_t) == 14, "Size mismatch for 'raw_linedef_doom_t'. Should be 14."); static_assert(sizeof(raw_sidedef_doom_t) == 30, "Size mismatch for 'raw_sidedef_doom_t'. Should be 30."); static_assert(sizeof(raw_sector_doom_t) == 26, "Size mismatch for 'raw_sector_doom_t'. Should be 26."); static_assert(sizeof(raw_thing_doom_t) == 10, "Size mismatch for 'raw_thing_doom_t'. Should be 10."); static_assert(sizeof(raw_linedef_hexen_t) == 16, "Size mismatch for 'raw_linedef_hexen_t'. Should be 16."); static_assert(sizeof(raw_thing_hexen_t) == 20, "Size mismatch for 'raw_thing_hexen_t'. Should be 20."); -static_assert(sizeof(raw_vertex_doom64_t) == 8, "Size mismatch for 'raw_vertex_doom64_t'. Should be 8."); static_assert(sizeof(raw_thing_doom64_t) == 12, "Size mismatch for 'raw_thing_doom64_t'. Should be 12."); static_assert(sizeof(raw_linedef_doom64_t) == 16, "Size mismatch for 'raw_linedef_doom64_t'. Should be 16."); static_assert(sizeof(raw_sidedef_doom64_t) == 12, "Size mismatch for 'raw_sidedef_doom64_t'. Should be 12."); @@ -1104,24 +807,6 @@ static_assert(sizeof(raw_sector_doom64_t) == 24, "Size mismatch for 'raw_sector_ static_assert(sizeof(raw_bbox_t) == 8, "Size mismatch for 'raw_bbox_t'. Should be 8."); static_assert(sizeof(raw_blockmap_header_t) == 8, "Size mismatch for 'raw_blockmap_header_t'. Should be 8."); static_assert(sizeof(raw_blockmap_xbm1_header_t) == 16, "Size mismatch for 'raw_blockmap_xbm1_header_t'. Should be 16."); -static_assert(sizeof(raw_node_vanilla_t) == 28, "Size mismatch for 'raw_node_vanilla_t'. Should be 28."); -static_assert(sizeof(raw_subsec_vanilla_t) == 4, "Size mismatch for 'raw_subsec_vanilla_t'. Should be 4."); -static_assert(sizeof(raw_seg_vanilla_t) == 12, "Size mismatch for 'raw_seg_vanilla_t'. Should be 12."); -static_assert(sizeof(raw_node_deepbspv4_t) == 32, "Size mismatch for 'raw_node_deepbspv4_t'. Should be 32."); -static_assert(sizeof(raw_subsec_deepbspv4_t) == 6, "Size mismatch for 'raw_subsec_deepbspv4_t'. Should be 6."); -static_assert(sizeof(raw_seg_deepbspv4_t) == 16, "Size mismatch for 'raw_seg_deepbspv4_t'. Should be 16."); -static_assert(sizeof(raw_vertex_xnod_t) == 8, "Size mismatch for 'raw_vertex_xnod_t'. Should be 8."); -static_assert(sizeof(raw_node_xnod_t) == 32, "Size mismatch for 'raw_node_xnod_t'. Should be 32."); -static_assert(sizeof(raw_subsec_xnod_t) == 4, "Size mismatch for 'raw_subsec_xnod_t'. Should be 4."); -static_assert(sizeof(raw_seg_xnod_t) == 11, "Size mismatch for 'raw_seg_xnod_t'. Should be 11."); -static_assert(sizeof(raw_seg_xgln_t) == 11, "Size mismatch for 'raw_seg_xgln_t'. Should be 11."); -static_assert(sizeof(raw_seg_xgl2_t) == 13, "Size mismatch for 'raw_seg_xgl2_t'. Should be 13."); -static_assert(sizeof(raw_node_xgl3_t) == 40, "Size mismatch for 'raw_node_xgl3_t'. Should be 40."); -static_assert(sizeof(raw_patchdef_t) == 10, "Size mismatch for 'raw_patchdef_t'. Should be 10."); -static_assert(sizeof(raw_strife_patchdef_t) == 6, "Size mismatch for 'raw_strife_patchdef_t'. Should be 6."); -static_assert(sizeof(raw_texture_t) == 32, "Size mismatch for 'raw_texture_t'. Should be 32."); -static_assert(sizeof(raw_strife_texture_t) == 24, "Size mismatch for 'raw_strife_texture_t'. Should be 24."); -static_assert(sizeof(raw_patch_t) == 12, "Size mismatch for 'raw_patch_t'. Should be 12."); // // LineDef attributes. @@ -1360,372 +1045,6 @@ using doomednum_t = enum doomednum_e : int16_t Hexen_PolyObj_SpawnHurt = 3003, // does any port actually handle this? }; -// -// File handling -// - -struct Lump_c; - -struct Wad_file -{ - char mode; // mode value passed to ::Open() - - FILE *fp; - - char kind; // 'P' for PWAD, 'I' for IWAD - - // zero means "currently unknown", which only occurs after a - // call to BeginWrite() and before any call to AddLump() or - // the finalizing EndWrite(). - int64_t total_size; - - std::vector directory; - - size_t dir_start; - size_t dir_count; - - // these are lump indices (into 'directory' vector) - std::vector levels; - std::vector patches; - std::vector sprites; - std::vector flats; - std::vector tx_tex; - - bool begun_write; - size_t begun_max_size; - - // when >= 0, the next added lump is placed _before_ this - size_t insert_point; - - // constructor is private - Wad_file(const char *_name, char _mode, FILE *_fp); - ~Wad_file(void); - - // open a wad file. - // - // mode is similar to the fopen() function: - // 'r' opens the wad for reading ONLY - // 'a' opens the wad for appending (read and write) - // 'w' opens the wad for writing (i.e. create it) - // - // Note: if 'a' is used and the file is read-only, it will be - // silently opened in 'r' mode instead. - // - static Wad_file *Open(const char *filename, char mode = 'a'); - - [[nodiscard]] bool IsReadOnly(void) const - { - return mode == 'r'; - } - - [[nodiscard]] size_t NumLumps(void) const - { - return directory.size(); - } - - Lump_c *GetLump(size_t index); - - [[nodiscard]] size_t LevelCount(void) const - { - return levels.size(); - } - - size_t LevelHeader(size_t lev_num); - size_t LevelLastLump(size_t lev_num); - - // returns a lump index, -1 if not found - size_t LevelLookupLump(size_t lev_num, const char *name); - - map_format_t LevelFormat(size_t lev_num); - - void SortLevels(void); - - // all changes to the wad must occur between calls to BeginWrite() - // and EndWrite() methods. the on-disk wad directory may be trashed - // during this period, it will be re-written by EndWrite(). - void BeginWrite(void); - void EndWrite(void); - - // remove the given lump(s) - // this will change index numbers on existing lumps - // (previous results of FindLumpNum or LevelHeader are invalidated). - void RemoveLumps(size_t index, size_t count = 1); - - // insert a new lump. - // The second form is for a level marker. - // The 'max_size' parameter (if >= 0) specifies the most data - // you will write into the lump -- writing more will corrupt - // something else in the WAD. - Lump_c *AddLump(const char *name, size_t max_size = NO_INDEX); - - // setup lump to write new data to it. - // the old contents are lost. - void RecreateLump(Lump_c *lump, size_t max_size = NO_INDEX); - - // set the insertion point -- the next lump will be added *before* - // this index, and it will be incremented so that a sequence of - // AddLump() calls produces lumps in the same order. - // - // passing a negative value or invalid index will reset the - // insertion point -- future lumps get added at the END. - // EndWrite() also reset it. - void InsertPoint(size_t index = NO_INDEX); - - static Wad_file *Create(const char *filename, char mode); - - // read the existing directory. - void ReadDirectory(void); - - void DetectLevels(void); - void ProcessNamespaces(void); - - // look at all the lumps and determine the lowest offset from - // start of file where we can write new data. The directory itself - // is ignored for this. - size_t HighWaterMark(void); - - // look at all lumps in directory and determine the lowest offset - // where a lump of the given length will fit. Returns same as - // HighWaterMark() when no largest gaps exist. The directory itself - // is ignored since it will be re-written at EndWrite(). - size_t FindFreeSpace(size_t length); - - // find a place (possibly at end of WAD) where we can write some - // data of max_size (-1 means unlimited), and seek to that spot - // (possibly writing some padding zeros -- the difference should - // be no more than a few bytes). Returns new position. - size_t PositionForWrite(size_t max_size = NO_INDEX); - - void FinishLump(size_t final_size); - size_t WritePadding(size_t count); - - // write the new directory, updating the dir_xxx variables - void WriteDirectory(void); - - void FixGroup(std::vector &group, size_t index, size_t num_added, size_t num_removed); -}; - -struct Lump_c -{ - struct Wad_file *parent; - - std::string lumpname; - - size_t l_start; - size_t l_length; - - bool zlib_init = false; - zng_stream zout_stream; - uint8_t zout_buffer[1024]; - - void MakeEntry(raw_wad_entry_t *entry); - - [[nodiscard]] const char *Name(void) const - { - return lumpname.c_str(); - } - - [[nodiscard]] size_t Length(void) const - { - return l_length; - } - - // case-insensitive match on the lump name - [[nodiscard]] bool Match(const char *s) const - { - return (0 == StringCaseCmp(lumpname.c_str(), s)); - } - - // ensure lump name is uppercase - void Rename(const char *new_name) - { - lumpname.clear(); - - for (const char *s = new_name; *s != 0; s++) - { - lumpname.push_back(static_cast(toupper(*s))); - } - } - - // attempt to seek to a position within the lump (default is - // the beginning). Returns true if OK, false on error. - [[nodiscard]] bool Seek(const size_t offset) const - { - return (fseek(parent->fp, static_cast(l_start + offset), SEEK_SET) == 0); - } - - // read some data from the lump, returning true if OK. - [[nodiscard]] bool Read(void *data, const size_t len) const - { - SYS_ASSERT(data && len > 0); - return (fread(data, len, 1, parent->fp) == 1); - } - - // write some data to the lump. Only the lump which had just - // been created with Wad_file::AddLump() or RecreateLump() can be - // written to. - bool Write(const void *data, const size_t len) - { - SYS_ASSERT(data && len > 0); - l_length += len; - return (fwrite(data, len, 1, parent->fp) == 1); - } - - // mark the lump as finished (after writing data to it). - void Finish(void) - { - if (l_length == 0) - { - l_start = 0; - } - - parent->FinishLump(l_length); - } - - // - // Zlib compression support - // - - void Begin_Zlib(void) - { - zlib_init = true; - zout_stream.zalloc = nullptr; - zout_stream.zfree = nullptr; - zout_stream.opaque = nullptr; - if (Z_OK != zng_deflateInit(&zout_stream, Z_DEFAULT_COMPRESSION)) - { - PrintLine(LOG_ERROR, "ERROR: Trouble starting Zlib compression."); - } - zout_stream.next_out = zout_buffer; - zout_stream.avail_out = sizeof(zout_buffer); - } - - void WriteZ(const void *data, uint32_t length) - { - if (!zlib_init) - { - this->Write(data, length); - return; - } - - SYS_ASSERT(length > 0); - - zout_stream.next_in = static_cast(data); - zout_stream.avail_in = length; - - while (zout_stream.avail_in > 0) - { - if (Z_OK != zng_deflate(&zout_stream, Z_NO_FLUSH)) - { - PrintLine(LOG_ERROR, "ERROR: Trouble Zlib compressing %d bytes.", length); - } - - if (zout_stream.avail_out == 0) - { - this->Write(zout_buffer, sizeof(zout_buffer)); - - zout_stream.next_out = zout_buffer; - zout_stream.avail_out = sizeof(zout_buffer); - } - } - } - - void Finish_Zlib(void) - { - zlib_init = false; - SYS_ASSERT(zout_stream.avail_out > 0) - size_t left_over; - - zout_stream.next_in = Z_NULL; - zout_stream.avail_in = 0; - - while (true) - { - int32_t err = zng_deflate(&zout_stream, Z_FINISH); - - if (err == Z_STREAM_END) break; - - if (err != Z_OK) - { - PrintLine(LOG_ERROR, "ERROR: Trouble finishing Zlib compression."); - } - - if (zout_stream.avail_out == 0) - { - this->Write(zout_buffer, sizeof(zout_buffer)); - - zout_stream.next_out = zout_buffer; - zout_stream.avail_out = sizeof(zout_buffer); - } - } - - left_over = sizeof(zout_buffer) - zout_stream.avail_out; - - if (left_over > 0) this->Write(zout_buffer, left_over); - - zng_deflateEnd(&zout_stream); - } -}; - -// -// Parsing -// - -enum token_kind_e -{ - TOK_EOF = 0, - TOK_ERROR, - - TOK_Ident, - TOK_Symbol, - TOK_Number, - TOK_String -}; - -struct lexer_c -{ - explicit lexer_c(const std::string &_data) : data(_data) - { - } - - ~lexer_c(void) = default; - - // parse the next token, storing contents into given string. - // returns TOK_EOF at the end of the data, and TOK_ERROR when a - // problem is encountered (s will be an error message). - token_kind_e Next(std::string &s); - - // check if the next token is an identifier or symbol matching the - // given string. the match is not case-sensitive. if it matches, - // the token is consumed and true is returned. if not, false is - // returned and the position is unchanged. - bool Match(const char *s); - - // rewind to the very beginning. - void Rewind(void); - - const std::string &data; - - size_t pos = 0; - size_t line = 1; - - void SkipToNext(); - - token_kind_e ParseIdentifier(std::string &s); - token_kind_e ParseNumber(std::string &s); - token_kind_e ParseString(std::string &s); - - void ParseEscape(std::string &s); -}; - -// helpers for converting numeric tokens. -size_t LEX_Index(const std::string &s); -int16_t LEX_Int16(const std::string &s); -int32_t LEX_Int(const std::string &s); -uint32_t LEX_UInt(const std::string &s); -double LEX_Double(const std::string &s); -bool LEX_Boolean(const std::string &s); - // // Node Build Information Structure // @@ -1755,7 +1074,6 @@ struct buildinfo_s bsp_format_t bsp_format = bsp_format_t::BSP_XNOD; bmap_format_t bmap_format = bmap_format_t::BMAP_DoomBSP; bool fast = false; // use a faster method to pick nodes - bool backup = false; // keep a copy of the WAD bool analysis = false; // write out CSV for data analysis and visualization bool verbose = false; // this affects how some messages are shown bool effects = true; // disable special effects @@ -1790,7 +1108,6 @@ constexpr const char PRINT_HELP[] = "\n" "\n" "Available options are:\n" " -v --verbose Verbose output, show all warnings\n" - " -b --backup Backup input files (.bak extension)\n" " -f --fast Faster partition selection\n" " -m --map XXXX Control which map(s) are built\n" " -c --cost ## Cost assigned to seg splits (1-32)\n" @@ -1801,34 +1118,6 @@ constexpr const char PRINT_HELP[] = "\n" "Map names should be full, like E1M3 or MAP24, but a list\n" "and/or ranges can be specified: MAP01,MAP04-MAP07,MAP12\n"; -using build_result_t = enum build_result_e -{ - // everything went peachy keen - BUILD_OK = 0, - - // when saving the map, one or more lumps overflowed - BUILD_LumpOverflow -}; - -// attempt to open a wad. on failure, the FatalError method in the -// buildinfo_t interface is called. -void OpenWad(const char *filename); - -// close a previously opened wad. -void CloseWad(void); - -// give the number of levels detected in the wad. -size_t LevelsInWad(void); - -// build the nodes of a particular level. otherwise the wad -// is updated to store the new lumps and returns either BUILD_OK or -// BUILD_LumpOverflow if some limits were exceeded. -build_result_e BuildLevel(struct level_t &level, const char *filename); - -void SetupAnalysisFile(const char *filepath); -void GenerateAnalysis(level_t &level, const char *filename); -void WriteAnalysis(const char *filename); - // // Benchmark // @@ -1837,10 +1126,10 @@ struct Benchmarker { using clock = std::chrono::steady_clock; clock::time_point start; - const char *name; + std::string_view name; bool enabled; - Benchmarker(const char *_name, bool _enabled = true) + Benchmarker(std::string_view _name, bool _enabled = true) { if (!_enabled) return; enabled = _enabled; @@ -1853,6 +1142,6 @@ struct Benchmarker if (!enabled) return; auto end = clock::now(); auto time = std::chrono::duration(end - start); - PrintLine(LOG_NORMAL, "[Benchmarker] '%s' runtime: %.2f ms", name, time.count()); + PrintLine(LOG_NORMAL, "[Benchmarker] '%s' runtime: %.2f ms", name.data(), time.count()); }; }; diff --git a/src/info.cpp b/src/info.cpp index b541406..a2bf666 100644 --- a/src/info.cpp +++ b/src/info.cpp @@ -28,7 +28,15 @@ static std::vector analysis_csv; //------------------------------------------------------------------------ -void SetupAnalysisFile(const char *filepath) +static void FileClear(std::string_view filename) +{ + if (FILE *fp = fopen(filename.data(), "w")) + { + fclose(fp); + } +} + +void SetupAnalysisFile(std::string_view filepath) { auto csv_path = std::string(filepath); @@ -48,7 +56,7 @@ void SetupAnalysisFile(const char *filepath) // writes out for current file // expects AnalysisPushLine to have been called with all 0-32 split costs during node-building -void WriteAnalysis(const char *filename) +void WriteAnalysis(std::string_view filename) { auto mark = Benchmarker(__func__); auto csv_path = std::string(filename); @@ -110,10 +118,10 @@ static void ComputeTotalBspHeights(const node_t *node, size_t depth, double &lea } } -void GenerateAnalysis(level_t &level, const char *filename) +void GenerateAnalysis(WadIO &io, level_t &level, std::string_view filename) { auto mark = Benchmarker(__func__); - auto generate_analysis_data = [](level_t &level, bool is_fast, size_t split_cost) -> auto + auto generate_analysis_data = [](WadIO &io, level_t &level, bool is_fast, size_t split_cost) -> auto { AnalysisData data; node_t *analysis_node = nullptr; @@ -138,7 +146,6 @@ void GenerateAnalysis(level_t &level, const char *filename) analysis_seg = CreateSegs(level); BuildNodes(level, analysis_seg, 0, &dummy, &analysis_node, &analysis_sub, static_cast(split_cost), is_fast, true); - // TODO: pass level data by context instead of globally :v data.vertex = level.num_old_vert; data.lines = level.linedefs.size(); data.sides = level.sidedefs.size(); @@ -174,7 +181,7 @@ void GenerateAnalysis(level_t &level, const char *filename) data.worst_case_ratio = min_epl / max_epl; data.tree_quality = ((min_epl / total_depth_sum) - data.worst_case_ratio) / (1 - data.worst_case_ratio); - std::string line = std::format("{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}", level.GetLevelName(), is_fast, + std::string line = std::format("{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{},{}", io.LumpName(level.lump), is_fast, split_cost, data.vertex, data.lines, data.sides, data.sectors, data.bsp_vertex, data.nodes, data.subsecs, data.segs, data.splits, data.left_depth, data.right_depth, data.average_depth, data.optimal_depth, data.tree_balance, data.worst_case_ratio, data.tree_quality); @@ -193,8 +200,8 @@ void GenerateAnalysis(level_t &level, const char *filename) // across all split costs for (size_t split_cost = 1; split_cost <= 32; split_cost++) { - generate_analysis_data(level, is_fast != 0, split_cost); - PrintLine(LOG_NORMAL, "[%s] Analyzed %s, %s mode, split cost factor of %zu", __func__, level.GetLevelName(), + generate_analysis_data(io, level, is_fast != 0, split_cost); + PrintLine(LOG_NORMAL, "[%s] Analyzed %s, %s mode, split cost factor of %zu", __func__, io.LumpName(level.lump), is_fast ? "fast" : "normal", split_cost); } } diff --git a/src/info.hpp b/src/info.hpp new file mode 100644 index 0000000..658405d --- /dev/null +++ b/src/info.hpp @@ -0,0 +1,28 @@ +//------------------------------------------------------------------------------ +// +// ELFBSP +// +//------------------------------------------------------------------------------ +// +// Copyright 2026 Guilherme Miranda +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +//------------------------------------------------------------------------------ + +#pragma once + +#include "local.hpp" +#include "wad.hpp" + +void SetupAnalysisFile(std::string_view filepath); +void GenerateAnalysis(WadIO &io, level_t &level, std::string_view filename); +void WriteAnalysis(std::string_view filename); diff --git a/src/level.cpp b/src/level.cpp index 4568821..a56f6aa 100644 --- a/src/level.cpp +++ b/src/level.cpp @@ -21,16 +21,17 @@ // //------------------------------------------------------------------------------ +#include "bsp.hpp" #include "core.hpp" +#include "info.hpp" #include "local.hpp" +#include "parse.hpp" #include #include #include -Wad_file *cur_wad; - -int CheckLinedefInsideBox(int xmin, int ymin, int xmax, int ymax, int x1, int y1, int x2, int y2) +bool CheckLinedefInsideBox(int xmin, int ymin, int xmax, int ymax, int x1, int y1, int x2, int y2) { int count = 2; int tmp; @@ -302,7 +303,7 @@ void FreeIntersections(level_t &level) /* ----- reading routines ------------------------------ */ -void ValidateLinedef(level_t &level, linedef_t *line) +static void ValidateLinedef(level_t &level, linedef_t *line) { if (line->right || line->left) { @@ -351,43 +352,18 @@ void ValidateLinedef(level_t &level, linedef_t *line) } } -static void GetVertices_Doom(level_t &level) +static void GetVertices_Integral(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("VERTEXES"); - - if (lump) - { - count = lump->Length() / sizeof(raw_vertex_t); - } + auto lump = io.ReadLevelLump(level.lump, "VERTEXES"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - if (lump == nullptr || count == 0) + for (auto &raw : lump) { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to vertices."); - } - - for (size_t i = 0; i < count; i++) - { - raw_vertex_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading vertices."); - } - vertex_t *vert = NewVertex(level); - vert->x = ShortToFloat(GetLittleEndian(raw.x)); vert->y = ShortToFloat(GetLittleEndian(raw.y)); } @@ -395,82 +371,53 @@ static void GetVertices_Doom(level_t &level) level.num_old_vert = level.vertices.size(); } -static void GetSectors_Doom(level_t &level) +static void GetVertices_Fractional(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("SECTORS"); + auto lump = io.ReadLevelLump(level.lump, "VERTEXES"); - if (lump) + if (HAS_BIT(config.debug, DEBUG_LOAD)) { - count = lump->Length() / sizeof(raw_sector_doom_t); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - if (lump == nullptr || count == 0) + for (auto &raw : lump) { - return; - } + vertex_t *vert = NewVertex(level); - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to sectors."); + vert->x = static_cast(GetLittleEndian(raw.x) / FRACFACTOR); + vert->y = static_cast(GetLittleEndian(raw.y) / FRACFACTOR); } + level.num_old_vert = level.vertices.size(); +} + +static void GetSectors_Doom(WadIO &io, level_t &level) +{ + auto lump = io.ReadLevelLump(level.lump, "SECTORS"); + if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &_ : lump) { - raw_sector_doom_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading sectors."); - } - sector_t *sector = NewSector(level); - sector->effects = FX_Sector_None; } } -static void GetThings_Doom(level_t &level) +static void GetThings_Doom(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("THINGS"); - - if (lump) - { - count = lump->Length() / sizeof(raw_thing_doom_t); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to things."); - } + auto lump = io.ReadLevelLump(level.lump, "THINGS"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &raw : lump) { - raw_thing_doom_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading things."); - } - thing_t *thing = NewThing(level); thing->x = GetLittleEndian(raw.x); @@ -479,41 +426,17 @@ static void GetThings_Doom(level_t &level) } } -static void GetSidedefs_Doom(level_t &level) +static void GetSidedefs_Doom(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("SIDEDEFS"); - - if (lump) - { - count = lump->Length() / sizeof(raw_sidedef_doom_t); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to sidedefs."); - } + auto lump = io.ReadLevelLump(level.lump, "SIDEDEFS"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &raw : lump) { - raw_sidedef_doom_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading sidedefs."); - } - sidedef_t *side = NewSidedef(level); side->offset_x = GetLittleEndian(raw.x_offset); @@ -521,49 +444,25 @@ static void GetSidedefs_Doom(level_t &level) memcpy(side->tex_upper, raw.upper_tex, 8); memcpy(side->tex_lower, raw.lower_tex, 8); memcpy(side->tex_middle, raw.mid_tex, 8); - side->sector = level.SafeLookupSector(GetLittleEndian(raw.sector), i); + side->sector = level.SafeLookupSector(GetLittleEndian(raw.sector), side->index); } } -static void GetLinedefs_Doom(level_t &level) +static void GetLinedefs_Doom(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("LINEDEFS"); - - if (lump) - { - count = lump->Length() / sizeof(raw_linedef_doom_t); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to linedefs."); - } + auto lump = io.ReadLevelLump(level.lump, "LINEDEFS"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &raw : lump) { - raw_linedef_doom_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading linedefs."); - } - linedef_t *line = NewLinedef(level); - line->start = level.SafeLookupVertex(GetLittleEndian(raw.start), i); - line->end = level.SafeLookupVertex(GetLittleEndian(raw.end), i); + line->start = level.SafeLookupVertex(GetLittleEndian(raw.start), line->index); + line->end = level.SafeLookupVertex(GetLittleEndian(raw.end), line->index); line->right = level.SafeLookupSidedef(GetLittleEndian(raw.right)); line->left = level.SafeLookupSidedef(GetLittleEndian(raw.left)); line->special = GetLittleEndian(raw.special); @@ -630,41 +529,17 @@ static void GetLinedefs_Doom(level_t &level) } } -static void GetThings_Hexen(level_t &level) +static void GetThings_Hexen(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("THINGS"); - - if (lump) - { - count = lump->Length() / sizeof(raw_thing_hexen_t); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to things."); - } + auto lump = io.ReadLevelLump(level.lump, "THINGS"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &raw : lump) { - raw_thing_hexen_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading things."); - } - thing_t *thing = NewThing(level); thing->x = GetLittleEndian(raw.x); @@ -673,45 +548,21 @@ static void GetThings_Hexen(level_t &level) } } -static void GetLinedefs_Hexen(level_t &level) +static void GetLinedefs_Hexen(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("LINEDEFS"); - - if (lump) - { - count = lump->Length() / sizeof(raw_linedef_hexen_t); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to linedefs."); - } + auto lump = io.ReadLevelLump(level.lump, "LINEDEFS"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &raw : lump) { - raw_linedef_hexen_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading linedefs."); - } - linedef_t *line = NewLinedef(level); - line->start = level.SafeLookupVertex(GetLittleEndian(raw.start), i); - line->end = level.SafeLookupVertex(GetLittleEndian(raw.end), i); + line->start = level.SafeLookupVertex(GetLittleEndian(raw.start), line->index); + line->end = level.SafeLookupVertex(GetLittleEndian(raw.end), line->index); line->special = raw.special; line->right = level.SafeLookupSidedef(GetLittleEndian(raw.right)); line->left = level.SafeLookupSidedef(GetLittleEndian(raw.left)); @@ -743,85 +594,17 @@ static void GetLinedefs_Hexen(level_t &level) } } -static void GetVertices_Doom64(level_t &level) +static void GetSectors_Doom64(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("VERTEXES"); - - if (lump) - { - count = lump->Length() / sizeof(raw_vertex_doom64_t); - } + auto lump = io.ReadLevelLump(level.lump, "SECTORS"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "Error seeking to 32bit vertices."); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &raw : lump) { - raw_vertex_doom64_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "Error reading 32bit vertices."); - } - - vertex_t *vert = NewVertex(level); - - vert->x = static_cast(GetLittleEndian(raw.x) / FRACFACTOR); - vert->y = static_cast(GetLittleEndian(raw.y) / FRACFACTOR); - } - - level.num_old_vert = level.vertices.size(); -} - -static void GetSectors_Doom64(level_t &level) -{ - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("SECTORS"); - - if (lump) - { - count = lump->Length() / sizeof(raw_sector_doom64_t); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "Error seeking to Doom64 sectors."); - } - - if (HAS_BIT(config.debug, DEBUG_LOAD)) - { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); - } - - for (size_t i = 0; i < count; i++) - { - raw_sector_doom64_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "Error reading Doom64 sectors."); - } - sector_t *sector = NewSector(level); if (!config.effects) continue; @@ -833,89 +616,41 @@ static void GetSectors_Doom64(level_t &level) } } -static void GetSidedefs_Doom64(level_t &level) +static void GetSidedefs_Doom64(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("SIDEDEFS"); - - if (lump) - { - count = lump->Length() / sizeof(raw_sidedef_doom64_t); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "Error seeking to Doom64 sidedefs."); - } + auto lump = io.ReadLevelLump(level.lump, "SIDEDEFS"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &raw : lump) { - raw_sidedef_doom64_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "Error reading Doom64 sidedefs."); - } - sidedef_t *side = NewSidedef(level); side->offset_x = GetLittleEndian(raw.x_offset); side->offset_y = GetLittleEndian(raw.y_offset); // We don't care about texture indexes here - side->sector = level.SafeLookupSector(GetLittleEndian(raw.sector), i); + side->sector = level.SafeLookupSector(GetLittleEndian(raw.sector), side->index); } } -static void GetLinedefs_Doom64(level_t &level) +static void GetLinedefs_Doom64(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("LINEDEFS"); - - if (lump) - { - count = lump->Length() / sizeof(raw_linedef_doom64_t); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "Error seeking to Doom64 linedefs."); - } + auto lump = io.ReadLevelLump(level.lump, "LINEDEFS"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &raw : lump) { - raw_linedef_doom64_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "Error reading Doom64 linedefs."); - } - linedef_t *line = NewLinedef(level); - line->start = level.SafeLookupVertex(GetLittleEndian(raw.start), i); - line->end = level.SafeLookupVertex(GetLittleEndian(raw.end), i); + line->start = level.SafeLookupVertex(GetLittleEndian(raw.start), line->index); + line->end = level.SafeLookupVertex(GetLittleEndian(raw.end), line->index); line->special = GetLittleEndian(raw.special); line->tag = GetLittleEndian(raw.tag); line->right = level.SafeLookupSidedef(GetLittleEndian(raw.right)); @@ -946,41 +681,17 @@ static void GetLinedefs_Doom64(level_t &level) } } -static void GetThings_Doom64(level_t &level) +static void GetThings_Doom64(WadIO &io, level_t &level) { - size_t count = 0; - - Lump_c *lump = level.FindLevelLump("THINGS"); - - if (lump) - { - count = lump->Length() / sizeof(raw_thing_doom64_t); - } - - if (lump == nullptr || count == 0) - { - return; - } - - if (!lump->Seek(0)) - { - PrintLine(LOG_ERROR, "Error seeking to Doom64 things."); - } + auto lump = io.ReadLevelLump(level.lump, "LINEDEFS"); if (HAS_BIT(config.debug, DEBUG_LOAD)) { - PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, count); + PrintLine(LOG_DEBUG, "[%s] num = %zu", __func__, lump.size()); } - for (size_t i = 0; i < count; i++) + for (auto &raw : lump) { - raw_thing_doom64_t raw; - - if (!lump->Read(&raw, sizeof(raw))) - { - PrintLine(LOG_ERROR, "Error reading Doom64 things."); - } - thing_t *thing = NewThing(level); thing->x = GetLittleEndian(raw.x); @@ -1291,35 +1002,12 @@ static void ParseUDMF_Pass(level_t &level, const std::string &data, int pass) } } -void ParseUDMF(level_t &level) +void ParseUDMF(WadIO &io, level_t &level) { - Lump_c *lump = level.FindLevelLump("TEXTMAP"); - - if (lump == nullptr || !lump->Seek(0)) - { - PrintLine(LOG_ERROR, "ERROR: Failure finding TEXTMAP lump."); - } - - size_t remain = lump->Length(); + auto lump = io.ReadLevelLump(level.lump, "TEXTMAP"); // load the lump into this string - std::string data; - - while (remain > 0) - { - char buffer[4096]; - - size_t want = std::min(remain, sizeof(buffer)); - - if (!lump->Read(buffer, want)) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading TEXTMAP lump."); - } - - data.append(buffer, want); - - remain -= want; - } + auto data = std::string(lump.begin(), lump.end()); // now parse it... @@ -1366,7 +1054,7 @@ static void CheckBinaryFormatLimits(level_t &level) } } -bsp_format_t CheckFormatBSP(buildinfo_t &ctx, level_t &level) +static bsp_format_t CheckFormatBSP(buildinfo_t &ctx, level_t &level) { bsp_format_t level_type = ctx.bsp_format; @@ -1389,19 +1077,19 @@ bsp_format_t CheckFormatBSP(buildinfo_t &ctx, level_t &level) level_type = BSP_DeePBSPV4; } - return level_type; + return level.bsp_format = level_type; } /* ----- whole-level routines --------------------------- */ -void LoadLevel(level_t &level) +void LoadLevel(WadIO &io, level_t &level) { auto mark = Benchmarker(__func__); - Lump_c *LEV = cur_wad->GetLump(level.level_header_lump_index); + auto LEV = io.LumpName(level.lump); level.overflows = false; - PrintLine(LOG_NORMAL, "[%s] Reading %s", __func__, LEV->Name()); + PrintLine(LOG_NORMAL, "[%s] Reading %s", __func__, LEV); level.num_new_vert = 0; level.num_real_lines = 0; @@ -1409,34 +1097,34 @@ void LoadLevel(level_t &level) switch (level.map_format) { case MapFormat_Doom: - GetVertices_Doom(level); - GetSectors_Doom(level); - GetSidedefs_Doom(level); - GetLinedefs_Doom(level); - GetThings_Doom(level); + GetVertices_Integral(io, level); + GetSectors_Doom(io, level); + GetSidedefs_Doom(io, level); + GetLinedefs_Doom(io, level); + GetThings_Doom(io, level); PruneVerticesAtEnd(level); break; case MapFormat_Hexen: - GetVertices_Doom(level); - GetSectors_Doom(level); - GetSidedefs_Doom(level); - GetLinedefs_Hexen(level); - GetThings_Hexen(level); + GetVertices_Integral(io, level); + GetSectors_Doom(io, level); + GetSidedefs_Doom(io, level); + GetLinedefs_Hexen(io, level); + GetThings_Hexen(io, level); PruneVerticesAtEnd(level); break; case MapFormat_Doom64: - GetVertices_Doom64(level); - GetSectors_Doom64(level); - GetSidedefs_Doom64(level); - GetLinedefs_Doom64(level); - GetThings_Doom64(level); + GetVertices_Fractional(io, level); + GetSectors_Doom64(io, level); + GetSidedefs_Doom64(io, level); + GetLinedefs_Doom64(io, level); + GetThings_Doom64(io, level); PruneVerticesAtEnd(level); break; case MapFormat_UDMF: - ParseUDMF(level); + ParseUDMF(io, level); break; case MapFormat_INVALID: - PrintLine(LOG_ERROR, "[%s] Unknown level format on level %s", __func__, LEV->Name()); + PrintLine(LOG_ERROR, "[%s] Unknown level format on level %s", __func__, LEV); break; } @@ -1463,221 +1151,148 @@ void LoadLevel(level_t &level) } } -void FreeLevel(level_t &level) -{ - FreeVertices(level); - FreeSidedefs(level); - FreeLinedefs(level); - FreeSectors(level); - FreeThings(level); - FreeSegs(level); - FreeSubsecs(level); - FreeNodes(level); - FreeWallTips(level); - FreeIntersections(level); -} +//------------------------------------------------------------------------ +// Writing stuff +//------------------------------------------------------------------------ -static void AddMissingLump(level_t &level, const char *name, const char *after) +build_result_t SaveLevelBinary(WadIO &io, level_t &level) { - if (cur_wad->LevelLookupLump(level.level_num, name) != NO_INDEX) - { - return; - } + auto mark = Benchmarker(__func__); - size_t exist = cur_wad->LevelLookupLump(level.level_num, after); + // Normal binary format limits + CheckBinaryFormatLimits(level); - // if this happens, the level structure is very broken - if (exist == NO_INDEX) - { - PrintLine(LOG_NORMAL, "WARNING: Missing %s lump -- level structure is broken", after); - config.total_warnings++; - exist = cur_wad->LevelLastLump(level.level_num); - } + // Use fractional VERTEXES lump in some map formats + bool do_fractional = level.map_format == MapFormat_Doom64; - cur_wad->InsertPoint(exist + 1); - cur_wad->AddLump(name)->Finish(); -} + // Generate LEAFS lump + bool do_leafs = level.map_format == MapFormat_Doom64; -build_result_e SaveLevelBinaryFormat(level_t &level, node_t *root_node) -{ - // Note: root_node may be nullptr + // On BSP overflow, bump to DeePBSPV4 from DoomBSP + bsp_format_t bsp_format = CheckFormatBSP(config, level); - cur_wad->BeginWrite(); + // Only include BSP-generated vertices in non-ZDBSP lump format + bool do_new_vert = bsp_format >= BSP_DoomBSP || bsp_format <= BSP_DeePBSPV4; - // ensure all necessary level lumps are present - AddMissingLump(level, "SEGS", "VERTEXES"); - AddMissingLump(level, "SSECTORS", "SEGS"); - AddMissingLump(level, "NODES", "SSECTORS"); - AddMissingLump(level, "SECTORS", "NODES"); - AddMissingLump(level, "REJECT", "SECTORS"); - AddMissingLump(level, "BLOCKMAP", "REJECT"); + // Use Zlib-compressed version of ZDBSP lump formats + bool zdbsp_compress = config.compress; - // No need to add BEHAVIOR, LIGHTS or MACROS - if (level.map_format == MapFormat_Doom64) - { - AddMissingLump(level, "LEAFS", "BLOCKMAP"); - } + // Copy marker wholesale + size_t lump = level.lump; + io.CopyLump(lump); - // check for overflows... - CheckBinaryFormatLimits(level); + // Correct order is paramount + io.LevelCopyLump(lump, "THINGS"); + io.LevelCopyLump(lump, "LINEDEFS"); + io.LevelCopyLump(lump, "SIDEDEFS"); - // If using DoomBSP format, bump to DeePBSPV4 on overflow - RaiseValue(level.bsp_format, CheckFormatBSP(config, level)); + // Do the four BSP lumps, before promptly doing the other normal lumps + if (bsp_format == BSP_DoomBSP) + { + NormaliseBspTree(level); + if (!do_fractional) RoundOffBspTree(level); + SortSegs(level); - // Using Zlib-compressed version of ZDBSP lump format - level.bsp_compress |= config.compress; + PutVertices(io, level, do_fractional, do_new_vert); + PutSegs_DoomBSP(io, level); + PutSubsecs_DoomBSP(io, level); + PutNodes_DoomBSP(io, level); + } + else if (bsp_format == BSP_DeePBSPV4) + { + NormaliseBspTree(level); + if (!do_fractional) RoundOffBspTree(level); + SortSegs(level); - if (level.map_format == MapFormat_Doom64) + PutVertices(io, level, do_fractional, do_new_vert); + PutSegs_DeePBSPV4(io, level); + PutSubsecs_DeePBSPV4(io, level); + PutNodes_DeePBSPV4(io, level); + } + else if (bsp_format == BSP_XNOD) { - switch (level.bsp_format) - { - case BSP_DeePBSPV4: - SaveDoom64_DeePBSPV4(level, root_node); - break; - case BSP_DoomBSP: - SaveDoom64_DoomBSP(level, root_node); - break; - default: - PrintLine(LOG_ERROR, "ERROR: Tried to write unsupported BSP format #%d on Doom64 map format", level.bsp_format); - break; - } + // ZDBSP formats do not write to VERTEXES + PutVertices(io, level, do_fractional, do_new_vert); + io.CreateEmptyLump("SEGS"); + io.CreateEmptyLump("SSECTORS"); + PutTree_ZDBSP(io, level, "NODES", bsp_format, zdbsp_compress); } - else // MapFormat_Doom or MapFormat_Hexen + else if (bsp_format >= BSP_XGLN && bsp_format <= BSP_XGL3) { - switch (level.bsp_format) - { - case BSP_XGL3: - SaveDoom_XGL3(level, root_node); - break; - case BSP_XGL2: - SaveDoom_XGL2(level, root_node); - break; - case BSP_XGLN: - SaveDoom_XGLN(level, root_node); - break; - case BSP_XNOD: - SaveDoom_XNOD(level, root_node); - break; - case BSP_DeePBSPV4: - SaveDoom_DeePBSPV4(level, root_node); - break; - case BSP_DoomBSP: - SaveDoom_DoomBSP(level, root_node); - break; - } + // ZDBSP formats do not write to VERTEXES + PutVertices(io, level, do_fractional, do_new_vert); + io.CreateEmptyLump("SEGS"); + PutTree_ZDBSP(io, level, "SSECTORS", bsp_format, zdbsp_compress); + io.CreateEmptyLump("NODES"); } - PutBlockmap(level); - PutReject(level); + // Why is SECTORS right here? T_T + io.LevelCopyLump(lump, "SECTORS"); - cur_wad->EndWrite(); + // Bruh + PutReject(io, level); + PutBlockmap(io, level); - if (level.overflows) + // The better? convex subsector option + if (do_leafs && bsp_format == BSP_DoomBSP) { - // no message here - // [ in verbose mode, each overflow already printed a message ] - // [ in normal mode, we don't want any messages at all ] - return BUILD_LumpOverflow; + PutLeafs_DoomBSP(io, level); + } + else if (do_leafs && bsp_format == BSP_DeePBSPV4) + { + PutLeafs_DeePBSPV4(io, level); } - return BUILD_OK; -} - -build_result_e SaveLevelTextMap(level_t &level, node_t *root_node) -{ - cur_wad->BeginWrite(); - - Lump_c *lump = CreateLevelLump(level, "ZNODES"); - AddMissingLump(level, "REJECT", "ZNODES"); - AddMissingLump(level, "BLOCKMAP", "REJECT"); - - if (level.num_real_lines == 0) + // Level scripts and stuff + if (level.map_format == MapFormat_Hexen) { - lump->Finish(); + io.LevelCopyLump(lump, "BEHAVIOR"); + io.LevelCopyLump(lump, "SCRIPTS"); } - else + else if (level.map_format == MapFormat_Doom64) { - SaveTextmap_ZNODES(level, root_node); + io.LevelCopyLump(lump, "LIGHTS"); + io.LevelCopyLump(lump, "MACROS"); + io.LevelCopyLump(lump, "SCRIPTS"); } - PutBlockmap(level); - PutReject(level); - - cur_wad->EndWrite(); - - return BUILD_OK; + return level.overflows ? BUILD_LumpOverflow : BUILD_OK; } -/* ---------------------------------------------------------------- */ -Lump_c *CreateLevelLump(level_t &level, const char *name, size_t max_size) +build_result_t SaveLevelTextMap(WadIO &io, level_t &level) { - // look for existing one - Lump_c *lump = level.FindLevelLump(name); + auto mark = Benchmarker(__func__); - if (lump) - { - cur_wad->RecreateLump(lump, max_size); - } - else - { - size_t last_idx = cur_wad->LevelLastLump(level.level_num); + // Copy marker wholesale + size_t lump = level.lump; + io.CopyLump(lump); - // in UDMF maps, insert before the ENDMAP lump, otherwise insert - // after the last known lump of the level. - if (level.map_format != MapFormat_UDMF) - { - last_idx += 1; - } + // duh. + io.LevelCopyLump(lump, "TEXTMAP"); - cur_wad->InsertPoint(last_idx); + // Always use ZGL3 for UDMF maps + PutTree_ZDBSP(io, level, "ZNODES", BSP_XGL3, true); + PutReject(io, level); + PutBlockmap(io, level); - lump = cur_wad->AddLump(name, max_size); - } + // Copy everything else we don't touch + io.LevelCopyLump(lump, "BEHAVIOR"); + io.LevelCopyLump(lump, "MACROS"); + io.LevelCopyLump(lump, "SCRIPTS"); + io.LevelCopyLump(lump, "DIALOGUE"); + io.LevelCopyLump(lump, "LIGHTMAP"); - return lump; + // Finish + io.CreateEmptyLump("ENDMAP"); + + return BUILD_OK; } //------------------------------------------------------------------------ // MAIN STUFF //------------------------------------------------------------------------ -void OpenWad(const char *filename) -{ - cur_wad = Wad_file::Open(filename, 'a'); - if (cur_wad == nullptr) - { - PrintLine(LOG_ERROR, "ERROR: Cannot open file: %s", filename); - } - - if (cur_wad->IsReadOnly()) - { - delete cur_wad; - cur_wad = nullptr; - PrintLine(LOG_ERROR, "ERROR: file is read only: %s", filename); - } -} - -void CloseWad(void) -{ - if (cur_wad != nullptr) - { - // this closes the file - delete cur_wad; - cur_wad = nullptr; - } -} - -size_t LevelsInWad(void) -{ - if (cur_wad == nullptr) - { - return 0; - } - - return cur_wad->LevelCount(); -} - size_t ComputeBspHeight(const node_t *node) { if (node == nullptr) @@ -1693,12 +1308,9 @@ size_t ComputeBspHeight(const node_t *node) /* ----- build nodes for a single level ----- */ -build_result_e BuildLevel(level_t &level, const char *filename) +build_result_e BuildLevel(WadIO &io, level_t &level, std::string_view filename) { - node_t *root_node = nullptr; - subsec_t *root_sub = nullptr; - - LoadLevel(level); + LoadLevel(io, level); InitBlockmap(level); @@ -1706,8 +1318,8 @@ build_result_e BuildLevel(level_t &level, const char *filename) { if (config.analysis) { - PrintLine(LOG_NORMAL, "[%s] Starting analysis loop for %s", __func__, level.GetLevelName()); - GenerateAnalysis(level, filename); + PrintLine(LOG_NORMAL, "[%s] Starting analysis loop for %s", __func__, io.LumpName(level.lump)); + GenerateAnalysis(io, level, filename); } bbox_t dummy; @@ -1715,19 +1327,19 @@ build_result_e BuildLevel(level_t &level, const char *filename) seg_t *seg_list = CreateSegs(level); // recursive function T-T auto mark = Benchmarker("BuildNodes"); - BuildNodes(level, seg_list, 0, &dummy, &root_node, &root_sub, config.split_cost, config.fast, false); + BuildNodes(level, seg_list, 0, &dummy, &level.root_node, &level.root_sub, config.split_cost, config.fast, false); } if (config.verbose) { PrintLine(LOG_NORMAL, "Built %zu NODES, %zu SSECTORS, %zu SEGS, %zu VERTEXES", level.nodes.size(), level.subsecs.size(), - level.segs.size(), level.num_old_vert + level.num_new_vert); + level.segs.size(), level.vertices.size()); } - if (config.verbose && root_node != nullptr) + if (config.verbose && level.root_node != nullptr) { - PrintLine(LOG_NORMAL, "Heights of subtrees: %zu / %zu", ComputeBspHeight(root_node->l.node), - ComputeBspHeight(root_node->r.node)); + PrintLine(LOG_NORMAL, "Heights of subtrees: %zu / %zu", ComputeBspHeight(level.root_node->l.node), + ComputeBspHeight(level.root_node->r.node)); } ClockwiseBspTree(level); @@ -1738,16 +1350,26 @@ build_result_e BuildLevel(level_t &level, const char *filename) case MapFormat_Doom: case MapFormat_Hexen: case MapFormat_Doom64: - ret = SaveLevelBinaryFormat(level, root_node); + ret = SaveLevelBinary(io, level); + break; break; case MapFormat_UDMF: - ret = SaveLevelTextMap(level, root_node); + ret = SaveLevelTextMap(io, level); break; default: break; } - FreeLevel(level); + FreeVertices(level); + FreeSidedefs(level); + FreeLinedefs(level); + FreeSectors(level); + FreeThings(level); + FreeSegs(level); + FreeSubsecs(level); + FreeNodes(level); + FreeWallTips(level); + FreeIntersections(level); if (config.analysis) { diff --git a/src/local.hpp b/src/local.hpp index dffa9f9..6882c0b 100644 --- a/src/local.hpp +++ b/src/local.hpp @@ -24,26 +24,22 @@ #pragma once #include "core.hpp" +#include "wad.hpp" #include -struct Lump_c; -struct Wad_file; - -// current WAD file -extern Wad_file *cur_wad; - //------------------------------------------------------------------------ // BLOCKMAP : Generate the blockmap //------------------------------------------------------------------------ // utility routines... -int CheckLinedefInsideBox(int xmin, int ymin, int xmax, int ymax, int x1, int y1, int x2, int y2); +bool CheckLinedefInsideBox(int xmin, int ymin, int xmax, int ymax, int x1, int y1, int x2, int y2); //------------------------------------------------------------------------ // LEVEL : Level structures & read/write functions. //------------------------------------------------------------------------ +struct level_t; struct node_t; struct sector_t; struct quadtree_c; @@ -240,11 +236,6 @@ struct seg_t } }; -// compute the seg private info (psx/y, pex/y, pdx/y, etc). -void Recompute(seg_t *seg); - -int PointOnLineSide(seg_t *seg, double x, double y); - // a seg with this index is removed by SortSegs(). // it must be a very high value. static constexpr uint32_t SEG_IS_GARBAGE = (1 << 29); @@ -266,18 +257,6 @@ struct subsec_t double mid_y; }; -void AddToTail(subsec_t *subsec); - -void DetermineMiddle(subsec_t *subsec); -void ClockwiseOrder(subsec_t *subsec); -void RenumberSegs(subsec_t *subsec, size_t &cur_seg_index); - -void RoundOffSubsector(level_t &level, subsec_t *subsec); -void Normalise(subsec_t *subsec); - -void SanityCheckClosed(subsec_t *subsec); -void SanityCheckHasRealSeg(subsec_t *subsec); - struct bbox_t { int16_t minx, miny; @@ -311,8 +290,6 @@ struct node_t size_t index; }; -void SetPartition(node_t *node, const seg_t *part); - struct quadtree_c { // NOTE: not a real quadtree, division is always binary. @@ -345,11 +322,6 @@ struct quadtree_c } }; -void AddSeg(quadtree_c *quadtree, seg_t *seg); -void AddList(quadtree_c *quadtree, seg_t *list); - -void ConvertToList(quadtree_c *quadtree, seg_t **__list); - // check relationship between this box and the partition line. // returns -1 or +1 if box is definitively on a particular side, // or 0 if the line intersects or touches the box. @@ -397,8 +369,7 @@ using level_t = struct level_t size_t num_old_vert = 0; size_t num_new_vert = 0; size_t num_real_lines = 0; - size_t level_num = NO_INDEX; - size_t level_header_lump_index = NO_INDEX; + size_t lump = NO_INDEX; bool overflows = false; std::vector vertices; @@ -407,13 +378,15 @@ using level_t = struct level_t std::vector sectors; std::vector things; + node_t *root_node = nullptr; + subsec_t *root_sub = nullptr; + std::vector segs; std::vector subsecs; std::vector nodes; std::vector walltips; std::vector intercuts; bsp_format_t bsp_format = bsp_format_t::BSP_XNOD; - bool bsp_compress = false; uint8_t *reject_matrix; size_t reject_size; @@ -429,20 +402,6 @@ using level_t = struct level_t double block_compression; bmap_format_t bmap_format = bmap_format_t::BMAP_DoomBSP; - inline Lump_c *FindLevelLump(const char *name) - { - SYS_ASSERT(cur_wad != nullptr); - size_t idx = cur_wad->LevelLookupLump(level_num, name); - return (idx != NO_INDEX) ? cur_wad->GetLump(idx) : nullptr; - } - - inline const char *GetLevelName(void) - { - SYS_ASSERT(cur_wad != nullptr); - size_t lump_idx = cur_wad->LevelHeader(level_num); - return cur_wad->GetLump(lump_idx)->Name(); - } - vertex_t *SafeLookupVertex(size_t num, size_t num_line) { if (num >= vertices.size()) @@ -505,12 +464,14 @@ void FreeNodes(level_t &level); void FreeWallTips(level_t &level); void FreeIntersections(level_t &level); -Lump_c *CreateLevelLump(level_t &level, const char *name, size_t max_size = NO_INDEX); - //------------------------------------------------------------------------ // ANALYZE : Analyzing level structures //------------------------------------------------------------------------ +// +// TODO: some of these are defined in misc, but only used elsewhere. static? +// + // detection routines void DetectOverlappingVertices(level_t &level); void DetectOverlappingLines(level_t &level); @@ -570,44 +531,16 @@ void BuildNodes(level_t &level, seg_t *seg_list, int depth, bbox_t *bounds, node // compute the height of the bsp tree, starting at 'node'. size_t ComputeBspHeight(const node_t *node); -// put all the segs in each subsector into clockwise order, and renumber -// the seg indices. -// -// [ This cannot be done DURING BuildNodes() since splitting a seg with -// a partner will insert another seg into that partner's list, usually -// in the wrong place order-wise. ] -void ClockwiseBspTree(level_t &level); - -// traverse the BSP tree and do whatever is necessary to convert the -// node information from GL standard to normal standard (for example, -// removing minisegs). -void NormaliseBspTree(level_t &level); - -// Mark new vertices as old for writing into the VERTEXES lump -// Needed for RoundOffBspTree and saving to Doom 64 map formats directly -void RoundOffVertices(level_t &level); - -// traverse the BSP tree, doing whatever is necessary to round -// vertices to integer coordinates (for example, removing segs whose -// rounded coordinates degenerate to the same point). -void RoundOffBspTree(level_t &level); - -// both BLOCKAMP and REJECT exist as single lumps on all supported map formats -void InitBlockmap(level_t &level); -void PutBlockmap(level_t &level); -void PutReject(level_t &level); - -// the BSP tree lumps differ notably on each map format -- Doom/Hexen/Doom64 -// have NODES, SSECTORS & SEGS, but UDMF is generally only ZNODES, and there's -// some format overlap between them -void SaveDoom_DoomBSP(level_t &level, node_t *root_node); -void SaveDoom_DeePBSPV4(level_t &level, node_t *root_node); -void SaveDoom_XNOD(level_t &level, node_t *root_node); -void SaveDoom_XGLN(level_t &level, node_t *root_node); -void SaveDoom_XGL2(level_t &level, node_t *root_node); -void SaveDoom_XGL3(level_t &level, node_t *root_node); - -void SaveDoom64_DoomBSP(level_t &level, node_t *root_node); -void SaveDoom64_DeePBSPV4(level_t &level, node_t *root_node); - -void SaveTextmap_ZNODES(level_t &level, node_t *root_node); +using build_result_t = enum build_result_e +{ + // everything went peachy keen + BUILD_OK = 0, + + // when saving the map, one or more lumps overflowed + BUILD_LumpOverflow +}; + +// build the nodes of a particular level. otherwise the wad +// is updated to store the new lumps and returns either BUILD_OK or +// BUILD_LumpOverflow if some limits were exceeded. +build_result_e BuildLevel(WadIO &io, level_t &level, std::string_view filename); diff --git a/src/main.cpp b/src/main.cpp index 28af59f..8b92486 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -22,18 +22,21 @@ //------------------------------------------------------------------------------ #include "core.hpp" +#include "info.hpp" #include "local.hpp" +#include "wad.hpp" #include #include +#include #include static bool opt_help = false; static bool opt_version = false; -static std::string opt_output; +static std::string_view opt_output; -static std::vector wad_list; +static std::vector wad_list; static size_t total_failed_files = 0; static size_t total_empty_files = 0; @@ -52,19 +55,35 @@ buildinfo_t config; //------------------------------------------------------------------------ -bool CheckMapInRange(const map_range_t *range, const char *name) +//------------------------------------------------------------------------ +// FILE MANAGEMENT +//------------------------------------------------------------------------ + +static bool FileExists(std::string filename) +{ + + if (FILE *fp = fopen(filename.c_str(), "rb")) + { + fclose(fp); + return true; + } + + return false; +} + +static bool CheckMapInRange(const map_range_t *range, std::string name) { - if (strlen(name) != range->low.size()) + if (name.size() != range->low.size()) { return false; } - if (strcmp(name, range->low.c_str()) < 0) + if (name < range->low) { return false; } - if (strcmp(name, range->high.c_str()) > 0) + if (name > range->high) { return false; } @@ -72,7 +91,7 @@ bool CheckMapInRange(const map_range_t *range, const char *name) return true; } -bool CheckMapInMapList(const char *name) +static bool CheckMapInMapList(std::string name) { // when --map is not used, allow everything if (map_list.empty()) @@ -91,163 +110,104 @@ bool CheckMapInMapList(const char *name) return false; } -static void BuildFile(const char *filename) +static void VisitFile(std::string_view filename) { - config.total_warnings = 0; - - const size_t num_levels = LevelsInWad(); - - if (num_levels == 0) - { - PrintLine(LOG_NORMAL, "No levels in wad"); - total_empty_files += 1; - return; - } - - size_t visited = 0; - size_t failures = 0; + auto mark = Benchmarker(__func__); + auto in_file = std::string(filename); + auto out_file = std::string(); - build_result_e res = BUILD_OK; - - // loop over each level in the wad - for (size_t n = 0; n < num_levels; n++) + // handle the -o option + // by this point, we are guaranteed to have an opt_output that is different than wad_list[0] + bool is_same_file = false; + if (opt_output.empty()) { - // IMPORTANT: always ensure a valid map - level_t level; - level.level_num = n; - level.level_header_lump_index = cur_wad->LevelHeader(level.level_num); - level.map_format = cur_wad->LevelFormat(level.level_num); - - if (!CheckMapInMapList(level.GetLevelName())) - { - continue; - } - - visited += 1; - - res = BuildLevel(level, filename); - - // handle a failed map (due to lump overflow) - if (res == BUILD_LumpOverflow) - { - res = BUILD_OK; - failures += 1; - continue; - } - - if (res != BUILD_OK) - { - break; - } - - total_built_maps += 1; + is_same_file = true; + out_file = in_file + ".x"; } - - if (visited == 0) + else { - PrintLine(LOG_NORMAL, "No matching levels"); - total_empty_files += 1; - return; + is_same_file = false; + out_file = opt_output; } - total_failed_maps += failures; - - if (failures > 0) + if (config.analysis) { - PrintLine(LOG_NORMAL, "Failed maps: %zu (out of %zu)", failures, visited); - - // allow building other files - total_failed_files += 1; + SetupAnalysisFile(in_file); } - PrintLine(LOG_NORMAL, "Serious warnings: %zu", config.total_warnings); -} - -void ValidateInputFilename(const char *filename) -{ - // NOTE: these checks are case-insensitive - - // files with ".bak" extension cannot be backed up, so refuse them - if (MatchExtension(filename, "bak")) + PrintLine(LOG_NORMAL, "Building %s", in_file.c_str()); { - PrintLine(LOG_ERROR, "ERROR: cannot process a backup file: %s", filename); - } + config.total_warnings = 0; + auto wad = WadIO(in_file, out_file); + size_t current = 0; + size_t max = wad.NumLumps(); - // we do not support packages - if (MatchExtension(filename, "pak") || MatchExtension(filename, "pk2") || MatchExtension(filename, "pk3") - || MatchExtension(filename, "pk4") || MatchExtension(filename, "pk7") || MatchExtension(filename, "epk") - || MatchExtension(filename, "pack") || MatchExtension(filename, "zip") || MatchExtension(filename, "rar")) - { - PrintLine(LOG_ERROR, "ERROR: package files (like PK3) are not supported: %s", filename); - } + size_t levels_visited = 0; + size_t levels_failed = 0; - // reject anything that isn't a WAD, or a UDB temp file - if (!MatchExtension(filename, "wad") && !MatchExtension(filename, "tmp")) - { - PrintLine(LOG_ERROR, "ERROR: not a wad file: %s", filename); - } -} + while (current < max) + { + if (wad.IsLevel(current) && CheckMapInMapList(wad.LumpName(current))) + { + level_t level; + level.lump = current; + level.map_format = wad.LevelFormat(current); + + levels_visited += 1; + auto result = BuildLevel(wad, level, in_file); + + // handle a failed map (due to lump overflow) + if (result == BUILD_LumpOverflow) + { + levels_failed += 1; + } + else + { + total_built_maps += 1; + } + + current = wad.LevelLastLump(current) + 1; + } + // TODO: process glbsp + else + { + wad.CopyLump(current); + current++; + } + } -void BackupFile(const char *filename) -{ - std::string dest_name = filename; + if (levels_visited == 0) + { + PrintLine(LOG_NORMAL, "No matching levels found"); + total_empty_files += 1; + } - // replace file extension (if any) with .bak + total_failed_maps += levels_failed; - size_t ext_pos = FindExtension(filename); - if (ext_pos > 0) - { - dest_name.resize(ext_pos); - } + if (levels_failed > 0) + { + PrintLine(LOG_NORMAL, "Failed maps: %zu (out of %zu)", levels_failed, levels_visited); - dest_name += ".bak"; + // allow building other files + total_failed_files += 1; + } - if (!FileCopy(filename, dest_name.c_str())) - { - PrintLine(LOG_ERROR, "ERROR: failed to create backup: %s", dest_name.c_str()); + PrintLine(LOG_NORMAL, "Serious warnings: %zu", config.total_warnings); } - PrintLine(LOG_NORMAL, "Created backup: %s", dest_name.c_str()); -} - -void VisitFile(const char *filename) -{ - // handle the -o option - if (!opt_output.empty()) + if (is_same_file) { - if (!FileCopy(filename, opt_output.c_str())) + remove(in_file.c_str()); + if (rename(out_file.c_str(), in_file.c_str())) { - PrintLine(LOG_ERROR, "ERROR: failed to create output file: %s", opt_output.c_str()); + throw std::runtime_error("ERROR: Failed to rename output file (" + out_file + ") to input file (" + in_file + ")"); } - - PrintLine(LOG_NORMAL, "Copied input file: %s", filename); - - filename = opt_output.c_str(); - } - - if (config.backup) - { - BackupFile(filename); - } - - if (config.analysis) - { - SetupAnalysisFile(filename); } - - PrintLine(LOG_NORMAL, "Building %s", filename); - - // this will fatal error if it fails - OpenWad(filename); - - BuildFile(filename); - - CloseWad(); } // ----- user information ----------------------------- -bool ValidateMapName(char *name) +static bool ValidateMapName(char *name) { if (strlen(name) < 2 || strlen(name) > 8) { @@ -276,7 +236,7 @@ bool ValidateMapName(char *name) return true; } -void ParseMapRange(char *tok) +static void ParseMapRange(char *tok) { char *low = tok; char *high = tok; @@ -331,7 +291,7 @@ void ParseMapRange(char *tok) map_list.push_back(range); } -void ParseMapList(const char *arg) +static void ParseMapList(const char *arg) { while (*arg != 0) { @@ -365,7 +325,7 @@ void ParseMapList(const char *arg) } } -void ParseShortArgument(const char *arg) +static void ParseShortArgument(const char *arg) { // skip the leading '-' arg++; @@ -386,9 +346,6 @@ void ParseShortArgument(const char *arg) case 'v': config.verbose = true; continue; - case 'b': - config.backup = true; - continue; case 'f': config.fast = true; continue; @@ -437,61 +394,61 @@ void ParseShortArgument(const char *arg) } } -bool ProcessDebugParam(const char *param, uint32_t &debug) +static bool ProcessDebugParam(std::string param, uint32_t &debug) { - if (strcmp(param, "--debug-blockmap") == 0) + if (param == "--debug-blockmap") { debug |= DEBUG_BLOCKMAP; } - else if (strcmp(param, "--debug-reject") == 0) + else if (param == "--debug-reject") { debug |= DEBUG_REJECT; } - else if (strcmp(param, "--debug-load") == 0) + else if (param == "--debug-load") { debug |= DEBUG_LOAD; } - else if (strcmp(param, "--debug-bsp") == 0) + else if (param == "--debug-bsp") { debug |= DEBUG_BSP; } - else if (strcmp(param, "--debug-walltips") == 0) + else if (param == "--debug-walltips") { debug |= DEBUG_WALLTIPS; } - else if (strcmp(param, "--debug-polyobj") == 0) + else if (param == "--debug-polyobj") { debug |= DEBUG_POLYOBJ; } - else if (strcmp(param, "--debug-overlaps") == 0) + else if (param == "--debug-overlaps") { debug |= DEBUG_OVERLAPS; } - else if (strcmp(param, "--debug-picknode") == 0) + else if (param == "--debug-picknode") { debug |= DEBUG_PICKNODE; } - else if (strcmp(param, "--debug-split") == 0) + else if (param == "--debug-split") { debug |= DEBUG_SPLIT; } - else if (strcmp(param, "--debug-cutlist") == 0) + else if (param == "--debug-cutlist") { debug |= DEBUG_CUTLIST; } - else if (strcmp(param, "--debug-builder") == 0) + else if (param == "--debug-builder") { debug |= DEBUG_BUILDER; } - else if (strcmp(param, "--debug-sorter") == 0) + else if (param == "--debug-sorter") { debug |= DEBUG_SORTER; } - else if (strcmp(param, "--debug-subsec") == 0) + else if (param == "--debug-subsec") { debug |= DEBUG_SUBSEC; } - else if (strcmp(param, "--debug-wad") == 0) + else if (param == "--debug-wad") { debug |= DEBUG_WAD; } @@ -499,43 +456,39 @@ bool ProcessDebugParam(const char *param, uint32_t &debug) return debug != 0; } -int32_t ParseLongArgument(const char *name, const int32_t argc, const char *argv[]) +static int32_t ParseLongArgument(std::string name, const int32_t argc, const char *argv[]) { int32_t used = 0; - if (strcmp(name, "--help") == 0) + if (name == "--help") { opt_help = true; } - else if (strcmp(name, "--version") == 0) + else if (name == "--version") { opt_version = true; } - else if (strcmp(name, "--analysis") == 0) + else if (name == "--analysis") { config.analysis = true; } - else if (strcmp(name, "--verbose") == 0) + else if (name == "--verbose") { config.verbose = true; } - else if (strcmp(name, "--backup") == 0 || strcmp(name, "--backups") == 0) - { - config.backup = true; - } - else if (strcmp(name, "--fast") == 0) + else if (name == "--fast") { config.fast = true; } - else if (strcmp(name, "--no-effects") == 0) + else if (name == "--no-effects") { config.effects = false; } - else if (strcmp(name, "--compress") == 0) + else if (name == "--compress") { config.compress = true; } - else if (strcmp(name, "--map") == 0 || strcmp(name, "--maps") == 0) + else if (name == "--map" || name == "--maps") { if (argc < 1 || argv[0][0] == '-') { @@ -546,7 +499,7 @@ int32_t ParseLongArgument(const char *name, const int32_t argc, const char *argv used = 1; } - else if (strcmp(name, "--type") == 0) + else if (name == "--type") { if (argc < 1 || !isdigit(argv[0][0])) { @@ -563,7 +516,7 @@ int32_t ParseLongArgument(const char *name, const int32_t argc, const char *argv config.bsp_format = static_cast(val); used = 1; } - else if (strcmp(name, "--bmap") == 0) + else if (name == "--bmap") { if (argc < 1 || !isdigit(argv[0][0])) { @@ -580,7 +533,7 @@ int32_t ParseLongArgument(const char *name, const int32_t argc, const char *argv config.bmap_format = static_cast(val); used = 1; } - else if (strcmp(name, "--cost") == 0) + else if (name == "--cost") { if (argc < 1 || !isdigit(argv[0][0])) { @@ -597,14 +550,14 @@ int32_t ParseLongArgument(const char *name, const int32_t argc, const char *argv config.split_cost = val; used = 1; } - else if (strcmp(name, "--polyobj") == 0) + else if (name == "--polyobj") { config.polyobj.anchor = Hexen_PolyObj_Anchor; config.polyobj.spawn = Hexen_PolyObj_Spawn; config.polyobj.spawn_crush = Hexen_PolyObj_SpawnCrush; config.polyobj.spawn_hurt = Hexen_PolyObj_SpawnHurt; } - else if (strcmp(name, "--output") == 0) + else if (name == "--output") { // this option is *only* for compatibility @@ -621,16 +574,16 @@ int32_t ParseLongArgument(const char *name, const int32_t argc, const char *argv opt_output = argv[0]; used = 1; } - else if (strncmp(name, "--debug-", 8) == 0) + else if (name.starts_with("--debug-")) { - if (!ProcessDebugParam(name, config.debug)) + if (!ProcessDebugParam(name.c_str(), config.debug)) { - PrintLine(LOG_ERROR, "ERROR: unknown debug parameter '%s'", name); + PrintLine(LOG_ERROR, "ERROR: unknown debug parameter '%s'", name.c_str()); } } else { - PrintLine(LOG_ERROR, "ERROR: unknown long option: '%s'", name); + PrintLine(LOG_ERROR, "ERROR: unknown long option: '%s'", name.c_str()); } return used; @@ -646,19 +599,19 @@ void ParseCommandLine(int32_t argc, const char *argv[]) while (argc > 0) { - const char *arg = *argv++; + std::string arg = *argv++; argc--; if constexpr (MACOS) { // ignore Mac OS X garbage - if (strncmp(arg, "-psn_", 5) == 0) + if (strncmp(arg.data(), "-psn_", 5) == 0) { continue; } } - if (strlen(arg) == 0) + if (arg.empty()) { continue; } @@ -670,38 +623,38 @@ void ParseCommandLine(int32_t argc, const char *argv[]) continue; } - if (strcmp(arg, "-") == 0) + if (arg == "-") { PrintLine(LOG_ERROR, "ERROR: illegal option '-'"); } - if (strcmp(arg, "--") == 0) + if (arg == "--") { rest_are_files = true; continue; } // handle short args which are isolate and require a value - if (strcmp(arg, "-t") == 0) + if (arg == "-t") { arg = "--type"; } - if (strcmp(arg, "-c") == 0) + if (arg == "-c") { arg = "--cost"; } - if (strcmp(arg, "-m") == 0) + if (arg == "-m") { arg = "--map"; } - if (strcmp(arg, "-o") == 0) + if (arg == "-o") { arg = "--output"; } if (arg[1] != '-') { - ParseShortArgument(arg); + ParseShortArgument(arg.c_str()); continue; } @@ -741,30 +694,23 @@ int32_t main(const int32_t argc, const char *argv[]) if (!opt_output.empty()) { - if (config.backup) - { - PrintLine(LOG_ERROR, "ERROR: cannot use --backup with --output"); - } - if (total_files > 1) { PrintLine(LOG_ERROR, "ERROR: cannot use multiple input files with --output"); } - if (StringCaseCmp(wad_list[0], opt_output.c_str()) == 0) + if (wad_list[0] == opt_output) { PrintLine(LOG_ERROR, "ERROR: input and output files are the same"); } } // validate all filenames before processing any of them - for (const auto filename : wad_list) + for (const auto &filename : wad_list) { - ValidateInputFilename(filename); - if (!FileExists(filename)) { - PrintLine(LOG_ERROR, "ERROR: no such file: %s", filename); + throw std::runtime_error("ERROR: no such file: " + filename); } } diff --git a/src/misc.cpp b/src/misc.cpp index 83c28b6..f1f7394 100644 --- a/src/misc.cpp +++ b/src/misc.cpp @@ -323,8 +323,6 @@ vertex_t *NewVertexFromSplitSeg(level_t &level, seg_t *seg, double x, double y) vert->is_new = true; vert->is_used = true; - - vert->index = level.num_new_vert; level.num_new_vert++; // compute wall-tip info diff --git a/src/node.cpp b/src/node.cpp index 76ba87a..9c86051 100644 --- a/src/node.cpp +++ b/src/node.cpp @@ -85,7 +85,7 @@ struct eval_info_t // // Fill in the fields 'angle', 'len', 'pdx', 'pdy', etc... // -void Recompute(seg_t *seg) +static void Recompute(seg_t *seg) { seg->psx = seg->start->x; seg->psy = seg->start->y; @@ -1031,7 +1031,7 @@ void AddMinisegs(level_t &level, intersection_t *cut_list, seg_t *part, seg_t ** // Rewritten by Andrew Apted (-AJA-), 1999-2000. // -void SetPartition(node_t *node, const seg_t *part) +static void SetPartition(node_t *node, const seg_t *part) { SYS_ASSERT(part->linedef); @@ -1064,7 +1064,7 @@ void SetPartition(node_t *node, const seg_t *part) // // Returns -1 for left, +1 for right, or 0 for intersect. // -int PointOnLineSide(const seg_t *seg, double x, double y) +static int PointOnLineSide(const seg_t *seg, double x, double y) { double perp = seg->PerpDist(x, y); @@ -1114,7 +1114,7 @@ quadtree_c::~quadtree_c(void) } } -void AddSeg(quadtree_c *quadtree, seg_t *seg) +static void AddSeg(quadtree_c *quadtree, seg_t *seg) { // update seg counts if (seg->linedef != nullptr) @@ -1169,7 +1169,7 @@ void AddSeg(quadtree_c *quadtree, seg_t *seg) seg->quad = quadtree; } -void AddList(quadtree_c *quadtree, seg_t *new_list) +static void AddList(quadtree_c *quadtree, seg_t *new_list) { while (new_list != nullptr) { @@ -1180,7 +1180,7 @@ void AddList(quadtree_c *quadtree, seg_t *new_list) } } -void ConvertToList(quadtree_c *quadtree, seg_t **_list) +static void ConvertToList(quadtree_c *quadtree, seg_t **_list) { while (quadtree->list != nullptr) { @@ -1358,7 +1358,7 @@ quadtree_c *TreeFromSegList(seg_t *list, const bbox_t *bounds) return tree; } -void DetermineMiddle(subsec_t *subsec) +static void DetermineMiddle(subsec_t *subsec) { subsec->mid_x = 0; subsec->mid_y = 0; @@ -1381,7 +1381,7 @@ void DetermineMiddle(subsec_t *subsec) } } -void AddToTail(subsec_t *subsec, seg_t *seg) +static void AddToTail(subsec_t *subsec, seg_t *seg) { seg->next = nullptr; @@ -1400,7 +1400,7 @@ void AddToTail(subsec_t *subsec, seg_t *seg) tail->next = seg; } -void ClockwiseOrder(subsec_t *subsec) +static void ClockwiseOrder(subsec_t *subsec) { seg_t *seg; @@ -1495,7 +1495,7 @@ void ClockwiseOrder(subsec_t *subsec) } } -void SanityCheckClosed(subsec_t *subsec) +static void SanityCheckClosed(subsec_t *subsec) { int gaps = 0; int total = 0; @@ -1536,7 +1536,7 @@ void SanityCheckClosed(subsec_t *subsec) } } -void SanityCheckHasRealSeg(subsec_t *subsec) +static void SanityCheckHasRealSeg(subsec_t *subsec) { for (seg_t *seg = subsec->seg_list; seg; seg = seg->next) { @@ -1550,7 +1550,7 @@ void SanityCheckHasRealSeg(subsec_t *subsec) subsec->mid_y); } -void RenumberSegs(subsec_t *subsec, size_t &cur_seg_index) +static void RenumberSegs(subsec_t *subsec, size_t &cur_seg_index) { if (HAS_BIT(config.debug, DEBUG_SUBSEC)) { @@ -1710,7 +1710,7 @@ void ClockwiseBspTree(level_t &level) } } -void Normalise(subsec_t *subsec) +static void Normalise(subsec_t *subsec) { // use head + tail to maintain same order of segs seg_t *new_head = nullptr; @@ -1779,22 +1779,7 @@ void NormaliseBspTree(level_t &level) } } -void RoundOffVertices(level_t &level) -{ - for (size_t i = 0; i < level.vertices.size(); i++) - { - vertex_t *vert = level.vertices[i]; - - if (vert->is_new) - { - vert->is_new = false; - vert->index = level.num_old_vert; - level.num_old_vert++; - } - } -} - -void RoundOffSubsector(level_t &level, subsec_t *subsec) +static void RoundOffSubsector(level_t &level, subsec_t *subsec) { // use head + tail to maintain same order of segs seg_t *new_head = nullptr; @@ -1916,8 +1901,6 @@ void RoundOffBspTree(level_t &level) { size_t cur_seg_index = 0; - RoundOffVertices(level); - for (size_t i = 0; i < level.subsecs.size(); i++) { subsec_t *sub = level.subsecs[i]; diff --git a/src/parse.cpp b/src/parse.cpp index f4d6bd8..739ead5 100644 --- a/src/parse.cpp +++ b/src/parse.cpp @@ -20,6 +20,7 @@ //------------------------------------------------------------------------------ #include "core.hpp" +#include "parse.hpp" #include #include diff --git a/src/parse.hpp b/src/parse.hpp new file mode 100644 index 0000000..94d47dc --- /dev/null +++ b/src/parse.hpp @@ -0,0 +1,85 @@ +//------------------------------------------------------------------------------ +// +// ELFBSP -- Lexer (tokenixer) +// +//------------------------------------------------------------------------------ +// +// Copyright 2025-2026 Guilherme Miranda +// Copyright 2022 Andrew Apted, et al +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +//------------------------------------------------------------------------------ + +#pragma once +// #include "core.hpp" + +// +// Parsing +// + +#include +#include + +enum token_kind_e +{ + TOK_EOF = 0, + TOK_ERROR, + + TOK_Ident, + TOK_Symbol, + TOK_Number, + TOK_String +}; + +struct lexer_c +{ + explicit lexer_c(const std::string &_data) : data(_data) + { + } + + ~lexer_c(void) = default; + + // parse the next token, storing contents into given string. + // returns TOK_EOF at the end of the data, and TOK_ERROR when a + // problem is encountered (s will be an error message). + token_kind_e Next(std::string &s); + + // check if the next token is an identifier or symbol matching the + // given string. the match is not case-sensitive. if it matches, + // the token is consumed and true is returned. if not, false is + // returned and the position is unchanged. + bool Match(const char *s); + + // rewind to the very beginning. + void Rewind(void); + + const std::string &data; + + size_t pos = 0; + size_t line = 1; + + void SkipToNext(); + + token_kind_e ParseIdentifier(std::string &s); + token_kind_e ParseNumber(std::string &s); + token_kind_e ParseString(std::string &s); + + void ParseEscape(std::string &s); +}; + +// helpers for converting numeric tokens. +size_t LEX_Index(const std::string &s); +int16_t LEX_Int16(const std::string &s); +int32_t LEX_Int(const std::string &s); +uint32_t LEX_UInt(const std::string &s); +double LEX_Double(const std::string &s); +bool LEX_Boolean(const std::string &s); diff --git a/src/reject.cpp b/src/reject.cpp index 43634b5..74c3f66 100644 --- a/src/reject.cpp +++ b/src/reject.cpp @@ -167,25 +167,19 @@ static void Reject_DebugGroups(level_t &level) } } -static void Reject_WriteLump(level_t &level) -{ - Lump_c *lump = CreateLevelLump(level, "REJECT", level.reject_size); - lump->Write(level.reject_matrix, level.reject_size); - lump->Finish(); -} - // // For now we only do very basic reject processing, limited to // determining all isolated groups of sectors (islands that are // surrounded by void space). // -void PutReject(level_t &level) +void PutReject(WadIO &io, level_t &level) { auto mark = Benchmarker(__func__); + io.StartWritingLump("REJECT"); + + // just leave an empty reject lump if (level.sectors.size() == 0) { - // just create an empty reject lump - CreateLevelLump(level, "REJECT")->Finish(); return; } @@ -193,8 +187,9 @@ void PutReject(level_t &level) Reject_GroupSectors(level); Reject_ProcessSectors(level); Reject_DebugGroups(level); - Reject_WriteLump(level); + io.AddToLump(level.reject_matrix, level.reject_size); Reject_Free(level); + if (config.verbose) { PrintLine(LOG_NORMAL, "Reject size: %zu", level.reject_size); diff --git a/src/wad.cpp b/src/wad.cpp index 49639fa..fa2f3af 100644 --- a/src/wad.cpp +++ b/src/wad.cpp @@ -19,331 +19,225 @@ // //------------------------------------------------------------------------------ +#include "wad.hpp" #include "core.hpp" -#include -#include #include +#include //------------------------------------------------------------------------ -// LUMP Handling +// Utilities //------------------------------------------------------------------------ -Lump_c *MakeLump(Wad_file *wad, const char *lumpname, size_t l_start, size_t l_length) -{ - Lump_c *new_lump = new Lump_c; - new_lump->Rename(lumpname); - new_lump->parent = wad; - new_lump->l_start = l_start; - new_lump->l_length = l_length; - return new_lump; -} +constexpr uint32_t HAS_THINGS = BIT(0); +constexpr uint32_t HAS_LINEDEFS = BIT(1); +constexpr uint32_t HAS_SIDEDEFS = BIT(2); +constexpr uint32_t HAS_VERTEXES = BIT(3); +constexpr uint32_t HAS_SECTORS = BIT(4); -Lump_c *MakeLumpFromEntry(Wad_file *wad, const raw_wad_entry_t *entry) -{ - Lump_c *new_lump = new Lump_c; - - // handle the entry name, which can lack a terminating NUL - char buffer[9]; - strncpy(buffer, entry->name, 8); - buffer[8] = 0; - new_lump->Rename(buffer); - new_lump->parent = wad; - new_lump->l_start = GetLittleEndian(entry->pos); - new_lump->l_length = GetLittleEndian(entry->size); - - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] New lump '%s' @ %zu len:%zu", __func__, new_lump->Name(), new_lump->l_start, new_lump->l_length); - } - return new_lump; -} +constexpr uint32_t HAS_REQUIRED_LUMPS = HAS_THINGS | HAS_LINEDEFS | HAS_SIDEDEFS | HAS_VERTEXES | HAS_SECTORS; -void Lump_c::MakeEntry(raw_wad_entry_t *entry) +static uint32_t WhatLevelPart(const std::string_view name) { - // do a dance to avoid a compiler warning from strncpy(), *sigh* - memset(entry->name, 0, 8); - memcpy(entry->name, lumpname.c_str(), lumpname.size()); + if (name == "THINGS") return HAS_THINGS; + if (name == "LINEDEFS") return HAS_LINEDEFS; + if (name == "SIDEDEFS") return HAS_SIDEDEFS; + if (name == "VERTEXES") return HAS_VERTEXES; + if (name == "SECTORS") return HAS_SECTORS; - entry->pos = GetLittleEndian(IndexToInt(l_start)); - entry->size = GetLittleEndian(IndexToInt(l_length)); + return 0; } +static const std::string_view TEXTMAP = "TEXTMAP"; +static const std::string_view ENDMAP = "ENDMAP"; + //------------------------------------------------------------------------ // WAD Reading Interface //------------------------------------------------------------------------ -Wad_file::Wad_file(const char *_name, char _mode, FILE *_fp) - : mode(_mode), fp(_fp), kind('P'), total_size(0), directory(), dir_start(0), dir_count(0), levels(), patches(), sprites(), - flats(), tx_tex(), begun_write(false), insert_point(NO_INDEX) -{ - // nothing needed -} - -Wad_file::~Wad_file(void) -{ - fclose(fp); - - // free the directory - for (size_t k = 0; k < NumLumps(); k++) - { - delete directory[k]; - } - - directory.clear(); -} - -Wad_file *Wad_file::Open(const char *filename, char mode) +WadIO::WadIO(std::string in_file, std::string out_file) { - SYS_ASSERT(mode == 'r' || mode == 'w' || mode == 'a'); - - if (mode == 'w') + // Input stuff + this->read_file = fopen(in_file.c_str(), "rb"); + if (this->read_file == NULL) { - return Create(filename, mode); + throw std::runtime_error("ERROR: Could not open input file"); } - if (HAS_BIT(config.debug, DEBUG_WAD)) + this->SafeRead(&this->read_header, sizeof(this->read_header)); + if (this->read_header.ident[0] != 'I' // check for IWADs (base game resources) + && this->read_header.ident[0] != 'P' // or PWADs (additional/modded resources) + && this->read_header.ident[1] != 'W' // + && this->read_header.ident[2] != 'A' // + && this->read_header.ident[3] != 'D') { - PrintLine(LOG_DEBUG, "[%s] Opening WAD file: %s", __func__, filename); + fclose(this->read_file); + this->read_file = NULL; + throw std::runtime_error("ERROR: Input file is not a wad"); } - FILE *fp = nullptr; - -retry: - fp = fopen(filename, (mode == 'r' ? "rb" : "r+b")); + auto num_entries = GetLittleEndian(this->read_header.num_entries); + auto dir_start = GetLittleEndian(this->read_header.dir_start); - if (!fp) + if (fseek(this->read_file, dir_start, SEEK_SET)) { - // mimic the fopen() semantics - if (mode == 'a' && errno == ENOENT) - { - return Create(filename, mode); - } - - // if file is read-only, open in 'r' mode instead - if (mode == 'a' && (errno == EACCES || errno == EROFS)) - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] Open r/w failed, trying again in read mode...", __func__); - } - mode = 'r'; - goto retry; - } - - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] Open file failed: %s", __func__, strerror(errno)); - } - return nullptr; + throw std::runtime_error("ERROR: Could not read wad directory"); } - Wad_file *w = new Wad_file(filename, mode, fp); + this->read_directory.resize(num_entries); + this->SafeRead(this->read_directory.data(), this->NumLumps() * sizeof(WadLump)); - // determine total size (seek to end) - if (fseek(fp, 0, SEEK_END) != 0) + for (size_t i = 0; i < this->NumLumps(); ++i) { - PrintLine(LOG_ERROR, "ERROR: Failure determining WAD size."); + this->read_directory[i].pos = GetLittleEndian(this->read_directory[i].pos); + this->read_directory[i].size = GetLittleEndian(this->read_directory[i].size); } - w->total_size = ftell(fp); - - if (HAS_BIT(config.debug, DEBUG_WAD)) + // Output stuff + this->write_file = fopen(out_file.c_str(), "wb"); + if (this->write_file == NULL) { - PrintLine(LOG_DEBUG, "[%s] total_size = %zu", __func__, static_cast(w->total_size)); + throw std::runtime_error("ERROR: Could not open output file"); } - if (w->total_size < 0) - { - PrintLine(LOG_ERROR, "ERROR: Failure determining WAD size."); - } - - w->ReadDirectory(); - w->DetectLevels(); - w->ProcessNamespaces(); + this->write_header.ident[0] = this->read_header.ident[0]; + this->write_header.ident[1] = this->read_header.ident[1]; + this->write_header.ident[2] = this->read_header.ident[2]; + this->write_header.ident[3] = this->read_header.ident[3]; + this->write_header.num_entries = 0; + this->write_header.dir_start = 0; - return w; + this->SafeWrite(&this->write_header, sizeof(this->write_header)); } -Wad_file *Wad_file::Create(const char *filename, char mode) +WadIO::~WadIO(void) { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] Creating new WAD file: %s", __func__, filename); - } + // Input stuff + fclose(this->read_file); + this->read_directory.clear(); - FILE *fp = fopen(filename, "w+b"); - if (!fp) - { - return nullptr; - } + // Output stuff + uint32_t num_entries = GetLittleEndian(IndexToInt(this->write_directory.size())); + uint32_t dir_start = GetLittleEndian(static_cast(ftell(this->write_file))); - Wad_file *w = new Wad_file(filename, mode, fp); + this->SafeWrite(this->write_directory.data(), sizeof(WadLump) * this->write_directory.size()); - // write out base header - raw_wad_header_t header; + fseek(this->write_file, sizeof(char[4]), SEEK_SET); + this->SafeWrite(&num_entries, sizeof(num_entries)); + this->SafeWrite(&dir_start, sizeof(dir_start)); + fclose(this->write_file); - memset(&header, 0, sizeof(header)); - memcpy(header.ident, "PWAD", 4); - - fwrite(&header, sizeof(header), 1, fp); - fflush(fp); - - w->total_size = sizeof(header); - - return w; + this->write_file = NULL; } -static size_t WhatLevelPart(const char *name) +// call only after seeking +void WadIO::SafeRead(void *buffer, size_t size) { - if (StringCaseCmp(name, "THINGS") == 0) - { - return 1; - } - if (StringCaseCmp(name, "LINEDEFS") == 0) - { - return 2; - } - if (StringCaseCmp(name, "SIDEDEFS") == 0) - { - return 3; - } - if (StringCaseCmp(name, "VERTEXES") == 0) + if (fread(buffer, 1, size, this->read_file) != size) { - return 4; + PrintLine(LOG_ERROR, "Failed to read"); } - if (StringCaseCmp(name, "SECTORS") == 0) - { - return 5; - } - - return 0; } -static bool IsLevelLump(const char *name) +size_t WadIO::NumLumps(void) const { - if (StringCaseCmp(name, "SEGS") == 0) - { - return true; - } - if (StringCaseCmp(name, "SSECTORS") == 0) - { - return true; - } - if (StringCaseCmp(name, "NODES") == 0) - { - return true; - } - if (StringCaseCmp(name, "REJECT") == 0) - { - return true; - } - if (StringCaseCmp(name, "BLOCKMAP") == 0) - { - return true; - } - if (StringCaseCmp(name, "BEHAVIOR") == 0) - { - return true; - } - if (StringCaseCmp(name, "SCRIPTS") == 0) - { - return true; - } - if (StringCaseCmp(name, "LEAFS") == 0) - { - return true; - } - if (StringCaseCmp(name, "LIGHTS") == 0) - { - return true; - } - if (StringCaseCmp(name, "MACROS") == 0) - { - return true; - } + return this->read_directory.size(); +} - return WhatLevelPart(name) != 0; +const char *WadIO::LumpName(size_t lump_index) const +{ + SYS_ASSERT(lump_index < this->NumLumps()); + static char name[9]; + strncpy(name, this->read_directory[lump_index].name, 8); + name[8] = 0; + return name; } -Lump_c *Wad_file::GetLump(size_t index) +bool WadIO::IsLevelLump(size_t lump_index) const { - SYS_ASSERT(index < NumLumps()); - SYS_ASSERT(directory[index]); + SYS_ASSERT(lump_index < this->NumLumps()); + const std::string_view name = this->LumpName(lump_index); + if (name == "SEGS") return true; + if (name == "SSECTORS") return true; + if (name == "NODES") return true; + if (name == "REJECT") return true; + if (name == "BLOCKMAP") return true; + if (name == "BEHAVIOR") return true; + if (name == "SCRIPTS") return true; + if (name == "LEAFS") return true; + if (name == "LIGHTS") return true; + if (name == "MACROS") return true; - return directory[index]; + return WhatLevelPart(name) != 0; } -size_t Wad_file::LevelLookupLump(size_t lev_num, const char *name) +bool WadIO::IsLevelTextmapFormat(size_t lump_index) const { - size_t start = LevelHeader(lev_num); - size_t finish = LevelLastLump(lev_num); - - for (size_t k = start + 1; k <= finish; k++) + if (lump_index + LL_TEXTMAP >= this->NumLumps()) { - SYS_ASSERT(k < NumLumps()); - - if (directory[k]->Match(name)) - { - return k; - } + return false; } - return NO_INDEX; // not found + return (TEXTMAP == this->LumpName(lump_index + LL_TEXTMAP)); } -size_t Wad_file::LevelLastLump(size_t lev_num) +bool WadIO::IsLevel(size_t lump_index) const { - size_t start = LevelHeader(lev_num); - size_t count = 1; + SYS_ASSERT(lump_index < this->NumLumps()); - // UDMF level? - if (directory[start + 1]->Match("TEXTMAP")) + // check for UDMF levels + if (this->IsLevelTextmapFormat(lump_index)) { - while (count < MAX_LUMPS_IN_A_LEVEL && start + count < NumLumps()) + if (HAS_BIT(config.debug, DEBUG_WAD)) { - if (directory[start + count]->Match("ENDMAP")) - { - count++; - break; - } - - count++; + PrintLine(LOG_DEBUG, "[%s] Detected level: %s (UDMF) [Index: %zu]", __func__, this->LumpName(lump_index), lump_index); } + return true; } - else // standard DOOM or HEXEN format + + uint32_t required_lumps = 0; + for (size_t i = 1; i < MAX_LUMPS_IN_A_LEVEL; ++i) { - while (count < MAX_LUMPS_IN_A_LEVEL && start + count < NumLumps() && IsLevelLump(directory[start + count]->Name())) + // Haven't found all required lumps yet? Out + if (lump_index + i >= this->NumLumps()) break; + // Not map lump? Also out + if (!this->IsLevelLump(lump_index + i)) break; + // Check if this lump is a required level part + required_lumps |= WhatLevelPart(this->LumpName(lump_index + i)); + } + + // Have found all required lumps? + if (HAS_ALL(required_lumps, HAS_REQUIRED_LUMPS)) + { + if (HAS_BIT(config.debug, DEBUG_WAD)) { - count++; + map_format_t format = this->LevelFormat(lump_index); + PrintLine(LOG_DEBUG, "[%s] Detected level: %s (%s) [Index: %zu]", __func__, this->LumpName(lump_index), + format_name[format].data(), lump_index); } + return true; + } + else + { + return false; } - - return start + count - 1; -} - -size_t Wad_file::LevelHeader(size_t lev_num) -{ - SYS_ASSERT(lev_num < LevelCount()); - - return levels[lev_num]; } -map_format_e Wad_file::LevelFormat(size_t lev_num) +map_format_e WadIO::LevelFormat(size_t lump_index) const { // UDMF maps can contain BEHAVIOR or MACROS // check exclusively TEXTMAP - if (LevelLookupLump(lev_num, "TEXTMAP") != NO_INDEX) + if (this->IsLevelTextmapFormat(lump_index)) { return MapFormat_UDMF; } - if (LevelLookupLump(lev_num, "BEHAVIOR") != NO_INDEX) + if (this->LevelHasLump(lump_index, "BEHAVIOR")) { return MapFormat_Hexen; } - if (LevelLookupLump(lev_num, "LIGHTS") != NO_INDEX && LevelLookupLump(lev_num, "MACROS") != NO_INDEX) + if (this->LevelHasLump(lump_index, "LIGHTS") && this->LevelHasLump(lump_index, "MACROS")) { return MapFormat_Doom64; } @@ -351,692 +245,238 @@ map_format_e Wad_file::LevelFormat(size_t lev_num) return MapFormat_Doom; } -void Wad_file::ReadDirectory(void) +size_t WadIO::LevelLastLump(size_t lump_index) const { - // WISH: no fatal errors - - rewind(fp); - - raw_wad_header_t header; - - if (fread(&header, sizeof(header), 1, fp) != 1) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading WAD header."); - } - - // WISH: check ident for PWAD or IWAD - - kind = header.ident[0]; - - dir_start = GetLittleEndian(header.dir_start); - dir_count = GetLittleEndian(header.num_entries); - - if (dir_count > 32000) - { - PrintLine(LOG_ERROR, "ERROR: Bad WAD header, too many entries (%zu)", dir_count); - } - - if (fseek(fp, static_cast(dir_start), SEEK_SET) != 0) - { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to WAD directory."); - } - - for (size_t i = 0; i < dir_count; i++) - { - raw_wad_entry_t entry; - - if (fread(&entry, sizeof(entry), 1, fp) != 1) - { - PrintLine(LOG_ERROR, "ERROR: Failure reading WAD directory."); - } - - Lump_c *lump = MakeLumpFromEntry(this, &entry); - - // WISH: check if entry is valid - - directory.push_back(lump); - } -} + size_t count = 1; -void Wad_file::DetectLevels(void) -{ - // Determine what lumps in the wad are level markers, based on the - // lumps which follow it. Store the result in the 'levels' vector. - // The test here is rather lax, since wads exist with a non-standard - // ordering of level lumps. - for (size_t k = 0; k + 1 < NumLumps(); k++) + // UDMF level? + if (this->IsLevelTextmapFormat(lump_index)) { - size_t part_mask = 0; - size_t part_count = 0; - - // Ignore non-header map lumps - // Fixes sliding window bug on single-level WADs - if (WhatLevelPart(directory[k]->Name()) != 0) + while (count < MAX_LUMPS_IN_A_LEVEL // + && lump_index + count < this->NumLumps()) // { - continue; - } - - // check for UDMF levels - if (directory[k + 1]->Match("TEXTMAP")) - { - levels.push_back(k); - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] Detected level : %s (UDMF)", __func__, directory[k]->Name()); - } - - continue; - } - - // check whether the next four lumps are level lumps - for (size_t i = 1; i <= 4; i++) - { - if (k + i >= NumLumps()) - { - break; - } - - size_t part = WhatLevelPart(directory[k + i]->Name()); - - if (part == 0) - { - break; - } - - // do not allow duplicates - if (part_mask & (1 << part)) + if (ENDMAP == this->LumpName(lump_index + count)) { + count++; break; } - part_mask |= (1 << part); - part_count++; - } - - if (part_count == 4) - { - levels.push_back(k); - - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] Detected level : %s", __func__, directory[k]->Name()); - } + count++; } } - - // sort levels into alphabetical order - SortLevels(); -} - -void Wad_file::SortLevels(void) -{ - // predicate for sorting the levels[] vector - struct level_name_CMP_pred + else // all other binary formats { - Wad_file *wad; - - level_name_CMP_pred(Wad_file *_w) : wad(_w) + while (count < MAX_LUMPS_IN_A_LEVEL // + && lump_index + count < this->NumLumps() // + && IsLevelLump(lump_index + count)) // { + count++; } - - inline bool operator()(const size_t A, const size_t B) const - { - const Lump_c *L1 = wad->directory[A]; - const Lump_c *L2 = wad->directory[B]; - - return (strcmp(L1->Name(), L2->Name()) < 0); - } - }; - - std::sort(levels.begin(), levels.end(), level_name_CMP_pred(this)); -} - -static bool IsDummyMarker(const char *name) -{ - // matches P1_START, F3_END etc... - if (strlen(name) < 3) - { - return false; } - if (!strchr("PSF", toupper(name[0]))) - { - return false; - } - - if (!isdigit(name[1])) - { - return false; - } - - if (StringCaseCmp(name + 2, "_START") == 0 || StringCaseCmp(name + 2, "_END") == 0) - { - return true; - } - - return false; + return lump_index + count - 1; } -void Wad_file::ProcessNamespaces(void) +// returns a lump index, NO_INDEX if not found +size_t WadIO::LevelLookupLump(size_t lump_index, std::string_view name) const { - char active = 0; + size_t finish = this->LevelLastLump(lump_index); - for (size_t k = 0; k < NumLumps(); k++) + for (size_t k = lump_index + 1; k <= finish; k++) { - const char *name = directory[k]->Name(); - - // skip the sub-namespace markers - if (IsDummyMarker(name)) - { - continue; - } - - if (StringCaseCmp(name, "P_START") == 0 || StringCaseCmp(name, "PP_START") == 0) - { - if (active && active != 'P') - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] Missing %c_END marker.", __func__, active); - } - } - - active = 'P'; - continue; - } - else if (StringCaseCmp(name, "P_END") == 0 || StringCaseCmp(name, "PP_END") == 0) - { - if (active != 'P') - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] Stray P_END marker found.", __func__); - } - } - - active = 0; - continue; - } - - if (StringCaseCmp(name, "S_START") == 0 || StringCaseCmp(name, "SS_START") == 0) - { - if (active && active != 'S') - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] Missing %c_END marker.", __func__, active); - } - } + SYS_ASSERT(k < this->NumLumps()); - active = 'S'; - continue; - } - else if (StringCaseCmp(name, "S_END") == 0 || StringCaseCmp(name, "SS_END") == 0) + if (name == this->LumpName(k)) { - if (active != 'S') - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] stray S_END marker found.", __func__); - } - } - - active = 0; - continue; - } - - if (StringCaseCmp(name, "F_START") == 0 || StringCaseCmp(name, "FF_START") == 0) - { - if (active && active != 'F') - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] missing %c_END marker.", __func__, active); - } - } - - active = 'F'; - continue; - } - else if (StringCaseCmp(name, "F_END") == 0 || StringCaseCmp(name, "FF_END") == 0) - { - if (active != 'F') - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] stray F_END marker found.", __func__); - } - } - - active = 0; - continue; - } - - if (StringCaseCmp(name, "TX_START") == 0) - { - if (active && active != 'T') - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] missing %c_END marker.", __func__, active); - } - } - - active = 'T'; - continue; - } - else if (StringCaseCmp(name, "TX_END") == 0) - { - if (active != 'T') - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] stray TX_END marker found.", __func__); - } - } - - active = 0; - continue; - } - - if (active) - { - if (directory[k]->Length() == 0) - { - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] skipping empty lump %s in %c_START", __func__, name, active); - } - continue; - } - - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] Namespace %c lump : %s", __func__, active, name); - } - - switch (active) - { - case 'P': - patches.push_back(k); - break; - case 'S': - sprites.push_back(k); - break; - case 'F': - flats.push_back(k); - break; - case 'T': - tx_tex.push_back(k); - break; - - default: - PrintLine(LOG_ERROR, "ERROR: ProcessNamespaces: active = 0x%02x", active); - } + return k; } } - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - if (active) - { - PrintLine(LOG_DEBUG, "[%s] Missing %c_END marker (at EOF)", __func__, active); - } - } + return NO_INDEX; // not found } -//------------------------------------------------------------------------ -// WAD Writing Interface -//------------------------------------------------------------------------ - -void Wad_file::BeginWrite(void) +bool WadIO::LevelHasLump(size_t lump_index, std::string_view name) const { - if (mode == 'r') - { - PrintLine(LOG_ERROR, "ERROR: Wad_file::BeginWrite() called on read-only file"); - } - - if (begun_write) - { - PrintLine(LOG_ERROR, "ERROR: Wad_file::BeginWrite() called again without EndWrite()"); - } - - // put the size into a quantum state - total_size = 0; - begun_write = true; + return this->LevelLookupLump(lump_index, name) != NO_INDEX; } -void Wad_file::EndWrite(void) +// +// Write operations +// +void WadIO::SafeWrite(const void *buffer, size_t size) { - if (!begun_write) + if (fwrite(buffer, 1, size, write_file) != size) { - PrintLine(LOG_ERROR, "ERROR: Wad_file::EndWrite() called without BeginWrite()"); + fclose(this->write_file); + this->write_file = NULL; + throw std::runtime_error( + "ERROR: Failed to write. Check that this directory is writable and that you have enough free disk space."); } - - begun_write = false; - - WriteDirectory(); - - // reset the insertion point - insert_point = NO_INDEX; } -void Wad_file::FixGroup(std::vector &group, size_t index, size_t num_added, size_t num_removed) +void WadIO::CreateEmptyLump(std::string_view name) { - bool did_remove = false; - - for (size_t k = 0; k < group.size(); k++) - { - if (group[k] < index) - { - continue; - } - - if (group[k] < index + num_removed) - { - group[k] = NO_INDEX; - did_remove = true; - continue; - } - - group[k] += num_added; - group[k] -= num_removed; - } - - if (did_remove) - { - std::vector::iterator ENDP; - ENDP = std::remove(group.begin(), group.end(), -1); - group.erase(ENDP, group.end()); - } + WadLump lump; + strncpy(lump.name, name.data(), 8); + lump.pos = GetLittleEndian(static_cast(ftell(this->write_file))); + lump.size = 0; + this->write_directory.push_back(lump); } -Lump_c *Wad_file::AddLump(const char *name, size_t max_size) +void WadIO::WriteLump(std::string_view name, const void *data, size_t size) { - SYS_ASSERT(begun_write); - - begun_max_size = max_size; - - size_t start = PositionForWrite(max_size); - - Lump_c *lump = MakeLump(this, name, start, 0); - - // check if the insert_point is still valid - if (insert_point >= NumLumps()) - { - insert_point = NO_INDEX; - } - - if (insert_point != NO_INDEX) - { - // fix various arrays containing lump indices - FixGroup(levels, insert_point, 1, 0); - FixGroup(patches, insert_point, 1, 0); - FixGroup(sprites, insert_point, 1, 0); - FixGroup(flats, insert_point, 1, 0); - FixGroup(tx_tex, insert_point, 1, 0); - - directory.insert(directory.begin() + static_cast(insert_point), lump); + WadLump lump; + strncpy(lump.name, name.data(), 8); + lump.pos = GetLittleEndian(static_cast(ftell(this->write_file))); + lump.size = IndexToInt(size); + this->write_directory.push_back(lump); + this->SafeWrite(data, size); +} - insert_point++; - } - else // add to end +void WadIO::CopyLump(size_t index) +{ + auto lump = this->ReadLump(index); + if (index < this->NumLumps()) { - directory.push_back(lump); + this->WriteLump(this->LumpName(index), lump.data(), lump.size()); } - - return lump; } -void Wad_file::RecreateLump(Lump_c *lump, size_t max_size) +void WadIO::LevelCopyLump(size_t lump, std::string_view name) { - SYS_ASSERT(begun_write); - - begun_max_size = max_size; + this->CopyLump(this->LevelLookupLump(lump, name)); +} - size_t start = PositionForWrite(max_size); +// +// Partial writing +// - lump->l_start = start; - lump->l_length = 0; +void WadIO::StartWritingLump(std::string_view name) +{ + this->CreateEmptyLump(name); } -void Wad_file::InsertPoint(size_t index) +void WadIO::AddToLump(const void *data, size_t len) { - // this is validated on usage - insert_point = index; + size_t current = this->write_directory.size() - 1; + this->SafeWrite(data, len); + this->write_directory[current].size += len; } -size_t Wad_file::HighWaterMark(void) +WadIO &WadIO::operator<<(uint8_t value) { - size_t offset = sizeof(raw_wad_header_t); - - for (size_t k = 0; k < NumLumps(); k++) - { - Lump_c *lump = directory[k]; - - // ignore zero-length lumps (their offset could be anything) - if (lump->Length() <= 0) - { - continue; - } - - size_t l_end = lump->l_start + lump->l_length; - - l_end = ((l_end + 3) / 4) * 4; - - if (offset < l_end) - { - offset = l_end; - } - } - - return offset; + this->AddToLumpZ(&value, sizeof(uint8_t)); + return *this; } -size_t Wad_file::FindFreeSpace(size_t length) +WadIO &WadIO::operator<<(uint16_t value) { - length = ((length + 3) / 4) * 4; - - // collect non-zero length lumps and sort by their offset - std::vector sorted_dir; - - for (size_t k = 0; k < NumLumps(); k++) - { - Lump_c *lump = directory[k]; + value = GetLittleEndian(value); + this->AddToLumpZ(reinterpret_cast(&value), sizeof(uint16_t)); + return *this; +} - if (lump->Length() > 0) - { - sorted_dir.push_back(lump); - } - } +WadIO &WadIO::operator<<(uint32_t value) +{ + value = GetLittleEndian(value); + this->AddToLumpZ(reinterpret_cast(&value), sizeof(uint32_t)); + return *this; +} - struct offset_CMP_pred - { - inline bool operator()(const Lump_c *A, const Lump_c *B) const - { - return A->l_start < B->l_start; - } - }; +WadIO &WadIO::operator<<(int16_t value) +{ + value = GetLittleEndian(value); + this->AddToLumpZ(reinterpret_cast(&value), sizeof(int16_t)); + return *this; +} - std::sort(sorted_dir.begin(), sorted_dir.end(), offset_CMP_pred()); +WadIO &WadIO::operator<<(fixed_t value) +{ + value = GetLittleEndian(value); + this->AddToLumpZ(reinterpret_cast(&value), sizeof(fixed_t)); + return *this; +} - size_t offset = sizeof(raw_wad_header_t); +// +// Zlib compression support +// - for (size_t k = 0; k < sorted_dir.size(); k++) +void WadIO::Begin_Zlib(void) +{ + this->zlib_init = true; + this->zout_stream.zalloc = nullptr; + this->zout_stream.zfree = nullptr; + this->zout_stream.opaque = nullptr; + if (Z_OK != zng_deflateInit(&this->zout_stream, Z_DEFAULT_COMPRESSION)) { - Lump_c *lump = sorted_dir[k]; - - size_t l_start = lump->l_start; - size_t l_end = lump->l_start + lump->l_length; - - l_end = ((l_end + 3) / 4) * 4; - - if (l_end <= offset) - { - continue; - } - - if (l_start >= offset + length) - { - continue; - } - - // the lump overlapped the current gap, so bump offset - - offset = l_end; + PrintLine(LOG_ERROR, "ERROR: Trouble starting Zlib compression."); } - - return offset; + this->zout_stream.next_out = this->zout_buffer; + this->zout_stream.avail_out = sizeof(this->zout_buffer); } -size_t Wad_file::PositionForWrite(size_t max_size) +void WadIO::AddToLumpZ(const void *data, size_t length) { - int64_t want_pos = static_cast(max_size == NO_INDEX ? HighWaterMark() : FindFreeSpace(max_size)); - - // determine if position is past end of file - // (difference should only be a few bytes) - // - // Note: doing this for every new lump may be a little expensive, - // but trying to optimise it away will just make the code - // needlessly complex and hard to follow. - - if (fseek(fp, 0, SEEK_END) < 0) + if (!this->zlib_init) { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to new write position."); + this->AddToLump(data, length); + return; } - total_size = ftell(fp); + SYS_ASSERT(length > 0); - if (total_size < 0) - { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to new write position."); - } - - if (want_pos > total_size) - { - SYS_ASSERT(want_pos < total_size + 8); + this->zout_stream.next_in = static_cast(data); + this->zout_stream.avail_in = IndexToInt(length); - WritePadding(static_cast(want_pos - total_size)); - } - else if (want_pos == total_size) - { - /* ready to write */ - } - else + while (this->zout_stream.avail_in > 0) { - if (fseek(fp, want_pos, SEEK_SET) < 0) + if (Z_OK != zng_deflate(&this->zout_stream, Z_NO_FLUSH)) { - PrintLine(LOG_ERROR, "ERROR: Failure seeking to new write position."); + PrintLine(LOG_ERROR, "ERROR: Trouble Zlib compressing %zu bytes.", length); } - } - - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] POSITION FOR WRITE: %zu (total_size %zu)", __func__, static_cast(want_pos), - static_cast(total_size)); - } - - return static_cast(want_pos); -} -void Wad_file::FinishLump(size_t final_size) -{ - fflush(fp); - - // sanity check - if (final_size > begun_max_size) - { - PrintLine(LOG_ERROR, "ERROR: wrote too much in lump (%zu > %zu)", final_size, begun_max_size); - } - - int64_t pos = ftell(fp); + if (this->zout_stream.avail_out == 0) + { + this->AddToLump(this->zout_buffer, sizeof(this->zout_buffer)); - if (pos & 3) - { - WritePadding(4 - (pos & 3)); + this->zout_stream.next_out = this->zout_buffer; + this->zout_stream.avail_out = sizeof(this->zout_buffer); + } } - - fflush(fp); -} - -size_t Wad_file::WritePadding(size_t count) -{ - static byte zeros[8] = {0, 0, 0, 0, 0, 0, 0, 0}; - - SYS_ASSERT(1 <= count && count <= 8); - - fwrite(zeros, count, 1, fp); - - return count; } -// -// IDEA : Truncate file to "total_size" after writing the directory. -// -// On Linux / MacOSX, this can be done as follows: -// - fflush(fp) -- ensure STDIO has empty buffers -// - ftruncate(fileno(fp), total_size); -// - freopen(fp) -// -// On Windows: -// - instead of ftruncate, use _chsize() or _chsize_s() -// [ investigate what the difference is.... ] -// - -void Wad_file::WriteDirectory(void) +void WadIO::Finish_Zlib(void) { - dir_start = PositionForWrite(); - dir_count = NumLumps(); + SYS_ASSERT(this->zout_stream.avail_out > 0) + size_t left_over; + this->zlib_init = false; - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] dir_start:%zu dir_count:%zu", __func__, dir_start, dir_count); - } + this->zout_stream.next_in = Z_NULL; + this->zout_stream.avail_in = 0; - for (size_t k = 0; k < dir_count; k++) + while (true) { - Lump_c *lump = directory[k]; - SYS_ASSERT(lump); + int32_t err = zng_deflate(&this->zout_stream, Z_FINISH); - raw_wad_entry_t entry; + if (err == Z_STREAM_END) break; - lump->MakeEntry(&entry); - - if (fwrite(&entry, sizeof(entry), 1, fp) != 1) + if (err != Z_OK) { - PrintLine(LOG_ERROR, "ERROR: Failure writing WAD directory."); + PrintLine(LOG_ERROR, "ERROR: Trouble finishing Zlib compression."); } - } - - fflush(fp); - total_size = ftell(fp); - - if (HAS_BIT(config.debug, DEBUG_WAD)) - { - PrintLine(LOG_DEBUG, "[%s] total_size: %zu", __func__, static_cast(total_size)); - } + if (this->zout_stream.avail_out == 0) + { + this->AddToLump(&this->zout_buffer, sizeof(this->zout_buffer)); - if (total_size < 0) - { - PrintLine(LOG_ERROR, "ERROR: Failure determining WAD size."); + this->zout_stream.next_out = this->zout_buffer; + this->zout_stream.avail_out = sizeof(this->zout_buffer); + } } - // update header at start of file + left_over = sizeof(zout_buffer) - zout_stream.avail_out; - rewind(fp); - - raw_wad_header_t header; - - memcpy(header.ident, (kind == 'I') ? "IWAD" : "PWAD", 4); - - header.dir_start = GetLittleEndian(IndexToInt(dir_start)); - header.num_entries = GetLittleEndian(IndexToInt(dir_count)); - - if (fwrite(&header, sizeof(header), 1, fp) != 1) - { - PrintLine(LOG_ERROR, "ERROR: Failure writing WAD header."); - } + if (left_over > 0) this->AddToLump(zout_buffer, left_over); - fflush(fp); + zng_deflateEnd(&zout_stream); } diff --git a/src/wad.hpp b/src/wad.hpp new file mode 100644 index 0000000..a8fdecd --- /dev/null +++ b/src/wad.hpp @@ -0,0 +1,141 @@ +//------------------------------------------------------------------------------ +// +// ELFBSP -- WAD Reading / Writing +// +//------------------------------------------------------------------------------ +// +// Copyright 2025-2026 Guilherme Miranda +// Copyright 2001-2018 Andrew Apted +// Copyright 2002-2006 Marisa Heit +// +// This program is free software; you can redistribute it and/or +// modify it under the terms of the GNU General Public License +// as published by the Free Software Foundation; either version 2 +// of the License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +//------------------------------------------------------------------------------ + +#pragma once + +#include "core.hpp" +#include + +//------------------------------------------------------------------------ +// WAD STRUCTURES +//------------------------------------------------------------------------ + +// wad header +using WadHeader = struct WadHeader +{ + char ident[4]; + uint32_t num_entries; + uint32_t dir_start; +} PACKEDATTR; + +// directory entry +using WadLump = struct WadLump +{ + uint32_t pos; + uint32_t size; + char name[8]; +} PACKEDATTR; + +static_size(WadHeader, 12); +static_size(WadLump, 16); + +// +// File handling +// + +struct WadIO +{ + // Read data + FILE *read_file; + WadHeader read_header; + std::vector read_directory; + + // Write data + FILE *write_file; + WadHeader write_header; + std::vector write_directory; + bool zlib_init = false; + zng_stream zout_stream; + byte zout_buffer[8192]; + + // Init/die + WadIO(std::string in_file, std::string out_file); + ~WadIO(void); + + // Read operations + void SafeRead(void *buffer, size_t size); + size_t NumLumps(void) const; + const char *LumpName(size_t lump_index) const; + bool IsLevelLump(size_t lump_index) const; + bool IsLevelTextmapFormat(size_t lump_index) const; + bool IsLevel(size_t lump_index) const; + map_format_t LevelFormat(size_t lump_index) const; + size_t LevelLastLump(size_t lump_index) const; + size_t LevelLookupLump(size_t lump_index, std::string_view name) const; + bool LevelHasLump(size_t lump_index, std::string_view name) const; + template + std::vector ReadLump(size_t lump_index); + template + std::vector ReadLevelLump(size_t lump_index, std::string_view name); + + // Write operations + void SafeWrite(const void *buffer, size_t size); + void CreateEmptyLump(std::string_view name); + void WriteLump(std::string_view name, const void *data, size_t size); + void CopyLump(size_t lump); + void LevelCopyLump(size_t lump, std::string_view name); + + // Partial writing + void StartWritingLump(std::string_view name); + void AddToLump(const void *data, size_t len); + WadIO &operator<<(uint8_t value); + WadIO &operator<<(uint16_t value); + WadIO &operator<<(uint32_t value); + WadIO &operator<<(int16_t value); + WadIO &operator<<(fixed_t value); + + // Zlib compression support + void Begin_Zlib(void); + void AddToLumpZ(const void *data, size_t length); + void Finish_Zlib(void); +}; + +// +// fuck C++ start +// +template +std::vector WadIO::ReadLump(size_t lump_index) +{ + std::vector lump; + if (lump_index >= this->read_directory.size()) + { + return lump; + } + if (fseek(this->read_file, this->read_directory[lump_index].pos, SEEK_SET)) + { + throw std::runtime_error("ERROR: Failed to seek"); + } + size_t size = this->read_directory[lump_index].size / sizeof(T); + lump.resize(size); + this->SafeRead(lump.data(), size * sizeof(T)); + return lump; +} + +template +std::vector WadIO::ReadLevelLump(size_t lump_index, std::string_view name) +{ + return ReadLump(this->LevelLookupLump(lump_index, name)); +} + +// +// fuck C++ end +//