diff --git a/bsputil/bsputil.cc b/bsputil/bsputil.cc index 240a536a2..87914005c 100644 --- a/bsputil/bsputil.cc +++ b/bsputil/bsputil.cc @@ -54,6 +54,22 @@ typedef struct { char name[16]; // must be null terminated } lumpinfo_t; +static size_t +CalcMipSize(size_t i, mbsp_t *bsp) +{ + size_t start = 0, end = bsp->texdatasize; + auto texdata = bsp->dtexdata; + + start = texdata->dataofs[i]; + while (++i < texdata->nummiptex) + { + if (texdata->dataofs[i] < 0) + continue; + end = texdata->dataofs[i]; + break; + } + return end - start; +} static void ExportWad(FILE *wadfile, mbsp_t *bsp) { @@ -62,6 +78,7 @@ ExportWad(FILE *wadfile, mbsp_t *bsp) dmiptexlump_t *texdata; miptex_t *miptex; int i, j, size, filepos, numvalid; + size_t mipsize; texdata = bsp->dtexdata; @@ -78,7 +95,8 @@ ExportWad(FILE *wadfile, mbsp_t *bsp) /* Byte-swap header and write out */ header.numlumps = LittleLong(header.numlumps); header.infotableofs = LittleLong(header.infotableofs); - fwrite(&header, sizeof(header), 1, wadfile); + if (wadfile) + fwrite(&header, sizeof(header), 1, wadfile); /* Miptex data will follow the lump headers */ filepos = sizeof(header) + numvalid * sizeof(lump); @@ -89,7 +107,7 @@ ExportWad(FILE *wadfile, mbsp_t *bsp) miptex = (miptex_t *)((uint8_t *)texdata + texdata->dataofs[i]); lump.filepos = filepos; - lump.size = sizeof(*miptex) + miptex->width * miptex->height / 64 * 85; + lump.size = CalcMipSize(i, bsp); lump.type = 'D'; lump.disksize = lump.size; lump.compression = 0; @@ -102,20 +120,32 @@ ExportWad(FILE *wadfile, mbsp_t *bsp) lump.filepos = LittleLong(lump.filepos); lump.disksize = LittleLong(lump.disksize); lump.size = LittleLong(lump.size); - fwrite(&lump, sizeof(lump), 1, wadfile); + + if (wadfile) { + fwrite(&lump, sizeof(lump), 1, wadfile); + } } for (i = 0; i < texdata->nummiptex; i++) { if (texdata->dataofs[i] < 0) continue; miptex = (miptex_t *)((uint8_t *)texdata + texdata->dataofs[i]); - size = sizeof(*miptex) + miptex->width * miptex->height / 64 * 85; + size = CalcMipSize(i, bsp); /* Byte-swap miptex info and write out */ miptex->width = LittleLong(miptex->width); miptex->height = LittleLong(miptex->height); for (j = 0; j < MIPLEVELS; j++) miptex->offsets[j] = LittleLong(miptex->offsets[j]); - fwrite(miptex, size, 1, wadfile); + if (wadfile) { + fwrite(miptex, size, 1, wadfile); + } else { + auto fname = std::string(miptex->name)+".mip"; + FILE *mipfile = fopen(fname.c_str(), "wb"); + if (mipfile) { + fwrite(miptex, size, 1, mipfile); + fclose(mipfile); + } + } } } @@ -516,6 +546,27 @@ FindFaces(const mbsp_t *bsp, const vec3_t pos, const vec3_t normal) } } +void Util_PrintUsage(void) +{ + printf("usage: bsputil <--command [args]> \n\n"); + + printf( " --extract-entities\n" " Extract the bsp's entity data as an .ent file\n" +// " --replace-entities entfile\n" " Replaces the bsp's entity data with the specified .ent file\n" + " --extract-textures\n" " Extract the bsp's texture data as an .wad file\n" +// " --replace-texture mipfile texname\n" " Inject the specified .mip file into the bsp\n" +// " --strip-textures\n" " Remove all mip data for copyright compliance.\n" + " --convert bsp29|bsp2|bsp2rmq|q2bsp\n" " Convert between bsp formats\n" + " --check\n" " Verifies indexes and offsets within the bsp.\n" + " --modelinfo\n" " Prints some basic info about inline models.\n" + " --compare otherbsp\n" " List any faces in one bsp not also in the other\n" + " --findfaces x y z nx ny nz\n" " Lists surfaces matching the point+normal\n" + " --settexinfo facenum texinfonum\n" " Change the texinfo entry for a specific surface.\n" + " --decompile\n" " Decompile to .map\n" + " --decompile-geomonly\n" " Decompile bsp tree without regard for actual faces\n" + " --embed file1 [file2 ...]\n" " Embed a file within the bsp. The name will be embedded directly.\n" + " --deembed file1 [file2 ...]\n" " Remove an embedded file by name.\n" + ); +} int main(int argc, char **argv) @@ -528,9 +579,7 @@ main(int argc, char **argv) printf("---- bsputil / ericw-tools " stringify(ERICWTOOLS_VERSION) " ----\n"); if (argc == 1) { - printf("usage: bsputil [--extract-entities] [--extract-textures] [--convert bsp29|bsp2|bsp2rmq|q2bsp] [--check] [--modelinfo]\n" - "[--check] [--compare otherbsp] [--findfaces x y z nx ny nz] [--settexinfo facenum texinfonum]\n" - "[--decompile] [--decompile-geomonly] bspfile\n"); + Util_PrintUsage(); exit(1); } @@ -571,13 +620,25 @@ main(int argc, char **argv) } int fmt; - if (!strcmp(argv[i], "bsp29")) { + const char *targ = argv[i]; + if (!strncmp(targ, "quake_", 6)) + { + bspdata.hullcount = MAX_MAP_HULLS_Q1; + targ += 6; + } + else if (!strncmp(targ, "hexen2_", 7)) + { + bspdata.hullcount = MAX_MAP_HULLS_H2; + targ += 7; + } + + if (!strcmp(targ, "bsp29")) { fmt = BSPVERSION; - } else if (!strcmp(argv[i], "bsp2")) { + } else if (!strcmp(targ, "bsp2")) { fmt = BSP2VERSION; - } else if (!strcmp(argv[i], "bsp2rmq")) { + } else if (!strcmp(targ, "bsp2rmq")) { fmt = BSP2RMQVERSION; - } else if (!strcmp(argv[i], "q2bsp")) { + } else if (!strcmp(targ, "q2bsp")) { fmt = Q2_BSPVERSION; } else { Error("Unsupported format %s", argv[i]); @@ -609,6 +670,53 @@ main(int argc, char **argv) if (err) Error("%s", strerror(errno)); + printf("done.\n"); + } else if (!strcmp(argv[i], "--deembed")) { + Zip_StartUpdate(&bspdata); + + if (i == argc-2) + ZipRepack_RemoveFile(NULL); + else for (; i < argc - 1; i++) + { + int n=i+1; + if (argv[n][0] == '-' && argv[n][1] == '-') + { + if (!argv[n][2]) + i=n; + break; + } + i=n; + ZipRepack_RemoveFile(argv[n]); + } + Zip_FinishUpdate(&bspdata); + + ConvertBSPFormat(bspdata.loadversion, &bspdata); + WriteBSPFile(source, &bspdata); + } else if (!strcmp(argv[i], "--embed")) { + Zip_StartUpdate(&bspdata); + + for (; i < argc - 1; i++) + { + int n=i+1; + if (argv[n][0] == '-' && argv[n][1] == '-') + { + if (!argv[n][2]) + i=n; + break; + } + i=n; + + void *file_data; + uint32_t flen = LoadFilePak(argv[i], &file_data); + ZipRepack_AddFile(argv[i], file_data, flen); + } + Zip_FinishUpdate(&bspdata); + + ConvertBSPFormat(bspdata.loadversion, &bspdata); + WriteBSPFile(source, &bspdata); + } else if (!strcmp(argv[i], "--extract-mips")) { + ExportWad(NULL, bsp); + printf("done.\n"); } else if (!strcmp(argv[i], "--extract-textures")) { StripExtension(source); @@ -633,6 +741,8 @@ main(int argc, char **argv) printf("Done.\n"); } else if (!strcmp(argv[i], "--modelinfo")) { PrintModelInfo(bsp); + } else if (!strcmp(argv[i], "--info")) { + PrintBSPFileSizes(&bspdata); } else if (!strcmp(argv[i], "--findfaces")) { // (i + 1) ... (i + 6) = x y z nx ny nz // i + 7 = bsp file diff --git a/common/bspfile.cc b/common/bspfile.cc index def143c83..9c904266d 100644 --- a/common/bspfile.cc +++ b/common/bspfile.cc @@ -2315,6 +2315,12 @@ LoadBSPFile(char *filename, bspdata_t *bspdata) for (i = 0; i < numlumps; i++) { lumps[i].fileofs = LittleLong(lumps[i].fileofs); lumps[i].filelen = LittleLong(lumps[i].filelen); + + if (lumps[i].fileofs + lumps[i].filelen > flen) + { + logprint("lump %i extends %i bytes beyond end of file (truncated bsp?)\n", i, lumps[i].fileofs+lumps[i].filelen-flen); + lumps[i].filelen = lumps[i].fileofs = 0; + } } if (isHexen2((dheader_t *)file_data)) @@ -2456,6 +2462,9 @@ LoadBSPFile(char *filename, bspdata_t *bspdata) void *lumpdata = malloc(len); memcpy(lumpdata, (const uint8_t*)header + ofs, len); BSPX_AddLump(bspdata, xlump[xlumps].lumpname, lumpdata, len); + + if (bspxofs < ofs+len) + bspxofs = ofs+len; } } else @@ -2464,6 +2473,15 @@ LoadBSPFile(char *filename, bspdata_t *bspdata) printf("invalid bspx header\n"); } } + + bspdata->zipsize = flen - bspxofs; + if (bspdata->zipsize>3) + { + bspdata->zip = malloc(bspdata->zipsize); + memcpy(bspdata->zip, (const uint8_t*)header + bspxofs, bspdata->zipsize); + } + else + bspdata->zipsize = 0; } /* everything has been copied out */ @@ -2710,10 +2728,15 @@ WriteBSPFile(const char *filename, bspdata_t *bspdata) SafeWrite(bspfile.file, pad, 4 - (x->lumpsize % 4)); } + if (bspdata->zipsize) + SafeWrite(bspfile.file, bspdata->zip, bspdata->zipsize); + fseek(bspfile.file, bspxheader, SEEK_SET); SafeWrite(bspfile.file, &xheader, sizeof(xheader)); SafeWrite(bspfile.file, xlumps, xheader.numlumps * sizeof(xlumps[0])); } + else if (bspdata->zipsize) + SafeWrite(bspfile.file, bspdata->zip, bspdata->zipsize); fseek(bspfile.file, 0, SEEK_SET); @@ -2727,8 +2750,315 @@ WriteBSPFile(const char *filename, bspdata_t *bspdata) fclose(bspfile.file); } +/* ========================================================================= */ + +static uint32_t ReadRawInt(const uint8_t *blob) +{ + return (blob[0]<<0) | (blob[1]<<8) | (blob[2]<<16) | (blob[3]<<24); +} +static uint16_t ReadRawShort(const uint8_t *blob) +{ + return (blob[0]<<0) | (blob[1]<<8); +} +static int EnumerateFilesFromPackageBlob(const uint8_t *blob, size_t blobsize, void (*cb)(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize)) +{ + unsigned int cdentries; + unsigned int cdlen; + const uint8_t *eocd; + const uint8_t *cd; + int nl,el,cl; + int ret = 0; + const unsigned char *le; + unsigned int csize, usize, method; + char name[1024]; + + if (blobsize >= 8 && !strncmp((const char*)blob, "PACK", 4)) + { + uint32_t ofs = ReadRawInt(blob+4); + uint32_t count = ReadRawInt(blob+8) / 64; + for (ret = 0; ret < count; ret++, ofs+=64) + { + uint32_t fofs = ReadRawInt(blob+ofs+56); + uint32_t flen = ReadRawInt(blob+ofs+60); + cb((const char*)blob+ofs, (const char*)blob+fofs, flen, 0, flen); + } + return ret; + } + + if (blobsize < 22) + return ret; + + //treat it as a zip + //FIXME: we don't allow for zip comments here. + eocd = blob; + eocd += blobsize-22; + if (ReadRawInt(eocd+0) != 0x06054b50) + return ret; + if (ReadRawShort(eocd+4) || ReadRawShort(eocd+6) || ReadRawShort(eocd+20) || ReadRawShort(eocd+8) != ReadRawShort(eocd+10)) + return ret; + cd = blob; + cd += ReadRawInt(eocd+16); + cdlen = ReadRawInt(eocd+12); + cdentries = ReadRawShort(eocd+10); + if (cd+cdlen>=(const uint8_t*)blob+blobsize) + return ret; + + for(; cdentries --> 0; cd += 46 + nl+el+cl) + { + if (ReadRawInt(cd+0) != 0x02014b50) + break; + nl = ReadRawShort(cd+28); + el = ReadRawShort(cd+30); + cl = ReadRawShort(cd+32); + + //1=encrypted + //2,4=encoder flags + //8=crc etc info is dodgy + //10=enhanced deflate + //20=patchdata + //40=strong encryption + //80,100,200,400=unused + //800=utf-8 + //1000=enh comp + //2000=masked localheader + //4000,8000=reserved + if (ReadRawShort(cd+8) & ~0x80e) + continue; + + //use the local entry header as the definitive version of everything but name. + le = (const unsigned char*)blob + ReadRawInt(cd+42); + + if (ReadRawInt(le+0) != 0x04034b50) + continue; + if (ReadRawShort(le+6) & ~0x80e) //general purpose flags + continue; + method = ReadRawShort(le+8); + if (method != 0 && method != 8) + continue; + if (nl != ReadRawShort(le+26)) + continue; //name is weird... +// if (el != ReadRawShort(le+28)) +// continue; //extradata is weird... + + csize = ReadRawInt(le+18); + usize = ReadRawInt(le+22); + if (nl >= sizeof(name)) + continue; //name is too long + memcpy(name, cd+46, nl); + name[nl] = 0; + + cb(name, le+30+ReadRawShort(le+26)+ReadRawShort(le+28), csize, method, usize); + ret++; + } + return ret; +} +int EnumerateFilesFromPackage(const bspdata_t *bspdata, void (*cb)(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize)) +{ + if (bspdata->zipsize) + return EnumerateFilesFromPackageBlob((const uint8_t *)bspdata->zip, bspdata->zipsize, cb); + return 0; +} + + +struct repackContext_s +{ + struct repackfile_s + { + std::string name; + const void *cdata; + size_t csize; + size_t usize; + int zipmethod; + + size_t localoffset; + }; + std::vector file; +}; + +static void *ZIP_Rewrite(repackContext_s *ctx, size_t *out_size) +{ + //helpers to deal with misaligned data. writes little-endian. +#define tab8(data) *tab++ = (data)&0xff +#define tab16(data) *tab++ = (data)&0xff,*tab++ = ((data)>>8)&0xff +#define tab32(data) *tab++ = (data)&0xff,*tab++ = ((data)>>8)&0xff,*tab++ = ((data)>>16)&0xff,*tab++ = ((data)>>24)&0xff + + uint8_t *zip, *tab, *cd,*eocd; + +#define GPF_TRAILINGSIZE (1u<<3) +#define GPF_UTF8 (1u<<11) + + //compute zip size + size_t totalsize = 0; + for (size_t i = 0; i < ctx->file.size(); i++) + { + size_t nl = strlen(ctx->file[i].name.c_str()); + totalsize += 30+nl; //local entry + totalsize += ctx->file[i].csize; //data size + totalsize += 46+nl; //central directory entry + } + if (!totalsize) + { + *out_size = 0; + return NULL; + } + totalsize += 22; //end of central dir + + zip = tab = (uint8_t*)malloc(totalsize); + if (!zip) + return NULL; + + //spit out the localentries (with the actual data in it) + for (size_t num = 0; num < ctx->file.size(); num++) + { + auto f = &ctx->file[num]; + const char *name = f->name.c_str(); + size_t nl = strlen(name); + + f->localoffset = tab-zip; + + tab32(0x04034b50); + tab16(45); //minver + tab16(GPF_UTF8);//general purpose flags + tab16(f->zipmethod); + tab16(0); //dostime + tab16(0); //dosdate + tab32(0); //crc FIXME + tab32(f->csize); + tab32(f->usize); + tab16(nl); + tab16(0); //extradata + memcpy(tab, name, nl); tab += nl; + memcpy(tab, f->cdata, f->csize); tab += f->csize; + } + + //now do it again for the central dir... + cd = tab; + for (size_t num = 0; num < ctx->file.size(); num++) + { + auto f = &ctx->file[num]; + const char *name = f->name.c_str(); + size_t nl = strlen(name); + + tab32(0x02014b50); + tab16((3<<8)|63); //ourver + tab16(45); //minver + tab16(GPF_UTF8); //general purpose flags + tab16(f->zipmethod); + tab16(0); //dostime + tab16(0); //dosdate + tab32(0); //crc FIXME + tab32(f->csize); + tab32(f->usize); + tab16(nl); + tab16(0); //extradata len + tab16(0); //comment len + tab16(0); //span index + tab16(0); //internal attr + tab32(0); //external attr + tab32(f->localoffset); + memcpy(tab, name, nl); tab += nl; + } + eocd = tab; + //write zip end-of-central-directory + tab32(0x06054b50); + tab16(0); //this disk number + tab16(0); //centraldir first disk + tab16(ctx->file.size());//centraldir entries + tab16(ctx->file.size());//total centraldir entries + tab32(eocd-cd); //centraldir size + tab32(cd-zip); //centraldir offset + tab16(0); //comment length + + //NOTE: the centraldir offset is meant to be an absolute offset into the final file for it to be a valid zip + //however, we don't take care to update it because concatenating a zip onto the end is easier and somewhat common + //zip tools should be able to figure it out by computing the difference between offset+size vs the actual offset of the trailer. + //they might complain though, however zip64 has issues with this. zip64 will likely cause other issues however. + + assert(tab == zip+totalsize); + + *out_size = tab-zip; + return zip; +} + +static repackContext_s repackctx; //evil global! oh noes! +static void ZipRepack_FoundFile(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) +{ + repackContext_s::repackfile_s f; + f.name = name; + f.cdata = compdata; + f.csize = compsize; + f.zipmethod = method; + f.usize = plainsize; + repackctx.file.push_back(f); +} +void ZipRepack_RemoveFile(const char *name) +{ + if (!name) + repackctx.file.clear(); + else for (auto it = repackctx.file.begin(); it != repackctx.file.end(); it++) + { + if (it->name == name) + { + repackctx.file.erase(it); + break; + } + } +} +void ZipRepack_AddFile(const char *name, const void *data, size_t datasize) +{ + ZipRepack_RemoveFile(name); + //lame compressionlessness, but that's good if you're going to gzip it + ZipRepack_FoundFile(name, data, datasize, 0, datasize); +} +void Zip_StartUpdate(const bspdata_t *bspdata) +{ + repackctx = {}; + EnumerateFilesFromPackage(bspdata, ZipRepack_FoundFile); +} +void Zip_FinishUpdate(bspdata_t *bspdata) +{ + void *old = bspdata->zip; + bspdata->zip = ZIP_Rewrite(&repackctx, &bspdata->zipsize); + free(old); + repackctx = {}; +} + + /* ========================================================================= */ +static void +CountEmbedded(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) +{ +} + +static void FormatSize(char *out, size_t outsize, size_t value) +{ + char tmp[64]; + const char *in; + unsigned dig; + q_snprintf(tmp,sizeof(tmp),"%u", (unsigned)value); + in = tmp; + dig = strlen(tmp); + while (*in) + { + *out++ = *in++; + if (!(--dig%3) && *in) + *out++ = ','; + } + *out = 0; +} +static void +PrintEmbeddedSize(const char *name, const void *compdata, size_t compsize, int method, size_t plainsize) +{ + char sz[64]; + FormatSize(sz, sizeof(sz), plainsize); + + if (method) + logprint("%12s %3u%% %s\n", sz, !plainsize?100u:(unsigned int)(compsize*100.0/plainsize), name); + else + logprint("%12s %s\n", sz, name); +} + static void PrintLumpSize(const lumpspec_t *lumpspec, int lumptype, int count) { @@ -2858,4 +3188,12 @@ PrintBSPFileSizes(const bspdata_t *bspdata) logprint("%7s %-12s %10i\n", "BSPX", x->lumpname, (int)x->lumpsize); } } + + if (bspdata->zipsize) + { + unsigned int count = EnumerateFilesFromPackage(bspdata, CountEmbedded); + logprint("%7u %-12s %10u\n", count, "archive", (unsigned)bspdata->zipsize); + printf("---------------------\n"); + EnumerateFilesFromPackage(bspdata, PrintEmbeddedSize); + } } diff --git a/include/common/bspfile.hh b/include/common/bspfile.hh index 7a6cb51c7..6ab5902c9 100644 --- a/include/common/bspfile.hh +++ b/include/common/bspfile.hh @@ -837,6 +837,8 @@ typedef struct { } data; bspxentry_t *bspxentries; + void *zip; + size_t zipsize; } bspdata_t; void LoadBSPFile(char *filename, bspdata_t *bspdata); //returns the filename as contained inside a bsp @@ -846,4 +848,10 @@ void ConvertBSPFormat(int32_t version, bspdata_t *bspdata); void BSPX_AddLump(bspdata_t *bspdata, const char *xname, const void *xdata, size_t xsize); const void *BSPX_GetLump(bspdata_t *bspdata, const char *xname, size_t *xsize); + +void ZipRepack_AddFile(const char *name, const void *data, size_t datasize); +void ZipRepack_RemoveFile(const char *name); +void Zip_StartUpdate(const bspdata_t *bspdata); +void Zip_FinishUpdate(bspdata_t *bspdata); + #endif /* __COMMON_BSPFILE_H__ */ diff --git a/include/light/light.hh b/include/light/light.hh index 1ffa9de68..f21a6a4fd 100644 --- a/include/light/light.hh +++ b/include/light/light.hh @@ -398,11 +398,20 @@ public: extern uint8_t *filebase; extern uint8_t *lit_filebase; +extern uint32_t *hdr_filebase; extern uint8_t *lux_filebase; +extern int facestyles; extern int oversample; -extern int write_litfile; -extern int write_luxfile; +#define LIT_EXTERNAL_RGB8 (1u<<0) +#define LIT_INTERNAL_RGB8 (1u<<1) +#define LIT_EXTERNAL_E5BGR9 (1u<<2) +#define LIT_INTERNAL_E5BGR9 (1u<<3) +#define LIT_EXTERNAL_LIT2 (1u<<4) +extern int write_litfile; //bitmask +#define LUX_EXTERNAL 1 +#define LUX_INTERNAL 2 +extern int write_luxfile; //bitmask extern qboolean onlyents; extern qboolean scaledonly; extern uint64_t *extended_texinfo_flags; @@ -424,8 +433,8 @@ extern char mapfilename[1024]; lockable_setting_t *FindSetting(std::string name); void SetGlobalSetting(std::string name, std::string value, bool cmdline); void FixupGlobalSettings(void); -void GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int size); -void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int lightofs); +void GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint32_t **hdrdata, uint8_t **deluxdata, int size); +void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, uint32_t **hdrdata, uint8_t **deluxdata, int lightofs); const modelinfo_t *ModelInfoForModel(const mbsp_t *bsp, int modelnum); /** * returs nullptr for "skip" faces diff --git a/include/light/litfile.hh b/include/light/litfile.hh index 1fe761339..245c53ad1 100644 --- a/include/light/litfile.hh +++ b/include/light/litfile.hh @@ -23,6 +23,7 @@ #include #define LIT_VERSION 1 +#define LIT_VERSION_E5BGR9 (0x00010000|LIT_VERSION) typedef struct litheader_s { struct { @@ -36,10 +37,13 @@ typedef struct litheader_s { } litheader_t; /* internal representation for bspx/lit2 */ +#define MAXLIGHTMAPSSUP 16 +#define INVALID_LIGHTSTYLE 0xffffu +#define INVALID_LIGHTSTYLE_OLD 0xffu typedef struct { float lmscale; - uint8_t styles[MAXLIGHTMAPS]; /* scaled styles */ - int32_t lightofs; /* scaled lighting */ + uint16_t styles[MAXLIGHTMAPSSUP]; /* scaled styles */ + int32_t lightofs; /* scaled lighting */ unsigned short extent[2]; } facesup_t; diff --git a/include/qbsp/bspfile.hh b/include/qbsp/bspfile.hh index 145e01d54..16cbd8184 100644 --- a/include/qbsp/bspfile.hh +++ b/include/qbsp/bspfile.hh @@ -172,7 +172,8 @@ typedef struct { uint32_t v[2]; /* vertex numbers */ } bsp2_dedge_t; -#define MAXLIGHTMAPS 4 +#define MAXLIGHTMAPS 4 +#define INVALID_LIGHTSTYLE_OLD 0xffu /*signifies 'no more lightstyles'*/ typedef struct { int16_t planenum; int16_t side; diff --git a/include/qbsp/wad.hh b/include/qbsp/wad.hh index 0c848e7c9..bee2c6bb2 100644 --- a/include/qbsp/wad.hh +++ b/include/qbsp/wad.hh @@ -42,7 +42,14 @@ typedef struct { char compression; char pad1, pad2; char name[16]; // must be null terminated -} lumpinfo_t; +} dlumpinfo_t; +typedef struct { + int filepos; + int disksize; + int size; // uncompressed + void *mip; + char name[128]; // must be null terminated +} mlumpinfo_t; typedef struct texture_s { char name[16]; @@ -60,14 +67,14 @@ typedef struct { typedef struct wad_s { wadinfo_t header; int version; - lumpinfo_t *lumps; + mlumpinfo_t *lumps; FILE *file; struct wad_s *next; } wad_t; wad_t *WADList_AddWad(const char *fpath, bool external, wad_t *current_wadlist); wad_t *WADList_Init(const char *wadstring); -void WADList_Process(const wad_t *wadlist); +void WADList_Process(wad_t *wadlist); void WADList_Free(wad_t *wadlist); const texture_t *WADList_GetTexture(const char *name); // for getting a texture width/height diff --git a/light/entities.cc b/light/entities.cc index be2fd378f..113b676f0 100644 --- a/light/entities.cc +++ b/light/entities.cc @@ -61,7 +61,6 @@ const char * light_t::classname() const { static std::vector> lightstyleForTargetname; -#define MAX_SWITCHABLE_STYLES 64 static entdict_t &WorldEnt() { @@ -99,12 +98,13 @@ LightStyleForTargetname(const globalconfig_t& cfg, const std::string &targetname // generate a new style number and return it const int newStylenum = cfg.compilerstyle_start.intValue() + lightstyleForTargetname.size(); - - // check if full - if (newStylenum >= MAX_SWITCHABLE_STYLES) { - Error("%s: Too many unique light targetnames (max=%d)\n", __func__, MAX_SWITCHABLE_STYLES); + if (newStylenum >= (facestyles?INVALID_LIGHTSTYLE:INVALID_LIGHTSTYLE_OLD)) + { + if (!facestyles) + Error("%s: Too many unique light targetnames (reached max of %i)\nTip: Use '-facestyles N' for 16bit lightstyle limits in supporting engines.", __func__, newStylenum-cfg.compilerstyle_start.intValue()); + else + Error("%s: Too many unique light targetnames (reached max of %i)\n", __func__, newStylenum-cfg.compilerstyle_start.intValue()); } - lightstyleForTargetname.emplace_back(targetname, newStylenum); //mxd. https://clang.llvm.org/extra/clang-tidy/checks/modernize-use-emplace.html if (verbose_log) { @@ -375,8 +375,8 @@ CheckEntityFields(const globalconfig_t &cfg, light_t *entity) entity->light.setFloatValue(entity->light.floatValue() / entity->samples.intValue()); } - if (entity->style.intValue() < 0 || entity->style.intValue() > 254) { - Error("Bad light style %i (must be 0-254)", entity->style.intValue()); + if (entity->style.intValue() < 0 || entity->style.intValue() > INVALID_LIGHTSTYLE) { + Error("Bad light style %i (must be 0-%i)", entity->style.intValue(), INVALID_LIGHTSTYLE-1); } } @@ -1272,9 +1272,8 @@ WriteEntitiesToString(const globalconfig_t& cfg, mbsp_t *bsp) free(bsp->dentdata); /* FIXME - why are we printing this here? */ - logprint("%i switchable light styles (%d max)\n", - static_cast(lightstyleForTargetname.size()), - MAX_SWITCHABLE_STYLES - cfg.compilerstyle_start.intValue()); + logprint("%i switchable light styles\n", + static_cast(lightstyleForTargetname.size())); bsp->entdatasize = entdata.size() + 1; // +1 for a null byte at the end bsp->dentdata = (char *) calloc(bsp->entdatasize, 1); diff --git a/light/light.cc b/light/light.cc index 2851c2b22..085dfcee3 100644 --- a/light/light.cc +++ b/light/light.cc @@ -81,6 +81,13 @@ static int lit_file_p; /// offset of end of space for litfile data static int lit_file_end; +/// start of litfile data +uint32_t *hdr_filebase; +/// offset of start of free space after litfile data (should be kept a multiple of 12) +static int hdr_file_p; +/// offset of end of space for litfile data +static int hdr_file_end; + /// start of luxfile data uint8_t *lux_filebase; /// offset of start of free space after luxfile data (should be kept a multiple of 12) @@ -94,9 +101,10 @@ std::vector selfshadowlist; std::vector shadowworldonlylist; std::vector switchableshadowlist; +int facestyles = 0; //max styles per face - uses bspx stuff. int oversample = 1; -int write_litfile = 0; /* 0 for none, 1 for .lit, 2 for bspx, 3 for both */ -int write_luxfile = 0; /* 0 for none, 1 for .lux, 2 for bspx, 3 for both */ +int write_litfile = 0; /* LIT_* bitmask */ +int write_luxfile = 0; /* LUX_* bitmask */ qboolean onlyents = false; qboolean novisapprox = false; bool nolights = false; @@ -180,12 +188,13 @@ PrintOptionsSummary(void) * and return in *lightdata */ void -GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int size) +GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint32_t **hdrdata, uint8_t **deluxdata, int size) { ThreadLock(); *lightdata = filebase + file_p; *colordata = lit_filebase + lit_file_p; + *hdrdata = hdr_filebase + hdr_file_p; *deluxdata = lux_filebase + lux_file_p; // if size isn't a multiple of 4, round up to the next multiple of 4 @@ -197,6 +206,7 @@ GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int // and 12-uint8_t boundaries (lit_file_p/lux_file_p) file_p += size; lit_file_p += 3 * size; + hdr_file_p += size; lux_file_p += 3 * size; ThreadUnlock(); @@ -212,7 +222,7 @@ GetFileSpace(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int * Special version of GetFileSpace for when we're relighting a .bsp and can't modify it. * In this case the offsets are already known. */ -void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, uint8_t **deluxdata, int lightofs) { +void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, uint32_t **hdrdata, uint8_t **deluxdata, int lightofs) { Q_assert(lightofs >= 0); *lightdata = filebase + lightofs; @@ -221,6 +231,10 @@ void GetFileSpace_PreserveOffsetInBsp(uint8_t **lightdata, uint8_t **colordata, *colordata = lit_filebase + (lightofs * 3); } + if (hdrdata) { + *hdrdata = hdr_filebase + lightofs; + } + if (deluxdata) { *deluxdata = lux_filebase + (lightofs * 3); } @@ -266,6 +280,10 @@ LightThread(void *arg) if (facenum == -1) break; +for (int i = 0; i < bsp->numfaces; i++) +if (bsp->dfaces[i].planenum < 0) +printf("Bad plane on face %i when processing %i\n", i, facenum); + bsp2_dface_t *f = const_cast(BSP_GetFace(const_cast(bsp), facenum)); /* Find the correct model offset */ @@ -281,15 +299,15 @@ LightThread(void *arg) else if (scaledonly) { f->lightofs = -1; - f->styles[0] = 255; + f->styles[0] = INVALID_LIGHTSTYLE_OLD; LightFace(bsp, f, faces_sup + facenum, cfg_static); } else if (faces_sup[facenum].lmscale == face_modelinfo->lightmapscale) { - LightFace(bsp, f, nullptr, cfg_static); - faces_sup[facenum].lightofs = f->lightofs; + LightFace(bsp, f, faces_sup + facenum, cfg_static); + f->lightofs = faces_sup[facenum].lightofs; for (int i = 0; i < MAXLIGHTMAPS; i++) - faces_sup[facenum].styles[i] = f->styles[i]; + f->styles[i] = faces_sup[facenum].styles[i]; } else { @@ -407,6 +425,13 @@ LightWorld(bspdata_t *bspdata, qboolean forcedscale) lit_file_p = 0; lit_file_end = (MAX_MAP_LIGHTING*3); + /* hdr data stored in a separate buffer */ + hdr_filebase = (uint32_t *)malloc(MAX_MAP_LIGHTING*sizeof(uint32_t)); + if (!hdr_filebase) + Error("%s: allocation of %u bytes failed.", __func__, MAX_MAP_LIGHTING*(unsigned)sizeof(uint32_t)); + hdr_file_p = 0; + hdr_file_end = MAX_MAP_LIGHTING; + /* lux data stored in a separate buffer */ lux_filebase = (uint8_t *)calloc(MAX_MAP_LIGHTING*3, 1); if (!lux_filebase) @@ -418,10 +443,8 @@ LightWorld(bspdata_t *bspdata, qboolean forcedscale) BSPX_AddLump(bspdata, "LMSHIFT", NULL, 0); const unsigned char *lmshift_lump = (const unsigned char *)BSPX_GetLump(bspdata, "LMSHIFT", NULL); - if (!lmshift_lump && write_litfile != ~0) - faces_sup = NULL; //no scales, no lit2 - else - { //we have scales or lit2 output. yay... + if (lmshift_lump || (write_litfile&LIT_INTERNAL_RGB8) || (write_luxfile&LUX_INTERNAL) || facestyles) + { //we have scales/bspx/lit2 output. yay... faces_sup = (facesup_t *)malloc(sizeof(*faces_sup) * bsp->numfaces); memset(faces_sup, 0, sizeof(*faces_sup) * bsp->numfaces); if (lmshift_lump) @@ -435,6 +458,10 @@ LightWorld(bspdata_t *bspdata, qboolean forcedscale) faces_sup[i].lmscale = modelinfo.at(0)->lightmapscale; } } + else + faces_sup = NULL; //no scales, no lit2, no -bspx + if (!facestyles) + facestyles = 4; CalcualateVertexNormals(bsp); @@ -484,20 +511,69 @@ LightWorld(bspdata_t *bspdata, qboolean forcedscale) } logprint("lightdatasize: %i\n", bsp->lightdatasize); - if (faces_sup) { - uint8_t *styles = (uint8_t *)malloc(sizeof(*styles)*4*bsp->numfaces); - int32_t *offsets = (int32_t *)malloc(sizeof(*offsets)*bsp->numfaces); + //kill this stuff if it lingered from a previous light compile + BSPX_AddLump(bspdata, "LMSTYLE16", NULL, 0); + BSPX_AddLump(bspdata, "LMSTYLE", NULL, 0); + BSPX_AddLump(bspdata, "LMOFFSET", NULL, 0); + + //write out new stuff if we have it. + if (faces_sup) + { + bool needoffsets = false; + bool needstyles = false; + int maxstyle = 0; + int stylesperface = 0; + for (int i = 0; i < bsp->numfaces; i++) { - offsets[i] = faces_sup[i].lightofs; - for (int j = 0; j < MAXLIGHTMAPS; j++) - styles[i*4+j] = faces_sup[i].styles[j]; + if (bsp->dfaces[i].lightofs != faces_sup[i].lightofs) + needoffsets = true; + int j = 0; + for (; j < MAXLIGHTMAPSSUP; j++) { + if (faces_sup[i].styles[j] == INVALID_LIGHTSTYLE) + break; + if (bsp->dfaces[i].styles[j] != faces_sup[i].styles[j]) + needstyles = true; + if (maxstyle < faces_sup[i].styles[j]) + maxstyle = faces_sup[i].styles[j]; + } + if (stylesperface < j) + stylesperface = j; + } + needstyles |= (stylesperface>4); + + logprint("max %i styles per face%s\n", stylesperface, maxstyle >= INVALID_LIGHTSTYLE_OLD?", 16bit lightstyles":""); + + if (needstyles) + { + if (maxstyle >= INVALID_LIGHTSTYLE_OLD/*needs bigger datatype*/) { + /*LMSTYLE16 lump provides for more than 4 styles per surface, as well as more than 255 styles*/ + uint16_t *styles = (uint16_t *)malloc(sizeof(*styles)*stylesperface*bsp->numfaces); + for (int i = 0; i < bsp->numfaces; i++) { + for (int j = 0; j < stylesperface; j++) + styles[i*stylesperface+j] = faces_sup[i].styles[j]; + } + BSPX_AddLump(bspdata, "LMSTYLE16", styles, sizeof(*styles)*stylesperface*bsp->numfaces); + } + else { + /*original LMSTYLE lump was just for different lmshift info*/ + if (stylesperface < 4) + stylesperface = 4; /*better compat*/ + uint8_t *styles = (uint8_t *)malloc(sizeof(*styles)*stylesperface*bsp->numfaces); + for (int i = 0; i < bsp->numfaces; i++) { + for (int j = 0; j < stylesperface; j++) + styles[i*stylesperface+j] = faces_sup[i].styles[j]; + } + BSPX_AddLump(bspdata, "LMSTYLE", styles, sizeof(*styles)*stylesperface*bsp->numfaces); + } + } + if (needoffsets) + { + int32_t *offsets = (int32_t *)malloc(sizeof(*offsets)*bsp->numfaces); + for (int i = 0; i < bsp->numfaces; i++) { + offsets[i] = faces_sup[i].lightofs; + } + BSPX_AddLump(bspdata, "LMOFFSET", offsets, sizeof(*offsets)*bsp->numfaces); } - BSPX_AddLump(bspdata, "LMSTYLE", styles, sizeof(*styles)*4*bsp->numfaces); - BSPX_AddLump(bspdata, "LMOFFSET", offsets, sizeof(*offsets)*bsp->numfaces); - } else { - //kill this stuff if its somehow found. - BSPX_AddLump(bspdata, "LMSTYLE", NULL, 0); - BSPX_AddLump(bspdata, "LMOFFSET", NULL, 0); } } @@ -712,11 +788,11 @@ static void SetLitNeeded() { if (!write_litfile) { if (scaledonly) { - write_litfile = 2; + write_litfile = LIT_INTERNAL_RGB8; logprint("Colored light entities/settings detected: " "bspxlit output enabled.\n"); } else { - write_litfile = 1; + write_litfile = LIT_EXTERNAL_RGB8; logprint("Colored light entities/settings detected: " ".lit output enabled.\n"); } @@ -806,11 +882,15 @@ static void PrintUsage() "Experimental options:\n" " -lit2 write .lit2 file\n" " -lmscale n change lightmap scale, vanilla engines only allow 16\n" -" -lux write .lux file\n" -" -bspxlit writes rgb data into the bsp itself\n" +" -lux write average light directions into a .lux file\n" +" -hdr write hdr .lit file with greater range\n" +" -bspxhdr writes rgb hdr data into the bsp itself (fte)\n" +" -bspxlit writes rgb data into the bsp itself (fte+qss+ezquake)\n" " -bspx writes both rgb and directions data into the bsp itself\n" " -novanilla implies -bspxlit. don't write vanilla lighting\n" -" -radlights filename.rad loads a file\n"); +" -radlights filename.rad loads a file\n" +" -facestyles n (bspx) overrides the max number of lightstyles per face\n" +); printf("\n"); printf("Overridable worldspawn keys:\n"); @@ -980,23 +1060,33 @@ light_main(int argc, const char **argv) if (fadegate > 1) { logprint( "WARNING: -gate value greater than 1 may cause artifacts\n" ); } + } else if (!strcmp(argv[i], "-hdr")) { + write_litfile |= LIT_EXTERNAL_E5BGR9; } else if (!strcmp(argv[i], "-lit")) { - write_litfile |= 1; + write_litfile |= LIT_EXTERNAL_RGB8; } else if (!strcmp(argv[i], "-lit2")) { - write_litfile = ~0; + write_litfile = LIT_EXTERNAL_LIT2|LIT_EXTERNAL_RGB8; + write_luxfile |= LUX_EXTERNAL; } else if (!strcmp(argv[i], "-lux")) { - write_luxfile |= 1; + write_luxfile |= LUX_EXTERNAL; + } else if (!strcmp(argv[i], "-bspxhdr")) { + write_litfile |= LIT_INTERNAL_E5BGR9; } else if (!strcmp(argv[i], "-bspxlit")) { - write_litfile |= 2; + write_litfile |= LIT_INTERNAL_RGB8; } else if (!strcmp(argv[i], "-bspxlux")) { - write_luxfile |= 2; + write_luxfile |= LUX_INTERNAL; } else if (!strcmp(argv[i], "-bspxonly")) { - write_litfile = 2; - write_luxfile = 2; + write_litfile = LIT_INTERNAL_E5BGR9; + write_luxfile = LUX_INTERNAL; scaledonly = true; } else if (!strcmp(argv[i], "-bspx")) { - write_litfile |= 2; - write_luxfile |= 2; + write_litfile |= LIT_INTERNAL_RGB8; + write_luxfile |= LUX_INTERNAL; + } else if (!strcmp(argv[i], "-facestyles")) { + if ((i + 1) < argc && isdigit(argv[i + 1][0])) + facestyles = atoi(argv[++i]); + else + facestyles = 0; } else if (!strcmp(argv[i], "-novanilla")) { scaledonly = true; } else if ( !strcmp( argv[ i ], "-radlights" ) ) { @@ -1041,7 +1131,7 @@ light_main(int argc, const char **argv) } else if ( !strcmp( argv[ i ], "-phongdebug" ) ) { CheckNoDebugModeSet(); debugmode = debugmode_phong; - write_litfile |= 1; + write_litfile |= LIT_EXTERNAL_RGB8; logprint( "Phong shading debug mode enabled\n" ); } else if ( !strcmp( argv[ i ], "-phongdebug_obj" ) ) { CheckNoDebugModeSet(); @@ -1128,7 +1218,7 @@ light_main(int argc, const char **argv) } if (debugmode != debugmode_none) { - write_litfile |= 1; + write_litfile |= LIT_EXTERNAL_RGB8; } #ifndef HAVE_EMBREE @@ -1146,17 +1236,19 @@ light_main(int argc, const char **argv) if (numthreads > 1) logprint("running with %d threads\n", numthreads); - if (write_litfile == ~0) + if (write_litfile & LIT_EXTERNAL_LIT2) logprint("generating lit2 output only.\n"); else { - if (write_litfile & 1) + if (write_litfile & LIT_EXTERNAL_RGB8) logprint(".lit colored light output requested on command line.\n"); - if (write_litfile & 2) + if (write_litfile & LIT_INTERNAL_RGB8) logprint("BSPX colored light output requested on command line.\n"); - if (write_luxfile & 1) + if (write_litfile & LIT_INTERNAL_E5BGR9) + logprint("BSPX hdr light output requested on command line.\n"); + if (write_luxfile & LUX_EXTERNAL) logprint(".lux light directions output requested on command line.\n"); - if (write_luxfile & 2) + if (write_luxfile & LUX_INTERNAL) logprint("BSPX light directions output requested on command line.\n"); } @@ -1184,6 +1276,10 @@ light_main(int argc, const char **argv) StripExtension(source); DefaultExtension(source, ".lit"); remove(source); + + StripExtension(source); + DefaultExtension(source, ".lux"); + remove(source); } { @@ -1250,9 +1346,10 @@ light_main(int argc, const char **argv) /*invalidate any bspx lighting info early*/ BSPX_AddLump(&bspdata, "RGBLIGHTING", NULL, 0); + BSPX_AddLump(&bspdata, "LIGHTING_E5BGR9", NULL, 0); BSPX_AddLump(&bspdata, "LIGHTINGDIR", NULL, 0); - if (write_litfile == ~0) + if (write_litfile & LIT_EXTERNAL_LIT2) { WriteLitFile(bsp, faces_sup, source, 2); return 0; //run away before any files are written @@ -1260,19 +1357,23 @@ light_main(int argc, const char **argv) else { /*fixme: add a new per-surface offset+lmscale lump for compat/versitility?*/ - if (write_litfile & 1) + if (write_litfile & LIT_EXTERNAL_RGB8) WriteLitFile(bsp, faces_sup, source, LIT_VERSION); - if (write_litfile & 2) + else if (write_litfile & LIT_EXTERNAL_E5BGR9) + WriteLitFile(bsp, faces_sup, source, LIT_VERSION_E5BGR9); + if (write_litfile & LIT_INTERNAL_RGB8) BSPX_AddLump(&bspdata, "RGBLIGHTING", lit_filebase, bsp->lightdatasize*3); - if (write_luxfile & 1) + if (write_litfile & LIT_INTERNAL_E5BGR9) + BSPX_AddLump(&bspdata, "LIGHTING_E5BGR9", hdr_filebase, bsp->lightdatasize*4); + if (write_luxfile & LUX_EXTERNAL) WriteLuxFile(bsp, source, LIT_VERSION); - if (write_luxfile & 2) + if (write_luxfile & LUX_INTERNAL) BSPX_AddLump(&bspdata, "LIGHTINGDIR", lux_filebase, bsp->lightdatasize*3); } } /* -novanilla + internal lighting = no grey lightmap */ - if (scaledonly && (write_litfile & 2)) + if (scaledonly && (write_litfile & (LIT_INTERNAL_RGB8|LIT_INTERNAL_E5BGR9))) bsp->lightdatasize = 0; #if 0 diff --git a/light/litfile.cc b/light/litfile.cc index 063f77d45..17fcf58c6 100644 --- a/light/litfile.cc +++ b/light/litfile.cc @@ -75,7 +75,12 @@ WriteLitFile(const mbsp_t *bsp, facesup_t *facesup, const char *filename, int ve SafeWrite(litfile, lux_filebase, bsp->lightdatasize * 3); } else - SafeWrite(litfile, lit_filebase, bsp->lightdatasize * 3); + { + if ((version & 0xffff0000)==0x00010000) + SafeWrite(litfile, hdr_filebase, bsp->lightdatasize * 4); + else + SafeWrite(litfile, lit_filebase, bsp->lightdatasize * 3); + } fclose(litfile); } diff --git a/light/ltface.cc b/light/ltface.cc index 6c9648779..fff2d14c2 100644 --- a/light/ltface.cc +++ b/light/ltface.cc @@ -900,7 +900,7 @@ Lightmap_ForStyle(lightmapdict_t *lightmaps, const int style, const lightsurf_t // no exact match, check for an unsaved one for (auto &lm : *lightmaps) { - if (lm.style == 255) { + if (lm.style == INVALID_LIGHTSTYLE) { Lightmap_AllocOrClear(&lm, lightsurf); return &lm; } @@ -908,7 +908,7 @@ Lightmap_ForStyle(lightmapdict_t *lightmaps, const int style, const lightsurf_t // add a new one to the vector (invalidates existing lightmap_t pointers) lightmap_t newLightmap {}; - newLightmap.style = 255; + newLightmap.style = INVALID_LIGHTSTYLE; Lightmap_AllocOrClear(&newLightmap, lightsurf); lightmaps->push_back(newLightmap); @@ -919,7 +919,7 @@ static void Lightmap_ClearAll(lightmapdict_t *lightmaps) { for (auto &lm : *lightmaps) { - lm.style = 255; + lm.style = INVALID_LIGHTSTYLE; } } @@ -933,7 +933,7 @@ static void Lightmap_Save(lightmapdict_t *lightmaps, const lightsurf_t *lightsurf, lightmap_t *lightmap, const int style) { - if (lightmap->style == 255) { + if (lightmap->style == INVALID_LIGHTSTYLE) { lightmap->style = style; } } @@ -2623,19 +2623,10 @@ LightFace_ScaleAndClamp(const lightsurf_t *lightsurf, lightmapdict_t *lightmaps) } /* Scale and clamp any out-of-range samples */ - vec_t maxcolor = 0; VectorScale(color, cfg.rangescale.floatValue(), color); for (int c = 0; c < 3; c++) { color[c] = pow( color[c] / 255.0f, 1.0 / cfg.lightmapgamma.floatValue() ) * 255.0f; } - for (int c = 0; c < 3; c++) { - if (color[c] > maxcolor) { - maxcolor = color[c]; - } - } - if (maxcolor > 255) { - VectorScale(color, 255.0f / maxcolor, color); - } } } } @@ -3020,13 +3011,45 @@ BoxBlurImage(const std::vector &input, int w, int h, int radius) return res; } +static unsigned int HDR_PackResult(qvec4f rgba) +{ +#define HDR_ONE 128.0 //logical value for 1.0 lighting (quake's overbrights give 255). + //we want 0-1-like values. except that we can oversample and express smaller values too. + float r = rgba[0]/HDR_ONE; + float g = rgba[1]/HDR_ONE; + float b = rgba[2]/HDR_ONE; + + int e = 0; + float m = max(max(r, g), b); + float scale; + + if (m >= 0.5) + { //positive exponent + while (m >= (1<<(e)) && e < 30-15) //don't do nans. + e++; + } + else + { //negative exponent... + while (m < 1/(1<<-e) && e > -15) //don't do nans. + e--; + } + + scale = pow(2, e-9); + + return ((e+15)<<27) | + (min((int)(r/scale + 0.5), 0x1ff)<<18) | + (min((int)(g/scale + 0.5), 0x1ff)<<9) | + (min((int)(b/scale + 0.5), 0x1ff)<<0); +} + + static void WriteSingleLightmap(const mbsp_t *bsp, const bsp2_dface_t *face, const lightsurf_t *lightsurf, const lightmap_t *lm, const int actual_width, const int actual_height, - uint8_t *out, uint8_t *lit, uint8_t *lux); + uint8_t *out, uint8_t *lit, uint32_t *hdr, uint8_t *lux); static void WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const lightsurf_t *lightsurf, @@ -3046,7 +3069,8 @@ WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const } uint8_t *out, *lit, *lux; - GetFileSpace_PreserveOffsetInBsp(&out, &lit, &lux, face->lightofs); + uint32_t *hdr; + GetFileSpace_PreserveOffsetInBsp(&out, &lit, &hdr, &lux, face->lightofs); for (int mapnum = 0; mapnum < MAXLIGHTMAPS; mapnum++) { const int style = face->styles[mapnum]; @@ -3058,7 +3082,7 @@ WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const // see if we have computed lighting for this style for (const lightmap_t& lm : *lightmaps) { if (lm.style == style) { - WriteSingleLightmap(bsp, face, lightsurf, &lm, actual_width, actual_height, out, lit, lux); + WriteSingleLightmap(bsp, face, lightsurf, &lm, actual_width, actual_height, out, lit, hdr, lux); break; } } @@ -3072,13 +3096,25 @@ WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const return; } + int maxfstyles = facesup?MAXLIGHTMAPSSUP:MAXLIGHTMAPS; + if (maxfstyles > facestyles) + maxfstyles = facestyles; //truncate it a little + int maxstyle = facesup?INVALID_LIGHTSTYLE:INVALID_LIGHTSTYLE_OLD; + // intermediate collection for sorting lightmaps std::vector> sortable; for (const lightmap_t &lightmap : *lightmaps) { // skip un-saved lightmaps - if (lightmap.style == 255) + if (lightmap.style == INVALID_LIGHTSTYLE) continue; + if (lightmap.style > maxstyle) { + logprint("WARNING: Style %i too high\n" + " lightmap point near (%s)\n", + lightmap.style, + VecStr(lightsurf->points[0]).c_str()); + continue; + } // skip lightmaps where all samples have brightness below 1 if (bsp->loadversion != Q2_BSPVERSION) { // HACK: don't do this on Q2. seems if all styles are 0xff, the face is drawn fullbright instead of black (Q1) @@ -3097,7 +3133,7 @@ WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const std::vector sorted; for (const auto &pair : sortable) { - if (sorted.size() == MAXLIGHTMAPS) { + if (sorted.size() == maxfstyles) { logprint("WARNING: Too many light styles on a face\n" " lightmap point near (%s)\n", VecStr(lightsurf->points[0]).c_str()); @@ -3109,7 +3145,7 @@ WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const /* final number of lightmaps */ const int numstyles = static_cast(sorted.size()); - Q_assert(numstyles <= MAXLIGHTMAPS); + Q_assert(numstyles <= MAXLIGHTMAPSSUP); /* update face info (either core data or supplementary stuff) */ if (facesup) @@ -3120,8 +3156,8 @@ WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const for (mapnum = 0; mapnum < numstyles; mapnum++) { facesup->styles[mapnum] = sorted.at(mapnum)->style; } - for (; mapnum < MAXLIGHTMAPS; mapnum++) { - facesup->styles[mapnum] = 255; + for (; mapnum < MAXLIGHTMAPSSUP; mapnum++) { + facesup->styles[mapnum] = INVALID_LIGHTSTYLE; } facesup->lmscale = lightsurf->lightmapscale; } @@ -3132,7 +3168,7 @@ WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const face->styles[mapnum] = sorted.at(mapnum)->style; } for (; mapnum < MAXLIGHTMAPS; mapnum++) { - face->styles[mapnum] = 255; + face->styles[mapnum] = INVALID_LIGHTSTYLE_OLD; } } @@ -3142,7 +3178,8 @@ WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const const int size = (lightsurf->texsize[0] + 1) * (lightsurf->texsize[1] + 1); uint8_t *out, *lit, *lux; - GetFileSpace(&out, &lit, &lux, size * numstyles); + uint32_t *hdr; + GetFileSpace(&out, &lit, &hdr, &lux, size * numstyles); // q2 support int lightofs; @@ -3169,10 +3206,11 @@ WriteLightmaps(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const for (int mapnum = 0; mapnum < numstyles; mapnum++) { const lightmap_t *lm = sorted.at(mapnum); - WriteSingleLightmap(bsp, face, lightsurf, lm, actual_width, actual_height, out, lit, lux); + WriteSingleLightmap(bsp, face, lightsurf, lm, actual_width, actual_height, out, lit, hdr, lux); out += (actual_width * actual_height); lit += (actual_width * actual_height * 3); + hdr += (actual_width * actual_height); lux += (actual_width * actual_height * 3); } } @@ -3188,7 +3226,7 @@ WriteSingleLightmap(const mbsp_t *bsp, const lightsurf_t *lightsurf, const lightmap_t *lm, const int actual_width, const int actual_height, - uint8_t *out, uint8_t *lit, uint8_t *lux) + uint8_t *out, uint8_t *lit, uint32_t *hdr, uint8_t *lux) { const int oversampled_width = actual_width * oversample; const int oversampled_height = actual_height * oversample; @@ -3218,6 +3256,22 @@ WriteSingleLightmap(const mbsp_t *bsp, for (int s = 0; s < actual_width; s++) { const int sampleindex = (t * actual_width) + s; qvec4f color = output_color.at(sampleindex); + + if (hdr) + *hdr++ = HDR_PackResult(color); + + vec_t maxcolor = 0; + for (int c = 0; c < 3; c++) { + if (color[c] > maxcolor) { + maxcolor = color[c]; + } + } + if (maxcolor>255) + { + color[0] *= 255.0/maxcolor; + color[1] *= 255.0/maxcolor; + color[2] *= 255.0/maxcolor; + } *lit++ = color[0]; *lit++ = color[1]; @@ -3297,14 +3351,14 @@ LightFace(const mbsp_t *bsp, bsp2_dface_t *face, facesup_t *facesup, const globa if (facesup) { facesup->lightofs = -1; - for (int i = 0; i < MAXLIGHTMAPS; i++) - facesup->styles[i] = 255; + for (int i = 0; i < MAXLIGHTMAPSSUP; i++) + facesup->styles[i] = INVALID_LIGHTSTYLE; } else { face->lightofs = -1; for (int i = 0; i < MAXLIGHTMAPS; i++) - face->styles[i] = 255; + face->styles[i] = INVALID_LIGHTSTYLE_OLD; } } diff --git a/qbsp/wad.cc b/qbsp/wad.cc index 5addbde6c..e70a2e2e0 100644 --- a/qbsp/wad.cc +++ b/qbsp/wad.cc @@ -27,7 +27,7 @@ static void WADList_LoadTextures(const wad_t *wadlist, dmiptexlump_t *lump); static int WAD_LoadLump(const wad_t *wad, const char *name, uint8_t *dest); -static void WADList_AddAnimationFrames(const wad_t *wadlist); +static void WADList_AddAnimationFrames(wad_t *wadlist); static texture_t *textures; @@ -41,6 +41,459 @@ uint8_t thepalette[768] = // Quake palette 43,175,47,47,159,47,47,143,47,47,127,47,47,111,47,47,95,43,43,79,35,35,63,27,27,47,19,19,31,11,11,15,43,0,0,59,0,0,75,7,0,95,7,0,111,15,0,127,23,7,147,31,7,163,39,11,183,51,15,195,75,27,207,99,43,219,127,59,227,151,79,231,171,95,239,191,119,247,211,139,167,123,59,183,155,55,199,195,55,231,227,87,127,191,255,171,231,255,215,255,255,103,0,0,139,0,0,179,0,0,215,0,0,255,0,0,255,243,147,255,247,199,255,255,255,159,91,83 }; +static bool +StringEndsWith(const std::string &gah, const char *woo) +{ + size_t l = strlen(woo); + size_t gl = gah.length(); + if (gl < l) + return false; + gl-=l; + while(*woo) { + if (tolower(gah.at(gl++)) != *woo++) + return false; + } + return true; +} + + + +//Spike: Basic dds support, with a limited number of pixel formats supported. +typedef struct { + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwFourCC; + + unsigned int bitcount; + unsigned int redmask; + unsigned int greenmask; + unsigned int bluemask; + unsigned int alphamask; +} ddspixelformat_t; + +typedef struct { + unsigned int dwSize; + unsigned int dwFlags; + unsigned int dwHeight; + unsigned int dwWidth; + unsigned int dwPitchOrLinearSize; + unsigned int dwDepth; + unsigned int dwMipMapCount; + unsigned int dwReserved1[11]; + ddspixelformat_t ddpfPixelFormat; + unsigned int ddsCaps[4]; + unsigned int dwReserved2; +} ddsheader_t; +typedef struct { + unsigned int dxgiformat; + unsigned int resourcetype; //0=unknown, 1=buffer, 2=1d, 3=2d, 4=3d + unsigned int miscflag; //singular... yeah. 4=cubemap. + unsigned int arraysize; + unsigned int miscflags2; +} dds10header_t; + +template inline t max (t a, t b) {return (a>b?a:b);} //because macro double-expansion sucks + +static size_t Image_ReadDDSFile(const char *fname, const char *mipname, uint8_t *filedata, size_t filesize, void **out) +{ + size_t lumpsize = 0; + int nummips; + int mipnum; + int datasize; + unsigned int w, h, d; + unsigned int blockwidth, blockheight, blockdepth=1, blockbytes, inblockbytes=0; + const char *encoding; + int layers = 1; + bool swap = false; + + ddsheader_t fmtheader; + dds10header_t fmt10header; + uint8_t *fileend = filedata + filesize; + + if (filesize < sizeof(fmtheader) || *(int*)filedata != (('D'<<0)|('D'<<8)|('S'<<16)|(' '<<24))) + return 0; + + memcpy(&fmtheader, filedata+4, sizeof(fmtheader)); + if (fmtheader.dwSize != sizeof(fmtheader)) + return 0; //corrupt/different version + fmtheader.dwSize += 4; + memset(&fmt10header, 0, sizeof(fmt10header)); + + fmt10header.arraysize = (fmtheader.ddsCaps[1] & 0x200)?6:1; //cubemaps need 6 faces... + + nummips = fmtheader.dwMipMapCount; + if (nummips < 1) + nummips = 1; + + if (!(fmtheader.ddpfPixelFormat.dwFlags & 4)) + { + #define IsPacked(bits,r,g,b,a) fmtheader.ddpfPixelFormat.bitcount==bits&&fmtheader.ddpfPixelFormat.redmask==r&&fmtheader.ddpfPixelFormat.greenmask==g&&fmtheader.ddpfPixelFormat.bluemask==b&&fmtheader.ddpfPixelFormat.alphamask==a + if (IsPacked(24, 0xff0000, 0x00ff00, 0x0000ff, 0)) + encoding = "RGB", blockbytes=3, blockwidth=blockheight=1, swap = true; + else if (IsPacked(24, 0x000000ff, 0x0000ff00, 0x00ff0000, 0)) + encoding = "RGB", blockbytes=3, blockwidth=blockheight=1; + else if (IsPacked(32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000)) + encoding = "RGBA", blockbytes=4, blockwidth=blockheight=1, swap = true; + else if (IsPacked(32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0xff000000)) + encoding = "RGBA", blockbytes=4, blockwidth=blockheight=1; + else if (IsPacked(32, 0x00ff0000, 0x0000ff00, 0x000000ff, 0)) + encoding = "RGB", blockbytes=3, blockwidth=blockheight=1, swap=true, inblockbytes=4; + else if (IsPacked(32, 0x000000ff, 0x0000ff00, 0x00ff0000, 0)) + encoding = "RGB", blockbytes=3, blockwidth=blockheight=1, inblockbytes=4; +// else if (IsPacked(32, 0x000003ff, 0x000ffc00, 0x3ff00000, 0xc0000000)) +// encoding = PTI_A2BGR10; + else if (IsPacked(16, 0xf800, 0x07e0, 0x001f, 0)) + encoding = "565", blockbytes=2, blockwidth=blockheight=1; + else if (IsPacked(16, 0xf800, 0x07c0, 0x003e, 0x0001)) + encoding = "5551", blockbytes=2, blockwidth=blockheight=1; + else if (IsPacked(16, 0xf000, 0x0f00, 0x00f0, 0x000f)) + encoding = "4444", blockbytes=2, blockwidth=blockheight=1; +/* else if (IsPacked( 8, 0x000000ff, 0x00000000, 0x00000000, 0x00000000)) + encoding = "LUM8"; + else if (IsPacked(16, 0x000000ff, 0x00000000, 0x00000000, 0x0000ff00)) + encoding = PTI_L8A8; +*/ else + { + logprint("Unsupported non-fourcc dds in %s\n", fname); + logprint(" bits: %u\n", fmtheader.ddpfPixelFormat.bitcount); + logprint(" red: %08x\n", fmtheader.ddpfPixelFormat.redmask); + logprint("green: %08x\n", fmtheader.ddpfPixelFormat.greenmask); + logprint(" blue: %08x\n", fmtheader.ddpfPixelFormat.bluemask); + logprint("alpha: %08x\n", fmtheader.ddpfPixelFormat.alphamask); + logprint(" used: %08x\n", fmtheader.ddpfPixelFormat.redmask^fmtheader.ddpfPixelFormat.greenmask^fmtheader.ddpfPixelFormat.bluemask^fmtheader.ddpfPixelFormat.alphamask); + return 0; + } +#undef IsPacked + } + else if (*(int*)&fmtheader.ddpfPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('1'<<24))) + encoding = "BC1", blockbytes=8, blockwidth=blockheight=4; //alpha or not? Assume yes, and let the drivers decide. + else if (*(int*)&fmtheader.ddpfPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('2'<<24))) //dx3 with premultiplied alpha + encoding = "BC2", blockbytes=16, blockwidth=blockheight=4; + else if (*(int*)&fmtheader.ddpfPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('3'<<24))) + encoding = "BC2", blockbytes=16, blockwidth=blockheight=4; + else if (*(int*)&fmtheader.ddpfPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('4'<<24))) //dx5 with premultiplied alpha + encoding = "BC3", blockbytes=16, blockwidth=blockheight=4; + else if (*(int*)&fmtheader.ddpfPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('T'<<16)|('5'<<24))) + encoding = "BC3", blockbytes=16, blockwidth=blockheight=4; + else if (*(int*)&fmtheader.ddpfPixelFormat.dwFourCC == (('E'<<0)|('T'<<8)|('C'<<16)|('2'<<24))) + encoding = "ETC2", blockbytes=8, blockwidth=blockheight=4; + else if (*(int*)&fmtheader.ddpfPixelFormat.dwFourCC == (('D'<<0)|('X'<<8)|('1'<<16)|('0'<<24))) + { + //this has some weird extra header with dxgi format types. + memcpy(&fmt10header, filedata+fmtheader.dwSize, sizeof(fmt10header)); + fmtheader.dwSize += sizeof(fmt10header); + switch(fmt10header.dxgiformat) + { + //note: we don't distinguish between ldr+srgb formats here... they might end up darker than intended... + case 0x1c/*DXGI_FORMAT_R8G8B8A8_UNORM*/: + case 0x1d/*DXGI_FORMAT_R8G8B8A8_UNORM_SRGB*/: encoding = "RGBA", blockbytes=4, blockwidth=1, blockheight=1; break; //32bit + case 0x43/*DXGI_FORMAT_R9G9B9E5_SHAREDEXP*/: encoding = "EXP5", blockbytes=4, blockwidth=1, blockheight=1; break; //32bit + case 0x47/*DXGI_FORMAT_BC1_UNORM*/: + case 0x48/*DXGI_FORMAT_BC1_UNORM_SRGB*/: encoding = "BC1", blockbytes=8, blockwidth=4, blockheight=4; break; //4 bit + case 0x4a/*DXGI_FORMAT_BC2_UNORM*/: + case 0x4b/*DXGI_FORMAT_BC2_UNORM_SRGB*/: encoding = "BC2", blockbytes=16, blockwidth=4, blockheight=4; break; //8 bit + case 0x4d/*DXGI_FORMAT_BC3_UNORM*/: + case 0x4e/*DXGI_FORMAT_BC3_UNORM_SRGB*/: encoding = "BC3", blockbytes=16, blockwidth=4, blockheight=4; break; //8 bit + case 0x50/*DXGI_FORMAT_BC4_UNORM*/: encoding = "BC4", blockbytes=8, blockwidth=4, blockheight=4; break; //4 bit + case 0x53/*DXGI_FORMAT_BC5_UNORM*/: encoding = "BC5", blockbytes=16, blockwidth=4, blockheight=4; break; //8 bit + case 0x5f/*DXGI_FORMAT_BC6H_UF16*/: encoding = "BC6", blockbytes=16, blockwidth=4, blockheight=4; break; //8 bit + case 0x62/*DXGI_FORMAT_BC7_UNORM*/: + case 0x63/*DXGI_FORMAT_BC7_UNORM_SRGB*/: encoding = "BC7", blockbytes=16, blockwidth=4, blockheight=4; break; //8 bit + case 134: + case 135: encoding = "AST4", blockbytes=16, blockwidth=4, blockheight=4; break; //8 bit + case 138: + case 139: encoding = "AS54", blockbytes=16, blockwidth=5, blockheight=4; break; //6.4 bit + case 142: + case 143: encoding = "AST5", blockbytes=16, blockwidth=5, blockheight=5; break; //5.12bit + case 146: + case 147: encoding = "AS65", blockbytes=16, blockwidth=6, blockheight=5; break; //4.27bit + case 150: + case 151: encoding = "AST6", blockbytes=16, blockwidth=6, blockheight=6; break; //3.55bit + case 154: + case 155: encoding = "AS85", blockbytes=16, blockwidth=8, blockheight=5; break; //3.2 bit + case 158: + case 159: encoding = "AS86", blockbytes=16, blockwidth=8, blockheight=6; break; //2.67bit + case 162: + case 163: encoding = "AST8", blockbytes=16, blockwidth=8, blockheight=8; break; //2 bit + case 166: + case 167: encoding = "AS05", blockbytes=16, blockwidth=10, blockheight=5; break; //2.56bit + case 170: + case 171: encoding = "AS06", blockbytes=16, blockwidth=10, blockheight=6; break; //2.13bit + case 174: + case 175: encoding = "AS08", blockbytes=16, blockwidth=10, blockheight=8; break; //1.6 bit + case 178: + case 179: encoding = "AST0", blockbytes=16, blockwidth=10, blockheight=10; break; //1.28bit + case 182: + case 183: encoding = "AS20", blockbytes=16, blockwidth=12, blockheight=10; break; //1.07bit + case 186: + case 187: encoding = "AST2", blockbytes=16, blockwidth=12, blockheight=12; break; //0.88bit + default: + logprint("Unsupported dds10 dxgi in %s - %u\n", fname, fmt10header.dxgiformat); + return 0; + } + } + else + { + logprint("Unsupported dds fourcc in %s - \"%c%c%c%c\"\n", fname, + ((char*)&fmtheader.ddpfPixelFormat.dwFourCC)[0], + ((char*)&fmtheader.ddpfPixelFormat.dwFourCC)[1], + ((char*)&fmtheader.ddpfPixelFormat.dwFourCC)[2], + ((char*)&fmtheader.ddpfPixelFormat.dwFourCC)[3]); + return 0; + } + if (!inblockbytes) + inblockbytes = blockbytes; + + if ((fmtheader.ddsCaps[1] & 0x200) && (fmtheader.ddsCaps[1] & 0xfc00) != 0xfc00) + return 0; //cubemap without all 6 faces defined. + + if (fmtheader.dwFlags & 8) + { //explicit pitch flag. we don't support any padding, so this check exists just to be sure none is required. + w = max(1u, fmtheader.dwWidth); + if (fmtheader.dwPitchOrLinearSize != inblockbytes*(w+blockwidth-1)/blockwidth) + return 0; + } + if (fmtheader.dwFlags & 0x80000) + { //linear size flag. we don't support any padding, so this check exists just to be sure none is required. + //linear-size of the top-level mip. + size_t linearsize; + w = max(1u, fmtheader.dwWidth); + h = max(1u, fmtheader.dwHeight); + d = max(1u, fmtheader.dwDepth); + linearsize = ((w+blockwidth-1)/blockwidth)* + ((h+blockheight-1)/blockheight)* + ((d+blockdepth-1)/blockdepth)* + inblockbytes; + if (fmtheader.dwPitchOrLinearSize != linearsize) + return 0; + } + + if (fmtheader.ddsCaps[1] & 0x200) + return 0; //no cubemaps stuff + else if (fmtheader.ddsCaps[1] & 0x200000) + return 0; //no 3d arrays + else + { + if (fmt10header.arraysize != 1) + return 0; //no array textures + } + + filedata += fmtheader.dwSize; + + w = max(1u, fmtheader.dwWidth); + h = max(1u, fmtheader.dwHeight); + d = max(1u, fmtheader.dwDepth); + + lumpsize = sizeof(dmiptex_t); + lumpsize += 20; + for (mipnum = 0; mipnum < nummips; mipnum++) + { + lumpsize += ((w+blockwidth-1)/blockwidth) * ((h+blockheight-1)/blockheight) * ((d+blockdepth-1)/blockdepth) * blockbytes; + w = max(1u, w>>1); + h = max(1u, h>>1); + d = max(1u, d>>1); + } + + + w = max(1u, fmtheader.dwWidth); + h = max(1u, fmtheader.dwHeight); + d = max(1u, fmtheader.dwDepth); + + auto outdata = (uint8_t*)AllocMem(OTHER, lumpsize, false); + *out = outdata; + + //spit out the mip header + { + auto mt = (dmiptex_t*)outdata; + q_snprintf(mt->name, sizeof(mt->name), "%s", mipname); + mt->width = w; + mt->height = h; + //no paletted data. sue me. + mt->offsets[0] = mt->offsets[1] = mt->offsets[2] = mt->offsets[3] = 0; + } outdata += sizeof(dmiptex_t); + + //magic ident, extsize, pixelformat, w, h. + outdata[0]=0x00; + outdata[1]=0xfb; + outdata[2]=0x2b; + outdata[3]=0xaf; outdata+=sizeof(int); + //extsize (little-endian) + *(int*)outdata = lumpsize-(outdata-(uint8_t*)*out); outdata+=sizeof(int); + //pixel format (fourcc) + outdata[0]=encoding[0]; + outdata[1]=encoding[1]; + outdata[2]=encoding[2]; + outdata[3]=encoding[3]; outdata+=sizeof(int); + //width (little-endian) + *(int*)outdata = w; outdata+=sizeof(int); + //height (little-endian) + *(int*)outdata = h; outdata+=sizeof(int); + + //now the mip chain. + //Note: dds groups by layer rather than level. we don't do cubemaps or arrays so w/e. + for (mipnum = 0; mipnum < nummips; mipnum++) + { + datasize = ((w+blockwidth-1)/blockwidth) * ((h+blockheight-1)/blockheight) * (layers*((d+blockdepth-1)/blockdepth)) * blockbytes; + if (swap || blockbytes != inblockbytes) + { + for (size_t p = 0; p < datasize/blockbytes; p++, filedata += inblockbytes, outdata += blockbytes) + { + if (swap) + { //bgr->rgb + outdata[0] = filedata[2]; + outdata[1] = filedata[1]; + outdata[2] = filedata[0]; + } + else + { //just stripping padding. + outdata[0] = filedata[0]; + outdata[1] = filedata[1]; + outdata[2] = filedata[2]; + } + if (blockbytes > 3) + outdata[3] = filedata[3]; + } + } + else + { + memcpy(outdata, filedata, datasize); + filedata += datasize; + outdata += datasize; + } + w = max(1u, w>>1); + h = max(1u, h>>1); + d = max(1u, d>>1); + } + + return lumpsize; +} + +static bool +WADList_LoadImageFile(const char *fname, const char *texname, wad_t *¤t_wadlist) +{ + auto f = fopen(fname, "rb"); + if (!f) + return false; //nope no file. + if (options.fVerbose) + Message(msgLiteral, "Reading image: %s\n", fname); + fseek(f, 0l, SEEK_END); + size_t filesize = ftell(f); + fseek(f, 0l, SEEK_SET); + mlumpinfo_t l = {}; + auto filedata = (uint8_t *)malloc(filesize); + if (filesize != fread(filedata, 1, filesize, f)) + filesize = 0; //something went wrong... + fclose(f); + + if (!l.mip) + l.size = l.disksize = Image_ReadDDSFile(fname, texname, filedata, filesize, &l.mip); + free(filedata); //no longer needed + + if (l.mip) + { + auto newwad = (wad_t *)AllocMem(OTHER, sizeof(wad_t), true); + newwad->file = NULL; + newwad->version = 2; + newwad->header.numlumps = 1; + newwad->lumps = (mlumpinfo_t *)AllocMem(OTHER, sizeof(*newwad->lumps), true); + *newwad->lumps = l; + memcpy(newwad->lumps->name, texname, sizeof(newwad->lumps->name)); + newwad->next = current_wadlist; + current_wadlist = newwad; + return true; + } + else if (options.fVerbose) + Message(msgLiteral, "Unknown format: %s\n", fname); + return false; +} + +static bool +WADList_AddMip(const char *fpath, wad_t *¤t_wadlist) +{ + std::string fullPath; + wad_t wad = {0}; + + if (options.wadPathsVec.empty() || IsAbsolutePath(fpath)) { + fullPath = fpath; + wad.file = fopen(fullPath.c_str(), "rb"); + if (!wad.file) + { + fullPath = std::string(fpath) + ".mip"; + wad.file = fopen(fullPath.c_str(), "rb"); + } + } else { + for (const options_t::wadpath& wadpath : options.wadPathsVec) { + if (*fpath == '*') //turbs are annoying + fullPath = wadpath.path + "/#" + (fpath+1) + ".mip"; + else + fullPath = wadpath.path + "/" + fpath + ".mip"; + wad.file = fopen(fullPath.c_str(), "rb"); + if (wad.file) + break; + + if (*fpath == '*') //turbs are annoying + fullPath = wadpath.path + "/#" + (fpath+1) + ".dds"; + else + fullPath = wadpath.path + "/" + fpath + ".dds"; + if (WADList_LoadImageFile(fullPath.c_str(), fpath, current_wadlist)) + return true; + + char *lpath = strdup(fpath); + bool lowered = false; + for (auto p = lpath; *p; p++) + if (*p >= 'A' && *p <= 'Z') + *p += 'a'-'A', lowered = true; + if (lowered) + { + if (*lpath == '*') //turbs are annoying + fullPath = wadpath.path + "/#" + (lpath+1) + ".dds"; + else + fullPath = wadpath.path + "/" + lpath + ".dds"; + if (WADList_LoadImageFile(fullPath.c_str(), fpath, current_wadlist)) + { + free(lpath); + return true; + } + } + free(lpath); + } + } + + if (wad.file && StringEndsWith(fullPath, ".mip")) { + if (options.fVerbose) + Message(msgLiteral, "Opened MIP: %s\n", fullPath.c_str()); + wad.version = 2; + wad.header.numlumps = 1; + wad.lumps = (mlumpinfo_t *)AllocMem(OTHER, sizeof(*wad.lumps), true); + + dmiptex_t miptex; + fread(&miptex, 1, sizeof(miptex), wad.file); + memcpy(wad.lumps->name, fpath, sizeof(wad.lumps->name)); + wad.lumps->filepos = 0; + fseek(wad.file, 0, SEEK_END); + wad.lumps->size = wad.lumps->disksize = ftell(wad.file); + + auto tex = (texture_t *)AllocMem(OTHER, sizeof(texture_t), true); + tex->next = textures; + textures = tex; + memcpy(tex->name, miptex.name, 16); + tex->name[15] = '\0'; + tex->width = miptex.width; + tex->height = miptex.height; + + + wad_t *newwad = (wad_t *)AllocMem(OTHER, sizeof(wad), true); + memcpy(newwad, &wad, sizeof(wad)); + newwad->next = current_wadlist; + current_wadlist = newwad; + + return true; + } + if (wad.file) + fclose(wad.file); + return false; +} + static bool WAD_LoadInfo(wad_t *wad, bool external) { @@ -48,6 +501,7 @@ WAD_LoadInfo(wad_t *wad, bool external) int i, len, lumpinfosize; dmiptex_t miptex; texture_t *tex; + dlumpinfo_t *lumps; external |= options.fNoTextures; @@ -63,25 +517,44 @@ WAD_LoadInfo(wad_t *wad, bool external) if (!wad->version) return false; - lumpinfosize = sizeof(lumpinfo_t) * hdr->numlumps; + lumpinfosize = sizeof(dlumpinfo_t) * hdr->numlumps; fseek(wad->file, hdr->infotableofs, SEEK_SET); - wad->lumps = (lumpinfo_t *)AllocMem(OTHER, lumpinfosize, true); + wad->lumps = (mlumpinfo_t *)AllocMem(OTHER, sizeof(mlumpinfo_t)*hdr->numlumps, true); + lumps = (dlumpinfo_t *)AllocMem(OTHER, lumpinfosize, true); len = fread(wad->lumps, 1, lumpinfosize, wad->file); if (len != lumpinfosize) - return false; + wad->header.numlumps = 0; /* Get the dimensions and make a texture_t */ for (i = 0; i < wad->header.numlumps; i++) { + wad->lumps[i].filepos = lumps[i].filepos; + wad->lumps[i].disksize = lumps[i].disksize; + wad->lumps[i].size = lumps[i].size; + wad->lumps[i].mip = NULL; + strncpy(wad->lumps[i].name, lumps[i].name, sizeof(lumps[i].name)); + wad->lumps[i].name[sizeof(lumps[i].name)] = 0; + fseek(wad->file, wad->lumps[i].filepos, SEEK_SET); len = fread(&miptex, 1, sizeof(miptex), wad->file); if (len == sizeof(miptex)) { + unsigned int magic; int w = LittleLong(miptex.width); int h = LittleLong(miptex.height); - wad->lumps[i].size = sizeof(miptex) + (w>>0)*(h>>0) + (w>>1)*(h>>1) + (w>>2)*(h>>2) + (w>>3)*(h>>3); + int m; + + wad->lumps[i].size = sizeof(miptex); + for (m = 0; m < 4 && miptex.offsets[m] && wad->lumps[i].size < LittleLong(miptex.offsets[m])+(w>>m)*(h>>m); m++) + wad->lumps[i].size += (w>>m)*(h>>m); if (options.BSPVersion == BSPHLVERSION) wad->lumps[i].size += 2+3*256; //palette size+palette data + + fseek(wad->file, wad->lumps[i].filepos+wad->lumps[i].size, SEEK_SET); + fread(&magic, 1, sizeof(magic), wad->file); + if (LittleLong(magic) == ((0x00<<8)|(0xfb<<8)|(0x2b<<16)|(0xafu<<24)) && !(wad->lumps[i].disksize&3)) //if there's extension data in there then just load it as the size its meant to be instead of messing stuff up. we don't know what's actually in there. + wad->lumps[i].size = wad->lumps[i].disksize; + wad->lumps[i].size = (wad->lumps[i].size+3) & ~3; //keep things aligned if we can. tex = (texture_t *)AllocMem(OTHER, sizeof(texture_t), true); @@ -101,8 +574,8 @@ WAD_LoadInfo(wad_t *wad, bool external) else wad->lumps[i].size = 0; } - - return true; + FreeMem(lumps, OTHER, lumpinfosize); + return wad->header.numlumps>0; } wad_t *WADList_AddWad(const char *fpath, bool external, wad_t *current_wadlist) @@ -174,13 +647,13 @@ WADList_Free(wad_t *wadlist) for (wad = wadlist; wad; wad = next) { next = wad->next; fclose(wad->file); - FreeMem(wad->lumps, OTHER, sizeof(lumpinfo_t) * wad->header.numlumps); + FreeMem(wad->lumps, OTHER, sizeof(mlumpinfo_t) * wad->header.numlumps); FreeMem(wad, OTHER, sizeof(*wad)); } } -static lumpinfo_t * -WADList_FindTexture(const wad_t *wadlist, const char *name) +static mlumpinfo_t * +WADList_FindTexture(wad_t *&wadlist, const char *name) { int i; const wad_t *wad; @@ -190,14 +663,17 @@ WADList_FindTexture(const wad_t *wadlist, const char *name) if (!Q_strcasecmp(name, wad->lumps[i].name)) return &wad->lumps[i]; + if (WADList_AddMip(name, wadlist)) + return &wadlist->lumps[0]; + return NULL; } void -WADList_Process(const wad_t *wadlist) +WADList_Process(wad_t *wadlist) { int i; - lumpinfo_t *texture; + mlumpinfo_t *texture; dmiptexlump_t *miptexlump; struct lumpdata *texdata = &pWorldEnt()->lumps[LUMP_TEXTURES]; @@ -267,6 +743,12 @@ WAD_LoadLump(const wad_t *wad, const char *name, uint8_t *dest) for (i = 0; i < wad->header.numlumps; i++) { if (!Q_strcasecmp(name, wad->lumps[i].name)) { + if (!wad->file) + { //pre-prepared mip. + memcpy(dest, wad->lumps[i].mip, wad->lumps[i].disksize); + return wad->lumps[i].disksize; + } + fseek(wad->file, wad->lumps[i].filepos, SEEK_SET); if (wad->lumps[i].disksize == sizeof(dmiptex_t)) { @@ -280,7 +762,6 @@ WAD_LoadLump(const wad_t *wad, const char *name, uint8_t *dest) if (wad->lumps[i].size != wad->lumps[i].disksize) { - logprint("Texture %s is %i bytes in wad, packed to %i bytes in bsp\n", name, wad->lumps[i].disksize, wad->lumps[i].size); std::vector data(wad->lumps[i].disksize); size = fread(data.data(), 1, wad->lumps[i].disksize, wad->file); if (size != wad->lumps[i].disksize) @@ -324,7 +805,7 @@ WAD_LoadLump(const wad_t *wad, const char *name, uint8_t *dest) } static void -WADList_AddAnimationFrames(const wad_t *wadlist) +WADList_AddAnimationFrames(wad_t *wadlist) { int oldcount, i, j;