// //--------------------------------------------------------------------------- // // Copyright(C) 2016 Christoph Oelckers // All rights reserved. // // 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 3 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. // // You should have received a copy of the GNU General Public License // along with this program. If not, see http://www.gnu.org/licenses/ // //-------------------------------------------------------------------------- // #include #include #include #include "wadext.h" #include "resourcefile.h" #include "fileformat.h" #pragma warning(disable:4996) bool isLevel(WadItemList * we); char profile[256]; char maindir[128]; bool pstartfound=false; WadItemList PNames(-1); char * getdir(const char * lump) { static char d[2]=""; if (*lump>='A' && *lump<='Z') d[0]=*lump; else if (*lump>='a' && *lump<='z') d[0]=*lump-32; else if (*lump>='0' && *lump<='9') d[0]='0'; else d[0]='@'; return d; } bool isPatch(const char * n) { if (!pstartfound || PNames.mLump < 0) return false; uint32_t * l = (uint32_t*)PNames.Address(); char * c = (char*)(l + 1); char nn[9]; if (!l) return false; strncpy(nn, n, 8); nn[8] = 0; strupr(nn); for (unsigned i = 0; i<*l; i++) { if (!strncmp(c, nn, 8)) return true; c += 8; } return false; } const char * MakeFileName(const char * base, const char * ext) { char basebuffer[20]; static char buffer[40]; strncpy(basebuffer,base,8); basebuffer[8]=0; for (int i = 0; i < 8; i++) { // replace special path characters in the lump name if (basebuffer[i] == '\\') basebuffer[i] = '^'; if (basebuffer[i] == '/') basebuffer[i] = '\xA7'; if (basebuffer[i] == ':') basebuffer[i] = '\xA1'; if (basebuffer[i] == '*') basebuffer[i] = '\xB2'; if (basebuffer[i] == '?') basebuffer[i] = '\xBF'; } for(int i=0;;i++) { if (i > 0) sprintf(buffer,"%s%s(%d)", basebuffer, ext, i); else sprintf(buffer, "%s%s", basebuffer, ext); strlwr(buffer); FILE *f = fopen(buffer, "rb"); if (f == NULL) return buffer; fclose(f); } } void Extract(WadItemList *w,const char * dir,const char * ext, int options, bool isflat) //It is completely impossible to detect flats, so we have to flag it when extracting the F_ namespace { FileType ft = IdentifyFileType(w->Name(), (uint8_t*)w->Address(), w->Length()); const char *filename; if (dir == NULL) { switch (ft.type & FG_MASK) { case FG_GFX: if (isPatch(w->Name())) dir = "patches"; else dir = "graphics"; break; case FG_SND: dir = "sounds"; break; case FG_MUS: dir = "music"; break; case FG_VOX: dir = "voxels"; break; } } // Flats are easy to misidentify so if we are in the flat namespace and the size is exactly 4096 bytes, let it pass as flat if (isflat && (ft.type & FG_MASK) != FG_GFX && (ft.type == FT_DOOMSND || ft.type == FT_MP3 || w->Length() == 4096)) { ft.type = FT_BINARY; ft.extension = ".LMP"; } if (dir != NULL) { mkdir(dir); chdir(dir); } printf("processing %.8s\n", w->Name()); if (ft.type == FT_DOOMGFX && !(options & NO_CONVERT_GFX)) { filename = MakeFileName(w->Name(), ".png"); PatchToPng(options, (const uint8_t*)w->Address(), w->Length(), filename); } else if (w->Length() > 0 && ft.type == FT_BINARY && !(options & NO_CONVERT_GFX) && (isflat || (w->Length() == 64000 && options&(DO_HEXEN_PAL | DO_HERETIC_PAL)))) { filename = MakeFileName(w->Name(), ".png"); FlatToPng(options, (const uint8_t*)w->Address(), w->Length(), filename); } else if (ft.type == FT_DOOMSND && !(options & NO_CONVERT_SND)) { filename = MakeFileName(w->Name(), ".wav"); DoomSndToWav((const uint8_t*)w->Address(), w->Length(), filename); } else { filename = MakeFileName(w->Name(), ft.extension); FILE *f = fopen(filename, "wb"); if (f == NULL) { printf("%s: Unable to create file\n", filename); chdir(maindir); return; } fwrite(w->Address(), 1, w->Length(), f); fclose(f); } chdir(maindir); } int ListExtract(int startmarker,const char * dir,const char * endm,const char * endm2,bool isflat,int options,int pack=0) { char dirbuf[200]; mkdir(dir); strcpy(dirbuf,dir); WadItemList ww(startmarker + 1); WadItemList *w = &ww; while (strncmp(w->Name(),endm,8) && strncmp(w->Name(),endm2,8)) { Extract(w, dir, NULL, options, isflat); ww = ww.mLump + 1; } return ww.mLump; } bool isLevel(int lump) { if (lump >= mainwad->NumLumps() - 3) return false; // any level is at least 3 lumps WadItemList w(lump + 1); if (!stricmp(w.Name(), "THINGS")) return true; if (!stricmp(w.Name(), "TEXTMAP")) return true; return false; } int MapExtract(int startmarker,const char *dir,int options) { int i; char buffer[40]; TArray directory; mkdir(dir); chdir(dir); WadItemList ww(startmarker); sprintf(buffer,"%.8s.wad", ww.Name()); printf("processing %s\n",buffer); FILE *f=fopen(buffer, "wb"); fwrite("PWAD\xb\0\0\0\0\0\0\0",1, 12, f); CWADLump lmp; lmp.offset = ftell(f); strncpy(lmp.name, ww.Name(), 8); lmp.length = ww.Length(); fwrite(ww.Address(), 1, ww.Length(), f); directory.Push(lmp); ww = ww.mLump + 1; if (!stricmp(ww.Name(), "TEXTMAP")) { // UDMF while (true) { if (!(options & DO_STRIP) || (stricmp(ww.Name(), "ZNODES") && stricmp(ww.Name(), "BLOCKMAP") && stricmp(ww.Name(), "REJECT"))) { CWADLump lmp; lmp.offset = ftell(f); strncpy(lmp.name, ww.Name(), 8); lmp.length = ww.Length(); fwrite(ww.Address(), 1, ww.Length(), f); directory.Push(lmp); } if (!stricmp(ww.Name(), "ENDMAP")) { goto finalize; } int lump = ww.mLump + 1; if (lump == mainwad->NumLumps()) { printf("Unexpected end of UDMF map found\n"); return lump; } ww = lump; } } else { static const char *maplumps[] = { "THINGS", "LINEDEFS", "SIDEDEFS", "VERTEXES", "SEGS", "SSECTORS", "NODES", "SECTORS", "REJECT", "BLOCKMAP", "BEHAVIOR", "SCRIPTS" }; for (i = 0; i < 12; i++) { if (ww.Name() != NULL && !stricmp(ww.Name(), maplumps[i])) { CWADLump lmp; lmp.offset = ftell(f); strncpy(lmp.name, ww.Name(), 8); if ((options & DO_STRIP) && (i == 4 || i == 5 || i == 6 || i == 8 || i == 9)) { lmp.length = 0; } else { lmp.length = ww.Length(); fwrite(ww.Address(), 1, ww.Length(), f); } directory.Push(lmp); ww = ww.mLump + 1; } } } finalize: uint32_t lumpc = directory.Size(); uint32_t dpos = ftell(f); for (auto &p : directory) { fwrite(&p, 1, 16, f); } fseek(f, 4, 0); fwrite(&lumpc, 1, 4, f); fwrite(&dpos, 1, 4, f); fclose(f); chdir(".."); return directory.Size(); } typedef char pname[8]; bool AddWallTexture(FILE * fo,maptexture_t * wt,pname * pNames, bool first) { int k; char buffer[9]; sprintf(buffer,"%.8s",wt->name); strupr(buffer); bool cc = strspn(buffer, "ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789") < strlen(buffer) || *buffer <= '9'; fprintf(fo,"WallTexture %s%s%s, %d, %d\n{\n",cc? "\"":"", buffer,cc? "\"":"", wt->width,wt->height); if (wt->ScaleX) fprintf(fo,"\tXScale %f\n",wt->ScaleX/8.0f); if (wt->ScaleY) fprintf(fo,"\tYScale %f\n",wt->ScaleY/8.0f); if (wt->Flags&0x8000) fprintf(fo,"\tWorldPanning\n"); if (first) fprintf(fo,"\tNullTexture\n"); for(k=0;kpatchcount;k++) { sprintf(buffer,"%.8s",pNames[wt->patches[k].patch]); strupr(buffer); cc = strspn(buffer, "ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789") < strlen(buffer) || *buffer <= '9'; fprintf(fo,"\tPatch %s%.8s%s, %d, %d\n",cc? "\"":"", buffer,cc? "\"":"", wt->patches[k].originx,wt->patches[k].originy); } fprintf(fo,"}\n"); return true; } bool AddWallTexture(FILE * fo,strifemaptexture_t * wt,pname * pNames, bool first) { int k; char buffer[9]; sprintf(buffer,"%.8s",wt->name); strupr(buffer); bool cc = strspn(buffer, "ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789") < strlen(buffer) || *buffer <= '9'; fprintf(fo,"WallTexture %s%s%s, %d, %d\n{\n",cc? "\"":"", buffer,cc? "\"":"", wt->width,wt->height); if (wt->ScaleX) fprintf(fo,"\tXScale %f\n",wt->ScaleX/8.0f); if (wt->ScaleY) fprintf(fo,"\tYScale %f\n",wt->ScaleY/8.0f); if (wt->Flags&0x8000) fprintf(fo,"\tWorldPanning\n"); for(k=0;kpatchcount;k++) { sprintf(buffer,"%.8s",pNames[wt->patches[k].patch]); strupr(buffer); cc = strspn(buffer, "ABCDEFGHIJKLMNOPQRSTUVWXYZ_0123456789") < strlen(buffer) || *buffer <= '9'; fprintf(fo,"\tPatch %s%.8s%s, %d, %d\n",cc? "\"":"", buffer,cc? "\"":"", wt->patches[k].originx,wt->patches[k].originy); } fprintf(fo,"}\n"); return true; } void AnimExtract(WadItemList *w) { int i,numentries; char * c; char buffer[30]; mkdir("decompiled"); chdir("decompiled"); strcpy(buffer,"animdefs.animated"); printf("processing %s\n",buffer); numentries=w->Length()/23; c=(char*)w->Address(); FILE * fo=fopen(buffer,"wt"); for(i=0;iLength()/20; c=(char*)w->Address(); FILE * fo=fopen(buffer,"wt"); for(i=0;i maxoff) { printf("%s: Bad texture directory\n", name); return; } maptexture_t* tex = (maptexture_t*)((uint8_t*)maptex + offset); // There is bizzarely a Doom editing tool that writes to the // first two elements of columndirectory, so I can't check those. if (tex->patchcount < 0 || tex->columndirectory[2] != 0 || tex->columndirectory[3] != 0) { isStrife = true; break; } } if (!isStrife) { for(int i=0;iName()[7]); bool nulltex = !strnicmp(pTex->Name(), "TEXTURE1", 8); GenerateTextureFile(buffer, (const char*)pTex->Address(), pTex->Length(), (const char *)pPNam->Address(), options, nulltex); } //========================================================================== // // IsSeparator (taken from ZDoom) // // Returns true if the character is a path separator. // //========================================================================== static inline bool IsSeparator(int c) { if (c == '/') return true; #ifdef WIN32 if (c == '\\' || c == ':') return true; #endif return false; } //========================================================================== // // ExtractFileBase (taken from ZDoom) // // Returns the file part of a pathname, optionally including the extension. // //========================================================================== std::string ExtractFileBase(const char *path, bool include_extension) { const char *src, *dot; src = path + strlen(path) - 1; if (src >= path) { // back up until a / or the start while (src != path && !IsSeparator(*(src - 1))) src--; // Check for files with drive specification but no path #if defined(_WIN32) if (src == path && src[0] != 0) { if (src[1] == ':') src += 2; } #endif if (!include_extension) { dot = src; while (*dot && *dot != '.') { dot++; } return std::string(src, dot - src); } else { return std::string(src); } } return std::string(); } void ExtractWad(char * wadfilename,int options) { WadItemList pTex1(-1), pTex2(-1), pPnam(-1); OpenMainWad(wadfilename); auto name = ExtractFileBase(wadfilename, false); mkdir(name.c_str()); chdir(name.c_str()); PNames = mainwad->FindLump("PNAMES"); getcwd(maindir, 128); for(int i=0;iNumLumps();i++) { WadItemList ww(i); WadItemList *w = &ww; { if (!strcmp(w->Name(),".")) { w->Release(); } else if (!strnicmp(w->Name(),"S_START",8) || !strnicmp(w->Name(),"SS_START",8) ) { i=ListExtract(i,"SPRITES","S_END","SS_END",false,options,false); } else if (!strnicmp(w->Name(),"F_START",8) || !strnicmp(w->Name(),"FF_START",8) ) { i=ListExtract(i,"FLATS","F_END","FF_END",true,options,false); } else if (!strnicmp(w->Name(),"C_START",8)) { i=ListExtract(i,"COLORMAPS","C_END","CC_END",false,false,options); } else if (!strnicmp(w->Name(),"A_START",8)) { i=ListExtract(i,"ACS","A_END","AA_END",false,false,options); } else if (!strnicmp(w->Name(),"P_START",8) || !strnicmp(w->Name(),"PP_START",8)) { pstartfound=true; } else if (!strnicmp(w->Name(),"P_END",8) || !strnicmp(w->Name(),"PP_END",8)) // ignore { } else if (!strnicmp(w->Name(),"TX_START",8)) { i=ListExtract(i,"TEXTURES","TX_END","TX_END",false,options); } else if (!strnicmp(w->Name(),"HI_START",8)) { i=ListExtract(i,"HIRES","HI_END","HI_END",false,options); } else if (!strnicmp(w->Name(),"VX_START",8)) { i=ListExtract(i,"VOXELS","VX_END","VX_END",false,false,options); } else if (!strnicmp(w->Name(),"GL_VERT",8)) // ignore { // skip GL nodes } else if (!strnicmp(w->Name(),"GL_SEGS",8)) // ignore { // skip GL nodes } else if (!strnicmp(w->Name(),"GL_SSECT",8)) // ignore { // skip GL nodes } else if (!strnicmp(w->Name(),"GL_NODES",8)) // ignore { // skip GL nodes } else if (!strnicmp(w->Name(), "GL_PVS", 8)) // ignore { // skip GL nodes } else if (isLevel(i)) { i += MapExtract(i,"MAPS",options) - 1; } else { if (!strnicmp(w->Name(), "TEXTURE1", 8)) pTex1 = *w; else if (!strnicmp(w->Name(), "TEXTURE2", 8)) pTex2 = *w; else if (!strnicmp(w->Name(), "PNAMES", 8)) pPnam = *w; else if (!strnicmp(w->Name(), "ANIMATED", 8)) { AnimExtract(w); } else if (!strnicmp(w->Name(), "SWITCHES", 8)) { SwitchExtract(w); } Extract(w, NULL, NULL, options, false); } if (pTex1.mLump >= 0 && pPnam.mLump >= 0) { GenerateTextureFile(&pTex1,&pPnam, options); pTex1 = -1; } if (pTex2.mLump >= 0 && pPnam.mLump >= 0) { GenerateTextureFile(&pTex2,&pPnam, options); pTex2=-1; } } } chdir(".."); } void ConvertTextureX() { FILE* ft1 = fopen("texture1", "rb"); if (!ft1) ft1 = fopen("texture1.lmp", "rb"); FILE* ft2 = fopen("texture2", "rb"); if (!ft2) ft2 = fopen("texture2.lmp", "rb"); FILE* pnm = fopen("pnames", "rb"); if (!pnm) pnm = fopen("pnames.lmp", "rb"); if (!pnm) exit(1); static char bt1[1000000], bpn[1000000]; fread(bpn, 1, 1000000, pnm), fclose(pnm); if (ft1) { int l = (int)fread(bt1, 1, 1000000, ft1); fclose(ft1); GenerateTextureFile("textures.txt", bt1, l, bpn, 0, true); } if (ft2) { int l = (int)fread(bt1, 1, 1000000, ft2); fclose(ft2); GenerateTextureFile("textures.txt2", bt1, l, bpn, 0, false); } } //========================================================================== // // CreatePath // // Creates a directory including all levels necessary // //========================================================================== #ifdef _WIN32 void CreatePath(const char* fn) { char drive[_MAX_DRIVE]; char dir[_MAX_DIR]; _splitpath_s(fn, drive, sizeof drive, dir, sizeof dir, nullptr, 0, nullptr, 0); if ('\0' == *dir) { // Root/current/parent directory always exists return; } char path[_MAX_PATH]; _makepath_s(path, sizeof path, drive, dir, nullptr, nullptr); if ('\0' == *path) { // No need to process empty relative path return; } // Remove trailing path separator(s) for (size_t i = strlen(path); 0 != i; --i) { char& lastchar = path[i - 1]; if ('/' == lastchar || '\\' == lastchar) { lastchar = '\0'; } else { break; } } // Create all directories for given path if ('\0' != *path) { CreatePath(path); _mkdir(path); // PAKs are ASCII only, so we won't need Unicode extension. } } #else void CreatePath(const char* fn) { char* copy, * p; if (fn[0] == '/' && fn[1] == '\0') { return; } p = copy = strdup(fn); do { p = strchr(p + 1, '/'); if (p != NULL) { *p = '\0'; } mkdir(copy, 0755); if (p != NULL) { *p = '/'; } } while (p); free(copy); } #endif //========================================================================== // // // //========================================================================== struct GrpInfo { uint32_t Magic[3]; uint32_t NumLumps; }; struct GrpLump { union { struct { char Name[12]; uint32_t Size; }; char NameWithZero[13]; }; }; void GrpExtract(const char* filename, FILE* f) { TArray fileinfo; GrpInfo header; if (1 != fread(&header, sizeof(header), 1, f)) return; fileinfo.Resize(header.NumLumps); if (header.NumLumps != fread(&fileinfo[0], sizeof(GrpLump), header.NumLumps, f)) return; if (memcmp(header.Magic, "KenSilverman", 12)) { return; } auto name = ExtractFileBase(filename, false); mkdir(name.c_str()); chdir(name.c_str()); TArray buffer; for (uint32_t i = 0; i < header.NumLumps; i++) { buffer.Resize(fileinfo[i].Size); fileinfo[i].NameWithZero[12] = '\0'; // Be sure filename is null-terminated if (buffer.Size() != fread(&buffer[0], 1, buffer.Size(), f)) return; FILE* fout = fopen(fileinfo[i].NameWithZero, "wb"); if (fout) { fwrite(&buffer[0], 1, fileinfo[i].Size, fout); fclose(fout); } } exit(1); } //========================================================================== // // // //========================================================================== struct dpackfile_t { char name[56]; uint32_t filepos, filelen; }; struct dpackheader_t { uint32_t ident; // == IDPAKHEADER uint32_t dirofs; uint32_t dirlen; }; void PakExtract(const char* filename, FILE* f) { TArray fileinfo; dpackheader_t header; if (1 != fread(&header, sizeof(header), 1, f)) return; if (memcmp(&header.ident, "PACK", 4)) { return; } fseek(f, header.dirofs, SEEK_SET); uint32_t NumLumps = header.dirlen / sizeof(dpackfile_t); fileinfo.Resize(NumLumps); if (NumLumps != fread(&fileinfo[0], sizeof(dpackfile_t), NumLumps, f)) return; auto name = ExtractFileBase(filename, false); mkdir(name.c_str()); chdir(name.c_str()); TArray buffer; for (uint32_t i = 0; i < NumLumps; i++) { buffer.Resize(fileinfo[i].filelen); fseek(f, fileinfo[i].filepos, SEEK_SET); if (buffer.Size() != fread(&buffer[0], 1, buffer.Size(), f)) return; CreatePath(fileinfo[i].name); FILE* fout = fopen(fileinfo[i].name, "wb"); if (fout) { fwrite(&buffer[0], 1, fileinfo[i].filelen, fout); fclose(fout); } } exit(1); }