// //--------------------------------------------------------------------------- // // 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/ // //-------------------------------------------------------------------------- // #ifdef _MSC_VER #include #else // !_MSC_VER #include #include #endif // _MSC_VER #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); #ifndef _MSC_VER static char* strlwr(char* str) { for (char* ch = str; '\0' != *ch; ++ch) { *ch = tolower(*ch); } return str; } static char* strupr(char* str) { for (char* ch = str; '\0' != *ch; ++ch) { *ch = toupper(*ch); } return str; } static int mkdir(const char *path) { return mkdir(path, 0755); } #endif // !_MSC_VER 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;iName()[7]); bool nulltex = !strnicmp(pTex->Name(), "TEXTURE1", 8); GenerateTextureFile(buffer, (const char*)pTex->Address(), (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[100000], bt2[100000], bpn[100000]; if (ft1) fread(bt1, 1, 100000, ft1), fclose(ft1); if (ft2) fread(bt2, 1, 100000, ft2), fclose(ft2); if (pnm) fread(bpn, 1, 100000, pnm), fclose(pnm); if (ft1) GenerateTextureFile("textures.txt", bt1, bpn, 0, true); if (ft2) GenerateTextureFile("textures.txt2", bt2, bpn, 0, false); } //========================================================================== // // // //========================================================================== 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 (fileinfo[i].Size != fread(&buffer[0], 1, fileinfo[i].Size, f)) return; FILE* fout = fopen(fileinfo[i].NameWithZero, "wb"); if (fout) { fwrite(&buffer[0], 1, fileinfo[i].Size, fout); fclose(fout); } } exit(1); }