/* iff2html - iff web page description generator iff2html.c - Copyright (c) 2012 Niotso Project Author(s): Fatbag Ahmed El-Mahdawy Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include "md5.h" #include "image.h" #ifndef min #define min(x,y) ((x) < (y) ? (x) : (y)) #endif #ifndef max #define max(x,y) ((x) > (y) ? (x) : (y)) #endif static void printsize(FILE * hFile, size_t FileSize){ /* For our purposes, our units are best described in kB and MB, if not bytes */ size_t temp = FileSize; unsigned position = 1; if(FileSize >= 1048576) fprintf(hFile, "%.1f MB (", (float)FileSize/1048576); else fprintf(hFile, "%.1f kB (", (float)FileSize/1024); while((temp/=1000) != 0) position *= 1000; fprintf(hFile, "%u", (unsigned) FileSize/position); FileSize -= (FileSize/position)*position; while((position/=1000) != 0){ fprintf(hFile, ",%.3u", (unsigned) FileSize/position); FileSize -= (FileSize/position)*position; } fprintf(hFile, " bytes)"); } int main(int argc, char *argv[]){ unsigned c; int slash; FILE * hFile; int overwrite = 0; char *InFile, *OutFile = NULL, *FileName, *OutDir = NULL; size_t FileSize; struct MD5Context md5c; unsigned char digest[16]; uint8_t * IFFData; IFFFile IFFFileInfo; IFFChunk * ChunkData; if(argc == 1 || !strcmp(argv[1], "-h") || !strcmp(argv[1], "--help")){ printf("Usage: iff2html [-f] infile (outfile)\n" "Produce an HTML webpage describing an EA IFF file.\n" "Use -f to force overwriting without confirmation.\n" "If outfile is unspecified, file.iff will output to file.html.\n" "\n" "Report bugs to .\n" "iff2html is maintained by the Niotso project.\n" "Home page: \n"); return 0; } if(argc >= 4 && !strcmp(argv[1], "-f")){ overwrite++; InFile = argv[2]; OutFile = argv[3]; }else if(argc == 3){ if(!strcmp(argv[1], "-f")){ overwrite++; InFile = argv[2]; }else{ InFile = argv[1]; OutFile = argv[2]; } }else InFile = argv[1]; if(OutFile == NULL){ unsigned length = strlen(InFile); OutFile = malloc(max(length+2, 6)); strcpy(OutFile, InFile); strcpy(max(OutFile+length-4, OutFile), ".html"); } for(c=0, slash=-1; OutFile[c]; c++) if(OutFile[c] == '/' || OutFile[c] == '\\') slash = c; if(slash >= 0){ OutDir = malloc(slash+2); memcpy(OutDir, OutFile, slash+1); OutDir[slash+1] = 0x00; }else OutDir = ""; for(c=0, slash=-1; InFile[c]; c++) if(InFile[c] == '/' || InFile[c] == '\\') slash = c; FileName = InFile + slash + 1; /**** ** Open the file and read in entire contents to memory */ hFile = fopen(InFile, "rb"); if(hFile == NULL){ printf("%sThe specified input file does not exist or could not be opened for reading.", "iff2html: error: "); return -1; } fseek(hFile, 0, SEEK_END); FileSize = ftell(hFile); if(FileSize < 64){ fclose(hFile); printf("%sNot a valid IFF file.", "iff2html: error: "); return -1; } fseek(hFile, 0, SEEK_SET); IFFData = malloc(FileSize); if(IFFData == NULL){ fclose(hFile); printf("%sMemory for this file could not be allocated.", "iff2html: error: "); return -1; } if(fread(IFFData, 1, FileSize, hFile) != FileSize){ fclose(hFile); printf("%sThe input file could not be read.", "iff2html: error: "); return -1; } fclose(hFile); /**** ** Load header information */ if(!iff_create(&IFFFileInfo)){ printf("%sMemory for this file could not be allocated.", "iff2html: error: "); return -1; } if(!iff_read_header(&IFFFileInfo, IFFData, FileSize)){ printf("%sNot a valid IFF file.", "iff2html: error: "); return -1; } /**** ** Load entry information */ if(!iff_enumerate_chunks(&IFFFileInfo, IFFData+64, FileSize-64)){ printf("%sChunk data is corrupt.", "iff2html: error: "); return -1; } /* Calculate the MD5, and then we can free the IFF data because we're done with it */ MD5Init(&md5c); MD5Update(&md5c, IFFData, FileSize); MD5Final(digest, &md5c); free(IFFData); for(c = 0, ChunkData = IFFFileInfo.Chunks; c < IFFFileInfo.ChunkCount; c++, ChunkData++) iff_parse_chunk(ChunkData, ChunkData->Data); /**** ** Open the output file and write the header */ if(!overwrite){ hFile = fopen(OutFile, "rb"); if(hFile != NULL){ /* File exists */ char c; fclose(hFile); printf("File \"%s\" exists.\nContinue anyway? (y/n) ", OutFile); c = getchar(); if(c != 'y' && c != 'Y'){ printf("\nAborted."); return -1; } } } hFile = fopen(OutFile, "wb"); if(hFile == NULL){ printf("%sThe output file could not be opened for writing.", "iff2html: error: "); return -1; } /**** ** We're splitting fprintf by line to guarantee compatibility; ** even C99 compilers are only required to support 4096 byte strings in printf()-related functions */ fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n", FileName); fprintf(hFile, "\n"); fprintf(hFile, "%s (iff2html)\n", FileName); fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "

%s

\n", FileName); fprintf(hFile, "
\n"); fprintf(hFile, "
"); for(c=0; c<16; c++) fprintf(hFile, "%.2x", digest[c]); fprintf(hFile, " (md5), "); printsize(hFile, FileSize); fprintf(hFile, "
\n"); fprintf(hFile, "
Dumped by iff2html.
\n"); fprintf(hFile, "\n"); fprintf(hFile, "
Contents – %u chunks
\n", IFFFileInfo.ChunkCount); fprintf(hFile, "
    \n"); for(c=1, ChunkData = IFFFileInfo.Chunks; c <= IFFFileInfo.ChunkCount; c++, ChunkData++) fprintf(hFile, "
  • %u [%s] (%.4X)%s%s
  • \n", c, ChunkData->ChunkID, c, ChunkData->Type, ChunkData->ChunkID, (ChunkData->Label[0] != 0x00) ? " – " : "", ChunkData->Label); fprintf(hFile, "
\n"); fprintf(hFile, "
\n"); fprintf(hFile, "\n"); for(c=0, ChunkData = IFFFileInfo.Chunks; c < IFFFileInfo.ChunkCount; c++, ChunkData++){ fprintf(hFile, "

%u [%s] (%.4X)%s%s (Jump)

\n", c+1, ChunkData->ChunkID, c+1, ChunkData->Type, ChunkData->ChunkID, (ChunkData->Label[0] != 0x00) ? " – " : "", ChunkData->Label, c+1, ChunkData->ChunkID); fprintf(hFile, "
\n"); if(ChunkData->FormattedData == NULL){ int success = 0; /* The iff library does not parse BMP_ or FBMP chunks */ if(!strcmp(ChunkData->Type, "BMP_") || !strcmp(ChunkData->Type, "FBMP")){ int bmp = !strcmp(ChunkData->Type, "BMP_"); size_t Width, Height; char filename[32]; sprintf(filename, "%s%s_%u_%.4x.png", OutDir, bmp ? "bmp" : "fbmp", c+1, ChunkData->ChunkID); if(WritePNG(filename, ChunkData, 0, NULL, &Width, &Height)){ fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n", bmp ? "bmp" : "fbmp", c+1, ChunkData->ChunkID, (unsigned) Width, (unsigned) Height); fprintf(hFile, "
Image
\"\"
\n"); success++; } } if(!success) fprintf(hFile, "The contents of this chunk could not be parsed.\n"); }else if(!strcmp(ChunkData->Type, "STR#") || !strcmp(ChunkData->Type, "CTSS") || !strcmp(ChunkData->Type, "FAMs") || !strcmp(ChunkData->Type, "TTAs") || !strcmp(ChunkData->Type, "CST") ){ /**** ** STR# parsing */ IFFString * StringData = ChunkData->FormattedData; fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "
Format:"); switch(StringData->Format){ case 0: fprintf(hFile, "00 00 (0)"); break; case -1: fprintf(hFile, "FF FF (−1)"); break; case -2: fprintf(hFile, "FE FF (−2)"); break; case -3: fprintf(hFile, "FD FF (−3)"); break; case -4: fprintf(hFile, "FC FF (−4)"); break; default: fprintf(hFile, "Unrecognized"); break; } fprintf(hFile, "
\n"); if(StringData->Format >= -4 && StringData->Format <= 0){ unsigned LanguageSet; const char * LanguageStrings[] = { "English (US)", "English (International)", "French", "German", "Italian", "Spanish", "Dutch", "Danish", "Swedish", "Norwegian", "Finnish", "Hebrew", "Russian", "Portuguese", "Japanese", "Polish", "Simplified Chinese", "Traditional Chinese", "Thai", "Korean" }; fprintf(hFile, "
\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); for(LanguageSet=0; LanguageSet<20; LanguageSet++){ IFFStringPair * Pair; unsigned PairIndex; if(StringData->LanguageSets[LanguageSet].PairCount == 0) continue; fprintf(hFile, "\n", StringData->LanguageSets[LanguageSet].PairCount, LanguageStrings[LanguageSet]); for(PairIndex=1, Pair = StringData->LanguageSets[LanguageSet].Pairs; PairIndex <= StringData->LanguageSets[LanguageSet].PairCount; PairIndex++, Pair++){ if(PairIndex != 1) fprintf(hFile, ""); fprintf(hFile, "\n", PairIndex, (Pair->Key) != NULL ? Pair->Key : "", (Pair->Value) != NULL ? Pair->Value : ""); } } fprintf(hFile, "
LanguageString pairs
%s
%u%s%s
\n"); } }else if(!strcmp(ChunkData->Type, "CATS")){ /**** ** Regular string pair */ IFFStringPair * Pair = ChunkData->FormattedData; fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n", (Pair->Key) != NULL ? Pair->Key : "", (Pair->Value) != NULL ? Pair->Value : ""); fprintf(hFile, "
KeyValue
%s%s
\n"); }else if(!strcmp(ChunkData->Type, "FWAV") || !strcmp(ChunkData->Type, "GLOB")){ /**** ** Regular string */ fprintf(hFile, "\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n", ChunkData->FormattedData ? (char*) ChunkData->FormattedData : ""); fprintf(hFile, "
String
%s
\n"); }else if(!strcmp(ChunkData->Type, "BCON")){ /**** ** BCON parsing */ IFF_BCON * BCONData = ChunkData->FormattedData; fprintf(hFile, "\n"); fprintf(hFile, "\n", BCONData->Flags, BCONData->Flags); fprintf(hFile, "
Flags:%02X (%u)
\n"); if(BCONData->ConstantCount > 0){ unsigned i; fprintf(hFile, "
\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); for(i=0; iConstantCount; i++) fprintf(hFile, "\n", i+1, BCONData->Constants[i]); fprintf(hFile, "
Constant Value
%u%u
\n"); } }else if(!strcmp(ChunkData->Type, "FCNS")){ /**** ** FCNS parsing */ IFFConstantList * List = ChunkData->FormattedData; fprintf(hFile, "\n"); fprintf(hFile, "\n", List->Version); fprintf(hFile, "
Version:%u
\n"); if(List->ConstantCount > 0){ IFFConstant * Constant; unsigned i; fprintf(hFile, "
\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); for(i=0, Constant=List->Constants; iConstantCount; i++, Constant++) fprintf(hFile, "\n", i+1, Constant->Name ? Constant->Name : "", Constant->Value, Constant->Description ? Constant->Description : ""); fprintf(hFile, "
NameValueDescription
%u%s%g%s
\n"); } }else if(!strcmp(ChunkData->Type, "TMPL")){ /**** ** TMPL parsing */ IFFTemplate * Template = ChunkData->FormattedData; IFFTemplateField * Field; unsigned i; fprintf(hFile, "\n"); fprintf(hFile, "\n"); for(i=0, Field=Template->Fields; iFieldCount; i++, Field++) fprintf(hFile, "\n", i+1, Field->Name ? Field->Name : "", Field->Type ? Field->Type : ""); fprintf(hFile, "
NameType
%u%s%s
\n"); }else if(!strcmp(ChunkData->Type, "TRCN")){ /**** ** TRCN parsing */ IFFRangeSet * RangeSet = ChunkData->FormattedData; fprintf(hFile, "\n"); fprintf(hFile, "\n", RangeSet->Version); fprintf(hFile, "
Version:%u
\n"); if(RangeSet->RangeCount > 0){ unsigned i; IFFRangeEntry * Range; fprintf(hFile, "
\n"); fprintf(hFile, "\n"); fprintf(hFile, "" "\n"); for(i=0, Range=RangeSet->Ranges; iRangeCount; i++, Range++) fprintf(hFile, "\n", i+1, Range->IsUsed ? "Yes" : "No", Range->DefaultValue, Range->Name ? Range->Name : "", Range->Comment ? Range->Comment : "", Range->Enforced ? "Yes" : "No", Range->RangeMin, Range->RangeMax); fprintf(hFile, "
In useDefault valueNameCommentRange is enforcedMinimumMaximum
%u%s%u%s%s%s%u%u
\n"); } }else if(!strcmp(ChunkData->Type, "PALT")){ /**** ** PALT parsing */ IFFPalette * Palette = ChunkData->FormattedData; uint8_t * Data = Palette->Data; unsigned i, j; fprintf(hFile, "\n"); fprintf(hFile, ""); for(i=0; i<16; i++) fprintf(hFile, "", i); fprintf(hFile, "\n"); for(i=0; i<16; i++){ fprintf(hFile, "", i); for(j=0; j<16; j++){ if(i*16 + j < Palette->ColorCount){ unsigned red = *(Data++); unsigned green = *(Data++); unsigned blue = *(Data++); fprintf(hFile, "\n", red, green, blue, i*16 + j, red, green, blue); }else fprintf(hFile, "\n"); } fprintf(hFile, "\n"); } fprintf(hFile, "
%X
%X
\n"); }else if(!strcmp(ChunkData->Type, "SPR#") || !strcmp(ChunkData->Type, "SPR2")){ /**** ** SPR# and SPR2 parsing */ int spr1 = !strcmp(ChunkData->Type, "SPR#"); IFFSpriteList * SpriteList = ChunkData->FormattedData; IFFChunk * Palette = NULL; IFFPalette BlankPalette; IFFPalette * PaletteData; unsigned i; fprintf(hFile, "\n"); fprintf(hFile, "\n", SpriteList->Version); fprintf(hFile, "\n", SpriteList->PaletteID); fprintf(hFile, "
Version:%u
Palette ID:%.4X
\n"); if(SpriteList->PaletteID < 0xFFFF){ Palette = iff_find_chunk(&IFFFileInfo, "PALT", SpriteList->PaletteID); if(!Palette || !Palette->FormattedData) Palette = iff_find_chunk(&IFFFileInfo, "PALT", ChunkData->ChunkID); if(!Palette || !Palette->FormattedData) Palette = iff_find_chunk(&IFFFileInfo, "PALT", -1); } if(!Palette || !Palette->FormattedData){ memset(&BlankPalette, 0, sizeof(IFFPalette)); BlankPalette.Version = 1; BlankPalette.ColorCount = 256; PaletteData = &BlankPalette; }else PaletteData = Palette->FormattedData; fprintf(hFile, "\n"); fprintf(hFile, ""); if(!spr1) fprintf(hFile, ""); fprintf(hFile, "\n"); for(i=0; iSpriteCount; i++){ IFFSprite * Sprite = &SpriteList->Sprites[i]; char filename[32]; sprintf(filename, "%s%s_%u_%.4x_%u.png", OutDir, spr1 ? "spr1" : "spr2", c+1, ChunkData->ChunkID, i+1); fprintf(hFile, "IndexData && iff_depalette(Sprite, PaletteData)){ WritePNG(filename, NULL, 0, Sprite, NULL, NULL); fprintf(hFile, ">\"\"", spr1 ? "spr1" : "spr2", c+1, ChunkData->ChunkID, i+1, Sprite->Width, Sprite->Height); if(!spr1){ sprintf(filename, "%sspr2_%u_%.4x_%u_z.png", OutDir, c+1, ChunkData->ChunkID, i+1); if(Sprite->ZBuffer){ WritePNG(filename, NULL, 1, Sprite, NULL, NULL); fprintf(hFile, "\n"); } fprintf(hFile, "
SpriteZ-Buffer
%u\"\"", c+1, ChunkData->ChunkID, i+1, Sprite->Width, Sprite->Height); }else fprintf(hFile, "None provided"); } }else fprintf(hFile, Sprite->InvalidDimensions ? "%sBlank sprite" : "%sThis sprite cannot be displayed.", !spr1 ? " colspan=\"2\">" : ">"); fprintf(hFile, "
\n"); }else if(!strcmp(ChunkData->Type, "DGRP")){ /**** ** DGRP parsing */ IFFDrawGroup * Group = ChunkData->FormattedData; IFFDrawAngle * Angle; IFFSpriteInfo * Sprite; unsigned i,j; const char * Zooms[] = {"Far", "Middle", "Close"}; fprintf(hFile, "\n"); fprintf(hFile, "\n", Group->Version); fprintf(hFile, "
Version:%u
\n"); fprintf(hFile, "
\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); for(i=0, Angle=Group->DrawAngles; i<12; i++, Angle++){ const char * Direction = (Angle->Direction == IFFDIRECTION_NORTHEAST) ? "North east" : (Angle->Direction == IFFDIRECTION_SOUTHEAST) ? "South east" : (Angle->Direction == IFFDIRECTION_NORTHWEST) ? "North west" : "South west"; if(Angle->SpriteCount){ fprintf(hFile, "" "" "\n", 1+Angle->SpriteCount, Direction, 1+Angle->SpriteCount, Zooms[Angle->Zoom-1]); for(j=0, Sprite = Angle->SpriteInfo; jSpriteCount; j++, Sprite++) fprintf(hFile, "" "", j+1, Sprite->Type, Sprite->ChunkID, Sprite->SpriteIndex, Sprite->Flags, Sprite->SpriteX, Sprite->SpriteY, Sprite->ObjectX, Sprite->ObjectY, Sprite->ObjectZ); }else{ fprintf(hFile, "", Direction, Zooms[Angle->Zoom-1]); } } fprintf(hFile, "
DirectionZoomSprite
%s%s#TypeChunk IDSprite indexFlagsSprite offsetObject offset
%u%u%.4X%u%u(%+d,%+d)(%+g,%+g,%+g)
%s%sNone specified
\n"); }else if(!strcmp(ChunkData->Type, "BHAV")){ /**** ** BHAV parsing */ IFFBehavior * Behavior = ChunkData->FormattedData; IFFInstruction * Instruction; fprintf(hFile, "\n"); fprintf(hFile, "\n", Behavior->Version); fprintf(hFile, "\n", Behavior->Type); fprintf(hFile, "\n", Behavior->ArgumentCount); fprintf(hFile, "\n", Behavior->LocalCount); fprintf(hFile, "\n", Behavior->Flags); fprintf(hFile, "
Version:%u
Type:%u
Arguments:%u
Locals:%u
Flags:%.4X
\n"); if(Behavior->InstructionCount > 0){ unsigned i; fprintf(hFile, "
\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); for(i=0, Instruction = Behavior->Instructions; iInstructionCount; i++, Instruction++) fprintf(hFile, "" "\n", i, Instruction->Opcode, Instruction->TDest, Instruction->FDest, Instruction->Operands[0], Instruction->Operands[1], Instruction->Operands[2], Instruction->Operands[3], Instruction->Operands[4], Instruction->Operands[5], Instruction->Operands[6], Instruction->Operands[7]); fprintf(hFile, "
OpcodeT-DestF-DestOperand data
%u%.4X%u%u%.2X %.2X %.2X %.2X %.2X %.2X %.2X %.2X
\n"); } }else if(!strcmp(ChunkData->Type, "OBJf")){ /**** ** OBJf parsing */ IFFFunctionTable * Table = ChunkData->FormattedData; fprintf(hFile, "\n"); fprintf(hFile, "\n", Table->Version); fprintf(hFile, "
Version:%u
\n"); if(Table->FunctionCount > 0){ unsigned i; fprintf(hFile, "
\n"); fprintf(hFile, "\n"); fprintf(hFile, "\n"); for(i=0; iFunctionCount; i++) fprintf(hFile, "\n", i+1, Table->Functions[i].ConditionID, Table->Functions[i].ActionID); fprintf(hFile, "
Condition functionAction function
%u%.4X%.4X
\n"); } }else{ fprintf(hFile, "The contents of this chunk cannot be shown on this page.\n"); } fprintf(hFile, "
\n\n"); } iff_delete(&IFFFileInfo); fprintf(hFile, "
This page was generated by the use of iff2html.\n"); fprintf(hFile, "The content of this page may be subject to copyright by the author(s) of the original iff file.
\n"); fprintf(hFile, "\n"); fprintf(hFile, ""); fclose(hFile); printf("Wrote contents to '%s'.\n", OutFile); return 0; }